[PATCH v3 5/5] hw/riscv/k230: Attach a standard SPI NOR flash
Kangjie Huang <[email protected]>
| Newsgroups | org.nongnu.qemu-devel,org.nongnu.qemu-riscv |
|---|---|
| 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 | 66 +++++++++++++++++++++++++ include/hw/riscv/k230.h | 2 + tests/qtest/k230-dwc-ssi-test.c | 85 +++++++++++++++++++++++++++++++++ 5 files changed, 155 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 0bc0d18ca2..94bf786ddc 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -163,3 +163,4 @@ config K230 select UNIMP select K230_WDT select DWC_SSI + select SSI_M25P80 diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c index 4911f2d1be..c2c101758d 100644 --- a/hw/riscv/k230.c +++ b/hw/riscv/k230.c @@ -21,7 +21,9 @@ #include "system/device_tree.h" #include "system/system.h" #include "system/memory.h" +#include "system/blockdev.h" #include "target/riscv/cpu.h" +#include "hw/block/flash.h" #include "hw/core/loader.h" #include "hw/core/sysbus.h" #include "hw/riscv/k230.h" @@ -31,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 @@ -541,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(DwcSsiState *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, @@ -572,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.dwc_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); @@ -584,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); @@ -593,6 +652,12 @@ static void k230_machine_class_init(ObjectClass *oc, const void *data) mc->default_cpus = 1; 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 = { @@ -600,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 4eee78ddec..1fe5713e53 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-dwc-ssi-test.c b/tests/qtest/k230-dwc-ssi-test.c index a522fc4973..807f143987 100644 --- a/tests/qtest/k230-dwc-ssi-test.c +++ b/tests/qtest/k230-dwc-ssi-test.c @@ -78,6 +78,7 @@ #define K230_SSI_XIP_MODE_BITS 0x0fc #define K230_SSI_FIFO_DEPTH 256 +#define K230_SSI_FLASH_SIZE MiB typedef struct K230SsiInstance { uint64_t base; @@ -577,6 +578,88 @@ 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"); + g_autofree uint8_t *image = g_malloc(K230_SSI_FLASH_SIZE); + g_autofree char *flash_path = NULL; + uint32_t byte; + int fd; + + 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); + + memset(image, 0xff, K230_SSI_FLASH_SIZE); + image[0x123] = 0x5a; + image[0x124] = 0xc3; + fd = g_file_open_tmp("qtest-k230-ssi-XXXXXX", &flash_path, NULL); + g_assert_cmpint(fd, >=, 0); + close(fd); + g_assert_true(g_file_set_contents(flash_path, (char *)image, + K230_SSI_FLASH_SIZE, NULL)); + + qts = qtest_initf("-machine k230,spi-flash=m25p80 " + "-drive file=%s,format=raw,if=mtd", flash_path); + k230_ssi_configure(qts, K230_SPI0_BASE, K230_SSI_TMOD_EEPROM_READ, + 8, 1); + k230_ssi_writel(qts, K230_SPI0_BASE, K230_SSI_SSIENR, 1); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x03); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x00); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x01); + k230_ssi_write_frame(qts, K230_SPI0_BASE, 0x23); + k230_ssi_writel(qts, K230_SPI0_BASE, K230_SSI_SER, BIT(0)); + k230_ssi_wait_mask(qts, K230_SPI0_BASE, K230_SSI_RXFLR, + UINT32_MAX, 2); + g_assert_cmphex(k230_ssi_read_frame(qts, K230_SPI0_BASE), ==, 0x5a); + g_assert_cmphex(k230_ssi_read_frame(qts, K230_SPI0_BASE), ==, 0xc3); + qtest_quit(qts); + g_assert_cmpint(g_unlink(flash_path), ==, 0); +} + static void test_unsupported_registers(void) { QTestState *qts = k230_ssi_start(); @@ -613,5 +696,7 @@ int main(int argc, char **argv) qtest_add_func("/k230-dwc-ssi/icr-total-clear", test_icr_total_clear); qtest_add_func("/k230-dwc-ssi/unsupported-registers", test_unsupported_registers); + qtest_add_func("/k230-dwc-ssi/flash-jedec-id", test_flash_jedec_id); + qtest_add_func("/k230-dwc-ssi/flash-fixed-read", test_flash_fixed_read); return g_test_run(); } -- 2.43.0