[PATCH] nand_ecc_micron: added support for 4 bits on-die ECC
Jelle Martijn Kok
jmkok at youcom.nl
Mon May 6 15:43:03 EDT 2013
The patch below enables support for the 4-bits on-die ECC (aka internal
ECC).
Some notes:
- it is detected and activate from nand_scan_tail().
- when called from nand_scan_ident() it fails as at least "atmel_nand.c"
overwrites some setup
- if an 4-bits on-die capable Micron nand flash is detected it will kick in
- UBI works like a charm and nicely moves the bit-flipped page/block
- when performing "dd if=/dev/mtdblock1 of=/dev/null" and encountering
such a bad page, it fails with an I/O error (is this correct ?)
- raw reads likely do not work. When the on-die ECC is enabled it will
automatically correct bitflips...
- Tested on MT29F1G08ABBDAHC and MT29F2G08ABBEAH4
Signed-off-by: Jelle Martijn Kok <jmkok at youcom.nl>
---
drivers/mtd/nand/Kconfig | 7 +
drivers/mtd/nand/Makefile | 1 +
drivers/mtd/nand/nand_base.c | 9 ++
drivers/mtd/nand/nand_ecc_micron.c | 238
+++++++++++++++++++++++++++++++++++
include/linux/mtd/nand_ecc_micron.h | 9 ++
5 files changed, 264 insertions(+), 0 deletions(-)
create mode 100644 drivers/mtd/nand/nand_ecc_micron.c
create mode 100644 include/linux/mtd/nand_ecc_micron.h
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index cce7b70..54afcaa 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -46,6 +46,13 @@ config MTD_NAND_ECC_BCH
ECC codes. They are used with NAND devices requiring more than 1 bit
of error correction.
+config MTD_NAND_ECC_MICRON
+ bool "Support Micron internal ECC"
+ default n
+ help
+ This enables support for Micron 4-bits internal ECC
+ Do not enable this if you will not be using it
+
config MTD_SM_COMMON
tristate
default n
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index 618f4ba..4db54de 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -49,5 +49,6 @@ obj-$(CONFIG_MTD_NAND_MPC5121_NFC) += mpc5121_nfc.o
obj-$(CONFIG_MTD_NAND_RICOH) += r852.o
obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o
obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi-nand/
+obj-$(CONFIG_MTD_NAND_ECC_MICRON) += nand_ecc_micron.o
nand-objs := nand_base.o nand_bbt.o
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index daed698..70a2415 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -42,6 +42,7 @@
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/nand_ecc.h>
+#include <linux/mtd/nand_ecc_micron.h>
#include <linux/mtd/nand_bch.h>
#include <linux/interrupt.h>
#include <linux/bitops.h>
@@ -3234,6 +3235,14 @@ int nand_scan_tail(struct mtd_info *mtd)
int i;
struct nand_chip *chip = mtd->priv;
+#ifdef CONFIG_MTD_NAND_ECC_MICRON
+ /* Detect and enable Micron 4-bits internal ECC */
+ if (nand_ecc_micron_present(mtd)) {
+ pr_info("NAND device: Using micron 4-bit internal ECC\n");
+ nand_ecc_micron_init(mtd);
+ }
+#endif
+
if (!(chip->options & NAND_OWN_BUFFERS))
chip->buffers = kmalloc(sizeof(*chip->buffers), GFP_KERNEL);
if (!chip->buffers)
diff --git a/drivers/mtd/nand/nand_ecc_micron.c
b/drivers/mtd/nand/nand_ecc_micron.c
new file mode 100644
index 0000000..49afaaf
--- /dev/null
+++ b/drivers/mtd/nand/nand_ecc_micron.c
@@ -0,0 +1,238 @@
+#include <linux/delay.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/nand_ecc_micron.h>
+
+/* Define the oob placement for the micron internal ECC nand */
+static struct nand_ecclayout nand_oob_micron_ecc_64 = {
+ .eccbytes = 32,
+ .eccpos = {
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ 24, 25, 26, 27, 28, 29, 30, 31,
+ 40, 41, 42, 43, 44, 45, 46, 47,
+ 56, 57, 58, 59, 60, 61, 62, 63
+ },
+ .oobfree = {
+ { .offset = 4, .length = 4 },
+ { .offset = 20, .length = 4 },
+ { .offset = 36, .length = 4 },
+ { .offset = 52, .length = 4 },
+ },
+};
+
+/**
+ * nand_command_ecc_micron
+ * @mtd: MTD device structure
+ * @command: the command to be sent
+ * @column: the column address for this command, -1 if none
+ * @page_addr: the page address for this command, -1 if none
+ *
+ * Identical to nand_command_lp, but additionally checks the micron
+ * status after a NAND_CMD_READ0.
+ */
+
+static void nand_command_ecc_micron(struct mtd_info *mtd, unsigned int
command,
+ int column, int page_addr)
+{
+ register struct nand_chip *chip = mtd->priv;
+
+ /* Emulate NAND_CMD_READOOB */
+ if (command == NAND_CMD_READOOB) {
+ column += mtd->writesize;
+ command = NAND_CMD_READ0;
+ }
+
+ /* Command latch cycle */
+ chip->cmd_ctrl(mtd, command & 0xff,
+ NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
+
+ if (column != -1 || page_addr != -1) {
+ int ctrl = NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE;
+
+ /* Serially input address */
+ if (column != -1) {
+ /* Adjust columns for 16 bit buswidth */
+ if (chip->options & NAND_BUSWIDTH_16)
+ column >>= 1;
+ chip->cmd_ctrl(mtd, column, ctrl);
+ ctrl &= ~NAND_CTRL_CHANGE;
+ chip->cmd_ctrl(mtd, column >> 8, ctrl);
+ }
+ if (page_addr != -1) {
+ chip->cmd_ctrl(mtd, page_addr, ctrl);
+ chip->cmd_ctrl(mtd, page_addr >> 8,
+ NAND_NCE | NAND_ALE);
+ /* One more address cycle for devices > 128MiB */
+ if (chip->chipsize > (128 << 20))
+ chip->cmd_ctrl(mtd, page_addr >> 16,
+ NAND_NCE | NAND_ALE);
+ }
+ }
+ chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
+
+ /*
+ * Program and erase have their own busy handlers status, sequential
+ * in, and deplete1 need no delay.
+ */
+ switch (command) {
+
+ case NAND_CMD_CACHEDPROG:
+ case NAND_CMD_PAGEPROG:
+ case NAND_CMD_ERASE1:
+ case NAND_CMD_ERASE2:
+ case NAND_CMD_SEQIN:
+ case NAND_CMD_RNDIN:
+ case NAND_CMD_STATUS:
+ case NAND_CMD_DEPLETE1:
+ return;
+
+ case NAND_CMD_STATUS_ERROR:
+ case NAND_CMD_STATUS_ERROR0:
+ case NAND_CMD_STATUS_ERROR1:
+ case NAND_CMD_STATUS_ERROR2:
+ case NAND_CMD_STATUS_ERROR3:
+ /* Read error status commands require only a short delay */
+ udelay(chip->chip_delay);
+ return;
+
+ case NAND_CMD_RESET:
+ if (chip->dev_ready)
+ break;
+ udelay(chip->chip_delay);
+ chip->cmd_ctrl(mtd, NAND_CMD_STATUS,
+ NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
+ chip->cmd_ctrl(mtd, NAND_CMD_NONE,
+ NAND_NCE | NAND_CTRL_CHANGE);
+ while (!(chip->read_byte(mtd) & NAND_STATUS_READY))
+ ;
+ return;
+
+ case NAND_CMD_RNDOUT:
+ /* No ready / busy check necessary */
+ chip->cmd_ctrl(mtd, NAND_CMD_RNDOUTSTART,
+ NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
+ chip->cmd_ctrl(mtd, NAND_CMD_NONE,
+ NAND_NCE | NAND_CTRL_CHANGE);
+ return;
+
+ case NAND_CMD_READ0:
+ chip->cmd_ctrl(mtd, NAND_CMD_READSTART,
+ NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
+ chip->cmd_ctrl(mtd, NAND_CMD_NONE,
+ NAND_NCE | NAND_CTRL_CHANGE);
+
+ /* This applies to read commands */
+ default:
+ /*
+ * If we don't have access to the busy pin, we apply the given
+ * command delay.
+ */
+ if (!chip->dev_ready) {
+ udelay(chip->chip_delay);
+ return;
+ }
+ }
+
+ /* Some additional things need to be done on read */
+ if (command == NAND_CMD_READ0) {
+ int status,cnt;
+ /* Wait until nand flash is ready - tR_ECC max */
+ for (cnt=0; cnt<70; cnt++) {
+ ndelay(1000); /* 1 usec delay */
+ chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+ status = chip->read_byte(mtd);
+ if (status & NAND_STATUS_READY)
+ break;
+ }
+ /* Check the status bits */
+ if (status & NAND_STATUS_FAIL)
+ mtd->ecc_stats.failed++;
+ if (status & NAND_STATUS_REWRITE)
+ mtd->ecc_stats.corrected++;
+ /* Re-issue CMD0 after the status check */
+ chip->cmd_ctrl(mtd, NAND_CMD_READ0,NAND_NCE | NAND_CLE |
NAND_CTRL_CHANGE);
+ chip->cmd_ctrl(mtd, NAND_CMD_NONE,NAND_NCE | NAND_CTRL_CHANGE);
+ }
+
+ /*
+ * Apply this short delay always to ensure that we do wait tWB in
+ * any case on any machine.
+ */
+ ndelay(100);
+
+ nand_wait_ready(mtd);
+}
+
+/**
+ * nand_read_page_ecc_micron - IDENTICAL to nand_read_page_raw
+ * @mtd: mtd info structure
+ * @chip: nand chip info structure
+ * @buf: buffer to store read data
+ * @page: page number to read
+ */
+
+static int nand_read_page_ecc_micron(struct mtd_info *mtd, struct
nand_chip *chip, uint8_t *buf, int page) {
+ chip->read_buf(mtd, buf, mtd->writesize);
+ chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+ return 0;
+}
+
+/**
+ * nand_write_page_ecc_micron - IDENTICAL to nand_write_page_raw
+ * @mtd: mtd info structure
+ * @chip: nand chip info structure
+ * @buf: data buffer
+ */
+
+static void nand_write_page_ecc_micron(struct mtd_info *mtd, struct
nand_chip *chip, const uint8_t *buf) {
+ chip->write_buf(mtd, buf, mtd->writesize);
+ chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
+}
+
+int nand_ecc_micron_present(struct mtd_info *mtd) {
+ int i;
+ u8 id_data[5];
+ struct nand_chip *chip = mtd->priv;
+
+ /* Currently it seems it cannot be detected by onfi, but only via
READID byte 4.
+ * So see if byte 4 indicates internal ECC is present (we do not
require it to
+ * be already enabled. This might be the case if the kernel is
loaded from another
+ * source (NOR flash, SD card or network)
+ * We ALWAYS enabled internal ECC if present, if you do not wish
this, then do not
+ * enable this feature in the kernel config
+ */
+ if (chip->onfi_params.jedec_id != NAND_MFR_MICRON)
+ return 0;
+ chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
+ for (i = 0; i < 5; i++)
+ id_data[i] = chip->read_byte(mtd);
+ if (id_data[0] == NAND_MFR_MICRON && (id_data[4] & 0x03) == 0x02)
+ return 1;
+ return 0;
+}
+
+static char micron_enable_ecc[] = {0x08,0,0,0};
+
+void nand_ecc_micron_init(struct mtd_info *mtd) {
+ struct nand_chip *chip = mtd->priv;
+
+ /* Setup the read/write functions
+ * NOTE: we are required to do so, or we are not allowed to set the
ecc.mode to NAND_ECC_HW */
+ chip->ecc.read_page = nand_read_page_ecc_micron;
+ chip->ecc.write_page = nand_write_page_ecc_micron;
+
+ /* We need to modify NAND_CMD_READ0, so hook into the cmdfunc */
+ chip->cmdfunc = nand_command_ecc_micron;
+
+ /* Make sure the internel ECC engine of the nand is active */
+ chip->cmd_ctrl(mtd, 0xEF, NAND_CTRL_CHANGE | NAND_NCE | NAND_CLE);
/* SET FEATURES */
+ chip->cmd_ctrl(mtd, 0x90, NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE);
/* Array operation mode */
+ chip->write_buf(mtd, micron_enable_ecc, 4);
+ ndelay(1000); /* tFEAT 1 usec delay */
+
+ /* Setup the ecc structure */
+ chip->ecc.layout = &nand_oob_micron_ecc_64;
+ chip->ecc.mode = NAND_ECC_HW;
+ chip->ecc.size = 512;
+ chip->ecc.bytes = 8;
+}
diff --git a/include/linux/mtd/nand_ecc_micron.h
b/include/linux/mtd/nand_ecc_micron.h
new file mode 100644
index 0000000..c270ddc
--- /dev/null
+++ b/include/linux/mtd/nand_ecc_micron.h
@@ -0,0 +1,9 @@
+#ifndef __LINUX_MTD_NAND_ECC_MICRON_H
+#define __LINUX_MTD_NAND_ECC_MICRON_H
+
+#define NAND_STATUS_REWRITE 0x08
+
+int nand_ecc_micron_present(struct mtd_info *mtd);
+void nand_ecc_micron_init(struct mtd_info *mtd);
+
+#endif /* __LINUX_MTD_NAND_ECC_MICRON_H */
--
1.7.0.4
More information about the linux-mtd
mailing list