mtd: spi-nor: add erase die (chip) capability
authorTudor Ambarus <tudor.ambarus@linaro.org>
Sat, 25 Nov 2023 12:35:25 +0000 (14:35 +0200)
committerTudor Ambarus <tudor.ambarus@linaro.org>
Wed, 6 Dec 2023 09:24:42 +0000 (11:24 +0200)
JESD216 mentions die erase, but does not provide an opcode for it.
Check BFPT dword 11, bits 30:24, "Chip Erase, Typical time", it says:

"Typical time to erase one chip (die). User must poll device busy to
determine if the operation has completed. For a device consisting of
multiple dies, that are individually accessed, the time is for each die
to which a chip erase command is applied."

So when a flash consists of a single die, this is the erase time for the
full chip (die) erase, and when it consists of multiple dies, it's the
die erase time. Chip and die are the same thing.

Add support for die erase. For now, benefit of the die erase when addr
and len are aligned with die size. This could be improved however for
the uniform and non-uniform erases cases to use the die erase when
possible. For example if one requests that an erase of a 2 die device
starting from the last 64KB of the first die to the end of the flash
size, we could use just 2 commands, a 64KB erase and a die erase.
This improvement is left as an exercise for the reader.

Tested-by: Fabio Estevam <festevam@denx.de>
Link: https://lore.kernel.org/r/20231125123529.55686-2-tudor.ambarus@linaro.org
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
drivers/mtd/spi-nor/core.c
drivers/mtd/spi-nor/core.h
drivers/mtd/spi-nor/debugfs.c

index 25a64c65717d2c9d5da95fc353314e8da0f19d3d..479494cf00c92b9382f3662eebab3849565fc0c2 100644 (file)
@@ -1060,24 +1060,32 @@ static int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2)
 }
 
 /**
- * spi_nor_erase_chip() - Erase the entire flash memory.
+ * spi_nor_erase_die() - Erase the entire die.
  * @nor:       pointer to 'struct spi_nor'.
+ * @addr:      address of the die.
+ * @die_size:  size of the die.
  *
  * Return: 0 on success, -errno otherwise.
  */
-static int spi_nor_erase_chip(struct spi_nor *nor)
+static int spi_nor_erase_die(struct spi_nor *nor, loff_t addr, size_t die_size)
 {
+       bool multi_die = nor->mtd.size != die_size;
        int ret;
 
-       dev_dbg(nor->dev, " %lldKiB\n", (long long)(nor->mtd.size >> 10));
+       dev_dbg(nor->dev, " %lldKiB\n", (long long)(die_size >> 10));
 
        if (nor->spimem) {
-               struct spi_mem_op op = SPI_NOR_CHIP_ERASE_OP;
+               struct spi_mem_op op =
+                       SPI_NOR_DIE_ERASE_OP(nor->params->die_erase_opcode,
+                                            nor->addr_nbytes, addr, multi_die);
 
                spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
 
                ret = spi_mem_exec_op(nor->spimem, &op);
        } else {
+               if (multi_die)
+                       return -EOPNOTSUPP;
+
                ret = spi_nor_controller_ops_write_reg(nor,
                                                       SPINOR_OP_CHIP_ERASE,
                                                       NULL, 0);
@@ -1792,6 +1800,51 @@ destroy_erase_cmd_list:
        return ret;
 }
 
+static int spi_nor_erase_dice(struct spi_nor *nor, loff_t addr,
+                             size_t len, size_t die_size)
+{
+       unsigned long timeout;
+       int ret;
+
+       /*
+        * 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)(nor->mtd.size / SZ_2M));
+
+       do {
+               ret = spi_nor_lock_device(nor);
+               if (ret)
+                       return ret;
+
+               ret = spi_nor_write_enable(nor);
+               if (ret) {
+                       spi_nor_unlock_device(nor);
+                       return ret;
+               }
+
+               ret = spi_nor_erase_die(nor, addr, die_size);
+
+               spi_nor_unlock_device(nor);
+               if (ret)
+                       return ret;
+
+               ret = spi_nor_wait_till_ready_with_timeout(nor, timeout);
+               if (ret)
+                       return ret;
+
+               addr += die_size;
+               len -= die_size;
+
+       } while (len);
+
+       return 0;
+}
+
 /*
  * Erase an address range on the nor chip.  The address range may extend
  * one or more erase sectors. Return an error if there is a problem erasing.
@@ -1799,7 +1852,10 @@ destroy_erase_cmd_list:
 static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
 {
        struct spi_nor *nor = mtd_to_spi_nor(mtd);
+       u8 n_dice = nor->params->n_dice;
+       bool multi_die_erase = false;
        u32 addr, len, rem;
+       size_t die_size;
        int ret;
 
        dev_dbg(nor->dev, "at 0x%llx, len %lld\n", (long long)instr->addr,
@@ -1814,39 +1870,22 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
        addr = instr->addr;
        len = instr->len;
 
+       if (n_dice) {
+               die_size = div_u64(mtd->size, n_dice);
+               if (!(len & (die_size - 1)) && !(addr & (die_size - 1)))
+                       multi_die_erase = true;
+       } else {
+               die_size = mtd->size;
+       }
+
        ret = spi_nor_prep_and_lock_pe(nor, instr->addr, instr->len);
        if (ret)
                return ret;
 
-       /* whole-chip erase? */
-       if (len == mtd->size && !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
-               unsigned long timeout;
-
-               ret = spi_nor_lock_device(nor);
-               if (ret)
-                       goto erase_err;
-
-               ret = spi_nor_write_enable(nor);
-               if (ret) {
-                       spi_nor_unlock_device(nor);
-                       goto erase_err;
-               }
-
-               ret = spi_nor_erase_chip(nor);
-               spi_nor_unlock_device(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);
+       /* chip (die) erase? */
+       if ((len == mtd->size && !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) ||
+           multi_die_erase) {
+               ret = spi_nor_erase_dice(nor, addr, len, die_size);
                if (ret)
                        goto erase_err;
 
@@ -2902,6 +2941,9 @@ static int spi_nor_late_init_params(struct spi_nor *nor)
                        return ret;
        }
 
+       if (!nor->params->die_erase_opcode)
+               nor->params->die_erase_opcode = SPINOR_OP_CHIP_ERASE;
+
        /* Default method kept for backward compatibility. */
        if (!params->set_4byte_addr_mode)
                params->set_4byte_addr_mode = spi_nor_set_4byte_addr_mode_brwr;
index a456042379ee52f28420c38e017634c086f9d8f1..b43ea2d49e74181382518a171659c5b4afbb4b33 100644 (file)
@@ -85,9 +85,9 @@
                   SPI_MEM_OP_NO_DUMMY,                                 \
                   SPI_MEM_OP_NO_DATA)
 
-#define SPI_NOR_CHIP_ERASE_OP                                          \
-       SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CHIP_ERASE, 0),             \
-                  SPI_MEM_OP_NO_ADDR,                                  \
+#define SPI_NOR_DIE_ERASE_OP(opcode, addr_nbytes, addr, dice)          \
+       SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0),                           \
+                  SPI_MEM_OP_ADDR(dice ? addr_nbytes : 0, addr, 0),    \
                   SPI_MEM_OP_NO_DUMMY,                                 \
                   SPI_MEM_OP_NO_DATA)
 
@@ -362,6 +362,7 @@ struct spi_nor_otp {
  *                     command in octal DTR mode.
  * @n_banks:           number of banks.
  * @n_dice:            number of dice in the flash memory.
+ * @die_erase_opcode:  die erase opcode. Defaults to SPINOR_OP_CHIP_ERASE.
  * @vreg_offset:       volatile register offset for each die.
  * @hwcaps:            describes the read and page program hardware
  *                     capabilities.
@@ -399,6 +400,7 @@ struct spi_nor_flash_parameter {
        u8                              rdsr_addr_nbytes;
        u8                              n_banks;
        u8                              n_dice;
+       u8                              die_erase_opcode;
        u32                             *vreg_offset;
 
        struct spi_nor_hwcaps           hwcaps;
index 6e163cb5b478c8e6f05fdbe608caa78f51356f38..2dbda6b6938abd9caceff084a7421e0ec46ea033 100644 (file)
@@ -138,7 +138,7 @@ static int spi_nor_params_show(struct seq_file *s, void *data)
 
        if (!(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
                string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf));
-               seq_printf(s, " %02x (%s)\n", SPINOR_OP_CHIP_ERASE, buf);
+               seq_printf(s, " %02x (%s)\n", nor->params->die_erase_opcode, buf);
        }
 
        seq_puts(s, "\nsector map\n");