diff --git a/hw/ssi/aspeed_smc.c b/hw/ssi/aspeed_smc.c index 9f3b6f4b45..f1e66870d7 100644 --- a/hw/ssi/aspeed_smc.c +++ b/hw/ssi/aspeed_smc.c @@ -145,6 +145,9 @@ /* Flash opcodes. */ #define SPI_OP_READ 0x03 /* Read data bytes (low frequency) */ +#define SNOOP_OFF 0xFF +#define SNOOP_START 0x0 + /* * Default segments mapping addresses and size for each slave per * controller. These can be changed when board is initialized with the @@ -566,6 +569,101 @@ static uint64_t aspeed_smc_flash_read(void *opaque, hwaddr addr, unsigned size) return ret; } +/* + * TODO (clg@kaod.org): stolen from xilinx_spips.c. Should move to a + * common include header. + */ +typedef enum { + READ = 0x3, READ_4 = 0x13, + FAST_READ = 0xb, FAST_READ_4 = 0x0c, + DOR = 0x3b, DOR_4 = 0x3c, + QOR = 0x6b, QOR_4 = 0x6c, + DIOR = 0xbb, DIOR_4 = 0xbc, + QIOR = 0xeb, QIOR_4 = 0xec, + + PP = 0x2, PP_4 = 0x12, + DPP = 0xa2, + QPP = 0x32, QPP_4 = 0x34, +} FlashCMD; + +static int aspeed_smc_num_dummies(uint8_t command) +{ + switch (command) { /* check for dummies */ + case READ: /* no dummy bytes/cycles */ + case PP: + case DPP: + case QPP: + case READ_4: + case PP_4: + case QPP_4: + return 0; + case FAST_READ: + case DOR: + case QOR: + case DOR_4: + case QOR_4: + return 1; + case DIOR: + case FAST_READ_4: + case DIOR_4: + return 2; + case QIOR: + case QIOR_4: + return 4; + default: + return -1; + } +} + +static bool aspeed_smc_do_snoop(AspeedSMCFlash *fl, uint64_t data, + unsigned size) +{ + AspeedSMCState *s = fl->controller; + uint8_t addr_width = aspeed_smc_flash_is_4byte(fl) ? 4 : 3; + + if (s->snoop_index == SNOOP_OFF) { + return false; /* Do nothing */ + + } else if (s->snoop_index == SNOOP_START) { + uint8_t cmd = data & 0xff; + int ndummies = aspeed_smc_num_dummies(cmd); + + /* + * No dummy cycles are expected with the current command. Turn + * off snooping and let the transfer proceed normally. + */ + if (ndummies <= 0) { + s->snoop_index = SNOOP_OFF; + return false; + } + + s->snoop_dummies = ndummies * 8; + + } else if (s->snoop_index >= addr_width + 1) { + + /* The SPI transfer has reached the dummy cycles sequence */ + for (; s->snoop_dummies; s->snoop_dummies--) { + ssi_transfer(s->spi, s->regs[R_DUMMY_DATA] & 0xff); + } + + /* If no more dummy cycles are expected, turn off snooping */ + if (!s->snoop_dummies) { + s->snoop_index = SNOOP_OFF; + } else { + s->snoop_index += size; + } + + /* + * Dummy cycles have been faked already. Ignore the current + * SPI transfer + */ + return true; + } + + s->snoop_index += size; + return false; +} + static void aspeed_smc_flash_write(void *opaque, hwaddr addr, uint64_t data, unsigned size) { @@ -581,6 +679,10 @@ static void aspeed_smc_flash_write(void *opaque, hwaddr addr, uint64_t data, switch (aspeed_smc_flash_mode(fl)) { case CTRL_USERMODE: + if (aspeed_smc_do_snoop(fl, data, size)) { + break; + } + for (i = 0; i < size; i++) { ssi_transfer(s->spi, (data >> (8 * i)) & 0xff); } @@ -613,7 +715,9 @@ static const MemoryRegionOps aspeed_smc_flash_ops = { static void aspeed_smc_flash_update_cs(AspeedSMCFlash *fl) { - const AspeedSMCState *s = fl->controller; + AspeedSMCState *s = fl->controller; + + s->snoop_index = aspeed_smc_is_ce_stop_active(fl) ? SNOOP_OFF : SNOOP_START; qemu_set_irq(s->cs_lines[fl->id], aspeed_smc_is_ce_stop_active(fl)); } @@ -652,6 +756,9 @@ static void aspeed_smc_reset(DeviceState *d) if (s->ctrl->segments == aspeed_segments_fmc) { s->regs[s->r_conf] |= (CONF_FLASH_TYPE_SPI << CONF_FLASH_TYPE0); } + + s->snoop_index = SNOOP_OFF; + s->snoop_dummies = 0; } static uint64_t aspeed_smc_read(void *opaque, hwaddr addr, unsigned int size) @@ -793,10 +900,12 @@ static void aspeed_smc_realize(DeviceState *dev, Error **errp) static const VMStateDescription vmstate_aspeed_smc = { .name = "aspeed.smc", - .version_id = 1, - .minimum_version_id = 1, + .version_id = 2, + .minimum_version_id = 2, .fields = (VMStateField[]) { VMSTATE_UINT32_ARRAY(regs, AspeedSMCState, ASPEED_SMC_R_MAX), + VMSTATE_UINT8(snoop_index, AspeedSMCState), + VMSTATE_UINT8(snoop_dummies, AspeedSMCState), VMSTATE_END_OF_LIST() } }; diff --git a/include/hw/ssi/aspeed_smc.h b/include/hw/ssi/aspeed_smc.h index 1f557313fa..3b1e7fce6c 100644 --- a/include/hw/ssi/aspeed_smc.h +++ b/include/hw/ssi/aspeed_smc.h @@ -98,6 +98,9 @@ typedef struct AspeedSMCState { uint8_t conf_enable_w0; AspeedSMCFlash *flashes; + + uint8_t snoop_index; + uint8_t snoop_dummies; } AspeedSMCState; #endif /* ASPEED_SMC_H */