nand: raw: add support for MediaTek MT7621 SoC
This patch adds NAND flash controller driver for MediaTek MT7621 SoC.
The NAND flash controller of MT7621 supports only SLC NAND flashes.
It supports 4~12 bits correction with maximum 4KB page size.
diff --git a/drivers/mtd/nand/raw/mt7621_nand_spl.c b/drivers/mtd/nand/raw/mt7621_nand_spl.c
new file mode 100644
index 0000000..114fc8b
--- /dev/null
+++ b/drivers/mtd/nand/raw/mt7621_nand_spl.c
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <image.h>
+#include <malloc.h>
+#include <linux/sizes.h>
+#include <linux/delay.h>
+#include <linux/mtd/rawnand.h>
+#include "mt7621_nand.h"
+
+static struct mt7621_nfc nfc_dev;
+static u8 *buffer;
+static int nand_valid;
+
+static void nand_command_lp(struct mtd_info *mtd, unsigned int command,
+ int column, int page_addr)
+{
+ register struct nand_chip *chip = mtd_to_nand(mtd);
+
+ /* Command latch cycle */
+ chip->cmd_ctrl(mtd, command, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
+
+ if (column != -1 || page_addr != -1) {
+ int ctrl = NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE;
+
+ /* Serially input address */
+ if (column != -1) {
+ chip->cmd_ctrl(mtd, column, ctrl);
+ ctrl &= ~NAND_CTRL_CHANGE;
+ if (command != NAND_CMD_READID)
+ chip->cmd_ctrl(mtd, column >> 8, ctrl);
+ }
+ if (page_addr != -1) {
+ chip->cmd_ctrl(mtd, page_addr, ctrl);
+ chip->cmd_ctrl(mtd, page_addr >> 8,
+ NAND_NCE | NAND_ALE);
+ if (chip->options & NAND_ROW_ADDR_3)
+ chip->cmd_ctrl(mtd, page_addr >> 16,
+ NAND_NCE | NAND_ALE);
+ }
+ }
+ chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
+
+ /*
+ * Program and erase have their own busy handlers status, sequential
+ * in and status need no delay.
+ */
+ switch (command) {
+ case NAND_CMD_STATUS:
+ case NAND_CMD_READID:
+ case NAND_CMD_SET_FEATURES:
+ return;
+
+ case NAND_CMD_READ0:
+ chip->cmd_ctrl(mtd, NAND_CMD_READSTART,
+ NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
+ chip->cmd_ctrl(mtd, NAND_CMD_NONE,
+ NAND_NCE | NAND_CTRL_CHANGE);
+ }
+
+ /*
+ * Apply this short delay always to ensure that we do wait tWB in
+ * any case on any machine.
+ */
+ ndelay(100);
+
+ nand_wait_ready(mtd);
+}
+
+static int nfc_read_page_hwecc(struct mtd_info *mtd, void *buf,
+ unsigned int page)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ int ret;
+
+ chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, page);
+
+ ret = chip->ecc.read_page(mtd, chip, buf, 1, page);
+ if (ret < 0 || ret > chip->ecc.strength)
+ return -1;
+
+ return 0;
+}
+
+static int nfc_read_oob_hwecc(struct mtd_info *mtd, void *buf, u32 len,
+ unsigned int page)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ int ret;
+
+ chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, page);
+
+ ret = chip->ecc.read_page(mtd, chip, NULL, 1, page);
+ if (ret < 0)
+ return -1;
+
+ if (len > mtd->oobsize)
+ len = mtd->oobsize;
+
+ memcpy(buf, chip->oob_poi, len);
+
+ return 0;
+}
+
+static int nfc_check_bad_block(struct mtd_info *mtd, unsigned int page)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ u32 pages_per_block, i = 0;
+ int ret;
+ u8 bad;
+
+ pages_per_block = 1 << (mtd->erasesize_shift - mtd->writesize_shift);
+
+ /* Read from first/last page(s) if necessary */
+ if (chip->bbt_options & NAND_BBT_SCANLASTPAGE) {
+ page += pages_per_block - 1;
+ if (chip->bbt_options & NAND_BBT_SCAN2NDPAGE)
+ page--;
+ }
+
+ do {
+ ret = nfc_read_oob_hwecc(mtd, &bad, 1, page);
+ if (ret)
+ return ret;
+
+ ret = bad != 0xFF;
+
+ i++;
+ page++;
+ } while (!ret && (chip->bbt_options & NAND_BBT_SCAN2NDPAGE) && i < 2);
+
+ return ret;
+}
+
+int nand_spl_load_image(uint32_t offs, unsigned int size, void *dest)
+{
+ struct mt7621_nfc *nfc = &nfc_dev;
+ struct nand_chip *chip = &nfc->nand;
+ struct mtd_info *mtd = &chip->mtd;
+ u32 addr, col, page, chksz;
+ bool check_bad = true;
+
+ if (!nand_valid)
+ return -ENODEV;
+
+ while (size) {
+ if (check_bad || !(offs & mtd->erasesize_mask)) {
+ addr = offs & (~mtd->erasesize_mask);
+ page = addr >> mtd->writesize_shift;
+ if (nfc_check_bad_block(mtd, page)) {
+ /* Skip bad block */
+ if (addr >= mtd->size - mtd->erasesize)
+ return -1;
+
+ offs += mtd->erasesize;
+ continue;
+ }
+
+ check_bad = false;
+ }
+
+ col = offs & mtd->writesize_mask;
+ page = offs >> mtd->writesize_shift;
+ chksz = min(mtd->writesize - col, (uint32_t)size);
+
+ if (unlikely(chksz < mtd->writesize)) {
+ /* Not reading a full page */
+ if (nfc_read_page_hwecc(mtd, buffer, page))
+ return -1;
+
+ memcpy(dest, buffer + col, chksz);
+ } else {
+ if (nfc_read_page_hwecc(mtd, dest, page))
+ return -1;
+ }
+
+ dest += chksz;
+ offs += chksz;
+ size -= chksz;
+ }
+
+ return 0;
+}
+
+int nand_default_bbt(struct mtd_info *mtd)
+{
+ return 0;
+}
+
+unsigned long nand_size(void)
+{
+ if (!nand_valid)
+ return 0;
+
+ /* Unlikely that NAND size > 2GBytes */
+ if (nfc_dev.nand.chipsize <= SZ_2G)
+ return nfc_dev.nand.chipsize;
+
+ return SZ_2G;
+}
+
+void nand_deselect(void)
+{
+}
+
+void nand_init(void)
+{
+ struct mtd_info *mtd;
+ struct nand_chip *chip;
+
+ if (nand_valid)
+ return;
+
+ mt7621_nfc_spl_init(&nfc_dev);
+
+ chip = &nfc_dev.nand;
+ mtd = &chip->mtd;
+ chip->cmdfunc = nand_command_lp;
+
+ if (mt7621_nfc_spl_post_init(&nfc_dev))
+ return;
+
+ mtd->erasesize_shift = ffs(mtd->erasesize) - 1;
+ mtd->writesize_shift = ffs(mtd->writesize) - 1;
+ mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1;
+ mtd->writesize_mask = (1 << mtd->writesize_shift) - 1;
+
+ buffer = malloc(mtd->writesize);
+ if (!buffer)
+ return;
+
+ nand_valid = 1;
+}