[PATCH v2 5/5] hw/riscv/k230: Attach a standard SPI NOR flash
Kangjie Huang <[email protected]> Sun, 2 Aug 2026 03:28:48 +0800
| Newsgroups | org.nongnu.qemu-riscv,org.nongnu.qemu-devel |
|---|---|
| Message-ID | <[email protected]> |
Add the optional spi-flash machine property and attach the selected M25P80-compatible flash device to logical spi0 chip select 0. Use the supplied MTD backend when present and retain the erased-flash default otherwise. Document the machine option and add qtests for Standard 1-1-1 JEDEC identification and fixed-address reads. Standard 1-1-1 transfers were also exercised manually through the K230 SDK U-Boot and Linux SPI paths against the attached flash. The enhanced SPI and IDMA boot paths are outside the scope of this series. Signed-off-by: Kangjie Huang <[email protected]> --- docs/system/riscv/k230.rst | 1 + hw/riscv/Kconfig | 1 + hw/riscv/k230.c | 63 ++++++++++++++++++++++++++++++++++ include/hw/riscv/k230.h | 2 ++ tests/qtest/k230-dw-ssi-test.c | 55 +++++++++++++++++++++++++++++ 5 files changed, 122 insertions(+) diff --git a/docs/system/riscv/k230.rst b/docs/system/riscv/k230.rst index 7a49cac95b..620ef263de 100644 --- a/docs/system/riscv/k230.rst +++ b/docs/system/riscv/k230.rst @@ -21,6 +21,7 @@ The ``k230`` machine supports the following devices: * 2 K230 Watchdog Timer * 5 UART * 3 K230 SSI controllers for SPI only +* Optional SPI NOR flash on spi0 CS0 Boot options ------------ diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index 60a260691c..9d59465ad6 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -163,3 +163,4 @@ config K230 select UNIMP select K230_WDT select DW_SSI + select SSI_M25P80 diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c index d856864839..5dd49a6752 100644 --- a/hw/riscv/k230.c +++ b/hw/riscv/k230.c @@ -33,6 +33,7 @@ #include "hw/intc/sifive_plic.h" #include "hw/char/serial-mm.h" #include "hw/misc/unimp.h" +#include "hw/ssi/ssi.h" /* Align K230_SDK k230_canmv_defconfig */ #define K230_DIRECT_OPENSBI_ADDR 0x8000000 @@ -543,6 +544,49 @@ static void k230_firmware_boot(K230MachineState *s, MachineState *machine) memmap[K230_DEV_BOOTROM].size, 0, 0); } +static char *k230_machine_get_spi_flash(Object *obj, Error **errp) +{ + K230MachineState *s = RISCV_K230_MACHINE(obj); + + return g_strdup(s->spi_flash_model); +} + +static void k230_machine_set_spi_flash(Object *obj, const char *value, + Error **errp) +{ + K230MachineState *s = RISCV_K230_MACHINE(obj); + + g_free(s->spi_flash_model); + s->spi_flash_model = g_strdup(value); +} + +static void k230_connect_spi_flash(DwSsiState *ssi, unsigned int cs, + const char *flash_type, DriveInfo *dinfo) +{ + ObjectClass *flash_class; + DeviceState *flash; + qemu_irq flash_cs; + + flash_class = module_object_class_by_name(flash_type); + if (!flash_class || object_class_is_abstract(flash_class) || + !object_class_dynamic_cast(flash_class, TYPE_M25P80)) { + error_report("'%s' is either abstract or not a subtype of m25p80", + flash_type); + exit(EXIT_FAILURE); + } + + flash = qdev_new(flash_type); + + if (dinfo) { + qdev_prop_set_drive(flash, "drive", blk_by_legacy_dinfo(dinfo)); + } + + qdev_realize_and_unref(flash, BUS(ssi->spi), &error_fatal); + + flash_cs = qdev_get_gpio_in_named(flash, SSI_GPIO_CS, 0); + qdev_connect_gpio_out_named(DEVICE(ssi), "cs", cs, flash_cs); +} + static void k230_machine_done(Notifier *notifier, void *data) { K230MachineState *s = container_of(notifier, K230MachineState, @@ -574,6 +618,12 @@ static void k230_machine_init(MachineState *machine) TYPE_RISCV_K230_SOC); qdev_realize(DEVICE(&s->soc), NULL, &error_fatal); + if (s->spi_flash_model) { + k230_connect_spi_flash(&s->soc.dw_ssi[2], 0, + s->spi_flash_model, + drive_get(IF_MTD, 0, 0)); + } + /* Data Memory */ memory_region_add_subregion(sys_mem, memmap[K230_DEV_DDRC].base, machine->ram); @@ -586,6 +636,13 @@ static void k230_machine_instance_init(Object *obj) { } +static void k230_machine_instance_finalize(Object *obj) +{ + K230MachineState *s = RISCV_K230_MACHINE(obj); + + g_clear_pointer(&s->spi_flash_model, g_free); +} + static void k230_machine_class_init(ObjectClass *oc, const void *data) { MachineClass *mc = MACHINE_CLASS(oc); @@ -596,6 +653,11 @@ static void k230_machine_class_init(ObjectClass *oc, const void *data) mc->default_ram_id = "riscv.K230.ram"; /* DDR */ mc->default_ram_size = memmap[K230_DEV_DDRC].size; + object_class_property_add_str(oc, "spi-flash", + k230_machine_get_spi_flash, + k230_machine_set_spi_flash); + object_class_property_set_description( + oc, "spi-flash", "Attach an M25P80-compatible flash to spi0 CS0"); } static const TypeInfo k230_machine_typeinfo = { @@ -603,6 +665,7 @@ static const TypeInfo k230_machine_typeinfo = { .parent = TYPE_MACHINE, .class_init = k230_machine_class_init, .instance_init = k230_machine_instance_init, + .instance_finalize = k230_machine_instance_finalize, .instance_size = sizeof(K230MachineState), .interfaces = riscv64_machine_interfaces, }; diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h index 275b6b62c3..80fb4c0e69 100644 --- a/include/hw/riscv/k230.h +++ b/include/hw/riscv/k230.h @@ -52,6 +52,8 @@ typedef struct K230MachineState { /*< public >*/ K230SoCState soc; Notifier machine_done; + + char *spi_flash_model; } K230MachineState; enum { diff --git a/tests/qtest/k230-dw-ssi-test.c b/tests/qtest/k230-dw-ssi-test.c index 7252302c0e..f381be14b5 100644 --- a/tests/qtest/k230-dw-ssi-test.c +++ b/tests/qtest/k230-dw-ssi-test.c @@ -474,6 +474,59 @@ static void test_icr_total_clear(void) qtest_quit(qts); } +static void test_flash_jedec_id(void) +{ + QTestState *qts = qtest_init("-machine k230,spi-flash=m25p80"); + uint32_t id; + + k230_ssi_configure(qts, K230_SPI0_BASE, K230_SSI_TMOD_TR, 8, 0); + k230_ssi_enable_cs(qts, K230_SPI0_BASE, BIT(0)); + + /* JEDEC RDID (0x9f): m25p80 reports 0x20 0x20 0x14. */ + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x9f); + (void)k230_ssi_read_frame(qts, K230_SPI0_BASE); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x00); + id = k230_ssi_read_frame(qts, K230_SPI0_BASE); + g_assert_cmphex(id & 0xff, ==, 0x20); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x00); + id = k230_ssi_read_frame(qts, K230_SPI0_BASE); + g_assert_cmphex(id & 0xff, ==, 0x20); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x00); + id = k230_ssi_read_frame(qts, K230_SPI0_BASE); + g_assert_cmphex(id & 0xff, ==, 0x14); + + qtest_quit(qts); +} + +static void test_flash_fixed_read(void) +{ + QTestState *qts = qtest_init("-machine k230,spi-flash=m25p80"); + uint32_t byte; + + k230_ssi_configure(qts, K230_SPI0_BASE, K230_SSI_TMOD_TR, 8, 0); + k230_ssi_enable_cs(qts, K230_SPI0_BASE, BIT(0)); + + /* Standard 1-1-1 READ (0x03) at address 0x000000. */ + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x03); + (void)k230_ssi_read_frame(qts, K230_SPI0_BASE); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x00); + (void)k230_ssi_read_frame(qts, K230_SPI0_BASE); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x00); + (void)k230_ssi_read_frame(qts, K230_SPI0_BASE); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x00); + (void)k230_ssi_read_frame(qts, K230_SPI0_BASE); + + /* Without a backend the flash is erased and reads back 0xff. */ + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x00); + byte = k230_ssi_read_frame(qts, K230_SPI0_BASE); + g_assert_cmphex(byte & 0xff, ==, 0xff); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x00); + byte = k230_ssi_read_frame(qts, K230_SPI0_BASE); + g_assert_cmphex(byte & 0xff, ==, 0xff); + + qtest_quit(qts); +} + static void test_unsupported_registers(void) { QTestState *qts = k230_ssi_start(); @@ -510,5 +563,7 @@ int main(int argc, char **argv) qtest_add_func("/k230-dw-ssi/icr-total-clear", test_icr_total_clear); qtest_add_func("/k230-dw-ssi/unsupported-registers", test_unsupported_registers); + qtest_add_func("/k230-dw-ssi/flash-jedec-id", test_flash_jedec_id); + qtest_add_func("/k230-dw-ssi/flash-fixed-read", test_flash_fixed_read); return g_test_run(); } -- 2.43.0