From f173f26a4d543fa57e6ed9505bbbbf9bec61f544 Mon Sep 17 00:00:00 2001 From: Vignesh Raghavendra Date: Tue, 6 Aug 2019 10:40:39 +0530 Subject: mtd: spi-nor: always use bounce buffer for register read/writes spi-mem layer expects all buffers passed to it to be DMA'able. But spi-nor layer mostly allocates buffers on stack for reading/writing to registers and therefore are not DMA'able. Introduce bounce buffer to be used to read/write to registers. This ensures that buffer passed to spi-mem layer during register read/writes is DMA'able. With this change nor->cmd-buf is no longer used, so drop it. Signed-off-by: Vignesh Raghavendra Reviewed-by: Boris Brezillon Signed-off-by: Tudor Ambarus --- include/linux/mtd/spi-nor.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'include/linux/mtd') diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 9f57cdfcc93d..6b5956a7a65a 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -344,6 +344,9 @@ struct flash_info; * @mtd: point to a mtd_info structure * @lock: the lock for the read/write/erase/lock/unlock operations * @dev: point to a spi device, or a spi nor controller device. + * @bouncebuf: bounce buffer used when the buffer passed by the MTD + * layer is not DMA-able + * @bouncebuf_size: size of the bounce buffer * @info: spi-nor part JDEC MFR id and other info * @page_size: the page size of the SPI NOR * @addr_width: number of address bytes @@ -356,7 +359,6 @@ struct flash_info; * @read_proto: the SPI protocol for read operations * @write_proto: the SPI protocol for write operations * @reg_proto the SPI protocol for read_reg/write_reg/erase operations - * @cmd_buf: used by the write_reg * @erase_map: the erase map of the SPI NOR * @prepare: [OPTIONAL] do some preparations for the * read/write/erase/lock/unlock operations @@ -382,6 +384,8 @@ struct spi_nor { struct mtd_info mtd; struct mutex lock; struct device *dev; + u8 *bouncebuf; + size_t bouncebuf_size; const struct flash_info *info; u32 page_size; u8 addr_width; @@ -394,7 +398,6 @@ struct spi_nor { enum spi_nor_protocol reg_proto; bool sst_write_second; u32 flags; - u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE]; struct spi_nor_erase_map erase_map; int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops); -- cgit v1.2.3 From b35b9a10362d203451d920d01ab8d6cd55cbaf2a Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 6 Aug 2019 10:40:40 +0530 Subject: mtd: spi-nor: Move m25p80 code in spi-nor.c The m25p80 driver is actually a generic wrapper around the spi-mem layer. Not only the driver name is misleading, but we'd expect such a common logic to be directly available in the core. Another reason for moving this code is that SPI NOR controller drivers should progressively be replaced by SPI controller drivers implementing the spi_mem_ops interface, and when the conversion is done, we should have a single spi-nor driver directly interfacing with the spi-mem layer. While moving the code we also fix a longstanding issue when non-DMA-able buffers are passed by the MTD layer. Signed-off-by: Boris Brezillon Signed-off-by: Vignesh Raghavendra Signed-off-by: Tudor Ambarus --- drivers/mtd/devices/Kconfig | 18 -- drivers/mtd/devices/Makefile | 1 - drivers/mtd/devices/m25p80.c | 347 ----------------------- drivers/mtd/spi-nor/Kconfig | 2 + drivers/mtd/spi-nor/spi-nor.c | 628 ++++++++++++++++++++++++++++++++++++++++-- include/linux/mtd/spi-nor.h | 3 + 6 files changed, 605 insertions(+), 394 deletions(-) delete mode 100644 drivers/mtd/devices/m25p80.c (limited to 'include/linux/mtd') diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig index 49abbc52457d..f96287c4b789 100644 --- a/drivers/mtd/devices/Kconfig +++ b/drivers/mtd/devices/Kconfig @@ -79,24 +79,6 @@ config MTD_DATAFLASH_OTP other key product data. The second half is programmed with a unique-to-each-chip bit pattern at the factory. -config MTD_M25P80 - tristate "Support most SPI Flash chips (AT26DF, M25P, W25X, ...)" - depends on SPI_MASTER && MTD_SPI_NOR - select SPI_MEM - help - This enables access to most modern SPI flash chips, used for - program and data storage. Series supported include Atmel AT26DF, - Spansion S25SL, SST 25VF, ST M25P, and Winbond W25X. Other chips - are supported as well. See the driver source for the current list, - or to add other chips. - - Note that the original DataFlash chips (AT45 series, not AT26DF), - need an entirely different driver. - - Set up your spi devices with the right board-specific platform data, - if you want to specify device partitioning or to use a device which - doesn't support the JEDEC ID instruction. - config MTD_MCHP23K256 tristate "Microchip 23K256 SRAM" depends on SPI_MASTER diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile index 94895eab3066..991c8d12c016 100644 --- a/drivers/mtd/devices/Makefile +++ b/drivers/mtd/devices/Makefile @@ -12,7 +12,6 @@ obj-$(CONFIG_MTD_MTDRAM) += mtdram.o obj-$(CONFIG_MTD_LART) += lart.o obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o -obj-$(CONFIG_MTD_M25P80) += m25p80.o obj-$(CONFIG_MTD_MCHP23K256) += mchp23k256.o obj-$(CONFIG_MTD_SPEAR_SMI) += spear_smi.o obj-$(CONFIG_MTD_SST25L) += sst25l.o diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c deleted file mode 100644 index c50888670250..000000000000 --- a/drivers/mtd/devices/m25p80.c +++ /dev/null @@ -1,347 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * MTD SPI driver for ST M25Pxx (and similar) serial flash chips - * - * Author: Mike Lavender, mike@steroidmicros.com - * - * Copyright (c) 2005, Intec Automation Inc. - * - * Some parts are based on lart.c by Abraham Van Der Merwe - * - * Cleaned up and generalized based on mtd_dataflash.c - */ - -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -struct m25p { - struct spi_mem *spimem; - struct spi_nor spi_nor; -}; - -static int m25p80_read_reg(struct spi_nor *nor, u8 code, u8 *val, int len) -{ - struct m25p *flash = nor->priv; - struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(code, 1), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_IN(len, NULL, 1)); - void *scratchbuf; - int ret; - - scratchbuf = kmalloc(len, GFP_KERNEL); - if (!scratchbuf) - return -ENOMEM; - - op.data.buf.in = scratchbuf; - ret = spi_mem_exec_op(flash->spimem, &op); - if (ret < 0) - dev_err(&flash->spimem->spi->dev, "error %d reading %x\n", ret, - code); - else - memcpy(val, scratchbuf, len); - - kfree(scratchbuf); - - return ret; -} - -static int m25p80_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len) -{ - struct m25p *flash = nor->priv; - struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 1), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_OUT(len, NULL, 1)); - void *scratchbuf; - int ret; - - scratchbuf = kmemdup(buf, len, GFP_KERNEL); - if (!scratchbuf) - return -ENOMEM; - - op.data.buf.out = scratchbuf; - ret = spi_mem_exec_op(flash->spimem, &op); - kfree(scratchbuf); - - return ret; -} - -static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len, - const u_char *buf) -{ - struct m25p *flash = nor->priv; - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(nor->program_opcode, 1), - SPI_MEM_OP_ADDR(nor->addr_width, to, 1), - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_OUT(len, buf, 1)); - int ret; - - /* get transfer protocols. */ - op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->write_proto); - op.addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->write_proto); - op.data.buswidth = spi_nor_get_protocol_data_nbits(nor->write_proto); - - if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second) - op.addr.nbytes = 0; - - ret = spi_mem_adjust_op_size(flash->spimem, &op); - if (ret) - return ret; - op.data.nbytes = len < op.data.nbytes ? len : op.data.nbytes; - - ret = spi_mem_exec_op(flash->spimem, &op); - if (ret) - return ret; - - return op.data.nbytes; -} - -/* - * Read an address range from the nor chip. The address range - * may be any size provided it is within the physical boundaries. - */ -static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len, - u_char *buf) -{ - struct m25p *flash = nor->priv; - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 1), - SPI_MEM_OP_ADDR(nor->addr_width, from, 1), - SPI_MEM_OP_DUMMY(nor->read_dummy, 1), - SPI_MEM_OP_DATA_IN(len, buf, 1)); - size_t remaining = len; - int ret; - - /* get transfer protocols. */ - op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->read_proto); - op.addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->read_proto); - op.dummy.buswidth = op.addr.buswidth; - op.data.buswidth = spi_nor_get_protocol_data_nbits(nor->read_proto); - - /* convert the dummy cycles to the number of bytes */ - op.dummy.nbytes = (nor->read_dummy * op.dummy.buswidth) / 8; - - while (remaining) { - op.data.nbytes = remaining < UINT_MAX ? remaining : UINT_MAX; - ret = spi_mem_adjust_op_size(flash->spimem, &op); - if (ret) - return ret; - - ret = spi_mem_exec_op(flash->spimem, &op); - if (ret) - return ret; - - op.addr.val += op.data.nbytes; - remaining -= op.data.nbytes; - op.data.buf.in += op.data.nbytes; - } - - return len; -} - -/* - * board specific setup should have ensured the SPI clock used here - * matches what the READ command supports, at least until this driver - * understands FAST_READ (for clocks over 25 MHz). - */ -static int m25p_probe(struct spi_mem *spimem) -{ - struct spi_device *spi = spimem->spi; - struct flash_platform_data *data; - struct m25p *flash; - struct spi_nor *nor; - struct spi_nor_hwcaps hwcaps = { - .mask = SNOR_HWCAPS_READ | - SNOR_HWCAPS_READ_FAST | - SNOR_HWCAPS_PP, - }; - char *flash_name; - int ret; - - data = dev_get_platdata(&spimem->spi->dev); - - flash = devm_kzalloc(&spimem->spi->dev, sizeof(*flash), GFP_KERNEL); - if (!flash) - return -ENOMEM; - - nor = &flash->spi_nor; - - /* install the hooks */ - nor->read = m25p80_read; - nor->write = m25p80_write; - nor->write_reg = m25p80_write_reg; - nor->read_reg = m25p80_read_reg; - - nor->dev = &spimem->spi->dev; - spi_nor_set_flash_node(nor, spi->dev.of_node); - nor->priv = flash; - - spi_mem_set_drvdata(spimem, flash); - flash->spimem = spimem; - - if (spi->mode & SPI_RX_OCTAL) { - hwcaps.mask |= SNOR_HWCAPS_READ_1_1_8; - - if (spi->mode & SPI_TX_OCTAL) - hwcaps.mask |= (SNOR_HWCAPS_READ_1_8_8 | - SNOR_HWCAPS_PP_1_1_8 | - SNOR_HWCAPS_PP_1_8_8); - } else if (spi->mode & SPI_RX_QUAD) { - hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4; - - if (spi->mode & SPI_TX_QUAD) - hwcaps.mask |= (SNOR_HWCAPS_READ_1_4_4 | - SNOR_HWCAPS_PP_1_1_4 | - SNOR_HWCAPS_PP_1_4_4); - } else if (spi->mode & SPI_RX_DUAL) { - hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2; - - if (spi->mode & SPI_TX_DUAL) - hwcaps.mask |= SNOR_HWCAPS_READ_1_2_2; - } - - if (data && data->name) - nor->mtd.name = data->name; - - if (!nor->mtd.name) - nor->mtd.name = spi_mem_get_name(spimem); - - /* For some (historical?) reason many platforms provide two different - * names in flash_platform_data: "name" and "type". Quite often name is - * set to "m25p80" and then "type" provides a real chip name. - * If that's the case, respect "type" and ignore a "name". - */ - if (data && data->type) - flash_name = data->type; - else if (!strcmp(spi->modalias, "spi-nor")) - flash_name = NULL; /* auto-detect */ - else - flash_name = spi->modalias; - - ret = spi_nor_scan(nor, flash_name, &hwcaps); - if (ret) - return ret; - - return mtd_device_register(&nor->mtd, data ? data->parts : NULL, - data ? data->nr_parts : 0); -} - - -static int m25p_remove(struct spi_mem *spimem) -{ - struct m25p *flash = spi_mem_get_drvdata(spimem); - - spi_nor_restore(&flash->spi_nor); - - /* Clean up MTD stuff. */ - return mtd_device_unregister(&flash->spi_nor.mtd); -} - -static void m25p_shutdown(struct spi_mem *spimem) -{ - struct m25p *flash = spi_mem_get_drvdata(spimem); - - spi_nor_restore(&flash->spi_nor); -} -/* - * Do NOT add to this array without reading the following: - * - * Historically, many flash devices are bound to this driver by their name. But - * since most of these flash are compatible to some extent, and their - * differences can often be differentiated by the JEDEC read-ID command, we - * encourage new users to add support to the spi-nor library, and simply bind - * against a generic string here (e.g., "jedec,spi-nor"). - * - * Many flash names are kept here in this list (as well as in spi-nor.c) to - * keep them available as module aliases for existing platforms. - */ -static const struct spi_device_id m25p_ids[] = { - /* - * Allow non-DT platform devices to bind to the "spi-nor" modalias, and - * hack around the fact that the SPI core does not provide uevent - * matching for .of_match_table - */ - {"spi-nor"}, - - /* - * Entries not used in DTs that should be safe to drop after replacing - * them with "spi-nor" in platform data. - */ - {"s25sl064a"}, {"w25x16"}, {"m25p10"}, {"m25px64"}, - - /* - * Entries that were used in DTs without "jedec,spi-nor" fallback and - * should be kept for backward compatibility. - */ - {"at25df321a"}, {"at25df641"}, {"at26df081a"}, - {"mx25l4005a"}, {"mx25l1606e"}, {"mx25l6405d"}, {"mx25l12805d"}, - {"mx25l25635e"},{"mx66l51235l"}, - {"n25q064"}, {"n25q128a11"}, {"n25q128a13"}, {"n25q512a"}, - {"s25fl256s1"}, {"s25fl512s"}, {"s25sl12801"}, {"s25fl008k"}, - {"s25fl064k"}, - {"sst25vf040b"},{"sst25vf016b"},{"sst25vf032b"},{"sst25wf040"}, - {"m25p40"}, {"m25p80"}, {"m25p16"}, {"m25p32"}, - {"m25p64"}, {"m25p128"}, - {"w25x80"}, {"w25x32"}, {"w25q32"}, {"w25q32dw"}, - {"w25q80bl"}, {"w25q128"}, {"w25q256"}, - - /* Flashes that can't be detected using JEDEC */ - {"m25p05-nonjedec"}, {"m25p10-nonjedec"}, {"m25p20-nonjedec"}, - {"m25p40-nonjedec"}, {"m25p80-nonjedec"}, {"m25p16-nonjedec"}, - {"m25p32-nonjedec"}, {"m25p64-nonjedec"}, {"m25p128-nonjedec"}, - - /* Everspin MRAMs (non-JEDEC) */ - { "mr25h128" }, /* 128 Kib, 40 MHz */ - { "mr25h256" }, /* 256 Kib, 40 MHz */ - { "mr25h10" }, /* 1 Mib, 40 MHz */ - { "mr25h40" }, /* 4 Mib, 40 MHz */ - - { }, -}; -MODULE_DEVICE_TABLE(spi, m25p_ids); - -static const struct of_device_id m25p_of_table[] = { - /* - * Generic compatibility for SPI NOR that can be identified by the - * JEDEC READ ID opcode (0x9F). Use this, if possible. - */ - { .compatible = "jedec,spi-nor" }, - {} -}; -MODULE_DEVICE_TABLE(of, m25p_of_table); - -static struct spi_mem_driver m25p80_driver = { - .spidrv = { - .driver = { - .name = "m25p80", - .of_match_table = m25p_of_table, - }, - .id_table = m25p_ids, - }, - .probe = m25p_probe, - .remove = m25p_remove, - .shutdown = m25p_shutdown, - - /* REVISIT: many of these chips have deep power-down modes, which - * should clearly be entered on suspend() to minimize power use. - * And also when they're otherwise idle... - */ -}; - -module_spi_mem_driver(m25p80_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Mike Lavender"); -MODULE_DESCRIPTION("MTD SPI driver for ST M25Pxx flash chips"); diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig index 6de83277ce8b..f237fcdf7f86 100644 --- a/drivers/mtd/spi-nor/Kconfig +++ b/drivers/mtd/spi-nor/Kconfig @@ -2,6 +2,8 @@ menuconfig MTD_SPI_NOR tristate "SPI-NOR device support" depends on MTD + depends on MTD && SPI_MASTER + select SPI_MEM help This is the framework for the SPI NOR which can be used by the SPI device drivers and the SPI-NOR device driver. diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index 7f0831be90a0..de906ab907c7 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -288,6 +289,154 @@ struct flash_info { #define JEDEC_MFR(info) ((info)->id[0]) +/** + * spi_nor_spimem_xfer_data() - helper function to read/write data to + * flash's memory region + * @nor: pointer to 'struct spi_nor' + * @op: pointer to 'struct spi_mem_op' template for transfer + * + * Return: number of bytes transferred on success, -errno otherwise + */ +static ssize_t spi_nor_spimem_xfer_data(struct spi_nor *nor, + struct spi_mem_op *op) +{ + bool usebouncebuf = false; + void *rdbuf = NULL; + const void *buf; + int ret; + + if (op->data.dir == SPI_MEM_DATA_IN) + buf = op->data.buf.in; + else + buf = op->data.buf.out; + + if (object_is_on_stack(buf) || !virt_addr_valid(buf)) + usebouncebuf = true; + + if (usebouncebuf) { + if (op->data.nbytes > nor->bouncebuf_size) + op->data.nbytes = nor->bouncebuf_size; + + if (op->data.dir == SPI_MEM_DATA_IN) { + rdbuf = op->data.buf.in; + op->data.buf.in = nor->bouncebuf; + } else { + op->data.buf.out = nor->bouncebuf; + memcpy(nor->bouncebuf, buf, + op->data.nbytes); + } + } + + ret = spi_mem_adjust_op_size(nor->spimem, op); + if (ret) + return ret; + + ret = spi_mem_exec_op(nor->spimem, op); + if (ret) + return ret; + + if (usebouncebuf && op->data.dir == SPI_MEM_DATA_IN) + memcpy(rdbuf, nor->bouncebuf, op->data.nbytes); + + return op->data.nbytes; +} + +/** + * spi_nor_spimem_read_data() - read data from flash's memory region via + * spi-mem + * @nor: pointer to 'struct spi_nor' + * @from: offset to read from + * @len: number of bytes to read + * @buf: pointer to dst buffer + * + * Return: number of bytes read successfully, -errno otherwise + */ +static ssize_t spi_nor_spimem_read_data(struct spi_nor *nor, loff_t from, + size_t len, u8 *buf) +{ + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 1), + SPI_MEM_OP_ADDR(nor->addr_width, from, 1), + SPI_MEM_OP_DUMMY(nor->read_dummy, 1), + SPI_MEM_OP_DATA_IN(len, buf, 1)); + + /* get transfer protocols. */ + op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->read_proto); + op.addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->read_proto); + op.dummy.buswidth = op.addr.buswidth; + op.data.buswidth = spi_nor_get_protocol_data_nbits(nor->read_proto); + + /* convert the dummy cycles to the number of bytes */ + op.dummy.nbytes = (nor->read_dummy * op.dummy.buswidth) / 8; + + return spi_nor_spimem_xfer_data(nor, &op); +} + +/** + * spi_nor_read_data() - read data from flash memory + * @nor: pointer to 'struct spi_nor' + * @from: offset to read from + * @len: number of bytes to read + * @buf: pointer to dst buffer + * + * Return: number of bytes read successfully, -errno otherwise + */ +static ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len, + u8 *buf) +{ + if (nor->spimem) + return spi_nor_spimem_read_data(nor, from, len, buf); + + return nor->read(nor, from, len, buf); +} + +/** + * spi_nor_spimem_write_data() - write data to flash memory via + * spi-mem + * @nor: pointer to 'struct spi_nor' + * @to: offset to write to + * @len: number of bytes to write + * @buf: pointer to src buffer + * + * Return: number of bytes written successfully, -errno otherwise + */ +static ssize_t spi_nor_spimem_write_data(struct spi_nor *nor, loff_t to, + size_t len, const u8 *buf) +{ + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(nor->program_opcode, 1), + SPI_MEM_OP_ADDR(nor->addr_width, to, 1), + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(len, buf, 1)); + + op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->write_proto); + op.addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->write_proto); + op.data.buswidth = spi_nor_get_protocol_data_nbits(nor->write_proto); + + if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second) + op.addr.nbytes = 0; + + return spi_nor_spimem_xfer_data(nor, &op); +} + +/** + * spi_nor_write_data() - write data to flash memory + * @nor: pointer to 'struct spi_nor' + * @to: offset to write to + * @len: number of bytes to write + * @buf: pointer to src buffer + * + * Return: number of bytes written successfully, -errno otherwise + */ +static ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len, + const u8 *buf) +{ + if (nor->spimem) + return spi_nor_spimem_write_data(nor, to, len, buf); + + return nor->write(nor, to, len, buf); +} + /* * Read the status register, returning its value in the location * Return the status register value. @@ -297,7 +446,18 @@ static int read_sr(struct spi_nor *nor) { int ret; - ret = nor->read_reg(nor, SPINOR_OP_RDSR, nor->bouncebuf, 1); + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 1)); + + ret = spi_mem_exec_op(nor->spimem, &op); + } else { + ret = nor->read_reg(nor, SPINOR_OP_RDSR, nor->bouncebuf, 1); + } + if (ret < 0) { pr_err("error %d reading SR\n", (int) ret); return ret; @@ -315,7 +475,18 @@ static int read_fsr(struct spi_nor *nor) { int ret; - ret = nor->read_reg(nor, SPINOR_OP_RDFSR, nor->bouncebuf, 1); + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDFSR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 1)); + + ret = spi_mem_exec_op(nor->spimem, &op); + } else { + ret = nor->read_reg(nor, SPINOR_OP_RDFSR, nor->bouncebuf, 1); + } + if (ret < 0) { pr_err("error %d reading FSR\n", ret); return ret; @@ -333,7 +504,18 @@ static int read_cr(struct spi_nor *nor) { int ret; - ret = nor->read_reg(nor, SPINOR_OP_RDCR, nor->bouncebuf, 1); + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDCR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 1)); + + ret = spi_mem_exec_op(nor->spimem, &op); + } else { + ret = nor->read_reg(nor, SPINOR_OP_RDCR, nor->bouncebuf, 1); + } + if (ret < 0) { dev_err(nor->dev, "error %d reading CR\n", ret); return ret; @@ -349,6 +531,16 @@ static int read_cr(struct spi_nor *nor) static int write_sr(struct spi_nor *nor, u8 val) { nor->bouncebuf[0] = val; + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 1)); + + return spi_mem_exec_op(nor->spimem, &op); + } + return nor->write_reg(nor, SPINOR_OP_WRSR, nor->bouncebuf, 1); } @@ -358,6 +550,16 @@ static int write_sr(struct spi_nor *nor, u8 val) */ static int write_enable(struct spi_nor *nor) { + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREN, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + + return spi_mem_exec_op(nor->spimem, &op); + } + return nor->write_reg(nor, SPINOR_OP_WREN, NULL, 0); } @@ -366,6 +568,16 @@ static int write_enable(struct spi_nor *nor) */ static int write_disable(struct spi_nor *nor) { + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRDI, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + + return spi_mem_exec_op(nor->spimem, &op); + } + return nor->write_reg(nor, SPINOR_OP_WRDI, NULL, 0); } @@ -465,12 +677,64 @@ static void spi_nor_set_4byte_opcodes(struct spi_nor *nor) } } +static int macronix_set_4byte(struct spi_nor *nor, bool enable) +{ + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(enable ? + SPINOR_OP_EN4B : + SPINOR_OP_EX4B, + 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->write_reg(nor, enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B, + NULL, 0); +} + +static int spansion_set_4byte(struct spi_nor *nor, bool enable) +{ + nor->bouncebuf[0] = enable << 7; + + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_BRWR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1)); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->write_reg(nor, SPINOR_OP_BRWR, nor->bouncebuf, 1); +} + +static int spi_nor_write_ear(struct spi_nor *nor, u8 ear) +{ + nor->bouncebuf[0] = ear; + + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREAR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1)); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->write_reg(nor, SPINOR_OP_WREAR, nor->bouncebuf, 1); +} + /* Enable/disable 4-byte addressing mode. */ static int set_4byte(struct spi_nor *nor, bool enable) { int status; bool need_wren = false; - u8 cmd; switch (JEDEC_MFR(nor->info)) { case SNOR_MFR_ST: @@ -483,8 +747,7 @@ static int set_4byte(struct spi_nor *nor, bool enable) if (need_wren) write_enable(nor); - cmd = enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B; - status = nor->write_reg(nor, cmd, NULL, 0); + status = macronix_set_4byte(nor, enable); if (need_wren) write_disable(nor); @@ -497,25 +760,37 @@ static int set_4byte(struct spi_nor *nor, bool enable) * We must clear the register to enable normal behavior. */ write_enable(nor); - nor->bouncebuf[0] = 0; - nor->write_reg(nor, SPINOR_OP_WREAR, - nor->bouncebuf, 1); + spi_nor_write_ear(nor, 0); write_disable(nor); } return status; default: /* Spansion style */ - nor->bouncebuf[0] = enable << 7; - return nor->write_reg(nor, SPINOR_OP_BRWR, nor->bouncebuf, 1); + return spansion_set_4byte(nor, enable); } } +static int spi_nor_xread_sr(struct spi_nor *nor, u8 *sr) +{ + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_XRDSR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(1, sr, 1)); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->read_reg(nor, SPINOR_OP_XRDSR, sr, 1); +} + static int s3an_sr_ready(struct spi_nor *nor) { int ret; - ret = nor->read_reg(nor, SPINOR_OP_XRDSR, nor->bouncebuf, 1); + ret = spi_nor_xread_sr(nor, nor->bouncebuf); if (ret < 0) { dev_err(nor->dev, "error %d reading XRDSR\n", (int) ret); return ret; @@ -524,6 +799,21 @@ static int s3an_sr_ready(struct spi_nor *nor) return !!(nor->bouncebuf[0] & XSR_RDY); } +static int spi_nor_clear_sr(struct spi_nor *nor) +{ + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLSR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->write_reg(nor, SPINOR_OP_CLSR, NULL, 0); +} + static int spi_nor_sr_ready(struct spi_nor *nor) { int sr = read_sr(nor); @@ -536,13 +826,28 @@ static int spi_nor_sr_ready(struct spi_nor *nor) else dev_err(nor->dev, "Programming Error occurred\n"); - nor->write_reg(nor, SPINOR_OP_CLSR, NULL, 0); + spi_nor_clear_sr(nor); return -EIO; } return !(sr & SR_WIP); } +static int spi_nor_clear_fsr(struct spi_nor *nor) +{ + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLFSR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->write_reg(nor, SPINOR_OP_CLFSR, NULL, 0); +} + static int spi_nor_fsr_ready(struct spi_nor *nor) { int fsr = read_fsr(nor); @@ -559,7 +864,7 @@ static int spi_nor_fsr_ready(struct spi_nor *nor) dev_err(nor->dev, "Attempted to modify a protected sector.\n"); - nor->write_reg(nor, SPINOR_OP_CLFSR, NULL, 0); + spi_nor_clear_fsr(nor); return -EIO; } @@ -627,6 +932,16 @@ static int erase_chip(struct spi_nor *nor) { dev_dbg(nor->dev, " %lldKiB\n", (long long)(nor->mtd.size >> 10)); + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CHIP_ERASE, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + + return spi_mem_exec_op(nor->spimem, &op); + } + return nor->write_reg(nor, SPINOR_OP_CHIP_ERASE, NULL, 0); } @@ -688,6 +1003,16 @@ static int spi_nor_erase_sector(struct spi_nor *nor, u32 addr) if (nor->erase) return nor->erase(nor, addr); + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(nor->erase_opcode, 1), + SPI_MEM_OP_ADDR(nor->addr_width, addr, 1), + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + + return spi_mem_exec_op(nor->spimem, &op); + } + /* * Default implementation, if driver doesn't have a specialized HW * control @@ -1403,7 +1728,18 @@ static int write_sr_cr(struct spi_nor *nor, u8 *sr_cr) write_enable(nor); - ret = nor->write_reg(nor, SPINOR_OP_WRSR, sr_cr, 2); + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(2, sr_cr, 1)); + + ret = spi_mem_exec_op(nor->spimem, &op); + } else { + ret = nor->write_reg(nor, SPINOR_OP_WRSR, sr_cr, 2); + } + if (ret < 0) { dev_err(nor->dev, "error while writing configuration register\n"); @@ -1584,6 +1920,36 @@ static int spansion_read_cr_quad_enable(struct spi_nor *nor) return 0; } +static int spi_nor_write_sr2(struct spi_nor *nor, u8 *sr2) +{ + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR2, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(1, sr2, 1)); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->write_reg(nor, SPINOR_OP_WRSR2, sr2, 1); +} + +static int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2) +{ + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR2, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(1, sr2, 1)); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->read_reg(nor, SPINOR_OP_RDSR2, sr2, 1); +} + /** * sr2_bit7_quad_enable() - set QE bit in Status Register 2. * @nor: pointer to a 'struct spi_nor' @@ -1602,7 +1968,7 @@ static int sr2_bit7_quad_enable(struct spi_nor *nor) int ret; /* Check current Quad Enable bit value. */ - ret = nor->read_reg(nor, SPINOR_OP_RDSR2, sr2, 1); + ret = spi_nor_read_sr2(nor, sr2); if (ret) return ret; if (*sr2 & SR2_QUAD_EN_BIT7) @@ -1613,7 +1979,7 @@ static int sr2_bit7_quad_enable(struct spi_nor *nor) write_enable(nor); - ret = nor->write_reg(nor, SPINOR_OP_WRSR2, sr2, 1); + ret = spi_nor_write_sr2(nor, sr2); if (ret < 0) { dev_err(nor->dev, "error while writing status register 2\n"); return -EINVAL; @@ -1626,7 +1992,7 @@ static int sr2_bit7_quad_enable(struct spi_nor *nor) } /* Read back and check it. */ - ret = nor->read_reg(nor, SPINOR_OP_RDSR2, sr2, 1); + ret = spi_nor_read_sr2(nor, sr2); if (!(ret > 0 && (*sr2 & SR2_QUAD_EN_BIT7))) { dev_err(nor->dev, "SR2 Quad bit not set\n"); return -EINVAL; @@ -2179,7 +2545,18 @@ static const struct flash_info *spi_nor_read_id(struct spi_nor *nor) u8 *id = nor->bouncebuf; const struct flash_info *info; - tmp = nor->read_reg(nor, SPINOR_OP_RDID, id, SPI_NOR_MAX_ID_LEN); + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(SPI_NOR_MAX_ID_LEN, id, 1)); + + tmp = spi_mem_exec_op(nor->spimem, &op); + } else { + tmp = nor->read_reg(nor, SPINOR_OP_RDID, id, + SPI_NOR_MAX_ID_LEN); + } if (tmp < 0) { dev_err(nor->dev, "error %d reading JEDEC ID\n", tmp); return ERR_PTR(tmp); @@ -2215,7 +2592,7 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len, if (nor->flags & SNOR_F_S3AN_ADDR_DEFAULT) addr = spi_nor_s3an_addr_convert(nor, addr); - ret = nor->read(nor, addr, len, buf); + ret = spi_nor_read_data(nor, addr, len, buf); if (ret == 0) { /* We shouldn't see 0-length reads */ ret = -EIO; @@ -2260,7 +2637,7 @@ static int sst_write(struct mtd_info *mtd, loff_t to, size_t len, nor->program_opcode = SPINOR_OP_BP; /* write one byte. */ - ret = nor->write(nor, to, 1, buf); + ret = spi_nor_write_data(nor, to, 1, buf); if (ret < 0) goto sst_write_err; WARN(ret != 1, "While writing 1 byte written %i bytes\n", @@ -2276,7 +2653,7 @@ static int sst_write(struct mtd_info *mtd, loff_t to, size_t len, nor->program_opcode = SPINOR_OP_AAI_WP; /* write two bytes. */ - ret = nor->write(nor, to, 2, buf + actual); + ret = spi_nor_write_data(nor, to, 2, buf + actual); if (ret < 0) goto sst_write_err; WARN(ret != 2, "While writing 2 bytes written %i bytes\n", @@ -2299,7 +2676,7 @@ static int sst_write(struct mtd_info *mtd, loff_t to, size_t len, write_enable(nor); nor->program_opcode = SPINOR_OP_BP; - ret = nor->write(nor, to, 1, buf + actual); + ret = spi_nor_write_data(nor, to, 1, buf + actual); if (ret < 0) goto sst_write_err; WARN(ret != 1, "While writing 1 byte written %i bytes\n", @@ -2361,7 +2738,7 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len, addr = spi_nor_s3an_addr_convert(nor, addr); write_enable(nor); - ret = nor->write(nor, addr, page_remain, buf + i); + ret = spi_nor_write_data(nor, addr, page_remain, buf + i); if (ret < 0) goto write_err; written = ret; @@ -2380,8 +2757,10 @@ write_err: static int spi_nor_check(struct spi_nor *nor) { - if (!nor->dev || !nor->read || !nor->write || - !nor->read_reg || !nor->write_reg) { + if (!nor->dev || + (!nor->spimem && + (!nor->read || !nor->write || !nor->read_reg || + !nor->write_reg))) { pr_err("spi-nor: please fill all the necessary fields!\n"); return -EINVAL; } @@ -2393,7 +2772,7 @@ static int s3an_nor_scan(struct spi_nor *nor) { int ret; - ret = nor->read_reg(nor, SPINOR_OP_XRDSR, nor->bouncebuf, 1); + ret = spi_nor_xread_sr(nor, nor->bouncebuf); if (ret < 0) { dev_err(nor->dev, "error %d reading XRDSR\n", (int) ret); return ret; @@ -2523,7 +2902,7 @@ static int spi_nor_read_raw(struct spi_nor *nor, u32 addr, size_t len, u8 *buf) int ret; while (len) { - ret = nor->read(nor, addr, len, buf); + ret = spi_nor_read_data(nor, addr, len, buf); if (!ret || ret > len) return -EIO; if (ret < 0) @@ -4122,6 +4501,10 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, /* * We need the bounce buffer early to read/write registers when going * through the spi-mem layer (buffers have to be DMA-able). + * For spi-mem drivers, we'll reallocate a new buffer if + * nor->page_size turns out to be greater than PAGE_SIZE (which + * shouldn't happen before long since NOR pages are usually less + * than 1KB) after spi_nor_scan() returns. */ nor->bouncebuf_size = PAGE_SIZE; nor->bouncebuf = devm_kmalloc(dev, nor->bouncebuf_size, @@ -4324,6 +4707,195 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, } EXPORT_SYMBOL_GPL(spi_nor_scan); +static int spi_nor_probe(struct spi_mem *spimem) +{ + struct spi_device *spi = spimem->spi; + struct flash_platform_data *data = dev_get_platdata(&spi->dev); + struct spi_nor *nor; + struct spi_nor_hwcaps hwcaps = { + .mask = SNOR_HWCAPS_READ | + SNOR_HWCAPS_READ_FAST | + SNOR_HWCAPS_PP, + }; + char *flash_name; + int ret; + + nor = devm_kzalloc(&spi->dev, sizeof(*nor), GFP_KERNEL); + if (!nor) + return -ENOMEM; + + nor->spimem = spimem; + nor->dev = &spi->dev; + spi_nor_set_flash_node(nor, spi->dev.of_node); + + spi_mem_set_drvdata(spimem, nor); + + if (spi->mode & SPI_RX_OCTAL) { + hwcaps.mask |= SNOR_HWCAPS_READ_1_1_8; + + if (spi->mode & SPI_TX_OCTAL) + hwcaps.mask |= (SNOR_HWCAPS_READ_1_8_8 | + SNOR_HWCAPS_PP_1_1_8 | + SNOR_HWCAPS_PP_1_8_8); + } else if (spi->mode & SPI_RX_QUAD) { + hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4; + + if (spi->mode & SPI_TX_QUAD) + hwcaps.mask |= (SNOR_HWCAPS_READ_1_4_4 | + SNOR_HWCAPS_PP_1_1_4 | + SNOR_HWCAPS_PP_1_4_4); + } else if (spi->mode & SPI_RX_DUAL) { + hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2; + + if (spi->mode & SPI_TX_DUAL) + hwcaps.mask |= SNOR_HWCAPS_READ_1_2_2; + } + + if (data && data->name) + nor->mtd.name = data->name; + + if (!nor->mtd.name) + nor->mtd.name = spi_mem_get_name(spimem); + + /* + * For some (historical?) reason many platforms provide two different + * names in flash_platform_data: "name" and "type". Quite often name is + * set to "m25p80" and then "type" provides a real chip name. + * If that's the case, respect "type" and ignore a "name". + */ + if (data && data->type) + flash_name = data->type; + else if (!strcmp(spi->modalias, "spi-nor")) + flash_name = NULL; /* auto-detect */ + else + flash_name = spi->modalias; + + ret = spi_nor_scan(nor, flash_name, &hwcaps); + if (ret) + return ret; + + /* + * None of the existing parts have > 512B pages, but let's play safe + * and add this logic so that if anyone ever adds support for such + * a NOR we don't end up with buffer overflows. + */ + if (nor->page_size > PAGE_SIZE) { + nor->bouncebuf_size = nor->page_size; + devm_kfree(nor->dev, nor->bouncebuf); + nor->bouncebuf = devm_kmalloc(nor->dev, + nor->bouncebuf_size, + GFP_KERNEL); + if (!nor->bouncebuf) + return -ENOMEM; + } + + return mtd_device_register(&nor->mtd, data ? data->parts : NULL, + data ? data->nr_parts : 0); +} + +static int spi_nor_remove(struct spi_mem *spimem) +{ + struct spi_nor *nor = spi_mem_get_drvdata(spimem); + + spi_nor_restore(nor); + + /* Clean up MTD stuff. */ + return mtd_device_unregister(&nor->mtd); +} + +static void spi_nor_shutdown(struct spi_mem *spimem) +{ + struct spi_nor *nor = spi_mem_get_drvdata(spimem); + + spi_nor_restore(nor); +} + +/* + * Do NOT add to this array without reading the following: + * + * Historically, many flash devices are bound to this driver by their name. But + * since most of these flash are compatible to some extent, and their + * differences can often be differentiated by the JEDEC read-ID command, we + * encourage new users to add support to the spi-nor library, and simply bind + * against a generic string here (e.g., "jedec,spi-nor"). + * + * Many flash names are kept here in this list (as well as in spi-nor.c) to + * keep them available as module aliases for existing platforms. + */ +static const struct spi_device_id spi_nor_dev_ids[] = { + /* + * Allow non-DT platform devices to bind to the "spi-nor" modalias, and + * hack around the fact that the SPI core does not provide uevent + * matching for .of_match_table + */ + {"spi-nor"}, + + /* + * Entries not used in DTs that should be safe to drop after replacing + * them with "spi-nor" in platform data. + */ + {"s25sl064a"}, {"w25x16"}, {"m25p10"}, {"m25px64"}, + + /* + * Entries that were used in DTs without "jedec,spi-nor" fallback and + * should be kept for backward compatibility. + */ + {"at25df321a"}, {"at25df641"}, {"at26df081a"}, + {"mx25l4005a"}, {"mx25l1606e"}, {"mx25l6405d"}, {"mx25l12805d"}, + {"mx25l25635e"},{"mx66l51235l"}, + {"n25q064"}, {"n25q128a11"}, {"n25q128a13"}, {"n25q512a"}, + {"s25fl256s1"}, {"s25fl512s"}, {"s25sl12801"}, {"s25fl008k"}, + {"s25fl064k"}, + {"sst25vf040b"},{"sst25vf016b"},{"sst25vf032b"},{"sst25wf040"}, + {"m25p40"}, {"m25p80"}, {"m25p16"}, {"m25p32"}, + {"m25p64"}, {"m25p128"}, + {"w25x80"}, {"w25x32"}, {"w25q32"}, {"w25q32dw"}, + {"w25q80bl"}, {"w25q128"}, {"w25q256"}, + + /* Flashes that can't be detected using JEDEC */ + {"m25p05-nonjedec"}, {"m25p10-nonjedec"}, {"m25p20-nonjedec"}, + {"m25p40-nonjedec"}, {"m25p80-nonjedec"}, {"m25p16-nonjedec"}, + {"m25p32-nonjedec"}, {"m25p64-nonjedec"}, {"m25p128-nonjedec"}, + + /* Everspin MRAMs (non-JEDEC) */ + { "mr25h128" }, /* 128 Kib, 40 MHz */ + { "mr25h256" }, /* 256 Kib, 40 MHz */ + { "mr25h10" }, /* 1 Mib, 40 MHz */ + { "mr25h40" }, /* 4 Mib, 40 MHz */ + + { }, +}; +MODULE_DEVICE_TABLE(spi, spi_nor_dev_ids); + +static const struct of_device_id spi_nor_of_table[] = { + /* + * Generic compatibility for SPI NOR that can be identified by the + * JEDEC READ ID opcode (0x9F). Use this, if possible. + */ + { .compatible = "jedec,spi-nor" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, spi_nor_of_table); + +/* + * REVISIT: many of these chips have deep power-down modes, which + * should clearly be entered on suspend() to minimize power use. + * And also when they're otherwise idle... + */ +static struct spi_mem_driver spi_nor_driver = { + .spidrv = { + .driver = { + .name = "spi-nor", + .of_match_table = spi_nor_of_table, + }, + .id_table = spi_nor_dev_ids, + }, + .probe = spi_nor_probe, + .remove = spi_nor_remove, + .shutdown = spi_nor_shutdown, +}; +module_spi_mem_driver(spi_nor_driver); + MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Huang Shijie "); MODULE_AUTHOR("Mike Lavender"); diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 6b5956a7a65a..4f35b1877889 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -9,6 +9,7 @@ #include #include #include +#include /* * Manufacturer IDs @@ -344,6 +345,7 @@ struct flash_info; * @mtd: point to a mtd_info structure * @lock: the lock for the read/write/erase/lock/unlock operations * @dev: point to a spi device, or a spi nor controller device. + * @spimem: point to the spi mem device * @bouncebuf: bounce buffer used when the buffer passed by the MTD * layer is not DMA-able * @bouncebuf_size: size of the bounce buffer @@ -384,6 +386,7 @@ struct spi_nor { struct mtd_info mtd; struct mutex lock; struct device *dev; + struct spi_mem *spimem; u8 *bouncebuf; size_t bouncebuf_size; const struct flash_info *info; -- cgit v1.2.3 From c76f5089796a948e8daaeda348040a3f98b18748 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 6 Aug 2019 10:40:41 +0530 Subject: mtd: spi-nor: Rework hwcaps selection for the spi-mem case The spi-mem layer provides a spi_mem_supports_op() function to check whether a specific operation is supported by the controller or not. This is much more accurate than the hwcaps selection logic based on SPI_{RX,TX}_ flags. Rework the hwcaps selection logic to use spi_mem_supports_op() when nor->spimem != NULL. Signed-off-by: Boris Brezillon Signed-off-by: Vignesh Raghavendra Signed-off-by: Tudor Ambarus --- drivers/mtd/spi-nor/spi-nor.c | 183 +++++++++++++++++++++++++++++++++--------- include/linux/mtd/spi-nor.h | 14 ++++ 2 files changed, 161 insertions(+), 36 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index de906ab907c7..63af87609bac 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -2951,6 +2951,129 @@ static int spi_nor_read_sfdp(struct spi_nor *nor, u32 addr, return ret; } +/** + * spi_nor_spimem_check_op - check if the operation is supported + * by controller + *@nor: pointer to a 'struct spi_nor' + *@op: pointer to op template to be checked + * + * Returns 0 if operation is supported, -ENOTSUPP otherwise. + */ +static int spi_nor_spimem_check_op(struct spi_nor *nor, + struct spi_mem_op *op) +{ + /* + * First test with 4 address bytes. The opcode itself might + * be a 3B addressing opcode but we don't care, because + * SPI controller implementation should not check the opcode, + * but just the sequence. + */ + op->addr.nbytes = 4; + if (!spi_mem_supports_op(nor->spimem, op)) { + if (nor->mtd.size > SZ_16M) + return -ENOTSUPP; + + /* If flash size <= 16MB, 3 address bytes are sufficient */ + op->addr.nbytes = 3; + if (!spi_mem_supports_op(nor->spimem, op)) + return -ENOTSUPP; + } + + return 0; +} + +/** + * spi_nor_spimem_check_readop - check if the read op is supported + * by controller + *@nor: pointer to a 'struct spi_nor' + *@read: pointer to op template to be checked + * + * Returns 0 if operation is supported, -ENOTSUPP otherwise. + */ +static int spi_nor_spimem_check_readop(struct spi_nor *nor, + const struct spi_nor_read_command *read) +{ + struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(read->opcode, 1), + SPI_MEM_OP_ADDR(3, 0, 1), + SPI_MEM_OP_DUMMY(0, 1), + SPI_MEM_OP_DATA_IN(0, NULL, 1)); + + op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(read->proto); + op.addr.buswidth = spi_nor_get_protocol_addr_nbits(read->proto); + op.data.buswidth = spi_nor_get_protocol_data_nbits(read->proto); + op.dummy.buswidth = op.addr.buswidth; + op.dummy.nbytes = (read->num_mode_clocks + read->num_wait_states) * + op.dummy.buswidth / 8; + + return spi_nor_spimem_check_op(nor, &op); +} + +/** + * spi_nor_spimem_check_pp - check if the page program op is supported + * by controller + *@nor: pointer to a 'struct spi_nor' + *@pp: pointer to op template to be checked + * + * Returns 0 if operation is supported, -ENOTSUPP otherwise. + */ +static int spi_nor_spimem_check_pp(struct spi_nor *nor, + const struct spi_nor_pp_command *pp) +{ + struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(pp->opcode, 1), + SPI_MEM_OP_ADDR(3, 0, 1), + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(0, NULL, 1)); + + op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(pp->proto); + op.addr.buswidth = spi_nor_get_protocol_addr_nbits(pp->proto); + op.data.buswidth = spi_nor_get_protocol_data_nbits(pp->proto); + + return spi_nor_spimem_check_op(nor, &op); +} + +/** + * spi_nor_spimem_adjust_hwcaps - Find optimal Read/Write protocol + * based on SPI controller capabilities + * @nor: pointer to a 'struct spi_nor' + * @params: pointer to the 'struct spi_nor_flash_parameter' + * representing SPI NOR flash capabilities + * @hwcaps: pointer to resulting capabilities after adjusting + * according to controller and flash's capability + */ +static void +spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, + const struct spi_nor_flash_parameter *params, + u32 *hwcaps) +{ + unsigned int cap; + + /* 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; + + for (cap = 0; cap < sizeof(*hwcaps) * BITS_PER_BYTE; cap++) { + int rdidx, ppidx; + + if (!(*hwcaps & BIT(cap))) + continue; + + rdidx = spi_nor_hwcaps_read2cmd(BIT(cap)); + if (rdidx >= 0 && + spi_nor_spimem_check_readop(nor, ¶ms->reads[rdidx])) + *hwcaps &= ~BIT(cap); + + ppidx = spi_nor_hwcaps_pp2cmd(BIT(cap)); + if (ppidx < 0) + continue; + + if (spi_nor_spimem_check_pp(nor, + ¶ms->page_programs[ppidx])) + *hwcaps &= ~BIT(cap); + } +} + /** * spi_nor_read_sfdp_dma_unsafe() - read Serial Flash Discoverable Parameters. * @nor: pointer to a 'struct spi_nor' @@ -4360,16 +4483,25 @@ static int spi_nor_setup(struct spi_nor *nor, */ shared_mask = hwcaps->mask & params->hwcaps.mask; - /* SPI n-n-n protocols are not supported yet. */ - ignored_mask = (SNOR_HWCAPS_READ_2_2_2 | - SNOR_HWCAPS_READ_4_4_4 | - SNOR_HWCAPS_READ_8_8_8 | - SNOR_HWCAPS_PP_4_4_4 | - SNOR_HWCAPS_PP_8_8_8); - if (shared_mask & ignored_mask) { - dev_dbg(nor->dev, - "SPI n-n-n protocols are not supported yet.\n"); - shared_mask &= ~ignored_mask; + if (nor->spimem) { + /* + * When called from spi_nor_probe(), all caps are set and we + * need to discard some of them based on what the SPI + * controller actually supports (using spi_mem_supports_op()). + */ + spi_nor_spimem_adjust_hwcaps(nor, params, &shared_mask); + } else { + /* + * SPI n-n-n protocols are not supported when the SPI + * controller directly implements the spi_nor interface. + * Yet another reason to switch to spi-mem. + */ + ignored_mask = SNOR_HWCAPS_X_X_X; + if (shared_mask & ignored_mask) { + dev_dbg(nor->dev, + "SPI n-n-n protocols are not supported.\n"); + shared_mask &= ~ignored_mask; + } } /* Select the (Fast) Read command. */ @@ -4712,11 +4844,11 @@ static int spi_nor_probe(struct spi_mem *spimem) struct spi_device *spi = spimem->spi; struct flash_platform_data *data = dev_get_platdata(&spi->dev); struct spi_nor *nor; - struct spi_nor_hwcaps hwcaps = { - .mask = SNOR_HWCAPS_READ | - SNOR_HWCAPS_READ_FAST | - SNOR_HWCAPS_PP, - }; + /* + * Enable all caps by default. The core will mask them after + * checking what's really supported using spi_mem_supports_op(). + */ + const struct spi_nor_hwcaps hwcaps = { .mask = SNOR_HWCAPS_ALL }; char *flash_name; int ret; @@ -4730,27 +4862,6 @@ static int spi_nor_probe(struct spi_mem *spimem) spi_mem_set_drvdata(spimem, nor); - if (spi->mode & SPI_RX_OCTAL) { - hwcaps.mask |= SNOR_HWCAPS_READ_1_1_8; - - if (spi->mode & SPI_TX_OCTAL) - hwcaps.mask |= (SNOR_HWCAPS_READ_1_8_8 | - SNOR_HWCAPS_PP_1_1_8 | - SNOR_HWCAPS_PP_1_8_8); - } else if (spi->mode & SPI_RX_QUAD) { - hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4; - - if (spi->mode & SPI_TX_QUAD) - hwcaps.mask |= (SNOR_HWCAPS_READ_1_4_4 | - SNOR_HWCAPS_PP_1_1_4 | - SNOR_HWCAPS_PP_1_4_4); - } else if (spi->mode & SPI_RX_DUAL) { - hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2; - - if (spi->mode & SPI_TX_DUAL) - hwcaps.mask |= SNOR_HWCAPS_READ_1_2_2; - } - if (data && data->name) nor->mtd.name = data->name; diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 4f35b1877889..5f1acb1867dd 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -524,6 +524,20 @@ struct spi_nor_hwcaps { #define SNOR_HWCAPS_PP_1_8_8 BIT(21) #define SNOR_HWCAPS_PP_8_8_8 BIT(22) +#define SNOR_HWCAPS_X_X_X (SNOR_HWCAPS_READ_2_2_2 | \ + SNOR_HWCAPS_READ_4_4_4 | \ + SNOR_HWCAPS_READ_8_8_8 | \ + SNOR_HWCAPS_PP_4_4_4 | \ + SNOR_HWCAPS_PP_8_8_8) + +#define SNOR_HWCAPS_DTR (SNOR_HWCAPS_READ_1_1_1_DTR | \ + SNOR_HWCAPS_READ_1_2_2_DTR | \ + SNOR_HWCAPS_READ_1_4_4_DTR | \ + SNOR_HWCAPS_READ_1_8_8_DTR) + +#define SNOR_HWCAPS_ALL (SNOR_HWCAPS_READ_MASK | \ + SNOR_HWCAPS_PP_MASK) + /** * spi_nor_scan() - scan the SPI NOR * @nor: the spi_nor structure -- cgit v1.2.3 From 5fbdac150d4813765fd515ffad6c7f5e63be14aa Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Wed, 17 Jul 2019 08:48:06 +0000 Subject: mtd: spi-nor: fix description for int (*flash_is_locked)() The description was interleaved. Signed-off-by: Tudor Ambarus Reviewed-by: Vignesh Raghavendra --- include/linux/mtd/spi-nor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/linux/mtd') diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 5f1acb1867dd..63560b375168 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -376,10 +376,10 @@ struct flash_info; * @flash_lock: [FLASH-SPECIFIC] lock a region of the SPI NOR * @flash_unlock: [FLASH-SPECIFIC] unlock a region of the SPI NOR * @flash_is_locked: [FLASH-SPECIFIC] check if a region of the SPI NOR is + * completely locked * @quad_enable: [FLASH-SPECIFIC] enables SPI NOR quad mode * @clear_sr_bp: [FLASH-SPECIFIC] clears the Block Protection Bits from * the SPI NOR Status Register. - * completely locked * @priv: the private data */ struct spi_nor { -- cgit v1.2.3 From 92b6d38f1a3d59fb43d36c2d9681b9b8f1e46c05 Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Thu, 22 Aug 2019 16:28:15 +0300 Subject: mtd: spi-nor: Remove unused macro Remove leftover from nor->cmd_buf. Signed-off-by: Tudor Ambarus --- include/linux/mtd/spi-nor.h | 1 - 1 file changed, 1 deletion(-) (limited to 'include/linux/mtd') diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 63560b375168..3075ac73b171 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -225,7 +225,6 @@ static inline u8 spi_nor_get_protocol_width(enum spi_nor_protocol proto) return spi_nor_get_protocol_data_nbits(proto); } -#define SPI_NOR_MAX_CMD_SIZE 8 enum spi_nor_ops { SPI_NOR_OPS_READ = 0, SPI_NOR_OPS_WRITE, -- cgit v1.2.3 From 47599127a2e8c42843e27fc370e63cc5344a2a38 Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Fri, 23 Aug 2019 15:53:35 +0000 Subject: mtd: spi-nor: Regroup flash parameter and settings The scope is to move all [FLASH-SPECIFIC] parameters and settings from 'struct spi_nor' to 'struct spi_nor_flash_parameter'. 'struct spi_nor_flash_parameter' describes the hardware capabilities and associated settings of the SPI NOR flash memory. It includes legacy flash parameters and settings that can be overwritten by the spi_nor_fixups hooks, or dynamically when parsing the JESD216 Serial Flash Discoverable Parameters (SFDP) tables. All SFDP params and settings will fit inside 'struct spi_nor_flash_parameter'. Move spi_nor_hwcaps related code to avoid forward declarations. Add a forward declaration that we can't avoid: 'struct spi_nor' will be used in 'struct spi_nor_flash_parameter'. Signed-off-by: Tudor Ambarus Reviewed-by: Boris Brezillon Reviewed-by: Vignesh Raghavendra --- drivers/mtd/spi-nor/spi-nor.c | 65 ------------ include/linux/mtd/spi-nor.h | 239 +++++++++++++++++++++++++++++------------- 2 files changed, 164 insertions(+), 140 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index 0597cb8257b0..d35dc6a97521 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -40,71 +40,6 @@ #define SPI_NOR_MAX_ID_LEN 6 #define SPI_NOR_MAX_ADDR_WIDTH 4 -struct spi_nor_read_command { - u8 num_mode_clocks; - u8 num_wait_states; - u8 opcode; - enum spi_nor_protocol proto; -}; - -struct spi_nor_pp_command { - u8 opcode; - enum spi_nor_protocol proto; -}; - -enum spi_nor_read_command_index { - SNOR_CMD_READ, - SNOR_CMD_READ_FAST, - SNOR_CMD_READ_1_1_1_DTR, - - /* Dual SPI */ - SNOR_CMD_READ_1_1_2, - SNOR_CMD_READ_1_2_2, - SNOR_CMD_READ_2_2_2, - SNOR_CMD_READ_1_2_2_DTR, - - /* Quad SPI */ - SNOR_CMD_READ_1_1_4, - SNOR_CMD_READ_1_4_4, - SNOR_CMD_READ_4_4_4, - SNOR_CMD_READ_1_4_4_DTR, - - /* Octal SPI */ - SNOR_CMD_READ_1_1_8, - SNOR_CMD_READ_1_8_8, - SNOR_CMD_READ_8_8_8, - SNOR_CMD_READ_1_8_8_DTR, - - SNOR_CMD_READ_MAX -}; - -enum spi_nor_pp_command_index { - SNOR_CMD_PP, - - /* Quad SPI */ - SNOR_CMD_PP_1_1_4, - SNOR_CMD_PP_1_4_4, - SNOR_CMD_PP_4_4_4, - - /* Octal SPI */ - SNOR_CMD_PP_1_1_8, - SNOR_CMD_PP_1_8_8, - SNOR_CMD_PP_8_8_8, - - SNOR_CMD_PP_MAX -}; - -struct spi_nor_flash_parameter { - u64 size; - u32 page_size; - - struct spi_nor_hwcaps hwcaps; - struct spi_nor_read_command reads[SNOR_CMD_READ_MAX]; - struct spi_nor_pp_command page_programs[SNOR_CMD_PP_MAX]; - - int (*quad_enable)(struct spi_nor *nor); -}; - struct sfdp_parameter_header { u8 id_lsb; u8 minor; diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 3075ac73b171..77ba692d9348 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -333,6 +333,165 @@ struct spi_nor_erase_map { u8 uniform_erase_type; }; +/** + * struct spi_nor_hwcaps - Structure for describing the hardware capabilies + * supported by the SPI controller (bus master). + * @mask: the bitmask listing all the supported hw capabilies + */ +struct spi_nor_hwcaps { + u32 mask; +}; + +/* + *(Fast) Read capabilities. + * MUST be ordered by priority: the higher bit position, the higher priority. + * As a matter of performances, it is relevant to use Octal SPI protocols first, + * then Quad SPI protocols before Dual SPI protocols, Fast Read and lastly + * (Slow) Read. + */ +#define SNOR_HWCAPS_READ_MASK GENMASK(14, 0) +#define SNOR_HWCAPS_READ BIT(0) +#define SNOR_HWCAPS_READ_FAST BIT(1) +#define SNOR_HWCAPS_READ_1_1_1_DTR BIT(2) + +#define SNOR_HWCAPS_READ_DUAL GENMASK(6, 3) +#define SNOR_HWCAPS_READ_1_1_2 BIT(3) +#define SNOR_HWCAPS_READ_1_2_2 BIT(4) +#define SNOR_HWCAPS_READ_2_2_2 BIT(5) +#define SNOR_HWCAPS_READ_1_2_2_DTR BIT(6) + +#define SNOR_HWCAPS_READ_QUAD GENMASK(10, 7) +#define SNOR_HWCAPS_READ_1_1_4 BIT(7) +#define SNOR_HWCAPS_READ_1_4_4 BIT(8) +#define SNOR_HWCAPS_READ_4_4_4 BIT(9) +#define SNOR_HWCAPS_READ_1_4_4_DTR BIT(10) + +#define SNOR_HWCAPS_READ_OCTAL GENMASK(14, 11) +#define SNOR_HWCAPS_READ_1_1_8 BIT(11) +#define SNOR_HWCAPS_READ_1_8_8 BIT(12) +#define SNOR_HWCAPS_READ_8_8_8 BIT(13) +#define SNOR_HWCAPS_READ_1_8_8_DTR BIT(14) + +/* + * Page Program capabilities. + * MUST be ordered by priority: the higher bit position, the higher priority. + * Like (Fast) Read capabilities, Octal/Quad SPI protocols are preferred to the + * legacy SPI 1-1-1 protocol. + * Note that Dual Page Programs are not supported because there is no existing + * JEDEC/SFDP standard to define them. Also at this moment no SPI flash memory + * implements such commands. + */ +#define SNOR_HWCAPS_PP_MASK GENMASK(22, 16) +#define SNOR_HWCAPS_PP BIT(16) + +#define SNOR_HWCAPS_PP_QUAD GENMASK(19, 17) +#define SNOR_HWCAPS_PP_1_1_4 BIT(17) +#define SNOR_HWCAPS_PP_1_4_4 BIT(18) +#define SNOR_HWCAPS_PP_4_4_4 BIT(19) + +#define SNOR_HWCAPS_PP_OCTAL GENMASK(22, 20) +#define SNOR_HWCAPS_PP_1_1_8 BIT(20) +#define SNOR_HWCAPS_PP_1_8_8 BIT(21) +#define SNOR_HWCAPS_PP_8_8_8 BIT(22) + +#define SNOR_HWCAPS_X_X_X (SNOR_HWCAPS_READ_2_2_2 | \ + SNOR_HWCAPS_READ_4_4_4 | \ + SNOR_HWCAPS_READ_8_8_8 | \ + SNOR_HWCAPS_PP_4_4_4 | \ + SNOR_HWCAPS_PP_8_8_8) + +#define SNOR_HWCAPS_DTR (SNOR_HWCAPS_READ_1_1_1_DTR | \ + SNOR_HWCAPS_READ_1_2_2_DTR | \ + SNOR_HWCAPS_READ_1_4_4_DTR | \ + SNOR_HWCAPS_READ_1_8_8_DTR) + +#define SNOR_HWCAPS_ALL (SNOR_HWCAPS_READ_MASK | \ + SNOR_HWCAPS_PP_MASK) + +struct spi_nor_read_command { + u8 num_mode_clocks; + u8 num_wait_states; + u8 opcode; + enum spi_nor_protocol proto; +}; + +struct spi_nor_pp_command { + u8 opcode; + enum spi_nor_protocol proto; +}; + +enum spi_nor_read_command_index { + SNOR_CMD_READ, + SNOR_CMD_READ_FAST, + SNOR_CMD_READ_1_1_1_DTR, + + /* Dual SPI */ + SNOR_CMD_READ_1_1_2, + SNOR_CMD_READ_1_2_2, + SNOR_CMD_READ_2_2_2, + SNOR_CMD_READ_1_2_2_DTR, + + /* Quad SPI */ + SNOR_CMD_READ_1_1_4, + SNOR_CMD_READ_1_4_4, + SNOR_CMD_READ_4_4_4, + SNOR_CMD_READ_1_4_4_DTR, + + /* Octal SPI */ + SNOR_CMD_READ_1_1_8, + SNOR_CMD_READ_1_8_8, + SNOR_CMD_READ_8_8_8, + SNOR_CMD_READ_1_8_8_DTR, + + SNOR_CMD_READ_MAX +}; + +enum spi_nor_pp_command_index { + SNOR_CMD_PP, + + /* Quad SPI */ + SNOR_CMD_PP_1_1_4, + SNOR_CMD_PP_1_4_4, + SNOR_CMD_PP_4_4_4, + + /* Octal SPI */ + SNOR_CMD_PP_1_1_8, + SNOR_CMD_PP_1_8_8, + SNOR_CMD_PP_8_8_8, + + SNOR_CMD_PP_MAX +}; + +/* Forward declaration that will be used in 'struct spi_nor_flash_parameter' */ +struct spi_nor; + +/** + * struct spi_nor_flash_parameter - SPI NOR flash parameters and settings. + * Includes legacy flash parameters and settings that can be overwritten + * by the spi_nor_fixups hooks, or dynamically when parsing the JESD216 + * Serial Flash Discoverable Parameters (SFDP) tables. + * + * @size: the flash memory density in bytes. + * @page_size: the page size of the SPI NOR flash memory. + * @hwcaps: describes the read and page program hardware + * capabilities. + * @reads: read capabilities ordered by priority: the higher index + * in the array, the higher priority. + * @page_programs: page program capabilities ordered by priority: the + * higher index in the array, the higher priority. + * @quad_enable: enables SPI NOR quad mode. + */ +struct spi_nor_flash_parameter { + u64 size; + u32 page_size; + + struct spi_nor_hwcaps hwcaps; + struct spi_nor_read_command reads[SNOR_CMD_READ_MAX]; + struct spi_nor_pp_command page_programs[SNOR_CMD_PP_MAX]; + + int (*quad_enable)(struct spi_nor *nor); +}; + /** * struct flash_info - Forward declaration of a structure used internally by * spi_nor_scan() @@ -379,6 +538,10 @@ struct flash_info; * @quad_enable: [FLASH-SPECIFIC] enables SPI NOR quad mode * @clear_sr_bp: [FLASH-SPECIFIC] clears the Block Protection Bits from * the SPI NOR Status Register. + * @params: [FLASH-SPECIFIC] SPI-NOR flash parameters and settings. + * The structure includes legacy flash parameters and + * settings that can be overwritten by the spi_nor_fixups + * hooks, or dynamically when parsing the SFDP tables. * @priv: the private data */ struct spi_nor { @@ -418,6 +581,7 @@ 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 (*clear_sr_bp)(struct spi_nor *nor); + struct spi_nor_flash_parameter params; void *priv; }; @@ -462,81 +626,6 @@ static inline struct device_node *spi_nor_get_flash_node(struct spi_nor *nor) return mtd_get_of_node(&nor->mtd); } -/** - * struct spi_nor_hwcaps - Structure for describing the hardware capabilies - * supported by the SPI controller (bus master). - * @mask: the bitmask listing all the supported hw capabilies - */ -struct spi_nor_hwcaps { - u32 mask; -}; - -/* - *(Fast) Read capabilities. - * MUST be ordered by priority: the higher bit position, the higher priority. - * As a matter of performances, it is relevant to use Octal SPI protocols first, - * then Quad SPI protocols before Dual SPI protocols, Fast Read and lastly - * (Slow) Read. - */ -#define SNOR_HWCAPS_READ_MASK GENMASK(14, 0) -#define SNOR_HWCAPS_READ BIT(0) -#define SNOR_HWCAPS_READ_FAST BIT(1) -#define SNOR_HWCAPS_READ_1_1_1_DTR BIT(2) - -#define SNOR_HWCAPS_READ_DUAL GENMASK(6, 3) -#define SNOR_HWCAPS_READ_1_1_2 BIT(3) -#define SNOR_HWCAPS_READ_1_2_2 BIT(4) -#define SNOR_HWCAPS_READ_2_2_2 BIT(5) -#define SNOR_HWCAPS_READ_1_2_2_DTR BIT(6) - -#define SNOR_HWCAPS_READ_QUAD GENMASK(10, 7) -#define SNOR_HWCAPS_READ_1_1_4 BIT(7) -#define SNOR_HWCAPS_READ_1_4_4 BIT(8) -#define SNOR_HWCAPS_READ_4_4_4 BIT(9) -#define SNOR_HWCAPS_READ_1_4_4_DTR BIT(10) - -#define SNOR_HWCAPS_READ_OCTAL GENMASK(14, 11) -#define SNOR_HWCAPS_READ_1_1_8 BIT(11) -#define SNOR_HWCAPS_READ_1_8_8 BIT(12) -#define SNOR_HWCAPS_READ_8_8_8 BIT(13) -#define SNOR_HWCAPS_READ_1_8_8_DTR BIT(14) - -/* - * Page Program capabilities. - * MUST be ordered by priority: the higher bit position, the higher priority. - * Like (Fast) Read capabilities, Octal/Quad SPI protocols are preferred to the - * legacy SPI 1-1-1 protocol. - * Note that Dual Page Programs are not supported because there is no existing - * JEDEC/SFDP standard to define them. Also at this moment no SPI flash memory - * implements such commands. - */ -#define SNOR_HWCAPS_PP_MASK GENMASK(22, 16) -#define SNOR_HWCAPS_PP BIT(16) - -#define SNOR_HWCAPS_PP_QUAD GENMASK(19, 17) -#define SNOR_HWCAPS_PP_1_1_4 BIT(17) -#define SNOR_HWCAPS_PP_1_4_4 BIT(18) -#define SNOR_HWCAPS_PP_4_4_4 BIT(19) - -#define SNOR_HWCAPS_PP_OCTAL GENMASK(22, 20) -#define SNOR_HWCAPS_PP_1_1_8 BIT(20) -#define SNOR_HWCAPS_PP_1_8_8 BIT(21) -#define SNOR_HWCAPS_PP_8_8_8 BIT(22) - -#define SNOR_HWCAPS_X_X_X (SNOR_HWCAPS_READ_2_2_2 | \ - SNOR_HWCAPS_READ_4_4_4 | \ - SNOR_HWCAPS_READ_8_8_8 | \ - SNOR_HWCAPS_PP_4_4_4 | \ - SNOR_HWCAPS_PP_8_8_8) - -#define SNOR_HWCAPS_DTR (SNOR_HWCAPS_READ_1_1_1_DTR | \ - SNOR_HWCAPS_READ_1_2_2_DTR | \ - SNOR_HWCAPS_READ_1_4_4_DTR | \ - SNOR_HWCAPS_READ_1_8_8_DTR) - -#define SNOR_HWCAPS_ALL (SNOR_HWCAPS_READ_MASK | \ - SNOR_HWCAPS_PP_MASK) - /** * spi_nor_scan() - scan the SPI NOR * @nor: the spi_nor structure -- cgit v1.2.3 From 42f5994724bcbcdbeb743dcc3e2756ec582cb86b Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Fri, 23 Aug 2019 15:53:39 +0000 Subject: mtd: spi-nor: Drop quad_enable() from 'struct spi-nor' All flash parameters and settings should reside inside 'struct spi_nor_flash_parameter'. Drop the local copy of quad_enable() and use the one from 'struct spi_nor_flash_parameter'. Signed-off-by: Tudor Ambarus Reviewed-by: Boris Brezillon Reviewed-by: Vignesh Raghavendra --- drivers/mtd/spi-nor/spi-nor.c | 40 +++++++++++++++++++++++----------------- include/linux/mtd/spi-nor.h | 2 -- 2 files changed, 23 insertions(+), 19 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index e9b9cd70a999..effda372cb33 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -4403,7 +4403,6 @@ static int spi_nor_setup(struct spi_nor *nor, { struct spi_nor_flash_parameter *params = &nor->params; u32 ignored_mask, shared_mask; - bool enable_quad_io; int err; /* @@ -4457,23 +4456,33 @@ static int spi_nor_setup(struct spi_nor *nor, return err; } - /* Enable Quad I/O if needed. */ - enable_quad_io = (spi_nor_get_protocol_width(nor->read_proto) == 4 || - spi_nor_get_protocol_width(nor->write_proto) == 4); - if (enable_quad_io && params->quad_enable) - nor->quad_enable = params->quad_enable; - else - nor->quad_enable = NULL; - return 0; } +/** + * spi_nor_quad_enable() - enable Quad I/O if needed. + * @nor: pointer to a 'struct spi_nor' + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_quad_enable(struct spi_nor *nor) +{ + if (!nor->params.quad_enable) + return 0; + + if (!(spi_nor_get_protocol_width(nor->read_proto) == 4 || + spi_nor_get_protocol_width(nor->write_proto) == 4)) + return 0; + + return nor->params.quad_enable(nor); +} + static int spi_nor_init(struct spi_nor *nor) { int err; if (nor->clear_sr_bp) { - if (nor->quad_enable == spansion_quad_enable) + if (nor->params.quad_enable == spansion_quad_enable) nor->clear_sr_bp = spi_nor_spansion_clear_sr_bp; err = nor->clear_sr_bp(nor); @@ -4484,12 +4493,10 @@ static int spi_nor_init(struct spi_nor *nor) } } - if (nor->quad_enable) { - err = nor->quad_enable(nor); - if (err) { - dev_err(nor->dev, "quad mode not supported\n"); - return err; - } + err = spi_nor_quad_enable(nor); + if (err) { + dev_err(nor->dev, "quad mode not supported\n"); + return err; } if (nor->addr_width == 4 && !(nor->flags & SNOR_F_4B_OPCODES)) { @@ -4706,7 +4713,6 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, * - select op codes for (Fast) Read, Page Program and Sector Erase. * - set the number of dummy cycles (mode cycles + wait states). * - set the SPI protocols for register and memory accesses. - * - set the Quad Enable bit if needed (required by SPI x-y-4 protos). */ ret = spi_nor_setup(nor, hwcaps); if (ret) diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 77ba692d9348..17787238f0e9 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -535,7 +535,6 @@ struct flash_info; * @flash_unlock: [FLASH-SPECIFIC] unlock a region of the SPI NOR * @flash_is_locked: [FLASH-SPECIFIC] check if a region of the SPI NOR is * completely locked - * @quad_enable: [FLASH-SPECIFIC] enables SPI NOR quad mode * @clear_sr_bp: [FLASH-SPECIFIC] clears the Block Protection Bits from * the SPI NOR Status Register. * @params: [FLASH-SPECIFIC] SPI-NOR flash parameters and settings. @@ -579,7 +578,6 @@ struct spi_nor { int (*flash_lock)(struct spi_nor *nor, loff_t ofs, uint64_t len); int (*flash_unlock)(struct spi_nor *nor, loff_t ofs, uint64_t len); int (*flash_is_locked)(struct spi_nor *nor, loff_t ofs, uint64_t len); - int (*quad_enable)(struct spi_nor *nor); int (*clear_sr_bp)(struct spi_nor *nor); struct spi_nor_flash_parameter params; -- cgit v1.2.3 From c46872170a54c902425c8580fd0a75a56ac1d147 Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Fri, 23 Aug 2019 17:36:03 +0300 Subject: mtd: spi-nor: Move erase_map to 'struct spi_nor_flash_parameter' All flash parameters and settings should reside inside 'struct spi_nor_flash_parameter'. Move the SMPT parsed erase map from 'struct spi_nor' to 'struct spi_nor_flash_parameter'. Please note that there is a roll-back mechanism for the flash parameter and settings, for cases when SFDP parser fails. The SFDP parser receives a Stack allocated copy of nor->params, called sfdp_params, and uses it to retrieve the serial flash discoverable parameters. JESD216 SFDP is a standard and has a higher priority than the default initialized flash parameters, so will overwrite the sfdp_params data when needed. All SFDP code uses the local copy of nor->params, that will overwrite it in the end, if the parser succeds. Saving and restoring the nor->params.erase_map is no longer needed, since the SFDP code does not touch it. Signed-off-by: Tudor Ambarus Reviewed-by: Boris Brezillon Reviewed-by: Vignesh Raghavendra --- drivers/mtd/spi-nor/spi-nor.c | 40 +++++++++++++++++++++------------------- include/linux/mtd/spi-nor.h | 8 +++++--- 2 files changed, 26 insertions(+), 22 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index effda372cb33..9dd6cd8cd13c 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -600,7 +600,7 @@ static void spi_nor_set_4byte_opcodes(struct spi_nor *nor) nor->erase_opcode = spi_nor_convert_3to4_erase(nor->erase_opcode); if (!spi_nor_has_uniform_erase(nor)) { - struct spi_nor_erase_map *map = &nor->erase_map; + struct spi_nor_erase_map *map = &nor->params.erase_map; struct spi_nor_erase_type *erase; int i; @@ -1133,7 +1133,7 @@ static int spi_nor_init_erase_cmd_list(struct spi_nor *nor, struct list_head *erase_list, u64 addr, u32 len) { - const struct spi_nor_erase_map *map = &nor->erase_map; + const struct spi_nor_erase_map *map = &nor->params.erase_map; const struct spi_nor_erase_type *erase, *prev_erase = NULL; struct spi_nor_erase_region *region; struct spi_nor_erase_command *cmd = NULL; @@ -3328,7 +3328,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, const struct sfdp_parameter_header *bfpt_header, struct spi_nor_flash_parameter *params) { - struct spi_nor_erase_map *map = &nor->erase_map; + struct spi_nor_erase_map *map = ¶ms->erase_map; struct spi_nor_erase_type *erase_type = map->erase_type; struct sfdp_bfpt bfpt; size_t len; @@ -3409,7 +3409,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, * Erase Types defined in the bfpt table. */ erase_mask = 0; - memset(&nor->erase_map, 0, sizeof(nor->erase_map)); + memset(¶ms->erase_map, 0, sizeof(params->erase_map)); for (i = 0; i < ARRAY_SIZE(sfdp_bfpt_erases); i++) { const struct sfdp_bfpt_erase *er = &sfdp_bfpt_erases[i]; u32 erasesize; @@ -3684,14 +3684,18 @@ spi_nor_region_check_overlay(struct spi_nor_erase_region *region, /** * spi_nor_init_non_uniform_erase_map() - initialize the non-uniform erase map * @nor: pointer to a 'struct spi_nor' + * @params: pointer to a duplicate 'struct spi_nor_flash_parameter' that is + * used for storing SFDP parsed data * @smpt: pointer to the sector map parameter table * * Return: 0 on success, -errno otherwise. */ -static int spi_nor_init_non_uniform_erase_map(struct spi_nor *nor, - const u32 *smpt) +static int +spi_nor_init_non_uniform_erase_map(struct spi_nor *nor, + struct spi_nor_flash_parameter *params, + const u32 *smpt) { - struct spi_nor_erase_map *map = &nor->erase_map; + struct spi_nor_erase_map *map = ¶ms->erase_map; struct spi_nor_erase_type *erase = map->erase_type; struct spi_nor_erase_region *region; u64 offset; @@ -3770,6 +3774,8 @@ static int spi_nor_init_non_uniform_erase_map(struct spi_nor *nor, * spi_nor_parse_smpt() - parse Sector Map Parameter Table * @nor: pointer to a 'struct spi_nor' * @smpt_header: sector map parameter table header + * @params: pointer to a duplicate 'struct spi_nor_flash_parameter' + * that is used for storing SFDP parsed data * * This table is optional, but when available, we parse it to identify the * location and size of sectors within the main data array of the flash memory @@ -3778,7 +3784,8 @@ static int spi_nor_init_non_uniform_erase_map(struct spi_nor *nor, * Return: 0 on success, -errno otherwise. */ static int spi_nor_parse_smpt(struct spi_nor *nor, - const struct sfdp_parameter_header *smpt_header) + const struct sfdp_parameter_header *smpt_header, + struct spi_nor_flash_parameter *params) { const u32 *sector_map; u32 *smpt; @@ -3807,11 +3814,11 @@ static int spi_nor_parse_smpt(struct spi_nor *nor, goto out; } - ret = spi_nor_init_non_uniform_erase_map(nor, sector_map); + ret = spi_nor_init_non_uniform_erase_map(nor, params, sector_map); if (ret) goto out; - spi_nor_regions_sort_erase_types(&nor->erase_map); + spi_nor_regions_sort_erase_types(¶ms->erase_map); /* fall through */ out: kfree(smpt); @@ -3867,7 +3874,7 @@ static int spi_nor_parse_4bait(struct spi_nor *nor, { 0u /* not used */, BIT(12) }, }; struct spi_nor_pp_command *params_pp = params->page_programs; - struct spi_nor_erase_map *map = &nor->erase_map; + struct spi_nor_erase_map *map = ¶ms->erase_map; struct spi_nor_erase_type *erase_type = map->erase_type; u32 *dwords; size_t len; @@ -4097,7 +4104,7 @@ static int spi_nor_parse_sfdp(struct spi_nor *nor, switch (SFDP_PARAM_HEADER_ID(param_header)) { case SFDP_SECTOR_MAP_ID: - err = spi_nor_parse_smpt(nor, param_header); + err = spi_nor_parse_smpt(nor, param_header, params); break; case SFDP_4BAIT_ID: @@ -4129,7 +4136,7 @@ exit: static int spi_nor_init_params(struct spi_nor *nor) { struct spi_nor_flash_parameter *params = &nor->params; - struct spi_nor_erase_map *map = &nor->erase_map; + struct spi_nor_erase_map *map = ¶ms->erase_map; const struct flash_info *info = nor->info; u8 i, erase_mask; @@ -4229,17 +4236,12 @@ static int spi_nor_init_params(struct spi_nor *nor) if ((info->flags & (SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)) && !(info->flags & SPI_NOR_SKIP_SFDP)) { struct spi_nor_flash_parameter sfdp_params; - struct spi_nor_erase_map prev_map; memcpy(&sfdp_params, params, sizeof(sfdp_params)); - memcpy(&prev_map, &nor->erase_map, sizeof(prev_map)); if (spi_nor_parse_sfdp(nor, &sfdp_params)) { nor->addr_width = 0; nor->flags &= ~SNOR_F_4B_OPCODES; - /* restore previous erase map */ - memcpy(&nor->erase_map, &prev_map, - sizeof(nor->erase_map)); } else { memcpy(params, &sfdp_params, sizeof(*params)); } @@ -4353,7 +4355,7 @@ spi_nor_select_uniform_erase(struct spi_nor_erase_map *map, static int spi_nor_select_erase(struct spi_nor *nor, u32 wanted_size) { - struct spi_nor_erase_map *map = &nor->erase_map; + struct spi_nor_erase_map *map = &nor->params.erase_map; const struct spi_nor_erase_type *erase = NULL; struct mtd_info *mtd = &nor->mtd; int i; diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 17787238f0e9..a86c0d9fb01d 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -479,6 +479,8 @@ struct spi_nor; * in the array, the higher priority. * @page_programs: page program capabilities ordered by priority: the * higher index in the array, the higher priority. + * @erase_map: the erase map parsed from the SFDP Sector Map Parameter + * Table. * @quad_enable: enables SPI NOR quad mode. */ struct spi_nor_flash_parameter { @@ -489,6 +491,8 @@ struct spi_nor_flash_parameter { struct spi_nor_read_command reads[SNOR_CMD_READ_MAX]; struct spi_nor_pp_command page_programs[SNOR_CMD_PP_MAX]; + struct spi_nor_erase_map erase_map; + int (*quad_enable)(struct spi_nor *nor); }; @@ -519,7 +523,6 @@ struct flash_info; * @read_proto: the SPI protocol for read operations * @write_proto: the SPI protocol for write operations * @reg_proto the SPI protocol for read_reg/write_reg/erase operations - * @erase_map: the erase map of the SPI NOR * @prepare: [OPTIONAL] do some preparations for the * read/write/erase/lock/unlock operations * @unprepare: [OPTIONAL] do some post work after the @@ -562,7 +565,6 @@ struct spi_nor { enum spi_nor_protocol reg_proto; bool sst_write_second; u32 flags; - struct spi_nor_erase_map erase_map; int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops); void (*unprepare)(struct spi_nor *nor, enum spi_nor_ops ops); @@ -610,7 +612,7 @@ spi_nor_region_mark_overlay(struct spi_nor_erase_region *region) static bool __maybe_unused spi_nor_has_uniform_erase(const struct spi_nor *nor) { - return !!nor->erase_map.uniform_erase_type; + return !!nor->params.erase_map.uniform_erase_type; } static inline void spi_nor_set_flash_node(struct spi_nor *nor, -- cgit v1.2.3 From 64c160f32235f725a5378c66fb3583c720357ab9 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Sat, 24 Aug 2019 08:45:28 +0300 Subject: mtd: spi-nor: Create a ->set_4byte() method The procedure used to enable 4 byte addressing mode depends on the NOR device, so let's provide a hook so that manufacturer specific handling can be implemented in a sane way. Signed-off-by: Boris Brezillon [tudor.ambarus@microchip.com: use nor->params.set_4byte() instead of nor->set_4byte()] Signed-off-by: Tudor Ambarus Reviewed-by: Vignesh Raghavendra --- drivers/mtd/spi-nor/spi-nor.c | 76 ++++++++++++++++++++++--------------------- include/linux/mtd/spi-nor.h | 2 ++ 2 files changed, 41 insertions(+), 37 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index e1a9e0ec7cec..b903fe9ac60e 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -633,6 +633,17 @@ static int macronix_set_4byte(struct spi_nor *nor, bool enable) NULL, 0); } +static int st_micron_set_4byte(struct spi_nor *nor, bool enable) +{ + int ret; + + write_enable(nor); + ret = macronix_set_4byte(nor, enable); + write_disable(nor); + + return ret; +} + static int spansion_set_4byte(struct spi_nor *nor, bool enable) { nor->bouncebuf[0] = enable << 7; @@ -667,45 +678,24 @@ static int spi_nor_write_ear(struct spi_nor *nor, u8 ear) return nor->write_reg(nor, SPINOR_OP_WREAR, nor->bouncebuf, 1); } -/* Enable/disable 4-byte addressing mode. */ -static int set_4byte(struct spi_nor *nor, bool enable) +static int winbond_set_4byte(struct spi_nor *nor, bool enable) { - int status; - bool need_wren = false; - - switch (JEDEC_MFR(nor->info)) { - case SNOR_MFR_ST: - case SNOR_MFR_MICRON: - /* Some Micron need WREN command; all will accept it */ - need_wren = true; - /* fall through */ - case SNOR_MFR_MACRONIX: - case SNOR_MFR_WINBOND: - if (need_wren) - write_enable(nor); + int ret; - status = macronix_set_4byte(nor, enable); - if (need_wren) - write_disable(nor); + ret = macronix_set_4byte(nor, enable); + if (ret || enable) + return ret; - if (!status && !enable && - JEDEC_MFR(nor->info) == SNOR_MFR_WINBOND) { - /* - * On Winbond W25Q256FV, leaving 4byte mode causes - * the Extended Address Register to be set to 1, so all - * 3-byte-address reads come from the second 16M. - * We must clear the register to enable normal behavior. - */ - write_enable(nor); - spi_nor_write_ear(nor, 0); - write_disable(nor); - } + /* + * On Winbond W25Q256FV, leaving 4byte mode causes the Extended Address + * Register to be set to 1, so all 3-byte-address reads come from the + * second 16M. We must clear the register to enable normal behavior. + */ + write_enable(nor); + ret = spi_nor_write_ear(nor, 0); + write_disable(nor); - return status; - default: - /* Spansion style */ - return spansion_set_4byte(nor, enable); - } + return ret; } static int spi_nor_xread_sr(struct spi_nor *nor, u8 *sr) @@ -4153,11 +4143,18 @@ exit: static void macronix_set_default_init(struct spi_nor *nor) { nor->params.quad_enable = macronix_quad_enable; + nor->params.set_4byte = macronix_set_4byte; } static void st_micron_set_default_init(struct spi_nor *nor) { nor->params.quad_enable = NULL; + nor->params.set_4byte = st_micron_set_4byte; +} + +static void winbond_set_default_init(struct spi_nor *nor) +{ + nor->params.set_4byte = winbond_set_4byte; } /** @@ -4178,6 +4175,10 @@ static void spi_nor_manufacturer_init_params(struct spi_nor *nor) st_micron_set_default_init(nor); break; + case SNOR_MFR_WINBOND: + winbond_set_default_init(nor); + break; + default: break; } @@ -4222,6 +4223,7 @@ static void spi_nor_info_init_params(struct spi_nor *nor) /* Initialize legacy flash parameters and settings. */ params->quad_enable = spansion_quad_enable; + params->set_4byte = spansion_set_4byte; /* Set SPI NOR sizes. */ params->size = (u64)info->sector_size * info->n_sectors; @@ -4587,7 +4589,7 @@ static int spi_nor_init(struct spi_nor *nor) */ WARN_ONCE(nor->flags & SNOR_F_BROKEN_RESET, "enabling reset hack; may not recover from unexpected reboots\n"); - set_4byte(nor, true); + nor->params.set_4byte(nor, true); } return 0; @@ -4611,7 +4613,7 @@ void spi_nor_restore(struct spi_nor *nor) /* restore the addressing mode */ if (nor->addr_width == 4 && !(nor->flags & SNOR_F_4B_OPCODES) && nor->flags & SNOR_F_BROKEN_RESET) - set_4byte(nor, false); + nor->params.set_4byte(nor, false); } EXPORT_SYMBOL_GPL(spi_nor_restore); diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index a86c0d9fb01d..7da89dd483cb 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -482,6 +482,7 @@ struct spi_nor; * @erase_map: the erase map parsed from the SFDP Sector Map Parameter * Table. * @quad_enable: enables SPI NOR quad mode. + * @set_4byte: puts the SPI NOR in 4 byte addressing mode. */ struct spi_nor_flash_parameter { u64 size; @@ -494,6 +495,7 @@ struct spi_nor_flash_parameter { struct spi_nor_erase_map erase_map; int (*quad_enable)(struct spi_nor *nor); + int (*set_4byte)(struct spi_nor *nor, bool enable); }; /** -- cgit v1.2.3 From dff972458acb05ab919b6950da99f8172ab14836 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Sat, 24 Aug 2019 09:22:03 +0300 Subject: mtd: spi-nor: Rework the SPI NOR lock/unlock logic Add the SNOR_F_HAS_LOCK flag and set it when SPI_NOR_HAS_LOCK is set in the flash_info entry or when it's a Micron or ST flash. Move the locking hooks in a separate struct so that we have just one field to update when we change the locking implementation. Signed-off-by: Boris Brezillon [tudor.ambarus@microchip.com: use ->default_init() hook, introduce spi_nor_late_init_params(), set ops in nor->params] Signed-off-by: Tudor Ambarus Reviewed-by: Vignesh Raghavendra --- drivers/mtd/spi-nor/spi-nor.c | 50 ++++++++++++++++++++++++++++++++----------- include/linux/mtd/spi-nor.h | 23 ++++++++++++++------ 2 files changed, 53 insertions(+), 20 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index b903fe9ac60e..f630ec9bad71 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -1598,6 +1598,12 @@ static int stm_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len) return stm_is_locked_sr(nor, ofs, len, status); } +static const struct spi_nor_locking_ops stm_locking_ops = { + .lock = stm_lock, + .unlock = stm_unlock, + .is_locked = stm_is_locked, +}; + static int spi_nor_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) { struct spi_nor *nor = mtd_to_spi_nor(mtd); @@ -1607,7 +1613,7 @@ static int spi_nor_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) if (ret) return ret; - ret = nor->flash_lock(nor, ofs, len); + ret = nor->params.locking_ops->lock(nor, ofs, len); spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_UNLOCK); return ret; @@ -1622,7 +1628,7 @@ static int spi_nor_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) if (ret) return ret; - ret = nor->flash_unlock(nor, ofs, len); + ret = nor->params.locking_ops->unlock(nor, ofs, len); spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_LOCK); return ret; @@ -1637,7 +1643,7 @@ static int spi_nor_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len) if (ret) return ret; - ret = nor->flash_is_locked(nor, ofs, len); + ret = nor->params.locking_ops->is_locked(nor, ofs, len); spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_LOCK); return ret; @@ -4148,6 +4154,7 @@ static void macronix_set_default_init(struct spi_nor *nor) static void st_micron_set_default_init(struct spi_nor *nor) { + nor->flags |= SNOR_F_HAS_LOCK; nor->params.quad_enable = NULL; nor->params.set_4byte = st_micron_set_4byte; } @@ -4291,6 +4298,23 @@ static void spi_nor_info_init_params(struct spi_nor *nor) spi_nor_init_uniform_erase_map(map, erase_mask, params->size); } +/** + * spi_nor_late_init_params() - Late initialization of default flash parameters. + * @nor: pointer to a 'struct spi_nor' + * + * Used to set default flash parameters and settings when the ->default_init() + * hook or the SFDP parser let voids. + */ +static void spi_nor_late_init_params(struct spi_nor *nor) +{ + /* + * NOR protection support. When locking_ops are not provided, we pick + * the default ones. + */ + if (nor->flags & SNOR_F_HAS_LOCK && !nor->params.locking_ops) + nor->params.locking_ops = &stm_locking_ops; +} + /** * spi_nor_init_params() - Initialize the flash's parameters and settings. * @nor: pointer to a 'struct spi-nor'. @@ -4316,6 +4340,10 @@ static void spi_nor_info_init_params(struct spi_nor *nor) * Please note that there is a ->post_bfpt() fixup hook that can overwrite * the flash parameters and settings immediately after parsing the Basic * Flash Parameter Table. + * + * 4/ Late default flash parameters initialization, used when the + * ->default_init() hook or the SFDP parser do not set specific params. + * spi_nor_late_init_params() */ static void spi_nor_init_params(struct spi_nor *nor) { @@ -4326,6 +4354,8 @@ static void spi_nor_init_params(struct spi_nor *nor) if ((nor->info->flags & (SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)) && !(nor->info->flags & SPI_NOR_SKIP_SFDP)) spi_nor_sfdp_init_params(nor); + + spi_nor_late_init_params(nor); } static int spi_nor_select_read(struct spi_nor *nor, @@ -4707,6 +4737,9 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, if (info->flags & SPI_S3AN) nor->flags |= SNOR_F_READY_XSR_RDY; + if (info->flags & SPI_NOR_HAS_LOCK) + nor->flags |= SNOR_F_HAS_LOCK; + /* * Atmel, SST, Intel/Numonyx, and others serial NOR tend to power up * with the software protection bits set. @@ -4731,16 +4764,7 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, mtd->_read = spi_nor_read; mtd->_resume = spi_nor_resume; - /* NOR protection support for STmicro/Micron chips and similar */ - if (JEDEC_MFR(info) == SNOR_MFR_ST || - JEDEC_MFR(info) == SNOR_MFR_MICRON || - info->flags & SPI_NOR_HAS_LOCK) { - nor->flash_lock = stm_lock; - nor->flash_unlock = stm_unlock; - nor->flash_is_locked = stm_is_locked; - } - - if (nor->flash_lock && nor->flash_unlock && nor->flash_is_locked) { + if (nor->params.locking_ops) { mtd->_lock = spi_nor_lock; mtd->_unlock = spi_nor_unlock; mtd->_is_locked = spi_nor_is_locked; diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 7da89dd483cb..ea3bcac54dc2 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -243,6 +243,7 @@ enum spi_nor_option_flags { SNOR_F_BROKEN_RESET = BIT(6), SNOR_F_4B_OPCODES = BIT(7), SNOR_F_HAS_4BAIT = BIT(8), + SNOR_F_HAS_LOCK = BIT(9), }; /** @@ -465,6 +466,18 @@ enum spi_nor_pp_command_index { /* Forward declaration that will be used in 'struct spi_nor_flash_parameter' */ struct spi_nor; +/** + * struct spi_nor_locking_ops - SPI NOR locking methods + * @lock: lock a region of the SPI NOR. + * @unlock: unlock a region of the SPI NOR. + * @is_locked: check if a region of the SPI NOR is completely locked + */ +struct spi_nor_locking_ops { + int (*lock)(struct spi_nor *nor, loff_t ofs, uint64_t len); + int (*unlock)(struct spi_nor *nor, loff_t ofs, uint64_t len); + int (*is_locked)(struct spi_nor *nor, loff_t ofs, uint64_t len); +}; + /** * struct spi_nor_flash_parameter - SPI NOR flash parameters and settings. * Includes legacy flash parameters and settings that can be overwritten @@ -483,6 +496,7 @@ struct spi_nor; * Table. * @quad_enable: enables SPI NOR quad mode. * @set_4byte: puts the SPI NOR in 4 byte addressing mode. + * @locking_ops: SPI NOR locking methods. */ struct spi_nor_flash_parameter { u64 size; @@ -496,6 +510,8 @@ struct spi_nor_flash_parameter { int (*quad_enable)(struct spi_nor *nor); int (*set_4byte)(struct spi_nor *nor, bool enable); + + const struct spi_nor_locking_ops *locking_ops; }; /** @@ -536,10 +552,6 @@ struct flash_info; * @erase: [DRIVER-SPECIFIC] erase a sector of the SPI NOR * at the offset @offs; if not provided by the driver, * spi-nor will send the erase opcode via write_reg() - * @flash_lock: [FLASH-SPECIFIC] lock a region of the SPI NOR - * @flash_unlock: [FLASH-SPECIFIC] unlock a region of the SPI NOR - * @flash_is_locked: [FLASH-SPECIFIC] check if a region of the SPI NOR is - * completely locked * @clear_sr_bp: [FLASH-SPECIFIC] clears the Block Protection Bits from * the SPI NOR Status Register. * @params: [FLASH-SPECIFIC] SPI-NOR flash parameters and settings. @@ -579,9 +591,6 @@ struct spi_nor { size_t len, const u_char *write_buf); int (*erase)(struct spi_nor *nor, loff_t offs); - int (*flash_lock)(struct spi_nor *nor, loff_t ofs, uint64_t len); - int (*flash_unlock)(struct spi_nor *nor, loff_t ofs, uint64_t len); - int (*flash_is_locked)(struct spi_nor *nor, loff_t ofs, uint64_t len); int (*clear_sr_bp)(struct spi_nor *nor); struct spi_nor_flash_parameter params; -- cgit v1.2.3 From 36499596280359d34a0663afe0abbf34d80862e8 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Sat, 24 Aug 2019 10:17:20 +0300 Subject: mtd: spi-nor: Add a ->convert_addr() method In order to separate manufacturer quirks from the core we need to get rid of all the manufacturer specific flags, like the SNOR_F_S3AN_ADDR_DEFAULT one. This can easily be replaced by a ->convert_addr() hook, which when implemented will provide the core with an easy way to convert an absolute address into something the flash understands. Right now the only user are the S3AN chips, but other manufacturers can implement it if needed. Signed-off-by: Boris Brezillon Signed-off-by: Tudor Ambarus Reviewed-by: Vignesh Raghavendra --- drivers/mtd/spi-nor/spi-nor.c | 24 ++++++++++++++---------- include/linux/mtd/spi-nor.h | 17 ++++++++++------- 2 files changed, 24 insertions(+), 17 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index 4ff3c9f0a017..9005ea8def87 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -899,10 +899,9 @@ static void spi_nor_unlock_and_unprep(struct spi_nor *nor, enum spi_nor_ops ops) * Addr can safely be unsigned int, the biggest S3AN device is smaller than * 4 MiB. */ -static loff_t spi_nor_s3an_addr_convert(struct spi_nor *nor, unsigned int addr) +static u32 s3an_convert_addr(struct spi_nor *nor, u32 addr) { - unsigned int offset; - unsigned int page; + u32 offset, page; offset = addr % nor->page_size; page = addr / nor->page_size; @@ -911,6 +910,14 @@ static loff_t spi_nor_s3an_addr_convert(struct spi_nor *nor, unsigned int addr) return page | offset; } +static u32 spi_nor_convert_addr(struct spi_nor *nor, loff_t addr) +{ + if (!nor->params.convert_addr) + return addr; + + return nor->params.convert_addr(nor, addr); +} + /* * Initiate the erasure of a single sector */ @@ -918,8 +925,7 @@ static int spi_nor_erase_sector(struct spi_nor *nor, u32 addr) { int i; - if (nor->flags & SNOR_F_S3AN_ADDR_DEFAULT) - addr = spi_nor_s3an_addr_convert(nor, addr); + addr = spi_nor_convert_addr(nor, addr); if (nor->erase) return nor->erase(nor, addr); @@ -2535,8 +2541,7 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len, while (len) { loff_t addr = from; - if (nor->flags & SNOR_F_S3AN_ADDR_DEFAULT) - addr = spi_nor_s3an_addr_convert(nor, addr); + addr = spi_nor_convert_addr(nor, addr); ret = spi_nor_read_data(nor, addr, len, buf); if (ret == 0) { @@ -2680,8 +2685,7 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len, page_remain = min_t(size_t, nor->page_size - page_offset, len - i); - if (nor->flags & SNOR_F_S3AN_ADDR_DEFAULT) - addr = spi_nor_s3an_addr_convert(nor, addr); + addr = spi_nor_convert_addr(nor, addr); write_enable(nor); ret = spi_nor_write_data(nor, addr, page_remain, buf + i); @@ -2748,7 +2752,7 @@ static int s3an_nor_scan(struct spi_nor *nor) nor->mtd.erasesize = 8 * nor->page_size; } else { /* Flash in Default addressing mode */ - nor->flags |= SNOR_F_S3AN_ADDR_DEFAULT; + nor->params.convert_addr = s3an_convert_addr; } return 0; diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index ea3bcac54dc2..35aad92a4ff8 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -237,13 +237,12 @@ enum spi_nor_option_flags { SNOR_F_USE_FSR = BIT(0), SNOR_F_HAS_SR_TB = BIT(1), SNOR_F_NO_OP_CHIP_ERASE = BIT(2), - SNOR_F_S3AN_ADDR_DEFAULT = BIT(3), - SNOR_F_READY_XSR_RDY = BIT(4), - SNOR_F_USE_CLSR = BIT(5), - SNOR_F_BROKEN_RESET = BIT(6), - SNOR_F_4B_OPCODES = BIT(7), - SNOR_F_HAS_4BAIT = BIT(8), - SNOR_F_HAS_LOCK = BIT(9), + SNOR_F_READY_XSR_RDY = BIT(3), + SNOR_F_USE_CLSR = BIT(4), + SNOR_F_BROKEN_RESET = BIT(5), + SNOR_F_4B_OPCODES = BIT(6), + SNOR_F_HAS_4BAIT = BIT(7), + SNOR_F_HAS_LOCK = BIT(8), }; /** @@ -496,6 +495,9 @@ struct spi_nor_locking_ops { * Table. * @quad_enable: enables SPI NOR quad mode. * @set_4byte: puts the SPI NOR in 4 byte addressing mode. + * @convert_addr: converts an absolute address into something the flash + * will understand. Particularly useful when pagesize is + * not a power-of-2. * @locking_ops: SPI NOR locking methods. */ struct spi_nor_flash_parameter { @@ -510,6 +512,7 @@ struct spi_nor_flash_parameter { int (*quad_enable)(struct spi_nor *nor); int (*set_4byte)(struct spi_nor *nor, bool enable); + u32 (*convert_addr)(struct spi_nor *nor, u32 addr); const struct spi_nor_locking_ops *locking_ops; }; -- cgit v1.2.3 From 2d7ff858e5f683393f32b07e64e565877a2e4bcb Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Sun, 25 Aug 2019 22:48:36 +0300 Subject: mtd: spi-nor: Add a ->setup() method nor->params.setup() configures the SPI NOR memory. Useful for SPI NOR flashes that have peculiarities to the SPI NOR standard, e.g. different opcodes, specific address calculation, page size, etc. Right now the only user will be the S3AN chips, but other manufacturers can implement it if needed. Move spi_nor_setup() related code in order to avoid a forward declaration to spi_nor_default_setup(). Signed-off-by: Tudor Ambarus Reviewed-by: Boris Brezillon Reviewed-by: Vignesh Raghavendra --- drivers/mtd/spi-nor/spi-nor.c | 432 +++++++++++++++++++++--------------------- include/linux/mtd/spi-nor.h | 5 + 2 files changed, 226 insertions(+), 211 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index 9005ea8def87..449c7bfff2bd 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -4144,6 +4144,226 @@ exit: return err; } +static int spi_nor_select_read(struct spi_nor *nor, + u32 shared_hwcaps) +{ + int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_READ_MASK) - 1; + const struct spi_nor_read_command *read; + + if (best_match < 0) + return -EINVAL; + + cmd = spi_nor_hwcaps_read2cmd(BIT(best_match)); + if (cmd < 0) + return -EINVAL; + + read = &nor->params.reads[cmd]; + nor->read_opcode = read->opcode; + nor->read_proto = read->proto; + + /* + * In the spi-nor framework, we don't need to make the difference + * between mode clock cycles and wait state clock cycles. + * Indeed, the value of the mode clock cycles is used by a QSPI + * flash memory to know whether it should enter or leave its 0-4-4 + * (Continuous Read / XIP) mode. + * eXecution In Place is out of the scope of the mtd sub-system. + * Hence we choose to merge both mode and wait state clock cycles + * into the so called dummy clock cycles. + */ + nor->read_dummy = read->num_mode_clocks + read->num_wait_states; + return 0; +} + +static int spi_nor_select_pp(struct spi_nor *nor, + u32 shared_hwcaps) +{ + int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_PP_MASK) - 1; + const struct spi_nor_pp_command *pp; + + if (best_match < 0) + return -EINVAL; + + cmd = spi_nor_hwcaps_pp2cmd(BIT(best_match)); + if (cmd < 0) + return -EINVAL; + + pp = &nor->params.page_programs[cmd]; + nor->program_opcode = pp->opcode; + nor->write_proto = pp->proto; + return 0; +} + +/** + * spi_nor_select_uniform_erase() - select optimum uniform erase type + * @map: the erase map of the SPI NOR + * @wanted_size: the erase type size to search for. Contains the value of + * info->sector_size or of the "small sector" size in case + * CONFIG_MTD_SPI_NOR_USE_4K_SECTORS is defined. + * + * Once the optimum uniform sector erase command is found, disable all the + * other. + * + * Return: pointer to erase type on success, NULL otherwise. + */ +static const struct spi_nor_erase_type * +spi_nor_select_uniform_erase(struct spi_nor_erase_map *map, + const u32 wanted_size) +{ + const struct spi_nor_erase_type *tested_erase, *erase = NULL; + int i; + u8 uniform_erase_type = map->uniform_erase_type; + + for (i = SNOR_ERASE_TYPE_MAX - 1; i >= 0; i--) { + if (!(uniform_erase_type & BIT(i))) + continue; + + tested_erase = &map->erase_type[i]; + + /* + * If the current erase size is the one, stop here: + * we have found the right uniform Sector Erase command. + */ + if (tested_erase->size == wanted_size) { + erase = tested_erase; + break; + } + + /* + * Otherwise, the current erase size is still a valid canditate. + * Select the biggest valid candidate. + */ + if (!erase && tested_erase->size) + erase = tested_erase; + /* keep iterating to find the wanted_size */ + } + + if (!erase) + return NULL; + + /* Disable all other Sector Erase commands. */ + map->uniform_erase_type &= ~SNOR_ERASE_TYPE_MASK; + map->uniform_erase_type |= BIT(erase - map->erase_type); + return erase; +} + +static int spi_nor_select_erase(struct spi_nor *nor, u32 wanted_size) +{ + struct spi_nor_erase_map *map = &nor->params.erase_map; + const struct spi_nor_erase_type *erase = NULL; + struct mtd_info *mtd = &nor->mtd; + int i; + + /* + * The previous implementation handling Sector Erase commands assumed + * that the SPI flash memory has an uniform layout then used only one + * of the supported erase sizes for all Sector Erase commands. + * So to be backward compatible, the new implementation also tries to + * manage the SPI flash memory as uniform with a single erase sector + * size, when possible. + */ +#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS + /* prefer "small sector" erase if possible */ + wanted_size = 4096u; +#endif + + if (spi_nor_has_uniform_erase(nor)) { + erase = spi_nor_select_uniform_erase(map, wanted_size); + if (!erase) + return -EINVAL; + nor->erase_opcode = erase->opcode; + mtd->erasesize = erase->size; + return 0; + } + + /* + * For non-uniform SPI flash memory, set mtd->erasesize to the + * maximum erase sector size. No need to set nor->erase_opcode. + */ + for (i = SNOR_ERASE_TYPE_MAX - 1; i >= 0; i--) { + if (map->erase_type[i].size) { + erase = &map->erase_type[i]; + break; + } + } + + if (!erase) + return -EINVAL; + + mtd->erasesize = erase->size; + return 0; +} + +static int spi_nor_default_setup(struct spi_nor *nor, + const struct spi_nor_hwcaps *hwcaps) +{ + struct spi_nor_flash_parameter *params = &nor->params; + u32 ignored_mask, shared_mask; + int err; + + /* + * Keep only the hardware capabilities supported by both the SPI + * controller and the SPI flash memory. + */ + shared_mask = hwcaps->mask & params->hwcaps.mask; + + if (nor->spimem) { + /* + * When called from spi_nor_probe(), all caps are set and we + * need to discard some of them based on what the SPI + * controller actually supports (using spi_mem_supports_op()). + */ + spi_nor_spimem_adjust_hwcaps(nor, &shared_mask); + } else { + /* + * SPI n-n-n protocols are not supported when the SPI + * controller directly implements the spi_nor interface. + * Yet another reason to switch to spi-mem. + */ + ignored_mask = SNOR_HWCAPS_X_X_X; + if (shared_mask & ignored_mask) { + dev_dbg(nor->dev, + "SPI n-n-n protocols are not supported.\n"); + shared_mask &= ~ignored_mask; + } + } + + /* Select the (Fast) Read command. */ + err = spi_nor_select_read(nor, shared_mask); + if (err) { + dev_err(nor->dev, + "can't select read settings supported by both the SPI controller and memory.\n"); + return err; + } + + /* Select the Page Program command. */ + err = spi_nor_select_pp(nor, shared_mask); + if (err) { + dev_err(nor->dev, + "can't select write settings supported by both the SPI controller and memory.\n"); + return err; + } + + /* Select the Sector Erase command. */ + err = spi_nor_select_erase(nor, nor->info->sector_size); + if (err) { + dev_err(nor->dev, + "can't select erase settings supported by both the SPI controller and memory.\n"); + return err; + } + + return 0; +} + +static int spi_nor_setup(struct spi_nor *nor, + const struct spi_nor_hwcaps *hwcaps) +{ + if (!nor->params.setup) + return 0; + + return nor->params.setup(nor, hwcaps); +} + static void macronix_set_default_init(struct spi_nor *nor) { nor->params.quad_enable = macronix_quad_enable; @@ -4229,6 +4449,7 @@ static void spi_nor_info_init_params(struct spi_nor *nor) /* Initialize legacy flash parameters and settings. */ params->quad_enable = spansion_quad_enable; params->set_4byte = spansion_set_4byte; + params->setup = spi_nor_default_setup; /* Set SPI NOR sizes. */ params->size = (u64)info->sector_size * info->n_sectors; @@ -4403,217 +4624,6 @@ static void spi_nor_init_params(struct spi_nor *nor) spi_nor_late_init_params(nor); } -static int spi_nor_select_read(struct spi_nor *nor, - u32 shared_hwcaps) -{ - int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_READ_MASK) - 1; - const struct spi_nor_read_command *read; - - if (best_match < 0) - return -EINVAL; - - cmd = spi_nor_hwcaps_read2cmd(BIT(best_match)); - if (cmd < 0) - return -EINVAL; - - read = &nor->params.reads[cmd]; - nor->read_opcode = read->opcode; - nor->read_proto = read->proto; - - /* - * In the spi-nor framework, we don't need to make the difference - * between mode clock cycles and wait state clock cycles. - * Indeed, the value of the mode clock cycles is used by a QSPI - * flash memory to know whether it should enter or leave its 0-4-4 - * (Continuous Read / XIP) mode. - * eXecution In Place is out of the scope of the mtd sub-system. - * Hence we choose to merge both mode and wait state clock cycles - * into the so called dummy clock cycles. - */ - nor->read_dummy = read->num_mode_clocks + read->num_wait_states; - return 0; -} - -static int spi_nor_select_pp(struct spi_nor *nor, - u32 shared_hwcaps) -{ - int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_PP_MASK) - 1; - const struct spi_nor_pp_command *pp; - - if (best_match < 0) - return -EINVAL; - - cmd = spi_nor_hwcaps_pp2cmd(BIT(best_match)); - if (cmd < 0) - return -EINVAL; - - pp = &nor->params.page_programs[cmd]; - nor->program_opcode = pp->opcode; - nor->write_proto = pp->proto; - return 0; -} - -/** - * spi_nor_select_uniform_erase() - select optimum uniform erase type - * @map: the erase map of the SPI NOR - * @wanted_size: the erase type size to search for. Contains the value of - * info->sector_size or of the "small sector" size in case - * CONFIG_MTD_SPI_NOR_USE_4K_SECTORS is defined. - * - * Once the optimum uniform sector erase command is found, disable all the - * other. - * - * Return: pointer to erase type on success, NULL otherwise. - */ -static const struct spi_nor_erase_type * -spi_nor_select_uniform_erase(struct spi_nor_erase_map *map, - const u32 wanted_size) -{ - const struct spi_nor_erase_type *tested_erase, *erase = NULL; - int i; - u8 uniform_erase_type = map->uniform_erase_type; - - for (i = SNOR_ERASE_TYPE_MAX - 1; i >= 0; i--) { - if (!(uniform_erase_type & BIT(i))) - continue; - - tested_erase = &map->erase_type[i]; - - /* - * If the current erase size is the one, stop here: - * we have found the right uniform Sector Erase command. - */ - if (tested_erase->size == wanted_size) { - erase = tested_erase; - break; - } - - /* - * Otherwise, the current erase size is still a valid canditate. - * Select the biggest valid candidate. - */ - if (!erase && tested_erase->size) - erase = tested_erase; - /* keep iterating to find the wanted_size */ - } - - if (!erase) - return NULL; - - /* Disable all other Sector Erase commands. */ - map->uniform_erase_type &= ~SNOR_ERASE_TYPE_MASK; - map->uniform_erase_type |= BIT(erase - map->erase_type); - return erase; -} - -static int spi_nor_select_erase(struct spi_nor *nor, u32 wanted_size) -{ - struct spi_nor_erase_map *map = &nor->params.erase_map; - const struct spi_nor_erase_type *erase = NULL; - struct mtd_info *mtd = &nor->mtd; - int i; - - /* - * The previous implementation handling Sector Erase commands assumed - * that the SPI flash memory has an uniform layout then used only one - * of the supported erase sizes for all Sector Erase commands. - * So to be backward compatible, the new implementation also tries to - * manage the SPI flash memory as uniform with a single erase sector - * size, when possible. - */ -#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS - /* prefer "small sector" erase if possible */ - wanted_size = 4096u; -#endif - - if (spi_nor_has_uniform_erase(nor)) { - erase = spi_nor_select_uniform_erase(map, wanted_size); - if (!erase) - return -EINVAL; - nor->erase_opcode = erase->opcode; - mtd->erasesize = erase->size; - return 0; - } - - /* - * For non-uniform SPI flash memory, set mtd->erasesize to the - * maximum erase sector size. No need to set nor->erase_opcode. - */ - for (i = SNOR_ERASE_TYPE_MAX - 1; i >= 0; i--) { - if (map->erase_type[i].size) { - erase = &map->erase_type[i]; - break; - } - } - - if (!erase) - return -EINVAL; - - mtd->erasesize = erase->size; - return 0; -} - -static int spi_nor_setup(struct spi_nor *nor, - const struct spi_nor_hwcaps *hwcaps) -{ - struct spi_nor_flash_parameter *params = &nor->params; - u32 ignored_mask, shared_mask; - int err; - - /* - * Keep only the hardware capabilities supported by both the SPI - * controller and the SPI flash memory. - */ - shared_mask = hwcaps->mask & params->hwcaps.mask; - - if (nor->spimem) { - /* - * When called from spi_nor_probe(), all caps are set and we - * need to discard some of them based on what the SPI - * controller actually supports (using spi_mem_supports_op()). - */ - spi_nor_spimem_adjust_hwcaps(nor, &shared_mask); - } else { - /* - * SPI n-n-n protocols are not supported when the SPI - * controller directly implements the spi_nor interface. - * Yet another reason to switch to spi-mem. - */ - ignored_mask = SNOR_HWCAPS_X_X_X; - if (shared_mask & ignored_mask) { - dev_dbg(nor->dev, - "SPI n-n-n protocols are not supported.\n"); - shared_mask &= ~ignored_mask; - } - } - - /* Select the (Fast) Read command. */ - err = spi_nor_select_read(nor, shared_mask); - if (err) { - dev_err(nor->dev, - "can't select read settings supported by both the SPI controller and memory.\n"); - return err; - } - - /* Select the Page Program command. */ - err = spi_nor_select_pp(nor, shared_mask); - if (err) { - dev_err(nor->dev, - "can't select write settings supported by both the SPI controller and memory.\n"); - return err; - } - - /* Select the Sector Erase command. */ - err = spi_nor_select_erase(nor, nor->info->sector_size); - if (err) { - dev_err(nor->dev, - "can't select erase settings supported by both the SPI controller and memory.\n"); - return err; - } - - return 0; -} - /** * spi_nor_quad_enable() - enable Quad I/O if needed. * @nor: pointer to a 'struct spi_nor' diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 35aad92a4ff8..fc0b4b19c900 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -498,6 +498,10 @@ struct spi_nor_locking_ops { * @convert_addr: converts an absolute address into something the flash * will understand. Particularly useful when pagesize is * not a power-of-2. + * @setup: configures the SPI NOR memory. Useful for SPI NOR + * flashes that have peculiarities to the SPI NOR standard + * e.g. different opcodes, specific address calculation, + * page size, etc. * @locking_ops: SPI NOR locking methods. */ struct spi_nor_flash_parameter { @@ -513,6 +517,7 @@ struct spi_nor_flash_parameter { int (*quad_enable)(struct spi_nor *nor); int (*set_4byte)(struct spi_nor *nor, bool enable); u32 (*convert_addr)(struct spi_nor *nor, u32 addr); + int (*setup)(struct spi_nor *nor, const struct spi_nor_hwcaps *hwcaps); const struct spi_nor_locking_ops *locking_ops; }; -- cgit v1.2.3 From 1018c94be6ea073115f6bcf993d6492138d2b8e3 Mon Sep 17 00:00:00 2001 From: Zhuohao Lee Date: Mon, 1 Jul 2019 00:07:10 +0800 Subject: mtd: mtdcore: add debugfs nodes for querying the flash name and id Currently, we don't have vfs nodes for querying the underlying flash name and flash id. This information is important especially when we want to know the flash detail of the defective system. In order to support the query, we add mtd_debugfs_populate() to create two debugfs nodes (ie. partname and partid). The upper driver can assign the pointer to partname and partid before calling mtd_device_register(). Signed-off-by: Zhuohao Lee Reviewed-by: Boris Brezillon Signed-off-by: Tudor Ambarus --- drivers/mtd/mtdcore.c | 86 +++++++++++++++++++++++++++++++++++++++++++------ include/linux/mtd/mtd.h | 3 ++ 2 files changed, 80 insertions(+), 9 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 408615f29e57..6cc7ecb0c788 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -335,6 +335,82 @@ static const struct device_type mtd_devtype = { .release = mtd_release, }; +static int mtd_partid_show(struct seq_file *s, void *p) +{ + struct mtd_info *mtd = s->private; + + seq_printf(s, "%s\n", mtd->dbg.partid); + + return 0; +} + +static int mtd_partid_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, mtd_partid_show, inode->i_private); +} + +static const struct file_operations mtd_partid_debug_fops = { + .open = mtd_partid_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int mtd_partname_show(struct seq_file *s, void *p) +{ + struct mtd_info *mtd = s->private; + + seq_printf(s, "%s\n", mtd->dbg.partname); + + return 0; +} + +static int mtd_partname_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, mtd_partname_show, inode->i_private); +} + +static const struct file_operations mtd_partname_debug_fops = { + .open = mtd_partname_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct dentry *dfs_dir_mtd; + +static void mtd_debugfs_populate(struct mtd_info *mtd) +{ + struct device *dev = &mtd->dev; + struct dentry *root, *dent; + + if (IS_ERR_OR_NULL(dfs_dir_mtd)) + return; + + root = debugfs_create_dir(dev_name(dev), dfs_dir_mtd); + if (IS_ERR_OR_NULL(root)) { + dev_dbg(dev, "won't show data in debugfs\n"); + return; + } + + mtd->dbg.dfs_dir = root; + + if (mtd->dbg.partid) { + dent = debugfs_create_file("partid", 0400, root, mtd, + &mtd_partid_debug_fops); + if (IS_ERR_OR_NULL(dent)) + dev_err(dev, "can't create debugfs entry for partid\n"); + } + + if (mtd->dbg.partname) { + dent = debugfs_create_file("partname", 0400, root, mtd, + &mtd_partname_debug_fops); + if (IS_ERR_OR_NULL(dent)) + dev_err(dev, + "can't create debugfs entry for partname\n"); + } +} + #ifndef CONFIG_MMU unsigned mtd_mmap_capabilities(struct mtd_info *mtd) { @@ -512,8 +588,6 @@ static int mtd_nvmem_add(struct mtd_info *mtd) return 0; } -static struct dentry *dfs_dir_mtd; - /** * add_mtd_device - register an MTD device * @mtd: pointer to new MTD device info structure @@ -607,13 +681,7 @@ int add_mtd_device(struct mtd_info *mtd) if (error) goto fail_nvmem_add; - if (!IS_ERR_OR_NULL(dfs_dir_mtd)) { - mtd->dbg.dfs_dir = debugfs_create_dir(dev_name(&mtd->dev), dfs_dir_mtd); - if (IS_ERR_OR_NULL(mtd->dbg.dfs_dir)) { - pr_debug("mtd device %s won't show data in debugfs\n", - dev_name(&mtd->dev)); - } - } + mtd_debugfs_populate(mtd); device_create(&mtd_class, mtd->dev.parent, MTD_DEVT(i) + 1, NULL, "mtd%dro", i); diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index 4ca8c1c845fb..249e8d9bfbcd 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -189,6 +189,9 @@ struct module; /* only needed for owner field in mtd_info */ */ struct mtd_debug_info { struct dentry *dfs_dir; + + const char *partname; + const char *partid; }; struct mtd_info { -- cgit v1.2.3