All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v4 0/8] Add device STM32L4x5 RCC
@ 2024-01-30 16:06 Arnaud Minier
  2024-01-30 16:06 ` [PATCH v4 1/8] Implement STM32L4x5_RCC skeleton Arnaud Minier
                   ` (7 more replies)
  0 siblings, 8 replies; 14+ messages in thread
From: Arnaud Minier @ 2024-01-30 16:06 UTC (permalink / raw)
  To: qemu-devel
  Cc: Laurent Vivier, Alistair Francis, Inès Varhol,
	Peter Maydell, Paolo Bonzini, Thomas Huth, qemu-arm,
	Arnaud Minier

This patch adds the STM32L4x5 RCC (Reset and Clock Control) device and is part
of a series implementing the STM32L4x5 with a few peripherals.

Due to the high number of lines, I tried to split the patch into several independent commits.
Each commit compiles on its own but I had to add temporary workarounds in intermediary commits to allow them to compile even if some functions are not used. However, they have been removed once the functions were used. Tell me if this is ok or if I should remove them.

Also, the tests are not very exhaustive for the moment. I have not found a way to test the clocks' frequency from the qtests, which limits severely the exhaustiveness of the tests.

Thanks to Philippe Mathieu-Daudé and Luc Michel for guiding me toward the hw/misc/bcm2835_cprman.c implementation and answering my questions about clock emulation in qemu !

Changes from v1 to v2:
- Removed a mention in the tests
- Added an early return to prevent a clang compilation error in rcc_update_pllsaixcfgr()

Changes from v2 to v3:
- Changed the timeout method used in the tests
- Added a real value for ICSR register
- Replaced some TODOs with correct error handling
- Added a commit that implements correct write protections for the CR register

Changes from v3 to v4:
- Rebased on top of current master
- Implemented reset functions for the multiplexers and the PLLs
- Added explanatory messages to every commit
- Addded logs for unimplemented registers
- Completed the VMState for the multiplexers and the PLLs

Arnaud Minier (8):
  Implement STM32L4x5_RCC skeleton
  Add an internal clock multiplexer object
  Add an internal PLL Clock object
  Add initialization information for PLLs and clock multiplexers
  RCC: Handle Register Updates
  Add write protections to CR register
  STM32L4x5: Use the RCC Sysclk
  Add tests for the STM32L4x5_RCC

 MAINTAINERS                               |    5 +-
 docs/system/arm/b-l475e-iot01a.rst        |    2 +-
 hw/arm/Kconfig                            |    1 +
 hw/arm/b-l475e-iot01a.c                   |   10 +-
 hw/arm/stm32l4x5_soc.c                    |   45 +-
 hw/misc/Kconfig                           |    3 +
 hw/misc/meson.build                       |    1 +
 hw/misc/stm32l4x5_rcc.c                   | 1415 +++++++++++++++++++++
 hw/misc/trace-events                      |   14 +
 include/hw/arm/stm32l4x5_soc.h            |    5 +-
 include/hw/misc/stm32l4x5_rcc.h           |  239 ++++
 include/hw/misc/stm32l4x5_rcc_internals.h | 1042 +++++++++++++++
 tests/qtest/meson.build                   |    3 +-
 tests/qtest/stm32l4x5_rcc-test.c          |  207 +++
 14 files changed, 2947 insertions(+), 45 deletions(-)
 create mode 100644 hw/misc/stm32l4x5_rcc.c
 create mode 100644 include/hw/misc/stm32l4x5_rcc.h
 create mode 100644 include/hw/misc/stm32l4x5_rcc_internals.h
 create mode 100644 tests/qtest/stm32l4x5_rcc-test.c

-- 
2.34.1



^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v4 1/8] Implement STM32L4x5_RCC skeleton
  2024-01-30 16:06 [PATCH v4 0/8] Add device STM32L4x5 RCC Arnaud Minier
@ 2024-01-30 16:06 ` Arnaud Minier
  2024-02-01  0:09   ` Alistair Francis
  2024-01-30 16:06 ` [PATCH v4 2/8] Add an internal clock multiplexer object Arnaud Minier
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 14+ messages in thread
From: Arnaud Minier @ 2024-01-30 16:06 UTC (permalink / raw)
  To: qemu-devel
  Cc: Laurent Vivier, Alistair Francis, Inès Varhol,
	Peter Maydell, Paolo Bonzini, Thomas Huth, qemu-arm,
	Arnaud Minier

Add the necessary files to add a simple RCC implementation with just
reads from and writes to registers. Also instanciate the RCC in the
STM32L4x5_SoC. It is needed for accurate emulation of all the SoC
clocks and timers.

Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
---
 MAINTAINERS                               |   5 +-
 docs/system/arm/b-l475e-iot01a.rst        |   2 +-
 hw/arm/Kconfig                            |   1 +
 hw/arm/stm32l4x5_soc.c                    |  12 +-
 hw/misc/Kconfig                           |   3 +
 hw/misc/meson.build                       |   1 +
 hw/misc/stm32l4x5_rcc.c                   | 433 ++++++++++++++++++++++
 hw/misc/trace-events                      |   4 +
 include/hw/arm/stm32l4x5_soc.h            |   2 +
 include/hw/misc/stm32l4x5_rcc.h           |  80 ++++
 include/hw/misc/stm32l4x5_rcc_internals.h | 286 ++++++++++++++
 11 files changed, 826 insertions(+), 3 deletions(-)
 create mode 100644 hw/misc/stm32l4x5_rcc.c
 create mode 100644 include/hw/misc/stm32l4x5_rcc.h
 create mode 100644 include/hw/misc/stm32l4x5_rcc_internals.h

diff --git a/MAINTAINERS b/MAINTAINERS
index dfaca8323e..50ab2982bb 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1128,7 +1128,10 @@ M: Inès Varhol <ines.varhol@telecom-paris.fr>
 L: qemu-arm@nongnu.org
 S: Maintained
 F: hw/arm/stm32l4x5_soc.c
-F: include/hw/arm/stm32l4x5_soc.h
+F: hw/misc/stm32l4x5_exti.c
+F: hw/misc/stm32l4x5_syscfg.c
+F: hw/misc/stm32l4x5_rcc.c
+F: include/hw/*/stm32l4x5_*.h
 
 B-L475E-IOT01A IoT Node
 M: Arnaud Minier <arnaud.minier@telecom-paris.fr>
diff --git a/docs/system/arm/b-l475e-iot01a.rst b/docs/system/arm/b-l475e-iot01a.rst
index 1a021b306a..b857a56ca4 100644
--- a/docs/system/arm/b-l475e-iot01a.rst
+++ b/docs/system/arm/b-l475e-iot01a.rst
@@ -17,13 +17,13 @@ Currently B-L475E-IOT01A machine's only supports the following devices:
 - Cortex-M4F based STM32L4x5 SoC
 - STM32L4x5 EXTI (Extended interrupts and events controller)
 - STM32L4x5 SYSCFG (System configuration controller)
+- STM32L4x5 RCC (Reset and clock control)
 
 Missing devices
 """""""""""""""
 
 The B-L475E-IOT01A does *not* support the following devices:
 
-- Reset and clock control (RCC)
 - Serial ports (UART)
 - General-purpose I/Os (GPIO)
 - Analog to Digital Converter (ADC)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index f927878152..92b72d56dc 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -465,6 +465,7 @@ config STM32L4X5_SOC
     select OR_IRQ
     select STM32L4X5_SYSCFG
     select STM32L4X5_EXTI
+    select STM32L4X5_RCC
 
 config XLNX_ZYNQMP_ARM
     bool
diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index f470ff74ec..d5c04b446d 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -75,6 +75,8 @@ static const int exti_irq[NUM_EXTI_IRQ] = {
     1,                      /* PVM4 wakeup             */
     78                      /* LCD wakeup, Direct      */
 };
+#define RCC_BASE_ADDRESS 0x40021000
+#define RCC_IRQ 5
 
 static void stm32l4x5_soc_initfn(Object *obj)
 {
@@ -82,6 +84,7 @@ static void stm32l4x5_soc_initfn(Object *obj)
 
     object_initialize_child(obj, "exti", &s->exti, TYPE_STM32L4X5_EXTI);
     object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_STM32L4X5_SYSCFG);
+    object_initialize_child(obj, "rcc", &s->rcc, TYPE_STM32L4X5_RCC);
 
     s->sysclk = qdev_init_clock_in(DEVICE(s), "sysclk", NULL, NULL, 0);
     s->refclk = qdev_init_clock_in(DEVICE(s), "refclk", NULL, NULL, 0);
@@ -184,6 +187,14 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
                               qdev_get_gpio_in(DEVICE(&s->exti), i));
     }
 
+    /* RCC device */
+    busdev = SYS_BUS_DEVICE(&s->rcc);
+    if (!sysbus_realize(busdev, errp)) {
+        return;
+    }
+    sysbus_mmio_map(busdev, 0, RCC_BASE_ADDRESS);
+    sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(armv7m, RCC_IRQ));
+
     /* APB1 BUS */
     create_unimplemented_device("TIM2",      0x40000000, 0x400);
     create_unimplemented_device("TIM3",      0x40000400, 0x400);
@@ -246,7 +257,6 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
     create_unimplemented_device("DMA1",      0x40020000, 0x400);
     create_unimplemented_device("DMA2",      0x40020400, 0x400);
     /* RESERVED:    0x40020800, 0x800 */
-    create_unimplemented_device("RCC",       0x40021000, 0x400);
     /* RESERVED:    0x40021400, 0xC00 */
     create_unimplemented_device("FLASH",     0x40022000, 0x400);
     /* RESERVED:    0x40022400, 0xC00 */
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 4fc6b29b43..727386fa4b 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -93,6 +93,9 @@ config STM32L4X5_EXTI
 config STM32L4X5_SYSCFG
     bool
 
+config STM32L4X5_RCC
+    bool
+
 config MIPS_ITU
     bool
 
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index e4ef1da5a5..afd344b374 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -112,6 +112,7 @@ system_ss.add(when: 'CONFIG_STM32F4XX_SYSCFG', if_true: files('stm32f4xx_syscfg.
 system_ss.add(when: 'CONFIG_STM32F4XX_EXTI', if_true: files('stm32f4xx_exti.c'))
 system_ss.add(when: 'CONFIG_STM32L4X5_EXTI', if_true: files('stm32l4x5_exti.c'))
 system_ss.add(when: 'CONFIG_STM32L4X5_SYSCFG', if_true: files('stm32l4x5_syscfg.c'))
+system_ss.add(when: 'CONFIG_STM32L4X5_RCC', if_true: files('stm32l4x5_rcc.c'))
 system_ss.add(when: 'CONFIG_MPS2_FPGAIO', if_true: files('mps2-fpgaio.c'))
 system_ss.add(when: 'CONFIG_MPS2_SCC', if_true: files('mps2-scc.c'))
 
diff --git a/hw/misc/stm32l4x5_rcc.c b/hw/misc/stm32l4x5_rcc.c
new file mode 100644
index 0000000000..38ca8aad7d
--- /dev/null
+++ b/hw/misc/stm32l4x5_rcc.c
@@ -0,0 +1,433 @@
+/*
+ * STM32L4X5 RCC (Reset and clock control)
+ *
+ * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr>
+ * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.fr>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * The reference used is the STMicroElectronics RM0351 Reference manual
+ * for STM32L4x5 and STM32L4x6 advanced Arm ® -based 32-bit MCUs.
+ *
+ * Inspired by the BCM2835 CPRMAN clock manager implementation by Luc Michel.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "qapi/error.h"
+#include "migration/vmstate.h"
+#include "hw/misc/stm32l4x5_rcc.h"
+#include "hw/misc/stm32l4x5_rcc_internals.h"
+#include "hw/clock.h"
+#include "hw/irq.h"
+#include "hw/qdev-clock.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-properties-system.h"
+#include "trace.h"
+
+#define HSE_DEFAULT_FRQ 48000000ULL
+#define HSI_FRQ 16000000ULL
+#define MSI_DEFAULT_FRQ 4000000ULL
+#define LSE_FRQ 32768ULL
+#define LSI_FRQ 32000ULL
+
+static void rcc_update_irq(Stm32l4x5RccState *s)
+{
+    if (s->cifr & CIFR_IRQ_MASK) {
+        qemu_irq_raise(s->irq);
+    } else {
+        qemu_irq_lower(s->irq);
+    }
+}
+
+static void stm32l4x5_rcc_reset_hold(Object *obj)
+{
+    Stm32l4x5RccState *s = STM32L4X5_RCC(obj);
+    s->cr = 0x00000063;
+    /*
+     * Factory-programmed calibration data
+     * From the reference manual: 0x10XX 00XX
+     * Value taken from a real card.
+     */
+    s->icscr = 0x106E0082;
+    s->cfgr = 0x0;
+    s->pllcfgr = 0x00001000;
+    s->pllsai1cfgr = 0x00001000;
+    s->pllsai2cfgr = 0x00001000;
+    s->cier = 0x0;
+    s->cifr = 0x0;
+    s->ahb1rstr = 0x0;
+    s->ahb2rstr = 0x0;
+    s->ahb3rstr = 0x0;
+    s->apb1rstr1 = 0x0;
+    s->apb1rstr2 = 0x0;
+    s->apb2rstr = 0x0;
+    s->ahb1enr = 0x00000100;
+    s->ahb2enr = 0x0;
+    s->ahb3enr = 0x0;
+    s->apb1enr1 = 0x0;
+    s->apb1enr2 = 0x0;
+    s->apb2enr = 0x0;
+    s->ahb1smenr = 0x00011303;
+    s->ahb2smenr = 0x000532FF;
+    s->ahb3smenr =  0x00000101;
+    s->apb1smenr1 = 0xF2FECA3F;
+    s->apb1smenr2 = 0x00000025;
+    s->apb2smenr = 0x01677C01;
+    s->ccipr = 0x0;
+    s->bdcr = 0x0;
+    s->csr = 0x0C000600;
+}
+
+static uint64_t stm32l4x5_rcc_read(void *opaque, hwaddr addr,
+                                     unsigned int size)
+{
+    Stm32l4x5RccState *s = opaque;
+    uint64_t retvalue = 0;
+
+    switch (addr) {
+    case A_CR:
+        retvalue = s->cr;
+        break;
+    case A_ICSCR:
+        retvalue = s->icscr;
+        break;
+    case A_CFGR:
+        retvalue = s->cfgr;
+        break;
+    case A_PLLCFGR:
+        retvalue = s->pllcfgr;
+        break;
+    case A_PLLSAI1CFGR:
+        retvalue = s->pllsai1cfgr;
+        break;
+    case A_PLLSAI2CFGR:
+        retvalue = s->pllsai2cfgr;
+        break;
+    case A_CIER:
+        retvalue = s->cier;
+        break;
+    case A_CIFR:
+        retvalue = s->cifr;
+        break;
+    case A_CICR:
+        /* CICR is write only, return the reset value = 0 */
+        break;
+    case A_AHB1RSTR:
+        retvalue = s->ahb1rstr;
+        break;
+    case A_AHB2RSTR:
+        retvalue = s->ahb2rstr;
+        break;
+    case A_AHB3RSTR:
+        retvalue = s->ahb3rstr;
+        break;
+    case A_APB1RSTR1:
+        retvalue = s->apb1rstr1;
+        break;
+    case A_APB1RSTR2:
+        retvalue = s->apb1rstr2;
+        break;
+    case A_APB2RSTR:
+        retvalue = s->apb2rstr;
+        break;
+    case A_AHB1ENR:
+        retvalue = s->ahb1enr;
+        break;
+    case A_AHB2ENR:
+        retvalue = s->ahb2enr;
+        break;
+    case A_AHB3ENR:
+        retvalue = s->ahb3enr;
+        break;
+    case A_APB1ENR1:
+        retvalue = s->apb1enr1;
+        break;
+    case A_APB1ENR2:
+        retvalue = s->apb1enr2;
+        break;
+    case A_APB2ENR:
+        retvalue = s->apb2enr;
+        break;
+    case A_AHB1SMENR:
+        retvalue = s->ahb1smenr;
+        break;
+    case A_AHB2SMENR:
+        retvalue = s->ahb2smenr;
+        break;
+    case A_AHB3SMENR:
+        retvalue = s->ahb3smenr;
+        break;
+    case A_APB1SMENR1:
+        retvalue = s->apb1smenr1;
+        break;
+    case A_APB1SMENR2:
+        retvalue = s->apb1smenr2;
+        break;
+    case A_APB2SMENR:
+        retvalue = s->apb2smenr;
+        break;
+    case A_CCIPR:
+        retvalue = s->ccipr;
+        break;
+    case A_BDCR:
+        retvalue = s->bdcr;
+        break;
+    case A_CSR:
+        retvalue = s->csr;
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
+        break;
+    }
+
+    trace_stm32l4x5_rcc_read(addr, retvalue);
+
+    return retvalue;
+}
+
+static void stm32l4x5_rcc_write(void *opaque, hwaddr addr,
+                                  uint64_t val64, unsigned int size)
+{
+    Stm32l4x5RccState *s = opaque;
+    const uint32_t value = val64;
+
+    trace_stm32l4x5_rcc_write(addr, value);
+
+    switch (addr) {
+    case A_CR:
+        s->cr = (s->cr & CR_READ_SET_MASK) |
+                (value & (CR_READ_SET_MASK | ~CR_READ_ONLY_MASK));
+        break;
+    case A_ICSCR:
+        s->icscr = value & ~ICSCR_READ_ONLY_MASK;
+        break;
+    case A_CFGR:
+        s->cfgr = value & ~CFGR_READ_ONLY_MASK;
+        break;
+    case A_PLLCFGR:
+        s->pllcfgr = value;
+        break;
+    case A_PLLSAI1CFGR:
+        s->pllsai1cfgr = value;
+        break;
+    case A_PLLSAI2CFGR:
+        s->pllsai2cfgr = value;
+        break;
+    case A_CIER:
+        s->cier = value;
+        break;
+    case A_CIFR:
+        qemu_log_mask(LOG_GUEST_ERROR,
+            "%s: Write attempt into read-only register (CIFR) 0x%"PRIx32"\n",
+            __func__, value);
+        break;
+    case A_CICR:
+        /* Clear interrupt flags by writing a 1 to the CICR register */
+        s->cifr &= ~value;
+        rcc_update_irq(s);
+        break;
+    /* Reset behaviors are not implemented */
+    case A_AHB1RSTR:
+        s->ahb1rstr = value;
+        break;
+    case A_AHB2RSTR:
+        s->ahb2rstr = value;
+        break;
+    case A_AHB3RSTR:
+        s->ahb3rstr = value;
+        break;
+    case A_APB1RSTR1:
+        s->apb1rstr1 = value;
+        break;
+    case A_APB1RSTR2:
+        s->apb1rstr2 = value;
+        break;
+    case A_APB2RSTR:
+        s->apb2rstr = value;
+        break;
+    case A_AHB1ENR:
+        s->ahb1enr = value;
+        break;
+    case A_AHB2ENR:
+        s->ahb2enr = value;
+        break;
+    case A_AHB3ENR:
+        s->ahb3enr = value;
+        break;
+    case A_APB1ENR1:
+        s->apb1enr1 = value;
+        break;
+    case A_APB1ENR2:
+        s->apb1enr2 = value;
+        break;
+    case A_APB2ENR:
+        s->apb2enr = (s->apb2enr & APB2ENR_READ_SET_MASK) | value;
+        break;
+    /* Behaviors for Sleep and Stop modes are not implemented */
+    case A_AHB1SMENR:
+        s->ahb1smenr = value;
+        break;
+    case A_AHB2SMENR:
+        s->ahb2smenr = value;
+        break;
+    case A_AHB3SMENR:
+        s->ahb3smenr = value;
+        break;
+    case A_APB1SMENR1:
+        s->apb1smenr1 = value;
+        break;
+    case A_APB1SMENR2:
+        s->apb1smenr2 = value;
+        break;
+    case A_APB2SMENR:
+        s->apb2smenr = value;
+        break;
+    case A_CCIPR:
+        s->ccipr = value;
+        break;
+    case A_BDCR:
+        s->bdcr = value & ~BDCR_READ_ONLY_MASK;
+        break;
+    case A_CSR:
+        s->csr = value & ~CSR_READ_ONLY_MASK;
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
+    }
+}
+
+static const MemoryRegionOps stm32l4x5_rcc_ops = {
+    .read = stm32l4x5_rcc_read,
+    .write = stm32l4x5_rcc_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .max_access_size = 4,
+        .unaligned = false
+    },
+};
+
+static const ClockPortInitArray stm32l4x5_rcc_clocks = {
+    QDEV_CLOCK_IN(Stm32l4x5RccState, hsi16_rc, NULL, 0),
+    QDEV_CLOCK_IN(Stm32l4x5RccState, msi_rc, NULL, 0),
+    QDEV_CLOCK_IN(Stm32l4x5RccState, hse, NULL, 0),
+    QDEV_CLOCK_IN(Stm32l4x5RccState, lsi_rc, NULL, 0),
+    QDEV_CLOCK_IN(Stm32l4x5RccState, lse_crystal, NULL, 0),
+    QDEV_CLOCK_IN(Stm32l4x5RccState, sai1_extclk, NULL, 0),
+    QDEV_CLOCK_IN(Stm32l4x5RccState, sai2_extclk, NULL, 0),
+    QDEV_CLOCK_END
+};
+
+
+static void stm32l4x5_rcc_init(Object *obj)
+{
+    Stm32l4x5RccState *s = STM32L4X5_RCC(obj);
+
+    sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+
+    memory_region_init_io(&s->mmio, obj, &stm32l4x5_rcc_ops, s,
+                          TYPE_STM32L4X5_RCC, 0x400);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+
+    qdev_init_clocks(DEVICE(s), stm32l4x5_rcc_clocks);
+
+    s->gnd = clock_new(obj, "gnd");
+}
+
+static const VMStateDescription vmstate_stm32l4x5_rcc = {
+    .name = TYPE_STM32L4X5_RCC,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(cr, Stm32l4x5RccState),
+        VMSTATE_UINT32(icscr, Stm32l4x5RccState),
+        VMSTATE_UINT32(cfgr, Stm32l4x5RccState),
+        VMSTATE_UINT32(pllcfgr, Stm32l4x5RccState),
+        VMSTATE_UINT32(pllsai1cfgr, Stm32l4x5RccState),
+        VMSTATE_UINT32(pllsai2cfgr, Stm32l4x5RccState),
+        VMSTATE_UINT32(cier, Stm32l4x5RccState),
+        VMSTATE_UINT32(cifr, Stm32l4x5RccState),
+        VMSTATE_UINT32(ahb1rstr, Stm32l4x5RccState),
+        VMSTATE_UINT32(ahb2rstr, Stm32l4x5RccState),
+        VMSTATE_UINT32(ahb3rstr, Stm32l4x5RccState),
+        VMSTATE_UINT32(apb1rstr1, Stm32l4x5RccState),
+        VMSTATE_UINT32(apb1rstr2, Stm32l4x5RccState),
+        VMSTATE_UINT32(apb2rstr, Stm32l4x5RccState),
+        VMSTATE_UINT32(ahb1enr, Stm32l4x5RccState),
+        VMSTATE_UINT32(ahb2enr, Stm32l4x5RccState),
+        VMSTATE_UINT32(ahb3enr, Stm32l4x5RccState),
+        VMSTATE_UINT32(apb1enr1, Stm32l4x5RccState),
+        VMSTATE_UINT32(apb1enr2, Stm32l4x5RccState),
+        VMSTATE_UINT32(apb2enr, Stm32l4x5RccState),
+        VMSTATE_UINT32(ahb1smenr, Stm32l4x5RccState),
+        VMSTATE_UINT32(ahb2smenr, Stm32l4x5RccState),
+        VMSTATE_UINT32(ahb3smenr, Stm32l4x5RccState),
+        VMSTATE_UINT32(apb1smenr1, Stm32l4x5RccState),
+        VMSTATE_UINT32(apb1smenr2, Stm32l4x5RccState),
+        VMSTATE_UINT32(apb2smenr, Stm32l4x5RccState),
+        VMSTATE_UINT32(ccipr, Stm32l4x5RccState),
+        VMSTATE_UINT32(bdcr, Stm32l4x5RccState),
+        VMSTATE_UINT32(csr, Stm32l4x5RccState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+
+static void stm32l4x5_rcc_realize(DeviceState *dev, Error **errp)
+{
+    Stm32l4x5RccState *s = STM32L4X5_RCC(dev);
+
+    if (s->hse_frequency <  4000000ULL ||
+        s->hse_frequency > 48000000ULL) {
+            error_setg(errp,
+                "HSE frequency is outside of the allowed [4-48]Mhz range: %" PRIx64 "",
+                s->hse_frequency);
+            return;
+        }
+
+    clock_update_hz(s->msi_rc, MSI_DEFAULT_FRQ);
+    clock_update_hz(s->sai1_extclk, s->sai1_extclk_frequency);
+    clock_update_hz(s->sai2_extclk, s->sai2_extclk_frequency);
+    clock_update(s->gnd, 0);
+}
+
+static Property stm32l4x5_rcc_properties[] = {
+    DEFINE_PROP_UINT64("hse_frequency", Stm32l4x5RccState,
+        hse_frequency, HSE_DEFAULT_FRQ),
+    DEFINE_PROP_UINT64("sai1_extclk_frequency", Stm32l4x5RccState,
+        sai1_extclk_frequency, 0),
+    DEFINE_PROP_UINT64("sai2_extclk_frequency", Stm32l4x5RccState,
+        sai2_extclk_frequency, 0),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void stm32l4x5_rcc_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+
+    rc->phases.hold = stm32l4x5_rcc_reset_hold;
+    device_class_set_props(dc, stm32l4x5_rcc_properties);
+    dc->realize = stm32l4x5_rcc_realize;
+    dc->vmsd = &vmstate_stm32l4x5_rcc;
+}
+
+static const TypeInfo stm32l4x5_rcc_types[] = {
+    {
+        .name           = TYPE_STM32L4X5_RCC,
+        .parent         = TYPE_SYS_BUS_DEVICE,
+        .instance_size  = sizeof(Stm32l4x5RccState),
+        .instance_init  = stm32l4x5_rcc_init,
+        .class_init     = stm32l4x5_rcc_class_init,
+    }
+};
+
+DEFINE_TYPES(stm32l4x5_rcc_types)
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index 5f5bc92222..62a7599353 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -174,6 +174,10 @@ stm32l4x5_exti_set_irq(int irq, int level) "Set EXTI: %d to %d"
 stm32l4x5_exti_read(uint64_t addr, uint64_t data) "reg read: addr: 0x%" PRIx64 " val: 0x%" PRIx64 ""
 stm32l4x5_exti_write(uint64_t addr, uint64_t data) "reg write: addr: 0x%" PRIx64 " val: 0x%" PRIx64 ""
 
+# stm32l4x5_rcc.c
+stm32l4x5_rcc_read(uint64_t addr, uint32_t data) "RCC: Read <0x%" PRIx64 "> -> 0x%" PRIx32 ""
+stm32l4x5_rcc_write(uint64_t addr, uint32_t data) "RCC: Write <0x%" PRIx64 "> <- 0x%" PRIx32 ""
+
 # tz-mpc.c
 tz_mpc_reg_read(uint32_t offset, uint64_t data, unsigned size) "TZ MPC regs read: offset 0x%x data 0x%" PRIx64 " size %u"
 tz_mpc_reg_write(uint32_t offset, uint64_t data, unsigned size) "TZ MPC regs write: offset 0x%x data 0x%" PRIx64 " size %u"
diff --git a/include/hw/arm/stm32l4x5_soc.h b/include/hw/arm/stm32l4x5_soc.h
index baf70410b5..e480fcc976 100644
--- a/include/hw/arm/stm32l4x5_soc.h
+++ b/include/hw/arm/stm32l4x5_soc.h
@@ -28,6 +28,7 @@
 #include "hw/arm/armv7m.h"
 #include "hw/misc/stm32l4x5_syscfg.h"
 #include "hw/misc/stm32l4x5_exti.h"
+#include "hw/misc/stm32l4x5_rcc.h"
 #include "qom/object.h"
 
 #define TYPE_STM32L4X5_SOC "stm32l4x5-soc"
@@ -43,6 +44,7 @@ struct Stm32l4x5SocState {
 
     Stm32l4x5ExtiState exti;
     Stm32l4x5SyscfgState syscfg;
+    Stm32l4x5RccState rcc;
 
     MemoryRegion sram1;
     MemoryRegion sram2;
diff --git a/include/hw/misc/stm32l4x5_rcc.h b/include/hw/misc/stm32l4x5_rcc.h
new file mode 100644
index 0000000000..5157e96635
--- /dev/null
+++ b/include/hw/misc/stm32l4x5_rcc.h
@@ -0,0 +1,80 @@
+/*
+ * STM32L4X5 RCC (Reset and clock control)
+ *
+ * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr>
+ * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.fr>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * The reference used is the STMicroElectronics RM0351 Reference manual
+ * for STM32L4x5 and STM32L4x6 advanced Arm ® -based 32-bit MCUs.
+ *
+ * Inspired by the BCM2835 CPRMAN clock manager by Luc Michel.
+ */
+
+#ifndef HW_STM32L4X5_RCC_H
+#define HW_STM32L4X5_RCC_H
+
+#include "hw/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_STM32L4X5_RCC "stm32l4x5-rcc"
+OBJECT_DECLARE_SIMPLE_TYPE(Stm32l4x5RccState, STM32L4X5_RCC)
+
+/* In the Stm32l4x5 clock tree, mux have at most 7 sources */
+#define RCC_NUM_CLOCK_MUX_SRC 7
+struct Stm32l4x5RccState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion mmio;
+
+    uint32_t cr;
+    uint32_t icscr;
+    uint32_t cfgr;
+    uint32_t pllcfgr;
+    uint32_t pllsai1cfgr;
+    uint32_t pllsai2cfgr;
+    uint32_t cier;
+    uint32_t cifr;
+    uint32_t ahb1rstr;
+    uint32_t ahb2rstr;
+    uint32_t ahb3rstr;
+    uint32_t apb1rstr1;
+    uint32_t apb1rstr2;
+    uint32_t apb2rstr;
+    uint32_t ahb1enr;
+    uint32_t ahb2enr;
+    uint32_t ahb3enr;
+    uint32_t apb1enr1;
+    uint32_t apb1enr2;
+    uint32_t apb2enr;
+    uint32_t ahb1smenr;
+    uint32_t ahb2smenr;
+    uint32_t ahb3smenr;
+    uint32_t apb1smenr1;
+    uint32_t apb1smenr2;
+    uint32_t apb2smenr;
+    uint32_t ccipr;
+    uint32_t bdcr;
+    uint32_t csr;
+
+    /* Clock sources */
+    Clock *gnd;
+    Clock *hsi16_rc;
+    Clock *msi_rc;
+    Clock *hse;
+    Clock *lsi_rc;
+    Clock *lse_crystal;
+    Clock *sai1_extclk;
+    Clock *sai2_extclk;
+
+    qemu_irq irq;
+    uint64_t hse_frequency;
+    uint64_t sai1_extclk_frequency;
+    uint64_t sai2_extclk_frequency;
+};
+
+#endif /* HW_STM32L4X5_RCC_H */
diff --git a/include/hw/misc/stm32l4x5_rcc_internals.h b/include/hw/misc/stm32l4x5_rcc_internals.h
new file mode 100644
index 0000000000..331ea30db5
--- /dev/null
+++ b/include/hw/misc/stm32l4x5_rcc_internals.h
@@ -0,0 +1,286 @@
+/*
+ * STM32L4X5 RCC (Reset and clock control)
+ *
+ * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr>
+ * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.fr>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * The reference used is the STMicroElectronics RM0351 Reference manual
+ * for STM32L4x5 and STM32L4x6 advanced Arm ® -based 32-bit MCUs.
+ *
+ * Inspired by the BCM2835 CPRMAN clock manager implementation by Luc Michel.
+ */
+
+#ifndef HW_STM32L4X5_RCC_INTERNALS_H
+#define HW_STM32L4X5_RCC_INTERNALS_H
+
+#include "hw/registerfields.h"
+#include "hw/misc/stm32l4x5_rcc.h"
+
+
+/* Register map */
+REG32(CR, 0x00)
+    FIELD(CR, PLLSAI2RDY, 29, 1)
+    FIELD(CR, PLLSAI2ON, 28, 1)
+    FIELD(CR, PLLSAI1RDY, 27, 1)
+    FIELD(CR, PLLSAI1ON, 26, 1)
+    FIELD(CR, PLLRDY, 25, 1)
+    FIELD(CR, PLLON, 24, 1)
+    FIELD(CR, CSSON, 19, 1)
+    FIELD(CR, HSEBYP, 18, 1)
+    FIELD(CR, HSERDY, 17, 1)
+    FIELD(CR, HSEON, 16, 1)
+    FIELD(CR, HSIASFS, 11, 1)
+    FIELD(CR, HSIRDY, 10, 1)
+    FIELD(CR, HSIKERON, 9, 1)
+    FIELD(CR, HSION, 8, 1)
+    FIELD(CR, MSIRANGE, 4, 4)
+    FIELD(CR, MSIRGSEL, 3, 1)
+    FIELD(CR, MSIPLLEN, 2, 1)
+    FIELD(CR, MSIRDY, 1, 1)
+    FIELD(CR, MSION, 0, 1)
+REG32(ICSCR, 0x04)
+    FIELD(ICSCR, HSITRIM, 24, 7)
+    FIELD(ICSCR, HSICAL, 16, 8)
+    FIELD(ICSCR, MSITRIM, 8, 8)
+    FIELD(ICSCR, MSICAL, 0, 8)
+REG32(CFGR, 0x08)
+    FIELD(CFGR, MCOPRE, 28, 3)
+    /* MCOSEL[2:0] only for STM32L475xx/476xx/486xx devices */
+    FIELD(CFGR, MCOSEL, 24, 3)
+    FIELD(CFGR, STOPWUCK, 15, 1)
+    FIELD(CFGR, PPRE2, 11, 3)
+    FIELD(CFGR, PPRE1, 8, 3)
+    FIELD(CFGR, HPRE, 4, 4)
+    FIELD(CFGR, SWS, 2, 2)
+    FIELD(CFGR, SW, 0, 2)
+REG32(PLLCFGR, 0x0C)
+    FIELD(PLLCFGR, PLLPDIV, 27, 5)
+    FIELD(PLLCFGR, PLLR, 25, 2)
+    FIELD(PLLCFGR, PLLREN, 24, 1)
+    FIELD(PLLCFGR, PLLQ, 21, 2)
+    FIELD(PLLCFGR, PLLQEN, 20, 1)
+    FIELD(PLLCFGR, PLLP, 17, 1)
+    FIELD(PLLCFGR, PLLPEN, 16, 1)
+    FIELD(PLLCFGR, PLLN, 8, 7)
+    FIELD(PLLCFGR, PLLM, 4, 3)
+    FIELD(PLLCFGR, PLLSRC, 0, 2)
+REG32(PLLSAI1CFGR, 0x10)
+    FIELD(PLLSAI1CFGR, PLLSAI1PDIV, 27, 5)
+    FIELD(PLLSAI1CFGR, PLLSAI1R, 25, 2)
+    FIELD(PLLSAI1CFGR, PLLSAI1REN, 24, 1)
+    FIELD(PLLSAI1CFGR, PLLSAI1Q, 21, 2)
+    FIELD(PLLSAI1CFGR, PLLSAI1QEN, 20, 1)
+    FIELD(PLLSAI1CFGR, PLLSAI1P, 17, 1)
+    FIELD(PLLSAI1CFGR, PLLSAI1PEN, 16, 1)
+    FIELD(PLLSAI1CFGR, PLLSAI1N, 8, 7)
+REG32(PLLSAI2CFGR, 0x14)
+    FIELD(PLLSAI2CFGR, PLLSAI2PDIV, 27, 5)
+    FIELD(PLLSAI2CFGR, PLLSAI2R, 25, 2)
+    FIELD(PLLSAI2CFGR, PLLSAI2REN, 24, 1)
+    FIELD(PLLSAI2CFGR, PLLSAI2Q, 21, 2)
+    FIELD(PLLSAI2CFGR, PLLSAI2QEN, 20, 1)
+    FIELD(PLLSAI2CFGR, PLLSAI2P, 17, 1)
+    FIELD(PLLSAI2CFGR, PLLSAI2PEN, 16, 1)
+    FIELD(PLLSAI2CFGR, PLLSAI2N, 8, 7)
+REG32(CIER, 0x18)
+    /* HSI48RDYIE: only on STM32L496xx/4A6xx devices */
+    FIELD(CIER, LSECSSIE, 9, 1)
+    FIELD(CIER, PLLSAI2RDYIE, 7, 1)
+    FIELD(CIER, PLLSAI1RDYIE, 6, 1)
+    FIELD(CIER, PLLRDYIE, 5, 1)
+    FIELD(CIER, HSERDYIE, 4, 1)
+    FIELD(CIER, HSIRDYIE, 3, 1)
+    FIELD(CIER, MSIRDYIE, 2, 1)
+    FIELD(CIER, LSERDYIE, 1, 1)
+    FIELD(CIER, LSIRDYIE, 0, 1)
+REG32(CIFR, 0x1C)
+    /* HSI48RDYF: only on STM32L496xx/4A6xx devices */
+    FIELD(CIFR, LSECSSF, 9, 1)
+    FIELD(CIFR, CSSF, 8, 1)
+    FIELD(CIFR, PLLSAI2RDYF, 7, 1)
+    FIELD(CIFR, PLLSAI1RDYF, 6, 1)
+    FIELD(CIFR, PLLRDYF, 5, 1)
+    FIELD(CIFR, HSERDYF, 4, 1)
+    FIELD(CIFR, HSIRDYF, 3, 1)
+    FIELD(CIFR, MSIRDYF, 2, 1)
+    FIELD(CIFR, LSERDYF, 1, 1)
+    FIELD(CIFR, LSIRDYF, 0, 1)
+REG32(CICR, 0x20)
+    /* HSI48RDYC: only on STM32L496xx/4A6xx devices */
+    FIELD(CICR, LSECSSC, 9, 1)
+    FIELD(CICR, CSSC, 8, 1)
+    FIELD(CICR, PLLSAI2RDYC, 7, 1)
+    FIELD(CICR, PLLSAI1RDYC, 6, 1)
+    FIELD(CICR, PLLRDYC, 5, 1)
+    FIELD(CICR, HSERDYC, 4, 1)
+    FIELD(CICR, HSIRDYC, 3, 1)
+    FIELD(CICR, MSIRDYC, 2, 1)
+    FIELD(CICR, LSERDYC, 1, 1)
+    FIELD(CICR, LSIRDYC, 0, 1)
+REG32(AHB1RSTR, 0x28)
+REG32(AHB2RSTR, 0x2C)
+REG32(AHB3RSTR, 0x30)
+REG32(APB1RSTR1, 0x38)
+REG32(APB1RSTR2, 0x3C)
+REG32(APB2RSTR, 0x40)
+REG32(AHB1ENR, 0x48)
+    /* DMA2DEN: reserved for STM32L475xx */
+    FIELD(AHB1ENR, TSCEN, 16, 1)
+    FIELD(AHB1ENR, CRCEN, 12, 1)
+    FIELD(AHB1ENR, FLASHEN, 8, 1)
+    FIELD(AHB1ENR, DMA2EN, 1, 1)
+    FIELD(AHB1ENR, DMA1EN, 0, 1)
+REG32(AHB2ENR, 0x4C)
+    FIELD(AHB2ENR, RNGEN, 18, 1)
+    /* HASHEN: reserved for STM32L475xx */
+    FIELD(AHB2ENR, AESEN, 16, 1)
+    /* DCMIEN: reserved for STM32L475xx */
+    FIELD(AHB2ENR, ADCEN, 13, 1)
+    FIELD(AHB2ENR, OTGFSEN, 12, 1)
+    /* GPIOIEN: reserved for STM32L475xx */
+    FIELD(AHB2ENR, GPIOHEN, 7, 1)
+    FIELD(AHB2ENR, GPIOGEN, 6, 1)
+    FIELD(AHB2ENR, GPIOFEN, 5, 1)
+    FIELD(AHB2ENR, GPIOEEN, 4, 1)
+    FIELD(AHB2ENR, GPIODEN, 3, 1)
+    FIELD(AHB2ENR, GPIOCEN, 2, 1)
+    FIELD(AHB2ENR, GPIOBEN, 1, 1)
+    FIELD(AHB2ENR, GPIOAEN, 0, 1)
+REG32(AHB3ENR, 0x50)
+    FIELD(AHB3ENR, QSPIEN, 8, 1)
+    FIELD(AHB3ENR, FMCEN, 0, 1)
+REG32(APB1ENR1, 0x58)
+    FIELD(APB1ENR1, LPTIM1EN, 31, 1)
+    FIELD(APB1ENR1, OPAMPEN, 30, 1)
+    FIELD(APB1ENR1, DAC1EN, 29, 1)
+    FIELD(APB1ENR1, PWREN, 28, 1)
+    FIELD(APB1ENR1, CAN2EN, 26, 1)
+    FIELD(APB1ENR1, CAN1EN, 25, 1)
+    /* CRSEN: reserved for STM32L475xx */
+    FIELD(APB1ENR1, I2C3EN, 23, 1)
+    FIELD(APB1ENR1, I2C2EN, 22, 1)
+    FIELD(APB1ENR1, I2C1EN, 21, 1)
+    FIELD(APB1ENR1, UART5EN, 20, 1)
+    FIELD(APB1ENR1, UART4EN, 19, 1)
+    FIELD(APB1ENR1, USART3EN, 18, 1)
+    FIELD(APB1ENR1, USART2EN, 17, 1)
+    FIELD(APB1ENR1, SPI3EN, 15, 1)
+    FIELD(APB1ENR1, SPI2EN, 14, 1)
+    FIELD(APB1ENR1, WWDGEN, 11, 1)
+    /* RTCAPBEN: reserved for STM32L475xx */
+    FIELD(APB1ENR1, LCDEN, 9, 1)
+    FIELD(APB1ENR1, TIM7EN, 5, 1)
+    FIELD(APB1ENR1, TIM6EN, 4, 1)
+    FIELD(APB1ENR1, TIM5EN, 3, 1)
+    FIELD(APB1ENR1, TIM4EN, 2, 1)
+    FIELD(APB1ENR1, TIM3EN, 1, 1)
+    FIELD(APB1ENR1, TIM2EN, 0, 1)
+REG32(APB1ENR2, 0x5C)
+    FIELD(APB1ENR2, LPTIM2EN, 5, 1)
+    FIELD(APB1ENR2, SWPMI1EN, 2, 1)
+    /* I2C4EN: reserved for STM32L475xx */
+    FIELD(APB1ENR2, LPUART1EN, 0, 1)
+REG32(APB2ENR, 0x60)
+    FIELD(APB2ENR, DFSDM1EN, 24, 1)
+    FIELD(APB2ENR, SAI2EN, 22, 1)
+    FIELD(APB2ENR, SAI1EN, 21, 1)
+    FIELD(APB2ENR, TIM17EN, 18, 1)
+    FIELD(APB2ENR, TIM16EN, 17, 1)
+    FIELD(APB2ENR, TIM15EN, 16, 1)
+    FIELD(APB2ENR, USART1EN, 14, 1)
+    FIELD(APB2ENR, TIM8EN, 13, 1)
+    FIELD(APB2ENR, SPI1EN, 12, 1)
+    FIELD(APB2ENR, TIM1EN, 11, 1)
+    FIELD(APB2ENR, SDMMC1EN, 10, 1)
+    FIELD(APB2ENR, FWEN, 7, 1)
+    FIELD(APB2ENR, SYSCFGEN, 0, 1)
+REG32(AHB1SMENR, 0x68)
+REG32(AHB2SMENR, 0x6C)
+REG32(AHB3SMENR, 0x70)
+REG32(APB1SMENR1, 0x78)
+REG32(APB1SMENR2, 0x7C)
+REG32(APB2SMENR, 0x80)
+REG32(CCIPR, 0x88)
+    FIELD(CCIPR, DFSDM1SEL, 31, 1)
+    FIELD(CCIPR, SWPMI1SEL, 30, 1)
+    FIELD(CCIPR, ADCSEL, 28, 2)
+    FIELD(CCIPR, CLK48SEL, 26, 2)
+    FIELD(CCIPR, SAI2SEL, 24, 2)
+    FIELD(CCIPR, SAI1SEL, 22, 2)
+    FIELD(CCIPR, LPTIM2SEL, 20, 2)
+    FIELD(CCIPR, LPTIM1SEL, 18, 2)
+    FIELD(CCIPR, I2C3SEL, 16, 2)
+    FIELD(CCIPR, I2C2SEL, 14, 2)
+    FIELD(CCIPR, I2C1SEL, 12, 2)
+    FIELD(CCIPR, LPUART1SEL, 10, 2)
+    FIELD(CCIPR, UART5SEL, 8, 2)
+    FIELD(CCIPR, UART4SEL, 6, 2)
+    FIELD(CCIPR, USART3SEL, 4, 2)
+    FIELD(CCIPR, USART2SEL, 2, 2)
+    FIELD(CCIPR, USART1SEL, 0, 2)
+REG32(BDCR, 0x90)
+    FIELD(BDCR, LSCOSEL, 25, 1)
+    FIELD(BDCR, LSCOEN, 24, 1)
+    FIELD(BDCR, BDRST, 16, 1)
+    FIELD(BDCR, RTCEN, 15, 1)
+    FIELD(BDCR, RTCSEL, 8, 2)
+    FIELD(BDCR, LSECSSD, 6, 1)
+    FIELD(BDCR, LSECSSON, 5, 1)
+    FIELD(BDCR, LSEDRV, 3, 2)
+    FIELD(BDCR, LSEBYP, 2, 1)
+    FIELD(BDCR, LSERDY, 1, 1)
+    FIELD(BDCR, LSEON, 0, 1)
+REG32(CSR, 0x94)
+    FIELD(CSR, LPWRRSTF, 31, 1)
+    FIELD(CSR, WWDGRSTF, 30, 1)
+    FIELD(CSR, IWWGRSTF, 29, 1)
+    FIELD(CSR, SFTRSTF, 28, 1)
+    FIELD(CSR, BORRSTF, 27, 1)
+    FIELD(CSR, PINRSTF, 26, 1)
+    FIELD(CSR, OBLRSTF, 25, 1)
+    FIELD(CSR, FWRSTF, 24, 1)
+    FIELD(CSR, RMVF, 23, 1)
+    FIELD(CSR, MSISRANGE, 8, 4)
+    FIELD(CSR, LSIRDY, 1, 1)
+    FIELD(CSR, LSION, 0, 1)
+/* CRRCR and CCIPR2 registers are present on L496/L4A6 devices only. */
+
+/* Read Only masks to prevent writes in unauthorized bits */
+#define CR_READ_ONLY_MASK (R_CR_PLLSAI2RDY_MASK | \
+                           R_CR_PLLSAI1RDY_MASK | \
+                           R_CR_PLLRDY_MASK     | \
+                           R_CR_HSERDY_MASK     | \
+                           R_CR_HSIRDY_MASK     | \
+                           R_CR_MSIRDY_MASK)
+#define CR_READ_SET_MASK (R_CR_CSSON_MASK | R_CR_MSIRGSEL_MASK)
+#define ICSCR_READ_ONLY_MASK (R_ICSCR_HSICAL_MASK | R_ICSCR_MSICAL_MASK)
+#define CFGR_READ_ONLY_MASK (R_CFGR_SWS_MASK)
+#define CIFR_READ_ONLY_MASK (R_CIFR_LSECSSF_MASK     | \
+                             R_CIFR_CSSF_MASK        | \
+                             R_CIFR_PLLSAI2RDYF_MASK | \
+                             R_CIFR_PLLSAI1RDYF_MASK | \
+                             R_CIFR_PLLRDYF_MASK     | \
+                             R_CIFR_HSERDYF_MASK     | \
+                             R_CIFR_HSIRDYF_MASK     | \
+                             R_CIFR_MSIRDYF_MASK     | \
+                             R_CIFR_LSERDYF_MASK     | \
+                             R_CIFR_LSIRDYF_MASK)
+#define CIFR_IRQ_MASK CIFR_READ_ONLY_MASK
+#define APB2ENR_READ_SET_MASK (R_APB2ENR_FWEN_MASK)
+#define BDCR_READ_ONLY_MASK (R_BDCR_LSECSSD_MASK | R_BDCR_LSERDY_MASK)
+#define CSR_READ_ONLY_MASK (R_CSR_LPWRRSTF_MASK | \
+                            R_CSR_WWDGRSTF_MASK | \
+                            R_CSR_IWWGRSTF_MASK | \
+                            R_CSR_SFTRSTF_MASK  | \
+                            R_CSR_BORRSTF_MASK  | \
+                            R_CSR_PINRSTF_MASK  | \
+                            R_CSR_OBLRSTF_MASK  | \
+                            R_CSR_FWRSTF_MASK   | \
+                            R_CSR_LSIRDY_MASK)
+
+#endif /* HW_STM32L4X5_RCC_INTERNALS_H */
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v4 2/8] Add an internal clock multiplexer object
  2024-01-30 16:06 [PATCH v4 0/8] Add device STM32L4x5 RCC Arnaud Minier
  2024-01-30 16:06 ` [PATCH v4 1/8] Implement STM32L4x5_RCC skeleton Arnaud Minier
@ 2024-01-30 16:06 ` Arnaud Minier
  2024-01-30 16:06 ` [PATCH v4 3/8] Add an internal PLL Clock object Arnaud Minier
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Arnaud Minier @ 2024-01-30 16:06 UTC (permalink / raw)
  To: qemu-devel
  Cc: Laurent Vivier, Alistair Francis, Inès Varhol,
	Peter Maydell, Paolo Bonzini, Thomas Huth, qemu-arm,
	Arnaud Minier, Alistair Francis

This object is used to represent every multiplexer in the clock tree as
well as every clock output, every presecaler, frequency multiplier, etc.
This allows to use a generic approach for every component of the clock tree
(except the PLLs).

Wasn't sure about how to handle the reset and the migration so used the
same appproach as the BCM2835 CPRMAN.

Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
Acked-by: Alistair Francis <alistair.francis@wdc.com>
---
 hw/misc/stm32l4x5_rcc.c                   | 158 ++++++++++++++++++++++
 hw/misc/trace-events                      |   5 +
 include/hw/misc/stm32l4x5_rcc.h           | 119 ++++++++++++++++
 include/hw/misc/stm32l4x5_rcc_internals.h |  29 ++++
 4 files changed, 311 insertions(+)

diff --git a/hw/misc/stm32l4x5_rcc.c b/hw/misc/stm32l4x5_rcc.c
index 38ca8aad7d..ed10832f88 100644
--- a/hw/misc/stm32l4x5_rcc.c
+++ b/hw/misc/stm32l4x5_rcc.c
@@ -36,6 +36,132 @@
 #define LSE_FRQ 32768ULL
 #define LSI_FRQ 32000ULL
 
+static void clock_mux_update(RccClockMuxState *mux)
+{
+    uint64_t src_freq, old_freq, freq;
+
+    src_freq = clock_get_hz(mux->srcs[mux->src]);
+    old_freq = clock_get_hz(mux->out);
+
+    if (!mux->enabled || !mux->divider) {
+        freq = 0;
+    } else {
+        freq = muldiv64(src_freq, mux->multiplier, mux->divider);
+    }
+
+    /* No change, early return to avoid log spam and useless propagation */
+    if (old_freq == freq) {
+        return;
+    }
+
+    clock_update_hz(mux->out, freq);
+    trace_stm32l4x5_rcc_mux_update(mux->id, mux->src, src_freq, freq);
+}
+
+static void clock_mux_src_update(void *opaque, ClockEvent event)
+{
+    RccClockMuxState **backref = opaque;
+    RccClockMuxState *s = *backref;
+    /*
+     * The backref value is equal to:
+     * s->backref + (sizeof(RccClockMuxState *) * update_src).
+     * By subtracting we can get back the index of the updated clock.
+     */
+    const uint32_t update_src = backref - s->backref;
+    /* Only update if the clock that was updated is the current source*/
+    if (update_src == s->src) {
+        clock_mux_update(s);
+    }
+}
+
+static void clock_mux_init(Object *obj)
+{
+    RccClockMuxState *s = RCC_CLOCK_MUX(obj);
+    size_t i;
+
+    for (i = 0; i < RCC_NUM_CLOCK_MUX_SRC; i++) {
+        char *name = g_strdup_printf("srcs[%zu]", i);
+        s->backref[i] = s;
+        s->srcs[i] = qdev_init_clock_in(DEVICE(s), name,
+                                        clock_mux_src_update,
+                                        &s->backref[i],
+                                        ClockUpdate);
+        g_free(name);
+    }
+
+    s->out = qdev_init_clock_out(DEVICE(s), "out");
+}
+
+static void clock_mux_reset_hold(Object *obj)
+{ }
+
+static const VMStateDescription clock_mux_vmstate = {
+    .name = TYPE_RCC_CLOCK_MUX,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(id, RccClockMuxState),
+        VMSTATE_ARRAY_CLOCK(srcs, RccClockMuxState,
+                            RCC_NUM_CLOCK_MUX_SRC),
+        VMSTATE_CLOCK(out, RccClockMuxState),
+        VMSTATE_BOOL(enabled, RccClockMuxState),
+        VMSTATE_UINT32(src, RccClockMuxState),
+        VMSTATE_UINT32(multiplier, RccClockMuxState),
+        VMSTATE_UINT32(divider, RccClockMuxState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void clock_mux_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    rc->phases.hold = clock_mux_reset_hold;
+    dc->vmsd = &clock_mux_vmstate;
+}
+
+static void clock_mux_set_enable(RccClockMuxState *mux, bool enabled)
+{
+    if (mux->enabled == enabled) {
+        return;
+    }
+
+    if (enabled) {
+        trace_stm32l4x5_rcc_mux_enable(mux->id);
+    } else {
+        trace_stm32l4x5_rcc_mux_disable(mux->id);
+    }
+
+    mux->enabled = enabled;
+    clock_mux_update(mux);
+}
+
+static void clock_mux_set_factor(RccClockMuxState *mux,
+                                 uint32_t multiplier, uint32_t divider)
+{
+    if (mux->multiplier == multiplier && mux->divider == divider) {
+        return;
+    }
+    trace_stm32l4x5_rcc_mux_set_factor(mux->id,
+        mux->multiplier, multiplier, mux->divider, divider);
+
+    mux->multiplier = multiplier;
+    mux->divider = divider;
+    clock_mux_update(mux);
+}
+
+static void clock_mux_set_source(RccClockMuxState *mux, RccClockMuxSource src)
+{
+    if (mux->src == src) {
+        return;
+    }
+
+    trace_stm32l4x5_rcc_mux_set_src(mux->id, mux->src, src);
+    mux->src = src;
+    clock_mux_update(mux);
+}
+
 static void rcc_update_irq(Stm32l4x5RccState *s)
 {
     if (s->cifr & CIFR_IRQ_MASK) {
@@ -329,6 +455,7 @@ static const ClockPortInitArray stm32l4x5_rcc_clocks = {
 static void stm32l4x5_rcc_init(Object *obj)
 {
     Stm32l4x5RccState *s = STM32L4X5_RCC(obj);
+    size_t i;
 
     sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
 
@@ -338,6 +465,14 @@ static void stm32l4x5_rcc_init(Object *obj)
 
     qdev_init_clocks(DEVICE(s), stm32l4x5_rcc_clocks);
 
+    for (i = 0; i < RCC_NUM_CLOCK_MUX; i++) {
+
+        object_initialize_child(obj, "clock[*]",
+                                &s->clock_muxes[i],
+                                TYPE_RCC_CLOCK_MUX);
+
+    }
+
     s->gnd = clock_new(obj, "gnd");
 }
 
@@ -383,6 +518,7 @@ static const VMStateDescription vmstate_stm32l4x5_rcc = {
 static void stm32l4x5_rcc_realize(DeviceState *dev, Error **errp)
 {
     Stm32l4x5RccState *s = STM32L4X5_RCC(dev);
+    size_t i;
 
     if (s->hse_frequency <  4000000ULL ||
         s->hse_frequency > 48000000ULL) {
@@ -392,10 +528,26 @@ static void stm32l4x5_rcc_realize(DeviceState *dev, Error **errp)
             return;
         }
 
+    for (i = 0; i < RCC_NUM_CLOCK_MUX; i++) {
+        RccClockMuxState *clock_mux = &s->clock_muxes[i];
+
+        if (!qdev_realize(DEVICE(clock_mux), NULL, errp)) {
+            return;
+        }
+    }
+
     clock_update_hz(s->msi_rc, MSI_DEFAULT_FRQ);
     clock_update_hz(s->sai1_extclk, s->sai1_extclk_frequency);
     clock_update_hz(s->sai2_extclk, s->sai2_extclk_frequency);
     clock_update(s->gnd, 0);
+
+    /*
+     * Dummy values to make compilation pass.
+     * Removed in later commits.
+     */
+    clock_mux_set_source(&s->clock_muxes[0], RCC_CLOCK_MUX_SRC_GND);
+    clock_mux_set_enable(&s->clock_muxes[0], true);
+    clock_mux_set_factor(&s->clock_muxes[0], 1, 1);
 }
 
 static Property stm32l4x5_rcc_properties[] = {
@@ -427,6 +579,12 @@ static const TypeInfo stm32l4x5_rcc_types[] = {
         .instance_size  = sizeof(Stm32l4x5RccState),
         .instance_init  = stm32l4x5_rcc_init,
         .class_init     = stm32l4x5_rcc_class_init,
+    }, {
+        .name = TYPE_RCC_CLOCK_MUX,
+        .parent = TYPE_DEVICE,
+        .instance_size = sizeof(RccClockMuxState),
+        .instance_init = clock_mux_init,
+        .class_init = clock_mux_class_init,
     }
 };
 
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index 62a7599353..d5e471811c 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -177,6 +177,11 @@ stm32l4x5_exti_write(uint64_t addr, uint64_t data) "reg write: addr: 0x%" PRIx64
 # stm32l4x5_rcc.c
 stm32l4x5_rcc_read(uint64_t addr, uint32_t data) "RCC: Read <0x%" PRIx64 "> -> 0x%" PRIx32 ""
 stm32l4x5_rcc_write(uint64_t addr, uint32_t data) "RCC: Write <0x%" PRIx64 "> <- 0x%" PRIx32 ""
+stm32l4x5_rcc_mux_enable(uint32_t mux_id) "RCC: Mux %d enabled"
+stm32l4x5_rcc_mux_disable(uint32_t mux_id) "RCC: Mux %d disabled"
+stm32l4x5_rcc_mux_set_factor(uint32_t mux_id, uint32_t old_multiplier, uint32_t new_multiplier, uint32_t old_divider, uint32_t new_divider) "RCC: Mux %d factor changed: multiplier (%u -> %u), divider (%u -> %u)"
+stm32l4x5_rcc_mux_set_src(uint32_t mux_id, uint32_t old_src, uint32_t new_src) "RCC: Mux %d source changed: from %u to %u"
+stm32l4x5_rcc_mux_update(uint32_t mux_id, uint32_t src, uint64_t src_freq, uint64_t new_freq) "RCC: Mux %d src %d update: src_freq %" PRIu64 " new_freq %" PRIu64 ""
 
 # tz-mpc.c
 tz_mpc_reg_read(uint32_t offset, uint64_t data, unsigned size) "TZ MPC regs read: offset 0x%x data 0x%" PRIx64 " size %u"
diff --git a/include/hw/misc/stm32l4x5_rcc.h b/include/hw/misc/stm32l4x5_rcc.h
index 5157e96635..6719be9fbe 100644
--- a/include/hw/misc/stm32l4x5_rcc.h
+++ b/include/hw/misc/stm32l4x5_rcc.h
@@ -26,6 +26,122 @@ OBJECT_DECLARE_SIMPLE_TYPE(Stm32l4x5RccState, STM32L4X5_RCC)
 
 /* In the Stm32l4x5 clock tree, mux have at most 7 sources */
 #define RCC_NUM_CLOCK_MUX_SRC 7
+/* NB: Prescaler are assimilated to mux with one source and one output */
+typedef enum RccClockMux {
+    /* Internal muxes that arent't exposed publicly to other peripherals */
+    RCC_CLOCK_MUX_SYSCLK,
+    RCC_CLOCK_MUX_PLL_INPUT,
+    RCC_CLOCK_MUX_HCLK,
+    RCC_CLOCK_MUX_PCLK1,
+    RCC_CLOCK_MUX_PCLK2,
+    RCC_CLOCK_MUX_HSE_OVER_32,
+    RCC_CLOCK_MUX_LCD_AND_RTC_COMMON,
+
+    /* Muxes with a publicly available output */
+    RCC_CLOCK_MUX_CORTEX_REFCLK,
+    RCC_CLOCK_MUX_USART1,
+    RCC_CLOCK_MUX_USART2,
+    RCC_CLOCK_MUX_USART3,
+    RCC_CLOCK_MUX_UART4,
+    RCC_CLOCK_MUX_UART5,
+    RCC_CLOCK_MUX_LPUART1,
+    RCC_CLOCK_MUX_I2C1,
+    RCC_CLOCK_MUX_I2C2,
+    RCC_CLOCK_MUX_I2C3,
+    RCC_CLOCK_MUX_LPTIM1,
+    RCC_CLOCK_MUX_LPTIM2,
+    RCC_CLOCK_MUX_SWPMI1,
+    RCC_CLOCK_MUX_MCO,
+    RCC_CLOCK_MUX_LSCO,
+    RCC_CLOCK_MUX_DFSDM1,
+    RCC_CLOCK_MUX_ADC,
+    RCC_CLOCK_MUX_CLK48,
+    RCC_CLOCK_MUX_SAI1,
+    RCC_CLOCK_MUX_SAI2,
+
+    /*
+     * Mux that have only one input and one output assigned to as peripheral.
+     * They could be direct lines but it is simpler
+     * to use the same logic for all outputs.
+     */
+    /* - AHB1 */
+    RCC_CLOCK_MUX_TSC,
+    RCC_CLOCK_MUX_CRC,
+    RCC_CLOCK_MUX_FLASH,
+    RCC_CLOCK_MUX_DMA2,
+    RCC_CLOCK_MUX_DMA1,
+
+    /* - AHB2 */
+    RCC_CLOCK_MUX_RNG,
+    RCC_CLOCK_MUX_AES,
+    RCC_CLOCK_MUX_OTGFS,
+    RCC_CLOCK_MUX_GPIOA,
+    RCC_CLOCK_MUX_GPIOB,
+    RCC_CLOCK_MUX_GPIOC,
+    RCC_CLOCK_MUX_GPIOD,
+    RCC_CLOCK_MUX_GPIOE,
+    RCC_CLOCK_MUX_GPIOF,
+    RCC_CLOCK_MUX_GPIOG,
+    RCC_CLOCK_MUX_GPIOH,
+
+    /* - AHB3 */
+    RCC_CLOCK_MUX_QSPI,
+    RCC_CLOCK_MUX_FMC,
+
+    /* - APB1 */
+    RCC_CLOCK_MUX_OPAMP,
+    RCC_CLOCK_MUX_DAC1,
+    RCC_CLOCK_MUX_PWR,
+    RCC_CLOCK_MUX_CAN1,
+    RCC_CLOCK_MUX_SPI3,
+    RCC_CLOCK_MUX_SPI2,
+    RCC_CLOCK_MUX_WWDG,
+    RCC_CLOCK_MUX_LCD,
+    RCC_CLOCK_MUX_TIM7,
+    RCC_CLOCK_MUX_TIM6,
+    RCC_CLOCK_MUX_TIM5,
+    RCC_CLOCK_MUX_TIM4,
+    RCC_CLOCK_MUX_TIM3,
+    RCC_CLOCK_MUX_TIM2,
+
+    /* - APB2 */
+    RCC_CLOCK_MUX_TIM17,
+    RCC_CLOCK_MUX_TIM16,
+    RCC_CLOCK_MUX_TIM15,
+    RCC_CLOCK_MUX_TIM8,
+    RCC_CLOCK_MUX_SPI1,
+    RCC_CLOCK_MUX_TIM1,
+    RCC_CLOCK_MUX_SDMMC1,
+    RCC_CLOCK_MUX_FW,
+    RCC_CLOCK_MUX_SYSCFG,
+
+    /* - BDCR */
+    RCC_CLOCK_MUX_RTC,
+
+    /* - OTHER */
+    RCC_CLOCK_MUX_CORTEX_FCLK,
+
+    RCC_NUM_CLOCK_MUX
+} RccClockMux;
+
+typedef struct RccClockMuxState {
+    DeviceState parent_obj;
+
+    RccClockMux id;
+    Clock *srcs[RCC_NUM_CLOCK_MUX_SRC];
+    Clock *out;
+    bool enabled;
+    uint32_t src;
+    uint32_t multiplier;
+    uint32_t divider;
+
+    /*
+     * Used by clock srcs update callback to retrieve both the clock and the
+     * source number.
+     */
+    struct RccClockMuxState *backref[RCC_NUM_CLOCK_MUX_SRC];
+} RccClockMuxState;
+
 struct Stm32l4x5RccState {
     SysBusDevice parent_obj;
 
@@ -71,6 +187,9 @@ struct Stm32l4x5RccState {
     Clock *sai1_extclk;
     Clock *sai2_extclk;
 
+    /* Muxes ~= outputs */
+    RccClockMuxState clock_muxes[RCC_NUM_CLOCK_MUX];
+
     qemu_irq irq;
     uint64_t hse_frequency;
     uint64_t sai1_extclk_frequency;
diff --git a/include/hw/misc/stm32l4x5_rcc_internals.h b/include/hw/misc/stm32l4x5_rcc_internals.h
index 331ea30db5..4aa836848b 100644
--- a/include/hw/misc/stm32l4x5_rcc_internals.h
+++ b/include/hw/misc/stm32l4x5_rcc_internals.h
@@ -21,6 +21,8 @@
 #include "hw/registerfields.h"
 #include "hw/misc/stm32l4x5_rcc.h"
 
+#define TYPE_RCC_CLOCK_MUX "stm32l4x5-rcc-clock-mux"
+OBJECT_DECLARE_SIMPLE_TYPE(RccClockMuxState, RCC_CLOCK_MUX)
 
 /* Register map */
 REG32(CR, 0x00)
@@ -283,4 +285,31 @@ REG32(CSR, 0x94)
                             R_CSR_FWRSTF_MASK   | \
                             R_CSR_LSIRDY_MASK)
 
+typedef enum RccClockMuxSource {
+    RCC_CLOCK_MUX_SRC_GND = 0,
+    RCC_CLOCK_MUX_SRC_HSI,
+    RCC_CLOCK_MUX_SRC_HSE,
+    RCC_CLOCK_MUX_SRC_MSI,
+    RCC_CLOCK_MUX_SRC_LSI,
+    RCC_CLOCK_MUX_SRC_LSE,
+    RCC_CLOCK_MUX_SRC_SAI1_EXTCLK,
+    RCC_CLOCK_MUX_SRC_SAI2_EXTCLK,
+    RCC_CLOCK_MUX_SRC_PLL,
+    RCC_CLOCK_MUX_SRC_PLLSAI1,
+    RCC_CLOCK_MUX_SRC_PLLSAI2,
+    RCC_CLOCK_MUX_SRC_PLLSAI3,
+    RCC_CLOCK_MUX_SRC_PLL48M1,
+    RCC_CLOCK_MUX_SRC_PLL48M2,
+    RCC_CLOCK_MUX_SRC_PLLADC1,
+    RCC_CLOCK_MUX_SRC_PLLADC2,
+    RCC_CLOCK_MUX_SRC_SYSCLK,
+    RCC_CLOCK_MUX_SRC_HCLK,
+    RCC_CLOCK_MUX_SRC_PCLK1,
+    RCC_CLOCK_MUX_SRC_PCLK2,
+    RCC_CLOCK_MUX_SRC_HSE_OVER_32,
+    RCC_CLOCK_MUX_SRC_LCD_AND_RTC_COMMON,
+
+    RCC_CLOCK_MUX_SRC_NUMBER,
+} RccClockMuxSource;
+
 #endif /* HW_STM32L4X5_RCC_INTERNALS_H */
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v4 3/8] Add an internal PLL Clock object
  2024-01-30 16:06 [PATCH v4 0/8] Add device STM32L4x5 RCC Arnaud Minier
  2024-01-30 16:06 ` [PATCH v4 1/8] Implement STM32L4x5_RCC skeleton Arnaud Minier
  2024-01-30 16:06 ` [PATCH v4 2/8] Add an internal clock multiplexer object Arnaud Minier
@ 2024-01-30 16:06 ` Arnaud Minier
  2024-02-01  0:18   ` Alistair Francis
  2024-01-30 16:06 ` [PATCH v4 4/8] Add initialization information for PLLs and clock multiplexers Arnaud Minier
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 14+ messages in thread
From: Arnaud Minier @ 2024-01-30 16:06 UTC (permalink / raw)
  To: qemu-devel
  Cc: Laurent Vivier, Alistair Francis, Inès Varhol,
	Peter Maydell, Paolo Bonzini, Thomas Huth, qemu-arm,
	Arnaud Minier

This object represents the PLLs and their channels. The PLLs allow for a
more fine-grained control of the clocks frequency.

Wasn't sure about how to handle the reset and the migration so used the
same appproach as the BCM2835 CPRMAN.

Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
---
 hw/misc/stm32l4x5_rcc.c                   | 175 ++++++++++++++++++++++
 hw/misc/trace-events                      |   5 +
 include/hw/misc/stm32l4x5_rcc.h           |  40 +++++
 include/hw/misc/stm32l4x5_rcc_internals.h |  22 +++
 4 files changed, 242 insertions(+)

diff --git a/hw/misc/stm32l4x5_rcc.c b/hw/misc/stm32l4x5_rcc.c
index ed10832f88..fb0233c3e9 100644
--- a/hw/misc/stm32l4x5_rcc.c
+++ b/hw/misc/stm32l4x5_rcc.c
@@ -162,6 +162,156 @@ static void clock_mux_set_source(RccClockMuxState *mux, RccClockMuxSource src)
     clock_mux_update(mux);
 }
 
+static void pll_update(RccPllState *pll)
+{
+    uint64_t vco_freq, old_channel_freq, channel_freq;
+    int i;
+
+    /* The common PLLM factor is handled by the PLL mux */
+    vco_freq = muldiv64(clock_get_hz(pll->in), pll->vco_multiplier, 1);
+
+    for (i = 0; i < RCC_NUM_CHANNEL_PLL_OUT; i++) {
+        if (!pll->channel_exists[i]) {
+            continue;
+        }
+
+        old_channel_freq = clock_get_hz(pll->channels[i]);
+        if (!pll->enabled ||
+            !pll->channel_enabled[i] ||
+            !pll->channel_divider[i]) {
+            channel_freq = 0;
+        } else {
+            channel_freq = muldiv64(vco_freq,
+                                    1,
+                                    pll->channel_divider[i]);
+        }
+
+        /* No change, early continue to avoid log spam and useless propagation */
+        if (old_channel_freq == channel_freq) {
+            continue;
+        }
+
+        clock_update_hz(pll->channels[i], channel_freq);
+        trace_stm32l4x5_rcc_pll_update(pll->id, i, vco_freq,
+            old_channel_freq, channel_freq);
+    }
+}
+
+static void pll_src_update(void *opaque, ClockEvent event)
+{
+    RccPllState *s = opaque;
+    pll_update(s);
+}
+
+static void pll_init(Object *obj)
+{
+    RccPllState *s = RCC_PLL(obj);
+    size_t i;
+
+    s->in = qdev_init_clock_in(DEVICE(s), "in",
+                               pll_src_update, s, ClockUpdate);
+
+    const char *names[] = {
+        "out-p", "out-q", "out-r",
+    };
+
+    for (i = 0; i < RCC_NUM_CHANNEL_PLL_OUT; i++) {
+        s->channels[i] = qdev_init_clock_out(DEVICE(s), names[i]);
+    }
+}
+
+static void pll_reset_hold(Object *obj)
+{ }
+
+static const VMStateDescription pll_vmstate = {
+    .name = TYPE_RCC_PLL,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(id, RccPllState),
+        VMSTATE_CLOCK(in, RccPllState),
+        VMSTATE_ARRAY_CLOCK(channels, RccPllState,
+                            RCC_NUM_CHANNEL_PLL_OUT),
+        VMSTATE_BOOL(enabled, RccPllState),
+        VMSTATE_UINT32(vco_multiplier, RccPllState),
+        VMSTATE_BOOL_ARRAY(channel_enabled, RccPllState, RCC_NUM_CHANNEL_PLL_OUT),
+        VMSTATE_BOOL_ARRAY(channel_exists, RccPllState, RCC_NUM_CHANNEL_PLL_OUT),
+        VMSTATE_UINT32_ARRAY(channel_divider, RccPllState, RCC_NUM_CHANNEL_PLL_OUT),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void pll_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    rc->phases.hold = pll_reset_hold;
+    dc->vmsd = &pll_vmstate;
+}
+
+static void pll_set_vco_multiplier(RccPllState *pll, uint32_t vco_multiplier)
+{
+    if (pll->vco_multiplier == vco_multiplier) {
+        return;
+    }
+
+    if (vco_multiplier < 8 || vco_multiplier > 86) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+            "%s: VCO multiplier is out of bound (%u) for PLL %u\n",
+            __func__, vco_multiplier, pll->id);
+    }
+
+    trace_stm32l4x5_rcc_pll_set_vco_multiplier(pll->id,
+        pll->vco_multiplier, vco_multiplier);
+
+    pll->vco_multiplier = vco_multiplier;
+    pll_update(pll);
+}
+
+static void pll_set_enable(RccPllState *pll, bool enabled)
+{
+    if (pll->enabled == enabled) {
+        return;
+    }
+
+    pll->enabled = enabled;
+    pll_update(pll);
+}
+
+static void pll_set_channel_enable(RccPllState *pll,
+                                   PllCommonChannels channel,
+                                   bool enabled)
+{
+    if (pll->channel_enabled[channel] == enabled) {
+        return;
+    }
+
+    if (enabled) {
+        trace_stm32l4x5_rcc_pll_channel_enable(pll->id, channel);
+    } else {
+        trace_stm32l4x5_rcc_pll_channel_disable(pll->id, channel);
+    }
+
+    pll->channel_enabled[channel] = enabled;
+    pll_update(pll);
+}
+
+static void pll_set_channel_divider(RccPllState *pll,
+                                    PllCommonChannels channel,
+                                    uint32_t divider)
+{
+    if (pll->channel_divider[channel] == divider) {
+        return;
+    }
+
+    trace_stm32l4x5_rcc_pll_set_channel_divider(pll->id,
+        channel, pll->channel_divider[channel], divider);
+
+    pll->channel_divider[channel] = divider;
+    pll_update(pll);
+}
+
 static void rcc_update_irq(Stm32l4x5RccState *s)
 {
     if (s->cifr & CIFR_IRQ_MASK) {
@@ -465,6 +615,11 @@ static void stm32l4x5_rcc_init(Object *obj)
 
     qdev_init_clocks(DEVICE(s), stm32l4x5_rcc_clocks);
 
+    for (i = 0; i < RCC_NUM_PLL; i++) {
+        object_initialize_child(obj, "pll[*]",
+                                &s->plls[i], TYPE_RCC_PLL);
+    }
+
     for (i = 0; i < RCC_NUM_CLOCK_MUX; i++) {
 
         object_initialize_child(obj, "clock[*]",
@@ -528,6 +683,16 @@ static void stm32l4x5_rcc_realize(DeviceState *dev, Error **errp)
             return;
         }
 
+    for (i = 0; i < RCC_NUM_PLL; i++) {
+        RccPllState *pll = &s->plls[i];
+
+        clock_set_source(pll->in, s->clock_muxes[RCC_CLOCK_MUX_PLL_INPUT].out);
+
+        if (!qdev_realize(DEVICE(pll), NULL, errp)) {
+            return;
+        }
+    }
+
     for (i = 0; i < RCC_NUM_CLOCK_MUX; i++) {
         RccClockMuxState *clock_mux = &s->clock_muxes[i];
 
@@ -548,6 +713,10 @@ static void stm32l4x5_rcc_realize(DeviceState *dev, Error **errp)
     clock_mux_set_source(&s->clock_muxes[0], RCC_CLOCK_MUX_SRC_GND);
     clock_mux_set_enable(&s->clock_muxes[0], true);
     clock_mux_set_factor(&s->clock_muxes[0], 1, 1);
+    pll_set_channel_divider(&s->plls[0], 0, 1);
+    pll_set_enable(&s->plls[0], true);
+    pll_set_channel_enable(&s->plls[0], 0, true);
+    pll_set_vco_multiplier(&s->plls[0], 1);
 }
 
 static Property stm32l4x5_rcc_properties[] = {
@@ -585,6 +754,12 @@ static const TypeInfo stm32l4x5_rcc_types[] = {
         .instance_size = sizeof(RccClockMuxState),
         .instance_init = clock_mux_init,
         .class_init = clock_mux_class_init,
+    }, {
+        .name = TYPE_RCC_PLL,
+        .parent = TYPE_DEVICE,
+        .instance_size = sizeof(RccPllState),
+        .instance_init = pll_init,
+        .class_init = pll_class_init,
     }
 };
 
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index d5e471811c..1b6054d88a 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -182,6 +182,11 @@ stm32l4x5_rcc_mux_disable(uint32_t mux_id) "RCC: Mux %d disabled"
 stm32l4x5_rcc_mux_set_factor(uint32_t mux_id, uint32_t old_multiplier, uint32_t new_multiplier, uint32_t old_divider, uint32_t new_divider) "RCC: Mux %d factor changed: multiplier (%u -> %u), divider (%u -> %u)"
 stm32l4x5_rcc_mux_set_src(uint32_t mux_id, uint32_t old_src, uint32_t new_src) "RCC: Mux %d source changed: from %u to %u"
 stm32l4x5_rcc_mux_update(uint32_t mux_id, uint32_t src, uint64_t src_freq, uint64_t new_freq) "RCC: Mux %d src %d update: src_freq %" PRIu64 " new_freq %" PRIu64 ""
+stm32l4x5_rcc_pll_set_vco_multiplier(uint32_t pll_id, uint32_t old_multiplier, uint32_t new_multiplier) "RCC: PLL %u: vco_multiplier changed (%u -> %u)"
+stm32l4x5_rcc_pll_channel_enable(uint32_t pll_id, uint32_t channel_id) "RCC: PLL %u, channel %u enabled"
+stm32l4x5_rcc_pll_channel_disable(uint32_t pll_id, uint32_t channel_id) "RCC: PLL %u, channel %u disabled"
+stm32l4x5_rcc_pll_set_channel_divider(uint32_t pll_id, uint32_t channel_id, uint32_t old_divider, uint32_t new_divider) "RCC: PLL %u, channel %u: divider changed (%u -> %u)"
+stm32l4x5_rcc_pll_update(uint32_t pll_id, uint32_t channel_id, uint64_t vco_freq, uint64_t old_freq, uint64_t new_freq) "RCC: PLL %d channel %d update: vco_freq %" PRIu64 " old_freq %" PRIu64 " new_freq %" PRIu64 ""
 
 # tz-mpc.c
 tz_mpc_reg_read(uint32_t offset, uint64_t data, unsigned size) "TZ MPC regs read: offset 0x%x data 0x%" PRIx64 " size %u"
diff --git a/include/hw/misc/stm32l4x5_rcc.h b/include/hw/misc/stm32l4x5_rcc.h
index 6719be9fbe..0fbfba5c40 100644
--- a/include/hw/misc/stm32l4x5_rcc.h
+++ b/include/hw/misc/stm32l4x5_rcc.h
@@ -26,6 +26,15 @@ OBJECT_DECLARE_SIMPLE_TYPE(Stm32l4x5RccState, STM32L4X5_RCC)
 
 /* In the Stm32l4x5 clock tree, mux have at most 7 sources */
 #define RCC_NUM_CLOCK_MUX_SRC 7
+
+typedef enum PllCommonChannels {
+    RCC_PLL_COMMON_CHANNEL_P = 0,
+    RCC_PLL_COMMON_CHANNEL_Q = 1,
+    RCC_PLL_COMMON_CHANNEL_R = 2,
+
+    RCC_NUM_CHANNEL_PLL_OUT = 3
+} PllCommonChannels;
+
 /* NB: Prescaler are assimilated to mux with one source and one output */
 typedef enum RccClockMux {
     /* Internal muxes that arent't exposed publicly to other peripherals */
@@ -124,6 +133,14 @@ typedef enum RccClockMux {
     RCC_NUM_CLOCK_MUX
 } RccClockMux;
 
+typedef enum RccPll {
+    RCC_PLL_PLL,
+    RCC_PLL_PLLSAI1,
+    RCC_PLL_PLLSAI2,
+
+    RCC_NUM_PLL
+} RccPll;
+
 typedef struct RccClockMuxState {
     DeviceState parent_obj;
 
@@ -142,6 +159,26 @@ typedef struct RccClockMuxState {
     struct RccClockMuxState *backref[RCC_NUM_CLOCK_MUX_SRC];
 } RccClockMuxState;
 
+typedef struct RccPllState {
+    DeviceState parent_obj;
+
+    RccPll id;
+    Clock *in;
+    uint32_t vco_multiplier;
+    Clock *channels[RCC_NUM_CHANNEL_PLL_OUT];
+    /* Global pll enabled flag */
+    bool enabled;
+    /* 'enabled' refers to the runtime configuration */
+    bool channel_enabled[RCC_NUM_CHANNEL_PLL_OUT];
+    /*
+     * 'exists' refers to the physical configuration
+     * It should only be set at pll initialization.
+     * e.g. pllsai2 doesn't have a Q output.
+     */
+    bool channel_exists[RCC_NUM_CHANNEL_PLL_OUT];
+    uint32_t channel_divider[RCC_NUM_CHANNEL_PLL_OUT];
+} RccPllState;
+
 struct Stm32l4x5RccState {
     SysBusDevice parent_obj;
 
@@ -187,6 +224,9 @@ struct Stm32l4x5RccState {
     Clock *sai1_extclk;
     Clock *sai2_extclk;
 
+    /* PLLs */
+    RccPllState plls[RCC_NUM_PLL];
+
     /* Muxes ~= outputs */
     RccClockMuxState clock_muxes[RCC_NUM_CLOCK_MUX];
 
diff --git a/include/hw/misc/stm32l4x5_rcc_internals.h b/include/hw/misc/stm32l4x5_rcc_internals.h
index 4aa836848b..a9da5e3be7 100644
--- a/include/hw/misc/stm32l4x5_rcc_internals.h
+++ b/include/hw/misc/stm32l4x5_rcc_internals.h
@@ -22,7 +22,10 @@
 #include "hw/misc/stm32l4x5_rcc.h"
 
 #define TYPE_RCC_CLOCK_MUX "stm32l4x5-rcc-clock-mux"
+#define TYPE_RCC_PLL "stm32l4x5-rcc-pll"
+
 OBJECT_DECLARE_SIMPLE_TYPE(RccClockMuxState, RCC_CLOCK_MUX)
+OBJECT_DECLARE_SIMPLE_TYPE(RccPllState, RCC_PLL)
 
 /* Register map */
 REG32(CR, 0x00)
@@ -285,6 +288,25 @@ REG32(CSR, 0x94)
                             R_CSR_FWRSTF_MASK   | \
                             R_CSR_LSIRDY_MASK)
 
+/* Pll Channels */
+enum PllChannels {
+    RCC_PLL_CHANNEL_PLLSAI3CLK = 0,
+    RCC_PLL_CHANNEL_PLL48M1CLK = 1,
+    RCC_PLL_CHANNEL_PLLCLK = 2,
+};
+
+enum PllSai1Channels {
+    RCC_PLLSAI1_CHANNEL_PLLSAI1CLK = 0,
+    RCC_PLLSAI1_CHANNEL_PLL48M2CLK = 1,
+    RCC_PLLSAI1_CHANNEL_PLLADC1CLK = 2,
+};
+
+enum PllSai2Channels {
+    RCC_PLLSAI2_CHANNEL_PLLSAI2CLK = 0,
+    /* No Q channel */
+    RCC_PLLSAI2_CHANNEL_PLLADC2CLK = 2,
+};
+
 typedef enum RccClockMuxSource {
     RCC_CLOCK_MUX_SRC_GND = 0,
     RCC_CLOCK_MUX_SRC_HSI,
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v4 4/8] Add initialization information for PLLs and clock multiplexers
  2024-01-30 16:06 [PATCH v4 0/8] Add device STM32L4x5 RCC Arnaud Minier
                   ` (2 preceding siblings ...)
  2024-01-30 16:06 ` [PATCH v4 3/8] Add an internal PLL Clock object Arnaud Minier
@ 2024-01-30 16:06 ` Arnaud Minier
  2024-01-30 16:06 ` [PATCH v4 5/8] RCC: Handle Register Updates Arnaud Minier
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Arnaud Minier @ 2024-01-30 16:06 UTC (permalink / raw)
  To: qemu-devel
  Cc: Laurent Vivier, Alistair Francis, Inès Varhol,
	Peter Maydell, Paolo Bonzini, Thomas Huth, qemu-arm,
	Arnaud Minier

Instanciate the whole clock tree and using the Clock multiplexers and
the PLLs defined in the previous commits. This allows to statically
define the clock tree and easily follow the clock signal from one end to
another.

Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
---
 hw/misc/stm32l4x5_rcc.c                   |  81 ++-
 include/hw/misc/stm32l4x5_rcc_internals.h | 705 ++++++++++++++++++++++
 2 files changed, 782 insertions(+), 4 deletions(-)

diff --git a/hw/misc/stm32l4x5_rcc.c b/hw/misc/stm32l4x5_rcc.c
index fb0233c3e9..aed61dd793 100644
--- a/hw/misc/stm32l4x5_rcc.c
+++ b/hw/misc/stm32l4x5_rcc.c
@@ -93,7 +93,11 @@ static void clock_mux_init(Object *obj)
 }
 
 static void clock_mux_reset_hold(Object *obj)
-{ }
+{
+    RccClockMuxState *s = RCC_CLOCK_MUX(obj);
+    set_clock_mux_init_info(s, s->id);
+    clock_mux_update(s);
+}
 
 static const VMStateDescription clock_mux_vmstate = {
     .name = TYPE_RCC_CLOCK_MUX,
@@ -221,7 +225,11 @@ static void pll_init(Object *obj)
 }
 
 static void pll_reset_hold(Object *obj)
-{ }
+{
+    RccPllState *s = RCC_PLL(obj);
+    set_pll_init_info(s, s->id);
+    pll_update(s);
+}
 
 static const VMStateDescription pll_vmstate = {
     .name = TYPE_RCC_PLL,
@@ -616,21 +624,79 @@ static void stm32l4x5_rcc_init(Object *obj)
     qdev_init_clocks(DEVICE(s), stm32l4x5_rcc_clocks);
 
     for (i = 0; i < RCC_NUM_PLL; i++) {
-        object_initialize_child(obj, "pll[*]",
+        object_initialize_child(obj, PLL_INIT_INFO[i].name,
                                 &s->plls[i], TYPE_RCC_PLL);
+        set_pll_init_info(&s->plls[i], i);
     }
 
     for (i = 0; i < RCC_NUM_CLOCK_MUX; i++) {
+        char *alias;
 
-        object_initialize_child(obj, "clock[*]",
+        object_initialize_child(obj, CLOCK_MUX_INIT_INFO[i].name,
                                 &s->clock_muxes[i],
                                 TYPE_RCC_CLOCK_MUX);
+        set_clock_mux_init_info(&s->clock_muxes[i], i);
 
+        if (!CLOCK_MUX_INIT_INFO[i].hidden) {
+            /* Expose muxes output as RCC outputs */
+            alias = g_strdup_printf("%s-out", CLOCK_MUX_INIT_INFO[i].name);
+            qdev_alias_clock(DEVICE(&s->clock_muxes[i]), "out", DEVICE(obj), alias);
+            g_free(alias);
+        }
     }
 
     s->gnd = clock_new(obj, "gnd");
 }
 
+static void connect_mux_sources(Stm32l4x5RccState *s,
+                                RccClockMuxState *mux,
+                                const RccClockMuxSource *clk_mapping)
+{
+    size_t i;
+
+    Clock * const CLK_SRC_MAPPING[] = {
+        [RCC_CLOCK_MUX_SRC_GND] = s->gnd,
+        [RCC_CLOCK_MUX_SRC_HSI] = s->hsi16_rc,
+        [RCC_CLOCK_MUX_SRC_HSE] = s->hse,
+        [RCC_CLOCK_MUX_SRC_MSI] = s->msi_rc,
+        [RCC_CLOCK_MUX_SRC_LSI] = s->lsi_rc,
+        [RCC_CLOCK_MUX_SRC_LSE] = s->lse_crystal,
+        [RCC_CLOCK_MUX_SRC_SAI1_EXTCLK] = s->sai1_extclk,
+        [RCC_CLOCK_MUX_SRC_SAI2_EXTCLK] = s->sai2_extclk,
+        [RCC_CLOCK_MUX_SRC_PLL] =
+            s->plls[RCC_PLL_PLL].channels[RCC_PLL_CHANNEL_PLLCLK],
+        [RCC_CLOCK_MUX_SRC_PLLSAI1] =
+            s->plls[RCC_PLL_PLLSAI1].channels[RCC_PLLSAI1_CHANNEL_PLLSAI1CLK],
+        [RCC_CLOCK_MUX_SRC_PLLSAI2] =
+            s->plls[RCC_PLL_PLLSAI2].channels[RCC_PLLSAI2_CHANNEL_PLLSAI2CLK],
+        [RCC_CLOCK_MUX_SRC_PLLSAI3] =
+            s->plls[RCC_PLL_PLL].channels[RCC_PLL_CHANNEL_PLLSAI3CLK],
+        [RCC_CLOCK_MUX_SRC_PLL48M1] =
+            s->plls[RCC_PLL_PLL].channels[RCC_PLL_CHANNEL_PLL48M1CLK],
+        [RCC_CLOCK_MUX_SRC_PLL48M2] =
+            s->plls[RCC_PLL_PLLSAI1].channels[RCC_PLLSAI1_CHANNEL_PLL48M2CLK],
+        [RCC_CLOCK_MUX_SRC_PLLADC1] =
+            s->plls[RCC_PLL_PLLSAI1].channels[RCC_PLLSAI1_CHANNEL_PLLADC1CLK],
+        [RCC_CLOCK_MUX_SRC_PLLADC2] =
+            s->plls[RCC_PLL_PLLSAI2] .channels[RCC_PLLSAI2_CHANNEL_PLLADC2CLK],
+        [RCC_CLOCK_MUX_SRC_SYSCLK] = s->clock_muxes[RCC_CLOCK_MUX_SYSCLK].out,
+        [RCC_CLOCK_MUX_SRC_HCLK] = s->clock_muxes[RCC_CLOCK_MUX_HCLK].out,
+        [RCC_CLOCK_MUX_SRC_PCLK1] = s->clock_muxes[RCC_CLOCK_MUX_PCLK1].out,
+        [RCC_CLOCK_MUX_SRC_PCLK2] = s->clock_muxes[RCC_CLOCK_MUX_PCLK2].out,
+        [RCC_CLOCK_MUX_SRC_HSE_OVER_32] = s->clock_muxes[RCC_CLOCK_MUX_HSE_OVER_32].out,
+        [RCC_CLOCK_MUX_SRC_LCD_AND_RTC_COMMON] =
+            s->clock_muxes[RCC_CLOCK_MUX_LCD_AND_RTC_COMMON].out,
+    };
+
+    assert(ARRAY_SIZE(CLK_SRC_MAPPING) == RCC_CLOCK_MUX_SRC_NUMBER);
+
+    for (i = 0; i < RCC_NUM_CLOCK_MUX_SRC; i++) {
+        RccClockMuxSource mapping = clk_mapping[i];
+        clock_set_source(mux->srcs[i], CLK_SRC_MAPPING[mapping]);
+    }
+}
+
+
 static const VMStateDescription vmstate_stm32l4x5_rcc = {
     .name = TYPE_STM32L4X5_RCC,
     .version_id = 1,
@@ -696,11 +762,17 @@ static void stm32l4x5_rcc_realize(DeviceState *dev, Error **errp)
     for (i = 0; i < RCC_NUM_CLOCK_MUX; i++) {
         RccClockMuxState *clock_mux = &s->clock_muxes[i];
 
+        connect_mux_sources(s, clock_mux, CLOCK_MUX_INIT_INFO[i].src_mapping);
+
         if (!qdev_realize(DEVICE(clock_mux), NULL, errp)) {
             return;
         }
     }
 
+    /*
+     * Start clocks after everything is connected
+     * to propagate the frequencies along the tree.
+     */
     clock_update_hz(s->msi_rc, MSI_DEFAULT_FRQ);
     clock_update_hz(s->sai1_extclk, s->sai1_extclk_frequency);
     clock_update_hz(s->sai2_extclk, s->sai2_extclk_frequency);
@@ -734,6 +806,7 @@ static void stm32l4x5_rcc_class_init(ObjectClass *klass, void *data)
     DeviceClass *dc = DEVICE_CLASS(klass);
     ResettableClass *rc = RESETTABLE_CLASS(klass);
 
+    assert(ARRAY_SIZE(CLOCK_MUX_INIT_INFO) == RCC_NUM_CLOCK_MUX);
 
     rc->phases.hold = stm32l4x5_rcc_reset_hold;
     device_class_set_props(dc, stm32l4x5_rcc_properties);
diff --git a/include/hw/misc/stm32l4x5_rcc_internals.h b/include/hw/misc/stm32l4x5_rcc_internals.h
index a9da5e3be7..ff1c834f69 100644
--- a/include/hw/misc/stm32l4x5_rcc_internals.h
+++ b/include/hw/misc/stm32l4x5_rcc_internals.h
@@ -334,4 +334,709 @@ typedef enum RccClockMuxSource {
     RCC_CLOCK_MUX_SRC_NUMBER,
 } RccClockMuxSource;
 
+/* PLL init info */
+typedef struct PllInitInfo {
+    const char *name;
+
+    const char *channel_name[RCC_NUM_CHANNEL_PLL_OUT];
+    bool channel_exists[RCC_NUM_CHANNEL_PLL_OUT];
+    uint32_t default_channel_divider[RCC_NUM_CHANNEL_PLL_OUT];
+
+    RccClockMuxSource src_mapping[RCC_NUM_CLOCK_MUX_SRC];
+} PllInitInfo;
+
+static const PllInitInfo PLL_INIT_INFO[] = {
+    [RCC_PLL_PLL] = {
+        .name = "pll",
+        .channel_name = {
+            "pllsai3clk",
+            "pll48m1clk",
+            "pllclk"
+        },
+        .channel_exists = {
+            true, true, true
+        },
+        /* From PLLCFGR register documentation */
+        .default_channel_divider = {
+            7, 2, 2
+        }
+    },
+    [RCC_PLL_PLLSAI1] = {
+        .name = "pllsai1",
+        .channel_name = {
+            "pllsai1clk",
+            "pll48m2clk",
+            "plladc1clk"
+        },
+        .channel_exists = {
+            true, true, true
+        },
+        /* From PLLSAI1CFGR register documentation */
+        .default_channel_divider = {
+            7, 2, 2
+        }
+    },
+    [RCC_PLL_PLLSAI2] = {
+        .name = "pllsai2",
+        .channel_name = {
+            "pllsai2clk",
+            NULL,
+            "plladc2clk"
+        },
+        .channel_exists = {
+            true, false, true
+        },
+        /* From PLLSAI2CFGR register documentation */
+        .default_channel_divider = {
+            7, 0, 2
+        }
+    }
+};
+
+static inline void set_pll_init_info(RccPllState *pll,
+                                     RccPll id)
+{
+    int i;
+
+    pll->id = id;
+    pll->vco_multiplier = 1;
+    for (i = 0; i < RCC_NUM_CHANNEL_PLL_OUT; i++) {
+        pll->channel_enabled[i] = false;
+        pll->channel_exists[i] = PLL_INIT_INFO[id].channel_exists[i];
+        pll->channel_divider[i] = PLL_INIT_INFO[id].default_channel_divider[i];
+    }
+}
+
+/* Clock mux init info */
+typedef struct ClockMuxInitInfo {
+    const char *name;
+
+    uint32_t multiplier;
+    uint32_t divider;
+    bool enabled;
+    /* If this is true, the clock will not be exposed outside of the device */
+    bool hidden;
+
+    RccClockMuxSource src_mapping[RCC_NUM_CLOCK_MUX_SRC];
+} ClockMuxInitInfo;
+
+#define FILL_DEFAULT_FACTOR \
+    .multiplier = 1, \
+    .divider =  1
+
+#define FILL_DEFAULT_INIT_ENABLED \
+    FILL_DEFAULT_FACTOR, \
+    .enabled = true
+
+#define FILL_DEFAULT_INIT_DISABLED \
+    FILL_DEFAULT_FACTOR, \
+    .enabled = false
+
+
+static const ClockMuxInitInfo CLOCK_MUX_INIT_INFO[] = {
+    [RCC_CLOCK_MUX_SYSCLK] = {
+        .name = "sysclk",
+        /* Same mapping as: CFGR_SW */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_MSI,
+            RCC_CLOCK_MUX_SRC_HSI,
+            RCC_CLOCK_MUX_SRC_HSE,
+            RCC_CLOCK_MUX_SRC_PLL,
+        },
+        .hidden = true,
+        FILL_DEFAULT_INIT_ENABLED,
+    },
+    [RCC_CLOCK_MUX_PLL_INPUT] = {
+        .name = "pll-input",
+        /* Same mapping as: PLLCFGR_PLLSRC */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_MSI,
+            RCC_CLOCK_MUX_SRC_HSI,
+            RCC_CLOCK_MUX_SRC_HSE,
+        },
+        .hidden = true,
+        FILL_DEFAULT_INIT_ENABLED,
+    },
+    [RCC_CLOCK_MUX_HCLK] = {
+        .name = "hclk",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        .hidden = true,
+        FILL_DEFAULT_INIT_ENABLED,
+    },
+    [RCC_CLOCK_MUX_PCLK1] = {
+        .name = "pclk1",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_HCLK,
+        },
+        .hidden = true,
+        FILL_DEFAULT_INIT_ENABLED,
+    },
+    [RCC_CLOCK_MUX_PCLK2] = {
+        .name = "pclk2",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_HCLK,
+        },
+        .hidden = true,
+        FILL_DEFAULT_INIT_ENABLED,
+    },
+    [RCC_CLOCK_MUX_HSE_OVER_32] = {
+        .name = "hse-divided-by-32",
+        .multiplier = 1,
+        .divider = 32,
+        .enabled = true,
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_HSE,
+        },
+        .hidden = true,
+    },
+    [RCC_CLOCK_MUX_LCD_AND_RTC_COMMON] = {
+        .name = "lcd-and-rtc-common-mux",
+        /* Same mapping as: BDCR_RTCSEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_GND,
+            RCC_CLOCK_MUX_SRC_LSE,
+            RCC_CLOCK_MUX_SRC_LSI,
+            RCC_CLOCK_MUX_SRC_HSE_OVER_32,
+        },
+        .hidden = true,
+        FILL_DEFAULT_INIT_ENABLED,
+    },
+    /* From now on, muxes with a publicly available output */
+    [RCC_CLOCK_MUX_CORTEX_REFCLK] = {
+        .name = "cortex-refclk",
+        .multiplier = 1,
+        /* REFCLK is always HCLK/8 */
+        .divider = 8,
+        .enabled = true,
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_HCLK,
+        }
+    },
+    [RCC_CLOCK_MUX_USART1] = {
+        .name = "usart1",
+        /* Same mapping as: CCIPR_USART1SEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK2,
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+            RCC_CLOCK_MUX_SRC_HSI,
+            RCC_CLOCK_MUX_SRC_LSE,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_USART2] = {
+        .name = "usart2",
+        /* Same mapping as: CCIPR_USART2SEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+            RCC_CLOCK_MUX_SRC_HSI,
+            RCC_CLOCK_MUX_SRC_LSE,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_USART3] = {
+        .name = "usart3",
+        /* Same mapping as: CCIPR_USART3SEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+            RCC_CLOCK_MUX_SRC_HSI,
+            RCC_CLOCK_MUX_SRC_LSE,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_UART4] = {
+        .name = "uart4",
+        /* Same mapping as: CCIPR_UART4SEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+            RCC_CLOCK_MUX_SRC_HSI,
+            RCC_CLOCK_MUX_SRC_LSE,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_UART5] = {
+        .name = "uart5",
+        /* Same mapping as: CCIPR_UART5SEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+            RCC_CLOCK_MUX_SRC_HSI,
+            RCC_CLOCK_MUX_SRC_LSE,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_LPUART1] = {
+        .name = "lpuart1",
+        /* Same mapping as: CCIPR_LPUART1SEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+            RCC_CLOCK_MUX_SRC_HSI,
+            RCC_CLOCK_MUX_SRC_LSE,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_I2C1] = {
+        .name = "i2c1",
+        /* Same mapping as: CCIPR_I2C1SEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+            RCC_CLOCK_MUX_SRC_HSI,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_I2C2] = {
+        .name = "i2c2",
+        /* Same mapping as: CCIPR_I2C2SEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+            RCC_CLOCK_MUX_SRC_HSI,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_I2C3] = {
+        .name = "i2c3",
+        /* Same mapping as: CCIPR_I2C3SEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+            RCC_CLOCK_MUX_SRC_HSI,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_LPTIM1] = {
+        .name = "lptim1",
+        /* Same mapping as: CCIPR_LPTIM1SEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+            RCC_CLOCK_MUX_SRC_LSI,
+            RCC_CLOCK_MUX_SRC_HSI,
+            RCC_CLOCK_MUX_SRC_LSE,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_LPTIM2] = {
+        .name = "lptim2",
+        /* Same mapping as: CCIPR_LPTIM2SEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+            RCC_CLOCK_MUX_SRC_LSI,
+            RCC_CLOCK_MUX_SRC_HSI,
+            RCC_CLOCK_MUX_SRC_LSE,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_SWPMI1] = {
+        .name = "swpmi1",
+        /* Same mapping as: CCIPR_SWPMI1SEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+            RCC_CLOCK_MUX_SRC_HSI,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_MCO] = {
+        .name = "mco",
+        /* Same mapping as: CFGR_MCOSEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+            RCC_CLOCK_MUX_SRC_MSI,
+            RCC_CLOCK_MUX_SRC_HSI,
+            RCC_CLOCK_MUX_SRC_HSE,
+            RCC_CLOCK_MUX_SRC_PLL,
+            RCC_CLOCK_MUX_SRC_LSI,
+            RCC_CLOCK_MUX_SRC_LSE,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_LSCO] = {
+        .name = "lsco",
+        /* Same mapping as: BDCR_LSCOSEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_LSI,
+            RCC_CLOCK_MUX_SRC_LSE,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_DFSDM1] = {
+        .name = "dfsdm1",
+        /* Same mapping as: CCIPR_DFSDM1SEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK2,
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_ADC] = {
+        .name = "adc",
+        /* Same mapping as: CCIPR_ADCSEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_GND,
+            RCC_CLOCK_MUX_SRC_PLLADC1,
+            RCC_CLOCK_MUX_SRC_PLLADC2,
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_CLK48] = {
+        .name = "clk48",
+        /* Same mapping as: CCIPR_CLK48SEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_GND,
+            RCC_CLOCK_MUX_SRC_PLL48M2,
+            RCC_CLOCK_MUX_SRC_PLL48M1,
+            RCC_CLOCK_MUX_SRC_MSI,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_SAI2] = {
+        .name = "sai2",
+        /* Same mapping as: CCIPR_SAI2SEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PLLSAI1,
+            RCC_CLOCK_MUX_SRC_PLLSAI2,
+            RCC_CLOCK_MUX_SRC_PLLSAI3,
+            RCC_CLOCK_MUX_SRC_SAI2_EXTCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_SAI1] = {
+        .name = "sai1",
+        /* Same mapping as: CCIPR_SAI1SEL */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PLLSAI1,
+            RCC_CLOCK_MUX_SRC_PLLSAI2,
+            RCC_CLOCK_MUX_SRC_PLLSAI3,
+            RCC_CLOCK_MUX_SRC_SAI1_EXTCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    /* From now on, these muxes only have one valid source */
+    [RCC_CLOCK_MUX_TSC] = {
+        .name = "tsc",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_CRC] = {
+        .name = "crc",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_FLASH] = {
+        .name = "flash",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_DMA2] = {
+        .name = "dma2",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_DMA1] = {
+        .name = "dma1",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_RNG] = {
+        .name = "rng",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_AES] = {
+        .name = "aes",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_OTGFS] = {
+        .name = "otgfs",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_GPIOA] = {
+        .name = "gpioa",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_GPIOB] = {
+        .name = "gpiob",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_GPIOC] = {
+        .name = "gpioc",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_GPIOD] = {
+        .name = "gpiod",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_GPIOE] = {
+        .name = "gpioe",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_GPIOF] = {
+        .name = "gpiof",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_GPIOG] = {
+        .name = "gpiog",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_GPIOH] = {
+        .name = "gpioh",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_QSPI] = {
+        .name = "qspi",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_FMC] = {
+        .name = "fmc",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_OPAMP] = {
+        .name = "opamp",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_DAC1] = {
+        .name = "dac1",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_PWR] = {
+        .name = "pwr",
+        /*
+         * PWREN is in the APB1ENR1 register,
+         * but PWR uses SYSCLK according to the clock tree.
+         */
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_SYSCLK,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_CAN1] = {
+        .name = "can1",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_SPI3] = {
+        .name = "spi3",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_SPI2] = {
+        .name = "spi2",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_WWDG] = {
+        .name = "wwdg",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_LCD] = {
+        .name = "lcd",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_LCD_AND_RTC_COMMON,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_TIM7] = {
+        .name = "tim7",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_TIM6] = {
+        .name = "tim6",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_TIM5] = {
+        .name = "tim5",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_TIM4] = {
+        .name = "tim4",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_TIM3] = {
+        .name = "tim3",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_TIM2] = {
+        .name = "tim2",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK1,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_TIM17] = {
+        .name = "tim17",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK2,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_TIM16] = {
+        .name = "tim16",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK2,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_TIM15] = {
+        .name = "tim15",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK2,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_TIM8] = {
+        .name = "tim8",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK2,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_SPI1] = {
+        .name = "spi1",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK2,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_TIM1] = {
+        .name = "tim1",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK2,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_SDMMC1] = {
+        .name = "sdmmc1",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK2,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_FW] = {
+        .name = "fw",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK2,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_SYSCFG] = {
+        .name = "syscfg",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_PCLK2,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_RTC] = {
+        .name = "rtc",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_LCD_AND_RTC_COMMON,
+        },
+        FILL_DEFAULT_INIT_DISABLED,
+    },
+    [RCC_CLOCK_MUX_CORTEX_FCLK] = {
+        .name = "cortex-fclk",
+        .src_mapping = {
+            RCC_CLOCK_MUX_SRC_HCLK,
+        },
+        FILL_DEFAULT_INIT_ENABLED,
+    },
+};
+
+static inline void set_clock_mux_init_info(RccClockMuxState *mux,
+                                           RccClockMux id)
+{
+    mux->id = id;
+    mux->multiplier = CLOCK_MUX_INIT_INFO[id].multiplier;
+    mux->divider = CLOCK_MUX_INIT_INFO[id].divider;
+    mux->enabled = CLOCK_MUX_INIT_INFO[id].enabled;
+    /*
+     * Every peripheral has the first source of their source list as
+     * as their default source.
+     */
+    mux->src = 0;
+}
+
 #endif /* HW_STM32L4X5_RCC_INTERNALS_H */
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v4 5/8] RCC: Handle Register Updates
  2024-01-30 16:06 [PATCH v4 0/8] Add device STM32L4x5 RCC Arnaud Minier
                   ` (3 preceding siblings ...)
  2024-01-30 16:06 ` [PATCH v4 4/8] Add initialization information for PLLs and clock multiplexers Arnaud Minier
@ 2024-01-30 16:06 ` Arnaud Minier
  2024-01-30 16:06 ` [PATCH v4 6/8] Add write protections to CR register Arnaud Minier
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Arnaud Minier @ 2024-01-30 16:06 UTC (permalink / raw)
  To: qemu-devel
  Cc: Laurent Vivier, Alistair Francis, Inès Varhol,
	Peter Maydell, Paolo Bonzini, Thomas Huth, qemu-arm,
	Arnaud Minier

Update the RCC state and propagate frequency changes when writing to the
RCC registers. Currently, ICSCR, CIER, the reset registers and the stop
mode registers are not implemented.

Some fields  have not been implemented due to uncertainty about
how to handle them (Like the clock security system or bypassing
mecanisms).

Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
---
 hw/misc/stm32l4x5_rcc.c | 536 +++++++++++++++++++++++++++++++++++++++-
 1 file changed, 524 insertions(+), 12 deletions(-)

diff --git a/hw/misc/stm32l4x5_rcc.c b/hw/misc/stm32l4x5_rcc.c
index aed61dd793..3bf4b41552 100644
--- a/hw/misc/stm32l4x5_rcc.c
+++ b/hw/misc/stm32l4x5_rcc.c
@@ -36,6 +36,19 @@
 #define LSE_FRQ 32768ULL
 #define LSI_FRQ 32000ULL
 
+/*
+ * Extract the bits of the field designated by `_bit_field_name` from the
+ * register `_register_name`, stored in the field `_struct_field_name` of the
+ * Stm32l4x5RccState struct named `_struct`.
+ *
+ * Usually, `_struct_field_name` is the lowercase version of `_register_name`.
+ */
+#define REGISTER_EXTRACT(_struct_name, _struct_field_name, \
+                         _register_name, _bit_field_name) \
+        extract32(_struct_name->_struct_field_name, \
+                  R_##_register_name##_##_bit_field_name##_SHIFT, \
+                  R_##_register_name##_##_bit_field_name##_LENGTH)
+
 static void clock_mux_update(RccClockMuxState *mux)
 {
     uint64_t src_freq, old_freq, freq;
@@ -322,6 +335,9 @@ static void pll_set_channel_divider(RccPllState *pll,
 
 static void rcc_update_irq(Stm32l4x5RccState *s)
 {
+    /*
+     * TODO: Handle LSECSSF and CSSF flags when the CSS is implemented.
+     */
     if (s->cifr & CIFR_IRQ_MASK) {
         qemu_irq_raise(s->irq);
     } else {
@@ -329,6 +345,472 @@ static void rcc_update_irq(Stm32l4x5RccState *s)
     }
 }
 
+static void rcc_update_cr_register(Stm32l4x5RccState *s)
+{
+    int val;
+
+    /* PLLSAI2ON and update PLLSAI2RDY */
+    val = extract32(s->cr, R_CR_PLLSAI2ON_SHIFT, R_CR_PLLSAI2ON_LENGTH);
+    pll_set_enable(&s->plls[RCC_PLL_PLLSAI2], val);
+    s->cr = (s->cr & ~R_CR_PLLSAI2RDY_MASK) |
+            (val << R_CR_PLLSAI2RDY_SHIFT);
+    if (s->cier & R_CIER_PLLSAI2RDYIE_MASK) {
+        s->cifr |= R_CIFR_PLLSAI2RDYF_MASK;
+    }
+
+    /* PLLSAI1ON and update PLLSAI1RDY */
+    val = extract32(s->cr, R_CR_PLLSAI1ON_SHIFT, R_CR_PLLSAI1ON_LENGTH);
+    pll_set_enable(&s->plls[RCC_PLL_PLLSAI1], val);
+    s->cr = (s->cr & ~R_CR_PLLSAI1RDY_MASK) |
+            (val << R_CR_PLLSAI1RDY_SHIFT);
+    if (s->cier & R_CIER_PLLSAI1RDYIE_MASK) {
+        s->cifr |= R_CIFR_PLLSAI1RDYF_MASK;
+    }
+
+    /* PLLON and update PLLRDY */
+    val = extract32(s->cr, R_CR_PLLON_SHIFT, R_CR_PLLON_LENGTH);
+    pll_set_enable(&s->plls[RCC_PLL_PLL], val);
+    s->cr = (s->cr & ~R_CR_PLLRDY_MASK) |
+            (val << R_CR_PLLRDY_SHIFT);
+    if (s->cier & R_CIER_PLLRDYIE_MASK) {
+        s->cifr |= R_CIFR_PLLRDYF_MASK;
+    }
+
+    /* CSSON: TODO */
+    /* HSEBYP: TODO */
+
+    /* HSEON and update HSERDY */
+    val = extract32(s->cr, R_CR_HSEON_SHIFT, R_CR_HSEON_LENGTH);
+    s->cr = (s->cr & ~R_CR_HSERDY_MASK) |
+            (val << R_CR_HSERDY_SHIFT);
+    if (val) {
+        clock_update_hz(s->hse, s->hse_frequency);
+        if (s->cier & R_CIER_HSERDYIE_MASK) {
+            s->cifr |= R_CIFR_HSERDYF_MASK;
+        }
+    } else {
+        clock_update_hz(s->hse, 0);
+    }
+
+    /* HSIAFS: TODO*/
+    /* HSIKERON: TODO*/
+
+    /* HSION and update HSIRDY*/
+    val = extract32(s->cr, R_CR_HSION_SHIFT, R_CR_HSION_LENGTH);
+    s->cr = (s->cr & ~R_CR_HSIRDY_MASK) |
+            (val << R_CR_HSIRDY_SHIFT);
+    if (val) {
+        clock_update_hz(s->hsi16_rc, HSI_FRQ);
+        if (s->cier & R_CIER_HSIRDYIE_MASK) {
+            s->cifr |= R_CIFR_HSIRDYF_MASK;
+        }
+    } else {
+        clock_update_hz(s->hsi16_rc, 0);
+    }
+
+    static const uint32_t msirange[] = {
+        100000, 200000, 400000, 800000, 1000000, 2000000,
+        4000000, 8000000, 16000000, 24000000, 32000000, 48000000
+    };
+    /* MSIRANGE and MSIRGSEL */
+    val = extract32(s->cr, R_CR_MSIRGSEL_SHIFT, R_CR_MSIRGSEL_LENGTH);
+    if (val) {
+        /* MSIRGSEL is set, use the MSIRANGE field */
+        val = extract32(s->cr, R_CR_MSIRANGE_SHIFT, R_CR_MSIRANGE_LENGTH);
+    } else {
+        /* MSIRGSEL is not set, use the MSISRANGE field */
+        val = extract32(s->csr, R_CSR_MSISRANGE_SHIFT, R_CSR_MSISRANGE_LENGTH);
+    }
+
+    if (val < ARRAY_SIZE(msirange)) {
+        clock_update_hz(s->msi_rc, msirange[val]);
+    } else {
+        clock_update_hz(s->msi_rc, MSI_DEFAULT_FRQ);
+        /* TODO: there is a write protection if the value is out of bound,
+           implement that instead of setting the default */
+    }
+
+    /* MSIPLLEN */
+
+    /* MSION and update MSIRDY */
+    val = extract32(s->cr, R_CR_MSION_SHIFT, R_CR_MSION_LENGTH);
+    s->cr = (s->cr & ~R_CR_MSIRDY_MASK) |
+            (val << R_CR_MSIRDY_SHIFT);
+    if (s->cier & R_CIER_MSIRDYIE_MASK) {
+        s->cifr |= R_CIFR_MSIRDYF_MASK;
+    }
+    rcc_update_irq(s);
+}
+
+static void rcc_update_cfgr_register(Stm32l4x5RccState *s)
+{
+    uint32_t val;
+    /* MCOPRE */
+    val = extract32(s->cfgr, R_CFGR_MCOPRE_SHIFT, R_CFGR_MCOPRE_LENGTH);
+    assert(val <= 0b100);
+    clock_mux_set_factor(&s->clock_muxes[RCC_CLOCK_MUX_MCO],
+                         1, 1 << val);
+
+    /* MCOSEL */
+    val = extract32(s->cfgr, R_CFGR_MCOSEL_SHIFT, R_CFGR_MCOSEL_LENGTH);
+    assert(val <= 0b111);
+    if (val == 0) {
+        clock_mux_set_enable(&s->clock_muxes[RCC_CLOCK_MUX_MCO], false);
+    } else {
+        clock_mux_set_enable(&s->clock_muxes[RCC_CLOCK_MUX_MCO], true);
+        clock_mux_set_source(&s->clock_muxes[RCC_CLOCK_MUX_MCO],
+                             val - 1);
+    }
+
+    /* STOPWUCK */
+    /* TODO */
+
+    /* PPRE2 */
+    val = extract32(s->cfgr, R_CFGR_PPRE2_SHIFT, R_CFGR_PPRE2_LENGTH);
+    if (val < 0b100) {
+        clock_mux_set_factor(&s->clock_muxes[RCC_CLOCK_MUX_PCLK2],
+                             1, 1);
+    } else {
+        clock_mux_set_factor(&s->clock_muxes[RCC_CLOCK_MUX_PCLK2],
+                             1, 1 << (val - 0b11));
+    }
+
+    /* PPRE1 */
+    val = extract32(s->cfgr, R_CFGR_PPRE1_SHIFT, R_CFGR_PPRE1_LENGTH);
+    if (val < 0b100) {
+        clock_mux_set_factor(&s->clock_muxes[RCC_CLOCK_MUX_PCLK1],
+                             1, 1);
+    } else {
+        clock_mux_set_factor(&s->clock_muxes[RCC_CLOCK_MUX_PCLK1],
+                             1, 1 << (val - 0b11));
+    }
+
+    /* HPRE */
+    val = extract32(s->cfgr, R_CFGR_HPRE_SHIFT, R_CFGR_HPRE_LENGTH);
+    if (val < 0b1000) {
+        clock_mux_set_factor(&s->clock_muxes[RCC_CLOCK_MUX_HCLK],
+                             1, 1);
+    } else {
+        clock_mux_set_factor(&s->clock_muxes[RCC_CLOCK_MUX_HCLK],
+                             1, 1 << (val - 0b111));
+    }
+
+    /* Update SWS */
+    val = extract32(s->cfgr, R_CFGR_SW_SHIFT, R_CFGR_SW_LENGTH);
+    clock_mux_set_source(&s->clock_muxes[RCC_CLOCK_MUX_SYSCLK],
+                         val);
+    s->cfgr &= ~R_CFGR_SWS_MASK;
+    s->cfgr |= val << R_CFGR_SWS_SHIFT;
+}
+
+static void rcc_update_ahb1enr(Stm32l4x5RccState *s)
+{
+    #define AHB1ENR_SET_ENABLE(_peripheral_name) \
+        clock_mux_set_enable(&s->clock_muxes[RCC_CLOCK_MUX_##_peripheral_name], \
+            REGISTER_EXTRACT(s, ahb1enr, AHB1ENR, _peripheral_name##EN))
+
+    /* DMA2DEN: reserved for STM32L475xx */
+    AHB1ENR_SET_ENABLE(TSC);
+    AHB1ENR_SET_ENABLE(CRC);
+    AHB1ENR_SET_ENABLE(FLASH);
+    AHB1ENR_SET_ENABLE(DMA2);
+    AHB1ENR_SET_ENABLE(DMA1);
+
+    #undef AHB1ENR_SET_ENABLE
+}
+
+static void rcc_update_ahb2enr(Stm32l4x5RccState *s)
+{
+    #define AHB2ENR_SET_ENABLE(_peripheral_name) \
+        clock_mux_set_enable(&s->clock_muxes[RCC_CLOCK_MUX_##_peripheral_name], \
+            REGISTER_EXTRACT(s, ahb2enr, AHB2ENR, _peripheral_name##EN))
+
+    AHB2ENR_SET_ENABLE(RNG);
+    /* HASHEN: reserved for STM32L475xx */
+    AHB2ENR_SET_ENABLE(AES);
+    /* DCMIEN: reserved for STM32L475xx */
+    AHB2ENR_SET_ENABLE(ADC);
+    AHB2ENR_SET_ENABLE(OTGFS);
+    /* GPIOIEN: reserved for STM32L475xx */
+    AHB2ENR_SET_ENABLE(GPIOA);
+    AHB2ENR_SET_ENABLE(GPIOB);
+    AHB2ENR_SET_ENABLE(GPIOC);
+    AHB2ENR_SET_ENABLE(GPIOD);
+    AHB2ENR_SET_ENABLE(GPIOE);
+    AHB2ENR_SET_ENABLE(GPIOF);
+    AHB2ENR_SET_ENABLE(GPIOG);
+    AHB2ENR_SET_ENABLE(GPIOH);
+
+    #undef AHB2ENR_SET_ENABLE
+}
+
+static void rcc_update_ahb3enr(Stm32l4x5RccState *s)
+{
+    #define AHB3ENR_SET_ENABLE(_peripheral_name) \
+        clock_mux_set_enable(&s->clock_muxes[RCC_CLOCK_MUX_##_peripheral_name], \
+            REGISTER_EXTRACT(s, ahb3enr, AHB3ENR, _peripheral_name##EN))
+
+    AHB3ENR_SET_ENABLE(QSPI);
+    AHB3ENR_SET_ENABLE(FMC);
+
+    #undef AHB3ENR_SET_ENABLE
+}
+
+static void rcc_update_apb1enr(Stm32l4x5RccState *s)
+{
+    #define APB1ENR1_SET_ENABLE(_peripheral_name) \
+        clock_mux_set_enable(&s->clock_muxes[RCC_CLOCK_MUX_##_peripheral_name], \
+            REGISTER_EXTRACT(s, apb1enr1, APB1ENR1, _peripheral_name##EN))
+    #define APB1ENR2_SET_ENABLE(_peripheral_name) \
+        clock_mux_set_enable(&s->clock_muxes[RCC_CLOCK_MUX_##_peripheral_name], \
+            REGISTER_EXTRACT(s, apb1enr2, APB1ENR2, _peripheral_name##EN))
+
+    /* APB1ENR1 */
+    APB1ENR1_SET_ENABLE(LPTIM1);
+    APB1ENR1_SET_ENABLE(OPAMP);
+    APB1ENR1_SET_ENABLE(DAC1);
+    APB1ENR1_SET_ENABLE(PWR);
+    /* CAN2: reserved for STM32L4x5 */
+    APB1ENR1_SET_ENABLE(CAN1);
+    /* CRSEN: reserved for STM32L4x5 */
+    APB1ENR1_SET_ENABLE(I2C3);
+    APB1ENR1_SET_ENABLE(I2C2);
+    APB1ENR1_SET_ENABLE(I2C1);
+    APB1ENR1_SET_ENABLE(UART5);
+    APB1ENR1_SET_ENABLE(UART4);
+    APB1ENR1_SET_ENABLE(USART3);
+    APB1ENR1_SET_ENABLE(USART2);
+    APB1ENR1_SET_ENABLE(SPI3);
+    APB1ENR1_SET_ENABLE(SPI2);
+    APB1ENR1_SET_ENABLE(WWDG);
+    /* RTCAPB: reserved for STM32L4x5 */
+    APB1ENR1_SET_ENABLE(LCD);
+    APB1ENR1_SET_ENABLE(TIM7);
+    APB1ENR1_SET_ENABLE(TIM6);
+    APB1ENR1_SET_ENABLE(TIM5);
+    APB1ENR1_SET_ENABLE(TIM4);
+    APB1ENR1_SET_ENABLE(TIM3);
+    APB1ENR1_SET_ENABLE(TIM2);
+
+    /* APB1ENR2 */
+    APB1ENR2_SET_ENABLE(LPTIM2);
+    APB1ENR2_SET_ENABLE(SWPMI1);
+    /* I2C4EN: reserved for STM32L4x5 */
+    APB1ENR2_SET_ENABLE(LPUART1);
+
+    #undef APB1ENR1_SET_ENABLE
+    #undef APB1ENR2_SET_ENABLE
+}
+
+static void rcc_update_apb2enr(Stm32l4x5RccState *s)
+{
+    #define APB2ENR_SET_ENABLE(_peripheral_name) \
+        clock_mux_set_enable(&s->clock_muxes[RCC_CLOCK_MUX_##_peripheral_name], \
+            REGISTER_EXTRACT(s, apb2enr, APB2ENR, _peripheral_name##EN))
+
+    APB2ENR_SET_ENABLE(DFSDM1);
+    APB2ENR_SET_ENABLE(SAI2);
+    APB2ENR_SET_ENABLE(SAI1);
+    APB2ENR_SET_ENABLE(TIM17);
+    APB2ENR_SET_ENABLE(TIM16);
+    APB2ENR_SET_ENABLE(TIM15);
+    APB2ENR_SET_ENABLE(USART1);
+    APB2ENR_SET_ENABLE(TIM8);
+    APB2ENR_SET_ENABLE(SPI1);
+    APB2ENR_SET_ENABLE(TIM1);
+    APB2ENR_SET_ENABLE(SDMMC1);
+    APB2ENR_SET_ENABLE(FW);
+    APB2ENR_SET_ENABLE(SYSCFG);
+
+    #undef APB2ENR_SET_ENABLE
+}
+
+/*
+ * The 3 PLLs share the same register layout
+ * so we can use the same function for all of them
+ * Note: no frequency bounds checking is done here.
+ */
+static void rcc_update_pllsaixcfgr(Stm32l4x5RccState *s, RccPll pll_id)
+{
+    uint32_t reg, val;
+    switch (pll_id) {
+    case RCC_PLL_PLL:
+        reg = s->pllcfgr;
+        break;
+    case RCC_PLL_PLLSAI1:
+        reg = s->pllsai1cfgr;
+        break;
+    case RCC_PLL_PLLSAI2:
+        reg = s->pllsai2cfgr;
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: Invalid PLL ID: %u\n", __func__, pll_id);
+        return;
+    }
+
+    /* PLLPDIV */
+    val = extract32(reg, R_PLLCFGR_PLLPDIV_SHIFT, R_PLLCFGR_PLLPDIV_LENGTH);
+    /* 1 is a reserved value */
+    if (val == 0) {
+        /* Get PLLP value */
+        val = extract32(reg, R_PLLCFGR_PLLP_SHIFT, R_PLLCFGR_PLLP_LENGTH);
+        pll_set_channel_divider(&s->plls[pll_id], RCC_PLL_COMMON_CHANNEL_P,
+            (val ? 17 : 7));
+    } else if (val > 1) {
+        pll_set_channel_divider(&s->plls[pll_id], RCC_PLL_COMMON_CHANNEL_P,
+            val);
+    }
+
+
+    /* PLLR */
+    val = extract32(reg, R_PLLCFGR_PLLR_SHIFT, R_PLLCFGR_PLLR_LENGTH);
+    pll_set_channel_divider(&s->plls[pll_id], RCC_PLL_COMMON_CHANNEL_R,
+        2 * (val + 1));
+
+    /* PLLREN */
+    val = extract32(reg, R_PLLCFGR_PLLREN_SHIFT, R_PLLCFGR_PLLREN_LENGTH);
+    pll_set_channel_enable(&s->plls[pll_id], RCC_PLL_COMMON_CHANNEL_R, val);
+
+    /* PLLQ */
+    val = extract32(reg, R_PLLCFGR_PLLQ_SHIFT, R_PLLCFGR_PLLQ_LENGTH);
+    pll_set_channel_divider(&s->plls[pll_id], RCC_PLL_COMMON_CHANNEL_Q,
+        2 * (val + 1));
+
+    /* PLLQEN */
+    val = extract32(reg, R_PLLCFGR_PLLQEN_SHIFT, R_PLLCFGR_PLLQEN_LENGTH);
+    pll_set_channel_enable(&s->plls[pll_id], RCC_PLL_COMMON_CHANNEL_Q, val);
+
+    /* PLLPEN */
+    val = extract32(reg, R_PLLCFGR_PLLPEN_SHIFT, R_PLLCFGR_PLLPEN_LENGTH);
+    pll_set_channel_enable(&s->plls[pll_id], RCC_PLL_COMMON_CHANNEL_P, val);
+
+    /* PLLN */
+    val = extract32(reg, R_PLLCFGR_PLLN_SHIFT, R_PLLCFGR_PLLN_LENGTH);
+    pll_set_vco_multiplier(&s->plls[pll_id], val);
+}
+
+static void rcc_update_pllcfgr(Stm32l4x5RccState *s)
+{
+    int val;
+
+    /* Use common layout */
+    rcc_update_pllsaixcfgr(s, RCC_PLL_PLL);
+
+    /* Fetch specific fields for pllcfgr */
+
+    /* PLLM */
+    val = extract32(s->pllcfgr, R_PLLCFGR_PLLM_SHIFT, R_PLLCFGR_PLLM_LENGTH);
+    clock_mux_set_factor(&s->clock_muxes[RCC_CLOCK_MUX_PLL_INPUT], 1, (val + 1));
+
+    /* PLLSRC */
+    val = extract32(s->pllcfgr, R_PLLCFGR_PLLSRC_SHIFT, R_PLLCFGR_PLLSRC_LENGTH);
+    if (val == 0) {
+        clock_mux_set_enable(&s->clock_muxes[RCC_CLOCK_MUX_PLL_INPUT], false);
+    } else {
+        clock_mux_set_source(&s->clock_muxes[RCC_CLOCK_MUX_PLL_INPUT], val - 1);
+        clock_mux_set_enable(&s->clock_muxes[RCC_CLOCK_MUX_PLL_INPUT], true);
+    }
+}
+
+static void rcc_update_ccipr(Stm32l4x5RccState *s)
+{
+    #define CCIPR_SET_SOURCE(_peripheral_name) \
+        clock_mux_set_source(&s->clock_muxes[RCC_CLOCK_MUX_##_peripheral_name], \
+            REGISTER_EXTRACT(s, ccipr, CCIPR, _peripheral_name##SEL))
+
+    CCIPR_SET_SOURCE(DFSDM1);
+    CCIPR_SET_SOURCE(SWPMI1);
+    CCIPR_SET_SOURCE(ADC);
+    CCIPR_SET_SOURCE(CLK48);
+    CCIPR_SET_SOURCE(SAI2);
+    CCIPR_SET_SOURCE(SAI1);
+    CCIPR_SET_SOURCE(LPTIM2);
+    CCIPR_SET_SOURCE(LPTIM1);
+    CCIPR_SET_SOURCE(I2C3);
+    CCIPR_SET_SOURCE(I2C2);
+    CCIPR_SET_SOURCE(I2C1);
+    CCIPR_SET_SOURCE(LPUART1);
+    CCIPR_SET_SOURCE(UART5);
+    CCIPR_SET_SOURCE(UART4);
+    CCIPR_SET_SOURCE(USART3);
+    CCIPR_SET_SOURCE(USART2);
+    CCIPR_SET_SOURCE(USART1);
+
+    #undef CCIPR_SET_SOURCE
+}
+
+static void rcc_update_bdcr(Stm32l4x5RccState *s)
+{
+    int val;
+
+    /* LSCOSEL */
+    val = extract32(s->bdcr, R_BDCR_LSCOSEL_SHIFT, R_BDCR_LSCOSEL_LENGTH);
+    clock_mux_set_source(&s->clock_muxes[RCC_CLOCK_MUX_LSCO], val);
+
+    val = extract32(s->bdcr, R_BDCR_LSCOEN_SHIFT, R_BDCR_LSCOEN_LENGTH);
+    clock_mux_set_enable(&s->clock_muxes[RCC_CLOCK_MUX_LSCO], val);
+
+    /* BDRST */
+    /*
+     * The documentation is not clear if the RTCEN flag disables the RTC and
+     * the LCD common mux or if it only affects the RTC.
+     * As the LCDEN flag exists, we assume here that it only affects the RTC.
+     */
+    val = extract32(s->bdcr, R_BDCR_RTCEN_SHIFT, R_BDCR_RTCEN_SHIFT);
+    clock_mux_set_enable(&s->clock_muxes[RCC_CLOCK_MUX_RTC], val);
+    /* LCD and RTC share the same clock */
+    val = extract32(s->bdcr, R_BDCR_RTCSEL_SHIFT, R_BDCR_RTCSEL_LENGTH);
+    clock_mux_set_source(&s->clock_muxes[RCC_CLOCK_MUX_LCD_AND_RTC_COMMON], val);
+
+    /* LSECSSON */
+    /* LSEDRV[1:0] */
+    /* LSEBYP */
+
+    /* LSEON: Update LSERDY at the same time */
+    val = extract32(s->bdcr, R_BDCR_LSEON_SHIFT, R_BDCR_LSEON_LENGTH);
+    if (val) {
+        clock_update_hz(s->lse_crystal, LSE_FRQ);
+        s->bdcr |= R_BDCR_LSERDY_MASK;
+        if (s->cier & R_CIER_LSERDYIE_MASK) {
+            s->cifr |= R_CIFR_LSERDYF_MASK;
+        }
+    } else {
+        clock_update_hz(s->lse_crystal, 0);
+        s->bdcr &= ~R_BDCR_LSERDY_MASK;
+    }
+
+    rcc_update_irq(s);
+}
+
+static void rcc_update_csr(Stm32l4x5RccState *s)
+{
+    int val;
+
+    /* Reset flags: Not implemented */
+    /* MSISRANGE: Not implemented after reset */
+
+    /* LSION: Update LSIRDY at the same time */
+    val = extract32(s->csr, R_CSR_LSION_SHIFT, R_CSR_LSION_LENGTH);
+    if (val) {
+        clock_update_hz(s->lsi_rc, LSI_FRQ);
+        s->csr |= R_CSR_LSIRDY_MASK;
+        if (s->cier & R_CIER_LSIRDYIE_MASK) {
+            s->cifr |= R_CIFR_LSIRDYF_MASK;
+        }
+    } else {
+        /*
+         * TODO: Handle when the LSI is set independently of LSION.
+         * E.g. when the LSI is set by the RTC.
+         * See the reference manual for more details.
+         */
+        clock_update_hz(s->lsi_rc, 0);
+        s->csr &= ~R_CSR_LSIRDY_MASK;
+    }
+
+    rcc_update_irq(s);
+}
+
 static void stm32l4x5_rcc_reset_hold(Object *obj)
 {
     Stm32l4x5RccState *s = STM32L4X5_RCC(obj);
@@ -488,24 +970,33 @@ static void stm32l4x5_rcc_write(void *opaque, hwaddr addr,
     case A_CR:
         s->cr = (s->cr & CR_READ_SET_MASK) |
                 (value & (CR_READ_SET_MASK | ~CR_READ_ONLY_MASK));
+        rcc_update_cr_register(s);
         break;
     case A_ICSCR:
         s->icscr = value & ~ICSCR_READ_ONLY_MASK;
+        qemu_log_mask(LOG_UNIMP,
+                "%s: Side-effects not implemented for ICSCR\n", __func__);
         break;
     case A_CFGR:
         s->cfgr = value & ~CFGR_READ_ONLY_MASK;
+        rcc_update_cfgr_register(s);
         break;
     case A_PLLCFGR:
         s->pllcfgr = value;
+        rcc_update_pllcfgr(s);
         break;
     case A_PLLSAI1CFGR:
         s->pllsai1cfgr = value;
+        rcc_update_pllsaixcfgr(s, RCC_PLL_PLLSAI1);
         break;
     case A_PLLSAI2CFGR:
         s->pllsai2cfgr = value;
+        rcc_update_pllsaixcfgr(s, RCC_PLL_PLLSAI2);
         break;
     case A_CIER:
         s->cier = value;
+        qemu_log_mask(LOG_UNIMP,
+                "%s: Side-effects not implemented for CIER\n", __func__);
         break;
     case A_CIFR:
         qemu_log_mask(LOG_GUEST_ERROR,
@@ -520,67 +1011,100 @@ static void stm32l4x5_rcc_write(void *opaque, hwaddr addr,
     /* Reset behaviors are not implemented */
     case A_AHB1RSTR:
         s->ahb1rstr = value;
+        qemu_log_mask(LOG_UNIMP,
+                "%s: Side-effects not implemented for AHB1RSTR\n", __func__);
         break;
     case A_AHB2RSTR:
         s->ahb2rstr = value;
+        qemu_log_mask(LOG_UNIMP,
+                "%s: Side-effects not implemented for AHB2RSTR\n", __func__);
         break;
     case A_AHB3RSTR:
         s->ahb3rstr = value;
+        qemu_log_mask(LOG_UNIMP,
+                "%s: Side-effects not implemented for AHB3RSTR\n", __func__);
         break;
     case A_APB1RSTR1:
         s->apb1rstr1 = value;
+        qemu_log_mask(LOG_UNIMP,
+                "%s: Side-effects not implemented for APB1RSTR1\n", __func__);
         break;
     case A_APB1RSTR2:
         s->apb1rstr2 = value;
+        qemu_log_mask(LOG_UNIMP,
+                "%s: Side-effects not implemented for APB1RSTR2\n", __func__);
         break;
     case A_APB2RSTR:
         s->apb2rstr = value;
+        qemu_log_mask(LOG_UNIMP,
+                "%s: Side-effects not implemented for APB2RSTR\n", __func__);
         break;
     case A_AHB1ENR:
         s->ahb1enr = value;
+        rcc_update_ahb1enr(s);
         break;
     case A_AHB2ENR:
         s->ahb2enr = value;
+        rcc_update_ahb2enr(s);
         break;
     case A_AHB3ENR:
         s->ahb3enr = value;
+        rcc_update_ahb3enr(s);
         break;
     case A_APB1ENR1:
         s->apb1enr1 = value;
+        rcc_update_apb1enr(s);
         break;
     case A_APB1ENR2:
         s->apb1enr2 = value;
+        rcc_update_apb1enr(s);
         break;
     case A_APB2ENR:
         s->apb2enr = (s->apb2enr & APB2ENR_READ_SET_MASK) | value;
+        rcc_update_apb2enr(s);
         break;
     /* Behaviors for Sleep and Stop modes are not implemented */
     case A_AHB1SMENR:
         s->ahb1smenr = value;
+        qemu_log_mask(LOG_UNIMP,
+                "%s: Side-effects not implemented for AHB1SMENR\n", __func__);
         break;
     case A_AHB2SMENR:
         s->ahb2smenr = value;
+        qemu_log_mask(LOG_UNIMP,
+                "%s: Side-effects not implemented for AHB2SMENR\n", __func__);
         break;
     case A_AHB3SMENR:
         s->ahb3smenr = value;
+        qemu_log_mask(LOG_UNIMP,
+                "%s: Side-effects not implemented for AHB3SMENR\n", __func__);
         break;
     case A_APB1SMENR1:
         s->apb1smenr1 = value;
+        qemu_log_mask(LOG_UNIMP,
+                "%s: Side-effects not implemented for APB1SMENR1\n", __func__);
         break;
     case A_APB1SMENR2:
         s->apb1smenr2 = value;
+        qemu_log_mask(LOG_UNIMP,
+                "%s: Side-effects not implemented for APB1SMENR2\n", __func__);
         break;
     case A_APB2SMENR:
         s->apb2smenr = value;
+        qemu_log_mask(LOG_UNIMP,
+                "%s: Side-effects not implemented for APB2SMENR\n", __func__);
         break;
     case A_CCIPR:
         s->ccipr = value;
+        rcc_update_ccipr(s);
         break;
     case A_BDCR:
         s->bdcr = value & ~BDCR_READ_ONLY_MASK;
+        rcc_update_bdcr(s);
         break;
     case A_CSR:
         s->csr = value & ~CSR_READ_ONLY_MASK;
+        rcc_update_csr(s);
         break;
     default:
         qemu_log_mask(LOG_GUEST_ERROR,
@@ -777,18 +1301,6 @@ static void stm32l4x5_rcc_realize(DeviceState *dev, Error **errp)
     clock_update_hz(s->sai1_extclk, s->sai1_extclk_frequency);
     clock_update_hz(s->sai2_extclk, s->sai2_extclk_frequency);
     clock_update(s->gnd, 0);
-
-    /*
-     * Dummy values to make compilation pass.
-     * Removed in later commits.
-     */
-    clock_mux_set_source(&s->clock_muxes[0], RCC_CLOCK_MUX_SRC_GND);
-    clock_mux_set_enable(&s->clock_muxes[0], true);
-    clock_mux_set_factor(&s->clock_muxes[0], 1, 1);
-    pll_set_channel_divider(&s->plls[0], 0, 1);
-    pll_set_enable(&s->plls[0], true);
-    pll_set_channel_enable(&s->plls[0], 0, true);
-    pll_set_vco_multiplier(&s->plls[0], 1);
 }
 
 static Property stm32l4x5_rcc_properties[] = {
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v4 6/8] Add write protections to CR register
  2024-01-30 16:06 [PATCH v4 0/8] Add device STM32L4x5 RCC Arnaud Minier
                   ` (4 preceding siblings ...)
  2024-01-30 16:06 ` [PATCH v4 5/8] RCC: Handle Register Updates Arnaud Minier
@ 2024-01-30 16:06 ` Arnaud Minier
  2024-01-30 16:06 ` [PATCH v4 7/8] STM32L4x5: Use the RCC Sysclk Arnaud Minier
  2024-01-30 16:13 ` [PATCH v4 8/8] Add tests for the STM32L4x5_RCC Arnaud Minier
  7 siblings, 0 replies; 14+ messages in thread
From: Arnaud Minier @ 2024-01-30 16:06 UTC (permalink / raw)
  To: qemu-devel
  Cc: Laurent Vivier, Alistair Francis, Inès Varhol,
	Peter Maydell, Paolo Bonzini, Thomas Huth, qemu-arm,
	Arnaud Minier

Add write protections for the fields in the CR register.
PLL configuration write protections (among others) have not
been handled yet. This is planned in a future patch set.

Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
---
 hw/misc/stm32l4x5_rcc.c | 164 ++++++++++++++++++++++++++++------------
 1 file changed, 114 insertions(+), 50 deletions(-)

diff --git a/hw/misc/stm32l4x5_rcc.c b/hw/misc/stm32l4x5_rcc.c
index 3bf4b41552..a192ce16d7 100644
--- a/hw/misc/stm32l4x5_rcc.c
+++ b/hw/misc/stm32l4x5_rcc.c
@@ -345,9 +345,47 @@ static void rcc_update_irq(Stm32l4x5RccState *s)
     }
 }
 
-static void rcc_update_cr_register(Stm32l4x5RccState *s)
+static void rcc_update_msi(Stm32l4x5RccState *s, uint32_t previous_value)
+{
+    uint32_t val;
+
+    static const uint32_t msirange[] = {
+        100000, 200000, 400000, 800000, 1000000, 2000000,
+        4000000, 8000000, 16000000, 24000000, 32000000, 48000000
+    };
+    /* MSIRANGE and MSIRGSEL */
+    val = extract32(s->cr, R_CR_MSIRGSEL_SHIFT, R_CR_MSIRGSEL_LENGTH);
+    if (val) {
+        /* MSIRGSEL is set, use the MSIRANGE field */
+        val = extract32(s->cr, R_CR_MSIRANGE_SHIFT, R_CR_MSIRANGE_LENGTH);
+    } else {
+        /* MSIRGSEL is not set, use the MSISRANGE field */
+        val = extract32(s->csr, R_CSR_MSISRANGE_SHIFT, R_CSR_MSISRANGE_LENGTH);
+    }
+
+    if (val < ARRAY_SIZE(msirange)) {
+        clock_update_hz(s->msi_rc, msirange[val]);
+    } else {
+        /*
+         * There is a hardware write protection if the value is out of bound.
+         * Restore the previous value.
+         */
+        s->cr = (s->cr & ~R_CSR_MSISRANGE_MASK) |
+                (previous_value & R_CSR_MSISRANGE_MASK);
+    }
+}
+
+/*
+ * TODO: Add write-protection for all registers:
+ * DONE: CR
+ */
+
+static void rcc_update_cr_register(Stm32l4x5RccState *s, uint32_t previous_value)
 {
     int val;
+    const RccClockMuxSource current_pll_src =
+        CLOCK_MUX_INIT_INFO[RCC_CLOCK_MUX_PLL_INPUT].src_mapping[
+            s->clock_muxes[RCC_CLOCK_MUX_PLL_INPUT].src];
 
     /* PLLSAI2ON and update PLLSAI2RDY */
     val = extract32(s->cr, R_CR_PLLSAI2ON_SHIFT, R_CR_PLLSAI2ON_LENGTH);
@@ -367,77 +405,101 @@ static void rcc_update_cr_register(Stm32l4x5RccState *s)
         s->cifr |= R_CIFR_PLLSAI1RDYF_MASK;
     }
 
-    /* PLLON and update PLLRDY */
+    /*
+     * PLLON and update PLLRDY
+     * PLLON cannot be reset if the PLL clock is used as the system clock.
+     */
     val = extract32(s->cr, R_CR_PLLON_SHIFT, R_CR_PLLON_LENGTH);
-    pll_set_enable(&s->plls[RCC_PLL_PLL], val);
-    s->cr = (s->cr & ~R_CR_PLLRDY_MASK) |
-            (val << R_CR_PLLRDY_SHIFT);
-    if (s->cier & R_CIER_PLLRDYIE_MASK) {
-        s->cifr |= R_CIFR_PLLRDYF_MASK;
+    if (extract32(s->cfgr, R_CFGR_SWS_SHIFT, R_CFGR_SWS_LENGTH) != 0b11) {
+        pll_set_enable(&s->plls[RCC_PLL_PLL], val);
+        s->cr = (s->cr & ~R_CR_PLLRDY_MASK) |
+                (val << R_CR_PLLRDY_SHIFT);
+        if (s->cier & R_CIER_PLLRDYIE_MASK) {
+            s->cifr |= R_CIFR_PLLRDYF_MASK;
+        }
+    } else {
+        s->cr |= R_CR_PLLON_MASK;
     }
 
     /* CSSON: TODO */
     /* HSEBYP: TODO */
 
-    /* HSEON and update HSERDY */
+    /*
+     * HSEON and update HSERDY.
+     * HSEON cannot be reset if the HSE oscillator is used directly or
+     * indirectly as the system clock.
+     */
     val = extract32(s->cr, R_CR_HSEON_SHIFT, R_CR_HSEON_LENGTH);
-    s->cr = (s->cr & ~R_CR_HSERDY_MASK) |
-            (val << R_CR_HSERDY_SHIFT);
-    if (val) {
-        clock_update_hz(s->hse, s->hse_frequency);
-        if (s->cier & R_CIER_HSERDYIE_MASK) {
-            s->cifr |= R_CIFR_HSERDYF_MASK;
+    if (extract32(s->cfgr, R_CFGR_SWS_SHIFT, R_CFGR_SWS_LENGTH) != 0b10 &&
+        current_pll_src != RCC_CLOCK_MUX_SRC_HSE) {
+        s->cr = (s->cr & ~R_CR_HSERDY_MASK) |
+                (val << R_CR_HSERDY_SHIFT);
+        if (val) {
+            clock_update_hz(s->hse, s->hse_frequency);
+            if (s->cier & R_CIER_HSERDYIE_MASK) {
+                s->cifr |= R_CIFR_HSERDYF_MASK;
+            }
+        } else {
+            clock_update_hz(s->hse, 0);
         }
     } else {
-        clock_update_hz(s->hse, 0);
+        s->cr |= R_CR_HSEON_MASK;
     }
 
     /* HSIAFS: TODO*/
     /* HSIKERON: TODO*/
 
-    /* HSION and update HSIRDY*/
-    val = extract32(s->cr, R_CR_HSION_SHIFT, R_CR_HSION_LENGTH);
-    s->cr = (s->cr & ~R_CR_HSIRDY_MASK) |
-            (val << R_CR_HSIRDY_SHIFT);
-    if (val) {
+    /*
+     * HSION and update HSIRDY
+     * HSION is set by hardware if the HSI16 is used directly
+     * or indirectly as system clock.
+     */
+    if (extract32(s->cfgr, R_CFGR_SWS_SHIFT, R_CFGR_SWS_LENGTH) == 0b01 ||
+        current_pll_src == RCC_CLOCK_MUX_SRC_HSI) {
+        s->cr |= (R_CR_HSION_MASK | R_CR_HSIRDY_MASK);
         clock_update_hz(s->hsi16_rc, HSI_FRQ);
         if (s->cier & R_CIER_HSIRDYIE_MASK) {
             s->cifr |= R_CIFR_HSIRDYF_MASK;
         }
     } else {
-        clock_update_hz(s->hsi16_rc, 0);
+        val = extract32(s->cr, R_CR_HSION_SHIFT, R_CR_HSION_LENGTH);
+        if (val) {
+            clock_update_hz(s->hsi16_rc, HSI_FRQ);
+            s->cr |= R_CR_HSIRDY_MASK;
+            if (s->cier & R_CIER_HSIRDYIE_MASK) {
+                s->cifr |= R_CIFR_HSIRDYF_MASK;
+            }
+        } else {
+            clock_update_hz(s->hsi16_rc, 0);
+            s->cr &= ~R_CR_HSIRDY_MASK;
+        }
     }
 
-    static const uint32_t msirange[] = {
-        100000, 200000, 400000, 800000, 1000000, 2000000,
-        4000000, 8000000, 16000000, 24000000, 32000000, 48000000
-    };
-    /* MSIRANGE and MSIRGSEL */
-    val = extract32(s->cr, R_CR_MSIRGSEL_SHIFT, R_CR_MSIRGSEL_LENGTH);
-    if (val) {
-        /* MSIRGSEL is set, use the MSIRANGE field */
-        val = extract32(s->cr, R_CR_MSIRANGE_SHIFT, R_CR_MSIRANGE_LENGTH);
-    } else {
-        /* MSIRGSEL is not set, use the MSISRANGE field */
-        val = extract32(s->csr, R_CSR_MSISRANGE_SHIFT, R_CSR_MSISRANGE_LENGTH);
-    }
+    /* MSIPLLEN: TODO */
 
-    if (val < ARRAY_SIZE(msirange)) {
-        clock_update_hz(s->msi_rc, msirange[val]);
+    /*
+     * MSION and update MSIRDY
+     * Set by hardware when used directly or indirectly as system clock.
+     */
+    if (extract32(s->cfgr, R_CFGR_SWS_SHIFT, R_CFGR_SWS_LENGTH) == 0b00 ||
+        current_pll_src == RCC_CLOCK_MUX_SRC_MSI) {
+            s->cr |= (R_CR_MSION_MASK | R_CR_MSIRDY_MASK);
+            if (!(previous_value & R_CR_MSION_MASK) && (s->cier & R_CIER_MSIRDYIE_MASK)) {
+                s->cifr |= R_CIFR_MSIRDYF_MASK;
+            }
+            rcc_update_msi(s, previous_value);
     } else {
-        clock_update_hz(s->msi_rc, MSI_DEFAULT_FRQ);
-        /* TODO: there is a write protection if the value is out of bound,
-           implement that instead of setting the default */
-    }
-
-    /* MSIPLLEN */
-
-    /* MSION and update MSIRDY */
-    val = extract32(s->cr, R_CR_MSION_SHIFT, R_CR_MSION_LENGTH);
-    s->cr = (s->cr & ~R_CR_MSIRDY_MASK) |
-            (val << R_CR_MSIRDY_SHIFT);
-    if (s->cier & R_CIER_MSIRDYIE_MASK) {
-        s->cifr |= R_CIFR_MSIRDYF_MASK;
+        val = extract32(s->cr, R_CR_MSION_SHIFT, R_CR_MSION_LENGTH);
+        if (val) {
+            s->cr |= R_CR_MSIRDY_MASK;
+            rcc_update_msi(s, previous_value);
+            if (s->cier & R_CIER_MSIRDYIE_MASK) {
+                s->cifr |= R_CIFR_MSIRDYF_MASK;
+            }
+        } else {
+            s->cr &= ~R_CR_MSIRDY_MASK;
+            clock_update_hz(s->msi_rc, 0);
+        }
     }
     rcc_update_irq(s);
 }
@@ -962,15 +1024,17 @@ static void stm32l4x5_rcc_write(void *opaque, hwaddr addr,
                                   uint64_t val64, unsigned int size)
 {
     Stm32l4x5RccState *s = opaque;
+    uint32_t previous_value = 0;
     const uint32_t value = val64;
 
     trace_stm32l4x5_rcc_write(addr, value);
 
     switch (addr) {
     case A_CR:
+        previous_value = s->cr;
         s->cr = (s->cr & CR_READ_SET_MASK) |
                 (value & (CR_READ_SET_MASK | ~CR_READ_ONLY_MASK));
-        rcc_update_cr_register(s);
+        rcc_update_cr_register(s, previous_value);
         break;
     case A_ICSCR:
         s->icscr = value & ~ICSCR_READ_ONLY_MASK;
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v4 7/8] STM32L4x5: Use the RCC Sysclk
  2024-01-30 16:06 [PATCH v4 0/8] Add device STM32L4x5 RCC Arnaud Minier
                   ` (5 preceding siblings ...)
  2024-01-30 16:06 ` [PATCH v4 6/8] Add write protections to CR register Arnaud Minier
@ 2024-01-30 16:06 ` Arnaud Minier
  2024-02-01  0:20   ` Alistair Francis
  2024-01-30 16:13 ` [PATCH v4 8/8] Add tests for the STM32L4x5_RCC Arnaud Minier
  7 siblings, 1 reply; 14+ messages in thread
From: Arnaud Minier @ 2024-01-30 16:06 UTC (permalink / raw)
  To: qemu-devel
  Cc: Laurent Vivier, Alistair Francis, Inès Varhol,
	Peter Maydell, Paolo Bonzini, Thomas Huth, qemu-arm,
	Arnaud Minier

Now that we can generate reliable clock frequencies from the RCC, remove
the hacky definition of the sysclk in the b_l475e_iot01a initialisation
code and use the correct RCC clock.

Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
---
 hw/arm/b-l475e-iot01a.c        | 10 +---------
 hw/arm/stm32l4x5_soc.c         | 33 ++++-----------------------------
 include/hw/arm/stm32l4x5_soc.h |  3 ---
 3 files changed, 5 insertions(+), 41 deletions(-)

diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c
index 6ecde2db15..d862aa43fc 100644
--- a/hw/arm/b-l475e-iot01a.c
+++ b/hw/arm/b-l475e-iot01a.c
@@ -26,27 +26,19 @@
 #include "qapi/error.h"
 #include "hw/boards.h"
 #include "hw/qdev-properties.h"
-#include "hw/qdev-clock.h"
 #include "qemu/error-report.h"
 #include "hw/arm/stm32l4x5_soc.h"
 #include "hw/arm/boot.h"
 
-/* Main SYSCLK frequency in Hz (80MHz) */
-#define MAIN_SYSCLK_FREQ_HZ 80000000ULL
+/* B-L475E-IOT01A implementation is derived from netduinoplus2 */
 
 static void b_l475e_iot01a_init(MachineState *machine)
 {
     const Stm32l4x5SocClass *sc;
     DeviceState *dev;
-    Clock *sysclk;
-
-    /* This clock doesn't need migration because it is fixed-frequency */
-    sysclk = clock_new(OBJECT(machine), "SYSCLK");
-    clock_set_hz(sysclk, MAIN_SYSCLK_FREQ_HZ);
 
     dev = qdev_new(TYPE_STM32L4X5XG_SOC);
     object_property_add_child(OBJECT(machine), "soc", OBJECT(dev));
-    qdev_connect_clock_in(dev, "sysclk", sysclk);
     sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
 
     sc = STM32L4X5_SOC_GET_CLASS(dev);
diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index d5c04b446d..347a5377e5 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -85,9 +85,6 @@ static void stm32l4x5_soc_initfn(Object *obj)
     object_initialize_child(obj, "exti", &s->exti, TYPE_STM32L4X5_EXTI);
     object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_STM32L4X5_SYSCFG);
     object_initialize_child(obj, "rcc", &s->rcc, TYPE_STM32L4X5_RCC);
-
-    s->sysclk = qdev_init_clock_in(DEVICE(s), "sysclk", NULL, NULL, 0);
-    s->refclk = qdev_init_clock_in(DEVICE(s), "refclk", NULL, NULL, 0);
 }
 
 static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
@@ -99,30 +96,6 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
     DeviceState *armv7m;
     SysBusDevice *busdev;
 
-    /*
-     * We use s->refclk internally and only define it with qdev_init_clock_in()
-     * so it is correctly parented and not leaked on an init/deinit; it is not
-     * intended as an externally exposed clock.
-     */
-    if (clock_has_source(s->refclk)) {
-        error_setg(errp, "refclk clock must not be wired up by the board code");
-        return;
-    }
-
-    if (!clock_has_source(s->sysclk)) {
-        error_setg(errp, "sysclk clock must be wired up by the board code");
-        return;
-    }
-
-    /*
-     * TODO: ideally we should model the SoC RCC and its ability to
-     * change the sysclk frequency and define different sysclk sources.
-     */
-
-    /* The refclk always runs at frequency HCLK / 8 */
-    clock_set_mul_div(s->refclk, 8, 1);
-    clock_set_source(s->refclk, s->sysclk);
-
     if (!memory_region_init_rom(&s->flash, OBJECT(dev_soc), "flash",
                                 sc->flash_size, errp)) {
         return;
@@ -152,8 +125,10 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
     qdev_prop_set_uint32(armv7m, "num-prio-bits", 4);
     qdev_prop_set_string(armv7m, "cpu-type", ARM_CPU_TYPE_NAME("cortex-m4"));
     qdev_prop_set_bit(armv7m, "enable-bitband", true);
-    qdev_connect_clock_in(armv7m, "cpuclk", s->sysclk);
-    qdev_connect_clock_in(armv7m, "refclk", s->refclk);
+    qdev_connect_clock_in(armv7m, "cpuclk",
+        qdev_get_clock_out(DEVICE(&(s->rcc)), "cortex-fclk-out"));
+    qdev_connect_clock_in(armv7m, "refclk",
+        qdev_get_clock_out(DEVICE(&(s->rcc)), "cortex-refclk-out"));
     object_property_set_link(OBJECT(&s->armv7m), "memory",
                              OBJECT(system_memory), &error_abort);
     if (!sysbus_realize(SYS_BUS_DEVICE(&s->armv7m), errp)) {
diff --git a/include/hw/arm/stm32l4x5_soc.h b/include/hw/arm/stm32l4x5_soc.h
index e480fcc976..1f71298b45 100644
--- a/include/hw/arm/stm32l4x5_soc.h
+++ b/include/hw/arm/stm32l4x5_soc.h
@@ -50,9 +50,6 @@ struct Stm32l4x5SocState {
     MemoryRegion sram2;
     MemoryRegion flash;
     MemoryRegion flash_alias;
-
-    Clock *sysclk;
-    Clock *refclk;
 };
 
 struct Stm32l4x5SocClass {
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v4 8/8] Add tests for the STM32L4x5_RCC
  2024-01-30 16:06 [PATCH v4 0/8] Add device STM32L4x5 RCC Arnaud Minier
                   ` (6 preceding siblings ...)
  2024-01-30 16:06 ` [PATCH v4 7/8] STM32L4x5: Use the RCC Sysclk Arnaud Minier
@ 2024-01-30 16:13 ` Arnaud Minier
  2024-01-31  6:04   ` Thomas Huth
  7 siblings, 1 reply; 14+ messages in thread
From: Arnaud Minier @ 2024-01-30 16:13 UTC (permalink / raw)
  To: qemu-devel
  Cc: Inès Varhol, Thomas Huth, qemu-arm, Peter Maydell,
	Arnaud Minier, Paolo Bonzini, Alistair Francis, Laurent Vivier

Tests:
- the ability to change the sysclk of the device
- the ability to enable/disable/configure the PLLs
- if the clock multiplexers work
- the register flags and the generation of irqs

Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
---
 tests/qtest/meson.build          |   3 +-
 tests/qtest/stm32l4x5_rcc-test.c | 207 +++++++++++++++++++++++++++++++
 2 files changed, 209 insertions(+), 1 deletion(-)
 create mode 100644 tests/qtest/stm32l4x5_rcc-test.c

diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 84a055a7d9..36f85c8920 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -201,7 +201,8 @@ qtests_aspeed = \
 
 qtests_stm32l4x5 = \
   ['stm32l4x5_exti-test',
-   'stm32l4x5_syscfg-test']
+   'stm32l4x5_syscfg-test',
+   'stm32l4x5_rcc-test']
 
 qtests_arm = \
   (config_all_devices.has_key('CONFIG_MPS2') ? ['sse-timer-test'] : []) + \
diff --git a/tests/qtest/stm32l4x5_rcc-test.c b/tests/qtest/stm32l4x5_rcc-test.c
new file mode 100644
index 0000000000..4157291052
--- /dev/null
+++ b/tests/qtest/stm32l4x5_rcc-test.c
@@ -0,0 +1,207 @@
+/*
+ * QTest testcase for STM32L4x5_RCC
+ *
+ * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr>
+ * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.fr>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/registerfields.h"
+#include "libqtest-single.h"
+#include "hw/misc/stm32l4x5_rcc_internals.h"
+
+#define RCC_BASE_ADDR 0x40021000
+#define NVIC_ISER 0xE000E100
+#define NVIC_ISPR 0xE000E200
+#define NVIC_ICPR 0xE000E280
+#define RCC_IRQ 5
+
+static void enable_nvic_irq(unsigned int n)
+{
+    writel(NVIC_ISER, 1 << n);
+}
+
+static void unpend_nvic_irq(unsigned int n)
+{
+    writel(NVIC_ICPR, 1 << n);
+}
+
+static bool check_nvic_pending(unsigned int n)
+{
+    return readl(NVIC_ISPR) & (1 << n);
+}
+
+static bool qts_wait_for_flag(QTestState *qts, uint32_t event_addr,
+                              uint32_t flag, uint32_t value)
+{
+    /* Wait at most 5 seconds */
+    for (int i = 0; i < 5000; i++) {
+        if ((qtest_readl(qts, event_addr) & flag) == value) {
+            return true;
+        }
+        g_usleep(1000);
+    }
+
+    return false;
+}
+
+static bool rcc_wait_for_flag(uint32_t event_addr, uint32_t flag,
+                              uint32_t value)
+{
+    return qts_wait_for_flag(global_qtest, RCC_BASE_ADDR + event_addr, flag, value);
+}
+
+static void rcc_writel(unsigned int offset, uint32_t value)
+{
+    writel(RCC_BASE_ADDR + offset, value);
+}
+
+static uint32_t rcc_readl(unsigned int offset)
+{
+    return readl(RCC_BASE_ADDR + offset);
+}
+
+static void test_init_msi(void)
+{
+    /* MSIRANGE can be set only when MSI is OFF or READY */
+    rcc_writel(A_CR, R_CR_MSION_MASK);
+    /* Wait until MSI is stable */
+    g_assert_true(rcc_wait_for_flag(A_CR, R_CR_MSIRDY_MASK, R_CR_MSIRDY_MASK));
+    /* TODO find a way to test MSI value */
+}
+
+static void test_set_msi_as_sysclk(void)
+{
+    /* Clocking from MSI, in case MSI was not the default source */
+    rcc_writel(A_CFGR, 0);
+    /* Wait until MSI is selected and stable */
+    g_assert_true(rcc_wait_for_flag(A_CFGR, R_CFGR_SWS_MASK, 0));
+}
+
+static void test_init_pll(void)
+{
+    uint32_t value;
+
+    /*
+     * Update PLL and set MSI as the source clock.
+     * PLLM = 1 --> 000
+     * PLLN = 40 --> 40
+     * PPLLR = 2 --> 00
+     * PLLDIV = unused, PLLP = unused (SAI3), PLLQ = unused (48M1)
+     * SRC = MSI --> 01
+     */
+    rcc_writel(A_PLLCFGR, R_PLLCFGR_PLLREN_MASK |
+            (40 << R_PLLCFGR_PLLN_SHIFT) |
+            (0b01 << R_PLLCFGR_PLLSRC_SHIFT));
+
+    /* PLL activation */
+    value = rcc_readl(A_CR);
+    rcc_writel(A_CR, value | R_CR_PLLON_MASK);
+
+    /* Waiting for PLL lock. */
+    g_assert_true(rcc_wait_for_flag(A_CR, R_CR_PLLRDY_MASK, R_CR_PLLRDY_MASK));
+
+    /* Switches on the PLL clock source */
+    value = rcc_readl(A_CFGR);
+    rcc_writel(A_CFGR, (value & ~R_CFGR_SW_MASK) |
+        (0b11 << R_CFGR_SW_SHIFT));
+
+    /* Wait until SYSCLK is stable. */
+    g_assert_true(rcc_wait_for_flag(A_CFGR, R_CFGR_SWS_MASK,
+        (0b11 << R_CFGR_SWS_SHIFT)));
+}
+
+static void test_activate_lse(void)
+{
+    /* LSE activation, no LSE Bypass */
+    rcc_writel(A_BDCR, R_BDCR_LSEDRV_MASK | R_BDCR_LSEON_MASK);
+    g_assert_true(rcc_wait_for_flag(A_BDCR, R_BDCR_LSERDY_MASK, R_BDCR_LSERDY_MASK));
+}
+
+static void test_irq(void)
+{
+    enable_nvic_irq(RCC_IRQ);
+
+    rcc_writel(A_CIER, R_CIER_LSIRDYIE_MASK);
+    rcc_writel(A_CSR, R_CSR_LSION_MASK);
+    g_assert_true(check_nvic_pending(RCC_IRQ));
+    rcc_writel(A_CICR, R_CICR_LSIRDYC_MASK);
+    unpend_nvic_irq(RCC_IRQ);
+
+    rcc_writel(A_CIER, R_CIER_LSERDYIE_MASK);
+    rcc_writel(A_BDCR, R_BDCR_LSEON_MASK);
+    g_assert_true(check_nvic_pending(RCC_IRQ));
+    rcc_writel(A_CICR, R_CICR_LSERDYC_MASK);
+    unpend_nvic_irq(RCC_IRQ);
+
+    /*
+     * MSI has been enabled by previous tests,
+     * shouln't generate an interruption.
+     */
+    rcc_writel(A_CIER, R_CIER_MSIRDYIE_MASK);
+    rcc_writel(A_CR, R_CR_MSION_MASK);
+    g_assert_false(check_nvic_pending(RCC_IRQ));
+
+    rcc_writel(A_CIER, R_CIER_HSIRDYIE_MASK);
+    rcc_writel(A_CR, R_CR_HSION_MASK);
+    g_assert_true(check_nvic_pending(RCC_IRQ));
+    rcc_writel(A_CICR, R_CICR_HSIRDYC_MASK);
+    unpend_nvic_irq(RCC_IRQ);
+
+    rcc_writel(A_CIER, R_CIER_HSERDYIE_MASK);
+    rcc_writel(A_CR, R_CR_HSEON_MASK);
+    g_assert_true(check_nvic_pending(RCC_IRQ));
+    rcc_writel(A_CICR, R_CICR_HSERDYC_MASK);
+    unpend_nvic_irq(RCC_IRQ);
+
+    /*
+     * PLL has been enabled by previous tests,
+     * shouln't generate an interruption.
+     */
+    rcc_writel(A_CIER, R_CIER_PLLRDYIE_MASK);
+    rcc_writel(A_CR, R_CR_PLLON_MASK);
+    g_assert_false(check_nvic_pending(RCC_IRQ));
+
+    rcc_writel(A_CIER, R_CIER_PLLSAI1RDYIE_MASK);
+    rcc_writel(A_CR, R_CR_PLLSAI1ON_MASK);
+    g_assert_true(check_nvic_pending(RCC_IRQ));
+    rcc_writel(A_CICR, R_CICR_PLLSAI1RDYC_MASK);
+    unpend_nvic_irq(RCC_IRQ);
+
+    rcc_writel(A_CIER, R_CIER_PLLSAI2RDYIE_MASK);
+    rcc_writel(A_CR, R_CR_PLLSAI2ON_MASK);
+    g_assert_true(check_nvic_pending(RCC_IRQ));
+    rcc_writel(A_CICR, R_CICR_PLLSAI2RDYC_MASK);
+    unpend_nvic_irq(RCC_IRQ);
+}
+
+int main(int argc, char **argv)
+{
+    int ret;
+
+    g_test_init(&argc, &argv, NULL);
+    g_test_set_nonfatal_assertions();
+    /*
+     * These test separately that we can enable the plls, change the sysclk,
+     * and enable different devices.
+     * They are dependent on one another.
+     */
+    qtest_add_func("stm32l4x5/rcc/init_msi", test_init_msi);
+    qtest_add_func("stm32l4x5/rcc/set_msi_as_sysclk",
+        test_set_msi_as_sysclk);
+    qtest_add_func("stm32l4x5/rcc/activate_lse", test_activate_lse);
+    qtest_add_func("stm32l4x5/rcc/init_pll", test_init_pll);
+
+    qtest_add_func("stm32l4x5/rcc/irq", test_irq);
+
+    qtest_start("-machine b-l475e-iot01a");
+    ret = g_test_run();
+    qtest_end();
+
+    return ret;
+}
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v4 8/8] Add tests for the STM32L4x5_RCC
  2024-01-30 16:13 ` [PATCH v4 8/8] Add tests for the STM32L4x5_RCC Arnaud Minier
@ 2024-01-31  6:04   ` Thomas Huth
  0 siblings, 0 replies; 14+ messages in thread
From: Thomas Huth @ 2024-01-31  6:04 UTC (permalink / raw)
  To: Arnaud Minier, qemu-devel
  Cc: Inès Varhol, qemu-arm, Peter Maydell, Paolo Bonzini,
	Alistair Francis, Laurent Vivier

On 30/01/2024 17.13, Arnaud Minier wrote:
> Tests:
> - the ability to change the sysclk of the device
> - the ability to enable/disable/configure the PLLs
> - if the clock multiplexers work
> - the register flags and the generation of irqs
> 
> Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
> Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
> ---
>   tests/qtest/meson.build          |   3 +-
>   tests/qtest/stm32l4x5_rcc-test.c | 207 +++++++++++++++++++++++++++++++
>   2 files changed, 209 insertions(+), 1 deletion(-)
>   create mode 100644 tests/qtest/stm32l4x5_rcc-test.c

Acked-by: Thomas Huth <thuth@redhat.com>



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v4 1/8] Implement STM32L4x5_RCC skeleton
  2024-01-30 16:06 ` [PATCH v4 1/8] Implement STM32L4x5_RCC skeleton Arnaud Minier
@ 2024-02-01  0:09   ` Alistair Francis
  0 siblings, 0 replies; 14+ messages in thread
From: Alistair Francis @ 2024-02-01  0:09 UTC (permalink / raw)
  To: Arnaud Minier
  Cc: qemu-devel, Laurent Vivier, Alistair Francis, Inès Varhol,
	Peter Maydell, Paolo Bonzini, Thomas Huth, qemu-arm

On Wed, Jan 31, 2024 at 2:09 AM Arnaud Minier
<arnaud.minier@telecom-paris.fr> wrote:
>
> Add the necessary files to add a simple RCC implementation with just
> reads from and writes to registers. Also instanciate the RCC in the
> STM32L4x5_SoC. It is needed for accurate emulation of all the SoC
> clocks and timers.
>
> Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
> Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>

Acked-by: Alistair Francis <alistair.francis@wdc.com>

Alistair

> ---
>  MAINTAINERS                               |   5 +-
>  docs/system/arm/b-l475e-iot01a.rst        |   2 +-
>  hw/arm/Kconfig                            |   1 +
>  hw/arm/stm32l4x5_soc.c                    |  12 +-
>  hw/misc/Kconfig                           |   3 +
>  hw/misc/meson.build                       |   1 +
>  hw/misc/stm32l4x5_rcc.c                   | 433 ++++++++++++++++++++++
>  hw/misc/trace-events                      |   4 +
>  include/hw/arm/stm32l4x5_soc.h            |   2 +
>  include/hw/misc/stm32l4x5_rcc.h           |  80 ++++
>  include/hw/misc/stm32l4x5_rcc_internals.h | 286 ++++++++++++++
>  11 files changed, 826 insertions(+), 3 deletions(-)
>  create mode 100644 hw/misc/stm32l4x5_rcc.c
>  create mode 100644 include/hw/misc/stm32l4x5_rcc.h
>  create mode 100644 include/hw/misc/stm32l4x5_rcc_internals.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index dfaca8323e..50ab2982bb 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1128,7 +1128,10 @@ M: Inès Varhol <ines.varhol@telecom-paris.fr>
>  L: qemu-arm@nongnu.org
>  S: Maintained
>  F: hw/arm/stm32l4x5_soc.c
> -F: include/hw/arm/stm32l4x5_soc.h
> +F: hw/misc/stm32l4x5_exti.c
> +F: hw/misc/stm32l4x5_syscfg.c
> +F: hw/misc/stm32l4x5_rcc.c
> +F: include/hw/*/stm32l4x5_*.h
>
>  B-L475E-IOT01A IoT Node
>  M: Arnaud Minier <arnaud.minier@telecom-paris.fr>
> diff --git a/docs/system/arm/b-l475e-iot01a.rst b/docs/system/arm/b-l475e-iot01a.rst
> index 1a021b306a..b857a56ca4 100644
> --- a/docs/system/arm/b-l475e-iot01a.rst
> +++ b/docs/system/arm/b-l475e-iot01a.rst
> @@ -17,13 +17,13 @@ Currently B-L475E-IOT01A machine's only supports the following devices:
>  - Cortex-M4F based STM32L4x5 SoC
>  - STM32L4x5 EXTI (Extended interrupts and events controller)
>  - STM32L4x5 SYSCFG (System configuration controller)
> +- STM32L4x5 RCC (Reset and clock control)
>
>  Missing devices
>  """""""""""""""
>
>  The B-L475E-IOT01A does *not* support the following devices:
>
> -- Reset and clock control (RCC)
>  - Serial ports (UART)
>  - General-purpose I/Os (GPIO)
>  - Analog to Digital Converter (ADC)
> diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
> index f927878152..92b72d56dc 100644
> --- a/hw/arm/Kconfig
> +++ b/hw/arm/Kconfig
> @@ -465,6 +465,7 @@ config STM32L4X5_SOC
>      select OR_IRQ
>      select STM32L4X5_SYSCFG
>      select STM32L4X5_EXTI
> +    select STM32L4X5_RCC
>
>  config XLNX_ZYNQMP_ARM
>      bool
> diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
> index f470ff74ec..d5c04b446d 100644
> --- a/hw/arm/stm32l4x5_soc.c
> +++ b/hw/arm/stm32l4x5_soc.c
> @@ -75,6 +75,8 @@ static const int exti_irq[NUM_EXTI_IRQ] = {
>      1,                      /* PVM4 wakeup             */
>      78                      /* LCD wakeup, Direct      */
>  };
> +#define RCC_BASE_ADDRESS 0x40021000
> +#define RCC_IRQ 5
>
>  static void stm32l4x5_soc_initfn(Object *obj)
>  {
> @@ -82,6 +84,7 @@ static void stm32l4x5_soc_initfn(Object *obj)
>
>      object_initialize_child(obj, "exti", &s->exti, TYPE_STM32L4X5_EXTI);
>      object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_STM32L4X5_SYSCFG);
> +    object_initialize_child(obj, "rcc", &s->rcc, TYPE_STM32L4X5_RCC);
>
>      s->sysclk = qdev_init_clock_in(DEVICE(s), "sysclk", NULL, NULL, 0);
>      s->refclk = qdev_init_clock_in(DEVICE(s), "refclk", NULL, NULL, 0);
> @@ -184,6 +187,14 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
>                                qdev_get_gpio_in(DEVICE(&s->exti), i));
>      }
>
> +    /* RCC device */
> +    busdev = SYS_BUS_DEVICE(&s->rcc);
> +    if (!sysbus_realize(busdev, errp)) {
> +        return;
> +    }
> +    sysbus_mmio_map(busdev, 0, RCC_BASE_ADDRESS);
> +    sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(armv7m, RCC_IRQ));
> +
>      /* APB1 BUS */
>      create_unimplemented_device("TIM2",      0x40000000, 0x400);
>      create_unimplemented_device("TIM3",      0x40000400, 0x400);
> @@ -246,7 +257,6 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
>      create_unimplemented_device("DMA1",      0x40020000, 0x400);
>      create_unimplemented_device("DMA2",      0x40020400, 0x400);
>      /* RESERVED:    0x40020800, 0x800 */
> -    create_unimplemented_device("RCC",       0x40021000, 0x400);
>      /* RESERVED:    0x40021400, 0xC00 */
>      create_unimplemented_device("FLASH",     0x40022000, 0x400);
>      /* RESERVED:    0x40022400, 0xC00 */
> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
> index 4fc6b29b43..727386fa4b 100644
> --- a/hw/misc/Kconfig
> +++ b/hw/misc/Kconfig
> @@ -93,6 +93,9 @@ config STM32L4X5_EXTI
>  config STM32L4X5_SYSCFG
>      bool
>
> +config STM32L4X5_RCC
> +    bool
> +
>  config MIPS_ITU
>      bool
>
> diff --git a/hw/misc/meson.build b/hw/misc/meson.build
> index e4ef1da5a5..afd344b374 100644
> --- a/hw/misc/meson.build
> +++ b/hw/misc/meson.build
> @@ -112,6 +112,7 @@ system_ss.add(when: 'CONFIG_STM32F4XX_SYSCFG', if_true: files('stm32f4xx_syscfg.
>  system_ss.add(when: 'CONFIG_STM32F4XX_EXTI', if_true: files('stm32f4xx_exti.c'))
>  system_ss.add(when: 'CONFIG_STM32L4X5_EXTI', if_true: files('stm32l4x5_exti.c'))
>  system_ss.add(when: 'CONFIG_STM32L4X5_SYSCFG', if_true: files('stm32l4x5_syscfg.c'))
> +system_ss.add(when: 'CONFIG_STM32L4X5_RCC', if_true: files('stm32l4x5_rcc.c'))
>  system_ss.add(when: 'CONFIG_MPS2_FPGAIO', if_true: files('mps2-fpgaio.c'))
>  system_ss.add(when: 'CONFIG_MPS2_SCC', if_true: files('mps2-scc.c'))
>
> diff --git a/hw/misc/stm32l4x5_rcc.c b/hw/misc/stm32l4x5_rcc.c
> new file mode 100644
> index 0000000000..38ca8aad7d
> --- /dev/null
> +++ b/hw/misc/stm32l4x5_rcc.c
> @@ -0,0 +1,433 @@
> +/*
> + * STM32L4X5 RCC (Reset and clock control)
> + *
> + * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr>
> + * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.fr>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2 or later.
> + * See the COPYING file in the top-level directory.
> + *
> + * The reference used is the STMicroElectronics RM0351 Reference manual
> + * for STM32L4x5 and STM32L4x6 advanced Arm ® -based 32-bit MCUs.
> + *
> + * Inspired by the BCM2835 CPRMAN clock manager implementation by Luc Michel.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "qemu/module.h"
> +#include "qemu/timer.h"
> +#include "qapi/error.h"
> +#include "migration/vmstate.h"
> +#include "hw/misc/stm32l4x5_rcc.h"
> +#include "hw/misc/stm32l4x5_rcc_internals.h"
> +#include "hw/clock.h"
> +#include "hw/irq.h"
> +#include "hw/qdev-clock.h"
> +#include "hw/qdev-properties.h"
> +#include "hw/qdev-properties-system.h"
> +#include "trace.h"
> +
> +#define HSE_DEFAULT_FRQ 48000000ULL
> +#define HSI_FRQ 16000000ULL
> +#define MSI_DEFAULT_FRQ 4000000ULL
> +#define LSE_FRQ 32768ULL
> +#define LSI_FRQ 32000ULL
> +
> +static void rcc_update_irq(Stm32l4x5RccState *s)
> +{
> +    if (s->cifr & CIFR_IRQ_MASK) {
> +        qemu_irq_raise(s->irq);
> +    } else {
> +        qemu_irq_lower(s->irq);
> +    }
> +}
> +
> +static void stm32l4x5_rcc_reset_hold(Object *obj)
> +{
> +    Stm32l4x5RccState *s = STM32L4X5_RCC(obj);
> +    s->cr = 0x00000063;
> +    /*
> +     * Factory-programmed calibration data
> +     * From the reference manual: 0x10XX 00XX
> +     * Value taken from a real card.
> +     */
> +    s->icscr = 0x106E0082;
> +    s->cfgr = 0x0;
> +    s->pllcfgr = 0x00001000;
> +    s->pllsai1cfgr = 0x00001000;
> +    s->pllsai2cfgr = 0x00001000;
> +    s->cier = 0x0;
> +    s->cifr = 0x0;
> +    s->ahb1rstr = 0x0;
> +    s->ahb2rstr = 0x0;
> +    s->ahb3rstr = 0x0;
> +    s->apb1rstr1 = 0x0;
> +    s->apb1rstr2 = 0x0;
> +    s->apb2rstr = 0x0;
> +    s->ahb1enr = 0x00000100;
> +    s->ahb2enr = 0x0;
> +    s->ahb3enr = 0x0;
> +    s->apb1enr1 = 0x0;
> +    s->apb1enr2 = 0x0;
> +    s->apb2enr = 0x0;
> +    s->ahb1smenr = 0x00011303;
> +    s->ahb2smenr = 0x000532FF;
> +    s->ahb3smenr =  0x00000101;
> +    s->apb1smenr1 = 0xF2FECA3F;
> +    s->apb1smenr2 = 0x00000025;
> +    s->apb2smenr = 0x01677C01;
> +    s->ccipr = 0x0;
> +    s->bdcr = 0x0;
> +    s->csr = 0x0C000600;
> +}
> +
> +static uint64_t stm32l4x5_rcc_read(void *opaque, hwaddr addr,
> +                                     unsigned int size)
> +{
> +    Stm32l4x5RccState *s = opaque;
> +    uint64_t retvalue = 0;
> +
> +    switch (addr) {
> +    case A_CR:
> +        retvalue = s->cr;
> +        break;
> +    case A_ICSCR:
> +        retvalue = s->icscr;
> +        break;
> +    case A_CFGR:
> +        retvalue = s->cfgr;
> +        break;
> +    case A_PLLCFGR:
> +        retvalue = s->pllcfgr;
> +        break;
> +    case A_PLLSAI1CFGR:
> +        retvalue = s->pllsai1cfgr;
> +        break;
> +    case A_PLLSAI2CFGR:
> +        retvalue = s->pllsai2cfgr;
> +        break;
> +    case A_CIER:
> +        retvalue = s->cier;
> +        break;
> +    case A_CIFR:
> +        retvalue = s->cifr;
> +        break;
> +    case A_CICR:
> +        /* CICR is write only, return the reset value = 0 */
> +        break;
> +    case A_AHB1RSTR:
> +        retvalue = s->ahb1rstr;
> +        break;
> +    case A_AHB2RSTR:
> +        retvalue = s->ahb2rstr;
> +        break;
> +    case A_AHB3RSTR:
> +        retvalue = s->ahb3rstr;
> +        break;
> +    case A_APB1RSTR1:
> +        retvalue = s->apb1rstr1;
> +        break;
> +    case A_APB1RSTR2:
> +        retvalue = s->apb1rstr2;
> +        break;
> +    case A_APB2RSTR:
> +        retvalue = s->apb2rstr;
> +        break;
> +    case A_AHB1ENR:
> +        retvalue = s->ahb1enr;
> +        break;
> +    case A_AHB2ENR:
> +        retvalue = s->ahb2enr;
> +        break;
> +    case A_AHB3ENR:
> +        retvalue = s->ahb3enr;
> +        break;
> +    case A_APB1ENR1:
> +        retvalue = s->apb1enr1;
> +        break;
> +    case A_APB1ENR2:
> +        retvalue = s->apb1enr2;
> +        break;
> +    case A_APB2ENR:
> +        retvalue = s->apb2enr;
> +        break;
> +    case A_AHB1SMENR:
> +        retvalue = s->ahb1smenr;
> +        break;
> +    case A_AHB2SMENR:
> +        retvalue = s->ahb2smenr;
> +        break;
> +    case A_AHB3SMENR:
> +        retvalue = s->ahb3smenr;
> +        break;
> +    case A_APB1SMENR1:
> +        retvalue = s->apb1smenr1;
> +        break;
> +    case A_APB1SMENR2:
> +        retvalue = s->apb1smenr2;
> +        break;
> +    case A_APB2SMENR:
> +        retvalue = s->apb2smenr;
> +        break;
> +    case A_CCIPR:
> +        retvalue = s->ccipr;
> +        break;
> +    case A_BDCR:
> +        retvalue = s->bdcr;
> +        break;
> +    case A_CSR:
> +        retvalue = s->csr;
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
> +        break;
> +    }
> +
> +    trace_stm32l4x5_rcc_read(addr, retvalue);
> +
> +    return retvalue;
> +}
> +
> +static void stm32l4x5_rcc_write(void *opaque, hwaddr addr,
> +                                  uint64_t val64, unsigned int size)
> +{
> +    Stm32l4x5RccState *s = opaque;
> +    const uint32_t value = val64;
> +
> +    trace_stm32l4x5_rcc_write(addr, value);
> +
> +    switch (addr) {
> +    case A_CR:
> +        s->cr = (s->cr & CR_READ_SET_MASK) |
> +                (value & (CR_READ_SET_MASK | ~CR_READ_ONLY_MASK));
> +        break;
> +    case A_ICSCR:
> +        s->icscr = value & ~ICSCR_READ_ONLY_MASK;
> +        break;
> +    case A_CFGR:
> +        s->cfgr = value & ~CFGR_READ_ONLY_MASK;
> +        break;
> +    case A_PLLCFGR:
> +        s->pllcfgr = value;
> +        break;
> +    case A_PLLSAI1CFGR:
> +        s->pllsai1cfgr = value;
> +        break;
> +    case A_PLLSAI2CFGR:
> +        s->pllsai2cfgr = value;
> +        break;
> +    case A_CIER:
> +        s->cier = value;
> +        break;
> +    case A_CIFR:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +            "%s: Write attempt into read-only register (CIFR) 0x%"PRIx32"\n",
> +            __func__, value);
> +        break;
> +    case A_CICR:
> +        /* Clear interrupt flags by writing a 1 to the CICR register */
> +        s->cifr &= ~value;
> +        rcc_update_irq(s);
> +        break;
> +    /* Reset behaviors are not implemented */
> +    case A_AHB1RSTR:
> +        s->ahb1rstr = value;
> +        break;
> +    case A_AHB2RSTR:
> +        s->ahb2rstr = value;
> +        break;
> +    case A_AHB3RSTR:
> +        s->ahb3rstr = value;
> +        break;
> +    case A_APB1RSTR1:
> +        s->apb1rstr1 = value;
> +        break;
> +    case A_APB1RSTR2:
> +        s->apb1rstr2 = value;
> +        break;
> +    case A_APB2RSTR:
> +        s->apb2rstr = value;
> +        break;
> +    case A_AHB1ENR:
> +        s->ahb1enr = value;
> +        break;
> +    case A_AHB2ENR:
> +        s->ahb2enr = value;
> +        break;
> +    case A_AHB3ENR:
> +        s->ahb3enr = value;
> +        break;
> +    case A_APB1ENR1:
> +        s->apb1enr1 = value;
> +        break;
> +    case A_APB1ENR2:
> +        s->apb1enr2 = value;
> +        break;
> +    case A_APB2ENR:
> +        s->apb2enr = (s->apb2enr & APB2ENR_READ_SET_MASK) | value;
> +        break;
> +    /* Behaviors for Sleep and Stop modes are not implemented */
> +    case A_AHB1SMENR:
> +        s->ahb1smenr = value;
> +        break;
> +    case A_AHB2SMENR:
> +        s->ahb2smenr = value;
> +        break;
> +    case A_AHB3SMENR:
> +        s->ahb3smenr = value;
> +        break;
> +    case A_APB1SMENR1:
> +        s->apb1smenr1 = value;
> +        break;
> +    case A_APB1SMENR2:
> +        s->apb1smenr2 = value;
> +        break;
> +    case A_APB2SMENR:
> +        s->apb2smenr = value;
> +        break;
> +    case A_CCIPR:
> +        s->ccipr = value;
> +        break;
> +    case A_BDCR:
> +        s->bdcr = value & ~BDCR_READ_ONLY_MASK;
> +        break;
> +    case A_CSR:
> +        s->csr = value & ~CSR_READ_ONLY_MASK;
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
> +    }
> +}
> +
> +static const MemoryRegionOps stm32l4x5_rcc_ops = {
> +    .read = stm32l4x5_rcc_read,
> +    .write = stm32l4x5_rcc_write,
> +    .endianness = DEVICE_NATIVE_ENDIAN,
> +    .valid = {
> +        .max_access_size = 4,
> +        .unaligned = false
> +    },
> +};
> +
> +static const ClockPortInitArray stm32l4x5_rcc_clocks = {
> +    QDEV_CLOCK_IN(Stm32l4x5RccState, hsi16_rc, NULL, 0),
> +    QDEV_CLOCK_IN(Stm32l4x5RccState, msi_rc, NULL, 0),
> +    QDEV_CLOCK_IN(Stm32l4x5RccState, hse, NULL, 0),
> +    QDEV_CLOCK_IN(Stm32l4x5RccState, lsi_rc, NULL, 0),
> +    QDEV_CLOCK_IN(Stm32l4x5RccState, lse_crystal, NULL, 0),
> +    QDEV_CLOCK_IN(Stm32l4x5RccState, sai1_extclk, NULL, 0),
> +    QDEV_CLOCK_IN(Stm32l4x5RccState, sai2_extclk, NULL, 0),
> +    QDEV_CLOCK_END
> +};
> +
> +
> +static void stm32l4x5_rcc_init(Object *obj)
> +{
> +    Stm32l4x5RccState *s = STM32L4X5_RCC(obj);
> +
> +    sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
> +
> +    memory_region_init_io(&s->mmio, obj, &stm32l4x5_rcc_ops, s,
> +                          TYPE_STM32L4X5_RCC, 0x400);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
> +
> +    qdev_init_clocks(DEVICE(s), stm32l4x5_rcc_clocks);
> +
> +    s->gnd = clock_new(obj, "gnd");
> +}
> +
> +static const VMStateDescription vmstate_stm32l4x5_rcc = {
> +    .name = TYPE_STM32L4X5_RCC,
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT32(cr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(icscr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(cfgr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(pllcfgr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(pllsai1cfgr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(pllsai2cfgr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(cier, Stm32l4x5RccState),
> +        VMSTATE_UINT32(cifr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(ahb1rstr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(ahb2rstr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(ahb3rstr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(apb1rstr1, Stm32l4x5RccState),
> +        VMSTATE_UINT32(apb1rstr2, Stm32l4x5RccState),
> +        VMSTATE_UINT32(apb2rstr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(ahb1enr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(ahb2enr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(ahb3enr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(apb1enr1, Stm32l4x5RccState),
> +        VMSTATE_UINT32(apb1enr2, Stm32l4x5RccState),
> +        VMSTATE_UINT32(apb2enr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(ahb1smenr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(ahb2smenr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(ahb3smenr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(apb1smenr1, Stm32l4x5RccState),
> +        VMSTATE_UINT32(apb1smenr2, Stm32l4x5RccState),
> +        VMSTATE_UINT32(apb2smenr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(ccipr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(bdcr, Stm32l4x5RccState),
> +        VMSTATE_UINT32(csr, Stm32l4x5RccState),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +
> +static void stm32l4x5_rcc_realize(DeviceState *dev, Error **errp)
> +{
> +    Stm32l4x5RccState *s = STM32L4X5_RCC(dev);
> +
> +    if (s->hse_frequency <  4000000ULL ||
> +        s->hse_frequency > 48000000ULL) {
> +            error_setg(errp,
> +                "HSE frequency is outside of the allowed [4-48]Mhz range: %" PRIx64 "",
> +                s->hse_frequency);
> +            return;
> +        }
> +
> +    clock_update_hz(s->msi_rc, MSI_DEFAULT_FRQ);
> +    clock_update_hz(s->sai1_extclk, s->sai1_extclk_frequency);
> +    clock_update_hz(s->sai2_extclk, s->sai2_extclk_frequency);
> +    clock_update(s->gnd, 0);
> +}
> +
> +static Property stm32l4x5_rcc_properties[] = {
> +    DEFINE_PROP_UINT64("hse_frequency", Stm32l4x5RccState,
> +        hse_frequency, HSE_DEFAULT_FRQ),
> +    DEFINE_PROP_UINT64("sai1_extclk_frequency", Stm32l4x5RccState,
> +        sai1_extclk_frequency, 0),
> +    DEFINE_PROP_UINT64("sai2_extclk_frequency", Stm32l4x5RccState,
> +        sai2_extclk_frequency, 0),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void stm32l4x5_rcc_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    ResettableClass *rc = RESETTABLE_CLASS(klass);
> +
> +
> +    rc->phases.hold = stm32l4x5_rcc_reset_hold;
> +    device_class_set_props(dc, stm32l4x5_rcc_properties);
> +    dc->realize = stm32l4x5_rcc_realize;
> +    dc->vmsd = &vmstate_stm32l4x5_rcc;
> +}
> +
> +static const TypeInfo stm32l4x5_rcc_types[] = {
> +    {
> +        .name           = TYPE_STM32L4X5_RCC,
> +        .parent         = TYPE_SYS_BUS_DEVICE,
> +        .instance_size  = sizeof(Stm32l4x5RccState),
> +        .instance_init  = stm32l4x5_rcc_init,
> +        .class_init     = stm32l4x5_rcc_class_init,
> +    }
> +};
> +
> +DEFINE_TYPES(stm32l4x5_rcc_types)
> diff --git a/hw/misc/trace-events b/hw/misc/trace-events
> index 5f5bc92222..62a7599353 100644
> --- a/hw/misc/trace-events
> +++ b/hw/misc/trace-events
> @@ -174,6 +174,10 @@ stm32l4x5_exti_set_irq(int irq, int level) "Set EXTI: %d to %d"
>  stm32l4x5_exti_read(uint64_t addr, uint64_t data) "reg read: addr: 0x%" PRIx64 " val: 0x%" PRIx64 ""
>  stm32l4x5_exti_write(uint64_t addr, uint64_t data) "reg write: addr: 0x%" PRIx64 " val: 0x%" PRIx64 ""
>
> +# stm32l4x5_rcc.c
> +stm32l4x5_rcc_read(uint64_t addr, uint32_t data) "RCC: Read <0x%" PRIx64 "> -> 0x%" PRIx32 ""
> +stm32l4x5_rcc_write(uint64_t addr, uint32_t data) "RCC: Write <0x%" PRIx64 "> <- 0x%" PRIx32 ""
> +
>  # tz-mpc.c
>  tz_mpc_reg_read(uint32_t offset, uint64_t data, unsigned size) "TZ MPC regs read: offset 0x%x data 0x%" PRIx64 " size %u"
>  tz_mpc_reg_write(uint32_t offset, uint64_t data, unsigned size) "TZ MPC regs write: offset 0x%x data 0x%" PRIx64 " size %u"
> diff --git a/include/hw/arm/stm32l4x5_soc.h b/include/hw/arm/stm32l4x5_soc.h
> index baf70410b5..e480fcc976 100644
> --- a/include/hw/arm/stm32l4x5_soc.h
> +++ b/include/hw/arm/stm32l4x5_soc.h
> @@ -28,6 +28,7 @@
>  #include "hw/arm/armv7m.h"
>  #include "hw/misc/stm32l4x5_syscfg.h"
>  #include "hw/misc/stm32l4x5_exti.h"
> +#include "hw/misc/stm32l4x5_rcc.h"
>  #include "qom/object.h"
>
>  #define TYPE_STM32L4X5_SOC "stm32l4x5-soc"
> @@ -43,6 +44,7 @@ struct Stm32l4x5SocState {
>
>      Stm32l4x5ExtiState exti;
>      Stm32l4x5SyscfgState syscfg;
> +    Stm32l4x5RccState rcc;
>
>      MemoryRegion sram1;
>      MemoryRegion sram2;
> diff --git a/include/hw/misc/stm32l4x5_rcc.h b/include/hw/misc/stm32l4x5_rcc.h
> new file mode 100644
> index 0000000000..5157e96635
> --- /dev/null
> +++ b/include/hw/misc/stm32l4x5_rcc.h
> @@ -0,0 +1,80 @@
> +/*
> + * STM32L4X5 RCC (Reset and clock control)
> + *
> + * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr>
> + * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.fr>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2 or later.
> + * See the COPYING file in the top-level directory.
> + *
> + * The reference used is the STMicroElectronics RM0351 Reference manual
> + * for STM32L4x5 and STM32L4x6 advanced Arm ® -based 32-bit MCUs.
> + *
> + * Inspired by the BCM2835 CPRMAN clock manager by Luc Michel.
> + */
> +
> +#ifndef HW_STM32L4X5_RCC_H
> +#define HW_STM32L4X5_RCC_H
> +
> +#include "hw/sysbus.h"
> +#include "qom/object.h"
> +
> +#define TYPE_STM32L4X5_RCC "stm32l4x5-rcc"
> +OBJECT_DECLARE_SIMPLE_TYPE(Stm32l4x5RccState, STM32L4X5_RCC)
> +
> +/* In the Stm32l4x5 clock tree, mux have at most 7 sources */
> +#define RCC_NUM_CLOCK_MUX_SRC 7
> +struct Stm32l4x5RccState {
> +    SysBusDevice parent_obj;
> +
> +    MemoryRegion mmio;
> +
> +    uint32_t cr;
> +    uint32_t icscr;
> +    uint32_t cfgr;
> +    uint32_t pllcfgr;
> +    uint32_t pllsai1cfgr;
> +    uint32_t pllsai2cfgr;
> +    uint32_t cier;
> +    uint32_t cifr;
> +    uint32_t ahb1rstr;
> +    uint32_t ahb2rstr;
> +    uint32_t ahb3rstr;
> +    uint32_t apb1rstr1;
> +    uint32_t apb1rstr2;
> +    uint32_t apb2rstr;
> +    uint32_t ahb1enr;
> +    uint32_t ahb2enr;
> +    uint32_t ahb3enr;
> +    uint32_t apb1enr1;
> +    uint32_t apb1enr2;
> +    uint32_t apb2enr;
> +    uint32_t ahb1smenr;
> +    uint32_t ahb2smenr;
> +    uint32_t ahb3smenr;
> +    uint32_t apb1smenr1;
> +    uint32_t apb1smenr2;
> +    uint32_t apb2smenr;
> +    uint32_t ccipr;
> +    uint32_t bdcr;
> +    uint32_t csr;
> +
> +    /* Clock sources */
> +    Clock *gnd;
> +    Clock *hsi16_rc;
> +    Clock *msi_rc;
> +    Clock *hse;
> +    Clock *lsi_rc;
> +    Clock *lse_crystal;
> +    Clock *sai1_extclk;
> +    Clock *sai2_extclk;
> +
> +    qemu_irq irq;
> +    uint64_t hse_frequency;
> +    uint64_t sai1_extclk_frequency;
> +    uint64_t sai2_extclk_frequency;
> +};
> +
> +#endif /* HW_STM32L4X5_RCC_H */
> diff --git a/include/hw/misc/stm32l4x5_rcc_internals.h b/include/hw/misc/stm32l4x5_rcc_internals.h
> new file mode 100644
> index 0000000000..331ea30db5
> --- /dev/null
> +++ b/include/hw/misc/stm32l4x5_rcc_internals.h
> @@ -0,0 +1,286 @@
> +/*
> + * STM32L4X5 RCC (Reset and clock control)
> + *
> + * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr>
> + * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.fr>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2 or later.
> + * See the COPYING file in the top-level directory.
> + *
> + * The reference used is the STMicroElectronics RM0351 Reference manual
> + * for STM32L4x5 and STM32L4x6 advanced Arm ® -based 32-bit MCUs.
> + *
> + * Inspired by the BCM2835 CPRMAN clock manager implementation by Luc Michel.
> + */
> +
> +#ifndef HW_STM32L4X5_RCC_INTERNALS_H
> +#define HW_STM32L4X5_RCC_INTERNALS_H
> +
> +#include "hw/registerfields.h"
> +#include "hw/misc/stm32l4x5_rcc.h"
> +
> +
> +/* Register map */
> +REG32(CR, 0x00)
> +    FIELD(CR, PLLSAI2RDY, 29, 1)
> +    FIELD(CR, PLLSAI2ON, 28, 1)
> +    FIELD(CR, PLLSAI1RDY, 27, 1)
> +    FIELD(CR, PLLSAI1ON, 26, 1)
> +    FIELD(CR, PLLRDY, 25, 1)
> +    FIELD(CR, PLLON, 24, 1)
> +    FIELD(CR, CSSON, 19, 1)
> +    FIELD(CR, HSEBYP, 18, 1)
> +    FIELD(CR, HSERDY, 17, 1)
> +    FIELD(CR, HSEON, 16, 1)
> +    FIELD(CR, HSIASFS, 11, 1)
> +    FIELD(CR, HSIRDY, 10, 1)
> +    FIELD(CR, HSIKERON, 9, 1)
> +    FIELD(CR, HSION, 8, 1)
> +    FIELD(CR, MSIRANGE, 4, 4)
> +    FIELD(CR, MSIRGSEL, 3, 1)
> +    FIELD(CR, MSIPLLEN, 2, 1)
> +    FIELD(CR, MSIRDY, 1, 1)
> +    FIELD(CR, MSION, 0, 1)
> +REG32(ICSCR, 0x04)
> +    FIELD(ICSCR, HSITRIM, 24, 7)
> +    FIELD(ICSCR, HSICAL, 16, 8)
> +    FIELD(ICSCR, MSITRIM, 8, 8)
> +    FIELD(ICSCR, MSICAL, 0, 8)
> +REG32(CFGR, 0x08)
> +    FIELD(CFGR, MCOPRE, 28, 3)
> +    /* MCOSEL[2:0] only for STM32L475xx/476xx/486xx devices */
> +    FIELD(CFGR, MCOSEL, 24, 3)
> +    FIELD(CFGR, STOPWUCK, 15, 1)
> +    FIELD(CFGR, PPRE2, 11, 3)
> +    FIELD(CFGR, PPRE1, 8, 3)
> +    FIELD(CFGR, HPRE, 4, 4)
> +    FIELD(CFGR, SWS, 2, 2)
> +    FIELD(CFGR, SW, 0, 2)
> +REG32(PLLCFGR, 0x0C)
> +    FIELD(PLLCFGR, PLLPDIV, 27, 5)
> +    FIELD(PLLCFGR, PLLR, 25, 2)
> +    FIELD(PLLCFGR, PLLREN, 24, 1)
> +    FIELD(PLLCFGR, PLLQ, 21, 2)
> +    FIELD(PLLCFGR, PLLQEN, 20, 1)
> +    FIELD(PLLCFGR, PLLP, 17, 1)
> +    FIELD(PLLCFGR, PLLPEN, 16, 1)
> +    FIELD(PLLCFGR, PLLN, 8, 7)
> +    FIELD(PLLCFGR, PLLM, 4, 3)
> +    FIELD(PLLCFGR, PLLSRC, 0, 2)
> +REG32(PLLSAI1CFGR, 0x10)
> +    FIELD(PLLSAI1CFGR, PLLSAI1PDIV, 27, 5)
> +    FIELD(PLLSAI1CFGR, PLLSAI1R, 25, 2)
> +    FIELD(PLLSAI1CFGR, PLLSAI1REN, 24, 1)
> +    FIELD(PLLSAI1CFGR, PLLSAI1Q, 21, 2)
> +    FIELD(PLLSAI1CFGR, PLLSAI1QEN, 20, 1)
> +    FIELD(PLLSAI1CFGR, PLLSAI1P, 17, 1)
> +    FIELD(PLLSAI1CFGR, PLLSAI1PEN, 16, 1)
> +    FIELD(PLLSAI1CFGR, PLLSAI1N, 8, 7)
> +REG32(PLLSAI2CFGR, 0x14)
> +    FIELD(PLLSAI2CFGR, PLLSAI2PDIV, 27, 5)
> +    FIELD(PLLSAI2CFGR, PLLSAI2R, 25, 2)
> +    FIELD(PLLSAI2CFGR, PLLSAI2REN, 24, 1)
> +    FIELD(PLLSAI2CFGR, PLLSAI2Q, 21, 2)
> +    FIELD(PLLSAI2CFGR, PLLSAI2QEN, 20, 1)
> +    FIELD(PLLSAI2CFGR, PLLSAI2P, 17, 1)
> +    FIELD(PLLSAI2CFGR, PLLSAI2PEN, 16, 1)
> +    FIELD(PLLSAI2CFGR, PLLSAI2N, 8, 7)
> +REG32(CIER, 0x18)
> +    /* HSI48RDYIE: only on STM32L496xx/4A6xx devices */
> +    FIELD(CIER, LSECSSIE, 9, 1)
> +    FIELD(CIER, PLLSAI2RDYIE, 7, 1)
> +    FIELD(CIER, PLLSAI1RDYIE, 6, 1)
> +    FIELD(CIER, PLLRDYIE, 5, 1)
> +    FIELD(CIER, HSERDYIE, 4, 1)
> +    FIELD(CIER, HSIRDYIE, 3, 1)
> +    FIELD(CIER, MSIRDYIE, 2, 1)
> +    FIELD(CIER, LSERDYIE, 1, 1)
> +    FIELD(CIER, LSIRDYIE, 0, 1)
> +REG32(CIFR, 0x1C)
> +    /* HSI48RDYF: only on STM32L496xx/4A6xx devices */
> +    FIELD(CIFR, LSECSSF, 9, 1)
> +    FIELD(CIFR, CSSF, 8, 1)
> +    FIELD(CIFR, PLLSAI2RDYF, 7, 1)
> +    FIELD(CIFR, PLLSAI1RDYF, 6, 1)
> +    FIELD(CIFR, PLLRDYF, 5, 1)
> +    FIELD(CIFR, HSERDYF, 4, 1)
> +    FIELD(CIFR, HSIRDYF, 3, 1)
> +    FIELD(CIFR, MSIRDYF, 2, 1)
> +    FIELD(CIFR, LSERDYF, 1, 1)
> +    FIELD(CIFR, LSIRDYF, 0, 1)
> +REG32(CICR, 0x20)
> +    /* HSI48RDYC: only on STM32L496xx/4A6xx devices */
> +    FIELD(CICR, LSECSSC, 9, 1)
> +    FIELD(CICR, CSSC, 8, 1)
> +    FIELD(CICR, PLLSAI2RDYC, 7, 1)
> +    FIELD(CICR, PLLSAI1RDYC, 6, 1)
> +    FIELD(CICR, PLLRDYC, 5, 1)
> +    FIELD(CICR, HSERDYC, 4, 1)
> +    FIELD(CICR, HSIRDYC, 3, 1)
> +    FIELD(CICR, MSIRDYC, 2, 1)
> +    FIELD(CICR, LSERDYC, 1, 1)
> +    FIELD(CICR, LSIRDYC, 0, 1)
> +REG32(AHB1RSTR, 0x28)
> +REG32(AHB2RSTR, 0x2C)
> +REG32(AHB3RSTR, 0x30)
> +REG32(APB1RSTR1, 0x38)
> +REG32(APB1RSTR2, 0x3C)
> +REG32(APB2RSTR, 0x40)
> +REG32(AHB1ENR, 0x48)
> +    /* DMA2DEN: reserved for STM32L475xx */
> +    FIELD(AHB1ENR, TSCEN, 16, 1)
> +    FIELD(AHB1ENR, CRCEN, 12, 1)
> +    FIELD(AHB1ENR, FLASHEN, 8, 1)
> +    FIELD(AHB1ENR, DMA2EN, 1, 1)
> +    FIELD(AHB1ENR, DMA1EN, 0, 1)
> +REG32(AHB2ENR, 0x4C)
> +    FIELD(AHB2ENR, RNGEN, 18, 1)
> +    /* HASHEN: reserved for STM32L475xx */
> +    FIELD(AHB2ENR, AESEN, 16, 1)
> +    /* DCMIEN: reserved for STM32L475xx */
> +    FIELD(AHB2ENR, ADCEN, 13, 1)
> +    FIELD(AHB2ENR, OTGFSEN, 12, 1)
> +    /* GPIOIEN: reserved for STM32L475xx */
> +    FIELD(AHB2ENR, GPIOHEN, 7, 1)
> +    FIELD(AHB2ENR, GPIOGEN, 6, 1)
> +    FIELD(AHB2ENR, GPIOFEN, 5, 1)
> +    FIELD(AHB2ENR, GPIOEEN, 4, 1)
> +    FIELD(AHB2ENR, GPIODEN, 3, 1)
> +    FIELD(AHB2ENR, GPIOCEN, 2, 1)
> +    FIELD(AHB2ENR, GPIOBEN, 1, 1)
> +    FIELD(AHB2ENR, GPIOAEN, 0, 1)
> +REG32(AHB3ENR, 0x50)
> +    FIELD(AHB3ENR, QSPIEN, 8, 1)
> +    FIELD(AHB3ENR, FMCEN, 0, 1)
> +REG32(APB1ENR1, 0x58)
> +    FIELD(APB1ENR1, LPTIM1EN, 31, 1)
> +    FIELD(APB1ENR1, OPAMPEN, 30, 1)
> +    FIELD(APB1ENR1, DAC1EN, 29, 1)
> +    FIELD(APB1ENR1, PWREN, 28, 1)
> +    FIELD(APB1ENR1, CAN2EN, 26, 1)
> +    FIELD(APB1ENR1, CAN1EN, 25, 1)
> +    /* CRSEN: reserved for STM32L475xx */
> +    FIELD(APB1ENR1, I2C3EN, 23, 1)
> +    FIELD(APB1ENR1, I2C2EN, 22, 1)
> +    FIELD(APB1ENR1, I2C1EN, 21, 1)
> +    FIELD(APB1ENR1, UART5EN, 20, 1)
> +    FIELD(APB1ENR1, UART4EN, 19, 1)
> +    FIELD(APB1ENR1, USART3EN, 18, 1)
> +    FIELD(APB1ENR1, USART2EN, 17, 1)
> +    FIELD(APB1ENR1, SPI3EN, 15, 1)
> +    FIELD(APB1ENR1, SPI2EN, 14, 1)
> +    FIELD(APB1ENR1, WWDGEN, 11, 1)
> +    /* RTCAPBEN: reserved for STM32L475xx */
> +    FIELD(APB1ENR1, LCDEN, 9, 1)
> +    FIELD(APB1ENR1, TIM7EN, 5, 1)
> +    FIELD(APB1ENR1, TIM6EN, 4, 1)
> +    FIELD(APB1ENR1, TIM5EN, 3, 1)
> +    FIELD(APB1ENR1, TIM4EN, 2, 1)
> +    FIELD(APB1ENR1, TIM3EN, 1, 1)
> +    FIELD(APB1ENR1, TIM2EN, 0, 1)
> +REG32(APB1ENR2, 0x5C)
> +    FIELD(APB1ENR2, LPTIM2EN, 5, 1)
> +    FIELD(APB1ENR2, SWPMI1EN, 2, 1)
> +    /* I2C4EN: reserved for STM32L475xx */
> +    FIELD(APB1ENR2, LPUART1EN, 0, 1)
> +REG32(APB2ENR, 0x60)
> +    FIELD(APB2ENR, DFSDM1EN, 24, 1)
> +    FIELD(APB2ENR, SAI2EN, 22, 1)
> +    FIELD(APB2ENR, SAI1EN, 21, 1)
> +    FIELD(APB2ENR, TIM17EN, 18, 1)
> +    FIELD(APB2ENR, TIM16EN, 17, 1)
> +    FIELD(APB2ENR, TIM15EN, 16, 1)
> +    FIELD(APB2ENR, USART1EN, 14, 1)
> +    FIELD(APB2ENR, TIM8EN, 13, 1)
> +    FIELD(APB2ENR, SPI1EN, 12, 1)
> +    FIELD(APB2ENR, TIM1EN, 11, 1)
> +    FIELD(APB2ENR, SDMMC1EN, 10, 1)
> +    FIELD(APB2ENR, FWEN, 7, 1)
> +    FIELD(APB2ENR, SYSCFGEN, 0, 1)
> +REG32(AHB1SMENR, 0x68)
> +REG32(AHB2SMENR, 0x6C)
> +REG32(AHB3SMENR, 0x70)
> +REG32(APB1SMENR1, 0x78)
> +REG32(APB1SMENR2, 0x7C)
> +REG32(APB2SMENR, 0x80)
> +REG32(CCIPR, 0x88)
> +    FIELD(CCIPR, DFSDM1SEL, 31, 1)
> +    FIELD(CCIPR, SWPMI1SEL, 30, 1)
> +    FIELD(CCIPR, ADCSEL, 28, 2)
> +    FIELD(CCIPR, CLK48SEL, 26, 2)
> +    FIELD(CCIPR, SAI2SEL, 24, 2)
> +    FIELD(CCIPR, SAI1SEL, 22, 2)
> +    FIELD(CCIPR, LPTIM2SEL, 20, 2)
> +    FIELD(CCIPR, LPTIM1SEL, 18, 2)
> +    FIELD(CCIPR, I2C3SEL, 16, 2)
> +    FIELD(CCIPR, I2C2SEL, 14, 2)
> +    FIELD(CCIPR, I2C1SEL, 12, 2)
> +    FIELD(CCIPR, LPUART1SEL, 10, 2)
> +    FIELD(CCIPR, UART5SEL, 8, 2)
> +    FIELD(CCIPR, UART4SEL, 6, 2)
> +    FIELD(CCIPR, USART3SEL, 4, 2)
> +    FIELD(CCIPR, USART2SEL, 2, 2)
> +    FIELD(CCIPR, USART1SEL, 0, 2)
> +REG32(BDCR, 0x90)
> +    FIELD(BDCR, LSCOSEL, 25, 1)
> +    FIELD(BDCR, LSCOEN, 24, 1)
> +    FIELD(BDCR, BDRST, 16, 1)
> +    FIELD(BDCR, RTCEN, 15, 1)
> +    FIELD(BDCR, RTCSEL, 8, 2)
> +    FIELD(BDCR, LSECSSD, 6, 1)
> +    FIELD(BDCR, LSECSSON, 5, 1)
> +    FIELD(BDCR, LSEDRV, 3, 2)
> +    FIELD(BDCR, LSEBYP, 2, 1)
> +    FIELD(BDCR, LSERDY, 1, 1)
> +    FIELD(BDCR, LSEON, 0, 1)
> +REG32(CSR, 0x94)
> +    FIELD(CSR, LPWRRSTF, 31, 1)
> +    FIELD(CSR, WWDGRSTF, 30, 1)
> +    FIELD(CSR, IWWGRSTF, 29, 1)
> +    FIELD(CSR, SFTRSTF, 28, 1)
> +    FIELD(CSR, BORRSTF, 27, 1)
> +    FIELD(CSR, PINRSTF, 26, 1)
> +    FIELD(CSR, OBLRSTF, 25, 1)
> +    FIELD(CSR, FWRSTF, 24, 1)
> +    FIELD(CSR, RMVF, 23, 1)
> +    FIELD(CSR, MSISRANGE, 8, 4)
> +    FIELD(CSR, LSIRDY, 1, 1)
> +    FIELD(CSR, LSION, 0, 1)
> +/* CRRCR and CCIPR2 registers are present on L496/L4A6 devices only. */
> +
> +/* Read Only masks to prevent writes in unauthorized bits */
> +#define CR_READ_ONLY_MASK (R_CR_PLLSAI2RDY_MASK | \
> +                           R_CR_PLLSAI1RDY_MASK | \
> +                           R_CR_PLLRDY_MASK     | \
> +                           R_CR_HSERDY_MASK     | \
> +                           R_CR_HSIRDY_MASK     | \
> +                           R_CR_MSIRDY_MASK)
> +#define CR_READ_SET_MASK (R_CR_CSSON_MASK | R_CR_MSIRGSEL_MASK)
> +#define ICSCR_READ_ONLY_MASK (R_ICSCR_HSICAL_MASK | R_ICSCR_MSICAL_MASK)
> +#define CFGR_READ_ONLY_MASK (R_CFGR_SWS_MASK)
> +#define CIFR_READ_ONLY_MASK (R_CIFR_LSECSSF_MASK     | \
> +                             R_CIFR_CSSF_MASK        | \
> +                             R_CIFR_PLLSAI2RDYF_MASK | \
> +                             R_CIFR_PLLSAI1RDYF_MASK | \
> +                             R_CIFR_PLLRDYF_MASK     | \
> +                             R_CIFR_HSERDYF_MASK     | \
> +                             R_CIFR_HSIRDYF_MASK     | \
> +                             R_CIFR_MSIRDYF_MASK     | \
> +                             R_CIFR_LSERDYF_MASK     | \
> +                             R_CIFR_LSIRDYF_MASK)
> +#define CIFR_IRQ_MASK CIFR_READ_ONLY_MASK
> +#define APB2ENR_READ_SET_MASK (R_APB2ENR_FWEN_MASK)
> +#define BDCR_READ_ONLY_MASK (R_BDCR_LSECSSD_MASK | R_BDCR_LSERDY_MASK)
> +#define CSR_READ_ONLY_MASK (R_CSR_LPWRRSTF_MASK | \
> +                            R_CSR_WWDGRSTF_MASK | \
> +                            R_CSR_IWWGRSTF_MASK | \
> +                            R_CSR_SFTRSTF_MASK  | \
> +                            R_CSR_BORRSTF_MASK  | \
> +                            R_CSR_PINRSTF_MASK  | \
> +                            R_CSR_OBLRSTF_MASK  | \
> +                            R_CSR_FWRSTF_MASK   | \
> +                            R_CSR_LSIRDY_MASK)
> +
> +#endif /* HW_STM32L4X5_RCC_INTERNALS_H */
> --
> 2.34.1
>
>


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v4 3/8] Add an internal PLL Clock object
  2024-01-30 16:06 ` [PATCH v4 3/8] Add an internal PLL Clock object Arnaud Minier
@ 2024-02-01  0:18   ` Alistair Francis
  2024-02-12 19:33     ` Arnaud Minier
  0 siblings, 1 reply; 14+ messages in thread
From: Alistair Francis @ 2024-02-01  0:18 UTC (permalink / raw)
  To: Arnaud Minier
  Cc: qemu-devel, Laurent Vivier, Alistair Francis, Inès Varhol,
	Peter Maydell, Paolo Bonzini, Thomas Huth, qemu-arm

On Wed, Jan 31, 2024 at 2:09 AM Arnaud Minier
<arnaud.minier@telecom-paris.fr> wrote:
>
> This object represents the PLLs and their channels. The PLLs allow for a
> more fine-grained control of the clocks frequency.
>
> Wasn't sure about how to handle the reset and the migration so used the
> same appproach as the BCM2835 CPRMAN.
>
> Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
> Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
> ---
>  hw/misc/stm32l4x5_rcc.c                   | 175 ++++++++++++++++++++++
>  hw/misc/trace-events                      |   5 +
>  include/hw/misc/stm32l4x5_rcc.h           |  40 +++++
>  include/hw/misc/stm32l4x5_rcc_internals.h |  22 +++
>  4 files changed, 242 insertions(+)
>
> diff --git a/hw/misc/stm32l4x5_rcc.c b/hw/misc/stm32l4x5_rcc.c
> index ed10832f88..fb0233c3e9 100644
> --- a/hw/misc/stm32l4x5_rcc.c
> +++ b/hw/misc/stm32l4x5_rcc.c
> @@ -162,6 +162,156 @@ static void clock_mux_set_source(RccClockMuxState *mux, RccClockMuxSource src)
>      clock_mux_update(mux);
>  }
>
> +static void pll_update(RccPllState *pll)
> +{
> +    uint64_t vco_freq, old_channel_freq, channel_freq;
> +    int i;
> +
> +    /* The common PLLM factor is handled by the PLL mux */
> +    vco_freq = muldiv64(clock_get_hz(pll->in), pll->vco_multiplier, 1);
> +
> +    for (i = 0; i < RCC_NUM_CHANNEL_PLL_OUT; i++) {
> +        if (!pll->channel_exists[i]) {
> +            continue;
> +        }
> +
> +        old_channel_freq = clock_get_hz(pll->channels[i]);
> +        if (!pll->enabled ||
> +            !pll->channel_enabled[i] ||
> +            !pll->channel_divider[i]) {
> +            channel_freq = 0;
> +        } else {
> +            channel_freq = muldiv64(vco_freq,
> +                                    1,
> +                                    pll->channel_divider[i]);
> +        }
> +
> +        /* No change, early continue to avoid log spam and useless propagation */
> +        if (old_channel_freq == channel_freq) {
> +            continue;
> +        }
> +
> +        clock_update_hz(pll->channels[i], channel_freq);
> +        trace_stm32l4x5_rcc_pll_update(pll->id, i, vco_freq,
> +            old_channel_freq, channel_freq);
> +    }
> +}
> +
> +static void pll_src_update(void *opaque, ClockEvent event)
> +{
> +    RccPllState *s = opaque;
> +    pll_update(s);
> +}
> +
> +static void pll_init(Object *obj)
> +{
> +    RccPllState *s = RCC_PLL(obj);
> +    size_t i;
> +
> +    s->in = qdev_init_clock_in(DEVICE(s), "in",
> +                               pll_src_update, s, ClockUpdate);
> +
> +    const char *names[] = {
> +        "out-p", "out-q", "out-r",
> +    };
> +
> +    for (i = 0; i < RCC_NUM_CHANNEL_PLL_OUT; i++) {
> +        s->channels[i] = qdev_init_clock_out(DEVICE(s), names[i]);
> +    }
> +}
> +
> +static void pll_reset_hold(Object *obj)
> +{ }
> +
> +static const VMStateDescription pll_vmstate = {
> +    .name = TYPE_RCC_PLL,
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT32(id, RccPllState),
> +        VMSTATE_CLOCK(in, RccPllState),
> +        VMSTATE_ARRAY_CLOCK(channels, RccPllState,
> +                            RCC_NUM_CHANNEL_PLL_OUT),
> +        VMSTATE_BOOL(enabled, RccPllState),
> +        VMSTATE_UINT32(vco_multiplier, RccPllState),
> +        VMSTATE_BOOL_ARRAY(channel_enabled, RccPllState, RCC_NUM_CHANNEL_PLL_OUT),
> +        VMSTATE_BOOL_ARRAY(channel_exists, RccPllState, RCC_NUM_CHANNEL_PLL_OUT),
> +        VMSTATE_UINT32_ARRAY(channel_divider, RccPllState, RCC_NUM_CHANNEL_PLL_OUT),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static void pll_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    ResettableClass *rc = RESETTABLE_CLASS(klass);
> +
> +    rc->phases.hold = pll_reset_hold;
> +    dc->vmsd = &pll_vmstate;
> +}
> +
> +static void pll_set_vco_multiplier(RccPllState *pll, uint32_t vco_multiplier)
> +{
> +    if (pll->vco_multiplier == vco_multiplier) {
> +        return;
> +    }
> +
> +    if (vco_multiplier < 8 || vco_multiplier > 86) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +            "%s: VCO multiplier is out of bound (%u) for PLL %u\n",
> +            __func__, vco_multiplier, pll->id);

Should we bail out with an invalid value?

Alistair

> +    }
> +
> +    trace_stm32l4x5_rcc_pll_set_vco_multiplier(pll->id,
> +        pll->vco_multiplier, vco_multiplier);
> +
> +    pll->vco_multiplier = vco_multiplier;
> +    pll_update(pll);
> +}
> +
> +static void pll_set_enable(RccPllState *pll, bool enabled)
> +{
> +    if (pll->enabled == enabled) {
> +        return;
> +    }
> +
> +    pll->enabled = enabled;
> +    pll_update(pll);
> +}
> +
> +static void pll_set_channel_enable(RccPllState *pll,
> +                                   PllCommonChannels channel,
> +                                   bool enabled)
> +{
> +    if (pll->channel_enabled[channel] == enabled) {
> +        return;
> +    }
> +
> +    if (enabled) {
> +        trace_stm32l4x5_rcc_pll_channel_enable(pll->id, channel);
> +    } else {
> +        trace_stm32l4x5_rcc_pll_channel_disable(pll->id, channel);
> +    }
> +
> +    pll->channel_enabled[channel] = enabled;
> +    pll_update(pll);
> +}
> +
> +static void pll_set_channel_divider(RccPllState *pll,
> +                                    PllCommonChannels channel,
> +                                    uint32_t divider)
> +{
> +    if (pll->channel_divider[channel] == divider) {
> +        return;
> +    }
> +
> +    trace_stm32l4x5_rcc_pll_set_channel_divider(pll->id,
> +        channel, pll->channel_divider[channel], divider);
> +
> +    pll->channel_divider[channel] = divider;
> +    pll_update(pll);
> +}
> +
>  static void rcc_update_irq(Stm32l4x5RccState *s)
>  {
>      if (s->cifr & CIFR_IRQ_MASK) {
> @@ -465,6 +615,11 @@ static void stm32l4x5_rcc_init(Object *obj)
>
>      qdev_init_clocks(DEVICE(s), stm32l4x5_rcc_clocks);
>
> +    for (i = 0; i < RCC_NUM_PLL; i++) {
> +        object_initialize_child(obj, "pll[*]",
> +                                &s->plls[i], TYPE_RCC_PLL);
> +    }
> +
>      for (i = 0; i < RCC_NUM_CLOCK_MUX; i++) {
>
>          object_initialize_child(obj, "clock[*]",
> @@ -528,6 +683,16 @@ static void stm32l4x5_rcc_realize(DeviceState *dev, Error **errp)
>              return;
>          }
>
> +    for (i = 0; i < RCC_NUM_PLL; i++) {
> +        RccPllState *pll = &s->plls[i];
> +
> +        clock_set_source(pll->in, s->clock_muxes[RCC_CLOCK_MUX_PLL_INPUT].out);
> +
> +        if (!qdev_realize(DEVICE(pll), NULL, errp)) {
> +            return;
> +        }
> +    }
> +
>      for (i = 0; i < RCC_NUM_CLOCK_MUX; i++) {
>          RccClockMuxState *clock_mux = &s->clock_muxes[i];
>
> @@ -548,6 +713,10 @@ static void stm32l4x5_rcc_realize(DeviceState *dev, Error **errp)
>      clock_mux_set_source(&s->clock_muxes[0], RCC_CLOCK_MUX_SRC_GND);
>      clock_mux_set_enable(&s->clock_muxes[0], true);
>      clock_mux_set_factor(&s->clock_muxes[0], 1, 1);
> +    pll_set_channel_divider(&s->plls[0], 0, 1);
> +    pll_set_enable(&s->plls[0], true);
> +    pll_set_channel_enable(&s->plls[0], 0, true);
> +    pll_set_vco_multiplier(&s->plls[0], 1);
>  }
>
>  static Property stm32l4x5_rcc_properties[] = {
> @@ -585,6 +754,12 @@ static const TypeInfo stm32l4x5_rcc_types[] = {
>          .instance_size = sizeof(RccClockMuxState),
>          .instance_init = clock_mux_init,
>          .class_init = clock_mux_class_init,
> +    }, {
> +        .name = TYPE_RCC_PLL,
> +        .parent = TYPE_DEVICE,
> +        .instance_size = sizeof(RccPllState),
> +        .instance_init = pll_init,
> +        .class_init = pll_class_init,
>      }
>  };
>
> diff --git a/hw/misc/trace-events b/hw/misc/trace-events
> index d5e471811c..1b6054d88a 100644
> --- a/hw/misc/trace-events
> +++ b/hw/misc/trace-events
> @@ -182,6 +182,11 @@ stm32l4x5_rcc_mux_disable(uint32_t mux_id) "RCC: Mux %d disabled"
>  stm32l4x5_rcc_mux_set_factor(uint32_t mux_id, uint32_t old_multiplier, uint32_t new_multiplier, uint32_t old_divider, uint32_t new_divider) "RCC: Mux %d factor changed: multiplier (%u -> %u), divider (%u -> %u)"
>  stm32l4x5_rcc_mux_set_src(uint32_t mux_id, uint32_t old_src, uint32_t new_src) "RCC: Mux %d source changed: from %u to %u"
>  stm32l4x5_rcc_mux_update(uint32_t mux_id, uint32_t src, uint64_t src_freq, uint64_t new_freq) "RCC: Mux %d src %d update: src_freq %" PRIu64 " new_freq %" PRIu64 ""
> +stm32l4x5_rcc_pll_set_vco_multiplier(uint32_t pll_id, uint32_t old_multiplier, uint32_t new_multiplier) "RCC: PLL %u: vco_multiplier changed (%u -> %u)"
> +stm32l4x5_rcc_pll_channel_enable(uint32_t pll_id, uint32_t channel_id) "RCC: PLL %u, channel %u enabled"
> +stm32l4x5_rcc_pll_channel_disable(uint32_t pll_id, uint32_t channel_id) "RCC: PLL %u, channel %u disabled"
> +stm32l4x5_rcc_pll_set_channel_divider(uint32_t pll_id, uint32_t channel_id, uint32_t old_divider, uint32_t new_divider) "RCC: PLL %u, channel %u: divider changed (%u -> %u)"
> +stm32l4x5_rcc_pll_update(uint32_t pll_id, uint32_t channel_id, uint64_t vco_freq, uint64_t old_freq, uint64_t new_freq) "RCC: PLL %d channel %d update: vco_freq %" PRIu64 " old_freq %" PRIu64 " new_freq %" PRIu64 ""
>
>  # tz-mpc.c
>  tz_mpc_reg_read(uint32_t offset, uint64_t data, unsigned size) "TZ MPC regs read: offset 0x%x data 0x%" PRIx64 " size %u"
> diff --git a/include/hw/misc/stm32l4x5_rcc.h b/include/hw/misc/stm32l4x5_rcc.h
> index 6719be9fbe..0fbfba5c40 100644
> --- a/include/hw/misc/stm32l4x5_rcc.h
> +++ b/include/hw/misc/stm32l4x5_rcc.h
> @@ -26,6 +26,15 @@ OBJECT_DECLARE_SIMPLE_TYPE(Stm32l4x5RccState, STM32L4X5_RCC)
>
>  /* In the Stm32l4x5 clock tree, mux have at most 7 sources */
>  #define RCC_NUM_CLOCK_MUX_SRC 7
> +
> +typedef enum PllCommonChannels {
> +    RCC_PLL_COMMON_CHANNEL_P = 0,
> +    RCC_PLL_COMMON_CHANNEL_Q = 1,
> +    RCC_PLL_COMMON_CHANNEL_R = 2,
> +
> +    RCC_NUM_CHANNEL_PLL_OUT = 3
> +} PllCommonChannels;
> +
>  /* NB: Prescaler are assimilated to mux with one source and one output */
>  typedef enum RccClockMux {
>      /* Internal muxes that arent't exposed publicly to other peripherals */
> @@ -124,6 +133,14 @@ typedef enum RccClockMux {
>      RCC_NUM_CLOCK_MUX
>  } RccClockMux;
>
> +typedef enum RccPll {
> +    RCC_PLL_PLL,
> +    RCC_PLL_PLLSAI1,
> +    RCC_PLL_PLLSAI2,
> +
> +    RCC_NUM_PLL
> +} RccPll;
> +
>  typedef struct RccClockMuxState {
>      DeviceState parent_obj;
>
> @@ -142,6 +159,26 @@ typedef struct RccClockMuxState {
>      struct RccClockMuxState *backref[RCC_NUM_CLOCK_MUX_SRC];
>  } RccClockMuxState;
>
> +typedef struct RccPllState {
> +    DeviceState parent_obj;
> +
> +    RccPll id;
> +    Clock *in;
> +    uint32_t vco_multiplier;
> +    Clock *channels[RCC_NUM_CHANNEL_PLL_OUT];
> +    /* Global pll enabled flag */
> +    bool enabled;
> +    /* 'enabled' refers to the runtime configuration */
> +    bool channel_enabled[RCC_NUM_CHANNEL_PLL_OUT];
> +    /*
> +     * 'exists' refers to the physical configuration
> +     * It should only be set at pll initialization.
> +     * e.g. pllsai2 doesn't have a Q output.
> +     */
> +    bool channel_exists[RCC_NUM_CHANNEL_PLL_OUT];
> +    uint32_t channel_divider[RCC_NUM_CHANNEL_PLL_OUT];
> +} RccPllState;
> +
>  struct Stm32l4x5RccState {
>      SysBusDevice parent_obj;
>
> @@ -187,6 +224,9 @@ struct Stm32l4x5RccState {
>      Clock *sai1_extclk;
>      Clock *sai2_extclk;
>
> +    /* PLLs */
> +    RccPllState plls[RCC_NUM_PLL];
> +
>      /* Muxes ~= outputs */
>      RccClockMuxState clock_muxes[RCC_NUM_CLOCK_MUX];
>
> diff --git a/include/hw/misc/stm32l4x5_rcc_internals.h b/include/hw/misc/stm32l4x5_rcc_internals.h
> index 4aa836848b..a9da5e3be7 100644
> --- a/include/hw/misc/stm32l4x5_rcc_internals.h
> +++ b/include/hw/misc/stm32l4x5_rcc_internals.h
> @@ -22,7 +22,10 @@
>  #include "hw/misc/stm32l4x5_rcc.h"
>
>  #define TYPE_RCC_CLOCK_MUX "stm32l4x5-rcc-clock-mux"
> +#define TYPE_RCC_PLL "stm32l4x5-rcc-pll"
> +
>  OBJECT_DECLARE_SIMPLE_TYPE(RccClockMuxState, RCC_CLOCK_MUX)
> +OBJECT_DECLARE_SIMPLE_TYPE(RccPllState, RCC_PLL)
>
>  /* Register map */
>  REG32(CR, 0x00)
> @@ -285,6 +288,25 @@ REG32(CSR, 0x94)
>                              R_CSR_FWRSTF_MASK   | \
>                              R_CSR_LSIRDY_MASK)
>
> +/* Pll Channels */
> +enum PllChannels {
> +    RCC_PLL_CHANNEL_PLLSAI3CLK = 0,
> +    RCC_PLL_CHANNEL_PLL48M1CLK = 1,
> +    RCC_PLL_CHANNEL_PLLCLK = 2,
> +};
> +
> +enum PllSai1Channels {
> +    RCC_PLLSAI1_CHANNEL_PLLSAI1CLK = 0,
> +    RCC_PLLSAI1_CHANNEL_PLL48M2CLK = 1,
> +    RCC_PLLSAI1_CHANNEL_PLLADC1CLK = 2,
> +};
> +
> +enum PllSai2Channels {
> +    RCC_PLLSAI2_CHANNEL_PLLSAI2CLK = 0,
> +    /* No Q channel */
> +    RCC_PLLSAI2_CHANNEL_PLLADC2CLK = 2,
> +};
> +
>  typedef enum RccClockMuxSource {
>      RCC_CLOCK_MUX_SRC_GND = 0,
>      RCC_CLOCK_MUX_SRC_HSI,
> --
> 2.34.1
>
>


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v4 7/8] STM32L4x5: Use the RCC Sysclk
  2024-01-30 16:06 ` [PATCH v4 7/8] STM32L4x5: Use the RCC Sysclk Arnaud Minier
@ 2024-02-01  0:20   ` Alistair Francis
  0 siblings, 0 replies; 14+ messages in thread
From: Alistair Francis @ 2024-02-01  0:20 UTC (permalink / raw)
  To: Arnaud Minier
  Cc: qemu-devel, Laurent Vivier, Alistair Francis, Inès Varhol,
	Peter Maydell, Paolo Bonzini, Thomas Huth, qemu-arm

On Wed, Jan 31, 2024 at 3:21 AM Arnaud Minier
<arnaud.minier@telecom-paris.fr> wrote:
>
> Now that we can generate reliable clock frequencies from the RCC, remove
> the hacky definition of the sysclk in the b_l475e_iot01a initialisation
> code and use the correct RCC clock.
>
> Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
> Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>

Acked-by: Alistair Francis <alistair.francis@wdc.com>

Alistair

> ---
>  hw/arm/b-l475e-iot01a.c        | 10 +---------
>  hw/arm/stm32l4x5_soc.c         | 33 ++++-----------------------------
>  include/hw/arm/stm32l4x5_soc.h |  3 ---
>  3 files changed, 5 insertions(+), 41 deletions(-)
>
> diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c
> index 6ecde2db15..d862aa43fc 100644
> --- a/hw/arm/b-l475e-iot01a.c
> +++ b/hw/arm/b-l475e-iot01a.c
> @@ -26,27 +26,19 @@
>  #include "qapi/error.h"
>  #include "hw/boards.h"
>  #include "hw/qdev-properties.h"
> -#include "hw/qdev-clock.h"
>  #include "qemu/error-report.h"
>  #include "hw/arm/stm32l4x5_soc.h"
>  #include "hw/arm/boot.h"
>
> -/* Main SYSCLK frequency in Hz (80MHz) */
> -#define MAIN_SYSCLK_FREQ_HZ 80000000ULL
> +/* B-L475E-IOT01A implementation is derived from netduinoplus2 */
>
>  static void b_l475e_iot01a_init(MachineState *machine)
>  {
>      const Stm32l4x5SocClass *sc;
>      DeviceState *dev;
> -    Clock *sysclk;
> -
> -    /* This clock doesn't need migration because it is fixed-frequency */
> -    sysclk = clock_new(OBJECT(machine), "SYSCLK");
> -    clock_set_hz(sysclk, MAIN_SYSCLK_FREQ_HZ);
>
>      dev = qdev_new(TYPE_STM32L4X5XG_SOC);
>      object_property_add_child(OBJECT(machine), "soc", OBJECT(dev));
> -    qdev_connect_clock_in(dev, "sysclk", sysclk);
>      sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
>
>      sc = STM32L4X5_SOC_GET_CLASS(dev);
> diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
> index d5c04b446d..347a5377e5 100644
> --- a/hw/arm/stm32l4x5_soc.c
> +++ b/hw/arm/stm32l4x5_soc.c
> @@ -85,9 +85,6 @@ static void stm32l4x5_soc_initfn(Object *obj)
>      object_initialize_child(obj, "exti", &s->exti, TYPE_STM32L4X5_EXTI);
>      object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_STM32L4X5_SYSCFG);
>      object_initialize_child(obj, "rcc", &s->rcc, TYPE_STM32L4X5_RCC);
> -
> -    s->sysclk = qdev_init_clock_in(DEVICE(s), "sysclk", NULL, NULL, 0);
> -    s->refclk = qdev_init_clock_in(DEVICE(s), "refclk", NULL, NULL, 0);
>  }
>
>  static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
> @@ -99,30 +96,6 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
>      DeviceState *armv7m;
>      SysBusDevice *busdev;
>
> -    /*
> -     * We use s->refclk internally and only define it with qdev_init_clock_in()
> -     * so it is correctly parented and not leaked on an init/deinit; it is not
> -     * intended as an externally exposed clock.
> -     */
> -    if (clock_has_source(s->refclk)) {
> -        error_setg(errp, "refclk clock must not be wired up by the board code");
> -        return;
> -    }
> -
> -    if (!clock_has_source(s->sysclk)) {
> -        error_setg(errp, "sysclk clock must be wired up by the board code");
> -        return;
> -    }
> -
> -    /*
> -     * TODO: ideally we should model the SoC RCC and its ability to
> -     * change the sysclk frequency and define different sysclk sources.
> -     */
> -
> -    /* The refclk always runs at frequency HCLK / 8 */
> -    clock_set_mul_div(s->refclk, 8, 1);
> -    clock_set_source(s->refclk, s->sysclk);
> -
>      if (!memory_region_init_rom(&s->flash, OBJECT(dev_soc), "flash",
>                                  sc->flash_size, errp)) {
>          return;
> @@ -152,8 +125,10 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
>      qdev_prop_set_uint32(armv7m, "num-prio-bits", 4);
>      qdev_prop_set_string(armv7m, "cpu-type", ARM_CPU_TYPE_NAME("cortex-m4"));
>      qdev_prop_set_bit(armv7m, "enable-bitband", true);
> -    qdev_connect_clock_in(armv7m, "cpuclk", s->sysclk);
> -    qdev_connect_clock_in(armv7m, "refclk", s->refclk);
> +    qdev_connect_clock_in(armv7m, "cpuclk",
> +        qdev_get_clock_out(DEVICE(&(s->rcc)), "cortex-fclk-out"));
> +    qdev_connect_clock_in(armv7m, "refclk",
> +        qdev_get_clock_out(DEVICE(&(s->rcc)), "cortex-refclk-out"));
>      object_property_set_link(OBJECT(&s->armv7m), "memory",
>                               OBJECT(system_memory), &error_abort);
>      if (!sysbus_realize(SYS_BUS_DEVICE(&s->armv7m), errp)) {
> diff --git a/include/hw/arm/stm32l4x5_soc.h b/include/hw/arm/stm32l4x5_soc.h
> index e480fcc976..1f71298b45 100644
> --- a/include/hw/arm/stm32l4x5_soc.h
> +++ b/include/hw/arm/stm32l4x5_soc.h
> @@ -50,9 +50,6 @@ struct Stm32l4x5SocState {
>      MemoryRegion sram2;
>      MemoryRegion flash;
>      MemoryRegion flash_alias;
> -
> -    Clock *sysclk;
> -    Clock *refclk;
>  };
>
>  struct Stm32l4x5SocClass {
> --
> 2.34.1
>
>


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v4 3/8] Add an internal PLL Clock object
  2024-02-01  0:18   ` Alistair Francis
@ 2024-02-12 19:33     ` Arnaud Minier
  0 siblings, 0 replies; 14+ messages in thread
From: Arnaud Minier @ 2024-02-12 19:33 UTC (permalink / raw)
  To: Alistair Francis
  Cc: qemu-devel, Laurent Vivier, Alistair Francis, Inès Varhol,
	Peter Maydell, Paolo Bonzini, Thomas Huth, qemu-arm,
	Samuel Tardieu

Hello Alistair,

Yes, I think we should bail out if pll_set_vco_multiplier receives an invalid value to respect the hardware defined bounds.
I actually intended to add a return there but I missed it. It will be added in the next version.

Thanks,
Arnaud Minier


----- Mail original -----
De: "Alistair Francis" <alistair23@gmail.com>
À: "Arnaud Minier" <arnaud.minier@telecom-paris.fr>
Cc: "qemu-devel" <qemu-devel@nongnu.org>, "Laurent Vivier" <lvivier@redhat.com>, "Alistair Francis" <alistair@alistair23.me>, "Inès Varhol" <ines.varhol@telecom-paris.fr>, "Peter Maydell" <peter.maydell@linaro.org>, "Paolo Bonzini" <pbonzini@redhat.com>, "Thomas Huth" <thuth@redhat.com>, qemu-arm@nongnu.org
Envoyé: Jeudi 1 Février 2024 01:18:22
Objet: Re: [PATCH v4 3/8] Add an internal PLL Clock object

On Wed, Jan 31, 2024 at 2:09 AM Arnaud Minier
<arnaud.minier@telecom-paris.fr> wrote:
>
> This object represents the PLLs and their channels. The PLLs allow for a
> more fine-grained control of the clocks frequency.
>
> Wasn't sure about how to handle the reset and the migration so used the
> same appproach as the BCM2835 CPRMAN.
>
> Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
> Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
> ---
>  hw/misc/stm32l4x5_rcc.c                   | 175 ++++++++++++++++++++++
>  hw/misc/trace-events                      |   5 +
>  include/hw/misc/stm32l4x5_rcc.h           |  40 +++++
>  include/hw/misc/stm32l4x5_rcc_internals.h |  22 +++
>  4 files changed, 242 insertions(+)
>
> diff --git a/hw/misc/stm32l4x5_rcc.c b/hw/misc/stm32l4x5_rcc.c
> index ed10832f88..fb0233c3e9 100644
> --- a/hw/misc/stm32l4x5_rcc.c
> +++ b/hw/misc/stm32l4x5_rcc.c
> @@ -162,6 +162,156 @@ static void clock_mux_set_source(RccClockMuxState *mux, RccClockMuxSource src)
>      clock_mux_update(mux);
>  }
>
> +static void pll_update(RccPllState *pll)
> +{
> +    uint64_t vco_freq, old_channel_freq, channel_freq;
> +    int i;
> +
> +    /* The common PLLM factor is handled by the PLL mux */
> +    vco_freq = muldiv64(clock_get_hz(pll->in), pll->vco_multiplier, 1);
> +
> +    for (i = 0; i < RCC_NUM_CHANNEL_PLL_OUT; i++) {
> +        if (!pll->channel_exists[i]) {
> +            continue;
> +        }
> +
> +        old_channel_freq = clock_get_hz(pll->channels[i]);
> +        if (!pll->enabled ||
> +            !pll->channel_enabled[i] ||
> +            !pll->channel_divider[i]) {
> +            channel_freq = 0;
> +        } else {
> +            channel_freq = muldiv64(vco_freq,
> +                                    1,
> +                                    pll->channel_divider[i]);
> +        }
> +
> +        /* No change, early continue to avoid log spam and useless propagation */
> +        if (old_channel_freq == channel_freq) {
> +            continue;
> +        }
> +
> +        clock_update_hz(pll->channels[i], channel_freq);
> +        trace_stm32l4x5_rcc_pll_update(pll->id, i, vco_freq,
> +            old_channel_freq, channel_freq);
> +    }
> +}
> +
> +static void pll_src_update(void *opaque, ClockEvent event)
> +{
> +    RccPllState *s = opaque;
> +    pll_update(s);
> +}
> +
> +static void pll_init(Object *obj)
> +{
> +    RccPllState *s = RCC_PLL(obj);
> +    size_t i;
> +
> +    s->in = qdev_init_clock_in(DEVICE(s), "in",
> +                               pll_src_update, s, ClockUpdate);
> +
> +    const char *names[] = {
> +        "out-p", "out-q", "out-r",
> +    };
> +
> +    for (i = 0; i < RCC_NUM_CHANNEL_PLL_OUT; i++) {
> +        s->channels[i] = qdev_init_clock_out(DEVICE(s), names[i]);
> +    }
> +}
> +
> +static void pll_reset_hold(Object *obj)
> +{ }
> +
> +static const VMStateDescription pll_vmstate = {
> +    .name = TYPE_RCC_PLL,
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT32(id, RccPllState),
> +        VMSTATE_CLOCK(in, RccPllState),
> +        VMSTATE_ARRAY_CLOCK(channels, RccPllState,
> +                            RCC_NUM_CHANNEL_PLL_OUT),
> +        VMSTATE_BOOL(enabled, RccPllState),
> +        VMSTATE_UINT32(vco_multiplier, RccPllState),
> +        VMSTATE_BOOL_ARRAY(channel_enabled, RccPllState, RCC_NUM_CHANNEL_PLL_OUT),
> +        VMSTATE_BOOL_ARRAY(channel_exists, RccPllState, RCC_NUM_CHANNEL_PLL_OUT),
> +        VMSTATE_UINT32_ARRAY(channel_divider, RccPllState, RCC_NUM_CHANNEL_PLL_OUT),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static void pll_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    ResettableClass *rc = RESETTABLE_CLASS(klass);
> +
> +    rc->phases.hold = pll_reset_hold;
> +    dc->vmsd = &pll_vmstate;
> +}
> +
> +static void pll_set_vco_multiplier(RccPllState *pll, uint32_t vco_multiplier)
> +{
> +    if (pll->vco_multiplier == vco_multiplier) {
> +        return;
> +    }
> +
> +    if (vco_multiplier < 8 || vco_multiplier > 86) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +            "%s: VCO multiplier is out of bound (%u) for PLL %u\n",
> +            __func__, vco_multiplier, pll->id);

Should we bail out with an invalid value?

Alistair

> +    }
> +
> +    trace_stm32l4x5_rcc_pll_set_vco_multiplier(pll->id,
> +        pll->vco_multiplier, vco_multiplier);
> +
> +    pll->vco_multiplier = vco_multiplier;
> +    pll_update(pll);
> +}
> +
> +static void pll_set_enable(RccPllState *pll, bool enabled)
> +{
> +    if (pll->enabled == enabled) {
> +        return;
> +    }
> +
> +    pll->enabled = enabled;
> +    pll_update(pll);
> +}
> +
> +static void pll_set_channel_enable(RccPllState *pll,
> +                                   PllCommonChannels channel,
> +                                   bool enabled)
> +{
> +    if (pll->channel_enabled[channel] == enabled) {
> +        return;
> +    }
> +
> +    if (enabled) {
> +        trace_stm32l4x5_rcc_pll_channel_enable(pll->id, channel);
> +    } else {
> +        trace_stm32l4x5_rcc_pll_channel_disable(pll->id, channel);
> +    }
> +
> +    pll->channel_enabled[channel] = enabled;
> +    pll_update(pll);
> +}
> +
> +static void pll_set_channel_divider(RccPllState *pll,
> +                                    PllCommonChannels channel,
> +                                    uint32_t divider)
> +{
> +    if (pll->channel_divider[channel] == divider) {
> +        return;
> +    }
> +
> +    trace_stm32l4x5_rcc_pll_set_channel_divider(pll->id,
> +        channel, pll->channel_divider[channel], divider);
> +
> +    pll->channel_divider[channel] = divider;
> +    pll_update(pll);
> +}
> +
>  static void rcc_update_irq(Stm32l4x5RccState *s)
>  {
>      if (s->cifr & CIFR_IRQ_MASK) {
> @@ -465,6 +615,11 @@ static void stm32l4x5_rcc_init(Object *obj)
>
>      qdev_init_clocks(DEVICE(s), stm32l4x5_rcc_clocks);
>
> +    for (i = 0; i < RCC_NUM_PLL; i++) {
> +        object_initialize_child(obj, "pll[*]",
> +                                &s->plls[i], TYPE_RCC_PLL);
> +    }
> +
>      for (i = 0; i < RCC_NUM_CLOCK_MUX; i++) {
>
>          object_initialize_child(obj, "clock[*]",
> @@ -528,6 +683,16 @@ static void stm32l4x5_rcc_realize(DeviceState *dev, Error **errp)
>              return;
>          }
>
> +    for (i = 0; i < RCC_NUM_PLL; i++) {
> +        RccPllState *pll = &s->plls[i];
> +
> +        clock_set_source(pll->in, s->clock_muxes[RCC_CLOCK_MUX_PLL_INPUT].out);
> +
> +        if (!qdev_realize(DEVICE(pll), NULL, errp)) {
> +            return;
> +        }
> +    }
> +
>      for (i = 0; i < RCC_NUM_CLOCK_MUX; i++) {
>          RccClockMuxState *clock_mux = &s->clock_muxes[i];
>
> @@ -548,6 +713,10 @@ static void stm32l4x5_rcc_realize(DeviceState *dev, Error **errp)
>      clock_mux_set_source(&s->clock_muxes[0], RCC_CLOCK_MUX_SRC_GND);
>      clock_mux_set_enable(&s->clock_muxes[0], true);
>      clock_mux_set_factor(&s->clock_muxes[0], 1, 1);
> +    pll_set_channel_divider(&s->plls[0], 0, 1);
> +    pll_set_enable(&s->plls[0], true);
> +    pll_set_channel_enable(&s->plls[0], 0, true);
> +    pll_set_vco_multiplier(&s->plls[0], 1);
>  }
>
>  static Property stm32l4x5_rcc_properties[] = {
> @@ -585,6 +754,12 @@ static const TypeInfo stm32l4x5_rcc_types[] = {
>          .instance_size = sizeof(RccClockMuxState),
>          .instance_init = clock_mux_init,
>          .class_init = clock_mux_class_init,
> +    }, {
> +        .name = TYPE_RCC_PLL,
> +        .parent = TYPE_DEVICE,
> +        .instance_size = sizeof(RccPllState),
> +        .instance_init = pll_init,
> +        .class_init = pll_class_init,
>      }
>  };
>
> diff --git a/hw/misc/trace-events b/hw/misc/trace-events
> index d5e471811c..1b6054d88a 100644
> --- a/hw/misc/trace-events
> +++ b/hw/misc/trace-events
> @@ -182,6 +182,11 @@ stm32l4x5_rcc_mux_disable(uint32_t mux_id) "RCC: Mux %d disabled"
>  stm32l4x5_rcc_mux_set_factor(uint32_t mux_id, uint32_t old_multiplier, uint32_t new_multiplier, uint32_t old_divider, uint32_t new_divider) "RCC: Mux %d factor changed: multiplier (%u -> %u), divider (%u -> %u)"
>  stm32l4x5_rcc_mux_set_src(uint32_t mux_id, uint32_t old_src, uint32_t new_src) "RCC: Mux %d source changed: from %u to %u"
>  stm32l4x5_rcc_mux_update(uint32_t mux_id, uint32_t src, uint64_t src_freq, uint64_t new_freq) "RCC: Mux %d src %d update: src_freq %" PRIu64 " new_freq %" PRIu64 ""
> +stm32l4x5_rcc_pll_set_vco_multiplier(uint32_t pll_id, uint32_t old_multiplier, uint32_t new_multiplier) "RCC: PLL %u: vco_multiplier changed (%u -> %u)"
> +stm32l4x5_rcc_pll_channel_enable(uint32_t pll_id, uint32_t channel_id) "RCC: PLL %u, channel %u enabled"
> +stm32l4x5_rcc_pll_channel_disable(uint32_t pll_id, uint32_t channel_id) "RCC: PLL %u, channel %u disabled"
> +stm32l4x5_rcc_pll_set_channel_divider(uint32_t pll_id, uint32_t channel_id, uint32_t old_divider, uint32_t new_divider) "RCC: PLL %u, channel %u: divider changed (%u -> %u)"
> +stm32l4x5_rcc_pll_update(uint32_t pll_id, uint32_t channel_id, uint64_t vco_freq, uint64_t old_freq, uint64_t new_freq) "RCC: PLL %d channel %d update: vco_freq %" PRIu64 " old_freq %" PRIu64 " new_freq %" PRIu64 ""
>
>  # tz-mpc.c
>  tz_mpc_reg_read(uint32_t offset, uint64_t data, unsigned size) "TZ MPC regs read: offset 0x%x data 0x%" PRIx64 " size %u"
> diff --git a/include/hw/misc/stm32l4x5_rcc.h b/include/hw/misc/stm32l4x5_rcc.h
> index 6719be9fbe..0fbfba5c40 100644
> --- a/include/hw/misc/stm32l4x5_rcc.h
> +++ b/include/hw/misc/stm32l4x5_rcc.h
> @@ -26,6 +26,15 @@ OBJECT_DECLARE_SIMPLE_TYPE(Stm32l4x5RccState, STM32L4X5_RCC)
>
>  /* In the Stm32l4x5 clock tree, mux have at most 7 sources */
>  #define RCC_NUM_CLOCK_MUX_SRC 7
> +
> +typedef enum PllCommonChannels {
> +    RCC_PLL_COMMON_CHANNEL_P = 0,
> +    RCC_PLL_COMMON_CHANNEL_Q = 1,
> +    RCC_PLL_COMMON_CHANNEL_R = 2,
> +
> +    RCC_NUM_CHANNEL_PLL_OUT = 3
> +} PllCommonChannels;
> +
>  /* NB: Prescaler are assimilated to mux with one source and one output */
>  typedef enum RccClockMux {
>      /* Internal muxes that arent't exposed publicly to other peripherals */
> @@ -124,6 +133,14 @@ typedef enum RccClockMux {
>      RCC_NUM_CLOCK_MUX
>  } RccClockMux;
>
> +typedef enum RccPll {
> +    RCC_PLL_PLL,
> +    RCC_PLL_PLLSAI1,
> +    RCC_PLL_PLLSAI2,
> +
> +    RCC_NUM_PLL
> +} RccPll;
> +
>  typedef struct RccClockMuxState {
>      DeviceState parent_obj;
>
> @@ -142,6 +159,26 @@ typedef struct RccClockMuxState {
>      struct RccClockMuxState *backref[RCC_NUM_CLOCK_MUX_SRC];
>  } RccClockMuxState;
>
> +typedef struct RccPllState {
> +    DeviceState parent_obj;
> +
> +    RccPll id;
> +    Clock *in;
> +    uint32_t vco_multiplier;
> +    Clock *channels[RCC_NUM_CHANNEL_PLL_OUT];
> +    /* Global pll enabled flag */
> +    bool enabled;
> +    /* 'enabled' refers to the runtime configuration */
> +    bool channel_enabled[RCC_NUM_CHANNEL_PLL_OUT];
> +    /*
> +     * 'exists' refers to the physical configuration
> +     * It should only be set at pll initialization.
> +     * e.g. pllsai2 doesn't have a Q output.
> +     */
> +    bool channel_exists[RCC_NUM_CHANNEL_PLL_OUT];
> +    uint32_t channel_divider[RCC_NUM_CHANNEL_PLL_OUT];
> +} RccPllState;
> +
>  struct Stm32l4x5RccState {
>      SysBusDevice parent_obj;
>
> @@ -187,6 +224,9 @@ struct Stm32l4x5RccState {
>      Clock *sai1_extclk;
>      Clock *sai2_extclk;
>
> +    /* PLLs */
> +    RccPllState plls[RCC_NUM_PLL];
> +
>      /* Muxes ~= outputs */
>      RccClockMuxState clock_muxes[RCC_NUM_CLOCK_MUX];
>
> diff --git a/include/hw/misc/stm32l4x5_rcc_internals.h b/include/hw/misc/stm32l4x5_rcc_internals.h
> index 4aa836848b..a9da5e3be7 100644
> --- a/include/hw/misc/stm32l4x5_rcc_internals.h
> +++ b/include/hw/misc/stm32l4x5_rcc_internals.h
> @@ -22,7 +22,10 @@
>  #include "hw/misc/stm32l4x5_rcc.h"
>
>  #define TYPE_RCC_CLOCK_MUX "stm32l4x5-rcc-clock-mux"
> +#define TYPE_RCC_PLL "stm32l4x5-rcc-pll"
> +
>  OBJECT_DECLARE_SIMPLE_TYPE(RccClockMuxState, RCC_CLOCK_MUX)
> +OBJECT_DECLARE_SIMPLE_TYPE(RccPllState, RCC_PLL)
>
>  /* Register map */
>  REG32(CR, 0x00)
> @@ -285,6 +288,25 @@ REG32(CSR, 0x94)
>                              R_CSR_FWRSTF_MASK   | \
>                              R_CSR_LSIRDY_MASK)
>
> +/* Pll Channels */
> +enum PllChannels {
> +    RCC_PLL_CHANNEL_PLLSAI3CLK = 0,
> +    RCC_PLL_CHANNEL_PLL48M1CLK = 1,
> +    RCC_PLL_CHANNEL_PLLCLK = 2,
> +};
> +
> +enum PllSai1Channels {
> +    RCC_PLLSAI1_CHANNEL_PLLSAI1CLK = 0,
> +    RCC_PLLSAI1_CHANNEL_PLL48M2CLK = 1,
> +    RCC_PLLSAI1_CHANNEL_PLLADC1CLK = 2,
> +};
> +
> +enum PllSai2Channels {
> +    RCC_PLLSAI2_CHANNEL_PLLSAI2CLK = 0,
> +    /* No Q channel */
> +    RCC_PLLSAI2_CHANNEL_PLLADC2CLK = 2,
> +};
> +
>  typedef enum RccClockMuxSource {
>      RCC_CLOCK_MUX_SRC_GND = 0,
>      RCC_CLOCK_MUX_SRC_HSI,
> --
> 2.34.1
>
>


^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2024-02-12 19:35 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-01-30 16:06 [PATCH v4 0/8] Add device STM32L4x5 RCC Arnaud Minier
2024-01-30 16:06 ` [PATCH v4 1/8] Implement STM32L4x5_RCC skeleton Arnaud Minier
2024-02-01  0:09   ` Alistair Francis
2024-01-30 16:06 ` [PATCH v4 2/8] Add an internal clock multiplexer object Arnaud Minier
2024-01-30 16:06 ` [PATCH v4 3/8] Add an internal PLL Clock object Arnaud Minier
2024-02-01  0:18   ` Alistair Francis
2024-02-12 19:33     ` Arnaud Minier
2024-01-30 16:06 ` [PATCH v4 4/8] Add initialization information for PLLs and clock multiplexers Arnaud Minier
2024-01-30 16:06 ` [PATCH v4 5/8] RCC: Handle Register Updates Arnaud Minier
2024-01-30 16:06 ` [PATCH v4 6/8] Add write protections to CR register Arnaud Minier
2024-01-30 16:06 ` [PATCH v4 7/8] STM32L4x5: Use the RCC Sysclk Arnaud Minier
2024-02-01  0:20   ` Alistair Francis
2024-01-30 16:13 ` [PATCH v4 8/8] Add tests for the STM32L4x5_RCC Arnaud Minier
2024-01-31  6:04   ` Thomas Huth

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.