:p
atchew
Login
The following changes since commit 4eac9dfbd72d346505642fb45ac3141c7eb2c516: Merge tag 'pull-tcg-20240301' of https://gitlab.com/rth7680/qemu into staging (2024-03-05 09:45:22 +0000) are available in the Git repository at: https://git.linaro.org/people/pmaydell/qemu-arm.git tags/pull-target-arm-20240305 for you to fetch changes up to 7558300c53057126514ee0fd5cf629c65ccc20e1: qemu-options.hx: Don't claim "-serial" has limit of 4 serial ports (2024-03-05 13:22:56 +0000) ---------------------------------------------------------------- target-arm queue: * raspi: Implement Broadcom Serial Controller (BSC) for BCM2835 boards * hw/char/pl011: Add support for loopback * STM32L4x5: Implement RCC clock control device * target/arm: Do memory type alignment checks * atomic.h: Reword confusing comment for qatomic_cmpxchg * qemu-options.hx: Don't claim "-serial" has limit of 4 serial ports ---------------------------------------------------------------- Arnaud Minier (8): hw/misc/stm32l4x5_rcc: Implement STM32L4x5_RCC skeleton hw/misc/stm32l4x5_rcc: Add an internal clock multiplexer object hw/misc/stm32l4x5_rcc: Add an internal PLL Clock object hw/misc/stm32l4x5_rcc: Initialize PLLs and clock multiplexers hw/misc/stm32l4x5_rcc: Handle Register Updates hw/misc/stm32l4x5_rcc: Add write protections to CR register hw/arm/stm32l4x5_soc.c: Use the RCC Sysclk tests/qtest/stm32l4x5_rcc-test.c: Add tests for the STM32L4x5_RCC Peter Maydell (1): atomic.h: Reword confusing comment for qatomic_cmpxchg Rayhan Faizel (3): hw/i2c: Implement Broadcom Serial Controller (BSC) hw/arm: Connect BSC to BCM2835 board as I2C0, I2C1 and I2C2 tests/qtest: Add testcase for BCM2835 BSC Richard Henderson (6): target/arm: Support 32-byte alignment in pow2_align exec/memattrs: Remove target_tlb_bit* accel/tcg: Add tlb_fill_flags to CPUTLBEntryFull accel/tcg: Add TLB_CHECK_ALIGNED target/arm: Do memory type alignment check when translation disabled target/arm: Do memory type alignment check when translation enabled Steven Shen (1): qemu-options.hx: Don't claim "-serial" has limit of 4 serial ports Tong Ho (1): hw/char/pl011: Add support for loopback MAINTAINERS | 5 +- docs/system/arm/b-l475e-iot01a.rst | 2 +- docs/system/arm/raspi.rst | 1 + include/exec/cpu-all.h | 4 +- include/exec/memattrs.h | 12 - include/hw/arm/bcm2835_peripherals.h | 4 +- include/hw/arm/stm32l4x5_soc.h | 5 +- include/hw/core/cpu.h | 3 + include/hw/i2c/bcm2835_i2c.h | 80 ++ include/hw/misc/stm32l4x5_rcc.h | 239 +++++ include/hw/misc/stm32l4x5_rcc_internals.h | 1042 +++++++++++++++++++++ include/qemu/atomic.h | 2 +- accel/tcg/cputlb.c | 35 +- hw/arm/b-l475e-iot01a.c | 10 +- hw/arm/bcm2835_peripherals.c | 45 +- hw/arm/stm32l4x5_soc.c | 45 +- hw/char/pl011.c | 110 ++- hw/i2c/bcm2835_i2c.c | 282 ++++++ hw/misc/stm32l4x5_rcc.c | 1457 +++++++++++++++++++++++++++++ target/arm/ptw.c | 39 + target/arm/tcg/hflags.c | 34 +- target/arm/tcg/translate.c | 8 +- target/sparc/mmu_helper.c | 2 +- tests/qtest/bcm2835-i2c-test.c | 115 +++ tests/qtest/stm32l4x5_rcc-test.c | 189 ++++ hw/arm/Kconfig | 2 + hw/i2c/Kconfig | 4 + hw/i2c/meson.build | 1 + hw/misc/Kconfig | 3 + hw/misc/meson.build | 1 + hw/misc/trace-events | 14 + qemu-options.hx | 2 +- tests/qtest/meson.build | 5 +- 33 files changed, 3718 insertions(+), 84 deletions(-) create mode 100644 include/hw/i2c/bcm2835_i2c.h create mode 100644 include/hw/misc/stm32l4x5_rcc.h create mode 100644 include/hw/misc/stm32l4x5_rcc_internals.h create mode 100644 hw/i2c/bcm2835_i2c.c create mode 100644 hw/misc/stm32l4x5_rcc.c create mode 100644 tests/qtest/bcm2835-i2c-test.c create mode 100644 tests/qtest/stm32l4x5_rcc-test.c
From: Rayhan Faizel <rayhan.faizel@gmail.com> A few deficiencies in the current device model need to be noted. 1. FIFOs are not used. All sends and receives are done directly. 2. Repeated starts are not emulated. Repeated starts can be triggered in real hardware by sending a new read transfer request in the window time between transfer active set of write transfer request and done bit set of the same. Signed-off-by: Rayhan Faizel <rayhan.faizel@gmail.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 20240224191038.2409945-2-rayhan.faizel@gmail.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- docs/system/arm/raspi.rst | 1 + include/hw/i2c/bcm2835_i2c.h | 80 ++++++++++ hw/i2c/bcm2835_i2c.c | 282 +++++++++++++++++++++++++++++++++++ hw/i2c/Kconfig | 4 + hw/i2c/meson.build | 1 + 5 files changed, 368 insertions(+) create mode 100644 include/hw/i2c/bcm2835_i2c.h create mode 100644 hw/i2c/bcm2835_i2c.c diff --git a/docs/system/arm/raspi.rst b/docs/system/arm/raspi.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/system/arm/raspi.rst +++ b/docs/system/arm/raspi.rst @@ -XXX,XX +XXX,XX @@ Implemented devices * MailBox controller (MBOX) * VideoCore firmware (property) * Peripheral SPI controller (SPI) + * Broadcom Serial Controller (I2C) Missing devices --------------- diff --git a/include/hw/i2c/bcm2835_i2c.h b/include/hw/i2c/bcm2835_i2c.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/i2c/bcm2835_i2c.h @@ -XXX,XX +XXX,XX @@ +/* + * Broadcom Serial Controller (BSC) + * + * Copyright (c) 2024 Rayhan Faizel <rayhan.faizel@gmail.com> + * + * SPDX-License-Identifier: MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "hw/sysbus.h" +#include "hw/i2c/i2c.h" +#include "qom/object.h" + +#define TYPE_BCM2835_I2C "bcm2835-i2c" +OBJECT_DECLARE_SIMPLE_TYPE(BCM2835I2CState, BCM2835_I2C) + +#define BCM2835_I2C_C 0x0 /* Control */ +#define BCM2835_I2C_S 0x4 /* Status */ +#define BCM2835_I2C_DLEN 0x8 /* Data Length */ +#define BCM2835_I2C_A 0xc /* Slave Address */ +#define BCM2835_I2C_FIFO 0x10 /* FIFO */ +#define BCM2835_I2C_DIV 0x14 /* Clock Divider */ +#define BCM2835_I2C_DEL 0x18 /* Data Delay */ +#define BCM2835_I2C_CLKT 0x20 /* Clock Stretch Timeout */ + +#define BCM2835_I2C_C_I2CEN BIT(15) /* I2C enable */ +#define BCM2835_I2C_C_INTR BIT(10) /* Interrupt on RXR */ +#define BCM2835_I2C_C_INTT BIT(9) /* Interrupt on TXW */ +#define BCM2835_I2C_C_INTD BIT(8) /* Interrupt on DONE */ +#define BCM2835_I2C_C_ST BIT(7) /* Start transfer */ +#define BCM2835_I2C_C_CLEAR (BIT(5) | BIT(4)) /* Clear FIFO */ +#define BCM2835_I2C_C_READ BIT(0) /* I2C read mode */ + +#define BCM2835_I2C_S_CLKT BIT(9) /* Clock stretch timeout */ +#define BCM2835_I2C_S_ERR BIT(8) /* Slave error */ +#define BCM2835_I2C_S_RXF BIT(7) /* RX FIFO full */ +#define BCM2835_I2C_S_TXE BIT(6) /* TX FIFO empty */ +#define BCM2835_I2C_S_RXD BIT(5) /* RX bytes available */ +#define BCM2835_I2C_S_TXD BIT(4) /* TX space available */ +#define BCM2835_I2C_S_RXR BIT(3) /* RX FIFO needs reading */ +#define BCM2835_I2C_S_TXW BIT(2) /* TX FIFO needs writing */ +#define BCM2835_I2C_S_DONE BIT(1) /* I2C Transfer complete */ +#define BCM2835_I2C_S_TA BIT(0) /* I2C Transfer active */ + +struct BCM2835I2CState { + /* <private> */ + SysBusDevice parent_obj; + + /* <public> */ + MemoryRegion iomem; + I2CBus *bus; + qemu_irq irq; + + uint32_t c; + uint32_t s; + uint32_t dlen; + uint32_t a; + uint32_t div; + uint32_t del; + uint32_t clkt; + + uint32_t last_dlen; +}; diff --git a/hw/i2c/bcm2835_i2c.c b/hw/i2c/bcm2835_i2c.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/i2c/bcm2835_i2c.c @@ -XXX,XX +XXX,XX @@ +/* + * Broadcom Serial Controller (BSC) + * + * Copyright (c) 2024 Rayhan Faizel <rayhan.faizel@gmail.com> + * + * SPDX-License-Identifier: MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/i2c/bcm2835_i2c.h" +#include "hw/irq.h" +#include "migration/vmstate.h" + +static void bcm2835_i2c_update_interrupt(BCM2835I2CState *s) +{ + int do_interrupt = 0; + /* Interrupt on RXR (Needs reading) */ + if (s->c & BCM2835_I2C_C_INTR && s->s & BCM2835_I2C_S_RXR) { + do_interrupt = 1; + } + + /* Interrupt on TXW (Needs writing) */ + if (s->c & BCM2835_I2C_C_INTT && s->s & BCM2835_I2C_S_TXW) { + do_interrupt = 1; + } + + /* Interrupt on DONE (Transfer complete) */ + if (s->c & BCM2835_I2C_C_INTD && s->s & BCM2835_I2C_S_DONE) { + do_interrupt = 1; + } + qemu_set_irq(s->irq, do_interrupt); +} + +static void bcm2835_i2c_begin_transfer(BCM2835I2CState *s) +{ + int direction = s->c & BCM2835_I2C_C_READ; + if (i2c_start_transfer(s->bus, s->a, direction)) { + s->s |= BCM2835_I2C_S_ERR; + } + s->s |= BCM2835_I2C_S_TA; + + if (direction) { + s->s |= BCM2835_I2C_S_RXR | BCM2835_I2C_S_RXD; + } else { + s->s |= BCM2835_I2C_S_TXW; + } +} + +static void bcm2835_i2c_finish_transfer(BCM2835I2CState *s) +{ + /* + * STOP is sent when DLEN counts down to zero. + * + * https://github.com/torvalds/linux/blob/v6.7/drivers/i2c/busses/i2c-bcm2835.c#L223-L261 + * It is possible to initiate repeated starts on real hardware. + * However, this requires sending another ST request before the bytes in + * TX FIFO are shifted out. + * + * This is not emulated currently. + */ + i2c_end_transfer(s->bus); + s->s |= BCM2835_I2C_S_DONE; + + /* Ensure RXD is cleared, otherwise the driver registers an error */ + s->s &= ~(BCM2835_I2C_S_TA | BCM2835_I2C_S_RXR | + BCM2835_I2C_S_TXW | BCM2835_I2C_S_RXD); +} + +static uint64_t bcm2835_i2c_read(void *opaque, hwaddr addr, unsigned size) +{ + BCM2835I2CState *s = opaque; + uint32_t readval = 0; + + switch (addr) { + case BCM2835_I2C_C: + readval = s->c; + break; + case BCM2835_I2C_S: + readval = s->s; + break; + case BCM2835_I2C_DLEN: + readval = s->dlen; + break; + case BCM2835_I2C_A: + readval = s->a; + break; + case BCM2835_I2C_FIFO: + /* We receive I2C messages directly instead of using FIFOs */ + if (s->s & BCM2835_I2C_S_TA) { + readval = i2c_recv(s->bus); + s->dlen -= 1; + + if (s->dlen == 0) { + bcm2835_i2c_finish_transfer(s); + } + } + bcm2835_i2c_update_interrupt(s); + break; + case BCM2835_I2C_DIV: + readval = s->div; + break; + case BCM2835_I2C_DEL: + readval = s->del; + break; + case BCM2835_I2C_CLKT: + readval = s->clkt; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, addr); + } + + return readval; +} + +static void bcm2835_i2c_write(void *opaque, hwaddr addr, + uint64_t value, unsigned int size) +{ + BCM2835I2CState *s = opaque; + uint32_t writeval = value; + + switch (addr) { + case BCM2835_I2C_C: + /* ST is a one-shot operation; it must read back as 0 */ + s->c = writeval & ~BCM2835_I2C_C_ST; + + /* Start transfer */ + if (writeval & (BCM2835_I2C_C_ST | BCM2835_I2C_C_I2CEN)) { + bcm2835_i2c_begin_transfer(s); + /* + * Handle special case where transfer starts with zero data length. + * Required for zero length i2c quick messages to work. + */ + if (s->dlen == 0) { + bcm2835_i2c_finish_transfer(s); + } + } + + bcm2835_i2c_update_interrupt(s); + break; + case BCM2835_I2C_S: + if (writeval & BCM2835_I2C_S_DONE && s->s & BCM2835_I2C_S_DONE) { + /* When DONE is cleared, DLEN should read last written value. */ + s->dlen = s->last_dlen; + } + + /* Clear DONE, CLKT and ERR by writing 1 */ + s->s &= ~(writeval & (BCM2835_I2C_S_DONE | + BCM2835_I2C_S_ERR | BCM2835_I2C_S_CLKT)); + break; + case BCM2835_I2C_DLEN: + s->dlen = writeval; + s->last_dlen = writeval; + break; + case BCM2835_I2C_A: + s->a = writeval; + break; + case BCM2835_I2C_FIFO: + /* We send I2C messages directly instead of using FIFOs */ + if (s->s & BCM2835_I2C_S_TA) { + if (s->s & BCM2835_I2C_S_TXD) { + if (!i2c_send(s->bus, writeval & 0xff)) { + s->dlen -= 1; + } else { + s->s |= BCM2835_I2C_S_ERR; + } + } + + if (s->dlen == 0) { + bcm2835_i2c_finish_transfer(s); + } + } + bcm2835_i2c_update_interrupt(s); + break; + case BCM2835_I2C_DIV: + s->div = writeval; + break; + case BCM2835_I2C_DEL: + s->del = writeval; + break; + case BCM2835_I2C_CLKT: + s->clkt = writeval; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, addr); + } +} + +static const MemoryRegionOps bcm2835_i2c_ops = { + .read = bcm2835_i2c_read, + .write = bcm2835_i2c_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void bcm2835_i2c_realize(DeviceState *dev, Error **errp) +{ + BCM2835I2CState *s = BCM2835_I2C(dev); + s->bus = i2c_init_bus(dev, NULL); + + memory_region_init_io(&s->iomem, OBJECT(dev), &bcm2835_i2c_ops, s, + TYPE_BCM2835_I2C, 0x24); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq); +} + +static void bcm2835_i2c_reset(DeviceState *dev) +{ + BCM2835I2CState *s = BCM2835_I2C(dev); + + /* Reset values according to BCM2835 Peripheral Documentation */ + s->c = 0x0; + s->s = BCM2835_I2C_S_TXD | BCM2835_I2C_S_TXE; + s->dlen = 0x0; + s->a = 0x0; + s->div = 0x5dc; + s->del = 0x00300030; + s->clkt = 0x40; +} + +static const VMStateDescription vmstate_bcm2835_i2c = { + .name = TYPE_BCM2835_I2C, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(c, BCM2835I2CState), + VMSTATE_UINT32(s, BCM2835I2CState), + VMSTATE_UINT32(dlen, BCM2835I2CState), + VMSTATE_UINT32(a, BCM2835I2CState), + VMSTATE_UINT32(div, BCM2835I2CState), + VMSTATE_UINT32(del, BCM2835I2CState), + VMSTATE_UINT32(clkt, BCM2835I2CState), + VMSTATE_UINT32(last_dlen, BCM2835I2CState), + VMSTATE_END_OF_LIST() + } +}; + +static void bcm2835_i2c_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = bcm2835_i2c_reset; + dc->realize = bcm2835_i2c_realize; + dc->vmsd = &vmstate_bcm2835_i2c; +} + +static const TypeInfo bcm2835_i2c_info = { + .name = TYPE_BCM2835_I2C, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(BCM2835I2CState), + .class_init = bcm2835_i2c_class_init, +}; + +static void bcm2835_i2c_register_types(void) +{ + type_register_static(&bcm2835_i2c_info); +} + +type_init(bcm2835_i2c_register_types) diff --git a/hw/i2c/Kconfig b/hw/i2c/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/i2c/Kconfig +++ b/hw/i2c/Kconfig @@ -XXX,XX +XXX,XX @@ config PCA954X config PMBUS bool select SMBUS + +config BCM2835_I2C + bool + select I2C diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build index XXXXXXX..XXXXXXX 100644 --- a/hw/i2c/meson.build +++ b/hw/i2c/meson.build @@ -XXX,XX +XXX,XX @@ i2c_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_i2c.c')) i2c_ss.add(when: 'CONFIG_PPC4XX', if_true: files('ppc4xx_i2c.c')) i2c_ss.add(when: 'CONFIG_PCA954X', if_true: files('i2c_mux_pca954x.c')) i2c_ss.add(when: 'CONFIG_PMBUS', if_true: files('pmbus_device.c')) +i2c_ss.add(when: 'CONFIG_BCM2835_I2C', if_true: files('bcm2835_i2c.c')) system_ss.add_all(when: 'CONFIG_I2C', if_true: i2c_ss) -- 2.34.1
From: Rayhan Faizel <rayhan.faizel@gmail.com> BCM2835 has three I2C controllers. All of them share the same interrupt line. Signed-off-by: Rayhan Faizel <rayhan.faizel@gmail.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20240224191038.2409945-3-rayhan.faizel@gmail.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- include/hw/arm/bcm2835_peripherals.h | 4 ++- hw/arm/bcm2835_peripherals.c | 45 ++++++++++++++++++++++++++-- hw/arm/Kconfig | 1 + 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/include/hw/arm/bcm2835_peripherals.h b/include/hw/arm/bcm2835_peripherals.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/bcm2835_peripherals.h +++ b/include/hw/arm/bcm2835_peripherals.h @@ -XXX,XX +XXX,XX @@ #include "hw/timer/bcm2835_systmr.h" #include "hw/usb/hcd-dwc2.h" #include "hw/ssi/bcm2835_spi.h" +#include "hw/i2c/bcm2835_i2c.h" #include "hw/misc/unimp.h" #include "qom/object.h" @@ -XXX,XX +XXX,XX @@ struct BCMSocPeripheralBaseState { BCM2835SDHostState sdhost; UnimplementedDeviceState i2s; BCM2835SPIState spi[1]; - UnimplementedDeviceState i2c[3]; + BCM2835I2CState i2c[3]; + OrIRQState orgated_i2c_irq; UnimplementedDeviceState otp; UnimplementedDeviceState dbus; UnimplementedDeviceState ave0; diff --git a/hw/arm/bcm2835_peripherals.c b/hw/arm/bcm2835_peripherals.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/bcm2835_peripherals.c +++ b/hw/arm/bcm2835_peripherals.c @@ -XXX,XX +XXX,XX @@ #define SEPARATE_DMA_IRQ_MAX 10 #define ORGATED_DMA_IRQ_COUNT 4 +/* All three I2C controllers share the same IRQ */ +#define ORGATED_I2C_IRQ_COUNT 3 + void create_unimp(BCMSocPeripheralBaseState *ps, UnimplementedDeviceState *uds, const char *name, hwaddr ofs, hwaddr size) @@ -XXX,XX +XXX,XX @@ static void raspi_peripherals_base_init(Object *obj) /* SPI */ object_initialize_child(obj, "bcm2835-spi0", &s->spi[0], TYPE_BCM2835_SPI); + + /* I2C */ + object_initialize_child(obj, "bcm2835-i2c0", &s->i2c[0], + TYPE_BCM2835_I2C); + object_initialize_child(obj, "bcm2835-i2c1", &s->i2c[1], + TYPE_BCM2835_I2C); + object_initialize_child(obj, "bcm2835-i2c2", &s->i2c[2], + TYPE_BCM2835_I2C); + + object_initialize_child(obj, "orgated-i2c-irq", + &s->orgated_i2c_irq, TYPE_OR_IRQ); + object_property_set_int(OBJECT(&s->orgated_i2c_irq), "num-lines", + ORGATED_I2C_IRQ_COUNT, &error_abort); } static void bcm2835_peripherals_realize(DeviceState *dev, Error **errp) @@ -XXX,XX +XXX,XX @@ void bcm_soc_peripherals_common_realize(DeviceState *dev, Error **errp) BCM2835_IC_GPU_IRQ, INTERRUPT_SPI)); + /* I2C */ + for (n = 0; n < 3; n++) { + if (!sysbus_realize(SYS_BUS_DEVICE(&s->i2c[n]), errp)) { + return; + } + } + + memory_region_add_subregion(&s->peri_mr, BSC0_OFFSET, + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->i2c[0]), 0)); + memory_region_add_subregion(&s->peri_mr, BSC1_OFFSET, + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->i2c[1]), 0)); + memory_region_add_subregion(&s->peri_mr, BSC2_OFFSET, + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->i2c[2]), 0)); + + if (!qdev_realize(DEVICE(&s->orgated_i2c_irq), NULL, errp)) { + return; + } + for (n = 0; n < ORGATED_I2C_IRQ_COUNT; n++) { + sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c[n]), 0, + qdev_get_gpio_in(DEVICE(&s->orgated_i2c_irq), n)); + } + qdev_connect_gpio_out(DEVICE(&s->orgated_i2c_irq), 0, + qdev_get_gpio_in_named(DEVICE(&s->ic), + BCM2835_IC_GPU_IRQ, + INTERRUPT_I2C)); + create_unimp(s, &s->txp, "bcm2835-txp", TXP_OFFSET, 0x1000); create_unimp(s, &s->armtmr, "bcm2835-sp804", ARMCTRL_TIMER0_1_OFFSET, 0x40); create_unimp(s, &s->i2s, "bcm2835-i2s", I2S_OFFSET, 0x100); create_unimp(s, &s->smi, "bcm2835-smi", SMI_OFFSET, 0x100); create_unimp(s, &s->bscsl, "bcm2835-spis", BSC_SL_OFFSET, 0x100); - create_unimp(s, &s->i2c[0], "bcm2835-i2c0", BSC0_OFFSET, 0x20); - create_unimp(s, &s->i2c[1], "bcm2835-i2c1", BSC1_OFFSET, 0x20); - create_unimp(s, &s->i2c[2], "bcm2835-i2c2", BSC2_OFFSET, 0x20); create_unimp(s, &s->otp, "bcm2835-otp", OTP_OFFSET, 0x80); create_unimp(s, &s->dbus, "bcm2835-dbus", DBUS_OFFSET, 0x8000); create_unimp(s, &s->ave0, "bcm2835-ave0", AVE0_OFFSET, 0x8000); diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config RASPI select SDHCI select USB_DWC2 select BCM2835_SPI + select BCM2835_I2C config STM32F100_SOC bool -- 2.34.1
From: Rayhan Faizel <rayhan.faizel@gmail.com> Simple testcase for validating proper operation of read and write for all three BSC controllers. Signed-off-by: Rayhan Faizel <rayhan.faizel@gmail.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 20240224191038.2409945-4-rayhan.faizel@gmail.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- tests/qtest/bcm2835-i2c-test.c | 115 +++++++++++++++++++++++++++++++++ tests/qtest/meson.build | 2 +- 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 tests/qtest/bcm2835-i2c-test.c diff --git a/tests/qtest/bcm2835-i2c-test.c b/tests/qtest/bcm2835-i2c-test.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/qtest/bcm2835-i2c-test.c @@ -XXX,XX +XXX,XX @@ +/* + * QTest testcase for Broadcom Serial Controller (BSC) + * + * Copyright (c) 2024 Rayhan Faizel <rayhan.faizel@gmail.com> + * + * SPDX-License-Identifier: MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "libqtest-single.h" + +#include "hw/i2c/bcm2835_i2c.h" +#include "hw/sensor/tmp105_regs.h" + +static const uint32_t bsc_base_addrs[] = { + 0x3f205000, /* I2C0 */ + 0x3f804000, /* I2C1 */ + 0x3f805000, /* I2C2 */ +}; + +static void bcm2835_i2c_init_transfer(uint32_t base_addr, bool read) +{ + /* read flag is bit 0 so we can write it directly */ + int interrupt = read ? BCM2835_I2C_C_INTR : BCM2835_I2C_C_INTT; + + writel(base_addr + BCM2835_I2C_C, + BCM2835_I2C_C_I2CEN | BCM2835_I2C_C_INTD | + BCM2835_I2C_C_ST | BCM2835_I2C_C_CLEAR | interrupt | read); +} + +static void test_i2c_read_write(gconstpointer data) +{ + uint32_t i2cdata; + intptr_t index = (intptr_t) data; + uint32_t base_addr = bsc_base_addrs[index]; + + /* Write to TMP105 register */ + writel(base_addr + BCM2835_I2C_A, 0x50); + writel(base_addr + BCM2835_I2C_DLEN, 3); + + bcm2835_i2c_init_transfer(base_addr, 0); + + writel(base_addr + BCM2835_I2C_FIFO, TMP105_REG_T_HIGH); + writel(base_addr + BCM2835_I2C_FIFO, 0xde); + writel(base_addr + BCM2835_I2C_FIFO, 0xad); + + /* Clear flags */ + writel(base_addr + BCM2835_I2C_S, BCM2835_I2C_S_DONE | BCM2835_I2C_S_ERR | + BCM2835_I2C_S_CLKT); + + /* Read from TMP105 register */ + writel(base_addr + BCM2835_I2C_A, 0x50); + writel(base_addr + BCM2835_I2C_DLEN, 1); + + bcm2835_i2c_init_transfer(base_addr, 0); + + writel(base_addr + BCM2835_I2C_FIFO, TMP105_REG_T_HIGH); + + writel(base_addr + BCM2835_I2C_DLEN, 2); + bcm2835_i2c_init_transfer(base_addr, 1); + + i2cdata = readl(base_addr + BCM2835_I2C_FIFO); + g_assert_cmpint(i2cdata, ==, 0xde); + + i2cdata = readl(base_addr + BCM2835_I2C_FIFO); + g_assert_cmpint(i2cdata, ==, 0xad); + + /* Clear flags */ + writel(base_addr + BCM2835_I2C_S, BCM2835_I2C_S_DONE | BCM2835_I2C_S_ERR | + BCM2835_I2C_S_CLKT); + +} + +int main(int argc, char **argv) +{ + int ret; + int i; + + g_test_init(&argc, &argv, NULL); + + for (i = 0; i < 3; i++) { + g_autofree char *test_name = + g_strdup_printf("/bcm2835/bcm2835-i2c%d/read_write", i); + qtest_add_data_func(test_name, (void *)(intptr_t) i, + test_i2c_read_write); + } + + /* Run I2C tests with TMP105 slaves on all three buses */ + qtest_start("-M raspi3b " + "-device tmp105,address=0x50,bus=i2c-bus.0 " + "-device tmp105,address=0x50,bus=i2c-bus.1 " + "-device tmp105,address=0x50,bus=i2c-bus.2"); + ret = g_test_run(); + qtest_end(); + + return ret; +} diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -XXX,XX +XXX,XX @@ qtests_aarch64 = \ ['tpm-tis-device-test', 'tpm-tis-device-swtpm-test'] : []) + \ (config_all_devices.has_key('CONFIG_XLNX_ZYNQMP_ARM') ? ['xlnx-can-test', 'fuzz-xlnx-dp-test'] : []) + \ (config_all_devices.has_key('CONFIG_XLNX_VERSAL') ? ['xlnx-canfd-test', 'xlnx-versal-trng-test'] : []) + \ - (config_all_devices.has_key('CONFIG_RASPI') ? ['bcm2835-dma-test'] : []) + \ + (config_all_devices.has_key('CONFIG_RASPI') ? ['bcm2835-dma-test', 'bcm2835-i2c-test'] : []) + \ (config_all_accel.has_key('CONFIG_TCG') and \ config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test'] : []) + \ ['arm-cpu-features', -- 2.34.1
From: Tong Ho <tong.ho@amd.com> This patch adds loopback for sent characters, sent BREAK, and modem-control signals. Loopback of send and modem-control is often used for uart self tests in real hardware but missing from current pl011 model, resulting in self-test failures when running in QEMU. This implementation matches what is observed in real pl011 hardware placed in loopback mode: 1. Input characters and BREAK events from serial backend are ignored, but 2. Both TX characters and BREAK events are still sent to serial backend, in addition to be looped back to RX. Signed-off-by: Tong Ho <tong.ho@amd.com> Signed-off-by: Francisco Iglesias <francisco.iglesias@amd.com> Message-id: 20240227054855.44204-1-tong.ho@amd.com Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/char/pl011.c | 110 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 2 deletions(-) diff --git a/hw/char/pl011.c b/hw/char/pl011.c index XXXXXXX..XXXXXXX 100644 --- a/hw/char/pl011.c +++ b/hw/char/pl011.c @@ -XXX,XX +XXX,XX @@ DeviceState *pl011_create(hwaddr addr, qemu_irq irq, Chardev *chr) } /* Flag Register, UARTFR */ +#define PL011_FLAG_RI 0x100 #define PL011_FLAG_TXFE 0x80 #define PL011_FLAG_RXFF 0x40 #define PL011_FLAG_TXFF 0x20 #define PL011_FLAG_RXFE 0x10 +#define PL011_FLAG_DCD 0x04 +#define PL011_FLAG_DSR 0x02 +#define PL011_FLAG_CTS 0x01 /* Data Register, UARTDR */ #define DR_BE (1 << 10) @@ -XXX,XX +XXX,XX @@ DeviceState *pl011_create(hwaddr addr, qemu_irq irq, Chardev *chr) #define LCR_FEN (1 << 4) #define LCR_BRK (1 << 0) +/* Control Register, UARTCR */ +#define CR_OUT2 (1 << 13) +#define CR_OUT1 (1 << 12) +#define CR_RTS (1 << 11) +#define CR_DTR (1 << 10) +#define CR_LBE (1 << 7) + static const unsigned char pl011_id_arm[8] = { 0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; static const unsigned char pl011_id_luminary[8] = @@ -XXX,XX +XXX,XX @@ static void pl011_trace_baudrate_change(const PL011State *s) s->ibrd, s->fbrd); } +static bool pl011_loopback_enabled(PL011State *s) +{ + return !!(s->cr & CR_LBE); +} + +static void pl011_loopback_mdmctrl(PL011State *s) +{ + uint32_t cr, fr, il; + + if (!pl011_loopback_enabled(s)) { + return; + } + + /* + * Loopback software-driven modem control outputs to modem status inputs: + * FR.RI <= CR.Out2 + * FR.DCD <= CR.Out1 + * FR.CTS <= CR.RTS + * FR.DSR <= CR.DTR + * + * The loopback happens immediately even if this call is triggered + * by setting only CR.LBE. + * + * CTS/RTS updates due to enabled hardware flow controls are not + * dealt with here. + */ + cr = s->cr; + fr = s->flags & ~(PL011_FLAG_RI | PL011_FLAG_DCD | + PL011_FLAG_DSR | PL011_FLAG_CTS); + fr |= (cr & CR_OUT2) ? PL011_FLAG_RI : 0; + fr |= (cr & CR_OUT1) ? PL011_FLAG_DCD : 0; + fr |= (cr & CR_RTS) ? PL011_FLAG_CTS : 0; + fr |= (cr & CR_DTR) ? PL011_FLAG_DSR : 0; + + /* Change interrupts based on updated FR */ + il = s->int_level & ~(INT_DSR | INT_DCD | INT_CTS | INT_RI); + il |= (fr & PL011_FLAG_DSR) ? INT_DSR : 0; + il |= (fr & PL011_FLAG_DCD) ? INT_DCD : 0; + il |= (fr & PL011_FLAG_CTS) ? INT_CTS : 0; + il |= (fr & PL011_FLAG_RI) ? INT_RI : 0; + + s->flags = fr; + s->int_level = il; + pl011_update(s); +} + +static void pl011_put_fifo(void *opaque, uint32_t value); + +static void pl011_loopback_tx(PL011State *s, uint32_t value) +{ + if (!pl011_loopback_enabled(s)) { + return; + } + + /* + * Caveat: + * + * In real hardware, TX loopback happens at the serial-bit level + * and then reassembled by the RX logics back into bytes and placed + * into the RX fifo. That is, loopback happens after TX fifo. + * + * Because the real hardware TX fifo is time-drained at the frame + * rate governed by the configured serial format, some loopback + * bytes in TX fifo may still be able to get into the RX fifo + * that could be full at times while being drained at software + * pace. + * + * In such scenario, the RX draining pace is the major factor + * deciding which loopback bytes get into the RX fifo, unless + * hardware flow-control is enabled. + * + * For simplicity, the above described is not emulated. + */ + pl011_put_fifo(s, value); +} + +static void pl011_loopback_break(PL011State *s, int brk_enable) +{ + if (brk_enable) { + pl011_loopback_tx(s, DR_BE); + } +} + static void pl011_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) { @@ -XXX,XX +XXX,XX @@ static void pl011_write(void *opaque, hwaddr offset, /* XXX this blocks entire thread. Rewrite to use * qemu_chr_fe_write and background I/O callbacks */ qemu_chr_fe_write_all(&s->chr, &ch, 1); + pl011_loopback_tx(s, ch); s->int_level |= INT_TX; pl011_update(s); break; @@ -XXX,XX +XXX,XX @@ static void pl011_write(void *opaque, hwaddr offset, int break_enable = value & LCR_BRK; qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_SET_BREAK, &break_enable); + pl011_loopback_break(s, break_enable); } s->lcr = value; pl011_set_read_trigger(s); break; case 12: /* UARTCR */ - /* ??? Need to implement the enable and loopback bits. */ + /* ??? Need to implement the enable bit. */ s->cr = value; + pl011_loopback_mdmctrl(s); break; case 13: /* UARTIFS */ s->ifl = value; @@ -XXX,XX +XXX,XX @@ static void pl011_put_fifo(void *opaque, uint32_t value) static void pl011_receive(void *opaque, const uint8_t *buf, int size) { + /* + * In loopback mode, the RX input signal is internally disconnected + * from the entire receiving logics; thus, all inputs are ignored, + * and BREAK detection on RX input signal is also not performed. + */ + if (pl011_loopback_enabled(opaque)) { + return; + } + pl011_put_fifo(opaque, *buf); } static void pl011_event(void *opaque, QEMUChrEvent event) { - if (event == CHR_EVENT_BREAK) { + if (event == CHR_EVENT_BREAK && !pl011_loopback_enabled(opaque)) { pl011_put_fifo(opaque, DR_BE); } } -- 2.34.1
From: Arnaud Minier <arnaud.minier@telecom-paris.fr> Add the necessary files to add a simple RCC implementation with just reads from and writes to registers. Also instantiate 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> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 20240303140643.81957-2-arnaud.minier@telecom-paris.fr Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- MAINTAINERS | 5 +- docs/system/arm/b-l475e-iot01a.rst | 2 +- include/hw/arm/stm32l4x5_soc.h | 2 + include/hw/misc/stm32l4x5_rcc.h | 80 ++++ include/hw/misc/stm32l4x5_rcc_internals.h | 286 ++++++++++++++ hw/arm/stm32l4x5_soc.c | 12 +- hw/misc/stm32l4x5_rcc.c | 446 ++++++++++++++++++++++ hw/arm/Kconfig | 1 + hw/misc/Kconfig | 3 + hw/misc/meson.build | 1 + hw/misc/trace-events | 4 + 11 files changed, 839 insertions(+), 3 deletions(-) create mode 100644 include/hw/misc/stm32l4x5_rcc.h create mode 100644 include/hw/misc/stm32l4x5_rcc_internals.h create mode 100644 hw/misc/stm32l4x5_rcc.c diff --git a/MAINTAINERS b/MAINTAINERS index XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX 100644 --- a/docs/system/arm/b-l475e-iot01a.rst +++ b/docs/system/arm/b-l475e-iot01a.rst @@ -XXX,XX +XXX,XX @@ 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/include/hw/arm/stm32l4x5_soc.h b/include/hw/arm/stm32l4x5_soc.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/stm32l4x5_soc.h +++ b/include/hw/arm/stm32l4x5_soc.h @@ -XXX,XX +XXX,XX @@ #include "hw/or-irq.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" @@ -XXX,XX +XXX,XX @@ struct Stm32l4x5SocState { Stm32l4x5ExtiState exti; OrIRQState exti_or_gates[NUM_EXTI_OR_GATES]; 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 XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/misc/stm32l4x5_rcc.h @@ -XXX,XX +XXX,XX @@ +/* + * 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 XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/misc/stm32l4x5_rcc_internals.h @@ -XXX,XX +XXX,XX @@ +/* + * 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 */ diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/stm32l4x5_soc.c +++ b/hw/arm/stm32l4x5_soc.c @@ -XXX,XX +XXX,XX @@ static const int exti_irq[NUM_EXTI_IRQ] = { -1, -1, -1, -1, /* PVM[1..4] OR gate 1 */ 78 /* LCD wakeup, Direct */ }; +#define RCC_BASE_ADDRESS 0x40021000 +#define RCC_IRQ 5 static const int exti_or_gates_out[NUM_EXTI_OR_GATES] = { 23, 40, 63, 1, @@ -XXX,XX +XXX,XX @@ static void stm32l4x5_soc_initfn(Object *obj) TYPE_OR_IRQ); } 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); @@ -XXX,XX +XXX,XX @@ 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); @@ -XXX,XX +XXX,XX @@ 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/stm32l4x5_rcc.c b/hw/misc/stm32l4x5_rcc.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/misc/stm32l4x5_rcc.c @@ -XXX,XX +XXX,XX @@ +/* + * 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, + .min_access_size = 4, + .unaligned = false + }, + .impl = { + .max_access_size = 4, + .min_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_CLOCK(hsi16_rc, Stm32l4x5RccState), + VMSTATE_CLOCK(msi_rc, Stm32l4x5RccState), + VMSTATE_CLOCK(hse, Stm32l4x5RccState), + VMSTATE_CLOCK(lsi_rc, Stm32l4x5RccState), + VMSTATE_CLOCK(lse_crystal, Stm32l4x5RccState), + VMSTATE_CLOCK(sai1_extclk, Stm32l4x5RccState), + VMSTATE_CLOCK(sai2_extclk, 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/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config STM32L4X5_SOC select OR_IRQ select STM32L4X5_SYSCFG select STM32L4X5_EXTI + select STM32L4X5_RCC config XLNX_ZYNQMP_ARM bool diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -XXX,XX +XXX,XX @@ 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/trace-events b/hw/misc/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -XXX,XX +XXX,XX @@ 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" -- 2.34.1
From: Arnaud Minier <arnaud.minier@telecom-paris.fr> 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). The migration handling is based on hw/misc/zynq_sclr.c. Three phase reset will be handled in a later commit. 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> Message-id: 20240303140643.81957-3-arnaud.minier@telecom-paris.fr Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> --- include/hw/misc/stm32l4x5_rcc.h | 119 ++++++++++++++++ include/hw/misc/stm32l4x5_rcc_internals.h | 29 ++++ hw/misc/stm32l4x5_rcc.c | 160 ++++++++++++++++++++++ hw/misc/trace-events | 5 + 4 files changed, 313 insertions(+) diff --git a/include/hw/misc/stm32l4x5_rcc.h b/include/hw/misc/stm32l4x5_rcc.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/misc/stm32l4x5_rcc.h +++ b/include/hw/misc/stm32l4x5_rcc.h @@ -XXX,XX +XXX,XX @@ 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; @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX 100644 --- a/include/hw/misc/stm32l4x5_rcc_internals.h +++ b/include/hw/misc/stm32l4x5_rcc_internals.h @@ -XXX,XX +XXX,XX @@ #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) @@ -XXX,XX +XXX,XX @@ 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 */ diff --git a/hw/misc/stm32l4x5_rcc.c b/hw/misc/stm32l4x5_rcc.c index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/stm32l4x5_rcc.c +++ b/hw/misc/stm32l4x5_rcc.c @@ -XXX,XX +XXX,XX @@ #define LSE_FRQ 32768ULL #define LSI_FRQ 32000ULL +static void clock_mux_update(RccClockMuxState *mux) +{ + uint64_t src_freq; + Clock *current_source = mux->srcs[mux->src]; + uint32_t freq_multiplier = 0; + /* + * To avoid rounding errors, we use the clock period instead of the + * frequency. + * This means that the multiplier of the mux becomes the divider of + * the clock and the divider of the mux becomes the multiplier of the + * clock. + */ + if (mux->enabled && mux->divider) { + freq_multiplier = mux->divider; + } + + clock_set_mul_div(mux->out, freq_multiplier, mux->multiplier); + clock_update(mux->out, clock_get(current_source)); + + src_freq = clock_get_hz(current_source); + /* TODO: can we simply detect if the config changed so that we reduce log spam ? */ + trace_stm32l4x5_rcc_mux_update(mux->id, mux->src, src_freq, + mux->multiplier, mux->divider); +} + +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_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) { @@ -XXX,XX +XXX,XX @@ 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); @@ -XXX,XX +XXX,XX @@ 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"); } @@ -XXX,XX +XXX,XX @@ 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) { @@ -XXX,XX +XXX,XX @@ 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[] = { @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -XXX,XX +XXX,XX @@ 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, uint32_t multiplier, uint32_t divider) "RCC: Mux %d src %d update: src_freq %" PRIu64 " multiplier %" PRIu32 " divider %" PRIu32 # 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" -- 2.34.1
From: Arnaud Minier <arnaud.minier@telecom-paris.fr> This object represents the PLLs and their channels. The PLLs allow for a more fine-grained control of the clocks frequency. The migration handling is based on hw/misc/zynq_sclr.c. Three phase reset will be handled in a later commit. Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr> Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr> Message-id: 20240303140643.81957-4-arnaud.minier@telecom-paris.fr Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> --- include/hw/misc/stm32l4x5_rcc.h | 40 +++++ include/hw/misc/stm32l4x5_rcc_internals.h | 22 +++ hw/misc/stm32l4x5_rcc.c | 176 ++++++++++++++++++++++ hw/misc/trace-events | 5 + 4 files changed, 243 insertions(+) diff --git a/include/hw/misc/stm32l4x5_rcc.h b/include/hw/misc/stm32l4x5_rcc.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/misc/stm32l4x5_rcc.h +++ b/include/hw/misc/stm32l4x5_rcc.h @@ -XXX,XX +XXX,XX @@ 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 */ @@ -XXX,XX +XXX,XX @@ 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; @@ -XXX,XX +XXX,XX @@ 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; @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX 100644 --- a/include/hw/misc/stm32l4x5_rcc_internals.h +++ b/include/hw/misc/stm32l4x5_rcc_internals.h @@ -XXX,XX +XXX,XX @@ #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) @@ -XXX,XX +XXX,XX @@ 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, diff --git a/hw/misc/stm32l4x5_rcc.c b/hw/misc/stm32l4x5_rcc.c index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/stm32l4x5_rcc.c +++ b/hw/misc/stm32l4x5_rcc.c @@ -XXX,XX +XXX,XX @@ 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); + return; + } + + 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) { @@ -XXX,XX +XXX,XX @@ 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[*]", @@ -XXX,XX +XXX,XX @@ 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]; @@ -XXX,XX +XXX,XX @@ 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[] = { @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -XXX,XX +XXX,XX @@ 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, uint32_t multiplier, uint32_t divider) "RCC: Mux %d src %d update: src_freq %" PRIu64 " multiplier %" PRIu32 " divider %" PRIu32 +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" -- 2.34.1
From: Arnaud Minier <arnaud.minier@telecom-paris.fr> Instantiate 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. Also handle three-phase reset now that we have defined a known base state for every object. (Reset handling based on hw/misc/zynq_sclr.c) Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr> Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr> Message-id: 20240303140643.81957-5-arnaud.minier@telecom-paris.fr Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> --- include/hw/misc/stm32l4x5_rcc_internals.h | 705 ++++++++++++++++++++++ hw/misc/stm32l4x5_rcc.c | 145 ++++- 2 files changed, 833 insertions(+), 17 deletions(-) diff --git a/include/hw/misc/stm32l4x5_rcc_internals.h b/include/hw/misc/stm32l4x5_rcc_internals.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/misc/stm32l4x5_rcc_internals.h +++ b/include/hw/misc/stm32l4x5_rcc_internals.h @@ -XXX,XX +XXX,XX @@ 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 */ diff --git a/hw/misc/stm32l4x5_rcc.c b/hw/misc/stm32l4x5_rcc.c index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/stm32l4x5_rcc.c +++ b/hw/misc/stm32l4x5_rcc.c @@ -XXX,XX +XXX,XX @@ #define LSE_FRQ 32768ULL #define LSI_FRQ 32000ULL -static void clock_mux_update(RccClockMuxState *mux) +/* + * Function to simply acknowledge and propagate changes in a clock mux + * frequency. + * `bypass_source` allows to bypass the period of the current source and just + * consider it equal to 0. This is useful during the hold phase of reset. + */ +static void clock_mux_update(RccClockMuxState *mux, bool bypass_source) { uint64_t src_freq; Clock *current_source = mux->srcs[mux->src]; @@ -XXX,XX +XXX,XX @@ static void clock_mux_update(RccClockMuxState *mux) * the clock and the divider of the mux becomes the multiplier of the * clock. */ - if (mux->enabled && mux->divider) { + if (!bypass_source && mux->enabled && mux->divider) { freq_multiplier = mux->divider; } @@ -XXX,XX +XXX,XX @@ static void clock_mux_src_update(void *opaque, ClockEvent event) 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); + clock_mux_update(s, false); } } @@ -XXX,XX +XXX,XX @@ static void clock_mux_init(Object *obj) s->out = qdev_init_clock_out(DEVICE(s), "out"); } +static void clock_mux_reset_enter(Object *obj, ResetType type) +{ + RccClockMuxState *s = RCC_CLOCK_MUX(obj); + set_clock_mux_init_info(s, s->id); +} + static void clock_mux_reset_hold(Object *obj) -{ } +{ + RccClockMuxState *s = RCC_CLOCK_MUX(obj); + clock_mux_update(s, true); +} + +static void clock_mux_reset_exit(Object *obj) +{ + RccClockMuxState *s = RCC_CLOCK_MUX(obj); + clock_mux_update(s, false); +} static const VMStateDescription clock_mux_vmstate = { .name = TYPE_RCC_CLOCK_MUX, @@ -XXX,XX +XXX,XX @@ static void clock_mux_class_init(ObjectClass *klass, void *data) DeviceClass *dc = DEVICE_CLASS(klass); ResettableClass *rc = RESETTABLE_CLASS(klass); + rc->phases.enter = clock_mux_reset_enter; rc->phases.hold = clock_mux_reset_hold; + rc->phases.exit = clock_mux_reset_exit; dc->vmsd = &clock_mux_vmstate; } @@ -XXX,XX +XXX,XX @@ static void clock_mux_set_enable(RccClockMuxState *mux, bool enabled) } mux->enabled = enabled; - clock_mux_update(mux); + clock_mux_update(mux, false); } static void clock_mux_set_factor(RccClockMuxState *mux, @@ -XXX,XX +XXX,XX @@ static void clock_mux_set_factor(RccClockMuxState *mux, mux->multiplier = multiplier; mux->divider = divider; - clock_mux_update(mux); + clock_mux_update(mux, false); } static void clock_mux_set_source(RccClockMuxState *mux, RccClockMuxSource src) @@ -XXX,XX +XXX,XX @@ static void clock_mux_set_source(RccClockMuxState *mux, RccClockMuxSource src) trace_stm32l4x5_rcc_mux_set_src(mux->id, mux->src, src); mux->src = src; - clock_mux_update(mux); + clock_mux_update(mux, false); } -static void pll_update(RccPllState *pll) +/* + * Acknowledge and propagate changes in a PLL frequency. + * `bypass_source` allows to bypass the period of the current source and just + * consider it equal to 0. This is useful during the hold phase of reset. + */ +static void pll_update(RccPllState *pll, bool bypass_source) { uint64_t vco_freq, old_channel_freq, channel_freq; int i; @@ -XXX,XX +XXX,XX @@ static void pll_update(RccPllState *pll) } old_channel_freq = clock_get_hz(pll->channels[i]); - if (!pll->enabled || + if (bypass_source || + !pll->enabled || !pll->channel_enabled[i] || !pll->channel_divider[i]) { channel_freq = 0; @@ -XXX,XX +XXX,XX @@ static void pll_update(RccPllState *pll) static void pll_src_update(void *opaque, ClockEvent event) { RccPllState *s = opaque; - pll_update(s); + pll_update(s, false); } static void pll_init(Object *obj) @@ -XXX,XX +XXX,XX @@ static void pll_init(Object *obj) } } +static void pll_reset_enter(Object *obj, ResetType type) +{ + RccPllState *s = RCC_PLL(obj); + set_pll_init_info(s, s->id); +} + static void pll_reset_hold(Object *obj) -{ } +{ + RccPllState *s = RCC_PLL(obj); + pll_update(s, true); +} + +static void pll_reset_exit(Object *obj) +{ + RccPllState *s = RCC_PLL(obj); + pll_update(s, false); +} static const VMStateDescription pll_vmstate = { .name = TYPE_RCC_PLL, @@ -XXX,XX +XXX,XX @@ static void pll_class_init(ObjectClass *klass, void *data) DeviceClass *dc = DEVICE_CLASS(klass); ResettableClass *rc = RESETTABLE_CLASS(klass); + rc->phases.enter = pll_reset_enter; rc->phases.hold = pll_reset_hold; + rc->phases.exit = pll_reset_exit; dc->vmsd = &pll_vmstate; } @@ -XXX,XX +XXX,XX @@ static void pll_set_vco_multiplier(RccPllState *pll, uint32_t vco_multiplier) pll->vco_multiplier, vco_multiplier); pll->vco_multiplier = vco_multiplier; - pll_update(pll); + pll_update(pll, false); } static void pll_set_enable(RccPllState *pll, bool enabled) @@ -XXX,XX +XXX,XX @@ static void pll_set_enable(RccPllState *pll, bool enabled) } pll->enabled = enabled; - pll_update(pll); + pll_update(pll, false); } static void pll_set_channel_enable(RccPllState *pll, @@ -XXX,XX +XXX,XX @@ static void pll_set_channel_enable(RccPllState *pll, } pll->channel_enabled[channel] = enabled; - pll_update(pll); + pll_update(pll, false); } static void pll_set_channel_divider(RccPllState *pll, @@ -XXX,XX +XXX,XX @@ static void pll_set_channel_divider(RccPllState *pll, channel, pll->channel_divider[channel], divider); pll->channel_divider[channel] = divider; - pll_update(pll); + pll_update(pll, false); } static void rcc_update_irq(Stm32l4x5RccState *s) @@ -XXX,XX +XXX,XX @@ 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, @@ -XXX,XX +XXX,XX @@ 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); @@ -XXX,XX +XXX,XX @@ 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); -- 2.34.1
From: Arnaud Minier <arnaud.minier@telecom-paris.fr> 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> Message-id: 20240303140643.81957-6-arnaud.minier@telecom-paris.fr Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> --- hw/misc/stm32l4x5_rcc.c | 524 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 512 insertions(+), 12 deletions(-) diff --git a/hw/misc/stm32l4x5_rcc.c b/hw/misc/stm32l4x5_rcc.c index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/stm32l4x5_rcc.c +++ b/hw/misc/stm32l4x5_rcc.c @@ -XXX,XX +XXX,XX @@ #include "hw/qdev-clock.h" #include "hw/qdev-properties.h" #include "hw/qdev-properties-system.h" +#include "hw/registerfields.h" #include "trace.h" #define HSE_DEFAULT_FRQ 48000000ULL @@ -XXX,XX +XXX,XX @@ 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 { @@ -XXX,XX +XXX,XX @@ static void rcc_update_irq(Stm32l4x5RccState *s) } } +static void rcc_update_cr_register(Stm32l4x5RccState *s) +{ + int val; + + /* PLLSAI2ON and update PLLSAI2RDY */ + val = FIELD_EX32(s->cr, CR, PLLSAI2ON); + 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 = FIELD_EX32(s->cr, CR, PLLSAI1ON); + 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 = FIELD_EX32(s->cr, CR, PLLON); + 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 = FIELD_EX32(s->cr, CR, HSEON); + 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(s->hse, 0); + } + + /* HSIAFS: TODO*/ + /* HSIKERON: TODO*/ + + /* HSION and update HSIRDY*/ + val = FIELD_EX32(s->cr, CR, HSION); + 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(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 = FIELD_EX32(s->cr, CR, MSIRGSEL); + if (val) { + /* MSIRGSEL is set, use the MSIRANGE field */ + val = FIELD_EX32(s->cr, CR, MSIRANGE); + } else { + /* MSIRGSEL is not set, use the MSISRANGE field */ + val = FIELD_EX32(s->csr, CSR, MSISRANGE); + } + + 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 = FIELD_EX32(s->cr, CR, MSION); + 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 = FIELD_EX32(s->cfgr, CFGR, MCOPRE); + assert(val <= 0b100); + clock_mux_set_factor(&s->clock_muxes[RCC_CLOCK_MUX_MCO], + 1, 1 << val); + + /* MCOSEL */ + val = FIELD_EX32(s->cfgr, CFGR, MCOSEL); + 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 = FIELD_EX32(s->cfgr, CFGR, PPRE2); + 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 = FIELD_EX32(s->cfgr, CFGR, PPRE1); + 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 = FIELD_EX32(s->cfgr, CFGR, HPRE); + 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 = FIELD_EX32(s->cfgr, CFGR, SW); + 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], \ + FIELD_EX32(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], \ + FIELD_EX32(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], \ + FIELD_EX32(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], \ + FIELD_EX32(s->apb1enr1, APB1ENR1, _peripheral_name##EN)) + #define APB1ENR2_SET_ENABLE(_peripheral_name) \ + clock_mux_set_enable(&s->clock_muxes[RCC_CLOCK_MUX_##_peripheral_name], \ + FIELD_EX32(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], \ + FIELD_EX32(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 = FIELD_EX32(reg, PLLCFGR, PLLPDIV); + /* 1 is a reserved value */ + if (val == 0) { + /* Get PLLP value */ + val = FIELD_EX32(reg, PLLCFGR, PLLP); + 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 = FIELD_EX32(reg, PLLCFGR, PLLR); + pll_set_channel_divider(&s->plls[pll_id], RCC_PLL_COMMON_CHANNEL_R, + 2 * (val + 1)); + + /* PLLREN */ + val = FIELD_EX32(reg, PLLCFGR, PLLREN); + pll_set_channel_enable(&s->plls[pll_id], RCC_PLL_COMMON_CHANNEL_R, val); + + /* PLLQ */ + val = FIELD_EX32(reg, PLLCFGR, PLLQ); + pll_set_channel_divider(&s->plls[pll_id], RCC_PLL_COMMON_CHANNEL_Q, + 2 * (val + 1)); + + /* PLLQEN */ + val = FIELD_EX32(reg, PLLCFGR, PLLQEN); + pll_set_channel_enable(&s->plls[pll_id], RCC_PLL_COMMON_CHANNEL_Q, val); + + /* PLLPEN */ + val = FIELD_EX32(reg, PLLCFGR, PLLPEN); + pll_set_channel_enable(&s->plls[pll_id], RCC_PLL_COMMON_CHANNEL_P, val); + + /* PLLN */ + val = FIELD_EX32(reg, PLLCFGR, PLLN); + 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 = FIELD_EX32(s->pllcfgr, PLLCFGR, PLLM); + clock_mux_set_factor(&s->clock_muxes[RCC_CLOCK_MUX_PLL_INPUT], 1, (val + 1)); + + /* PLLSRC */ + val = FIELD_EX32(s->pllcfgr, PLLCFGR, PLLSRC); + 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], \ + FIELD_EX32(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 = FIELD_EX32(s->bdcr, BDCR, LSCOSEL); + clock_mux_set_source(&s->clock_muxes[RCC_CLOCK_MUX_LSCO], val); + + val = FIELD_EX32(s->bdcr, BDCR, LSCOEN); + 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 = FIELD_EX32(s->bdcr, BDCR, RTCEN); + clock_mux_set_enable(&s->clock_muxes[RCC_CLOCK_MUX_RTC], val); + /* LCD and RTC share the same clock */ + val = FIELD_EX32(s->bdcr, BDCR, RTCSEL); + 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 = FIELD_EX32(s->bdcr, BDCR, LSEON); + 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(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 = FIELD_EX32(s->csr, CSR, LSION); + 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(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); @@ -XXX,XX +XXX,XX @@ 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, @@ -XXX,XX +XXX,XX @@ 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, @@ -XXX,XX +XXX,XX @@ 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
From: Arnaud Minier <arnaud.minier@telecom-paris.fr> 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> Message-id: 20240303140643.81957-7-arnaud.minier@telecom-paris.fr Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> --- hw/misc/stm32l4x5_rcc.c | 166 ++++++++++++++++++++++++++++------------ 1 file changed, 115 insertions(+), 51 deletions(-) diff --git a/hw/misc/stm32l4x5_rcc.c b/hw/misc/stm32l4x5_rcc.c index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/stm32l4x5_rcc.c +++ b/hw/misc/stm32l4x5_rcc.c @@ -XXX,XX +XXX,XX @@ 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 = FIELD_EX32(s->cr, CR, PLLSAI2ON); @@ -XXX,XX +XXX,XX @@ 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 = FIELD_EX32(s->cr, CR, PLLON); - 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 (FIELD_EX32(s->cfgr, CFGR, SWS) != 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 = FIELD_EX32(s->cr, CR, HSEON); - 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 (FIELD_EX32(s->cfgr, CFGR, SWS) != 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(s->hse, 0); } } else { - clock_update(s->hse, 0); + s->cr |= R_CR_HSEON_MASK; } /* HSIAFS: TODO*/ /* HSIKERON: TODO*/ - /* HSION and update HSIRDY*/ - val = FIELD_EX32(s->cr, CR, HSION); - 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 (FIELD_EX32(s->cfgr, CFGR, SWS) == 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(s->hsi16_rc, 0); + val = FIELD_EX32(s->cr, CR, HSION); + 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(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 = FIELD_EX32(s->cr, CR, MSIRGSEL); - if (val) { - /* MSIRGSEL is set, use the MSIRANGE field */ - val = FIELD_EX32(s->cr, CR, MSIRANGE); + /* MSIPLLEN: TODO */ + + /* + * MSION and update MSIRDY + * Set by hardware when used directly or indirectly as system clock. + */ + if (FIELD_EX32(s->cfgr, CFGR, SWS) == 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 { - /* MSIRGSEL is not set, use the MSISRANGE field */ - val = FIELD_EX32(s->csr, CSR, MSISRANGE); - } - - 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 = FIELD_EX32(s->cr, CR, MSION); - 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 = FIELD_EX32(s->cr, CR, MSION); + 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(s->msi_rc, 0); + } } rcc_update_irq(s); } @@ -XXX,XX +XXX,XX @@ 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
From: Arnaud Minier <arnaud.minier@telecom-paris.fr> 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> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 20240303140643.81957-8-arnaud.minier@telecom-paris.fr Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- include/hw/arm/stm32l4x5_soc.h | 3 --- hw/arm/b-l475e-iot01a.c | 10 +--------- hw/arm/stm32l4x5_soc.c | 33 ++++----------------------------- 3 files changed, 5 insertions(+), 41 deletions(-) diff --git a/include/hw/arm/stm32l4x5_soc.h b/include/hw/arm/stm32l4x5_soc.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/stm32l4x5_soc.h +++ b/include/hw/arm/stm32l4x5_soc.h @@ -XXX,XX +XXX,XX @@ struct Stm32l4x5SocState { MemoryRegion sram2; MemoryRegion flash; MemoryRegion flash_alias; - - Clock *sysclk; - Clock *refclk; }; struct Stm32l4x5SocClass { diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/b-l475e-iot01a.c +++ b/hw/arm/b-l475e-iot01a.c @@ -XXX,XX +XXX,XX @@ #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 XXXXXXX..XXXXXXX 100644 --- a/hw/arm/stm32l4x5_soc.c +++ b/hw/arm/stm32l4x5_soc.c @@ -XXX,XX +XXX,XX @@ static void stm32l4x5_soc_initfn(Object *obj) } 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) @@ -XXX,XX +XXX,XX @@ 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; @@ -XXX,XX +XXX,XX @@ 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)) { -- 2.34.1
From: Arnaud Minier <arnaud.minier@telecom-paris.fr> 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> Acked-by: Thomas Huth <thuth@redhat.com> Message-id: 20240303140643.81957-9-arnaud.minier@telecom-paris.fr Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> --- tests/qtest/stm32l4x5_rcc-test.c | 189 +++++++++++++++++++++++++++++++ tests/qtest/meson.build | 3 +- 2 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 tests/qtest/stm32l4x5_rcc-test.c diff --git a/tests/qtest/stm32l4x5_rcc-test.c b/tests/qtest/stm32l4x5_rcc-test.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/qtest/stm32l4x5_rcc-test.c @@ -XXX,XX +XXX,XX @@ +/* + * 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 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_readl(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_readl(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_readl(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_readl(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_readl(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. + * We assume that all operations that would take some time to have an effect + * (e.g. changing the PLL frequency) are done instantaneously. + */ + 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; +} diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -XXX,XX +XXX,XX @@ 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'] : []) + \ -- 2.34.1
From: Richard Henderson <richard.henderson@linaro.org> Now that we have removed TARGET_PAGE_BITS_MIN-6 from TLB_FLAGS_MASK, we can test for 32-byte alignment. Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20240301204110.656742-2-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/translate.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/target/arm/tcg/translate.c b/target/arm/tcg/translate.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/translate.c +++ b/target/arm/tcg/translate.c @@ -XXX,XX +XXX,XX @@ static inline void store_reg_from_load(DisasContext *s, int reg, TCGv_i32 var) MemOp pow2_align(unsigned i) { static const MemOp mop_align[] = { - 0, MO_ALIGN_2, MO_ALIGN_4, MO_ALIGN_8, MO_ALIGN_16, - /* - * FIXME: TARGET_PAGE_BITS_MIN affects TLB_FLAGS_MASK such - * that 256-bit alignment (MO_ALIGN_32) cannot be supported: - * see get_alignment_bits(). Enforce only 128-bit alignment for now. - */ - MO_ALIGN_16 + 0, MO_ALIGN_2, MO_ALIGN_4, MO_ALIGN_8, MO_ALIGN_16, MO_ALIGN_32 }; g_assert(i < ARRAY_SIZE(mop_align)); return mop_align[i]; -- 2.34.1
From: Richard Henderson <richard.henderson@linaro.org> These fields are no longer used since 937f224559. Target specific extensions to the page tables should be done with TARGET_PAGE_ENTRY_EXTRA. Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20240301204110.656742-3-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- include/exec/memattrs.h | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/include/exec/memattrs.h b/include/exec/memattrs.h index XXXXXXX..XXXXXXX 100644 --- a/include/exec/memattrs.h +++ b/include/exec/memattrs.h @@ -XXX,XX +XXX,XX @@ typedef struct MemTxAttrs { unsigned int requester_id:16; /* Invert endianness for this page */ unsigned int byte_swap:1; - /* - * The following are target-specific page-table bits. These are not - * related to actual memory transactions at all. However, this structure - * is part of the tlb_fill interface, cached in the cputlb structure, - * and has unused bits. These fields will be read by target-specific - * helpers using env->iotlb[mmu_idx][tlb_index()].attrs.target_tlb_bitN. - */ - unsigned int target_tlb_bit0 : 1; - unsigned int target_tlb_bit1 : 1; - unsigned int target_tlb_bit2 : 1; } MemTxAttrs; /* Bus masters which don't specify any attributes will get this, -- 2.34.1
From: Richard Henderson <richard.henderson@linaro.org> Allow the target to set tlb flags to apply to all of the comparators. Remove MemTxAttrs.byte_swap, as the bit is not relevant to memory transactions, only the page mapping. Adjust target/sparc to set TLB_BSWAP directly. Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20240301204110.656742-4-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- include/exec/memattrs.h | 2 -- include/hw/core/cpu.h | 3 +++ accel/tcg/cputlb.c | 5 +---- target/sparc/mmu_helper.c | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/include/exec/memattrs.h b/include/exec/memattrs.h index XXXXXXX..XXXXXXX 100644 --- a/include/exec/memattrs.h +++ b/include/exec/memattrs.h @@ -XXX,XX +XXX,XX @@ typedef struct MemTxAttrs { unsigned int memory:1; /* Requester ID (for MSI for example) */ unsigned int requester_id:16; - /* Invert endianness for this page */ - unsigned int byte_swap:1; } MemTxAttrs; /* Bus masters which don't specify any attributes will get this, diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/core/cpu.h +++ b/include/hw/core/cpu.h @@ -XXX,XX +XXX,XX @@ typedef struct CPUTLBEntryFull { /* @lg_page_size contains the log2 of the page size. */ uint8_t lg_page_size; + /* Additional tlb flags requested by tlb_fill. */ + uint8_t tlb_fill_flags; + /* * Additional tlb flags for use by the slow path. If non-zero, * the corresponding CPUTLBEntry comparator must have TLB_FORCE_SLOW. diff --git a/accel/tcg/cputlb.c b/accel/tcg/cputlb.c index XXXXXXX..XXXXXXX 100644 --- a/accel/tcg/cputlb.c +++ b/accel/tcg/cputlb.c @@ -XXX,XX +XXX,XX @@ void tlb_set_page_full(CPUState *cpu, int mmu_idx, " prot=%x idx=%d\n", addr, full->phys_addr, prot, mmu_idx); - read_flags = 0; + read_flags = full->tlb_fill_flags; if (full->lg_page_size < TARGET_PAGE_BITS) { /* Repeat the MMU check and TLB fill on every access. */ read_flags |= TLB_INVALID_MASK; } - if (full->attrs.byte_swap) { - read_flags |= TLB_BSWAP; - } is_ram = memory_region_is_ram(section->mr); is_romd = memory_region_is_romd(section->mr); diff --git a/target/sparc/mmu_helper.c b/target/sparc/mmu_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/sparc/mmu_helper.c +++ b/target/sparc/mmu_helper.c @@ -XXX,XX +XXX,XX @@ static int get_physical_address_data(CPUSPARCState *env, CPUTLBEntryFull *full, int do_fault = 0; if (TTE_IS_IE(env->dtlb[i].tte)) { - full->attrs.byte_swap = true; + full->tlb_fill_flags |= TLB_BSWAP; } /* access ok? */ -- 2.34.1
From: Richard Henderson <richard.henderson@linaro.org> This creates a per-page method for checking of alignment. Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20240301204110.656742-5-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- include/exec/cpu-all.h | 4 +++- accel/tcg/cputlb.c | 30 +++++++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/include/exec/cpu-all.h b/include/exec/cpu-all.h index XXXXXXX..XXXXXXX 100644 --- a/include/exec/cpu-all.h +++ b/include/exec/cpu-all.h @@ -XXX,XX +XXX,XX @@ static inline int cpu_mmu_index(CPUState *cs, bool ifetch) #define TLB_BSWAP (1 << 0) /* Set if TLB entry contains a watchpoint. */ #define TLB_WATCHPOINT (1 << 1) +/* Set if TLB entry requires aligned accesses. */ +#define TLB_CHECK_ALIGNED (1 << 2) -#define TLB_SLOW_FLAGS_MASK (TLB_BSWAP | TLB_WATCHPOINT) +#define TLB_SLOW_FLAGS_MASK (TLB_BSWAP | TLB_WATCHPOINT | TLB_CHECK_ALIGNED) /* The two sets of flags must not overlap. */ QEMU_BUILD_BUG_ON(TLB_FLAGS_MASK & TLB_SLOW_FLAGS_MASK); diff --git a/accel/tcg/cputlb.c b/accel/tcg/cputlb.c index XXXXXXX..XXXXXXX 100644 --- a/accel/tcg/cputlb.c +++ b/accel/tcg/cputlb.c @@ -XXX,XX +XXX,XX @@ static int probe_access_internal(CPUState *cpu, vaddr addr, flags |= full->slow_flags[access_type]; /* Fold all "mmio-like" bits into TLB_MMIO. This is not RAM. */ - if (unlikely(flags & ~(TLB_WATCHPOINT | TLB_NOTDIRTY)) - || - (access_type != MMU_INST_FETCH && force_mmio)) { + if (unlikely(flags & ~(TLB_WATCHPOINT | TLB_NOTDIRTY | TLB_CHECK_ALIGNED)) + || (access_type != MMU_INST_FETCH && force_mmio)) { *phost = NULL; return TLB_MMIO; } @@ -XXX,XX +XXX,XX @@ static bool mmu_lookup(CPUState *cpu, vaddr addr, MemOpIdx oi, tcg_debug_assert((flags & TLB_BSWAP) == 0); } + /* + * This alignment check differs from the one above, in that this is + * based on the atomicity of the operation. The intended use case is + * the ARM memory type field of each PTE, where access to pages with + * Device memory type require alignment. + */ + if (unlikely(flags & TLB_CHECK_ALIGNED)) { + MemOp size = l->memop & MO_SIZE; + + switch (l->memop & MO_ATOM_MASK) { + case MO_ATOM_NONE: + size = MO_8; + break; + case MO_ATOM_IFALIGN_PAIR: + case MO_ATOM_WITHIN16_PAIR: + size = size ? size - 1 : 0; + break; + default: + break; + } + if (addr & ((1 << size) - 1)) { + cpu_unaligned_access(cpu, addr, type, l->mmu_idx, ra); + } + } + return crosspage; } -- 2.34.1
From: Richard Henderson <richard.henderson@linaro.org> If translation is disabled, the default memory type is Device, which requires alignment checking. This is more optimally done early via the MemOp given to the TCG memory operation. Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reported-by: Idan Horowitz <idan.horowitz@gmail.com> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20240301204110.656742-6-richard.henderson@linaro.org Resolves: https://gitlab.com/qemu-project/qemu/-/issues/1204 Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/hflags.c | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/target/arm/tcg/hflags.c b/target/arm/tcg/hflags.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/hflags.c +++ b/target/arm/tcg/hflags.c @@ -XXX,XX +XXX,XX @@ static inline bool fgt_svc(CPUARMState *env, int el) FIELD_EX64(env->cp15.fgt_exec[FGTREG_HFGITR], HFGITR_EL2, SVC_EL1); } +/* Return true if memory alignment should be enforced. */ +static bool aprofile_require_alignment(CPUARMState *env, int el, uint64_t sctlr) +{ +#ifdef CONFIG_USER_ONLY + return false; +#else + /* Check the alignment enable bit. */ + if (sctlr & SCTLR_A) { + return true; + } + + /* + * If translation is disabled, then the default memory type is + * Device(-nGnRnE) instead of Normal, which requires that alignment + * be enforced. Since this affects all ram, it is most efficient + * to handle this during translation. + */ + if (sctlr & SCTLR_M) { + /* Translation enabled: memory type in PTE via MAIR_ELx. */ + return false; + } + if (el < 2 && (arm_hcr_el2_eff(env) & (HCR_DC | HCR_VM))) { + /* Stage 2 translation enabled: memory type in PTE. */ + return false; + } + return true; +#endif +} + static CPUARMTBFlags rebuild_hflags_common(CPUARMState *env, int fp_el, ARMMMUIdx mmu_idx, CPUARMTBFlags flags) @@ -XXX,XX +XXX,XX @@ static CPUARMTBFlags rebuild_hflags_a32(CPUARMState *env, int fp_el, { CPUARMTBFlags flags = {}; int el = arm_current_el(env); + uint64_t sctlr = arm_sctlr(env, el); - if (arm_sctlr(env, el) & SCTLR_A) { + if (aprofile_require_alignment(env, el, sctlr)) { DP_TBFLAG_ANY(flags, ALIGN_MEM, 1); } @@ -XXX,XX +XXX,XX @@ static CPUARMTBFlags rebuild_hflags_a64(CPUARMState *env, int el, int fp_el, sctlr = regime_sctlr(env, stage1); - if (sctlr & SCTLR_A) { + if (aprofile_require_alignment(env, el, sctlr)) { DP_TBFLAG_ANY(flags, ALIGN_MEM, 1); } -- 2.34.1
From: Richard Henderson <richard.henderson@linaro.org> If translation is enabled, and the PTE memory type is Device, enable checking alignment via TLB_CHECK_ALIGNMENT. While the check is done later than it should be per the ARM, it's better than not performing the check at all. Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20240301204110.656742-7-richard.henderson@linaro.org [PMM: tweaks to comment text] Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/ptw.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/target/arm/ptw.c b/target/arm/ptw.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/ptw.c +++ b/target/arm/ptw.c @@ -XXX,XX +XXX,XX @@ static bool granule_protection_check(CPUARMState *env, uint64_t paddress, return false; } +static bool S1_attrs_are_device(uint8_t attrs) +{ + /* + * This slightly under-decodes the MAIR_ELx field: + * 0b0000dd01 is Device with FEAT_XS, otherwise UNPREDICTABLE; + * 0b0000dd1x is UNPREDICTABLE. + */ + return (attrs & 0xf0) == 0; +} + static bool S2_attrs_are_device(uint64_t hcr, uint8_t attrs) { /* @@ -XXX,XX +XXX,XX @@ static bool get_phys_addr_lpae(CPUARMState *env, S1Translate *ptw, bool aarch64 = arm_el_is_aa64(env, el); uint64_t descriptor, new_descriptor; ARMSecuritySpace out_space; + bool device; /* TODO: This code does not support shareability levels. */ if (aarch64) { @@ -XXX,XX +XXX,XX @@ static bool get_phys_addr_lpae(CPUARMState *env, S1Translate *ptw, if (regime_is_stage2(mmu_idx)) { result->cacheattrs.is_s2_format = true; result->cacheattrs.attrs = extract32(attrs, 2, 4); + /* + * Security state does not really affect HCR_EL2.FWB; + * we only need to filter FWB for aa32 or other FEAT. + */ + device = S2_attrs_are_device(arm_hcr_el2_eff(env), + result->cacheattrs.attrs); } else { /* Index into MAIR registers for cache attributes */ uint8_t attrindx = extract32(attrs, 2, 3); @@ -XXX,XX +XXX,XX @@ static bool get_phys_addr_lpae(CPUARMState *env, S1Translate *ptw, if (aarch64 && cpu_isar_feature(aa64_bti, cpu)) { result->f.extra.arm.guarded = extract64(attrs, 50, 1); /* GP */ } + device = S1_attrs_are_device(result->cacheattrs.attrs); + } + + /* + * Enable alignment checks on Device memory. + * + * Per R_XCHFJ, this check is mis-ordered. The correct ordering + * for alignment, permission, and stage 2 faults should be: + * - Alignment fault caused by the memory type + * - Permission fault + * - A stage 2 fault on the memory access + * but due to the way the TCG softmmu TLB operates, we will have + * implicitly done the permission check and the stage2 lookup in + * finding the TLB entry, so the alignment check cannot be done sooner. + * + * In v7, for a CPU without the Virtualization Extensions this + * access is UNPREDICTABLE; we choose to make it take the alignment + * fault as is required for a v7VE CPU. (QEMU doesn't emulate any + * CPUs with ARM_FEATURE_LPAE but not ARM_FEATURE_V7VE anyway.) + */ + if (device) { + result->f.tlb_fill_flags |= TLB_CHECK_ALIGNED; } /* -- 2.34.1
The qatomic_cmpxchg() and qatomic_cmpxchg__nocheck() macros have a comment that reads: Returns the eventual value, failed or not This is somewhere between cryptic and wrong, since the value actually returned is the value that was in memory before the cmpxchg. Reword to match how we describe these macros in atomics.rst. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com> Reviewed-by: Zhao Liu <zhao1.liu@intel.com> Message-id: 20240223182035.1048541-1-peter.maydell@linaro.org --- include/qemu/atomic.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/qemu/atomic.h b/include/qemu/atomic.h index XXXXXXX..XXXXXXX 100644 --- a/include/qemu/atomic.h +++ b/include/qemu/atomic.h @@ -XXX,XX +XXX,XX @@ qatomic_xchg__nocheck(ptr, i); \ }) -/* Returns the eventual value, failed or not */ +/* Returns the old value of '*ptr' (whether the cmpxchg failed or not) */ #define qatomic_cmpxchg__nocheck(ptr, old, new) ({ \ typeof_strip_qual(*ptr) _old = (old); \ (void)__atomic_compare_exchange_n(ptr, &_old, new, false, \ -- 2.34.1
From: Steven Shen <steven.shen@jaguarmicro.com> Before v2.12, the implementation of serial ports was limited to a value of MAX_SERIAL_PORTS = 4. We now dynamically allocate the data structures for serial ports, so this limit is no longer present, but the documentation for the -serial options still reads: "This option can be used several times to simulate up to 4 serial ports." Update to "This option can be used several times to simulate multiple serial ports." to avoid misleading. Signed-off-by: Steven Shen <steven.shen@jaguarmicro.com> Message-id: 20240305013016.2268-1-steven.shen@jaguarmicro.com Reviewed-by: Peter Maydell <peter.maydell@linaro.org> [PMM: tweaked commit message] Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- qemu-options.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qemu-options.hx b/qemu-options.hx index XXXXXXX..XXXXXXX 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -XXX,XX +XXX,XX @@ SRST default device is ``vc`` in graphical mode and ``stdio`` in non graphical mode. - This option can be used several times to simulate up to 4 serial + This option can be used several times to simulate multiple serial ports. You can use ``-serial none`` to suppress the creation of default -- 2.34.1
v3->v4: Windows headers define an INT type which clashed with an enum value name in arm_gicv3_its.c... The following changes since commit eae587e8e3694b1aceab23239493fb4c7e1a80f5: Merge remote-tracking branch 'remotes/armbru/tags/pull-qapi-2021-09-13' into staging (2021-09-13 11:00:30 +0100) are available in the Git repository at: https://git.linaro.org/people/pmaydell/qemu-arm.git tags/pull-target-arm-20210913-3 for you to fetch changes up to 28e987a7e7edaa3ca7feeac65edca26145df8814: hw/arm/mps2.c: Mark internal-only I2C buses as 'full' (2021-09-13 21:01:08 +0100) ---------------------------------------------------------------- target-arm queue: * mark MPS2/MPS3 board-internal i2c buses as 'full' so that command line user-created devices are not plugged into them * Take an exception if PSTATE.IL is set * Support an emulated ITS in the virt board * Add support for kudo-bmc board * Probe for KVM_CAP_ARM_VM_IPA_SIZE when creating scratch VM * cadence_uart: Fix clock handling issues that prevented u-boot from running ---------------------------------------------------------------- Bin Meng (6): hw/misc: zynq_slcr: Correctly compute output clocks in the reset exit phase hw/char: cadence_uart: Disable transmit when input clock is disabled hw/char: cadence_uart: Move clock/reset check to uart_can_receive() hw/char: cadence_uart: Convert to memop_with_attrs() ops hw/char: cadence_uart: Ignore access when unclocked or in reset for uart_{read, write}() hw/char: cadence_uart: Log a guest error when device is unclocked or in reset Chris Rauer (1): hw/arm: Add support for kudo-bmc board. Marc Zyngier (1): hw/arm/virt: KVM: Probe for KVM_CAP_ARM_VM_IPA_SIZE when creating scratch VM Peter Maydell (5): target/arm: Take an exception if PSTATE.IL is set qdev: Support marking individual buses as 'full' hw/arm/mps2-tz.c: Add extra data parameter to MakeDevFn hw/arm/mps2-tz.c: Mark internal-only I2C buses as 'full' hw/arm/mps2.c: Mark internal-only I2C buses as 'full' Richard Henderson (1): target/arm: Merge disas_a64_insn into aarch64_tr_translate_insn Shashi Mallela (9): hw/intc: GICv3 ITS initial framework hw/intc: GICv3 ITS register definitions added hw/intc: GICv3 ITS command queue framework hw/intc: GICv3 ITS Command processing hw/intc: GICv3 ITS Feature enablement hw/intc: GICv3 redistributor ITS processing tests/data/acpi/virt: Add IORT files for ITS hw/arm/virt: add ITS support in virt GIC tests/data/acpi/virt: Update IORT files for ITS docs/system/arm/nuvoton.rst | 1 + hw/intc/gicv3_internal.h | 188 ++++- include/hw/arm/virt.h | 2 + include/hw/intc/arm_gicv3_common.h | 13 + include/hw/intc/arm_gicv3_its_common.h | 32 +- include/hw/qdev-core.h | 24 + target/arm/cpu.h | 1 + target/arm/kvm_arm.h | 4 +- target/arm/syndrome.h | 5 + target/arm/translate.h | 2 + hw/arm/mps2-tz.c | 92 ++- hw/arm/mps2.c | 12 +- hw/arm/npcm7xx_boards.c | 34 + hw/arm/virt.c | 29 +- hw/char/cadence_uart.c | 61 +- hw/intc/arm_gicv3.c | 14 + hw/intc/arm_gicv3_common.c | 13 + hw/intc/arm_gicv3_cpuif.c | 7 +- hw/intc/arm_gicv3_dist.c | 5 +- hw/intc/arm_gicv3_its.c | 1322 ++++++++++++++++++++++++++++++++ hw/intc/arm_gicv3_its_common.c | 7 +- hw/intc/arm_gicv3_its_kvm.c | 2 +- hw/intc/arm_gicv3_redist.c | 153 +++- hw/misc/zynq_slcr.c | 31 +- softmmu/qdev-monitor.c | 7 +- target/arm/helper-a64.c | 1 + target/arm/helper.c | 8 + target/arm/kvm.c | 7 +- target/arm/translate-a64.c | 255 +++--- target/arm/translate.c | 21 + hw/intc/meson.build | 1 + tests/data/acpi/virt/IORT | Bin 0 -> 124 bytes tests/data/acpi/virt/IORT.memhp | Bin 0 -> 124 bytes tests/data/acpi/virt/IORT.numamem | Bin 0 -> 124 bytes tests/data/acpi/virt/IORT.pxb | Bin 0 -> 124 bytes 35 files changed, 2144 insertions(+), 210 deletions(-) create mode 100644 hw/intc/arm_gicv3_its.c create mode 100644 tests/data/acpi/virt/IORT create mode 100644 tests/data/acpi/virt/IORT.memhp create mode 100644 tests/data/acpi/virt/IORT.numamem create mode 100644 tests/data/acpi/virt/IORT.pxb