[PATCH v2 10/13] mtd: spi-nor: Add stacked memories support in spi-nor
Michal Simek
michal.simek at amd.com
Mon Jan 23 04:40:48 PST 2023
On 1/19/23 19:53, Amit Kumar Mahapatra wrote:
> Each flash that is connected in stacked mode should have a separate
> parameter structure. So, the flash parameter member(*params) of the spi_nor
> structure is changed to an array (*params[2]). The array is used to store
> the parameters of each flash connected in stacked configuration.
>
> The current implementation assumes that a maximum of two flashes are
> connected in stacked mode and both the flashes are of same make but can
> differ in sizes. So, except the sizes all other flash parameters of both
> the flashes are identical.
>
> SPI-NOR is not aware of the chip_select values, for any incoming request
> SPI-NOR will decide the flash index with the help of individual flash size
> and the configuration type (single/stacked). SPI-NOR will pass on the flash
> index information to the SPI core & SPI driver by setting the appropriate
> bit in nor->spimem->spi->cs_index_mask. For example, if nth bit of
> nor->spimem->spi->cs_index_mask is set then the driver would
> assert/de-assert spi->chip_slect[n].
>
> Signed-off-by: Amit Kumar Mahapatra <amit.kumar-mahapatra at amd.com>
> ---
> drivers/mtd/spi-nor/core.c | 282 +++++++++++++++++++++++++++++-------
> drivers/mtd/spi-nor/core.h | 4 +
> drivers/mtd/spi-nor/otp.c | 4 +-
> include/linux/mtd/spi-nor.h | 12 +-
> 4 files changed, 246 insertions(+), 56 deletions(-)
>
> diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
> index 8a4a54bf2d0e..bb7326dc8b70 100644
> --- a/drivers/mtd/spi-nor/core.c
> +++ b/drivers/mtd/spi-nor/core.c
> @@ -1441,13 +1441,18 @@ static int spi_nor_erase_multi_sectors(struct spi_nor *nor, u64 addr, u32 len)
> static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
> {
> struct spi_nor *nor = mtd_to_spi_nor(mtd);
> - u32 addr, len;
> + struct spi_nor_flash_parameter *params;
> + u32 addr, len, offset, cur_cs_num = 0;
> uint32_t rem;
> int ret;
> + u64 sz;
>
> dev_dbg(nor->dev, "at 0x%llx, len %lld\n", (long long)instr->addr,
> (long long)instr->len);
>
> + params = spi_nor_get_params(nor, 0);
> + sz = params->size;
> +
> if (spi_nor_has_uniform_erase(nor)) {
> div_u64_rem(instr->len, mtd->erasesize, &rem);
> if (rem)
> @@ -1465,26 +1470,30 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
> if (len == mtd->size && !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
> unsigned long timeout;
>
> - ret = spi_nor_write_enable(nor);
> - if (ret)
> - goto erase_err;
> + while (cur_cs_num < SNOR_FLASH_CNT_MAX && params) {
> + nor->spimem->spi->cs_index_mask = 0x01 << cur_cs_num;
> + ret = spi_nor_write_enable(nor);
> + if (ret)
> + goto erase_err;
>
> - ret = spi_nor_erase_chip(nor);
> - if (ret)
> - goto erase_err;
> + ret = spi_nor_erase_chip(nor);
> + if (ret)
> + goto erase_err;
>
> - /*
> - * Scale the timeout linearly with the size of the flash, with
> - * a minimum calibrated to an old 2MB flash. We could try to
> - * pull these from CFI/SFDP, but these values should be good
> - * enough for now.
> - */
> - timeout = max(CHIP_ERASE_2MB_READY_WAIT_JIFFIES,
> - CHIP_ERASE_2MB_READY_WAIT_JIFFIES *
> - (unsigned long)(mtd->size / SZ_2M));
> - ret = spi_nor_wait_till_ready_with_timeout(nor, timeout);
> - if (ret)
> - goto erase_err;
> + /*
> + * Scale the timeout linearly with the size of the flash, with
> + * a minimum calibrated to an old 2MB flash. We could try to
> + * pull these from CFI/SFDP, but these values should be good
> + * enough for now.
> + */
> + timeout = max(CHIP_ERASE_2MB_READY_WAIT_JIFFIES,
> + CHIP_ERASE_2MB_READY_WAIT_JIFFIES *
> + (unsigned long)(params->size / SZ_2M));
> + ret = spi_nor_wait_till_ready_with_timeout(nor, timeout);
> + if (ret)
> + goto erase_err;
> + cur_cs_num++;
> + }
>
> /* REVISIT in some cases we could speed up erasing large regions
> * by using SPINOR_OP_SE instead of SPINOR_OP_BE_4K. We may have set up
> @@ -1493,12 +1502,26 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
>
> /* "sector"-at-a-time erase */
> } else if (spi_nor_has_uniform_erase(nor)) {
> + /* Determine the flash from which the operation need to start */
> + while ((cur_cs_num < SNOR_FLASH_CNT_MAX) && (addr > sz - 1) && params) {
> + cur_cs_num++;
> + params = spi_nor_get_params(nor, cur_cs_num);
> + sz += params->size;
> + }
> +
> while (len) {
> + nor->spimem->spi->cs_index_mask = 0x01 << cur_cs_num;
> ret = spi_nor_write_enable(nor);
> if (ret)
> goto erase_err;
>
> - ret = spi_nor_erase_sector(nor, addr);
> + offset = addr;
> + if (nor->flags & SNOR_F_HAS_STACKED) {
> + params = spi_nor_get_params(nor, cur_cs_num);
> + offset -= (sz - params->size);
> + }
> +
> + ret = spi_nor_erase_sector(nor, offset);
> if (ret)
> goto erase_err;
>
> @@ -1508,13 +1531,45 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
>
> addr += mtd->erasesize;
> len -= mtd->erasesize;
> +
> + /*
> + * Flash cross over condition in stacked mode.
> + */
> + if ((nor->flags & SNOR_F_HAS_STACKED) && (addr > sz - 1)) {
> + cur_cs_num++;
> + params = spi_nor_get_params(nor, cur_cs_num);
> + sz += params->size;
> + }
> }
>
> /* erase multiple sectors */
> } else {
> - ret = spi_nor_erase_multi_sectors(nor, addr, len);
> - if (ret)
> - goto erase_err;
> + u64 erase_len = 0;
> +
> + /* Determine the flash from which the operation need to start */
> + while ((cur_cs_num < SNOR_FLASH_CNT_MAX) && (addr > sz - 1) && params) {
> + cur_cs_num++;
> + params = spi_nor_get_params(nor, cur_cs_num);
> + sz += params->size;
> + }
> + /* perform multi sector erase onec per Flash*/
> + while (len) {
> + erase_len = (len > (sz - addr)) ? (sz - addr) : len;
> + offset = addr;
> + nor->spimem->spi->cs_index_mask = 0x01 << cur_cs_num;
> + if (nor->flags & SNOR_F_HAS_STACKED) {
> + params = spi_nor_get_params(nor, cur_cs_num);
> + offset -= (sz - params->size);
> + }
> + ret = spi_nor_erase_multi_sectors(nor, offset, erase_len);
> + if (ret)
> + goto erase_err;
> + len -= erase_len;
> + addr += erase_len;
> + cur_cs_num++;
> + params = spi_nor_get_params(nor, cur_cs_num);
> + sz += params->size;
> + }
> }
>
> ret = spi_nor_write_disable(nor);
> @@ -1713,7 +1768,10 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
> size_t *retlen, u_char *buf)
> {
> struct spi_nor *nor = mtd_to_spi_nor(mtd);
> - ssize_t ret;
> + struct spi_nor_flash_parameter *params;
> + ssize_t ret, read_len;
> + u32 cur_cs_num = 0;
> + u64 sz;
>
> dev_dbg(nor->dev, "from 0x%08x, len %zd\n", (u32)from, len);
>
> @@ -1721,9 +1779,23 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
> if (ret)
> return ret;
>
> + params = spi_nor_get_params(nor, 0);
> + sz = params->size;
> +
> + /* Determine the flash from which the operation need to start */
> + while ((cur_cs_num < SNOR_FLASH_CNT_MAX) && (from > sz - 1) && params) {
> + cur_cs_num++;
> + params = spi_nor_get_params(nor, cur_cs_num);
> + sz += params->size;
> + }
> while (len) {
> loff_t addr = from;
>
> + nor->spimem->spi->cs_index_mask = 0x01 << cur_cs_num;
> + read_len = (len > (sz - addr)) ? (sz - addr) : len;
> + params = spi_nor_get_params(nor, cur_cs_num);
> + addr -= (sz - params->size);
> +
> addr = spi_nor_convert_addr(nor, addr);
>
> ret = spi_nor_read_data(nor, addr, len, buf);
> @@ -1735,11 +1807,22 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
> if (ret < 0)
> goto read_err;
>
> - WARN_ON(ret > len);
> + WARN_ON(ret > read_len);
> *retlen += ret;
> buf += ret;
> from += ret;
> len -= ret;
> +
> + /*
> + * Flash cross over condition in stacked mode.
> + *
> + */
> + if ((nor->flags & SNOR_F_HAS_STACKED) && (from > sz - 1)) {
> + cur_cs_num++;
> + params = spi_nor_get_params(nor, cur_cs_num);
> + sz += params->size;
> + }
> +
> }
> ret = 0;
>
> @@ -1759,13 +1842,22 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
> struct spi_nor *nor = mtd_to_spi_nor(mtd);
> struct spi_nor_flash_parameter *params;
> size_t page_offset, page_remain, i;
> + u32 page_size, cur_cs_num = 0;
> ssize_t ret;
> - u32 page_size;
> + u64 sz;
>
> dev_dbg(nor->dev, "to 0x%08x, len %zd\n", (u32)to, len);
>
> params = spi_nor_get_params(nor, 0);
> page_size = params->page_size;
> + sz = params->size;
> +
> + /* Determine the flash from which the operation need to start */
> + while ((cur_cs_num < SNOR_FLASH_CNT_MAX) && (to > sz - 1) && params) {
> + cur_cs_num++;
> + params = spi_nor_get_params(nor, cur_cs_num);
> + sz += params->size;
> + }
>
> ret = spi_nor_lock_and_prep(nor);
> if (ret)
> @@ -1790,6 +1882,10 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
> /* the size of data remaining on the first page */
> page_remain = min_t(size_t, page_size - page_offset, len - i);
>
> + nor->spimem->spi->cs_index_mask = 0x01 << cur_cs_num;
> + params = spi_nor_get_params(nor, cur_cs_num);
> + addr -= (sz - params->size);
> +
> addr = spi_nor_convert_addr(nor, addr);
>
> ret = spi_nor_write_enable(nor);
> @@ -1806,6 +1902,15 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
> goto write_err;
> *retlen += written;
> i += written;
> +
> + /*
> + * Flash cross over condition in stacked mode.
> + */
> + if ((nor->flags & SNOR_F_HAS_STACKED) && ((to + i) > sz - 1)) {
> + cur_cs_num++;
> + params = spi_nor_get_params(nor, cur_cs_num);
> + sz += params->size;
> + }
> }
>
> write_err:
> @@ -1918,8 +2023,6 @@ int spi_nor_hwcaps_pp2cmd(u32 hwcaps)
> static int spi_nor_spimem_check_op(struct spi_nor *nor,
> struct spi_mem_op *op)
> {
> - struct spi_nor_flash_parameter *params = spi_nor_get_params(nor, 0);
> -
> /*
> * First test with 4 address bytes. The opcode itself might
> * be a 3B addressing opcode but we don't care, because
> @@ -1928,7 +2031,7 @@ static int spi_nor_spimem_check_op(struct spi_nor *nor,
> */
> op->addr.nbytes = 4;
> if (!spi_mem_supports_op(nor->spimem, op)) {
> - if (params->size > SZ_16M)
> + if (nor->mtd.size > SZ_16M)
> return -EOPNOTSUPP;
>
> /* If flash size <= 16MB, 3 address bytes are sufficient */
> @@ -2516,6 +2619,10 @@ static void spi_nor_init_fixup_flags(struct spi_nor *nor)
> static void spi_nor_late_init_params(struct spi_nor *nor)
> {
> struct spi_nor_flash_parameter *params = spi_nor_get_params(nor, 0);
> + struct device_node *np = spi_nor_get_flash_node(nor);
> + u64 flash_size[SNOR_FLASH_CNT_MAX];
> + u32 idx = 0, i = 0;
> + int rc;
>
> if (nor->manufacturer && nor->manufacturer->fixups &&
> nor->manufacturer->fixups->late_init)
> @@ -2533,6 +2640,36 @@ static void spi_nor_late_init_params(struct spi_nor *nor)
> */
> if (nor->flags & SNOR_F_HAS_LOCK && !params->locking_ops)
> spi_nor_init_default_locking_ops(nor);
> + /*
> + * The flashes that are connected in stacked mode should be of same make.
> + * Except the flash size all other properties are identical for all the
> + * flashes connected in stacked mode.
> + * The flashes that are connected in parallel mode should be identical.
> + */
> + while (i < SNOR_FLASH_CNT_MAX) {
> + rc = of_property_read_u64_index(np, "stacked-memories", idx, &flash_size[i]);
> + if (rc == -EINVAL) {
> + break;
> + } else if (rc == -EOVERFLOW) {
> + idx++;
> + } else {
> + idx++;
> + i++;
> + if (!(nor->flags & SNOR_F_HAS_STACKED))
> + nor->flags |= SNOR_F_HAS_STACKED;
> + }
> + }
> + if (nor->flags & SNOR_F_HAS_STACKED) {
> + for (idx = 1; idx < SNOR_FLASH_CNT_MAX; idx++) {
> + params = spi_nor_get_params(nor, idx);
> + params = devm_kzalloc(nor->dev, sizeof(*params), GFP_KERNEL);
> + if (params) {
> + memcpy(params, spi_nor_get_params(nor, 0), sizeof(*params));
> + params->size = flash_size[idx];
> + spi_nor_set_params(nor, idx, params);
> + }
> + }
> + }
> }
>
> /**
> @@ -2741,22 +2878,36 @@ static int spi_nor_octal_dtr_enable(struct spi_nor *nor, bool enable)
> */
> static int spi_nor_quad_enable(struct spi_nor *nor)
> {
> - struct spi_nor_flash_parameter *params = spi_nor_get_params(nor, 0);
> + struct spi_nor_flash_parameter *params;
> + int err, idx;
>
> - if (!params->quad_enable)
> - return 0;
> + for (idx = 0; idx < SNOR_FLASH_CNT_MAX; idx++) {
> + params = spi_nor_get_params(nor, idx);
> + if (params) {
> + if (!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;
> + if (!(spi_nor_get_protocol_width(nor->read_proto) == 4 ||
> + spi_nor_get_protocol_width(nor->write_proto) == 4))
> + return 0;
> + /*
> + * Set the appropriate CS index before
> + * issuing the command.
> + */
> + nor->spimem->spi->cs_index_mask = 0x01 << idx;
>
> - return params->quad_enable(nor);
> + err = params->quad_enable(nor);
> + if (err)
> + return err;
> + }
> + }
> + return err;
> }
>
> static int spi_nor_init(struct spi_nor *nor)
> {
> - struct spi_nor_flash_parameter *params = spi_nor_get_params(nor, 0);
> - int err;
> + struct spi_nor_flash_parameter *params;
> + int err, idx;
>
> err = spi_nor_octal_dtr_enable(nor, true);
> if (err) {
> @@ -2797,9 +2948,19 @@ 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");
> - err = params->set_4byte_addr_mode(nor, true);
> - if (err && err != -ENOTSUPP)
> - return err;
> + for (idx = 0; idx < SNOR_FLASH_CNT_MAX; idx++) {
> + params = spi_nor_get_params(nor, idx);
> + if (params) {
> + /*
> + * Select the appropriate CS index before
> + * issuing the command.
> + */
> + nor->spimem->spi->cs_index_mask = 0x01 << idx;
> + err = params->set_4byte_addr_mode(nor, true);
> + if (err && err != -ENOTSUPP)
> + return err;
> + }
> + }
> }
>
> return 0;
> @@ -2915,19 +3076,31 @@ void spi_nor_restore(struct spi_nor *nor)
> {
> struct spi_nor_flash_parameter *params;
> int ret;
> + int idx;
>
> /* restore the addressing mode */
> if (nor->addr_nbytes == 4 && !(nor->flags & SNOR_F_4B_OPCODES) &&
> nor->flags & SNOR_F_BROKEN_RESET) {
> - params = spi_nor_get_params(nor, 0);
> - ret = params->set_4byte_addr_mode(nor, false);
> - if (ret)
> - /*
> - * Do not stop the execution in the hope that the flash
> - * will default to the 3-byte address mode after the
> - * software reset.
> - */
> - dev_err(nor->dev, "Failed to exit 4-byte address mode, err = %d\n", ret);
> + for (idx = 0; idx < SNOR_FLASH_CNT_MAX; idx++) {
> + params = spi_nor_get_params(nor, idx);
> + if (params) {
> + /*
> + * Select the appropriate CS index before
> + * issuing the command.
> + */
> + nor->spimem->spi->cs_index_mask = 0x01 << idx;
> + ret = params->set_4byte_addr_mode(nor, false);
> + if (ret)
> + /*
> + * Do not stop the execution in the hope that the flash
> + * will default to the 3-byte address mode after the
> + * software reset.
> + */
> + dev_err(nor->dev,
> + "Failed to exit 4-byte address mode, err = %d\n",
> + ret);
> + }
> + }
> }
>
> if (nor->flags & SNOR_F_SOFT_RESET)
> @@ -2995,6 +3168,8 @@ static void spi_nor_set_mtd_info(struct spi_nor *nor)
> struct spi_nor_flash_parameter *params = spi_nor_get_params(nor, 0);
> struct mtd_info *mtd = &nor->mtd;
> struct device *dev = nor->dev;
> + u64 total_sz = 0;
> + int idx;
>
> spi_nor_set_mtd_locking_ops(nor);
> spi_nor_set_mtd_otp_ops(nor);
> @@ -3010,7 +3185,12 @@ static void spi_nor_set_mtd_info(struct spi_nor *nor)
> mtd->_erase = spi_nor_erase;
> mtd->writesize = params->writesize;
> mtd->writebufsize = params->page_size;
> - mtd->size = params->size;
> + for (idx = 0; idx < SNOR_FLASH_CNT_MAX; idx++) {
> + params = spi_nor_get_params(nor, idx);
> + if (params)
> + total_sz += params->size;
> + }
> + mtd->size = total_sz;
> mtd->_read = spi_nor_read;
> /* Might be already set by some SST flashes. */
> if (!mtd->_write)
> diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
> index f03b55cf7e6f..e94107cc465e 100644
> --- a/drivers/mtd/spi-nor/core.h
> +++ b/drivers/mtd/spi-nor/core.h
> @@ -11,6 +11,9 @@
>
> #define SPI_NOR_MAX_ID_LEN 6
>
> +/* In single configuration enable CS0 */
> +#define SPI_NOR_ENABLE_CS0 BIT(0)
> +
> /* Standard SPI NOR flash operations. */
> #define SPI_NOR_READID_OP(naddr, ndummy, buf, len) \
> SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 0), \
> @@ -130,6 +133,7 @@ enum spi_nor_option_flags {
> SNOR_F_IO_MODE_EN_VOLATILE = BIT(11),
> SNOR_F_SOFT_RESET = BIT(12),
> SNOR_F_SWP_IS_VOLATILE = BIT(13),
> + SNOR_F_HAS_STACKED = BIT(14),
> };
>
> struct spi_nor_read_command {
> diff --git a/drivers/mtd/spi-nor/otp.c b/drivers/mtd/spi-nor/otp.c
> index a9c0844d55ef..b8c7e085a90d 100644
> --- a/drivers/mtd/spi-nor/otp.c
> +++ b/drivers/mtd/spi-nor/otp.c
> @@ -11,8 +11,8 @@
>
> #include "core.h"
>
> -#define spi_nor_otp_region_len(nor) ((nor)->params->otp.org->len)
> -#define spi_nor_otp_n_regions(nor) ((nor)->params->otp.org->n_regions)
> +#define spi_nor_otp_region_len(nor) ((nor)->params[0]->otp.org->len)
> +#define spi_nor_otp_n_regions(nor) ((nor)->params[0]->otp.org->n_regions)
I think this should be also converted to static inline functions and use
spi_nor_get_params(nor, 0); instead of using arrays.
Can be done on the top of this series if it is the only one problem with it.
Thanks,
Michal
More information about the linux-amlogic
mailing list