Some NORs only support 1-1-1 and X-X-X modes, and in the case, the benefit of using X-X-X mode is obvious, even if this implies extra complexity in the core logic, since now the protocol used for a given operation depends on the mode the NOR is currently operating in. To deal with this stateful aspect, we add a ->mode field which keeps track of the current mode. We also add a ->change_mode() hook, so that NORs can have their own procedure to switch from one mode to another, and ->adjust_op() is here to live patch spi_mem_op when a given operation is executed (depending on the mode, it might involve extending the number of dummy cycle, adding address or cmd cycles, ...). Finally, ->preferred_mode is here let the core know what mode it should enter when resuming or at init time. The preferred mode selection logic (spi_nor_select_preferred_mode()) is pretty basic and might be extended at some point. Signed-off-by: Boris Brezillon <boris.brezillon@xxxxxxxxxxx> --- drivers/mtd/spi-nor/spi-nor.c | 96 +++++++++++++++++++++++++++++++++++++++++-- include/linux/mtd/spi-nor.h | 12 ++++++ 2 files changed, 105 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index 42f299a0b76f..33a07d9eb7a8 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -93,10 +93,18 @@ struct flash_info { #define USE_CLSR BIT(14) /* use CLSR command */ int (*quad_enable)(struct spi_nor *nor); + int (*change_mode)(struct spi_nor *nor, u32 newmode); + void (*adjust_op)(struct spi_nor *nor, struct spi_mem_op *op); }; #define JEDEC_MFR(info) ((info)->id[0]) +static void spi_nor_adjust_op(struct spi_nor *nor, struct spi_mem_op *op) +{ + if (nor->adjust_op) + nor->adjust_op(nor, op); +} + static int spi_nor_exec_op(struct spi_nor *nor, struct spi_mem_op *op, u64 *addr, void *buf, unsigned int len) { @@ -106,6 +114,8 @@ static int spi_nor_exec_op(struct spi_nor *nor, struct spi_mem_op *op, if (!op || (len && !buf)) return -EINVAL; + spi_nor_adjust_op(nor, op); + if (op->addr.nbytes && addr) op->addr.val = *addr; @@ -150,11 +160,17 @@ static int spi_nor_nodata_op(struct spi_nor *nor, struct spi_mem_op *op) static int spi_nor_read_reg(struct spi_nor *nor, u8 opcode, u8 *val, int len) { + if (WARN_ON_ONCE(nor->mode != SPI_NOR_MODE_SPI)) + return -ENOTSUPP; + return nor->read_reg(nor, opcode, val, len); } static int spi_nor_write_reg(struct spi_nor *nor, u8 opcode, u8 *val, int len) { + if (WARN_ON_ONCE(nor->mode != SPI_NOR_MODE_SPI)) + return -ENOTSUPP; + return nor->write_reg(nor, opcode, val, len); } @@ -184,6 +200,8 @@ static ssize_t spi_nor_spimem_read_data(struct spi_nor *nor, loff_t ofs, /* convert the dummy cycles to the number of bytes */ op.dummy.nbytes = (nor->read_dummy * op.dummy.buswidth) / 8; + spi_nor_adjust_op(nor, &op); + while (remaining) { op.data.nbytes = remaining < UINT_MAX ? remaining : UINT_MAX; if (!usebouncebuf) @@ -243,6 +261,8 @@ static ssize_t spi_nor_spimem_write_data(struct spi_nor *nor, loff_t ofs, if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second) op.addr.nbytes = 0; + spi_nor_adjust_op(nor, &op); + op.data.nbytes = len < UINT_MAX ? len : UINT_MAX; if (!usebouncebuf) { @@ -422,6 +442,25 @@ static int write_disable(struct spi_nor *nor) return spi_nor_write_reg(nor, SPINOR_OP_WRDI, NULL, 0); } +static int spi_nor_change_mode(struct spi_nor *nor, u32 newmode) +{ + int ret; + + if (newmode == nor->mode) + return 0; + + if (!nor->change_mode) + return -ENOTSUPP; + + ret = nor->change_mode(nor, newmode); + if (ret) + return ret; + + nor->mode = newmode; + + return 0; +} + static struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd) { return mtd->priv; @@ -2902,9 +2941,6 @@ spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, /* DTR modes are not supported yet, mask them all. */ *hwcaps &= ~SNOR_HWCAPS_DTR; - /* X-X-X modes are not supported yet, mask them all. */ - *hwcaps &= ~SNOR_HWCAPS_X_X_X; - /* Start with read commands. */ for (cap = 0; cap < 32; cap++) { int idx; @@ -3884,6 +3920,46 @@ static int spi_nor_select_erase(struct spi_nor *nor, u32 wanted_size) return 0; } +static void spi_nor_select_preferred_mode(struct spi_nor *nor, u32 hwcaps) +{ + nor->preferred_mode = SPI_NOR_MODE_SPI; + + /* + * Let's just avoid using stateful modes when SNOR_F_BROKEN_RESET is + * set. + */ + if (nor->flags & SNOR_F_BROKEN_RESET) + return; + + /* + * Stateless (1-n-n or 1-1-n) opcodes should always be preferred to + * stateful (n-n-n) ones if supported. + */ + if (hwcaps & SNOR_HWCPAS_READ_OCTO && hwcaps & SNOR_HWCAPS_PP_OCTO) + return; + + if (hwcaps & SNOR_HWCAPS_OPI) { + nor->preferred_mode = SPI_NOR_MODE_OPI; + return; + } + + if (hwcaps & SNOR_HWCAPS_READ_QUAD && hwcaps & SNOR_HWCAPS_PP_QUAD) + return; + + if (hwcaps & SNOR_HWCAPS_QPI) { + nor->preferred_mode = SPI_NOR_MODE_QPI; + return; + } + + if (hwcaps & SNOR_HWCAPS_READ_DUAL) + return; + + if (hwcaps & SNOR_HWCAPS_DPI) { + nor->preferred_mode = SPI_NOR_MODE_DPI; + return; + } +} + static int spi_nor_setup(struct spi_nor *nor, const struct flash_info *info, const struct spi_nor_flash_parameter *params, const struct spi_nor_hwcaps *hwcaps) @@ -3892,6 +3968,10 @@ static int spi_nor_setup(struct spi_nor *nor, const struct flash_info *info, bool enable_quad_io; int err; + /* Set ->adjust_op() and ->change_mode(). */ + nor->adjust_op = info->adjust_op; + nor->change_mode = info->change_mode; + /* * Keep only the hardware capabilities supported by both the SPI * controller and the SPI flash memory. @@ -3951,6 +4031,8 @@ static int spi_nor_setup(struct spi_nor *nor, const struct flash_info *info, else nor->quad_enable = NULL; + spi_nor_select_preferred_mode(nor, shared_mask); + return 0; } @@ -3971,6 +4053,11 @@ static int spi_nor_init(struct spi_nor *nor) spi_nor_wait_till_ready(nor); } + err = spi_nor_change_mode(nor, nor->preferred_mode); + if (err) + dev_err(nor->dev, "failed to switch to mode %x", + nor->preferred_mode); + if (nor->quad_enable) { err = nor->quad_enable(nor); if (err) { @@ -4011,6 +4098,9 @@ static void spi_nor_resume(struct mtd_info *mtd) void spi_nor_restore(struct spi_nor *nor) { + /* Restore the NOR to SPI mode. */ + spi_nor_change_mode(nor, SPI_NOR_MODE_SPI); + /* restore the addressing mode */ if ((nor->addr_width == 4) && !(nor->info->flags & SPI_NOR_4B_OPCODES) && diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index f2154672f75a..f80aba464eb2 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -333,6 +333,14 @@ struct spi_nor_erase_map { */ struct flash_info; +enum spi_nor_mode { + SPI_NOR_MODE_SPI, + SPI_NOR_MODE_DPI, + SPI_NOR_MODE_QPI, + SPI_NOR_MODE_OPI, + SPI_NOR_NUM_MODES, +}; + /** * struct spi_nor - Structure for defining a the SPI NOR layer * @mtd: point to a mtd_info structure @@ -381,6 +389,8 @@ struct spi_nor { struct spi_mem *spimem; void *bouncebuf; unsigned int bouncebuf_size; + enum spi_nor_mode preferred_mode; + enum spi_nor_mode mode; const struct flash_info *info; u32 page_size; u8 addr_width; @@ -412,6 +422,8 @@ struct spi_nor { int (*flash_is_locked)(struct spi_nor *nor, loff_t ofs, uint64_t len); int (*quad_enable)(struct spi_nor *nor); int (*set_4byte)(struct spi_nor *nor, bool enable); + int (*change_mode)(struct spi_nor *nor, enum spi_nor_mode newmode); + void (*adjust_op)(struct spi_nor *nor, struct spi_mem_op *op); void *priv; }; -- 2.14.1 ______________________________________________________ Linux MTD discussion mailing list http://lists.infradead.org/mailman/listinfo/linux-mtd/