* [PATCH qemu 1/3] hw/block: Add Macronix MX25R6435F flash
2026-11-12 15:03 [PATCH qemu 0/3] hw/arm: Add STM32L496G-DISCO QuadSPI support ~mohammadodeh
@ 2026-09-27 7:08 ` ~mohammadodeh
2026-11-14 10:20 ` Samuel Tardieu
2026-09-27 7:09 ` [PATCH qemu 2/3] hw/ssi: Add STM32L4x5 QuadSPI controller ~mohammadodeh
` (2 subsequent siblings)
3 siblings, 1 reply; 8+ messages in thread
From: ~mohammadodeh @ 2026-09-27 7:08 UTC (permalink / raw)
To: qemu-devel
Cc: Alexandre Iooss, Pierrick Bouvier, Peter Maydell, Paolo Bonzini,
Samuel Tardieu, Alistair Francis, Kevin Wolf, Hanna Reitz,
Fabiano Rosas, Laurent Vivier, Philippe Mathieu-Daudé,
Markus Armbruster, Cédric Le Goater, Bernhard Beschow,
Gaurav Sharma, Jan Kiszka, Bin Meng, Shameer Kolothum, Jamin Lin,
Marc-André Lureau, qemu-arm, qemu-block
From: Mohammad Odeh <mohammad.odeh@exalt.ps>
Add the 64-Mbit Macronix MX25R6435F serial NOR flash model, its
SFDP data, and the 1-4-4 quad page-program command used by the
device.
Signed-off-by: Mohammad Odeh <zephyr@exalt.ps>
---
hw/block/m25p80.c | 5 +++++
hw/block/m25p80_sfdp.c | 20 ++++++++++++++++++++
hw/block/m25p80_sfdp.h | 1 +
3 files changed, 26 insertions(+)
diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c
index baf798c6b5..9b931f5861 100644
--- a/hw/block/m25p80.c
+++ b/hw/block/m25p80.c
@@ -243,6 +243,8 @@ static const FlashPartInfo known_devices[] = {
{ INFO6("mx25l25635f", 0xc22019, 0xc22019, 64 << 10, 512,
ER_4K | ER_32K), .sfdp_read = m25p80_sfdp_mx25l25635f },
{ INFO("mx25l25655e", 0xc22619, 0, 64 << 10, 512, 0) },
+ { INFO("mx25r6435", 0xc22817, 0, 64 << 10, 128, ER_4K | ER_32K),
+ .sfdp_read = m25p80_sfdp_mx25r6435 },
{ INFO("mx66l51235f", 0xc2201a, 0, 64 << 10, 1024, ER_4K | ER_32K) },
{ INFO("mx66u51235f", 0xc2253a, 0, 64 << 10, 1024, ER_4K | ER_32K) },
{ INFO("mx66u1g45g", 0xc2253b, 0, 64 << 10, 2048, ER_4K | ER_32K) },
@@ -405,6 +407,7 @@ typedef enum {
PP = 0x02,
PP4 = 0x12,
PP4_4 = 0x3e,
+ QPP_1_4_4 = 0x38,
DPP = 0xa2,
QPP = 0x32,
QPP_4 = 0x34,
@@ -759,6 +762,7 @@ static void complete_collecting_data(Flash *s)
switch (s->cmd_in_progress) {
case DPP:
+ case QPP_1_4_4:
case QPP:
case QPP_4:
case PP:
@@ -1255,6 +1259,7 @@ static void decode_new_cmd(Flash *s, uint32_t value)
"QIO mode\n", s->cmd_in_progress);
}
break;
+ case QPP_1_4_4:
case QPP:
case QPP_4:
case PP4_4:
diff --git a/hw/block/m25p80_sfdp.c b/hw/block/m25p80_sfdp.c
index 8ac603e212..29fc6aa5af 100644
--- a/hw/block/m25p80_sfdp.c
+++ b/hw/block/m25p80_sfdp.c
@@ -135,6 +135,26 @@ define_sfdp_read(mt35xu02g);
* Matronix
*/
+static const uint8_t sfdp_mx25r6435[] = {
+ 0x53, 0x46, 0x44, 0x50, 0x06, 0x01, 0x00, 0xff,
+ 0x00, 0x06, 0x01, 0x10, 0x10, 0x00, 0x00, 0xff,
+ 0xe5, 0x20, 0xf1, 0xff, 0xff, 0xff, 0xff, 0x03,
+ 0x44, 0xeb, 0x08, 0x6b, 0x08, 0x3b, 0x04, 0xbb,
+ 0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff,
+ 0xff, 0xff, 0x00, 0xff, 0x0c, 0x20, 0x0f, 0x52,
+ 0x10, 0xd8, 0x00, 0xff, 0x23, 0x72, 0xf5, 0x00,
+ 0x82, 0xed, 0x04, 0xcc, 0x44, 0x83, 0x68, 0x44,
+ 0x30, 0xb0, 0x30, 0xb0, 0xf7, 0xc4, 0xd5, 0x5c,
+ 0x00, 0xbe, 0x29, 0xff, 0xf0, 0xd0, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+};
+define_sfdp_read(mx25r6435)
+
/* mx25l25635e. No 4B opcodes */
static const uint8_t sfdp_mx25l25635e[] = {
0x53, 0x46, 0x44, 0x50, 0x00, 0x01, 0x01, 0xff,
diff --git a/hw/block/m25p80_sfdp.h b/hw/block/m25p80_sfdp.h
index 61544fb7cc..da3e7d74bc 100644
--- a/hw/block/m25p80_sfdp.h
+++ b/hw/block/m25p80_sfdp.h
@@ -19,6 +19,7 @@ uint8_t m25p80_sfdp_n25q256a(uint32_t addr);
uint8_t m25p80_sfdp_mt35xu01g(uint32_t addr);
uint8_t m25p80_sfdp_mt35xu02g(uint32_t addr);
+uint8_t m25p80_sfdp_mx25r6435(uint32_t addr);
uint8_t m25p80_sfdp_mx25l25635e(uint32_t addr);
uint8_t m25p80_sfdp_mx25l25635f(uint32_t addr);
uint8_t m25p80_sfdp_mx66l1g45g(uint32_t addr);
--
2.49.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH qemu 2/3] hw/ssi: Add STM32L4x5 QuadSPI controller
2026-11-12 15:03 [PATCH qemu 0/3] hw/arm: Add STM32L496G-DISCO QuadSPI support ~mohammadodeh
2026-09-27 7:08 ` [PATCH qemu 1/3] hw/block: Add Macronix MX25R6435F flash ~mohammadodeh
@ 2026-09-27 7:09 ` ~mohammadodeh
2026-09-27 7:32 ` [PATCH qemu 3/3] hw/arm: Add STM32L496G-DISCO machine ~mohammadodeh
2026-11-14 10:22 ` [PATCH qemu 0/3] hw/arm: Add STM32L496G-DISCO QuadSPI support Samuel Tardieu
3 siblings, 0 replies; 8+ messages in thread
From: ~mohammadodeh @ 2026-09-27 7:09 UTC (permalink / raw)
To: qemu-devel
Cc: Alexandre Iooss, Pierrick Bouvier, Peter Maydell, Paolo Bonzini,
Samuel Tardieu, Alistair Francis, Kevin Wolf, Hanna Reitz,
Fabiano Rosas, Laurent Vivier, Philippe Mathieu-Daudé,
Markus Armbruster, Cédric Le Goater, Bernhard Beschow,
Gaurav Sharma, Jan Kiszka, Bin Meng, Shameer Kolothum, Jamin Lin,
Marc-André Lureau, qemu-arm, qemu-block
From: Mohammad Odeh <mohammad.odeh@exalt.ps>
Model the STM32L4x5 QuadSPI controller indirect read and write modes.
Wire its clock, interrupt, and SSI bus into the STM32L4x5 SoC.
Automatic polling, memory-mapped mode, and DMA are not implemented.
Signed-off-by: Mohammad Odeh <zephyr@exalt.ps>
---
MAINTAINERS | 1 +
hw/arm/Kconfig | 1 +
hw/arm/stm32l4x5_soc.c | 17 +
hw/ssi/Kconfig | 4 +
hw/ssi/meson.build | 1 +
hw/ssi/stm32l4x5_quadspi.c | 680 +++++++++++++++++++++++++++++
include/hw/arm/stm32l4x5_soc.h | 2 +
include/hw/ssi/stm32l4x5_quadspi.h | 51 +++
8 files changed, 757 insertions(+)
create mode 100644 hw/ssi/stm32l4x5_quadspi.c
create mode 100644 include/hw/ssi/stm32l4x5_quadspi.h
diff --git a/MAINTAINERS b/MAINTAINERS
index fa0c6d0c9b..7a820703f8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1119,6 +1119,7 @@ F: hw/misc/stm32l4x5_exti.c
F: hw/misc/stm32l4x5_syscfg.c
F: hw/misc/stm32l4x5_rcc.c
F: hw/gpio/stm32l4x5_gpio.c
+F: hw/ssi/stm32l4x5_quadspi.c
F: include/hw/*/stm32l4x5_*.h
F: tests/qtest/stm32l4x5*
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index e9f796f718..94a01c412b 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -398,6 +398,7 @@ config STM32L4X5_SOC
select STM32L4X5_RCC
select STM32L4X5_GPIO
select STM32L4X5_USART
+ select STM32L4X5_QUADSPI
config XLNX_ZYNQMP_ARM
bool
diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index 39b8cdf97d..d91d18e88b 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -81,6 +81,9 @@ static const int exti_irq[NUM_EXTI_IRQ] = {
#define RCC_BASE_ADDRESS 0x40021000
#define RCC_IRQ 5
+#define QUADSPI_BASE_ADDRESS 0xa0001000
+#define QUADSPI_IRQ 71
+
#define EXTI_USART1_IRQ 26
#define EXTI_UART4_IRQ 29
#define EXTI_LPUART1_IRQ 31
@@ -144,6 +147,8 @@ 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);
+ object_initialize_child(obj, "quadspi", &s->quadspi,
+ TYPE_STM32L4X5_QUADSPI);
for (unsigned i = 0; i < NUM_GPIOS; i++) {
g_autofree char *name = g_strdup_printf("gpio%c", 'a' + i);
@@ -313,6 +318,18 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
sysbus_mmio_map(busdev, 0, RCC_BASE_ADDRESS);
sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(armv7m, RCC_IRQ));
+ /* QuadSPI controller */
+ dev = DEVICE(&s->quadspi);
+ qdev_connect_clock_in(dev, "clk",
+ qdev_get_clock_out(DEVICE(&s->rcc), "qspi-out"));
+ busdev = SYS_BUS_DEVICE(dev);
+ if (!sysbus_realize(busdev, errp)) {
+ return;
+ }
+ sysbus_mmio_map(busdev, 0, QUADSPI_BASE_ADDRESS);
+ sysbus_connect_irq(busdev, 0,
+ qdev_get_gpio_in(armv7m, QUADSPI_IRQ));
+
/* USART devices */
for (int i = 0; i < STM_NUM_USARTS; i++) {
g_autofree char *name = g_strdup_printf("usart%d-out", i + 1);
diff --git a/hw/ssi/Kconfig b/hw/ssi/Kconfig
index d477d20e9b..1b79da6d90 100644
--- a/hw/ssi/Kconfig
+++ b/hw/ssi/Kconfig
@@ -21,6 +21,10 @@ config STM32F2XX_SPI
bool
select SSI
+config STM32L4X5_QUADSPI
+ bool
+ select SSI
+
config BCM2835_SPI
bool
select SSI
diff --git a/hw/ssi/meson.build b/hw/ssi/meson.build
index f334e48e2f..ce9511b5df 100644
--- a/hw/ssi/meson.build
+++ b/hw/ssi/meson.build
@@ -6,6 +6,7 @@ system_ss.add(when: 'CONFIG_PL022', if_true: files('pl022.c'))
system_ss.add(when: 'CONFIG_SIFIVE_SPI', if_true: files('sifive_spi.c'))
system_ss.add(when: 'CONFIG_SSI', if_true: files('ssi.c'))
system_ss.add(when: 'CONFIG_STM32F2XX_SPI', if_true: files('stm32f2xx_spi.c'))
+system_ss.add(when: 'CONFIG_STM32L4X5_QUADSPI', if_true: files('stm32l4x5_quadspi.c'))
system_ss.add(when: 'CONFIG_XILINX_SPI', if_true: files('xilinx_spi.c'))
system_ss.add(when: 'CONFIG_XILINX_SPIPS', if_true: files('xilinx_spips.c'))
system_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal-ospi.c'))
diff --git a/hw/ssi/stm32l4x5_quadspi.c b/hw/ssi/stm32l4x5_quadspi.c
new file mode 100644
index 0000000000..cb3e7eb665
--- /dev/null
+++ b/hw/ssi/stm32l4x5_quadspi.c
@@ -0,0 +1,680 @@
+/*
+ * STM32L4x5 QuadSPI controller
+ *
+ * Copyright (c) 2026 EXALT Technologies
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * The reference used is the STMicroelectronics RM0351 reference manual for
+ * STM32L47xxx, STM32L48xxx, STM32L49xxx and STM32L4Axxx MCUs.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "hw/core/qdev-clock.h"
+#include "hw/core/irq.h"
+#include "hw/ssi/stm32l4x5_quadspi.h"
+#include "migration/vmstate.h"
+
+#define QUADSPI_MMIO_SIZE 0x400
+#define QUADSPI_FIFO_SIZE 16
+
+#define QUADSPI_CR 0x00
+#define QUADSPI_DCR 0x04
+#define QUADSPI_SR 0x08
+#define QUADSPI_FCR 0x0c
+#define QUADSPI_DLR 0x10
+#define QUADSPI_CCR 0x14
+#define QUADSPI_AR 0x18
+#define QUADSPI_ABR 0x1c
+#define QUADSPI_DR 0x20
+#define QUADSPI_PSMKR 0x24
+#define QUADSPI_PSMAR 0x28
+#define QUADSPI_PIR 0x2c
+#define QUADSPI_LPTR 0x30
+
+#define CR_EN BIT(0)
+#define CR_ABORT BIT(1)
+#define CR_DFM BIT(6)
+#define CR_FTHRES_SHIFT 8
+#define CR_FTHRES MAKE_64BIT_MASK(8, 4)
+#define CR_TEIE BIT(16)
+#define CR_TCIE BIT(17)
+#define CR_FTIE BIT(18)
+#define CR_SMIE BIT(19)
+#define CR_TOIE BIT(20)
+
+#define SR_TEF BIT(0)
+#define SR_TCF BIT(1)
+#define SR_FTF BIT(2)
+#define SR_SMF BIT(3)
+#define SR_TOF BIT(4)
+#define SR_BUSY BIT(5)
+#define SR_FLEVEL MAKE_64BIT_MASK(8, 5)
+#define SR_FLEVEL_SHIFT 8
+
+#define CCR_INSTRUCTION MAKE_64BIT_MASK(0, 8)
+#define CCR_IMODE MAKE_64BIT_MASK(8, 2)
+#define CCR_ADMODE MAKE_64BIT_MASK(10, 2)
+#define CCR_ADSIZE MAKE_64BIT_MASK(12, 2)
+#define CCR_ABMODE MAKE_64BIT_MASK(14, 2)
+#define CCR_ABSIZE MAKE_64BIT_MASK(16, 2)
+#define CCR_DCYC MAKE_64BIT_MASK(18, 5)
+#define CCR_DMODE MAKE_64BIT_MASK(24, 2)
+#define CCR_FMODE MAKE_64BIT_MASK(26, 2)
+#define CCR_SIOO BIT(28)
+#define CCR_DDRM BIT(31)
+
+#define CCR_IMODE_SHIFT 8
+#define CCR_ADMODE_SHIFT 10
+#define CCR_ADSIZE_SHIFT 12
+#define CCR_ABMODE_SHIFT 14
+#define CCR_ABSIZE_SHIFT 16
+#define CCR_DCYC_SHIFT 18
+#define CCR_DMODE_SHIFT 24
+#define CCR_FMODE_SHIFT 26
+
+enum QuadSpiFunctionalMode {
+ QUADSPI_INDIRECT_WRITE,
+ QUADSPI_INDIRECT_READ,
+ QUADSPI_AUTO_POLLING,
+ QUADSPI_MEMORY_MAPPED,
+};
+
+static unsigned int stm32l4x5_quadspi_mode_width(uint32_t mode)
+{
+ static const unsigned int widths[] = { 0, 1, 2, 4 };
+
+ return widths[mode & 3];
+}
+
+static unsigned int stm32l4x5_quadspi_fmode(Stm32l4x5QuadSpiState *s)
+{
+ return (s->ccr & CCR_FMODE) >> CCR_FMODE_SHIFT;
+}
+
+static void stm32l4x5_quadspi_update_irq(Stm32l4x5QuadSpiState *s)
+{
+ bool level = ((s->sr & SR_TEF) && (s->cr & CR_TEIE)) ||
+ ((s->sr & SR_TCF) && (s->cr & CR_TCIE)) ||
+ ((s->sr & SR_FTF) && (s->cr & CR_FTIE)) ||
+ ((s->sr & SR_SMF) && (s->cr & CR_SMIE)) ||
+ ((s->sr & SR_TOF) && (s->cr & CR_TOIE));
+
+ qemu_set_irq(s->irq, level);
+}
+
+static void stm32l4x5_quadspi_update_fifo(Stm32l4x5QuadSpiState *s)
+{
+ unsigned int level = fifo8_num_used(&s->fifo);
+ unsigned int threshold = ((s->cr & CR_FTHRES) >> CR_FTHRES_SHIFT) + 1;
+ unsigned int mode = stm32l4x5_quadspi_fmode(s);
+
+ s->sr &= ~(SR_FLEVEL | SR_FTF | SR_BUSY);
+ s->sr |= level << SR_FLEVEL_SHIFT;
+ if (s->transaction_active ||
+ (mode == QUADSPI_INDIRECT_READ && level)) {
+ s->sr |= SR_BUSY;
+ }
+ if (s->cr & CR_EN) {
+ if (mode == QUADSPI_INDIRECT_READ &&
+ (level >= threshold ||
+ (!s->remaining && !s->indefinite && level))) {
+ s->sr |= SR_FTF;
+ } else if (mode == QUADSPI_INDIRECT_WRITE &&
+ fifo8_num_free(&s->fifo) >= threshold) {
+ s->sr |= SR_FTF;
+ }
+ }
+ stm32l4x5_quadspi_update_irq(s);
+}
+
+static void stm32l4x5_quadspi_select(Stm32l4x5QuadSpiState *s)
+{
+ if (!s->transaction_active) {
+ qemu_set_irq(s->cs, 0);
+ s->transaction_active = true;
+ s->sr |= SR_BUSY;
+ }
+}
+
+static void stm32l4x5_quadspi_deselect(Stm32l4x5QuadSpiState *s)
+{
+ if (s->transaction_active) {
+ qemu_set_irq(s->cs, 1);
+ s->transaction_active = false;
+ }
+ s->remaining = 0;
+ s->indefinite = false;
+ s->write_pending = false;
+}
+
+static void stm32l4x5_quadspi_transfer_value(Stm32l4x5QuadSpiState *s,
+ uint32_t value,
+ unsigned int count)
+{
+ for (int i = count - 1; i >= 0; i--) {
+ ssi_transfer(s->ssi, extract32(value, i * 8, 8));
+ }
+}
+
+static void stm32l4x5_quadspi_send_prefix(Stm32l4x5QuadSpiState *s)
+{
+ unsigned int imode = (s->ccr & CCR_IMODE) >> CCR_IMODE_SHIFT;
+ unsigned int admode = (s->ccr & CCR_ADMODE) >> CCR_ADMODE_SHIFT;
+ unsigned int abmode = (s->ccr & CCR_ABMODE) >> CCR_ABMODE_SHIFT;
+ unsigned int dmode = (s->ccr & CCR_DMODE) >> CCR_DMODE_SHIFT;
+ unsigned int dummy_cycles = (s->ccr & CCR_DCYC) >> CCR_DCYC_SHIFT;
+ unsigned int dummy_width;
+ unsigned int dummy_bytes;
+
+ stm32l4x5_quadspi_select(s);
+
+ if (imode) {
+ ssi_transfer(s->ssi, s->ccr & CCR_INSTRUCTION);
+ }
+ if (admode) {
+ unsigned int address_bytes =
+ ((s->ccr & CCR_ADSIZE) >> CCR_ADSIZE_SHIFT) + 1;
+
+ stm32l4x5_quadspi_transfer_value(s, s->ar, address_bytes);
+ }
+ if (abmode) {
+ unsigned int alternate_bytes =
+ ((s->ccr & CCR_ABSIZE) >> CCR_ABSIZE_SHIFT) + 1;
+
+ stm32l4x5_quadspi_transfer_value(s, s->abr, alternate_bytes);
+ }
+
+ dummy_width = stm32l4x5_quadspi_mode_width(admode ? admode : dmode);
+ dummy_bytes = DIV_ROUND_UP(dummy_cycles * dummy_width, 8);
+ for (unsigned int i = 0; i < dummy_bytes; i++) {
+ ssi_transfer(s->ssi, 0);
+ }
+}
+
+static void stm32l4x5_quadspi_complete(Stm32l4x5QuadSpiState *s)
+{
+ stm32l4x5_quadspi_deselect(s);
+ s->rx_stalled = false;
+ if (stm32l4x5_quadspi_fmode(s) == QUADSPI_INDIRECT_WRITE) {
+ fifo8_reset(&s->fifo);
+ }
+ s->sr |= SR_TCF;
+ stm32l4x5_quadspi_update_fifo(s);
+}
+
+static bool stm32l4x5_quadspi_prepare_data(Stm32l4x5QuadSpiState *s)
+{
+ unsigned int fsize = extract32(s->dcr, 16, 5);
+ uint64_t flash_size = 1ULL << (fsize + 1);
+ uint64_t address = (s->ccr & CCR_ADMODE) ? s->ar : 0;
+ bool has_data = s->ccr & CCR_DMODE;
+
+ fifo8_reset(&s->fifo);
+ s->rx_stalled = false;
+ s->write_pending = false;
+ s->indefinite = has_data && s->dlr == UINT32_MAX && fsize == 31;
+ s->remaining = has_data ? (uint64_t)s->dlr + 1 : 0;
+ s->sr &= ~SR_TCF;
+ if ((s->ccr & CCR_ADMODE) &&
+ (address >= flash_size ||
+ (has_data && s->dlr != UINT32_MAX &&
+ s->remaining > flash_size - address))) {
+ s->remaining = 0;
+ s->indefinite = false;
+ s->sr |= SR_TEF;
+ stm32l4x5_quadspi_update_fifo(s);
+ return false;
+ }
+ if (has_data && s->dlr == UINT32_MAX) {
+ s->remaining = s->indefinite ? 0 : flash_size - address;
+ }
+ stm32l4x5_quadspi_update_fifo(s);
+ return true;
+}
+
+static bool stm32l4x5_quadspi_indirect(Stm32l4x5QuadSpiState *s)
+{
+ if (stm32l4x5_quadspi_fmode(s) >= QUADSPI_AUTO_POLLING ||
+ (s->ccr & (CCR_SIOO | CCR_DDRM)) || (s->cr & CR_DFM)) {
+ qemu_log_mask(LOG_UNIMP,
+ "%s: polling, mapped, SIOO, DDR, or dual flash mode\n",
+ TYPE_STM32L4X5_QUADSPI);
+ return false;
+ }
+ return true;
+}
+
+static void stm32l4x5_quadspi_fill_fifo(Stm32l4x5QuadSpiState *s)
+{
+ if (!(s->cr & CR_EN) || !s->transaction_active) {
+ return;
+ }
+ /* After a full FIFO, reception resumes when FLEVEL is at most 11. */
+ if (s->rx_stalled && fifo8_num_used(&s->fifo) > 11) {
+ return;
+ }
+ s->rx_stalled = false;
+ while ((s->remaining || s->indefinite) && !fifo8_is_full(&s->fifo)) {
+ fifo8_push(&s->fifo, ssi_transfer(s->ssi, 0));
+ if (!s->indefinite) {
+ s->remaining--;
+ }
+ }
+ if (!s->remaining && !s->indefinite) {
+ stm32l4x5_quadspi_complete(s);
+ } else {
+ s->rx_stalled = true;
+ stm32l4x5_quadspi_update_fifo(s);
+ }
+}
+
+static void stm32l4x5_quadspi_start_read(Stm32l4x5QuadSpiState *s)
+{
+ if (!stm32l4x5_quadspi_prepare_data(s)) {
+ return;
+ }
+ stm32l4x5_quadspi_send_prefix(s);
+ stm32l4x5_quadspi_fill_fifo(s);
+}
+
+static void stm32l4x5_quadspi_execute_command(Stm32l4x5QuadSpiState *s)
+{
+ if (!stm32l4x5_quadspi_prepare_data(s)) {
+ return;
+ }
+ stm32l4x5_quadspi_send_prefix(s);
+ stm32l4x5_quadspi_complete(s);
+}
+
+static uint64_t stm32l4x5_quadspi_read_register(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ Stm32l4x5QuadSpiState *s = opaque;
+ uint32_t value = 0;
+
+ switch (addr) {
+ case QUADSPI_CR:
+ return s->cr;
+ case QUADSPI_DCR:
+ return s->dcr;
+ case QUADSPI_SR:
+ return s->sr;
+ case QUADSPI_FCR:
+ return 0;
+ case QUADSPI_DLR:
+ return s->dlr;
+ case QUADSPI_CCR:
+ return s->ccr;
+ case QUADSPI_AR:
+ return s->ar;
+ case QUADSPI_ABR:
+ return s->abr;
+ case QUADSPI_DR:
+ if (!(s->cr & CR_EN) ||
+ stm32l4x5_quadspi_fmode(s) != QUADSPI_INDIRECT_READ) {
+ return 0;
+ }
+ for (unsigned int i = 0; i < size && !fifo8_is_empty(&s->fifo); i++) {
+ value |= (uint32_t)fifo8_pop(&s->fifo) << (i * 8);
+ }
+ s->dr = value;
+ stm32l4x5_quadspi_fill_fifo(s);
+ stm32l4x5_quadspi_update_fifo(s);
+ return value;
+ case QUADSPI_PSMKR:
+ return s->psmkr;
+ case QUADSPI_PSMAR:
+ return s->psmar;
+ case QUADSPI_PIR:
+ return s->pir;
+ case QUADSPI_LPTR:
+ return s->lptr;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: bad read offset 0x%" HWADDR_PRIx "\n",
+ TYPE_STM32L4X5_QUADSPI, addr);
+ return 0;
+ }
+}
+
+static void stm32l4x5_quadspi_write_data(Stm32l4x5QuadSpiState *s,
+ uint32_t value,
+ unsigned int size)
+{
+ if (!(s->cr & CR_EN) ||
+ stm32l4x5_quadspi_fmode(s) != QUADSPI_INDIRECT_WRITE ||
+ (!s->write_pending && !s->remaining && !s->indefinite)) {
+ return;
+ }
+ if (s->write_pending) {
+ if (!stm32l4x5_quadspi_indirect(s) ||
+ !stm32l4x5_quadspi_prepare_data(s)) {
+ s->write_pending = false;
+ return;
+ }
+ }
+ if (fifo8_num_free(&s->fifo) < size) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: write to full FIFO\n",
+ TYPE_STM32L4X5_QUADSPI);
+ return;
+ }
+ for (unsigned int i = 0; i < size; i++) {
+ fifo8_push(&s->fifo, extract32(value, i * 8, 8));
+ }
+ s->dr = value;
+ if (!s->transaction_active) {
+ stm32l4x5_quadspi_send_prefix(s);
+ }
+
+ while ((s->remaining || s->indefinite) && !fifo8_is_empty(&s->fifo)) {
+ ssi_transfer(s->ssi, fifo8_pop(&s->fifo));
+ if (!s->indefinite) {
+ s->remaining--;
+ }
+ }
+
+ if (!s->remaining && !s->indefinite) {
+ stm32l4x5_quadspi_complete(s);
+ } else {
+ stm32l4x5_quadspi_update_fifo(s);
+ }
+}
+
+static void stm32l4x5_quadspi_abort(Stm32l4x5QuadSpiState *s)
+{
+ stm32l4x5_quadspi_deselect(s);
+ fifo8_reset(&s->fifo);
+ s->rx_stalled = false;
+ s->cr &= ~CR_ABORT;
+ s->sr |= SR_TCF;
+ stm32l4x5_quadspi_update_fifo(s);
+}
+
+static void stm32l4x5_quadspi_write_register(void *opaque, hwaddr addr,
+ uint64_t value, unsigned int size)
+{
+ Stm32l4x5QuadSpiState *s = opaque;
+
+ switch (addr) {
+ case QUADSPI_CR:
+ if (s->sr & SR_BUSY) {
+ uint32_t mutable = CR_EN | CR_ABORT | CR_TEIE | CR_TCIE |
+ CR_FTIE | CR_SMIE | CR_TOIE;
+ value = (s->cr & ~mutable) | (value & mutable);
+ }
+ s->cr = value;
+ if (s->cr & CR_ABORT) {
+ stm32l4x5_quadspi_abort(s);
+ } else {
+ if (!(s->cr & CR_EN)) {
+ /* Disabling is not a reset or a transfer-complete event. */
+ stm32l4x5_quadspi_deselect(s);
+ fifo8_reset(&s->fifo);
+ s->rx_stalled = false;
+ }
+ stm32l4x5_quadspi_update_fifo(s);
+ }
+ return;
+ case QUADSPI_DCR:
+ s->dcr = value;
+ return;
+ case QUADSPI_SR:
+ return;
+ case QUADSPI_FCR:
+ s->sr &= ~(value & (SR_TEF | SR_TCF | SR_SMF | SR_TOF));
+ stm32l4x5_quadspi_update_irq(s);
+ return;
+ case QUADSPI_DLR:
+ s->dlr = value;
+ return;
+ case QUADSPI_CCR:
+ s->ccr = value;
+ s->write_pending = false;
+ if (!(s->cr & CR_EN) || !stm32l4x5_quadspi_indirect(s)) {
+ return;
+ }
+ if (((s->ccr & CCR_DMODE) == 0) &&
+ ((s->ccr & CCR_ADMODE) == 0) &&
+ ((s->ccr & CCR_IMODE) != 0)) {
+ stm32l4x5_quadspi_execute_command(s);
+ } else if ((s->ccr & CCR_DMODE) &&
+ stm32l4x5_quadspi_fmode(s) == QUADSPI_INDIRECT_WRITE) {
+ s->write_pending = true;
+ stm32l4x5_quadspi_update_fifo(s);
+ } else if ((s->ccr & CCR_DMODE) &&
+ !(s->ccr & CCR_ADMODE) &&
+ stm32l4x5_quadspi_fmode(s) == QUADSPI_INDIRECT_READ) {
+ stm32l4x5_quadspi_start_read(s);
+ }
+ return;
+ case QUADSPI_AR:
+ s->ar = value;
+ if (!(s->cr & CR_EN) || !stm32l4x5_quadspi_indirect(s)) {
+ return;
+ }
+ if ((s->ccr & CCR_DMODE) && (s->ccr & CCR_ADMODE) &&
+ stm32l4x5_quadspi_fmode(s) == QUADSPI_INDIRECT_READ) {
+ stm32l4x5_quadspi_start_read(s);
+ } else if (((s->ccr & CCR_DMODE) == 0) &&
+ ((s->ccr & CCR_ADMODE) != 0)) {
+ stm32l4x5_quadspi_execute_command(s);
+ }
+ return;
+ case QUADSPI_ABR:
+ s->abr = value;
+ return;
+ case QUADSPI_DR:
+ stm32l4x5_quadspi_write_data(s, value, size);
+ return;
+ case QUADSPI_PSMKR:
+ s->psmkr = value;
+ return;
+ case QUADSPI_PSMAR:
+ s->psmar = value;
+ return;
+ case QUADSPI_PIR:
+ s->pir = value;
+ return;
+ case QUADSPI_LPTR:
+ s->lptr = value;
+ return;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: bad write offset 0x%" HWADDR_PRIx "\n",
+ TYPE_STM32L4X5_QUADSPI, addr);
+ }
+}
+
+static uint64_t stm32l4x5_quadspi_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ uint64_t value;
+
+ if ((addr & ~3ULL) == QUADSPI_DR) {
+ /* DR access size determines the number of FIFO bytes consumed. */
+ return stm32l4x5_quadspi_read_register(opaque, addr, size);
+ }
+ value = stm32l4x5_quadspi_read_register(opaque, addr & ~3ULL, 4);
+ return extract32(value, (addr & 3) * 8, size * 8);
+}
+
+static void stm32l4x5_quadspi_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned int size)
+{
+ Stm32l4x5QuadSpiState *s = opaque;
+ unsigned int shift = (addr & 3) * 8;
+ hwaddr reg = addr & ~3ULL;
+
+ if ((s->sr & SR_BUSY) && reg != QUADSPI_CR &&
+ reg != QUADSPI_FCR && reg != QUADSPI_SR && reg != QUADSPI_DR) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: configuration write while busy\n",
+ TYPE_STM32L4X5_QUADSPI);
+ return;
+ }
+ if (reg == QUADSPI_FCR) {
+ value <<= shift;
+ } else if (reg != QUADSPI_DR && size != 4) {
+ uint32_t mask = MAKE_64BIT_MASK(shift, size * 8);
+ uint32_t old = stm32l4x5_quadspi_read_register(s, reg, 4);
+
+ value = (old & ~mask) | ((value << shift) & mask);
+ }
+ stm32l4x5_quadspi_write_register(s, reg, value, size);
+}
+
+static bool stm32l4x5_quadspi_accepts(void *opaque, hwaddr addr,
+ unsigned int size, bool is_write,
+ MemTxAttrs attrs)
+{
+ return (size == 1 || size == 2 || size == 4) &&
+ ((addr & ~3ULL) != QUADSPI_DR || addr == QUADSPI_DR);
+}
+
+static const MemoryRegionOps stm32l4x5_quadspi_ops = {
+ .read = stm32l4x5_quadspi_read,
+ .write = stm32l4x5_quadspi_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 4,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .valid.unaligned = false,
+ .valid.accepts = stm32l4x5_quadspi_accepts,
+};
+
+static void stm32l4x5_quadspi_hold_reset(Object *obj, ResetType type)
+{
+ Stm32l4x5QuadSpiState *s = STM32L4X5_QUADSPI(obj);
+
+ s->cr = 0;
+ s->dcr = 0;
+ s->sr = 0;
+ s->dlr = 0;
+ s->ccr = 0;
+ s->ar = 0;
+ s->abr = 0;
+ s->dr = 0;
+ s->psmkr = 0;
+ s->psmar = 0;
+ s->pir = 0;
+ s->lptr = 0;
+ s->remaining = 0;
+ s->transaction_active = false;
+ s->rx_stalled = false;
+ s->indefinite = false;
+ s->write_pending = false;
+ fifo8_reset(&s->fifo);
+ qemu_set_irq(s->cs, 1);
+ qemu_irq_lower(s->irq);
+}
+
+static void stm32l4x5_quadspi_init(Object *obj)
+{
+ Stm32l4x5QuadSpiState *s = STM32L4X5_QUADSPI(obj);
+ DeviceState *dev = DEVICE(obj);
+
+ fifo8_create(&s->fifo, QUADSPI_FIFO_SIZE);
+ memory_region_init_io(&s->mmio, obj, &stm32l4x5_quadspi_ops, s,
+ TYPE_STM32L4X5_QUADSPI, QUADSPI_MMIO_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+ qdev_init_gpio_out_named(dev, &s->cs, "cs", 1);
+ s->ssi = ssi_create_bus(dev, "ssi");
+ s->clk = qdev_init_clock_in(dev, "clk", NULL, s, 0);
+}
+
+static void stm32l4x5_quadspi_finalize(Object *obj)
+{
+ Stm32l4x5QuadSpiState *s = STM32L4X5_QUADSPI(obj);
+
+ fifo8_destroy(&s->fifo);
+}
+
+static void stm32l4x5_quadspi_realize(DeviceState *dev, Error **errp)
+{
+ Stm32l4x5QuadSpiState *s = STM32L4X5_QUADSPI(dev);
+
+ if (!clock_has_source(s->clk)) {
+ error_setg(errp, "%s: clk input must be connected",
+ TYPE_STM32L4X5_QUADSPI);
+ }
+}
+
+static int stm32l4x5_quadspi_post_load(void *opaque, int version_id)
+{
+ Stm32l4x5QuadSpiState *s = opaque;
+
+ if (s->fifo.head >= QUADSPI_FIFO_SIZE ||
+ fifo8_num_used(&s->fifo) > QUADSPI_FIFO_SIZE) {
+ return -EINVAL;
+ }
+ if ((s->transaction_active && !(s->cr & CR_EN)) ||
+ (s->indefinite && s->remaining)) {
+ return -EINVAL;
+ }
+ qemu_set_irq(s->cs, !s->transaction_active);
+ stm32l4x5_quadspi_update_fifo(s);
+ return 0;
+}
+
+static const VMStateDescription vmstate_stm32l4x5_quadspi = {
+ .name = TYPE_STM32L4X5_QUADSPI,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = stm32l4x5_quadspi_post_load,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(cr, Stm32l4x5QuadSpiState),
+ VMSTATE_UINT32(dcr, Stm32l4x5QuadSpiState),
+ VMSTATE_UINT32(sr, Stm32l4x5QuadSpiState),
+ VMSTATE_UINT32(dlr, Stm32l4x5QuadSpiState),
+ VMSTATE_UINT32(ccr, Stm32l4x5QuadSpiState),
+ VMSTATE_UINT32(ar, Stm32l4x5QuadSpiState),
+ VMSTATE_UINT32(abr, Stm32l4x5QuadSpiState),
+ VMSTATE_UINT32(dr, Stm32l4x5QuadSpiState),
+ VMSTATE_UINT32(psmkr, Stm32l4x5QuadSpiState),
+ VMSTATE_UINT32(psmar, Stm32l4x5QuadSpiState),
+ VMSTATE_UINT32(pir, Stm32l4x5QuadSpiState),
+ VMSTATE_UINT32(lptr, Stm32l4x5QuadSpiState),
+ VMSTATE_UINT64(remaining, Stm32l4x5QuadSpiState),
+ VMSTATE_BOOL(transaction_active, Stm32l4x5QuadSpiState),
+ VMSTATE_FIFO8(fifo, Stm32l4x5QuadSpiState),
+ VMSTATE_BOOL(rx_stalled, Stm32l4x5QuadSpiState),
+ VMSTATE_BOOL(indefinite, Stm32l4x5QuadSpiState),
+ VMSTATE_BOOL(write_pending, Stm32l4x5QuadSpiState),
+ VMSTATE_CLOCK(clk, Stm32l4x5QuadSpiState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void stm32l4x5_quadspi_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->realize = stm32l4x5_quadspi_realize;
+ dc->vmsd = &vmstate_stm32l4x5_quadspi;
+ rc->phases.hold = stm32l4x5_quadspi_hold_reset;
+}
+
+static const TypeInfo stm32l4x5_quadspi_info = {
+ .name = TYPE_STM32L4X5_QUADSPI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Stm32l4x5QuadSpiState),
+ .instance_init = stm32l4x5_quadspi_init,
+ .instance_finalize = stm32l4x5_quadspi_finalize,
+ .class_init = stm32l4x5_quadspi_class_init,
+};
+
+static void stm32l4x5_quadspi_register_types(void)
+{
+ type_register_static(&stm32l4x5_quadspi_info);
+}
+
+type_init(stm32l4x5_quadspi_register_types)
diff --git a/include/hw/arm/stm32l4x5_soc.h b/include/hw/arm/stm32l4x5_soc.h
index 9c3d662bca..5b5e817ee7 100644
--- a/include/hw/arm/stm32l4x5_soc.h
+++ b/include/hw/arm/stm32l4x5_soc.h
@@ -32,6 +32,7 @@
#include "hw/misc/stm32l4x5_rcc.h"
#include "hw/gpio/stm32l4x5_gpio.h"
#include "hw/char/stm32l4x5_usart.h"
+#include "hw/ssi/stm32l4x5_quadspi.h"
#include "qom/object.h"
#define TYPE_STM32L4X5_SOC "stm32l4x5-soc"
@@ -56,6 +57,7 @@ struct Stm32l4x5SocState {
Stm32l4x5RccState rcc;
Stm32l4x5GpioState gpio[NUM_GPIOS];
Stm32l4x5UsartBaseState usart[STM_NUM_USARTS];
+ Stm32l4x5QuadSpiState quadspi;
Stm32l4x5UsartBaseState uart[STM_NUM_UARTS];
Stm32l4x5UsartBaseState lpuart;
diff --git a/include/hw/ssi/stm32l4x5_quadspi.h b/include/hw/ssi/stm32l4x5_quadspi.h
new file mode 100644
index 0000000000..e9882dd996
--- /dev/null
+++ b/include/hw/ssi/stm32l4x5_quadspi.h
@@ -0,0 +1,51 @@
+/*
+ * STM32L4x5 QuadSPI controller
+ *
+ * Copyright (c) 2026 EXALT Technologies
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_STM32L4X5_QUADSPI_H
+#define HW_STM32L4X5_QUADSPI_H
+
+#include "hw/core/clock.h"
+#include "hw/core/sysbus.h"
+#include "hw/ssi/ssi.h"
+#include "qemu/fifo8.h"
+#include "qom/object.h"
+
+#define TYPE_STM32L4X5_QUADSPI "stm32l4x5-quadspi"
+OBJECT_DECLARE_SIMPLE_TYPE(Stm32l4x5QuadSpiState, STM32L4X5_QUADSPI)
+
+struct Stm32l4x5QuadSpiState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ Clock *clk;
+ qemu_irq irq;
+ qemu_irq cs;
+ SSIBus *ssi;
+
+ uint32_t cr;
+ uint32_t dcr;
+ uint32_t sr;
+ uint32_t dlr;
+ uint32_t ccr;
+ uint32_t ar;
+ uint32_t abr;
+ uint32_t dr;
+ uint32_t psmkr;
+ uint32_t psmar;
+ uint32_t pir;
+ uint32_t lptr;
+
+ Fifo8 fifo;
+ uint64_t remaining;
+ bool transaction_active;
+ bool rx_stalled;
+ bool indefinite;
+ bool write_pending;
+};
+
+#endif /* HW_STM32L4X5_QUADSPI_H */
--
2.49.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH qemu 3/3] hw/arm: Add STM32L496G-DISCO machine
2026-11-12 15:03 [PATCH qemu 0/3] hw/arm: Add STM32L496G-DISCO QuadSPI support ~mohammadodeh
2026-09-27 7:08 ` [PATCH qemu 1/3] hw/block: Add Macronix MX25R6435F flash ~mohammadodeh
2026-09-27 7:09 ` [PATCH qemu 2/3] hw/ssi: Add STM32L4x5 QuadSPI controller ~mohammadodeh
@ 2026-09-27 7:32 ` ~mohammadodeh
2026-11-14 10:40 ` Samuel Tardieu
2026-11-14 10:49 ` Peter Maydell
2026-11-14 10:22 ` [PATCH qemu 0/3] hw/arm: Add STM32L496G-DISCO QuadSPI support Samuel Tardieu
3 siblings, 2 replies; 8+ messages in thread
From: ~mohammadodeh @ 2026-09-27 7:32 UTC (permalink / raw)
To: qemu-devel
Cc: Alexandre Iooss, Pierrick Bouvier, Peter Maydell, Paolo Bonzini,
Samuel Tardieu, Alistair Francis, Kevin Wolf, Hanna Reitz,
Fabiano Rosas, Laurent Vivier, Philippe Mathieu-Daudé,
Markus Armbruster, Cédric Le Goater, Bernhard Beschow,
Gaurav Sharma, Jan Kiszka, Bin Meng, Shameer Kolothum, Jamin Lin,
Marc-André Lureau, qemu-arm, qemu-block
From: Mohammad Odeh <mohammad.odeh@exalt.ps>
Add a machine model for the STM32L496G-DISCO Discovery kit.
Introduce the STM32L496xG SoC variant with its flash and SRAM sizes.
Attach the on-board MX25R6435F flash to the QuadSPI controller and add
qtests for identification, SFDP access, and data transfer.
Signed-off-by: Mohammad Odeh <zephyr@exalt.ps>
---
MAINTAINERS | 8 +
docs/system/arm/stm32.rst | 1 +
docs/system/arm/stm32l496g-disco.rst | 43 ++
docs/system/target-arm.rst | 1 +
hw/arm/Kconfig | 7 +
hw/arm/meson.build | 1 +
hw/arm/stm32l496g-disco.c | 95 ++++
hw/arm/stm32l4x5_soc.c | 25 +-
include/hw/arm/stm32l4x5_soc.h | 3 +
tests/qtest/meson.build | 1 +
tests/qtest/stm32l496g-disco-test.c | 693 +++++++++++++++++++++++++++
11 files changed, 874 insertions(+), 4 deletions(-)
create mode 100644 docs/system/arm/stm32l496g-disco.rst
create mode 100644 hw/arm/stm32l496g-disco.c
create mode 100644 tests/qtest/stm32l496g-disco-test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 7a820703f8..cb1cad7f5c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1123,6 +1123,14 @@ F: hw/ssi/stm32l4x5_quadspi.c
F: include/hw/*/stm32l4x5_*.h
F: tests/qtest/stm32l4x5*
+STM32L496G-DISCO
+M: Mohammad Odeh <zephyr@exalt.ps>
+L: qemu-arm@nongnu.org
+S: Maintained
+F: hw/arm/stm32l496g-disco.c
+F: docs/system/arm/stm32l496g-disco.rst
+F: tests/qtest/stm32l496g-disco-test.c
+
STM32VLDISCOVERY
M: Alexandre Iooss <erdnaxe@crans.org>
L: qemu-arm@nongnu.org
diff --git a/docs/system/arm/stm32.rst b/docs/system/arm/stm32.rst
index 23b03f9029..70746f8e79 100644
--- a/docs/system/arm/stm32.rst
+++ b/docs/system/arm/stm32.rst
@@ -23,6 +23,7 @@ The following machines are based on this Arm Cortex-M4F chip :
- ``netduinoplus2`` Netduino Plus 2 board with STM32F405RGT6 microcontroller
- ``olimex-stm32-h405`` Olimex STM32 H405 board with STM32F405RGT6 microcontroller
- ``b-l475e-iot01a`` :doc:`B-L475E-IOT01A IoT Node </system/arm/b-l475e-iot01a>` board with STM32L475VG microcontroller
+- ``stm32l496g-disco`` :doc:`STM32L496G-DISCO Discovery Kit </system/arm/stm32l496g-disco>` board with STM32L496AG microcontroller
There are many other STM32 series that are currently not supported by QEMU.
diff --git a/docs/system/arm/stm32l496g-disco.rst b/docs/system/arm/stm32l496g-disco.rst
new file mode 100644
index 0000000000..732f98b202
--- /dev/null
+++ b/docs/system/arm/stm32l496g-disco.rst
@@ -0,0 +1,43 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+STM32L496G-DISCO Discovery Kit (``stm32l496g-disco``)
+=====================================================
+
+The STM32L496G-DISCO Discovery Kit uses an STM32L496AG microcontroller with
+an Arm Cortex-M4F core, 1 MiB of internal flash, 256 KiB of SRAM1, and 64 KiB
+of SRAM2.
+
+Supported devices
+-----------------
+
+The machine initially supports the devices shared with the STM32L4x5 model:
+
+- Cortex-M4F processor
+- Internal flash and SRAM
+- Reset and clock control
+- EXTI and system configuration controllers
+- GPIO ports A through H
+- USART1, USART2, USART3, UART4, UART5, and LPUART1
+- QuadSPI controller in indirect mode
+- MX25R6435F QuadSPI flash
+
+Missing devices
+---------------
+
+The following board features are not currently modeled:
+
+- GPIO port I
+- Timers, RTC, watchdogs, ADC, and DAC
+- I2C, SPI, SDMMC, USB, and FMC controllers
+- On-board display, joystick, and sensors
+
+Boot options
+------------
+
+USART2 is the console used by the Zephyr board configuration. It is QEMU's
+second serial backend, so start a Zephyr image with:
+
+.. code-block:: bash
+
+ qemu-system-arm -M stm32l496g-disco -kernel zephyr.elf \
+ -nographic -serial null -serial stdio
diff --git a/docs/system/target-arm.rst b/docs/system/target-arm.rst
index 260cd7e4d6..c7d9947655 100644
--- a/docs/system/target-arm.rst
+++ b/docs/system/target-arm.rst
@@ -82,6 +82,7 @@ Board-specific documentation
arm/aspeed
arm/bananapi_m2u.rst
arm/b-l475e-iot01a.rst
+ arm/stm32l496g-disco.rst
arm/sabrelite
arm/digic
arm/cubieboard
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 94a01c412b..5faa5d4ee1 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -389,6 +389,13 @@ config B_L475E_IOT01A
select STM32L4X5_SOC
imply DM163
+config STM32L496G_DISCO
+ bool
+ default y
+ depends on TCG && ARM
+ select STM32L4X5_SOC
+ select SSI_M25P80
+
config STM32L4X5_SOC
bool
select ARM_V7M
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index f3c1b0e00f..bc6d8ff86b 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -35,6 +35,7 @@ arm_common_ss.add(when: 'CONFIG_STM32F100_SOC', if_true: files('stm32f100_soc.c'
arm_common_ss.add(when: 'CONFIG_STM32F205_SOC', if_true: files('stm32f205_soc.c'))
arm_common_ss.add(when: 'CONFIG_STM32F405_SOC', if_true: files('stm32f405_soc.c'))
arm_common_ss.add(when: 'CONFIG_B_L475E_IOT01A', if_true: files('b-l475e-iot01a.c'))
+arm_common_ss.add(when: 'CONFIG_STM32L496G_DISCO', if_true: files('stm32l496g-disco.c'))
arm_common_ss.add(when: 'CONFIG_STM32L4X5_SOC', if_true: files('stm32l4x5_soc.c'))
arm_common_ss.add(when: 'CONFIG_XLNX_ZYNQMP_ARM', if_true: files('xlnx-zynqmp.c', 'xlnx-zcu102.c'))
arm_common_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal.c', 'xlnx-versal-virt.c'))
diff --git a/hw/arm/stm32l496g-disco.c b/hw/arm/stm32l496g-disco.c
new file mode 100644
index 0000000000..9f3ad6e043
--- /dev/null
+++ b/hw/arm/stm32l496g-disco.c
@@ -0,0 +1,95 @@
+/*
+ * STM32L496G-DISCO Discovery Kit machine
+ *
+ * Copyright (c) 2026 EXALT Technologies
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * The reference used is the STMicroelectronics UM2160 user manual for the
+ * Discovery kit with STM32L496AG MCU and the RM0351 reference manual.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/arm/armv7m.h"
+#include "hw/arm/boot.h"
+#include "hw/arm/stm32l4x5_soc.h"
+#include "hw/core/boards.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/irq.h"
+#include "hw/ssi/ssi.h"
+#include "system/blockdev.h"
+
+#define TYPE_STM32L496G_DISCO MACHINE_TYPE_NAME("stm32l496g-disco")
+OBJECT_DECLARE_SIMPLE_TYPE(Stm32l496gDiscoMachineState,
+ STM32L496G_DISCO)
+
+typedef struct Stm32l496gDiscoMachineState {
+ MachineState parent_obj;
+
+ Stm32l4x5SocState soc;
+} Stm32l496gDiscoMachineState;
+
+static void stm32l496g_disco_init(MachineState *machine)
+{
+ Stm32l496gDiscoMachineState *s = STM32L496G_DISCO(machine);
+ const Stm32l4x5SocClass *sc;
+ DeviceState *flash;
+ DriveInfo *dinfo;
+ BusState *bus;
+ qemu_irq flash_cs;
+
+ object_initialize_child(OBJECT(machine), "soc", &s->soc,
+ TYPE_STM32L496XG_SOC);
+ sysbus_realize(SYS_BUS_DEVICE(&s->soc), &error_fatal);
+
+ bus = qdev_get_child_bus(DEVICE(&s->soc.quadspi), "ssi");
+ flash = qdev_new("mx25r6435");
+ dinfo = drive_get(IF_MTD, 0, 0);
+ if (dinfo) {
+ qdev_prop_set_drive_err(flash, "drive", blk_by_legacy_dinfo(dinfo),
+ &error_fatal);
+ }
+ qdev_realize_and_unref(flash, bus, &error_fatal);
+
+ flash_cs = qdev_get_gpio_in_named(flash, SSI_GPIO_CS, 0);
+ qdev_connect_gpio_out_named(DEVICE(&s->soc.quadspi), "cs", 0,
+ flash_cs);
+ qemu_set_irq(flash_cs, 1);
+
+ sc = STM32L4X5_SOC_GET_CLASS(&s->soc);
+ armv7m_load_kernel(s->soc.armv7m.cpu, machine->kernel_filename, 0,
+ sc->flash_size);
+}
+
+static void stm32l496g_disco_machine_init(ObjectClass *oc, const void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+ static const char *machine_valid_cpu_types[] = {
+ ARM_CPU_TYPE_NAME("cortex-m4"),
+ NULL
+ };
+
+ mc->desc = "STM32L496G-DISCO Discovery Kit (Cortex-M4)";
+ mc->init = stm32l496g_disco_init;
+ mc->valid_cpu_types = machine_valid_cpu_types;
+
+ /* SRAM is allocated by the SoC. */
+ mc->default_ram_size = 0;
+}
+
+static void stm32l496g_disco_machine_register_types(void)
+{
+ static const TypeInfo machine_type = {
+ .name = TYPE_STM32L496G_DISCO,
+ .parent = TYPE_MACHINE,
+ .instance_size = sizeof(Stm32l496gDiscoMachineState),
+ .class_init = stm32l496g_disco_machine_init,
+ };
+
+ type_register_static(&machine_type);
+}
+
+type_init(stm32l496g_disco_machine_register_types)
diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index d91d18e88b..bd05a837fa 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -35,9 +35,7 @@
#define FLASH_BASE_ADDRESS 0x08000000
#define SRAM1_BASE_ADDRESS 0x20000000
-#define SRAM1_SIZE (96 * KiB)
#define SRAM2_BASE_ADDRESS 0x10000000
-#define SRAM2_SIZE (32 * KiB)
#define EXTI_ADDR 0x40010400
#define SYSCFG_ADDR 0x40010000
@@ -189,13 +187,15 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
memory_region_add_subregion(system_memory, FLASH_BASE_ADDRESS, &s->flash);
memory_region_add_subregion(system_memory, 0, &s->flash_alias);
- if (!memory_region_init_ram(&s->sram1, OBJECT(dev_soc), "SRAM1", SRAM1_SIZE,
+ if (!memory_region_init_ram(&s->sram1, OBJECT(dev_soc), "SRAM1",
+ sc->sram1_size,
errp)) {
return;
}
memory_region_add_subregion(system_memory, SRAM1_BASE_ADDRESS, &s->sram1);
- if (!memory_region_init_ram(&s->sram2, OBJECT(dev_soc), "SRAM2", SRAM2_SIZE,
+ if (!memory_region_init_ram(&s->sram2, OBJECT(dev_soc), "SRAM2",
+ sc->sram2_size,
errp)) {
return;
}
@@ -456,11 +456,15 @@ static void stm32l4x5_soc_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
+ Stm32l4x5SocClass *ssc = STM32L4X5_SOC_CLASS(klass);
dc->realize = stm32l4x5_soc_realize;
/* Reason: Mapped at fixed location on the system bus */
dc->user_creatable = false;
/* No vmstate or reset required: device has no internal state */
+
+ ssc->sram1_size = 96 * KiB;
+ ssc->sram2_size = 32 * KiB;
}
static void stm32l4x5xc_soc_class_init(ObjectClass *oc, const void *data)
@@ -484,6 +488,15 @@ static void stm32l4x5xg_soc_class_init(ObjectClass *oc, const void *data)
ssc->flash_size = 1 * MiB;
}
+static void stm32l496xg_soc_class_init(ObjectClass *oc, const void *data)
+{
+ Stm32l4x5SocClass *ssc = STM32L4X5_SOC_CLASS(oc);
+
+ ssc->flash_size = 1 * MiB;
+ ssc->sram1_size = 256 * KiB;
+ ssc->sram2_size = 64 * KiB;
+}
+
static const TypeInfo stm32l4x5_soc_types[] = {
{
.name = TYPE_STM32L4X5XC_SOC,
@@ -497,6 +510,10 @@ static const TypeInfo stm32l4x5_soc_types[] = {
.name = TYPE_STM32L4X5XG_SOC,
.parent = TYPE_STM32L4X5_SOC,
.class_init = stm32l4x5xg_soc_class_init,
+ }, {
+ .name = TYPE_STM32L496XG_SOC,
+ .parent = TYPE_STM32L4X5_SOC,
+ .class_init = stm32l496xg_soc_class_init,
}, {
.name = TYPE_STM32L4X5_SOC,
.parent = TYPE_SYS_BUS_DEVICE,
diff --git a/include/hw/arm/stm32l4x5_soc.h b/include/hw/arm/stm32l4x5_soc.h
index 5b5e817ee7..a10bc10cb4 100644
--- a/include/hw/arm/stm32l4x5_soc.h
+++ b/include/hw/arm/stm32l4x5_soc.h
@@ -39,6 +39,7 @@
#define TYPE_STM32L4X5XC_SOC "stm32l4x5xc-soc"
#define TYPE_STM32L4X5XE_SOC "stm32l4x5xe-soc"
#define TYPE_STM32L4X5XG_SOC "stm32l4x5xg-soc"
+#define TYPE_STM32L496XG_SOC "stm32l496xg-soc"
OBJECT_DECLARE_TYPE(Stm32l4x5SocState, Stm32l4x5SocClass, STM32L4X5_SOC)
#define NUM_EXTI_OR_GATES 4
@@ -71,6 +72,8 @@ struct Stm32l4x5SocClass {
SysBusDeviceClass parent_class;
size_t flash_size;
+ size_t sram1_size;
+ size_t sram2_size;
};
#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 7994696086..29febd1a08 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -271,6 +271,7 @@ qtests_arm = \
(config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \
(config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
(config_all_devices.has_key('CONFIG_STM32L4X5_SOC') ? qtests_stm32l4x5 : []) + \
+ (config_all_devices.has_key('CONFIG_STM32L496G_DISCO') ? ['stm32l496g-disco-test'] : []) + \
(config_all_devices.has_key('CONFIG_FSI_APB2OPB_ASPEED') ? ['aspeed_fsi-test'] : []) + \
(config_all_devices.has_key('CONFIG_CAN_FLEXCAN') ? ['flexcan-test'] : []) + \
(config_all_devices.has_key('CONFIG_STM32L4X5_SOC') and
diff --git a/tests/qtest/stm32l496g-disco-test.c b/tests/qtest/stm32l496g-disco-test.c
new file mode 100644
index 0000000000..9529618fa7
--- /dev/null
+++ b/tests/qtest/stm32l496g-disco-test.c
@@ -0,0 +1,693 @@
+/*
+ * QTest testcase for the STM32L496G-DISCO machine
+ *
+ * Copyright (c) 2026 EXALT Technologies
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "libqtest.h"
+#include "qobject/qdict.h"
+
+#define QUADSPI_BASE 0xa0001000
+
+#define QUADSPI_CR (QUADSPI_BASE + 0x00)
+#define QUADSPI_DCR (QUADSPI_BASE + 0x04)
+#define QUADSPI_SR (QUADSPI_BASE + 0x08)
+#define QUADSPI_FCR (QUADSPI_BASE + 0x0c)
+#define QUADSPI_DLR (QUADSPI_BASE + 0x10)
+#define QUADSPI_CCR (QUADSPI_BASE + 0x14)
+#define QUADSPI_AR (QUADSPI_BASE + 0x18)
+#define QUADSPI_DR (QUADSPI_BASE + 0x20)
+#define QUADSPI_ABR (QUADSPI_BASE + 0x1c)
+#define DCR_FSIZE(bits) (((bits) - 1) << 16)
+
+#define CR_EN BIT(0)
+#define CR_ABORT BIT(1)
+#define CR_TEIE BIT(16)
+#define CR_FTHRES(bytes) (((bytes) - 1) << 8)
+#define CR_TCIE BIT(17)
+#define CR_FTIE BIT(18)
+
+#define SR_TCF BIT(1)
+#define SR_TEF BIT(0)
+#define SR_FTF BIT(2)
+#define SR_BUSY BIT(5)
+#define SR_FLEVEL(sr) extract32((sr), 8, 5)
+
+#define FCR_CTCF BIT(1)
+
+#define CCR_IMODE_SINGLE (1 << 8)
+#define CCR_ADMODE_SINGLE (1 << 10)
+#define CCR_ADSIZE_24BIT (2 << 12)
+#define CCR_DCYC(cycles) ((cycles) << 18)
+#define CCR_DMODE_SINGLE (1 << 24)
+#define CCR_FMODE_INDIRECT_READ (1 << 26)
+#define CCR_ADMODE_DUAL (2 << 10)
+#define CCR_ADMODE_QUAD (3 << 10)
+#define CCR_DMODE_DUAL (2 << 24)
+#define CCR_DMODE_QUAD (3 << 24)
+#define CCR_ABMODE_QUAD (3 << 14)
+#define FCR_ALL 0x1b
+
+#define CMD_WRITE_ENABLE 0x06
+#define CMD_WRITE_DISABLE 0x04
+#define CMD_READ_STATUS 0x05
+#define CMD_READ 0x03
+#define CMD_PAGE_PROGRAM 0x02
+#define CMD_READ_ID 0x9f
+#define CMD_READ_SFDP 0x5a
+
+static QTestState *qts;
+
+static void qspi_command(uint8_t command)
+{
+ qtest_writel(qts, QUADSPI_FCR, FCR_CTCF);
+ qtest_writel(qts, QUADSPI_CCR, CCR_IMODE_SINGLE | command);
+}
+
+static void qspi_start_read(uint8_t command, uint32_t address,
+ bool address_phase, unsigned int dummy_cycles,
+ unsigned int length)
+{
+ uint32_t ccr = CCR_IMODE_SINGLE | CCR_DMODE_SINGLE |
+ CCR_FMODE_INDIRECT_READ | command;
+
+ if (address_phase) {
+ ccr |= CCR_ADMODE_SINGLE | CCR_ADSIZE_24BIT;
+ }
+ ccr |= CCR_DCYC(dummy_cycles);
+
+ qtest_writel(qts, QUADSPI_FCR, FCR_CTCF);
+ qtest_writel(qts, QUADSPI_CCR, 0);
+ qtest_writel(qts, QUADSPI_DLR, length - 1);
+ qtest_writel(qts, QUADSPI_CCR, ccr);
+ if (address_phase) {
+ qtest_writel(qts, QUADSPI_AR, address);
+ }
+}
+
+static uint32_t qspi_read(uint8_t command, uint32_t address,
+ bool address_phase, unsigned int dummy_cycles,
+ unsigned int length)
+{
+ qspi_start_read(command, address, address_phase, dummy_cycles, length);
+ return qtest_readl(qts, QUADSPI_DR);
+}
+
+static void qspi_start_write(uint32_t address, unsigned int length)
+{
+ uint32_t ccr = CMD_PAGE_PROGRAM | CCR_IMODE_SINGLE |
+ CCR_ADMODE_SINGLE | CCR_ADSIZE_24BIT |
+ CCR_DMODE_SINGLE;
+
+ qspi_command(CMD_WRITE_ENABLE);
+ qtest_writel(qts, QUADSPI_FCR, FCR_CTCF);
+ qtest_writel(qts, QUADSPI_CCR, 0);
+ qtest_writel(qts, QUADSPI_AR, address);
+ qtest_writel(qts, QUADSPI_DLR, length - 1);
+ qtest_writel(qts, QUADSPI_CCR, ccr);
+}
+
+static void qspi_page_program(uint32_t address, uint32_t value)
+{
+ qspi_start_write(address, sizeof(value));
+ qtest_writel(qts, QUADSPI_DR, value);
+}
+
+static void test_quadspi_identification(void)
+{
+ uint32_t id = qspi_read(CMD_READ_ID, 0, false, 0, 3);
+
+ g_assert_cmphex(id & 0x00ffffff, ==, 0x001728c2);
+}
+
+static void test_quadspi_sfdp(void)
+{
+ uint32_t signature = qspi_read(CMD_READ_SFDP, 0, true, 8, 4);
+
+ g_assert_cmphex(signature, ==, 0x50444653);
+}
+
+static void test_quadspi_program_read(void)
+{
+ const uint32_t address = 0x100;
+ const uint32_t value = 0x5aa55aa5;
+ uint32_t actual;
+
+ qspi_page_program(address, value);
+ actual = qspi_read(CMD_READ, address, true, 0, sizeof(value));
+
+ g_assert_cmphex(actual, ==, value);
+}
+
+/* Verify FIFO capacity, RX backpressure, threshold IRQs, and byte ordering. */
+static void test_quadspi_fifo_refill(void)
+{
+ const uint32_t address = 0x200;
+ const unsigned int length = 40;
+ uint32_t sr;
+
+ qspi_start_write(address, length);
+ for (unsigned int i = 0; i < length; i++) {
+ qtest_writeb(qts, QUADSPI_DR, i);
+ }
+ qtest_writel(qts, QUADSPI_CR, CR_EN | CR_FTHRES(16) | CR_FTIE);
+ qspi_start_read(CMD_READ, address, true, 0, length);
+ sr = qtest_readl(qts, QUADSPI_SR);
+ g_assert_cmpuint(SR_FLEVEL(sr), ==, 16);
+ g_assert_true(sr & SR_BUSY);
+ g_assert_true(sr & SR_FTF);
+ g_assert_false(sr & SR_TCF);
+ g_assert_true(qtest_get_irq(qts, 0));
+
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_DR), ==, 0x03020100);
+ sr = qtest_readl(qts, QUADSPI_SR);
+ g_assert_cmpuint(SR_FLEVEL(sr), ==, 12);
+ g_assert_false(sr & SR_FTF);
+ g_assert_false(qtest_get_irq(qts, 0));
+
+ g_assert_cmpuint(qtest_readb(qts, QUADSPI_DR), ==, 4);
+ g_assert_cmpuint(SR_FLEVEL(qtest_readl(qts, QUADSPI_SR)), ==, 16);
+ g_assert_true(qtest_get_irq(qts, 0));
+ for (unsigned int i = 5; i < length; i++) {
+ g_assert_cmpuint(qtest_readb(qts, QUADSPI_DR), ==, i);
+ sr = qtest_readl(qts, QUADSPI_SR);
+ g_assert_cmpuint(SR_FLEVEL(sr), <=, 16);
+ if (i + 1 < length) {
+ g_assert_true(sr & SR_BUSY);
+ }
+ }
+ g_assert_cmpuint(SR_FLEVEL(sr), ==, 0);
+ g_assert_true(sr & SR_TCF);
+ g_assert_false(sr & (SR_BUSY | SR_FTF));
+ g_assert_false(qtest_get_irq(qts, 0));
+ qtest_writel(qts, QUADSPI_CR, CR_EN);
+}
+
+/* A short final RX fragment must remain readable below the FIFO threshold. */
+static void test_quadspi_fifo_tail(void)
+{
+ uint32_t sr;
+
+ qtest_writel(qts, QUADSPI_CR, CR_EN | CR_FTHRES(16) | CR_FTIE);
+ qspi_start_read(CMD_READ_ID, 0, false, 0, 3);
+ sr = qtest_readl(qts, QUADSPI_SR);
+ g_assert_cmpuint(SR_FLEVEL(sr), ==, 3);
+ g_assert_true(sr & SR_TCF);
+ g_assert_true(sr & SR_BUSY);
+ g_assert_true(sr & SR_FTF);
+ g_assert_true(qtest_get_irq(qts, 0));
+ qtest_writel(qts, QUADSPI_FCR, FCR_CTCF);
+ g_assert_cmphex(qtest_readw(qts, QUADSPI_DR), ==, 0x28c2);
+ g_assert_cmpuint(SR_FLEVEL(qtest_readl(qts, QUADSPI_SR)), ==, 1);
+ g_assert_true(qtest_get_irq(qts, 0));
+ g_assert_cmphex(qtest_readb(qts, QUADSPI_DR), ==, 0x17);
+ sr = qtest_readl(qts, QUADSPI_SR);
+ g_assert_false(sr & (SR_BUSY | SR_FTF | SR_TCF));
+ g_assert_false(qtest_get_irq(qts, 0));
+ g_assert_cmpuint(qtest_readb(qts, QUADSPI_DR), ==, 0);
+ qtest_writel(qts, QUADSPI_CR, CR_EN);
+}
+
+/* Byte, halfword, and word accesses must preserve the FIFO byte sequence. */
+static void test_quadspi_fifo_mixed_access(void)
+{
+ qspi_start_write(0x300, 7);
+ qtest_writeb(qts, QUADSPI_DR, 0xa1);
+ qtest_writew(qts, QUADSPI_DR, 0xc3b2);
+ qtest_writel(qts, QUADSPI_DR, 0x07f6e5d4);
+ g_assert_true(qtest_readl(qts, QUADSPI_SR) & SR_TCF);
+ g_assert_cmpuint(SR_FLEVEL(qtest_readl(qts, QUADSPI_SR)), ==, 0);
+ qspi_start_read(CMD_READ, 0x300, true, 0, 7);
+ g_assert_cmphex(qtest_readb(qts, QUADSPI_DR), ==, 0xa1);
+ g_assert_cmphex(qtest_readw(qts, QUADSPI_DR), ==, 0xc3b2);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_DR), ==, 0x07f6e5d4);
+}
+
+/* Extra bytes in a final word write must not be programmed beyond DLR. */
+static void test_quadspi_fifo_write_excess(void)
+{
+ qspi_start_write(0x400, 3);
+ qtest_writel(qts, QUADSPI_DR, 0x44332211);
+ g_assert_cmpuint(SR_FLEVEL(qtest_readl(qts, QUADSPI_SR)), ==, 0);
+ g_assert_cmphex(qspi_read(CMD_READ, 0x400, true, 0, 4), ==, 0xff332211);
+}
+
+/* Aborting a paused RX transfer must discard its FIFO and permit a new read. */
+static void test_quadspi_fifo_abort(void)
+{
+ uint32_t sr;
+
+ qspi_start_read(CMD_READ, 0x200, true, 0, 40);
+ g_assert_cmpuint(SR_FLEVEL(qtest_readl(qts, QUADSPI_SR)), ==, 16);
+ qtest_writel(qts, QUADSPI_CR, CR_EN | CR_ABORT);
+ sr = qtest_readl(qts, QUADSPI_SR);
+ g_assert_cmpuint(SR_FLEVEL(sr), ==, 0);
+ g_assert_false(sr & (SR_BUSY | SR_FTF));
+ g_assert_true(sr & SR_TCF);
+ g_assert_false(qtest_readl(qts, QUADSPI_CR) & CR_ABORT);
+ g_assert_cmpuint(qtest_readb(qts, QUADSPI_DR), ==, 0);
+ g_assert_cmphex(qspi_read(CMD_READ_ID, 0, false, 0, 3), ==, 0x001728c2);
+}
+
+/* Reset must discard buffered RX data and deassert the threshold interrupt. */
+static void test_quadspi_fifo_reset(void)
+{
+ qtest_writel(qts, QUADSPI_CR, CR_EN | CR_FTIE);
+ qspi_start_read(CMD_READ, 0x200, true, 0, 40);
+ g_assert_true(qtest_get_irq(qts, 0));
+ qtest_system_reset(qts);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_SR), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_CR), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_DLR), ==, 0);
+ g_assert_false(qtest_get_irq(qts, 0));
+ qtest_writel(qts, QUADSPI_CR, CR_EN);
+ qtest_writel(qts, QUADSPI_DCR, DCR_FSIZE(23));
+ g_assert_cmphex(qspi_read(CMD_READ_ID, 0, false, 0, 3), ==, 0x001728c2);
+}
+
+/* Disabled command writes must not set the flash's write-enable latch. */
+static void test_quadspi_disabled_command(void)
+{
+ qspi_command(CMD_WRITE_DISABLE);
+ qtest_writel(qts, QUADSPI_FCR, FCR_CTCF);
+ qtest_writel(qts, QUADSPI_CR, CR_TCIE | CR_FTIE);
+ qspi_command(CMD_WRITE_ENABLE);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_CCR), ==,
+ CCR_IMODE_SINGLE | CMD_WRITE_ENABLE);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_SR), ==, 0);
+ g_assert_false(qtest_get_irq(qts, 0));
+
+ qtest_writel(qts, QUADSPI_CR, CR_EN);
+ g_assert_false(qtest_readl(qts, QUADSPI_SR) & SR_TCF);
+ g_assert_cmphex(qspi_read(CMD_READ_STATUS, 0, false, 0, 1) & 2, ==, 0);
+ qspi_command(CMD_WRITE_ENABLE);
+ g_assert_cmphex(qspi_read(CMD_READ_STATUS, 0, false, 0, 1) & 2, ==, 2);
+ qspi_command(CMD_WRITE_DISABLE);
+}
+
+/* CCR, AR, and DR accesses must not exchange flash data while disabled. */
+static void test_quadspi_disabled_data(void)
+{
+ qtest_writel(qts, QUADSPI_FCR, FCR_CTCF);
+ qtest_writel(qts, QUADSPI_CR, CR_TCIE | CR_FTIE);
+ qspi_start_read(CMD_READ_ID, 0, false, 0, 3);
+ g_assert_cmpuint(qtest_readb(qts, QUADSPI_DR), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_SR), ==, 0);
+ qspi_start_read(CMD_READ, 0x500, true, 0, 4);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_AR), ==, 0x500);
+ g_assert_cmpuint(qtest_readl(qts, QUADSPI_DLR), ==, 3);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_DR), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_SR), ==, 0);
+
+ qspi_start_write(0x500, 4);
+ qtest_writel(qts, QUADSPI_DR, 0x12345678);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_SR), ==, 0);
+ g_assert_false(qtest_get_irq(qts, 0));
+ qtest_writel(qts, QUADSPI_CR, CR_EN);
+ qtest_writel(qts, QUADSPI_DR, 0x12345678);
+ g_assert_false(qtest_readl(qts, QUADSPI_SR) & SR_TCF);
+ g_assert_cmphex(qspi_read(CMD_READ, 0x500, true, 0, 4), ==, 0xffffffff);
+ g_assert_cmphex(qspi_read(CMD_READ_ID, 0, false, 0, 3), ==, 0x001728c2);
+}
+
+/* Disable clears RX state without synthesizing completion or replaying CCR. */
+static void test_quadspi_disable_read(void)
+{
+ uint32_t ccr;
+
+ qtest_writel(qts, QUADSPI_CR, CR_EN | CR_FTIE | CR_TCIE);
+ qspi_start_read(CMD_READ, 0, true, 0, 40);
+ ccr = qtest_readl(qts, QUADSPI_CCR);
+ g_assert_cmpuint(SR_FLEVEL(qtest_readl(qts, QUADSPI_SR)), ==, 16);
+ g_assert_true(qtest_get_irq(qts, 0));
+ qtest_writel(qts, QUADSPI_CR, CR_FTIE | CR_TCIE);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_SR), ==, 0);
+ g_assert_false(qtest_get_irq(qts, 0));
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_CCR), ==, ccr);
+ g_assert_cmpuint(qtest_readl(qts, QUADSPI_DLR), ==, 39);
+ g_assert_cmpuint(qtest_readb(qts, QUADSPI_DR), ==, 0);
+ qtest_writel(qts, QUADSPI_CR, CR_EN | CR_FTIE | CR_TCIE);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_SR), ==, 0);
+ g_assert_false(qtest_get_irq(qts, 0));
+
+ qspi_start_read(CMD_READ_ID, 0, false, 0, 3);
+ g_assert_true(qtest_readl(qts, QUADSPI_SR) & SR_TCF);
+ qtest_writel(qts, QUADSPI_CR, CR_TCIE);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_SR), ==, SR_TCF);
+ g_assert_true(qtest_get_irq(qts, 0));
+ qtest_writel(qts, QUADSPI_FCR, FCR_CTCF);
+ g_assert_false(qtest_get_irq(qts, 0));
+ qtest_writel(qts, QUADSPI_CR, CR_EN);
+}
+
+/* Disable stops a partial write without replaying data on re-enable. */
+static void test_quadspi_disable_write(void)
+{
+ uint32_t ccr;
+
+ qspi_start_write(0x600, 4);
+ qtest_writeb(qts, QUADSPI_DR, 0xa5);
+ ccr = qtest_readl(qts, QUADSPI_CCR);
+ g_assert_true(qtest_readl(qts, QUADSPI_SR) & SR_BUSY);
+ qtest_writel(qts, QUADSPI_CR, 0);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_SR), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_CCR), ==, ccr);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_AR), ==, 0x600);
+ qtest_writel(qts, QUADSPI_DR, 0);
+ qtest_writel(qts, QUADSPI_CR, CR_EN);
+ qtest_writel(qts, QUADSPI_DR, 0);
+ g_assert_false(qtest_readl(qts, QUADSPI_SR) & SR_TCF);
+ g_assert_cmphex(qspi_read(CMD_READ, 0x600, true, 0, 4), ==, 0xffffffa5);
+}
+
+/* Reject out-of-range reads/writes before exchanging any flash payload. */
+static void test_quadspi_address_bounds(void)
+{
+ qtest_writel(qts, QUADSPI_DCR, DCR_FSIZE(12));
+ qtest_writel(qts, QUADSPI_CR, CR_EN | CR_TEIE);
+ qspi_start_read(CMD_READ, 0x1000, true, 0, 1);
+ g_assert_true(qtest_readl(qts, QUADSPI_SR) & SR_TEF);
+ g_assert_false(qtest_readl(qts, QUADSPI_SR) & (SR_BUSY | SR_TCF));
+ g_assert_true(qtest_get_irq(qts, 0));
+ qtest_writeb(qts, QUADSPI_FCR, SR_TEF);
+ g_assert_false(qtest_get_irq(qts, 0));
+ qspi_start_read(CMD_READ, 0xfff, true, 0, 2);
+ g_assert_true(qtest_readl(qts, QUADSPI_SR) & SR_TEF);
+ qtest_writel(qts, QUADSPI_FCR, FCR_ALL);
+ qspi_start_write(0xfff, 2);
+ qtest_writew(qts, QUADSPI_DR, 0x1234);
+ g_assert_true(qtest_readl(qts, QUADSPI_SR) & SR_TEF);
+ qtest_writel(qts, QUADSPI_FCR, FCR_ALL);
+ g_assert_cmphex(qspi_read(CMD_READ, 0xffc, true, 0, 4), ==, 0xffffffff);
+ qtest_writel(qts, QUADSPI_DCR, DCR_FSIZE(32));
+ qspi_start_read(0x13, UINT32_MAX, true, 0, 2);
+ g_assert_true(qtest_readl(qts, QUADSPI_SR) & SR_TEF);
+ qtest_writel(qts, QUADSPI_FCR, FCR_ALL);
+ qtest_writel(qts, QUADSPI_DCR, DCR_FSIZE(23));
+ qtest_writel(qts, QUADSPI_CR, CR_EN);
+}
+
+/* Undefined length ends at FSIZE, or continues until abort for FSIZE = 31. */
+static void test_quadspi_undefined_length(void)
+{
+ qtest_writel(qts, QUADSPI_DCR, DCR_FSIZE(12));
+ qspi_start_read(CMD_READ, 0xffc, true, 0, 0);
+ g_assert_true(qtest_readl(qts, QUADSPI_SR) & SR_TCF);
+ g_assert_cmpuint(SR_FLEVEL(qtest_readl(qts, QUADSPI_SR)), ==, 4);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_DR), ==, 0xffffffff);
+ g_assert_false(qtest_readl(qts, QUADSPI_SR) & SR_BUSY);
+ qspi_start_write(0xffc, 0);
+ qtest_writel(qts, QUADSPI_DR, 0x78563412);
+ g_assert_true(qtest_readl(qts, QUADSPI_SR) & SR_TCF);
+ g_assert_cmphex(qspi_read(CMD_READ, 0xffc, true, 0, 4), ==, 0x78563412);
+ qtest_writel(qts, QUADSPI_DCR, DCR_FSIZE(32));
+ qspi_start_read(CMD_READ, 0, true, 0, 0);
+ for (unsigned int i = 0; i < 12; i++) {
+ qtest_readl(qts, QUADSPI_DR);
+ g_assert_true(qtest_readl(qts, QUADSPI_SR) & SR_BUSY);
+ g_assert_false(qtest_readl(qts, QUADSPI_SR) & SR_TCF);
+ }
+ qtest_writel(qts, QUADSPI_CR, CR_EN | CR_ABORT);
+ qspi_start_write(0x900, 0);
+ qtest_writel(qts, QUADSPI_DR, 0x12345678);
+ g_assert_true(qtest_readl(qts, QUADSPI_SR) & SR_BUSY);
+ g_assert_false(qtest_readl(qts, QUADSPI_SR) & SR_TCF);
+ qtest_writel(qts, QUADSPI_CR, CR_EN | CR_ABORT);
+ g_assert_cmphex(qspi_read(CMD_READ, 0x900, true, 0, 4), ==, 0x12345678);
+ qtest_writel(qts, QUADSPI_DCR, DCR_FSIZE(23));
+}
+
+/* Subword register accesses preserve adjacent bytes and write-one clears. */
+static void test_quadspi_partial_registers(void)
+{
+ qtest_writel(qts, QUADSPI_ABR, 0x12345678);
+ qtest_writeb(qts, QUADSPI_ABR + 1, 0xab);
+ qtest_writew(qts, QUADSPI_ABR + 2, 0xcdef);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_ABR), ==, 0xcdefab78);
+ g_assert_cmphex(qtest_readb(qts, QUADSPI_ABR + 1), ==, 0xab);
+ g_assert_cmphex(qtest_readw(qts, QUADSPI_ABR + 2), ==, 0xcdef);
+ qtest_writel(qts, QUADSPI_CR, CR_EN | CR_TCIE);
+ qtest_writeb(qts, QUADSPI_CR + 1, 0x03);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_CR), ==,
+ CR_EN | CR_TCIE | CR_FTHRES(4));
+ qspi_command(CMD_WRITE_DISABLE);
+ g_assert_true(qtest_readl(qts, QUADSPI_SR) & SR_TCF);
+ qtest_writeb(qts, QUADSPI_FCR + 1, 0xff);
+ g_assert_true(qtest_readl(qts, QUADSPI_SR) & SR_TCF);
+ qtest_writeb(qts, QUADSPI_FCR, FCR_CTCF);
+ g_assert_false(qtest_readl(qts, QUADSPI_SR) & SR_TCF);
+ qtest_writel(qts, QUADSPI_CR, CR_EN);
+}
+
+/* A busy controller must not reinterpret its active command configuration. */
+static void test_quadspi_busy_registers(void)
+{
+ uint32_t ccr;
+
+ qspi_start_read(CMD_READ, 0, true, 0, 40);
+ ccr = qtest_readl(qts, QUADSPI_CCR);
+ qtest_writel(qts, QUADSPI_CCR, CCR_IMODE_SINGLE | CMD_WRITE_ENABLE);
+ qtest_writel(qts, QUADSPI_DLR, 1);
+ qtest_writel(qts, QUADSPI_AR, 0x800);
+ qtest_writel(qts, QUADSPI_DCR, 0);
+ qtest_writel(qts, QUADSPI_ABR, 0);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_CCR), ==, ccr);
+ g_assert_cmpuint(qtest_readl(qts, QUADSPI_DLR), ==, 39);
+ g_assert_cmpuint(qtest_readl(qts, QUADSPI_AR), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_DCR), ==, DCR_FSIZE(23));
+ qtest_writel(qts, QUADSPI_CR, CR_EN | CR_FTHRES(16) | CR_FTIE);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_CR), ==, CR_EN | CR_FTIE);
+ qtest_writel(qts, QUADSPI_CR, CR_EN | CR_ABORT);
+}
+
+/* Deferred modes must never issue an accidental WREN command. */
+static void test_quadspi_unsupported_modes(void)
+{
+ static const uint32_t configs[] = {
+ (2 << 26), (3 << 26), BIT(28), BIT(31),
+ };
+
+ qspi_command(CMD_WRITE_DISABLE);
+ for (unsigned int i = 0; i < ARRAY_SIZE(configs); i++) {
+ qtest_writel(qts, QUADSPI_FCR, FCR_ALL);
+ qtest_writel(qts, QUADSPI_CCR,
+ configs[i] | CCR_IMODE_SINGLE | CMD_WRITE_ENABLE);
+ g_assert_false(qtest_readl(qts, QUADSPI_SR) & (SR_TCF | SR_BUSY));
+ g_assert_cmphex(qspi_read(CMD_READ_STATUS, 0, false, 0, 1) & 2, ==, 0);
+ }
+ qtest_writel(qts, QUADSPI_CR, CR_EN | BIT(6));
+ qtest_writel(qts, QUADSPI_FCR, FCR_ALL);
+ qtest_writel(qts, QUADSPI_CCR, CCR_IMODE_SINGLE | CMD_WRITE_ENABLE);
+ g_assert_false(qtest_readl(qts, QUADSPI_SR) & (SR_TCF | SR_BUSY));
+ qtest_writel(qts, QUADSPI_CR, CR_EN);
+ g_assert_cmphex(qspi_read(CMD_READ_STATUS, 0, false, 0, 1) & 2, ==, 0);
+}
+
+/* Exercise fast-read opcodes and single/dual/quad byte prefixes. */
+static void test_quadspi_lane_modes(void)
+{
+ static const struct {
+ uint8_t opcode;
+ uint32_t admode;
+ uint32_t dmode;
+ unsigned int dummy;
+ bool alternate;
+ } modes[] = {
+ { 0x0b, CCR_ADMODE_SINGLE, CCR_DMODE_SINGLE, 8, false },
+ { 0x3b, CCR_ADMODE_SINGLE, CCR_DMODE_DUAL, 8, false },
+ { 0x6b, CCR_ADMODE_SINGLE, CCR_DMODE_QUAD, 8, false },
+ { 0xbb, CCR_ADMODE_DUAL, CCR_DMODE_DUAL, 4, false },
+ { 0xeb, CCR_ADMODE_QUAD, CCR_DMODE_QUAD, 6, false },
+ { 0xeb, CCR_ADMODE_QUAD, CCR_DMODE_QUAD, 4, true },
+ };
+
+ qspi_page_program(0x800, 0x76543210);
+ for (unsigned int i = 0; i < ARRAY_SIZE(modes); i++) {
+ uint32_t ccr = CCR_IMODE_SINGLE | CCR_ADSIZE_24BIT |
+ CCR_FMODE_INDIRECT_READ | modes[i].admode |
+ modes[i].dmode | CCR_DCYC(modes[i].dummy) |
+ modes[i].opcode;
+
+ qtest_writel(qts, QUADSPI_ABR, 0);
+ if (modes[i].alternate) {
+ ccr |= CCR_ABMODE_QUAD;
+ }
+ qtest_writel(qts, QUADSPI_DLR, 3);
+ qtest_writel(qts, QUADSPI_CCR, ccr);
+ qtest_writel(qts, QUADSPI_AR, 0x800);
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_DR), ==, 0x76543210);
+ }
+}
+
+static void qspi_wait_migration(QTestState *state)
+{
+ int64_t deadline = g_get_monotonic_time() + 10 * G_TIME_SPAN_SECOND;
+
+ do {
+ QDict *response = qtest_qmp(state, "{'execute': 'query-migrate'}");
+ QDict *result = qdict_get_qdict(response, "return");
+ const char *status = qdict_get_str(result, "status");
+ bool complete = !strcmp(status, "completed");
+
+ if (!strcmp(status, "failed")) {
+ g_error("Migration failed: %s",
+ qdict_get_try_str(result, "error-desc"));
+ }
+ qobject_unref(response);
+ if (complete) {
+ return;
+ }
+ g_usleep(1000);
+ } while (g_get_monotonic_time() < deadline);
+ g_error("Migration did not complete within 10 seconds");
+}
+
+static void qspi_migrate(void)
+{
+ char *path = NULL;
+ int fd = g_file_open_tmp("qspi-migration-XXXXXX", &path, NULL);
+ g_autofree char *uri = g_strdup_printf("file:%s", path);
+ QTestState *source = qts;
+
+ g_assert_cmpint(fd, >=, 0);
+ close(fd);
+ qtest_qmp_assert_success(source,
+ "{'execute': 'migrate', 'arguments': {'uri': %s}}", uri);
+ qspi_wait_migration(source);
+ qts = qtest_init("-machine stm32l496g-disco -incoming defer");
+ qtest_qmp_assert_success(qts,
+ "{'execute': 'migrate-incoming', 'arguments': {'uri': %s}}", uri);
+ qspi_wait_migration(qts);
+ qtest_quit(source);
+ unlink(path);
+ g_free(path);
+}
+
+/* SFDP data exposes lost FIFO bytes or a reset flash-parser cursor. */
+static void test_quadspi_migration_read(void)
+{
+ QTestState *original = qts;
+ uint8_t expected[40];
+
+ qts = qtest_init("-machine stm32l496g-disco");
+ qtest_writel(qts, QUADSPI_CR, CR_EN);
+ qtest_writel(qts, QUADSPI_DCR, DCR_FSIZE(23));
+ qspi_start_read(CMD_READ_SFDP, 0, true, 8, sizeof(expected));
+ for (unsigned int i = 0; i < sizeof(expected); i++) {
+ expected[i] = qtest_readb(qts, QUADSPI_DR);
+ }
+ qspi_start_read(CMD_READ_SFDP, 0, true, 8, sizeof(expected));
+ for (unsigned int i = 0; i < 3; i++) {
+ g_assert_cmpuint(qtest_readb(qts, QUADSPI_DR), ==, expected[i]);
+ }
+ qspi_migrate();
+ for (unsigned int i = 3; i < sizeof(expected); i++) {
+ g_assert_cmpuint(qtest_readb(qts, QUADSPI_DR), ==, expected[i]);
+ }
+ g_assert_false(qtest_readl(qts, QUADSPI_SR) & SR_BUSY);
+ qtest_writel(qts, QUADSPI_DCR, DCR_FSIZE(32));
+ qspi_start_read(CMD_READ_SFDP, 0, true, 8, 0);
+ for (unsigned int i = 0; i < 3; i++) {
+ g_assert_cmpuint(qtest_readb(qts, QUADSPI_DR), ==, expected[i]);
+ }
+ qspi_migrate();
+ for (unsigned int i = 3; i < sizeof(expected); i++) {
+ g_assert_cmpuint(qtest_readb(qts, QUADSPI_DR), ==, expected[i]);
+ }
+ g_assert_true(qtest_readl(qts, QUADSPI_SR) & SR_BUSY);
+ g_assert_false(qtest_readl(qts, QUADSPI_SR) & SR_TCF);
+ qtest_writel(qts, QUADSPI_CR, CR_EN | CR_ABORT);
+ qtest_quit(qts);
+ qts = original;
+}
+
+/* Restore an unread final fragment and an in-flight register write. */
+static void test_quadspi_migration_write(void)
+{
+ QTestState *original = qts;
+
+ qts = qtest_init("-machine stm32l496g-disco");
+ qtest_writel(qts, QUADSPI_CR, CR_EN);
+ qtest_writel(qts, QUADSPI_DCR, DCR_FSIZE(23));
+ qspi_start_read(CMD_READ_ID, 0, false, 0, 3);
+ qspi_migrate();
+ g_assert_cmphex(qtest_readl(qts, QUADSPI_DR), ==, 0x001728c2);
+ qspi_command(CMD_WRITE_ENABLE);
+ qtest_writel(qts, QUADSPI_DLR, 1);
+ qtest_writel(qts, QUADSPI_CCR,
+ CCR_IMODE_SINGLE | CCR_DMODE_SINGLE | 0x01);
+ qspi_migrate();
+ qtest_writeb(qts, QUADSPI_DR, 0x40);
+ qspi_migrate();
+ qtest_writeb(qts, QUADSPI_DR, 0);
+ g_assert_true(qtest_readl(qts, QUADSPI_SR) & SR_TCF);
+ g_assert_cmphex(qspi_read(CMD_READ_STATUS, 0, false, 0, 1) & 0x40,
+ ==, 0x40);
+ qtest_quit(qts);
+ qts = original;
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+ g_test_set_nonfatal_assertions();
+
+ qts = qtest_init("-machine stm32l496g-disco");
+ qtest_irq_intercept_out_named(qts, "/machine/soc/quadspi",
+ "sysbus-irq");
+ qtest_writel(qts, QUADSPI_CR, CR_EN);
+ qtest_writel(qts, QUADSPI_DCR, DCR_FSIZE(23));
+
+ qtest_add_func("stm32l496g-disco/quadspi/identification",
+ test_quadspi_identification);
+ qtest_add_func("stm32l496g-disco/quadspi/sfdp", test_quadspi_sfdp);
+ qtest_add_func("stm32l496g-disco/quadspi/program-read",
+ test_quadspi_program_read);
+ qtest_add_func("stm32l496g-disco/quadspi/fifo-refill",
+ test_quadspi_fifo_refill);
+ qtest_add_func("stm32l496g-disco/quadspi/fifo-tail",
+ test_quadspi_fifo_tail);
+ qtest_add_func("stm32l496g-disco/quadspi/fifo-mixed-access",
+ test_quadspi_fifo_mixed_access);
+ qtest_add_func("stm32l496g-disco/quadspi/fifo-write-excess",
+ test_quadspi_fifo_write_excess);
+ qtest_add_func("stm32l496g-disco/quadspi/fifo-abort",
+ test_quadspi_fifo_abort);
+ qtest_add_func("stm32l496g-disco/quadspi/fifo-reset",
+ test_quadspi_fifo_reset);
+ qtest_add_func("stm32l496g-disco/quadspi/disabled-command",
+ test_quadspi_disabled_command);
+ qtest_add_func("stm32l496g-disco/quadspi/disabled-data",
+ test_quadspi_disabled_data);
+ qtest_add_func("stm32l496g-disco/quadspi/disable-read",
+ test_quadspi_disable_read);
+ qtest_add_func("stm32l496g-disco/quadspi/disable-write",
+ test_quadspi_disable_write);
+ qtest_add_func("stm32l496g-disco/quadspi/address-bounds",
+ test_quadspi_address_bounds);
+ qtest_add_func("stm32l496g-disco/quadspi/undefined-length",
+ test_quadspi_undefined_length);
+ qtest_add_func("stm32l496g-disco/quadspi/partial-registers",
+ test_quadspi_partial_registers);
+ qtest_add_func("stm32l496g-disco/quadspi/busy-registers",
+ test_quadspi_busy_registers);
+ qtest_add_func("stm32l496g-disco/quadspi/unsupported-modes",
+ test_quadspi_unsupported_modes);
+ qtest_add_func("stm32l496g-disco/quadspi/lane-modes",
+ test_quadspi_lane_modes);
+ qtest_add_func("stm32l496g-disco/quadspi/migration-read",
+ test_quadspi_migration_read);
+ qtest_add_func("stm32l496g-disco/quadspi/migration-write",
+ test_quadspi_migration_write);
+
+ ret = g_test_run();
+ qtest_quit(qts);
+
+ return ret;
+}
--
2.49.1
^ permalink raw reply related [flat|nested] 8+ messages in thread