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@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@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@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@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.
+
