Message ID | 20200611223016.259837-11-hskinnemoen@google.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Add Nuvoton NPCM730/NPCM750 SoCs and two BMC machines | expand |
On 6/12/20 12:30 AM, Havard Skinnemoen wrote: > This implements a device model for the NPCM7xx SPI flash controller. > > Direct reads and writes, and user-mode transactions have been tested in > various modes. Protection features are not implemented yet. > > All the FIU instances are available in the SoC's address space, > regardless of whether or not they're connected to actual flash chips. > > Change-Id: Ia4985761931714c6ca35b505f47aef3f4008c1d8 > Reviewed-by: Tyrone Ting <kfting@nuvoton.com> > Signed-off-by: Havard Skinnemoen <hskinnemoen@google.com> LGTM, Reviewed-by: Cédric Le Goater <clg@kaod.org> > --- > MAINTAINERS | 2 + > hw/arm/Kconfig | 1 + > hw/arm/npcm7xx.c | 59 +++++ > hw/ssi/Makefile.objs | 1 + > hw/ssi/npcm7xx_fiu.c | 497 +++++++++++++++++++++++++++++++++++ > hw/ssi/trace-events | 9 + > include/hw/arm/npcm7xx.h | 2 + > include/hw/ssi/npcm7xx_fiu.h | 99 +++++++ > 8 files changed, 670 insertions(+) > create mode 100644 hw/ssi/npcm7xx_fiu.c > create mode 100644 include/hw/ssi/npcm7xx_fiu.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index 9a289366ba..8a5c08c2c2 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -730,11 +730,13 @@ F: hw/arm/npcm7xx* > F: hw/mem/npcm7xx* > F: hw/misc/npcm7xx* > F: hw/nvram/npcm7xx* > +F: hw/ssi/npcm7xx* > F: hw/timer/npcm7xx* > F: include/hw/arm/npcm7xx* > F: include/hw/mem/npcm7xx* > F: include/hw/misc/npcm7xx* > F: include/hw/nvram/npcm7xx* > +F: include/hw/ssi/npcm7xx* > F: include/hw/timer/npcm7xx* > > nSeries > diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig > index 3738c1f5cd..8cc99f6190 100644 > --- a/hw/arm/Kconfig > +++ b/hw/arm/Kconfig > @@ -360,6 +360,7 @@ config NPCM7XX > select ARM_GIC > select PL310 # cache controller > select SERIAL > + select SSI > select UNIMP > > config FSL_IMX25 > diff --git a/hw/arm/npcm7xx.c b/hw/arm/npcm7xx.c > index 54d84bafa1..50319d0d33 100644 > --- a/hw/arm/npcm7xx.c > +++ b/hw/arm/npcm7xx.c > @@ -98,6 +98,37 @@ static const hwaddr npcm7xx_uart_addr[] = { > 0xF0004000, > }; > > +static const hwaddr npcm7xx_fiu0_flash_addr[] = { > + 0x80000000, > + 0x88000000, > +}; > + > +static const hwaddr npcm7xx_fiu3_flash_addr[] = { > + 0xa0000000, > + 0xa8000000, > + 0xb0000000, > + 0xb8000000, > +}; > + > +static const struct { > + const char *name; > + hwaddr regs_addr; > + int cs_count; > + const hwaddr *flash_addr; > +} npcm7xx_fiu[] = { > + { > + .name = "fiu0", > + .regs_addr = 0xfb000000, > + .cs_count = ARRAY_SIZE(npcm7xx_fiu0_flash_addr), > + .flash_addr = npcm7xx_fiu0_flash_addr, > + }, { > + .name = "fiu3", > + .regs_addr = 0xc0000000, > + .cs_count = ARRAY_SIZE(npcm7xx_fiu3_flash_addr), > + .flash_addr = npcm7xx_fiu3_flash_addr, > + }, > +}; > + > void npcm7xx_write_secondary_boot(ARMCPU *cpu, const struct arm_boot_info *info) > { > /* > @@ -169,6 +200,13 @@ static void npcm7xx_init(Object *obj) > sysbus_init_child_obj(obj, "tim[*]", OBJECT(&s->tim[i]), > sizeof(s->tim[i]), TYPE_NPCM7XX_TIMER); > } > + > + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_fiu) != ARRAY_SIZE(s->fiu)); > + for (i = 0; i < ARRAY_SIZE(s->fiu); i++) { > + sysbus_init_child_obj(obj, npcm7xx_fiu[i].name, > + OBJECT(&s->fiu[i]), sizeof(s->fiu[i]), > + TYPE_NPCM7XX_FIU); > + } > } > > static void npcm7xx_realize(DeviceState *dev, Error **errp) > @@ -297,6 +335,27 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp) > serial_hd(i), DEVICE_LITTLE_ENDIAN); > } > > + /* Flash Interface Unit (FIU) */ > + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_fiu) != ARRAY_SIZE(s->fiu)); > + for (i = 0; i < ARRAY_SIZE(s->fiu); i++) { > + Object *o = OBJECT(&s->fiu[i]); > + int j; > + > + object_property_set_int(o, npcm7xx_fiu[i].cs_count, "cs-count", > + &error_abort); > + object_property_set_bool(o, true, "realized", &err); > + if (err) { > + error_propagate(errp, err); > + return; > + } > + > + sysbus_mmio_map(SYS_BUS_DEVICE(o), 0, npcm7xx_fiu[i].regs_addr); > + for (j = 0; j < npcm7xx_fiu[i].cs_count; j++) { > + sysbus_mmio_map(SYS_BUS_DEVICE(o), j + 1, > + npcm7xx_fiu[i].flash_addr[j]); > + } > + } > + > /* RAM2 (SRAM) */ > memory_region_init_ram(&s->sram, OBJECT(dev), "ram2", > NPCM7XX_RAM2_SZ, &err); > diff --git a/hw/ssi/Makefile.objs b/hw/ssi/Makefile.objs > index 07a85f1967..cab48e72c9 100644 > --- a/hw/ssi/Makefile.objs > +++ b/hw/ssi/Makefile.objs > @@ -5,6 +5,7 @@ common-obj-$(CONFIG_XILINX_SPIPS) += xilinx_spips.o > common-obj-$(CONFIG_ASPEED_SOC) += aspeed_smc.o > common-obj-$(CONFIG_STM32F2XX_SPI) += stm32f2xx_spi.o > common-obj-$(CONFIG_MSF2) += mss-spi.o > +common-obj-$(CONFIG_NPCM7XX) += npcm7xx_fiu.o > > common-obj-$(CONFIG_OMAP) += omap_spi.o > common-obj-$(CONFIG_IMX) += imx_spi.o > diff --git a/hw/ssi/npcm7xx_fiu.c b/hw/ssi/npcm7xx_fiu.c > new file mode 100644 > index 0000000000..dd7e1c8a4e > --- /dev/null > +++ b/hw/ssi/npcm7xx_fiu.c > @@ -0,0 +1,497 @@ > +/* > + * Nuvoton NPCM7xx Flash Interface Unit (FIU) > + * > + * Copyright 2020 Google LLC > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + */ > + > +#include "qemu/osdep.h" > + > +#include "hw/irq.h" > +#include "hw/qdev-properties.h" > +#include "hw/ssi/npcm7xx_fiu.h" > +#include "qapi/error.h" > +#include "qemu/error-report.h" > +#include "qemu/log.h" > +#include "qemu/module.h" > +#include "qemu/units.h" > + > +#include "trace.h" > + > +/* Up to 128 MiB of flash may be accessed directly as memory. */ > +#define NPCM7XX_FIU_FLASH_WINDOW_SIZE (128 * MiB) > + > +/* Each module has 4 KiB of register space. Only a fraction of it is used. */ > +#define NPCM7XX_FIU_CTRL_REGS_SIZE (4 * KiB) > + > +/* FIU_{DRD,DWR,UMA,PTR}_CFG cannot be written when this bit is set. */ > +#define NPCM7XX_FIU_CFG_LCK BIT(31) > + > +/* Direct Read configuration register fields. */ > +#define FIU_DRD_CFG_ADDSIZ(rv) extract32(rv, 16, 2) > +#define FIU_ADDSIZ_3BYTES 0 > +#define FIU_ADDSIZ_4BYTES 1 > +#define FIU_DRD_CFG_DBW(rv) extract32(rv, 12, 2) > +#define FIU_DRD_CFG_ACCTYPE(rv) extract32(rv, 8, 2) > +#define FIU_DRD_CFG_RDCMD(rv) extract32(rv, 0, 8) > + > +/* Direct Write configuration register fields. */ > +#define FIU_DWR_CFG_ADDSIZ(rv) extract32(rv, 16, 2) > +#define FIU_DWR_CFG_WRCMD(rv) extract32(rv, 0, 8) > + > slK0mser-Mode Access register fields. */ > + > +/* Command Mode Lock and the bits protected by it. */ > +#define FIU_UMA_CFG_CMMLCK BIT(30) > +#define FIU_UMA_CFG_CMMLCK_MASK 0x00000403 > + > +#define FIU_UMA_CFG_RDATSIZ(rv) extract32(rv, 24, 5) > +#define FIU_UMA_CFG_DBSIZ(rv) extract32(rv, 21, 3) > +#define FIU_UMA_CFG_WDATSIZ(rv) extract32(rv, 16, 5) > +#define FIU_UMA_CFG_ADDSIZ(rv) extract32(rv, 11, 3) > +#define FIU_UMA_CFG_CMDSIZ(rv) extract32(rv, 10, 1) > +#define FIU_UMA_CFG_DBPCK(rv) extract32(rv, 6, 2) > + > +#define FIU_UMA_CTS_RDYIE BIT(25) > +#define FIU_UMA_CTS_RDYST BIT(24) > +#define FIU_UMA_CTS_SW_CS BIT(16) > +#define FIU_UMA_CTS_DEV_NUM(rv) extract32(rv, 8, 2) > +#define FIU_UMA_CTS_EXEC_DONE BIT(0) > + > +/* Direct flash memory read handler. */ > +static uint64_t npcm7xx_fiu_flash_read(void *opaque, hwaddr addr, > + unsigned int size) > +{ > + NPCM7xxFIUFlash *f = opaque; > + NPCM7xxFIUState *fiu = f->fiu; > + int cs_id = f - fiu->flash; > + uint64_t value = 0; > + uint32_t drd_cfg; > + int dummy_cycles; > + int i; > + > + drd_cfg = fiu->regs[NPCM7XX_FIU_DRD_CFG]; > + > + qemu_irq_lower(fiu->cs_lines[cs_id]); > + ssi_transfer(fiu->spi, FIU_DRD_CFG_RDCMD(drd_cfg)); > + > + switch (FIU_DRD_CFG_ADDSIZ(drd_cfg)) { > + case FIU_ADDSIZ_4BYTES: > + ssi_transfer(fiu->spi, extract32(addr, 24, 8)); > + /* fall through */ > + case FIU_ADDSIZ_3BYTES: > + ssi_transfer(fiu->spi, extract32(addr, 16, 8)); > + ssi_transfer(fiu->spi, extract32(addr, 8, 8)); > + ssi_transfer(fiu->spi, extract32(addr, 0, 8)); > + break; > + > + default: > + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad address size %d\n", > + DEVICE(fiu)->canonical_path, FIU_DRD_CFG_ADDSIZ(drd_cfg)); > + break; > + } > + > + /* Flash chip model expects one transfer per dummy bit, not byte */ > + dummy_cycles = > + (FIU_DRD_CFG_DBW(drd_cfg) * 8) >> FIU_DRD_CFG_ACCTYPE(drd_cfg); > + for (i = 0; i < dummy_cycles; i++) { > + ssi_transfer(fiu->spi, 0); > + } > + > + for (i = 0; i < size; i++) { > + value |= ssi_transfer(fiu->spi, 0) << (8 * i); > + } > + > + qemu_irq_raise(fiu->cs_lines[cs_id]); > + > + trace_npcm7xx_fiu_flash_read(DEVICE(fiu)->canonical_path, cs_id, addr, size, > + value); > + > + return value; > +} > + > +/* Direct flash memory write handler. */ > +static void npcm7xx_fiu_flash_write(void *opaque, hwaddr addr, uint64_t v, > + unsigned int size) > +{ > + NPCM7xxFIUFlash *f = opaque; > + NPCM7xxFIUState *fiu = f->fiu; > + int cs_id = f - fiu->flash; > + uint32_t dwr_cfg; > + int i; > + > + trace_npcm7xx_fiu_flash_write(DEVICE(fiu)->canonical_path, cs_id, addr, > + size, v); > + > + dwr_cfg = fiu->regs[NPCM7XX_FIU_DWR_CFG]; > + > + qemu_irq_lower(fiu->cs_lines[cs_id]); > + ssi_transfer(fiu->spi, FIU_DWR_CFG_WRCMD(dwr_cfg)); > + > + switch (FIU_DWR_CFG_ADDSIZ(dwr_cfg)) { > + case FIU_ADDSIZ_4BYTES: > + ssi_transfer(fiu->spi, extract32(addr, 24, 8)); > + /* fall through */ > + case FIU_ADDSIZ_3BYTES: > + ssi_transfer(fiu->spi, extract32(addr, 16, 8)); > + ssi_transfer(fiu->spi, extract32(addr, 8, 8)); > + ssi_transfer(fiu->spi, extract32(addr, 0, 8)); > + break; > + > + default: > + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad address size %d\n", > + DEVICE(fiu)->canonical_path, FIU_DWR_CFG_ADDSIZ(dwr_cfg)); > + break; > + } > + > + for (i = 0; i < size; i++) { > + ssi_transfer(fiu->spi, v & 0xff); > + v >>= 8; > + } > + > + qemu_irq_raise(fiu->cs_lines[cs_id]); > +} > + > +static const MemoryRegionOps npcm7xx_fiu_flash_ops = { > + .read = npcm7xx_fiu_flash_read, > + .write = npcm7xx_fiu_flash_write, > + .endianness = DEVICE_LITTLE_ENDIAN, > + .valid = { > + .min_access_size = 1, > + .max_access_size = 8, > + .unaligned = true, > + }, > +}; > + > +/* Control register read handler. */ > +static uint64_t npcm7xx_fiu_ctrl_read(void *opaque, hwaddr addr, > + unsigned int size) > +{ > + hwaddr reg = addr / sizeof(uint32_t); > + NPCM7xxFIUState *s = opaque; > + uint32_t value; > + > + if (reg < NPCM7XX_FIO_NR_REGS) { > + value = s->regs[reg]; > + } else { > + qemu_log_mask(LOG_GUEST_ERROR, > + "%s: read from invalid offset 0x%" PRIx64 "\n", > + DEVICE(s)->canonical_path, addr); > + value = 0; > + } > + > + trace_npcm7xx_fiu_ctrl_read(DEVICE(s)->canonical_path, addr, value); > + > + return value; > +} > + > +/* Send the specified number of address bytes from the UMA address register. */ > +static void send_address(SSIBus *spi, unsigned int addsiz, uint32_t addr) > +{ > + /* All cases fall through */ > + switch (addsiz) { > + case 4: > + ssi_transfer(spi, extract32(addr, 24, 8)); > + case 3: > + ssi_transfer(spi, extract32(addr, 16, 8)); > + case 2: > + ssi_transfer(spi, extract32(addr, 8, 8)); > + case 1: > + ssi_transfer(spi, extract32(addr, 0, 8)); > + case 0: > + break; > + } > +} > + > +/* Send the number of dummy bits specified in the UMA config register. */ > +static void send_dummy_bits(SSIBus *spi, uint32_t uma_cfg, uint32_t uma_cmd) > +{ > + unsigned int bits_per_clock = 1U << FIU_UMA_CFG_DBPCK(uma_cfg); > + unsigned int i; > + > + for (i = 0; i < FIU_UMA_CFG_DBSIZ(uma_cfg); i++) { > + /* Use bytes 0 and 1 first, then keep repeating byte 2 */ > + unsigned int field = (i < 2) ? ((i + 1) * 8) : 24; > + unsigned int j; > + > + for (j = 0; j < 8; j += bits_per_clock) { > + ssi_transfer(spi, extract32(uma_cmd, field + j, bits_per_clock)); > + } > + } > +} > + > +/* Assert the chip select specified in the UMA Control/Status Register. */ > +static void npcm7xx_fiu_select(NPCM7xxFIUState *s) > +{ > + int cs_id; > + > + cs_id = FIU_UMA_CTS_DEV_NUM(s->regs[NPCM7XX_FIU_UMA_CTS]); > + if (cs_id < s->cs_count) { > + qemu_irq_lower(s->cs_lines[cs_id]); > + } else { > + qemu_log_mask(LOG_GUEST_ERROR, > + "%s: UMA to CS%d; this module has only %d chip selects", > + DEVICE(s)->canonical_path, cs_id, s->cs_count); > + cs_id = -1; > + } > + > + s->active_cs = cs_id; > +} > + > +/* Deassert the currently active chip select. */ > +static void npcm7xx_fiu_deselect(NPCM7xxFIUState *s) > +{ > + if (s->active_cs < 0) { > + return; > + } > + > + qemu_irq_raise(s->cs_lines[s->active_cs]); > + s->active_cs = -1; > +} > + > +/* Perform a User-Mode Access transaction. */ > +static void npcm7xx_fiu_uma_transaction(NPCM7xxFIUState *s) > +{ > + uint32_t uma_cts = s->regs[NPCM7XX_FIU_UMA_CTS]; > + uint32_t uma_cfg; > + unsigned int i; > + > + /* SW_CS means the CS is already forced low, so don't touch it. */ > + if (uma_cts & FIU_UMA_CTS_SW_CS) { > + npcm7xx_fiu_select(s); > + } > + > + /* Send command, if present. */ > + uma_cfg = s->regs[NPCM7XX_FIU_UMA_CFG]; > + if (FIU_UMA_CFG_CMDSIZ(uma_cfg) > 0) { > + ssi_transfer(s->spi, extract32(s->regs[NPCM7XX_FIU_UMA_CMD], 0, 8)); > + } > + > + /* Send address, if present. */ > + send_address(s->spi, FIU_UMA_CFG_ADDSIZ(uma_cfg), > + s->regs[NPCM7XX_FIU_UMA_ADDR]); > + > + /* Write data, if present. */ > + for (i = 0; i < FIU_UMA_CFG_WDATSIZ(uma_cfg); i++) { > + unsigned int reg = > + (i < 16) ? (NPCM7XX_FIU_UMA_DW0 + i / 4) : NPCM7XX_FIU_UMA_DW3; > + unsigned int field = (i % 4) * 8; > + > + ssi_transfer(s->spi, extract32(s->regs[reg], field, 8)); > + } > + > + /* Send aummy bits, if present. */ > + send_dummy_bits(s->spi, uma_cfg, s->regs[NPCM7XX_FIU_UMA_CMD]); > + > + /* Read data, if present. */ > + for (i = 0; i < FIU_UMA_CFG_RDATSIZ(uma_cfg); i++) { > + unsigned int reg = NPCM7XX_FIU_UMA_DR0 + i / 4; > + unsigned int field = (i % 4) * 8; > + uint8_t c; > + > + c = ssi_transfer(s->spi, 0); > + if (reg <= NPCM7XX_FIU_UMA_DR3) { > + s->regs[reg] = deposit32(s->regs[reg], field, 8, c); > + } > + } > + > + /* Again, don't touch CS if the user is forcing it low. */ > + if (uma_cts & FIU_UMA_CTS_SW_CS) { > + npcm7xx_fiu_deselect(s); > + } > + > + /* RDYST means a command has completed since it was cleared. */ > + s->regs[NPCM7XX_FIU_UMA_CTS] |= FIU_UMA_CTS_RDYST; > + /* EXEC_DONE means Execute Command / Not Done, so clear it here. */ > + s->regs[NPCM7XX_FIU_UMA_CTS] &= ~FIU_UMA_CTS_EXEC_DONE; > +} > + > +/* Control register write handler. */ > +static void npcm7xx_fiu_ctrl_write(void *opaque, hwaddr addr, uint64_t v, > + unsigned int size) > +{ > + hwaddr reg = addr / sizeof(uint32_t); > + NPCM7xxFIUState *s = opaque; > + uint32_t value = v; > + > + trace_npcm7xx_fiu_ctrl_write(DEVICE(s)->canonical_path, addr, value); > + > + switch (reg) { > + case NPCM7XX_FIU_UMA_CFG: > + if (s->regs[reg] & FIU_UMA_CFG_CMMLCK) { > + value &= ~FIU_UMA_CFG_CMMLCK_MASK; > + value |= (s->regs[reg] & FIU_UMA_CFG_CMMLCK_MASK); > + } > + /* fall through */ > + case NPCM7XX_FIU_DRD_CFG: > + case NPCM7XX_FIU_DWR_CFG: > + if (s->regs[reg] & NPCM7XX_FIU_CFG_LCK) { > + qemu_log_mask(LOG_GUEST_ERROR, > + "%s: write to locked register @ 0x%" PRIx64 "\n", > + DEVICE(s)->canonical_path, addr); > + return; > + } > + s->regs[reg] = value; > + break; > + > + case NPCM7XX_FIU_UMA_CTS: > + if (value & FIU_UMA_CTS_RDYST) { > + value &= ~FIU_UMA_CTS_RDYST; > + } else { > + value |= s->regs[reg] & FIU_UMA_CTS_RDYST; > + } > + if ((s->regs[reg] ^ value) & FIU_UMA_CTS_SW_CS) { > + if (value & FIU_UMA_CTS_SW_CS) { > + /* > + * Don't drop CS if there's a transfer in progress, or we're > + * about to start one. > + */ > + if (!((value | s->regs[reg]) & FIU_UMA_CTS_EXEC_DONE)) { > + npcm7xx_fiu_deselect(s); > + } > + } else { > + npcm7xx_fiu_select(s); > + } > + } > + s->regs[reg] = value | (s->regs[reg] & FIU_UMA_CTS_EXEC_DONE); > + if (value & FIU_UMA_CTS_EXEC_DONE) { > + npcm7xx_fiu_uma_transaction(s); > + } > + break; > + > + case NPCM7XX_FIU_UMA_DR0 ... NPCM7XX_FIU_UMA_DR3: > + qemu_log_mask(LOG_GUEST_ERROR, > + "%s: write to read-only register @ 0x%" PRIx64 "\n", > + DEVICE(s)->canonical_path, addr); > + return; > + > + case NPCM7XX_FIU_PRT_CFG: > + case NPCM7XX_FIU_PRT_CMD0 ... NPCM7XX_FIU_PRT_CMD9: > + qemu_log_mask(LOG_UNIMP, "%s: PRT is not implemented\n", __func__); > + break; > + > + case NPCM7XX_FIU_UMA_CMD: > + case NPCM7XX_FIU_UMA_ADDR: > + case NPCM7XX_FIU_UMA_DW0 ... NPCM7XX_FIU_UMA_DW3: > + case NPCM7XX_FIU_CFG: > + s->regs[reg] = value; > + break; > + > + default: > + qemu_log_mask(LOG_GUEST_ERROR, > + "%s: write to invalid offset 0x%" PRIx64 "\n", > + DEVICE(s)->canonical_path, addr); > + return; > + } > +} > + > +static const MemoryRegionOps npcm7xx_fiu_ctrl_ops = { > + .read = npcm7xx_fiu_ctrl_read, > + .write = npcm7xx_fiu_ctrl_write, > + .endianness = DEVICE_LITTLE_ENDIAN, > + .valid = { > + .min_access_size = 4, > + .max_access_size = 4, > + .unaligned = false, > + }, > +}; > + > +static void npcm7xx_fiu_enter_reset(Object *obj, ResetType type) > +{ > + NPCM7xxFIUState *s = NPCM7XX_FIU(obj); > + > + trace_npcm7xx_fiu_enter_reset(DEVICE(obj)->canonical_path, type); > + > + memset(s->regs, 0, sizeof(s->regs)); > + > + s->regs[NPCM7XX_FIU_DRD_CFG] = 0x0300100b; > + s->regs[NPCM7XX_FIU_DWR_CFG] = 0x03000002; > + s->regs[NPCM7XX_FIU_UMA_CFG] = 0x00000400; > + s->regs[NPCM7XX_FIU_UMA_CTS] = 0x00010000; > + s->regs[NPCM7XX_FIU_UMA_CMD] = 0x0000000b; > + s->regs[NPCM7XX_FIU_PRT_CFG] = 0x00000400; > + s->regs[NPCM7XX_FIU_CFG] = 0x0000000b; > +} > + > +static void npcm7xx_fiu_hold_reset(Object *obj) > +{ > + NPCM7xxFIUState *s = NPCM7XX_FIU(obj); > + int i; > + > + trace_npcm7xx_fiu_hold_reset(DEVICE(obj)->canonical_path); > + > + for (i = 0; i < s->cs_count; i++) { > + qemu_irq_raise(s->cs_lines[i]); > + } > +} > + > +static void npcm7xx_fiu_realize(DeviceState *dev, Error **errp) > +{ > + NPCM7xxFIUState *s = NPCM7XX_FIU(dev); > + SysBusDevice *sbd = &s->parent; > + int i; > + > + if (s->cs_count <= 0) { > + error_setg(errp, "%s: %d chip selects specified, need at least one", > + dev->canonical_path, s->cs_count); > + return; > + } > + > + s->spi = ssi_create_bus(dev, "spi"); > + s->cs_lines = g_new0(qemu_irq, s->cs_count); > + ssi_auto_connect_slaves(dev, s->cs_lines, s->spi); ssi_auto_connect_slaves() is gone. > + s->flash = g_new0(NPCM7xxFIUFlash, s->cs_count); > + > + /* > + * Register the control registers region first. It may be followed by one > + * or more direct flash access regions. > + */ > + memory_region_init_io(&s->mmio, OBJECT(s), &npcm7xx_fiu_ctrl_ops, s, "ctrl", > + NPCM7XX_FIU_CTRL_REGS_SIZE); > + sysbus_init_mmio(sbd, &s->mmio); > + > + for (i = 0; i < s->cs_count; i++) { > + NPCM7xxFIUFlash *flash = &s->flash[i]; > + sysbus_init_irq(sbd, &s->cs_lines[i]); > + flash->fiu = s; > + memory_region_init_io(&flash->direct_access, OBJECT(s), > + &npcm7xx_fiu_flash_ops, &s->flash[i], "flash", > + NPCM7XX_FIU_FLASH_WINDOW_SIZE); > + sysbus_init_mmio(sbd, &flash->direct_access); > + } > +} > + > +static Property npcm7xx_fiu_properties[] = { > + DEFINE_PROP_INT32("cs-count", NPCM7xxFIUState, cs_count, 0), > + DEFINE_PROP_END_OF_LIST(), > +}; > + > +static void npcm7xx_fiu_class_init(ObjectClass *klass, void *data) > +{ > + ResettableClass *rc = RESETTABLE_CLASS(klass); > + DeviceClass *dc = DEVICE_CLASS(klass); > + > + dc->desc = "NPCM7xx Flash Interface Unit"; > + dc->realize = npcm7xx_fiu_realize; > + rc->phases.enter = npcm7xx_fiu_enter_reset; > + rc->phases.hold = npcm7xx_fiu_hold_reset; > + device_class_set_props(dc, npcm7xx_fiu_properties); > +} > + > +static const TypeInfo npcm7xx_fiu_types[] = { > + { > + .name = TYPE_NPCM7XX_FIU, > + .parent = TYPE_SYS_BUS_DEVICE, > + .instance_size = sizeof(NPCM7xxFIUState), > + .class_init = npcm7xx_fiu_class_init, > + }, > +}; > +DEFINE_TYPES(npcm7xx_fiu_types); > diff --git a/hw/ssi/trace-events b/hw/ssi/trace-events > index 0ea498de91..8024253c1f 100644 > --- a/hw/ssi/trace-events > +++ b/hw/ssi/trace-events > @@ -9,3 +9,12 @@ aspeed_smc_dma_checksum(uint32_t addr, uint32_t data) "0x%08x: 0x%08x" > aspeed_smc_dma_rw(const char *dir, uint32_t flash_addr, uint32_t dram_addr, uint32_t size) "%s flash:@0x%08x dram:@0x%08x size:0x%08x" > aspeed_smc_write(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PRIx64 " size %u: 0x%" PRIx64 > aspeed_smc_flash_select(int cs, const char *prefix) "CS%d %sselect" > + > +# npcm7xx_fiu.c > + > +npcm7xx_fiu_enter_reset(const char *id, int reset_type) "%s reset type: %d" > +npcm7xx_fiu_hold_reset(const char *id) "%s" > +npcm7xx_fiu_ctrl_read(const char *id, uint64_t addr, uint32_t data) "%s offset: 0x%04" PRIx64 " value: 0x%08x" > +npcm7xx_fiu_ctrl_write(const char *id, uint64_t addr, uint32_t data) "%s offset: 0x%04" PRIx64 " value: 0x%08x" > +npcm7xx_fiu_flash_read(const char *id, int cs, uint64_t addr, unsigned int size, uint64_t value) "%s[%d] offset: 0x%08" PRIx64 " size: %u value: 0x%" PRIx64 > +npcm7xx_fiu_flash_write(const char *id, int cs, uint64_t addr, unsigned int size, uint64_t value) "%s[%d] offset: 0x%08" PRIx64 " size: %u value: 0x%" PRIx64 > diff --git a/include/hw/arm/npcm7xx.h b/include/hw/arm/npcm7xx.h > index 8f49f7015c..e357f4bf59 100644 > --- a/include/hw/arm/npcm7xx.h > +++ b/include/hw/arm/npcm7xx.h > @@ -22,6 +22,7 @@ > #include "hw/misc/npcm7xx_gcr.h" > #include "hw/nvram/npcm7xx_otp.h" > #include "hw/timer/npcm7xx_timer.h" > +#include "hw/ssi/npcm7xx_fiu.h" > #include "target/arm/cpu.h" > > #define NPCM7XX_MAX_NUM_CPUS (2) > @@ -67,6 +68,7 @@ typedef struct NPCM7xxState { > NPCM7xxOTPState key_storage; > NPCM7xxOTPState fuse_array; > NPCM7xxMCState mc; > + NPCM7xxFIUState fiu[2]; > } NPCM7xxState; > > #define TYPE_NPCM7XX "npcm7xx" > diff --git a/include/hw/ssi/npcm7xx_fiu.h b/include/hw/ssi/npcm7xx_fiu.h > new file mode 100644 > index 0000000000..88bfd4ba8e > --- /dev/null > +++ b/include/hw/ssi/npcm7xx_fiu.h > @@ -0,0 +1,99 @@ > +/* > + * Nuvoton NPCM7xx Flash Interface Unit (FIU) > + * > + * Copyright 2020 Google LLC > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + */ > +#ifndef NPCM7XX_FIU_H > +#define NPCM7XX_FIU_H > + > +#include "hw/ssi/ssi.h" > +#include "hw/sysbus.h" > + > +/** > + * enum NPCM7xxFIURegister - 32-bit FIU register indices. > + */ > +enum NPCM7xxFIURegister { > + NPCM7XX_FIU_DRD_CFG, > + NPCM7XX_FIU_DWR_CFG, > + NPCM7XX_FIU_UMA_CFG, > + NPCM7XX_FIU_UMA_CTS, > + NPCM7XX_FIU_UMA_CMD, > + NPCM7XX_FIU_UMA_ADDR, > + NPCM7XX_FIU_PRT_CFG, > + NPCM7XX_FIU_UMA_DW0 = 0x0020 / sizeof(uint32_t), > + NPCM7XX_FIU_UMA_DW1, > + NPCM7XX_FIU_UMA_DW2, > + NPCM7XX_FIU_UMA_DW3, > + NPCM7XX_FIU_UMA_DR0, > + NPCM7XX_FIU_UMA_DR1, > + NPCM7XX_FIU_UMA_DR2, > + NPCM7XX_FIU_UMA_DR3, > + NPCM7XX_FIU_PRT_CMD0, > + NPCM7XX_FIU_PRT_CMD1, > + NPCM7XX_FIU_PRT_CMD2, > + NPCM7XX_FIU_PRT_CMD3, > + NPCM7XX_FIU_PRT_CMD4, > + NPCM7XX_FIU_PRT_CMD5, > + NPCM7XX_FIU_PRT_CMD6, > + NPCM7XX_FIU_PRT_CMD7, > + NPCM7XX_FIU_PRT_CMD8, > + NPCM7XX_FIU_PRT_CMD9, > + NPCM7XX_FIU_CFG = 0x78 / sizeof(uint32_t), > + NPCM7XX_FIO_NR_REGS, > +}; > + > +typedef struct NPCM7xxFIUState NPCM7xxFIUState; > + > +/** > + * struct NPCM7xxFIUFlash - Per-chipselect flash controller state. > + * @direct_access: Memory region for direct flash access. > + * @fiu: Pointer to flash controller shared state. > + */ > +typedef struct NPCM7xxFIUFlash { > + MemoryRegion direct_access; > + NPCM7xxFIUState *fiu; > +} NPCM7xxFIUFlash; > + > +/** > + * NPCM7xxFIUState - Device state for one Flash Interface Unit. > + * @parent: System bus device. > + * @mmio: Memory region for register access. > + * @cs_count: Number of flash chips that may be connected to this module. > + * @active_cs: Currently active chip select, or -1 if no chip is selected. > + * @cs_lines: GPIO lines that may be wired to flash chips. > + * @flash: Array of @cs_count per-flash-chip state objects. > + * @spi: The SPI bus mastered by this controller. > + * @regs: Register contents. > + * > + * Each FIU has a shared bank of registers, and controls up to four chip > + * selects. Each chip select has a dedicated memory region which may be used to > + * read and write the flash connected to that chip select as if it were memory. > + */ > +struct NPCM7xxFIUState { > + SysBusDevice parent; > + > + MemoryRegion mmio; > + > + int32_t cs_count; > + int32_t active_cs; > + qemu_irq *cs_lines; > + NPCM7xxFIUFlash *flash; > + > + SSIBus *spi; > + > + uint32_t regs[NPCM7XX_FIO_NR_REGS]; > +}; > + > +#define TYPE_NPCM7XX_FIU "npcm7xx-fiu" > +#define NPCM7XX_FIU(obj) OBJECT_CHECK(NPCM7xxFIUState, (obj), TYPE_NPCM7XX_FIU) > + > +#endif /* NPCM7XX_FIU_H */ >
diff --git a/MAINTAINERS b/MAINTAINERS index 9a289366ba..8a5c08c2c2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -730,11 +730,13 @@ F: hw/arm/npcm7xx* F: hw/mem/npcm7xx* F: hw/misc/npcm7xx* F: hw/nvram/npcm7xx* +F: hw/ssi/npcm7xx* F: hw/timer/npcm7xx* F: include/hw/arm/npcm7xx* F: include/hw/mem/npcm7xx* F: include/hw/misc/npcm7xx* F: include/hw/nvram/npcm7xx* +F: include/hw/ssi/npcm7xx* F: include/hw/timer/npcm7xx* nSeries diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 3738c1f5cd..8cc99f6190 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -360,6 +360,7 @@ config NPCM7XX select ARM_GIC select PL310 # cache controller select SERIAL + select SSI select UNIMP config FSL_IMX25 diff --git a/hw/arm/npcm7xx.c b/hw/arm/npcm7xx.c index 54d84bafa1..50319d0d33 100644 --- a/hw/arm/npcm7xx.c +++ b/hw/arm/npcm7xx.c @@ -98,6 +98,37 @@ static const hwaddr npcm7xx_uart_addr[] = { 0xF0004000, }; +static const hwaddr npcm7xx_fiu0_flash_addr[] = { + 0x80000000, + 0x88000000, +}; + +static const hwaddr npcm7xx_fiu3_flash_addr[] = { + 0xa0000000, + 0xa8000000, + 0xb0000000, + 0xb8000000, +}; + +static const struct { + const char *name; + hwaddr regs_addr; + int cs_count; + const hwaddr *flash_addr; +} npcm7xx_fiu[] = { + { + .name = "fiu0", + .regs_addr = 0xfb000000, + .cs_count = ARRAY_SIZE(npcm7xx_fiu0_flash_addr), + .flash_addr = npcm7xx_fiu0_flash_addr, + }, { + .name = "fiu3", + .regs_addr = 0xc0000000, + .cs_count = ARRAY_SIZE(npcm7xx_fiu3_flash_addr), + .flash_addr = npcm7xx_fiu3_flash_addr, + }, +}; + void npcm7xx_write_secondary_boot(ARMCPU *cpu, const struct arm_boot_info *info) { /* @@ -169,6 +200,13 @@ static void npcm7xx_init(Object *obj) sysbus_init_child_obj(obj, "tim[*]", OBJECT(&s->tim[i]), sizeof(s->tim[i]), TYPE_NPCM7XX_TIMER); } + + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_fiu) != ARRAY_SIZE(s->fiu)); + for (i = 0; i < ARRAY_SIZE(s->fiu); i++) { + sysbus_init_child_obj(obj, npcm7xx_fiu[i].name, + OBJECT(&s->fiu[i]), sizeof(s->fiu[i]), + TYPE_NPCM7XX_FIU); + } } static void npcm7xx_realize(DeviceState *dev, Error **errp) @@ -297,6 +335,27 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp) serial_hd(i), DEVICE_LITTLE_ENDIAN); } + /* Flash Interface Unit (FIU) */ + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_fiu) != ARRAY_SIZE(s->fiu)); + for (i = 0; i < ARRAY_SIZE(s->fiu); i++) { + Object *o = OBJECT(&s->fiu[i]); + int j; + + object_property_set_int(o, npcm7xx_fiu[i].cs_count, "cs-count", + &error_abort); + object_property_set_bool(o, true, "realized", &err); + if (err) { + error_propagate(errp, err); + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(o), 0, npcm7xx_fiu[i].regs_addr); + for (j = 0; j < npcm7xx_fiu[i].cs_count; j++) { + sysbus_mmio_map(SYS_BUS_DEVICE(o), j + 1, + npcm7xx_fiu[i].flash_addr[j]); + } + } + /* RAM2 (SRAM) */ memory_region_init_ram(&s->sram, OBJECT(dev), "ram2", NPCM7XX_RAM2_SZ, &err); diff --git a/hw/ssi/Makefile.objs b/hw/ssi/Makefile.objs index 07a85f1967..cab48e72c9 100644 --- a/hw/ssi/Makefile.objs +++ b/hw/ssi/Makefile.objs @@ -5,6 +5,7 @@ common-obj-$(CONFIG_XILINX_SPIPS) += xilinx_spips.o common-obj-$(CONFIG_ASPEED_SOC) += aspeed_smc.o common-obj-$(CONFIG_STM32F2XX_SPI) += stm32f2xx_spi.o common-obj-$(CONFIG_MSF2) += mss-spi.o +common-obj-$(CONFIG_NPCM7XX) += npcm7xx_fiu.o common-obj-$(CONFIG_OMAP) += omap_spi.o common-obj-$(CONFIG_IMX) += imx_spi.o diff --git a/hw/ssi/npcm7xx_fiu.c b/hw/ssi/npcm7xx_fiu.c new file mode 100644 index 0000000000..dd7e1c8a4e --- /dev/null +++ b/hw/ssi/npcm7xx_fiu.c @@ -0,0 +1,497 @@ +/* + * Nuvoton NPCM7xx Flash Interface Unit (FIU) + * + * Copyright 2020 Google LLC + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "qemu/osdep.h" + +#include "hw/irq.h" +#include "hw/qdev-properties.h" +#include "hw/ssi/npcm7xx_fiu.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/units.h" + +#include "trace.h" + +/* Up to 128 MiB of flash may be accessed directly as memory. */ +#define NPCM7XX_FIU_FLASH_WINDOW_SIZE (128 * MiB) + +/* Each module has 4 KiB of register space. Only a fraction of it is used. */ +#define NPCM7XX_FIU_CTRL_REGS_SIZE (4 * KiB) + +/* FIU_{DRD,DWR,UMA,PTR}_CFG cannot be written when this bit is set. */ +#define NPCM7XX_FIU_CFG_LCK BIT(31) + +/* Direct Read configuration register fields. */ +#define FIU_DRD_CFG_ADDSIZ(rv) extract32(rv, 16, 2) +#define FIU_ADDSIZ_3BYTES 0 +#define FIU_ADDSIZ_4BYTES 1 +#define FIU_DRD_CFG_DBW(rv) extract32(rv, 12, 2) +#define FIU_DRD_CFG_ACCTYPE(rv) extract32(rv, 8, 2) +#define FIU_DRD_CFG_RDCMD(rv) extract32(rv, 0, 8) + +/* Direct Write configuration register fields. */ +#define FIU_DWR_CFG_ADDSIZ(rv) extract32(rv, 16, 2) +#define FIU_DWR_CFG_WRCMD(rv) extract32(rv, 0, 8) + +/* User-Mode Access register fields. */ + +/* Command Mode Lock and the bits protected by it. */ +#define FIU_UMA_CFG_CMMLCK BIT(30) +#define FIU_UMA_CFG_CMMLCK_MASK 0x00000403 + +#define FIU_UMA_CFG_RDATSIZ(rv) extract32(rv, 24, 5) +#define FIU_UMA_CFG_DBSIZ(rv) extract32(rv, 21, 3) +#define FIU_UMA_CFG_WDATSIZ(rv) extract32(rv, 16, 5) +#define FIU_UMA_CFG_ADDSIZ(rv) extract32(rv, 11, 3) +#define FIU_UMA_CFG_CMDSIZ(rv) extract32(rv, 10, 1) +#define FIU_UMA_CFG_DBPCK(rv) extract32(rv, 6, 2) + +#define FIU_UMA_CTS_RDYIE BIT(25) +#define FIU_UMA_CTS_RDYST BIT(24) +#define FIU_UMA_CTS_SW_CS BIT(16) +#define FIU_UMA_CTS_DEV_NUM(rv) extract32(rv, 8, 2) +#define FIU_UMA_CTS_EXEC_DONE BIT(0) + +/* Direct flash memory read handler. */ +static uint64_t npcm7xx_fiu_flash_read(void *opaque, hwaddr addr, + unsigned int size) +{ + NPCM7xxFIUFlash *f = opaque; + NPCM7xxFIUState *fiu = f->fiu; + int cs_id = f - fiu->flash; + uint64_t value = 0; + uint32_t drd_cfg; + int dummy_cycles; + int i; + + drd_cfg = fiu->regs[NPCM7XX_FIU_DRD_CFG]; + + qemu_irq_lower(fiu->cs_lines[cs_id]); + ssi_transfer(fiu->spi, FIU_DRD_CFG_RDCMD(drd_cfg)); + + switch (FIU_DRD_CFG_ADDSIZ(drd_cfg)) { + case FIU_ADDSIZ_4BYTES: + ssi_transfer(fiu->spi, extract32(addr, 24, 8)); + /* fall through */ + case FIU_ADDSIZ_3BYTES: + ssi_transfer(fiu->spi, extract32(addr, 16, 8)); + ssi_transfer(fiu->spi, extract32(addr, 8, 8)); + ssi_transfer(fiu->spi, extract32(addr, 0, 8)); + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad address size %d\n", + DEVICE(fiu)->canonical_path, FIU_DRD_CFG_ADDSIZ(drd_cfg)); + break; + } + + /* Flash chip model expects one transfer per dummy bit, not byte */ + dummy_cycles = + (FIU_DRD_CFG_DBW(drd_cfg) * 8) >> FIU_DRD_CFG_ACCTYPE(drd_cfg); + for (i = 0; i < dummy_cycles; i++) { + ssi_transfer(fiu->spi, 0); + } + + for (i = 0; i < size; i++) { + value |= ssi_transfer(fiu->spi, 0) << (8 * i); + } + + qemu_irq_raise(fiu->cs_lines[cs_id]); + + trace_npcm7xx_fiu_flash_read(DEVICE(fiu)->canonical_path, cs_id, addr, size, + value); + + return value; +} + +/* Direct flash memory write handler. */ +static void npcm7xx_fiu_flash_write(void *opaque, hwaddr addr, uint64_t v, + unsigned int size) +{ + NPCM7xxFIUFlash *f = opaque; + NPCM7xxFIUState *fiu = f->fiu; + int cs_id = f - fiu->flash; + uint32_t dwr_cfg; + int i; + + trace_npcm7xx_fiu_flash_write(DEVICE(fiu)->canonical_path, cs_id, addr, + size, v); + + dwr_cfg = fiu->regs[NPCM7XX_FIU_DWR_CFG]; + + qemu_irq_lower(fiu->cs_lines[cs_id]); + ssi_transfer(fiu->spi, FIU_DWR_CFG_WRCMD(dwr_cfg)); + + switch (FIU_DWR_CFG_ADDSIZ(dwr_cfg)) { + case FIU_ADDSIZ_4BYTES: + ssi_transfer(fiu->spi, extract32(addr, 24, 8)); + /* fall through */ + case FIU_ADDSIZ_3BYTES: + ssi_transfer(fiu->spi, extract32(addr, 16, 8)); + ssi_transfer(fiu->spi, extract32(addr, 8, 8)); + ssi_transfer(fiu->spi, extract32(addr, 0, 8)); + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad address size %d\n", + DEVICE(fiu)->canonical_path, FIU_DWR_CFG_ADDSIZ(dwr_cfg)); + break; + } + + for (i = 0; i < size; i++) { + ssi_transfer(fiu->spi, v & 0xff); + v >>= 8; + } + + qemu_irq_raise(fiu->cs_lines[cs_id]); +} + +static const MemoryRegionOps npcm7xx_fiu_flash_ops = { + .read = npcm7xx_fiu_flash_read, + .write = npcm7xx_fiu_flash_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + .unaligned = true, + }, +}; + +/* Control register read handler. */ +static uint64_t npcm7xx_fiu_ctrl_read(void *opaque, hwaddr addr, + unsigned int size) +{ + hwaddr reg = addr / sizeof(uint32_t); + NPCM7xxFIUState *s = opaque; + uint32_t value; + + if (reg < NPCM7XX_FIO_NR_REGS) { + value = s->regs[reg]; + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: read from invalid offset 0x%" PRIx64 "\n", + DEVICE(s)->canonical_path, addr); + value = 0; + } + + trace_npcm7xx_fiu_ctrl_read(DEVICE(s)->canonical_path, addr, value); + + return value; +} + +/* Send the specified number of address bytes from the UMA address register. */ +static void send_address(SSIBus *spi, unsigned int addsiz, uint32_t addr) +{ + /* All cases fall through */ + switch (addsiz) { + case 4: + ssi_transfer(spi, extract32(addr, 24, 8)); + case 3: + ssi_transfer(spi, extract32(addr, 16, 8)); + case 2: + ssi_transfer(spi, extract32(addr, 8, 8)); + case 1: + ssi_transfer(spi, extract32(addr, 0, 8)); + case 0: + break; + } +} + +/* Send the number of dummy bits specified in the UMA config register. */ +static void send_dummy_bits(SSIBus *spi, uint32_t uma_cfg, uint32_t uma_cmd) +{ + unsigned int bits_per_clock = 1U << FIU_UMA_CFG_DBPCK(uma_cfg); + unsigned int i; + + for (i = 0; i < FIU_UMA_CFG_DBSIZ(uma_cfg); i++) { + /* Use bytes 0 and 1 first, then keep repeating byte 2 */ + unsigned int field = (i < 2) ? ((i + 1) * 8) : 24; + unsigned int j; + + for (j = 0; j < 8; j += bits_per_clock) { + ssi_transfer(spi, extract32(uma_cmd, field + j, bits_per_clock)); + } + } +} + +/* Assert the chip select specified in the UMA Control/Status Register. */ +static void npcm7xx_fiu_select(NPCM7xxFIUState *s) +{ + int cs_id; + + cs_id = FIU_UMA_CTS_DEV_NUM(s->regs[NPCM7XX_FIU_UMA_CTS]); + if (cs_id < s->cs_count) { + qemu_irq_lower(s->cs_lines[cs_id]); + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: UMA to CS%d; this module has only %d chip selects", + DEVICE(s)->canonical_path, cs_id, s->cs_count); + cs_id = -1; + } + + s->active_cs = cs_id; +} + +/* Deassert the currently active chip select. */ +static void npcm7xx_fiu_deselect(NPCM7xxFIUState *s) +{ + if (s->active_cs < 0) { + return; + } + + qemu_irq_raise(s->cs_lines[s->active_cs]); + s->active_cs = -1; +} + +/* Perform a User-Mode Access transaction. */ +static void npcm7xx_fiu_uma_transaction(NPCM7xxFIUState *s) +{ + uint32_t uma_cts = s->regs[NPCM7XX_FIU_UMA_CTS]; + uint32_t uma_cfg; + unsigned int i; + + /* SW_CS means the CS is already forced low, so don't touch it. */ + if (uma_cts & FIU_UMA_CTS_SW_CS) { + npcm7xx_fiu_select(s); + } + + /* Send command, if present. */ + uma_cfg = s->regs[NPCM7XX_FIU_UMA_CFG]; + if (FIU_UMA_CFG_CMDSIZ(uma_cfg) > 0) { + ssi_transfer(s->spi, extract32(s->regs[NPCM7XX_FIU_UMA_CMD], 0, 8)); + } + + /* Send address, if present. */ + send_address(s->spi, FIU_UMA_CFG_ADDSIZ(uma_cfg), + s->regs[NPCM7XX_FIU_UMA_ADDR]); + + /* Write data, if present. */ + for (i = 0; i < FIU_UMA_CFG_WDATSIZ(uma_cfg); i++) { + unsigned int reg = + (i < 16) ? (NPCM7XX_FIU_UMA_DW0 + i / 4) : NPCM7XX_FIU_UMA_DW3; + unsigned int field = (i % 4) * 8; + + ssi_transfer(s->spi, extract32(s->regs[reg], field, 8)); + } + + /* Send aummy bits, if present. */ + send_dummy_bits(s->spi, uma_cfg, s->regs[NPCM7XX_FIU_UMA_CMD]); + + /* Read data, if present. */ + for (i = 0; i < FIU_UMA_CFG_RDATSIZ(uma_cfg); i++) { + unsigned int reg = NPCM7XX_FIU_UMA_DR0 + i / 4; + unsigned int field = (i % 4) * 8; + uint8_t c; + + c = ssi_transfer(s->spi, 0); + if (reg <= NPCM7XX_FIU_UMA_DR3) { + s->regs[reg] = deposit32(s->regs[reg], field, 8, c); + } + } + + /* Again, don't touch CS if the user is forcing it low. */ + if (uma_cts & FIU_UMA_CTS_SW_CS) { + npcm7xx_fiu_deselect(s); + } + + /* RDYST means a command has completed since it was cleared. */ + s->regs[NPCM7XX_FIU_UMA_CTS] |= FIU_UMA_CTS_RDYST; + /* EXEC_DONE means Execute Command / Not Done, so clear it here. */ + s->regs[NPCM7XX_FIU_UMA_CTS] &= ~FIU_UMA_CTS_EXEC_DONE; +} + +/* Control register write handler. */ +static void npcm7xx_fiu_ctrl_write(void *opaque, hwaddr addr, uint64_t v, + unsigned int size) +{ + hwaddr reg = addr / sizeof(uint32_t); + NPCM7xxFIUState *s = opaque; + uint32_t value = v; + + trace_npcm7xx_fiu_ctrl_write(DEVICE(s)->canonical_path, addr, value); + + switch (reg) { + case NPCM7XX_FIU_UMA_CFG: + if (s->regs[reg] & FIU_UMA_CFG_CMMLCK) { + value &= ~FIU_UMA_CFG_CMMLCK_MASK; + value |= (s->regs[reg] & FIU_UMA_CFG_CMMLCK_MASK); + } + /* fall through */ + case NPCM7XX_FIU_DRD_CFG: + case NPCM7XX_FIU_DWR_CFG: + if (s->regs[reg] & NPCM7XX_FIU_CFG_LCK) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to locked register @ 0x%" PRIx64 "\n", + DEVICE(s)->canonical_path, addr); + return; + } + s->regs[reg] = value; + break; + + case NPCM7XX_FIU_UMA_CTS: + if (value & FIU_UMA_CTS_RDYST) { + value &= ~FIU_UMA_CTS_RDYST; + } else { + value |= s->regs[reg] & FIU_UMA_CTS_RDYST; + } + if ((s->regs[reg] ^ value) & FIU_UMA_CTS_SW_CS) { + if (value & FIU_UMA_CTS_SW_CS) { + /* + * Don't drop CS if there's a transfer in progress, or we're + * about to start one. + */ + if (!((value | s->regs[reg]) & FIU_UMA_CTS_EXEC_DONE)) { + npcm7xx_fiu_deselect(s); + } + } else { + npcm7xx_fiu_select(s); + } + } + s->regs[reg] = value | (s->regs[reg] & FIU_UMA_CTS_EXEC_DONE); + if (value & FIU_UMA_CTS_EXEC_DONE) { + npcm7xx_fiu_uma_transaction(s); + } + break; + + case NPCM7XX_FIU_UMA_DR0 ... NPCM7XX_FIU_UMA_DR3: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to read-only register @ 0x%" PRIx64 "\n", + DEVICE(s)->canonical_path, addr); + return; + + case NPCM7XX_FIU_PRT_CFG: + case NPCM7XX_FIU_PRT_CMD0 ... NPCM7XX_FIU_PRT_CMD9: + qemu_log_mask(LOG_UNIMP, "%s: PRT is not implemented\n", __func__); + break; + + case NPCM7XX_FIU_UMA_CMD: + case NPCM7XX_FIU_UMA_ADDR: + case NPCM7XX_FIU_UMA_DW0 ... NPCM7XX_FIU_UMA_DW3: + case NPCM7XX_FIU_CFG: + s->regs[reg] = value; + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to invalid offset 0x%" PRIx64 "\n", + DEVICE(s)->canonical_path, addr); + return; + } +} + +static const MemoryRegionOps npcm7xx_fiu_ctrl_ops = { + .read = npcm7xx_fiu_ctrl_read, + .write = npcm7xx_fiu_ctrl_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void npcm7xx_fiu_enter_reset(Object *obj, ResetType type) +{ + NPCM7xxFIUState *s = NPCM7XX_FIU(obj); + + trace_npcm7xx_fiu_enter_reset(DEVICE(obj)->canonical_path, type); + + memset(s->regs, 0, sizeof(s->regs)); + + s->regs[NPCM7XX_FIU_DRD_CFG] = 0x0300100b; + s->regs[NPCM7XX_FIU_DWR_CFG] = 0x03000002; + s->regs[NPCM7XX_FIU_UMA_CFG] = 0x00000400; + s->regs[NPCM7XX_FIU_UMA_CTS] = 0x00010000; + s->regs[NPCM7XX_FIU_UMA_CMD] = 0x0000000b; + s->regs[NPCM7XX_FIU_PRT_CFG] = 0x00000400; + s->regs[NPCM7XX_FIU_CFG] = 0x0000000b; +} + +static void npcm7xx_fiu_hold_reset(Object *obj) +{ + NPCM7xxFIUState *s = NPCM7XX_FIU(obj); + int i; + + trace_npcm7xx_fiu_hold_reset(DEVICE(obj)->canonical_path); + + for (i = 0; i < s->cs_count; i++) { + qemu_irq_raise(s->cs_lines[i]); + } +} + +static void npcm7xx_fiu_realize(DeviceState *dev, Error **errp) +{ + NPCM7xxFIUState *s = NPCM7XX_FIU(dev); + SysBusDevice *sbd = &s->parent; + int i; + + if (s->cs_count <= 0) { + error_setg(errp, "%s: %d chip selects specified, need at least one", + dev->canonical_path, s->cs_count); + return; + } + + s->spi = ssi_create_bus(dev, "spi"); + s->cs_lines = g_new0(qemu_irq, s->cs_count); + ssi_auto_connect_slaves(dev, s->cs_lines, s->spi); + s->flash = g_new0(NPCM7xxFIUFlash, s->cs_count); + + /* + * Register the control registers region first. It may be followed by one + * or more direct flash access regions. + */ + memory_region_init_io(&s->mmio, OBJECT(s), &npcm7xx_fiu_ctrl_ops, s, "ctrl", + NPCM7XX_FIU_CTRL_REGS_SIZE); + sysbus_init_mmio(sbd, &s->mmio); + + for (i = 0; i < s->cs_count; i++) { + NPCM7xxFIUFlash *flash = &s->flash[i]; + sysbus_init_irq(sbd, &s->cs_lines[i]); + flash->fiu = s; + memory_region_init_io(&flash->direct_access, OBJECT(s), + &npcm7xx_fiu_flash_ops, &s->flash[i], "flash", + NPCM7XX_FIU_FLASH_WINDOW_SIZE); + sysbus_init_mmio(sbd, &flash->direct_access); + } +} + +static Property npcm7xx_fiu_properties[] = { + DEFINE_PROP_INT32("cs-count", NPCM7xxFIUState, cs_count, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void npcm7xx_fiu_class_init(ObjectClass *klass, void *data) +{ + ResettableClass *rc = RESETTABLE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "NPCM7xx Flash Interface Unit"; + dc->realize = npcm7xx_fiu_realize; + rc->phases.enter = npcm7xx_fiu_enter_reset; + rc->phases.hold = npcm7xx_fiu_hold_reset; + device_class_set_props(dc, npcm7xx_fiu_properties); +} + +static const TypeInfo npcm7xx_fiu_types[] = { + { + .name = TYPE_NPCM7XX_FIU, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NPCM7xxFIUState), + .class_init = npcm7xx_fiu_class_init, + }, +}; +DEFINE_TYPES(npcm7xx_fiu_types); diff --git a/hw/ssi/trace-events b/hw/ssi/trace-events index 0ea498de91..8024253c1f 100644 --- a/hw/ssi/trace-events +++ b/hw/ssi/trace-events @@ -9,3 +9,12 @@ aspeed_smc_dma_checksum(uint32_t addr, uint32_t data) "0x%08x: 0x%08x" aspeed_smc_dma_rw(const char *dir, uint32_t flash_addr, uint32_t dram_addr, uint32_t size) "%s flash:@0x%08x dram:@0x%08x size:0x%08x" aspeed_smc_write(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PRIx64 " size %u: 0x%" PRIx64 aspeed_smc_flash_select(int cs, const char *prefix) "CS%d %sselect" + +# npcm7xx_fiu.c + +npcm7xx_fiu_enter_reset(const char *id, int reset_type) "%s reset type: %d" +npcm7xx_fiu_hold_reset(const char *id) "%s" +npcm7xx_fiu_ctrl_read(const char *id, uint64_t addr, uint32_t data) "%s offset: 0x%04" PRIx64 " value: 0x%08x" +npcm7xx_fiu_ctrl_write(const char *id, uint64_t addr, uint32_t data) "%s offset: 0x%04" PRIx64 " value: 0x%08x" +npcm7xx_fiu_flash_read(const char *id, int cs, uint64_t addr, unsigned int size, uint64_t value) "%s[%d] offset: 0x%08" PRIx64 " size: %u value: 0x%" PRIx64 +npcm7xx_fiu_flash_write(const char *id, int cs, uint64_t addr, unsigned int size, uint64_t value) "%s[%d] offset: 0x%08" PRIx64 " size: %u value: 0x%" PRIx64 diff --git a/include/hw/arm/npcm7xx.h b/include/hw/arm/npcm7xx.h index 8f49f7015c..e357f4bf59 100644 --- a/include/hw/arm/npcm7xx.h +++ b/include/hw/arm/npcm7xx.h @@ -22,6 +22,7 @@ #include "hw/misc/npcm7xx_gcr.h" #include "hw/nvram/npcm7xx_otp.h" #include "hw/timer/npcm7xx_timer.h" +#include "hw/ssi/npcm7xx_fiu.h" #include "target/arm/cpu.h" #define NPCM7XX_MAX_NUM_CPUS (2) @@ -67,6 +68,7 @@ typedef struct NPCM7xxState { NPCM7xxOTPState key_storage; NPCM7xxOTPState fuse_array; NPCM7xxMCState mc; + NPCM7xxFIUState fiu[2]; } NPCM7xxState; #define TYPE_NPCM7XX "npcm7xx" diff --git a/include/hw/ssi/npcm7xx_fiu.h b/include/hw/ssi/npcm7xx_fiu.h new file mode 100644 index 0000000000..88bfd4ba8e --- /dev/null +++ b/include/hw/ssi/npcm7xx_fiu.h @@ -0,0 +1,99 @@ +/* + * Nuvoton NPCM7xx Flash Interface Unit (FIU) + * + * Copyright 2020 Google LLC + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef NPCM7XX_FIU_H +#define NPCM7XX_FIU_H + +#include "hw/ssi/ssi.h" +#include "hw/sysbus.h" + +/** + * enum NPCM7xxFIURegister - 32-bit FIU register indices. + */ +enum NPCM7xxFIURegister { + NPCM7XX_FIU_DRD_CFG, + NPCM7XX_FIU_DWR_CFG, + NPCM7XX_FIU_UMA_CFG, + NPCM7XX_FIU_UMA_CTS, + NPCM7XX_FIU_UMA_CMD, + NPCM7XX_FIU_UMA_ADDR, + NPCM7XX_FIU_PRT_CFG, + NPCM7XX_FIU_UMA_DW0 = 0x0020 / sizeof(uint32_t), + NPCM7XX_FIU_UMA_DW1, + NPCM7XX_FIU_UMA_DW2, + NPCM7XX_FIU_UMA_DW3, + NPCM7XX_FIU_UMA_DR0, + NPCM7XX_FIU_UMA_DR1, + NPCM7XX_FIU_UMA_DR2, + NPCM7XX_FIU_UMA_DR3, + NPCM7XX_FIU_PRT_CMD0, + NPCM7XX_FIU_PRT_CMD1, + NPCM7XX_FIU_PRT_CMD2, + NPCM7XX_FIU_PRT_CMD3, + NPCM7XX_FIU_PRT_CMD4, + NPCM7XX_FIU_PRT_CMD5, + NPCM7XX_FIU_PRT_CMD6, + NPCM7XX_FIU_PRT_CMD7, + NPCM7XX_FIU_PRT_CMD8, + NPCM7XX_FIU_PRT_CMD9, + NPCM7XX_FIU_CFG = 0x78 / sizeof(uint32_t), + NPCM7XX_FIO_NR_REGS, +}; + +typedef struct NPCM7xxFIUState NPCM7xxFIUState; + +/** + * struct NPCM7xxFIUFlash - Per-chipselect flash controller state. + * @direct_access: Memory region for direct flash access. + * @fiu: Pointer to flash controller shared state. + */ +typedef struct NPCM7xxFIUFlash { + MemoryRegion direct_access; + NPCM7xxFIUState *fiu; +} NPCM7xxFIUFlash; + +/** + * NPCM7xxFIUState - Device state for one Flash Interface Unit. + * @parent: System bus device. + * @mmio: Memory region for register access. + * @cs_count: Number of flash chips that may be connected to this module. + * @active_cs: Currently active chip select, or -1 if no chip is selected. + * @cs_lines: GPIO lines that may be wired to flash chips. + * @flash: Array of @cs_count per-flash-chip state objects. + * @spi: The SPI bus mastered by this controller. + * @regs: Register contents. + * + * Each FIU has a shared bank of registers, and controls up to four chip + * selects. Each chip select has a dedicated memory region which may be used to + * read and write the flash connected to that chip select as if it were memory. + */ +struct NPCM7xxFIUState { + SysBusDevice parent; + + MemoryRegion mmio; + + int32_t cs_count; + int32_t active_cs; + qemu_irq *cs_lines; + NPCM7xxFIUFlash *flash; + + SSIBus *spi; + + uint32_t regs[NPCM7XX_FIO_NR_REGS]; +}; + +#define TYPE_NPCM7XX_FIU "npcm7xx-fiu" +#define NPCM7XX_FIU(obj) OBJECT_CHECK(NPCM7xxFIUState, (obj), TYPE_NPCM7XX_FIU) + +#endif /* NPCM7XX_FIU_H */