[PATCH v2 3/3] mtd: rawnand: micron: Address the shallow erase issue

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



With recent SLC NANDs, Micron admits that a "shallow erase" issue may
be observable. It is actually the chip itself not doing a correct
erase operation because of its internal machinery stating that the
pages have not been programmed. Micron told us that there is a way to
workaround this issue: ensure that all the odd pages in the 16 first
ones of each block to erase have been fully written.

To avoid a very big performance drawback by re-writting all the pages
for each erase operation, the fix proposed here overloads the ->erase
and ->write_oob hooks to count the pages actually written at runtime
and avoid re-writting them if not needed.

Signed-off-by: Miquel Raynal <miquel.raynal@xxxxxxxxxxx>
---
 drivers/mtd/nand/raw/nand_micron.c | 121 +++++++++++++++++++++++++++++
 1 file changed, 121 insertions(+)

diff --git a/drivers/mtd/nand/raw/nand_micron.c b/drivers/mtd/nand/raw/nand_micron.c
index 56654030ec7f..a9afd1b9a9e8 100644
--- a/drivers/mtd/nand/raw/nand_micron.c
+++ b/drivers/mtd/nand/raw/nand_micron.c
@@ -36,6 +36,15 @@
 #define NAND_ECC_STATUS_1_3_CORRECTED	BIT(4)
 #define NAND_ECC_STATUS_7_8_CORRECTED	(BIT(4) | BIT(3))
 
+/*
+ * Micron SLC chips are subject to a shallow erase issue: if the first
+ * pages of a block have not enough bytes programmed, the internal
+ * machinery might declare the block empty and skip the actual erase
+ * operation. This is the number of pages we check by software.
+ */
+#define MICRON_SHALLOW_ERASE_MIN_PAGE 16
+#define MICRON_PAGE_MASK_TRIGGER GENMASK(MICRON_SHALLOW_ERASE_MIN_PAGE, 0)
+
 struct nand_onfi_vendor_micron {
 	u8 two_plane_read;
 	u8 read_cache;
@@ -64,6 +73,7 @@ struct micron_on_die_ecc {
 
 struct micron_nand {
 	struct micron_on_die_ecc ecc;
+	u16 *writtenp;
 };
 
 static int micron_nand_setup_read_retry(struct nand_chip *chip, int retry_mode)
@@ -429,6 +439,106 @@ static int micron_supports_on_die_ecc(struct nand_chip *chip)
 	return MICRON_ON_DIE_SUPPORTED;
 }
 
+static int micron_nand_avoid_shallow_erase(struct nand_chip *chip,
+					   unsigned int eb)
+{
+	struct micron_nand *micron = nand_get_manufacturer_data(chip);
+	unsigned int page = eb * nanddev_pages_per_eraseblock(&chip->base);
+	u8 *databuf = nand_get_data_buf(chip);
+	int ret, i;
+
+	memset(databuf, 0x00, nanddev_page_size(&chip->base));
+
+	/* Micron advises to only write the first 8 odd pages, counting from 1 */
+	for (i = 0; i < MICRON_SHALLOW_ERASE_MIN_PAGE; i += 2, page += 2) {
+		if (!(micron->writtenp[eb] & BIT(i))) {
+			ret = nand_write_page_raw(chip, databuf, false, page);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int micron_nand_erase(struct nand_chip *chip, struct erase_info *instr,
+			int allowbbt)
+{
+	struct micron_nand *micron = nand_get_manufacturer_data(chip);
+	unsigned int eb_sz = nanddev_eraseblock_size(&chip->base);
+	unsigned int first_eb = DIV_ROUND_DOWN_ULL(instr->addr, eb_sz);
+	unsigned int nb_eb = DIV_ROUND_UP_ULL(instr->len, eb_sz);
+	unsigned int eb;
+
+	if (!micron)
+		return -EINVAL;
+
+	/*
+	 * Check that enough pages have been written in each block.
+	 * If not, write them before actually erasing.
+	 */
+	for (eb = first_eb; eb < first_eb + nb_eb; eb++) {
+		/* Il all the first pages are not written yet, do it */
+		if (micron->writtenp[eb] != MICRON_PAGE_MASK_TRIGGER)
+			micron_nand_avoid_shallow_erase(chip, eb);
+
+		micron->writtenp[eb] = 0;
+	}
+
+	return nand_erase_nand(chip, instr, allowbbt);
+}
+static int micron_nand_write_oob(struct nand_chip *chip, loff_t to,
+				 struct mtd_oob_ops *ops)
+{
+	struct micron_nand *micron = nand_get_manufacturer_data(chip);
+	unsigned int eb_sz = nanddev_eraseblock_size(&chip->base);
+	unsigned int p_sz = nanddev_page_size(&chip->base);
+	unsigned int ppeb = nanddev_pages_per_eraseblock(&chip->base);
+	unsigned int nb_p_tot = ops->len / p_sz;
+	unsigned int first_eb = DIV_ROUND_DOWN_ULL(to, eb_sz);
+	unsigned int first_p = DIV_ROUND_UP_ULL(to - (first_eb * eb_sz), p_sz);
+	unsigned int nb_eb = DIV_ROUND_UP_ULL(first_p + nb_p_tot, ppeb);
+	unsigned int remaining_p, eb, nb_p;
+	int ret;
+
+	ret = nand_write_oob_nand(chip, to, ops);
+	if (ret || (ops->len != ops->retlen))
+		return ret;
+
+	/* Mark the last pages of the first erase block to write */
+	nb_p = min(nb_p_tot, ppeb - first_p);
+	micron->writtenp[first_eb] |= GENMASK(first_p + nb_p, first_p) &
+				      MICRON_PAGE_MASK_TRIGGER;
+	remaining_p = nb_p_tot - nb_p;
+
+	/* Mark all the pages of all "in-the-middle" erase blocks */
+	for (eb = first_eb + 1; eb < first_eb + nb_eb - 1; eb++) {
+		micron->writtenp[eb] |= MICRON_PAGE_MASK_TRIGGER;
+		remaining_p -= ppeb;
+	}
+
+	/* Mark the first pages of the last erase block to write */
+	if (remaining_p)
+		micron->writtenp[eb] |= GENMASK(remaining_p - 1, 0) &
+					MICRON_PAGE_MASK_TRIGGER;
+
+	return 0;
+}
+
+static bool micron_nand_with_shallow_erase_issue(struct nand_chip *chip)
+{
+	/*
+	 * The shallow erase issue has been observed with MT29F*G*A
+	 * parts but Micron suspects that the issue can happen with
+	 * almost all recent SLC but at such a low probability that it
+	 * is almost invisible. Nevertheless, as we mitigate the
+	 * performance penalty at runtime by following the number of
+	 * written pages in a block before erasing it, we may want to
+	 * enable this fix by default.
+	 */
+	return nand_is_slc(chip);
+}
+
 static int micron_nand_init(struct nand_chip *chip)
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
@@ -513,6 +623,17 @@ static int micron_nand_init(struct nand_chip *chip)
 		}
 	}
 
+	if (micron_nand_with_shallow_erase_issue(chip)) {
+		micron->writtenp = kzalloc(sizeof(u16) *
+					   nanddev_neraseblocks(&chip->base),
+					   GFP_KERNEL);
+		if (!micron->writtenp)
+			goto err_free_manuf_data;
+
+		chip->erase = micron_nand_erase;
+		chip->write_oob = micron_nand_write_oob;
+	}
+
 	return 0;
 
 err_free_manuf_data:
-- 
2.20.1


______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/



[Index of Archives]     [LARTC]     [Bugtraq]     [Yosemite Forum]     [Photo]

  Powered by Linux