diff mbox series

[2/7] Add an internal clock multiplexer object

Message ID 20240113102913.18278-3-arnaud.minier@telecom-paris.fr
State New
Headers show
Series Add device STM32L4x5 RCC | expand

Commit Message

Arnaud Minier Jan. 13, 2024, 10:29 a.m. UTC
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                   | 154 ++++++++++++++++++++++
 hw/misc/trace-events                      |   5 +
 include/hw/misc/stm32l4x5_rcc.h           | 119 +++++++++++++++++
 include/hw/misc/stm32l4x5_rcc_internals.h |  29 ++++
 4 files changed, 307 insertions(+)

Comments

Alistair Francis Jan. 22, 2024, 4:36 a.m. UTC | #1
On Sat, Jan 13, 2024 at 8:31 PM Arnaud Minier
<arnaud.minier@telecom-paris.fr> wrote:
>
> 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/misc/stm32l4x5_rcc.c                   | 154 ++++++++++++++++++++++
>  hw/misc/trace-events                      |   5 +
>  include/hw/misc/stm32l4x5_rcc.h           | 119 +++++++++++++++++
>  include/hw/misc/stm32l4x5_rcc_internals.h |  29 ++++
>  4 files changed, 307 insertions(+)
>
> diff --git a/hw/misc/stm32l4x5_rcc.c b/hw/misc/stm32l4x5_rcc.c
> index 5a6f475740..bcc69510b0 100644
> --- a/hw/misc/stm32l4x5_rcc.c
> +++ b/hw/misc/stm32l4x5_rcc.c
> @@ -35,6 +35,128 @@
>  #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_ARRAY_CLOCK(srcs, RccClockMuxState,
> +                            RCC_NUM_CLOCK_MUX_SRC),
> +        VMSTATE_BOOL(enabled, RccClockMuxState),
> +        VMSTATE_UINT32(src, 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) {
> @@ -326,6 +448,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);
>
> @@ -335,6 +458,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");
>  }
>
> @@ -380,6 +511,7 @@ static const VMStateDescription vmstate_stm32l4x5_rcc = {
>  static void stm32l4x5_rcc_realize(DeviceState *dev, Error **errp)
>  {
>      Stm32l4x5RccState *s = STM32L4X5_RCC(dev);
> +    size_t i;
>
>      /* The HSE frequency must be in range 4-48 MHz */
>      if (s->hse_frequency <  4000000ULL ||
> @@ -388,10 +520,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[] = {
> @@ -423,6 +571,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
>
>
diff mbox series

Patch

diff --git a/hw/misc/stm32l4x5_rcc.c b/hw/misc/stm32l4x5_rcc.c
index 5a6f475740..bcc69510b0 100644
--- a/hw/misc/stm32l4x5_rcc.c
+++ b/hw/misc/stm32l4x5_rcc.c
@@ -35,6 +35,128 @@ 
 #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_ARRAY_CLOCK(srcs, RccClockMuxState,
+                            RCC_NUM_CLOCK_MUX_SRC),
+        VMSTATE_BOOL(enabled, RccClockMuxState),
+        VMSTATE_UINT32(src, 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) {
@@ -326,6 +448,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);
 
@@ -335,6 +458,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");
 }
 
@@ -380,6 +511,7 @@  static const VMStateDescription vmstate_stm32l4x5_rcc = {
 static void stm32l4x5_rcc_realize(DeviceState *dev, Error **errp)
 {
     Stm32l4x5RccState *s = STM32L4X5_RCC(dev);
+    size_t i;
 
     /* The HSE frequency must be in range 4-48 MHz */
     if (s->hse_frequency <  4000000ULL ||
@@ -388,10 +520,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[] = {
@@ -423,6 +571,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 */