[PATCH qemu 15/38] aspeed/smc: handle SPI flash Command mode
Andrew Jeffery
andrew at aj.id.au
Fri Nov 25 15:23:40 AEDT 2016
On Fri, 2016-11-18 at 15:21 +0100, Cédric Le Goater wrote:
> The Aspeed SMC controllers have a mode (Command mode) in which
> accesses to the flash content are no different than doing MMIOs. The
> controller generates all the necessary commands to load (or store)
> data in memory.
>
> However, accesses are restricted to the segment window assigned the
> the flash module by the controller. This window is defined by the
> Segment Address Register.
>
> Signed-off-by: Cédric Le Goater <clg at kaod.org>
Reviewed-by: Andrew Jeffery <andrew at aj.id.au>
> ---
> hw/ssi/aspeed_smc.c | 174
> ++++++++++++++++++++++++++++++++++++++++++++++++----
> 1 file changed, 162 insertions(+), 12 deletions(-)
>
> diff --git a/hw/ssi/aspeed_smc.c b/hw/ssi/aspeed_smc.c
> index 72a44150b0a1..eec087199a22 100644
> --- a/hw/ssi/aspeed_smc.c
> +++ b/hw/ssi/aspeed_smc.c
> @@ -69,6 +69,7 @@
> #define R_CTRL0 (0x10 / 4)
> #define CTRL_CMD_SHIFT 16
> #define CTRL_CMD_MASK 0xff
> +#define CTRL_AST2400_SPI_4BYTE (1 << 13)
> #define CTRL_CE_STOP_ACTIVE (1 << 2)
> #define CTRL_CMD_MODE_MASK 0x3
> #define CTRL_READMODE 0x0
> @@ -135,6 +136,16 @@
> #define ASPEED_SOC_SPI_FLASH_BASE 0x30000000
> #define ASPEED_SOC_SPI2_FLASH_BASE 0x38000000
>
> +/* Flash opcodes. */
> +#define SPI_OP_READ 0x03 /* Read data bytes (low frequency)
> */
> +#define SPI_OP_WRDI 0x04 /* Write disable */
> +#define SPI_OP_RDSR 0x05 /* Read status register */
> +#define SPI_OP_WREN 0x06 /* Write enable */
> +
> +/* Used for Macronix and Winbond flashes. */
> +#define SPI_OP_EN4B 0xb7 /* Enter 4-byte mode */
> +#define SPI_OP_EX4B 0xe9 /* Exit 4-byte mode */
> +
> /*
> * Default segments mapping addresses and size for each slave per
> * controller. These can be changed when board is initialized with
> the
> @@ -357,6 +368,98 @@ static inline bool aspeed_smc_is_writable(const
> AspeedSMCFlash *fl)
> return s->regs[s->r_conf] & (1 << (s->conf_enable_w0 + fl->id));
> }
>
> +static inline int aspeed_smc_flash_cmd(const AspeedSMCFlash *fl)
> +{
> + AspeedSMCState *s = fl->controller;
> + int cmd = (s->regs[s->r_ctrl0 + fl->id] >> CTRL_CMD_SHIFT) &
> CTRL_CMD_MASK;
> +
> + /* This is the default value for read mode. In other modes, the
> + * command should be defined */
> + if (aspeed_smc_flash_mode(fl) == CTRL_READMODE) {
> + cmd = SPI_OP_READ;
> + }
> +
> + if (!cmd) {
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: no command defined for
> mode %d\n",
> + __func__, aspeed_smc_flash_mode(fl));
> + }
> +
> + return cmd;
> +}
> +
> +static inline int aspeed_smc_flash_is_4byte(const AspeedSMCFlash
> *fl)
> +{
> + AspeedSMCState *s = fl->controller;
> +
> + if (s->ctrl->segments == aspeed_segments_spi) {
> + return s->regs[s->r_ctrl0] & CTRL_AST2400_SPI_4BYTE;
> + } else {
> + return s->regs[s->r_ce_ctrl] & (1 << (CTRL_EXTENDED0 + fl-
> >id));
> + }
> +}
> +
> +static void aspeed_smc_flash_select(const AspeedSMCFlash *fl)
> +{
> + AspeedSMCState *s = fl->controller;
> +
> + s->regs[s->r_ctrl0 + fl->id] &= ~CTRL_CE_STOP_ACTIVE;
> + qemu_set_irq(s->cs_lines[fl->id],
> aspeed_smc_is_ce_stop_active(fl));
> +}
> +
> +static void aspeed_smc_flash_unselect(const AspeedSMCFlash *fl)
> +{
> + AspeedSMCState *s = fl->controller;
> +
> + s->regs[s->r_ctrl0 + fl->id] |= CTRL_CE_STOP_ACTIVE;
> + qemu_set_irq(s->cs_lines[fl->id],
> aspeed_smc_is_ce_stop_active(fl));
> +}
> +
> +static uint32_t aspeed_smc_check_segment_addr(AspeedSMCFlash *fl,
> uint32_t addr)
> +{
> + AspeedSMCState *s = fl->controller;
> + AspeedSegments seg;
> +
> + aspeed_smc_reg_to_segment(s->regs[R_SEG_ADDR0 + fl->id], &seg);
> + if ((addr & (seg.size - 1)) != addr) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: invalid address 0x%08x for CS%d segment :
> "
> + "[ 0x%"HWADDR_PRIx" - 0x%"HWADDR_PRIx" ]\n",
> + s->ctrl->name, addr, fl->id, seg.addr,
> + seg.addr + seg.size);
> + }
> +
> + addr &= seg.size - 1;
> + return addr;
> +}
> +
> +static void aspeed_smc_flash_setup_read(AspeedSMCFlash *fl, uint32_t
> addr)
> +{
> + AspeedSMCState *s = fl->controller;
> + uint8_t cmd = aspeed_smc_flash_cmd(fl);
> +
> + /*
> + * To be checked: I am not sure the Aspeed SPI controller needs
> to
> + * enable writes when running in READ/FREAD command mode
> + */
> +
> + /* access can not exceed CS segment */
> + addr = aspeed_smc_check_segment_addr(fl, addr);
> +
> + /* TODO: do we have to send 4BYTE each time ? */
> + if (aspeed_smc_flash_is_4byte(fl)) {
> + ssi_transfer(s->spi, SPI_OP_EN4B);
> + }
> +
> + ssi_transfer(s->spi, cmd);
> +
> + if (aspeed_smc_flash_is_4byte(fl)) {
> + ssi_transfer(s->spi, (addr >> 24) & 0xff);
> + }
> + ssi_transfer(s->spi, (addr >> 16) & 0xff);
> + ssi_transfer(s->spi, (addr >> 8) & 0xff);
> + ssi_transfer(s->spi, (addr & 0xff));
> +}
> +
> static uint64_t aspeed_smc_flash_read(void *opaque, hwaddr addr,
> unsigned size)
> {
> AspeedSMCFlash *fl = opaque;
> @@ -364,19 +467,55 @@ static uint64_t aspeed_smc_flash_read(void
> *opaque, hwaddr addr, unsigned size)
> uint64_t ret = 0;
> int i;
>
> - if (aspeed_smc_is_usermode(fl)) {
> + switch (aspeed_smc_flash_mode(fl)) {
> + case CTRL_USERMODE:
> for (i = 0; i < size; i++) {
> ret |= ssi_transfer(s->spi, 0x0) << (8 * i);
> }
> - } else {
> - qemu_log_mask(LOG_UNIMP, "%s: usermode not implemented\n",
> - __func__);
> - ret = -1;
> + break;
> + case CTRL_READMODE:
> + case CTRL_FREADMODE:
> + aspeed_smc_flash_select(fl);
> + aspeed_smc_flash_setup_read(fl, addr);
> +
> + for (i = 0; i < size; i++) {
> + ret |= ssi_transfer(s->spi, 0x0) << (8 * i);
> + }
> +
> + aspeed_smc_flash_unselect(fl);
> + break;
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid flash mode
> %d\n",
> + __func__, aspeed_smc_flash_mode(fl));
> }
>
> return ret;
> }
>
> +static void aspeed_smc_flash_setup_write(AspeedSMCFlash *fl,
> uint32_t addr)
> +{
> + AspeedSMCState *s = fl->controller;
> + uint8_t cmd = aspeed_smc_flash_cmd(fl);
> +
> + /* Flash access can not exceed CS segment */
> + addr = aspeed_smc_check_segment_addr(fl, addr);
> +
> + /* TODO: do we have to send 4BYTE each time ? */
> + if (aspeed_smc_flash_is_4byte(fl)) {
> + ssi_transfer(s->spi, SPI_OP_EN4B);
> + }
> +
> + ssi_transfer(s->spi, SPI_OP_WREN);
> + ssi_transfer(s->spi, cmd);
> +
> + if (aspeed_smc_flash_is_4byte(fl)) {
> + ssi_transfer(s->spi, (addr >> 24) & 0xff);
> + }
> + ssi_transfer(s->spi, (addr >> 16) & 0xff);
> + ssi_transfer(s->spi, (addr >> 8) & 0xff);
> + ssi_transfer(s->spi, (addr & 0xff));
> +}
> +
> static void aspeed_smc_flash_write(void *opaque, hwaddr addr,
> uint64_t data,
> unsigned size)
> {
> @@ -390,14 +529,25 @@ static void aspeed_smc_flash_write(void
> *opaque, hwaddr addr, uint64_t data,
> return;
> }
>
> - if (!aspeed_smc_is_usermode(fl)) {
> - qemu_log_mask(LOG_UNIMP, "%s: usermode not implemented\n",
> - __func__);
> - return;
> - }
> + switch (aspeed_smc_flash_mode(fl)) {
> + case CTRL_USERMODE:
> + for (i = 0; i < size; i++) {
> + ssi_transfer(s->spi, (data >> (8 * i)) & 0xff);
> + }
> + break;
> + case CTRL_WRITEMODE:
> + aspeed_smc_flash_select(fl);
> + aspeed_smc_flash_setup_write(fl, addr);
> +
> + for (i = 0; i < size; i++) {
> + ssi_transfer(s->spi, (data >> (8 * i)) & 0xff);
> + }
>
> - for (i = 0; i < size; i++) {
> - ssi_transfer(s->spi, (data >> (8 * i)) & 0xff);
> + aspeed_smc_flash_unselect(fl);
> + break;
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid flash mode
> %d\n",
> + __func__, aspeed_smc_flash_mode(fl));
> }
> }
>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 801 bytes
Desc: This is a digitally signed message part
URL: <http://lists.ozlabs.org/pipermail/openbmc/attachments/20161125/b1854081/attachment.sig>
More information about the openbmc
mailing list