[PATCH] mtd: rawnand: driver for Mediatek MT7621 SoC NAND flash controller

gerg at kernel.org gerg at kernel.org
Wed Nov 6 23:35:21 PST 2019


From: Greg Ungerer <gerg at kernel.org>

Add a driver to support the NAND flash controller of the MediaTek MT7621
System-on-Chip device. (This one is the MIPS based parts from Mediatek).

This code is a re-working of the earlier patches for this hardware that
have been floating around the internet for years:

https://github.com/ReclaimYourPrivacy/cloak/blob/master/target/linux/ramips/patches-3.18/0045-mtd-add-mt7621-nand-support.patch

This is a much cleaned up version, put in staging to start with.
It does still have some problems, mainly that it still uses a lot of the
mtd raw nand legacy support.

The driver not only compiles, but it works well on the small range of
hardware platforms that it has been used on so far. I have been using
for quite a while now, cleaning up as I get time.

So... I am looking for comments on the best approach forward with this.
At least in staging it can get some more eyeballs going over it.

There is a mediatek nand driver already, mtk_nand.c, for their ARM based
System-on-Chip devices. That hardware module looks to have some hardware
similarities with this one. At this point I don't know if that can be
used on the 7621 based devices. (I tried a quick and dirty setup and had
no success using it on the 7621).

Thoughts?

Signed-off-by: Greg Ungerer <gerg at kernel.org>
---
 drivers/staging/Kconfig                       |    2 +
 drivers/staging/Makefile                      |    1 +
 drivers/staging/mt7621-nand/Kconfig           |    7 +
 drivers/staging/mt7621-nand/Makefile          |    1 +
 drivers/staging/mt7621-nand/TODO              |    6 +
 drivers/staging/mt7621-nand/mt7621_nand.c     | 1713 +++++++++++++++++
 drivers/staging/mt7621-nand/mt7621_nand.h     |  362 ++++
 .../staging/mt7621-nand/mtk,mt7621-nand.txt   |   29 +
 8 files changed, 2121 insertions(+)
 create mode 100644 drivers/staging/mt7621-nand/Kconfig
 create mode 100644 drivers/staging/mt7621-nand/Makefile
 create mode 100644 drivers/staging/mt7621-nand/TODO
 create mode 100644 drivers/staging/mt7621-nand/mt7621_nand.c
 create mode 100644 drivers/staging/mt7621-nand/mt7621_nand.h
 create mode 100644 drivers/staging/mt7621-nand/mtk,mt7621-nand.txt

diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 6f1fa4c849a1..03167062b623 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -108,6 +108,8 @@ source "drivers/staging/ralink-gdma/Kconfig"
 
 source "drivers/staging/mt7621-dts/Kconfig"
 
+source "drivers/staging/mt7621-nand/Kconfig"
+
 source "drivers/staging/gasket/Kconfig"
 
 source "drivers/staging/axis-fifo/Kconfig"
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index a90f9b308c8d..8cb3bf2503ba 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_PINCTRL_RT2880)	+= mt7621-pinctrl/
 obj-$(CONFIG_SOC_MT7621)	+= mt7621-dma/
 obj-$(CONFIG_DMA_RALINK)	+= ralink-gdma/
 obj-$(CONFIG_SOC_MT7621)	+= mt7621-dts/
+obj-$(CONFIG_SOC_MT7621)	+= mt7621-nand/
 obj-$(CONFIG_STAGING_GASKET_FRAMEWORK)	+= gasket/
 obj-$(CONFIG_XIL_AXIS_FIFO)	+= axis-fifo/
 obj-$(CONFIG_FIELDBUS_DEV)     += fieldbus/
diff --git a/drivers/staging/mt7621-nand/Kconfig b/drivers/staging/mt7621-nand/Kconfig
new file mode 100644
index 000000000000..be46db601119
--- /dev/null
+++ b/drivers/staging/mt7621-nand/Kconfig
@@ -0,0 +1,7 @@
+config MTD_NAND_MT7621
+	tristate "Support for Mediatek MT7621 SoC NAND controller"
+	depends on SOC_MT7621
+	help
+	  Enable support for the NAND controller on the Mediatek MT7621
+	  SoC platforms.
+
diff --git a/drivers/staging/mt7621-nand/Makefile b/drivers/staging/mt7621-nand/Makefile
new file mode 100644
index 000000000000..04d6fabecf2b
--- /dev/null
+++ b/drivers/staging/mt7621-nand/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_MTD_NAND_MT7621) += mt7621_nand.o
diff --git a/drivers/staging/mt7621-nand/TODO b/drivers/staging/mt7621-nand/TODO
new file mode 100644
index 000000000000..adca75274ee8
--- /dev/null
+++ b/drivers/staging/mt7621-nand/TODO
@@ -0,0 +1,6 @@
+mt7621-nand TODO
+
+- convert from legacy raw nand interface
+- general code cleanup and review
+
+Cc: gerg at kernel.org
diff --git a/drivers/staging/mt7621-nand/mt7621_nand.c b/drivers/staging/mt7621-nand/mt7621_nand.c
new file mode 100644
index 000000000000..706b7d7588de
--- /dev/null
+++ b/drivers/staging/mt7621-nand/mt7621_nand.c
@@ -0,0 +1,1713 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mt7621_nand.c - Mediatek MT7621 NAND Flash Controller device driver
+ *
+ * Copyright 2009-2012 MediaTek Co.,Ltd.
+ * Copyright 2018-2019 Greg Ungerer <gerg at kernel.org>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/rawnand.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/nand_ecc.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include "mt7621_nand.h"
+
+static const char * const probe_types[] = {
+	"cmdlinepart",
+	"ofpart",
+	NULL
+};
+
+struct host {
+	struct nand_chip nand_chip;
+	struct mtd_info *mtd;
+	void __iomem *regs;
+	void __iomem *ecc;
+	u32 addr_cycles;
+	u32 access_timing;
+	u32 column;
+	u32 row;
+	u32 OOBrow;
+	u8 OOB[288];
+	u8 *data_buf;
+	bool cmdstatus;
+	bool legacybbt;
+	u32 legacybbt_block_num;
+	u32 last_failed;
+};
+
+static struct host *host;
+
+/*
+ * This constant was taken from linux/nand/nand.h v 3.14
+ * in later versions it seems it was removed in order to save a bit of space
+ */
+#define NAND_MAX_OOBSIZE 774
+static u8 local_oob_buf[NAND_MAX_OOBSIZE];
+
+struct nand_ecclayout {
+	u32 eccbytes;
+	u32 oobavail;
+	u32 eccpos[MTD_MAX_ECCPOS_ENTRIES_LARGE];
+	struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES_LARGE];
+};
+
+static struct nand_ecclayout *layout;
+
+static struct nand_ecclayout nand_oob_16 = {
+	.eccbytes = 8,
+	.eccpos = { 8, 9, 10, 11, 12, 13, 14, 15 },
+	.oobfree = { {1, 6}, {0, 0} }
+};
+
+static struct nand_ecclayout nand_oob_64 = {
+	.eccbytes = 32,
+	.eccpos = {
+		32, 33, 34, 35, 36, 37, 38, 39,
+		40, 41, 42, 43, 44, 45, 46, 47,
+		48, 49, 50, 51, 52, 53, 54, 55,
+		56, 57, 58, 59, 60, 61, 62, 63
+	},
+	.oobfree = { {1, 7}, {9, 7}, {17, 7}, {25, 6}, {0, 0} }
+};
+
+static struct nand_ecclayout nand_oob_128 = {
+	.eccbytes = 64,
+	.eccpos = {
+		64, 65, 66, 67, 68, 69, 70, 71,
+		72, 73, 74, 75, 76, 77, 78, 79,
+		80, 81, 82, 83, 84, 85, 86, 86,
+		88, 89, 90, 91, 92, 93, 94, 95,
+		96, 97, 98, 99, 100, 101, 102, 103,
+		104, 105, 106, 107, 108, 109, 110, 111,
+		112, 113, 114, 115, 116, 117, 118, 119,
+		120, 121, 122, 123, 124, 125, 126, 127
+	},
+	.oobfree = {
+		{1, 7}, {9, 7}, {17, 7}, {25, 7},
+		{33, 7}, {41, 7}, {49, 7}, {57, 6}
+	}
+};
+
+/*
+ * Local read/write/set/clear register operations.
+ */
+static inline u8 regread8(u32 addr)
+{
+	return readb(host->regs + addr);
+}
+
+static inline u16 regread16(u32 addr)
+{
+	return readw(host->regs + addr);
+}
+
+static inline void regwrite16(u32 addr, u16 data)
+{
+	writew(data, host->regs + addr);
+}
+
+static inline u32 regread32(u32 addr)
+{
+	return readl(host->regs + addr);
+}
+
+static inline void regwrite32(u32 addr, u32 data)
+{
+	writel(data, host->regs + addr);
+}
+
+static inline void regset32(u32 addr, u32 data)
+{
+	u32 value;
+
+	value = regread32(addr) | data;
+	regwrite32(addr, value);
+}
+
+static inline void regset16(u32 addr, u16 data)
+{
+	u16 value;
+
+	value = regread16(addr) | data;
+	regwrite16(addr, value);
+}
+
+static inline void regclr16(u32 addr, u16 data)
+{
+	u16 value;
+
+	value = regread16(addr) & (~data);
+	regwrite16(addr, value);
+}
+
+/*
+ * Local ECC register read/write operations. These are implemented as a
+ * separate set of methods because the ECC register bank is a physically
+ * separate window within the address space.
+ */
+static inline u16 ecc_read16(u32 addr)
+{
+	return readw(host->ecc + addr);
+}
+
+static inline void ecc_write16(u32 addr, u16 data)
+{
+	writew(data, host->ecc + addr);
+}
+
+static inline u32 ecc_read32(u32 addr)
+{
+	return readl(host->ecc + addr);
+}
+
+static inline void ecc_write32(u32 addr, u32 data)
+{
+	writel(data, host->ecc + addr);
+}
+
+static void ecc_config(u32 ecc_bit)
+{
+	u32 ecc_bit_cfg = ECC_CNFG_ECC4;
+	u32 ENCODESize;
+	u32 DECODESize;
+	u32 val;
+
+	switch (ecc_bit) {
+	case 4:
+		ecc_bit_cfg = ECC_CNFG_ECC4;
+		break;
+	case 8:
+		ecc_bit_cfg = ECC_CNFG_ECC8;
+		break;
+	case 10:
+		ecc_bit_cfg = ECC_CNFG_ECC10;
+		break;
+	case 12:
+		ecc_bit_cfg = ECC_CNFG_ECC12;
+		break;
+	default:
+		break;
+	}
+
+	ecc_write16(ECC_DECCON_REG16, DEC_DE);
+	while (!ecc_read16(ECC_DECIDLE_REG16))
+		;
+
+	ecc_write16(ECC_ENCCON_REG16, ENC_DE);
+	while (!ecc_read32(ECC_ENCIDLE_REG32))
+		;
+
+	/* setup FDM register base */
+	ecc_write32(ECC_FDMADDR_REG32, (u32)NFI_FDM0L_REG32);
+
+	/* Sector + FDM */
+	ENCODESize = (NAND_SECTOR_SIZE + 8) << 3;
+	/* Sector + FDM + YAFFS2 meta data bits */
+	DECODESize = ((NAND_SECTOR_SIZE + 8) << 3) + ecc_bit * 13;
+
+	/* configure ECC decoder && encoder */
+	ecc_write32(ECC_DECCNFG_REG32,
+		    ecc_bit_cfg | DEC_CNFG_NFI | DEC_CNFG_EMPTY_EN |
+		    (DECODESize << DEC_CNFG_CODE_SHIFT));
+
+	ecc_write32(ECC_ENCCNFG_REG32,
+		    ecc_bit_cfg | ENC_CNFG_NFI |
+		    (ENCODESize << ENC_CNFG_MSG_SHIFT));
+
+	val = ecc_read32(ECC_DECCNFG_REG32) | DEC_CNFG_EL;
+	ecc_write32(ECC_DECCNFG_REG32, val);
+}
+
+static void ecc_decode_start(void)
+{
+	while (!(ecc_read16(ECC_DECIDLE_REG16) & DEC_IDLE))
+		;
+	ecc_write16(ECC_DECCON_REG16, DEC_EN);
+}
+
+static void ecc_decode_end(void)
+{
+	while (!(ecc_read16(ECC_DECIDLE_REG16) & DEC_IDLE))
+		;
+	ecc_write16(ECC_DECCON_REG16, DEC_DE);
+}
+
+static void ecc_encode_start(void)
+{
+	while (!(ecc_read32(ECC_ENCIDLE_REG32) & ENC_IDLE))
+		;
+	ecc_write16(ECC_ENCCON_REG16, ENC_EN);
+}
+
+static void ecc_encode_end(void)
+{
+	/* wait for device returning idle */
+	while (!(ecc_read32(ECC_ENCIDLE_REG32) & ENC_IDLE))
+		;
+	ecc_write16(ECC_ENCCON_REG16, ENC_DE);
+}
+
+static int check_bch_error(struct mtd_info *mtd, u8 *buf, u32 sector, u32 page)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	struct host *host = nand_get_controller_data(chip);
+	u16 sectormask = 1 << sector;
+	u32 errdebug, i, errnum;
+	u32 timeout = 0xffff;
+	u32 byte, bitoffset, bit1, bit2;
+	u32 bit[6];
+	int corrected = 0;
+
+	/* Wait for Decode Done */
+	while ((sectormask & ecc_read16(ECC_DECDONE_REG16)) == 0) {
+		timeout--;
+		if (timeout == 0)
+			return -EIO;
+	}
+
+	/*
+	 * We will manually correct the error bits in the last sector,
+	 * not all the sectors of the page!
+	 */
+	memset(bit, 0, sizeof(bit));
+	errdebug = ecc_read32(ECC_DECENUM_REG32);
+	errnum = ecc_read32(ECC_DECENUM_REG32) >> (sector << 2);
+	errnum &= 0xf;
+
+	if (errnum == 0)
+		return 0;
+
+	if (errnum == 0xf) {
+		/*
+		 * Increment the last read's failed counter only. The
+		 * caller supposed to check if it is a blank page with
+		 * bit-flips, or a real ECC error. If the latter, it
+		 * should increment the failed counter with this last
+		 * read's failed counter
+		 */
+		host->last_failed++;
+		corrected = -EBADMSG;
+	} else {
+		corrected = errnum;
+
+		for (i = 0; i < ((errnum + 1) >> 1); ++i) {
+			bit[i] = ecc_read32(ECC_DECEL0_REG32 + i);
+			bit1 = bit[i] & 0x1FFF;
+			/*
+			 * Check if bitflip is in data block (< 0x1000)
+			 * or OOB. Fix it only in data block.
+			 */
+			if (bit1 < 0x1000) {
+				byte = bit1 / 8;
+				bitoffset = bit1 % 8;
+				buf[byte] = buf[byte] ^ (1 << bitoffset);
+			}
+
+			mtd->ecc_stats.corrected++;
+
+			bit2 = (bit[i] >> 16) & 0x1FFF;
+			if (bit2 != 0) {
+				/*
+				 * Check if bitflip is in data block
+				 * (< 0x1000) or OOB. Fix it only in
+				 * data block.
+				 */
+				if (bit2 < 0x1000) {
+					byte = bit2 / 8;
+					bitoffset = bit2 % 8;
+					buf[byte] = buf[byte] ^
+						    (1 << bitoffset);
+				}
+
+				mtd->ecc_stats.corrected++;
+			}
+		}
+	}
+	if ((ecc_read16(ECC_DECFER_REG16) & (1 << sector)) == 0)
+		corrected = -EIO;
+
+	return corrected;
+}
+
+static bool RFIFOValidSize(u16 size)
+{
+	u32 timeout = 0xffff;
+
+	while (FIFO_RD_REMAIN(regread16(NFI_FIFOSTA_REG16)) < size) {
+		timeout--;
+		if (timeout == 0)
+			return false;
+	}
+	return true;
+}
+
+static bool WFIFOValidSize(u16 size)
+{
+	u32 timeout = 0xffff;
+
+	while (FIFO_WR_REMAIN(regread16(NFI_FIFOSTA_REG16)) > size) {
+		timeout--;
+		if (timeout == 0)
+			return false;
+	}
+	return true;
+}
+
+static bool status_ready(u32 status)
+{
+	u32 timeout = 0xffff;
+
+	while ((regread32(NFI_STA_REG32) & status) != 0) {
+		timeout--;
+		if (timeout == 0)
+			return false;
+	}
+	return true;
+}
+
+static bool reset(void)
+{
+	int timeout = 0xffff;
+
+	if (regread16(NFI_MASTERSTA_REG16)) {
+		regwrite16(NFI_CON_REG16, CON_FIFO_FLUSH | CON_NFI_RST);
+		while (regread16(NFI_MASTERSTA_REG16)) {
+			timeout--;
+			if (!timeout)
+				pr_err("mt7621-nand: %s timeout\n", __func__);
+		}
+	}
+
+	/* issue reset operation */
+	regwrite16(NFI_CON_REG16, CON_FIFO_FLUSH | CON_NFI_RST);
+
+	return status_ready(STA_NFI_FSM_MASK | STA_NAND_BUSY) &&
+			    RFIFOValidSize(0) &&
+			    WFIFOValidSize(0);
+}
+
+static void set_mode(u16 mode)
+{
+	u16 v = regread16(NFI_CNFG_REG16);
+
+	v &= ~CNFG_OP_MODE_MASK;
+	v |= mode;
+	regwrite16(NFI_CNFG_REG16, v);
+}
+
+static void set_autoformat(bool enable)
+{
+	if (enable)
+		regset16(NFI_CNFG_REG16, CNFG_AUTO_FMT_EN);
+	else
+		regclr16(NFI_CNFG_REG16, CNFG_AUTO_FMT_EN);
+}
+
+static void configure_fdm(u16 size)
+{
+	regclr16(NFI_PAGEFMT_REG16, PAGEFMT_FDM_MASK | PAGEFMT_FDM_ECC_MASK);
+	regset16(NFI_PAGEFMT_REG16, size << PAGEFMT_FDM_SHIFT);
+	regset16(NFI_PAGEFMT_REG16, size << PAGEFMT_FDM_ECC_SHIFT);
+}
+
+static void configure_lock(void)
+{
+	const u32 write_col = 2;
+	const u32 write_row = 3;
+	const u32 erase_col = 0;
+	const u32 erase_row = 3;
+
+	regwrite16(NFI_LOCKANOB_REG16,
+		   (write_col << PROG_CADD_NOB_SHIFT) |
+		   (write_row << PROG_RADD_NOB_SHIFT) |
+		   (erase_col << ERASE_CADD_NOB_SHIFT) |
+		   (erase_row << ERASE_RADD_NOB_SHIFT));
+}
+
+static bool pio_ready(void)
+{
+	int count = 0;
+
+	while (!(regread16(NFI_PIO_DIRDY_REG16) & 1)) {
+		count++;
+		if (count > 0xffff) {
+			pr_err("mt7621-nand: %s timeout\n", __func__);
+			return false;
+		}
+	}
+	return true;
+}
+
+static bool set_command(u16 command)
+{
+	regwrite16(NFI_CMD_REG16, command);
+	return status_ready(STA_CMD_STATE);
+}
+
+static bool set_address(u32 column, u32 row, u16 colnob, u16 rownob)
+{
+	regwrite32(NFI_COLADDR_REG32, column);
+	regwrite32(NFI_ROWADDR_REG32, row);
+	regwrite16(NFI_ADDRNOB_REG16, colnob | (rownob << ADDR_ROW_NOB_SHIFT));
+	return status_ready(STA_ADDR_STATE);
+}
+
+static void mt7621_cmd_ctrl(struct nand_chip *chip, int dat, unsigned int ctrl)
+{
+	if (ctrl & NAND_ALE) {
+		set_address(dat, 0, 1, 0);
+	} else if (ctrl & NAND_CLE) {
+		reset();
+		set_mode(0x6000);
+		set_command(dat);
+	}
+}
+
+static bool check_RW_count(u16 writesize)
+{
+	u32 timeout = 0xffff;
+	u16 sec = writesize >> 9;
+
+	while (ADDRCNTR_CNTR(regread16(NFI_ADDRCNTR_REG16)) < sec) {
+		timeout--;
+		if (timeout == 0) {
+			pr_warn("mt7621-nand: %s timeout\n", __func__);
+			return false;
+		}
+	}
+	return true;
+}
+
+/*
+ * Reset NFI HW internal state machine and flush NFI in/out FIFO
+ */
+static bool ready_for_read(struct nand_chip *chip, u32 row,
+			   u32 column, bool full, u8 *buf)
+{
+	u16 sec = 1 << (chip->page_shift - 9);
+	u32 colnob = 2, rownob = host->addr_cycles - 2;
+	bool ret = false;
+
+	if (chip->options & NAND_BUSWIDTH_16)
+		column /= 2;
+
+	if (!reset())
+		goto cleanup;
+
+	regset16(NFI_CNFG_REG16, CNFG_HW_ECC_EN);
+	set_mode(CNFG_OP_READ);
+	regset16(NFI_CNFG_REG16, CNFG_READ_EN);
+	regwrite16(NFI_CON_REG16, sec << CON_NFI_SEC_SHIFT);
+
+	if (full) {
+		regclr16(NFI_CNFG_REG16, CNFG_AHB);
+		regset16(NFI_CNFG_REG16, CNFG_HW_ECC_EN);
+	} else {
+		regclr16(NFI_CNFG_REG16, CNFG_HW_ECC_EN);
+		regclr16(NFI_CNFG_REG16, CNFG_AHB);
+	}
+
+	set_autoformat(full);
+	if (full)
+		ecc_decode_start();
+	if (!set_command(NAND_CMD_READ0))
+		goto cleanup;
+	if (!set_address(column, row, colnob, rownob))
+		goto cleanup;
+	if (!set_command(NAND_CMD_READSTART))
+		goto cleanup;
+	if (!status_ready(STA_NAND_BUSY))
+		goto cleanup;
+
+	ret = true;
+
+cleanup:
+	return ret;
+}
+
+static bool ready_for_write(struct nand_chip *chip, u32 row,
+			    u32 column, bool full, u8 *buf)
+{
+	u32 sec = 1 << (chip->page_shift - 9);
+	u32 colnob = 2, rownob = host->addr_cycles - 2;
+	bool ret = false;
+
+	if (chip->options & NAND_BUSWIDTH_16)
+		column /= 2;
+
+	/* Reset NFI HW internal state machine and flush NFI in/out FIFO */
+	if (!reset())
+		return false;
+
+	set_mode(CNFG_OP_PRGM);
+
+	regclr16(NFI_CNFG_REG16, CNFG_READ_EN);
+
+	regwrite16(NFI_CON_REG16, sec << CON_NFI_SEC_SHIFT);
+
+	if (full) {
+		regclr16(NFI_CNFG_REG16, CNFG_AHB);
+		regset16(NFI_CNFG_REG16, CNFG_HW_ECC_EN);
+	} else {
+		regclr16(NFI_CNFG_REG16, CNFG_HW_ECC_EN);
+		regclr16(NFI_CNFG_REG16, CNFG_AHB);
+	}
+
+	set_autoformat(full);
+
+	if (full)
+		ecc_encode_start();
+
+	if (!set_command(NAND_CMD_SEQIN))
+		goto cleanup;
+	/* FIX ME: For Any Kind of AddrCycle */
+	if (!set_address(column, row, colnob, rownob))
+		goto cleanup;
+
+	if (!status_ready(STA_NAND_BUSY))
+		goto cleanup;
+
+	ret = true;
+
+cleanup:
+	return ret;
+}
+
+static bool check_dececc_done(u32 sec)
+{
+	u32 timeout, dec_mask;
+
+	timeout = 0xffff;
+	dec_mask = (1 << sec) - 1;
+	while ((dec_mask != ecc_read16(ECC_DECDONE_REG16)) && timeout > 0)
+		timeout--;
+	if (timeout == 0) {
+		pr_err("mt7621-nand: ECC_DECDONE: timeout\n");
+		return false;
+	}
+	return true;
+}
+
+static bool mcu_read_data(u8 *buf, u32 length)
+{
+	int timeout = 0xffff;
+	u32 *buf32 = (u32 *)buf;
+	u32 i;
+
+	if ((u32)buf % 4 || length % 4)
+		regset16(NFI_CNFG_REG16, CNFG_BYTE_RW);
+	else
+		regclr16(NFI_CNFG_REG16, CNFG_BYTE_RW);
+
+	/* regwrite32(NFI_STRADDR_REG32, 0); */
+	regset16(NFI_CON_REG16, CON_NFI_BRD);
+
+	if ((u32)buf % 4 || length % 4) {
+		for (i = 0; (i < (length)) && (timeout > 0);) {
+			if (regread16(NFI_PIO_DIRDY_REG16) & 1) {
+				*buf++ = (u8)regread32(NFI_DATAR_REG32);
+				i++;
+			} else {
+				timeout--;
+			}
+			if (timeout == 0) {
+				pr_warn("mt7621-nand: %s timeout\n", __func__);
+				return false;
+			}
+		}
+	} else {
+		for (i = 0; (i < (length >> 2)) && (timeout > 0);) {
+			if (regread16(NFI_PIO_DIRDY_REG16) & 1) {
+				*buf32++ = regread32(NFI_DATAR_REG32);
+				i++;
+			} else {
+				timeout--;
+			}
+			if (timeout == 0) {
+				pr_warn("mt7621-nand: %s timeout\n", __func__);
+				return false;
+			}
+		}
+	}
+
+	return true;
+}
+
+static bool mcu_write_data(struct mtd_info *mtd, const u8 *buf, u32 length)
+{
+	u32 timeout = 0xffff;
+	u32 *buf32;
+	u32 i;
+
+	regclr16(NFI_CNFG_REG16, CNFG_BYTE_RW);
+	regset16(NFI_CON_REG16, CON_NFI_BWR);
+	buf32 = (u32 *)buf;
+
+	if ((u32)buf % 4 || length % 4)
+		regset16(NFI_CNFG_REG16, CNFG_BYTE_RW);
+	else
+		regclr16(NFI_CNFG_REG16, CNFG_BYTE_RW);
+
+	if ((u32)buf % 4 || length % 4) {
+		for (i = 0; (i < (length)) && (timeout > 0);) {
+			if (regread16(NFI_PIO_DIRDY_REG16) & 1) {
+				regwrite32(NFI_DATAW_REG32, *buf++);
+				i++;
+			} else {
+				timeout--;
+			}
+			if (timeout == 0) {
+				pr_warn("mt7621-nand: %s timeout\n", __func__);
+				return false;
+			}
+		}
+	} else {
+		for (i = 0; (i < (length >> 2)) && (timeout > 0);) {
+			if (regread16(NFI_PIO_DIRDY_REG16) & 1) {
+				regwrite32(NFI_DATAW_REG32, *buf32++);
+				i++;
+			} else {
+				timeout--;
+			}
+			if (timeout == 0) {
+				pr_warn("mt7621-nand: %s timeout\n", __func__);
+				return false;
+			}
+		}
+	}
+
+	return true;
+}
+
+static void read_fdm_data(u8 *buf, u32 sec)
+{
+	u32 *buf32 = (u32 *)buf;
+	u32 i;
+
+	if (buf32) {
+		for (i = 0; i < sec; ++i) {
+			*buf32++ = regread32(NFI_FDM0L_REG32 + (i << 3));
+			*buf32++ = regread32(NFI_FDM0M_REG32 + (i << 3));
+		}
+	}
+}
+
+static u8 fdm_buf[64];
+
+static void write_fdm_data(struct nand_chip *chip, u8 *buf, u32 sec)
+{
+	struct nand_oobfree *free_entry;
+	bool empty = true;
+	u8 checksum = 0;
+	u32 *buf32;
+	u32 i, j;
+
+	memcpy(fdm_buf, buf, sec * 8);
+
+	free_entry = layout->oobfree;
+	for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && free_entry[i].length; i++) {
+		for (j = 0; j < free_entry[i].length; j++) {
+			if (buf[free_entry[i].offset + j] != 0xff)
+				empty = false;
+			checksum ^= buf[free_entry[i].offset + j];
+		}
+	}
+
+	if (!empty)
+		fdm_buf[free_entry[i - 1].offset + free_entry[i - 1].length] =
+			checksum;
+
+	buf32 = (u32 *)fdm_buf;
+	for (i = 0; i < sec; ++i) {
+		regwrite32(NFI_FDM0L_REG32 + (i << 3), *buf32++);
+		regwrite32(NFI_FDM0M_REG32 + (i << 3), *buf32++);
+	}
+}
+
+static void stop_read(void)
+{
+	regclr16(NFI_CON_REG16, CON_NFI_BRD);
+	reset();
+	ecc_decode_end();
+	regwrite16(NFI_INTR_EN_REG16, 0);
+}
+
+static void stop_write(void)
+{
+	regclr16(NFI_CON_REG16, CON_NFI_BWR);
+	ecc_encode_end();
+	regwrite16(NFI_INTR_EN_REG16, 0);
+}
+
+/*
+ * This is a copy of the nand_check_erased_buf() function from nand_base.c, to
+ * keep the nand_base.c clean
+ */
+static int check_erased_buf(void *buf, int len, int bitflips_threshold)
+{
+	const unsigned char *bitmap = buf;
+	int bitflips = 0;
+	int weight;
+
+	for (; len && ((uintptr_t)bitmap) % sizeof(long);
+	     len--, bitmap++) {
+		weight = hweight8(*bitmap);
+		bitflips += BITS_PER_BYTE - weight;
+		if (unlikely(bitflips > bitflips_threshold))
+			return -EBADMSG;
+	}
+
+	for (; len >= sizeof(long);
+	     len -= sizeof(long), bitmap += sizeof(long)) {
+		unsigned long d = *((unsigned long *)bitmap);
+
+		if (d == ~0UL)
+			continue;
+		weight = hweight_long(d);
+		bitflips += BITS_PER_LONG - weight;
+		if (unlikely(bitflips > bitflips_threshold))
+			return -EBADMSG;
+	}
+
+	for (; len > 0; len--, bitmap++) {
+		weight = hweight8(*bitmap);
+		bitflips += BITS_PER_BYTE - weight;
+		if (unlikely(bitflips > bitflips_threshold))
+			return -EBADMSG;
+	}
+
+	return bitflips;
+}
+
+/*
+ * This is the modified version of the nand_check_erased_ecc_chunk() in
+ * nand_base.c. This driver cannot use the generic function, as the ECC layout
+ * is slightly different (the 2k(data)+64(OOB) page is split up in to 4
+ * (512-byte data + 16-byte OOB) pages. Each of these sectors have 4-bit ECC,
+ * so we check them separately, and allow 4 bitflips / sector.
+ * We'll fix up the page to all-0xff only if all sectors in the page appears to
+ * be blank
+ */
+static int check_erased_ecc_chunk(void *data, int datalen, void *oob,
+				  int ooblen, int bitflips_threshold)
+{
+	int data_bitflips = 0, oob_bitflips = 0;
+
+	data_bitflips = check_erased_buf(data, datalen, bitflips_threshold);
+	if (data_bitflips < 0)
+		return data_bitflips;
+
+	bitflips_threshold -= data_bitflips;
+
+	oob_bitflips = check_erased_buf(oob, ooblen, bitflips_threshold);
+	if (oob_bitflips < 0)
+		return oob_bitflips;
+
+	bitflips_threshold -= oob_bitflips;
+	return data_bitflips + oob_bitflips;
+}
+
+static int read_oob_raw(struct mtd_info *mtd, u8 *buf, int page, int len)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	int sec_num = 1 << (chip->page_shift - 9);
+	int spare_per_sector = mtd->oobsize / sec_num;
+	u32 column = 0;
+	u32 sector = 0;
+	int res = 0;
+	int read_len = 0;
+
+	if (len > NAND_MAX_OOBSIZE || len % OOB_AVAI_PER_SECTOR || !buf) {
+		pr_warn("mt7621-nand: invalid parameter, len: %d, buf: %p\n",
+			len, buf);
+		return -EINVAL;
+	}
+
+	while (len > 0) {
+		read_len = min(len, spare_per_sector);
+		column = NAND_SECTOR_SIZE +
+			 sector * (NAND_SECTOR_SIZE + spare_per_sector);
+		if (!ready_for_read(chip, page, column, false, NULL)) {
+			pr_warn("mt7621-nand: ready_for_read() failed\n");
+			res = -EIO;
+			goto error;
+		}
+		if (!mcu_read_data(buf + spare_per_sector * sector, read_len)) {
+			pr_warn("mt7621-nand: mcu_read_data() failed\n");
+			res = -EIO;
+			goto error;
+		}
+		check_RW_count(read_len);
+		stop_read();
+		sector++;
+		len -= read_len;
+	}
+
+error:
+	regclr16(NFI_CON_REG16, CON_NFI_BRD);
+	return res;
+}
+
+static int write_oob_raw(struct mtd_info *mtd, const u8 *buf, int page, int len)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	int sec_num = 1 << (chip->page_shift - 9);
+	int spare_per_sector = mtd->oobsize / sec_num;
+	u32 column = 0;
+	u32 sector = 0;
+	int write_len = 0;
+	int status;
+
+	if (len > NAND_MAX_OOBSIZE || len % OOB_AVAI_PER_SECTOR || !buf) {
+		pr_warn("mt7621-nand: invalid parameter, len: %d, buf: %p\n",
+			len, buf);
+		return -EINVAL;
+	}
+
+	while (len > 0) {
+		write_len = min(len, spare_per_sector);
+		column = sector * (NAND_SECTOR_SIZE + spare_per_sector) +
+			 NAND_SECTOR_SIZE;
+		if (!ready_for_write(chip, page, column, false, NULL))
+			return -EIO;
+		if (!mcu_write_data(mtd, buf + sector * spare_per_sector,
+				    write_len))
+			return -EIO;
+		check_RW_count(write_len);
+		regclr16(NFI_CON_REG16, CON_NFI_BWR);
+		set_command(NAND_CMD_PAGEPROG);
+		while (regread32(NFI_STA_REG32) & STA_NAND_BUSY)
+			;
+		status = chip->legacy.waitfunc(chip);
+		if (status & NAND_STATUS_FAIL) {
+			pr_warn("mt7621-nand: failed status: %d\n", status);
+			return -EIO;
+		}
+		len -= write_len;
+		sector++;
+	}
+
+	return 0;
+}
+
+static int exec_read_page(struct mtd_info *mtd, u32 row, u32 page_size,
+			  u8 *buf, u8 *fdmbuf)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	struct host *host = nand_get_controller_data(chip);
+	u32 sec = page_size >> 9;
+	int corrected = 0;
+
+	host->last_failed = 0;
+
+	if (ready_for_read(chip, row, 0, true, buf)) {
+		int j;
+
+		for (j = 0; j < sec; j++) {
+			int ret;
+
+			if (!mcu_read_data(buf + j * 512, 512)) {
+				corrected = -EIO;
+				break;
+			}
+			if (!check_dececc_done(j + 1)) {
+				corrected = -EIO;
+				break;
+			}
+			ret = check_bch_error(mtd, buf + j * 512, j, row);
+			if (ret < 0) {
+				corrected = ret;
+				if (ret != -EBADMSG)
+					break;
+			} else if (corrected >= 0) {
+				corrected = max_t(int, corrected, ret);
+			}
+		}
+		if (!status_ready(STA_NAND_BUSY))
+			corrected = -EIO;
+
+		read_fdm_data(fdmbuf, sec);
+		stop_read();
+	} else {
+		corrected = -EIO;
+	}
+
+	if (corrected == -EBADMSG) {
+		u32 local_oob_aligned[NAND_MAX_OOBSIZE / sizeof(u32)];
+		u8 *local_oob = (u8 *)local_oob_aligned;
+		int ret;
+
+		/*
+		 * If there was an uncorrectable ECC error, check if it is a
+		 * blank page with bit-flips. For this, we check the number of
+		 * 0s in each sector (including the OOB), which should not
+		 * exceed the ECC strength. Until the number of 0s is lower or
+		 * equal, we consider it as a blank page
+		 */
+		ret = read_oob_raw(mtd, local_oob, row, mtd->oobsize);
+		if (ret == 0) {
+			int spare_per_sector = mtd->oobsize / sec;
+			int sector_size = page_size / sec;
+			int max_bitflips = 0;
+			u32 corrected = 0;
+			int i;
+
+			for (i = 0; i < sec; i++) {
+				ret = check_erased_ecc_chunk(
+					&buf[i * sector_size], sector_size,
+					&local_oob[i * spare_per_sector],
+					spare_per_sector, chip->ecc.strength);
+				if (ret < 0)
+					break;
+
+				max_bitflips = max_t(int, max_bitflips, ret);
+				corrected += ret;
+			}
+
+			if (ret >= 0) {
+				mtd->ecc_stats.corrected += corrected;
+
+				memset(buf, 0xff, page_size);
+				memset(fdmbuf, 0xff, sizeof(u32) * 2 * sec);
+
+				corrected = max_bitflips;
+			} else {
+				mtd->ecc_stats.failed += host->last_failed;
+				pr_err("mt7621-nand: uncorrectable ECC errors at page=%d\n",
+				       row);
+			}
+		} else {
+			mtd->ecc_stats.failed += host->last_failed;
+			pr_warn("mt7621-nand: %s failed to read oob after ECC error\n",
+				__func__);
+			/* Keep return value as -EBADMSG */
+		}
+	}
+
+	return corrected;
+}
+
+static int exec_write_page(struct mtd_info *mtd, u32 row,
+			   u32 page_size, u8 *buf, u8 *fdmbuf)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	u32 sec = page_size >> 9;
+	u8 status;
+
+	if (ready_for_write(chip, row, 0, true, buf)) {
+		write_fdm_data(chip, fdmbuf, sec);
+		mcu_write_data(mtd, buf, page_size);
+		check_RW_count(page_size);
+		stop_write();
+		set_command(NAND_CMD_PAGEPROG);
+		while (regread32(NFI_STA_REG32) & STA_NAND_BUSY)
+			;
+	}
+
+	status = chip->legacy.waitfunc(chip);
+	if (status & NAND_STATUS_FAIL)
+		return -EIO;
+	return 0;
+}
+
+static void command_bp(struct nand_chip *chip, unsigned int command,
+		       int column, int page)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	switch (command) {
+	case NAND_CMD_SEQIN:
+		memset(host->OOB, 0xff, sizeof(host->OOB));
+		host->data_buf = NULL;
+		host->row = page;
+		host->column = column;
+		break;
+
+	case NAND_CMD_PAGEPROG:
+		if (host->data_buf || (host->OOB[0] != 0xff)) {
+			u8 *buf = host->data_buf ? host->data_buf
+						 : chip->data_buf;
+
+			exec_write_page(mtd, host->row, mtd->writesize,
+					buf, host->OOB);
+			host->row = (u32)-1;
+			host->OOBrow = (u32)-1;
+		}
+		break;
+
+	case NAND_CMD_READOOB:
+		host->row = page;
+		host->column = column + mtd->writesize;
+		break;
+
+	case NAND_CMD_READ0:
+		host->row = page;
+		host->column = column;
+		break;
+
+	case NAND_CMD_ERASE1:
+		reset();
+		set_mode(CNFG_OP_ERASE);
+		set_command(NAND_CMD_ERASE1);
+		set_address(0, page, 0, host->addr_cycles - 2);
+		break;
+
+	case NAND_CMD_ERASE2:
+		set_command(NAND_CMD_ERASE2);
+		while (regread32(NFI_STA_REG32) & STA_NAND_BUSY)
+			;
+		break;
+
+	case NAND_CMD_STATUS:
+		reset();
+		regclr16(NFI_CNFG_REG16, CNFG_BYTE_RW);
+		set_mode(CNFG_OP_SRD);
+		set_mode(CNFG_READ_EN);
+		regclr16(NFI_CNFG_REG16, CNFG_AHB);
+		regclr16(NFI_CNFG_REG16, CNFG_HW_ECC_EN);
+		set_command(NAND_CMD_STATUS);
+		regclr16(NFI_CON_REG16, CON_NFI_NOB_MASK);
+		regwrite16(NFI_CON_REG16,
+			   CON_NFI_SRD | (1 << CON_NFI_NOB_SHIFT));
+		host->cmdstatus = true;
+		break;
+
+	case NAND_CMD_RESET:
+		reset();
+		regwrite16(NFI_INTR_EN_REG16, INTR_RST_DONE_EN);
+		set_command(NAND_CMD_RESET);
+		regwrite16(NFI_CNRNB_REG16, 0xf1);
+		while (!(regread16(NFI_INTR_REG16) & INTR_RST_DONE_EN))
+			;
+		break;
+
+	case NAND_CMD_READID:
+		reset();
+		/* Disable HW ECC */
+		regclr16(NFI_CNFG_REG16, CNFG_HW_ECC_EN);
+		regclr16(NFI_CNFG_REG16, CNFG_AHB);
+		regset16(NFI_CNFG_REG16, CNFG_READ_EN | CNFG_BYTE_RW);
+		reset();
+		set_mode(CNFG_OP_SRD);
+		set_command(NAND_CMD_READID);
+		set_address(0, 0, 1, 0);
+		regwrite16(NFI_CON_REG16, CON_NFI_SRD);
+		while (regread32(NFI_STA_REG32) & STA_DATAR_STATE)
+			;
+		break;
+
+	default:
+		pr_err("mt7621-nand: unknown NAND command, 0x%x\n", command);
+		break;
+	}
+}
+
+static void set_ecc_mode(struct mtd_info *mtd)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	u32 spare_per_sector = mtd->oobsize / (mtd->writesize / 512);
+	u32 spare_bit = PAGEFMT_SPARE_16;
+	u32 ecc_bit = 4;
+
+	if (spare_per_sector >= 28) {
+		spare_bit = PAGEFMT_SPARE_28;
+		ecc_bit = 12;
+		spare_per_sector = 28;
+	} else if (spare_per_sector >= 27) {
+		spare_bit = PAGEFMT_SPARE_27;
+		ecc_bit = 8;
+		spare_per_sector = 27;
+	} else if (spare_per_sector >= 26) {
+		spare_bit = PAGEFMT_SPARE_26;
+		ecc_bit = 8;
+		spare_per_sector = 26;
+	} else if (spare_per_sector >= 16) {
+		spare_bit = PAGEFMT_SPARE_16;
+		ecc_bit = 4;
+		spare_per_sector = 16;
+	} else {
+		pr_warn("mt7621-nand: NFI not support oobsize: %x\n",
+			spare_per_sector);
+	}
+	chip->ecc.strength = ecc_bit;
+	mtd->oobsize = spare_per_sector * (mtd->writesize / 512);
+	pr_info("mt7621-nand: ecc bit: %d, spare_per_sector: %d\n",
+		ecc_bit, spare_per_sector);
+
+	/* Setup PageFormat */
+	if (mtd->writesize == 4096) {
+		regset16(NFI_PAGEFMT_REG16,
+			 (spare_bit << PAGEFMT_SPARE_SHIFT) | PAGEFMT_4K);
+		chip->legacy.cmdfunc = command_bp;
+	} else if (mtd->writesize == 2048) {
+		regset16(NFI_PAGEFMT_REG16,
+			 (spare_bit << PAGEFMT_SPARE_SHIFT) | PAGEFMT_2K);
+		chip->legacy.cmdfunc = command_bp;
+	}
+	ecc_config(ecc_bit);
+}
+
+static void select_chip(struct nand_chip *chip, int nr)
+{
+	switch (nr) {
+	case -1:
+		break;
+	case 0:
+	case 1:
+		/* Jun Shen, 2011.04.13 */
+		/* Note: MT6577 EVB NAND is mounted on CS0, but FPGA is CS1 */
+		regwrite16(NFI_CSEL_REG16, nr);
+		/* Jun Shen, 2011.04.13 */
+		break;
+	}
+}
+
+static u8 read_byte(struct nand_chip *chip)
+{
+	u8 retval = 0;
+
+	if (!pio_ready()) {
+		pr_err("mt7621-nand: pio ready timeout\n");
+		retval = false;
+	}
+
+	if (host->cmdstatus) {
+		retval = regread8(NFI_DATAR_REG32);
+		regclr16(NFI_CON_REG16, CON_NFI_NOB_MASK);
+		reset();
+		regset16(NFI_CNFG_REG16, CNFG_HW_ECC_EN);
+		host->cmdstatus = false;
+	} else {
+		retval = regread8(NFI_DATAR_REG32);
+	}
+
+	return retval;
+}
+
+static void read_buf(struct nand_chip *chip, u8 *buf, int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	u32 page_size = mtd->writesize;
+	u32 column = host->column;
+
+	if (column < page_size) {
+		if ((column == 0) && (len >= page_size)) {
+			exec_read_page(mtd, host->row, page_size, buf,
+				       host->OOB);
+			if (len > page_size) {
+				u32 size;
+
+				size = min(len - page_size, sizeof(host->OOB));
+				memcpy(buf + page_size, host->OOB, size);
+			}
+		} else {
+			exec_read_page(mtd, host->row, page_size,
+				       chip->data_buf, host->OOB);
+			memcpy(buf, chip->data_buf + column, len);
+		}
+		host->OOBrow = host->row;
+	} else {
+		u32 offset = column - page_size;
+		u32 size = min(len - offset, sizeof(host->OOB));
+
+		if (host->OOBrow != host->row) {
+			exec_read_page(mtd, host->row, page_size,
+				       chip->data_buf, host->OOB);
+			host->OOBrow = host->row;
+		}
+		memcpy(buf, host->OOB + offset, size);
+	}
+	host->column += len;
+}
+
+static void write_buf(struct nand_chip *chip, const u8 *buf, int len)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	u32 page_size = mtd->writesize;
+	u32 column = host->column;
+	int size, i;
+
+	if (column >= page_size) {
+		u32 offset = column - page_size;
+		u8 *OOB = host->OOB + offset;
+
+		size = min(len, (int)(sizeof(host->OOB) - offset));
+		for (i = 0; i < size; i++)
+			OOB[i] &= buf[i];
+	} else {
+		host->data_buf = (u8 *)buf;
+	}
+
+	host->column += len;
+}
+
+static int write_page_hwecc(struct nand_chip *chip, const u8 *buf,
+			    int oob_required, int page)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize);
+	write_buf(chip, chip->oob_poi, mtd->oobsize);
+	return nand_prog_page_end_op(chip);
+}
+
+static int read_page_hwecc(struct nand_chip *chip, u8 *buf,
+			   int oob_required, int page)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	int corrected;
+
+	corrected = exec_read_page(mtd, page, mtd->writesize,
+				   buf, chip->oob_poi);
+
+	return (corrected == -EBADMSG) ? 0 : corrected;
+}
+
+static int write_oob_hw(struct nand_chip *chip, int page)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	int sec_num = 1 << (chip->page_shift - 9);
+	int spare_per_sector = mtd->oobsize / sec_num;
+	int i, iter;
+
+	memcpy(local_oob_buf, chip->oob_poi, mtd->oobsize);
+
+	/* copy ecc data */
+	for (i = 0; i < layout->eccbytes; i++) {
+		iter = (i / (spare_per_sector - OOB_AVAI_PER_SECTOR)) *
+		       spare_per_sector +
+		       OOB_AVAI_PER_SECTOR +
+		       i % (spare_per_sector - OOB_AVAI_PER_SECTOR);
+		local_oob_buf[iter] = chip->oob_poi[layout->eccpos[i]];
+	}
+
+	/* copy FDM data */
+	for (i = 0; i < sec_num; i++)
+		memcpy(&local_oob_buf[i * spare_per_sector],
+		       &chip->oob_poi[i * OOB_AVAI_PER_SECTOR],
+		       OOB_AVAI_PER_SECTOR);
+
+	return write_oob_raw(mtd, local_oob_buf, page, mtd->oobsize);
+}
+
+static int write_oob(struct nand_chip *chip, int page)
+{
+	int page_per_block = 1 << (chip->phys_erase_shift - chip->page_shift);
+	int block = page / page_per_block;
+	int page_in_block = page % page_per_block;
+
+	if (write_oob_hw(chip, page_in_block + block * page_per_block)) {
+		pr_warn("mt7621-nand: write oob fail at block: 0x%x, page: 0x%x\n",
+			block, page_in_block);
+		return NAND_STATUS_FAIL;
+	}
+
+	return 0;
+}
+
+static int read_oob_hw(struct nand_chip *chip, int page)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	int sec_num = 1 << (chip->page_shift - 9);
+	int spare_per_sector = mtd->oobsize / sec_num;
+	int i;
+	u8 iter = 0;
+
+	if (read_oob_raw(mtd, chip->oob_poi, page, mtd->oobsize)) {
+		pr_err("mt7621-nand: read_oob_raw() return failed\n");
+		return -EIO;
+	}
+
+	/* adjust to ecc physical layout to memory layout */
+	/*********************************************************/
+	/* FDM0 | ECC0 | FDM1 | ECC1 | FDM2 | ECC2 | FDM3 | ECC3 */
+	/*  8B  |  8B  |  8B  |  8B  |  8B  |  8B  |  8B  |  8B  */
+	/*********************************************************/
+
+	memcpy(local_oob_buf, chip->oob_poi, mtd->oobsize);
+
+	/* copy ecc data */
+	for (i = 0; i < layout->eccbytes; i++) {
+		iter = (i / (spare_per_sector - OOB_AVAI_PER_SECTOR)) *
+		       spare_per_sector +
+		       OOB_AVAI_PER_SECTOR +
+		       i % (spare_per_sector - OOB_AVAI_PER_SECTOR);
+		chip->oob_poi[layout->eccpos[i]] = local_oob_buf[iter];
+	}
+
+	/* copy FDM data */
+	for (i = 0; i < sec_num; i++)
+		memcpy(&chip->oob_poi[i * OOB_AVAI_PER_SECTOR],
+		       &local_oob_buf[i * spare_per_sector],
+		       OOB_AVAI_PER_SECTOR);
+
+	return 0;
+}
+
+static int read_oob(struct nand_chip *chip, int page)
+{
+	int page_per_block = 1 << (chip->phys_erase_shift - chip->page_shift);
+	int block = page / page_per_block;
+	int page_in_block = page % page_per_block;
+
+	if (read_oob_hw(chip, page_in_block + block * page_per_block) != 0)
+		return -1;
+	return 0;
+}
+
+static int block_bad_hw(struct nand_chip *chip, loff_t ofs)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	int page = (int)(ofs >> chip->page_shift);
+	unsigned int page_per_block;
+	u8 oob_buf[8];
+
+	page_per_block = 1 << (chip->phys_erase_shift - chip->page_shift);
+	page &= ~(page_per_block - 1);
+	if (read_oob_raw(mtd, oob_buf, page, sizeof(oob_buf))) {
+		pr_warn("mt7621-nand: read_oob_raw() failed\n");
+		return 1;
+	}
+
+	if (oob_buf[0] != 0xff) {
+		pr_warn("mt7621-nand: bad block detected at page=%d\n", page);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int block_bad(struct nand_chip *chip, loff_t ofs)
+{
+	int block = (int)ofs >> chip->phys_erase_shift;
+
+	return block_bad_hw(chip, block << chip->phys_erase_shift);
+}
+
+static void init_hw(struct host *host)
+{
+	host->OOBrow = (u32)-1;
+
+	/* Set default NFI access timing control */
+	regwrite32(NFI_ACCCON_REG32, host->access_timing);
+	regwrite16(NFI_CNFG_REG16, 0);
+	regwrite16(NFI_PAGEFMT_REG16, 0);
+
+	/* Reset the state machine and data FIFO, because flushing FIFO */
+	reset();
+
+	/* Set the ECC engine */
+	if (host->nand_chip.ecc.mode == NAND_ECC_HW) {
+		regset16(NFI_CNFG_REG16, CNFG_HW_ECC_EN);
+		ecc_config(4);
+		configure_fdm(8);
+		configure_lock();
+	}
+
+	regset16(NFI_IOCON_REG16, 0x47);
+}
+
+static int dev_ready(struct nand_chip *chip)
+{
+	return !(regread32(NFI_STA_REG32) & STA_NAND_BUSY);
+}
+
+static int oob_mt7621_ooblayout_ecc(struct mtd_info *mtd, int section,
+				    struct mtd_oob_region *oobregion)
+{
+	oobregion->length = 8;
+	oobregion->offset = layout->eccpos[section * 8];
+	return 0;
+}
+
+static int oob_mt7621_ooblayout_free(struct mtd_info *mtd, int section,
+				     struct mtd_oob_region *oobregion)
+{
+	if (section >= (layout->eccbytes / 8))
+		return -ERANGE;
+	oobregion->offset = layout->oobfree[section].offset;
+	oobregion->length = layout->oobfree[section].length;
+	return 0;
+}
+
+/*
+ * Code to support the legacy mediatek nand flash bad block table.
+ * The default for this driver is to use the standard Linux bad block
+ * table format. However you need a new boot loader that supports that.
+ * The old (and most often used) medaitek boot loaders use their own
+ * BBT format, and this code implements that. There is a devicetree
+ * binding that enables use of this.
+ */
+#define BBT_BLOCK_NUM_DEFAULT	32
+#define BBT_OOB_SIGNATURE	1
+#define BBT_SIGNATURE_LEN	7
+
+static const u8 oob_signature[] = "mtknand";
+static u32 bbt_size;
+
+static int read_legacy_bbt_page(struct mtd_info *mtd, unsigned int page)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+
+	if (read_oob_hw(chip, page) == 0) {
+		int corrected;
+
+		if (chip->oob_poi[0] != 0xff) {
+			pr_info("mt7621-nand: Bad Block on page=%d\n", page);
+			return -ENODEV;
+		}
+		if (memcmp(&chip->oob_poi[BBT_OOB_SIGNATURE], oob_signature,
+			   BBT_SIGNATURE_LEN) != 0) {
+			pr_info("mt7621-nand: no BBT signature, page=%d\n",
+				page);
+			return -EINVAL;
+		}
+		corrected = exec_read_page(mtd, page, mtd->writesize,
+					   chip->data_buf, chip->oob_poi);
+		if (corrected >= 0) {
+			int bbt_bytes = (bbt_size <= mtd->writesize)
+					? bbt_size
+					: mtd->writesize;
+
+			pr_info("mt7621-nand: BBT signature match, page=%d\n",
+				page);
+			memcpy(chip->bbt, chip->data_buf, bbt_bytes);
+			return 0;
+		}
+	}
+
+	pr_err("mt7621-nand: legacy BBT read failed at page %d\n", page);
+	return -EIO;
+}
+
+static int load_legacy_bbt(struct mtd_info *mtd)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	struct host *host = nand_get_controller_data(chip);
+	u32 blocks;
+	int i;
+
+	blocks = 0x1 << (chip->chip_shift - chip->phys_erase_shift);
+	bbt_size = blocks >> 2;
+
+	if (!chip->bbt) {
+		chip->bbt = kzalloc(bbt_size, GFP_KERNEL);
+		if (!chip->bbt)
+			return -ENOMEM;
+	}
+
+	for (i = blocks - 1; i >= (blocks - host->legacybbt_block_num); i--) {
+		int page = i << (chip->phys_erase_shift - chip->page_shift);
+
+		if (read_legacy_bbt_page(mtd, page) == 0) {
+			pr_info("mt7621-nand: loading BBT success (%d)\n",
+				page);
+			return 0;
+		}
+	}
+
+	pr_err("mt7621-nand: loading Bad Block Table failed\n");
+	return -ENODEV;
+}
+
+static int mt7621_nand_attach(struct nand_chip *chip)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	int i;
+
+	set_ecc_mode(mtd);
+
+	if (nanddev_target_size(&chip->base) < (256 * 1024 * 1024))
+		host->addr_cycles = 4;
+
+	/* allocate buffers or call select_chip here or a bit earlier*/
+	chip->data_buf = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL);
+	chip->ecc.calc_buf = kzalloc(mtd->oobsize, GFP_KERNEL);
+	chip->ecc.code_buf = kzalloc(mtd->oobsize, GFP_KERNEL);
+	if (!chip->data_buf || !chip->ecc.calc_buf || !chip->ecc.code_buf)
+		return -ENOMEM;
+
+	chip->oob_poi = chip->data_buf + mtd->writesize;
+	chip->badblockpos = 0;
+	chip->ecc.size = mtd->writesize;
+	chip->ecc.bytes = (mtd->writesize / NAND_SECTOR_SIZE) * 8;
+
+	if (mtd->writesize == 4096)
+		layout = &nand_oob_128;
+	else if (mtd->writesize == 2048)
+		layout = &nand_oob_64;
+	else if (mtd->writesize == 512)
+		layout = &nand_oob_16;
+
+	layout->eccbytes = mtd->oobsize - OOB_AVAI_PER_SECTOR *
+			   (mtd->writesize / NAND_SECTOR_SIZE);
+	for (i = 0; i < layout->eccbytes; i++)
+		layout->eccpos[i] = OOB_AVAI_PER_SECTOR *
+				    (mtd->writesize / NAND_SECTOR_SIZE) + i;
+
+	regwrite32(NFI_ACCCON_REG32, host->access_timing);
+
+	if (chip->options & NAND_BUSWIDTH_16)
+		regset16(NFI_PAGEFMT_REG16, PAGEFMT_DBYTE_EN);
+
+	chip->legacy.select_chip(chip, 0);
+
+	return 0;
+}
+
+static const struct mtd_ooblayout_ops oob_mt7621_ops = {
+	.ecc = oob_mt7621_ooblayout_ecc,
+	.free = oob_mt7621_ooblayout_free,
+};
+
+static const struct nand_controller_ops mt7621_controller_ops = {
+	.attach_chip = mt7621_nand_attach,
+};
+
+static int mt7621_nand_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mtd_part_parser_data ppdata;
+	struct nand_chip *chip;
+	struct mtd_info *mtd;
+	struct resource *r;
+	int err = 0;
+
+	/* Allocate memory for the device structure (and zero it) */
+	host = devm_kzalloc(dev, sizeof(struct host), GFP_KERNEL);
+	if (!host)
+		return -ENOMEM;
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	host->regs = devm_ioremap_resource(dev, r);
+	pr_info("mt7621-nand: NAND register bank at 0x%x\n", (int)host->regs);
+	if (IS_ERR(host->regs))
+		return PTR_ERR(host->regs);
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	host->ecc = devm_ioremap_resource(dev, r);
+	pr_info("mt7621-nand: ECC register bank at 0x%x\n", (int)host->ecc);
+	if (IS_ERR(host->ecc))
+		return PTR_ERR(host->ecc);
+
+	host->access_timing = NFI_DEFAULT_ACCESS_TIMING;
+	host->addr_cycles = 5;
+
+	if (of_property_read_bool(dev->of_node, "mediatek,nand-bbt-compat")) {
+		pr_info("mt7621-nand: using legacy BBT format\n");
+		host->legacybbt = true;
+	}
+	if (host->legacybbt) {
+		err = of_property_read_u32(dev->of_node,
+					   "mediatek,nand-bbt-block-num",
+					   &host->legacybbt_block_num);
+		if (err)
+			host->legacybbt_block_num = BBT_BLOCK_NUM_DEFAULT;
+	}
+
+	/* init mtd data structure */
+	chip = &host->nand_chip;
+	chip->priv = host;
+
+	mtd = nand_to_mtd(chip);
+	mtd->priv = chip;
+	mtd->owner = THIS_MODULE;
+	mtd->name = "MT7621-NAND";
+	mtd->dev.parent = &pdev->dev;
+	host->mtd = mtd;
+
+	nand_set_flash_node(chip, pdev->dev.of_node);
+
+	chip->legacy.select_chip = select_chip;
+	chip->legacy.chip_delay = 20; /* 20us command delay time */
+	chip->legacy.read_byte = read_byte;
+	chip->legacy.read_buf = read_buf;
+	chip->legacy.write_buf = write_buf;
+	chip->legacy.dev_ready = dev_ready;
+	chip->legacy.cmdfunc = command_bp;
+	chip->legacy.block_bad = block_bad;
+	chip->legacy.cmd_ctrl = mt7621_cmd_ctrl;
+	chip->legacy.dummy_controller.ops = &mt7621_controller_ops;
+
+	chip->ecc.mode = NAND_ECC_HW;
+	chip->ecc.strength = 1;
+	chip->ecc.read_page = read_page_hwecc;
+	chip->ecc.write_page = write_page_hwecc;
+	chip->ecc.write_oob = write_oob;
+	chip->ecc.read_oob = read_oob;
+
+	chip->options |= NAND_USE_BOUNCE_BUFFER;
+	chip->bbt_options |= NAND_BBT_USE_FLASH;
+	chip->buf_align = 16;
+
+	if (host->legacybbt)
+		chip->options |= NAND_SKIP_BBTSCAN;
+
+	mtd_set_ooblayout(mtd, &oob_mt7621_ops);
+
+	init_hw(host);
+
+	err = nand_scan(chip, 1);
+	if (err) {
+		pr_err("mt7621-nand: failed to identify NAND device\n");
+		goto out;
+	}
+
+	platform_set_drvdata(pdev, host);
+
+	err = mtd_device_parse_register(mtd, probe_types, &ppdata, NULL, 0);
+	if (err)
+		goto out;
+
+	if (host->legacybbt) {
+		err = load_legacy_bbt(mtd);
+		if (err)
+			goto out;
+	}
+
+	return 0;
+
+out:
+	pr_err("mt7621-nand: probe failed, err=%d\n", err);
+	nand_release(chip);
+	platform_set_drvdata(pdev, NULL);
+
+	return err;
+}
+
+static int mt7621_nand_remove(struct platform_device *pdev)
+{
+	struct host *host = platform_get_drvdata(pdev);
+	struct nand_chip *chip = &host->nand_chip;
+
+	nand_release(chip);
+	return 0;
+}
+
+static const struct of_device_id match[] = {
+	{ .compatible = "mtk,mt7621-nand" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, match);
+
+static struct platform_driver mt7621_nand_driver = {
+	.probe = mt7621_nand_probe,
+	.remove = mt7621_nand_remove,
+	.driver = {
+		.name = "MT7621-NAND",
+		.owner = THIS_MODULE,
+		.of_match_table = match,
+	},
+};
+module_platform_driver(mt7621_nand_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MediaTek MT7621 NAND controller driver");
diff --git a/drivers/staging/mt7621-nand/mt7621_nand.h b/drivers/staging/mt7621-nand/mt7621_nand.h
new file mode 100644
index 000000000000..fc9145435d5b
--- /dev/null
+++ b/drivers/staging/mt7621-nand/mt7621_nand.h
@@ -0,0 +1,362 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * mt7621_nand.h - Mediatek MT7621 NAND Flash Controller device driver
+ *
+ * Copyright 2009-2012 MediaTek Co.,Ltd.
+ * Copyright 2018-2019 Greg Ungerer <gerg at kernel.org>
+ */
+
+#ifndef MT7621_NAND_H
+#define MT7621_NAND_H
+
+#define NFI_DEFAULT_ACCESS_TIMING	0x30C77fff
+
+#define NAND_SECTOR_SIZE	512
+#define OOB_PER_SECTOR		16
+#define OOB_AVAI_PER_SECTOR	8
+
+/****************************************************************************
+ * NFI Register Definition
+ ****************************************************************************/
+
+#define NFI_CNFG_REG16		0x0000
+#define NFI_PAGEFMT_REG16	0x0004
+#define NFI_CON_REG16		0x0008
+#define NFI_ACCCON_REG32	0x000C
+#define NFI_INTR_EN_REG16	0x0010
+#define NFI_INTR_REG16		0x0014
+
+#define NFI_CMD_REG16		0x0020
+
+#define NFI_ADDRNOB_REG16	0x0030
+#define NFI_COLADDR_REG32	0x0034
+#define NFI_ROWADDR_REG32	0x0038
+
+#define NFI_STRDATA_REG16	0x0040
+#define NFI_CNRNB_REG16		0x0044
+
+#define NFI_DATAW_REG32		0x0050
+#define NFI_DATAR_REG32		0x0054
+#define NFI_PIO_DIRDY_REG16	0x0058
+
+#define NFI_STA_REG32		0x0060
+#define NFI_FIFOSTA_REG16	0x0064
+#define NFI_LOCKSTA_REG16	0x0068
+
+#define NFI_ADDRCNTR_REG16	0x0070
+
+#define NFI_STRADDR_REG32	0x0080
+#define NFI_BYTELEN_REG16	0x0084
+
+#define NFI_CSEL_REG16		0x0090
+#define NFI_IOCON_REG16		0x0094
+
+#define NFI_FDM0L_REG32		0x00A0
+#define NFI_FDM0M_REG32		0x00A4
+
+#define NFI_LOCK_REG16		0x0100
+#define NFI_LOCKCON_REG32	0x0104
+#define NFI_LOCKANOB_REG16	0x0108
+#define NFI_LOCK00ADD_REG32	0x0110
+#define NFI_LOCK00FMT_REG32	0x0114
+#define NFI_LOCK01ADD_REG32	0x0118
+#define NFI_LOCK01FMT_REG32	0x011C
+#define NFI_LOCK02ADD_REG32	0x0120
+#define NFI_LOCK02FMT_REG32	0x0124
+#define NFI_LOCK03ADD_REG32	0x0128
+#define NFI_LOCK03FMT_REG32	0x012C
+#define NFI_LOCK04ADD_REG32	0x0130
+#define NFI_LOCK04FMT_REG32	0x0134
+#define NFI_LOCK05ADD_REG32	0x0138
+#define NFI_LOCK05FMT_REG32	0x013C
+#define NFI_LOCK06ADD_REG32	0x0140
+#define NFI_LOCK06FMT_REG32	0x0144
+#define NFI_LOCK07ADD_REG32	0x0148
+#define NFI_LOCK07FMT_REG32	0x014C
+#define NFI_LOCK08ADD_REG32	0x0150
+#define NFI_LOCK08FMT_REG32	0x0154
+#define NFI_LOCK09ADD_REG32	0x0158
+#define NFI_LOCK09FMT_REG32	0x015C
+#define NFI_LOCK10ADD_REG32	0x0160
+#define NFI_LOCK10FMT_REG32	0x0164
+#define NFI_LOCK11ADD_REG32	0x0168
+#define NFI_LOCK11FMT_REG32	0x016C
+#define NFI_LOCK12ADD_REG32	0x0170
+#define NFI_LOCK12FMT_REG32	0x0174
+#define NFI_LOCK13ADD_REG32	0x0178
+#define NFI_LOCK13FMT_REG32	0x017C
+#define NFI_LOCK14ADD_REG32	0x0180
+#define NFI_LOCK14FMT_REG32	0x0184
+#define NFI_LOCK15ADD_REG32	0x0188
+#define NFI_LOCK15FMT_REG32	0x018C
+
+#define NFI_FIFODATA0_REG32	0x0190
+#define NFI_FIFODATA1_REG32	0x0194
+#define NFI_FIFODATA2_REG32	0x0198
+#define NFI_FIFODATA3_REG32	0x019C
+#define NFI_MASTERSTA_REG16	0x0210
+
+/*****************************************************************************
+ * NFI Register Field Definition
+ *****************************************************************************/
+
+/* NFI_CNFG */
+#define CNFG_AHB		0x0001
+#define CNFG_READ_EN		0x0002
+#define CNFG_DMA_BURST_EN	0x0004
+#define CNFG_BYTE_RW		0x0040
+#define CNFG_HW_ECC_EN		0x0100
+#define CNFG_AUTO_FMT_EN	0x0200
+#define CNFG_OP_IDLE		0x0000
+#define CNFG_OP_READ		0x1000
+#define CNFG_OP_SRD		0x2000
+#define CNFG_OP_PRGM		0x3000
+#define CNFG_OP_ERASE		0x4000
+#define CNFG_OP_RESET		0x5000
+#define CNFG_OP_CUST		0x6000
+#define CNFG_OP_MODE_MASK	0x7000
+#define CNFG_OP_MODE_SHIFT	12
+
+/* NFI_PAGEFMT */
+#define PAGEFMT_512		0x0000
+#define PAGEFMT_2K		0x0001
+#define PAGEFMT_4K		0x0002
+
+#define PAGEFMT_PAGE_MASK	0x0003
+
+#define PAGEFMT_DBYTE_EN	0x0008
+
+#define PAGEFMT_SPARE_16	0x0000
+#define PAGEFMT_SPARE_26	0x0001
+#define PAGEFMT_SPARE_27	0x0002
+#define PAGEFMT_SPARE_28	0x0003
+#define PAGEFMT_SPARE_MASK	0x0030
+#define PAGEFMT_SPARE_SHIFT	4
+
+#define PAGEFMT_FDM_MASK	0x0F00
+#define PAGEFMT_FDM_SHIFT	8
+
+#define PAGEFMT_FDM_ECC_MASK	0xF000
+#define PAGEFMT_FDM_ECC_SHIFT	12
+
+/* NFI_CON */
+#define CON_FIFO_FLUSH		0x0001
+#define CON_NFI_RST		0x0002
+#define CON_NFI_SRD		0x0010
+
+#define CON_NFI_NOB_MASK	0x0060
+#define CON_NFI_NOB_SHIFT	5
+
+#define CON_NFI_BRD		0x0100
+#define CON_NFI_BWR		0x0200
+
+#define CON_NFI_SEC_MASK	0xF000
+#define CON_NFI_SEC_SHIFT	12
+
+/* NFI_ACCCON */
+#define ACCCON_SETTING		()
+
+/* NFI_INTR_EN */
+#define INTR_RD_DONE_EN		0x0001
+#define INTR_WR_DONE_EN		0x0002
+#define INTR_RST_DONE_EN	0x0004
+#define INTR_ERASE_DONE_EN	0x0008
+#define INTR_BSY_RTN_EN		0x0010
+#define INTR_ACC_LOCK_EN	0x0020
+#define INTR_AHB_DONE_EN	0x0040
+#define INTR_ALL_INTR_DE	0x0000
+#define INTR_ALL_INTR_EN	0x007F
+
+/* NFI_INTR */
+#define INTR_RD_DONE		0x0001
+#define INTR_WR_DONE		0x0002
+#define INTR_RST_DONE		0x0004
+#define INTR_ERASE_DONE		0x0008
+#define INTR_BSY_RTN		0x0010
+#define INTR_ACC_LOCK		0x0020
+#define INTR_AHB_DONE		0x0040
+
+/* NFI_ADDRNOB */
+#define ADDR_COL_NOB_MASK	0x0003
+#define ADDR_COL_NOB_SHIFT	0
+#define ADDR_ROW_NOB_MASK	0x0030
+#define ADDR_ROW_NOB_SHIFT	4
+
+/* NFI_STA */
+#define STA_READ_EMPTY		0x00001000
+#define STA_ACC_LOCK		0x00000010
+#define STA_CMD_STATE		0x00000001
+#define STA_ADDR_STATE		0x00000002
+#define STA_DATAR_STATE		0x00000004
+#define STA_DATAW_STATE		0x00000008
+
+#define STA_NAND_FSM_MASK	0x1F000000
+#define STA_NAND_BUSY		0x00000100
+#define STA_NAND_BUSY_RETURN	0x00000200
+#define STA_NFI_FSM_MASK	0x000F0000
+#define STA_NFI_OP_MASK		0x0000000F
+
+/* NFI_FIFOSTA */
+#define FIFO_RD_EMPTY		0x0040
+#define FIFO_RD_FULL		0x0080
+#define FIFO_WR_FULL		0x8000
+#define FIFO_WR_EMPTY		0x4000
+#define FIFO_RD_REMAIN(x)	(0x1F & (x))
+#define FIFO_WR_REMAIN(x)	((0x1F00 & (x)) >> 8)
+
+/* NFI_ADDRCNTR */
+#define ADDRCNTR_CNTR(x)	((0xF000 & (x)) >> 12)
+#define ADDRCNTR_OFFSET(x)	(0x03FF & (x))
+
+/* NFI_LOCK */
+#define NFI_LOCK_ON		0x0001
+
+/* NFI_LOCKANOB */
+#define PROG_RADD_NOB_MASK	0x7000
+#define PROG_RADD_NOB_SHIFT	12
+#define PROG_CADD_NOB_MASK	0x0300
+#define PROG_CADD_NOB_SHIFT	8
+#define ERASE_RADD_NOB_MASK	0x0070
+#define ERASE_RADD_NOB_SHIFT	4
+#define ERASE_CADD_NOB_MASK	0x0007
+#define ERASE_CADD_NOB_SHIFT	0
+
+/*****************************************************************************
+ * ECC Register Definition
+ *****************************************************************************/
+
+#define ECC_ENCCON_REG16	0x0000
+#define ECC_ENCCNFG_REG32	0x0004
+#define ECC_ENCDIADDR_REG32	0x0008
+#define ECC_ENCIDLE_REG32	0x000C
+#define ECC_ENCPAR0_REG32	0x0010
+#define ECC_ENCPAR1_REG32	0x0014
+#define ECC_ENCPAR2_REG32	0x0018
+#define ECC_ENCPAR3_REG32	0x001C
+#define ECC_ENCPAR4_REG32	0x0020
+#define ECC_ENCSTA_REG32	0x0024
+#define ECC_ENCIRQEN_REG16	0x0028
+#define ECC_ENCIRQSTA_REG16	0x002C
+
+#define ECC_DECCON_REG16	0x0100
+#define ECC_DECCNFG_REG32	0x0104
+#define ECC_DECDIADDR_REG32	0x0108
+#define ECC_DECIDLE_REG16	0x010C
+#define ECC_DECFER_REG16	0x0110
+#define ECC_DECENUM_REG32	0x0114
+#define ECC_DECDONE_REG16	0x0118
+#define ECC_DECEL0_REG32	0x011C
+#define ECC_DECEL1_REG32	0x0120
+#define ECC_DECEL2_REG32	0x0124
+#define ECC_DECEL3_REG32	0x0128
+#define ECC_DECEL4_REG32	0x012C
+#define ECC_DECEL5_REG32	0x0130
+#define ECC_DECIRQEN_REG16	0x0134
+#define ECC_DECIRQSTA_REG16	0x0138
+#define ECC_FDMADDR_REG32	0x013C
+#define ECC_DECFSM_REG32	0x0140
+#define ECC_SYNSTA_REG32	0x0144
+#define ECC_DECNFIDI_REG32	0x0148
+#define ECC_SYN0_REG32		0x014C
+
+/*****************************************************************************
+ * ECC register definition
+ *****************************************************************************/
+
+/* ECC_ENCON */
+#define ENC_EN			0x0001
+#define ENC_DE			0x0000
+
+/* ECC_ENCCNFG */
+#define ECC_CNFG_ECC4		0x0000
+#define ECC_CNFG_ECC6		0x0001
+#define ECC_CNFG_ECC8		0x0002
+#define ECC_CNFG_ECC10		0x0003
+#define ECC_CNFG_ECC12		0x0004
+#define ECC_CNFG_ECC_MASK	0x00000007
+
+#define ENC_CNFG_NFI		0x0010
+#define ENC_CNFG_MODE_MASK	0x0010
+
+#define ENC_CNFG_META6		0x10300000
+#define ENC_CNFG_META8		0x10400000
+
+#define ENC_CNFG_MSG_MASK	0x1FFF0000
+#define ENC_CNFG_MSG_SHIFT	0x10
+
+/* ECC_ENCIDLE */
+#define ENC_IDLE		0x0001
+
+/* ECC_ENCSTA */
+#define STA_FSM			0x001F
+#define STA_COUNT_PS		0xFF10
+#define STA_COUNT_MS		0x3FFF0000
+
+/* ECC_ENCIRQEN */
+#define ENC_IRQEN		0x0001
+
+/* ECC_ENCIRQSTA */
+#define ENC_IRQSTA		0x0001
+
+/* ECC_DECCON */
+#define DEC_EN			0x0001
+#define DEC_DE			0x0000
+
+/* ECC_ENCCNFG */
+#define DEC_CNFG_ECC4		0x0000
+//#define DEC_CNFG_ECC6		0x0001
+//#define DEC_CNFG_ECC12	0x0002
+#define DEC_CNFG_NFI		0x0010
+//#define DEC_CNFG_META6	0x10300000
+//#define DEC_CNFG_META8	0x10400000
+
+#define DEC_CNFG_FER		0x01000
+#define DEC_CNFG_EL		0x02000
+#define DEC_CNFG_CORRECT	0x03000
+#define DEC_CNFG_TYPE_MASK	0x03000
+
+#define DEC_CNFG_EMPTY_EN	0x80000000
+
+#define DEC_CNFG_CODE_MASK	0x1FFF0000
+#define DEC_CNFG_CODE_SHIFT	0x10
+
+/* ECC_DECIDLE */
+#define DEC_IDLE		0x0001
+
+/* ECC_DECFER */
+#define DEC_FER0		0x0001
+#define DEC_FER1		0x0002
+#define DEC_FER2		0x0004
+#define DEC_FER3		0x0008
+#define DEC_FER4		0x0010
+#define DEC_FER5		0x0020
+#define DEC_FER6		0x0040
+#define DEC_FER7		0x0080
+
+/* ECC_DECENUM */
+#define ERR_NUM0		0x0000000F
+#define ERR_NUM1		0x000000F0
+#define ERR_NUM2		0x00000F00
+#define ERR_NUM3		0x0000F000
+#define ERR_NUM4		0x000F0000
+#define ERR_NUM5		0x00F00000
+#define ERR_NUM6		0x0F000000
+#define ERR_NUM7		0xF0000000
+
+/* ECC_DECDONE */
+#define DEC_DONE0		0x0001
+#define DEC_DONE1		0x0002
+#define DEC_DONE2		0x0004
+#define DEC_DONE3		0x0008
+#define DEC_DONE4		0x0010
+#define DEC_DONE5		0x0020
+#define DEC_DONE6		0x0040
+#define DEC_DONE7		0x0080
+
+/* ECC_DECIRQEN */
+#define DEC_IRQEN		0x0001
+
+/* ECC_DECIRQSTA */
+#define DEC_IRQSTA		0x0001
+
+#endif /* MT7621_NAND_H */
diff --git a/drivers/staging/mt7621-nand/mtk,mt7621-nand.txt b/drivers/staging/mt7621-nand/mtk,mt7621-nand.txt
new file mode 100644
index 000000000000..8f2b48377d86
--- /dev/null
+++ b/drivers/staging/mt7621-nand/mtk,mt7621-nand.txt
@@ -0,0 +1,29 @@
+MediaTek MT7621 NAND controller
+
+Required properties:
+- compatible "mtk,mt7621-nand"
+- reg: base address and length of controller and ECC register banks
+- #address-cells: must be 1
+- #size-cells: must be 1
+- status: either "disabled" or "okay"
+
+Example:
+
+        nand: nand at 1e003000 {
+                status = "okay";
+
+                compatible = "mtk,mt7621-nand";
+                reg = <0x1e003000 0x800
+                        0x1e003800 0x800>;
+                #address-cells = <1>;
+                #size-cells = <1>;
+        };
+
+Optional:
+- mediatek,nand-bbt-compat: expect on-flash legacy Mediatek bad block table
+- mediatek,nand-bbt-block-num: possible number of legacy bad blocks
+
+If using the legacy u-boot provided by MediaTek you will want to use the
+"mediatek,nand-bbt-compat" binding. The driver default is to expect a
+standard Linux bad block table in flash.
+
-- 
2.17.1




More information about the linux-mtd mailing list