From: Greg Ungerer <g...@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 <g...@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: g...@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 <g...@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 <g...@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.
+
-- 
2.17.1

_______________________________________________
devel mailing list
de...@linuxdriverproject.org
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel

Reply via email to