diff --git a/patches/ramips/0006-ramips-add-support-for-actiontec-web7200.patch b/patches/ramips/0006-ramips-add-support-for-actiontec-web7200.patch index 2c70a399a..b19548425 100644 --- a/patches/ramips/0006-ramips-add-support-for-actiontec-web7200.patch +++ b/patches/ramips/0006-ramips-add-support-for-actiontec-web7200.patch @@ -1,7 +1,7 @@ -From 2858efa76a021729e068e45675c86f670674aa16 Mon Sep 17 00:00:00 2001 +From a42b4b5273ed91281c046939492d5bb02b0ca3fa Mon Sep 17 00:00:00 2001 From: John Crispin Date: Fri, 7 Jan 2022 10:58:08 +0100 -Subject: [PATCH] ramips: add support for actiontec web7200 +Subject: [PATCH 06/14] ramips: add support for actiontec web7200 Signed-off-by: John Crispin --- @@ -34,7 +34,7 @@ Signed-off-by: John Crispin create mode 100644 target/linux/ramips/patches-5.4/499-mtd-add-nmbm-support.patch diff --git a/package/boot/uboot-envtools/files/ramips b/package/boot/uboot-envtools/files/ramips -index 33af86c846..ce389d6ef8 100644 +index 33af86c846..c13ee08103 100644 --- a/package/boot/uboot-envtools/files/ramips +++ b/package/boot/uboot-envtools/files/ramips @@ -13,6 +13,9 @@ touch /etc/config/ubootenv @@ -3196,30 +3196,30 @@ index 0000000000..18dfb6adda +#include "nmbm-debug.inl" diff --git a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.h b/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.h new file mode 100644 -index 0000000000..582118333e +index 0000000000..7f9dfed9e1 --- /dev/null +++ b/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.h @@ -0,0 +1,20 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* -+ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. -+ * -+ * Debug addons for NAND Mapped-block Management (NMBM) -+ * -+ * Author: Weijie Gao -+ */ -+ -+#ifndef _NMBM_DEBUG_H_ -+#define _NMBM_DEBUG_H_ -+ -+#define nmbm_mark_block_color_normal(ni, start_ba, end_ba) -+#define nmbm_mark_block_color_bad(ni, ba) -+#define nmbm_mark_block_color_mgmt(ni, start_ba, end_ba) -+#define nmbm_mark_block_color_signature(ni, ba) -+#define nmbm_mark_block_color_info_table(ni, start_ba, end_ba) -+#define nmbm_mark_block_color_mapped(ni, ba) -+ -+#endif /* _NMBM_DEBUG_H_ */ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * Debug addons for NAND Mapped-block Management (NMBM) ++ * ++ * Author: Weijie Gao ++ */ ++ ++#ifndef _NMBM_DEBUG_H_ ++#define _NMBM_DEBUG_H_ ++ ++#define nmbm_mark_block_color_normal(ni, start_ba, end_ba) ++#define nmbm_mark_block_color_bad(ni, ba) ++#define nmbm_mark_block_color_mgmt(ni, start_ba, end_ba) ++#define nmbm_mark_block_color_signature(ni, ba) ++#define nmbm_mark_block_color_info_table(ni, start_ba, end_ba) ++#define nmbm_mark_block_color_mapped(ni, ba) ++ ++#endif /* _NMBM_DEBUG_H_ */ diff --git a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.inl b/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.inl new file mode 100644 index 0000000000..e69de29bb2 diff --git a/patches/ramips/0007-actiontec-web7200.patch b/patches/ramips/0007-actiontec-web7200.patch deleted file mode 100644 index a42e077a5..000000000 --- a/patches/ramips/0007-actiontec-web7200.patch +++ /dev/null @@ -1,11150 +0,0 @@ -From ee31f71c832f27a1a58d1dd3c33704d077f389d3 Mon Sep 17 00:00:00 2001 -From: Felix Fietkau -Date: Thu, 10 Mar 2022 18:19:37 +0100 -Subject: [PATCH 1/8] ramips: add mtk_bmt support (includes bbt, bmt v2, nmbm) - -Signed-off-by: Felix Fietkau ---- - target/linux/generic/config-5.4 | 1 + - .../ramips/files/drivers/mtd/nand/mtk_bmt.c | 465 ++++ - .../ramips/files/drivers/mtd/nand/mtk_bmt.h | 131 + - .../files/drivers/mtd/nand/mtk_bmt_bbt.c | 203 ++ - .../files/drivers/mtd/nand/mtk_bmt_nmbm.c | 2348 +++++++++++++++++ - .../files/drivers/mtd/nand/mtk_bmt_v2.c | 513 ++++ - .../ramips/files/include/linux/mtd/mtk_bmt.h | 18 + - target/linux/ramips/mt7621/config-5.4 | 1 + - .../patches-5.4/430-mtk-bmt-support.patch | 23 + - 9 files changed, 3703 insertions(+) - create mode 100644 target/linux/ramips/files/drivers/mtd/nand/mtk_bmt.c - create mode 100644 target/linux/ramips/files/drivers/mtd/nand/mtk_bmt.h - create mode 100644 target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_bbt.c - create mode 100644 target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_nmbm.c - create mode 100644 target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_v2.c - create mode 100644 target/linux/ramips/files/include/linux/mtd/mtk_bmt.h - create mode 100644 target/linux/ramips/patches-5.4/430-mtk-bmt-support.patch - -diff --git a/target/linux/generic/config-5.4 b/target/linux/generic/config-5.4 -index a62b2cadcbca..a821a1de5f16 100644 ---- a/target/linux/generic/config-5.4 -+++ b/target/linux/generic/config-5.4 -@@ -3234,6 +3234,7 @@ CONFIG_MTD_NAND_IDS=y - # CONFIG_MTD_NAND_JZ4740 is not set - # CONFIG_MTD_NAND_MPC5121_NFC is not set - # CONFIG_MTD_NAND_MTK is not set -+# CONFIG_MTD_NAND_MTK_BMT is not set - # CONFIG_MTD_NAND_MXC is not set - # CONFIG_MTD_NAND_MXIC is not set - # CONFIG_MTD_NAND_NANDSIM is not set -diff --git a/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt.c b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt.c -new file mode 100644 -index 000000000000..f1df4a11f12d ---- /dev/null -+++ b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt.c -@@ -0,0 +1,465 @@ -+/* -+ * Copyright (c) 2017 MediaTek Inc. -+ * Author: Xiangsheng Hou -+ * Copyright (c) 2020-2022 Felix Fietkau -+ * -+ * This program is free software; you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 2 as -+ * published by the Free Software Foundation. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ */ -+ -+#include -+#include -+#include -+#include -+#include "mtk_bmt.h" -+ -+struct bmt_desc bmtd = {}; -+ -+/* -------- Nand operations wrapper -------- */ -+int bbt_nand_copy(u16 dest_blk, u16 src_blk, loff_t max_offset) -+{ -+ int pages = bmtd.blk_size >> bmtd.pg_shift; -+ loff_t src = (loff_t)src_blk << bmtd.blk_shift; -+ loff_t dest = (loff_t)dest_blk << bmtd.blk_shift; -+ loff_t offset = 0; -+ uint8_t oob[64]; -+ int i, ret; -+ -+ for (i = 0; i < pages; i++) { -+ struct mtd_oob_ops rd_ops = { -+ .mode = MTD_OPS_PLACE_OOB, -+ .oobbuf = oob, -+ .ooblen = min_t(int, bmtd.mtd->oobsize / pages, sizeof(oob)), -+ .datbuf = bmtd.data_buf, -+ .len = bmtd.pg_size, -+ }; -+ struct mtd_oob_ops wr_ops = { -+ .mode = MTD_OPS_PLACE_OOB, -+ .oobbuf = oob, -+ .datbuf = bmtd.data_buf, -+ .len = bmtd.pg_size, -+ }; -+ -+ if (offset >= max_offset) -+ break; -+ -+ ret = bmtd._read_oob(bmtd.mtd, src + offset, &rd_ops); -+ if (ret < 0 && !mtd_is_bitflip(ret)) -+ return ret; -+ -+ if (!rd_ops.retlen) -+ break; -+ -+ ret = bmtd._write_oob(bmtd.mtd, dest + offset, &wr_ops); -+ if (ret < 0) -+ return ret; -+ -+ wr_ops.ooblen = rd_ops.oobretlen; -+ offset += rd_ops.retlen; -+ } -+ -+ return 0; -+} -+ -+/* -------- Bad Blocks Management -------- */ -+bool mapping_block_in_range(int block, int *start, int *end) -+{ -+ const __be32 *cur = bmtd.remap_range; -+ u32 addr = block << bmtd.blk_shift; -+ int i; -+ -+ if (!cur || !bmtd.remap_range_len) { -+ *start = 0; -+ *end = bmtd.total_blks; -+ return true; -+ } -+ -+ for (i = 0; i < bmtd.remap_range_len; i++, cur += 2) { -+ if (addr < be32_to_cpu(cur[0]) || addr >= be32_to_cpu(cur[1])) -+ continue; -+ -+ *start = be32_to_cpu(cur[0]); -+ *end = be32_to_cpu(cur[1]); -+ return true; -+ } -+ -+ return false; -+} -+ -+static bool -+mtk_bmt_remap_block(u32 block, u32 mapped_block, int copy_len) -+{ -+ int start, end; -+ -+ if (!mapping_block_in_range(block, &start, &end)) -+ return false; -+ -+ return bmtd.ops->remap_block(block, mapped_block, copy_len); -+} -+ -+static int -+mtk_bmt_read(struct mtd_info *mtd, loff_t from, -+ struct mtd_oob_ops *ops) -+{ -+ struct mtd_oob_ops cur_ops = *ops; -+ int retry_count = 0; -+ loff_t cur_from; -+ int ret = 0; -+ int max_bitflips = 0; -+ -+ ops->retlen = 0; -+ ops->oobretlen = 0; -+ -+ while (ops->retlen < ops->len || ops->oobretlen < ops->ooblen) { -+ int cur_ret; -+ -+ u32 offset = from & (bmtd.blk_size - 1); -+ u32 block = from >> bmtd.blk_shift; -+ int cur_block; -+ -+ cur_block = bmtd.ops->get_mapping_block(block); -+ if (cur_block < 0) -+ return -EIO; -+ -+ cur_from = ((loff_t)cur_block << bmtd.blk_shift) + offset; -+ -+ cur_ops.oobretlen = 0; -+ cur_ops.retlen = 0; -+ cur_ops.len = min_t(u32, mtd->erasesize - offset, -+ ops->len - ops->retlen); -+ cur_ret = bmtd._read_oob(mtd, cur_from, &cur_ops); -+ if (cur_ret < 0) -+ ret = cur_ret; -+ else -+ max_bitflips = max_t(int, max_bitflips, cur_ret); -+ if (cur_ret < 0 && !mtd_is_bitflip(cur_ret)) { -+ if (mtk_bmt_remap_block(block, cur_block, mtd->erasesize) && -+ retry_count++ < 10) -+ continue; -+ -+ goto out; -+ } -+ -+ if (cur_ret >= mtd->bitflip_threshold) -+ mtk_bmt_remap_block(block, cur_block, mtd->erasesize); -+ -+ ops->retlen += cur_ops.retlen; -+ ops->oobretlen += cur_ops.oobretlen; -+ -+ cur_ops.ooboffs = 0; -+ cur_ops.datbuf += cur_ops.retlen; -+ cur_ops.oobbuf += cur_ops.oobretlen; -+ cur_ops.ooblen -= cur_ops.oobretlen; -+ -+ if (!cur_ops.len) -+ cur_ops.len = mtd->erasesize - offset; -+ -+ from += cur_ops.len; -+ retry_count = 0; -+ } -+ -+out: -+ if (ret < 0) -+ return ret; -+ -+ return max_bitflips; -+} -+ -+static int -+mtk_bmt_write(struct mtd_info *mtd, loff_t to, -+ struct mtd_oob_ops *ops) -+{ -+ struct mtd_oob_ops cur_ops = *ops; -+ int retry_count = 0; -+ loff_t cur_to; -+ int ret; -+ -+ ops->retlen = 0; -+ ops->oobretlen = 0; -+ -+ while (ops->retlen < ops->len || ops->oobretlen < ops->ooblen) { -+ u32 offset = to & (bmtd.blk_size - 1); -+ u32 block = to >> bmtd.blk_shift; -+ int cur_block; -+ -+ cur_block = bmtd.ops->get_mapping_block(block); -+ if (cur_block < 0) -+ return -EIO; -+ -+ cur_to = ((loff_t)cur_block << bmtd.blk_shift) + offset; -+ -+ cur_ops.oobretlen = 0; -+ cur_ops.retlen = 0; -+ cur_ops.len = min_t(u32, bmtd.blk_size - offset, -+ ops->len - ops->retlen); -+ ret = bmtd._write_oob(mtd, cur_to, &cur_ops); -+ if (ret < 0) { -+ if (mtk_bmt_remap_block(block, cur_block, offset) && -+ retry_count++ < 10) -+ continue; -+ -+ return ret; -+ } -+ -+ ops->retlen += cur_ops.retlen; -+ ops->oobretlen += cur_ops.oobretlen; -+ -+ cur_ops.ooboffs = 0; -+ cur_ops.datbuf += cur_ops.retlen; -+ cur_ops.oobbuf += cur_ops.oobretlen; -+ cur_ops.ooblen -= cur_ops.oobretlen; -+ -+ if (!cur_ops.len) -+ cur_ops.len = mtd->erasesize - offset; -+ -+ to += cur_ops.len; -+ retry_count = 0; -+ } -+ -+ return 0; -+} -+ -+static int -+mtk_bmt_mtd_erase(struct mtd_info *mtd, struct erase_info *instr) -+{ -+ struct erase_info mapped_instr = { -+ .len = bmtd.blk_size, -+ }; -+ int retry_count = 0; -+ u64 start_addr, end_addr; -+ int ret; -+ u16 orig_block; -+ int block; -+ -+ start_addr = instr->addr & (~mtd->erasesize_mask); -+ end_addr = instr->addr + instr->len; -+ -+ while (start_addr < end_addr) { -+ orig_block = start_addr >> bmtd.blk_shift; -+ block = bmtd.ops->get_mapping_block(orig_block); -+ if (block < 0) -+ return -EIO; -+ mapped_instr.addr = (loff_t)block << bmtd.blk_shift; -+ ret = bmtd._erase(mtd, &mapped_instr); -+ if (ret) { -+ if (mtk_bmt_remap_block(orig_block, block, 0) && -+ retry_count++ < 10) -+ continue; -+ instr->fail_addr = start_addr; -+ break; -+ } -+ start_addr += mtd->erasesize; -+ retry_count = 0; -+ } -+ -+ return ret; -+} -+static int -+mtk_bmt_block_isbad(struct mtd_info *mtd, loff_t ofs) -+{ -+ int retry_count = 0; -+ u16 orig_block = ofs >> bmtd.blk_shift; -+ u16 block; -+ int ret; -+ -+retry: -+ block = bmtd.ops->get_mapping_block(orig_block); -+ ret = bmtd._block_isbad(mtd, (loff_t)block << bmtd.blk_shift); -+ if (ret) { -+ if (mtk_bmt_remap_block(orig_block, block, bmtd.blk_size) && -+ retry_count++ < 10) -+ goto retry; -+ } -+ return ret; -+} -+ -+static int -+mtk_bmt_block_markbad(struct mtd_info *mtd, loff_t ofs) -+{ -+ u16 orig_block = ofs >> bmtd.blk_shift; -+ int block; -+ -+ block = bmtd.ops->get_mapping_block(orig_block); -+ if (block < 0) -+ return -EIO; -+ -+ mtk_bmt_remap_block(orig_block, block, bmtd.blk_size); -+ -+ return bmtd._block_markbad(mtd, (loff_t)block << bmtd.blk_shift); -+} -+ -+static void -+mtk_bmt_replace_ops(struct mtd_info *mtd) -+{ -+ bmtd._read_oob = mtd->_read_oob; -+ bmtd._write_oob = mtd->_write_oob; -+ bmtd._erase = mtd->_erase; -+ bmtd._block_isbad = mtd->_block_isbad; -+ bmtd._block_markbad = mtd->_block_markbad; -+ -+ mtd->_read_oob = mtk_bmt_read; -+ mtd->_write_oob = mtk_bmt_write; -+ mtd->_erase = mtk_bmt_mtd_erase; -+ mtd->_block_isbad = mtk_bmt_block_isbad; -+ mtd->_block_markbad = mtk_bmt_block_markbad; -+} -+ -+static int mtk_bmt_debug_repair(void *data, u64 val) -+{ -+ int block = val >> bmtd.blk_shift; -+ int prev_block, new_block; -+ -+ prev_block = bmtd.ops->get_mapping_block(block); -+ if (prev_block < 0) -+ return -EIO; -+ -+ bmtd.ops->unmap_block(block); -+ new_block = bmtd.ops->get_mapping_block(block); -+ if (new_block < 0) -+ return -EIO; -+ -+ if (prev_block == new_block) -+ return 0; -+ -+ bbt_nand_erase(new_block); -+ bbt_nand_copy(new_block, prev_block, bmtd.blk_size); -+ -+ return 0; -+} -+ -+static int mtk_bmt_debug_mark_good(void *data, u64 val) -+{ -+ bmtd.ops->unmap_block(val >> bmtd.blk_shift); -+ -+ return 0; -+} -+ -+static int mtk_bmt_debug_mark_bad(void *data, u64 val) -+{ -+ u32 block = val >> bmtd.blk_shift; -+ int cur_block; -+ -+ cur_block = bmtd.ops->get_mapping_block(block); -+ if (cur_block < 0) -+ return -EIO; -+ -+ mtk_bmt_remap_block(block, cur_block, bmtd.blk_size); -+ -+ return 0; -+} -+ -+static int mtk_bmt_debug(void *data, u64 val) -+{ -+ return bmtd.ops->debug(data, val); -+} -+ -+ -+DEFINE_DEBUGFS_ATTRIBUTE(fops_repair, NULL, mtk_bmt_debug_repair, "%llu\n"); -+DEFINE_DEBUGFS_ATTRIBUTE(fops_mark_good, NULL, mtk_bmt_debug_mark_good, "%llu\n"); -+DEFINE_DEBUGFS_ATTRIBUTE(fops_mark_bad, NULL, mtk_bmt_debug_mark_bad, "%llu\n"); -+DEFINE_DEBUGFS_ATTRIBUTE(fops_debug, NULL, mtk_bmt_debug, "%llu\n"); -+ -+static void -+mtk_bmt_add_debugfs(void) -+{ -+ struct dentry *dir; -+ -+ dir = bmtd.debugfs_dir = debugfs_create_dir("mtk-bmt", NULL); -+ if (!dir) -+ return; -+ -+ debugfs_create_file_unsafe("repair", S_IWUSR, dir, NULL, &fops_repair); -+ debugfs_create_file_unsafe("mark_good", S_IWUSR, dir, NULL, &fops_mark_good); -+ debugfs_create_file_unsafe("mark_bad", S_IWUSR, dir, NULL, &fops_mark_bad); -+ debugfs_create_file_unsafe("debug", S_IWUSR, dir, NULL, &fops_debug); -+} -+ -+void mtk_bmt_detach(struct mtd_info *mtd) -+{ -+ if (bmtd.mtd != mtd) -+ return; -+ -+ if (bmtd.debugfs_dir) -+ debugfs_remove_recursive(bmtd.debugfs_dir); -+ bmtd.debugfs_dir = NULL; -+ -+ kfree(bmtd.bbt_buf); -+ kfree(bmtd.data_buf); -+ -+ mtd->_read_oob = bmtd._read_oob; -+ mtd->_write_oob = bmtd._write_oob; -+ mtd->_erase = bmtd._erase; -+ mtd->_block_isbad = bmtd._block_isbad; -+ mtd->_block_markbad = bmtd._block_markbad; -+ mtd->size = bmtd.total_blks << bmtd.blk_shift; -+ -+ memset(&bmtd, 0, sizeof(bmtd)); -+} -+ -+ -+int mtk_bmt_attach(struct mtd_info *mtd) -+{ -+ struct device_node *np; -+ int ret = 0; -+ -+ if (bmtd.mtd) -+ return -ENOSPC; -+ -+ np = mtd_get_of_node(mtd); -+ if (!np) -+ return 0; -+ -+ if (of_property_read_bool(np, "mediatek,bmt-v2")) -+ bmtd.ops = &mtk_bmt_v2_ops; -+ else if (of_property_read_bool(np, "mediatek,nmbm")) -+ bmtd.ops = &mtk_bmt_nmbm_ops; -+ else if (of_property_read_bool(np, "mediatek,bbt")) -+ bmtd.ops = &mtk_bmt_bbt_ops; -+ else -+ return 0; -+ -+ bmtd.remap_range = of_get_property(np, "mediatek,bmt-remap-range", -+ &bmtd.remap_range_len); -+ bmtd.remap_range_len /= 8; -+ -+ bmtd.mtd = mtd; -+ mtk_bmt_replace_ops(mtd); -+ -+ bmtd.blk_size = mtd->erasesize; -+ bmtd.blk_shift = ffs(bmtd.blk_size) - 1; -+ bmtd.pg_size = mtd->writesize; -+ bmtd.pg_shift = ffs(bmtd.pg_size) - 1; -+ bmtd.total_blks = mtd->size >> bmtd.blk_shift; -+ -+ bmtd.data_buf = kzalloc(bmtd.pg_size + bmtd.mtd->oobsize, GFP_KERNEL); -+ if (!bmtd.data_buf) { -+ pr_info("nand: FATAL ERR: allocate buffer failed!\n"); -+ ret = -1; -+ goto error; -+ } -+ -+ memset(bmtd.data_buf, 0xff, bmtd.pg_size + bmtd.mtd->oobsize); -+ -+ ret = bmtd.ops->init(np); -+ if (ret) -+ goto error; -+ -+ mtk_bmt_add_debugfs(); -+ return 0; -+ -+error: -+ mtk_bmt_detach(mtd); -+ return ret; -+} -+ -+ -+MODULE_LICENSE("GPL"); -+MODULE_AUTHOR("Xiangsheng Hou , Felix Fietkau "); -+MODULE_DESCRIPTION("Bad Block mapping management v2 for MediaTek NAND Flash Driver"); -+ -diff --git a/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt.h b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt.h -new file mode 100644 -index 000000000000..dff1f28c8171 ---- /dev/null -+++ b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt.h -@@ -0,0 +1,131 @@ -+#ifndef __MTK_BMT_PRIV_H -+#define __MTK_BMT_PRIV_H -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#define MAIN_SIGNATURE_OFFSET 0 -+#define OOB_SIGNATURE_OFFSET 1 -+ -+#define BBT_LOG(fmt, ...) pr_debug("[BBT][%s|%d] "fmt"\n", __func__, __LINE__, ##__VA_ARGS__) -+ -+struct mtk_bmt_ops { -+ char *sig; -+ unsigned int sig_len; -+ int (*init)(struct device_node *np); -+ bool (*remap_block)(u16 block, u16 mapped_block, int copy_len); -+ void (*unmap_block)(u16 block); -+ int (*get_mapping_block)(int block); -+ int (*debug)(void *data, u64 val); -+}; -+ -+struct bbbt; -+struct nmbm_instance; -+ -+struct bmt_desc { -+ struct mtd_info *mtd; -+ unsigned char *bbt_buf; -+ unsigned char *data_buf; -+ -+ int (*_read_oob) (struct mtd_info *mtd, loff_t from, -+ struct mtd_oob_ops *ops); -+ int (*_write_oob) (struct mtd_info *mtd, loff_t to, -+ struct mtd_oob_ops *ops); -+ int (*_erase) (struct mtd_info *mtd, struct erase_info *instr); -+ int (*_block_isbad) (struct mtd_info *mtd, loff_t ofs); -+ int (*_block_markbad) (struct mtd_info *mtd, loff_t ofs); -+ -+ const struct mtk_bmt_ops *ops; -+ -+ union { -+ struct bbbt *bbt; -+ struct nmbm_instance *ni; -+ }; -+ -+ struct dentry *debugfs_dir; -+ -+ u32 table_size; -+ u32 pg_size; -+ u32 blk_size; -+ u16 pg_shift; -+ u16 blk_shift; -+ /* bbt logical address */ -+ u16 pool_lba; -+ /* bbt physical address */ -+ u16 pool_pba; -+ /* Maximum count of bad blocks that the vendor guaranteed */ -+ u16 bb_max; -+ /* Total blocks of the Nand Chip */ -+ u16 total_blks; -+ /* The block(n) BMT is located at (bmt_tbl[n]) */ -+ u16 bmt_blk_idx; -+ /* How many pages needs to store 'struct bbbt' */ -+ u32 bmt_pgs; -+ -+ const __be32 *remap_range; -+ int remap_range_len; -+ -+ /* to compensate for driver level remapping */ -+ u8 oob_offset; -+}; -+ -+extern struct bmt_desc bmtd; -+extern const struct mtk_bmt_ops mtk_bmt_v2_ops; -+extern const struct mtk_bmt_ops mtk_bmt_bbt_ops; -+extern const struct mtk_bmt_ops mtk_bmt_nmbm_ops; -+ -+static inline u32 blk_pg(u16 block) -+{ -+ return (u32)(block << (bmtd.blk_shift - bmtd.pg_shift)); -+} -+ -+static inline int -+bbt_nand_read(u32 page, unsigned char *dat, int dat_len, -+ unsigned char *fdm, int fdm_len) -+{ -+ struct mtd_oob_ops ops = { -+ .mode = MTD_OPS_PLACE_OOB, -+ .ooboffs = bmtd.oob_offset, -+ .oobbuf = fdm, -+ .ooblen = fdm_len, -+ .datbuf = dat, -+ .len = dat_len, -+ }; -+ -+ return bmtd._read_oob(bmtd.mtd, page << bmtd.pg_shift, &ops); -+} -+ -+static inline int bbt_nand_erase(u16 block) -+{ -+ struct mtd_info *mtd = bmtd.mtd; -+ struct erase_info instr = { -+ .addr = (loff_t)block << bmtd.blk_shift, -+ .len = bmtd.blk_size, -+ }; -+ -+ return bmtd._erase(mtd, &instr); -+} -+ -+static inline int write_bmt(u16 block, unsigned char *dat) -+{ -+ struct mtd_oob_ops ops = { -+ .mode = MTD_OPS_PLACE_OOB, -+ .ooboffs = OOB_SIGNATURE_OFFSET + bmtd.oob_offset, -+ .oobbuf = bmtd.ops->sig, -+ .ooblen = bmtd.ops->sig_len, -+ .datbuf = dat, -+ .len = bmtd.bmt_pgs << bmtd.pg_shift, -+ }; -+ loff_t addr = (loff_t)block << bmtd.blk_shift; -+ -+ return bmtd._write_oob(bmtd.mtd, addr, &ops); -+} -+ -+int bbt_nand_copy(u16 dest_blk, u16 src_blk, loff_t max_offset); -+bool mapping_block_in_range(int block, int *start, int *end); -+ -+#endif -diff --git a/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_bbt.c b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_bbt.c -new file mode 100644 -index 000000000000..519e1ed70c7b ---- /dev/null -+++ b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_bbt.c -@@ -0,0 +1,203 @@ -+/* -+ * Copyright (c) 2017 MediaTek Inc. -+ * Author: Xiangsheng Hou -+ * Copyright (c) 2020-2022 Felix Fietkau -+ * -+ * This program is free software; you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 2 as -+ * published by the Free Software Foundation. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ */ -+ -+#include -+#include -+#include "mtk_bmt.h" -+ -+static bool -+bbt_block_is_bad(u16 block) -+{ -+ u8 cur = bmtd.bbt_buf[block / 4]; -+ -+ return cur & (3 << ((block % 4) * 2)); -+} -+ -+static void -+bbt_set_block_state(u16 block, bool bad) -+{ -+ u8 mask = (3 << ((block % 4) * 2)); -+ -+ if (bad) -+ bmtd.bbt_buf[block / 4] |= mask; -+ else -+ bmtd.bbt_buf[block / 4] &= ~mask; -+ -+ bbt_nand_erase(bmtd.bmt_blk_idx); -+ write_bmt(bmtd.bmt_blk_idx, bmtd.bbt_buf); -+} -+ -+static int -+get_mapping_block_index_bbt(int block) -+{ -+ int start, end, ofs; -+ int bad_blocks = 0; -+ int i; -+ -+ if (!mapping_block_in_range(block, &start, &end)) -+ return block; -+ -+ start >>= bmtd.blk_shift; -+ end >>= bmtd.blk_shift; -+ /* skip bad blocks within the mapping range */ -+ ofs = block - start; -+ for (i = start; i < end; i++) { -+ if (bbt_block_is_bad(i)) -+ bad_blocks++; -+ else if (ofs) -+ ofs--; -+ else -+ break; -+ } -+ -+ if (i < end) -+ return i; -+ -+ /* when overflowing, remap remaining blocks to bad ones */ -+ for (i = end - 1; bad_blocks > 0; i--) { -+ if (!bbt_block_is_bad(i)) -+ continue; -+ -+ bad_blocks--; -+ if (bad_blocks <= ofs) -+ return i; -+ } -+ -+ return block; -+} -+ -+static bool remap_block_bbt(u16 block, u16 mapped_blk, int copy_len) -+{ -+ int start, end; -+ u16 new_blk; -+ -+ if (!mapping_block_in_range(block, &start, &end)) -+ return false; -+ -+ bbt_set_block_state(mapped_blk, true); -+ -+ new_blk = get_mapping_block_index_bbt(block); -+ bbt_nand_erase(new_blk); -+ if (copy_len > 0) -+ bbt_nand_copy(new_blk, mapped_blk, copy_len); -+ -+ return true; -+} -+ -+static void -+unmap_block_bbt(u16 block) -+{ -+ bbt_set_block_state(block, false); -+} -+ -+static int -+mtk_bmt_read_bbt(void) -+{ -+ u8 oob_buf[8]; -+ int i; -+ -+ for (i = bmtd.total_blks - 1; i >= bmtd.total_blks - 5; i--) { -+ u32 page = i << (bmtd.blk_shift - bmtd.pg_shift); -+ -+ if (bbt_nand_read(page, bmtd.bbt_buf, bmtd.pg_size, -+ oob_buf, sizeof(oob_buf))) { -+ pr_info("read_bbt: could not read block %d\n", i); -+ continue; -+ } -+ -+ if (oob_buf[0] != 0xff) { -+ pr_info("read_bbt: bad block at %d\n", i); -+ continue; -+ } -+ -+ if (memcmp(&oob_buf[1], "mtknand", 7) != 0) { -+ pr_info("read_bbt: signature mismatch in block %d\n", i); -+ print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, oob_buf, 8, 1); -+ continue; -+ } -+ -+ pr_info("read_bbt: found bbt at block %d\n", i); -+ bmtd.bmt_blk_idx = i; -+ return 0; -+ } -+ -+ return -EIO; -+} -+ -+ -+static int -+mtk_bmt_init_bbt(struct device_node *np) -+{ -+ int buf_size = round_up(bmtd.total_blks >> 2, bmtd.blk_size); -+ int ret; -+ -+ bmtd.bbt_buf = kmalloc(buf_size, GFP_KERNEL); -+ if (!bmtd.bbt_buf) -+ return -ENOMEM; -+ -+ memset(bmtd.bbt_buf, 0xff, buf_size); -+ bmtd.mtd->size -= 4 * bmtd.mtd->erasesize; -+ -+ ret = mtk_bmt_read_bbt(); -+ if (ret) -+ return ret; -+ -+ bmtd.bmt_pgs = buf_size / bmtd.pg_size; -+ -+ return 0; -+} -+ -+static int mtk_bmt_debug_bbt(void *data, u64 val) -+{ -+ char buf[5]; -+ int i, k; -+ -+ switch (val) { -+ case 0: -+ for (i = 0; i < bmtd.total_blks; i += 4) { -+ u8 cur = bmtd.bbt_buf[i / 4]; -+ -+ for (k = 0; k < 4; k++, cur >>= 2) -+ buf[k] = (cur & 3) ? 'B' : '.'; -+ -+ buf[4] = 0; -+ printk("[%06x] %s\n", i * bmtd.blk_size, buf); -+ } -+ break; -+ case 100: -+#if 0 -+ for (i = bmtd.bmt_blk_idx; i < bmtd.total_blks - 1; i++) -+ bbt_nand_erase(bmtd.bmt_blk_idx); -+#endif -+ -+ bmtd.bmt_blk_idx = bmtd.total_blks - 1; -+ bbt_nand_erase(bmtd.bmt_blk_idx); -+ write_bmt(bmtd.bmt_blk_idx, bmtd.bbt_buf); -+ break; -+ default: -+ break; -+ } -+ return 0; -+} -+ -+const struct mtk_bmt_ops mtk_bmt_bbt_ops = { -+ .sig = "mtknand", -+ .sig_len = 7, -+ .init = mtk_bmt_init_bbt, -+ .remap_block = remap_block_bbt, -+ .unmap_block = unmap_block_bbt, -+ .get_mapping_block = get_mapping_block_index_bbt, -+ .debug = mtk_bmt_debug_bbt, -+}; -diff --git a/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_nmbm.c b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_nmbm.c -new file mode 100644 -index 000000000000..a896e49ec047 ---- /dev/null -+++ b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_nmbm.c -@@ -0,0 +1,2348 @@ -+#include -+#include -+#include "mtk_bmt.h" -+ -+#define nlog_err(ni, ...) printk(KERN_ERR __VA_ARGS__) -+#define nlog_info(ni, ...) printk(KERN_INFO __VA_ARGS__) -+#define nlog_debug(ni, ...) printk(KERN_INFO __VA_ARGS__) -+#define nlog_warn(ni, ...) printk(KERN_WARNING __VA_ARGS__) -+ -+#define NMBM_MAGIC_SIGNATURE 0x304d4d4e /* NMM0 */ -+#define NMBM_MAGIC_INFO_TABLE 0x314d4d4e /* NMM1 */ -+ -+#define NMBM_VERSION_MAJOR_S 0 -+#define NMBM_VERSION_MAJOR_M 0xffff -+#define NMBM_VERSION_MINOR_S 16 -+#define NMBM_VERSION_MINOR_M 0xffff -+#define NMBM_VERSION_MAKE(major, minor) (((major) & NMBM_VERSION_MAJOR_M) | \ -+ (((minor) & NMBM_VERSION_MINOR_M) << \ -+ NMBM_VERSION_MINOR_S)) -+#define NMBM_VERSION_MAJOR_GET(ver) (((ver) >> NMBM_VERSION_MAJOR_S) & \ -+ NMBM_VERSION_MAJOR_M) -+#define NMBM_VERSION_MINOR_GET(ver) (((ver) >> NMBM_VERSION_MINOR_S) & \ -+ NMBM_VERSION_MINOR_M) -+ -+#define NMBM_BITMAP_UNIT_SIZE (sizeof(u32)) -+#define NMBM_BITMAP_BITS_PER_BLOCK 2 -+#define NMBM_BITMAP_BITS_PER_UNIT (8 * sizeof(u32)) -+#define NMBM_BITMAP_BLOCKS_PER_UNIT (NMBM_BITMAP_BITS_PER_UNIT / \ -+ NMBM_BITMAP_BITS_PER_BLOCK) -+ -+#define NMBM_SPARE_BLOCK_MULTI 1 -+#define NMBM_SPARE_BLOCK_DIV 2 -+#define NMBM_SPARE_BLOCK_MIN 2 -+ -+#define NMBM_MGMT_DIV 16 -+#define NMBM_MGMT_BLOCKS_MIN 32 -+ -+#define NMBM_TRY_COUNT 3 -+ -+#define BLOCK_ST_BAD 0 -+#define BLOCK_ST_NEED_REMAP 2 -+#define BLOCK_ST_GOOD 3 -+#define BLOCK_ST_MASK 3 -+ -+#define NMBM_VER_MAJOR 1 -+#define NMBM_VER_MINOR 0 -+#define NMBM_VER NMBM_VERSION_MAKE(NMBM_VER_MAJOR, \ -+ NMBM_VER_MINOR) -+ -+struct nmbm_header { -+ u32 magic; -+ u32 version; -+ u32 size; -+ u32 checksum; -+}; -+ -+struct nmbm_signature { -+ struct nmbm_header header; -+ uint64_t nand_size; -+ u32 block_size; -+ u32 page_size; -+ u32 spare_size; -+ u32 mgmt_start_pb; -+ u8 max_try_count; -+ u8 padding[3]; -+}; -+ -+struct nmbm_info_table_header { -+ struct nmbm_header header; -+ u32 write_count; -+ u32 state_table_off; -+ u32 mapping_table_off; -+ u32 padding; -+}; -+ -+struct nmbm_instance { -+ u32 rawpage_size; -+ u32 rawblock_size; -+ u32 rawchip_size; -+ -+ struct nmbm_signature signature; -+ -+ u8 *info_table_cache; -+ u32 info_table_size; -+ u32 info_table_spare_blocks; -+ struct nmbm_info_table_header info_table; -+ -+ u32 *block_state; -+ u32 block_state_changed; -+ u32 state_table_size; -+ -+ int32_t *block_mapping; -+ u32 block_mapping_changed; -+ u32 mapping_table_size; -+ -+ u8 *page_cache; -+ -+ int protected; -+ -+ u32 block_count; -+ u32 data_block_count; -+ -+ u32 mgmt_start_ba; -+ u32 main_table_ba; -+ u32 backup_table_ba; -+ u32 mapping_blocks_ba; -+ u32 mapping_blocks_top_ba; -+ u32 signature_ba; -+ -+ u32 max_ratio; -+ u32 max_reserved_blocks; -+ bool empty_page_ecc_ok; -+ bool force_create; -+}; -+ -+static inline u32 nmbm_crc32(u32 crcval, const void *buf, size_t size) -+{ -+ unsigned int chksz; -+ const unsigned char *p = buf; -+ -+ while (size) { -+ if (size > UINT_MAX) -+ chksz = UINT_MAX; -+ else -+ chksz = (uint)size; -+ -+ crcval = crc32_le(crcval, p, chksz); -+ size -= chksz; -+ p += chksz; -+ } -+ -+ return crcval; -+} -+/* -+ * nlog_table_creation - Print log of table creation event -+ * @ni: NMBM instance structure -+ * @main_table: whether the table is main info table -+ * @start_ba: start block address of the table -+ * @end_ba: block address after the end of the table -+ */ -+static void nlog_table_creation(struct nmbm_instance *ni, bool main_table, -+ uint32_t start_ba, uint32_t end_ba) -+{ -+ if (start_ba == end_ba - 1) -+ nlog_info(ni, "%s info table has been written to block %u\n", -+ main_table ? "Main" : "Backup", start_ba); -+ else -+ nlog_info(ni, "%s info table has been written to block %u-%u\n", -+ main_table ? "Main" : "Backup", start_ba, end_ba - 1); -+} -+ -+/* -+ * nlog_table_update - Print log of table update event -+ * @ni: NMBM instance structure -+ * @main_table: whether the table is main info table -+ * @start_ba: start block address of the table -+ * @end_ba: block address after the end of the table -+ */ -+static void nlog_table_update(struct nmbm_instance *ni, bool main_table, -+ uint32_t start_ba, uint32_t end_ba) -+{ -+ if (start_ba == end_ba - 1) -+ nlog_debug(ni, "%s info table has been updated in block %u\n", -+ main_table ? "Main" : "Backup", start_ba); -+ else -+ nlog_debug(ni, "%s info table has been updated in block %u-%u\n", -+ main_table ? "Main" : "Backup", start_ba, end_ba - 1); -+} -+ -+/* -+ * nlog_table_found - Print log of table found event -+ * @ni: NMBM instance structure -+ * @first_table: whether the table is first found info table -+ * @write_count: write count of the info table -+ * @start_ba: start block address of the table -+ * @end_ba: block address after the end of the table -+ */ -+static void nlog_table_found(struct nmbm_instance *ni, bool first_table, -+ uint32_t write_count, uint32_t start_ba, -+ uint32_t end_ba) -+{ -+ if (start_ba == end_ba - 1) -+ nlog_info(ni, "%s info table with writecount %u found in block %u\n", -+ first_table ? "First" : "Second", write_count, -+ start_ba); -+ else -+ nlog_info(ni, "%s info table with writecount %u found in block %u-%u\n", -+ first_table ? "First" : "Second", write_count, -+ start_ba, end_ba - 1); -+} -+ -+/*****************************************************************************/ -+/* Address conversion functions */ -+/*****************************************************************************/ -+ -+/* -+ * ba2addr - Convert a block address to linear address -+ * @ni: NMBM instance structure -+ * @ba: Block address -+ */ -+static uint64_t ba2addr(struct nmbm_instance *ni, uint32_t ba) -+{ -+ return (uint64_t)ba << bmtd.blk_shift; -+} -+/* -+ * size2blk - Get minimum required blocks for storing specific size of data -+ * @ni: NMBM instance structure -+ * @size: size for storing -+ */ -+static uint32_t size2blk(struct nmbm_instance *ni, uint64_t size) -+{ -+ return (size + bmtd.blk_size - 1) >> bmtd.blk_shift; -+} -+ -+/*****************************************************************************/ -+/* High level NAND chip APIs */ -+/*****************************************************************************/ -+ -+/* -+ * nmbm_read_phys_page - Read page with retry -+ * @ni: NMBM instance structure -+ * @addr: linear address where the data will be read from -+ * @data: the main data to be read -+ * @oob: the oob data to be read -+ * -+ * Read a page for at most NMBM_TRY_COUNT times. -+ * -+ * Return 0 for success, positive value for corrected bitflip count, -+ * -EBADMSG for ecc error, other negative values for other errors -+ */ -+static int nmbm_read_phys_page(struct nmbm_instance *ni, uint64_t addr, -+ void *data, void *oob) -+{ -+ int tries, ret; -+ -+ for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { -+ struct mtd_oob_ops ops = { -+ .mode = MTD_OPS_PLACE_OOB, -+ .oobbuf = oob, -+ .datbuf = data, -+ }; -+ -+ if (data) -+ ops.len = bmtd.pg_size; -+ if (oob) -+ ops.ooblen = mtd_oobavail(bmtd.mtd, &ops); -+ -+ ret = bmtd._read_oob(bmtd.mtd, addr, &ops); -+ if (ret == -EUCLEAN) -+ return min_t(u32, bmtd.mtd->bitflip_threshold + 1, -+ bmtd.mtd->ecc_strength); -+ if (ret >= 0) -+ return 0; -+ } -+ -+ if (ret != -EBADMSG) -+ nlog_err(ni, "Page read failed at address 0x%08llx\n", addr); -+ -+ return ret; -+} -+ -+/* -+ * nmbm_write_phys_page - Write page with retry -+ * @ni: NMBM instance structure -+ * @addr: linear address where the data will be written to -+ * @data: the main data to be written -+ * @oob: the oob data to be written -+ * -+ * Write a page for at most NMBM_TRY_COUNT times. -+ */ -+static bool nmbm_write_phys_page(struct nmbm_instance *ni, uint64_t addr, -+ const void *data, const void *oob) -+{ -+ int tries, ret; -+ -+ for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { -+ struct mtd_oob_ops ops = { -+ .mode = MTD_OPS_PLACE_OOB, -+ .oobbuf = (void *)oob, -+ .datbuf = (void *)data, -+ }; -+ -+ if (data) -+ ops.len = bmtd.pg_size; -+ if (oob) -+ ops.ooblen = mtd_oobavail(bmtd.mtd, &ops); -+ -+ ret = bmtd._write_oob(bmtd.mtd, addr, &ops); -+ if (!ret) -+ return true; -+ } -+ -+ nlog_err(ni, "Page write failed at address 0x%08llx\n", addr); -+ -+ return false; -+} -+ -+/* -+ * nmbm_erase_phys_block - Erase a block with retry -+ * @ni: NMBM instance structure -+ * @addr: Linear address -+ * -+ * Erase a block for at most NMBM_TRY_COUNT times. -+ */ -+static bool nmbm_erase_phys_block(struct nmbm_instance *ni, uint64_t addr) -+{ -+ int tries, ret; -+ -+ for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { -+ struct erase_info ei = { -+ .addr = addr, -+ .len = bmtd.mtd->erasesize, -+ }; -+ -+ ret = bmtd._erase(bmtd.mtd, &ei); -+ if (!ret) -+ return true; -+ } -+ -+ nlog_err(ni, "Block erasure failed at address 0x%08llx\n", addr); -+ -+ return false; -+} -+ -+/* -+ * nmbm_check_bad_phys_block - Check whether a block is marked bad in OOB -+ * @ni: NMBM instance structure -+ * @ba: block address -+ */ -+static bool nmbm_check_bad_phys_block(struct nmbm_instance *ni, uint32_t ba) -+{ -+ uint64_t addr = ba2addr(ni, ba); -+ -+ return bmtd._block_isbad(bmtd.mtd, addr); -+} -+ -+/* -+ * nmbm_mark_phys_bad_block - Mark a block bad -+ * @ni: NMBM instance structure -+ * @addr: Linear address -+ */ -+static int nmbm_mark_phys_bad_block(struct nmbm_instance *ni, uint32_t ba) -+{ -+ uint64_t addr = ba2addr(ni, ba); -+ -+ nlog_info(ni, "Block %u [0x%08llx] will be marked bad\n", ba, addr); -+ -+ return bmtd._block_markbad(bmtd.mtd, addr); -+} -+ -+/*****************************************************************************/ -+/* NMBM related functions */ -+/*****************************************************************************/ -+ -+/* -+ * nmbm_check_header - Check whether a NMBM structure is valid -+ * @data: pointer to a NMBM structure with a NMBM header at beginning -+ * @size: Size of the buffer pointed by @header -+ * -+ * The size of the NMBM structure may be larger than NMBM header, -+ * e.g. block mapping table and block state table. -+ */ -+static bool nmbm_check_header(const void *data, uint32_t size) -+{ -+ const struct nmbm_header *header = data; -+ struct nmbm_header nhdr; -+ uint32_t new_checksum; -+ -+ /* -+ * Make sure expected structure size is equal or smaller than -+ * buffer size. -+ */ -+ if (header->size > size) -+ return false; -+ -+ memcpy(&nhdr, data, sizeof(nhdr)); -+ -+ nhdr.checksum = 0; -+ new_checksum = nmbm_crc32(0, &nhdr, sizeof(nhdr)); -+ if (header->size > sizeof(nhdr)) -+ new_checksum = nmbm_crc32(new_checksum, -+ (const uint8_t *)data + sizeof(nhdr), -+ header->size - sizeof(nhdr)); -+ -+ if (header->checksum != new_checksum) -+ return false; -+ -+ return true; -+} -+ -+/* -+ * nmbm_update_checksum - Update checksum of a NMBM structure -+ * @header: pointer to a NMBM structure with a NMBM header at beginning -+ * -+ * The size of the NMBM structure must be specified by @header->size -+ */ -+static void nmbm_update_checksum(struct nmbm_header *header) -+{ -+ header->checksum = 0; -+ header->checksum = nmbm_crc32(0, header, header->size); -+} -+ -+/* -+ * nmbm_get_spare_block_count - Calculate number of blocks should be reserved -+ * @block_count: number of blocks of data -+ * -+ * Calculate number of blocks should be reserved for data -+ */ -+static uint32_t nmbm_get_spare_block_count(uint32_t block_count) -+{ -+ uint32_t val; -+ -+ val = (block_count + NMBM_SPARE_BLOCK_DIV / 2) / NMBM_SPARE_BLOCK_DIV; -+ val *= NMBM_SPARE_BLOCK_MULTI; -+ -+ if (val < NMBM_SPARE_BLOCK_MIN) -+ val = NMBM_SPARE_BLOCK_MIN; -+ -+ return val; -+} -+ -+/* -+ * nmbm_get_block_state_raw - Get state of a block from raw block state table -+ * @block_state: pointer to raw block state table (bitmap) -+ * @ba: block address -+ */ -+static uint32_t nmbm_get_block_state_raw(u32 *block_state, -+ uint32_t ba) -+{ -+ uint32_t unit, shift; -+ -+ unit = ba / NMBM_BITMAP_BLOCKS_PER_UNIT; -+ shift = (ba % NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_BITS_PER_BLOCK; -+ -+ return (block_state[unit] >> shift) & BLOCK_ST_MASK; -+} -+ -+/* -+ * nmbm_get_block_state - Get state of a block from block state table -+ * @ni: NMBM instance structure -+ * @ba: block address -+ */ -+static uint32_t nmbm_get_block_state(struct nmbm_instance *ni, uint32_t ba) -+{ -+ return nmbm_get_block_state_raw(ni->block_state, ba); -+} -+ -+/* -+ * nmbm_set_block_state - Set state of a block to block state table -+ * @ni: NMBM instance structure -+ * @ba: block address -+ * @state: block state -+ * -+ * Set state of a block. If the block state changed, ni->block_state_changed -+ * will be increased. -+ */ -+static bool nmbm_set_block_state(struct nmbm_instance *ni, uint32_t ba, -+ uint32_t state) -+{ -+ uint32_t unit, shift, orig; -+ u32 uv; -+ -+ unit = ba / NMBM_BITMAP_BLOCKS_PER_UNIT; -+ shift = (ba % NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_BITS_PER_BLOCK; -+ -+ orig = (ni->block_state[unit] >> shift) & BLOCK_ST_MASK; -+ state &= BLOCK_ST_MASK; -+ -+ uv = ni->block_state[unit] & (~(BLOCK_ST_MASK << shift)); -+ uv |= state << shift; -+ ni->block_state[unit] = uv; -+ -+ if (orig != state) { -+ ni->block_state_changed++; -+ return true; -+ } -+ -+ return false; -+} -+ -+/* -+ * nmbm_block_walk_asc - Skip specified number of good blocks, ascending addr. -+ * @ni: NMBM instance structure -+ * @ba: start physical block address -+ * @nba: return physical block address after walk -+ * @count: number of good blocks to be skipped -+ * @limit: highest block address allowed for walking -+ * -+ * Start from @ba, skipping any bad blocks, counting @count good blocks, and -+ * return the next good block address. -+ * -+ * If no enough good blocks counted while @limit reached, false will be returned. -+ * -+ * If @count == 0, nearest good block address will be returned. -+ * @limit is not counted in walking. -+ */ -+static bool nmbm_block_walk_asc(struct nmbm_instance *ni, uint32_t ba, -+ uint32_t *nba, uint32_t count, -+ uint32_t limit) -+{ -+ int32_t nblock = count; -+ -+ if (limit >= ni->block_count) -+ limit = ni->block_count - 1; -+ -+ while (ba < limit) { -+ if (nmbm_get_block_state(ni, ba) == BLOCK_ST_GOOD) -+ nblock--; -+ -+ if (nblock < 0) { -+ *nba = ba; -+ return true; -+ } -+ -+ ba++; -+ } -+ -+ return false; -+} -+ -+/* -+ * nmbm_block_walk_desc - Skip specified number of good blocks, descending addr -+ * @ni: NMBM instance structure -+ * @ba: start physical block address -+ * @nba: return physical block address after walk -+ * @count: number of good blocks to be skipped -+ * @limit: lowest block address allowed for walking -+ * -+ * Start from @ba, skipping any bad blocks, counting @count good blocks, and -+ * return the next good block address. -+ * -+ * If no enough good blocks counted while @limit reached, false will be returned. -+ * -+ * If @count == 0, nearest good block address will be returned. -+ * @limit is not counted in walking. -+ */ -+static bool nmbm_block_walk_desc(struct nmbm_instance *ni, uint32_t ba, -+ uint32_t *nba, uint32_t count, uint32_t limit) -+{ -+ int32_t nblock = count; -+ -+ if (limit >= ni->block_count) -+ limit = ni->block_count - 1; -+ -+ while (ba > limit) { -+ if (nmbm_get_block_state(ni, ba) == BLOCK_ST_GOOD) -+ nblock--; -+ -+ if (nblock < 0) { -+ *nba = ba; -+ return true; -+ } -+ -+ ba--; -+ } -+ -+ return false; -+} -+ -+/* -+ * nmbm_block_walk - Skip specified number of good blocks from curr. block addr -+ * @ni: NMBM instance structure -+ * @ascending: whether to walk ascending -+ * @ba: start physical block address -+ * @nba: return physical block address after walk -+ * @count: number of good blocks to be skipped -+ * @limit: highest/lowest block address allowed for walking -+ * -+ * Start from @ba, skipping any bad blocks, counting @count good blocks, and -+ * return the next good block address. -+ * -+ * If no enough good blocks counted while @limit reached, false will be returned. -+ * -+ * If @count == 0, nearest good block address will be returned. -+ * @limit can be set to negative if no limit required. -+ * @limit is not counted in walking. -+ */ -+static bool nmbm_block_walk(struct nmbm_instance *ni, bool ascending, -+ uint32_t ba, uint32_t *nba, int32_t count, -+ int32_t limit) -+{ -+ if (ascending) -+ return nmbm_block_walk_asc(ni, ba, nba, count, limit); -+ -+ return nmbm_block_walk_desc(ni, ba, nba, count, limit); -+} -+ -+/* -+ * nmbm_scan_badblocks - Scan and record all bad blocks -+ * @ni: NMBM instance structure -+ * -+ * Scan the entire lower NAND chip and record all bad blocks in to block state -+ * table. -+ */ -+static void nmbm_scan_badblocks(struct nmbm_instance *ni) -+{ -+ uint32_t ba; -+ -+ for (ba = 0; ba < ni->block_count; ba++) { -+ if (nmbm_check_bad_phys_block(ni, ba)) { -+ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); -+ nlog_info(ni, "Bad block %u [0x%08llx]\n", ba, -+ ba2addr(ni, ba)); -+ } -+ } -+} -+ -+/* -+ * nmbm_build_mapping_table - Build initial block mapping table -+ * @ni: NMBM instance structure -+ * -+ * The initial mapping table will be compatible with the stratage of -+ * factory production. -+ */ -+static void nmbm_build_mapping_table(struct nmbm_instance *ni) -+{ -+ uint32_t pb, lb; -+ -+ for (pb = 0, lb = 0; pb < ni->mgmt_start_ba; pb++) { -+ if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) -+ continue; -+ -+ /* Always map to the next good block */ -+ ni->block_mapping[lb++] = pb; -+ } -+ -+ ni->data_block_count = lb; -+ -+ /* Unusable/Management blocks */ -+ for (pb = lb; pb < ni->block_count; pb++) -+ ni->block_mapping[pb] = -1; -+} -+ -+/* -+ * nmbm_erase_block_and_check - Erase a block and check its usability -+ * @ni: NMBM instance structure -+ * @ba: block address to be erased -+ * -+ * Erase a block anc check its usability -+ * -+ * Return true if the block is usable, false if erasure failure or the block -+ * has too many bitflips. -+ */ -+static bool nmbm_erase_block_and_check(struct nmbm_instance *ni, uint32_t ba) -+{ -+ uint64_t addr, off; -+ bool success; -+ int ret; -+ -+ success = nmbm_erase_phys_block(ni, ba2addr(ni, ba)); -+ if (!success) -+ return false; -+ -+ if (!ni->empty_page_ecc_ok) -+ return true; -+ -+ /* Check every page to make sure there aren't too many bitflips */ -+ -+ addr = ba2addr(ni, ba); -+ -+ for (off = 0; off < bmtd.blk_size; off += bmtd.pg_size) { -+ ret = nmbm_read_phys_page(ni, addr + off, ni->page_cache, NULL); -+ if (ret == -EBADMSG) { -+ /* -+ * empty_page_ecc_ok means the empty page is -+ * still protected by ECC. So reading pages with ECC -+ * enabled and -EBADMSG means there are too many -+ * bitflips that can't be recovered, and the block -+ * containing the page should be marked bad. -+ */ -+ nlog_err(ni, -+ "Too many bitflips in empty page at 0x%llx\n", -+ addr + off); -+ return false; -+ } -+ } -+ -+ return true; -+} -+ -+/* -+ * nmbm_erase_range - Erase a range of blocks -+ * @ni: NMBM instance structure -+ * @ba: block address where the erasure will start -+ * @limit: top block address allowed for erasure -+ * -+ * Erase blocks within the specific range. Newly-found bad blocks will be -+ * marked. -+ * -+ * @limit is not counted into the allowed erasure address. -+ */ -+static void nmbm_erase_range(struct nmbm_instance *ni, uint32_t ba, -+ uint32_t limit) -+{ -+ bool success; -+ -+ while (ba < limit) { -+ if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) -+ goto next_block; -+ -+ /* Insurance to detect unexpected bad block marked by user */ -+ if (nmbm_check_bad_phys_block(ni, ba)) { -+ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); -+ goto next_block; -+ } -+ -+ success = nmbm_erase_block_and_check(ni, ba); -+ if (success) -+ goto next_block; -+ -+ nmbm_mark_phys_bad_block(ni, ba); -+ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); -+ -+ next_block: -+ ba++; -+ } -+} -+ -+/* -+ * nmbm_write_repeated_data - Write critical data to a block with retry -+ * @ni: NMBM instance structure -+ * @ba: block address where the data will be written to -+ * @data: the data to be written -+ * @size: size of the data -+ * -+ * Write data to every page of the block. Success only if all pages within -+ * this block have been successfully written. -+ * -+ * Make sure data size is not bigger than one page. -+ * -+ * This function will write and verify every page for at most -+ * NMBM_TRY_COUNT times. -+ */ -+static bool nmbm_write_repeated_data(struct nmbm_instance *ni, uint32_t ba, -+ const void *data, uint32_t size) -+{ -+ uint64_t addr, off; -+ bool success; -+ int ret; -+ -+ if (size > bmtd.pg_size) -+ return false; -+ -+ addr = ba2addr(ni, ba); -+ -+ for (off = 0; off < bmtd.blk_size; off += bmtd.pg_size) { -+ /* Prepare page data. fill 0xff to unused region */ -+ memcpy(ni->page_cache, data, size); -+ memset(ni->page_cache + size, 0xff, ni->rawpage_size - size); -+ -+ success = nmbm_write_phys_page(ni, addr + off, ni->page_cache, NULL); -+ if (!success) -+ return false; -+ -+ /* Verify the data just written. ECC error indicates failure */ -+ ret = nmbm_read_phys_page(ni, addr + off, ni->page_cache, NULL); -+ if (ret < 0) -+ return false; -+ -+ if (memcmp(ni->page_cache, data, size)) -+ return false; -+ } -+ -+ return true; -+} -+ -+/* -+ * nmbm_write_signature - Write signature to NAND chip -+ * @ni: NMBM instance structure -+ * @limit: top block address allowed for writing -+ * @signature: the signature to be written -+ * @signature_ba: the actual block address where signature is written to -+ * -+ * Write signature within a specific range, from chip bottom to limit. -+ * At most one block will be written. -+ * -+ * @limit is not counted into the allowed write address. -+ */ -+static bool nmbm_write_signature(struct nmbm_instance *ni, uint32_t limit, -+ const struct nmbm_signature *signature, -+ uint32_t *signature_ba) -+{ -+ uint32_t ba = ni->block_count - 1; -+ bool success; -+ -+ while (ba > limit) { -+ if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) -+ goto next_block; -+ -+ /* Insurance to detect unexpected bad block marked by user */ -+ if (nmbm_check_bad_phys_block(ni, ba)) { -+ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); -+ goto next_block; -+ } -+ -+ success = nmbm_erase_block_and_check(ni, ba); -+ if (!success) -+ goto skip_bad_block; -+ -+ success = nmbm_write_repeated_data(ni, ba, signature, -+ sizeof(*signature)); -+ if (success) { -+ *signature_ba = ba; -+ return true; -+ } -+ -+ skip_bad_block: -+ nmbm_mark_phys_bad_block(ni, ba); -+ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); -+ -+ next_block: -+ ba--; -+ }; -+ -+ return false; -+} -+ -+/* -+ * nmbn_read_data - Read data -+ * @ni: NMBM instance structure -+ * @addr: linear address where the data will be read from -+ * @data: the data to be read -+ * @size: the size of data -+ * -+ * Read data range. -+ * Every page will be tried for at most NMBM_TRY_COUNT times. -+ * -+ * Return 0 for success, positive value for corrected bitflip count, -+ * -EBADMSG for ecc error, other negative values for other errors -+ */ -+static int nmbn_read_data(struct nmbm_instance *ni, uint64_t addr, void *data, -+ uint32_t size) -+{ -+ uint64_t off = addr; -+ uint8_t *ptr = data; -+ uint32_t sizeremain = size, chunksize, leading; -+ int ret; -+ -+ while (sizeremain) { -+ leading = off & (bmtd.pg_size - 1); -+ chunksize = bmtd.pg_size - leading; -+ if (chunksize > sizeremain) -+ chunksize = sizeremain; -+ -+ if (chunksize == bmtd.pg_size) { -+ ret = nmbm_read_phys_page(ni, off - leading, ptr, NULL); -+ if (ret < 0) -+ return ret; -+ } else { -+ ret = nmbm_read_phys_page(ni, off - leading, -+ ni->page_cache, NULL); -+ if (ret < 0) -+ return ret; -+ -+ memcpy(ptr, ni->page_cache + leading, chunksize); -+ } -+ -+ off += chunksize; -+ ptr += chunksize; -+ sizeremain -= chunksize; -+ } -+ -+ return 0; -+} -+ -+/* -+ * nmbn_write_verify_data - Write data with validation -+ * @ni: NMBM instance structure -+ * @addr: linear address where the data will be written to -+ * @data: the data to be written -+ * @size: the size of data -+ * -+ * Write data and verify. -+ * Every page will be tried for at most NMBM_TRY_COUNT times. -+ */ -+static bool nmbn_write_verify_data(struct nmbm_instance *ni, uint64_t addr, -+ const void *data, uint32_t size) -+{ -+ uint64_t off = addr; -+ const uint8_t *ptr = data; -+ uint32_t sizeremain = size, chunksize, leading; -+ bool success; -+ int ret; -+ -+ while (sizeremain) { -+ leading = off & (bmtd.pg_size - 1); -+ chunksize = bmtd.pg_size - leading; -+ if (chunksize > sizeremain) -+ chunksize = sizeremain; -+ -+ /* Prepare page data. fill 0xff to unused region */ -+ memset(ni->page_cache, 0xff, ni->rawpage_size); -+ memcpy(ni->page_cache + leading, ptr, chunksize); -+ -+ success = nmbm_write_phys_page(ni, off - leading, -+ ni->page_cache, NULL); -+ if (!success) -+ return false; -+ -+ /* Verify the data just written. ECC error indicates failure */ -+ ret = nmbm_read_phys_page(ni, off - leading, ni->page_cache, NULL); -+ if (ret < 0) -+ return false; -+ -+ if (memcmp(ni->page_cache + leading, ptr, chunksize)) -+ return false; -+ -+ off += chunksize; -+ ptr += chunksize; -+ sizeremain -= chunksize; -+ } -+ -+ return true; -+} -+ -+/* -+ * nmbm_write_mgmt_range - Write management data into NAND within a range -+ * @ni: NMBM instance structure -+ * @addr: preferred start block address for writing -+ * @limit: highest block address allowed for writing -+ * @data: the data to be written -+ * @size: the size of data -+ * @actual_start_ba: actual start block address of data -+ * @actual_end_ba: block address after the end of data -+ * -+ * @limit is not counted into the allowed write address. -+ */ -+static bool nmbm_write_mgmt_range(struct nmbm_instance *ni, uint32_t ba, -+ uint32_t limit, const void *data, -+ uint32_t size, uint32_t *actual_start_ba, -+ uint32_t *actual_end_ba) -+{ -+ const uint8_t *ptr = data; -+ uint32_t sizeremain = size, chunksize; -+ bool success; -+ -+ while (sizeremain && ba < limit) { -+ chunksize = sizeremain; -+ if (chunksize > bmtd.blk_size) -+ chunksize = bmtd.blk_size; -+ -+ if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) -+ goto next_block; -+ -+ /* Insurance to detect unexpected bad block marked by user */ -+ if (nmbm_check_bad_phys_block(ni, ba)) { -+ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); -+ goto next_block; -+ } -+ -+ success = nmbm_erase_block_and_check(ni, ba); -+ if (!success) -+ goto skip_bad_block; -+ -+ success = nmbn_write_verify_data(ni, ba2addr(ni, ba), ptr, -+ chunksize); -+ if (!success) -+ goto skip_bad_block; -+ -+ if (sizeremain == size) -+ *actual_start_ba = ba; -+ -+ ptr += chunksize; -+ sizeremain -= chunksize; -+ -+ goto next_block; -+ -+ skip_bad_block: -+ nmbm_mark_phys_bad_block(ni, ba); -+ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); -+ -+ next_block: -+ ba++; -+ } -+ -+ if (sizeremain) -+ return false; -+ -+ *actual_end_ba = ba; -+ -+ return true; -+} -+ -+/* -+ * nmbm_generate_info_table_cache - Generate info table cache data -+ * @ni: NMBM instance structure -+ * -+ * Generate info table cache data to be written into flash. -+ */ -+static bool nmbm_generate_info_table_cache(struct nmbm_instance *ni) -+{ -+ bool changed = false; -+ -+ memset(ni->info_table_cache, 0xff, ni->info_table_size); -+ -+ memcpy(ni->info_table_cache + ni->info_table.state_table_off, -+ ni->block_state, ni->state_table_size); -+ -+ memcpy(ni->info_table_cache + ni->info_table.mapping_table_off, -+ ni->block_mapping, ni->mapping_table_size); -+ -+ ni->info_table.header.magic = NMBM_MAGIC_INFO_TABLE; -+ ni->info_table.header.version = NMBM_VER; -+ ni->info_table.header.size = ni->info_table_size; -+ -+ if (ni->block_state_changed || ni->block_mapping_changed) { -+ ni->info_table.write_count++; -+ changed = true; -+ } -+ -+ memcpy(ni->info_table_cache, &ni->info_table, sizeof(ni->info_table)); -+ -+ nmbm_update_checksum((struct nmbm_header *)ni->info_table_cache); -+ -+ return changed; -+} -+ -+/* -+ * nmbm_write_info_table - Write info table into NAND within a range -+ * @ni: NMBM instance structure -+ * @ba: preferred start block address for writing -+ * @limit: highest block address allowed for writing -+ * @actual_start_ba: actual start block address of info table -+ * @actual_end_ba: block address after the end of info table -+ * -+ * @limit is counted into the allowed write address. -+ */ -+static bool nmbm_write_info_table(struct nmbm_instance *ni, uint32_t ba, -+ uint32_t limit, uint32_t *actual_start_ba, -+ uint32_t *actual_end_ba) -+{ -+ return nmbm_write_mgmt_range(ni, ba, limit, ni->info_table_cache, -+ ni->info_table_size, actual_start_ba, -+ actual_end_ba); -+} -+ -+/* -+ * nmbm_mark_tables_clean - Mark info table `clean' -+ * @ni: NMBM instance structure -+ */ -+static void nmbm_mark_tables_clean(struct nmbm_instance *ni) -+{ -+ ni->block_state_changed = 0; -+ ni->block_mapping_changed = 0; -+} -+ -+/* -+ * nmbm_try_reserve_blocks - Reserve blocks with compromisation -+ * @ni: NMBM instance structure -+ * @ba: start physical block address -+ * @nba: return physical block address after reservation -+ * @count: number of good blocks to be skipped -+ * @min_count: minimum number of good blocks to be skipped -+ * @limit: highest/lowest block address allowed for walking -+ * -+ * Reserve specific blocks. If failed, try to reserve as many as possible. -+ */ -+static bool nmbm_try_reserve_blocks(struct nmbm_instance *ni, uint32_t ba, -+ uint32_t *nba, uint32_t count, -+ int32_t min_count, int32_t limit) -+{ -+ int32_t nblocks = count; -+ bool success; -+ -+ while (nblocks >= min_count) { -+ success = nmbm_block_walk(ni, true, ba, nba, nblocks, limit); -+ if (success) -+ return true; -+ -+ nblocks--; -+ } -+ -+ return false; -+} -+ -+/* -+ * nmbm_rebuild_info_table - Build main & backup info table from scratch -+ * @ni: NMBM instance structure -+ * @allow_no_gap: allow no spare blocks between two tables -+ */ -+static bool nmbm_rebuild_info_table(struct nmbm_instance *ni) -+{ -+ uint32_t table_start_ba, table_end_ba, next_start_ba; -+ uint32_t main_table_end_ba; -+ bool success; -+ -+ /* Set initial value */ -+ ni->main_table_ba = 0; -+ ni->backup_table_ba = 0; -+ ni->mapping_blocks_ba = ni->mapping_blocks_top_ba; -+ -+ /* Write main table */ -+ success = nmbm_write_info_table(ni, ni->mgmt_start_ba, -+ ni->mapping_blocks_top_ba, -+ &table_start_ba, &table_end_ba); -+ if (!success) { -+ /* Failed to write main table, data will be lost */ -+ nlog_err(ni, "Unable to write at least one info table!\n"); -+ nlog_err(ni, "Please save your data before power off!\n"); -+ ni->protected = 1; -+ return false; -+ } -+ -+ /* Main info table is successfully written, record its offset */ -+ ni->main_table_ba = table_start_ba; -+ main_table_end_ba = table_end_ba; -+ -+ /* Adjust mapping_blocks_ba */ -+ ni->mapping_blocks_ba = table_end_ba; -+ -+ nmbm_mark_tables_clean(ni); -+ -+ nlog_table_creation(ni, true, table_start_ba, table_end_ba); -+ -+ /* Reserve spare blocks for main info table. */ -+ success = nmbm_try_reserve_blocks(ni, table_end_ba, -+ &next_start_ba, -+ ni->info_table_spare_blocks, 0, -+ ni->mapping_blocks_top_ba - -+ size2blk(ni, ni->info_table_size)); -+ if (!success) { -+ /* There is no spare block. */ -+ nlog_debug(ni, "No room for backup info table\n"); -+ return true; -+ } -+ -+ /* Write backup info table. */ -+ success = nmbm_write_info_table(ni, next_start_ba, -+ ni->mapping_blocks_top_ba, -+ &table_start_ba, &table_end_ba); -+ if (!success) { -+ /* There is no enough blocks for backup table. */ -+ nlog_debug(ni, "No room for backup info table\n"); -+ return true; -+ } -+ -+ /* Backup table is successfully written, record its offset */ -+ ni->backup_table_ba = table_start_ba; -+ -+ /* Adjust mapping_blocks_off */ -+ ni->mapping_blocks_ba = table_end_ba; -+ -+ /* Erase spare blocks of main table to clean possible interference data */ -+ nmbm_erase_range(ni, main_table_end_ba, ni->backup_table_ba); -+ -+ nlog_table_creation(ni, false, table_start_ba, table_end_ba); -+ -+ return true; -+} -+ -+/* -+ * nmbm_rescue_single_info_table - Rescue when there is only one info table -+ * @ni: NMBM instance structure -+ * -+ * This function is called when there is only one info table exists. -+ * This function may fail if we can't write new info table -+ */ -+static bool nmbm_rescue_single_info_table(struct nmbm_instance *ni) -+{ -+ uint32_t table_start_ba, table_end_ba, write_ba; -+ bool success; -+ -+ /* Try to write new info table in front of existing table */ -+ success = nmbm_write_info_table(ni, ni->mgmt_start_ba, -+ ni->main_table_ba, -+ &table_start_ba, -+ &table_end_ba); -+ if (success) { -+ /* -+ * New table becomes the main table, existing table becomes -+ * the backup table. -+ */ -+ ni->backup_table_ba = ni->main_table_ba; -+ ni->main_table_ba = table_start_ba; -+ -+ nmbm_mark_tables_clean(ni); -+ -+ /* Erase spare blocks of main table to clean possible interference data */ -+ nmbm_erase_range(ni, table_end_ba, ni->backup_table_ba); -+ -+ nlog_table_creation(ni, true, table_start_ba, table_end_ba); -+ -+ return true; -+ } -+ -+ /* Try to reserve spare blocks for existing table */ -+ success = nmbm_try_reserve_blocks(ni, ni->mapping_blocks_ba, &write_ba, -+ ni->info_table_spare_blocks, 0, -+ ni->mapping_blocks_top_ba - -+ size2blk(ni, ni->info_table_size)); -+ if (!success) { -+ nlog_warn(ni, "Failed to rescue single info table\n"); -+ return false; -+ } -+ -+ /* Try to write new info table next to the existing table */ -+ while (write_ba >= ni->mapping_blocks_ba) { -+ success = nmbm_write_info_table(ni, write_ba, -+ ni->mapping_blocks_top_ba, -+ &table_start_ba, -+ &table_end_ba); -+ if (success) -+ break; -+ -+ write_ba--; -+ } -+ -+ if (success) { -+ /* Erase spare blocks of main table to clean possible interference data */ -+ nmbm_erase_range(ni, ni->mapping_blocks_ba, table_start_ba); -+ -+ /* New table becomes the backup table */ -+ ni->backup_table_ba = table_start_ba; -+ ni->mapping_blocks_ba = table_end_ba; -+ -+ nmbm_mark_tables_clean(ni); -+ -+ nlog_table_creation(ni, false, table_start_ba, table_end_ba); -+ -+ return true; -+ } -+ -+ nlog_warn(ni, "Failed to rescue single info table\n"); -+ return false; -+} -+ -+/* -+ * nmbm_update_single_info_table - Update specific one info table -+ * @ni: NMBM instance structure -+ */ -+static bool nmbm_update_single_info_table(struct nmbm_instance *ni, -+ bool update_main_table) -+{ -+ uint32_t write_start_ba, write_limit, table_start_ba, table_end_ba; -+ bool success; -+ -+ /* Determine the write range */ -+ if (update_main_table) { -+ write_start_ba = ni->main_table_ba; -+ write_limit = ni->backup_table_ba; -+ } else { -+ write_start_ba = ni->backup_table_ba; -+ write_limit = ni->mapping_blocks_top_ba; -+ } -+ -+ success = nmbm_write_info_table(ni, write_start_ba, write_limit, -+ &table_start_ba, &table_end_ba); -+ if (success) { -+ if (update_main_table) { -+ ni->main_table_ba = table_start_ba; -+ } else { -+ ni->backup_table_ba = table_start_ba; -+ ni->mapping_blocks_ba = table_end_ba; -+ } -+ -+ nmbm_mark_tables_clean(ni); -+ -+ nlog_table_update(ni, update_main_table, table_start_ba, -+ table_end_ba); -+ -+ return true; -+ } -+ -+ if (update_main_table) { -+ /* -+ * If failed to update main table, make backup table the new -+ * main table, and call nmbm_rescue_single_info_table() -+ */ -+ nlog_warn(ni, "Unable to update %s info table\n", -+ update_main_table ? "Main" : "Backup"); -+ -+ ni->main_table_ba = ni->backup_table_ba; -+ ni->backup_table_ba = 0; -+ return nmbm_rescue_single_info_table(ni); -+ } -+ -+ /* Only one table left */ -+ ni->mapping_blocks_ba = ni->backup_table_ba; -+ ni->backup_table_ba = 0; -+ -+ return false; -+} -+ -+/* -+ * nmbm_rescue_main_info_table - Rescue when failed to write main info table -+ * @ni: NMBM instance structure -+ * -+ * This function is called when main info table failed to be written, and -+ * backup info table exists. -+ */ -+static bool nmbm_rescue_main_info_table(struct nmbm_instance *ni) -+{ -+ uint32_t tmp_table_start_ba, tmp_table_end_ba, main_table_start_ba; -+ uint32_t main_table_end_ba, write_ba; -+ uint32_t info_table_erasesize = size2blk(ni, ni->info_table_size); -+ bool success; -+ -+ /* Try to reserve spare blocks for existing backup info table */ -+ success = nmbm_try_reserve_blocks(ni, ni->mapping_blocks_ba, &write_ba, -+ ni->info_table_spare_blocks, 0, -+ ni->mapping_blocks_top_ba - -+ info_table_erasesize); -+ if (!success) { -+ /* There is no spare block. Backup info table becomes the main table. */ -+ nlog_err(ni, "No room for temporary info table\n"); -+ ni->main_table_ba = ni->backup_table_ba; -+ ni->backup_table_ba = 0; -+ return true; -+ } -+ -+ /* Try to write temporary info table into spare unmapped blocks */ -+ while (write_ba >= ni->mapping_blocks_ba) { -+ success = nmbm_write_info_table(ni, write_ba, -+ ni->mapping_blocks_top_ba, -+ &tmp_table_start_ba, -+ &tmp_table_end_ba); -+ if (success) -+ break; -+ -+ write_ba--; -+ } -+ -+ if (!success) { -+ /* Backup info table becomes the main table */ -+ nlog_err(ni, "Failed to update main info table\n"); -+ ni->main_table_ba = ni->backup_table_ba; -+ ni->backup_table_ba = 0; -+ return true; -+ } -+ -+ /* Adjust mapping_blocks_off */ -+ ni->mapping_blocks_ba = tmp_table_end_ba; -+ -+ /* -+ * Now write main info table at the beginning of management area. -+ * This operation will generally destroy the original backup info -+ * table. -+ */ -+ success = nmbm_write_info_table(ni, ni->mgmt_start_ba, -+ tmp_table_start_ba, -+ &main_table_start_ba, -+ &main_table_end_ba); -+ if (!success) { -+ /* Temporary info table becomes the main table */ -+ ni->main_table_ba = tmp_table_start_ba; -+ ni->backup_table_ba = 0; -+ -+ nmbm_mark_tables_clean(ni); -+ -+ nlog_err(ni, "Failed to update main info table\n"); -+ -+ return true; -+ } -+ -+ /* Main info table has been successfully written, record its offset */ -+ ni->main_table_ba = main_table_start_ba; -+ -+ nmbm_mark_tables_clean(ni); -+ -+ nlog_table_creation(ni, true, main_table_start_ba, main_table_end_ba); -+ -+ /* -+ * Temporary info table becomes the new backup info table if it's -+ * not overwritten. -+ */ -+ if (main_table_end_ba <= tmp_table_start_ba) { -+ ni->backup_table_ba = tmp_table_start_ba; -+ -+ nlog_table_creation(ni, false, tmp_table_start_ba, -+ tmp_table_end_ba); -+ -+ return true; -+ } -+ -+ /* Adjust mapping_blocks_off */ -+ ni->mapping_blocks_ba = main_table_end_ba; -+ -+ /* Try to reserve spare blocks for new main info table */ -+ success = nmbm_try_reserve_blocks(ni, main_table_end_ba, &write_ba, -+ ni->info_table_spare_blocks, 0, -+ ni->mapping_blocks_top_ba - -+ info_table_erasesize); -+ if (!success) { -+ /* There is no spare block. Only main table exists. */ -+ nlog_err(ni, "No room for backup info table\n"); -+ ni->backup_table_ba = 0; -+ return true; -+ } -+ -+ /* Write new backup info table. */ -+ while (write_ba >= main_table_end_ba) { -+ success = nmbm_write_info_table(ni, write_ba, -+ ni->mapping_blocks_top_ba, -+ &tmp_table_start_ba, -+ &tmp_table_end_ba); -+ if (success) -+ break; -+ -+ write_ba--; -+ } -+ -+ if (!success) { -+ nlog_err(ni, "No room for backup info table\n"); -+ ni->backup_table_ba = 0; -+ return true; -+ } -+ -+ /* Backup info table has been successfully written, record its offset */ -+ ni->backup_table_ba = tmp_table_start_ba; -+ -+ /* Adjust mapping_blocks_off */ -+ ni->mapping_blocks_ba = tmp_table_end_ba; -+ -+ /* Erase spare blocks of main table to clean possible interference data */ -+ nmbm_erase_range(ni, main_table_end_ba, ni->backup_table_ba); -+ -+ nlog_table_creation(ni, false, tmp_table_start_ba, tmp_table_end_ba); -+ -+ return true; -+} -+ -+/* -+ * nmbm_update_info_table_once - Update info table once -+ * @ni: NMBM instance structure -+ * @force: force update -+ * -+ * Update both main and backup info table. Return true if at least one info -+ * table has been successfully written. -+ * This function only try to update info table once regard less of the result. -+ */ -+static bool nmbm_update_info_table_once(struct nmbm_instance *ni, bool force) -+{ -+ uint32_t table_start_ba, table_end_ba; -+ uint32_t main_table_limit; -+ bool success; -+ -+ /* Do nothing if there is no change */ -+ if (!nmbm_generate_info_table_cache(ni) && !force) -+ return true; -+ -+ /* Check whether both two tables exist */ -+ if (!ni->backup_table_ba) { -+ main_table_limit = ni->mapping_blocks_top_ba; -+ goto write_main_table; -+ } -+ -+ /* -+ * Write backup info table in its current range. -+ * Note that limit is set to mapping_blocks_top_off to provide as many -+ * spare blocks as possible for the backup table. If at last -+ * unmapped blocks are used by backup table, mapping_blocks_off will -+ * be adjusted. -+ */ -+ success = nmbm_write_info_table(ni, ni->backup_table_ba, -+ ni->mapping_blocks_top_ba, -+ &table_start_ba, &table_end_ba); -+ if (!success) { -+ /* -+ * There is nothing to do if failed to write backup table. -+ * Write the main table now. -+ */ -+ nlog_err(ni, "No room for backup table\n"); -+ ni->mapping_blocks_ba = ni->backup_table_ba; -+ ni->backup_table_ba = 0; -+ main_table_limit = ni->mapping_blocks_top_ba; -+ goto write_main_table; -+ } -+ -+ /* Backup table is successfully written, record its offset */ -+ ni->backup_table_ba = table_start_ba; -+ -+ /* Adjust mapping_blocks_off */ -+ ni->mapping_blocks_ba = table_end_ba; -+ -+ nmbm_mark_tables_clean(ni); -+ -+ /* The normal limit of main table */ -+ main_table_limit = ni->backup_table_ba; -+ -+ nlog_table_update(ni, false, table_start_ba, table_end_ba); -+ -+write_main_table: -+ if (!ni->main_table_ba) -+ goto rebuild_tables; -+ -+ /* Write main info table in its current range */ -+ success = nmbm_write_info_table(ni, ni->main_table_ba, -+ main_table_limit, &table_start_ba, -+ &table_end_ba); -+ if (!success) { -+ /* If failed to write main table, go rescue procedure */ -+ if (!ni->backup_table_ba) -+ goto rebuild_tables; -+ -+ return nmbm_rescue_main_info_table(ni); -+ } -+ -+ /* Main info table is successfully written, record its offset */ -+ ni->main_table_ba = table_start_ba; -+ -+ /* Adjust mapping_blocks_off */ -+ if (!ni->backup_table_ba) -+ ni->mapping_blocks_ba = table_end_ba; -+ -+ nmbm_mark_tables_clean(ni); -+ -+ nlog_table_update(ni, true, table_start_ba, table_end_ba); -+ -+ return true; -+ -+rebuild_tables: -+ return nmbm_rebuild_info_table(ni); -+} -+ -+/* -+ * nmbm_update_info_table - Update info table -+ * @ni: NMBM instance structure -+ * -+ * Update both main and backup info table. Return true if at least one table -+ * has been successfully written. -+ * This function will try to update info table repeatedly until no new bad -+ * block found during updating. -+ */ -+static bool nmbm_update_info_table(struct nmbm_instance *ni) -+{ -+ bool success; -+ -+ if (ni->protected) -+ return true; -+ -+ while (ni->block_state_changed || ni->block_mapping_changed) { -+ success = nmbm_update_info_table_once(ni, false); -+ if (!success) { -+ nlog_err(ni, "Failed to update info table\n"); -+ return false; -+ } -+ } -+ -+ return true; -+} -+ -+/* -+ * nmbm_map_block - Map a bad block to a unused spare block -+ * @ni: NMBM instance structure -+ * @lb: logic block addr to map -+ */ -+static bool nmbm_map_block(struct nmbm_instance *ni, uint32_t lb) -+{ -+ uint32_t pb; -+ bool success; -+ -+ if (ni->mapping_blocks_ba == ni->mapping_blocks_top_ba) { -+ nlog_warn(ni, "No spare unmapped blocks.\n"); -+ return false; -+ } -+ -+ success = nmbm_block_walk(ni, false, ni->mapping_blocks_top_ba, &pb, 0, -+ ni->mapping_blocks_ba); -+ if (!success) { -+ nlog_warn(ni, "No spare unmapped blocks.\n"); -+ nmbm_update_info_table(ni); -+ ni->mapping_blocks_top_ba = ni->mapping_blocks_ba; -+ return false; -+ } -+ -+ ni->block_mapping[lb] = pb; -+ ni->mapping_blocks_top_ba--; -+ ni->block_mapping_changed++; -+ -+ nlog_info(ni, "Logic block %u mapped to physical block %u\n", lb, pb); -+ -+ return true; -+} -+ -+/* -+ * nmbm_create_info_table - Create info table(s) -+ * @ni: NMBM instance structure -+ * -+ * This function assumes that the chip has no existing info table(s) -+ */ -+static bool nmbm_create_info_table(struct nmbm_instance *ni) -+{ -+ uint32_t lb; -+ bool success; -+ -+ /* Set initial mapping_blocks_top_off */ -+ success = nmbm_block_walk(ni, false, ni->signature_ba, -+ &ni->mapping_blocks_top_ba, 1, -+ ni->mgmt_start_ba); -+ if (!success) { -+ nlog_err(ni, "No room for spare blocks\n"); -+ return false; -+ } -+ -+ /* Generate info table cache */ -+ nmbm_generate_info_table_cache(ni); -+ -+ /* Write info table */ -+ success = nmbm_rebuild_info_table(ni); -+ if (!success) { -+ nlog_err(ni, "Failed to build info tables\n"); -+ return false; -+ } -+ -+ /* Remap bad block(s) at end of data area */ -+ for (lb = ni->data_block_count; lb < ni->mgmt_start_ba; lb++) { -+ success = nmbm_map_block(ni, lb); -+ if (!success) -+ break; -+ -+ ni->data_block_count++; -+ } -+ -+ /* If state table and/or mapping table changed, update info table. */ -+ success = nmbm_update_info_table(ni); -+ if (!success) -+ return false; -+ -+ return true; -+} -+ -+/* -+ * nmbm_create_new - Create NMBM on a new chip -+ * @ni: NMBM instance structure -+ */ -+static bool nmbm_create_new(struct nmbm_instance *ni) -+{ -+ bool success; -+ -+ /* Determine the boundary of management blocks */ -+ ni->mgmt_start_ba = ni->block_count * (NMBM_MGMT_DIV - ni->max_ratio) / NMBM_MGMT_DIV; -+ -+ if (ni->max_reserved_blocks && ni->block_count - ni->mgmt_start_ba > ni->max_reserved_blocks) -+ ni->mgmt_start_ba = ni->block_count - ni->max_reserved_blocks; -+ -+ nlog_info(ni, "NMBM management region starts at block %u [0x%08llx]\n", -+ ni->mgmt_start_ba, ba2addr(ni, ni->mgmt_start_ba)); -+ -+ /* Fill block state table & mapping table */ -+ nmbm_scan_badblocks(ni); -+ nmbm_build_mapping_table(ni); -+ -+ /* Write signature */ -+ ni->signature.header.magic = NMBM_MAGIC_SIGNATURE; -+ ni->signature.header.version = NMBM_VER; -+ ni->signature.header.size = sizeof(ni->signature); -+ ni->signature.nand_size = bmtd.total_blks << bmtd.blk_shift; -+ ni->signature.block_size = bmtd.blk_size; -+ ni->signature.page_size = bmtd.pg_size; -+ ni->signature.spare_size = bmtd.mtd->oobsize; -+ ni->signature.mgmt_start_pb = ni->mgmt_start_ba; -+ ni->signature.max_try_count = NMBM_TRY_COUNT; -+ nmbm_update_checksum(&ni->signature.header); -+ -+ success = nmbm_write_signature(ni, ni->mgmt_start_ba, -+ &ni->signature, &ni->signature_ba); -+ if (!success) { -+ nlog_err(ni, "Failed to write signature to a proper offset\n"); -+ return false; -+ } -+ -+ nlog_info(ni, "Signature has been written to block %u [0x%08llx]\n", -+ ni->signature_ba, ba2addr(ni, ni->signature_ba)); -+ -+ /* Write info table(s) */ -+ success = nmbm_create_info_table(ni); -+ if (success) { -+ nlog_info(ni, "NMBM has been successfully created\n"); -+ return true; -+ } -+ -+ return false; -+} -+ -+/* -+ * nmbm_check_info_table_header - Check if a info table header is valid -+ * @ni: NMBM instance structure -+ * @data: pointer to the info table header -+ */ -+static bool nmbm_check_info_table_header(struct nmbm_instance *ni, void *data) -+{ -+ struct nmbm_info_table_header *ifthdr = data; -+ -+ if (ifthdr->header.magic != NMBM_MAGIC_INFO_TABLE) -+ return false; -+ -+ if (ifthdr->header.size != ni->info_table_size) -+ return false; -+ -+ if (ifthdr->mapping_table_off - ifthdr->state_table_off < ni->state_table_size) -+ return false; -+ -+ if (ni->info_table_size - ifthdr->mapping_table_off < ni->mapping_table_size) -+ return false; -+ -+ return true; -+} -+ -+/* -+ * nmbm_check_info_table - Check if a whole info table is valid -+ * @ni: NMBM instance structure -+ * @start_ba: start block address of this table -+ * @end_ba: end block address of this table -+ * @data: pointer to the info table header -+ * @mapping_blocks_top_ba: return the block address of top remapped block -+ */ -+static bool nmbm_check_info_table(struct nmbm_instance *ni, uint32_t start_ba, -+ uint32_t end_ba, void *data, -+ uint32_t *mapping_blocks_top_ba) -+{ -+ struct nmbm_info_table_header *ifthdr = data; -+ int32_t *block_mapping = (int32_t *)((uintptr_t)data + ifthdr->mapping_table_off); -+ u32 *block_state = (u32 *)((uintptr_t)data + ifthdr->state_table_off); -+ uint32_t minimum_mapping_pb = ni->signature_ba; -+ uint32_t ba; -+ -+ for (ba = 0; ba < ni->data_block_count; ba++) { -+ if ((block_mapping[ba] >= ni->data_block_count && block_mapping[ba] < end_ba) || -+ block_mapping[ba] == ni->signature_ba) -+ return false; -+ -+ if (block_mapping[ba] >= end_ba && block_mapping[ba] < minimum_mapping_pb) -+ minimum_mapping_pb = block_mapping[ba]; -+ } -+ -+ for (ba = start_ba; ba < end_ba; ba++) { -+ if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) -+ continue; -+ -+ if (nmbm_get_block_state_raw(block_state, ba) != BLOCK_ST_GOOD) -+ return false; -+ } -+ -+ *mapping_blocks_top_ba = minimum_mapping_pb - 1; -+ -+ return true; -+} -+ -+/* -+ * nmbm_try_load_info_table - Try to load info table from a address -+ * @ni: NMBM instance structure -+ * @ba: start block address of the info table -+ * @eba: return the block address after end of the table -+ * @write_count: return the write count of this table -+ * @mapping_blocks_top_ba: return the block address of top remapped block -+ * @table_loaded: used to record whether ni->info_table has valid data -+ */ -+static bool nmbm_try_load_info_table(struct nmbm_instance *ni, uint32_t ba, -+ uint32_t *eba, uint32_t *write_count, -+ uint32_t *mapping_blocks_top_ba, -+ bool table_loaded) -+{ -+ struct nmbm_info_table_header *ifthdr = (void *)ni->info_table_cache; -+ uint8_t *off = ni->info_table_cache; -+ uint32_t limit = ba + size2blk(ni, ni->info_table_size); -+ uint32_t start_ba = 0, chunksize, sizeremain = ni->info_table_size; -+ bool success, checkhdr = true; -+ int ret; -+ -+ while (sizeremain && ba < limit) { -+ if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) -+ goto next_block; -+ -+ if (nmbm_check_bad_phys_block(ni, ba)) { -+ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); -+ goto next_block; -+ } -+ -+ chunksize = sizeremain; -+ if (chunksize > bmtd.blk_size) -+ chunksize = bmtd.blk_size; -+ -+ /* Assume block with ECC error has no info table data */ -+ ret = nmbn_read_data(ni, ba2addr(ni, ba), off, chunksize); -+ if (ret < 0) -+ goto skip_bad_block; -+ else if (ret > 0) -+ return false; -+ -+ if (checkhdr) { -+ success = nmbm_check_info_table_header(ni, off); -+ if (!success) -+ return false; -+ -+ start_ba = ba; -+ checkhdr = false; -+ } -+ -+ off += chunksize; -+ sizeremain -= chunksize; -+ -+ goto next_block; -+ -+ skip_bad_block: -+ /* Only mark bad in memory */ -+ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); -+ -+ next_block: -+ ba++; -+ } -+ -+ if (sizeremain) -+ return false; -+ -+ success = nmbm_check_header(ni->info_table_cache, ni->info_table_size); -+ if (!success) -+ return false; -+ -+ *eba = ba; -+ *write_count = ifthdr->write_count; -+ -+ success = nmbm_check_info_table(ni, start_ba, ba, ni->info_table_cache, -+ mapping_blocks_top_ba); -+ if (!success) -+ return false; -+ -+ if (!table_loaded || ifthdr->write_count > ni->info_table.write_count) { -+ memcpy(&ni->info_table, ifthdr, sizeof(ni->info_table)); -+ memcpy(ni->block_state, -+ (uint8_t *)ifthdr + ifthdr->state_table_off, -+ ni->state_table_size); -+ memcpy(ni->block_mapping, -+ (uint8_t *)ifthdr + ifthdr->mapping_table_off, -+ ni->mapping_table_size); -+ ni->info_table.write_count = ifthdr->write_count; -+ } -+ -+ return true; -+} -+ -+/* -+ * nmbm_search_info_table - Search info table from specific address -+ * @ni: NMBM instance structure -+ * @ba: start block address to search -+ * @limit: highest block address allowed for searching -+ * @table_start_ba: return the start block address of this table -+ * @table_end_ba: return the block address after end of this table -+ * @write_count: return the write count of this table -+ * @mapping_blocks_top_ba: return the block address of top remapped block -+ * @table_loaded: used to record whether ni->info_table has valid data -+ */ -+static bool nmbm_search_info_table(struct nmbm_instance *ni, uint32_t ba, -+ uint32_t limit, uint32_t *table_start_ba, -+ uint32_t *table_end_ba, -+ uint32_t *write_count, -+ uint32_t *mapping_blocks_top_ba, -+ bool table_loaded) -+{ -+ bool success; -+ -+ while (ba < limit - size2blk(ni, ni->info_table_size)) { -+ success = nmbm_try_load_info_table(ni, ba, table_end_ba, -+ write_count, -+ mapping_blocks_top_ba, -+ table_loaded); -+ if (success) { -+ *table_start_ba = ba; -+ return true; -+ } -+ -+ ba++; -+ } -+ -+ return false; -+} -+ -+/* -+ * nmbm_load_info_table - Load info table(s) from a chip -+ * @ni: NMBM instance structure -+ * @ba: start block address to search info table -+ * @limit: highest block address allowed for searching -+ */ -+static bool nmbm_load_info_table(struct nmbm_instance *ni, uint32_t ba, -+ uint32_t limit) -+{ -+ uint32_t main_table_end_ba, backup_table_end_ba, table_end_ba; -+ uint32_t main_mapping_blocks_top_ba, backup_mapping_blocks_top_ba; -+ uint32_t main_table_write_count, backup_table_write_count; -+ uint32_t i; -+ bool success; -+ -+ /* Set initial value */ -+ ni->main_table_ba = 0; -+ ni->backup_table_ba = 0; -+ ni->info_table.write_count = 0; -+ ni->mapping_blocks_top_ba = ni->signature_ba - 1; -+ ni->data_block_count = ni->signature.mgmt_start_pb; -+ -+ /* Find first info table */ -+ success = nmbm_search_info_table(ni, ba, limit, &ni->main_table_ba, -+ &main_table_end_ba, &main_table_write_count, -+ &main_mapping_blocks_top_ba, false); -+ if (!success) { -+ nlog_warn(ni, "No valid info table found\n"); -+ return false; -+ } -+ -+ table_end_ba = main_table_end_ba; -+ -+ nlog_table_found(ni, true, main_table_write_count, ni->main_table_ba, -+ main_table_end_ba); -+ -+ /* Find second info table */ -+ success = nmbm_search_info_table(ni, main_table_end_ba, limit, -+ &ni->backup_table_ba, &backup_table_end_ba, -+ &backup_table_write_count, &backup_mapping_blocks_top_ba, true); -+ if (!success) { -+ nlog_warn(ni, "Second info table not found\n"); -+ } else { -+ table_end_ba = backup_table_end_ba; -+ -+ nlog_table_found(ni, false, backup_table_write_count, -+ ni->backup_table_ba, backup_table_end_ba); -+ } -+ -+ /* Pick mapping_blocks_top_ba */ -+ if (!ni->backup_table_ba) { -+ ni->mapping_blocks_top_ba= main_mapping_blocks_top_ba; -+ } else { -+ if (main_table_write_count >= backup_table_write_count) -+ ni->mapping_blocks_top_ba = main_mapping_blocks_top_ba; -+ else -+ ni->mapping_blocks_top_ba = backup_mapping_blocks_top_ba; -+ } -+ -+ /* Set final mapping_blocks_ba */ -+ ni->mapping_blocks_ba = table_end_ba; -+ -+ /* Set final data_block_count */ -+ for (i = ni->signature.mgmt_start_pb; i > 0; i--) { -+ if (ni->block_mapping[i - 1] >= 0) { -+ ni->data_block_count = i; -+ break; -+ } -+ } -+ -+ /* Regenerate the info table cache from the final selected info table */ -+ nmbm_generate_info_table_cache(ni); -+ -+ /* -+ * If only one table exists, try to write another table. -+ * If two tables have different write count, try to update info table -+ */ -+ if (!ni->backup_table_ba) { -+ success = nmbm_rescue_single_info_table(ni); -+ } else if (main_table_write_count != backup_table_write_count) { -+ /* Mark state & mapping tables changed */ -+ ni->block_state_changed = 1; -+ ni->block_mapping_changed = 1; -+ -+ success = nmbm_update_single_info_table(ni, -+ main_table_write_count < backup_table_write_count); -+ } else { -+ success = true; -+ } -+ -+ /* -+ * If there is no spare unmapped blocks, or still only one table -+ * exists, set the chip to read-only -+ */ -+ if (ni->mapping_blocks_ba == ni->mapping_blocks_top_ba) { -+ nlog_warn(ni, "No spare unmapped blocks. Device is now read-only\n"); -+ ni->protected = 1; -+ } else if (!success) { -+ nlog_warn(ni, "Only one info table found. Device is now read-only\n"); -+ ni->protected = 1; -+ } -+ -+ return true; -+} -+ -+/* -+ * nmbm_load_existing - Load NMBM from a new chip -+ * @ni: NMBM instance structure -+ */ -+static bool nmbm_load_existing(struct nmbm_instance *ni) -+{ -+ bool success; -+ -+ /* Calculate the boundary of management blocks */ -+ ni->mgmt_start_ba = ni->signature.mgmt_start_pb; -+ -+ nlog_debug(ni, "NMBM management region starts at block %u [0x%08llx]\n", -+ ni->mgmt_start_ba, ba2addr(ni, ni->mgmt_start_ba)); -+ -+ /* Look for info table(s) */ -+ success = nmbm_load_info_table(ni, ni->mgmt_start_ba, -+ ni->signature_ba); -+ if (success) { -+ nlog_info(ni, "NMBM has been successfully attached\n"); -+ return true; -+ } -+ -+ if (!ni->force_create) { -+ printk("not creating NMBM table\n"); -+ return false; -+ } -+ -+ /* Fill block state table & mapping table */ -+ nmbm_scan_badblocks(ni); -+ nmbm_build_mapping_table(ni); -+ -+ /* Write info table(s) */ -+ success = nmbm_create_info_table(ni); -+ if (success) { -+ nlog_info(ni, "NMBM has been successfully created\n"); -+ return true; -+ } -+ -+ return false; -+} -+ -+/* -+ * nmbm_find_signature - Find signature in the lower NAND chip -+ * @ni: NMBM instance structure -+ * @signature_ba: used for storing block address of the signature -+ * @signature_ba: return the actual block address of signature block -+ * -+ * Find a valid signature from a specific range in the lower NAND chip, -+ * from bottom (highest address) to top (lowest address) -+ * -+ * Return true if found. -+ */ -+static bool nmbm_find_signature(struct nmbm_instance *ni, -+ struct nmbm_signature *signature, -+ uint32_t *signature_ba) -+{ -+ struct nmbm_signature sig; -+ uint64_t off, addr; -+ uint32_t block_count, ba, limit; -+ bool success; -+ int ret; -+ -+ /* Calculate top and bottom block address */ -+ block_count = bmtd.total_blks; -+ ba = block_count; -+ limit = (block_count / NMBM_MGMT_DIV) * (NMBM_MGMT_DIV - ni->max_ratio); -+ if (ni->max_reserved_blocks && block_count - limit > ni->max_reserved_blocks) -+ limit = block_count - ni->max_reserved_blocks; -+ -+ while (ba >= limit) { -+ ba--; -+ addr = ba2addr(ni, ba); -+ -+ if (nmbm_check_bad_phys_block(ni, ba)) -+ continue; -+ -+ /* Check every page. -+ * As long as at leaset one page contains valid signature, -+ * the block is treated as a valid signature block. -+ */ -+ for (off = 0; off < bmtd.blk_size; -+ off += bmtd.pg_size) { -+ ret = nmbn_read_data(ni, addr + off, &sig, -+ sizeof(sig)); -+ if (ret) -+ continue; -+ -+ /* Check for header size and checksum */ -+ success = nmbm_check_header(&sig, sizeof(sig)); -+ if (!success) -+ continue; -+ -+ /* Check for header magic */ -+ if (sig.header.magic == NMBM_MAGIC_SIGNATURE) { -+ /* Found it */ -+ memcpy(signature, &sig, sizeof(sig)); -+ *signature_ba = ba; -+ return true; -+ } -+ } -+ }; -+ -+ return false; -+} -+ -+/* -+ * nmbm_calc_structure_size - Calculate the instance structure size -+ * @nld: NMBM lower device structure -+ */ -+static size_t nmbm_calc_structure_size(void) -+{ -+ uint32_t state_table_size, mapping_table_size, info_table_size; -+ uint32_t block_count; -+ -+ block_count = bmtd.total_blks; -+ -+ /* Calculate info table size */ -+ state_table_size = ((block_count + NMBM_BITMAP_BLOCKS_PER_UNIT - 1) / -+ NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_UNIT_SIZE; -+ mapping_table_size = block_count * sizeof(int32_t); -+ -+ info_table_size = ALIGN(sizeof(struct nmbm_info_table_header), -+ bmtd.pg_size); -+ info_table_size += ALIGN(state_table_size, bmtd.pg_size); -+ info_table_size += ALIGN(mapping_table_size, bmtd.pg_size); -+ -+ return info_table_size + state_table_size + mapping_table_size + -+ sizeof(struct nmbm_instance); -+} -+ -+/* -+ * nmbm_init_structure - Initialize members of instance structure -+ * @ni: NMBM instance structure -+ */ -+static void nmbm_init_structure(struct nmbm_instance *ni) -+{ -+ uint32_t pages_per_block, blocks_per_chip; -+ uintptr_t ptr; -+ -+ pages_per_block = bmtd.blk_size / bmtd.pg_size; -+ blocks_per_chip = bmtd.total_blks; -+ -+ ni->rawpage_size = bmtd.pg_size + bmtd.mtd->oobsize; -+ ni->rawblock_size = pages_per_block * ni->rawpage_size; -+ ni->rawchip_size = blocks_per_chip * ni->rawblock_size; -+ -+ /* Calculate number of block this chip */ -+ ni->block_count = blocks_per_chip; -+ -+ /* Calculate info table size */ -+ ni->state_table_size = ((ni->block_count + NMBM_BITMAP_BLOCKS_PER_UNIT - 1) / -+ NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_UNIT_SIZE; -+ ni->mapping_table_size = ni->block_count * sizeof(*ni->block_mapping); -+ -+ ni->info_table_size = ALIGN(sizeof(ni->info_table), -+ bmtd.pg_size); -+ ni->info_table.state_table_off = ni->info_table_size; -+ -+ ni->info_table_size += ALIGN(ni->state_table_size, -+ bmtd.pg_size); -+ ni->info_table.mapping_table_off = ni->info_table_size; -+ -+ ni->info_table_size += ALIGN(ni->mapping_table_size, -+ bmtd.pg_size); -+ -+ ni->info_table_spare_blocks = nmbm_get_spare_block_count( -+ size2blk(ni, ni->info_table_size)); -+ -+ /* Assign memory to members */ -+ ptr = (uintptr_t)ni + sizeof(*ni); -+ -+ ni->info_table_cache = (void *)ptr; -+ ptr += ni->info_table_size; -+ -+ ni->block_state = (void *)ptr; -+ ptr += ni->state_table_size; -+ -+ ni->block_mapping = (void *)ptr; -+ ptr += ni->mapping_table_size; -+ -+ ni->page_cache = bmtd.data_buf; -+ -+ /* Initialize block state table */ -+ ni->block_state_changed = 0; -+ memset(ni->block_state, 0xff, ni->state_table_size); -+ -+ /* Initialize block mapping table */ -+ ni->block_mapping_changed = 0; -+} -+ -+/* -+ * nmbm_attach - Attach to a lower device -+ * @ni: NMBM instance structure -+ */ -+static int nmbm_attach(struct nmbm_instance *ni) -+{ -+ bool success; -+ -+ if (!ni) -+ return -EINVAL; -+ -+ /* Initialize NMBM instance */ -+ nmbm_init_structure(ni); -+ -+ success = nmbm_find_signature(ni, &ni->signature, &ni->signature_ba); -+ if (!success) { -+ if (!ni->force_create) { -+ nlog_err(ni, "Signature not found\n"); -+ return -ENODEV; -+ } -+ -+ success = nmbm_create_new(ni); -+ if (!success) -+ return -ENODEV; -+ -+ return 0; -+ } -+ -+ nlog_info(ni, "Signature found at block %u [0x%08llx]\n", -+ ni->signature_ba, ba2addr(ni, ni->signature_ba)); -+ -+ if (ni->signature.header.version != NMBM_VER) { -+ nlog_err(ni, "NMBM version %u.%u is not supported\n", -+ NMBM_VERSION_MAJOR_GET(ni->signature.header.version), -+ NMBM_VERSION_MINOR_GET(ni->signature.header.version)); -+ return -EINVAL; -+ } -+ -+ if (ni->signature.nand_size != bmtd.total_blks << bmtd.blk_shift || -+ ni->signature.block_size != bmtd.blk_size || -+ ni->signature.page_size != bmtd.pg_size || -+ ni->signature.spare_size != bmtd.mtd->oobsize) { -+ nlog_err(ni, "NMBM configuration mismatch\n"); -+ return -EINVAL; -+ } -+ -+ success = nmbm_load_existing(ni); -+ if (!success) -+ return -ENODEV; -+ -+ return 0; -+} -+ -+static bool remap_block_nmbm(u16 block, u16 mapped_block, int copy_len) -+{ -+ struct nmbm_instance *ni = bmtd.ni; -+ int new_block; -+ -+ if (block >= ni->data_block_count) -+ return false; -+ -+ nmbm_set_block_state(ni, mapped_block, BLOCK_ST_BAD); -+ if (!nmbm_map_block(ni, block)) -+ return false; -+ -+ new_block = ni->block_mapping[block]; -+ bbt_nand_erase(new_block); -+ if (copy_len > 0) -+ bbt_nand_copy(new_block, mapped_block, copy_len); -+ nmbm_update_info_table(ni); -+ -+ return true; -+} -+ -+static int get_mapping_block_index_nmbm(int block) -+{ -+ struct nmbm_instance *ni = bmtd.ni; -+ -+ if (block >= ni->data_block_count) -+ return -1; -+ -+ return ni->block_mapping[block]; -+} -+ -+static int mtk_bmt_init_nmbm(struct device_node *np) -+{ -+ struct nmbm_instance *ni; -+ int ret; -+ -+ ni = kzalloc(nmbm_calc_structure_size(), GFP_KERNEL); -+ if (!ni) -+ return -ENOMEM; -+ -+ bmtd.ni = ni; -+ -+ if (of_property_read_u32(np, "mediatek,bmt-max-ratio", &ni->max_ratio)) -+ ni->max_ratio = 1; -+ if (of_property_read_u32(np, "mediatek,bmt-max-reserved-blocks", -+ &ni->max_reserved_blocks)) -+ ni->max_reserved_blocks = 256; -+ if (of_property_read_bool(np, "mediatek,empty-page-ecc-protected")) -+ ni->empty_page_ecc_ok = true; -+ if (of_property_read_bool(np, "mediatek,bmt-force-create")) -+ ni->force_create = true; -+ -+ ret = nmbm_attach(ni); -+ if (ret) -+ goto out; -+ -+ bmtd.mtd->size = ni->data_block_count << bmtd.blk_shift; -+ -+ return 0; -+ -+out: -+ kfree(ni); -+ bmtd.ni = NULL; -+ -+ return ret; -+} -+ -+static int mtk_bmt_debug_nmbm(void *data, u64 val) -+{ -+ struct nmbm_instance *ni = bmtd.ni; -+ int i; -+ -+ switch (val) { -+ case 0: -+ for (i = 1; i < ni->data_block_count; i++) { -+ if (ni->block_mapping[i] < ni->mapping_blocks_ba) -+ continue; -+ -+ printk("remap [%x->%x]\n", i, ni->block_mapping[i]); -+ } -+ } -+ -+ return 0; -+} -+ -+static void unmap_block_nmbm(u16 block) -+{ -+ struct nmbm_instance *ni = bmtd.ni; -+ int start, offset; -+ int new_block; -+ -+ if (block >= ni->data_block_count) -+ return; -+ -+ start = block; -+ offset = 0; -+ while (ni->block_mapping[start] >= ni->mapping_blocks_ba) { -+ start--; -+ offset++; -+ if (start < 0) -+ return; -+ } -+ -+ if (!offset) -+ return; -+ -+ new_block = ni->block_mapping[start] + offset; -+ nmbm_set_block_state(ni, new_block, BLOCK_ST_GOOD); -+ ni->block_mapping[block] = new_block; -+ ni->block_mapping_changed++; -+ -+ new_block = ni->signature_ba - 1; -+ for (block = 0; block < ni->data_block_count; block++) { -+ int cur = ni->block_mapping[block]; -+ -+ if (cur < ni->mapping_blocks_ba) -+ continue; -+ -+ if (cur <= new_block) -+ new_block = cur - 1; -+ } -+ -+ ni->mapping_blocks_top_ba = new_block; -+ -+ nmbm_update_info_table(ni); -+} -+ -+const struct mtk_bmt_ops mtk_bmt_nmbm_ops = { -+ .init = mtk_bmt_init_nmbm, -+ .remap_block = remap_block_nmbm, -+ .unmap_block = unmap_block_nmbm, -+ .get_mapping_block = get_mapping_block_index_nmbm, -+ .debug = mtk_bmt_debug_nmbm, -+}; -diff --git a/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_v2.c b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_v2.c -new file mode 100644 -index 000000000000..2770376e98bb ---- /dev/null -+++ b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_v2.c -@@ -0,0 +1,513 @@ -+/* -+ * Copyright (c) 2017 MediaTek Inc. -+ * Author: Xiangsheng Hou -+ * Copyright (c) 2020-2022 Felix Fietkau -+ * -+ * This program is free software; you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 2 as -+ * published by the Free Software Foundation. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ */ -+ -+#include -+#include -+#include "mtk_bmt.h" -+ -+struct bbbt { -+ char signature[3]; -+ /* This version is used to distinguish the legacy and new algorithm */ -+#define BBMT_VERSION 2 -+ unsigned char version; -+ /* Below 2 tables will be written in SLC */ -+ u16 bb_tbl[]; -+}; -+ -+struct bbmt { -+ u16 block; -+#define NO_MAPPED 0 -+#define NORMAL_MAPPED 1 -+#define BMT_MAPPED 2 -+ u16 mapped; -+}; -+ -+/* Maximum 8k blocks */ -+#define BBPOOL_RATIO 2 -+#define BB_TABLE_MAX bmtd.table_size -+#define BMT_TABLE_MAX (BB_TABLE_MAX * BBPOOL_RATIO / 100) -+#define BMT_TBL_DEF_VAL 0x0 -+ -+static inline struct bbmt *bmt_tbl(struct bbbt *bbbt) -+{ -+ return (struct bbmt *)&bbbt->bb_tbl[bmtd.table_size]; -+} -+ -+static u16 find_valid_block(u16 block) -+{ -+ u8 fdm[4]; -+ int ret; -+ int loop = 0; -+ -+retry: -+ if (block >= bmtd.total_blks) -+ return 0; -+ -+ ret = bbt_nand_read(blk_pg(block), bmtd.data_buf, bmtd.pg_size, -+ fdm, sizeof(fdm)); -+ /* Read the 1st byte of FDM to judge whether it's a bad -+ * or not -+ */ -+ if (ret || fdm[0] != 0xff) { -+ pr_info("nand: found bad block 0x%x\n", block); -+ if (loop >= bmtd.bb_max) { -+ pr_info("nand: FATAL ERR: too many bad blocks!!\n"); -+ return 0; -+ } -+ -+ loop++; -+ block++; -+ goto retry; -+ } -+ -+ return block; -+} -+ -+/* Find out all bad blocks, and fill in the mapping table */ -+static int scan_bad_blocks(struct bbbt *bbt) -+{ -+ int i; -+ u16 block = 0; -+ -+ /* First time download, the block0 MUST NOT be a bad block, -+ * this is guaranteed by vendor -+ */ -+ bbt->bb_tbl[0] = 0; -+ -+ /* -+ * Construct the mapping table of Normal data area(non-PMT/BMTPOOL) -+ * G - Good block; B - Bad block -+ * --------------------------- -+ * physical |G|G|B|G|B|B|G|G|G|G|B|G|B| -+ * --------------------------- -+ * What bb_tbl[i] looks like: -+ * physical block(i): -+ * 0 1 2 3 4 5 6 7 8 9 a b c -+ * mapped block(bb_tbl[i]): -+ * 0 1 3 6 7 8 9 b ...... -+ * ATTENTION: -+ * If new bad block ocurred(n), search bmt_tbl to find -+ * a available block(x), and fill in the bb_tbl[n] = x; -+ */ -+ for (i = 1; i < bmtd.pool_lba; i++) { -+ bbt->bb_tbl[i] = find_valid_block(bbt->bb_tbl[i - 1] + 1); -+ BBT_LOG("bb_tbl[0x%x] = 0x%x", i, bbt->bb_tbl[i]); -+ if (bbt->bb_tbl[i] == 0) -+ return -1; -+ } -+ -+ /* Physical Block start Address of BMT pool */ -+ bmtd.pool_pba = bbt->bb_tbl[i - 1] + 1; -+ if (bmtd.pool_pba >= bmtd.total_blks - 2) { -+ pr_info("nand: FATAL ERR: Too many bad blocks!!\n"); -+ return -1; -+ } -+ -+ BBT_LOG("pool_pba=0x%x", bmtd.pool_pba); -+ i = 0; -+ block = bmtd.pool_pba; -+ /* -+ * The bmt table is used for runtime bad block mapping -+ * G - Good block; B - Bad block -+ * --------------------------- -+ * physical |G|G|B|G|B|B|G|G|G|G|B|G|B| -+ * --------------------------- -+ * block: 0 1 2 3 4 5 6 7 8 9 a b c -+ * What bmt_tbl[i] looks like in initial state: -+ * i: -+ * 0 1 2 3 4 5 6 7 -+ * bmt_tbl[i].block: -+ * 0 1 3 6 7 8 9 b -+ * bmt_tbl[i].mapped: -+ * N N N N N N N B -+ * N - Not mapped(Available) -+ * M - Mapped -+ * B - BMT -+ * ATTENTION: -+ * BMT always in the last valid block in pool -+ */ -+ while ((block = find_valid_block(block)) != 0) { -+ bmt_tbl(bbt)[i].block = block; -+ bmt_tbl(bbt)[i].mapped = NO_MAPPED; -+ BBT_LOG("bmt_tbl[%d].block = 0x%x", i, block); -+ block++; -+ i++; -+ } -+ -+ /* i - How many available blocks in pool, which is the length of bmt_tbl[] -+ * bmtd.bmt_blk_idx - bmt_tbl[bmtd.bmt_blk_idx].block => the BMT block -+ */ -+ bmtd.bmt_blk_idx = i - 1; -+ bmt_tbl(bbt)[bmtd.bmt_blk_idx].mapped = BMT_MAPPED; -+ -+ if (i < 1) { -+ pr_info("nand: FATAL ERR: no space to store BMT!!\n"); -+ return -1; -+ } -+ -+ pr_info("[BBT] %d available blocks in BMT pool\n", i); -+ -+ return 0; -+} -+ -+static bool is_valid_bmt(unsigned char *buf, unsigned char *fdm) -+{ -+ struct bbbt *bbt = (struct bbbt *)buf; -+ u8 *sig = (u8*)bbt->signature + MAIN_SIGNATURE_OFFSET; -+ -+ -+ if (memcmp(bbt->signature + MAIN_SIGNATURE_OFFSET, "BMT", 3) == 0 && -+ memcmp(fdm + OOB_SIGNATURE_OFFSET, "bmt", 3) == 0) { -+ if (bbt->version == BBMT_VERSION) -+ return true; -+ } -+ BBT_LOG("[BBT] BMT Version not match,upgrage preloader and uboot please! sig=%02x%02x%02x, fdm=%02x%02x%02x", -+ sig[0], sig[1], sig[2], -+ fdm[1], fdm[2], fdm[3]); -+ return false; -+} -+ -+static u16 get_bmt_index(struct bbmt *bmt) -+{ -+ int i = 0; -+ -+ while (bmt[i].block != BMT_TBL_DEF_VAL) { -+ if (bmt[i].mapped == BMT_MAPPED) -+ return i; -+ i++; -+ } -+ return 0; -+} -+ -+static int -+read_bmt(u16 block, unsigned char *dat, unsigned char *fdm, int fdm_len) -+{ -+ u32 len = bmtd.bmt_pgs << bmtd.pg_shift; -+ -+ return bbt_nand_read(blk_pg(block), dat, len, fdm, fdm_len); -+} -+ -+static struct bbbt *scan_bmt(u16 block) -+{ -+ u8 fdm[4]; -+ -+ if (block < bmtd.pool_lba) -+ return NULL; -+ -+ if (read_bmt(block, bmtd.bbt_buf, fdm, sizeof(fdm))) -+ return scan_bmt(block - 1); -+ -+ if (is_valid_bmt(bmtd.bbt_buf, fdm)) { -+ bmtd.bmt_blk_idx = get_bmt_index(bmt_tbl((struct bbbt *)bmtd.bbt_buf)); -+ if (bmtd.bmt_blk_idx == 0) { -+ pr_info("[BBT] FATAL ERR: bmt block index is wrong!\n"); -+ return NULL; -+ } -+ pr_info("[BBT] BMT.v2 is found at 0x%x\n", block); -+ return (struct bbbt *)bmtd.bbt_buf; -+ } else -+ return scan_bmt(block - 1); -+} -+ -+/* Write the Burner Bad Block Table to Nand Flash -+ * n - write BMT to bmt_tbl[n] -+ */ -+static u16 upload_bmt(struct bbbt *bbt, int n) -+{ -+ u16 block; -+ -+retry: -+ if (n < 0 || bmt_tbl(bbt)[n].mapped == NORMAL_MAPPED) { -+ pr_info("nand: FATAL ERR: no space to store BMT!\n"); -+ return (u16)-1; -+ } -+ -+ block = bmt_tbl(bbt)[n].block; -+ BBT_LOG("n = 0x%x, block = 0x%x", n, block); -+ if (bbt_nand_erase(block)) { -+ bmt_tbl(bbt)[n].block = 0; -+ /* erase failed, try the previous block: bmt_tbl[n - 1].block */ -+ n--; -+ goto retry; -+ } -+ -+ /* The signature offset is fixed set to 0, -+ * oob signature offset is fixed set to 1 -+ */ -+ memcpy(bbt->signature + MAIN_SIGNATURE_OFFSET, "BMT", 3); -+ bbt->version = BBMT_VERSION; -+ -+ if (write_bmt(block, (unsigned char *)bbt)) { -+ bmt_tbl(bbt)[n].block = 0; -+ -+ /* write failed, try the previous block in bmt_tbl[n - 1] */ -+ n--; -+ goto retry; -+ } -+ -+ /* Return the current index(n) of BMT pool (bmt_tbl[n]) */ -+ return n; -+} -+ -+static u16 find_valid_block_in_pool(struct bbbt *bbt) -+{ -+ int i; -+ -+ if (bmtd.bmt_blk_idx == 0) -+ goto error; -+ -+ for (i = 0; i < bmtd.bmt_blk_idx; i++) { -+ if (bmt_tbl(bbt)[i].block != 0 && bmt_tbl(bbt)[i].mapped == NO_MAPPED) { -+ bmt_tbl(bbt)[i].mapped = NORMAL_MAPPED; -+ return bmt_tbl(bbt)[i].block; -+ } -+ } -+ -+error: -+ pr_info("nand: FATAL ERR: BMT pool is run out!\n"); -+ return 0; -+} -+ -+/* We met a bad block, mark it as bad and map it to a valid block in pool, -+ * if it's a write failure, we need to write the data to mapped block -+ */ -+static bool remap_block_v2(u16 block, u16 mapped_block, int copy_len) -+{ -+ u16 new_block; -+ struct bbbt *bbt; -+ -+ bbt = bmtd.bbt; -+ new_block = find_valid_block_in_pool(bbt); -+ if (new_block == 0) -+ return false; -+ -+ /* Map new bad block to available block in pool */ -+ bbt->bb_tbl[block] = new_block; -+ -+ /* Erase new block */ -+ bbt_nand_erase(new_block); -+ if (copy_len > 0) -+ bbt_nand_copy(new_block, mapped_block, copy_len); -+ -+ bmtd.bmt_blk_idx = upload_bmt(bbt, bmtd.bmt_blk_idx); -+ -+ return true; -+} -+ -+static int get_mapping_block_index_v2(int block) -+{ -+ int start, end; -+ -+ if (block >= bmtd.pool_lba) -+ return block; -+ -+ if (!mapping_block_in_range(block, &start, &end)) -+ return block; -+ -+ return bmtd.bbt->bb_tbl[block]; -+} -+ -+static void -+unmap_block_v2(u16 block) -+{ -+ bmtd.bbt->bb_tbl[block] = block; -+ bmtd.bmt_blk_idx = upload_bmt(bmtd.bbt, bmtd.bmt_blk_idx); -+} -+ -+static unsigned long * -+mtk_bmt_get_mapping_mask(void) -+{ -+ struct bbmt *bbmt = bmt_tbl(bmtd.bbt); -+ int main_blocks = bmtd.mtd->size >> bmtd.blk_shift; -+ unsigned long *used; -+ int i, k; -+ -+ used = kcalloc(sizeof(unsigned long), BIT_WORD(bmtd.bmt_blk_idx) + 1, GFP_KERNEL); -+ if (!used) -+ return NULL; -+ -+ for (i = 1; i < main_blocks; i++) { -+ if (bmtd.bbt->bb_tbl[i] == i) -+ continue; -+ -+ for (k = 0; k < bmtd.bmt_blk_idx; k++) { -+ if (bmtd.bbt->bb_tbl[i] != bbmt[k].block) -+ continue; -+ -+ set_bit(k, used); -+ break; -+ } -+ } -+ -+ return used; -+} -+ -+static int mtk_bmt_debug_v2(void *data, u64 val) -+{ -+ struct bbmt *bbmt = bmt_tbl(bmtd.bbt); -+ struct mtd_info *mtd = bmtd.mtd; -+ unsigned long *used; -+ int main_blocks = mtd->size >> bmtd.blk_shift; -+ int n_remap = 0; -+ int i; -+ -+ used = mtk_bmt_get_mapping_mask(); -+ if (!used) -+ return -ENOMEM; -+ -+ switch (val) { -+ case 0: -+ for (i = 1; i < main_blocks; i++) { -+ if (bmtd.bbt->bb_tbl[i] == i) -+ continue; -+ -+ printk("remap [%x->%x]\n", i, bmtd.bbt->bb_tbl[i]); -+ n_remap++; -+ } -+ for (i = 0; i <= bmtd.bmt_blk_idx; i++) { -+ char c; -+ -+ switch (bbmt[i].mapped) { -+ case NO_MAPPED: -+ continue; -+ case NORMAL_MAPPED: -+ c = 'm'; -+ if (test_bit(i, used)) -+ c = 'M'; -+ break; -+ case BMT_MAPPED: -+ c = 'B'; -+ break; -+ default: -+ c = 'X'; -+ break; -+ } -+ printk("[%x:%c] = 0x%x\n", i, c, bbmt[i].block); -+ } -+ break; -+ case 100: -+ for (i = 0; i <= bmtd.bmt_blk_idx; i++) { -+ if (bbmt[i].mapped != NORMAL_MAPPED) -+ continue; -+ -+ if (test_bit(i, used)) -+ continue; -+ -+ n_remap++; -+ bbmt[i].mapped = NO_MAPPED; -+ printk("free block [%d:%x]\n", i, bbmt[i].block); -+ } -+ if (n_remap) -+ bmtd.bmt_blk_idx = upload_bmt(bmtd.bbt, bmtd.bmt_blk_idx); -+ break; -+ } -+ -+ kfree(used); -+ -+ return 0; -+} -+ -+static int mtk_bmt_init_v2(struct device_node *np) -+{ -+ u32 bmt_pool_size, bmt_table_size; -+ u32 bufsz, block; -+ u16 pmt_block; -+ -+ if (of_property_read_u32(np, "mediatek,bmt-pool-size", -+ &bmt_pool_size) != 0) -+ bmt_pool_size = 80; -+ -+ if (of_property_read_u8(np, "mediatek,bmt-oob-offset", -+ &bmtd.oob_offset) != 0) -+ bmtd.oob_offset = 0; -+ -+ if (of_property_read_u32(np, "mediatek,bmt-table-size", -+ &bmt_table_size) != 0) -+ bmt_table_size = 0x2000U; -+ -+ bmtd.table_size = bmt_table_size; -+ -+ pmt_block = bmtd.total_blks - bmt_pool_size - 2; -+ -+ bmtd.mtd->size = pmt_block << bmtd.blk_shift; -+ -+ /* -+ * --------------------------------------- -+ * | PMT(2blks) | BMT POOL(totalblks * 2%) | -+ * --------------------------------------- -+ * ^ ^ -+ * | | -+ * pmt_block pmt_block + 2blocks(pool_lba) -+ * -+ * ATTETION!!!!!! -+ * The blocks ahead of the boundary block are stored in bb_tbl -+ * and blocks behind are stored in bmt_tbl -+ */ -+ -+ bmtd.pool_lba = (u16)(pmt_block + 2); -+ bmtd.bb_max = bmtd.total_blks * BBPOOL_RATIO / 100; -+ -+ bufsz = round_up(sizeof(struct bbbt) + -+ bmt_table_size * sizeof(struct bbmt), bmtd.pg_size); -+ bmtd.bmt_pgs = bufsz >> bmtd.pg_shift; -+ -+ bmtd.bbt_buf = kzalloc(bufsz, GFP_KERNEL); -+ if (!bmtd.bbt_buf) -+ return -ENOMEM; -+ -+ memset(bmtd.bbt_buf, 0xff, bufsz); -+ -+ /* Scanning start from the first page of the last block -+ * of whole flash -+ */ -+ bmtd.bbt = scan_bmt(bmtd.total_blks - 1); -+ if (!bmtd.bbt) { -+ /* BMT not found */ -+ if (bmtd.total_blks > BB_TABLE_MAX + BMT_TABLE_MAX) { -+ pr_info("nand: FATAL: Too many blocks, can not support!\n"); -+ return -1; -+ } -+ -+ bmtd.bbt = (struct bbbt *)bmtd.bbt_buf; -+ memset(bmt_tbl(bmtd.bbt), BMT_TBL_DEF_VAL, -+ bmtd.table_size * sizeof(struct bbmt)); -+ -+ if (scan_bad_blocks(bmtd.bbt)) -+ return -1; -+ -+ /* BMT always in the last valid block in pool */ -+ bmtd.bmt_blk_idx = upload_bmt(bmtd.bbt, bmtd.bmt_blk_idx); -+ block = bmt_tbl(bmtd.bbt)[bmtd.bmt_blk_idx].block; -+ pr_notice("[BBT] BMT.v2 is written into PBA:0x%x\n", block); -+ -+ if (bmtd.bmt_blk_idx == 0) -+ pr_info("nand: Warning: no available block in BMT pool!\n"); -+ else if (bmtd.bmt_blk_idx == (u16)-1) -+ return -1; -+ } -+ -+ return 0; -+} -+ -+ -+const struct mtk_bmt_ops mtk_bmt_v2_ops = { -+ .sig = "bmt", -+ .sig_len = 3, -+ .init = mtk_bmt_init_v2, -+ .remap_block = remap_block_v2, -+ .unmap_block = unmap_block_v2, -+ .get_mapping_block = get_mapping_block_index_v2, -+ .debug = mtk_bmt_debug_v2, -+}; -diff --git a/target/linux/ramips/files/include/linux/mtd/mtk_bmt.h b/target/linux/ramips/files/include/linux/mtd/mtk_bmt.h -new file mode 100644 -index 000000000000..cbb6d04d8952 ---- /dev/null -+++ b/target/linux/ramips/files/include/linux/mtd/mtk_bmt.h -@@ -0,0 +1,18 @@ -+#ifndef __MTK_BMT_H -+#define __MTK_BMT_H -+ -+#ifdef CONFIG_MTD_NAND_MTK_BMT -+int mtk_bmt_attach(struct mtd_info *mtd); -+void mtk_bmt_detach(struct mtd_info *mtd); -+#else -+static inline int mtk_bmt_attach(struct mtd_info *mtd) -+{ -+ return 0; -+} -+ -+static inline void mtk_bmt_detach(struct mtd_info *mtd) -+{ -+} -+#endif -+ -+#endif -diff --git a/target/linux/ramips/mt7621/config-5.4 b/target/linux/ramips/mt7621/config-5.4 -index 1484fc026944..ae4a0679bd76 100644 ---- a/target/linux/ramips/mt7621/config-5.4 -+++ b/target/linux/ramips/mt7621/config-5.4 -@@ -158,6 +158,7 @@ CONFIG_MTD_CMDLINE_PARTS=y - CONFIG_MTD_NAND_CORE=y - CONFIG_MTD_NAND_ECC_SW_HAMMING=y - CONFIG_MTD_NAND_MT7621=y -+CONFIG_MTD_NAND_MTK_BMT=y - CONFIG_MTD_PHYSMAP=y - CONFIG_MTD_RAW_NAND=y - CONFIG_MTD_ROUTERBOOT_PARTS=y -diff --git a/target/linux/ramips/patches-5.4/430-mtk-bmt-support.patch b/target/linux/ramips/patches-5.4/430-mtk-bmt-support.patch -new file mode 100644 -index 000000000000..9533e4a7ee43 ---- /dev/null -+++ b/target/linux/ramips/patches-5.4/430-mtk-bmt-support.patch -@@ -0,0 +1,23 @@ -+--- a/drivers/mtd/nand/Kconfig -++++ b/drivers/mtd/nand/Kconfig -+@@ -2,6 +2,10 @@ -+ config MTD_NAND_CORE -+ tristate -+ -++config MTD_NAND_MTK_BMT -++ bool "Support MediaTek NAND Bad-block Management Table" -++ default n -++ -+ source "drivers/mtd/nand/onenand/Kconfig" -+ source "drivers/mtd/nand/raw/Kconfig" -+ source "drivers/mtd/nand/spi/Kconfig" -+--- a/drivers/mtd/nand/Makefile -++++ b/drivers/mtd/nand/Makefile -+@@ -2,6 +2,7 @@ -+ -+ nandcore-objs := core.o bbt.o -+ obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o -++obj-$(CONFIG_MTD_NAND_MTK_BMT) += mtk_bmt.o mtk_bmt_v2.o mtk_bmt_bbt.o mtk_bmt_nmbm.o -+ -+ obj-y += onenand/ -+ obj-y += raw/ --- -2.32.0 - - -From f25e41e05c139a8efa4582148a2d9ebacaf165a9 Mon Sep 17 00:00:00 2001 -From: Stijn Tintel -Date: Wed, 19 Jan 2022 15:44:58 +0200 -Subject: [PATCH 2/8] ramips: move mt7621_nand driver to files - -The patch was rejected by upstream. The mtk_nand driver should be -modified to support the mt7621 flash controller instead. As there is no -newer version to backport, or no upstream version to fix bugs, let's -move the driver to the files dir under the ramips target. This makes it -easier to make changes to the driver while waiting for mt7621 support to -land in mtk_nand. - -Signed-off-by: Stijn Tintel ---- - .../files/drivers/mtd/nand/raw/mt7621_nand.c | 1350 ++++++++++++++++ - ...driver-support-for-MT7621-nand-flash.patch | 1354 +---------------- - 2 files changed, 1351 insertions(+), 1353 deletions(-) - create mode 100644 target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c - -diff --git a/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c -new file mode 100644 -index 000000000000..678295a68db3 ---- /dev/null -+++ b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c -@@ -0,0 +1,1350 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * MediaTek MT7621 NAND Flash Controller driver -+ * -+ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. -+ * -+ * Author: Weijie Gao -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+/* NFI core registers */ -+#define NFI_CNFG 0x000 -+#define CNFG_OP_MODE_S 12 -+#define CNFG_OP_MODE_M GENMASK(14, 12) -+#define CNFG_OP_CUSTOM 6 -+#define CNFG_AUTO_FMT_EN BIT(9) -+#define CNFG_HW_ECC_EN BIT(8) -+#define CNFG_BYTE_RW BIT(6) -+#define CNFG_READ_MODE BIT(1) -+ -+#define NFI_PAGEFMT 0x004 -+#define PAGEFMT_FDM_ECC_S 12 -+#define PAGEFMT_FDM_ECC_M GENMASK(15, 12) -+#define PAGEFMT_FDM_S 8 -+#define PAGEFMT_FDM_M GENMASK(11, 8) -+#define PAGEFMT_SPARE_S 4 -+#define PAGEFMT_SPARE_M GENMASK(5, 4) -+#define PAGEFMT_PAGE_S 0 -+#define PAGEFMT_PAGE_M GENMASK(1, 0) -+ -+#define NFI_CON 0x008 -+#define CON_NFI_SEC_S 12 -+#define CON_NFI_SEC_M GENMASK(15, 12) -+#define CON_NFI_BWR BIT(9) -+#define CON_NFI_BRD BIT(8) -+#define CON_NFI_RST BIT(1) -+#define CON_FIFO_FLUSH BIT(0) -+ -+#define NFI_ACCCON 0x00c -+#define ACCCON_POECS_S 28 -+#define ACCCON_POECS_MAX 0x0f -+#define ACCCON_POECS_DEF 3 -+#define ACCCON_PRECS_S 22 -+#define ACCCON_PRECS_MAX 0x3f -+#define ACCCON_PRECS_DEF 3 -+#define ACCCON_C2R_S 16 -+#define ACCCON_C2R_MAX 0x3f -+#define ACCCON_C2R_DEF 7 -+#define ACCCON_W2R_S 12 -+#define ACCCON_W2R_MAX 0x0f -+#define ACCCON_W2R_DEF 7 -+#define ACCCON_WH_S 8 -+#define ACCCON_WH_MAX 0x0f -+#define ACCCON_WH_DEF 15 -+#define ACCCON_WST_S 4 -+#define ACCCON_WST_MAX 0x0f -+#define ACCCON_WST_DEF 15 -+#define ACCCON_WST_MIN 3 -+#define ACCCON_RLT_S 0 -+#define ACCCON_RLT_MAX 0x0f -+#define ACCCON_RLT_DEF 15 -+#define ACCCON_RLT_MIN 3 -+ -+#define NFI_CMD 0x020 -+ -+#define NFI_ADDRNOB 0x030 -+#define ADDR_ROW_NOB_S 4 -+#define ADDR_ROW_NOB_M GENMASK(6, 4) -+#define ADDR_COL_NOB_S 0 -+#define ADDR_COL_NOB_M GENMASK(2, 0) -+ -+#define NFI_COLADDR 0x034 -+#define NFI_ROWADDR 0x038 -+ -+#define NFI_STRDATA 0x040 -+#define STR_DATA BIT(0) -+ -+#define NFI_CNRNB 0x044 -+#define CB2R_TIME_S 4 -+#define CB2R_TIME_M GENMASK(7, 4) -+#define STR_CNRNB BIT(0) -+ -+#define NFI_DATAW 0x050 -+#define NFI_DATAR 0x054 -+ -+#define NFI_PIO_DIRDY 0x058 -+#define PIO_DIRDY BIT(0) -+ -+#define NFI_STA 0x060 -+#define STA_NFI_FSM_S 16 -+#define STA_NFI_FSM_M GENMASK(19, 16) -+#define STA_FSM_CUSTOM_DATA 14 -+#define STA_BUSY BIT(8) -+#define STA_ADDR BIT(1) -+#define STA_CMD BIT(0) -+ -+#define NFI_ADDRCNTR 0x070 -+#define SEC_CNTR_S 12 -+#define SEC_CNTR_M GENMASK(15, 12) -+#define SEC_ADDR_S 0 -+#define SEC_ADDR_M GENMASK(9, 0) -+ -+#define NFI_CSEL 0x090 -+#define CSEL_S 0 -+#define CSEL_M GENMASK(1, 0) -+ -+#define NFI_FDM0L 0x0a0 -+#define NFI_FDML(n) (0x0a0 + ((n) << 3)) -+ -+#define NFI_FDM0M 0x0a4 -+#define NFI_FDMM(n) (0x0a4 + ((n) << 3)) -+ -+#define NFI_MASTER_STA 0x210 -+#define MAS_ADDR GENMASK(11, 9) -+#define MAS_RD GENMASK(8, 6) -+#define MAS_WR GENMASK(5, 3) -+#define MAS_RDDLY GENMASK(2, 0) -+ -+/* ECC engine registers */ -+#define ECC_ENCCON 0x000 -+#define ENC_EN BIT(0) -+ -+#define ECC_ENCCNFG 0x004 -+#define ENC_CNFG_MSG_S 16 -+#define ENC_CNFG_MSG_M GENMASK(28, 16) -+#define ENC_MODE_S 4 -+#define ENC_MODE_M GENMASK(5, 4) -+#define ENC_MODE_NFI 1 -+#define ENC_TNUM_S 0 -+#define ENC_TNUM_M GENMASK(2, 0) -+ -+#define ECC_ENCIDLE 0x00c -+#define ENC_IDLE BIT(0) -+ -+#define ECC_DECCON 0x100 -+#define DEC_EN BIT(0) -+ -+#define ECC_DECCNFG 0x104 -+#define DEC_EMPTY_EN BIT(31) -+#define DEC_CS_S 16 -+#define DEC_CS_M GENMASK(28, 16) -+#define DEC_CON_S 12 -+#define DEC_CON_M GENMASK(13, 12) -+#define DEC_CON_EL 2 -+#define DEC_MODE_S 4 -+#define DEC_MODE_M GENMASK(5, 4) -+#define DEC_MODE_NFI 1 -+#define DEC_TNUM_S 0 -+#define DEC_TNUM_M GENMASK(2, 0) -+ -+#define ECC_DECIDLE 0x10c -+#define DEC_IDLE BIT(1) -+ -+#define ECC_DECENUM 0x114 -+#define ERRNUM_S 2 -+#define ERRNUM_M GENMASK(3, 0) -+ -+#define ECC_DECDONE 0x118 -+#define DEC_DONE7 BIT(7) -+#define DEC_DONE6 BIT(6) -+#define DEC_DONE5 BIT(5) -+#define DEC_DONE4 BIT(4) -+#define DEC_DONE3 BIT(3) -+#define DEC_DONE2 BIT(2) -+#define DEC_DONE1 BIT(1) -+#define DEC_DONE0 BIT(0) -+ -+#define ECC_DECEL(n) (0x11c + (n) * 4) -+#define DEC_EL_ODD_S 16 -+#define DEC_EL_EVEN_S 0 -+#define DEC_EL_M 0x1fff -+#define DEC_EL_BYTE_POS_S 3 -+#define DEC_EL_BIT_POS_M GENMASK(2, 0) -+ -+#define ECC_FDMADDR 0x13c -+ -+/* ENCIDLE and DECIDLE */ -+#define ECC_IDLE BIT(0) -+ -+#define ACCTIMING(tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt) \ -+ ((tpoecs) << ACCCON_POECS_S | (tprecs) << ACCCON_PRECS_S | \ -+ (tc2r) << ACCCON_C2R_S | (tw2r) << ACCCON_W2R_S | \ -+ (twh) << ACCCON_WH_S | (twst) << ACCCON_WST_S | (trlt)) -+ -+#define MASTER_STA_MASK (MAS_ADDR | MAS_RD | MAS_WR | \ -+ MAS_RDDLY) -+#define NFI_RESET_TIMEOUT 1000000 -+#define NFI_CORE_TIMEOUT 500000 -+#define ECC_ENGINE_TIMEOUT 500000 -+ -+#define ECC_SECTOR_SIZE 512 -+#define ECC_PARITY_BITS 13 -+ -+#define NFI_FDM_SIZE 8 -+ -+#define MT7621_NFC_NAME "mt7621-nand" -+ -+struct mt7621_nfc { -+ struct nand_controller controller; -+ struct nand_chip nand; -+ struct clk *nfi_clk; -+ struct device *dev; -+ -+ void __iomem *nfi_regs; -+ void __iomem *ecc_regs; -+ -+ u32 spare_per_sector; -+}; -+ -+static const u16 mt7621_nfi_page_size[] = { SZ_512, SZ_2K, SZ_4K }; -+static const u8 mt7621_nfi_spare_size[] = { 16, 26, 27, 28 }; -+static const u8 mt7621_ecc_strength[] = { 4, 6, 8, 10, 12 }; -+ -+static inline u32 nfi_read32(struct mt7621_nfc *nfc, u32 reg) -+{ -+ return readl(nfc->nfi_regs + reg); -+} -+ -+static inline void nfi_write32(struct mt7621_nfc *nfc, u32 reg, u32 val) -+{ -+ writel(val, nfc->nfi_regs + reg); -+} -+ -+static inline u16 nfi_read16(struct mt7621_nfc *nfc, u32 reg) -+{ -+ return readw(nfc->nfi_regs + reg); -+} -+ -+static inline void nfi_write16(struct mt7621_nfc *nfc, u32 reg, u16 val) -+{ -+ writew(val, nfc->nfi_regs + reg); -+} -+ -+static inline void ecc_write16(struct mt7621_nfc *nfc, u32 reg, u16 val) -+{ -+ writew(val, nfc->ecc_regs + reg); -+} -+ -+static inline u32 ecc_read32(struct mt7621_nfc *nfc, u32 reg) -+{ -+ return readl(nfc->ecc_regs + reg); -+} -+ -+static inline void ecc_write32(struct mt7621_nfc *nfc, u32 reg, u32 val) -+{ -+ return writel(val, nfc->ecc_regs + reg); -+} -+ -+static inline u8 *oob_fdm_ptr(struct nand_chip *nand, int sect) -+{ -+ return nand->oob_poi + sect * NFI_FDM_SIZE; -+} -+ -+static inline u8 *oob_ecc_ptr(struct mt7621_nfc *nfc, int sect) -+{ -+ struct nand_chip *nand = &nfc->nand; -+ -+ return nand->oob_poi + nand->ecc.steps * NFI_FDM_SIZE + -+ sect * (nfc->spare_per_sector - NFI_FDM_SIZE); -+} -+ -+static inline u8 *page_data_ptr(struct nand_chip *nand, const u8 *buf, -+ int sect) -+{ -+ return (u8 *)buf + sect * nand->ecc.size; -+} -+ -+static int mt7621_ecc_wait_idle(struct mt7621_nfc *nfc, u32 reg) -+{ -+ struct device *dev = nfc->dev; -+ u32 val; -+ int ret; -+ -+ ret = readw_poll_timeout_atomic(nfc->ecc_regs + reg, val, -+ val & ECC_IDLE, 10, -+ ECC_ENGINE_TIMEOUT); -+ if (ret) { -+ dev_warn(dev, "ECC engine timed out entering idle mode\n"); -+ return -EIO; -+ } -+ -+ return 0; -+} -+ -+static int mt7621_ecc_decoder_wait_done(struct mt7621_nfc *nfc, u32 sect) -+{ -+ struct device *dev = nfc->dev; -+ u32 val; -+ int ret; -+ -+ ret = readw_poll_timeout_atomic(nfc->ecc_regs + ECC_DECDONE, val, -+ val & (1 << sect), 10, -+ ECC_ENGINE_TIMEOUT); -+ -+ if (ret) { -+ dev_warn(dev, "ECC decoder for sector %d timed out\n", -+ sect); -+ return -ETIMEDOUT; -+ } -+ -+ return 0; -+} -+ -+static void mt7621_ecc_encoder_op(struct mt7621_nfc *nfc, bool enable) -+{ -+ mt7621_ecc_wait_idle(nfc, ECC_ENCIDLE); -+ ecc_write16(nfc, ECC_ENCCON, enable ? ENC_EN : 0); -+} -+ -+static void mt7621_ecc_decoder_op(struct mt7621_nfc *nfc, bool enable) -+{ -+ mt7621_ecc_wait_idle(nfc, ECC_DECIDLE); -+ ecc_write16(nfc, ECC_DECCON, enable ? DEC_EN : 0); -+} -+ -+static int mt7621_ecc_correct_check(struct mt7621_nfc *nfc, u8 *sector_buf, -+ u8 *fdm_buf, u32 sect) -+{ -+ struct nand_chip *nand = &nfc->nand; -+ u32 decnum, num_error_bits, fdm_end_bits; -+ u32 error_locations, error_bit_loc; -+ u32 error_byte_pos, error_bit_pos; -+ int bitflips = 0; -+ u32 i; -+ -+ decnum = ecc_read32(nfc, ECC_DECENUM); -+ num_error_bits = (decnum >> (sect << ERRNUM_S)) & ERRNUM_M; -+ fdm_end_bits = (nand->ecc.size + NFI_FDM_SIZE) << 3; -+ -+ if (!num_error_bits) -+ return 0; -+ -+ if (num_error_bits == ERRNUM_M) -+ return -1; -+ -+ for (i = 0; i < num_error_bits; i++) { -+ error_locations = ecc_read32(nfc, ECC_DECEL(i / 2)); -+ error_bit_loc = (error_locations >> ((i % 2) * DEC_EL_ODD_S)) & -+ DEC_EL_M; -+ error_byte_pos = error_bit_loc >> DEC_EL_BYTE_POS_S; -+ error_bit_pos = error_bit_loc & DEC_EL_BIT_POS_M; -+ -+ if (error_bit_loc < (nand->ecc.size << 3)) { -+ if (sector_buf) { -+ sector_buf[error_byte_pos] ^= -+ (1 << error_bit_pos); -+ } -+ } else if (error_bit_loc < fdm_end_bits) { -+ if (fdm_buf) { -+ fdm_buf[error_byte_pos - nand->ecc.size] ^= -+ (1 << error_bit_pos); -+ } -+ } -+ -+ bitflips++; -+ } -+ -+ return bitflips; -+} -+ -+static int mt7621_nfc_wait_write_completion(struct mt7621_nfc *nfc, -+ struct nand_chip *nand) -+{ -+ struct device *dev = nfc->dev; -+ u16 val; -+ int ret; -+ -+ ret = readw_poll_timeout_atomic(nfc->nfi_regs + NFI_ADDRCNTR, val, -+ ((val & SEC_CNTR_M) >> SEC_CNTR_S) >= nand->ecc.steps, 10, -+ NFI_CORE_TIMEOUT); -+ -+ if (ret) { -+ dev_warn(dev, "NFI core write operation timed out\n"); -+ return -ETIMEDOUT; -+ } -+ -+ return ret; -+} -+ -+static void mt7621_nfc_hw_reset(struct mt7621_nfc *nfc) -+{ -+ u32 val; -+ int ret; -+ -+ /* reset all registers and force the NFI master to terminate */ -+ nfi_write16(nfc, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST); -+ -+ /* wait for the master to finish the last transaction */ -+ ret = readw_poll_timeout(nfc->nfi_regs + NFI_MASTER_STA, val, -+ !(val & MASTER_STA_MASK), 50, -+ NFI_RESET_TIMEOUT); -+ if (ret) { -+ dev_warn(nfc->dev, "Failed to reset NFI master in %dms\n", -+ NFI_RESET_TIMEOUT); -+ } -+ -+ /* ensure any status register affected by the NFI master is reset */ -+ nfi_write16(nfc, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST); -+ nfi_write16(nfc, NFI_STRDATA, 0); -+} -+ -+static inline void mt7621_nfc_hw_init(struct mt7621_nfc *nfc) -+{ -+ u32 acccon; -+ -+ /* -+ * CNRNB: nand ready/busy register -+ * ------------------------------- -+ * 7:4: timeout register for polling the NAND busy/ready signal -+ * 0 : poll the status of the busy/ready signal after [7:4]*16 cycles. -+ */ -+ nfi_write16(nfc, NFI_CNRNB, CB2R_TIME_M | STR_CNRNB); -+ -+ mt7621_nfc_hw_reset(nfc); -+ -+ /* Apply default access timing */ -+ acccon = ACCTIMING(ACCCON_POECS_DEF, ACCCON_PRECS_DEF, ACCCON_C2R_DEF, -+ ACCCON_W2R_DEF, ACCCON_WH_DEF, ACCCON_WST_DEF, -+ ACCCON_RLT_DEF); -+ -+ nfi_write32(nfc, NFI_ACCCON, acccon); -+} -+ -+static int mt7621_nfc_send_command(struct mt7621_nfc *nfc, u8 command) -+{ -+ struct device *dev = nfc->dev; -+ u32 val; -+ int ret; -+ -+ nfi_write32(nfc, NFI_CMD, command); -+ -+ ret = readl_poll_timeout_atomic(nfc->nfi_regs + NFI_STA, val, -+ !(val & STA_CMD), 10, -+ NFI_CORE_TIMEOUT); -+ if (ret) { -+ dev_warn(dev, "NFI core timed out entering command mode\n"); -+ return -EIO; -+ } -+ -+ return 0; -+} -+ -+static int mt7621_nfc_send_address_byte(struct mt7621_nfc *nfc, int addr) -+{ -+ struct device *dev = nfc->dev; -+ u32 val; -+ int ret; -+ -+ nfi_write32(nfc, NFI_COLADDR, addr); -+ nfi_write32(nfc, NFI_ROWADDR, 0); -+ nfi_write16(nfc, NFI_ADDRNOB, 1); -+ -+ ret = readl_poll_timeout_atomic(nfc->nfi_regs + NFI_STA, val, -+ !(val & STA_ADDR), 10, -+ NFI_CORE_TIMEOUT); -+ if (ret) { -+ dev_warn(dev, "NFI core timed out entering address mode\n"); -+ return -EIO; -+ } -+ -+ return 0; -+} -+ -+static int mt7621_nfc_send_address(struct mt7621_nfc *nfc, const u8 *addr, -+ unsigned int naddrs) -+{ -+ int ret; -+ -+ while (naddrs) { -+ ret = mt7621_nfc_send_address_byte(nfc, *addr); -+ if (ret) -+ return ret; -+ -+ addr++; -+ naddrs--; -+ } -+ -+ return 0; -+} -+ -+static void mt7621_nfc_wait_pio_ready(struct mt7621_nfc *nfc) -+{ -+ struct device *dev = nfc->dev; -+ int ret; -+ u16 val; -+ -+ ret = readw_poll_timeout_atomic(nfc->nfi_regs + NFI_PIO_DIRDY, val, -+ val & PIO_DIRDY, 10, -+ NFI_CORE_TIMEOUT); -+ if (ret < 0) -+ dev_err(dev, "NFI core PIO mode not ready\n"); -+} -+ -+static u32 mt7621_nfc_pio_read(struct mt7621_nfc *nfc, bool br) -+{ -+ u32 reg; -+ -+ /* after each byte read, the NFI_STA reg is reset by the hardware */ -+ reg = (nfi_read32(nfc, NFI_STA) & STA_NFI_FSM_M) >> STA_NFI_FSM_S; -+ if (reg != STA_FSM_CUSTOM_DATA) { -+ reg = nfi_read16(nfc, NFI_CNFG); -+ reg |= CNFG_READ_MODE | CNFG_BYTE_RW; -+ if (!br) -+ reg &= ~CNFG_BYTE_RW; -+ nfi_write16(nfc, NFI_CNFG, reg); -+ -+ /* -+ * set to max sector to allow the HW to continue reading over -+ * unaligned accesses -+ */ -+ nfi_write16(nfc, NFI_CON, CON_NFI_SEC_M | CON_NFI_BRD); -+ -+ /* trigger to fetch data */ -+ nfi_write16(nfc, NFI_STRDATA, STR_DATA); -+ } -+ -+ mt7621_nfc_wait_pio_ready(nfc); -+ -+ return nfi_read32(nfc, NFI_DATAR); -+} -+ -+static void mt7621_nfc_read_data(struct mt7621_nfc *nfc, u8 *buf, u32 len) -+{ -+ while (((uintptr_t)buf & 3) && len) { -+ *buf = mt7621_nfc_pio_read(nfc, true); -+ buf++; -+ len--; -+ } -+ -+ while (len >= 4) { -+ *(u32 *)buf = mt7621_nfc_pio_read(nfc, false); -+ buf += 4; -+ len -= 4; -+ } -+ -+ while (len) { -+ *buf = mt7621_nfc_pio_read(nfc, true); -+ buf++; -+ len--; -+ } -+} -+ -+static void mt7621_nfc_read_data_discard(struct mt7621_nfc *nfc, u32 len) -+{ -+ while (len >= 4) { -+ mt7621_nfc_pio_read(nfc, false); -+ len -= 4; -+ } -+ -+ while (len) { -+ mt7621_nfc_pio_read(nfc, true); -+ len--; -+ } -+} -+ -+static void mt7621_nfc_pio_write(struct mt7621_nfc *nfc, u32 val, bool bw) -+{ -+ u32 reg; -+ -+ reg = (nfi_read32(nfc, NFI_STA) & STA_NFI_FSM_M) >> STA_NFI_FSM_S; -+ if (reg != STA_FSM_CUSTOM_DATA) { -+ reg = nfi_read16(nfc, NFI_CNFG); -+ reg &= ~(CNFG_READ_MODE | CNFG_BYTE_RW); -+ if (bw) -+ reg |= CNFG_BYTE_RW; -+ nfi_write16(nfc, NFI_CNFG, reg); -+ -+ nfi_write16(nfc, NFI_CON, CON_NFI_SEC_M | CON_NFI_BWR); -+ nfi_write16(nfc, NFI_STRDATA, STR_DATA); -+ } -+ -+ mt7621_nfc_wait_pio_ready(nfc); -+ nfi_write32(nfc, NFI_DATAW, val); -+} -+ -+static void mt7621_nfc_write_data(struct mt7621_nfc *nfc, const u8 *buf, -+ u32 len) -+{ -+ while (((uintptr_t)buf & 3) && len) { -+ mt7621_nfc_pio_write(nfc, *buf, true); -+ buf++; -+ len--; -+ } -+ -+ while (len >= 4) { -+ mt7621_nfc_pio_write(nfc, *(const u32 *)buf, false); -+ buf += 4; -+ len -= 4; -+ } -+ -+ while (len) { -+ mt7621_nfc_pio_write(nfc, *buf, true); -+ buf++; -+ len--; -+ } -+} -+ -+static void mt7621_nfc_write_data_empty(struct mt7621_nfc *nfc, u32 len) -+{ -+ while (len >= 4) { -+ mt7621_nfc_pio_write(nfc, 0xffffffff, false); -+ len -= 4; -+ } -+ -+ while (len) { -+ mt7621_nfc_pio_write(nfc, 0xff, true); -+ len--; -+ } -+} -+ -+static int mt7621_nfc_dev_ready(struct mt7621_nfc *nfc, -+ unsigned int timeout_ms) -+{ -+ u32 val; -+ -+ return readl_poll_timeout_atomic(nfc->nfi_regs + NFI_STA, val, -+ !(val & STA_BUSY), 10, -+ timeout_ms * 1000); -+} -+ -+static int mt7621_nfc_exec_instr(struct nand_chip *nand, -+ const struct nand_op_instr *instr) -+{ -+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); -+ -+ switch (instr->type) { -+ case NAND_OP_CMD_INSTR: -+ mt7621_nfc_hw_reset(nfc); -+ nfi_write16(nfc, NFI_CNFG, CNFG_OP_CUSTOM << CNFG_OP_MODE_S); -+ return mt7621_nfc_send_command(nfc, instr->ctx.cmd.opcode); -+ case NAND_OP_ADDR_INSTR: -+ return mt7621_nfc_send_address(nfc, instr->ctx.addr.addrs, -+ instr->ctx.addr.naddrs); -+ case NAND_OP_DATA_IN_INSTR: -+ mt7621_nfc_read_data(nfc, instr->ctx.data.buf.in, -+ instr->ctx.data.len); -+ return 0; -+ case NAND_OP_DATA_OUT_INSTR: -+ mt7621_nfc_write_data(nfc, instr->ctx.data.buf.out, -+ instr->ctx.data.len); -+ return 0; -+ case NAND_OP_WAITRDY_INSTR: -+ return mt7621_nfc_dev_ready(nfc, -+ instr->ctx.waitrdy.timeout_ms); -+ default: -+ WARN_ONCE(1, "unsupported NAND instruction type: %d\n", -+ instr->type); -+ -+ return -EINVAL; -+ } -+} -+ -+static int mt7621_nfc_exec_op(struct nand_chip *nand, -+ const struct nand_operation *op, bool check_only) -+{ -+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); -+ int i, ret; -+ -+ if (check_only) -+ return 0; -+ -+ /* Only CS0 available */ -+ nfi_write16(nfc, NFI_CSEL, 0); -+ -+ for (i = 0; i < op->ninstrs; i++) { -+ ret = mt7621_nfc_exec_instr(nand, &op->instrs[i]); -+ if (ret) -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static int mt7621_nfc_setup_data_interface(struct nand_chip *nand, int csline, -+ const struct nand_data_interface *conf) -+{ -+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); -+ const struct nand_sdr_timings *timings; -+ u32 acccon, temp, rate, tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt; -+ -+ if (!nfc->nfi_clk) -+ return -ENOTSUPP; -+ -+ timings = nand_get_sdr_timings(conf); -+ if (IS_ERR(timings)) -+ return -ENOTSUPP; -+ -+ rate = clk_get_rate(nfc->nfi_clk); -+ -+ /* turn clock rate into KHZ */ -+ rate /= 1000; -+ -+ tpoecs = max(timings->tALH_min, timings->tCLH_min) / 1000; -+ tpoecs = DIV_ROUND_UP(tpoecs * rate, 1000000); -+ tpoecs = min_t(u32, tpoecs, ACCCON_POECS_MAX); -+ -+ tprecs = max(timings->tCLS_min, timings->tALS_min) / 1000; -+ tprecs = DIV_ROUND_UP(tprecs * rate, 1000000); -+ tprecs = min_t(u32, tprecs, ACCCON_PRECS_MAX); -+ -+ /* sdr interface has no tCR which means CE# low to RE# low */ -+ tc2r = 0; -+ -+ tw2r = timings->tWHR_min / 1000; -+ tw2r = DIV_ROUND_UP(tw2r * rate, 1000000); -+ tw2r = DIV_ROUND_UP(tw2r - 1, 2); -+ tw2r = min_t(u32, tw2r, ACCCON_W2R_MAX); -+ -+ twh = max(timings->tREH_min, timings->tWH_min) / 1000; -+ twh = DIV_ROUND_UP(twh * rate, 1000000) - 1; -+ twh = min_t(u32, twh, ACCCON_WH_MAX); -+ -+ /* Calculate real WE#/RE# hold time in nanosecond */ -+ temp = (twh + 1) * 1000000 / rate; -+ /* nanosecond to picosecond */ -+ temp *= 1000; -+ -+ /* -+ * WE# low level time should be expaned to meet WE# pulse time -+ * and WE# cycle time at the same time. -+ */ -+ if (temp < timings->tWC_min) -+ twst = timings->tWC_min - temp; -+ else -+ twst = 0; -+ twst = max(timings->tWP_min, twst) / 1000; -+ twst = DIV_ROUND_UP(twst * rate, 1000000) - 1; -+ twst = min_t(u32, twst, ACCCON_WST_MAX); -+ -+ /* -+ * RE# low level time should be expaned to meet RE# pulse time -+ * and RE# cycle time at the same time. -+ */ -+ if (temp < timings->tRC_min) -+ trlt = timings->tRC_min - temp; -+ else -+ trlt = 0; -+ trlt = max(trlt, timings->tRP_min) / 1000; -+ trlt = DIV_ROUND_UP(trlt * rate, 1000000) - 1; -+ trlt = min_t(u32, trlt, ACCCON_RLT_MAX); -+ -+ if (csline == NAND_DATA_IFACE_CHECK_ONLY) { -+ if (twst < ACCCON_WST_MIN || trlt < ACCCON_RLT_MIN) -+ return -ENOTSUPP; -+ } -+ -+ acccon = ACCTIMING(tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt); -+ -+ dev_info(nfc->dev, "Using programmed access timing: %08x\n", acccon); -+ -+ nfi_write32(nfc, NFI_ACCCON, acccon); -+ -+ return 0; -+} -+ -+static int mt7621_nfc_calc_ecc_strength(struct mt7621_nfc *nfc, -+ u32 avail_ecc_bytes) -+{ -+ struct nand_chip *nand = &nfc->nand; -+ struct mtd_info *mtd = nand_to_mtd(nand); -+ u32 strength; -+ int i; -+ -+ strength = avail_ecc_bytes * 8 / ECC_PARITY_BITS; -+ -+ /* Find the closest supported ecc strength */ -+ for (i = ARRAY_SIZE(mt7621_ecc_strength) - 1; i >= 0; i--) { -+ if (mt7621_ecc_strength[i] <= strength) -+ break; -+ } -+ -+ if (unlikely(i < 0)) { -+ dev_err(nfc->dev, "OOB size (%u) is not supported\n", -+ mtd->oobsize); -+ return -EINVAL; -+ } -+ -+ nand->ecc.strength = mt7621_ecc_strength[i]; -+ nand->ecc.bytes = -+ DIV_ROUND_UP(nand->ecc.strength * ECC_PARITY_BITS, 8); -+ -+ dev_info(nfc->dev, "ECC strength adjusted to %u bits\n", -+ nand->ecc.strength); -+ -+ return i; -+} -+ -+static int mt7621_nfc_set_spare_per_sector(struct mt7621_nfc *nfc) -+{ -+ struct nand_chip *nand = &nfc->nand; -+ struct mtd_info *mtd = nand_to_mtd(nand); -+ u32 size; -+ int i; -+ -+ size = nand->ecc.bytes + NFI_FDM_SIZE; -+ -+ /* Find the closest supported spare size */ -+ for (i = 0; i < ARRAY_SIZE(mt7621_nfi_spare_size); i++) { -+ if (mt7621_nfi_spare_size[i] >= size) -+ break; -+ } -+ -+ if (unlikely(i >= ARRAY_SIZE(mt7621_nfi_spare_size))) { -+ dev_err(nfc->dev, "OOB size (%u) is not supported\n", -+ mtd->oobsize); -+ return -EINVAL; -+ } -+ -+ nfc->spare_per_sector = mt7621_nfi_spare_size[i]; -+ -+ return i; -+} -+ -+static int mt7621_nfc_ecc_init(struct mt7621_nfc *nfc) -+{ -+ struct nand_chip *nand = &nfc->nand; -+ struct mtd_info *mtd = nand_to_mtd(nand); -+ u32 spare_per_sector, encode_block_size, decode_block_size; -+ u32 ecc_enccfg, ecc_deccfg; -+ int ecc_cap; -+ -+ /* Only hardware ECC mode is supported */ -+ if (nand->ecc.mode != NAND_ECC_HW_SYNDROME) { -+ dev_err(nfc->dev, "Only hardware ECC mode is supported\n"); -+ return -EINVAL; -+ } -+ -+ nand->ecc.size = ECC_SECTOR_SIZE; -+ nand->ecc.steps = mtd->writesize / nand->ecc.size; -+ -+ spare_per_sector = mtd->oobsize / nand->ecc.steps; -+ -+ ecc_cap = mt7621_nfc_calc_ecc_strength(nfc, -+ spare_per_sector - NFI_FDM_SIZE); -+ if (ecc_cap < 0) -+ return ecc_cap; -+ -+ /* Sector + FDM */ -+ encode_block_size = (nand->ecc.size + NFI_FDM_SIZE) * 8; -+ ecc_enccfg = ecc_cap | (ENC_MODE_NFI << ENC_MODE_S) | -+ (encode_block_size << ENC_CNFG_MSG_S); -+ -+ /* Sector + FDM + ECC parity bits */ -+ decode_block_size = ((nand->ecc.size + NFI_FDM_SIZE) * 8) + -+ nand->ecc.strength * ECC_PARITY_BITS; -+ ecc_deccfg = ecc_cap | (DEC_MODE_NFI << DEC_MODE_S) | -+ (decode_block_size << DEC_CS_S) | -+ (DEC_CON_EL << DEC_CON_S) | DEC_EMPTY_EN; -+ -+ mt7621_ecc_encoder_op(nfc, false); -+ ecc_write32(nfc, ECC_ENCCNFG, ecc_enccfg); -+ -+ mt7621_ecc_decoder_op(nfc, false); -+ ecc_write32(nfc, ECC_DECCNFG, ecc_deccfg); -+ -+ return 0; -+} -+ -+static int mt7621_nfc_set_page_format(struct mt7621_nfc *nfc) -+{ -+ struct nand_chip *nand = &nfc->nand; -+ struct mtd_info *mtd = nand_to_mtd(nand); -+ int i, spare_size; -+ u32 pagefmt; -+ -+ spare_size = mt7621_nfc_set_spare_per_sector(nfc); -+ if (spare_size < 0) -+ return spare_size; -+ -+ for (i = 0; i < ARRAY_SIZE(mt7621_nfi_page_size); i++) { -+ if (mt7621_nfi_page_size[i] == mtd->writesize) -+ break; -+ } -+ -+ if (unlikely(i >= ARRAY_SIZE(mt7621_nfi_page_size))) { -+ dev_err(nfc->dev, "Page size (%u) is not supported\n", -+ mtd->writesize); -+ return -EINVAL; -+ } -+ -+ pagefmt = i | (spare_size << PAGEFMT_SPARE_S) | -+ (NFI_FDM_SIZE << PAGEFMT_FDM_S) | -+ (NFI_FDM_SIZE << PAGEFMT_FDM_ECC_S); -+ -+ nfi_write16(nfc, NFI_PAGEFMT, pagefmt); -+ -+ return 0; -+} -+ -+static int mt7621_nfc_attach_chip(struct nand_chip *nand) -+{ -+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); -+ int ret; -+ -+ if (nand->options & NAND_BUSWIDTH_16) { -+ dev_err(nfc->dev, "16-bit buswidth is not supported"); -+ return -EINVAL; -+ } -+ -+ ret = mt7621_nfc_ecc_init(nfc); -+ if (ret) -+ return ret; -+ -+ return mt7621_nfc_set_page_format(nfc); -+} -+ -+static const struct nand_controller_ops mt7621_nfc_controller_ops = { -+ .attach_chip = mt7621_nfc_attach_chip, -+ .exec_op = mt7621_nfc_exec_op, -+ .setup_data_interface = mt7621_nfc_setup_data_interface, -+}; -+ -+static int mt7621_nfc_ooblayout_free(struct mtd_info *mtd, int section, -+ struct mtd_oob_region *oob_region) -+{ -+ struct nand_chip *nand = mtd_to_nand(mtd); -+ -+ if (section >= nand->ecc.steps) -+ return -ERANGE; -+ -+ oob_region->length = NFI_FDM_SIZE - 1; -+ oob_region->offset = section * NFI_FDM_SIZE + 1; -+ -+ return 0; -+} -+ -+static int mt7621_nfc_ooblayout_ecc(struct mtd_info *mtd, int section, -+ struct mtd_oob_region *oob_region) -+{ -+ struct nand_chip *nand = mtd_to_nand(mtd); -+ -+ if (section) -+ return -ERANGE; -+ -+ oob_region->offset = NFI_FDM_SIZE * nand->ecc.steps; -+ oob_region->length = mtd->oobsize - oob_region->offset; -+ -+ return 0; -+} -+ -+static const struct mtd_ooblayout_ops mt7621_nfc_ooblayout_ops = { -+ .free = mt7621_nfc_ooblayout_free, -+ .ecc = mt7621_nfc_ooblayout_ecc, -+}; -+ -+static void mt7621_nfc_write_fdm(struct mt7621_nfc *nfc) -+{ -+ struct nand_chip *nand = &nfc->nand; -+ u32 vall, valm; -+ u8 *oobptr; -+ int i, j; -+ -+ for (i = 0; i < nand->ecc.steps; i++) { -+ vall = 0; -+ valm = 0; -+ oobptr = oob_fdm_ptr(nand, i); -+ -+ for (j = 0; j < 4; j++) -+ vall |= (u32)oobptr[j] << (j * 8); -+ -+ for (j = 0; j < 4; j++) -+ valm |= (u32)oobptr[j + 4] << (j * 8); -+ -+ nfi_write32(nfc, NFI_FDML(i), vall); -+ nfi_write32(nfc, NFI_FDMM(i), valm); -+ } -+} -+ -+static void mt7621_nfc_read_sector_fdm(struct mt7621_nfc *nfc, u32 sect) -+{ -+ struct nand_chip *nand = &nfc->nand; -+ u32 vall, valm; -+ u8 *oobptr; -+ int i; -+ -+ vall = nfi_read32(nfc, NFI_FDML(sect)); -+ valm = nfi_read32(nfc, NFI_FDMM(sect)); -+ oobptr = oob_fdm_ptr(nand, sect); -+ -+ for (i = 0; i < 4; i++) -+ oobptr[i] = (vall >> (i * 8)) & 0xff; -+ -+ for (i = 0; i < 4; i++) -+ oobptr[i + 4] = (valm >> (i * 8)) & 0xff; -+} -+ -+static int mt7621_nfc_read_page_hwecc(struct nand_chip *nand, uint8_t *buf, -+ int oob_required, int page) -+{ -+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); -+ struct mtd_info *mtd = nand_to_mtd(nand); -+ int bitflips = 0; -+ int rc, i; -+ -+ nand_read_page_op(nand, page, 0, NULL, 0); -+ -+ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S) | -+ CNFG_READ_MODE | CNFG_AUTO_FMT_EN | CNFG_HW_ECC_EN); -+ -+ mt7621_ecc_decoder_op(nfc, true); -+ -+ nfi_write16(nfc, NFI_CON, -+ CON_NFI_BRD | (nand->ecc.steps << CON_NFI_SEC_S)); -+ -+ for (i = 0; i < nand->ecc.steps; i++) { -+ if (buf) -+ mt7621_nfc_read_data(nfc, page_data_ptr(nand, buf, i), -+ nand->ecc.size); -+ else -+ mt7621_nfc_read_data_discard(nfc, nand->ecc.size); -+ -+ rc = mt7621_ecc_decoder_wait_done(nfc, i); -+ -+ mt7621_nfc_read_sector_fdm(nfc, i); -+ -+ if (rc < 0) { -+ bitflips = -EIO; -+ continue; -+ } -+ -+ rc = mt7621_ecc_correct_check(nfc, -+ buf ? page_data_ptr(nand, buf, i) : NULL, -+ oob_fdm_ptr(nand, i), i); -+ -+ if (rc < 0) { -+ dev_dbg(nfc->dev, -+ "Uncorrectable ECC error at page %d.%d\n", -+ page, i); -+ bitflips = -EBADMSG; -+ mtd->ecc_stats.failed++; -+ } else if (bitflips >= 0) { -+ bitflips += rc; -+ mtd->ecc_stats.corrected += rc; -+ } -+ } -+ -+ mt7621_ecc_decoder_op(nfc, false); -+ -+ nfi_write16(nfc, NFI_CON, 0); -+ -+ return bitflips; -+} -+ -+static int mt7621_nfc_read_page_raw(struct nand_chip *nand, uint8_t *buf, -+ int oob_required, int page) -+{ -+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); -+ int i; -+ -+ nand_read_page_op(nand, page, 0, NULL, 0); -+ -+ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S) | -+ CNFG_READ_MODE); -+ -+ nfi_write16(nfc, NFI_CON, -+ CON_NFI_BRD | (nand->ecc.steps << CON_NFI_SEC_S)); -+ -+ for (i = 0; i < nand->ecc.steps; i++) { -+ /* Read data */ -+ if (buf) -+ mt7621_nfc_read_data(nfc, page_data_ptr(nand, buf, i), -+ nand->ecc.size); -+ else -+ mt7621_nfc_read_data_discard(nfc, nand->ecc.size); -+ -+ /* Read FDM */ -+ mt7621_nfc_read_data(nfc, oob_fdm_ptr(nand, i), NFI_FDM_SIZE); -+ -+ /* Read ECC parity data */ -+ mt7621_nfc_read_data(nfc, oob_ecc_ptr(nfc, i), -+ nfc->spare_per_sector - NFI_FDM_SIZE); -+ } -+ -+ nfi_write16(nfc, NFI_CON, 0); -+ -+ return 0; -+} -+ -+static int mt7621_nfc_read_oob_hwecc(struct nand_chip *nand, int page) -+{ -+ return mt7621_nfc_read_page_hwecc(nand, NULL, 1, page); -+} -+ -+static int mt7621_nfc_read_oob_raw(struct nand_chip *nand, int page) -+{ -+ return mt7621_nfc_read_page_raw(nand, NULL, 1, page); -+} -+ -+static int mt7621_nfc_check_empty_page(struct nand_chip *nand, const u8 *buf) -+{ -+ struct mtd_info *mtd = nand_to_mtd(nand); -+ uint32_t i, j; -+ u8 *oobptr; -+ -+ if (buf) { -+ for (i = 0; i < mtd->writesize; i++) -+ if (buf[i] != 0xff) -+ return 0; -+ } -+ -+ for (i = 0; i < nand->ecc.steps; i++) { -+ oobptr = oob_fdm_ptr(nand, i); -+ for (j = 0; j < NFI_FDM_SIZE; j++) -+ if (oobptr[j] != 0xff) -+ return 0; -+ } -+ -+ return 1; -+} -+ -+static int mt7621_nfc_write_page_hwecc(struct nand_chip *nand, -+ const uint8_t *buf, int oob_required, -+ int page) -+{ -+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); -+ struct mtd_info *mtd = nand_to_mtd(nand); -+ -+ if (mt7621_nfc_check_empty_page(nand, buf)) { -+ /* -+ * MT7621 ECC engine always generates parity code for input -+ * pages, even for empty pages. Doing so will write back ECC -+ * parity code to the oob region, which means such pages will -+ * no longer be empty pages. -+ * -+ * To avoid this, stop write operation if current page is an -+ * empty page. -+ */ -+ return 0; -+ } -+ -+ nand_prog_page_begin_op(nand, page, 0, NULL, 0); -+ -+ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S) | -+ CNFG_AUTO_FMT_EN | CNFG_HW_ECC_EN); -+ -+ mt7621_ecc_encoder_op(nfc, true); -+ -+ mt7621_nfc_write_fdm(nfc); -+ -+ nfi_write16(nfc, NFI_CON, -+ CON_NFI_BWR | (nand->ecc.steps << CON_NFI_SEC_S)); -+ -+ if (buf) -+ mt7621_nfc_write_data(nfc, buf, mtd->writesize); -+ else -+ mt7621_nfc_write_data_empty(nfc, mtd->writesize); -+ -+ mt7621_nfc_wait_write_completion(nfc, nand); -+ -+ mt7621_ecc_encoder_op(nfc, false); -+ -+ nfi_write16(nfc, NFI_CON, 0); -+ -+ return nand_prog_page_end_op(nand); -+} -+ -+static int mt7621_nfc_write_page_raw(struct nand_chip *nand, -+ const uint8_t *buf, int oob_required, -+ int page) -+{ -+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); -+ int i; -+ -+ nand_prog_page_begin_op(nand, page, 0, NULL, 0); -+ -+ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S)); -+ -+ nfi_write16(nfc, NFI_CON, -+ CON_NFI_BWR | (nand->ecc.steps << CON_NFI_SEC_S)); -+ -+ for (i = 0; i < nand->ecc.steps; i++) { -+ /* Write data */ -+ if (buf) -+ mt7621_nfc_write_data(nfc, page_data_ptr(nand, buf, i), -+ nand->ecc.size); -+ else -+ mt7621_nfc_write_data_empty(nfc, nand->ecc.size); -+ -+ /* Write FDM */ -+ mt7621_nfc_write_data(nfc, oob_fdm_ptr(nand, i), -+ NFI_FDM_SIZE); -+ -+ /* Write dummy ECC parity data */ -+ mt7621_nfc_write_data_empty(nfc, nfc->spare_per_sector - -+ NFI_FDM_SIZE); -+ } -+ -+ mt7621_nfc_wait_write_completion(nfc, nand); -+ -+ nfi_write16(nfc, NFI_CON, 0); -+ -+ return nand_prog_page_end_op(nand); -+} -+ -+static int mt7621_nfc_write_oob_hwecc(struct nand_chip *nand, int page) -+{ -+ return mt7621_nfc_write_page_hwecc(nand, NULL, 1, page); -+} -+ -+static int mt7621_nfc_write_oob_raw(struct nand_chip *nand, int page) -+{ -+ return mt7621_nfc_write_page_raw(nand, NULL, 1, page); -+} -+ -+static int mt7621_nfc_init_chip(struct mt7621_nfc *nfc) -+{ -+ struct nand_chip *nand = &nfc->nand; -+ struct mtd_info *mtd; -+ int ret; -+ -+ nand->controller = &nfc->controller; -+ nand_set_controller_data(nand, (void *)nfc); -+ nand_set_flash_node(nand, nfc->dev->of_node); -+ -+ nand->options |= NAND_USE_BOUNCE_BUFFER | NAND_NO_SUBPAGE_WRITE; -+ if (!nfc->nfi_clk) -+ nand->options |= NAND_KEEP_TIMINGS; -+ -+ nand->ecc.mode = NAND_ECC_HW_SYNDROME; -+ nand->ecc.read_page = mt7621_nfc_read_page_hwecc; -+ nand->ecc.read_page_raw = mt7621_nfc_read_page_raw; -+ nand->ecc.write_page = mt7621_nfc_write_page_hwecc; -+ nand->ecc.write_page_raw = mt7621_nfc_write_page_raw; -+ nand->ecc.read_oob = mt7621_nfc_read_oob_hwecc; -+ nand->ecc.read_oob_raw = mt7621_nfc_read_oob_raw; -+ nand->ecc.write_oob = mt7621_nfc_write_oob_hwecc; -+ nand->ecc.write_oob_raw = mt7621_nfc_write_oob_raw; -+ -+ mtd = nand_to_mtd(nand); -+ mtd->owner = THIS_MODULE; -+ mtd->dev.parent = nfc->dev; -+ mtd->name = MT7621_NFC_NAME; -+ mtd_set_ooblayout(mtd, &mt7621_nfc_ooblayout_ops); -+ -+ mt7621_nfc_hw_init(nfc); -+ -+ ret = nand_scan(nand, 1); -+ if (ret) -+ return ret; -+ -+ ret = mtd_device_register(mtd, NULL, 0); -+ if (ret) { -+ dev_err(nfc->dev, "Failed to register MTD: %d\n", ret); -+ nand_release(nand); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static int mt7621_nfc_probe(struct platform_device *pdev) -+{ -+ struct device *dev = &pdev->dev; -+ struct mt7621_nfc *nfc; -+ struct resource *res; -+ int ret; -+ -+ nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL); -+ if (!nfc) -+ return -ENOMEM; -+ -+ nand_controller_init(&nfc->controller); -+ nfc->controller.ops = &mt7621_nfc_controller_ops; -+ nfc->dev = dev; -+ -+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nfi"); -+ nfc->nfi_regs = devm_ioremap_resource(dev, res); -+ if (IS_ERR(nfc->nfi_regs)) { -+ ret = PTR_ERR(nfc->nfi_regs); -+ return ret; -+ } -+ -+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ecc"); -+ nfc->ecc_regs = devm_ioremap_resource(dev, res); -+ if (IS_ERR(nfc->ecc_regs)) { -+ ret = PTR_ERR(nfc->ecc_regs); -+ return ret; -+ } -+ -+ nfc->nfi_clk = devm_clk_get(dev, "nfi_clk"); -+ if (IS_ERR(nfc->nfi_clk)) { -+ dev_warn(dev, "nfi clk not provided\n"); -+ nfc->nfi_clk = NULL; -+ } else { -+ ret = clk_prepare_enable(nfc->nfi_clk); -+ if (ret) { -+ dev_err(dev, "Failed to enable nfi core clock\n"); -+ return ret; -+ } -+ } -+ -+ platform_set_drvdata(pdev, nfc); -+ -+ ret = mt7621_nfc_init_chip(nfc); -+ if (ret) { -+ dev_err(dev, "Failed to initialize nand chip\n"); -+ goto clk_disable; -+ } -+ -+ return 0; -+ -+clk_disable: -+ clk_disable_unprepare(nfc->nfi_clk); -+ -+ return ret; -+} -+ -+static int mt7621_nfc_remove(struct platform_device *pdev) -+{ -+ struct mt7621_nfc *nfc = platform_get_drvdata(pdev); -+ -+ nand_release(&nfc->nand); -+ clk_disable_unprepare(nfc->nfi_clk); -+ -+ return 0; -+} -+ -+static const struct of_device_id mt7621_nfc_id_table[] = { -+ { .compatible = "mediatek,mt7621-nfc" }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(of, match); -+ -+static struct platform_driver mt7621_nfc_driver = { -+ .probe = mt7621_nfc_probe, -+ .remove = mt7621_nfc_remove, -+ .driver = { -+ .name = MT7621_NFC_NAME, -+ .owner = THIS_MODULE, -+ .of_match_table = mt7621_nfc_id_table, -+ }, -+}; -+module_platform_driver(mt7621_nfc_driver); -+ -+MODULE_LICENSE("GPL"); -+MODULE_AUTHOR("Weijie Gao "); -+MODULE_DESCRIPTION("MediaTek MT7621 NAND Flash Controller driver"); -diff --git a/target/linux/ramips/patches-5.4/0300-mtd-rawnand-add-driver-support-for-MT7621-nand-flash.patch b/target/linux/ramips/patches-5.4/0300-mtd-rawnand-add-driver-support-for-MT7621-nand-flash.patch -index ba844fed0f0e..586db208dbe6 100644 ---- a/target/linux/ramips/patches-5.4/0300-mtd-rawnand-add-driver-support-for-MT7621-nand-flash.patch -+++ b/target/linux/ramips/patches-5.4/0300-mtd-rawnand-add-driver-support-for-MT7621-nand-flash.patch -@@ -45,1356 +45,4 @@ Signed-off-by: Weijie Gao - obj-$(CONFIG_MTD_NAND_MTK) += mtk_ecc.o mtk_nand.o - obj-$(CONFIG_MTD_NAND_MXIC) += mxic_nand.o - obj-$(CONFIG_MTD_NAND_TEGRA) += tegra_nand.o ----- /dev/null --+++ b/drivers/mtd/nand/raw/mt7621_nand.c --@@ -0,0 +1,1350 @@ --+// SPDX-License-Identifier: GPL-2.0 --+/* --+ * MediaTek MT7621 NAND Flash Controller driver --+ * --+ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. --+ * --+ * Author: Weijie Gao --+ */ --+ --+#include --+#include --+#include --+#include --+#include --+#include --+#include --+#include --+#include --+#include --+#include --+#include --+#include --+ --+/* NFI core registers */ --+#define NFI_CNFG 0x000 --+#define CNFG_OP_MODE_S 12 --+#define CNFG_OP_MODE_M GENMASK(14, 12) --+#define CNFG_OP_CUSTOM 6 --+#define CNFG_AUTO_FMT_EN BIT(9) --+#define CNFG_HW_ECC_EN BIT(8) --+#define CNFG_BYTE_RW BIT(6) --+#define CNFG_READ_MODE BIT(1) --+ --+#define NFI_PAGEFMT 0x004 --+#define PAGEFMT_FDM_ECC_S 12 --+#define PAGEFMT_FDM_ECC_M GENMASK(15, 12) --+#define PAGEFMT_FDM_S 8 --+#define PAGEFMT_FDM_M GENMASK(11, 8) --+#define PAGEFMT_SPARE_S 4 --+#define PAGEFMT_SPARE_M GENMASK(5, 4) --+#define PAGEFMT_PAGE_S 0 --+#define PAGEFMT_PAGE_M GENMASK(1, 0) --+ --+#define NFI_CON 0x008 --+#define CON_NFI_SEC_S 12 --+#define CON_NFI_SEC_M GENMASK(15, 12) --+#define CON_NFI_BWR BIT(9) --+#define CON_NFI_BRD BIT(8) --+#define CON_NFI_RST BIT(1) --+#define CON_FIFO_FLUSH BIT(0) --+ --+#define NFI_ACCCON 0x00c --+#define ACCCON_POECS_S 28 --+#define ACCCON_POECS_MAX 0x0f --+#define ACCCON_POECS_DEF 3 --+#define ACCCON_PRECS_S 22 --+#define ACCCON_PRECS_MAX 0x3f --+#define ACCCON_PRECS_DEF 3 --+#define ACCCON_C2R_S 16 --+#define ACCCON_C2R_MAX 0x3f --+#define ACCCON_C2R_DEF 7 --+#define ACCCON_W2R_S 12 --+#define ACCCON_W2R_MAX 0x0f --+#define ACCCON_W2R_DEF 7 --+#define ACCCON_WH_S 8 --+#define ACCCON_WH_MAX 0x0f --+#define ACCCON_WH_DEF 15 --+#define ACCCON_WST_S 4 --+#define ACCCON_WST_MAX 0x0f --+#define ACCCON_WST_DEF 15 --+#define ACCCON_WST_MIN 3 --+#define ACCCON_RLT_S 0 --+#define ACCCON_RLT_MAX 0x0f --+#define ACCCON_RLT_DEF 15 --+#define ACCCON_RLT_MIN 3 --+ --+#define NFI_CMD 0x020 --+ --+#define NFI_ADDRNOB 0x030 --+#define ADDR_ROW_NOB_S 4 --+#define ADDR_ROW_NOB_M GENMASK(6, 4) --+#define ADDR_COL_NOB_S 0 --+#define ADDR_COL_NOB_M GENMASK(2, 0) --+ --+#define NFI_COLADDR 0x034 --+#define NFI_ROWADDR 0x038 --+ --+#define NFI_STRDATA 0x040 --+#define STR_DATA BIT(0) --+ --+#define NFI_CNRNB 0x044 --+#define CB2R_TIME_S 4 --+#define CB2R_TIME_M GENMASK(7, 4) --+#define STR_CNRNB BIT(0) --+ --+#define NFI_DATAW 0x050 --+#define NFI_DATAR 0x054 --+ --+#define NFI_PIO_DIRDY 0x058 --+#define PIO_DIRDY BIT(0) --+ --+#define NFI_STA 0x060 --+#define STA_NFI_FSM_S 16 --+#define STA_NFI_FSM_M GENMASK(19, 16) --+#define STA_FSM_CUSTOM_DATA 14 --+#define STA_BUSY BIT(8) --+#define STA_ADDR BIT(1) --+#define STA_CMD BIT(0) --+ --+#define NFI_ADDRCNTR 0x070 --+#define SEC_CNTR_S 12 --+#define SEC_CNTR_M GENMASK(15, 12) --+#define SEC_ADDR_S 0 --+#define SEC_ADDR_M GENMASK(9, 0) --+ --+#define NFI_CSEL 0x090 --+#define CSEL_S 0 --+#define CSEL_M GENMASK(1, 0) --+ --+#define NFI_FDM0L 0x0a0 --+#define NFI_FDML(n) (0x0a0 + ((n) << 3)) --+ --+#define NFI_FDM0M 0x0a4 --+#define NFI_FDMM(n) (0x0a4 + ((n) << 3)) --+ --+#define NFI_MASTER_STA 0x210 --+#define MAS_ADDR GENMASK(11, 9) --+#define MAS_RD GENMASK(8, 6) --+#define MAS_WR GENMASK(5, 3) --+#define MAS_RDDLY GENMASK(2, 0) --+ --+/* ECC engine registers */ --+#define ECC_ENCCON 0x000 --+#define ENC_EN BIT(0) --+ --+#define ECC_ENCCNFG 0x004 --+#define ENC_CNFG_MSG_S 16 --+#define ENC_CNFG_MSG_M GENMASK(28, 16) --+#define ENC_MODE_S 4 --+#define ENC_MODE_M GENMASK(5, 4) --+#define ENC_MODE_NFI 1 --+#define ENC_TNUM_S 0 --+#define ENC_TNUM_M GENMASK(2, 0) --+ --+#define ECC_ENCIDLE 0x00c --+#define ENC_IDLE BIT(0) --+ --+#define ECC_DECCON 0x100 --+#define DEC_EN BIT(0) --+ --+#define ECC_DECCNFG 0x104 --+#define DEC_EMPTY_EN BIT(31) --+#define DEC_CS_S 16 --+#define DEC_CS_M GENMASK(28, 16) --+#define DEC_CON_S 12 --+#define DEC_CON_M GENMASK(13, 12) --+#define DEC_CON_EL 2 --+#define DEC_MODE_S 4 --+#define DEC_MODE_M GENMASK(5, 4) --+#define DEC_MODE_NFI 1 --+#define DEC_TNUM_S 0 --+#define DEC_TNUM_M GENMASK(2, 0) --+ --+#define ECC_DECIDLE 0x10c --+#define DEC_IDLE BIT(1) --+ --+#define ECC_DECENUM 0x114 --+#define ERRNUM_S 2 --+#define ERRNUM_M GENMASK(3, 0) --+ --+#define ECC_DECDONE 0x118 --+#define DEC_DONE7 BIT(7) --+#define DEC_DONE6 BIT(6) --+#define DEC_DONE5 BIT(5) --+#define DEC_DONE4 BIT(4) --+#define DEC_DONE3 BIT(3) --+#define DEC_DONE2 BIT(2) --+#define DEC_DONE1 BIT(1) --+#define DEC_DONE0 BIT(0) --+ --+#define ECC_DECEL(n) (0x11c + (n) * 4) --+#define DEC_EL_ODD_S 16 --+#define DEC_EL_EVEN_S 0 --+#define DEC_EL_M 0x1fff --+#define DEC_EL_BYTE_POS_S 3 --+#define DEC_EL_BIT_POS_M GENMASK(3, 0) --+ --+#define ECC_FDMADDR 0x13c --+ --+/* ENCIDLE and DECIDLE */ --+#define ECC_IDLE BIT(0) --+ --+#define ACCTIMING(tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt) \ --+ ((tpoecs) << ACCCON_POECS_S | (tprecs) << ACCCON_PRECS_S | \ --+ (tc2r) << ACCCON_C2R_S | (tw2r) << ACCCON_W2R_S | \ --+ (twh) << ACCCON_WH_S | (twst) << ACCCON_WST_S | (trlt)) --+ --+#define MASTER_STA_MASK (MAS_ADDR | MAS_RD | MAS_WR | \ --+ MAS_RDDLY) --+#define NFI_RESET_TIMEOUT 1000000 --+#define NFI_CORE_TIMEOUT 500000 --+#define ECC_ENGINE_TIMEOUT 500000 --+ --+#define ECC_SECTOR_SIZE 512 --+#define ECC_PARITY_BITS 13 --+ --+#define NFI_FDM_SIZE 8 --+ --+#define MT7621_NFC_NAME "mt7621-nand" --+ --+struct mt7621_nfc { --+ struct nand_controller controller; --+ struct nand_chip nand; --+ struct clk *nfi_clk; --+ struct device *dev; --+ --+ void __iomem *nfi_regs; --+ void __iomem *ecc_regs; --+ --+ u32 spare_per_sector; --+}; --+ --+static const u16 mt7621_nfi_page_size[] = { SZ_512, SZ_2K, SZ_4K }; --+static const u8 mt7621_nfi_spare_size[] = { 16, 26, 27, 28 }; --+static const u8 mt7621_ecc_strength[] = { 4, 6, 8, 10, 12 }; --+ --+static inline u32 nfi_read32(struct mt7621_nfc *nfc, u32 reg) --+{ --+ return readl(nfc->nfi_regs + reg); --+} --+ --+static inline void nfi_write32(struct mt7621_nfc *nfc, u32 reg, u32 val) --+{ --+ writel(val, nfc->nfi_regs + reg); --+} --+ --+static inline u16 nfi_read16(struct mt7621_nfc *nfc, u32 reg) --+{ --+ return readw(nfc->nfi_regs + reg); --+} --+ --+static inline void nfi_write16(struct mt7621_nfc *nfc, u32 reg, u16 val) --+{ --+ writew(val, nfc->nfi_regs + reg); --+} --+ --+static inline void ecc_write16(struct mt7621_nfc *nfc, u32 reg, u16 val) --+{ --+ writew(val, nfc->ecc_regs + reg); --+} --+ --+static inline u32 ecc_read32(struct mt7621_nfc *nfc, u32 reg) --+{ --+ return readl(nfc->ecc_regs + reg); --+} --+ --+static inline void ecc_write32(struct mt7621_nfc *nfc, u32 reg, u32 val) --+{ --+ return writel(val, nfc->ecc_regs + reg); --+} --+ --+static inline u8 *oob_fdm_ptr(struct nand_chip *nand, int sect) --+{ --+ return nand->oob_poi + sect * NFI_FDM_SIZE; --+} --+ --+static inline u8 *oob_ecc_ptr(struct mt7621_nfc *nfc, int sect) --+{ --+ struct nand_chip *nand = &nfc->nand; --+ --+ return nand->oob_poi + nand->ecc.steps * NFI_FDM_SIZE + --+ sect * (nfc->spare_per_sector - NFI_FDM_SIZE); --+} --+ --+static inline u8 *page_data_ptr(struct nand_chip *nand, const u8 *buf, --+ int sect) --+{ --+ return (u8 *)buf + sect * nand->ecc.size; --+} --+ --+static int mt7621_ecc_wait_idle(struct mt7621_nfc *nfc, u32 reg) --+{ --+ struct device *dev = nfc->dev; --+ u32 val; --+ int ret; --+ --+ ret = readw_poll_timeout_atomic(nfc->ecc_regs + reg, val, --+ val & ECC_IDLE, 10, --+ ECC_ENGINE_TIMEOUT); --+ if (ret) { --+ dev_warn(dev, "ECC engine timed out entering idle mode\n"); --+ return -EIO; --+ } --+ --+ return 0; --+} --+ --+static int mt7621_ecc_decoder_wait_done(struct mt7621_nfc *nfc, u32 sect) --+{ --+ struct device *dev = nfc->dev; --+ u32 val; --+ int ret; --+ --+ ret = readw_poll_timeout_atomic(nfc->ecc_regs + ECC_DECDONE, val, --+ val & (1 << sect), 10, --+ ECC_ENGINE_TIMEOUT); --+ --+ if (ret) { --+ dev_warn(dev, "ECC decoder for sector %d timed out\n", --+ sect); --+ return -ETIMEDOUT; --+ } --+ --+ return 0; --+} --+ --+static void mt7621_ecc_encoder_op(struct mt7621_nfc *nfc, bool enable) --+{ --+ mt7621_ecc_wait_idle(nfc, ECC_ENCIDLE); --+ ecc_write16(nfc, ECC_ENCCON, enable ? ENC_EN : 0); --+} --+ --+static void mt7621_ecc_decoder_op(struct mt7621_nfc *nfc, bool enable) --+{ --+ mt7621_ecc_wait_idle(nfc, ECC_DECIDLE); --+ ecc_write16(nfc, ECC_DECCON, enable ? DEC_EN : 0); --+} --+ --+static int mt7621_ecc_correct_check(struct mt7621_nfc *nfc, u8 *sector_buf, --+ u8 *fdm_buf, u32 sect) --+{ --+ struct nand_chip *nand = &nfc->nand; --+ u32 decnum, num_error_bits, fdm_end_bits; --+ u32 error_locations, error_bit_loc; --+ u32 error_byte_pos, error_bit_pos; --+ int bitflips = 0; --+ u32 i; --+ --+ decnum = ecc_read32(nfc, ECC_DECENUM); --+ num_error_bits = (decnum >> (sect << ERRNUM_S)) & ERRNUM_M; --+ fdm_end_bits = (nand->ecc.size + NFI_FDM_SIZE) << 3; --+ --+ if (!num_error_bits) --+ return 0; --+ --+ if (num_error_bits == ERRNUM_M) --+ return -1; --+ --+ for (i = 0; i < num_error_bits; i++) { --+ error_locations = ecc_read32(nfc, ECC_DECEL(i / 2)); --+ error_bit_loc = (error_locations >> ((i % 2) * DEC_EL_ODD_S)) & --+ DEC_EL_M; --+ error_byte_pos = error_bit_loc >> DEC_EL_BYTE_POS_S; --+ error_bit_pos = error_bit_loc & DEC_EL_BIT_POS_M; --+ --+ if (error_bit_loc < (nand->ecc.size << 3)) { --+ if (sector_buf) { --+ sector_buf[error_byte_pos] ^= --+ (1 << error_bit_pos); --+ } --+ } else if (error_bit_loc < fdm_end_bits) { --+ if (fdm_buf) { --+ fdm_buf[error_byte_pos - nand->ecc.size] ^= --+ (1 << error_bit_pos); --+ } --+ } --+ --+ bitflips++; --+ } --+ --+ return bitflips; --+} --+ --+static int mt7621_nfc_wait_write_completion(struct mt7621_nfc *nfc, --+ struct nand_chip *nand) --+{ --+ struct device *dev = nfc->dev; --+ u16 val; --+ int ret; --+ --+ ret = readw_poll_timeout_atomic(nfc->nfi_regs + NFI_ADDRCNTR, val, --+ ((val & SEC_CNTR_M) >> SEC_CNTR_S) >= nand->ecc.steps, 10, --+ NFI_CORE_TIMEOUT); --+ --+ if (ret) { --+ dev_warn(dev, "NFI core write operation timed out\n"); --+ return -ETIMEDOUT; --+ } --+ --+ return ret; --+} --+ --+static void mt7621_nfc_hw_reset(struct mt7621_nfc *nfc) --+{ --+ u32 val; --+ int ret; --+ --+ /* reset all registers and force the NFI master to terminate */ --+ nfi_write16(nfc, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST); --+ --+ /* wait for the master to finish the last transaction */ --+ ret = readw_poll_timeout(nfc->nfi_regs + NFI_MASTER_STA, val, --+ !(val & MASTER_STA_MASK), 50, --+ NFI_RESET_TIMEOUT); --+ if (ret) { --+ dev_warn(nfc->dev, "Failed to reset NFI master in %dms\n", --+ NFI_RESET_TIMEOUT); --+ } --+ --+ /* ensure any status register affected by the NFI master is reset */ --+ nfi_write16(nfc, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST); --+ nfi_write16(nfc, NFI_STRDATA, 0); --+} --+ --+static inline void mt7621_nfc_hw_init(struct mt7621_nfc *nfc) --+{ --+ u32 acccon; --+ --+ /* --+ * CNRNB: nand ready/busy register --+ * ------------------------------- --+ * 7:4: timeout register for polling the NAND busy/ready signal --+ * 0 : poll the status of the busy/ready signal after [7:4]*16 cycles. --+ */ --+ nfi_write16(nfc, NFI_CNRNB, CB2R_TIME_M | STR_CNRNB); --+ --+ mt7621_nfc_hw_reset(nfc); --+ --+ /* Apply default access timing */ --+ acccon = ACCTIMING(ACCCON_POECS_DEF, ACCCON_PRECS_DEF, ACCCON_C2R_DEF, --+ ACCCON_W2R_DEF, ACCCON_WH_DEF, ACCCON_WST_DEF, --+ ACCCON_RLT_DEF); --+ --+ nfi_write32(nfc, NFI_ACCCON, acccon); --+} --+ --+static int mt7621_nfc_send_command(struct mt7621_nfc *nfc, u8 command) --+{ --+ struct device *dev = nfc->dev; --+ u32 val; --+ int ret; --+ --+ nfi_write32(nfc, NFI_CMD, command); --+ --+ ret = readl_poll_timeout_atomic(nfc->nfi_regs + NFI_STA, val, --+ !(val & STA_CMD), 10, --+ NFI_CORE_TIMEOUT); --+ if (ret) { --+ dev_warn(dev, "NFI core timed out entering command mode\n"); --+ return -EIO; --+ } --+ --+ return 0; --+} --+ --+static int mt7621_nfc_send_address_byte(struct mt7621_nfc *nfc, int addr) --+{ --+ struct device *dev = nfc->dev; --+ u32 val; --+ int ret; --+ --+ nfi_write32(nfc, NFI_COLADDR, addr); --+ nfi_write32(nfc, NFI_ROWADDR, 0); --+ nfi_write16(nfc, NFI_ADDRNOB, 1); --+ --+ ret = readl_poll_timeout_atomic(nfc->nfi_regs + NFI_STA, val, --+ !(val & STA_ADDR), 10, --+ NFI_CORE_TIMEOUT); --+ if (ret) { --+ dev_warn(dev, "NFI core timed out entering address mode\n"); --+ return -EIO; --+ } --+ --+ return 0; --+} --+ --+static int mt7621_nfc_send_address(struct mt7621_nfc *nfc, const u8 *addr, --+ unsigned int naddrs) --+{ --+ int ret; --+ --+ while (naddrs) { --+ ret = mt7621_nfc_send_address_byte(nfc, *addr); --+ if (ret) --+ return ret; --+ --+ addr++; --+ naddrs--; --+ } --+ --+ return 0; --+} --+ --+static void mt7621_nfc_wait_pio_ready(struct mt7621_nfc *nfc) --+{ --+ struct device *dev = nfc->dev; --+ int ret; --+ u16 val; --+ --+ ret = readw_poll_timeout_atomic(nfc->nfi_regs + NFI_PIO_DIRDY, val, --+ val & PIO_DIRDY, 10, --+ NFI_CORE_TIMEOUT); --+ if (ret < 0) --+ dev_err(dev, "NFI core PIO mode not ready\n"); --+} --+ --+static u32 mt7621_nfc_pio_read(struct mt7621_nfc *nfc, bool br) --+{ --+ u32 reg; --+ --+ /* after each byte read, the NFI_STA reg is reset by the hardware */ --+ reg = (nfi_read32(nfc, NFI_STA) & STA_NFI_FSM_M) >> STA_NFI_FSM_S; --+ if (reg != STA_FSM_CUSTOM_DATA) { --+ reg = nfi_read16(nfc, NFI_CNFG); --+ reg |= CNFG_READ_MODE | CNFG_BYTE_RW; --+ if (!br) --+ reg &= ~CNFG_BYTE_RW; --+ nfi_write16(nfc, NFI_CNFG, reg); --+ --+ /* --+ * set to max sector to allow the HW to continue reading over --+ * unaligned accesses --+ */ --+ nfi_write16(nfc, NFI_CON, CON_NFI_SEC_M | CON_NFI_BRD); --+ --+ /* trigger to fetch data */ --+ nfi_write16(nfc, NFI_STRDATA, STR_DATA); --+ } --+ --+ mt7621_nfc_wait_pio_ready(nfc); --+ --+ return nfi_read32(nfc, NFI_DATAR); --+} --+ --+static void mt7621_nfc_read_data(struct mt7621_nfc *nfc, u8 *buf, u32 len) --+{ --+ while (((uintptr_t)buf & 3) && len) { --+ *buf = mt7621_nfc_pio_read(nfc, true); --+ buf++; --+ len--; --+ } --+ --+ while (len >= 4) { --+ *(u32 *)buf = mt7621_nfc_pio_read(nfc, false); --+ buf += 4; --+ len -= 4; --+ } --+ --+ while (len) { --+ *buf = mt7621_nfc_pio_read(nfc, true); --+ buf++; --+ len--; --+ } --+} --+ --+static void mt7621_nfc_read_data_discard(struct mt7621_nfc *nfc, u32 len) --+{ --+ while (len >= 4) { --+ mt7621_nfc_pio_read(nfc, false); --+ len -= 4; --+ } --+ --+ while (len) { --+ mt7621_nfc_pio_read(nfc, true); --+ len--; --+ } --+} --+ --+static void mt7621_nfc_pio_write(struct mt7621_nfc *nfc, u32 val, bool bw) --+{ --+ u32 reg; --+ --+ reg = (nfi_read32(nfc, NFI_STA) & STA_NFI_FSM_M) >> STA_NFI_FSM_S; --+ if (reg != STA_FSM_CUSTOM_DATA) { --+ reg = nfi_read16(nfc, NFI_CNFG); --+ reg &= ~(CNFG_READ_MODE | CNFG_BYTE_RW); --+ if (bw) --+ reg |= CNFG_BYTE_RW; --+ nfi_write16(nfc, NFI_CNFG, reg); --+ --+ nfi_write16(nfc, NFI_CON, CON_NFI_SEC_M | CON_NFI_BWR); --+ nfi_write16(nfc, NFI_STRDATA, STR_DATA); --+ } --+ --+ mt7621_nfc_wait_pio_ready(nfc); --+ nfi_write32(nfc, NFI_DATAW, val); --+} --+ --+static void mt7621_nfc_write_data(struct mt7621_nfc *nfc, const u8 *buf, --+ u32 len) --+{ --+ while (((uintptr_t)buf & 3) && len) { --+ mt7621_nfc_pio_write(nfc, *buf, true); --+ buf++; --+ len--; --+ } --+ --+ while (len >= 4) { --+ mt7621_nfc_pio_write(nfc, *(const u32 *)buf, false); --+ buf += 4; --+ len -= 4; --+ } --+ --+ while (len) { --+ mt7621_nfc_pio_write(nfc, *buf, true); --+ buf++; --+ len--; --+ } --+} --+ --+static void mt7621_nfc_write_data_empty(struct mt7621_nfc *nfc, u32 len) --+{ --+ while (len >= 4) { --+ mt7621_nfc_pio_write(nfc, 0xffffffff, false); --+ len -= 4; --+ } --+ --+ while (len) { --+ mt7621_nfc_pio_write(nfc, 0xff, true); --+ len--; --+ } --+} --+ --+static int mt7621_nfc_dev_ready(struct mt7621_nfc *nfc, --+ unsigned int timeout_ms) --+{ --+ u32 val; --+ --+ return readl_poll_timeout_atomic(nfc->nfi_regs + NFI_STA, val, --+ !(val & STA_BUSY), 10, --+ timeout_ms * 1000); --+} --+ --+static int mt7621_nfc_exec_instr(struct nand_chip *nand, --+ const struct nand_op_instr *instr) --+{ --+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); --+ --+ switch (instr->type) { --+ case NAND_OP_CMD_INSTR: --+ mt7621_nfc_hw_reset(nfc); --+ nfi_write16(nfc, NFI_CNFG, CNFG_OP_CUSTOM << CNFG_OP_MODE_S); --+ return mt7621_nfc_send_command(nfc, instr->ctx.cmd.opcode); --+ case NAND_OP_ADDR_INSTR: --+ return mt7621_nfc_send_address(nfc, instr->ctx.addr.addrs, --+ instr->ctx.addr.naddrs); --+ case NAND_OP_DATA_IN_INSTR: --+ mt7621_nfc_read_data(nfc, instr->ctx.data.buf.in, --+ instr->ctx.data.len); --+ return 0; --+ case NAND_OP_DATA_OUT_INSTR: --+ mt7621_nfc_write_data(nfc, instr->ctx.data.buf.out, --+ instr->ctx.data.len); --+ return 0; --+ case NAND_OP_WAITRDY_INSTR: --+ return mt7621_nfc_dev_ready(nfc, --+ instr->ctx.waitrdy.timeout_ms); --+ default: --+ WARN_ONCE(1, "unsupported NAND instruction type: %d\n", --+ instr->type); --+ --+ return -EINVAL; --+ } --+} --+ --+static int mt7621_nfc_exec_op(struct nand_chip *nand, --+ const struct nand_operation *op, bool check_only) --+{ --+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); --+ int i, ret; --+ --+ if (check_only) --+ return 0; --+ --+ /* Only CS0 available */ --+ nfi_write16(nfc, NFI_CSEL, 0); --+ --+ for (i = 0; i < op->ninstrs; i++) { --+ ret = mt7621_nfc_exec_instr(nand, &op->instrs[i]); --+ if (ret) --+ return ret; --+ } --+ --+ return 0; --+} --+ --+static int mt7621_nfc_setup_data_interface(struct nand_chip *nand, int csline, --+ const struct nand_data_interface *conf) --+{ --+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); --+ const struct nand_sdr_timings *timings; --+ u32 acccon, temp, rate, tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt; --+ --+ if (!nfc->nfi_clk) --+ return -ENOTSUPP; --+ --+ timings = nand_get_sdr_timings(conf); --+ if (IS_ERR(timings)) --+ return -ENOTSUPP; --+ --+ rate = clk_get_rate(nfc->nfi_clk); --+ --+ /* turn clock rate into KHZ */ --+ rate /= 1000; --+ --+ tpoecs = max(timings->tALH_min, timings->tCLH_min) / 1000; --+ tpoecs = DIV_ROUND_UP(tpoecs * rate, 1000000); --+ tpoecs = min_t(u32, tpoecs, ACCCON_POECS_MAX); --+ --+ tprecs = max(timings->tCLS_min, timings->tALS_min) / 1000; --+ tprecs = DIV_ROUND_UP(tprecs * rate, 1000000); --+ tprecs = min_t(u32, tprecs, ACCCON_PRECS_MAX); --+ --+ /* sdr interface has no tCR which means CE# low to RE# low */ --+ tc2r = 0; --+ --+ tw2r = timings->tWHR_min / 1000; --+ tw2r = DIV_ROUND_UP(tw2r * rate, 1000000); --+ tw2r = DIV_ROUND_UP(tw2r - 1, 2); --+ tw2r = min_t(u32, tw2r, ACCCON_W2R_MAX); --+ --+ twh = max(timings->tREH_min, timings->tWH_min) / 1000; --+ twh = DIV_ROUND_UP(twh * rate, 1000000) - 1; --+ twh = min_t(u32, twh, ACCCON_WH_MAX); --+ --+ /* Calculate real WE#/RE# hold time in nanosecond */ --+ temp = (twh + 1) * 1000000 / rate; --+ /* nanosecond to picosecond */ --+ temp *= 1000; --+ --+ /* --+ * WE# low level time should be expaned to meet WE# pulse time --+ * and WE# cycle time at the same time. --+ */ --+ if (temp < timings->tWC_min) --+ twst = timings->tWC_min - temp; --+ else --+ twst = 0; --+ twst = max(timings->tWP_min, twst) / 1000; --+ twst = DIV_ROUND_UP(twst * rate, 1000000) - 1; --+ twst = min_t(u32, twst, ACCCON_WST_MAX); --+ --+ /* --+ * RE# low level time should be expaned to meet RE# pulse time --+ * and RE# cycle time at the same time. --+ */ --+ if (temp < timings->tRC_min) --+ trlt = timings->tRC_min - temp; --+ else --+ trlt = 0; --+ trlt = max(trlt, timings->tRP_min) / 1000; --+ trlt = DIV_ROUND_UP(trlt * rate, 1000000) - 1; --+ trlt = min_t(u32, trlt, ACCCON_RLT_MAX); --+ --+ if (csline == NAND_DATA_IFACE_CHECK_ONLY) { --+ if (twst < ACCCON_WST_MIN || trlt < ACCCON_RLT_MIN) --+ return -ENOTSUPP; --+ } --+ --+ acccon = ACCTIMING(tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt); --+ --+ dev_info(nfc->dev, "Using programmed access timing: %08x\n", acccon); --+ --+ nfi_write32(nfc, NFI_ACCCON, acccon); --+ --+ return 0; --+} --+ --+static int mt7621_nfc_calc_ecc_strength(struct mt7621_nfc *nfc, --+ u32 avail_ecc_bytes) --+{ --+ struct nand_chip *nand = &nfc->nand; --+ struct mtd_info *mtd = nand_to_mtd(nand); --+ u32 strength; --+ int i; --+ --+ strength = avail_ecc_bytes * 8 / ECC_PARITY_BITS; --+ --+ /* Find the closest supported ecc strength */ --+ for (i = ARRAY_SIZE(mt7621_ecc_strength) - 1; i >= 0; i--) { --+ if (mt7621_ecc_strength[i] <= strength) --+ break; --+ } --+ --+ if (unlikely(i < 0)) { --+ dev_err(nfc->dev, "OOB size (%u) is not supported\n", --+ mtd->oobsize); --+ return -EINVAL; --+ } --+ --+ nand->ecc.strength = mt7621_ecc_strength[i]; --+ nand->ecc.bytes = --+ DIV_ROUND_UP(nand->ecc.strength * ECC_PARITY_BITS, 8); --+ --+ dev_info(nfc->dev, "ECC strength adjusted to %u bits\n", --+ nand->ecc.strength); --+ --+ return i; --+} --+ --+static int mt7621_nfc_set_spare_per_sector(struct mt7621_nfc *nfc) --+{ --+ struct nand_chip *nand = &nfc->nand; --+ struct mtd_info *mtd = nand_to_mtd(nand); --+ u32 size; --+ int i; --+ --+ size = nand->ecc.bytes + NFI_FDM_SIZE; --+ --+ /* Find the closest supported spare size */ --+ for (i = 0; i < ARRAY_SIZE(mt7621_nfi_spare_size); i++) { --+ if (mt7621_nfi_spare_size[i] >= size) --+ break; --+ } --+ --+ if (unlikely(i >= ARRAY_SIZE(mt7621_nfi_spare_size))) { --+ dev_err(nfc->dev, "OOB size (%u) is not supported\n", --+ mtd->oobsize); --+ return -EINVAL; --+ } --+ --+ nfc->spare_per_sector = mt7621_nfi_spare_size[i]; --+ --+ return i; --+} --+ --+static int mt7621_nfc_ecc_init(struct mt7621_nfc *nfc) --+{ --+ struct nand_chip *nand = &nfc->nand; --+ struct mtd_info *mtd = nand_to_mtd(nand); --+ u32 spare_per_sector, encode_block_size, decode_block_size; --+ u32 ecc_enccfg, ecc_deccfg; --+ int ecc_cap; --+ --+ /* Only hardware ECC mode is supported */ --+ if (nand->ecc.mode != NAND_ECC_HW_SYNDROME) { --+ dev_err(nfc->dev, "Only hardware ECC mode is supported\n"); --+ return -EINVAL; --+ } --+ --+ nand->ecc.size = ECC_SECTOR_SIZE; --+ nand->ecc.steps = mtd->writesize / nand->ecc.size; --+ --+ spare_per_sector = mtd->oobsize / nand->ecc.steps; --+ --+ ecc_cap = mt7621_nfc_calc_ecc_strength(nfc, --+ spare_per_sector - NFI_FDM_SIZE); --+ if (ecc_cap < 0) --+ return ecc_cap; --+ --+ /* Sector + FDM */ --+ encode_block_size = (nand->ecc.size + NFI_FDM_SIZE) * 8; --+ ecc_enccfg = ecc_cap | (ENC_MODE_NFI << ENC_MODE_S) | --+ (encode_block_size << ENC_CNFG_MSG_S); --+ --+ /* Sector + FDM + ECC parity bits */ --+ decode_block_size = ((nand->ecc.size + NFI_FDM_SIZE) * 8) + --+ nand->ecc.strength * ECC_PARITY_BITS; --+ ecc_deccfg = ecc_cap | (DEC_MODE_NFI << DEC_MODE_S) | --+ (decode_block_size << DEC_CS_S) | --+ (DEC_CON_EL << DEC_CON_S) | DEC_EMPTY_EN; --+ --+ mt7621_ecc_encoder_op(nfc, false); --+ ecc_write32(nfc, ECC_ENCCNFG, ecc_enccfg); --+ --+ mt7621_ecc_decoder_op(nfc, false); --+ ecc_write32(nfc, ECC_DECCNFG, ecc_deccfg); --+ --+ return 0; --+} --+ --+static int mt7621_nfc_set_page_format(struct mt7621_nfc *nfc) --+{ --+ struct nand_chip *nand = &nfc->nand; --+ struct mtd_info *mtd = nand_to_mtd(nand); --+ int i, spare_size; --+ u32 pagefmt; --+ --+ spare_size = mt7621_nfc_set_spare_per_sector(nfc); --+ if (spare_size < 0) --+ return spare_size; --+ --+ for (i = 0; i < ARRAY_SIZE(mt7621_nfi_page_size); i++) { --+ if (mt7621_nfi_page_size[i] == mtd->writesize) --+ break; --+ } --+ --+ if (unlikely(i >= ARRAY_SIZE(mt7621_nfi_page_size))) { --+ dev_err(nfc->dev, "Page size (%u) is not supported\n", --+ mtd->writesize); --+ return -EINVAL; --+ } --+ --+ pagefmt = i | (spare_size << PAGEFMT_SPARE_S) | --+ (NFI_FDM_SIZE << PAGEFMT_FDM_S) | --+ (NFI_FDM_SIZE << PAGEFMT_FDM_ECC_S); --+ --+ nfi_write16(nfc, NFI_PAGEFMT, pagefmt); --+ --+ return 0; --+} --+ --+static int mt7621_nfc_attach_chip(struct nand_chip *nand) --+{ --+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); --+ int ret; --+ --+ if (nand->options & NAND_BUSWIDTH_16) { --+ dev_err(nfc->dev, "16-bit buswidth is not supported"); --+ return -EINVAL; --+ } --+ --+ ret = mt7621_nfc_ecc_init(nfc); --+ if (ret) --+ return ret; --+ --+ return mt7621_nfc_set_page_format(nfc); --+} --+ --+static const struct nand_controller_ops mt7621_nfc_controller_ops = { --+ .attach_chip = mt7621_nfc_attach_chip, --+ .exec_op = mt7621_nfc_exec_op, --+ .setup_data_interface = mt7621_nfc_setup_data_interface, --+}; --+ --+static int mt7621_nfc_ooblayout_free(struct mtd_info *mtd, int section, --+ struct mtd_oob_region *oob_region) --+{ --+ struct nand_chip *nand = mtd_to_nand(mtd); --+ --+ if (section >= nand->ecc.steps) --+ return -ERANGE; --+ --+ oob_region->length = NFI_FDM_SIZE - 1; --+ oob_region->offset = section * NFI_FDM_SIZE + 1; --+ --+ return 0; --+} --+ --+static int mt7621_nfc_ooblayout_ecc(struct mtd_info *mtd, int section, --+ struct mtd_oob_region *oob_region) --+{ --+ struct nand_chip *nand = mtd_to_nand(mtd); --+ --+ if (section) --+ return -ERANGE; --+ --+ oob_region->offset = NFI_FDM_SIZE * nand->ecc.steps; --+ oob_region->length = mtd->oobsize - oob_region->offset; --+ --+ return 0; --+} --+ --+static const struct mtd_ooblayout_ops mt7621_nfc_ooblayout_ops = { --+ .free = mt7621_nfc_ooblayout_free, --+ .ecc = mt7621_nfc_ooblayout_ecc, --+}; --+ --+static void mt7621_nfc_write_fdm(struct mt7621_nfc *nfc) --+{ --+ struct nand_chip *nand = &nfc->nand; --+ u32 vall, valm; --+ u8 *oobptr; --+ int i, j; --+ --+ for (i = 0; i < nand->ecc.steps; i++) { --+ vall = 0; --+ valm = 0; --+ oobptr = oob_fdm_ptr(nand, i); --+ --+ for (j = 0; j < 4; j++) --+ vall |= (u32)oobptr[j] << (j * 8); --+ --+ for (j = 0; j < 4; j++) --+ valm |= (u32)oobptr[j + 4] << ((j - 4) * 8); --+ --+ nfi_write32(nfc, NFI_FDML(i), vall); --+ nfi_write32(nfc, NFI_FDMM(i), valm); --+ } --+} --+ --+static void mt7621_nfc_read_sector_fdm(struct mt7621_nfc *nfc, u32 sect) --+{ --+ struct nand_chip *nand = &nfc->nand; --+ u32 vall, valm; --+ u8 *oobptr; --+ int i; --+ --+ vall = nfi_read32(nfc, NFI_FDML(sect)); --+ valm = nfi_read32(nfc, NFI_FDMM(sect)); --+ oobptr = oob_fdm_ptr(nand, sect); --+ --+ for (i = 0; i < 4; i++) --+ oobptr[i] = (vall >> (i * 8)) & 0xff; --+ --+ for (i = 0; i < 4; i++) --+ oobptr[i + 4] = (valm >> (i * 8)) & 0xff; --+} --+ --+static int mt7621_nfc_read_page_hwecc(struct nand_chip *nand, uint8_t *buf, --+ int oob_required, int page) --+{ --+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); --+ struct mtd_info *mtd = nand_to_mtd(nand); --+ int bitflips = 0; --+ int rc, i; --+ --+ nand_read_page_op(nand, page, 0, NULL, 0); --+ --+ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S) | --+ CNFG_READ_MODE | CNFG_AUTO_FMT_EN | CNFG_HW_ECC_EN); --+ --+ mt7621_ecc_decoder_op(nfc, true); --+ --+ nfi_write16(nfc, NFI_CON, --+ CON_NFI_BRD | (nand->ecc.steps << CON_NFI_SEC_S)); --+ --+ for (i = 0; i < nand->ecc.steps; i++) { --+ if (buf) --+ mt7621_nfc_read_data(nfc, page_data_ptr(nand, buf, i), --+ nand->ecc.size); --+ else --+ mt7621_nfc_read_data_discard(nfc, nand->ecc.size); --+ --+ rc = mt7621_ecc_decoder_wait_done(nfc, i); --+ --+ mt7621_nfc_read_sector_fdm(nfc, i); --+ --+ if (rc < 0) { --+ bitflips = -EIO; --+ continue; --+ } --+ --+ rc = mt7621_ecc_correct_check(nfc, --+ buf ? page_data_ptr(nand, buf, i) : NULL, --+ oob_fdm_ptr(nand, i), i); --+ --+ if (rc < 0) { --+ dev_warn(nfc->dev, --+ "Uncorrectable ECC error at page %d.%d\n", --+ page, i); --+ bitflips = -EBADMSG; --+ mtd->ecc_stats.failed++; --+ } else if (bitflips >= 0) { --+ bitflips += rc; --+ mtd->ecc_stats.corrected += rc; --+ } --+ } --+ --+ mt7621_ecc_decoder_op(nfc, false); --+ --+ nfi_write16(nfc, NFI_CON, 0); --+ --+ return bitflips; --+} --+ --+static int mt7621_nfc_read_page_raw(struct nand_chip *nand, uint8_t *buf, --+ int oob_required, int page) --+{ --+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); --+ int i; --+ --+ nand_read_page_op(nand, page, 0, NULL, 0); --+ --+ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S) | --+ CNFG_READ_MODE); --+ --+ nfi_write16(nfc, NFI_CON, --+ CON_NFI_BRD | (nand->ecc.steps << CON_NFI_SEC_S)); --+ --+ for (i = 0; i < nand->ecc.steps; i++) { --+ /* Read data */ --+ if (buf) --+ mt7621_nfc_read_data(nfc, page_data_ptr(nand, buf, i), --+ nand->ecc.size); --+ else --+ mt7621_nfc_read_data_discard(nfc, nand->ecc.size); --+ --+ /* Read FDM */ --+ mt7621_nfc_read_data(nfc, oob_fdm_ptr(nand, i), NFI_FDM_SIZE); --+ --+ /* Read ECC parity data */ --+ mt7621_nfc_read_data(nfc, oob_ecc_ptr(nfc, i), --+ nfc->spare_per_sector - NFI_FDM_SIZE); --+ } --+ --+ nfi_write16(nfc, NFI_CON, 0); --+ --+ return 0; --+} --+ --+static int mt7621_nfc_read_oob_hwecc(struct nand_chip *nand, int page) --+{ --+ return mt7621_nfc_read_page_hwecc(nand, NULL, 1, page); --+} --+ --+static int mt7621_nfc_read_oob_raw(struct nand_chip *nand, int page) --+{ --+ return mt7621_nfc_read_page_raw(nand, NULL, 1, page); --+} --+ --+static int mt7621_nfc_check_empty_page(struct nand_chip *nand, const u8 *buf) --+{ --+ struct mtd_info *mtd = nand_to_mtd(nand); --+ uint32_t i, j; --+ u8 *oobptr; --+ --+ if (buf) { --+ for (i = 0; i < mtd->writesize; i++) --+ if (buf[i] != 0xff) --+ return 0; --+ } --+ --+ for (i = 0; i < nand->ecc.steps; i++) { --+ oobptr = oob_fdm_ptr(nand, i); --+ for (j = 0; j < NFI_FDM_SIZE; j++) --+ if (oobptr[j] != 0xff) --+ return 0; --+ } --+ --+ return 1; --+} --+ --+static int mt7621_nfc_write_page_hwecc(struct nand_chip *nand, --+ const uint8_t *buf, int oob_required, --+ int page) --+{ --+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); --+ struct mtd_info *mtd = nand_to_mtd(nand); --+ --+ if (mt7621_nfc_check_empty_page(nand, buf)) { --+ /* --+ * MT7621 ECC engine always generates parity code for input --+ * pages, even for empty pages. Doing so will write back ECC --+ * parity code to the oob region, which means such pages will --+ * no longer be empty pages. --+ * --+ * To avoid this, stop write operation if current page is an --+ * empty page. --+ */ --+ return 0; --+ } --+ --+ nand_prog_page_begin_op(nand, page, 0, NULL, 0); --+ --+ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S) | --+ CNFG_AUTO_FMT_EN | CNFG_HW_ECC_EN); --+ --+ mt7621_ecc_encoder_op(nfc, true); --+ --+ mt7621_nfc_write_fdm(nfc); --+ --+ nfi_write16(nfc, NFI_CON, --+ CON_NFI_BWR | (nand->ecc.steps << CON_NFI_SEC_S)); --+ --+ if (buf) --+ mt7621_nfc_write_data(nfc, buf, mtd->writesize); --+ else --+ mt7621_nfc_write_data_empty(nfc, mtd->writesize); --+ --+ mt7621_nfc_wait_write_completion(nfc, nand); --+ --+ mt7621_ecc_encoder_op(nfc, false); --+ --+ nfi_write16(nfc, NFI_CON, 0); --+ --+ return nand_prog_page_end_op(nand); --+} --+ --+static int mt7621_nfc_write_page_raw(struct nand_chip *nand, --+ const uint8_t *buf, int oob_required, --+ int page) --+{ --+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); --+ int i; --+ --+ nand_prog_page_begin_op(nand, page, 0, NULL, 0); --+ --+ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S)); --+ --+ nfi_write16(nfc, NFI_CON, --+ CON_NFI_BWR | (nand->ecc.steps << CON_NFI_SEC_S)); --+ --+ for (i = 0; i < nand->ecc.steps; i++) { --+ /* Write data */ --+ if (buf) --+ mt7621_nfc_write_data(nfc, page_data_ptr(nand, buf, i), --+ nand->ecc.size); --+ else --+ mt7621_nfc_write_data_empty(nfc, nand->ecc.size); --+ --+ /* Write FDM */ --+ mt7621_nfc_write_data(nfc, oob_fdm_ptr(nand, i), --+ NFI_FDM_SIZE); --+ --+ /* Write dummy ECC parity data */ --+ mt7621_nfc_write_data_empty(nfc, nfc->spare_per_sector - --+ NFI_FDM_SIZE); --+ } --+ --+ mt7621_nfc_wait_write_completion(nfc, nand); --+ --+ nfi_write16(nfc, NFI_CON, 0); --+ --+ return nand_prog_page_end_op(nand); --+} --+ --+static int mt7621_nfc_write_oob_hwecc(struct nand_chip *nand, int page) --+{ --+ return mt7621_nfc_write_page_hwecc(nand, NULL, 1, page); --+} --+ --+static int mt7621_nfc_write_oob_raw(struct nand_chip *nand, int page) --+{ --+ return mt7621_nfc_write_page_raw(nand, NULL, 1, page); --+} --+ --+static int mt7621_nfc_init_chip(struct mt7621_nfc *nfc) --+{ --+ struct nand_chip *nand = &nfc->nand; --+ struct mtd_info *mtd; --+ int ret; --+ --+ nand->controller = &nfc->controller; --+ nand_set_controller_data(nand, (void *)nfc); --+ nand_set_flash_node(nand, nfc->dev->of_node); --+ --+ nand->options |= NAND_USE_BOUNCE_BUFFER | NAND_NO_SUBPAGE_WRITE; --+ if (!nfc->nfi_clk) --+ nand->options |= NAND_KEEP_TIMINGS; --+ --+ nand->ecc.mode = NAND_ECC_HW_SYNDROME; --+ nand->ecc.read_page = mt7621_nfc_read_page_hwecc; --+ nand->ecc.read_page_raw = mt7621_nfc_read_page_raw; --+ nand->ecc.write_page = mt7621_nfc_write_page_hwecc; --+ nand->ecc.write_page_raw = mt7621_nfc_write_page_raw; --+ nand->ecc.read_oob = mt7621_nfc_read_oob_hwecc; --+ nand->ecc.read_oob_raw = mt7621_nfc_read_oob_raw; --+ nand->ecc.write_oob = mt7621_nfc_write_oob_hwecc; --+ nand->ecc.write_oob_raw = mt7621_nfc_write_oob_raw; --+ --+ mtd = nand_to_mtd(nand); --+ mtd->owner = THIS_MODULE; --+ mtd->dev.parent = nfc->dev; --+ mtd->name = MT7621_NFC_NAME; --+ mtd_set_ooblayout(mtd, &mt7621_nfc_ooblayout_ops); --+ --+ mt7621_nfc_hw_init(nfc); --+ --+ ret = nand_scan(nand, 1); --+ if (ret) --+ return ret; --+ --+ ret = mtd_device_register(mtd, NULL, 0); --+ if (ret) { --+ dev_err(nfc->dev, "Failed to register MTD: %d\n", ret); --+ nand_release(nand); --+ return ret; --+ } --+ --+ return 0; --+} --+ --+static int mt7621_nfc_probe(struct platform_device *pdev) --+{ --+ struct device *dev = &pdev->dev; --+ struct mt7621_nfc *nfc; --+ struct resource *res; --+ int ret; --+ --+ nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL); --+ if (!nfc) --+ return -ENOMEM; --+ --+ nand_controller_init(&nfc->controller); --+ nfc->controller.ops = &mt7621_nfc_controller_ops; --+ nfc->dev = dev; --+ --+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nfi"); --+ nfc->nfi_regs = devm_ioremap_resource(dev, res); --+ if (IS_ERR(nfc->nfi_regs)) { --+ ret = PTR_ERR(nfc->nfi_regs); --+ return ret; --+ } --+ --+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ecc"); --+ nfc->ecc_regs = devm_ioremap_resource(dev, res); --+ if (IS_ERR(nfc->ecc_regs)) { --+ ret = PTR_ERR(nfc->ecc_regs); --+ return ret; --+ } --+ --+ nfc->nfi_clk = devm_clk_get(dev, "nfi_clk"); --+ if (IS_ERR(nfc->nfi_clk)) { --+ dev_warn(dev, "nfi clk not provided\n"); --+ nfc->nfi_clk = NULL; --+ } else { --+ ret = clk_prepare_enable(nfc->nfi_clk); --+ if (ret) { --+ dev_err(dev, "Failed to enable nfi core clock\n"); --+ return ret; --+ } --+ } --+ --+ platform_set_drvdata(pdev, nfc); --+ --+ ret = mt7621_nfc_init_chip(nfc); --+ if (ret) { --+ dev_err(dev, "Failed to initialize nand chip\n"); --+ goto clk_disable; --+ } --+ --+ return 0; --+ --+clk_disable: --+ clk_disable_unprepare(nfc->nfi_clk); --+ --+ return ret; --+} --+ --+static int mt7621_nfc_remove(struct platform_device *pdev) --+{ --+ struct mt7621_nfc *nfc = platform_get_drvdata(pdev); --+ --+ nand_release(&nfc->nand); --+ clk_disable_unprepare(nfc->nfi_clk); --+ --+ return 0; --+} --+ --+static const struct of_device_id mt7621_nfc_id_table[] = { --+ { .compatible = "mediatek,mt7621-nfc" }, --+ { }, --+}; --+MODULE_DEVICE_TABLE(of, match); --+ --+static struct platform_driver mt7621_nfc_driver = { --+ .probe = mt7621_nfc_probe, --+ .remove = mt7621_nfc_remove, --+ .driver = { --+ .name = MT7621_NFC_NAME, --+ .owner = THIS_MODULE, --+ .of_match_table = mt7621_nfc_id_table, --+ }, --+}; --+module_platform_driver(mt7621_nfc_driver); --+ --+MODULE_LICENSE("GPL"); --+MODULE_AUTHOR("Weijie Gao "); --+MODULE_DESCRIPTION("MediaTek MT7621 NAND Flash Controller driver"); -+ --- -2.32.0 - - -From e4726abccf94cd3e111c7d54fa5c66f9568f3a1a Mon Sep 17 00:00:00 2001 -From: Stijn Tintel -Date: Wed, 19 Jan 2022 17:59:51 +0200 -Subject: [PATCH 3/8] ramips: mt7621_nand: reduce log verbosity - -Avoid flooding the log with the message below by increasing the log -level to debug: - - mt7621-nand 1e003000.nand: Using programmed access timing: 31c07388 - -Signed-off-by: Stijn Tintel ---- - target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c -index 678295a68db3..16493a2e974c 100644 ---- a/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c -+++ b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c -@@ -759,7 +759,7 @@ static int mt7621_nfc_setup_data_interface(struct nand_chip *nand, int csline, - - acccon = ACCTIMING(tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt); - -- dev_info(nfc->dev, "Using programmed access timing: %08x\n", acccon); -+ dev_dbg(nfc->dev, "Using programmed access timing: %08x\n", acccon); - - nfi_write32(nfc, NFI_ACCCON, acccon); - --- -2.32.0 - - -From b7fcea8ff0ed783310ac72ad14c8281a6c05f6b5 Mon Sep 17 00:00:00 2001 -From: Felix Fietkau -Date: Wed, 9 Mar 2022 20:46:21 +0100 -Subject: [PATCH 4/8] ramips: mt7621_nand: initialize ECC_FDMADDR - -This is needed for the ECC controller to access FDM data - -Signed-off-by: Felix Fietkau ---- - target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c -index 16493a2e974c..0751e59c3741 100644 ---- a/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c -+++ b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c -@@ -214,6 +214,7 @@ struct mt7621_nfc { - struct clk *nfi_clk; - struct device *dev; - -+ u32 nfi_base; - void __iomem *nfi_regs; - void __iomem *ecc_regs; - -@@ -860,6 +861,8 @@ static int mt7621_nfc_ecc_init(struct mt7621_nfc *nfc) - (decode_block_size << DEC_CS_S) | - (DEC_CON_EL << DEC_CON_S) | DEC_EMPTY_EN; - -+ ecc_write32(nfc, ECC_FDMADDR, nfc->nfi_base + NFI_FDML(0)); -+ - mt7621_ecc_encoder_op(nfc, false); - ecc_write32(nfc, ECC_ENCCNFG, ecc_enccfg); - -@@ -1277,6 +1280,7 @@ static int mt7621_nfc_probe(struct platform_device *pdev) - nfc->dev = dev; - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nfi"); -+ nfc->nfi_base = res->start; - nfc->nfi_regs = devm_ioremap_resource(dev, res); - if (IS_ERR(nfc->nfi_regs)) { - ret = PTR_ERR(nfc->nfi_regs); --- -2.32.0 - - -From b848a7ab2db5e74aa4baed5842af72395e2c763e Mon Sep 17 00:00:00 2001 -From: Felix Fietkau -Date: Thu, 10 Mar 2022 11:45:00 +0100 -Subject: [PATCH 5/8] ramips: enable support for mtk_bmt in the nand flash - driver - -Signed-off-by: Felix Fietkau ---- - .../linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c -index 0751e59c3741..1072450898ab 100644 ---- a/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c -+++ b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c -@@ -18,6 +18,7 @@ - #include - #include - #include -+#include - #include - #include - -@@ -1254,9 +1255,12 @@ static int mt7621_nfc_init_chip(struct mt7621_nfc *nfc) - if (ret) - return ret; - -+ mtk_bmt_attach(mtd); -+ - ret = mtd_device_register(mtd, NULL, 0); - if (ret) { - dev_err(nfc->dev, "Failed to register MTD: %d\n", ret); -+ mtk_bmt_detach(mtd); - nand_release(nand); - return ret; - } -@@ -1325,7 +1329,11 @@ clk_disable: - static int mt7621_nfc_remove(struct platform_device *pdev) - { - struct mt7621_nfc *nfc = platform_get_drvdata(pdev); -+ struct nand_chip *nand = &nfc->nand; -+ struct mtd_info *mtd = nand_to_mtd(nand); - -+ mtk_bmt_detach(mtd); -+ mtd_device_unregister(mtd); - nand_release(&nfc->nand); - clk_disable_unprepare(nfc->nfi_clk); - --- -2.32.0 - - -From 110ae2a34144df4dde224230a25d5243c16563b5 Mon Sep 17 00:00:00 2001 -From: Felix Fietkau -Date: Thu, 10 Mar 2022 15:20:29 +0100 -Subject: [PATCH 6/8] ramips: skip bbt scan on mt7621 - -reduces unnecessary flash reads and speeds up boot time - -Signed-off-by: Felix Fietkau ---- - target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c | 3 ++- - 1 file changed, 2 insertions(+), 1 deletion(-) - -diff --git a/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c -index 1072450898ab..f01e1e8a8ec4 100644 ---- a/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c -+++ b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c -@@ -1229,7 +1229,8 @@ static int mt7621_nfc_init_chip(struct mt7621_nfc *nfc) - nand_set_controller_data(nand, (void *)nfc); - nand_set_flash_node(nand, nfc->dev->of_node); - -- nand->options |= NAND_USE_BOUNCE_BUFFER | NAND_NO_SUBPAGE_WRITE; -+ nand->options |= NAND_USE_BOUNCE_BUFFER | NAND_NO_SUBPAGE_WRITE | -+ NAND_SKIP_BBTSCAN; - if (!nfc->nfi_clk) - nand->options |= NAND_KEEP_TIMINGS; - --- -2.32.0 - - -From ef3812488bf110e05e42d7cf97e29d99248d364b Mon Sep 17 00:00:00 2001 -From: Felix Fietkau -Date: Thu, 10 Mar 2022 17:32:20 +0100 -Subject: [PATCH 7/8] kernel: mtdsplit: support UBI after FIT images - -Change the partition name accordingly. Same behavior as mtdsplit_uimage - -Signed-off-by: Felix Fietkau ---- - .../generic/files/drivers/mtd/mtdsplit/mtdsplit_fit.c | 8 ++++++-- - 1 file changed, 6 insertions(+), 2 deletions(-) - -diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_fit.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_fit.c -index 5cc1658dbde6..f0434289494d 100644 ---- a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_fit.c -+++ b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_fit.c -@@ -57,6 +57,7 @@ mtdsplit_fit_parse(struct mtd_info *mtd, - size_t fit_offset, fit_size; - size_t rootfs_offset, rootfs_size; - struct mtd_partition *parts; -+ enum mtdsplit_part_type type; - int ret; - - of_property_read_string(np, "openwrt,cmdline-match", &cmdline_match); -@@ -101,7 +102,7 @@ mtdsplit_fit_parse(struct mtd_info *mtd, - - /* Search for the rootfs partition after the FIT image */ - ret = mtd_find_rootfs_from(mtd, fit_offset + fit_size, mtd->size, -- &rootfs_offset, NULL); -+ &rootfs_offset, &type); - if (ret) { - pr_info("no rootfs found after FIT image in \"%s\"\n", - mtd->name); -@@ -118,7 +119,10 @@ mtdsplit_fit_parse(struct mtd_info *mtd, - parts[0].offset = fit_offset; - parts[0].size = mtd_rounddown_to_eb(fit_size, mtd) + mtd->erasesize; - -- parts[1].name = ROOTFS_PART_NAME; -+ if (type == MTDSPLIT_PART_TYPE_UBI) -+ parts[1].name = UBI_PART_NAME; -+ else -+ parts[1].name = ROOTFS_PART_NAME; - parts[1].offset = rootfs_offset; - parts[1].size = rootfs_size; - --- -2.32.0 - - -From 3b215d68e84ceb40979cc1db98623aaec2ec0e93 Mon Sep 17 00:00:00 2001 -From: Felix Fietkau -Date: Thu, 10 Mar 2022 18:09:15 +0100 -Subject: [PATCH 8/8] update actiontec web7200 support - -Signed-off-by: Felix Fietkau ---- - .../ramips/dts/mt7621_actiontec_web7200.dts | 98 +- - .../ramips/files/drivers/mtd/nmbm/Kconfig | 35 - - .../ramips/files/drivers/mtd/nmbm/Makefile | 6 - - .../ramips/files/drivers/mtd/nmbm/nmbm-core.c | 2936 ----------------- - .../files/drivers/mtd/nmbm/nmbm-debug.h | 20 - - .../files/drivers/mtd/nmbm/nmbm-debug.inl | 0 - .../ramips/files/drivers/mtd/nmbm/nmbm-mtd.c | 795 ----- - .../files/drivers/mtd/nmbm/nmbm-private.h | 137 - - .../linux/ramips/files/include/nmbm/nmbm-os.h | 69 - - target/linux/ramips/files/include/nmbm/nmbm.h | 102 - - target/linux/ramips/image/mt7621.mk | 20 +- - .../mt7621/base-files/lib/upgrade/platform.sh | 1 + - target/linux/ramips/mt7621/config-5.4 | 8 - - .../499-mtd-add-nmbm-support.patch | 21 - - 14 files changed, 65 insertions(+), 4183 deletions(-) - delete mode 100644 target/linux/ramips/files/drivers/mtd/nmbm/Kconfig - delete mode 100644 target/linux/ramips/files/drivers/mtd/nmbm/Makefile - delete mode 100644 target/linux/ramips/files/drivers/mtd/nmbm/nmbm-core.c - delete mode 100644 target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.h - delete mode 100644 target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.inl - delete mode 100644 target/linux/ramips/files/drivers/mtd/nmbm/nmbm-mtd.c - delete mode 100644 target/linux/ramips/files/drivers/mtd/nmbm/nmbm-private.h - delete mode 100644 target/linux/ramips/files/include/nmbm/nmbm-os.h - delete mode 100644 target/linux/ramips/files/include/nmbm/nmbm.h - delete mode 100644 target/linux/ramips/patches-5.4/499-mtd-add-nmbm-support.patch - -diff --git a/target/linux/ramips/dts/mt7621_actiontec_web7200.dts b/target/linux/ramips/dts/mt7621_actiontec_web7200.dts -index f112354ec375..eb7dbdd31689 100644 ---- a/target/linux/ramips/dts/mt7621_actiontec_web7200.dts -+++ b/target/linux/ramips/dts/mt7621_actiontec_web7200.dts -@@ -51,68 +51,60 @@ - }; - }; - -- nmbm { -- compatible = "generic,nmbm"; -- lower-mtd-device = <&nand>; -- forced-create; -- #size-cells = <0x01>; -- #address-cells = <0x01>; -- -- partitions { -- compatible = "fixed-partitions"; -- #size-cells = <0x01>; -- #address-cells = <0x01>; -- -- partition@0 { -- label = "u-boot"; -- reg = <0x00 0x80000>; -- read-only; -- }; -- -- partition@80000 { -- label = "u-boot-env"; -- reg = <0x80000 0x80000>; -- }; -- -- factory: partition@100000 { -- label = "factory"; -- reg = <0x100000 0x80000>; -- }; -- -- partition@180000 { -- compatible = "denx,fit"; -- label = "firmware"; -- openwrt,cmdline-match = "bootselect=0"; -- reg = <0x180000 0x3000000>; -- }; -- -- partition@3180000 { -- compatible = "denx,fit"; -- label = "firmware2"; -- openwrt,cmdline-match = "bootselect=1"; -- reg = <0x3180000 0x3000000>; -- }; -- -- partition@6980000 { -- label = "aei_data"; -- reg = <0x6980000 0x400000>; -- }; -- -- partition@6180000 { -- label = "aei_data_ext"; -- reg = <0x6180000 0x800000>; -- }; -- }; -- }; - }; - - &nand { - status = "okay"; - -+ mediatek,nmbm; -+ mediatek,bmt-remap-range = -+ <0x0 0x580000>, -+ <0x3180000 0x3580000>; -+ - partitions { - compatible = "fixed-partitions"; - #address-cells = <1>; - #size-cells = <1>; -+ -+ partition@0 { -+ label = "u-boot"; -+ reg = <0x00 0x80000>; -+ read-only; -+ }; -+ -+ partition@80000 { -+ label = "u-boot-env"; -+ reg = <0x80000 0x80000>; -+ }; -+ -+ factory: partition@100000 { -+ label = "factory"; -+ reg = <0x100000 0x80000>; -+ }; -+ -+ partition@180000 { -+ compatible = "denx,fit"; -+ label = "firmware"; -+ openwrt,cmdline-match = "bootselect=0"; -+ reg = <0x180000 0x3000000>; -+ }; -+ -+ partition@3180000 { -+ compatible = "denx,fit"; -+ label = "firmware2"; -+ openwrt,cmdline-match = "bootselect=1"; -+ reg = <0x3180000 0x3000000>; -+ }; -+ -+ partition@6980000 { -+ label = "aei_data"; -+ reg = <0x6980000 0x400000>; -+ }; -+ -+ partition@6180000 { -+ label = "aei_data_ext"; -+ reg = <0x6180000 0x800000>; -+ }; - }; - }; - -diff --git a/target/linux/ramips/files/drivers/mtd/nmbm/Kconfig b/target/linux/ramips/files/drivers/mtd/nmbm/Kconfig -deleted file mode 100644 -index 98df305728bc..000000000000 ---- a/target/linux/ramips/files/drivers/mtd/nmbm/Kconfig -+++ /dev/null -@@ -1,35 +0,0 @@ -- --config NMBM -- bool "Enable NAND mapping block management" -- default n -- select CRC32 -- --choice -- prompt "Default log level" -- depends on NMBM -- default NMBM_LOG_LEVEL_INFO -- --config NMBM_LOG_LEVEL_DEBUG -- bool "0 - Debug" -- --config NMBM_LOG_LEVEL_INFO -- bool "1 - Info" -- --config NMBM_LOG_LEVEL_WARN -- bool "2 - Warn" -- --config NMBM_LOG_LEVEL_ERR -- bool "3 - Error" -- --config NMBM_LOG_LEVEL_EMERG -- bool "4 - Emergency" -- --config NMBM_LOG_LEVEL_NONE -- bool "5 - None" -- --endchoice -- --config NMBM_MTD -- bool "Enable MTD based NAND mapping block management" -- default n -- depends on NMBM -diff --git a/target/linux/ramips/files/drivers/mtd/nmbm/Makefile b/target/linux/ramips/files/drivers/mtd/nmbm/Makefile -deleted file mode 100644 -index 46e6d50a800f..000000000000 ---- a/target/linux/ramips/files/drivers/mtd/nmbm/Makefile -+++ /dev/null -@@ -1,6 +0,0 @@ --# SPDX-License-Identifier: GPL-2.0 --# --# (C) Copyright 2020 MediaTek Inc. All rights reserved. -- --obj-$(CONFIG_NMBM) += nmbm-core.o --obj-$(CONFIG_NMBM_MTD) += nmbm-mtd.o -diff --git a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-core.c b/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-core.c -deleted file mode 100644 -index 18dfb6adda06..000000000000 ---- a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-core.c -+++ /dev/null -@@ -1,2936 +0,0 @@ --// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause --/* -- * Copyright (C) 2021 MediaTek Inc. All Rights Reserved. -- * -- * Author: Weijie Gao -- */ -- --#include "nmbm-private.h" -- --#include "nmbm-debug.h" -- --#define NMBM_VER_MAJOR 1 --#define NMBM_VER_MINOR 0 --#define NMBM_VER NMBM_VERSION_MAKE(NMBM_VER_MAJOR, \ -- NMBM_VER_MINOR) -- --#define NMBM_ALIGN(v, a) (((v) + (a) - 1) & ~((a) - 1)) -- --/*****************************************************************************/ --/* Logging related functions */ --/*****************************************************************************/ -- --/* -- * nmbm_log_lower - Print log using OS specific routine -- * @nld: NMBM lower device structure -- * @level: log level -- * @fmt: format string -- */ --static void nmbm_log_lower(struct nmbm_lower_device *nld, -- enum nmbm_log_category level, const char *fmt, ...) --{ -- va_list ap; -- -- if (!nld->logprint) -- return; -- -- va_start(ap, fmt); -- nld->logprint(nld->arg, level, fmt, ap); -- va_end(ap); --} -- --/* -- * nmbm_log - Print log using OS specific routine -- * @ni: NMBM instance structure -- * @level: log level -- * @fmt: format string -- */ --static void nmbm_log(struct nmbm_instance *ni, enum nmbm_log_category level, -- const char *fmt, ...) --{ -- va_list ap; -- -- if (!ni) -- return; -- -- if (!ni->lower.logprint || level < ni->log_display_level) -- return; -- -- va_start(ap, fmt); -- ni->lower.logprint(ni->lower.arg, level, fmt, ap); -- va_end(ap); --} -- --/* -- * nmbm_set_log_level - Set log display level -- * @ni: NMBM instance structure -- * @level: log display level -- */ --enum nmbm_log_category nmbm_set_log_level(struct nmbm_instance *ni, -- enum nmbm_log_category level) --{ -- enum nmbm_log_category old; -- -- if (!ni) -- return __NMBM_LOG_MAX; -- -- old = ni->log_display_level; -- ni->log_display_level = level; -- return old; --} -- --/* -- * nlog_table_creation - Print log of table creation event -- * @ni: NMBM instance structure -- * @main_table: whether the table is main info table -- * @start_ba: start block address of the table -- * @end_ba: block address after the end of the table -- */ --static void nlog_table_creation(struct nmbm_instance *ni, bool main_table, -- uint32_t start_ba, uint32_t end_ba) --{ -- if (start_ba == end_ba - 1) -- nlog_info(ni, "%s info table has been written to block %u\n", -- main_table ? "Main" : "Backup", start_ba); -- else -- nlog_info(ni, "%s info table has been written to block %u-%u\n", -- main_table ? "Main" : "Backup", start_ba, end_ba - 1); -- -- nmbm_mark_block_color_info_table(ni, start_ba, end_ba - 1); --} -- --/* -- * nlog_table_update - Print log of table update event -- * @ni: NMBM instance structure -- * @main_table: whether the table is main info table -- * @start_ba: start block address of the table -- * @end_ba: block address after the end of the table -- */ --static void nlog_table_update(struct nmbm_instance *ni, bool main_table, -- uint32_t start_ba, uint32_t end_ba) --{ -- if (start_ba == end_ba - 1) -- nlog_debug(ni, "%s info table has been updated in block %u\n", -- main_table ? "Main" : "Backup", start_ba); -- else -- nlog_debug(ni, "%s info table has been updated in block %u-%u\n", -- main_table ? "Main" : "Backup", start_ba, end_ba - 1); -- -- nmbm_mark_block_color_info_table(ni, start_ba, end_ba - 1); --} -- --/* -- * nlog_table_found - Print log of table found event -- * @ni: NMBM instance structure -- * @first_table: whether the table is first found info table -- * @write_count: write count of the info table -- * @start_ba: start block address of the table -- * @end_ba: block address after the end of the table -- */ --static void nlog_table_found(struct nmbm_instance *ni, bool first_table, -- uint32_t write_count, uint32_t start_ba, -- uint32_t end_ba) --{ -- if (start_ba == end_ba - 1) -- nlog_info(ni, "%s info table with writecount %u found in block %u\n", -- first_table ? "First" : "Second", write_count, -- start_ba); -- else -- nlog_info(ni, "%s info table with writecount %u found in block %u-%u\n", -- first_table ? "First" : "Second", write_count, -- start_ba, end_ba - 1); -- -- nmbm_mark_block_color_info_table(ni, start_ba, end_ba - 1); --} -- --/*****************************************************************************/ --/* Address conversion functions */ --/*****************************************************************************/ -- --/* -- * addr2ba - Convert a linear address to block address -- * @ni: NMBM instance structure -- * @addr: Linear address -- */ --static uint32_t addr2ba(struct nmbm_instance *ni, uint64_t addr) --{ -- return addr >> ni->erasesize_shift; --} -- --/* -- * ba2addr - Convert a block address to linear address -- * @ni: NMBM instance structure -- * @ba: Block address -- */ --static uint64_t ba2addr(struct nmbm_instance *ni, uint32_t ba) --{ -- return (uint64_t)ba << ni->erasesize_shift; --} --/* -- * size2blk - Get minimum required blocks for storing specific size of data -- * @ni: NMBM instance structure -- * @size: size for storing -- */ --static uint32_t size2blk(struct nmbm_instance *ni, uint64_t size) --{ -- return (size + ni->lower.erasesize - 1) >> ni->erasesize_shift; --} -- --/*****************************************************************************/ --/* High level NAND chip APIs */ --/*****************************************************************************/ -- --/* -- * nmbm_reset_chip - Reset NAND device -- * @nld: Lower NAND chip structure -- */ --static void nmbm_reset_chip(struct nmbm_instance *ni) --{ -- if (ni->lower.reset_chip) -- ni->lower.reset_chip(ni->lower.arg); --} -- --/* -- * nmbm_read_phys_page - Read page with retry -- * @ni: NMBM instance structure -- * @addr: linear address where the data will be read from -- * @data: the main data to be read -- * @oob: the oob data to be read -- * @mode: mode for processing oob data -- * -- * Read a page for at most NMBM_TRY_COUNT times. -- * -- * Return 0 for success, positive value for corrected bitflip count, -- * -EBADMSG for ecc error, other negative values for other errors -- */ --static int nmbm_read_phys_page(struct nmbm_instance *ni, uint64_t addr, -- void *data, void *oob, enum nmbm_oob_mode mode) --{ -- int tries, ret; -- -- for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { -- ret = ni->lower.read_page(ni->lower.arg, addr, data, oob, mode); -- if (ret >= 0) -- return ret; -- -- nmbm_reset_chip(ni); -- } -- -- if (ret != -EBADMSG) -- nlog_err(ni, "Page read failed at address 0x%08llx\n", addr); -- -- return ret; --} -- --/* -- * nmbm_write_phys_page - Write page with retry -- * @ni: NMBM instance structure -- * @addr: linear address where the data will be written to -- * @data: the main data to be written -- * @oob: the oob data to be written -- * @mode: mode for processing oob data -- * -- * Write a page for at most NMBM_TRY_COUNT times. -- */ --static bool nmbm_write_phys_page(struct nmbm_instance *ni, uint64_t addr, -- const void *data, const void *oob, -- enum nmbm_oob_mode mode) --{ -- int tries, ret; -- -- if (ni->lower.flags & NMBM_F_READ_ONLY) { -- nlog_err(ni, "%s called with NMBM_F_READ_ONLY set\n", addr); -- return false; -- } -- -- for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { -- ret = ni->lower.write_page(ni->lower.arg, addr, data, oob, mode); -- if (!ret) -- return true; -- -- nmbm_reset_chip(ni); -- } -- -- nlog_err(ni, "Page write failed at address 0x%08llx\n", addr); -- -- return false; --} -- --/* -- * nmbm_erase_phys_block - Erase a block with retry -- * @ni: NMBM instance structure -- * @addr: Linear address -- * -- * Erase a block for at most NMBM_TRY_COUNT times. -- */ --static bool nmbm_erase_phys_block(struct nmbm_instance *ni, uint64_t addr) --{ -- int tries, ret; -- -- if (ni->lower.flags & NMBM_F_READ_ONLY) { -- nlog_err(ni, "%s called with NMBM_F_READ_ONLY set\n", addr); -- return false; -- } -- -- for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { -- ret = ni->lower.erase_block(ni->lower.arg, addr); -- if (!ret) -- return true; -- -- nmbm_reset_chip(ni); -- } -- -- nlog_err(ni, "Block erasure failed at address 0x%08llx\n", addr); -- -- return false; --} -- --/* -- * nmbm_check_bad_phys_block - Check whether a block is marked bad in OOB -- * @ni: NMBM instance structure -- * @ba: block address -- */ --static bool nmbm_check_bad_phys_block(struct nmbm_instance *ni, uint32_t ba) --{ -- uint64_t addr = ba2addr(ni, ba); -- int ret; -- -- if (ni->lower.is_bad_block) -- return ni->lower.is_bad_block(ni->lower.arg, addr); -- -- /* Treat ECC error as read success */ -- ret = nmbm_read_phys_page(ni, addr, NULL, -- ni->page_cache + ni->lower.writesize, -- NMBM_MODE_RAW); -- if (ret < 0 && ret != -EBADMSG) -- return true; -- -- return ni->page_cache[ni->lower.writesize] != 0xff; --} -- --/* -- * nmbm_mark_phys_bad_block - Mark a block bad -- * @ni: NMBM instance structure -- * @addr: Linear address -- */ --static int nmbm_mark_phys_bad_block(struct nmbm_instance *ni, uint32_t ba) --{ -- uint64_t addr = ba2addr(ni, ba); -- enum nmbm_log_category level; -- uint32_t off; -- -- if (ni->lower.flags & NMBM_F_READ_ONLY) { -- nlog_err(ni, "%s called with NMBM_F_READ_ONLY set\n", addr); -- return false; -- } -- -- nlog_info(ni, "Block %u [0x%08llx] will be marked bad\n", ba, addr); -- -- if (ni->lower.mark_bad_block) -- return ni->lower.mark_bad_block(ni->lower.arg, addr); -- -- /* Whole page set to 0x00 */ -- memset(ni->page_cache, 0, ni->rawpage_size); -- -- /* Write to all pages within this block, disable all errors */ -- level = nmbm_set_log_level(ni, __NMBM_LOG_MAX); -- -- for (off = 0; off < ni->lower.erasesize; off += ni->lower.writesize) { -- nmbm_write_phys_page(ni, addr + off, ni->page_cache, -- ni->page_cache + ni->lower.writesize, -- NMBM_MODE_RAW); -- } -- -- nmbm_set_log_level(ni, level); -- -- return 0; --} -- --/*****************************************************************************/ --/* NMBM related functions */ --/*****************************************************************************/ -- --/* -- * nmbm_check_header - Check whether a NMBM structure is valid -- * @data: pointer to a NMBM structure with a NMBM header at beginning -- * @size: Size of the buffer pointed by @header -- * -- * The size of the NMBM structure may be larger than NMBM header, -- * e.g. block mapping table and block state table. -- */ --static bool nmbm_check_header(const void *data, uint32_t size) --{ -- const struct nmbm_header *header = data; -- struct nmbm_header nhdr; -- uint32_t new_checksum; -- -- /* -- * Make sure expected structure size is equal or smaller than -- * buffer size. -- */ -- if (header->size > size) -- return false; -- -- memcpy(&nhdr, data, sizeof(nhdr)); -- -- nhdr.checksum = 0; -- new_checksum = nmbm_crc32(0, &nhdr, sizeof(nhdr)); -- if (header->size > sizeof(nhdr)) -- new_checksum = nmbm_crc32(new_checksum, -- (const uint8_t *)data + sizeof(nhdr), -- header->size - sizeof(nhdr)); -- -- if (header->checksum != new_checksum) -- return false; -- -- return true; --} -- --/* -- * nmbm_update_checksum - Update checksum of a NMBM structure -- * @header: pointer to a NMBM structure with a NMBM header at beginning -- * -- * The size of the NMBM structure must be specified by @header->size -- */ --static void nmbm_update_checksum(struct nmbm_header *header) --{ -- header->checksum = 0; -- header->checksum = nmbm_crc32(0, header, header->size); --} -- --/* -- * nmbm_get_spare_block_count - Calculate number of blocks should be reserved -- * @block_count: number of blocks of data -- * -- * Calculate number of blocks should be reserved for data -- */ --static uint32_t nmbm_get_spare_block_count(uint32_t block_count) --{ -- uint32_t val; -- -- val = (block_count + NMBM_SPARE_BLOCK_DIV / 2) / NMBM_SPARE_BLOCK_DIV; -- val *= NMBM_SPARE_BLOCK_MULTI; -- -- if (val < NMBM_SPARE_BLOCK_MIN) -- val = NMBM_SPARE_BLOCK_MIN; -- -- return val; --} -- --/* -- * nmbm_get_block_state_raw - Get state of a block from raw block state table -- * @block_state: pointer to raw block state table (bitmap) -- * @ba: block address -- */ --static uint32_t nmbm_get_block_state_raw(nmbm_bitmap_t *block_state, -- uint32_t ba) --{ -- uint32_t unit, shift; -- -- unit = ba / NMBM_BITMAP_BLOCKS_PER_UNIT; -- shift = (ba % NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_BITS_PER_BLOCK; -- -- return (block_state[unit] >> shift) & BLOCK_ST_MASK; --} -- --/* -- * nmbm_get_block_state - Get state of a block from block state table -- * @ni: NMBM instance structure -- * @ba: block address -- */ --static uint32_t nmbm_get_block_state(struct nmbm_instance *ni, uint32_t ba) --{ -- return nmbm_get_block_state_raw(ni->block_state, ba); --} -- --/* -- * nmbm_set_block_state - Set state of a block to block state table -- * @ni: NMBM instance structure -- * @ba: block address -- * @state: block state -- * -- * Set state of a block. If the block state changed, ni->block_state_changed -- * will be increased. -- */ --static bool nmbm_set_block_state(struct nmbm_instance *ni, uint32_t ba, -- uint32_t state) --{ -- uint32_t unit, shift, orig; -- nmbm_bitmap_t uv; -- -- unit = ba / NMBM_BITMAP_BLOCKS_PER_UNIT; -- shift = (ba % NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_BITS_PER_BLOCK; -- -- orig = (ni->block_state[unit] >> shift) & BLOCK_ST_MASK; -- state &= BLOCK_ST_MASK; -- -- uv = ni->block_state[unit] & (~(BLOCK_ST_MASK << shift)); -- uv |= state << shift; -- ni->block_state[unit] = uv; -- -- if (state == BLOCK_ST_BAD) -- nmbm_mark_block_color_bad(ni, ba); -- -- if (orig != state) { -- ni->block_state_changed++; -- return true; -- } -- -- return false; --} -- --/* -- * nmbm_block_walk_asc - Skip specified number of good blocks, ascending addr. -- * @ni: NMBM instance structure -- * @ba: start physical block address -- * @nba: return physical block address after walk -- * @count: number of good blocks to be skipped -- * @limit: highest block address allowed for walking -- * -- * Start from @ba, skipping any bad blocks, counting @count good blocks, and -- * return the next good block address. -- * -- * If no enough good blocks counted while @limit reached, false will be returned. -- * -- * If @count == 0, nearest good block address will be returned. -- * @limit is not counted in walking. -- */ --static bool nmbm_block_walk_asc(struct nmbm_instance *ni, uint32_t ba, -- uint32_t *nba, uint32_t count, -- uint32_t limit) --{ -- int32_t nblock = count; -- -- if (limit >= ni->block_count) -- limit = ni->block_count - 1; -- -- while (ba < limit) { -- if (nmbm_get_block_state(ni, ba) == BLOCK_ST_GOOD) -- nblock--; -- -- if (nblock < 0) { -- *nba = ba; -- return true; -- } -- -- ba++; -- } -- -- return false; --} -- --/* -- * nmbm_block_walk_desc - Skip specified number of good blocks, descending addr -- * @ni: NMBM instance structure -- * @ba: start physical block address -- * @nba: return physical block address after walk -- * @count: number of good blocks to be skipped -- * @limit: lowest block address allowed for walking -- * -- * Start from @ba, skipping any bad blocks, counting @count good blocks, and -- * return the next good block address. -- * -- * If no enough good blocks counted while @limit reached, false will be returned. -- * -- * If @count == 0, nearest good block address will be returned. -- * @limit is not counted in walking. -- */ --static bool nmbm_block_walk_desc(struct nmbm_instance *ni, uint32_t ba, -- uint32_t *nba, uint32_t count, uint32_t limit) --{ -- int32_t nblock = count; -- -- if (limit >= ni->block_count) -- limit = ni->block_count - 1; -- -- while (ba > limit) { -- if (nmbm_get_block_state(ni, ba) == BLOCK_ST_GOOD) -- nblock--; -- -- if (nblock < 0) { -- *nba = ba; -- return true; -- } -- -- ba--; -- } -- -- return false; --} -- --/* -- * nmbm_block_walk - Skip specified number of good blocks from curr. block addr -- * @ni: NMBM instance structure -- * @ascending: whether to walk ascending -- * @ba: start physical block address -- * @nba: return physical block address after walk -- * @count: number of good blocks to be skipped -- * @limit: highest/lowest block address allowed for walking -- * -- * Start from @ba, skipping any bad blocks, counting @count good blocks, and -- * return the next good block address. -- * -- * If no enough good blocks counted while @limit reached, false will be returned. -- * -- * If @count == 0, nearest good block address will be returned. -- * @limit can be set to negative if no limit required. -- * @limit is not counted in walking. -- */ --static bool nmbm_block_walk(struct nmbm_instance *ni, bool ascending, -- uint32_t ba, uint32_t *nba, int32_t count, -- int32_t limit) --{ -- if (ascending) -- return nmbm_block_walk_asc(ni, ba, nba, count, limit); -- -- return nmbm_block_walk_desc(ni, ba, nba, count, limit); --} -- --/* -- * nmbm_scan_badblocks - Scan and record all bad blocks -- * @ni: NMBM instance structure -- * -- * Scan the entire lower NAND chip and record all bad blocks in to block state -- * table. -- */ --static void nmbm_scan_badblocks(struct nmbm_instance *ni) --{ -- uint32_t ba; -- -- for (ba = 0; ba < ni->block_count; ba++) { -- if (nmbm_check_bad_phys_block(ni, ba)) { -- nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); -- nlog_info(ni, "Bad block %u [0x%08llx]\n", ba, -- ba2addr(ni, ba)); -- } -- } --} -- --/* -- * nmbm_build_mapping_table - Build initial block mapping table -- * @ni: NMBM instance structure -- * -- * The initial mapping table will be compatible with the stratage of -- * factory production. -- */ --static void nmbm_build_mapping_table(struct nmbm_instance *ni) --{ -- uint32_t pb, lb; -- -- for (pb = 0, lb = 0; pb < ni->mgmt_start_ba; pb++) { -- if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) -- continue; -- -- /* Always map to the next good block */ -- ni->block_mapping[lb++] = pb; -- } -- -- ni->data_block_count = lb; -- -- /* Unusable/Management blocks */ -- for (pb = lb; pb < ni->block_count; pb++) -- ni->block_mapping[pb] = -1; --} -- --/* -- * nmbm_erase_block_and_check - Erase a block and check its usability -- * @ni: NMBM instance structure -- * @ba: block address to be erased -- * -- * Erase a block anc check its usability -- * -- * Return true if the block is usable, false if erasure failure or the block -- * has too many bitflips. -- */ --static bool nmbm_erase_block_and_check(struct nmbm_instance *ni, uint32_t ba) --{ -- uint64_t addr, off; -- bool success; -- int ret; -- -- success = nmbm_erase_phys_block(ni, ba2addr(ni, ba)); -- if (!success) -- return false; -- -- if (!(ni->lower.flags & NMBM_F_EMPTY_PAGE_ECC_OK)) -- return true; -- -- /* Check every page to make sure there aren't too many bitflips */ -- -- addr = ba2addr(ni, ba); -- -- for (off = 0; off < ni->lower.erasesize; off += ni->lower.writesize) { -- WATCHDOG_RESET(); -- -- ret = nmbm_read_phys_page(ni, addr + off, ni->page_cache, NULL, -- NMBM_MODE_PLACE_OOB); -- if (ret == -EBADMSG) { -- /* -- * NMBM_F_EMPTY_PAGE_ECC_OK means the empty page is -- * still protected by ECC. So reading pages with ECC -- * enabled and -EBADMSG means there are too many -- * bitflips that can't be recovered, and the block -- * containing the page should be marked bad. -- */ -- nlog_err(ni, -- "Too many bitflips in empty page at 0x%llx\n", -- addr + off); -- return false; -- } -- } -- -- return true; --} -- --/* -- * nmbm_erase_range - Erase a range of blocks -- * @ni: NMBM instance structure -- * @ba: block address where the erasure will start -- * @limit: top block address allowed for erasure -- * -- * Erase blocks within the specific range. Newly-found bad blocks will be -- * marked. -- * -- * @limit is not counted into the allowed erasure address. -- */ --static void nmbm_erase_range(struct nmbm_instance *ni, uint32_t ba, -- uint32_t limit) --{ -- bool success; -- -- while (ba < limit) { -- WATCHDOG_RESET(); -- -- if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) -- goto next_block; -- -- /* Insurance to detect unexpected bad block marked by user */ -- if (nmbm_check_bad_phys_block(ni, ba)) { -- nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); -- goto next_block; -- } -- -- success = nmbm_erase_block_and_check(ni, ba); -- if (success) -- goto next_block; -- -- nmbm_mark_phys_bad_block(ni, ba); -- nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); -- -- next_block: -- ba++; -- } --} -- --/* -- * nmbm_write_repeated_data - Write critical data to a block with retry -- * @ni: NMBM instance structure -- * @ba: block address where the data will be written to -- * @data: the data to be written -- * @size: size of the data -- * -- * Write data to every page of the block. Success only if all pages within -- * this block have been successfully written. -- * -- * Make sure data size is not bigger than one page. -- * -- * This function will write and verify every page for at most -- * NMBM_TRY_COUNT times. -- */ --static bool nmbm_write_repeated_data(struct nmbm_instance *ni, uint32_t ba, -- const void *data, uint32_t size) --{ -- uint64_t addr, off; -- bool success; -- int ret; -- -- if (size > ni->lower.writesize) -- return false; -- -- addr = ba2addr(ni, ba); -- -- for (off = 0; off < ni->lower.erasesize; off += ni->lower.writesize) { -- WATCHDOG_RESET(); -- -- /* Prepare page data. fill 0xff to unused region */ -- memcpy(ni->page_cache, data, size); -- memset(ni->page_cache + size, 0xff, ni->rawpage_size - size); -- -- success = nmbm_write_phys_page(ni, addr + off, ni->page_cache, -- NULL, NMBM_MODE_PLACE_OOB); -- if (!success) -- return false; -- -- /* Verify the data just written. ECC error indicates failure */ -- ret = nmbm_read_phys_page(ni, addr + off, ni->page_cache, NULL, -- NMBM_MODE_PLACE_OOB); -- if (ret < 0) -- return false; -- -- if (memcmp(ni->page_cache, data, size)) -- return false; -- } -- -- return true; --} -- --/* -- * nmbm_write_signature - Write signature to NAND chip -- * @ni: NMBM instance structure -- * @limit: top block address allowed for writing -- * @signature: the signature to be written -- * @signature_ba: the actual block address where signature is written to -- * -- * Write signature within a specific range, from chip bottom to limit. -- * At most one block will be written. -- * -- * @limit is not counted into the allowed write address. -- */ --static bool nmbm_write_signature(struct nmbm_instance *ni, uint32_t limit, -- const struct nmbm_signature *signature, -- uint32_t *signature_ba) --{ -- uint32_t ba = ni->block_count - 1; -- bool success; -- -- while (ba > limit) { -- WATCHDOG_RESET(); -- -- if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) -- goto next_block; -- -- /* Insurance to detect unexpected bad block marked by user */ -- if (nmbm_check_bad_phys_block(ni, ba)) { -- nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); -- goto next_block; -- } -- -- success = nmbm_erase_block_and_check(ni, ba); -- if (!success) -- goto skip_bad_block; -- -- success = nmbm_write_repeated_data(ni, ba, signature, -- sizeof(*signature)); -- if (success) { -- *signature_ba = ba; -- return true; -- } -- -- skip_bad_block: -- nmbm_mark_phys_bad_block(ni, ba); -- nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); -- -- next_block: -- ba--; -- }; -- -- return false; --} -- --/* -- * nmbn_read_data - Read data -- * @ni: NMBM instance structure -- * @addr: linear address where the data will be read from -- * @data: the data to be read -- * @size: the size of data -- * -- * Read data range. -- * Every page will be tried for at most NMBM_TRY_COUNT times. -- * -- * Return 0 for success, positive value for corrected bitflip count, -- * -EBADMSG for ecc error, other negative values for other errors -- */ --static int nmbn_read_data(struct nmbm_instance *ni, uint64_t addr, void *data, -- uint32_t size) --{ -- uint64_t off = addr; -- uint8_t *ptr = data; -- uint32_t sizeremain = size, chunksize, leading; -- int ret; -- -- while (sizeremain) { -- WATCHDOG_RESET(); -- -- leading = off & ni->writesize_mask; -- chunksize = ni->lower.writesize - leading; -- if (chunksize > sizeremain) -- chunksize = sizeremain; -- -- if (chunksize == ni->lower.writesize) { -- ret = nmbm_read_phys_page(ni, off - leading, ptr, NULL, -- NMBM_MODE_PLACE_OOB); -- if (ret < 0) -- return ret; -- } else { -- ret = nmbm_read_phys_page(ni, off - leading, -- ni->page_cache, NULL, -- NMBM_MODE_PLACE_OOB); -- if (ret < 0) -- return ret; -- -- memcpy(ptr, ni->page_cache + leading, chunksize); -- } -- -- off += chunksize; -- ptr += chunksize; -- sizeremain -= chunksize; -- } -- -- return 0; --} -- --/* -- * nmbn_write_verify_data - Write data with validation -- * @ni: NMBM instance structure -- * @addr: linear address where the data will be written to -- * @data: the data to be written -- * @size: the size of data -- * -- * Write data and verify. -- * Every page will be tried for at most NMBM_TRY_COUNT times. -- */ --static bool nmbn_write_verify_data(struct nmbm_instance *ni, uint64_t addr, -- const void *data, uint32_t size) --{ -- uint64_t off = addr; -- const uint8_t *ptr = data; -- uint32_t sizeremain = size, chunksize, leading; -- bool success; -- int ret; -- -- while (sizeremain) { -- WATCHDOG_RESET(); -- -- leading = off & ni->writesize_mask; -- chunksize = ni->lower.writesize - leading; -- if (chunksize > sizeremain) -- chunksize = sizeremain; -- -- /* Prepare page data. fill 0xff to unused region */ -- memset(ni->page_cache, 0xff, ni->rawpage_size); -- memcpy(ni->page_cache + leading, ptr, chunksize); -- -- success = nmbm_write_phys_page(ni, off - leading, -- ni->page_cache, NULL, -- NMBM_MODE_PLACE_OOB); -- if (!success) -- return false; -- -- /* Verify the data just written. ECC error indicates failure */ -- ret = nmbm_read_phys_page(ni, off - leading, ni->page_cache, -- NULL, NMBM_MODE_PLACE_OOB); -- if (ret < 0) -- return false; -- -- if (memcmp(ni->page_cache + leading, ptr, chunksize)) -- return false; -- -- off += chunksize; -- ptr += chunksize; -- sizeremain -= chunksize; -- } -- -- return true; --} -- --/* -- * nmbm_write_mgmt_range - Write management data into NAND within a range -- * @ni: NMBM instance structure -- * @addr: preferred start block address for writing -- * @limit: highest block address allowed for writing -- * @data: the data to be written -- * @size: the size of data -- * @actual_start_ba: actual start block address of data -- * @actual_end_ba: block address after the end of data -- * -- * @limit is not counted into the allowed write address. -- */ --static bool nmbm_write_mgmt_range(struct nmbm_instance *ni, uint32_t ba, -- uint32_t limit, const void *data, -- uint32_t size, uint32_t *actual_start_ba, -- uint32_t *actual_end_ba) --{ -- const uint8_t *ptr = data; -- uint32_t sizeremain = size, chunksize; -- bool success; -- -- while (sizeremain && ba < limit) { -- WATCHDOG_RESET(); -- -- chunksize = sizeremain; -- if (chunksize > ni->lower.erasesize) -- chunksize = ni->lower.erasesize; -- -- if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) -- goto next_block; -- -- /* Insurance to detect unexpected bad block marked by user */ -- if (nmbm_check_bad_phys_block(ni, ba)) { -- nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); -- goto next_block; -- } -- -- success = nmbm_erase_block_and_check(ni, ba); -- if (!success) -- goto skip_bad_block; -- -- success = nmbn_write_verify_data(ni, ba2addr(ni, ba), ptr, -- chunksize); -- if (!success) -- goto skip_bad_block; -- -- if (sizeremain == size) -- *actual_start_ba = ba; -- -- ptr += chunksize; -- sizeremain -= chunksize; -- -- goto next_block; -- -- skip_bad_block: -- nmbm_mark_phys_bad_block(ni, ba); -- nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); -- -- next_block: -- ba++; -- } -- -- if (sizeremain) -- return false; -- -- *actual_end_ba = ba; -- -- return true; --} -- --/* -- * nmbm_generate_info_table_cache - Generate info table cache data -- * @ni: NMBM instance structure -- * -- * Generate info table cache data to be written into flash. -- */ --static bool nmbm_generate_info_table_cache(struct nmbm_instance *ni) --{ -- bool changed = false; -- -- memset(ni->info_table_cache, 0xff, ni->info_table_size); -- -- memcpy(ni->info_table_cache + ni->info_table.state_table_off, -- ni->block_state, ni->state_table_size); -- -- memcpy(ni->info_table_cache + ni->info_table.mapping_table_off, -- ni->block_mapping, ni->mapping_table_size); -- -- ni->info_table.header.magic = NMBM_MAGIC_INFO_TABLE; -- ni->info_table.header.version = NMBM_VER; -- ni->info_table.header.size = ni->info_table_size; -- -- if (ni->block_state_changed || ni->block_mapping_changed) { -- ni->info_table.write_count++; -- changed = true; -- } -- -- memcpy(ni->info_table_cache, &ni->info_table, sizeof(ni->info_table)); -- -- nmbm_update_checksum((struct nmbm_header *)ni->info_table_cache); -- -- return changed; --} -- --/* -- * nmbm_write_info_table - Write info table into NAND within a range -- * @ni: NMBM instance structure -- * @ba: preferred start block address for writing -- * @limit: highest block address allowed for writing -- * @actual_start_ba: actual start block address of info table -- * @actual_end_ba: block address after the end of info table -- * -- * @limit is counted into the allowed write address. -- */ --static bool nmbm_write_info_table(struct nmbm_instance *ni, uint32_t ba, -- uint32_t limit, uint32_t *actual_start_ba, -- uint32_t *actual_end_ba) --{ -- return nmbm_write_mgmt_range(ni, ba, limit, ni->info_table_cache, -- ni->info_table_size, actual_start_ba, -- actual_end_ba); --} -- --/* -- * nmbm_mark_tables_clean - Mark info table `clean' -- * @ni: NMBM instance structure -- */ --static void nmbm_mark_tables_clean(struct nmbm_instance *ni) --{ -- ni->block_state_changed = 0; -- ni->block_mapping_changed = 0; --} -- --/* -- * nmbm_try_reserve_blocks - Reserve blocks with compromisation -- * @ni: NMBM instance structure -- * @ba: start physical block address -- * @nba: return physical block address after reservation -- * @count: number of good blocks to be skipped -- * @min_count: minimum number of good blocks to be skipped -- * @limit: highest/lowest block address allowed for walking -- * -- * Reserve specific blocks. If failed, try to reserve as many as possible. -- */ --static bool nmbm_try_reserve_blocks(struct nmbm_instance *ni, uint32_t ba, -- uint32_t *nba, uint32_t count, -- int32_t min_count, int32_t limit) --{ -- int32_t nblocks = count; -- bool success; -- -- while (nblocks >= min_count) { -- success = nmbm_block_walk(ni, true, ba, nba, nblocks, limit); -- if (success) -- return true; -- -- nblocks--; -- } -- -- return false; --} -- --/* -- * nmbm_rebuild_info_table - Build main & backup info table from scratch -- * @ni: NMBM instance structure -- * @allow_no_gap: allow no spare blocks between two tables -- */ --static bool nmbm_rebuild_info_table(struct nmbm_instance *ni) --{ -- uint32_t table_start_ba, table_end_ba, next_start_ba; -- uint32_t main_table_end_ba; -- bool success; -- -- /* Set initial value */ -- ni->main_table_ba = 0; -- ni->backup_table_ba = 0; -- ni->mapping_blocks_ba = ni->mapping_blocks_top_ba; -- -- /* Write main table */ -- success = nmbm_write_info_table(ni, ni->mgmt_start_ba, -- ni->mapping_blocks_top_ba, -- &table_start_ba, &table_end_ba); -- if (!success) { -- /* Failed to write main table, data will be lost */ -- nlog_emerg(ni, "Unable to write at least one info table!\n"); -- nlog_emerg(ni, "Please save your data before power off!\n"); -- ni->protected = 1; -- return false; -- } -- -- /* Main info table is successfully written, record its offset */ -- ni->main_table_ba = table_start_ba; -- main_table_end_ba = table_end_ba; -- -- /* Adjust mapping_blocks_ba */ -- ni->mapping_blocks_ba = table_end_ba; -- -- nmbm_mark_tables_clean(ni); -- -- nlog_table_creation(ni, true, table_start_ba, table_end_ba); -- -- /* Reserve spare blocks for main info table. */ -- success = nmbm_try_reserve_blocks(ni, table_end_ba, -- &next_start_ba, -- ni->info_table_spare_blocks, 0, -- ni->mapping_blocks_top_ba - -- size2blk(ni, ni->info_table_size)); -- if (!success) { -- /* There is no spare block. */ -- nlog_debug(ni, "No room for backup info table\n"); -- return true; -- } -- -- /* Write backup info table. */ -- success = nmbm_write_info_table(ni, next_start_ba, -- ni->mapping_blocks_top_ba, -- &table_start_ba, &table_end_ba); -- if (!success) { -- /* There is no enough blocks for backup table. */ -- nlog_debug(ni, "No room for backup info table\n"); -- return true; -- } -- -- /* Backup table is successfully written, record its offset */ -- ni->backup_table_ba = table_start_ba; -- -- /* Adjust mapping_blocks_off */ -- ni->mapping_blocks_ba = table_end_ba; -- -- /* Erase spare blocks of main table to clean possible interference data */ -- nmbm_erase_range(ni, main_table_end_ba, ni->backup_table_ba); -- -- nlog_table_creation(ni, false, table_start_ba, table_end_ba); -- -- return true; --} -- --/* -- * nmbm_rescue_single_info_table - Rescue when there is only one info table -- * @ni: NMBM instance structure -- * -- * This function is called when there is only one info table exists. -- * This function may fail if we can't write new info table -- */ --static bool nmbm_rescue_single_info_table(struct nmbm_instance *ni) --{ -- uint32_t table_start_ba, table_end_ba, write_ba; -- bool success; -- -- /* Try to write new info table in front of existing table */ -- success = nmbm_write_info_table(ni, ni->mgmt_start_ba, -- ni->main_table_ba, -- &table_start_ba, -- &table_end_ba); -- if (success) { -- /* -- * New table becomes the main table, existing table becomes -- * the backup table. -- */ -- ni->backup_table_ba = ni->main_table_ba; -- ni->main_table_ba = table_start_ba; -- -- nmbm_mark_tables_clean(ni); -- -- /* Erase spare blocks of main table to clean possible interference data */ -- nmbm_erase_range(ni, table_end_ba, ni->backup_table_ba); -- -- nlog_table_creation(ni, true, table_start_ba, table_end_ba); -- -- return true; -- } -- -- /* Try to reserve spare blocks for existing table */ -- success = nmbm_try_reserve_blocks(ni, ni->mapping_blocks_ba, &write_ba, -- ni->info_table_spare_blocks, 0, -- ni->mapping_blocks_top_ba - -- size2blk(ni, ni->info_table_size)); -- if (!success) { -- nlog_warn(ni, "Failed to rescue single info table\n"); -- return false; -- } -- -- /* Try to write new info table next to the existing table */ -- while (write_ba >= ni->mapping_blocks_ba) { -- WATCHDOG_RESET(); -- -- success = nmbm_write_info_table(ni, write_ba, -- ni->mapping_blocks_top_ba, -- &table_start_ba, -- &table_end_ba); -- if (success) -- break; -- -- write_ba--; -- } -- -- if (success) { -- /* Erase spare blocks of main table to clean possible interference data */ -- nmbm_erase_range(ni, ni->mapping_blocks_ba, table_start_ba); -- -- /* New table becomes the backup table */ -- ni->backup_table_ba = table_start_ba; -- ni->mapping_blocks_ba = table_end_ba; -- -- nmbm_mark_tables_clean(ni); -- -- nlog_table_creation(ni, false, table_start_ba, table_end_ba); -- -- return true; -- } -- -- nlog_warn(ni, "Failed to rescue single info table\n"); -- return false; --} -- --/* -- * nmbm_update_single_info_table - Update specific one info table -- * @ni: NMBM instance structure -- */ --static bool nmbm_update_single_info_table(struct nmbm_instance *ni, -- bool update_main_table) --{ -- uint32_t write_start_ba, write_limit, table_start_ba, table_end_ba; -- bool success; -- -- /* Determine the write range */ -- if (update_main_table) { -- write_start_ba = ni->main_table_ba; -- write_limit = ni->backup_table_ba; -- } else { -- write_start_ba = ni->backup_table_ba; -- write_limit = ni->mapping_blocks_top_ba; -- } -- -- nmbm_mark_block_color_mgmt(ni, write_start_ba, write_limit - 1); -- -- success = nmbm_write_info_table(ni, write_start_ba, write_limit, -- &table_start_ba, &table_end_ba); -- if (success) { -- if (update_main_table) { -- ni->main_table_ba = table_start_ba; -- } else { -- ni->backup_table_ba = table_start_ba; -- ni->mapping_blocks_ba = table_end_ba; -- } -- -- nmbm_mark_tables_clean(ni); -- -- nlog_table_update(ni, update_main_table, table_start_ba, -- table_end_ba); -- -- return true; -- } -- -- if (update_main_table) { -- /* -- * If failed to update main table, make backup table the new -- * main table, and call nmbm_rescue_single_info_table() -- */ -- nlog_warn(ni, "Unable to update %s info table\n", -- update_main_table ? "Main" : "Backup"); -- -- ni->main_table_ba = ni->backup_table_ba; -- ni->backup_table_ba = 0; -- return nmbm_rescue_single_info_table(ni); -- } -- -- /* Only one table left */ -- ni->mapping_blocks_ba = ni->backup_table_ba; -- ni->backup_table_ba = 0; -- -- return false; --} -- --/* -- * nmbm_rescue_main_info_table - Rescue when failed to write main info table -- * @ni: NMBM instance structure -- * -- * This function is called when main info table failed to be written, and -- * backup info table exists. -- */ --static bool nmbm_rescue_main_info_table(struct nmbm_instance *ni) --{ -- uint32_t tmp_table_start_ba, tmp_table_end_ba, main_table_start_ba; -- uint32_t main_table_end_ba, write_ba; -- uint32_t info_table_erasesize = size2blk(ni, ni->info_table_size); -- bool success; -- -- /* Try to reserve spare blocks for existing backup info table */ -- success = nmbm_try_reserve_blocks(ni, ni->mapping_blocks_ba, &write_ba, -- ni->info_table_spare_blocks, 0, -- ni->mapping_blocks_top_ba - -- info_table_erasesize); -- if (!success) { -- /* There is no spare block. Backup info table becomes the main table. */ -- nlog_err(ni, "No room for temporary info table\n"); -- ni->main_table_ba = ni->backup_table_ba; -- ni->backup_table_ba = 0; -- return true; -- } -- -- /* Try to write temporary info table into spare unmapped blocks */ -- while (write_ba >= ni->mapping_blocks_ba) { -- WATCHDOG_RESET(); -- -- success = nmbm_write_info_table(ni, write_ba, -- ni->mapping_blocks_top_ba, -- &tmp_table_start_ba, -- &tmp_table_end_ba); -- if (success) -- break; -- -- write_ba--; -- } -- -- if (!success) { -- /* Backup info table becomes the main table */ -- nlog_err(ni, "Failed to update main info table\n"); -- ni->main_table_ba = ni->backup_table_ba; -- ni->backup_table_ba = 0; -- return true; -- } -- -- /* Adjust mapping_blocks_off */ -- ni->mapping_blocks_ba = tmp_table_end_ba; -- -- nmbm_mark_block_color_mgmt(ni, ni->backup_table_ba, -- tmp_table_end_ba - 1); -- -- /* -- * Now write main info table at the beginning of management area. -- * This operation will generally destroy the original backup info -- * table. -- */ -- success = nmbm_write_info_table(ni, ni->mgmt_start_ba, -- tmp_table_start_ba, -- &main_table_start_ba, -- &main_table_end_ba); -- if (!success) { -- /* Temporary info table becomes the main table */ -- ni->main_table_ba = tmp_table_start_ba; -- ni->backup_table_ba = 0; -- -- nmbm_mark_tables_clean(ni); -- -- nlog_err(ni, "Failed to update main info table\n"); -- nmbm_mark_block_color_info_table(ni, tmp_table_start_ba, -- tmp_table_end_ba - 1); -- -- return true; -- } -- -- /* Main info table has been successfully written, record its offset */ -- ni->main_table_ba = main_table_start_ba; -- -- nmbm_mark_tables_clean(ni); -- -- nlog_table_creation(ni, true, main_table_start_ba, main_table_end_ba); -- -- /* -- * Temporary info table becomes the new backup info table if it's -- * not overwritten. -- */ -- if (main_table_end_ba <= tmp_table_start_ba) { -- ni->backup_table_ba = tmp_table_start_ba; -- -- nlog_table_creation(ni, false, tmp_table_start_ba, -- tmp_table_end_ba); -- -- return true; -- } -- -- /* Adjust mapping_blocks_off */ -- ni->mapping_blocks_ba = main_table_end_ba; -- -- /* Try to reserve spare blocks for new main info table */ -- success = nmbm_try_reserve_blocks(ni, main_table_end_ba, &write_ba, -- ni->info_table_spare_blocks, 0, -- ni->mapping_blocks_top_ba - -- info_table_erasesize); -- if (!success) { -- /* There is no spare block. Only main table exists. */ -- nlog_err(ni, "No room for backup info table\n"); -- ni->backup_table_ba = 0; -- return true; -- } -- -- /* Write new backup info table. */ -- while (write_ba >= main_table_end_ba) { -- WATCHDOG_RESET(); -- -- success = nmbm_write_info_table(ni, write_ba, -- ni->mapping_blocks_top_ba, -- &tmp_table_start_ba, -- &tmp_table_end_ba); -- if (success) -- break; -- -- write_ba--; -- } -- -- if (!success) { -- nlog_err(ni, "No room for backup info table\n"); -- ni->backup_table_ba = 0; -- return true; -- } -- -- /* Backup info table has been successfully written, record its offset */ -- ni->backup_table_ba = tmp_table_start_ba; -- -- /* Adjust mapping_blocks_off */ -- ni->mapping_blocks_ba = tmp_table_end_ba; -- -- /* Erase spare blocks of main table to clean possible interference data */ -- nmbm_erase_range(ni, main_table_end_ba, ni->backup_table_ba); -- -- nlog_table_creation(ni, false, tmp_table_start_ba, tmp_table_end_ba); -- -- return true; --} -- --/* -- * nmbm_update_info_table_once - Update info table once -- * @ni: NMBM instance structure -- * @force: force update -- * -- * Update both main and backup info table. Return true if at least one info -- * table has been successfully written. -- * This function only try to update info table once regard less of the result. -- */ --static bool nmbm_update_info_table_once(struct nmbm_instance *ni, bool force) --{ -- uint32_t table_start_ba, table_end_ba; -- uint32_t main_table_limit; -- bool success; -- -- /* Do nothing if there is no change */ -- if (!nmbm_generate_info_table_cache(ni) && !force) -- return true; -- -- /* Check whether both two tables exist */ -- if (!ni->backup_table_ba) { -- main_table_limit = ni->mapping_blocks_top_ba; -- goto write_main_table; -- } -- -- nmbm_mark_block_color_mgmt(ni, ni->backup_table_ba, -- ni->mapping_blocks_ba - 1); -- -- /* -- * Write backup info table in its current range. -- * Note that limit is set to mapping_blocks_top_off to provide as many -- * spare blocks as possible for the backup table. If at last -- * unmapped blocks are used by backup table, mapping_blocks_off will -- * be adjusted. -- */ -- success = nmbm_write_info_table(ni, ni->backup_table_ba, -- ni->mapping_blocks_top_ba, -- &table_start_ba, &table_end_ba); -- if (!success) { -- /* -- * There is nothing to do if failed to write backup table. -- * Write the main table now. -- */ -- nlog_err(ni, "No room for backup table\n"); -- ni->mapping_blocks_ba = ni->backup_table_ba; -- ni->backup_table_ba = 0; -- main_table_limit = ni->mapping_blocks_top_ba; -- goto write_main_table; -- } -- -- /* Backup table is successfully written, record its offset */ -- ni->backup_table_ba = table_start_ba; -- -- /* Adjust mapping_blocks_off */ -- ni->mapping_blocks_ba = table_end_ba; -- -- nmbm_mark_tables_clean(ni); -- -- /* The normal limit of main table */ -- main_table_limit = ni->backup_table_ba; -- -- nlog_table_update(ni, false, table_start_ba, table_end_ba); -- --write_main_table: -- if (!ni->main_table_ba) -- goto rebuild_tables; -- -- if (!ni->backup_table_ba) -- nmbm_mark_block_color_mgmt(ni, ni->mgmt_start_ba, -- ni->mapping_blocks_ba - 1); -- else -- nmbm_mark_block_color_mgmt(ni, ni->mgmt_start_ba, -- ni->backup_table_ba - 1); -- -- /* Write main info table in its current range */ -- success = nmbm_write_info_table(ni, ni->main_table_ba, -- main_table_limit, &table_start_ba, -- &table_end_ba); -- if (!success) { -- /* If failed to write main table, go rescue procedure */ -- if (!ni->backup_table_ba) -- goto rebuild_tables; -- -- return nmbm_rescue_main_info_table(ni); -- } -- -- /* Main info table is successfully written, record its offset */ -- ni->main_table_ba = table_start_ba; -- -- /* Adjust mapping_blocks_off */ -- if (!ni->backup_table_ba) -- ni->mapping_blocks_ba = table_end_ba; -- -- nmbm_mark_tables_clean(ni); -- -- nlog_table_update(ni, true, table_start_ba, table_end_ba); -- -- return true; -- --rebuild_tables: -- return nmbm_rebuild_info_table(ni); --} -- --/* -- * nmbm_update_info_table - Update info table -- * @ni: NMBM instance structure -- * -- * Update both main and backup info table. Return true if at least one table -- * has been successfully written. -- * This function will try to update info table repeatedly until no new bad -- * block found during updating. -- */ --static bool nmbm_update_info_table(struct nmbm_instance *ni) --{ -- bool success; -- -- if (ni->protected) -- return true; -- -- while (ni->block_state_changed || ni->block_mapping_changed) { -- success = nmbm_update_info_table_once(ni, false); -- if (!success) { -- nlog_err(ni, "Failed to update info table\n"); -- return false; -- } -- } -- -- return true; --} -- --/* -- * nmbm_map_block - Map a bad block to a unused spare block -- * @ni: NMBM instance structure -- * @lb: logic block addr to map -- */ --static bool nmbm_map_block(struct nmbm_instance *ni, uint32_t lb) --{ -- uint32_t pb; -- bool success; -- -- if (ni->mapping_blocks_ba == ni->mapping_blocks_top_ba) { -- nlog_warn(ni, "No spare unmapped blocks.\n"); -- return false; -- } -- -- success = nmbm_block_walk(ni, false, ni->mapping_blocks_top_ba, &pb, 0, -- ni->mapping_blocks_ba); -- if (!success) { -- nlog_warn(ni, "No spare unmapped blocks.\n"); -- nmbm_update_info_table(ni); -- ni->mapping_blocks_top_ba = ni->mapping_blocks_ba; -- return false; -- } -- -- ni->block_mapping[lb] = pb; -- ni->mapping_blocks_top_ba--; -- ni->block_mapping_changed++; -- -- nlog_info(ni, "Logic block %u mapped to physical blcok %u\n", lb, pb); -- nmbm_mark_block_color_mapped(ni, pb); -- -- return true; --} -- --/* -- * nmbm_create_info_table - Create info table(s) -- * @ni: NMBM instance structure -- * -- * This function assumes that the chip has no existing info table(s) -- */ --static bool nmbm_create_info_table(struct nmbm_instance *ni) --{ -- uint32_t lb; -- bool success; -- -- /* Set initial mapping_blocks_top_off */ -- success = nmbm_block_walk(ni, false, ni->signature_ba, -- &ni->mapping_blocks_top_ba, 1, -- ni->mgmt_start_ba); -- if (!success) { -- nlog_err(ni, "No room for spare blocks\n"); -- return false; -- } -- -- /* Generate info table cache */ -- nmbm_generate_info_table_cache(ni); -- -- /* Write info table */ -- success = nmbm_rebuild_info_table(ni); -- if (!success) { -- nlog_err(ni, "Failed to build info tables\n"); -- return false; -- } -- -- /* Remap bad block(s) at end of data area */ -- for (lb = ni->data_block_count; lb < ni->mgmt_start_ba; lb++) { -- success = nmbm_map_block(ni, lb); -- if (!success) -- break; -- -- ni->data_block_count++; -- } -- -- /* If state table and/or mapping table changed, update info table. */ -- success = nmbm_update_info_table(ni); -- if (!success) -- return false; -- -- return true; --} -- --/* -- * nmbm_create_new - Create NMBM on a new chip -- * @ni: NMBM instance structure -- */ --static bool nmbm_create_new(struct nmbm_instance *ni) --{ -- bool success; -- -- /* Determine the boundary of management blocks */ -- ni->mgmt_start_ba = ni->block_count * (NMBM_MGMT_DIV - ni->lower.max_ratio) / NMBM_MGMT_DIV; -- -- if (ni->lower.max_reserved_blocks && ni->block_count - ni->mgmt_start_ba > ni->lower.max_reserved_blocks) -- ni->mgmt_start_ba = ni->block_count - ni->lower.max_reserved_blocks; -- -- nlog_info(ni, "NMBM management region starts at block %u [0x%08llx]\n", -- ni->mgmt_start_ba, ba2addr(ni, ni->mgmt_start_ba)); -- nmbm_mark_block_color_mgmt(ni, ni->mgmt_start_ba, ni->block_count - 1); -- -- /* Fill block state table & mapping table */ -- nmbm_scan_badblocks(ni); -- nmbm_build_mapping_table(ni); -- -- /* Write signature */ -- ni->signature.header.magic = NMBM_MAGIC_SIGNATURE; -- ni->signature.header.version = NMBM_VER; -- ni->signature.header.size = sizeof(ni->signature); -- ni->signature.nand_size = ni->lower.size; -- ni->signature.block_size = ni->lower.erasesize; -- ni->signature.page_size = ni->lower.writesize; -- ni->signature.spare_size = ni->lower.oobsize; -- ni->signature.mgmt_start_pb = ni->mgmt_start_ba; -- ni->signature.max_try_count = NMBM_TRY_COUNT; -- nmbm_update_checksum(&ni->signature.header); -- -- if (ni->lower.flags & NMBM_F_READ_ONLY) { -- nlog_info(ni, "NMBM has been initialized in read-only mode\n"); -- return true; -- } -- -- success = nmbm_write_signature(ni, ni->mgmt_start_ba, -- &ni->signature, &ni->signature_ba); -- if (!success) { -- nlog_err(ni, "Failed to write signature to a proper offset\n"); -- return false; -- } -- -- nlog_info(ni, "Signature has been written to block %u [0x%08llx]\n", -- ni->signature_ba, ba2addr(ni, ni->signature_ba)); -- nmbm_mark_block_color_signature(ni, ni->signature_ba); -- -- /* Write info table(s) */ -- success = nmbm_create_info_table(ni); -- if (success) { -- nlog_info(ni, "NMBM has been successfully created\n"); -- return true; -- } -- -- return false; --} -- --/* -- * nmbm_check_info_table_header - Check if a info table header is valid -- * @ni: NMBM instance structure -- * @data: pointer to the info table header -- */ --static bool nmbm_check_info_table_header(struct nmbm_instance *ni, void *data) --{ -- struct nmbm_info_table_header *ifthdr = data; -- -- if (ifthdr->header.magic != NMBM_MAGIC_INFO_TABLE) -- return false; -- -- if (ifthdr->header.size != ni->info_table_size) -- return false; -- -- if (ifthdr->mapping_table_off - ifthdr->state_table_off < ni->state_table_size) -- return false; -- -- if (ni->info_table_size - ifthdr->mapping_table_off < ni->mapping_table_size) -- return false; -- -- return true; --} -- --/* -- * nmbm_check_info_table - Check if a whole info table is valid -- * @ni: NMBM instance structure -- * @start_ba: start block address of this table -- * @end_ba: end block address of this table -- * @data: pointer to the info table header -- * @mapping_blocks_top_ba: return the block address of top remapped block -- */ --static bool nmbm_check_info_table(struct nmbm_instance *ni, uint32_t start_ba, -- uint32_t end_ba, void *data, -- uint32_t *mapping_blocks_top_ba) --{ -- struct nmbm_info_table_header *ifthdr = data; -- int32_t *block_mapping = (int32_t *)((uintptr_t)data + ifthdr->mapping_table_off); -- nmbm_bitmap_t *block_state = (nmbm_bitmap_t *)((uintptr_t)data + ifthdr->state_table_off); -- uint32_t minimum_mapping_pb = ni->signature_ba; -- uint32_t ba; -- -- for (ba = 0; ba < ni->data_block_count; ba++) { -- if ((block_mapping[ba] >= ni->data_block_count && block_mapping[ba] < end_ba) || -- block_mapping[ba] == ni->signature_ba) -- return false; -- -- if (block_mapping[ba] >= end_ba && block_mapping[ba] < minimum_mapping_pb) -- minimum_mapping_pb = block_mapping[ba]; -- } -- -- for (ba = start_ba; ba < end_ba; ba++) { -- if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) -- continue; -- -- if (nmbm_get_block_state_raw(block_state, ba) != BLOCK_ST_GOOD) -- return false; -- } -- -- *mapping_blocks_top_ba = minimum_mapping_pb - 1; -- -- return true; --} -- --/* -- * nmbm_try_load_info_table - Try to load info table from a address -- * @ni: NMBM instance structure -- * @ba: start block address of the info table -- * @eba: return the block address after end of the table -- * @write_count: return the write count of this table -- * @mapping_blocks_top_ba: return the block address of top remapped block -- * @table_loaded: used to record whether ni->info_table has valid data -- */ --static bool nmbm_try_load_info_table(struct nmbm_instance *ni, uint32_t ba, -- uint32_t *eba, uint32_t *write_count, -- uint32_t *mapping_blocks_top_ba, -- bool table_loaded) --{ -- struct nmbm_info_table_header *ifthdr = (void *)ni->info_table_cache; -- uint8_t *off = ni->info_table_cache; -- uint32_t limit = ba + size2blk(ni, ni->info_table_size); -- uint32_t start_ba = 0, chunksize, sizeremain = ni->info_table_size; -- bool success, checkhdr = true; -- int ret; -- -- while (sizeremain && ba < limit) { -- WATCHDOG_RESET(); -- -- if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) -- goto next_block; -- -- if (nmbm_check_bad_phys_block(ni, ba)) { -- nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); -- goto next_block; -- } -- -- chunksize = sizeremain; -- if (chunksize > ni->lower.erasesize) -- chunksize = ni->lower.erasesize; -- -- /* Assume block with ECC error has no info table data */ -- ret = nmbn_read_data(ni, ba2addr(ni, ba), off, chunksize); -- if (ret < 0) -- goto skip_bad_block; -- else if (ret > 0) -- return false; -- -- if (checkhdr) { -- success = nmbm_check_info_table_header(ni, off); -- if (!success) -- return false; -- -- start_ba = ba; -- checkhdr = false; -- } -- -- off += chunksize; -- sizeremain -= chunksize; -- -- goto next_block; -- -- skip_bad_block: -- /* Only mark bad in memory */ -- nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); -- -- next_block: -- ba++; -- } -- -- if (sizeremain) -- return false; -- -- success = nmbm_check_header(ni->info_table_cache, ni->info_table_size); -- if (!success) -- return false; -- -- *eba = ba; -- *write_count = ifthdr->write_count; -- -- success = nmbm_check_info_table(ni, start_ba, ba, ni->info_table_cache, -- mapping_blocks_top_ba); -- if (!success) -- return false; -- -- if (!table_loaded || ifthdr->write_count > ni->info_table.write_count) { -- memcpy(&ni->info_table, ifthdr, sizeof(ni->info_table)); -- memcpy(ni->block_state, -- (uint8_t *)ifthdr + ifthdr->state_table_off, -- ni->state_table_size); -- memcpy(ni->block_mapping, -- (uint8_t *)ifthdr + ifthdr->mapping_table_off, -- ni->mapping_table_size); -- ni->info_table.write_count = ifthdr->write_count; -- } -- -- return true; --} -- --/* -- * nmbm_search_info_table - Search info table from specific address -- * @ni: NMBM instance structure -- * @ba: start block address to search -- * @limit: highest block address allowed for searching -- * @table_start_ba: return the start block address of this table -- * @table_end_ba: return the block address after end of this table -- * @write_count: return the write count of this table -- * @mapping_blocks_top_ba: return the block address of top remapped block -- * @table_loaded: used to record whether ni->info_table has valid data -- */ --static bool nmbm_search_info_table(struct nmbm_instance *ni, uint32_t ba, -- uint32_t limit, uint32_t *table_start_ba, -- uint32_t *table_end_ba, -- uint32_t *write_count, -- uint32_t *mapping_blocks_top_ba, -- bool table_loaded) --{ -- bool success; -- -- while (ba < limit - size2blk(ni, ni->info_table_size)) { -- WATCHDOG_RESET(); -- -- success = nmbm_try_load_info_table(ni, ba, table_end_ba, -- write_count, -- mapping_blocks_top_ba, -- table_loaded); -- if (success) { -- *table_start_ba = ba; -- return true; -- } -- -- ba++; -- } -- -- return false; --} -- --/* -- * nmbm_load_info_table - Load info table(s) from a chip -- * @ni: NMBM instance structure -- * @ba: start block address to search info table -- * @limit: highest block address allowed for searching -- */ --static bool nmbm_load_info_table(struct nmbm_instance *ni, uint32_t ba, -- uint32_t limit) --{ -- uint32_t main_table_end_ba, backup_table_end_ba, table_end_ba; -- uint32_t main_mapping_blocks_top_ba, backup_mapping_blocks_top_ba; -- uint32_t main_table_write_count, backup_table_write_count; -- uint32_t i; -- bool success; -- -- /* Set initial value */ -- ni->main_table_ba = 0; -- ni->backup_table_ba = 0; -- ni->info_table.write_count = 0; -- ni->mapping_blocks_top_ba = ni->signature_ba - 1; -- ni->data_block_count = ni->signature.mgmt_start_pb; -- -- /* Find first info table */ -- success = nmbm_search_info_table(ni, ba, limit, &ni->main_table_ba, -- &main_table_end_ba, &main_table_write_count, -- &main_mapping_blocks_top_ba, false); -- if (!success) { -- nlog_warn(ni, "No valid info table found\n"); -- return false; -- } -- -- table_end_ba = main_table_end_ba; -- -- nlog_table_found(ni, true, main_table_write_count, ni->main_table_ba, -- main_table_end_ba); -- -- /* Find second info table */ -- success = nmbm_search_info_table(ni, main_table_end_ba, limit, -- &ni->backup_table_ba, &backup_table_end_ba, -- &backup_table_write_count, &backup_mapping_blocks_top_ba, true); -- if (!success) { -- nlog_warn(ni, "Second info table not found\n"); -- } else { -- table_end_ba = backup_table_end_ba; -- -- nlog_table_found(ni, false, backup_table_write_count, -- ni->backup_table_ba, backup_table_end_ba); -- } -- -- /* Pick mapping_blocks_top_ba */ -- if (!ni->backup_table_ba) { -- ni->mapping_blocks_top_ba= main_mapping_blocks_top_ba; -- } else { -- if (main_table_write_count >= backup_table_write_count) -- ni->mapping_blocks_top_ba = main_mapping_blocks_top_ba; -- else -- ni->mapping_blocks_top_ba = backup_mapping_blocks_top_ba; -- } -- -- /* Set final mapping_blocks_ba */ -- ni->mapping_blocks_ba = table_end_ba; -- -- /* Set final data_block_count */ -- for (i = ni->signature.mgmt_start_pb; i > 0; i--) { -- if (ni->block_mapping[i - 1] >= 0) { -- ni->data_block_count = i; -- break; -- } -- } -- -- /* Debug purpose: mark mapped blocks and bad blocks */ -- for (i = 0; i < ni->data_block_count; i++) { -- if (ni->block_mapping[i] > ni->mapping_blocks_top_ba) -- nmbm_mark_block_color_mapped(ni, ni->block_mapping[i]); -- } -- -- for (i = 0; i < ni->block_count; i++) { -- if (nmbm_get_block_state(ni, i) == BLOCK_ST_BAD) -- nmbm_mark_block_color_bad(ni, i); -- } -- -- /* Regenerate the info table cache from the final selected info table */ -- nmbm_generate_info_table_cache(ni); -- -- if (ni->lower.flags & NMBM_F_READ_ONLY) -- return true; -- -- /* -- * If only one table exists, try to write another table. -- * If two tables have different write count, try to update info table -- */ -- if (!ni->backup_table_ba) { -- success = nmbm_rescue_single_info_table(ni); -- } else if (main_table_write_count != backup_table_write_count) { -- /* Mark state & mapping tables changed */ -- ni->block_state_changed = 1; -- ni->block_mapping_changed = 1; -- -- success = nmbm_update_single_info_table(ni, -- main_table_write_count < backup_table_write_count); -- } else { -- success = true; -- } -- -- /* -- * If there is no spare unmapped blocks, or still only one table -- * exists, set the chip to read-only -- */ -- if (ni->mapping_blocks_ba == ni->mapping_blocks_top_ba) { -- nlog_warn(ni, "No spare unmapped blocks. Device is now read-only\n"); -- ni->protected = 1; -- } else if (!success) { -- nlog_warn(ni, "Only one info table found. Device is now read-only\n"); -- ni->protected = 1; -- } -- -- return true; --} -- --/* -- * nmbm_load_existing - Load NMBM from a new chip -- * @ni: NMBM instance structure -- */ --static bool nmbm_load_existing(struct nmbm_instance *ni) --{ -- bool success; -- -- /* Calculate the boundary of management blocks */ -- ni->mgmt_start_ba = ni->signature.mgmt_start_pb; -- -- nlog_debug(ni, "NMBM management region starts at block %u [0x%08llx]\n", -- ni->mgmt_start_ba, ba2addr(ni, ni->mgmt_start_ba)); -- nmbm_mark_block_color_mgmt(ni, ni->mgmt_start_ba, -- ni->signature_ba - 1); -- -- /* Look for info table(s) */ -- success = nmbm_load_info_table(ni, ni->mgmt_start_ba, -- ni->signature_ba); -- if (success) { -- nlog_info(ni, "NMBM has been successfully attached %s\n", -- (ni->lower.flags & NMBM_F_READ_ONLY) ? "in read-only mode" : ""); -- return true; -- } -- -- if (!(ni->lower.flags & NMBM_F_CREATE)) -- return false; -- -- /* Fill block state table & mapping table */ -- nmbm_scan_badblocks(ni); -- nmbm_build_mapping_table(ni); -- -- if (ni->lower.flags & NMBM_F_READ_ONLY) { -- nlog_info(ni, "NMBM has been initialized in read-only mode\n"); -- return true; -- } -- -- /* Write info table(s) */ -- success = nmbm_create_info_table(ni); -- if (success) { -- nlog_info(ni, "NMBM has been successfully created\n"); -- return true; -- } -- -- return false; --} -- --/* -- * nmbm_find_signature - Find signature in the lower NAND chip -- * @ni: NMBM instance structure -- * @signature_ba: used for storing block address of the signature -- * @signature_ba: return the actual block address of signature block -- * -- * Find a valid signature from a specific range in the lower NAND chip, -- * from bottom (highest address) to top (lowest address) -- * -- * Return true if found. -- */ --static bool nmbm_find_signature(struct nmbm_instance *ni, -- struct nmbm_signature *signature, -- uint32_t *signature_ba) --{ -- struct nmbm_signature sig; -- uint64_t off, addr; -- uint32_t block_count, ba, limit; -- bool success; -- int ret; -- -- /* Calculate top and bottom block address */ -- block_count = ni->lower.size >> ni->erasesize_shift; -- ba = block_count; -- limit = (block_count / NMBM_MGMT_DIV) * (NMBM_MGMT_DIV - ni->lower.max_ratio); -- if (ni->lower.max_reserved_blocks && block_count - limit > ni->lower.max_reserved_blocks) -- limit = block_count - ni->lower.max_reserved_blocks; -- -- while (ba >= limit) { -- WATCHDOG_RESET(); -- -- ba--; -- addr = ba2addr(ni, ba); -- -- if (nmbm_check_bad_phys_block(ni, ba)) -- continue; -- -- /* Check every page. -- * As long as at leaset one page contains valid signature, -- * the block is treated as a valid signature block. -- */ -- for (off = 0; off < ni->lower.erasesize; -- off += ni->lower.writesize) { -- WATCHDOG_RESET(); -- -- ret = nmbn_read_data(ni, addr + off, &sig, -- sizeof(sig)); -- if (ret) -- continue; -- -- /* Check for header size and checksum */ -- success = nmbm_check_header(&sig, sizeof(sig)); -- if (!success) -- continue; -- -- /* Check for header magic */ -- if (sig.header.magic == NMBM_MAGIC_SIGNATURE) { -- /* Found it */ -- memcpy(signature, &sig, sizeof(sig)); -- *signature_ba = ba; -- return true; -- } -- } -- }; -- -- return false; --} -- --/* -- * is_power_of_2_u64 - Check whether a 64-bit integer is power of 2 -- * @n: number to check -- */ --static bool is_power_of_2_u64(uint64_t n) --{ -- return (n != 0 && ((n & (n - 1)) == 0)); --} -- --/* -- * nmbm_check_lower_members - Validate the members of lower NAND device -- * @nld: Lower NAND chip structure -- */ --static bool nmbm_check_lower_members(struct nmbm_lower_device *nld) --{ -- -- if (!nld->size || !is_power_of_2_u64(nld->size)) { -- nmbm_log_lower(nld, NMBM_LOG_ERR, -- "Chip size %llu is not valid\n", nld->size); -- return false; -- } -- -- if (!nld->erasesize || !is_power_of_2(nld->erasesize)) { -- nmbm_log_lower(nld, NMBM_LOG_ERR, -- "Block size %u is not valid\n", nld->erasesize); -- return false; -- } -- -- if (!nld->writesize || !is_power_of_2(nld->writesize)) { -- nmbm_log_lower(nld, NMBM_LOG_ERR, -- "Page size %u is not valid\n", nld->writesize); -- return false; -- } -- -- if (!nld->oobsize || !is_power_of_2(nld->oobsize)) { -- nmbm_log_lower(nld, NMBM_LOG_ERR, -- "Page spare size %u is not valid\n", nld->oobsize); -- return false; -- } -- -- if (!nld->read_page) { -- nmbm_log_lower(nld, NMBM_LOG_ERR, "read_page() is required\n"); -- return false; -- } -- -- if (!(nld->flags & NMBM_F_READ_ONLY) && (!nld->write_page || !nld->erase_block)) { -- nmbm_log_lower(nld, NMBM_LOG_ERR, -- "write_page() and erase_block() are required\n"); -- return false; -- } -- -- /* Data sanity check */ -- if (!nld->max_ratio) -- nld->max_ratio = 1; -- -- if (nld->max_ratio >= NMBM_MGMT_DIV - 1) { -- nmbm_log_lower(nld, NMBM_LOG_ERR, -- "max ratio %u is invalid\n", nld->max_ratio); -- return false; -- } -- -- if (nld->max_reserved_blocks && nld->max_reserved_blocks < NMBM_MGMT_BLOCKS_MIN) { -- nmbm_log_lower(nld, NMBM_LOG_ERR, -- "max reserved blocks %u is too small\n", nld->max_reserved_blocks); -- return false; -- } -- -- return true; --} -- --/* -- * nmbm_calc_structure_size - Calculate the instance structure size -- * @nld: NMBM lower device structure -- */ --size_t nmbm_calc_structure_size(struct nmbm_lower_device *nld) --{ -- uint32_t state_table_size, mapping_table_size, info_table_size; -- uint32_t block_count; -- -- block_count = nmbm_lldiv(nld->size, nld->erasesize); -- -- /* Calculate info table size */ -- state_table_size = ((block_count + NMBM_BITMAP_BLOCKS_PER_UNIT - 1) / -- NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_UNIT_SIZE; -- mapping_table_size = block_count * sizeof(int32_t); -- -- info_table_size = NMBM_ALIGN(sizeof(struct nmbm_info_table_header), -- nld->writesize); -- info_table_size += NMBM_ALIGN(state_table_size, nld->writesize); -- info_table_size += NMBM_ALIGN(mapping_table_size, nld->writesize); -- -- return info_table_size + state_table_size + mapping_table_size + -- nld->writesize + nld->oobsize + sizeof(struct nmbm_instance); --} -- --/* -- * nmbm_init_structure - Initialize members of instance structure -- * @ni: NMBM instance structure -- */ --static void nmbm_init_structure(struct nmbm_instance *ni) --{ -- uint32_t pages_per_block, blocks_per_chip; -- uintptr_t ptr; -- -- pages_per_block = ni->lower.erasesize / ni->lower.writesize; -- blocks_per_chip = nmbm_lldiv(ni->lower.size, ni->lower.erasesize); -- -- ni->rawpage_size = ni->lower.writesize + ni->lower.oobsize; -- ni->rawblock_size = pages_per_block * ni->rawpage_size; -- ni->rawchip_size = blocks_per_chip * ni->rawblock_size; -- -- ni->writesize_mask = ni->lower.writesize - 1; -- ni->erasesize_mask = ni->lower.erasesize - 1; -- -- ni->writesize_shift = ffs(ni->lower.writesize) - 1; -- ni->erasesize_shift = ffs(ni->lower.erasesize) - 1; -- -- /* Calculate number of block this chip */ -- ni->block_count = ni->lower.size >> ni->erasesize_shift; -- -- /* Calculate info table size */ -- ni->state_table_size = ((ni->block_count + NMBM_BITMAP_BLOCKS_PER_UNIT - 1) / -- NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_UNIT_SIZE; -- ni->mapping_table_size = ni->block_count * sizeof(*ni->block_mapping); -- -- ni->info_table_size = NMBM_ALIGN(sizeof(ni->info_table), -- ni->lower.writesize); -- ni->info_table.state_table_off = ni->info_table_size; -- -- ni->info_table_size += NMBM_ALIGN(ni->state_table_size, -- ni->lower.writesize); -- ni->info_table.mapping_table_off = ni->info_table_size; -- -- ni->info_table_size += NMBM_ALIGN(ni->mapping_table_size, -- ni->lower.writesize); -- -- ni->info_table_spare_blocks = nmbm_get_spare_block_count( -- size2blk(ni, ni->info_table_size)); -- -- /* Assign memory to members */ -- ptr = (uintptr_t)ni + sizeof(*ni); -- -- ni->info_table_cache = (void *)ptr; -- ptr += ni->info_table_size; -- -- ni->block_state = (void *)ptr; -- ptr += ni->state_table_size; -- -- ni->block_mapping = (void *)ptr; -- ptr += ni->mapping_table_size; -- -- ni->page_cache = (uint8_t *)ptr; -- -- /* Initialize block state table */ -- ni->block_state_changed = 0; -- memset(ni->block_state, 0xff, ni->state_table_size); -- -- /* Initialize block mapping table */ -- ni->block_mapping_changed = 0; --} -- --/* -- * nmbm_attach - Attach to a lower device -- * @nld: NMBM lower device structure -- * @ni: NMBM instance structure -- */ --int nmbm_attach(struct nmbm_lower_device *nld, struct nmbm_instance *ni) --{ -- bool success; -- -- if (!nld || !ni) -- return -EINVAL; -- -- /* Set default log level */ -- ni->log_display_level = NMBM_DEFAULT_LOG_LEVEL; -- -- /* Check lower members */ -- success = nmbm_check_lower_members(nld); -- if (!success) -- return -EINVAL; -- -- /* Initialize NMBM instance */ -- memcpy(&ni->lower, nld, sizeof(struct nmbm_lower_device)); -- nmbm_init_structure(ni); -- -- success = nmbm_find_signature(ni, &ni->signature, &ni->signature_ba); -- if (!success) { -- if (!(nld->flags & NMBM_F_CREATE)) { -- nlog_err(ni, "Signature not found\n"); -- return -ENODEV; -- } -- -- success = nmbm_create_new(ni); -- if (!success) -- return -ENODEV; -- -- return 0; -- } -- -- nlog_info(ni, "Signature found at block %u [0x%08llx]\n", -- ni->signature_ba, ba2addr(ni, ni->signature_ba)); -- nmbm_mark_block_color_signature(ni, ni->signature_ba); -- -- if (ni->signature.header.version != NMBM_VER) { -- nlog_err(ni, "NMBM version %u.%u is not supported\n", -- NMBM_VERSION_MAJOR_GET(ni->signature.header.version), -- NMBM_VERSION_MINOR_GET(ni->signature.header.version)); -- return -EINVAL; -- } -- -- if (ni->signature.nand_size != nld->size || -- ni->signature.block_size != nld->erasesize || -- ni->signature.page_size != nld->writesize || -- ni->signature.spare_size != nld->oobsize) { -- nlog_err(ni, "NMBM configuration mismatch\n"); -- return -EINVAL; -- } -- -- success = nmbm_load_existing(ni); -- if (!success) -- return -ENODEV; -- -- return 0; --} -- --/* -- * nmbm_detach - Detach from a lower device, and save all tables -- * @ni: NMBM instance structure -- */ --int nmbm_detach(struct nmbm_instance *ni) --{ -- if (!ni) -- return -EINVAL; -- -- if (!(ni->lower.flags & NMBM_F_READ_ONLY)) -- nmbm_update_info_table(ni); -- -- nmbm_mark_block_color_normal(ni, 0, ni->block_count - 1); -- -- return 0; --} -- --/* -- * nmbm_erase_logic_block - Erase a logic block -- * @ni: NMBM instance structure -- * @nmbm_erase_logic_block: logic block address -- * -- * Logic block will be mapped to physical block before erasing. -- * Bad block found during erasinh will be remapped to a good block if there is -- * still at least one good spare block available. -- */ --static int nmbm_erase_logic_block(struct nmbm_instance *ni, uint32_t block_addr) --{ -- uint32_t pb; -- bool success; -- --retry: -- /* Map logic block to physical block */ -- pb = ni->block_mapping[block_addr]; -- -- /* Whether the logic block is good (has valid mapping) */ -- if ((int32_t)pb < 0) { -- nlog_debug(ni, "Logic block %u is a bad block\n", block_addr); -- return -EIO; -- } -- -- /* Remap logic block if current physical block is a bad block */ -- if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD || -- nmbm_get_block_state(ni, pb) == BLOCK_ST_NEED_REMAP) -- goto remap_logic_block; -- -- /* Insurance to detect unexpected bad block marked by user */ -- if (nmbm_check_bad_phys_block(ni, pb)) { -- nlog_warn(ni, "Found unexpected bad block possibly marked by user\n"); -- nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); -- goto remap_logic_block; -- } -- -- success = nmbm_erase_block_and_check(ni, pb); -- if (success) -- return 0; -- -- /* Mark bad block */ -- nmbm_mark_phys_bad_block(ni, pb); -- nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); -- --remap_logic_block: -- /* Try to assign a new block */ -- success = nmbm_map_block(ni, block_addr); -- if (!success) { -- /* Mark logic block unusable, and update info table */ -- ni->block_mapping[block_addr] = -1; -- if (nmbm_get_block_state(ni, pb) != BLOCK_ST_NEED_REMAP) -- nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); -- nmbm_update_info_table(ni); -- return -EIO; -- } -- -- /* Update info table before erasing */ -- if (nmbm_get_block_state(ni, pb) != BLOCK_ST_NEED_REMAP) -- nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); -- nmbm_update_info_table(ni); -- -- goto retry; --} -- --/* -- * nmbm_erase_block_range - Erase logic blocks -- * @ni: NMBM instance structure -- * @addr: logic linear address -- * @size: erase range -- * @failed_addr: return failed block address if error occurs -- */ --int nmbm_erase_block_range(struct nmbm_instance *ni, uint64_t addr, -- uint64_t size, uint64_t *failed_addr) --{ -- uint32_t start_ba, end_ba; -- int ret; -- -- if (!ni) -- return -EINVAL; -- -- /* Sanity check */ -- if (ni->protected || (ni->lower.flags & NMBM_F_READ_ONLY)) { -- nlog_debug(ni, "Device is forced read-only\n"); -- return -EROFS; -- } -- -- if (addr >= ba2addr(ni, ni->data_block_count)) { -- nlog_err(ni, "Address 0x%llx is invalid\n", addr); -- return -EINVAL; -- } -- -- if (addr + size > ba2addr(ni, ni->data_block_count)) { -- nlog_err(ni, "Erase range 0xllxu is too large\n", size); -- return -EINVAL; -- } -- -- if (!size) { -- nlog_warn(ni, "No blocks to be erased\n"); -- return 0; -- } -- -- start_ba = addr2ba(ni, addr); -- end_ba = addr2ba(ni, addr + size - 1); -- -- while (start_ba <= end_ba) { -- WATCHDOG_RESET(); -- -- ret = nmbm_erase_logic_block(ni, start_ba); -- if (ret) { -- if (failed_addr) -- *failed_addr = ba2addr(ni, start_ba); -- return ret; -- } -- -- start_ba++; -- } -- -- return 0; --} -- --/* -- * nmbm_read_logic_page - Read page based on logic address -- * @ni: NMBM instance structure -- * @addr: logic linear address -- * @data: buffer to store main data. optional. -- * @oob: buffer to store oob data. optional. -- * @mode: read mode -- * -- * Return 0 for success, positive value for corrected bitflip count, -- * -EBADMSG for ecc error, other negative values for other errors -- */ --static int nmbm_read_logic_page(struct nmbm_instance *ni, uint64_t addr, -- void *data, void *oob, enum nmbm_oob_mode mode) --{ -- uint32_t lb, pb, offset; -- uint64_t paddr; -- -- /* Extract block address and in-block offset */ -- lb = addr2ba(ni, addr); -- offset = addr & ni->erasesize_mask; -- -- /* Map logic block to physical block */ -- pb = ni->block_mapping[lb]; -- -- /* Whether the logic block is good (has valid mapping) */ -- if ((int32_t)pb < 0) { -- nlog_debug(ni, "Logic block %u is a bad block\n", lb); -- return -EIO; -- } -- -- /* Fail if physical block is marked bad */ -- if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) -- return -EIO; -- -- /* Assemble new address */ -- paddr = ba2addr(ni, pb) + offset; -- -- return nmbm_read_phys_page(ni, paddr, data, oob, mode); --} -- --/* -- * nmbm_read_single_page - Read one page based on logic address -- * @ni: NMBM instance structure -- * @addr: logic linear address -- * @data: buffer to store main data. optional. -- * @oob: buffer to store oob data. optional. -- * @mode: read mode -- * -- * Return 0 for success, positive value for corrected bitflip count, -- * -EBADMSG for ecc error, other negative values for other errors -- */ --int nmbm_read_single_page(struct nmbm_instance *ni, uint64_t addr, void *data, -- void *oob, enum nmbm_oob_mode mode) --{ -- if (!ni) -- return -EINVAL; -- -- /* Sanity check */ -- if (ni->protected) { -- nlog_debug(ni, "Device is forced read-only\n"); -- return -EROFS; -- } -- -- if (addr >= ba2addr(ni, ni->data_block_count)) { -- nlog_err(ni, "Address 0x%llx is invalid\n", addr); -- return -EINVAL; -- } -- -- return nmbm_read_logic_page(ni, addr, data, oob, mode); --} -- --/* -- * nmbm_read_range - Read data without oob -- * @ni: NMBM instance structure -- * @addr: logic linear address -- * @size: data size to read -- * @data: buffer to store main data to be read -- * @mode: read mode -- * @retlen: return actual data size read -- * -- * Return 0 for success, positive value for corrected bitflip count, -- * -EBADMSG for ecc error, other negative values for other errors -- */ --int nmbm_read_range(struct nmbm_instance *ni, uint64_t addr, size_t size, -- void *data, enum nmbm_oob_mode mode, size_t *retlen) --{ -- uint64_t off = addr; -- uint8_t *ptr = data; -- size_t sizeremain = size, chunksize, leading; -- bool has_ecc_err = false; -- int ret, max_bitflips = 0; -- -- if (!ni) -- return -EINVAL; -- -- /* Sanity check */ -- if (ni->protected) { -- nlog_debug(ni, "Device is forced read-only\n"); -- return -EROFS; -- } -- -- if (addr >= ba2addr(ni, ni->data_block_count)) { -- nlog_err(ni, "Address 0x%llx is invalid\n", addr); -- return -EINVAL; -- } -- -- if (addr + size > ba2addr(ni, ni->data_block_count)) { -- nlog_err(ni, "Read range 0x%llx is too large\n", size); -- return -EINVAL; -- } -- -- if (!size) { -- nlog_warn(ni, "No data to be read\n"); -- return 0; -- } -- -- while (sizeremain) { -- WATCHDOG_RESET(); -- -- leading = off & ni->writesize_mask; -- chunksize = ni->lower.writesize - leading; -- if (chunksize > sizeremain) -- chunksize = sizeremain; -- -- if (chunksize == ni->lower.writesize) { -- ret = nmbm_read_logic_page(ni, off - leading, ptr, -- NULL, mode); -- if (ret < 0 && ret != -EBADMSG) -- break; -- } else { -- ret = nmbm_read_logic_page(ni, off - leading, -- ni->page_cache, NULL, -- mode); -- if (ret < 0 && ret != -EBADMSG) -- break; -- -- memcpy(ptr, ni->page_cache + leading, chunksize); -- } -- -- if (ret == -EBADMSG) -- has_ecc_err = true; -- -- if (ret > max_bitflips) -- max_bitflips = ret; -- -- off += chunksize; -- ptr += chunksize; -- sizeremain -= chunksize; -- } -- -- if (retlen) -- *retlen = size - sizeremain; -- -- if (ret < 0 && ret != -EBADMSG) -- return ret; -- -- if (has_ecc_err) -- return -EBADMSG; -- -- return max_bitflips; --} -- --/* -- * nmbm_write_logic_page - Read page based on logic address -- * @ni: NMBM instance structure -- * @addr: logic linear address -- * @data: buffer contains main data. optional. -- * @oob: buffer contains oob data. optional. -- * @mode: write mode -- */ --static int nmbm_write_logic_page(struct nmbm_instance *ni, uint64_t addr, -- const void *data, const void *oob, -- enum nmbm_oob_mode mode) --{ -- uint32_t lb, pb, offset; -- uint64_t paddr; -- bool success; -- -- /* Extract block address and in-block offset */ -- lb = addr2ba(ni, addr); -- offset = addr & ni->erasesize_mask; -- -- /* Map logic block to physical block */ -- pb = ni->block_mapping[lb]; -- -- /* Whether the logic block is good (has valid mapping) */ -- if ((int32_t)pb < 0) { -- nlog_debug(ni, "Logic block %u is a bad block\n", lb); -- return -EIO; -- } -- -- /* Fail if physical block is marked bad */ -- if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) -- return -EIO; -- -- /* Assemble new address */ -- paddr = ba2addr(ni, pb) + offset; -- -- success = nmbm_write_phys_page(ni, paddr, data, oob, mode); -- if (success) -- return 0; -- -- /* -- * Do not remap bad block here. Just mark this block in state table. -- * Remap this block on erasing. -- */ -- nmbm_set_block_state(ni, pb, BLOCK_ST_NEED_REMAP); -- nmbm_update_info_table(ni); -- -- return -EIO; --} -- --/* -- * nmbm_write_single_page - Write one page based on logic address -- * @ni: NMBM instance structure -- * @addr: logic linear address -- * @data: buffer contains main data. optional. -- * @oob: buffer contains oob data. optional. -- * @mode: write mode -- */ --int nmbm_write_single_page(struct nmbm_instance *ni, uint64_t addr, -- const void *data, const void *oob, -- enum nmbm_oob_mode mode) --{ -- if (!ni) -- return -EINVAL; -- -- /* Sanity check */ -- if (ni->protected || (ni->lower.flags & NMBM_F_READ_ONLY)) { -- nlog_debug(ni, "Device is forced read-only\n"); -- return -EROFS; -- } -- -- if (addr >= ba2addr(ni, ni->data_block_count)) { -- nlog_err(ni, "Address 0x%llx is invalid\n", addr); -- return -EINVAL; -- } -- -- return nmbm_write_logic_page(ni, addr, data, oob, mode); --} -- --/* -- * nmbm_write_range - Write data without oob -- * @ni: NMBM instance structure -- * @addr: logic linear address -- * @size: data size to write -- * @data: buffer contains data to be written -- * @mode: write mode -- * @retlen: return actual data size written -- */ --int nmbm_write_range(struct nmbm_instance *ni, uint64_t addr, size_t size, -- const void *data, enum nmbm_oob_mode mode, -- size_t *retlen) --{ -- uint64_t off = addr; -- const uint8_t *ptr = data; -- size_t sizeremain = size, chunksize, leading; -- int ret; -- -- if (!ni) -- return -EINVAL; -- -- /* Sanity check */ -- if (ni->protected || (ni->lower.flags & NMBM_F_READ_ONLY)) { -- nlog_debug(ni, "Device is forced read-only\n"); -- return -EROFS; -- } -- -- if (addr >= ba2addr(ni, ni->data_block_count)) { -- nlog_err(ni, "Address 0x%llx is invalid\n", addr); -- return -EINVAL; -- } -- -- if (addr + size > ba2addr(ni, ni->data_block_count)) { -- nlog_err(ni, "Write size 0x%zx is too large\n", size); -- return -EINVAL; -- } -- -- if (!size) { -- nlog_warn(ni, "No data to be written\n"); -- return 0; -- } -- -- while (sizeremain) { -- WATCHDOG_RESET(); -- -- leading = off & ni->writesize_mask; -- chunksize = ni->lower.writesize - leading; -- if (chunksize > sizeremain) -- chunksize = sizeremain; -- -- if (chunksize == ni->lower.writesize) { -- ret = nmbm_write_logic_page(ni, off - leading, ptr, -- NULL, mode); -- if (ret) -- break; -- } else { -- memset(ni->page_cache, 0xff, leading); -- memcpy(ni->page_cache + leading, ptr, chunksize); -- -- ret = nmbm_write_logic_page(ni, off - leading, -- ni->page_cache, NULL, -- mode); -- if (ret) -- break; -- } -- -- off += chunksize; -- ptr += chunksize; -- sizeremain -= chunksize; -- } -- -- if (retlen) -- *retlen = size - sizeremain; -- -- return ret; --} -- --/* -- * nmbm_check_bad_block - Check whether a logic block is usable -- * @ni: NMBM instance structure -- * @addr: logic linear address -- */ --int nmbm_check_bad_block(struct nmbm_instance *ni, uint64_t addr) --{ -- uint32_t lb, pb; -- -- if (!ni) -- return -EINVAL; -- -- if (addr >= ba2addr(ni, ni->data_block_count)) { -- nlog_err(ni, "Address 0x%llx is invalid\n", addr); -- return -EINVAL; -- } -- -- lb = addr2ba(ni, addr); -- -- /* Map logic block to physical block */ -- pb = ni->block_mapping[lb]; -- -- if ((int32_t)pb < 0) -- return 1; -- -- if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) -- return 1; -- -- return 0; --} -- --/* -- * nmbm_mark_bad_block - Mark a logic block unusable -- * @ni: NMBM instance structure -- * @addr: logic linear address -- */ --int nmbm_mark_bad_block(struct nmbm_instance *ni, uint64_t addr) --{ -- uint32_t lb, pb; -- -- if (!ni) -- return -EINVAL; -- -- /* Sanity check */ -- if (ni->protected || (ni->lower.flags & NMBM_F_READ_ONLY)) { -- nlog_debug(ni, "Device is forced read-only\n"); -- return -EROFS; -- } -- -- if (addr >= ba2addr(ni, ni->data_block_count)) { -- nlog_err(ni, "Address 0x%llx is invalid\n", addr); -- return -EINVAL; -- } -- -- lb = addr2ba(ni, addr); -- -- /* Map logic block to physical block */ -- pb = ni->block_mapping[lb]; -- -- if ((int32_t)pb < 0) -- return 0; -- -- ni->block_mapping[lb] = -1; -- nmbm_mark_phys_bad_block(ni, pb); -- nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); -- nmbm_update_info_table(ni); -- -- return 0; --} -- --/* -- * nmbm_get_avail_size - Get available user data size -- * @ni: NMBM instance structure -- */ --uint64_t nmbm_get_avail_size(struct nmbm_instance *ni) --{ -- if (!ni) -- return 0; -- -- return (uint64_t)ni->data_block_count << ni->erasesize_shift; --} -- --/* -- * nmbm_get_lower_device - Get lower device structure -- * @ni: NMBM instance structure -- * @nld: pointer to hold the data of lower device structure -- */ --int nmbm_get_lower_device(struct nmbm_instance *ni, struct nmbm_lower_device *nld) --{ -- if (!ni) -- return -EINVAL; -- -- if (nld) -- memcpy(nld, &ni->lower, sizeof(*nld)); -- -- return 0; --} -- --#include "nmbm-debug.inl" -diff --git a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.h b/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.h -deleted file mode 100644 -index 7f9dfed9e188..000000000000 ---- a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.h -+++ /dev/null -@@ -1,20 +0,0 @@ --/* SPDX-License-Identifier: GPL-2.0 */ --/* -- * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. -- * -- * Debug addons for NAND Mapped-block Management (NMBM) -- * -- * Author: Weijie Gao -- */ -- --#ifndef _NMBM_DEBUG_H_ --#define _NMBM_DEBUG_H_ -- --#define nmbm_mark_block_color_normal(ni, start_ba, end_ba) --#define nmbm_mark_block_color_bad(ni, ba) --#define nmbm_mark_block_color_mgmt(ni, start_ba, end_ba) --#define nmbm_mark_block_color_signature(ni, ba) --#define nmbm_mark_block_color_info_table(ni, start_ba, end_ba) --#define nmbm_mark_block_color_mapped(ni, ba) -- --#endif /* _NMBM_DEBUG_H_ */ -diff --git a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.inl b/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.inl -deleted file mode 100644 -index e69de29bb2d1..000000000000 -diff --git a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-mtd.c b/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-mtd.c -deleted file mode 100644 -index a3e9e1832deb..000000000000 ---- a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-mtd.c -+++ /dev/null -@@ -1,795 +0,0 @@ --// SPDX-License-Identifier: GPL-2.0 --/* -- * MTD layer for NAND Mapped-block Management (NMBM) -- * -- * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. -- * -- * Author: Weijie Gao -- */ -- --#include --#include --#include --#include --#include --#include --#include --#include --#include --#include --#include --#include --#include --#include -- --#include "nmbm-private.h" --#include "nmbm-debug.h" -- --#define NMBM_MAX_RATIO_DEFAULT 1 --#define NMBM_MAX_BLOCKS_DEFAULT 256 -- --struct nmbm_mtd { -- struct mtd_info upper; -- struct mtd_info *lower; -- -- struct nmbm_instance *ni; -- uint8_t *page_cache; -- -- flstate_t state; -- spinlock_t lock; -- wait_queue_head_t wq; -- -- struct device *dev; -- struct list_head node; --}; -- --struct list_head nmbm_devs; --static DEFINE_MUTEX(nmbm_devs_lock); -- --static int nmbm_lower_read_page(void *arg, uint64_t addr, void *buf, void *oob, -- enum nmbm_oob_mode mode) --{ -- struct nmbm_mtd *nm = arg; -- struct mtd_oob_ops ops; -- int ret; -- -- memset(&ops, 0, sizeof(ops)); -- -- switch (mode) { -- case NMBM_MODE_PLACE_OOB: -- ops.mode = MTD_OPS_PLACE_OOB; -- break; -- case NMBM_MODE_AUTO_OOB: -- ops.mode = MTD_OPS_AUTO_OOB; -- break; -- case NMBM_MODE_RAW: -- ops.mode = MTD_OPS_RAW; -- break; -- default: -- pr_debug("%s: unsupported NMBM mode: %u\n", __func__, mode); -- return -ENOTSUPP; -- } -- -- if (buf) { -- ops.datbuf = buf; -- ops.len = nm->lower->writesize; -- } -- -- if (oob) { -- ops.oobbuf = oob; -- ops.ooblen = mtd_oobavail(nm->lower, &ops); -- } -- -- ret = mtd_read_oob(nm->lower, addr, &ops); -- nm->upper.ecc_stats.corrected = nm->lower->ecc_stats.corrected; -- nm->upper.ecc_stats.failed = nm->lower->ecc_stats.failed; -- -- /* Report error on failure (including ecc error) */ -- if (ret < 0 && ret != -EUCLEAN) -- return ret; -- -- /* -- * Since mtd_read_oob() won't report exact bitflips, what we can know -- * is whether bitflips exceeds the threshold. -- * We want the -EUCLEAN to be passed to the upper layer, but not the -- * error value itself. To achieve this, report bitflips above the -- * threshold. -- */ -- -- if (ret == -EUCLEAN) { -- return min_t(u32, nm->lower->bitflip_threshold + 1, -- nm->lower->ecc_strength); -- } -- -- /* For bitflips less than the threshold, return 0 */ -- return 0; --} -- --static int nmbm_lower_write_page(void *arg, uint64_t addr, const void *buf, -- const void *oob, enum nmbm_oob_mode mode) --{ -- struct nmbm_mtd *nm = arg; -- struct mtd_oob_ops ops; -- -- memset(&ops, 0, sizeof(ops)); -- -- switch (mode) { -- case NMBM_MODE_PLACE_OOB: -- ops.mode = MTD_OPS_PLACE_OOB; -- break; -- case NMBM_MODE_AUTO_OOB: -- ops.mode = MTD_OPS_AUTO_OOB; -- break; -- case NMBM_MODE_RAW: -- ops.mode = MTD_OPS_RAW; -- break; -- default: -- pr_debug("%s: unsupported NMBM mode: %u\n", __func__, mode); -- return -ENOTSUPP; -- } -- -- if (buf) { -- ops.datbuf = (uint8_t *)buf; -- ops.len = nm->lower->writesize; -- } -- -- if (oob) { -- ops.oobbuf = (uint8_t *)oob; -- ops.ooblen = mtd_oobavail(nm->lower, &ops); -- } -- -- return mtd_write_oob(nm->lower, addr, &ops); --} -- --static int nmbm_lower_erase_block(void *arg, uint64_t addr) --{ -- struct nmbm_mtd *nm = arg; -- struct erase_info ei; -- -- memset(&ei, 0, sizeof(ei)); -- -- ei.addr = addr; -- ei.len = nm->lower->erasesize; -- -- return mtd_erase(nm->lower, &ei); --} -- --static int nmbm_lower_is_bad_block(void *arg, uint64_t addr) --{ -- struct nmbm_mtd *nm = arg; -- -- return mtd_block_isbad(nm->lower, addr); --} -- --static int nmbm_lower_mark_bad_block(void *arg, uint64_t addr) --{ -- struct nmbm_mtd *nm = arg; -- -- return mtd_block_markbad(nm->lower, addr); --} -- --static void nmbm_lower_log(void *arg, enum nmbm_log_category level, -- const char *fmt, va_list ap) --{ -- struct nmbm_mtd *nm = arg; -- char *msg; -- char *kl; -- -- msg = kvasprintf(GFP_KERNEL, fmt, ap); -- if (!msg) { -- dev_warn(nm->dev, "unable to print log\n"); -- return; -- } -- -- switch (level) { -- case NMBM_LOG_DEBUG: -- kl = KERN_DEBUG; -- break; -- case NMBM_LOG_WARN: -- kl = KERN_WARNING; -- break; -- case NMBM_LOG_ERR: -- kl = KERN_ERR; -- break; -- case NMBM_LOG_EMERG: -- kl = KERN_EMERG; -- break; -- default: -- kl = KERN_INFO ; -- } -- -- dev_printk(kl, nm->dev, "%s", msg); -- -- kfree(msg); --} -- --static int nmbm_get_device(struct nmbm_mtd *nm, int new_state) --{ -- DECLARE_WAITQUEUE(wait, current); -- --retry: -- spin_lock(&nm->lock); -- -- if (nm->state == FL_READY) { -- nm->state = new_state; -- spin_unlock(&nm->lock); -- return 0; -- } -- -- if (new_state == FL_PM_SUSPENDED) { -- if (nm->state == FL_PM_SUSPENDED) { -- spin_unlock(&nm->lock); -- return 0; -- } -- } -- -- set_current_state(TASK_UNINTERRUPTIBLE); -- add_wait_queue(&nm->wq, &wait); -- spin_unlock(&nm->lock); -- schedule(); -- remove_wait_queue(&nm->wq, &wait); -- goto retry; --} -- --static void nmbm_release_device(struct nmbm_mtd *nm) --{ -- spin_lock(&nm->lock); -- nm->state = FL_READY; -- wake_up(&nm->wq); -- spin_unlock(&nm->lock); --} -- --static int nmbm_mtd_erase(struct mtd_info *mtd, struct erase_info *instr) --{ -- struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); -- int ret; -- -- nmbm_get_device(nm, FL_ERASING); -- -- ret = nmbm_erase_block_range(nm->ni, instr->addr, instr->len, -- &instr->fail_addr); -- -- nmbm_release_device(nm); -- -- if (!ret) -- return 0; -- -- return -EIO; --} -- --static int nmbm_mtd_read_data(struct nmbm_mtd *nm, uint64_t addr, -- struct mtd_oob_ops *ops, enum nmbm_oob_mode mode) --{ -- size_t len, ooblen, maxooblen, chklen; -- uint32_t col, ooboffs; -- uint8_t *datcache, *oobcache; -- bool has_ecc_err = false; -- int ret, max_bitflips = 0; -- -- col = addr & nm->lower->writesize_mask; -- addr &= ~nm->lower->writesize_mask; -- maxooblen = mtd_oobavail(nm->lower, ops); -- ooboffs = ops->ooboffs; -- ooblen = ops->ooblen; -- len = ops->len; -- -- datcache = len ? nm->page_cache : NULL; -- oobcache = ooblen ? nm->page_cache + nm->lower->writesize : NULL; -- -- ops->oobretlen = 0; -- ops->retlen = 0; -- -- while (len || ooblen) { -- ret = nmbm_read_single_page(nm->ni, addr, datcache, oobcache, -- mode); -- if (ret < 0 && ret != -EBADMSG) -- return ret; -- -- /* Continue reading on ecc error */ -- if (ret == -EBADMSG) -- has_ecc_err = true; -- -- /* Record the maximum bitflips between pages */ -- if (ret > max_bitflips) -- max_bitflips = ret; -- -- if (len) { -- /* Move data */ -- chklen = nm->lower->writesize - col; -- if (chklen > len) -- chklen = len; -- -- memcpy(ops->datbuf + ops->retlen, datcache + col, -- chklen); -- len -= chklen; -- col = 0; /* (col + chklen) % */ -- ops->retlen += chklen; -- } -- -- if (ooblen) { -- /* Move oob */ -- chklen = maxooblen - ooboffs; -- if (chklen > ooblen) -- chklen = ooblen; -- -- memcpy(ops->oobbuf + ops->oobretlen, oobcache + ooboffs, -- chklen); -- ooblen -= chklen; -- ooboffs = 0; /* (ooboffs + chklen) % maxooblen; */ -- ops->oobretlen += chklen; -- } -- -- addr += nm->lower->writesize; -- } -- -- if (has_ecc_err) -- return -EBADMSG; -- -- return max_bitflips; --} -- --static int nmbm_mtd_read_oob(struct mtd_info *mtd, loff_t from, -- struct mtd_oob_ops *ops) --{ -- struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); -- uint32_t maxooblen; -- enum nmbm_oob_mode mode; -- int ret; -- -- if (!ops->oobbuf && !ops->datbuf) { -- if (ops->ooblen || ops->len) -- return -EINVAL; -- -- return 0; -- } -- -- switch (ops->mode) { -- case MTD_OPS_PLACE_OOB: -- mode = NMBM_MODE_PLACE_OOB; -- break; -- case MTD_OPS_AUTO_OOB: -- mode = NMBM_MODE_AUTO_OOB; -- break; -- case MTD_OPS_RAW: -- mode = NMBM_MODE_RAW; -- break; -- default: -- pr_debug("%s: unsupported oob mode: %u\n", __func__, ops->mode); -- return -ENOTSUPP; -- } -- -- maxooblen = mtd_oobavail(mtd, ops); -- -- /* Do not allow read past end of device */ -- if (ops->datbuf && (from + ops->len) > mtd->size) { -- pr_debug("%s: attempt to read beyond end of device\n", -- __func__); -- return -EINVAL; -- } -- -- if (!ops->oobbuf) { -- nmbm_get_device(nm, FL_READING); -- -- /* Optimized for reading data only */ -- ret = nmbm_read_range(nm->ni, from, ops->len, ops->datbuf, -- mode, &ops->retlen); -- -- nmbm_release_device(nm); -- -- return ret; -- } -- -- if (unlikely(ops->ooboffs >= maxooblen)) { -- pr_debug("%s: attempt to start read outside oob\n", -- __func__); -- return -EINVAL; -- } -- -- if (unlikely(from >= mtd->size || -- ops->ooboffs + ops->ooblen > ((mtd->size >> mtd->writesize_shift) - -- (from >> mtd->writesize_shift)) * maxooblen)) { -- pr_debug("%s: attempt to read beyond end of device\n", -- __func__); -- return -EINVAL; -- } -- -- nmbm_get_device(nm, FL_READING); -- ret = nmbm_mtd_read_data(nm, from, ops, mode); -- nmbm_release_device(nm); -- -- return ret; --} -- --static int nmbm_mtd_write_data(struct nmbm_mtd *nm, uint64_t addr, -- struct mtd_oob_ops *ops, enum nmbm_oob_mode mode) --{ -- size_t len, ooblen, maxooblen, chklen; -- uint32_t col, ooboffs; -- uint8_t *datcache, *oobcache; -- int ret; -- -- col = addr & nm->lower->writesize_mask; -- addr &= ~nm->lower->writesize_mask; -- maxooblen = mtd_oobavail(nm->lower, ops); -- ooboffs = ops->ooboffs; -- ooblen = ops->ooblen; -- len = ops->len; -- -- datcache = len ? nm->page_cache : NULL; -- oobcache = ooblen ? nm->page_cache + nm->lower->writesize : NULL; -- -- ops->oobretlen = 0; -- ops->retlen = 0; -- -- while (len || ooblen) { -- if (len) { -- /* Move data */ -- chklen = nm->lower->writesize - col; -- if (chklen > len) -- chklen = len; -- -- memset(datcache, 0xff, col); -- memcpy(datcache + col, ops->datbuf + ops->retlen, -- chklen); -- memset(datcache + col + chklen, 0xff, -- nm->lower->writesize - col - chklen); -- len -= chklen; -- col = 0; /* (col + chklen) % */ -- ops->retlen += chklen; -- } -- -- if (ooblen) { -- /* Move oob */ -- chklen = maxooblen - ooboffs; -- if (chklen > ooblen) -- chklen = ooblen; -- -- memset(oobcache, 0xff, ooboffs); -- memcpy(oobcache + ooboffs, -- ops->oobbuf + ops->oobretlen, chklen); -- memset(oobcache + ooboffs + chklen, 0xff, -- nm->lower->oobsize - ooboffs - chklen); -- ooblen -= chklen; -- ooboffs = 0; /* (ooboffs + chklen) % maxooblen; */ -- ops->oobretlen += chklen; -- } -- -- ret = nmbm_write_single_page(nm->ni, addr, datcache, oobcache, -- mode); -- if (ret) -- return ret; -- -- addr += nm->lower->writesize; -- } -- -- return 0; --} -- --static int nmbm_mtd_write_oob(struct mtd_info *mtd, loff_t to, -- struct mtd_oob_ops *ops) --{ -- struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); -- enum nmbm_oob_mode mode; -- uint32_t maxooblen; -- int ret; -- -- if (!ops->oobbuf && !ops->datbuf) { -- if (ops->ooblen || ops->len) -- return -EINVAL; -- -- return 0; -- } -- -- switch (ops->mode) { -- case MTD_OPS_PLACE_OOB: -- mode = NMBM_MODE_PLACE_OOB; -- break; -- case MTD_OPS_AUTO_OOB: -- mode = NMBM_MODE_AUTO_OOB; -- break; -- case MTD_OPS_RAW: -- mode = NMBM_MODE_RAW; -- break; -- default: -- pr_debug("%s: unsupported oob mode: %u\n", __func__, -- ops->mode); -- return -ENOTSUPP; -- } -- -- maxooblen = mtd_oobavail(mtd, ops); -- -- /* Do not allow write past end of device */ -- if (ops->datbuf && (to + ops->len) > mtd->size) { -- pr_debug("%s: attempt to write beyond end of device\n", -- __func__); -- return -EINVAL; -- } -- -- if (!ops->oobbuf) { -- nmbm_get_device(nm, FL_WRITING); -- -- /* Optimized for writing data only */ -- ret = nmbm_write_range(nm->ni, to, ops->len, ops->datbuf, -- mode, &ops->retlen); -- -- nmbm_release_device(nm); -- -- return ret; -- } -- -- if (unlikely(ops->ooboffs >= maxooblen)) { -- pr_debug("%s: attempt to start write outside oob\n", -- __func__); -- return -EINVAL; -- } -- -- if (unlikely(to >= mtd->size || -- ops->ooboffs + ops->ooblen > ((mtd->size >> mtd->writesize_shift) - -- (to >> mtd->writesize_shift)) * maxooblen)) { -- pr_debug("%s: attempt to write beyond end of device\n", -- __func__); -- return -EINVAL; -- } -- -- nmbm_get_device(nm, FL_WRITING); -- ret = nmbm_mtd_write_data(nm, to, ops, mode); -- nmbm_release_device(nm); -- -- return ret; --} -- --static int nmbm_mtd_block_isbad(struct mtd_info *mtd, loff_t offs) --{ -- struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); -- int ret; -- -- nmbm_get_device(nm, FL_READING); -- ret = nmbm_check_bad_block(nm->ni, offs); -- nmbm_release_device(nm); -- -- return ret; --} -- --static int nmbm_mtd_block_markbad(struct mtd_info *mtd, loff_t offs) --{ -- struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); -- int ret; -- -- nmbm_get_device(nm, FL_WRITING); -- ret = nmbm_mark_bad_block(nm->ni, offs); -- nmbm_release_device(nm); -- -- return ret; --} -- --static void nmbm_mtd_shutdown(struct mtd_info *mtd) --{ -- struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); -- -- nmbm_get_device(nm, FL_PM_SUSPENDED); --} -- --static int nmbm_probe(struct platform_device *pdev) --{ -- struct device_node *mtd_np, *np = pdev->dev.of_node; -- uint32_t max_ratio, max_reserved_blocks, alloc_size; -- bool forced_create, empty_page_ecc_ok; -- struct nmbm_lower_device nld; -- struct mtd_info *lower, *mtd; -- struct nmbm_mtd *nm; -- const char *mtdname; -- int ret; -- -- mtd_np = of_parse_phandle(np, "lower-mtd-device", 0); -- if (mtd_np) { -- lower = get_mtd_device_by_node(mtd_np); -- if (!IS_ERR(lower)) -- goto do_attach_mtd; -- -- dev_dbg(&pdev->dev, "failed to find mtd device by phandle\n"); -- return -EPROBE_DEFER; -- } -- -- ret = of_property_read_string(np, "lower-mtd-name", &mtdname); -- if (!ret) { -- lower = get_mtd_device_nm(mtdname); -- if (!IS_ERR(lower)) -- goto do_attach_mtd; -- -- dev_dbg(&pdev->dev, "failed to find mtd device by name '%s'\n", -- mtdname); -- return -EPROBE_DEFER; -- } -- --do_attach_mtd: -- if (of_property_read_u32(np, "max-ratio", &max_ratio)) -- max_ratio = NMBM_MAX_RATIO_DEFAULT; -- -- if (of_property_read_u32(np, "max-reserved-blocks", -- &max_reserved_blocks)) -- max_reserved_blocks = NMBM_MAX_BLOCKS_DEFAULT; -- -- forced_create = of_property_read_bool(np, "forced-create"); -- empty_page_ecc_ok = of_property_read_bool(np, -- "empty-page-ecc-protected"); -- -- memset(&nld, 0, sizeof(nld)); -- -- nld.flags = 0; -- -- if (forced_create) -- nld.flags |= NMBM_F_CREATE; -- -- if (empty_page_ecc_ok) -- nld.flags |= NMBM_F_EMPTY_PAGE_ECC_OK; -- -- nld.max_ratio = max_ratio; -- nld.max_reserved_blocks = max_reserved_blocks; -- -- nld.size = lower->size; -- nld.erasesize = lower->erasesize; -- nld.writesize = lower->writesize; -- nld.oobsize = lower->oobsize; -- nld.oobavail = lower->oobavail; -- -- nld.read_page = nmbm_lower_read_page; -- nld.write_page = nmbm_lower_write_page; -- nld.erase_block = nmbm_lower_erase_block; -- nld.is_bad_block = nmbm_lower_is_bad_block; -- nld.mark_bad_block = nmbm_lower_mark_bad_block; -- -- nld.logprint = nmbm_lower_log; -- -- alloc_size = nmbm_calc_structure_size(&nld); -- -- nm = devm_kzalloc(&pdev->dev, sizeof(*nm) + alloc_size + -- lower->writesize + lower->oobsize, GFP_KERNEL); -- if (!nm) { -- ret = -ENOMEM; -- goto out; -- } -- -- nm->ni = (void *)nm + sizeof(*nm); -- nm->page_cache = (uint8_t *)nm->ni + alloc_size; -- nm->lower = lower; -- nm->dev = &pdev->dev; -- -- INIT_LIST_HEAD(&nm->node); -- spin_lock_init(&nm->lock); -- init_waitqueue_head(&nm->wq); -- -- nld.arg = nm; -- -- ret = nmbm_attach(&nld, nm->ni); -- if (ret) -- goto out; -- -- /* Initialize upper mtd */ -- mtd = &nm->upper; -- -- mtd->owner = THIS_MODULE; -- mtd->dev.parent = &pdev->dev; -- mtd->type = lower->type; -- mtd->flags = lower->flags; -- -- mtd->size = (uint64_t)nm->ni->data_block_count * lower->erasesize; -- mtd->erasesize = lower->erasesize; -- mtd->writesize = lower->writesize; -- mtd->writebufsize = lower->writesize; -- mtd->oobsize = lower->oobsize; -- mtd->oobavail = lower->oobavail; -- -- mtd->erasesize_shift = lower->erasesize_shift; -- mtd->writesize_shift = lower->writesize_shift; -- mtd->erasesize_mask = lower->erasesize_mask; -- mtd->writesize_mask = lower->writesize_mask; -- -- mtd->bitflip_threshold = lower->bitflip_threshold; -- -- mtd->ooblayout = lower->ooblayout; -- -- mtd->ecc_step_size = lower->ecc_step_size; -- mtd->ecc_strength = lower->ecc_strength; -- -- mtd->numeraseregions = lower->numeraseregions; -- mtd->eraseregions = lower->eraseregions; -- -- mtd->_erase = nmbm_mtd_erase; -- mtd->_read_oob = nmbm_mtd_read_oob; -- mtd->_write_oob = nmbm_mtd_write_oob; -- mtd->_block_isbad = nmbm_mtd_block_isbad; -- mtd->_block_markbad = nmbm_mtd_block_markbad; -- mtd->_reboot = nmbm_mtd_shutdown; -- -- mtd_set_of_node(mtd, np); -- -- ret = mtd_device_register(mtd, NULL, 0); -- if (ret) { -- dev_err(&pdev->dev, "failed to register mtd device\n"); -- nmbm_detach(nm->ni); -- goto out; -- } -- -- platform_set_drvdata(pdev, nm); -- -- mutex_lock(&nmbm_devs_lock); -- list_add_tail(&nm->node, &nmbm_devs); -- mutex_unlock(&nmbm_devs_lock); -- -- return 0; -- --out: -- if (nm) -- devm_kfree(&pdev->dev, nm); -- -- put_mtd_device(lower); -- -- return ret; --} -- --static int nmbm_remove(struct platform_device *pdev) --{ -- struct nmbm_mtd *nm = platform_get_drvdata(pdev); -- struct mtd_info *lower = nm->lower; -- int ret; -- -- ret = mtd_device_unregister(&nm->upper); -- if (ret) -- return ret; -- -- nmbm_detach(nm->ni); -- -- mutex_lock(&nmbm_devs_lock); -- list_add_tail(&nm->node, &nmbm_devs); -- mutex_unlock(&nmbm_devs_lock); -- -- devm_kfree(&pdev->dev, nm); -- -- put_mtd_device(lower); -- -- platform_set_drvdata(pdev, NULL); -- -- return 0; --} -- --static const struct of_device_id nmbm_ids[] = { -- { .compatible = "generic,nmbm" }, -- { }, --}; -- --MODULE_DEVICE_TABLE(of, nmbm_ids); -- --static struct platform_driver nmbm_driver = { -- .probe = nmbm_probe, -- .remove = nmbm_remove, -- .driver = { -- .name = "nmbm", -- .of_match_table = nmbm_ids, -- }, --}; -- --static int __init nmbm_init(void) --{ -- int ret; -- -- INIT_LIST_HEAD(&nmbm_devs); -- -- ret = platform_driver_register(&nmbm_driver); -- if (ret) { -- pr_err("failed to register nmbm driver\n"); -- return ret; -- } -- -- return 0; --} --module_init(nmbm_init); -- --static void __exit nmbm_exit(void) --{ -- platform_driver_unregister(&nmbm_driver); --} --module_exit(nmbm_exit); -- --MODULE_LICENSE("GPL"); --MODULE_AUTHOR("Weijie Gao "); --MODULE_DESCRIPTION("NAND mapping block management"); -diff --git a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-private.h b/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-private.h -deleted file mode 100644 -index c285aeb9ddb0..000000000000 ---- a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-private.h -+++ /dev/null -@@ -1,137 +0,0 @@ --/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ --/* -- * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. -- * -- * Definitions for NAND Mapped-block Management (NMBM) -- * -- * Author: Weijie Gao -- */ -- --#ifndef _NMBM_PRIVATE_H_ --#define _NMBM_PRIVATE_H_ -- --#include -- --#define NMBM_MAGIC_SIGNATURE 0x304d4d4e /* NMM0 */ --#define NMBM_MAGIC_INFO_TABLE 0x314d4d4e /* NMM1 */ -- --#define NMBM_VERSION_MAJOR_S 0 --#define NMBM_VERSION_MAJOR_M 0xffff --#define NMBM_VERSION_MINOR_S 16 --#define NMBM_VERSION_MINOR_M 0xffff --#define NMBM_VERSION_MAKE(major, minor) (((major) & NMBM_VERSION_MAJOR_M) | \ -- (((minor) & NMBM_VERSION_MINOR_M) << \ -- NMBM_VERSION_MINOR_S)) --#define NMBM_VERSION_MAJOR_GET(ver) (((ver) >> NMBM_VERSION_MAJOR_S) & \ -- NMBM_VERSION_MAJOR_M) --#define NMBM_VERSION_MINOR_GET(ver) (((ver) >> NMBM_VERSION_MINOR_S) & \ -- NMBM_VERSION_MINOR_M) -- --typedef uint32_t nmbm_bitmap_t; --#define NMBM_BITMAP_UNIT_SIZE (sizeof(nmbm_bitmap_t)) --#define NMBM_BITMAP_BITS_PER_BLOCK 2 --#define NMBM_BITMAP_BITS_PER_UNIT (8 * sizeof(nmbm_bitmap_t)) --#define NMBM_BITMAP_BLOCKS_PER_UNIT (NMBM_BITMAP_BITS_PER_UNIT / \ -- NMBM_BITMAP_BITS_PER_BLOCK) -- --#define NMBM_SPARE_BLOCK_MULTI 1 --#define NMBM_SPARE_BLOCK_DIV 2 --#define NMBM_SPARE_BLOCK_MIN 2 -- --#define NMBM_MGMT_DIV 16 --#define NMBM_MGMT_BLOCKS_MIN 32 -- --#define NMBM_TRY_COUNT 3 -- --#define BLOCK_ST_BAD 0 --#define BLOCK_ST_NEED_REMAP 2 --#define BLOCK_ST_GOOD 3 --#define BLOCK_ST_MASK 3 -- --struct nmbm_header { -- uint32_t magic; -- uint32_t version; -- uint32_t size; -- uint32_t checksum; --}; -- --struct nmbm_signature { -- struct nmbm_header header; -- uint64_t nand_size; -- uint32_t block_size; -- uint32_t page_size; -- uint32_t spare_size; -- uint32_t mgmt_start_pb; -- uint8_t max_try_count; -- uint8_t padding[3]; --}; -- --struct nmbm_info_table_header { -- struct nmbm_header header; -- uint32_t write_count; -- uint32_t state_table_off; -- uint32_t mapping_table_off; -- uint32_t padding; --}; -- --struct nmbm_instance { -- struct nmbm_lower_device lower; -- -- uint32_t rawpage_size; -- uint32_t rawblock_size; -- uint32_t rawchip_size; -- -- uint32_t writesize_mask; -- uint32_t erasesize_mask; -- uint16_t writesize_shift; -- uint16_t erasesize_shift; -- -- struct nmbm_signature signature; -- -- uint8_t *info_table_cache; -- uint32_t info_table_size; -- uint32_t info_table_spare_blocks; -- struct nmbm_info_table_header info_table; -- -- nmbm_bitmap_t *block_state; -- uint32_t block_state_changed; -- uint32_t state_table_size; -- -- int32_t *block_mapping; -- uint32_t block_mapping_changed; -- uint32_t mapping_table_size; -- -- uint8_t *page_cache; -- -- int protected; -- -- uint32_t block_count; -- uint32_t data_block_count; -- -- uint32_t mgmt_start_ba; -- uint32_t main_table_ba; -- uint32_t backup_table_ba; -- uint32_t mapping_blocks_ba; -- uint32_t mapping_blocks_top_ba; -- uint32_t signature_ba; -- -- enum nmbm_log_category log_display_level; --}; -- --/* Log utilities */ --#define nlog_debug(ni, fmt, ...) \ -- nmbm_log(ni, NMBM_LOG_DEBUG, fmt, ##__VA_ARGS__) -- --#define nlog_info(ni, fmt, ...) \ -- nmbm_log(ni, NMBM_LOG_INFO, fmt, ##__VA_ARGS__) -- --#define nlog_warn(ni, fmt, ...) \ -- nmbm_log(ni, NMBM_LOG_WARN, fmt, ##__VA_ARGS__) -- --#define nlog_err(ni, fmt, ...) \ -- nmbm_log(ni, NMBM_LOG_ERR, fmt, ##__VA_ARGS__) -- --#define nlog_emerg(ni, fmt, ...) \ -- nmbm_log(ni, NMBM_LOG_EMERG, fmt, ##__VA_ARGS__) -- --#endif /* _NMBM_PRIVATE_H_ */ -diff --git a/target/linux/ramips/files/include/nmbm/nmbm-os.h b/target/linux/ramips/files/include/nmbm/nmbm-os.h -deleted file mode 100644 -index 1cae854df159..000000000000 ---- a/target/linux/ramips/files/include/nmbm/nmbm-os.h -+++ /dev/null -@@ -1,69 +0,0 @@ --/* SPDX-License-Identifier: GPL-2.0 */ --/* -- * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. -- * -- * OS-dependent definitions for NAND Mapped-block Management (NMBM) -- * -- * Author: Weijie Gao -- */ -- --#ifndef _NMBM_OS_H_ --#define _NMBM_OS_H_ -- --#include --#include --#include --#include --#include --#include --#include --#include -- --static inline uint32_t nmbm_crc32(uint32_t crcval, const void *buf, size_t size) --{ -- uint chksz; -- const unsigned char *p = buf; -- -- while (size) { -- if (size > UINT_MAX) -- chksz = UINT_MAX; -- else -- chksz = (uint)size; -- -- crcval = crc32_le(crcval, p, chksz); -- size -= chksz; -- p += chksz; -- } -- -- return crcval; --} -- --static inline uint32_t nmbm_lldiv(uint64_t dividend, uint32_t divisor) --{ --#if BITS_PER_LONG == 64 -- return dividend / divisor; --#else -- do_div(dividend, divisor); -- return dividend; --#endif --} -- --#define WATCHDOG_RESET() -- --#ifdef CONFIG_NMBM_LOG_LEVEL_DEBUG --#define NMBM_DEFAULT_LOG_LEVEL 0 --#elif defined(NMBM_LOG_LEVEL_INFO) --#define NMBM_DEFAULT_LOG_LEVEL 1 --#elif defined(NMBM_LOG_LEVEL_WARN) --#define NMBM_DEFAULT_LOG_LEVEL 2 --#elif defined(NMBM_LOG_LEVEL_ERR) --#define NMBM_DEFAULT_LOG_LEVEL 3 --#elif defined(NMBM_LOG_LEVEL_EMERG) --#define NMBM_DEFAULT_LOG_LEVEL 4 --#elif defined(NMBM_LOG_LEVEL_NONE) --#define NMBM_DEFAULT_LOG_LEVEL 5 --#else --#define NMBM_DEFAULT_LOG_LEVEL 1 --#endif -- --#endif /* _NMBM_OS_H_ */ -diff --git a/target/linux/ramips/files/include/nmbm/nmbm.h b/target/linux/ramips/files/include/nmbm/nmbm.h -deleted file mode 100644 -index c0400988b1f6..000000000000 ---- a/target/linux/ramips/files/include/nmbm/nmbm.h -+++ /dev/null -@@ -1,102 +0,0 @@ --/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ --/* -- * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. -- * -- * Definitions for NAND Mapped-block Management (NMBM) -- * -- * Author: Weijie Gao -- */ -- --#ifndef _NMBM_H_ --#define _NMBM_H_ -- --#include -- --enum nmbm_log_category { -- NMBM_LOG_DEBUG, -- NMBM_LOG_INFO, -- NMBM_LOG_WARN, -- NMBM_LOG_ERR, -- NMBM_LOG_EMERG, -- -- __NMBM_LOG_MAX --}; -- --enum nmbm_oob_mode { -- NMBM_MODE_PLACE_OOB, -- NMBM_MODE_AUTO_OOB, -- NMBM_MODE_RAW, -- -- __NMBM_MODE_MAX --}; -- --struct nmbm_lower_device { -- uint32_t max_ratio; -- uint32_t max_reserved_blocks; -- int flags; -- -- uint64_t size; -- uint32_t erasesize; -- uint32_t writesize; -- uint32_t oobsize; -- uint32_t oobavail; -- -- void *arg; -- int (*reset_chip)(void *arg); -- -- /* -- * read_page: -- * return 0 if succeeds -- * return positive number for ecc error -- * return negative number for other errors -- */ -- int (*read_page)(void *arg, uint64_t addr, void *buf, void *oob, enum nmbm_oob_mode mode); -- int (*write_page)(void *arg, uint64_t addr, const void *buf, const void *oob, enum nmbm_oob_mode mode); -- int (*erase_block)(void *arg, uint64_t addr); -- -- int (*is_bad_block)(void *arg, uint64_t addr); -- int (*mark_bad_block)(void *arg, uint64_t addr); -- -- /* OS-dependent logging function */ -- void (*logprint)(void *arg, enum nmbm_log_category level, const char *fmt, va_list ap); --}; -- --struct nmbm_instance; -- --/* Create NMBM if management area not found, or not complete */ --#define NMBM_F_CREATE 0x01 -- --/* Empty page is also protected by ECC, and bitflip(s) can be corrected */ --#define NMBM_F_EMPTY_PAGE_ECC_OK 0x02 -- --/* Do not write anything back to flash */ --#define NMBM_F_READ_ONLY 0x04 -- --size_t nmbm_calc_structure_size(struct nmbm_lower_device *nld); --int nmbm_attach(struct nmbm_lower_device *nld, struct nmbm_instance *ni); --int nmbm_detach(struct nmbm_instance *ni); -- --enum nmbm_log_category nmbm_set_log_level(struct nmbm_instance *ni, -- enum nmbm_log_category level); -- --int nmbm_erase_block_range(struct nmbm_instance *ni, uint64_t addr, -- uint64_t size, uint64_t *failed_addr); --int nmbm_read_single_page(struct nmbm_instance *ni, uint64_t addr, void *data, -- void *oob, enum nmbm_oob_mode mode); --int nmbm_read_range(struct nmbm_instance *ni, uint64_t addr, size_t size, -- void *data, enum nmbm_oob_mode mode, size_t *retlen); --int nmbm_write_single_page(struct nmbm_instance *ni, uint64_t addr, -- const void *data, const void *oob, -- enum nmbm_oob_mode mode); --int nmbm_write_range(struct nmbm_instance *ni, uint64_t addr, size_t size, -- const void *data, enum nmbm_oob_mode mode, -- size_t *retlen); -- --int nmbm_check_bad_block(struct nmbm_instance *ni, uint64_t addr); --int nmbm_mark_bad_block(struct nmbm_instance *ni, uint64_t addr); -- --uint64_t nmbm_get_avail_size(struct nmbm_instance *ni); -- --int nmbm_get_lower_device(struct nmbm_instance *ni, struct nmbm_lower_device *nld); -- --#endif /* _NMBM_H_ */ -diff --git a/target/linux/ramips/image/mt7621.mk b/target/linux/ramips/image/mt7621.mk -index 57c3efcf2b92..81ef6bbd142e 100644 ---- a/target/linux/ramips/image/mt7621.mk -+++ b/target/linux/ramips/image/mt7621.mk -@@ -111,11 +111,20 @@ define Device/dsa-migration - endef - - define Device/actiontec_web7200 -+ $(Device/dsa-migration) - DEVICE_VENDOR := Actiontec - DEVICE_MODEL := EB7200 - DEVICE_PACKAGES += kmod-mt7603 kmod-mt7915e kmod-usb3 uboot-envtools kmod-i2c-core -+ LOADER_TYPE := bin -+ KERNEL_SIZE := 4096k -+ BLOCKSIZE := 128k -+ PAGESIZE := 2048 -+ UBINIZE_OPTS := -E 5 -+ KERNEL_INITRAMFS := kernel-bin | lzma | loader-kernel | gzip | fit-relocate gzip $$(KDIR)/image-$$(firstword $$(DEVICE_DTS)).dtb - KERNEL := kernel-bin | relocate-kernel | lzma | fit-relocate lzma $$(KDIR)/image-$$(firstword $$(DEVICE_DTS)).dtb -- IMAGE_SIZE := 15552k -+ IMAGES += factory.bin -+ IMAGE/sysupgrade.bin := sysupgrade-tar | append-metadata -+ IMAGE/factory.bin := append-kernel | pad-to $$(KERNEL_SIZE) | append-ubi - endef - TARGET_DEVICES += actiontec_web7200 - -@@ -1404,6 +1413,15 @@ define Device/ubnt_unifi-6-lite - endef - TARGET_DEVICES += ubnt_unifi-6-lite - -+define Device/ubnt_unifi-nanohd -+ $(Device/dsa-migration) -+ DEVICE_VENDOR := Ubiquiti -+ DEVICE_MODEL := UniFi nanoHD -+ DEVICE_PACKAGES += kmod-mt7603 kmod-mt7615e kmod-mt7615-firmware -+ IMAGE_SIZE := 15552k -+endef -+TARGET_DEVICES += ubnt_unifi-nanohd -+ - define Device/unielec_u7621-01-16m - $(Device/dsa-migration) - $(Device/uimage-lzma-loader) -diff --git a/target/linux/ramips/mt7621/base-files/lib/upgrade/platform.sh b/target/linux/ramips/mt7621/base-files/lib/upgrade/platform.sh -index 924f259e2563..033ec23425db 100755 ---- a/target/linux/ramips/mt7621/base-files/lib/upgrade/platform.sh -+++ b/target/linux/ramips/mt7621/base-files/lib/upgrade/platform.sh -@@ -58,6 +58,7 @@ platform_do_upgrade() { - esac - - case "$board" in -+ actiontec,web7200|\ - ampedwireless,ally-00x19k|\ - ampedwireless,ally-r1900k|\ - asus,rt-ac65p|\ -diff --git a/target/linux/ramips/mt7621/config-5.4 b/target/linux/ramips/mt7621/config-5.4 -index ae4a0679bd76..eada0ff6021c 100644 ---- a/target/linux/ramips/mt7621/config-5.4 -+++ b/target/linux/ramips/mt7621/config-5.4 -@@ -185,14 +185,6 @@ CONFIG_NET_MEDIATEK_SOC=y - CONFIG_NET_SWITCHDEV=y - CONFIG_NET_VENDOR_MEDIATEK=y - # CONFIG_NET_VENDOR_RALINK is not set --CONFIG_NMBM=y --# CONFIG_NMBM_LOG_LEVEL_DEBUG is not set --# CONFIG_NMBM_LOG_LEVEL_EMERG is not set --# CONFIG_NMBM_LOG_LEVEL_ERR is not set --CONFIG_NMBM_LOG_LEVEL_INFO=y --# CONFIG_NMBM_LOG_LEVEL_NONE is not set --# CONFIG_NMBM_LOG_LEVEL_WARN is not set --CONFIG_NMBM_MTD=y - CONFIG_NR_CPUS=4 - CONFIG_OF=y - CONFIG_OF_ADDRESS=y -diff --git a/target/linux/ramips/patches-5.4/499-mtd-add-nmbm-support.patch b/target/linux/ramips/patches-5.4/499-mtd-add-nmbm-support.patch -deleted file mode 100644 -index 5cbaae2b7bba..000000000000 ---- a/target/linux/ramips/patches-5.4/499-mtd-add-nmbm-support.patch -+++ /dev/null -@@ -1,21 +0,0 @@ ----- a/drivers/mtd/Kconfig --+++ b/drivers/mtd/Kconfig --@@ -228,6 +228,8 @@ source "drivers/mtd/ubi/Kconfig" -- -- source "drivers/mtd/hyperbus/Kconfig" -- --+source "drivers/mtd/nmbm/Kconfig" --+ -- source "drivers/mtd/composite/Kconfig" -- -- endif # MTD ----- a/drivers/mtd/Makefile --+++ b/drivers/mtd/Makefile --@@ -33,5 +33,7 @@ obj-$(CONFIG_MTD_SPI_NOR) += spi-nor/ -- obj-$(CONFIG_MTD_UBI) += ubi/ -- obj-$(CONFIG_MTD_HYPERBUS) += hyperbus/ -- --+obj-y += nmbm/ --+ -- # Composite drivers must be loaded last -- obj-y += composite/ --- -2.32.0 - diff --git a/patches/ramips/0007-ramips-add-mtk_bmt-support-includes-bbt-bmt-v2-nmbm.patch b/patches/ramips/0007-ramips-add-mtk_bmt-support-includes-bbt-bmt-v2-nmbm.patch new file mode 100644 index 000000000..45d6dc8c3 --- /dev/null +++ b/patches/ramips/0007-ramips-add-mtk_bmt-support-includes-bbt-bmt-v2-nmbm.patch @@ -0,0 +1,3796 @@ +From ed2d3983fdc3b7fe8b67d6fbf104258c23f0544c Mon Sep 17 00:00:00 2001 +From: Felix Fietkau +Date: Thu, 10 Mar 2022 18:19:37 +0100 +Subject: [PATCH 07/14] ramips: add mtk_bmt support (includes bbt, bmt v2, + nmbm) + +Signed-off-by: Felix Fietkau +--- + target/linux/generic/config-5.4 | 1 + + .../ramips/files/drivers/mtd/nand/mtk_bmt.c | 465 ++++ + .../ramips/files/drivers/mtd/nand/mtk_bmt.h | 131 + + .../files/drivers/mtd/nand/mtk_bmt_bbt.c | 203 ++ + .../files/drivers/mtd/nand/mtk_bmt_nmbm.c | 2348 +++++++++++++++++ + .../files/drivers/mtd/nand/mtk_bmt_v2.c | 513 ++++ + .../ramips/files/include/linux/mtd/mtk_bmt.h | 18 + + target/linux/ramips/mt7621/config-5.4 | 1 + + .../patches-5.4/430-mtk-bmt-support.patch | 23 + + 9 files changed, 3703 insertions(+) + create mode 100644 target/linux/ramips/files/drivers/mtd/nand/mtk_bmt.c + create mode 100644 target/linux/ramips/files/drivers/mtd/nand/mtk_bmt.h + create mode 100644 target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_bbt.c + create mode 100644 target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_nmbm.c + create mode 100644 target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_v2.c + create mode 100644 target/linux/ramips/files/include/linux/mtd/mtk_bmt.h + create mode 100644 target/linux/ramips/patches-5.4/430-mtk-bmt-support.patch + +diff --git a/target/linux/generic/config-5.4 b/target/linux/generic/config-5.4 +index a62b2cadcb..a821a1de5f 100644 +--- a/target/linux/generic/config-5.4 ++++ b/target/linux/generic/config-5.4 +@@ -3234,6 +3234,7 @@ CONFIG_MTD_NAND_IDS=y + # CONFIG_MTD_NAND_JZ4740 is not set + # CONFIG_MTD_NAND_MPC5121_NFC is not set + # CONFIG_MTD_NAND_MTK is not set ++# CONFIG_MTD_NAND_MTK_BMT is not set + # CONFIG_MTD_NAND_MXC is not set + # CONFIG_MTD_NAND_MXIC is not set + # CONFIG_MTD_NAND_NANDSIM is not set +diff --git a/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt.c b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt.c +new file mode 100644 +index 0000000000..f1df4a11f1 +--- /dev/null ++++ b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt.c +@@ -0,0 +1,465 @@ ++/* ++ * Copyright (c) 2017 MediaTek Inc. ++ * Author: Xiangsheng Hou ++ * Copyright (c) 2020-2022 Felix Fietkau ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include "mtk_bmt.h" ++ ++struct bmt_desc bmtd = {}; ++ ++/* -------- Nand operations wrapper -------- */ ++int bbt_nand_copy(u16 dest_blk, u16 src_blk, loff_t max_offset) ++{ ++ int pages = bmtd.blk_size >> bmtd.pg_shift; ++ loff_t src = (loff_t)src_blk << bmtd.blk_shift; ++ loff_t dest = (loff_t)dest_blk << bmtd.blk_shift; ++ loff_t offset = 0; ++ uint8_t oob[64]; ++ int i, ret; ++ ++ for (i = 0; i < pages; i++) { ++ struct mtd_oob_ops rd_ops = { ++ .mode = MTD_OPS_PLACE_OOB, ++ .oobbuf = oob, ++ .ooblen = min_t(int, bmtd.mtd->oobsize / pages, sizeof(oob)), ++ .datbuf = bmtd.data_buf, ++ .len = bmtd.pg_size, ++ }; ++ struct mtd_oob_ops wr_ops = { ++ .mode = MTD_OPS_PLACE_OOB, ++ .oobbuf = oob, ++ .datbuf = bmtd.data_buf, ++ .len = bmtd.pg_size, ++ }; ++ ++ if (offset >= max_offset) ++ break; ++ ++ ret = bmtd._read_oob(bmtd.mtd, src + offset, &rd_ops); ++ if (ret < 0 && !mtd_is_bitflip(ret)) ++ return ret; ++ ++ if (!rd_ops.retlen) ++ break; ++ ++ ret = bmtd._write_oob(bmtd.mtd, dest + offset, &wr_ops); ++ if (ret < 0) ++ return ret; ++ ++ wr_ops.ooblen = rd_ops.oobretlen; ++ offset += rd_ops.retlen; ++ } ++ ++ return 0; ++} ++ ++/* -------- Bad Blocks Management -------- */ ++bool mapping_block_in_range(int block, int *start, int *end) ++{ ++ const __be32 *cur = bmtd.remap_range; ++ u32 addr = block << bmtd.blk_shift; ++ int i; ++ ++ if (!cur || !bmtd.remap_range_len) { ++ *start = 0; ++ *end = bmtd.total_blks; ++ return true; ++ } ++ ++ for (i = 0; i < bmtd.remap_range_len; i++, cur += 2) { ++ if (addr < be32_to_cpu(cur[0]) || addr >= be32_to_cpu(cur[1])) ++ continue; ++ ++ *start = be32_to_cpu(cur[0]); ++ *end = be32_to_cpu(cur[1]); ++ return true; ++ } ++ ++ return false; ++} ++ ++static bool ++mtk_bmt_remap_block(u32 block, u32 mapped_block, int copy_len) ++{ ++ int start, end; ++ ++ if (!mapping_block_in_range(block, &start, &end)) ++ return false; ++ ++ return bmtd.ops->remap_block(block, mapped_block, copy_len); ++} ++ ++static int ++mtk_bmt_read(struct mtd_info *mtd, loff_t from, ++ struct mtd_oob_ops *ops) ++{ ++ struct mtd_oob_ops cur_ops = *ops; ++ int retry_count = 0; ++ loff_t cur_from; ++ int ret = 0; ++ int max_bitflips = 0; ++ ++ ops->retlen = 0; ++ ops->oobretlen = 0; ++ ++ while (ops->retlen < ops->len || ops->oobretlen < ops->ooblen) { ++ int cur_ret; ++ ++ u32 offset = from & (bmtd.blk_size - 1); ++ u32 block = from >> bmtd.blk_shift; ++ int cur_block; ++ ++ cur_block = bmtd.ops->get_mapping_block(block); ++ if (cur_block < 0) ++ return -EIO; ++ ++ cur_from = ((loff_t)cur_block << bmtd.blk_shift) + offset; ++ ++ cur_ops.oobretlen = 0; ++ cur_ops.retlen = 0; ++ cur_ops.len = min_t(u32, mtd->erasesize - offset, ++ ops->len - ops->retlen); ++ cur_ret = bmtd._read_oob(mtd, cur_from, &cur_ops); ++ if (cur_ret < 0) ++ ret = cur_ret; ++ else ++ max_bitflips = max_t(int, max_bitflips, cur_ret); ++ if (cur_ret < 0 && !mtd_is_bitflip(cur_ret)) { ++ if (mtk_bmt_remap_block(block, cur_block, mtd->erasesize) && ++ retry_count++ < 10) ++ continue; ++ ++ goto out; ++ } ++ ++ if (cur_ret >= mtd->bitflip_threshold) ++ mtk_bmt_remap_block(block, cur_block, mtd->erasesize); ++ ++ ops->retlen += cur_ops.retlen; ++ ops->oobretlen += cur_ops.oobretlen; ++ ++ cur_ops.ooboffs = 0; ++ cur_ops.datbuf += cur_ops.retlen; ++ cur_ops.oobbuf += cur_ops.oobretlen; ++ cur_ops.ooblen -= cur_ops.oobretlen; ++ ++ if (!cur_ops.len) ++ cur_ops.len = mtd->erasesize - offset; ++ ++ from += cur_ops.len; ++ retry_count = 0; ++ } ++ ++out: ++ if (ret < 0) ++ return ret; ++ ++ return max_bitflips; ++} ++ ++static int ++mtk_bmt_write(struct mtd_info *mtd, loff_t to, ++ struct mtd_oob_ops *ops) ++{ ++ struct mtd_oob_ops cur_ops = *ops; ++ int retry_count = 0; ++ loff_t cur_to; ++ int ret; ++ ++ ops->retlen = 0; ++ ops->oobretlen = 0; ++ ++ while (ops->retlen < ops->len || ops->oobretlen < ops->ooblen) { ++ u32 offset = to & (bmtd.blk_size - 1); ++ u32 block = to >> bmtd.blk_shift; ++ int cur_block; ++ ++ cur_block = bmtd.ops->get_mapping_block(block); ++ if (cur_block < 0) ++ return -EIO; ++ ++ cur_to = ((loff_t)cur_block << bmtd.blk_shift) + offset; ++ ++ cur_ops.oobretlen = 0; ++ cur_ops.retlen = 0; ++ cur_ops.len = min_t(u32, bmtd.blk_size - offset, ++ ops->len - ops->retlen); ++ ret = bmtd._write_oob(mtd, cur_to, &cur_ops); ++ if (ret < 0) { ++ if (mtk_bmt_remap_block(block, cur_block, offset) && ++ retry_count++ < 10) ++ continue; ++ ++ return ret; ++ } ++ ++ ops->retlen += cur_ops.retlen; ++ ops->oobretlen += cur_ops.oobretlen; ++ ++ cur_ops.ooboffs = 0; ++ cur_ops.datbuf += cur_ops.retlen; ++ cur_ops.oobbuf += cur_ops.oobretlen; ++ cur_ops.ooblen -= cur_ops.oobretlen; ++ ++ if (!cur_ops.len) ++ cur_ops.len = mtd->erasesize - offset; ++ ++ to += cur_ops.len; ++ retry_count = 0; ++ } ++ ++ return 0; ++} ++ ++static int ++mtk_bmt_mtd_erase(struct mtd_info *mtd, struct erase_info *instr) ++{ ++ struct erase_info mapped_instr = { ++ .len = bmtd.blk_size, ++ }; ++ int retry_count = 0; ++ u64 start_addr, end_addr; ++ int ret; ++ u16 orig_block; ++ int block; ++ ++ start_addr = instr->addr & (~mtd->erasesize_mask); ++ end_addr = instr->addr + instr->len; ++ ++ while (start_addr < end_addr) { ++ orig_block = start_addr >> bmtd.blk_shift; ++ block = bmtd.ops->get_mapping_block(orig_block); ++ if (block < 0) ++ return -EIO; ++ mapped_instr.addr = (loff_t)block << bmtd.blk_shift; ++ ret = bmtd._erase(mtd, &mapped_instr); ++ if (ret) { ++ if (mtk_bmt_remap_block(orig_block, block, 0) && ++ retry_count++ < 10) ++ continue; ++ instr->fail_addr = start_addr; ++ break; ++ } ++ start_addr += mtd->erasesize; ++ retry_count = 0; ++ } ++ ++ return ret; ++} ++static int ++mtk_bmt_block_isbad(struct mtd_info *mtd, loff_t ofs) ++{ ++ int retry_count = 0; ++ u16 orig_block = ofs >> bmtd.blk_shift; ++ u16 block; ++ int ret; ++ ++retry: ++ block = bmtd.ops->get_mapping_block(orig_block); ++ ret = bmtd._block_isbad(mtd, (loff_t)block << bmtd.blk_shift); ++ if (ret) { ++ if (mtk_bmt_remap_block(orig_block, block, bmtd.blk_size) && ++ retry_count++ < 10) ++ goto retry; ++ } ++ return ret; ++} ++ ++static int ++mtk_bmt_block_markbad(struct mtd_info *mtd, loff_t ofs) ++{ ++ u16 orig_block = ofs >> bmtd.blk_shift; ++ int block; ++ ++ block = bmtd.ops->get_mapping_block(orig_block); ++ if (block < 0) ++ return -EIO; ++ ++ mtk_bmt_remap_block(orig_block, block, bmtd.blk_size); ++ ++ return bmtd._block_markbad(mtd, (loff_t)block << bmtd.blk_shift); ++} ++ ++static void ++mtk_bmt_replace_ops(struct mtd_info *mtd) ++{ ++ bmtd._read_oob = mtd->_read_oob; ++ bmtd._write_oob = mtd->_write_oob; ++ bmtd._erase = mtd->_erase; ++ bmtd._block_isbad = mtd->_block_isbad; ++ bmtd._block_markbad = mtd->_block_markbad; ++ ++ mtd->_read_oob = mtk_bmt_read; ++ mtd->_write_oob = mtk_bmt_write; ++ mtd->_erase = mtk_bmt_mtd_erase; ++ mtd->_block_isbad = mtk_bmt_block_isbad; ++ mtd->_block_markbad = mtk_bmt_block_markbad; ++} ++ ++static int mtk_bmt_debug_repair(void *data, u64 val) ++{ ++ int block = val >> bmtd.blk_shift; ++ int prev_block, new_block; ++ ++ prev_block = bmtd.ops->get_mapping_block(block); ++ if (prev_block < 0) ++ return -EIO; ++ ++ bmtd.ops->unmap_block(block); ++ new_block = bmtd.ops->get_mapping_block(block); ++ if (new_block < 0) ++ return -EIO; ++ ++ if (prev_block == new_block) ++ return 0; ++ ++ bbt_nand_erase(new_block); ++ bbt_nand_copy(new_block, prev_block, bmtd.blk_size); ++ ++ return 0; ++} ++ ++static int mtk_bmt_debug_mark_good(void *data, u64 val) ++{ ++ bmtd.ops->unmap_block(val >> bmtd.blk_shift); ++ ++ return 0; ++} ++ ++static int mtk_bmt_debug_mark_bad(void *data, u64 val) ++{ ++ u32 block = val >> bmtd.blk_shift; ++ int cur_block; ++ ++ cur_block = bmtd.ops->get_mapping_block(block); ++ if (cur_block < 0) ++ return -EIO; ++ ++ mtk_bmt_remap_block(block, cur_block, bmtd.blk_size); ++ ++ return 0; ++} ++ ++static int mtk_bmt_debug(void *data, u64 val) ++{ ++ return bmtd.ops->debug(data, val); ++} ++ ++ ++DEFINE_DEBUGFS_ATTRIBUTE(fops_repair, NULL, mtk_bmt_debug_repair, "%llu\n"); ++DEFINE_DEBUGFS_ATTRIBUTE(fops_mark_good, NULL, mtk_bmt_debug_mark_good, "%llu\n"); ++DEFINE_DEBUGFS_ATTRIBUTE(fops_mark_bad, NULL, mtk_bmt_debug_mark_bad, "%llu\n"); ++DEFINE_DEBUGFS_ATTRIBUTE(fops_debug, NULL, mtk_bmt_debug, "%llu\n"); ++ ++static void ++mtk_bmt_add_debugfs(void) ++{ ++ struct dentry *dir; ++ ++ dir = bmtd.debugfs_dir = debugfs_create_dir("mtk-bmt", NULL); ++ if (!dir) ++ return; ++ ++ debugfs_create_file_unsafe("repair", S_IWUSR, dir, NULL, &fops_repair); ++ debugfs_create_file_unsafe("mark_good", S_IWUSR, dir, NULL, &fops_mark_good); ++ debugfs_create_file_unsafe("mark_bad", S_IWUSR, dir, NULL, &fops_mark_bad); ++ debugfs_create_file_unsafe("debug", S_IWUSR, dir, NULL, &fops_debug); ++} ++ ++void mtk_bmt_detach(struct mtd_info *mtd) ++{ ++ if (bmtd.mtd != mtd) ++ return; ++ ++ if (bmtd.debugfs_dir) ++ debugfs_remove_recursive(bmtd.debugfs_dir); ++ bmtd.debugfs_dir = NULL; ++ ++ kfree(bmtd.bbt_buf); ++ kfree(bmtd.data_buf); ++ ++ mtd->_read_oob = bmtd._read_oob; ++ mtd->_write_oob = bmtd._write_oob; ++ mtd->_erase = bmtd._erase; ++ mtd->_block_isbad = bmtd._block_isbad; ++ mtd->_block_markbad = bmtd._block_markbad; ++ mtd->size = bmtd.total_blks << bmtd.blk_shift; ++ ++ memset(&bmtd, 0, sizeof(bmtd)); ++} ++ ++ ++int mtk_bmt_attach(struct mtd_info *mtd) ++{ ++ struct device_node *np; ++ int ret = 0; ++ ++ if (bmtd.mtd) ++ return -ENOSPC; ++ ++ np = mtd_get_of_node(mtd); ++ if (!np) ++ return 0; ++ ++ if (of_property_read_bool(np, "mediatek,bmt-v2")) ++ bmtd.ops = &mtk_bmt_v2_ops; ++ else if (of_property_read_bool(np, "mediatek,nmbm")) ++ bmtd.ops = &mtk_bmt_nmbm_ops; ++ else if (of_property_read_bool(np, "mediatek,bbt")) ++ bmtd.ops = &mtk_bmt_bbt_ops; ++ else ++ return 0; ++ ++ bmtd.remap_range = of_get_property(np, "mediatek,bmt-remap-range", ++ &bmtd.remap_range_len); ++ bmtd.remap_range_len /= 8; ++ ++ bmtd.mtd = mtd; ++ mtk_bmt_replace_ops(mtd); ++ ++ bmtd.blk_size = mtd->erasesize; ++ bmtd.blk_shift = ffs(bmtd.blk_size) - 1; ++ bmtd.pg_size = mtd->writesize; ++ bmtd.pg_shift = ffs(bmtd.pg_size) - 1; ++ bmtd.total_blks = mtd->size >> bmtd.blk_shift; ++ ++ bmtd.data_buf = kzalloc(bmtd.pg_size + bmtd.mtd->oobsize, GFP_KERNEL); ++ if (!bmtd.data_buf) { ++ pr_info("nand: FATAL ERR: allocate buffer failed!\n"); ++ ret = -1; ++ goto error; ++ } ++ ++ memset(bmtd.data_buf, 0xff, bmtd.pg_size + bmtd.mtd->oobsize); ++ ++ ret = bmtd.ops->init(np); ++ if (ret) ++ goto error; ++ ++ mtk_bmt_add_debugfs(); ++ return 0; ++ ++error: ++ mtk_bmt_detach(mtd); ++ return ret; ++} ++ ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Xiangsheng Hou , Felix Fietkau "); ++MODULE_DESCRIPTION("Bad Block mapping management v2 for MediaTek NAND Flash Driver"); ++ +diff --git a/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt.h b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt.h +new file mode 100644 +index 0000000000..dff1f28c81 +--- /dev/null ++++ b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt.h +@@ -0,0 +1,131 @@ ++#ifndef __MTK_BMT_PRIV_H ++#define __MTK_BMT_PRIV_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MAIN_SIGNATURE_OFFSET 0 ++#define OOB_SIGNATURE_OFFSET 1 ++ ++#define BBT_LOG(fmt, ...) pr_debug("[BBT][%s|%d] "fmt"\n", __func__, __LINE__, ##__VA_ARGS__) ++ ++struct mtk_bmt_ops { ++ char *sig; ++ unsigned int sig_len; ++ int (*init)(struct device_node *np); ++ bool (*remap_block)(u16 block, u16 mapped_block, int copy_len); ++ void (*unmap_block)(u16 block); ++ int (*get_mapping_block)(int block); ++ int (*debug)(void *data, u64 val); ++}; ++ ++struct bbbt; ++struct nmbm_instance; ++ ++struct bmt_desc { ++ struct mtd_info *mtd; ++ unsigned char *bbt_buf; ++ unsigned char *data_buf; ++ ++ int (*_read_oob) (struct mtd_info *mtd, loff_t from, ++ struct mtd_oob_ops *ops); ++ int (*_write_oob) (struct mtd_info *mtd, loff_t to, ++ struct mtd_oob_ops *ops); ++ int (*_erase) (struct mtd_info *mtd, struct erase_info *instr); ++ int (*_block_isbad) (struct mtd_info *mtd, loff_t ofs); ++ int (*_block_markbad) (struct mtd_info *mtd, loff_t ofs); ++ ++ const struct mtk_bmt_ops *ops; ++ ++ union { ++ struct bbbt *bbt; ++ struct nmbm_instance *ni; ++ }; ++ ++ struct dentry *debugfs_dir; ++ ++ u32 table_size; ++ u32 pg_size; ++ u32 blk_size; ++ u16 pg_shift; ++ u16 blk_shift; ++ /* bbt logical address */ ++ u16 pool_lba; ++ /* bbt physical address */ ++ u16 pool_pba; ++ /* Maximum count of bad blocks that the vendor guaranteed */ ++ u16 bb_max; ++ /* Total blocks of the Nand Chip */ ++ u16 total_blks; ++ /* The block(n) BMT is located at (bmt_tbl[n]) */ ++ u16 bmt_blk_idx; ++ /* How many pages needs to store 'struct bbbt' */ ++ u32 bmt_pgs; ++ ++ const __be32 *remap_range; ++ int remap_range_len; ++ ++ /* to compensate for driver level remapping */ ++ u8 oob_offset; ++}; ++ ++extern struct bmt_desc bmtd; ++extern const struct mtk_bmt_ops mtk_bmt_v2_ops; ++extern const struct mtk_bmt_ops mtk_bmt_bbt_ops; ++extern const struct mtk_bmt_ops mtk_bmt_nmbm_ops; ++ ++static inline u32 blk_pg(u16 block) ++{ ++ return (u32)(block << (bmtd.blk_shift - bmtd.pg_shift)); ++} ++ ++static inline int ++bbt_nand_read(u32 page, unsigned char *dat, int dat_len, ++ unsigned char *fdm, int fdm_len) ++{ ++ struct mtd_oob_ops ops = { ++ .mode = MTD_OPS_PLACE_OOB, ++ .ooboffs = bmtd.oob_offset, ++ .oobbuf = fdm, ++ .ooblen = fdm_len, ++ .datbuf = dat, ++ .len = dat_len, ++ }; ++ ++ return bmtd._read_oob(bmtd.mtd, page << bmtd.pg_shift, &ops); ++} ++ ++static inline int bbt_nand_erase(u16 block) ++{ ++ struct mtd_info *mtd = bmtd.mtd; ++ struct erase_info instr = { ++ .addr = (loff_t)block << bmtd.blk_shift, ++ .len = bmtd.blk_size, ++ }; ++ ++ return bmtd._erase(mtd, &instr); ++} ++ ++static inline int write_bmt(u16 block, unsigned char *dat) ++{ ++ struct mtd_oob_ops ops = { ++ .mode = MTD_OPS_PLACE_OOB, ++ .ooboffs = OOB_SIGNATURE_OFFSET + bmtd.oob_offset, ++ .oobbuf = bmtd.ops->sig, ++ .ooblen = bmtd.ops->sig_len, ++ .datbuf = dat, ++ .len = bmtd.bmt_pgs << bmtd.pg_shift, ++ }; ++ loff_t addr = (loff_t)block << bmtd.blk_shift; ++ ++ return bmtd._write_oob(bmtd.mtd, addr, &ops); ++} ++ ++int bbt_nand_copy(u16 dest_blk, u16 src_blk, loff_t max_offset); ++bool mapping_block_in_range(int block, int *start, int *end); ++ ++#endif +diff --git a/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_bbt.c b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_bbt.c +new file mode 100644 +index 0000000000..519e1ed70c +--- /dev/null ++++ b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_bbt.c +@@ -0,0 +1,203 @@ ++/* ++ * Copyright (c) 2017 MediaTek Inc. ++ * Author: Xiangsheng Hou ++ * Copyright (c) 2020-2022 Felix Fietkau ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include "mtk_bmt.h" ++ ++static bool ++bbt_block_is_bad(u16 block) ++{ ++ u8 cur = bmtd.bbt_buf[block / 4]; ++ ++ return cur & (3 << ((block % 4) * 2)); ++} ++ ++static void ++bbt_set_block_state(u16 block, bool bad) ++{ ++ u8 mask = (3 << ((block % 4) * 2)); ++ ++ if (bad) ++ bmtd.bbt_buf[block / 4] |= mask; ++ else ++ bmtd.bbt_buf[block / 4] &= ~mask; ++ ++ bbt_nand_erase(bmtd.bmt_blk_idx); ++ write_bmt(bmtd.bmt_blk_idx, bmtd.bbt_buf); ++} ++ ++static int ++get_mapping_block_index_bbt(int block) ++{ ++ int start, end, ofs; ++ int bad_blocks = 0; ++ int i; ++ ++ if (!mapping_block_in_range(block, &start, &end)) ++ return block; ++ ++ start >>= bmtd.blk_shift; ++ end >>= bmtd.blk_shift; ++ /* skip bad blocks within the mapping range */ ++ ofs = block - start; ++ for (i = start; i < end; i++) { ++ if (bbt_block_is_bad(i)) ++ bad_blocks++; ++ else if (ofs) ++ ofs--; ++ else ++ break; ++ } ++ ++ if (i < end) ++ return i; ++ ++ /* when overflowing, remap remaining blocks to bad ones */ ++ for (i = end - 1; bad_blocks > 0; i--) { ++ if (!bbt_block_is_bad(i)) ++ continue; ++ ++ bad_blocks--; ++ if (bad_blocks <= ofs) ++ return i; ++ } ++ ++ return block; ++} ++ ++static bool remap_block_bbt(u16 block, u16 mapped_blk, int copy_len) ++{ ++ int start, end; ++ u16 new_blk; ++ ++ if (!mapping_block_in_range(block, &start, &end)) ++ return false; ++ ++ bbt_set_block_state(mapped_blk, true); ++ ++ new_blk = get_mapping_block_index_bbt(block); ++ bbt_nand_erase(new_blk); ++ if (copy_len > 0) ++ bbt_nand_copy(new_blk, mapped_blk, copy_len); ++ ++ return true; ++} ++ ++static void ++unmap_block_bbt(u16 block) ++{ ++ bbt_set_block_state(block, false); ++} ++ ++static int ++mtk_bmt_read_bbt(void) ++{ ++ u8 oob_buf[8]; ++ int i; ++ ++ for (i = bmtd.total_blks - 1; i >= bmtd.total_blks - 5; i--) { ++ u32 page = i << (bmtd.blk_shift - bmtd.pg_shift); ++ ++ if (bbt_nand_read(page, bmtd.bbt_buf, bmtd.pg_size, ++ oob_buf, sizeof(oob_buf))) { ++ pr_info("read_bbt: could not read block %d\n", i); ++ continue; ++ } ++ ++ if (oob_buf[0] != 0xff) { ++ pr_info("read_bbt: bad block at %d\n", i); ++ continue; ++ } ++ ++ if (memcmp(&oob_buf[1], "mtknand", 7) != 0) { ++ pr_info("read_bbt: signature mismatch in block %d\n", i); ++ print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, oob_buf, 8, 1); ++ continue; ++ } ++ ++ pr_info("read_bbt: found bbt at block %d\n", i); ++ bmtd.bmt_blk_idx = i; ++ return 0; ++ } ++ ++ return -EIO; ++} ++ ++ ++static int ++mtk_bmt_init_bbt(struct device_node *np) ++{ ++ int buf_size = round_up(bmtd.total_blks >> 2, bmtd.blk_size); ++ int ret; ++ ++ bmtd.bbt_buf = kmalloc(buf_size, GFP_KERNEL); ++ if (!bmtd.bbt_buf) ++ return -ENOMEM; ++ ++ memset(bmtd.bbt_buf, 0xff, buf_size); ++ bmtd.mtd->size -= 4 * bmtd.mtd->erasesize; ++ ++ ret = mtk_bmt_read_bbt(); ++ if (ret) ++ return ret; ++ ++ bmtd.bmt_pgs = buf_size / bmtd.pg_size; ++ ++ return 0; ++} ++ ++static int mtk_bmt_debug_bbt(void *data, u64 val) ++{ ++ char buf[5]; ++ int i, k; ++ ++ switch (val) { ++ case 0: ++ for (i = 0; i < bmtd.total_blks; i += 4) { ++ u8 cur = bmtd.bbt_buf[i / 4]; ++ ++ for (k = 0; k < 4; k++, cur >>= 2) ++ buf[k] = (cur & 3) ? 'B' : '.'; ++ ++ buf[4] = 0; ++ printk("[%06x] %s\n", i * bmtd.blk_size, buf); ++ } ++ break; ++ case 100: ++#if 0 ++ for (i = bmtd.bmt_blk_idx; i < bmtd.total_blks - 1; i++) ++ bbt_nand_erase(bmtd.bmt_blk_idx); ++#endif ++ ++ bmtd.bmt_blk_idx = bmtd.total_blks - 1; ++ bbt_nand_erase(bmtd.bmt_blk_idx); ++ write_bmt(bmtd.bmt_blk_idx, bmtd.bbt_buf); ++ break; ++ default: ++ break; ++ } ++ return 0; ++} ++ ++const struct mtk_bmt_ops mtk_bmt_bbt_ops = { ++ .sig = "mtknand", ++ .sig_len = 7, ++ .init = mtk_bmt_init_bbt, ++ .remap_block = remap_block_bbt, ++ .unmap_block = unmap_block_bbt, ++ .get_mapping_block = get_mapping_block_index_bbt, ++ .debug = mtk_bmt_debug_bbt, ++}; +diff --git a/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_nmbm.c b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_nmbm.c +new file mode 100644 +index 0000000000..a896e49ec0 +--- /dev/null ++++ b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_nmbm.c +@@ -0,0 +1,2348 @@ ++#include ++#include ++#include "mtk_bmt.h" ++ ++#define nlog_err(ni, ...) printk(KERN_ERR __VA_ARGS__) ++#define nlog_info(ni, ...) printk(KERN_INFO __VA_ARGS__) ++#define nlog_debug(ni, ...) printk(KERN_INFO __VA_ARGS__) ++#define nlog_warn(ni, ...) printk(KERN_WARNING __VA_ARGS__) ++ ++#define NMBM_MAGIC_SIGNATURE 0x304d4d4e /* NMM0 */ ++#define NMBM_MAGIC_INFO_TABLE 0x314d4d4e /* NMM1 */ ++ ++#define NMBM_VERSION_MAJOR_S 0 ++#define NMBM_VERSION_MAJOR_M 0xffff ++#define NMBM_VERSION_MINOR_S 16 ++#define NMBM_VERSION_MINOR_M 0xffff ++#define NMBM_VERSION_MAKE(major, minor) (((major) & NMBM_VERSION_MAJOR_M) | \ ++ (((minor) & NMBM_VERSION_MINOR_M) << \ ++ NMBM_VERSION_MINOR_S)) ++#define NMBM_VERSION_MAJOR_GET(ver) (((ver) >> NMBM_VERSION_MAJOR_S) & \ ++ NMBM_VERSION_MAJOR_M) ++#define NMBM_VERSION_MINOR_GET(ver) (((ver) >> NMBM_VERSION_MINOR_S) & \ ++ NMBM_VERSION_MINOR_M) ++ ++#define NMBM_BITMAP_UNIT_SIZE (sizeof(u32)) ++#define NMBM_BITMAP_BITS_PER_BLOCK 2 ++#define NMBM_BITMAP_BITS_PER_UNIT (8 * sizeof(u32)) ++#define NMBM_BITMAP_BLOCKS_PER_UNIT (NMBM_BITMAP_BITS_PER_UNIT / \ ++ NMBM_BITMAP_BITS_PER_BLOCK) ++ ++#define NMBM_SPARE_BLOCK_MULTI 1 ++#define NMBM_SPARE_BLOCK_DIV 2 ++#define NMBM_SPARE_BLOCK_MIN 2 ++ ++#define NMBM_MGMT_DIV 16 ++#define NMBM_MGMT_BLOCKS_MIN 32 ++ ++#define NMBM_TRY_COUNT 3 ++ ++#define BLOCK_ST_BAD 0 ++#define BLOCK_ST_NEED_REMAP 2 ++#define BLOCK_ST_GOOD 3 ++#define BLOCK_ST_MASK 3 ++ ++#define NMBM_VER_MAJOR 1 ++#define NMBM_VER_MINOR 0 ++#define NMBM_VER NMBM_VERSION_MAKE(NMBM_VER_MAJOR, \ ++ NMBM_VER_MINOR) ++ ++struct nmbm_header { ++ u32 magic; ++ u32 version; ++ u32 size; ++ u32 checksum; ++}; ++ ++struct nmbm_signature { ++ struct nmbm_header header; ++ uint64_t nand_size; ++ u32 block_size; ++ u32 page_size; ++ u32 spare_size; ++ u32 mgmt_start_pb; ++ u8 max_try_count; ++ u8 padding[3]; ++}; ++ ++struct nmbm_info_table_header { ++ struct nmbm_header header; ++ u32 write_count; ++ u32 state_table_off; ++ u32 mapping_table_off; ++ u32 padding; ++}; ++ ++struct nmbm_instance { ++ u32 rawpage_size; ++ u32 rawblock_size; ++ u32 rawchip_size; ++ ++ struct nmbm_signature signature; ++ ++ u8 *info_table_cache; ++ u32 info_table_size; ++ u32 info_table_spare_blocks; ++ struct nmbm_info_table_header info_table; ++ ++ u32 *block_state; ++ u32 block_state_changed; ++ u32 state_table_size; ++ ++ int32_t *block_mapping; ++ u32 block_mapping_changed; ++ u32 mapping_table_size; ++ ++ u8 *page_cache; ++ ++ int protected; ++ ++ u32 block_count; ++ u32 data_block_count; ++ ++ u32 mgmt_start_ba; ++ u32 main_table_ba; ++ u32 backup_table_ba; ++ u32 mapping_blocks_ba; ++ u32 mapping_blocks_top_ba; ++ u32 signature_ba; ++ ++ u32 max_ratio; ++ u32 max_reserved_blocks; ++ bool empty_page_ecc_ok; ++ bool force_create; ++}; ++ ++static inline u32 nmbm_crc32(u32 crcval, const void *buf, size_t size) ++{ ++ unsigned int chksz; ++ const unsigned char *p = buf; ++ ++ while (size) { ++ if (size > UINT_MAX) ++ chksz = UINT_MAX; ++ else ++ chksz = (uint)size; ++ ++ crcval = crc32_le(crcval, p, chksz); ++ size -= chksz; ++ p += chksz; ++ } ++ ++ return crcval; ++} ++/* ++ * nlog_table_creation - Print log of table creation event ++ * @ni: NMBM instance structure ++ * @main_table: whether the table is main info table ++ * @start_ba: start block address of the table ++ * @end_ba: block address after the end of the table ++ */ ++static void nlog_table_creation(struct nmbm_instance *ni, bool main_table, ++ uint32_t start_ba, uint32_t end_ba) ++{ ++ if (start_ba == end_ba - 1) ++ nlog_info(ni, "%s info table has been written to block %u\n", ++ main_table ? "Main" : "Backup", start_ba); ++ else ++ nlog_info(ni, "%s info table has been written to block %u-%u\n", ++ main_table ? "Main" : "Backup", start_ba, end_ba - 1); ++} ++ ++/* ++ * nlog_table_update - Print log of table update event ++ * @ni: NMBM instance structure ++ * @main_table: whether the table is main info table ++ * @start_ba: start block address of the table ++ * @end_ba: block address after the end of the table ++ */ ++static void nlog_table_update(struct nmbm_instance *ni, bool main_table, ++ uint32_t start_ba, uint32_t end_ba) ++{ ++ if (start_ba == end_ba - 1) ++ nlog_debug(ni, "%s info table has been updated in block %u\n", ++ main_table ? "Main" : "Backup", start_ba); ++ else ++ nlog_debug(ni, "%s info table has been updated in block %u-%u\n", ++ main_table ? "Main" : "Backup", start_ba, end_ba - 1); ++} ++ ++/* ++ * nlog_table_found - Print log of table found event ++ * @ni: NMBM instance structure ++ * @first_table: whether the table is first found info table ++ * @write_count: write count of the info table ++ * @start_ba: start block address of the table ++ * @end_ba: block address after the end of the table ++ */ ++static void nlog_table_found(struct nmbm_instance *ni, bool first_table, ++ uint32_t write_count, uint32_t start_ba, ++ uint32_t end_ba) ++{ ++ if (start_ba == end_ba - 1) ++ nlog_info(ni, "%s info table with writecount %u found in block %u\n", ++ first_table ? "First" : "Second", write_count, ++ start_ba); ++ else ++ nlog_info(ni, "%s info table with writecount %u found in block %u-%u\n", ++ first_table ? "First" : "Second", write_count, ++ start_ba, end_ba - 1); ++} ++ ++/*****************************************************************************/ ++/* Address conversion functions */ ++/*****************************************************************************/ ++ ++/* ++ * ba2addr - Convert a block address to linear address ++ * @ni: NMBM instance structure ++ * @ba: Block address ++ */ ++static uint64_t ba2addr(struct nmbm_instance *ni, uint32_t ba) ++{ ++ return (uint64_t)ba << bmtd.blk_shift; ++} ++/* ++ * size2blk - Get minimum required blocks for storing specific size of data ++ * @ni: NMBM instance structure ++ * @size: size for storing ++ */ ++static uint32_t size2blk(struct nmbm_instance *ni, uint64_t size) ++{ ++ return (size + bmtd.blk_size - 1) >> bmtd.blk_shift; ++} ++ ++/*****************************************************************************/ ++/* High level NAND chip APIs */ ++/*****************************************************************************/ ++ ++/* ++ * nmbm_read_phys_page - Read page with retry ++ * @ni: NMBM instance structure ++ * @addr: linear address where the data will be read from ++ * @data: the main data to be read ++ * @oob: the oob data to be read ++ * ++ * Read a page for at most NMBM_TRY_COUNT times. ++ * ++ * Return 0 for success, positive value for corrected bitflip count, ++ * -EBADMSG for ecc error, other negative values for other errors ++ */ ++static int nmbm_read_phys_page(struct nmbm_instance *ni, uint64_t addr, ++ void *data, void *oob) ++{ ++ int tries, ret; ++ ++ for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { ++ struct mtd_oob_ops ops = { ++ .mode = MTD_OPS_PLACE_OOB, ++ .oobbuf = oob, ++ .datbuf = data, ++ }; ++ ++ if (data) ++ ops.len = bmtd.pg_size; ++ if (oob) ++ ops.ooblen = mtd_oobavail(bmtd.mtd, &ops); ++ ++ ret = bmtd._read_oob(bmtd.mtd, addr, &ops); ++ if (ret == -EUCLEAN) ++ return min_t(u32, bmtd.mtd->bitflip_threshold + 1, ++ bmtd.mtd->ecc_strength); ++ if (ret >= 0) ++ return 0; ++ } ++ ++ if (ret != -EBADMSG) ++ nlog_err(ni, "Page read failed at address 0x%08llx\n", addr); ++ ++ return ret; ++} ++ ++/* ++ * nmbm_write_phys_page - Write page with retry ++ * @ni: NMBM instance structure ++ * @addr: linear address where the data will be written to ++ * @data: the main data to be written ++ * @oob: the oob data to be written ++ * ++ * Write a page for at most NMBM_TRY_COUNT times. ++ */ ++static bool nmbm_write_phys_page(struct nmbm_instance *ni, uint64_t addr, ++ const void *data, const void *oob) ++{ ++ int tries, ret; ++ ++ for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { ++ struct mtd_oob_ops ops = { ++ .mode = MTD_OPS_PLACE_OOB, ++ .oobbuf = (void *)oob, ++ .datbuf = (void *)data, ++ }; ++ ++ if (data) ++ ops.len = bmtd.pg_size; ++ if (oob) ++ ops.ooblen = mtd_oobavail(bmtd.mtd, &ops); ++ ++ ret = bmtd._write_oob(bmtd.mtd, addr, &ops); ++ if (!ret) ++ return true; ++ } ++ ++ nlog_err(ni, "Page write failed at address 0x%08llx\n", addr); ++ ++ return false; ++} ++ ++/* ++ * nmbm_erase_phys_block - Erase a block with retry ++ * @ni: NMBM instance structure ++ * @addr: Linear address ++ * ++ * Erase a block for at most NMBM_TRY_COUNT times. ++ */ ++static bool nmbm_erase_phys_block(struct nmbm_instance *ni, uint64_t addr) ++{ ++ int tries, ret; ++ ++ for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { ++ struct erase_info ei = { ++ .addr = addr, ++ .len = bmtd.mtd->erasesize, ++ }; ++ ++ ret = bmtd._erase(bmtd.mtd, &ei); ++ if (!ret) ++ return true; ++ } ++ ++ nlog_err(ni, "Block erasure failed at address 0x%08llx\n", addr); ++ ++ return false; ++} ++ ++/* ++ * nmbm_check_bad_phys_block - Check whether a block is marked bad in OOB ++ * @ni: NMBM instance structure ++ * @ba: block address ++ */ ++static bool nmbm_check_bad_phys_block(struct nmbm_instance *ni, uint32_t ba) ++{ ++ uint64_t addr = ba2addr(ni, ba); ++ ++ return bmtd._block_isbad(bmtd.mtd, addr); ++} ++ ++/* ++ * nmbm_mark_phys_bad_block - Mark a block bad ++ * @ni: NMBM instance structure ++ * @addr: Linear address ++ */ ++static int nmbm_mark_phys_bad_block(struct nmbm_instance *ni, uint32_t ba) ++{ ++ uint64_t addr = ba2addr(ni, ba); ++ ++ nlog_info(ni, "Block %u [0x%08llx] will be marked bad\n", ba, addr); ++ ++ return bmtd._block_markbad(bmtd.mtd, addr); ++} ++ ++/*****************************************************************************/ ++/* NMBM related functions */ ++/*****************************************************************************/ ++ ++/* ++ * nmbm_check_header - Check whether a NMBM structure is valid ++ * @data: pointer to a NMBM structure with a NMBM header at beginning ++ * @size: Size of the buffer pointed by @header ++ * ++ * The size of the NMBM structure may be larger than NMBM header, ++ * e.g. block mapping table and block state table. ++ */ ++static bool nmbm_check_header(const void *data, uint32_t size) ++{ ++ const struct nmbm_header *header = data; ++ struct nmbm_header nhdr; ++ uint32_t new_checksum; ++ ++ /* ++ * Make sure expected structure size is equal or smaller than ++ * buffer size. ++ */ ++ if (header->size > size) ++ return false; ++ ++ memcpy(&nhdr, data, sizeof(nhdr)); ++ ++ nhdr.checksum = 0; ++ new_checksum = nmbm_crc32(0, &nhdr, sizeof(nhdr)); ++ if (header->size > sizeof(nhdr)) ++ new_checksum = nmbm_crc32(new_checksum, ++ (const uint8_t *)data + sizeof(nhdr), ++ header->size - sizeof(nhdr)); ++ ++ if (header->checksum != new_checksum) ++ return false; ++ ++ return true; ++} ++ ++/* ++ * nmbm_update_checksum - Update checksum of a NMBM structure ++ * @header: pointer to a NMBM structure with a NMBM header at beginning ++ * ++ * The size of the NMBM structure must be specified by @header->size ++ */ ++static void nmbm_update_checksum(struct nmbm_header *header) ++{ ++ header->checksum = 0; ++ header->checksum = nmbm_crc32(0, header, header->size); ++} ++ ++/* ++ * nmbm_get_spare_block_count - Calculate number of blocks should be reserved ++ * @block_count: number of blocks of data ++ * ++ * Calculate number of blocks should be reserved for data ++ */ ++static uint32_t nmbm_get_spare_block_count(uint32_t block_count) ++{ ++ uint32_t val; ++ ++ val = (block_count + NMBM_SPARE_BLOCK_DIV / 2) / NMBM_SPARE_BLOCK_DIV; ++ val *= NMBM_SPARE_BLOCK_MULTI; ++ ++ if (val < NMBM_SPARE_BLOCK_MIN) ++ val = NMBM_SPARE_BLOCK_MIN; ++ ++ return val; ++} ++ ++/* ++ * nmbm_get_block_state_raw - Get state of a block from raw block state table ++ * @block_state: pointer to raw block state table (bitmap) ++ * @ba: block address ++ */ ++static uint32_t nmbm_get_block_state_raw(u32 *block_state, ++ uint32_t ba) ++{ ++ uint32_t unit, shift; ++ ++ unit = ba / NMBM_BITMAP_BLOCKS_PER_UNIT; ++ shift = (ba % NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_BITS_PER_BLOCK; ++ ++ return (block_state[unit] >> shift) & BLOCK_ST_MASK; ++} ++ ++/* ++ * nmbm_get_block_state - Get state of a block from block state table ++ * @ni: NMBM instance structure ++ * @ba: block address ++ */ ++static uint32_t nmbm_get_block_state(struct nmbm_instance *ni, uint32_t ba) ++{ ++ return nmbm_get_block_state_raw(ni->block_state, ba); ++} ++ ++/* ++ * nmbm_set_block_state - Set state of a block to block state table ++ * @ni: NMBM instance structure ++ * @ba: block address ++ * @state: block state ++ * ++ * Set state of a block. If the block state changed, ni->block_state_changed ++ * will be increased. ++ */ ++static bool nmbm_set_block_state(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t state) ++{ ++ uint32_t unit, shift, orig; ++ u32 uv; ++ ++ unit = ba / NMBM_BITMAP_BLOCKS_PER_UNIT; ++ shift = (ba % NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_BITS_PER_BLOCK; ++ ++ orig = (ni->block_state[unit] >> shift) & BLOCK_ST_MASK; ++ state &= BLOCK_ST_MASK; ++ ++ uv = ni->block_state[unit] & (~(BLOCK_ST_MASK << shift)); ++ uv |= state << shift; ++ ni->block_state[unit] = uv; ++ ++ if (orig != state) { ++ ni->block_state_changed++; ++ return true; ++ } ++ ++ return false; ++} ++ ++/* ++ * nmbm_block_walk_asc - Skip specified number of good blocks, ascending addr. ++ * @ni: NMBM instance structure ++ * @ba: start physical block address ++ * @nba: return physical block address after walk ++ * @count: number of good blocks to be skipped ++ * @limit: highest block address allowed for walking ++ * ++ * Start from @ba, skipping any bad blocks, counting @count good blocks, and ++ * return the next good block address. ++ * ++ * If no enough good blocks counted while @limit reached, false will be returned. ++ * ++ * If @count == 0, nearest good block address will be returned. ++ * @limit is not counted in walking. ++ */ ++static bool nmbm_block_walk_asc(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t *nba, uint32_t count, ++ uint32_t limit) ++{ ++ int32_t nblock = count; ++ ++ if (limit >= ni->block_count) ++ limit = ni->block_count - 1; ++ ++ while (ba < limit) { ++ if (nmbm_get_block_state(ni, ba) == BLOCK_ST_GOOD) ++ nblock--; ++ ++ if (nblock < 0) { ++ *nba = ba; ++ return true; ++ } ++ ++ ba++; ++ } ++ ++ return false; ++} ++ ++/* ++ * nmbm_block_walk_desc - Skip specified number of good blocks, descending addr ++ * @ni: NMBM instance structure ++ * @ba: start physical block address ++ * @nba: return physical block address after walk ++ * @count: number of good blocks to be skipped ++ * @limit: lowest block address allowed for walking ++ * ++ * Start from @ba, skipping any bad blocks, counting @count good blocks, and ++ * return the next good block address. ++ * ++ * If no enough good blocks counted while @limit reached, false will be returned. ++ * ++ * If @count == 0, nearest good block address will be returned. ++ * @limit is not counted in walking. ++ */ ++static bool nmbm_block_walk_desc(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t *nba, uint32_t count, uint32_t limit) ++{ ++ int32_t nblock = count; ++ ++ if (limit >= ni->block_count) ++ limit = ni->block_count - 1; ++ ++ while (ba > limit) { ++ if (nmbm_get_block_state(ni, ba) == BLOCK_ST_GOOD) ++ nblock--; ++ ++ if (nblock < 0) { ++ *nba = ba; ++ return true; ++ } ++ ++ ba--; ++ } ++ ++ return false; ++} ++ ++/* ++ * nmbm_block_walk - Skip specified number of good blocks from curr. block addr ++ * @ni: NMBM instance structure ++ * @ascending: whether to walk ascending ++ * @ba: start physical block address ++ * @nba: return physical block address after walk ++ * @count: number of good blocks to be skipped ++ * @limit: highest/lowest block address allowed for walking ++ * ++ * Start from @ba, skipping any bad blocks, counting @count good blocks, and ++ * return the next good block address. ++ * ++ * If no enough good blocks counted while @limit reached, false will be returned. ++ * ++ * If @count == 0, nearest good block address will be returned. ++ * @limit can be set to negative if no limit required. ++ * @limit is not counted in walking. ++ */ ++static bool nmbm_block_walk(struct nmbm_instance *ni, bool ascending, ++ uint32_t ba, uint32_t *nba, int32_t count, ++ int32_t limit) ++{ ++ if (ascending) ++ return nmbm_block_walk_asc(ni, ba, nba, count, limit); ++ ++ return nmbm_block_walk_desc(ni, ba, nba, count, limit); ++} ++ ++/* ++ * nmbm_scan_badblocks - Scan and record all bad blocks ++ * @ni: NMBM instance structure ++ * ++ * Scan the entire lower NAND chip and record all bad blocks in to block state ++ * table. ++ */ ++static void nmbm_scan_badblocks(struct nmbm_instance *ni) ++{ ++ uint32_t ba; ++ ++ for (ba = 0; ba < ni->block_count; ba++) { ++ if (nmbm_check_bad_phys_block(ni, ba)) { ++ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); ++ nlog_info(ni, "Bad block %u [0x%08llx]\n", ba, ++ ba2addr(ni, ba)); ++ } ++ } ++} ++ ++/* ++ * nmbm_build_mapping_table - Build initial block mapping table ++ * @ni: NMBM instance structure ++ * ++ * The initial mapping table will be compatible with the stratage of ++ * factory production. ++ */ ++static void nmbm_build_mapping_table(struct nmbm_instance *ni) ++{ ++ uint32_t pb, lb; ++ ++ for (pb = 0, lb = 0; pb < ni->mgmt_start_ba; pb++) { ++ if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) ++ continue; ++ ++ /* Always map to the next good block */ ++ ni->block_mapping[lb++] = pb; ++ } ++ ++ ni->data_block_count = lb; ++ ++ /* Unusable/Management blocks */ ++ for (pb = lb; pb < ni->block_count; pb++) ++ ni->block_mapping[pb] = -1; ++} ++ ++/* ++ * nmbm_erase_block_and_check - Erase a block and check its usability ++ * @ni: NMBM instance structure ++ * @ba: block address to be erased ++ * ++ * Erase a block anc check its usability ++ * ++ * Return true if the block is usable, false if erasure failure or the block ++ * has too many bitflips. ++ */ ++static bool nmbm_erase_block_and_check(struct nmbm_instance *ni, uint32_t ba) ++{ ++ uint64_t addr, off; ++ bool success; ++ int ret; ++ ++ success = nmbm_erase_phys_block(ni, ba2addr(ni, ba)); ++ if (!success) ++ return false; ++ ++ if (!ni->empty_page_ecc_ok) ++ return true; ++ ++ /* Check every page to make sure there aren't too many bitflips */ ++ ++ addr = ba2addr(ni, ba); ++ ++ for (off = 0; off < bmtd.blk_size; off += bmtd.pg_size) { ++ ret = nmbm_read_phys_page(ni, addr + off, ni->page_cache, NULL); ++ if (ret == -EBADMSG) { ++ /* ++ * empty_page_ecc_ok means the empty page is ++ * still protected by ECC. So reading pages with ECC ++ * enabled and -EBADMSG means there are too many ++ * bitflips that can't be recovered, and the block ++ * containing the page should be marked bad. ++ */ ++ nlog_err(ni, ++ "Too many bitflips in empty page at 0x%llx\n", ++ addr + off); ++ return false; ++ } ++ } ++ ++ return true; ++} ++ ++/* ++ * nmbm_erase_range - Erase a range of blocks ++ * @ni: NMBM instance structure ++ * @ba: block address where the erasure will start ++ * @limit: top block address allowed for erasure ++ * ++ * Erase blocks within the specific range. Newly-found bad blocks will be ++ * marked. ++ * ++ * @limit is not counted into the allowed erasure address. ++ */ ++static void nmbm_erase_range(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t limit) ++{ ++ bool success; ++ ++ while (ba < limit) { ++ if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) ++ goto next_block; ++ ++ /* Insurance to detect unexpected bad block marked by user */ ++ if (nmbm_check_bad_phys_block(ni, ba)) { ++ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); ++ goto next_block; ++ } ++ ++ success = nmbm_erase_block_and_check(ni, ba); ++ if (success) ++ goto next_block; ++ ++ nmbm_mark_phys_bad_block(ni, ba); ++ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); ++ ++ next_block: ++ ba++; ++ } ++} ++ ++/* ++ * nmbm_write_repeated_data - Write critical data to a block with retry ++ * @ni: NMBM instance structure ++ * @ba: block address where the data will be written to ++ * @data: the data to be written ++ * @size: size of the data ++ * ++ * Write data to every page of the block. Success only if all pages within ++ * this block have been successfully written. ++ * ++ * Make sure data size is not bigger than one page. ++ * ++ * This function will write and verify every page for at most ++ * NMBM_TRY_COUNT times. ++ */ ++static bool nmbm_write_repeated_data(struct nmbm_instance *ni, uint32_t ba, ++ const void *data, uint32_t size) ++{ ++ uint64_t addr, off; ++ bool success; ++ int ret; ++ ++ if (size > bmtd.pg_size) ++ return false; ++ ++ addr = ba2addr(ni, ba); ++ ++ for (off = 0; off < bmtd.blk_size; off += bmtd.pg_size) { ++ /* Prepare page data. fill 0xff to unused region */ ++ memcpy(ni->page_cache, data, size); ++ memset(ni->page_cache + size, 0xff, ni->rawpage_size - size); ++ ++ success = nmbm_write_phys_page(ni, addr + off, ni->page_cache, NULL); ++ if (!success) ++ return false; ++ ++ /* Verify the data just written. ECC error indicates failure */ ++ ret = nmbm_read_phys_page(ni, addr + off, ni->page_cache, NULL); ++ if (ret < 0) ++ return false; ++ ++ if (memcmp(ni->page_cache, data, size)) ++ return false; ++ } ++ ++ return true; ++} ++ ++/* ++ * nmbm_write_signature - Write signature to NAND chip ++ * @ni: NMBM instance structure ++ * @limit: top block address allowed for writing ++ * @signature: the signature to be written ++ * @signature_ba: the actual block address where signature is written to ++ * ++ * Write signature within a specific range, from chip bottom to limit. ++ * At most one block will be written. ++ * ++ * @limit is not counted into the allowed write address. ++ */ ++static bool nmbm_write_signature(struct nmbm_instance *ni, uint32_t limit, ++ const struct nmbm_signature *signature, ++ uint32_t *signature_ba) ++{ ++ uint32_t ba = ni->block_count - 1; ++ bool success; ++ ++ while (ba > limit) { ++ if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) ++ goto next_block; ++ ++ /* Insurance to detect unexpected bad block marked by user */ ++ if (nmbm_check_bad_phys_block(ni, ba)) { ++ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); ++ goto next_block; ++ } ++ ++ success = nmbm_erase_block_and_check(ni, ba); ++ if (!success) ++ goto skip_bad_block; ++ ++ success = nmbm_write_repeated_data(ni, ba, signature, ++ sizeof(*signature)); ++ if (success) { ++ *signature_ba = ba; ++ return true; ++ } ++ ++ skip_bad_block: ++ nmbm_mark_phys_bad_block(ni, ba); ++ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); ++ ++ next_block: ++ ba--; ++ }; ++ ++ return false; ++} ++ ++/* ++ * nmbn_read_data - Read data ++ * @ni: NMBM instance structure ++ * @addr: linear address where the data will be read from ++ * @data: the data to be read ++ * @size: the size of data ++ * ++ * Read data range. ++ * Every page will be tried for at most NMBM_TRY_COUNT times. ++ * ++ * Return 0 for success, positive value for corrected bitflip count, ++ * -EBADMSG for ecc error, other negative values for other errors ++ */ ++static int nmbn_read_data(struct nmbm_instance *ni, uint64_t addr, void *data, ++ uint32_t size) ++{ ++ uint64_t off = addr; ++ uint8_t *ptr = data; ++ uint32_t sizeremain = size, chunksize, leading; ++ int ret; ++ ++ while (sizeremain) { ++ leading = off & (bmtd.pg_size - 1); ++ chunksize = bmtd.pg_size - leading; ++ if (chunksize > sizeremain) ++ chunksize = sizeremain; ++ ++ if (chunksize == bmtd.pg_size) { ++ ret = nmbm_read_phys_page(ni, off - leading, ptr, NULL); ++ if (ret < 0) ++ return ret; ++ } else { ++ ret = nmbm_read_phys_page(ni, off - leading, ++ ni->page_cache, NULL); ++ if (ret < 0) ++ return ret; ++ ++ memcpy(ptr, ni->page_cache + leading, chunksize); ++ } ++ ++ off += chunksize; ++ ptr += chunksize; ++ sizeremain -= chunksize; ++ } ++ ++ return 0; ++} ++ ++/* ++ * nmbn_write_verify_data - Write data with validation ++ * @ni: NMBM instance structure ++ * @addr: linear address where the data will be written to ++ * @data: the data to be written ++ * @size: the size of data ++ * ++ * Write data and verify. ++ * Every page will be tried for at most NMBM_TRY_COUNT times. ++ */ ++static bool nmbn_write_verify_data(struct nmbm_instance *ni, uint64_t addr, ++ const void *data, uint32_t size) ++{ ++ uint64_t off = addr; ++ const uint8_t *ptr = data; ++ uint32_t sizeremain = size, chunksize, leading; ++ bool success; ++ int ret; ++ ++ while (sizeremain) { ++ leading = off & (bmtd.pg_size - 1); ++ chunksize = bmtd.pg_size - leading; ++ if (chunksize > sizeremain) ++ chunksize = sizeremain; ++ ++ /* Prepare page data. fill 0xff to unused region */ ++ memset(ni->page_cache, 0xff, ni->rawpage_size); ++ memcpy(ni->page_cache + leading, ptr, chunksize); ++ ++ success = nmbm_write_phys_page(ni, off - leading, ++ ni->page_cache, NULL); ++ if (!success) ++ return false; ++ ++ /* Verify the data just written. ECC error indicates failure */ ++ ret = nmbm_read_phys_page(ni, off - leading, ni->page_cache, NULL); ++ if (ret < 0) ++ return false; ++ ++ if (memcmp(ni->page_cache + leading, ptr, chunksize)) ++ return false; ++ ++ off += chunksize; ++ ptr += chunksize; ++ sizeremain -= chunksize; ++ } ++ ++ return true; ++} ++ ++/* ++ * nmbm_write_mgmt_range - Write management data into NAND within a range ++ * @ni: NMBM instance structure ++ * @addr: preferred start block address for writing ++ * @limit: highest block address allowed for writing ++ * @data: the data to be written ++ * @size: the size of data ++ * @actual_start_ba: actual start block address of data ++ * @actual_end_ba: block address after the end of data ++ * ++ * @limit is not counted into the allowed write address. ++ */ ++static bool nmbm_write_mgmt_range(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t limit, const void *data, ++ uint32_t size, uint32_t *actual_start_ba, ++ uint32_t *actual_end_ba) ++{ ++ const uint8_t *ptr = data; ++ uint32_t sizeremain = size, chunksize; ++ bool success; ++ ++ while (sizeremain && ba < limit) { ++ chunksize = sizeremain; ++ if (chunksize > bmtd.blk_size) ++ chunksize = bmtd.blk_size; ++ ++ if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) ++ goto next_block; ++ ++ /* Insurance to detect unexpected bad block marked by user */ ++ if (nmbm_check_bad_phys_block(ni, ba)) { ++ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); ++ goto next_block; ++ } ++ ++ success = nmbm_erase_block_and_check(ni, ba); ++ if (!success) ++ goto skip_bad_block; ++ ++ success = nmbn_write_verify_data(ni, ba2addr(ni, ba), ptr, ++ chunksize); ++ if (!success) ++ goto skip_bad_block; ++ ++ if (sizeremain == size) ++ *actual_start_ba = ba; ++ ++ ptr += chunksize; ++ sizeremain -= chunksize; ++ ++ goto next_block; ++ ++ skip_bad_block: ++ nmbm_mark_phys_bad_block(ni, ba); ++ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); ++ ++ next_block: ++ ba++; ++ } ++ ++ if (sizeremain) ++ return false; ++ ++ *actual_end_ba = ba; ++ ++ return true; ++} ++ ++/* ++ * nmbm_generate_info_table_cache - Generate info table cache data ++ * @ni: NMBM instance structure ++ * ++ * Generate info table cache data to be written into flash. ++ */ ++static bool nmbm_generate_info_table_cache(struct nmbm_instance *ni) ++{ ++ bool changed = false; ++ ++ memset(ni->info_table_cache, 0xff, ni->info_table_size); ++ ++ memcpy(ni->info_table_cache + ni->info_table.state_table_off, ++ ni->block_state, ni->state_table_size); ++ ++ memcpy(ni->info_table_cache + ni->info_table.mapping_table_off, ++ ni->block_mapping, ni->mapping_table_size); ++ ++ ni->info_table.header.magic = NMBM_MAGIC_INFO_TABLE; ++ ni->info_table.header.version = NMBM_VER; ++ ni->info_table.header.size = ni->info_table_size; ++ ++ if (ni->block_state_changed || ni->block_mapping_changed) { ++ ni->info_table.write_count++; ++ changed = true; ++ } ++ ++ memcpy(ni->info_table_cache, &ni->info_table, sizeof(ni->info_table)); ++ ++ nmbm_update_checksum((struct nmbm_header *)ni->info_table_cache); ++ ++ return changed; ++} ++ ++/* ++ * nmbm_write_info_table - Write info table into NAND within a range ++ * @ni: NMBM instance structure ++ * @ba: preferred start block address for writing ++ * @limit: highest block address allowed for writing ++ * @actual_start_ba: actual start block address of info table ++ * @actual_end_ba: block address after the end of info table ++ * ++ * @limit is counted into the allowed write address. ++ */ ++static bool nmbm_write_info_table(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t limit, uint32_t *actual_start_ba, ++ uint32_t *actual_end_ba) ++{ ++ return nmbm_write_mgmt_range(ni, ba, limit, ni->info_table_cache, ++ ni->info_table_size, actual_start_ba, ++ actual_end_ba); ++} ++ ++/* ++ * nmbm_mark_tables_clean - Mark info table `clean' ++ * @ni: NMBM instance structure ++ */ ++static void nmbm_mark_tables_clean(struct nmbm_instance *ni) ++{ ++ ni->block_state_changed = 0; ++ ni->block_mapping_changed = 0; ++} ++ ++/* ++ * nmbm_try_reserve_blocks - Reserve blocks with compromisation ++ * @ni: NMBM instance structure ++ * @ba: start physical block address ++ * @nba: return physical block address after reservation ++ * @count: number of good blocks to be skipped ++ * @min_count: minimum number of good blocks to be skipped ++ * @limit: highest/lowest block address allowed for walking ++ * ++ * Reserve specific blocks. If failed, try to reserve as many as possible. ++ */ ++static bool nmbm_try_reserve_blocks(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t *nba, uint32_t count, ++ int32_t min_count, int32_t limit) ++{ ++ int32_t nblocks = count; ++ bool success; ++ ++ while (nblocks >= min_count) { ++ success = nmbm_block_walk(ni, true, ba, nba, nblocks, limit); ++ if (success) ++ return true; ++ ++ nblocks--; ++ } ++ ++ return false; ++} ++ ++/* ++ * nmbm_rebuild_info_table - Build main & backup info table from scratch ++ * @ni: NMBM instance structure ++ * @allow_no_gap: allow no spare blocks between two tables ++ */ ++static bool nmbm_rebuild_info_table(struct nmbm_instance *ni) ++{ ++ uint32_t table_start_ba, table_end_ba, next_start_ba; ++ uint32_t main_table_end_ba; ++ bool success; ++ ++ /* Set initial value */ ++ ni->main_table_ba = 0; ++ ni->backup_table_ba = 0; ++ ni->mapping_blocks_ba = ni->mapping_blocks_top_ba; ++ ++ /* Write main table */ ++ success = nmbm_write_info_table(ni, ni->mgmt_start_ba, ++ ni->mapping_blocks_top_ba, ++ &table_start_ba, &table_end_ba); ++ if (!success) { ++ /* Failed to write main table, data will be lost */ ++ nlog_err(ni, "Unable to write at least one info table!\n"); ++ nlog_err(ni, "Please save your data before power off!\n"); ++ ni->protected = 1; ++ return false; ++ } ++ ++ /* Main info table is successfully written, record its offset */ ++ ni->main_table_ba = table_start_ba; ++ main_table_end_ba = table_end_ba; ++ ++ /* Adjust mapping_blocks_ba */ ++ ni->mapping_blocks_ba = table_end_ba; ++ ++ nmbm_mark_tables_clean(ni); ++ ++ nlog_table_creation(ni, true, table_start_ba, table_end_ba); ++ ++ /* Reserve spare blocks for main info table. */ ++ success = nmbm_try_reserve_blocks(ni, table_end_ba, ++ &next_start_ba, ++ ni->info_table_spare_blocks, 0, ++ ni->mapping_blocks_top_ba - ++ size2blk(ni, ni->info_table_size)); ++ if (!success) { ++ /* There is no spare block. */ ++ nlog_debug(ni, "No room for backup info table\n"); ++ return true; ++ } ++ ++ /* Write backup info table. */ ++ success = nmbm_write_info_table(ni, next_start_ba, ++ ni->mapping_blocks_top_ba, ++ &table_start_ba, &table_end_ba); ++ if (!success) { ++ /* There is no enough blocks for backup table. */ ++ nlog_debug(ni, "No room for backup info table\n"); ++ return true; ++ } ++ ++ /* Backup table is successfully written, record its offset */ ++ ni->backup_table_ba = table_start_ba; ++ ++ /* Adjust mapping_blocks_off */ ++ ni->mapping_blocks_ba = table_end_ba; ++ ++ /* Erase spare blocks of main table to clean possible interference data */ ++ nmbm_erase_range(ni, main_table_end_ba, ni->backup_table_ba); ++ ++ nlog_table_creation(ni, false, table_start_ba, table_end_ba); ++ ++ return true; ++} ++ ++/* ++ * nmbm_rescue_single_info_table - Rescue when there is only one info table ++ * @ni: NMBM instance structure ++ * ++ * This function is called when there is only one info table exists. ++ * This function may fail if we can't write new info table ++ */ ++static bool nmbm_rescue_single_info_table(struct nmbm_instance *ni) ++{ ++ uint32_t table_start_ba, table_end_ba, write_ba; ++ bool success; ++ ++ /* Try to write new info table in front of existing table */ ++ success = nmbm_write_info_table(ni, ni->mgmt_start_ba, ++ ni->main_table_ba, ++ &table_start_ba, ++ &table_end_ba); ++ if (success) { ++ /* ++ * New table becomes the main table, existing table becomes ++ * the backup table. ++ */ ++ ni->backup_table_ba = ni->main_table_ba; ++ ni->main_table_ba = table_start_ba; ++ ++ nmbm_mark_tables_clean(ni); ++ ++ /* Erase spare blocks of main table to clean possible interference data */ ++ nmbm_erase_range(ni, table_end_ba, ni->backup_table_ba); ++ ++ nlog_table_creation(ni, true, table_start_ba, table_end_ba); ++ ++ return true; ++ } ++ ++ /* Try to reserve spare blocks for existing table */ ++ success = nmbm_try_reserve_blocks(ni, ni->mapping_blocks_ba, &write_ba, ++ ni->info_table_spare_blocks, 0, ++ ni->mapping_blocks_top_ba - ++ size2blk(ni, ni->info_table_size)); ++ if (!success) { ++ nlog_warn(ni, "Failed to rescue single info table\n"); ++ return false; ++ } ++ ++ /* Try to write new info table next to the existing table */ ++ while (write_ba >= ni->mapping_blocks_ba) { ++ success = nmbm_write_info_table(ni, write_ba, ++ ni->mapping_blocks_top_ba, ++ &table_start_ba, ++ &table_end_ba); ++ if (success) ++ break; ++ ++ write_ba--; ++ } ++ ++ if (success) { ++ /* Erase spare blocks of main table to clean possible interference data */ ++ nmbm_erase_range(ni, ni->mapping_blocks_ba, table_start_ba); ++ ++ /* New table becomes the backup table */ ++ ni->backup_table_ba = table_start_ba; ++ ni->mapping_blocks_ba = table_end_ba; ++ ++ nmbm_mark_tables_clean(ni); ++ ++ nlog_table_creation(ni, false, table_start_ba, table_end_ba); ++ ++ return true; ++ } ++ ++ nlog_warn(ni, "Failed to rescue single info table\n"); ++ return false; ++} ++ ++/* ++ * nmbm_update_single_info_table - Update specific one info table ++ * @ni: NMBM instance structure ++ */ ++static bool nmbm_update_single_info_table(struct nmbm_instance *ni, ++ bool update_main_table) ++{ ++ uint32_t write_start_ba, write_limit, table_start_ba, table_end_ba; ++ bool success; ++ ++ /* Determine the write range */ ++ if (update_main_table) { ++ write_start_ba = ni->main_table_ba; ++ write_limit = ni->backup_table_ba; ++ } else { ++ write_start_ba = ni->backup_table_ba; ++ write_limit = ni->mapping_blocks_top_ba; ++ } ++ ++ success = nmbm_write_info_table(ni, write_start_ba, write_limit, ++ &table_start_ba, &table_end_ba); ++ if (success) { ++ if (update_main_table) { ++ ni->main_table_ba = table_start_ba; ++ } else { ++ ni->backup_table_ba = table_start_ba; ++ ni->mapping_blocks_ba = table_end_ba; ++ } ++ ++ nmbm_mark_tables_clean(ni); ++ ++ nlog_table_update(ni, update_main_table, table_start_ba, ++ table_end_ba); ++ ++ return true; ++ } ++ ++ if (update_main_table) { ++ /* ++ * If failed to update main table, make backup table the new ++ * main table, and call nmbm_rescue_single_info_table() ++ */ ++ nlog_warn(ni, "Unable to update %s info table\n", ++ update_main_table ? "Main" : "Backup"); ++ ++ ni->main_table_ba = ni->backup_table_ba; ++ ni->backup_table_ba = 0; ++ return nmbm_rescue_single_info_table(ni); ++ } ++ ++ /* Only one table left */ ++ ni->mapping_blocks_ba = ni->backup_table_ba; ++ ni->backup_table_ba = 0; ++ ++ return false; ++} ++ ++/* ++ * nmbm_rescue_main_info_table - Rescue when failed to write main info table ++ * @ni: NMBM instance structure ++ * ++ * This function is called when main info table failed to be written, and ++ * backup info table exists. ++ */ ++static bool nmbm_rescue_main_info_table(struct nmbm_instance *ni) ++{ ++ uint32_t tmp_table_start_ba, tmp_table_end_ba, main_table_start_ba; ++ uint32_t main_table_end_ba, write_ba; ++ uint32_t info_table_erasesize = size2blk(ni, ni->info_table_size); ++ bool success; ++ ++ /* Try to reserve spare blocks for existing backup info table */ ++ success = nmbm_try_reserve_blocks(ni, ni->mapping_blocks_ba, &write_ba, ++ ni->info_table_spare_blocks, 0, ++ ni->mapping_blocks_top_ba - ++ info_table_erasesize); ++ if (!success) { ++ /* There is no spare block. Backup info table becomes the main table. */ ++ nlog_err(ni, "No room for temporary info table\n"); ++ ni->main_table_ba = ni->backup_table_ba; ++ ni->backup_table_ba = 0; ++ return true; ++ } ++ ++ /* Try to write temporary info table into spare unmapped blocks */ ++ while (write_ba >= ni->mapping_blocks_ba) { ++ success = nmbm_write_info_table(ni, write_ba, ++ ni->mapping_blocks_top_ba, ++ &tmp_table_start_ba, ++ &tmp_table_end_ba); ++ if (success) ++ break; ++ ++ write_ba--; ++ } ++ ++ if (!success) { ++ /* Backup info table becomes the main table */ ++ nlog_err(ni, "Failed to update main info table\n"); ++ ni->main_table_ba = ni->backup_table_ba; ++ ni->backup_table_ba = 0; ++ return true; ++ } ++ ++ /* Adjust mapping_blocks_off */ ++ ni->mapping_blocks_ba = tmp_table_end_ba; ++ ++ /* ++ * Now write main info table at the beginning of management area. ++ * This operation will generally destroy the original backup info ++ * table. ++ */ ++ success = nmbm_write_info_table(ni, ni->mgmt_start_ba, ++ tmp_table_start_ba, ++ &main_table_start_ba, ++ &main_table_end_ba); ++ if (!success) { ++ /* Temporary info table becomes the main table */ ++ ni->main_table_ba = tmp_table_start_ba; ++ ni->backup_table_ba = 0; ++ ++ nmbm_mark_tables_clean(ni); ++ ++ nlog_err(ni, "Failed to update main info table\n"); ++ ++ return true; ++ } ++ ++ /* Main info table has been successfully written, record its offset */ ++ ni->main_table_ba = main_table_start_ba; ++ ++ nmbm_mark_tables_clean(ni); ++ ++ nlog_table_creation(ni, true, main_table_start_ba, main_table_end_ba); ++ ++ /* ++ * Temporary info table becomes the new backup info table if it's ++ * not overwritten. ++ */ ++ if (main_table_end_ba <= tmp_table_start_ba) { ++ ni->backup_table_ba = tmp_table_start_ba; ++ ++ nlog_table_creation(ni, false, tmp_table_start_ba, ++ tmp_table_end_ba); ++ ++ return true; ++ } ++ ++ /* Adjust mapping_blocks_off */ ++ ni->mapping_blocks_ba = main_table_end_ba; ++ ++ /* Try to reserve spare blocks for new main info table */ ++ success = nmbm_try_reserve_blocks(ni, main_table_end_ba, &write_ba, ++ ni->info_table_spare_blocks, 0, ++ ni->mapping_blocks_top_ba - ++ info_table_erasesize); ++ if (!success) { ++ /* There is no spare block. Only main table exists. */ ++ nlog_err(ni, "No room for backup info table\n"); ++ ni->backup_table_ba = 0; ++ return true; ++ } ++ ++ /* Write new backup info table. */ ++ while (write_ba >= main_table_end_ba) { ++ success = nmbm_write_info_table(ni, write_ba, ++ ni->mapping_blocks_top_ba, ++ &tmp_table_start_ba, ++ &tmp_table_end_ba); ++ if (success) ++ break; ++ ++ write_ba--; ++ } ++ ++ if (!success) { ++ nlog_err(ni, "No room for backup info table\n"); ++ ni->backup_table_ba = 0; ++ return true; ++ } ++ ++ /* Backup info table has been successfully written, record its offset */ ++ ni->backup_table_ba = tmp_table_start_ba; ++ ++ /* Adjust mapping_blocks_off */ ++ ni->mapping_blocks_ba = tmp_table_end_ba; ++ ++ /* Erase spare blocks of main table to clean possible interference data */ ++ nmbm_erase_range(ni, main_table_end_ba, ni->backup_table_ba); ++ ++ nlog_table_creation(ni, false, tmp_table_start_ba, tmp_table_end_ba); ++ ++ return true; ++} ++ ++/* ++ * nmbm_update_info_table_once - Update info table once ++ * @ni: NMBM instance structure ++ * @force: force update ++ * ++ * Update both main and backup info table. Return true if at least one info ++ * table has been successfully written. ++ * This function only try to update info table once regard less of the result. ++ */ ++static bool nmbm_update_info_table_once(struct nmbm_instance *ni, bool force) ++{ ++ uint32_t table_start_ba, table_end_ba; ++ uint32_t main_table_limit; ++ bool success; ++ ++ /* Do nothing if there is no change */ ++ if (!nmbm_generate_info_table_cache(ni) && !force) ++ return true; ++ ++ /* Check whether both two tables exist */ ++ if (!ni->backup_table_ba) { ++ main_table_limit = ni->mapping_blocks_top_ba; ++ goto write_main_table; ++ } ++ ++ /* ++ * Write backup info table in its current range. ++ * Note that limit is set to mapping_blocks_top_off to provide as many ++ * spare blocks as possible for the backup table. If at last ++ * unmapped blocks are used by backup table, mapping_blocks_off will ++ * be adjusted. ++ */ ++ success = nmbm_write_info_table(ni, ni->backup_table_ba, ++ ni->mapping_blocks_top_ba, ++ &table_start_ba, &table_end_ba); ++ if (!success) { ++ /* ++ * There is nothing to do if failed to write backup table. ++ * Write the main table now. ++ */ ++ nlog_err(ni, "No room for backup table\n"); ++ ni->mapping_blocks_ba = ni->backup_table_ba; ++ ni->backup_table_ba = 0; ++ main_table_limit = ni->mapping_blocks_top_ba; ++ goto write_main_table; ++ } ++ ++ /* Backup table is successfully written, record its offset */ ++ ni->backup_table_ba = table_start_ba; ++ ++ /* Adjust mapping_blocks_off */ ++ ni->mapping_blocks_ba = table_end_ba; ++ ++ nmbm_mark_tables_clean(ni); ++ ++ /* The normal limit of main table */ ++ main_table_limit = ni->backup_table_ba; ++ ++ nlog_table_update(ni, false, table_start_ba, table_end_ba); ++ ++write_main_table: ++ if (!ni->main_table_ba) ++ goto rebuild_tables; ++ ++ /* Write main info table in its current range */ ++ success = nmbm_write_info_table(ni, ni->main_table_ba, ++ main_table_limit, &table_start_ba, ++ &table_end_ba); ++ if (!success) { ++ /* If failed to write main table, go rescue procedure */ ++ if (!ni->backup_table_ba) ++ goto rebuild_tables; ++ ++ return nmbm_rescue_main_info_table(ni); ++ } ++ ++ /* Main info table is successfully written, record its offset */ ++ ni->main_table_ba = table_start_ba; ++ ++ /* Adjust mapping_blocks_off */ ++ if (!ni->backup_table_ba) ++ ni->mapping_blocks_ba = table_end_ba; ++ ++ nmbm_mark_tables_clean(ni); ++ ++ nlog_table_update(ni, true, table_start_ba, table_end_ba); ++ ++ return true; ++ ++rebuild_tables: ++ return nmbm_rebuild_info_table(ni); ++} ++ ++/* ++ * nmbm_update_info_table - Update info table ++ * @ni: NMBM instance structure ++ * ++ * Update both main and backup info table. Return true if at least one table ++ * has been successfully written. ++ * This function will try to update info table repeatedly until no new bad ++ * block found during updating. ++ */ ++static bool nmbm_update_info_table(struct nmbm_instance *ni) ++{ ++ bool success; ++ ++ if (ni->protected) ++ return true; ++ ++ while (ni->block_state_changed || ni->block_mapping_changed) { ++ success = nmbm_update_info_table_once(ni, false); ++ if (!success) { ++ nlog_err(ni, "Failed to update info table\n"); ++ return false; ++ } ++ } ++ ++ return true; ++} ++ ++/* ++ * nmbm_map_block - Map a bad block to a unused spare block ++ * @ni: NMBM instance structure ++ * @lb: logic block addr to map ++ */ ++static bool nmbm_map_block(struct nmbm_instance *ni, uint32_t lb) ++{ ++ uint32_t pb; ++ bool success; ++ ++ if (ni->mapping_blocks_ba == ni->mapping_blocks_top_ba) { ++ nlog_warn(ni, "No spare unmapped blocks.\n"); ++ return false; ++ } ++ ++ success = nmbm_block_walk(ni, false, ni->mapping_blocks_top_ba, &pb, 0, ++ ni->mapping_blocks_ba); ++ if (!success) { ++ nlog_warn(ni, "No spare unmapped blocks.\n"); ++ nmbm_update_info_table(ni); ++ ni->mapping_blocks_top_ba = ni->mapping_blocks_ba; ++ return false; ++ } ++ ++ ni->block_mapping[lb] = pb; ++ ni->mapping_blocks_top_ba--; ++ ni->block_mapping_changed++; ++ ++ nlog_info(ni, "Logic block %u mapped to physical block %u\n", lb, pb); ++ ++ return true; ++} ++ ++/* ++ * nmbm_create_info_table - Create info table(s) ++ * @ni: NMBM instance structure ++ * ++ * This function assumes that the chip has no existing info table(s) ++ */ ++static bool nmbm_create_info_table(struct nmbm_instance *ni) ++{ ++ uint32_t lb; ++ bool success; ++ ++ /* Set initial mapping_blocks_top_off */ ++ success = nmbm_block_walk(ni, false, ni->signature_ba, ++ &ni->mapping_blocks_top_ba, 1, ++ ni->mgmt_start_ba); ++ if (!success) { ++ nlog_err(ni, "No room for spare blocks\n"); ++ return false; ++ } ++ ++ /* Generate info table cache */ ++ nmbm_generate_info_table_cache(ni); ++ ++ /* Write info table */ ++ success = nmbm_rebuild_info_table(ni); ++ if (!success) { ++ nlog_err(ni, "Failed to build info tables\n"); ++ return false; ++ } ++ ++ /* Remap bad block(s) at end of data area */ ++ for (lb = ni->data_block_count; lb < ni->mgmt_start_ba; lb++) { ++ success = nmbm_map_block(ni, lb); ++ if (!success) ++ break; ++ ++ ni->data_block_count++; ++ } ++ ++ /* If state table and/or mapping table changed, update info table. */ ++ success = nmbm_update_info_table(ni); ++ if (!success) ++ return false; ++ ++ return true; ++} ++ ++/* ++ * nmbm_create_new - Create NMBM on a new chip ++ * @ni: NMBM instance structure ++ */ ++static bool nmbm_create_new(struct nmbm_instance *ni) ++{ ++ bool success; ++ ++ /* Determine the boundary of management blocks */ ++ ni->mgmt_start_ba = ni->block_count * (NMBM_MGMT_DIV - ni->max_ratio) / NMBM_MGMT_DIV; ++ ++ if (ni->max_reserved_blocks && ni->block_count - ni->mgmt_start_ba > ni->max_reserved_blocks) ++ ni->mgmt_start_ba = ni->block_count - ni->max_reserved_blocks; ++ ++ nlog_info(ni, "NMBM management region starts at block %u [0x%08llx]\n", ++ ni->mgmt_start_ba, ba2addr(ni, ni->mgmt_start_ba)); ++ ++ /* Fill block state table & mapping table */ ++ nmbm_scan_badblocks(ni); ++ nmbm_build_mapping_table(ni); ++ ++ /* Write signature */ ++ ni->signature.header.magic = NMBM_MAGIC_SIGNATURE; ++ ni->signature.header.version = NMBM_VER; ++ ni->signature.header.size = sizeof(ni->signature); ++ ni->signature.nand_size = bmtd.total_blks << bmtd.blk_shift; ++ ni->signature.block_size = bmtd.blk_size; ++ ni->signature.page_size = bmtd.pg_size; ++ ni->signature.spare_size = bmtd.mtd->oobsize; ++ ni->signature.mgmt_start_pb = ni->mgmt_start_ba; ++ ni->signature.max_try_count = NMBM_TRY_COUNT; ++ nmbm_update_checksum(&ni->signature.header); ++ ++ success = nmbm_write_signature(ni, ni->mgmt_start_ba, ++ &ni->signature, &ni->signature_ba); ++ if (!success) { ++ nlog_err(ni, "Failed to write signature to a proper offset\n"); ++ return false; ++ } ++ ++ nlog_info(ni, "Signature has been written to block %u [0x%08llx]\n", ++ ni->signature_ba, ba2addr(ni, ni->signature_ba)); ++ ++ /* Write info table(s) */ ++ success = nmbm_create_info_table(ni); ++ if (success) { ++ nlog_info(ni, "NMBM has been successfully created\n"); ++ return true; ++ } ++ ++ return false; ++} ++ ++/* ++ * nmbm_check_info_table_header - Check if a info table header is valid ++ * @ni: NMBM instance structure ++ * @data: pointer to the info table header ++ */ ++static bool nmbm_check_info_table_header(struct nmbm_instance *ni, void *data) ++{ ++ struct nmbm_info_table_header *ifthdr = data; ++ ++ if (ifthdr->header.magic != NMBM_MAGIC_INFO_TABLE) ++ return false; ++ ++ if (ifthdr->header.size != ni->info_table_size) ++ return false; ++ ++ if (ifthdr->mapping_table_off - ifthdr->state_table_off < ni->state_table_size) ++ return false; ++ ++ if (ni->info_table_size - ifthdr->mapping_table_off < ni->mapping_table_size) ++ return false; ++ ++ return true; ++} ++ ++/* ++ * nmbm_check_info_table - Check if a whole info table is valid ++ * @ni: NMBM instance structure ++ * @start_ba: start block address of this table ++ * @end_ba: end block address of this table ++ * @data: pointer to the info table header ++ * @mapping_blocks_top_ba: return the block address of top remapped block ++ */ ++static bool nmbm_check_info_table(struct nmbm_instance *ni, uint32_t start_ba, ++ uint32_t end_ba, void *data, ++ uint32_t *mapping_blocks_top_ba) ++{ ++ struct nmbm_info_table_header *ifthdr = data; ++ int32_t *block_mapping = (int32_t *)((uintptr_t)data + ifthdr->mapping_table_off); ++ u32 *block_state = (u32 *)((uintptr_t)data + ifthdr->state_table_off); ++ uint32_t minimum_mapping_pb = ni->signature_ba; ++ uint32_t ba; ++ ++ for (ba = 0; ba < ni->data_block_count; ba++) { ++ if ((block_mapping[ba] >= ni->data_block_count && block_mapping[ba] < end_ba) || ++ block_mapping[ba] == ni->signature_ba) ++ return false; ++ ++ if (block_mapping[ba] >= end_ba && block_mapping[ba] < minimum_mapping_pb) ++ minimum_mapping_pb = block_mapping[ba]; ++ } ++ ++ for (ba = start_ba; ba < end_ba; ba++) { ++ if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) ++ continue; ++ ++ if (nmbm_get_block_state_raw(block_state, ba) != BLOCK_ST_GOOD) ++ return false; ++ } ++ ++ *mapping_blocks_top_ba = minimum_mapping_pb - 1; ++ ++ return true; ++} ++ ++/* ++ * nmbm_try_load_info_table - Try to load info table from a address ++ * @ni: NMBM instance structure ++ * @ba: start block address of the info table ++ * @eba: return the block address after end of the table ++ * @write_count: return the write count of this table ++ * @mapping_blocks_top_ba: return the block address of top remapped block ++ * @table_loaded: used to record whether ni->info_table has valid data ++ */ ++static bool nmbm_try_load_info_table(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t *eba, uint32_t *write_count, ++ uint32_t *mapping_blocks_top_ba, ++ bool table_loaded) ++{ ++ struct nmbm_info_table_header *ifthdr = (void *)ni->info_table_cache; ++ uint8_t *off = ni->info_table_cache; ++ uint32_t limit = ba + size2blk(ni, ni->info_table_size); ++ uint32_t start_ba = 0, chunksize, sizeremain = ni->info_table_size; ++ bool success, checkhdr = true; ++ int ret; ++ ++ while (sizeremain && ba < limit) { ++ if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) ++ goto next_block; ++ ++ if (nmbm_check_bad_phys_block(ni, ba)) { ++ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); ++ goto next_block; ++ } ++ ++ chunksize = sizeremain; ++ if (chunksize > bmtd.blk_size) ++ chunksize = bmtd.blk_size; ++ ++ /* Assume block with ECC error has no info table data */ ++ ret = nmbn_read_data(ni, ba2addr(ni, ba), off, chunksize); ++ if (ret < 0) ++ goto skip_bad_block; ++ else if (ret > 0) ++ return false; ++ ++ if (checkhdr) { ++ success = nmbm_check_info_table_header(ni, off); ++ if (!success) ++ return false; ++ ++ start_ba = ba; ++ checkhdr = false; ++ } ++ ++ off += chunksize; ++ sizeremain -= chunksize; ++ ++ goto next_block; ++ ++ skip_bad_block: ++ /* Only mark bad in memory */ ++ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); ++ ++ next_block: ++ ba++; ++ } ++ ++ if (sizeremain) ++ return false; ++ ++ success = nmbm_check_header(ni->info_table_cache, ni->info_table_size); ++ if (!success) ++ return false; ++ ++ *eba = ba; ++ *write_count = ifthdr->write_count; ++ ++ success = nmbm_check_info_table(ni, start_ba, ba, ni->info_table_cache, ++ mapping_blocks_top_ba); ++ if (!success) ++ return false; ++ ++ if (!table_loaded || ifthdr->write_count > ni->info_table.write_count) { ++ memcpy(&ni->info_table, ifthdr, sizeof(ni->info_table)); ++ memcpy(ni->block_state, ++ (uint8_t *)ifthdr + ifthdr->state_table_off, ++ ni->state_table_size); ++ memcpy(ni->block_mapping, ++ (uint8_t *)ifthdr + ifthdr->mapping_table_off, ++ ni->mapping_table_size); ++ ni->info_table.write_count = ifthdr->write_count; ++ } ++ ++ return true; ++} ++ ++/* ++ * nmbm_search_info_table - Search info table from specific address ++ * @ni: NMBM instance structure ++ * @ba: start block address to search ++ * @limit: highest block address allowed for searching ++ * @table_start_ba: return the start block address of this table ++ * @table_end_ba: return the block address after end of this table ++ * @write_count: return the write count of this table ++ * @mapping_blocks_top_ba: return the block address of top remapped block ++ * @table_loaded: used to record whether ni->info_table has valid data ++ */ ++static bool nmbm_search_info_table(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t limit, uint32_t *table_start_ba, ++ uint32_t *table_end_ba, ++ uint32_t *write_count, ++ uint32_t *mapping_blocks_top_ba, ++ bool table_loaded) ++{ ++ bool success; ++ ++ while (ba < limit - size2blk(ni, ni->info_table_size)) { ++ success = nmbm_try_load_info_table(ni, ba, table_end_ba, ++ write_count, ++ mapping_blocks_top_ba, ++ table_loaded); ++ if (success) { ++ *table_start_ba = ba; ++ return true; ++ } ++ ++ ba++; ++ } ++ ++ return false; ++} ++ ++/* ++ * nmbm_load_info_table - Load info table(s) from a chip ++ * @ni: NMBM instance structure ++ * @ba: start block address to search info table ++ * @limit: highest block address allowed for searching ++ */ ++static bool nmbm_load_info_table(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t limit) ++{ ++ uint32_t main_table_end_ba, backup_table_end_ba, table_end_ba; ++ uint32_t main_mapping_blocks_top_ba, backup_mapping_blocks_top_ba; ++ uint32_t main_table_write_count, backup_table_write_count; ++ uint32_t i; ++ bool success; ++ ++ /* Set initial value */ ++ ni->main_table_ba = 0; ++ ni->backup_table_ba = 0; ++ ni->info_table.write_count = 0; ++ ni->mapping_blocks_top_ba = ni->signature_ba - 1; ++ ni->data_block_count = ni->signature.mgmt_start_pb; ++ ++ /* Find first info table */ ++ success = nmbm_search_info_table(ni, ba, limit, &ni->main_table_ba, ++ &main_table_end_ba, &main_table_write_count, ++ &main_mapping_blocks_top_ba, false); ++ if (!success) { ++ nlog_warn(ni, "No valid info table found\n"); ++ return false; ++ } ++ ++ table_end_ba = main_table_end_ba; ++ ++ nlog_table_found(ni, true, main_table_write_count, ni->main_table_ba, ++ main_table_end_ba); ++ ++ /* Find second info table */ ++ success = nmbm_search_info_table(ni, main_table_end_ba, limit, ++ &ni->backup_table_ba, &backup_table_end_ba, ++ &backup_table_write_count, &backup_mapping_blocks_top_ba, true); ++ if (!success) { ++ nlog_warn(ni, "Second info table not found\n"); ++ } else { ++ table_end_ba = backup_table_end_ba; ++ ++ nlog_table_found(ni, false, backup_table_write_count, ++ ni->backup_table_ba, backup_table_end_ba); ++ } ++ ++ /* Pick mapping_blocks_top_ba */ ++ if (!ni->backup_table_ba) { ++ ni->mapping_blocks_top_ba= main_mapping_blocks_top_ba; ++ } else { ++ if (main_table_write_count >= backup_table_write_count) ++ ni->mapping_blocks_top_ba = main_mapping_blocks_top_ba; ++ else ++ ni->mapping_blocks_top_ba = backup_mapping_blocks_top_ba; ++ } ++ ++ /* Set final mapping_blocks_ba */ ++ ni->mapping_blocks_ba = table_end_ba; ++ ++ /* Set final data_block_count */ ++ for (i = ni->signature.mgmt_start_pb; i > 0; i--) { ++ if (ni->block_mapping[i - 1] >= 0) { ++ ni->data_block_count = i; ++ break; ++ } ++ } ++ ++ /* Regenerate the info table cache from the final selected info table */ ++ nmbm_generate_info_table_cache(ni); ++ ++ /* ++ * If only one table exists, try to write another table. ++ * If two tables have different write count, try to update info table ++ */ ++ if (!ni->backup_table_ba) { ++ success = nmbm_rescue_single_info_table(ni); ++ } else if (main_table_write_count != backup_table_write_count) { ++ /* Mark state & mapping tables changed */ ++ ni->block_state_changed = 1; ++ ni->block_mapping_changed = 1; ++ ++ success = nmbm_update_single_info_table(ni, ++ main_table_write_count < backup_table_write_count); ++ } else { ++ success = true; ++ } ++ ++ /* ++ * If there is no spare unmapped blocks, or still only one table ++ * exists, set the chip to read-only ++ */ ++ if (ni->mapping_blocks_ba == ni->mapping_blocks_top_ba) { ++ nlog_warn(ni, "No spare unmapped blocks. Device is now read-only\n"); ++ ni->protected = 1; ++ } else if (!success) { ++ nlog_warn(ni, "Only one info table found. Device is now read-only\n"); ++ ni->protected = 1; ++ } ++ ++ return true; ++} ++ ++/* ++ * nmbm_load_existing - Load NMBM from a new chip ++ * @ni: NMBM instance structure ++ */ ++static bool nmbm_load_existing(struct nmbm_instance *ni) ++{ ++ bool success; ++ ++ /* Calculate the boundary of management blocks */ ++ ni->mgmt_start_ba = ni->signature.mgmt_start_pb; ++ ++ nlog_debug(ni, "NMBM management region starts at block %u [0x%08llx]\n", ++ ni->mgmt_start_ba, ba2addr(ni, ni->mgmt_start_ba)); ++ ++ /* Look for info table(s) */ ++ success = nmbm_load_info_table(ni, ni->mgmt_start_ba, ++ ni->signature_ba); ++ if (success) { ++ nlog_info(ni, "NMBM has been successfully attached\n"); ++ return true; ++ } ++ ++ if (!ni->force_create) { ++ printk("not creating NMBM table\n"); ++ return false; ++ } ++ ++ /* Fill block state table & mapping table */ ++ nmbm_scan_badblocks(ni); ++ nmbm_build_mapping_table(ni); ++ ++ /* Write info table(s) */ ++ success = nmbm_create_info_table(ni); ++ if (success) { ++ nlog_info(ni, "NMBM has been successfully created\n"); ++ return true; ++ } ++ ++ return false; ++} ++ ++/* ++ * nmbm_find_signature - Find signature in the lower NAND chip ++ * @ni: NMBM instance structure ++ * @signature_ba: used for storing block address of the signature ++ * @signature_ba: return the actual block address of signature block ++ * ++ * Find a valid signature from a specific range in the lower NAND chip, ++ * from bottom (highest address) to top (lowest address) ++ * ++ * Return true if found. ++ */ ++static bool nmbm_find_signature(struct nmbm_instance *ni, ++ struct nmbm_signature *signature, ++ uint32_t *signature_ba) ++{ ++ struct nmbm_signature sig; ++ uint64_t off, addr; ++ uint32_t block_count, ba, limit; ++ bool success; ++ int ret; ++ ++ /* Calculate top and bottom block address */ ++ block_count = bmtd.total_blks; ++ ba = block_count; ++ limit = (block_count / NMBM_MGMT_DIV) * (NMBM_MGMT_DIV - ni->max_ratio); ++ if (ni->max_reserved_blocks && block_count - limit > ni->max_reserved_blocks) ++ limit = block_count - ni->max_reserved_blocks; ++ ++ while (ba >= limit) { ++ ba--; ++ addr = ba2addr(ni, ba); ++ ++ if (nmbm_check_bad_phys_block(ni, ba)) ++ continue; ++ ++ /* Check every page. ++ * As long as at leaset one page contains valid signature, ++ * the block is treated as a valid signature block. ++ */ ++ for (off = 0; off < bmtd.blk_size; ++ off += bmtd.pg_size) { ++ ret = nmbn_read_data(ni, addr + off, &sig, ++ sizeof(sig)); ++ if (ret) ++ continue; ++ ++ /* Check for header size and checksum */ ++ success = nmbm_check_header(&sig, sizeof(sig)); ++ if (!success) ++ continue; ++ ++ /* Check for header magic */ ++ if (sig.header.magic == NMBM_MAGIC_SIGNATURE) { ++ /* Found it */ ++ memcpy(signature, &sig, sizeof(sig)); ++ *signature_ba = ba; ++ return true; ++ } ++ } ++ }; ++ ++ return false; ++} ++ ++/* ++ * nmbm_calc_structure_size - Calculate the instance structure size ++ * @nld: NMBM lower device structure ++ */ ++static size_t nmbm_calc_structure_size(void) ++{ ++ uint32_t state_table_size, mapping_table_size, info_table_size; ++ uint32_t block_count; ++ ++ block_count = bmtd.total_blks; ++ ++ /* Calculate info table size */ ++ state_table_size = ((block_count + NMBM_BITMAP_BLOCKS_PER_UNIT - 1) / ++ NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_UNIT_SIZE; ++ mapping_table_size = block_count * sizeof(int32_t); ++ ++ info_table_size = ALIGN(sizeof(struct nmbm_info_table_header), ++ bmtd.pg_size); ++ info_table_size += ALIGN(state_table_size, bmtd.pg_size); ++ info_table_size += ALIGN(mapping_table_size, bmtd.pg_size); ++ ++ return info_table_size + state_table_size + mapping_table_size + ++ sizeof(struct nmbm_instance); ++} ++ ++/* ++ * nmbm_init_structure - Initialize members of instance structure ++ * @ni: NMBM instance structure ++ */ ++static void nmbm_init_structure(struct nmbm_instance *ni) ++{ ++ uint32_t pages_per_block, blocks_per_chip; ++ uintptr_t ptr; ++ ++ pages_per_block = bmtd.blk_size / bmtd.pg_size; ++ blocks_per_chip = bmtd.total_blks; ++ ++ ni->rawpage_size = bmtd.pg_size + bmtd.mtd->oobsize; ++ ni->rawblock_size = pages_per_block * ni->rawpage_size; ++ ni->rawchip_size = blocks_per_chip * ni->rawblock_size; ++ ++ /* Calculate number of block this chip */ ++ ni->block_count = blocks_per_chip; ++ ++ /* Calculate info table size */ ++ ni->state_table_size = ((ni->block_count + NMBM_BITMAP_BLOCKS_PER_UNIT - 1) / ++ NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_UNIT_SIZE; ++ ni->mapping_table_size = ni->block_count * sizeof(*ni->block_mapping); ++ ++ ni->info_table_size = ALIGN(sizeof(ni->info_table), ++ bmtd.pg_size); ++ ni->info_table.state_table_off = ni->info_table_size; ++ ++ ni->info_table_size += ALIGN(ni->state_table_size, ++ bmtd.pg_size); ++ ni->info_table.mapping_table_off = ni->info_table_size; ++ ++ ni->info_table_size += ALIGN(ni->mapping_table_size, ++ bmtd.pg_size); ++ ++ ni->info_table_spare_blocks = nmbm_get_spare_block_count( ++ size2blk(ni, ni->info_table_size)); ++ ++ /* Assign memory to members */ ++ ptr = (uintptr_t)ni + sizeof(*ni); ++ ++ ni->info_table_cache = (void *)ptr; ++ ptr += ni->info_table_size; ++ ++ ni->block_state = (void *)ptr; ++ ptr += ni->state_table_size; ++ ++ ni->block_mapping = (void *)ptr; ++ ptr += ni->mapping_table_size; ++ ++ ni->page_cache = bmtd.data_buf; ++ ++ /* Initialize block state table */ ++ ni->block_state_changed = 0; ++ memset(ni->block_state, 0xff, ni->state_table_size); ++ ++ /* Initialize block mapping table */ ++ ni->block_mapping_changed = 0; ++} ++ ++/* ++ * nmbm_attach - Attach to a lower device ++ * @ni: NMBM instance structure ++ */ ++static int nmbm_attach(struct nmbm_instance *ni) ++{ ++ bool success; ++ ++ if (!ni) ++ return -EINVAL; ++ ++ /* Initialize NMBM instance */ ++ nmbm_init_structure(ni); ++ ++ success = nmbm_find_signature(ni, &ni->signature, &ni->signature_ba); ++ if (!success) { ++ if (!ni->force_create) { ++ nlog_err(ni, "Signature not found\n"); ++ return -ENODEV; ++ } ++ ++ success = nmbm_create_new(ni); ++ if (!success) ++ return -ENODEV; ++ ++ return 0; ++ } ++ ++ nlog_info(ni, "Signature found at block %u [0x%08llx]\n", ++ ni->signature_ba, ba2addr(ni, ni->signature_ba)); ++ ++ if (ni->signature.header.version != NMBM_VER) { ++ nlog_err(ni, "NMBM version %u.%u is not supported\n", ++ NMBM_VERSION_MAJOR_GET(ni->signature.header.version), ++ NMBM_VERSION_MINOR_GET(ni->signature.header.version)); ++ return -EINVAL; ++ } ++ ++ if (ni->signature.nand_size != bmtd.total_blks << bmtd.blk_shift || ++ ni->signature.block_size != bmtd.blk_size || ++ ni->signature.page_size != bmtd.pg_size || ++ ni->signature.spare_size != bmtd.mtd->oobsize) { ++ nlog_err(ni, "NMBM configuration mismatch\n"); ++ return -EINVAL; ++ } ++ ++ success = nmbm_load_existing(ni); ++ if (!success) ++ return -ENODEV; ++ ++ return 0; ++} ++ ++static bool remap_block_nmbm(u16 block, u16 mapped_block, int copy_len) ++{ ++ struct nmbm_instance *ni = bmtd.ni; ++ int new_block; ++ ++ if (block >= ni->data_block_count) ++ return false; ++ ++ nmbm_set_block_state(ni, mapped_block, BLOCK_ST_BAD); ++ if (!nmbm_map_block(ni, block)) ++ return false; ++ ++ new_block = ni->block_mapping[block]; ++ bbt_nand_erase(new_block); ++ if (copy_len > 0) ++ bbt_nand_copy(new_block, mapped_block, copy_len); ++ nmbm_update_info_table(ni); ++ ++ return true; ++} ++ ++static int get_mapping_block_index_nmbm(int block) ++{ ++ struct nmbm_instance *ni = bmtd.ni; ++ ++ if (block >= ni->data_block_count) ++ return -1; ++ ++ return ni->block_mapping[block]; ++} ++ ++static int mtk_bmt_init_nmbm(struct device_node *np) ++{ ++ struct nmbm_instance *ni; ++ int ret; ++ ++ ni = kzalloc(nmbm_calc_structure_size(), GFP_KERNEL); ++ if (!ni) ++ return -ENOMEM; ++ ++ bmtd.ni = ni; ++ ++ if (of_property_read_u32(np, "mediatek,bmt-max-ratio", &ni->max_ratio)) ++ ni->max_ratio = 1; ++ if (of_property_read_u32(np, "mediatek,bmt-max-reserved-blocks", ++ &ni->max_reserved_blocks)) ++ ni->max_reserved_blocks = 256; ++ if (of_property_read_bool(np, "mediatek,empty-page-ecc-protected")) ++ ni->empty_page_ecc_ok = true; ++ if (of_property_read_bool(np, "mediatek,bmt-force-create")) ++ ni->force_create = true; ++ ++ ret = nmbm_attach(ni); ++ if (ret) ++ goto out; ++ ++ bmtd.mtd->size = ni->data_block_count << bmtd.blk_shift; ++ ++ return 0; ++ ++out: ++ kfree(ni); ++ bmtd.ni = NULL; ++ ++ return ret; ++} ++ ++static int mtk_bmt_debug_nmbm(void *data, u64 val) ++{ ++ struct nmbm_instance *ni = bmtd.ni; ++ int i; ++ ++ switch (val) { ++ case 0: ++ for (i = 1; i < ni->data_block_count; i++) { ++ if (ni->block_mapping[i] < ni->mapping_blocks_ba) ++ continue; ++ ++ printk("remap [%x->%x]\n", i, ni->block_mapping[i]); ++ } ++ } ++ ++ return 0; ++} ++ ++static void unmap_block_nmbm(u16 block) ++{ ++ struct nmbm_instance *ni = bmtd.ni; ++ int start, offset; ++ int new_block; ++ ++ if (block >= ni->data_block_count) ++ return; ++ ++ start = block; ++ offset = 0; ++ while (ni->block_mapping[start] >= ni->mapping_blocks_ba) { ++ start--; ++ offset++; ++ if (start < 0) ++ return; ++ } ++ ++ if (!offset) ++ return; ++ ++ new_block = ni->block_mapping[start] + offset; ++ nmbm_set_block_state(ni, new_block, BLOCK_ST_GOOD); ++ ni->block_mapping[block] = new_block; ++ ni->block_mapping_changed++; ++ ++ new_block = ni->signature_ba - 1; ++ for (block = 0; block < ni->data_block_count; block++) { ++ int cur = ni->block_mapping[block]; ++ ++ if (cur < ni->mapping_blocks_ba) ++ continue; ++ ++ if (cur <= new_block) ++ new_block = cur - 1; ++ } ++ ++ ni->mapping_blocks_top_ba = new_block; ++ ++ nmbm_update_info_table(ni); ++} ++ ++const struct mtk_bmt_ops mtk_bmt_nmbm_ops = { ++ .init = mtk_bmt_init_nmbm, ++ .remap_block = remap_block_nmbm, ++ .unmap_block = unmap_block_nmbm, ++ .get_mapping_block = get_mapping_block_index_nmbm, ++ .debug = mtk_bmt_debug_nmbm, ++}; +diff --git a/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_v2.c b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_v2.c +new file mode 100644 +index 0000000000..2770376e98 +--- /dev/null ++++ b/target/linux/ramips/files/drivers/mtd/nand/mtk_bmt_v2.c +@@ -0,0 +1,513 @@ ++/* ++ * Copyright (c) 2017 MediaTek Inc. ++ * Author: Xiangsheng Hou ++ * Copyright (c) 2020-2022 Felix Fietkau ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include "mtk_bmt.h" ++ ++struct bbbt { ++ char signature[3]; ++ /* This version is used to distinguish the legacy and new algorithm */ ++#define BBMT_VERSION 2 ++ unsigned char version; ++ /* Below 2 tables will be written in SLC */ ++ u16 bb_tbl[]; ++}; ++ ++struct bbmt { ++ u16 block; ++#define NO_MAPPED 0 ++#define NORMAL_MAPPED 1 ++#define BMT_MAPPED 2 ++ u16 mapped; ++}; ++ ++/* Maximum 8k blocks */ ++#define BBPOOL_RATIO 2 ++#define BB_TABLE_MAX bmtd.table_size ++#define BMT_TABLE_MAX (BB_TABLE_MAX * BBPOOL_RATIO / 100) ++#define BMT_TBL_DEF_VAL 0x0 ++ ++static inline struct bbmt *bmt_tbl(struct bbbt *bbbt) ++{ ++ return (struct bbmt *)&bbbt->bb_tbl[bmtd.table_size]; ++} ++ ++static u16 find_valid_block(u16 block) ++{ ++ u8 fdm[4]; ++ int ret; ++ int loop = 0; ++ ++retry: ++ if (block >= bmtd.total_blks) ++ return 0; ++ ++ ret = bbt_nand_read(blk_pg(block), bmtd.data_buf, bmtd.pg_size, ++ fdm, sizeof(fdm)); ++ /* Read the 1st byte of FDM to judge whether it's a bad ++ * or not ++ */ ++ if (ret || fdm[0] != 0xff) { ++ pr_info("nand: found bad block 0x%x\n", block); ++ if (loop >= bmtd.bb_max) { ++ pr_info("nand: FATAL ERR: too many bad blocks!!\n"); ++ return 0; ++ } ++ ++ loop++; ++ block++; ++ goto retry; ++ } ++ ++ return block; ++} ++ ++/* Find out all bad blocks, and fill in the mapping table */ ++static int scan_bad_blocks(struct bbbt *bbt) ++{ ++ int i; ++ u16 block = 0; ++ ++ /* First time download, the block0 MUST NOT be a bad block, ++ * this is guaranteed by vendor ++ */ ++ bbt->bb_tbl[0] = 0; ++ ++ /* ++ * Construct the mapping table of Normal data area(non-PMT/BMTPOOL) ++ * G - Good block; B - Bad block ++ * --------------------------- ++ * physical |G|G|B|G|B|B|G|G|G|G|B|G|B| ++ * --------------------------- ++ * What bb_tbl[i] looks like: ++ * physical block(i): ++ * 0 1 2 3 4 5 6 7 8 9 a b c ++ * mapped block(bb_tbl[i]): ++ * 0 1 3 6 7 8 9 b ...... ++ * ATTENTION: ++ * If new bad block ocurred(n), search bmt_tbl to find ++ * a available block(x), and fill in the bb_tbl[n] = x; ++ */ ++ for (i = 1; i < bmtd.pool_lba; i++) { ++ bbt->bb_tbl[i] = find_valid_block(bbt->bb_tbl[i - 1] + 1); ++ BBT_LOG("bb_tbl[0x%x] = 0x%x", i, bbt->bb_tbl[i]); ++ if (bbt->bb_tbl[i] == 0) ++ return -1; ++ } ++ ++ /* Physical Block start Address of BMT pool */ ++ bmtd.pool_pba = bbt->bb_tbl[i - 1] + 1; ++ if (bmtd.pool_pba >= bmtd.total_blks - 2) { ++ pr_info("nand: FATAL ERR: Too many bad blocks!!\n"); ++ return -1; ++ } ++ ++ BBT_LOG("pool_pba=0x%x", bmtd.pool_pba); ++ i = 0; ++ block = bmtd.pool_pba; ++ /* ++ * The bmt table is used for runtime bad block mapping ++ * G - Good block; B - Bad block ++ * --------------------------- ++ * physical |G|G|B|G|B|B|G|G|G|G|B|G|B| ++ * --------------------------- ++ * block: 0 1 2 3 4 5 6 7 8 9 a b c ++ * What bmt_tbl[i] looks like in initial state: ++ * i: ++ * 0 1 2 3 4 5 6 7 ++ * bmt_tbl[i].block: ++ * 0 1 3 6 7 8 9 b ++ * bmt_tbl[i].mapped: ++ * N N N N N N N B ++ * N - Not mapped(Available) ++ * M - Mapped ++ * B - BMT ++ * ATTENTION: ++ * BMT always in the last valid block in pool ++ */ ++ while ((block = find_valid_block(block)) != 0) { ++ bmt_tbl(bbt)[i].block = block; ++ bmt_tbl(bbt)[i].mapped = NO_MAPPED; ++ BBT_LOG("bmt_tbl[%d].block = 0x%x", i, block); ++ block++; ++ i++; ++ } ++ ++ /* i - How many available blocks in pool, which is the length of bmt_tbl[] ++ * bmtd.bmt_blk_idx - bmt_tbl[bmtd.bmt_blk_idx].block => the BMT block ++ */ ++ bmtd.bmt_blk_idx = i - 1; ++ bmt_tbl(bbt)[bmtd.bmt_blk_idx].mapped = BMT_MAPPED; ++ ++ if (i < 1) { ++ pr_info("nand: FATAL ERR: no space to store BMT!!\n"); ++ return -1; ++ } ++ ++ pr_info("[BBT] %d available blocks in BMT pool\n", i); ++ ++ return 0; ++} ++ ++static bool is_valid_bmt(unsigned char *buf, unsigned char *fdm) ++{ ++ struct bbbt *bbt = (struct bbbt *)buf; ++ u8 *sig = (u8*)bbt->signature + MAIN_SIGNATURE_OFFSET; ++ ++ ++ if (memcmp(bbt->signature + MAIN_SIGNATURE_OFFSET, "BMT", 3) == 0 && ++ memcmp(fdm + OOB_SIGNATURE_OFFSET, "bmt", 3) == 0) { ++ if (bbt->version == BBMT_VERSION) ++ return true; ++ } ++ BBT_LOG("[BBT] BMT Version not match,upgrage preloader and uboot please! sig=%02x%02x%02x, fdm=%02x%02x%02x", ++ sig[0], sig[1], sig[2], ++ fdm[1], fdm[2], fdm[3]); ++ return false; ++} ++ ++static u16 get_bmt_index(struct bbmt *bmt) ++{ ++ int i = 0; ++ ++ while (bmt[i].block != BMT_TBL_DEF_VAL) { ++ if (bmt[i].mapped == BMT_MAPPED) ++ return i; ++ i++; ++ } ++ return 0; ++} ++ ++static int ++read_bmt(u16 block, unsigned char *dat, unsigned char *fdm, int fdm_len) ++{ ++ u32 len = bmtd.bmt_pgs << bmtd.pg_shift; ++ ++ return bbt_nand_read(blk_pg(block), dat, len, fdm, fdm_len); ++} ++ ++static struct bbbt *scan_bmt(u16 block) ++{ ++ u8 fdm[4]; ++ ++ if (block < bmtd.pool_lba) ++ return NULL; ++ ++ if (read_bmt(block, bmtd.bbt_buf, fdm, sizeof(fdm))) ++ return scan_bmt(block - 1); ++ ++ if (is_valid_bmt(bmtd.bbt_buf, fdm)) { ++ bmtd.bmt_blk_idx = get_bmt_index(bmt_tbl((struct bbbt *)bmtd.bbt_buf)); ++ if (bmtd.bmt_blk_idx == 0) { ++ pr_info("[BBT] FATAL ERR: bmt block index is wrong!\n"); ++ return NULL; ++ } ++ pr_info("[BBT] BMT.v2 is found at 0x%x\n", block); ++ return (struct bbbt *)bmtd.bbt_buf; ++ } else ++ return scan_bmt(block - 1); ++} ++ ++/* Write the Burner Bad Block Table to Nand Flash ++ * n - write BMT to bmt_tbl[n] ++ */ ++static u16 upload_bmt(struct bbbt *bbt, int n) ++{ ++ u16 block; ++ ++retry: ++ if (n < 0 || bmt_tbl(bbt)[n].mapped == NORMAL_MAPPED) { ++ pr_info("nand: FATAL ERR: no space to store BMT!\n"); ++ return (u16)-1; ++ } ++ ++ block = bmt_tbl(bbt)[n].block; ++ BBT_LOG("n = 0x%x, block = 0x%x", n, block); ++ if (bbt_nand_erase(block)) { ++ bmt_tbl(bbt)[n].block = 0; ++ /* erase failed, try the previous block: bmt_tbl[n - 1].block */ ++ n--; ++ goto retry; ++ } ++ ++ /* The signature offset is fixed set to 0, ++ * oob signature offset is fixed set to 1 ++ */ ++ memcpy(bbt->signature + MAIN_SIGNATURE_OFFSET, "BMT", 3); ++ bbt->version = BBMT_VERSION; ++ ++ if (write_bmt(block, (unsigned char *)bbt)) { ++ bmt_tbl(bbt)[n].block = 0; ++ ++ /* write failed, try the previous block in bmt_tbl[n - 1] */ ++ n--; ++ goto retry; ++ } ++ ++ /* Return the current index(n) of BMT pool (bmt_tbl[n]) */ ++ return n; ++} ++ ++static u16 find_valid_block_in_pool(struct bbbt *bbt) ++{ ++ int i; ++ ++ if (bmtd.bmt_blk_idx == 0) ++ goto error; ++ ++ for (i = 0; i < bmtd.bmt_blk_idx; i++) { ++ if (bmt_tbl(bbt)[i].block != 0 && bmt_tbl(bbt)[i].mapped == NO_MAPPED) { ++ bmt_tbl(bbt)[i].mapped = NORMAL_MAPPED; ++ return bmt_tbl(bbt)[i].block; ++ } ++ } ++ ++error: ++ pr_info("nand: FATAL ERR: BMT pool is run out!\n"); ++ return 0; ++} ++ ++/* We met a bad block, mark it as bad and map it to a valid block in pool, ++ * if it's a write failure, we need to write the data to mapped block ++ */ ++static bool remap_block_v2(u16 block, u16 mapped_block, int copy_len) ++{ ++ u16 new_block; ++ struct bbbt *bbt; ++ ++ bbt = bmtd.bbt; ++ new_block = find_valid_block_in_pool(bbt); ++ if (new_block == 0) ++ return false; ++ ++ /* Map new bad block to available block in pool */ ++ bbt->bb_tbl[block] = new_block; ++ ++ /* Erase new block */ ++ bbt_nand_erase(new_block); ++ if (copy_len > 0) ++ bbt_nand_copy(new_block, mapped_block, copy_len); ++ ++ bmtd.bmt_blk_idx = upload_bmt(bbt, bmtd.bmt_blk_idx); ++ ++ return true; ++} ++ ++static int get_mapping_block_index_v2(int block) ++{ ++ int start, end; ++ ++ if (block >= bmtd.pool_lba) ++ return block; ++ ++ if (!mapping_block_in_range(block, &start, &end)) ++ return block; ++ ++ return bmtd.bbt->bb_tbl[block]; ++} ++ ++static void ++unmap_block_v2(u16 block) ++{ ++ bmtd.bbt->bb_tbl[block] = block; ++ bmtd.bmt_blk_idx = upload_bmt(bmtd.bbt, bmtd.bmt_blk_idx); ++} ++ ++static unsigned long * ++mtk_bmt_get_mapping_mask(void) ++{ ++ struct bbmt *bbmt = bmt_tbl(bmtd.bbt); ++ int main_blocks = bmtd.mtd->size >> bmtd.blk_shift; ++ unsigned long *used; ++ int i, k; ++ ++ used = kcalloc(sizeof(unsigned long), BIT_WORD(bmtd.bmt_blk_idx) + 1, GFP_KERNEL); ++ if (!used) ++ return NULL; ++ ++ for (i = 1; i < main_blocks; i++) { ++ if (bmtd.bbt->bb_tbl[i] == i) ++ continue; ++ ++ for (k = 0; k < bmtd.bmt_blk_idx; k++) { ++ if (bmtd.bbt->bb_tbl[i] != bbmt[k].block) ++ continue; ++ ++ set_bit(k, used); ++ break; ++ } ++ } ++ ++ return used; ++} ++ ++static int mtk_bmt_debug_v2(void *data, u64 val) ++{ ++ struct bbmt *bbmt = bmt_tbl(bmtd.bbt); ++ struct mtd_info *mtd = bmtd.mtd; ++ unsigned long *used; ++ int main_blocks = mtd->size >> bmtd.blk_shift; ++ int n_remap = 0; ++ int i; ++ ++ used = mtk_bmt_get_mapping_mask(); ++ if (!used) ++ return -ENOMEM; ++ ++ switch (val) { ++ case 0: ++ for (i = 1; i < main_blocks; i++) { ++ if (bmtd.bbt->bb_tbl[i] == i) ++ continue; ++ ++ printk("remap [%x->%x]\n", i, bmtd.bbt->bb_tbl[i]); ++ n_remap++; ++ } ++ for (i = 0; i <= bmtd.bmt_blk_idx; i++) { ++ char c; ++ ++ switch (bbmt[i].mapped) { ++ case NO_MAPPED: ++ continue; ++ case NORMAL_MAPPED: ++ c = 'm'; ++ if (test_bit(i, used)) ++ c = 'M'; ++ break; ++ case BMT_MAPPED: ++ c = 'B'; ++ break; ++ default: ++ c = 'X'; ++ break; ++ } ++ printk("[%x:%c] = 0x%x\n", i, c, bbmt[i].block); ++ } ++ break; ++ case 100: ++ for (i = 0; i <= bmtd.bmt_blk_idx; i++) { ++ if (bbmt[i].mapped != NORMAL_MAPPED) ++ continue; ++ ++ if (test_bit(i, used)) ++ continue; ++ ++ n_remap++; ++ bbmt[i].mapped = NO_MAPPED; ++ printk("free block [%d:%x]\n", i, bbmt[i].block); ++ } ++ if (n_remap) ++ bmtd.bmt_blk_idx = upload_bmt(bmtd.bbt, bmtd.bmt_blk_idx); ++ break; ++ } ++ ++ kfree(used); ++ ++ return 0; ++} ++ ++static int mtk_bmt_init_v2(struct device_node *np) ++{ ++ u32 bmt_pool_size, bmt_table_size; ++ u32 bufsz, block; ++ u16 pmt_block; ++ ++ if (of_property_read_u32(np, "mediatek,bmt-pool-size", ++ &bmt_pool_size) != 0) ++ bmt_pool_size = 80; ++ ++ if (of_property_read_u8(np, "mediatek,bmt-oob-offset", ++ &bmtd.oob_offset) != 0) ++ bmtd.oob_offset = 0; ++ ++ if (of_property_read_u32(np, "mediatek,bmt-table-size", ++ &bmt_table_size) != 0) ++ bmt_table_size = 0x2000U; ++ ++ bmtd.table_size = bmt_table_size; ++ ++ pmt_block = bmtd.total_blks - bmt_pool_size - 2; ++ ++ bmtd.mtd->size = pmt_block << bmtd.blk_shift; ++ ++ /* ++ * --------------------------------------- ++ * | PMT(2blks) | BMT POOL(totalblks * 2%) | ++ * --------------------------------------- ++ * ^ ^ ++ * | | ++ * pmt_block pmt_block + 2blocks(pool_lba) ++ * ++ * ATTETION!!!!!! ++ * The blocks ahead of the boundary block are stored in bb_tbl ++ * and blocks behind are stored in bmt_tbl ++ */ ++ ++ bmtd.pool_lba = (u16)(pmt_block + 2); ++ bmtd.bb_max = bmtd.total_blks * BBPOOL_RATIO / 100; ++ ++ bufsz = round_up(sizeof(struct bbbt) + ++ bmt_table_size * sizeof(struct bbmt), bmtd.pg_size); ++ bmtd.bmt_pgs = bufsz >> bmtd.pg_shift; ++ ++ bmtd.bbt_buf = kzalloc(bufsz, GFP_KERNEL); ++ if (!bmtd.bbt_buf) ++ return -ENOMEM; ++ ++ memset(bmtd.bbt_buf, 0xff, bufsz); ++ ++ /* Scanning start from the first page of the last block ++ * of whole flash ++ */ ++ bmtd.bbt = scan_bmt(bmtd.total_blks - 1); ++ if (!bmtd.bbt) { ++ /* BMT not found */ ++ if (bmtd.total_blks > BB_TABLE_MAX + BMT_TABLE_MAX) { ++ pr_info("nand: FATAL: Too many blocks, can not support!\n"); ++ return -1; ++ } ++ ++ bmtd.bbt = (struct bbbt *)bmtd.bbt_buf; ++ memset(bmt_tbl(bmtd.bbt), BMT_TBL_DEF_VAL, ++ bmtd.table_size * sizeof(struct bbmt)); ++ ++ if (scan_bad_blocks(bmtd.bbt)) ++ return -1; ++ ++ /* BMT always in the last valid block in pool */ ++ bmtd.bmt_blk_idx = upload_bmt(bmtd.bbt, bmtd.bmt_blk_idx); ++ block = bmt_tbl(bmtd.bbt)[bmtd.bmt_blk_idx].block; ++ pr_notice("[BBT] BMT.v2 is written into PBA:0x%x\n", block); ++ ++ if (bmtd.bmt_blk_idx == 0) ++ pr_info("nand: Warning: no available block in BMT pool!\n"); ++ else if (bmtd.bmt_blk_idx == (u16)-1) ++ return -1; ++ } ++ ++ return 0; ++} ++ ++ ++const struct mtk_bmt_ops mtk_bmt_v2_ops = { ++ .sig = "bmt", ++ .sig_len = 3, ++ .init = mtk_bmt_init_v2, ++ .remap_block = remap_block_v2, ++ .unmap_block = unmap_block_v2, ++ .get_mapping_block = get_mapping_block_index_v2, ++ .debug = mtk_bmt_debug_v2, ++}; +diff --git a/target/linux/ramips/files/include/linux/mtd/mtk_bmt.h b/target/linux/ramips/files/include/linux/mtd/mtk_bmt.h +new file mode 100644 +index 0000000000..cbb6d04d89 +--- /dev/null ++++ b/target/linux/ramips/files/include/linux/mtd/mtk_bmt.h +@@ -0,0 +1,18 @@ ++#ifndef __MTK_BMT_H ++#define __MTK_BMT_H ++ ++#ifdef CONFIG_MTD_NAND_MTK_BMT ++int mtk_bmt_attach(struct mtd_info *mtd); ++void mtk_bmt_detach(struct mtd_info *mtd); ++#else ++static inline int mtk_bmt_attach(struct mtd_info *mtd) ++{ ++ return 0; ++} ++ ++static inline void mtk_bmt_detach(struct mtd_info *mtd) ++{ ++} ++#endif ++ ++#endif +diff --git a/target/linux/ramips/mt7621/config-5.4 b/target/linux/ramips/mt7621/config-5.4 +index 1484fc0269..ae4a0679bd 100644 +--- a/target/linux/ramips/mt7621/config-5.4 ++++ b/target/linux/ramips/mt7621/config-5.4 +@@ -158,6 +158,7 @@ CONFIG_MTD_CMDLINE_PARTS=y + CONFIG_MTD_NAND_CORE=y + CONFIG_MTD_NAND_ECC_SW_HAMMING=y + CONFIG_MTD_NAND_MT7621=y ++CONFIG_MTD_NAND_MTK_BMT=y + CONFIG_MTD_PHYSMAP=y + CONFIG_MTD_RAW_NAND=y + CONFIG_MTD_ROUTERBOOT_PARTS=y +diff --git a/target/linux/ramips/patches-5.4/430-mtk-bmt-support.patch b/target/linux/ramips/patches-5.4/430-mtk-bmt-support.patch +new file mode 100644 +index 0000000000..9533e4a7ee +--- /dev/null ++++ b/target/linux/ramips/patches-5.4/430-mtk-bmt-support.patch +@@ -0,0 +1,23 @@ ++--- a/drivers/mtd/nand/Kconfig +++++ b/drivers/mtd/nand/Kconfig ++@@ -2,6 +2,10 @@ ++ config MTD_NAND_CORE ++ tristate ++ +++config MTD_NAND_MTK_BMT +++ bool "Support MediaTek NAND Bad-block Management Table" +++ default n +++ ++ source "drivers/mtd/nand/onenand/Kconfig" ++ source "drivers/mtd/nand/raw/Kconfig" ++ source "drivers/mtd/nand/spi/Kconfig" ++--- a/drivers/mtd/nand/Makefile +++++ b/drivers/mtd/nand/Makefile ++@@ -2,6 +2,7 @@ ++ ++ nandcore-objs := core.o bbt.o ++ obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o +++obj-$(CONFIG_MTD_NAND_MTK_BMT) += mtk_bmt.o mtk_bmt_v2.o mtk_bmt_bbt.o mtk_bmt_nmbm.o ++ ++ obj-y += onenand/ ++ obj-y += raw/ +-- +2.25.1 + diff --git a/patches/ramips/0008-ramips-move-mt7621_nand-driver-to-files.patch b/patches/ramips/0008-ramips-move-mt7621_nand-driver-to-files.patch new file mode 100644 index 000000000..cdb600e4e --- /dev/null +++ b/patches/ramips/0008-ramips-move-mt7621_nand-driver-to-files.patch @@ -0,0 +1,2740 @@ +From 897e51080f5d0e9bf99ac7108e7444319b8c6f99 Mon Sep 17 00:00:00 2001 +From: Stijn Tintel +Date: Wed, 19 Jan 2022 15:44:58 +0200 +Subject: [PATCH 08/14] ramips: move mt7621_nand driver to files + +The patch was rejected by upstream. The mtk_nand driver should be +modified to support the mt7621 flash controller instead. As there is no +newer version to backport, or no upstream version to fix bugs, let's +move the driver to the files dir under the ramips target. This makes it +easier to make changes to the driver while waiting for mt7621 support to +land in mtk_nand. + +Signed-off-by: Stijn Tintel +--- + .../files/drivers/mtd/nand/raw/mt7621_nand.c | 1350 ++++++++++++++++ + ...driver-support-for-MT7621-nand-flash.patch | 1354 +---------------- + 2 files changed, 1351 insertions(+), 1353 deletions(-) + create mode 100644 target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c + +diff --git a/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c +new file mode 100644 +index 0000000000..678295a68d +--- /dev/null ++++ b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c +@@ -0,0 +1,1350 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * MediaTek MT7621 NAND Flash Controller driver ++ * ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * Author: Weijie Gao ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* NFI core registers */ ++#define NFI_CNFG 0x000 ++#define CNFG_OP_MODE_S 12 ++#define CNFG_OP_MODE_M GENMASK(14, 12) ++#define CNFG_OP_CUSTOM 6 ++#define CNFG_AUTO_FMT_EN BIT(9) ++#define CNFG_HW_ECC_EN BIT(8) ++#define CNFG_BYTE_RW BIT(6) ++#define CNFG_READ_MODE BIT(1) ++ ++#define NFI_PAGEFMT 0x004 ++#define PAGEFMT_FDM_ECC_S 12 ++#define PAGEFMT_FDM_ECC_M GENMASK(15, 12) ++#define PAGEFMT_FDM_S 8 ++#define PAGEFMT_FDM_M GENMASK(11, 8) ++#define PAGEFMT_SPARE_S 4 ++#define PAGEFMT_SPARE_M GENMASK(5, 4) ++#define PAGEFMT_PAGE_S 0 ++#define PAGEFMT_PAGE_M GENMASK(1, 0) ++ ++#define NFI_CON 0x008 ++#define CON_NFI_SEC_S 12 ++#define CON_NFI_SEC_M GENMASK(15, 12) ++#define CON_NFI_BWR BIT(9) ++#define CON_NFI_BRD BIT(8) ++#define CON_NFI_RST BIT(1) ++#define CON_FIFO_FLUSH BIT(0) ++ ++#define NFI_ACCCON 0x00c ++#define ACCCON_POECS_S 28 ++#define ACCCON_POECS_MAX 0x0f ++#define ACCCON_POECS_DEF 3 ++#define ACCCON_PRECS_S 22 ++#define ACCCON_PRECS_MAX 0x3f ++#define ACCCON_PRECS_DEF 3 ++#define ACCCON_C2R_S 16 ++#define ACCCON_C2R_MAX 0x3f ++#define ACCCON_C2R_DEF 7 ++#define ACCCON_W2R_S 12 ++#define ACCCON_W2R_MAX 0x0f ++#define ACCCON_W2R_DEF 7 ++#define ACCCON_WH_S 8 ++#define ACCCON_WH_MAX 0x0f ++#define ACCCON_WH_DEF 15 ++#define ACCCON_WST_S 4 ++#define ACCCON_WST_MAX 0x0f ++#define ACCCON_WST_DEF 15 ++#define ACCCON_WST_MIN 3 ++#define ACCCON_RLT_S 0 ++#define ACCCON_RLT_MAX 0x0f ++#define ACCCON_RLT_DEF 15 ++#define ACCCON_RLT_MIN 3 ++ ++#define NFI_CMD 0x020 ++ ++#define NFI_ADDRNOB 0x030 ++#define ADDR_ROW_NOB_S 4 ++#define ADDR_ROW_NOB_M GENMASK(6, 4) ++#define ADDR_COL_NOB_S 0 ++#define ADDR_COL_NOB_M GENMASK(2, 0) ++ ++#define NFI_COLADDR 0x034 ++#define NFI_ROWADDR 0x038 ++ ++#define NFI_STRDATA 0x040 ++#define STR_DATA BIT(0) ++ ++#define NFI_CNRNB 0x044 ++#define CB2R_TIME_S 4 ++#define CB2R_TIME_M GENMASK(7, 4) ++#define STR_CNRNB BIT(0) ++ ++#define NFI_DATAW 0x050 ++#define NFI_DATAR 0x054 ++ ++#define NFI_PIO_DIRDY 0x058 ++#define PIO_DIRDY BIT(0) ++ ++#define NFI_STA 0x060 ++#define STA_NFI_FSM_S 16 ++#define STA_NFI_FSM_M GENMASK(19, 16) ++#define STA_FSM_CUSTOM_DATA 14 ++#define STA_BUSY BIT(8) ++#define STA_ADDR BIT(1) ++#define STA_CMD BIT(0) ++ ++#define NFI_ADDRCNTR 0x070 ++#define SEC_CNTR_S 12 ++#define SEC_CNTR_M GENMASK(15, 12) ++#define SEC_ADDR_S 0 ++#define SEC_ADDR_M GENMASK(9, 0) ++ ++#define NFI_CSEL 0x090 ++#define CSEL_S 0 ++#define CSEL_M GENMASK(1, 0) ++ ++#define NFI_FDM0L 0x0a0 ++#define NFI_FDML(n) (0x0a0 + ((n) << 3)) ++ ++#define NFI_FDM0M 0x0a4 ++#define NFI_FDMM(n) (0x0a4 + ((n) << 3)) ++ ++#define NFI_MASTER_STA 0x210 ++#define MAS_ADDR GENMASK(11, 9) ++#define MAS_RD GENMASK(8, 6) ++#define MAS_WR GENMASK(5, 3) ++#define MAS_RDDLY GENMASK(2, 0) ++ ++/* ECC engine registers */ ++#define ECC_ENCCON 0x000 ++#define ENC_EN BIT(0) ++ ++#define ECC_ENCCNFG 0x004 ++#define ENC_CNFG_MSG_S 16 ++#define ENC_CNFG_MSG_M GENMASK(28, 16) ++#define ENC_MODE_S 4 ++#define ENC_MODE_M GENMASK(5, 4) ++#define ENC_MODE_NFI 1 ++#define ENC_TNUM_S 0 ++#define ENC_TNUM_M GENMASK(2, 0) ++ ++#define ECC_ENCIDLE 0x00c ++#define ENC_IDLE BIT(0) ++ ++#define ECC_DECCON 0x100 ++#define DEC_EN BIT(0) ++ ++#define ECC_DECCNFG 0x104 ++#define DEC_EMPTY_EN BIT(31) ++#define DEC_CS_S 16 ++#define DEC_CS_M GENMASK(28, 16) ++#define DEC_CON_S 12 ++#define DEC_CON_M GENMASK(13, 12) ++#define DEC_CON_EL 2 ++#define DEC_MODE_S 4 ++#define DEC_MODE_M GENMASK(5, 4) ++#define DEC_MODE_NFI 1 ++#define DEC_TNUM_S 0 ++#define DEC_TNUM_M GENMASK(2, 0) ++ ++#define ECC_DECIDLE 0x10c ++#define DEC_IDLE BIT(1) ++ ++#define ECC_DECENUM 0x114 ++#define ERRNUM_S 2 ++#define ERRNUM_M GENMASK(3, 0) ++ ++#define ECC_DECDONE 0x118 ++#define DEC_DONE7 BIT(7) ++#define DEC_DONE6 BIT(6) ++#define DEC_DONE5 BIT(5) ++#define DEC_DONE4 BIT(4) ++#define DEC_DONE3 BIT(3) ++#define DEC_DONE2 BIT(2) ++#define DEC_DONE1 BIT(1) ++#define DEC_DONE0 BIT(0) ++ ++#define ECC_DECEL(n) (0x11c + (n) * 4) ++#define DEC_EL_ODD_S 16 ++#define DEC_EL_EVEN_S 0 ++#define DEC_EL_M 0x1fff ++#define DEC_EL_BYTE_POS_S 3 ++#define DEC_EL_BIT_POS_M GENMASK(2, 0) ++ ++#define ECC_FDMADDR 0x13c ++ ++/* ENCIDLE and DECIDLE */ ++#define ECC_IDLE BIT(0) ++ ++#define ACCTIMING(tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt) \ ++ ((tpoecs) << ACCCON_POECS_S | (tprecs) << ACCCON_PRECS_S | \ ++ (tc2r) << ACCCON_C2R_S | (tw2r) << ACCCON_W2R_S | \ ++ (twh) << ACCCON_WH_S | (twst) << ACCCON_WST_S | (trlt)) ++ ++#define MASTER_STA_MASK (MAS_ADDR | MAS_RD | MAS_WR | \ ++ MAS_RDDLY) ++#define NFI_RESET_TIMEOUT 1000000 ++#define NFI_CORE_TIMEOUT 500000 ++#define ECC_ENGINE_TIMEOUT 500000 ++ ++#define ECC_SECTOR_SIZE 512 ++#define ECC_PARITY_BITS 13 ++ ++#define NFI_FDM_SIZE 8 ++ ++#define MT7621_NFC_NAME "mt7621-nand" ++ ++struct mt7621_nfc { ++ struct nand_controller controller; ++ struct nand_chip nand; ++ struct clk *nfi_clk; ++ struct device *dev; ++ ++ void __iomem *nfi_regs; ++ void __iomem *ecc_regs; ++ ++ u32 spare_per_sector; ++}; ++ ++static const u16 mt7621_nfi_page_size[] = { SZ_512, SZ_2K, SZ_4K }; ++static const u8 mt7621_nfi_spare_size[] = { 16, 26, 27, 28 }; ++static const u8 mt7621_ecc_strength[] = { 4, 6, 8, 10, 12 }; ++ ++static inline u32 nfi_read32(struct mt7621_nfc *nfc, u32 reg) ++{ ++ return readl(nfc->nfi_regs + reg); ++} ++ ++static inline void nfi_write32(struct mt7621_nfc *nfc, u32 reg, u32 val) ++{ ++ writel(val, nfc->nfi_regs + reg); ++} ++ ++static inline u16 nfi_read16(struct mt7621_nfc *nfc, u32 reg) ++{ ++ return readw(nfc->nfi_regs + reg); ++} ++ ++static inline void nfi_write16(struct mt7621_nfc *nfc, u32 reg, u16 val) ++{ ++ writew(val, nfc->nfi_regs + reg); ++} ++ ++static inline void ecc_write16(struct mt7621_nfc *nfc, u32 reg, u16 val) ++{ ++ writew(val, nfc->ecc_regs + reg); ++} ++ ++static inline u32 ecc_read32(struct mt7621_nfc *nfc, u32 reg) ++{ ++ return readl(nfc->ecc_regs + reg); ++} ++ ++static inline void ecc_write32(struct mt7621_nfc *nfc, u32 reg, u32 val) ++{ ++ return writel(val, nfc->ecc_regs + reg); ++} ++ ++static inline u8 *oob_fdm_ptr(struct nand_chip *nand, int sect) ++{ ++ return nand->oob_poi + sect * NFI_FDM_SIZE; ++} ++ ++static inline u8 *oob_ecc_ptr(struct mt7621_nfc *nfc, int sect) ++{ ++ struct nand_chip *nand = &nfc->nand; ++ ++ return nand->oob_poi + nand->ecc.steps * NFI_FDM_SIZE + ++ sect * (nfc->spare_per_sector - NFI_FDM_SIZE); ++} ++ ++static inline u8 *page_data_ptr(struct nand_chip *nand, const u8 *buf, ++ int sect) ++{ ++ return (u8 *)buf + sect * nand->ecc.size; ++} ++ ++static int mt7621_ecc_wait_idle(struct mt7621_nfc *nfc, u32 reg) ++{ ++ struct device *dev = nfc->dev; ++ u32 val; ++ int ret; ++ ++ ret = readw_poll_timeout_atomic(nfc->ecc_regs + reg, val, ++ val & ECC_IDLE, 10, ++ ECC_ENGINE_TIMEOUT); ++ if (ret) { ++ dev_warn(dev, "ECC engine timed out entering idle mode\n"); ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++static int mt7621_ecc_decoder_wait_done(struct mt7621_nfc *nfc, u32 sect) ++{ ++ struct device *dev = nfc->dev; ++ u32 val; ++ int ret; ++ ++ ret = readw_poll_timeout_atomic(nfc->ecc_regs + ECC_DECDONE, val, ++ val & (1 << sect), 10, ++ ECC_ENGINE_TIMEOUT); ++ ++ if (ret) { ++ dev_warn(dev, "ECC decoder for sector %d timed out\n", ++ sect); ++ return -ETIMEDOUT; ++ } ++ ++ return 0; ++} ++ ++static void mt7621_ecc_encoder_op(struct mt7621_nfc *nfc, bool enable) ++{ ++ mt7621_ecc_wait_idle(nfc, ECC_ENCIDLE); ++ ecc_write16(nfc, ECC_ENCCON, enable ? ENC_EN : 0); ++} ++ ++static void mt7621_ecc_decoder_op(struct mt7621_nfc *nfc, bool enable) ++{ ++ mt7621_ecc_wait_idle(nfc, ECC_DECIDLE); ++ ecc_write16(nfc, ECC_DECCON, enable ? DEC_EN : 0); ++} ++ ++static int mt7621_ecc_correct_check(struct mt7621_nfc *nfc, u8 *sector_buf, ++ u8 *fdm_buf, u32 sect) ++{ ++ struct nand_chip *nand = &nfc->nand; ++ u32 decnum, num_error_bits, fdm_end_bits; ++ u32 error_locations, error_bit_loc; ++ u32 error_byte_pos, error_bit_pos; ++ int bitflips = 0; ++ u32 i; ++ ++ decnum = ecc_read32(nfc, ECC_DECENUM); ++ num_error_bits = (decnum >> (sect << ERRNUM_S)) & ERRNUM_M; ++ fdm_end_bits = (nand->ecc.size + NFI_FDM_SIZE) << 3; ++ ++ if (!num_error_bits) ++ return 0; ++ ++ if (num_error_bits == ERRNUM_M) ++ return -1; ++ ++ for (i = 0; i < num_error_bits; i++) { ++ error_locations = ecc_read32(nfc, ECC_DECEL(i / 2)); ++ error_bit_loc = (error_locations >> ((i % 2) * DEC_EL_ODD_S)) & ++ DEC_EL_M; ++ error_byte_pos = error_bit_loc >> DEC_EL_BYTE_POS_S; ++ error_bit_pos = error_bit_loc & DEC_EL_BIT_POS_M; ++ ++ if (error_bit_loc < (nand->ecc.size << 3)) { ++ if (sector_buf) { ++ sector_buf[error_byte_pos] ^= ++ (1 << error_bit_pos); ++ } ++ } else if (error_bit_loc < fdm_end_bits) { ++ if (fdm_buf) { ++ fdm_buf[error_byte_pos - nand->ecc.size] ^= ++ (1 << error_bit_pos); ++ } ++ } ++ ++ bitflips++; ++ } ++ ++ return bitflips; ++} ++ ++static int mt7621_nfc_wait_write_completion(struct mt7621_nfc *nfc, ++ struct nand_chip *nand) ++{ ++ struct device *dev = nfc->dev; ++ u16 val; ++ int ret; ++ ++ ret = readw_poll_timeout_atomic(nfc->nfi_regs + NFI_ADDRCNTR, val, ++ ((val & SEC_CNTR_M) >> SEC_CNTR_S) >= nand->ecc.steps, 10, ++ NFI_CORE_TIMEOUT); ++ ++ if (ret) { ++ dev_warn(dev, "NFI core write operation timed out\n"); ++ return -ETIMEDOUT; ++ } ++ ++ return ret; ++} ++ ++static void mt7621_nfc_hw_reset(struct mt7621_nfc *nfc) ++{ ++ u32 val; ++ int ret; ++ ++ /* reset all registers and force the NFI master to terminate */ ++ nfi_write16(nfc, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST); ++ ++ /* wait for the master to finish the last transaction */ ++ ret = readw_poll_timeout(nfc->nfi_regs + NFI_MASTER_STA, val, ++ !(val & MASTER_STA_MASK), 50, ++ NFI_RESET_TIMEOUT); ++ if (ret) { ++ dev_warn(nfc->dev, "Failed to reset NFI master in %dms\n", ++ NFI_RESET_TIMEOUT); ++ } ++ ++ /* ensure any status register affected by the NFI master is reset */ ++ nfi_write16(nfc, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST); ++ nfi_write16(nfc, NFI_STRDATA, 0); ++} ++ ++static inline void mt7621_nfc_hw_init(struct mt7621_nfc *nfc) ++{ ++ u32 acccon; ++ ++ /* ++ * CNRNB: nand ready/busy register ++ * ------------------------------- ++ * 7:4: timeout register for polling the NAND busy/ready signal ++ * 0 : poll the status of the busy/ready signal after [7:4]*16 cycles. ++ */ ++ nfi_write16(nfc, NFI_CNRNB, CB2R_TIME_M | STR_CNRNB); ++ ++ mt7621_nfc_hw_reset(nfc); ++ ++ /* Apply default access timing */ ++ acccon = ACCTIMING(ACCCON_POECS_DEF, ACCCON_PRECS_DEF, ACCCON_C2R_DEF, ++ ACCCON_W2R_DEF, ACCCON_WH_DEF, ACCCON_WST_DEF, ++ ACCCON_RLT_DEF); ++ ++ nfi_write32(nfc, NFI_ACCCON, acccon); ++} ++ ++static int mt7621_nfc_send_command(struct mt7621_nfc *nfc, u8 command) ++{ ++ struct device *dev = nfc->dev; ++ u32 val; ++ int ret; ++ ++ nfi_write32(nfc, NFI_CMD, command); ++ ++ ret = readl_poll_timeout_atomic(nfc->nfi_regs + NFI_STA, val, ++ !(val & STA_CMD), 10, ++ NFI_CORE_TIMEOUT); ++ if (ret) { ++ dev_warn(dev, "NFI core timed out entering command mode\n"); ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++static int mt7621_nfc_send_address_byte(struct mt7621_nfc *nfc, int addr) ++{ ++ struct device *dev = nfc->dev; ++ u32 val; ++ int ret; ++ ++ nfi_write32(nfc, NFI_COLADDR, addr); ++ nfi_write32(nfc, NFI_ROWADDR, 0); ++ nfi_write16(nfc, NFI_ADDRNOB, 1); ++ ++ ret = readl_poll_timeout_atomic(nfc->nfi_regs + NFI_STA, val, ++ !(val & STA_ADDR), 10, ++ NFI_CORE_TIMEOUT); ++ if (ret) { ++ dev_warn(dev, "NFI core timed out entering address mode\n"); ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++static int mt7621_nfc_send_address(struct mt7621_nfc *nfc, const u8 *addr, ++ unsigned int naddrs) ++{ ++ int ret; ++ ++ while (naddrs) { ++ ret = mt7621_nfc_send_address_byte(nfc, *addr); ++ if (ret) ++ return ret; ++ ++ addr++; ++ naddrs--; ++ } ++ ++ return 0; ++} ++ ++static void mt7621_nfc_wait_pio_ready(struct mt7621_nfc *nfc) ++{ ++ struct device *dev = nfc->dev; ++ int ret; ++ u16 val; ++ ++ ret = readw_poll_timeout_atomic(nfc->nfi_regs + NFI_PIO_DIRDY, val, ++ val & PIO_DIRDY, 10, ++ NFI_CORE_TIMEOUT); ++ if (ret < 0) ++ dev_err(dev, "NFI core PIO mode not ready\n"); ++} ++ ++static u32 mt7621_nfc_pio_read(struct mt7621_nfc *nfc, bool br) ++{ ++ u32 reg; ++ ++ /* after each byte read, the NFI_STA reg is reset by the hardware */ ++ reg = (nfi_read32(nfc, NFI_STA) & STA_NFI_FSM_M) >> STA_NFI_FSM_S; ++ if (reg != STA_FSM_CUSTOM_DATA) { ++ reg = nfi_read16(nfc, NFI_CNFG); ++ reg |= CNFG_READ_MODE | CNFG_BYTE_RW; ++ if (!br) ++ reg &= ~CNFG_BYTE_RW; ++ nfi_write16(nfc, NFI_CNFG, reg); ++ ++ /* ++ * set to max sector to allow the HW to continue reading over ++ * unaligned accesses ++ */ ++ nfi_write16(nfc, NFI_CON, CON_NFI_SEC_M | CON_NFI_BRD); ++ ++ /* trigger to fetch data */ ++ nfi_write16(nfc, NFI_STRDATA, STR_DATA); ++ } ++ ++ mt7621_nfc_wait_pio_ready(nfc); ++ ++ return nfi_read32(nfc, NFI_DATAR); ++} ++ ++static void mt7621_nfc_read_data(struct mt7621_nfc *nfc, u8 *buf, u32 len) ++{ ++ while (((uintptr_t)buf & 3) && len) { ++ *buf = mt7621_nfc_pio_read(nfc, true); ++ buf++; ++ len--; ++ } ++ ++ while (len >= 4) { ++ *(u32 *)buf = mt7621_nfc_pio_read(nfc, false); ++ buf += 4; ++ len -= 4; ++ } ++ ++ while (len) { ++ *buf = mt7621_nfc_pio_read(nfc, true); ++ buf++; ++ len--; ++ } ++} ++ ++static void mt7621_nfc_read_data_discard(struct mt7621_nfc *nfc, u32 len) ++{ ++ while (len >= 4) { ++ mt7621_nfc_pio_read(nfc, false); ++ len -= 4; ++ } ++ ++ while (len) { ++ mt7621_nfc_pio_read(nfc, true); ++ len--; ++ } ++} ++ ++static void mt7621_nfc_pio_write(struct mt7621_nfc *nfc, u32 val, bool bw) ++{ ++ u32 reg; ++ ++ reg = (nfi_read32(nfc, NFI_STA) & STA_NFI_FSM_M) >> STA_NFI_FSM_S; ++ if (reg != STA_FSM_CUSTOM_DATA) { ++ reg = nfi_read16(nfc, NFI_CNFG); ++ reg &= ~(CNFG_READ_MODE | CNFG_BYTE_RW); ++ if (bw) ++ reg |= CNFG_BYTE_RW; ++ nfi_write16(nfc, NFI_CNFG, reg); ++ ++ nfi_write16(nfc, NFI_CON, CON_NFI_SEC_M | CON_NFI_BWR); ++ nfi_write16(nfc, NFI_STRDATA, STR_DATA); ++ } ++ ++ mt7621_nfc_wait_pio_ready(nfc); ++ nfi_write32(nfc, NFI_DATAW, val); ++} ++ ++static void mt7621_nfc_write_data(struct mt7621_nfc *nfc, const u8 *buf, ++ u32 len) ++{ ++ while (((uintptr_t)buf & 3) && len) { ++ mt7621_nfc_pio_write(nfc, *buf, true); ++ buf++; ++ len--; ++ } ++ ++ while (len >= 4) { ++ mt7621_nfc_pio_write(nfc, *(const u32 *)buf, false); ++ buf += 4; ++ len -= 4; ++ } ++ ++ while (len) { ++ mt7621_nfc_pio_write(nfc, *buf, true); ++ buf++; ++ len--; ++ } ++} ++ ++static void mt7621_nfc_write_data_empty(struct mt7621_nfc *nfc, u32 len) ++{ ++ while (len >= 4) { ++ mt7621_nfc_pio_write(nfc, 0xffffffff, false); ++ len -= 4; ++ } ++ ++ while (len) { ++ mt7621_nfc_pio_write(nfc, 0xff, true); ++ len--; ++ } ++} ++ ++static int mt7621_nfc_dev_ready(struct mt7621_nfc *nfc, ++ unsigned int timeout_ms) ++{ ++ u32 val; ++ ++ return readl_poll_timeout_atomic(nfc->nfi_regs + NFI_STA, val, ++ !(val & STA_BUSY), 10, ++ timeout_ms * 1000); ++} ++ ++static int mt7621_nfc_exec_instr(struct nand_chip *nand, ++ const struct nand_op_instr *instr) ++{ ++ struct mt7621_nfc *nfc = nand_get_controller_data(nand); ++ ++ switch (instr->type) { ++ case NAND_OP_CMD_INSTR: ++ mt7621_nfc_hw_reset(nfc); ++ nfi_write16(nfc, NFI_CNFG, CNFG_OP_CUSTOM << CNFG_OP_MODE_S); ++ return mt7621_nfc_send_command(nfc, instr->ctx.cmd.opcode); ++ case NAND_OP_ADDR_INSTR: ++ return mt7621_nfc_send_address(nfc, instr->ctx.addr.addrs, ++ instr->ctx.addr.naddrs); ++ case NAND_OP_DATA_IN_INSTR: ++ mt7621_nfc_read_data(nfc, instr->ctx.data.buf.in, ++ instr->ctx.data.len); ++ return 0; ++ case NAND_OP_DATA_OUT_INSTR: ++ mt7621_nfc_write_data(nfc, instr->ctx.data.buf.out, ++ instr->ctx.data.len); ++ return 0; ++ case NAND_OP_WAITRDY_INSTR: ++ return mt7621_nfc_dev_ready(nfc, ++ instr->ctx.waitrdy.timeout_ms); ++ default: ++ WARN_ONCE(1, "unsupported NAND instruction type: %d\n", ++ instr->type); ++ ++ return -EINVAL; ++ } ++} ++ ++static int mt7621_nfc_exec_op(struct nand_chip *nand, ++ const struct nand_operation *op, bool check_only) ++{ ++ struct mt7621_nfc *nfc = nand_get_controller_data(nand); ++ int i, ret; ++ ++ if (check_only) ++ return 0; ++ ++ /* Only CS0 available */ ++ nfi_write16(nfc, NFI_CSEL, 0); ++ ++ for (i = 0; i < op->ninstrs; i++) { ++ ret = mt7621_nfc_exec_instr(nand, &op->instrs[i]); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int mt7621_nfc_setup_data_interface(struct nand_chip *nand, int csline, ++ const struct nand_data_interface *conf) ++{ ++ struct mt7621_nfc *nfc = nand_get_controller_data(nand); ++ const struct nand_sdr_timings *timings; ++ u32 acccon, temp, rate, tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt; ++ ++ if (!nfc->nfi_clk) ++ return -ENOTSUPP; ++ ++ timings = nand_get_sdr_timings(conf); ++ if (IS_ERR(timings)) ++ return -ENOTSUPP; ++ ++ rate = clk_get_rate(nfc->nfi_clk); ++ ++ /* turn clock rate into KHZ */ ++ rate /= 1000; ++ ++ tpoecs = max(timings->tALH_min, timings->tCLH_min) / 1000; ++ tpoecs = DIV_ROUND_UP(tpoecs * rate, 1000000); ++ tpoecs = min_t(u32, tpoecs, ACCCON_POECS_MAX); ++ ++ tprecs = max(timings->tCLS_min, timings->tALS_min) / 1000; ++ tprecs = DIV_ROUND_UP(tprecs * rate, 1000000); ++ tprecs = min_t(u32, tprecs, ACCCON_PRECS_MAX); ++ ++ /* sdr interface has no tCR which means CE# low to RE# low */ ++ tc2r = 0; ++ ++ tw2r = timings->tWHR_min / 1000; ++ tw2r = DIV_ROUND_UP(tw2r * rate, 1000000); ++ tw2r = DIV_ROUND_UP(tw2r - 1, 2); ++ tw2r = min_t(u32, tw2r, ACCCON_W2R_MAX); ++ ++ twh = max(timings->tREH_min, timings->tWH_min) / 1000; ++ twh = DIV_ROUND_UP(twh * rate, 1000000) - 1; ++ twh = min_t(u32, twh, ACCCON_WH_MAX); ++ ++ /* Calculate real WE#/RE# hold time in nanosecond */ ++ temp = (twh + 1) * 1000000 / rate; ++ /* nanosecond to picosecond */ ++ temp *= 1000; ++ ++ /* ++ * WE# low level time should be expaned to meet WE# pulse time ++ * and WE# cycle time at the same time. ++ */ ++ if (temp < timings->tWC_min) ++ twst = timings->tWC_min - temp; ++ else ++ twst = 0; ++ twst = max(timings->tWP_min, twst) / 1000; ++ twst = DIV_ROUND_UP(twst * rate, 1000000) - 1; ++ twst = min_t(u32, twst, ACCCON_WST_MAX); ++ ++ /* ++ * RE# low level time should be expaned to meet RE# pulse time ++ * and RE# cycle time at the same time. ++ */ ++ if (temp < timings->tRC_min) ++ trlt = timings->tRC_min - temp; ++ else ++ trlt = 0; ++ trlt = max(trlt, timings->tRP_min) / 1000; ++ trlt = DIV_ROUND_UP(trlt * rate, 1000000) - 1; ++ trlt = min_t(u32, trlt, ACCCON_RLT_MAX); ++ ++ if (csline == NAND_DATA_IFACE_CHECK_ONLY) { ++ if (twst < ACCCON_WST_MIN || trlt < ACCCON_RLT_MIN) ++ return -ENOTSUPP; ++ } ++ ++ acccon = ACCTIMING(tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt); ++ ++ dev_info(nfc->dev, "Using programmed access timing: %08x\n", acccon); ++ ++ nfi_write32(nfc, NFI_ACCCON, acccon); ++ ++ return 0; ++} ++ ++static int mt7621_nfc_calc_ecc_strength(struct mt7621_nfc *nfc, ++ u32 avail_ecc_bytes) ++{ ++ struct nand_chip *nand = &nfc->nand; ++ struct mtd_info *mtd = nand_to_mtd(nand); ++ u32 strength; ++ int i; ++ ++ strength = avail_ecc_bytes * 8 / ECC_PARITY_BITS; ++ ++ /* Find the closest supported ecc strength */ ++ for (i = ARRAY_SIZE(mt7621_ecc_strength) - 1; i >= 0; i--) { ++ if (mt7621_ecc_strength[i] <= strength) ++ break; ++ } ++ ++ if (unlikely(i < 0)) { ++ dev_err(nfc->dev, "OOB size (%u) is not supported\n", ++ mtd->oobsize); ++ return -EINVAL; ++ } ++ ++ nand->ecc.strength = mt7621_ecc_strength[i]; ++ nand->ecc.bytes = ++ DIV_ROUND_UP(nand->ecc.strength * ECC_PARITY_BITS, 8); ++ ++ dev_info(nfc->dev, "ECC strength adjusted to %u bits\n", ++ nand->ecc.strength); ++ ++ return i; ++} ++ ++static int mt7621_nfc_set_spare_per_sector(struct mt7621_nfc *nfc) ++{ ++ struct nand_chip *nand = &nfc->nand; ++ struct mtd_info *mtd = nand_to_mtd(nand); ++ u32 size; ++ int i; ++ ++ size = nand->ecc.bytes + NFI_FDM_SIZE; ++ ++ /* Find the closest supported spare size */ ++ for (i = 0; i < ARRAY_SIZE(mt7621_nfi_spare_size); i++) { ++ if (mt7621_nfi_spare_size[i] >= size) ++ break; ++ } ++ ++ if (unlikely(i >= ARRAY_SIZE(mt7621_nfi_spare_size))) { ++ dev_err(nfc->dev, "OOB size (%u) is not supported\n", ++ mtd->oobsize); ++ return -EINVAL; ++ } ++ ++ nfc->spare_per_sector = mt7621_nfi_spare_size[i]; ++ ++ return i; ++} ++ ++static int mt7621_nfc_ecc_init(struct mt7621_nfc *nfc) ++{ ++ struct nand_chip *nand = &nfc->nand; ++ struct mtd_info *mtd = nand_to_mtd(nand); ++ u32 spare_per_sector, encode_block_size, decode_block_size; ++ u32 ecc_enccfg, ecc_deccfg; ++ int ecc_cap; ++ ++ /* Only hardware ECC mode is supported */ ++ if (nand->ecc.mode != NAND_ECC_HW_SYNDROME) { ++ dev_err(nfc->dev, "Only hardware ECC mode is supported\n"); ++ return -EINVAL; ++ } ++ ++ nand->ecc.size = ECC_SECTOR_SIZE; ++ nand->ecc.steps = mtd->writesize / nand->ecc.size; ++ ++ spare_per_sector = mtd->oobsize / nand->ecc.steps; ++ ++ ecc_cap = mt7621_nfc_calc_ecc_strength(nfc, ++ spare_per_sector - NFI_FDM_SIZE); ++ if (ecc_cap < 0) ++ return ecc_cap; ++ ++ /* Sector + FDM */ ++ encode_block_size = (nand->ecc.size + NFI_FDM_SIZE) * 8; ++ ecc_enccfg = ecc_cap | (ENC_MODE_NFI << ENC_MODE_S) | ++ (encode_block_size << ENC_CNFG_MSG_S); ++ ++ /* Sector + FDM + ECC parity bits */ ++ decode_block_size = ((nand->ecc.size + NFI_FDM_SIZE) * 8) + ++ nand->ecc.strength * ECC_PARITY_BITS; ++ ecc_deccfg = ecc_cap | (DEC_MODE_NFI << DEC_MODE_S) | ++ (decode_block_size << DEC_CS_S) | ++ (DEC_CON_EL << DEC_CON_S) | DEC_EMPTY_EN; ++ ++ mt7621_ecc_encoder_op(nfc, false); ++ ecc_write32(nfc, ECC_ENCCNFG, ecc_enccfg); ++ ++ mt7621_ecc_decoder_op(nfc, false); ++ ecc_write32(nfc, ECC_DECCNFG, ecc_deccfg); ++ ++ return 0; ++} ++ ++static int mt7621_nfc_set_page_format(struct mt7621_nfc *nfc) ++{ ++ struct nand_chip *nand = &nfc->nand; ++ struct mtd_info *mtd = nand_to_mtd(nand); ++ int i, spare_size; ++ u32 pagefmt; ++ ++ spare_size = mt7621_nfc_set_spare_per_sector(nfc); ++ if (spare_size < 0) ++ return spare_size; ++ ++ for (i = 0; i < ARRAY_SIZE(mt7621_nfi_page_size); i++) { ++ if (mt7621_nfi_page_size[i] == mtd->writesize) ++ break; ++ } ++ ++ if (unlikely(i >= ARRAY_SIZE(mt7621_nfi_page_size))) { ++ dev_err(nfc->dev, "Page size (%u) is not supported\n", ++ mtd->writesize); ++ return -EINVAL; ++ } ++ ++ pagefmt = i | (spare_size << PAGEFMT_SPARE_S) | ++ (NFI_FDM_SIZE << PAGEFMT_FDM_S) | ++ (NFI_FDM_SIZE << PAGEFMT_FDM_ECC_S); ++ ++ nfi_write16(nfc, NFI_PAGEFMT, pagefmt); ++ ++ return 0; ++} ++ ++static int mt7621_nfc_attach_chip(struct nand_chip *nand) ++{ ++ struct mt7621_nfc *nfc = nand_get_controller_data(nand); ++ int ret; ++ ++ if (nand->options & NAND_BUSWIDTH_16) { ++ dev_err(nfc->dev, "16-bit buswidth is not supported"); ++ return -EINVAL; ++ } ++ ++ ret = mt7621_nfc_ecc_init(nfc); ++ if (ret) ++ return ret; ++ ++ return mt7621_nfc_set_page_format(nfc); ++} ++ ++static const struct nand_controller_ops mt7621_nfc_controller_ops = { ++ .attach_chip = mt7621_nfc_attach_chip, ++ .exec_op = mt7621_nfc_exec_op, ++ .setup_data_interface = mt7621_nfc_setup_data_interface, ++}; ++ ++static int mt7621_nfc_ooblayout_free(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *oob_region) ++{ ++ struct nand_chip *nand = mtd_to_nand(mtd); ++ ++ if (section >= nand->ecc.steps) ++ return -ERANGE; ++ ++ oob_region->length = NFI_FDM_SIZE - 1; ++ oob_region->offset = section * NFI_FDM_SIZE + 1; ++ ++ return 0; ++} ++ ++static int mt7621_nfc_ooblayout_ecc(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *oob_region) ++{ ++ struct nand_chip *nand = mtd_to_nand(mtd); ++ ++ if (section) ++ return -ERANGE; ++ ++ oob_region->offset = NFI_FDM_SIZE * nand->ecc.steps; ++ oob_region->length = mtd->oobsize - oob_region->offset; ++ ++ return 0; ++} ++ ++static const struct mtd_ooblayout_ops mt7621_nfc_ooblayout_ops = { ++ .free = mt7621_nfc_ooblayout_free, ++ .ecc = mt7621_nfc_ooblayout_ecc, ++}; ++ ++static void mt7621_nfc_write_fdm(struct mt7621_nfc *nfc) ++{ ++ struct nand_chip *nand = &nfc->nand; ++ u32 vall, valm; ++ u8 *oobptr; ++ int i, j; ++ ++ for (i = 0; i < nand->ecc.steps; i++) { ++ vall = 0; ++ valm = 0; ++ oobptr = oob_fdm_ptr(nand, i); ++ ++ for (j = 0; j < 4; j++) ++ vall |= (u32)oobptr[j] << (j * 8); ++ ++ for (j = 0; j < 4; j++) ++ valm |= (u32)oobptr[j + 4] << (j * 8); ++ ++ nfi_write32(nfc, NFI_FDML(i), vall); ++ nfi_write32(nfc, NFI_FDMM(i), valm); ++ } ++} ++ ++static void mt7621_nfc_read_sector_fdm(struct mt7621_nfc *nfc, u32 sect) ++{ ++ struct nand_chip *nand = &nfc->nand; ++ u32 vall, valm; ++ u8 *oobptr; ++ int i; ++ ++ vall = nfi_read32(nfc, NFI_FDML(sect)); ++ valm = nfi_read32(nfc, NFI_FDMM(sect)); ++ oobptr = oob_fdm_ptr(nand, sect); ++ ++ for (i = 0; i < 4; i++) ++ oobptr[i] = (vall >> (i * 8)) & 0xff; ++ ++ for (i = 0; i < 4; i++) ++ oobptr[i + 4] = (valm >> (i * 8)) & 0xff; ++} ++ ++static int mt7621_nfc_read_page_hwecc(struct nand_chip *nand, uint8_t *buf, ++ int oob_required, int page) ++{ ++ struct mt7621_nfc *nfc = nand_get_controller_data(nand); ++ struct mtd_info *mtd = nand_to_mtd(nand); ++ int bitflips = 0; ++ int rc, i; ++ ++ nand_read_page_op(nand, page, 0, NULL, 0); ++ ++ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S) | ++ CNFG_READ_MODE | CNFG_AUTO_FMT_EN | CNFG_HW_ECC_EN); ++ ++ mt7621_ecc_decoder_op(nfc, true); ++ ++ nfi_write16(nfc, NFI_CON, ++ CON_NFI_BRD | (nand->ecc.steps << CON_NFI_SEC_S)); ++ ++ for (i = 0; i < nand->ecc.steps; i++) { ++ if (buf) ++ mt7621_nfc_read_data(nfc, page_data_ptr(nand, buf, i), ++ nand->ecc.size); ++ else ++ mt7621_nfc_read_data_discard(nfc, nand->ecc.size); ++ ++ rc = mt7621_ecc_decoder_wait_done(nfc, i); ++ ++ mt7621_nfc_read_sector_fdm(nfc, i); ++ ++ if (rc < 0) { ++ bitflips = -EIO; ++ continue; ++ } ++ ++ rc = mt7621_ecc_correct_check(nfc, ++ buf ? page_data_ptr(nand, buf, i) : NULL, ++ oob_fdm_ptr(nand, i), i); ++ ++ if (rc < 0) { ++ dev_dbg(nfc->dev, ++ "Uncorrectable ECC error at page %d.%d\n", ++ page, i); ++ bitflips = -EBADMSG; ++ mtd->ecc_stats.failed++; ++ } else if (bitflips >= 0) { ++ bitflips += rc; ++ mtd->ecc_stats.corrected += rc; ++ } ++ } ++ ++ mt7621_ecc_decoder_op(nfc, false); ++ ++ nfi_write16(nfc, NFI_CON, 0); ++ ++ return bitflips; ++} ++ ++static int mt7621_nfc_read_page_raw(struct nand_chip *nand, uint8_t *buf, ++ int oob_required, int page) ++{ ++ struct mt7621_nfc *nfc = nand_get_controller_data(nand); ++ int i; ++ ++ nand_read_page_op(nand, page, 0, NULL, 0); ++ ++ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S) | ++ CNFG_READ_MODE); ++ ++ nfi_write16(nfc, NFI_CON, ++ CON_NFI_BRD | (nand->ecc.steps << CON_NFI_SEC_S)); ++ ++ for (i = 0; i < nand->ecc.steps; i++) { ++ /* Read data */ ++ if (buf) ++ mt7621_nfc_read_data(nfc, page_data_ptr(nand, buf, i), ++ nand->ecc.size); ++ else ++ mt7621_nfc_read_data_discard(nfc, nand->ecc.size); ++ ++ /* Read FDM */ ++ mt7621_nfc_read_data(nfc, oob_fdm_ptr(nand, i), NFI_FDM_SIZE); ++ ++ /* Read ECC parity data */ ++ mt7621_nfc_read_data(nfc, oob_ecc_ptr(nfc, i), ++ nfc->spare_per_sector - NFI_FDM_SIZE); ++ } ++ ++ nfi_write16(nfc, NFI_CON, 0); ++ ++ return 0; ++} ++ ++static int mt7621_nfc_read_oob_hwecc(struct nand_chip *nand, int page) ++{ ++ return mt7621_nfc_read_page_hwecc(nand, NULL, 1, page); ++} ++ ++static int mt7621_nfc_read_oob_raw(struct nand_chip *nand, int page) ++{ ++ return mt7621_nfc_read_page_raw(nand, NULL, 1, page); ++} ++ ++static int mt7621_nfc_check_empty_page(struct nand_chip *nand, const u8 *buf) ++{ ++ struct mtd_info *mtd = nand_to_mtd(nand); ++ uint32_t i, j; ++ u8 *oobptr; ++ ++ if (buf) { ++ for (i = 0; i < mtd->writesize; i++) ++ if (buf[i] != 0xff) ++ return 0; ++ } ++ ++ for (i = 0; i < nand->ecc.steps; i++) { ++ oobptr = oob_fdm_ptr(nand, i); ++ for (j = 0; j < NFI_FDM_SIZE; j++) ++ if (oobptr[j] != 0xff) ++ return 0; ++ } ++ ++ return 1; ++} ++ ++static int mt7621_nfc_write_page_hwecc(struct nand_chip *nand, ++ const uint8_t *buf, int oob_required, ++ int page) ++{ ++ struct mt7621_nfc *nfc = nand_get_controller_data(nand); ++ struct mtd_info *mtd = nand_to_mtd(nand); ++ ++ if (mt7621_nfc_check_empty_page(nand, buf)) { ++ /* ++ * MT7621 ECC engine always generates parity code for input ++ * pages, even for empty pages. Doing so will write back ECC ++ * parity code to the oob region, which means such pages will ++ * no longer be empty pages. ++ * ++ * To avoid this, stop write operation if current page is an ++ * empty page. ++ */ ++ return 0; ++ } ++ ++ nand_prog_page_begin_op(nand, page, 0, NULL, 0); ++ ++ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S) | ++ CNFG_AUTO_FMT_EN | CNFG_HW_ECC_EN); ++ ++ mt7621_ecc_encoder_op(nfc, true); ++ ++ mt7621_nfc_write_fdm(nfc); ++ ++ nfi_write16(nfc, NFI_CON, ++ CON_NFI_BWR | (nand->ecc.steps << CON_NFI_SEC_S)); ++ ++ if (buf) ++ mt7621_nfc_write_data(nfc, buf, mtd->writesize); ++ else ++ mt7621_nfc_write_data_empty(nfc, mtd->writesize); ++ ++ mt7621_nfc_wait_write_completion(nfc, nand); ++ ++ mt7621_ecc_encoder_op(nfc, false); ++ ++ nfi_write16(nfc, NFI_CON, 0); ++ ++ return nand_prog_page_end_op(nand); ++} ++ ++static int mt7621_nfc_write_page_raw(struct nand_chip *nand, ++ const uint8_t *buf, int oob_required, ++ int page) ++{ ++ struct mt7621_nfc *nfc = nand_get_controller_data(nand); ++ int i; ++ ++ nand_prog_page_begin_op(nand, page, 0, NULL, 0); ++ ++ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S)); ++ ++ nfi_write16(nfc, NFI_CON, ++ CON_NFI_BWR | (nand->ecc.steps << CON_NFI_SEC_S)); ++ ++ for (i = 0; i < nand->ecc.steps; i++) { ++ /* Write data */ ++ if (buf) ++ mt7621_nfc_write_data(nfc, page_data_ptr(nand, buf, i), ++ nand->ecc.size); ++ else ++ mt7621_nfc_write_data_empty(nfc, nand->ecc.size); ++ ++ /* Write FDM */ ++ mt7621_nfc_write_data(nfc, oob_fdm_ptr(nand, i), ++ NFI_FDM_SIZE); ++ ++ /* Write dummy ECC parity data */ ++ mt7621_nfc_write_data_empty(nfc, nfc->spare_per_sector - ++ NFI_FDM_SIZE); ++ } ++ ++ mt7621_nfc_wait_write_completion(nfc, nand); ++ ++ nfi_write16(nfc, NFI_CON, 0); ++ ++ return nand_prog_page_end_op(nand); ++} ++ ++static int mt7621_nfc_write_oob_hwecc(struct nand_chip *nand, int page) ++{ ++ return mt7621_nfc_write_page_hwecc(nand, NULL, 1, page); ++} ++ ++static int mt7621_nfc_write_oob_raw(struct nand_chip *nand, int page) ++{ ++ return mt7621_nfc_write_page_raw(nand, NULL, 1, page); ++} ++ ++static int mt7621_nfc_init_chip(struct mt7621_nfc *nfc) ++{ ++ struct nand_chip *nand = &nfc->nand; ++ struct mtd_info *mtd; ++ int ret; ++ ++ nand->controller = &nfc->controller; ++ nand_set_controller_data(nand, (void *)nfc); ++ nand_set_flash_node(nand, nfc->dev->of_node); ++ ++ nand->options |= NAND_USE_BOUNCE_BUFFER | NAND_NO_SUBPAGE_WRITE; ++ if (!nfc->nfi_clk) ++ nand->options |= NAND_KEEP_TIMINGS; ++ ++ nand->ecc.mode = NAND_ECC_HW_SYNDROME; ++ nand->ecc.read_page = mt7621_nfc_read_page_hwecc; ++ nand->ecc.read_page_raw = mt7621_nfc_read_page_raw; ++ nand->ecc.write_page = mt7621_nfc_write_page_hwecc; ++ nand->ecc.write_page_raw = mt7621_nfc_write_page_raw; ++ nand->ecc.read_oob = mt7621_nfc_read_oob_hwecc; ++ nand->ecc.read_oob_raw = mt7621_nfc_read_oob_raw; ++ nand->ecc.write_oob = mt7621_nfc_write_oob_hwecc; ++ nand->ecc.write_oob_raw = mt7621_nfc_write_oob_raw; ++ ++ mtd = nand_to_mtd(nand); ++ mtd->owner = THIS_MODULE; ++ mtd->dev.parent = nfc->dev; ++ mtd->name = MT7621_NFC_NAME; ++ mtd_set_ooblayout(mtd, &mt7621_nfc_ooblayout_ops); ++ ++ mt7621_nfc_hw_init(nfc); ++ ++ ret = nand_scan(nand, 1); ++ if (ret) ++ return ret; ++ ++ ret = mtd_device_register(mtd, NULL, 0); ++ if (ret) { ++ dev_err(nfc->dev, "Failed to register MTD: %d\n", ret); ++ nand_release(nand); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int mt7621_nfc_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct mt7621_nfc *nfc; ++ struct resource *res; ++ int ret; ++ ++ nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL); ++ if (!nfc) ++ return -ENOMEM; ++ ++ nand_controller_init(&nfc->controller); ++ nfc->controller.ops = &mt7621_nfc_controller_ops; ++ nfc->dev = dev; ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nfi"); ++ nfc->nfi_regs = devm_ioremap_resource(dev, res); ++ if (IS_ERR(nfc->nfi_regs)) { ++ ret = PTR_ERR(nfc->nfi_regs); ++ return ret; ++ } ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ecc"); ++ nfc->ecc_regs = devm_ioremap_resource(dev, res); ++ if (IS_ERR(nfc->ecc_regs)) { ++ ret = PTR_ERR(nfc->ecc_regs); ++ return ret; ++ } ++ ++ nfc->nfi_clk = devm_clk_get(dev, "nfi_clk"); ++ if (IS_ERR(nfc->nfi_clk)) { ++ dev_warn(dev, "nfi clk not provided\n"); ++ nfc->nfi_clk = NULL; ++ } else { ++ ret = clk_prepare_enable(nfc->nfi_clk); ++ if (ret) { ++ dev_err(dev, "Failed to enable nfi core clock\n"); ++ return ret; ++ } ++ } ++ ++ platform_set_drvdata(pdev, nfc); ++ ++ ret = mt7621_nfc_init_chip(nfc); ++ if (ret) { ++ dev_err(dev, "Failed to initialize nand chip\n"); ++ goto clk_disable; ++ } ++ ++ return 0; ++ ++clk_disable: ++ clk_disable_unprepare(nfc->nfi_clk); ++ ++ return ret; ++} ++ ++static int mt7621_nfc_remove(struct platform_device *pdev) ++{ ++ struct mt7621_nfc *nfc = platform_get_drvdata(pdev); ++ ++ nand_release(&nfc->nand); ++ clk_disable_unprepare(nfc->nfi_clk); ++ ++ return 0; ++} ++ ++static const struct of_device_id mt7621_nfc_id_table[] = { ++ { .compatible = "mediatek,mt7621-nfc" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, match); ++ ++static struct platform_driver mt7621_nfc_driver = { ++ .probe = mt7621_nfc_probe, ++ .remove = mt7621_nfc_remove, ++ .driver = { ++ .name = MT7621_NFC_NAME, ++ .owner = THIS_MODULE, ++ .of_match_table = mt7621_nfc_id_table, ++ }, ++}; ++module_platform_driver(mt7621_nfc_driver); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Weijie Gao "); ++MODULE_DESCRIPTION("MediaTek MT7621 NAND Flash Controller driver"); +diff --git a/target/linux/ramips/patches-5.4/0300-mtd-rawnand-add-driver-support-for-MT7621-nand-flash.patch b/target/linux/ramips/patches-5.4/0300-mtd-rawnand-add-driver-support-for-MT7621-nand-flash.patch +index ba844fed0f..586db208db 100644 +--- a/target/linux/ramips/patches-5.4/0300-mtd-rawnand-add-driver-support-for-MT7621-nand-flash.patch ++++ b/target/linux/ramips/patches-5.4/0300-mtd-rawnand-add-driver-support-for-MT7621-nand-flash.patch +@@ -45,1356 +45,4 @@ Signed-off-by: Weijie Gao + obj-$(CONFIG_MTD_NAND_MTK) += mtk_ecc.o mtk_nand.o + obj-$(CONFIG_MTD_NAND_MXIC) += mxic_nand.o + obj-$(CONFIG_MTD_NAND_TEGRA) += tegra_nand.o +---- /dev/null +-+++ b/drivers/mtd/nand/raw/mt7621_nand.c +-@@ -0,0 +1,1350 @@ +-+// SPDX-License-Identifier: GPL-2.0 +-+/* +-+ * MediaTek MT7621 NAND Flash Controller driver +-+ * +-+ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. +-+ * +-+ * Author: Weijie Gao +-+ */ +-+ +-+#include +-+#include +-+#include +-+#include +-+#include +-+#include +-+#include +-+#include +-+#include +-+#include +-+#include +-+#include +-+#include +-+ +-+/* NFI core registers */ +-+#define NFI_CNFG 0x000 +-+#define CNFG_OP_MODE_S 12 +-+#define CNFG_OP_MODE_M GENMASK(14, 12) +-+#define CNFG_OP_CUSTOM 6 +-+#define CNFG_AUTO_FMT_EN BIT(9) +-+#define CNFG_HW_ECC_EN BIT(8) +-+#define CNFG_BYTE_RW BIT(6) +-+#define CNFG_READ_MODE BIT(1) +-+ +-+#define NFI_PAGEFMT 0x004 +-+#define PAGEFMT_FDM_ECC_S 12 +-+#define PAGEFMT_FDM_ECC_M GENMASK(15, 12) +-+#define PAGEFMT_FDM_S 8 +-+#define PAGEFMT_FDM_M GENMASK(11, 8) +-+#define PAGEFMT_SPARE_S 4 +-+#define PAGEFMT_SPARE_M GENMASK(5, 4) +-+#define PAGEFMT_PAGE_S 0 +-+#define PAGEFMT_PAGE_M GENMASK(1, 0) +-+ +-+#define NFI_CON 0x008 +-+#define CON_NFI_SEC_S 12 +-+#define CON_NFI_SEC_M GENMASK(15, 12) +-+#define CON_NFI_BWR BIT(9) +-+#define CON_NFI_BRD BIT(8) +-+#define CON_NFI_RST BIT(1) +-+#define CON_FIFO_FLUSH BIT(0) +-+ +-+#define NFI_ACCCON 0x00c +-+#define ACCCON_POECS_S 28 +-+#define ACCCON_POECS_MAX 0x0f +-+#define ACCCON_POECS_DEF 3 +-+#define ACCCON_PRECS_S 22 +-+#define ACCCON_PRECS_MAX 0x3f +-+#define ACCCON_PRECS_DEF 3 +-+#define ACCCON_C2R_S 16 +-+#define ACCCON_C2R_MAX 0x3f +-+#define ACCCON_C2R_DEF 7 +-+#define ACCCON_W2R_S 12 +-+#define ACCCON_W2R_MAX 0x0f +-+#define ACCCON_W2R_DEF 7 +-+#define ACCCON_WH_S 8 +-+#define ACCCON_WH_MAX 0x0f +-+#define ACCCON_WH_DEF 15 +-+#define ACCCON_WST_S 4 +-+#define ACCCON_WST_MAX 0x0f +-+#define ACCCON_WST_DEF 15 +-+#define ACCCON_WST_MIN 3 +-+#define ACCCON_RLT_S 0 +-+#define ACCCON_RLT_MAX 0x0f +-+#define ACCCON_RLT_DEF 15 +-+#define ACCCON_RLT_MIN 3 +-+ +-+#define NFI_CMD 0x020 +-+ +-+#define NFI_ADDRNOB 0x030 +-+#define ADDR_ROW_NOB_S 4 +-+#define ADDR_ROW_NOB_M GENMASK(6, 4) +-+#define ADDR_COL_NOB_S 0 +-+#define ADDR_COL_NOB_M GENMASK(2, 0) +-+ +-+#define NFI_COLADDR 0x034 +-+#define NFI_ROWADDR 0x038 +-+ +-+#define NFI_STRDATA 0x040 +-+#define STR_DATA BIT(0) +-+ +-+#define NFI_CNRNB 0x044 +-+#define CB2R_TIME_S 4 +-+#define CB2R_TIME_M GENMASK(7, 4) +-+#define STR_CNRNB BIT(0) +-+ +-+#define NFI_DATAW 0x050 +-+#define NFI_DATAR 0x054 +-+ +-+#define NFI_PIO_DIRDY 0x058 +-+#define PIO_DIRDY BIT(0) +-+ +-+#define NFI_STA 0x060 +-+#define STA_NFI_FSM_S 16 +-+#define STA_NFI_FSM_M GENMASK(19, 16) +-+#define STA_FSM_CUSTOM_DATA 14 +-+#define STA_BUSY BIT(8) +-+#define STA_ADDR BIT(1) +-+#define STA_CMD BIT(0) +-+ +-+#define NFI_ADDRCNTR 0x070 +-+#define SEC_CNTR_S 12 +-+#define SEC_CNTR_M GENMASK(15, 12) +-+#define SEC_ADDR_S 0 +-+#define SEC_ADDR_M GENMASK(9, 0) +-+ +-+#define NFI_CSEL 0x090 +-+#define CSEL_S 0 +-+#define CSEL_M GENMASK(1, 0) +-+ +-+#define NFI_FDM0L 0x0a0 +-+#define NFI_FDML(n) (0x0a0 + ((n) << 3)) +-+ +-+#define NFI_FDM0M 0x0a4 +-+#define NFI_FDMM(n) (0x0a4 + ((n) << 3)) +-+ +-+#define NFI_MASTER_STA 0x210 +-+#define MAS_ADDR GENMASK(11, 9) +-+#define MAS_RD GENMASK(8, 6) +-+#define MAS_WR GENMASK(5, 3) +-+#define MAS_RDDLY GENMASK(2, 0) +-+ +-+/* ECC engine registers */ +-+#define ECC_ENCCON 0x000 +-+#define ENC_EN BIT(0) +-+ +-+#define ECC_ENCCNFG 0x004 +-+#define ENC_CNFG_MSG_S 16 +-+#define ENC_CNFG_MSG_M GENMASK(28, 16) +-+#define ENC_MODE_S 4 +-+#define ENC_MODE_M GENMASK(5, 4) +-+#define ENC_MODE_NFI 1 +-+#define ENC_TNUM_S 0 +-+#define ENC_TNUM_M GENMASK(2, 0) +-+ +-+#define ECC_ENCIDLE 0x00c +-+#define ENC_IDLE BIT(0) +-+ +-+#define ECC_DECCON 0x100 +-+#define DEC_EN BIT(0) +-+ +-+#define ECC_DECCNFG 0x104 +-+#define DEC_EMPTY_EN BIT(31) +-+#define DEC_CS_S 16 +-+#define DEC_CS_M GENMASK(28, 16) +-+#define DEC_CON_S 12 +-+#define DEC_CON_M GENMASK(13, 12) +-+#define DEC_CON_EL 2 +-+#define DEC_MODE_S 4 +-+#define DEC_MODE_M GENMASK(5, 4) +-+#define DEC_MODE_NFI 1 +-+#define DEC_TNUM_S 0 +-+#define DEC_TNUM_M GENMASK(2, 0) +-+ +-+#define ECC_DECIDLE 0x10c +-+#define DEC_IDLE BIT(1) +-+ +-+#define ECC_DECENUM 0x114 +-+#define ERRNUM_S 2 +-+#define ERRNUM_M GENMASK(3, 0) +-+ +-+#define ECC_DECDONE 0x118 +-+#define DEC_DONE7 BIT(7) +-+#define DEC_DONE6 BIT(6) +-+#define DEC_DONE5 BIT(5) +-+#define DEC_DONE4 BIT(4) +-+#define DEC_DONE3 BIT(3) +-+#define DEC_DONE2 BIT(2) +-+#define DEC_DONE1 BIT(1) +-+#define DEC_DONE0 BIT(0) +-+ +-+#define ECC_DECEL(n) (0x11c + (n) * 4) +-+#define DEC_EL_ODD_S 16 +-+#define DEC_EL_EVEN_S 0 +-+#define DEC_EL_M 0x1fff +-+#define DEC_EL_BYTE_POS_S 3 +-+#define DEC_EL_BIT_POS_M GENMASK(3, 0) +-+ +-+#define ECC_FDMADDR 0x13c +-+ +-+/* ENCIDLE and DECIDLE */ +-+#define ECC_IDLE BIT(0) +-+ +-+#define ACCTIMING(tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt) \ +-+ ((tpoecs) << ACCCON_POECS_S | (tprecs) << ACCCON_PRECS_S | \ +-+ (tc2r) << ACCCON_C2R_S | (tw2r) << ACCCON_W2R_S | \ +-+ (twh) << ACCCON_WH_S | (twst) << ACCCON_WST_S | (trlt)) +-+ +-+#define MASTER_STA_MASK (MAS_ADDR | MAS_RD | MAS_WR | \ +-+ MAS_RDDLY) +-+#define NFI_RESET_TIMEOUT 1000000 +-+#define NFI_CORE_TIMEOUT 500000 +-+#define ECC_ENGINE_TIMEOUT 500000 +-+ +-+#define ECC_SECTOR_SIZE 512 +-+#define ECC_PARITY_BITS 13 +-+ +-+#define NFI_FDM_SIZE 8 +-+ +-+#define MT7621_NFC_NAME "mt7621-nand" +-+ +-+struct mt7621_nfc { +-+ struct nand_controller controller; +-+ struct nand_chip nand; +-+ struct clk *nfi_clk; +-+ struct device *dev; +-+ +-+ void __iomem *nfi_regs; +-+ void __iomem *ecc_regs; +-+ +-+ u32 spare_per_sector; +-+}; +-+ +-+static const u16 mt7621_nfi_page_size[] = { SZ_512, SZ_2K, SZ_4K }; +-+static const u8 mt7621_nfi_spare_size[] = { 16, 26, 27, 28 }; +-+static const u8 mt7621_ecc_strength[] = { 4, 6, 8, 10, 12 }; +-+ +-+static inline u32 nfi_read32(struct mt7621_nfc *nfc, u32 reg) +-+{ +-+ return readl(nfc->nfi_regs + reg); +-+} +-+ +-+static inline void nfi_write32(struct mt7621_nfc *nfc, u32 reg, u32 val) +-+{ +-+ writel(val, nfc->nfi_regs + reg); +-+} +-+ +-+static inline u16 nfi_read16(struct mt7621_nfc *nfc, u32 reg) +-+{ +-+ return readw(nfc->nfi_regs + reg); +-+} +-+ +-+static inline void nfi_write16(struct mt7621_nfc *nfc, u32 reg, u16 val) +-+{ +-+ writew(val, nfc->nfi_regs + reg); +-+} +-+ +-+static inline void ecc_write16(struct mt7621_nfc *nfc, u32 reg, u16 val) +-+{ +-+ writew(val, nfc->ecc_regs + reg); +-+} +-+ +-+static inline u32 ecc_read32(struct mt7621_nfc *nfc, u32 reg) +-+{ +-+ return readl(nfc->ecc_regs + reg); +-+} +-+ +-+static inline void ecc_write32(struct mt7621_nfc *nfc, u32 reg, u32 val) +-+{ +-+ return writel(val, nfc->ecc_regs + reg); +-+} +-+ +-+static inline u8 *oob_fdm_ptr(struct nand_chip *nand, int sect) +-+{ +-+ return nand->oob_poi + sect * NFI_FDM_SIZE; +-+} +-+ +-+static inline u8 *oob_ecc_ptr(struct mt7621_nfc *nfc, int sect) +-+{ +-+ struct nand_chip *nand = &nfc->nand; +-+ +-+ return nand->oob_poi + nand->ecc.steps * NFI_FDM_SIZE + +-+ sect * (nfc->spare_per_sector - NFI_FDM_SIZE); +-+} +-+ +-+static inline u8 *page_data_ptr(struct nand_chip *nand, const u8 *buf, +-+ int sect) +-+{ +-+ return (u8 *)buf + sect * nand->ecc.size; +-+} +-+ +-+static int mt7621_ecc_wait_idle(struct mt7621_nfc *nfc, u32 reg) +-+{ +-+ struct device *dev = nfc->dev; +-+ u32 val; +-+ int ret; +-+ +-+ ret = readw_poll_timeout_atomic(nfc->ecc_regs + reg, val, +-+ val & ECC_IDLE, 10, +-+ ECC_ENGINE_TIMEOUT); +-+ if (ret) { +-+ dev_warn(dev, "ECC engine timed out entering idle mode\n"); +-+ return -EIO; +-+ } +-+ +-+ return 0; +-+} +-+ +-+static int mt7621_ecc_decoder_wait_done(struct mt7621_nfc *nfc, u32 sect) +-+{ +-+ struct device *dev = nfc->dev; +-+ u32 val; +-+ int ret; +-+ +-+ ret = readw_poll_timeout_atomic(nfc->ecc_regs + ECC_DECDONE, val, +-+ val & (1 << sect), 10, +-+ ECC_ENGINE_TIMEOUT); +-+ +-+ if (ret) { +-+ dev_warn(dev, "ECC decoder for sector %d timed out\n", +-+ sect); +-+ return -ETIMEDOUT; +-+ } +-+ +-+ return 0; +-+} +-+ +-+static void mt7621_ecc_encoder_op(struct mt7621_nfc *nfc, bool enable) +-+{ +-+ mt7621_ecc_wait_idle(nfc, ECC_ENCIDLE); +-+ ecc_write16(nfc, ECC_ENCCON, enable ? ENC_EN : 0); +-+} +-+ +-+static void mt7621_ecc_decoder_op(struct mt7621_nfc *nfc, bool enable) +-+{ +-+ mt7621_ecc_wait_idle(nfc, ECC_DECIDLE); +-+ ecc_write16(nfc, ECC_DECCON, enable ? DEC_EN : 0); +-+} +-+ +-+static int mt7621_ecc_correct_check(struct mt7621_nfc *nfc, u8 *sector_buf, +-+ u8 *fdm_buf, u32 sect) +-+{ +-+ struct nand_chip *nand = &nfc->nand; +-+ u32 decnum, num_error_bits, fdm_end_bits; +-+ u32 error_locations, error_bit_loc; +-+ u32 error_byte_pos, error_bit_pos; +-+ int bitflips = 0; +-+ u32 i; +-+ +-+ decnum = ecc_read32(nfc, ECC_DECENUM); +-+ num_error_bits = (decnum >> (sect << ERRNUM_S)) & ERRNUM_M; +-+ fdm_end_bits = (nand->ecc.size + NFI_FDM_SIZE) << 3; +-+ +-+ if (!num_error_bits) +-+ return 0; +-+ +-+ if (num_error_bits == ERRNUM_M) +-+ return -1; +-+ +-+ for (i = 0; i < num_error_bits; i++) { +-+ error_locations = ecc_read32(nfc, ECC_DECEL(i / 2)); +-+ error_bit_loc = (error_locations >> ((i % 2) * DEC_EL_ODD_S)) & +-+ DEC_EL_M; +-+ error_byte_pos = error_bit_loc >> DEC_EL_BYTE_POS_S; +-+ error_bit_pos = error_bit_loc & DEC_EL_BIT_POS_M; +-+ +-+ if (error_bit_loc < (nand->ecc.size << 3)) { +-+ if (sector_buf) { +-+ sector_buf[error_byte_pos] ^= +-+ (1 << error_bit_pos); +-+ } +-+ } else if (error_bit_loc < fdm_end_bits) { +-+ if (fdm_buf) { +-+ fdm_buf[error_byte_pos - nand->ecc.size] ^= +-+ (1 << error_bit_pos); +-+ } +-+ } +-+ +-+ bitflips++; +-+ } +-+ +-+ return bitflips; +-+} +-+ +-+static int mt7621_nfc_wait_write_completion(struct mt7621_nfc *nfc, +-+ struct nand_chip *nand) +-+{ +-+ struct device *dev = nfc->dev; +-+ u16 val; +-+ int ret; +-+ +-+ ret = readw_poll_timeout_atomic(nfc->nfi_regs + NFI_ADDRCNTR, val, +-+ ((val & SEC_CNTR_M) >> SEC_CNTR_S) >= nand->ecc.steps, 10, +-+ NFI_CORE_TIMEOUT); +-+ +-+ if (ret) { +-+ dev_warn(dev, "NFI core write operation timed out\n"); +-+ return -ETIMEDOUT; +-+ } +-+ +-+ return ret; +-+} +-+ +-+static void mt7621_nfc_hw_reset(struct mt7621_nfc *nfc) +-+{ +-+ u32 val; +-+ int ret; +-+ +-+ /* reset all registers and force the NFI master to terminate */ +-+ nfi_write16(nfc, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST); +-+ +-+ /* wait for the master to finish the last transaction */ +-+ ret = readw_poll_timeout(nfc->nfi_regs + NFI_MASTER_STA, val, +-+ !(val & MASTER_STA_MASK), 50, +-+ NFI_RESET_TIMEOUT); +-+ if (ret) { +-+ dev_warn(nfc->dev, "Failed to reset NFI master in %dms\n", +-+ NFI_RESET_TIMEOUT); +-+ } +-+ +-+ /* ensure any status register affected by the NFI master is reset */ +-+ nfi_write16(nfc, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST); +-+ nfi_write16(nfc, NFI_STRDATA, 0); +-+} +-+ +-+static inline void mt7621_nfc_hw_init(struct mt7621_nfc *nfc) +-+{ +-+ u32 acccon; +-+ +-+ /* +-+ * CNRNB: nand ready/busy register +-+ * ------------------------------- +-+ * 7:4: timeout register for polling the NAND busy/ready signal +-+ * 0 : poll the status of the busy/ready signal after [7:4]*16 cycles. +-+ */ +-+ nfi_write16(nfc, NFI_CNRNB, CB2R_TIME_M | STR_CNRNB); +-+ +-+ mt7621_nfc_hw_reset(nfc); +-+ +-+ /* Apply default access timing */ +-+ acccon = ACCTIMING(ACCCON_POECS_DEF, ACCCON_PRECS_DEF, ACCCON_C2R_DEF, +-+ ACCCON_W2R_DEF, ACCCON_WH_DEF, ACCCON_WST_DEF, +-+ ACCCON_RLT_DEF); +-+ +-+ nfi_write32(nfc, NFI_ACCCON, acccon); +-+} +-+ +-+static int mt7621_nfc_send_command(struct mt7621_nfc *nfc, u8 command) +-+{ +-+ struct device *dev = nfc->dev; +-+ u32 val; +-+ int ret; +-+ +-+ nfi_write32(nfc, NFI_CMD, command); +-+ +-+ ret = readl_poll_timeout_atomic(nfc->nfi_regs + NFI_STA, val, +-+ !(val & STA_CMD), 10, +-+ NFI_CORE_TIMEOUT); +-+ if (ret) { +-+ dev_warn(dev, "NFI core timed out entering command mode\n"); +-+ return -EIO; +-+ } +-+ +-+ return 0; +-+} +-+ +-+static int mt7621_nfc_send_address_byte(struct mt7621_nfc *nfc, int addr) +-+{ +-+ struct device *dev = nfc->dev; +-+ u32 val; +-+ int ret; +-+ +-+ nfi_write32(nfc, NFI_COLADDR, addr); +-+ nfi_write32(nfc, NFI_ROWADDR, 0); +-+ nfi_write16(nfc, NFI_ADDRNOB, 1); +-+ +-+ ret = readl_poll_timeout_atomic(nfc->nfi_regs + NFI_STA, val, +-+ !(val & STA_ADDR), 10, +-+ NFI_CORE_TIMEOUT); +-+ if (ret) { +-+ dev_warn(dev, "NFI core timed out entering address mode\n"); +-+ return -EIO; +-+ } +-+ +-+ return 0; +-+} +-+ +-+static int mt7621_nfc_send_address(struct mt7621_nfc *nfc, const u8 *addr, +-+ unsigned int naddrs) +-+{ +-+ int ret; +-+ +-+ while (naddrs) { +-+ ret = mt7621_nfc_send_address_byte(nfc, *addr); +-+ if (ret) +-+ return ret; +-+ +-+ addr++; +-+ naddrs--; +-+ } +-+ +-+ return 0; +-+} +-+ +-+static void mt7621_nfc_wait_pio_ready(struct mt7621_nfc *nfc) +-+{ +-+ struct device *dev = nfc->dev; +-+ int ret; +-+ u16 val; +-+ +-+ ret = readw_poll_timeout_atomic(nfc->nfi_regs + NFI_PIO_DIRDY, val, +-+ val & PIO_DIRDY, 10, +-+ NFI_CORE_TIMEOUT); +-+ if (ret < 0) +-+ dev_err(dev, "NFI core PIO mode not ready\n"); +-+} +-+ +-+static u32 mt7621_nfc_pio_read(struct mt7621_nfc *nfc, bool br) +-+{ +-+ u32 reg; +-+ +-+ /* after each byte read, the NFI_STA reg is reset by the hardware */ +-+ reg = (nfi_read32(nfc, NFI_STA) & STA_NFI_FSM_M) >> STA_NFI_FSM_S; +-+ if (reg != STA_FSM_CUSTOM_DATA) { +-+ reg = nfi_read16(nfc, NFI_CNFG); +-+ reg |= CNFG_READ_MODE | CNFG_BYTE_RW; +-+ if (!br) +-+ reg &= ~CNFG_BYTE_RW; +-+ nfi_write16(nfc, NFI_CNFG, reg); +-+ +-+ /* +-+ * set to max sector to allow the HW to continue reading over +-+ * unaligned accesses +-+ */ +-+ nfi_write16(nfc, NFI_CON, CON_NFI_SEC_M | CON_NFI_BRD); +-+ +-+ /* trigger to fetch data */ +-+ nfi_write16(nfc, NFI_STRDATA, STR_DATA); +-+ } +-+ +-+ mt7621_nfc_wait_pio_ready(nfc); +-+ +-+ return nfi_read32(nfc, NFI_DATAR); +-+} +-+ +-+static void mt7621_nfc_read_data(struct mt7621_nfc *nfc, u8 *buf, u32 len) +-+{ +-+ while (((uintptr_t)buf & 3) && len) { +-+ *buf = mt7621_nfc_pio_read(nfc, true); +-+ buf++; +-+ len--; +-+ } +-+ +-+ while (len >= 4) { +-+ *(u32 *)buf = mt7621_nfc_pio_read(nfc, false); +-+ buf += 4; +-+ len -= 4; +-+ } +-+ +-+ while (len) { +-+ *buf = mt7621_nfc_pio_read(nfc, true); +-+ buf++; +-+ len--; +-+ } +-+} +-+ +-+static void mt7621_nfc_read_data_discard(struct mt7621_nfc *nfc, u32 len) +-+{ +-+ while (len >= 4) { +-+ mt7621_nfc_pio_read(nfc, false); +-+ len -= 4; +-+ } +-+ +-+ while (len) { +-+ mt7621_nfc_pio_read(nfc, true); +-+ len--; +-+ } +-+} +-+ +-+static void mt7621_nfc_pio_write(struct mt7621_nfc *nfc, u32 val, bool bw) +-+{ +-+ u32 reg; +-+ +-+ reg = (nfi_read32(nfc, NFI_STA) & STA_NFI_FSM_M) >> STA_NFI_FSM_S; +-+ if (reg != STA_FSM_CUSTOM_DATA) { +-+ reg = nfi_read16(nfc, NFI_CNFG); +-+ reg &= ~(CNFG_READ_MODE | CNFG_BYTE_RW); +-+ if (bw) +-+ reg |= CNFG_BYTE_RW; +-+ nfi_write16(nfc, NFI_CNFG, reg); +-+ +-+ nfi_write16(nfc, NFI_CON, CON_NFI_SEC_M | CON_NFI_BWR); +-+ nfi_write16(nfc, NFI_STRDATA, STR_DATA); +-+ } +-+ +-+ mt7621_nfc_wait_pio_ready(nfc); +-+ nfi_write32(nfc, NFI_DATAW, val); +-+} +-+ +-+static void mt7621_nfc_write_data(struct mt7621_nfc *nfc, const u8 *buf, +-+ u32 len) +-+{ +-+ while (((uintptr_t)buf & 3) && len) { +-+ mt7621_nfc_pio_write(nfc, *buf, true); +-+ buf++; +-+ len--; +-+ } +-+ +-+ while (len >= 4) { +-+ mt7621_nfc_pio_write(nfc, *(const u32 *)buf, false); +-+ buf += 4; +-+ len -= 4; +-+ } +-+ +-+ while (len) { +-+ mt7621_nfc_pio_write(nfc, *buf, true); +-+ buf++; +-+ len--; +-+ } +-+} +-+ +-+static void mt7621_nfc_write_data_empty(struct mt7621_nfc *nfc, u32 len) +-+{ +-+ while (len >= 4) { +-+ mt7621_nfc_pio_write(nfc, 0xffffffff, false); +-+ len -= 4; +-+ } +-+ +-+ while (len) { +-+ mt7621_nfc_pio_write(nfc, 0xff, true); +-+ len--; +-+ } +-+} +-+ +-+static int mt7621_nfc_dev_ready(struct mt7621_nfc *nfc, +-+ unsigned int timeout_ms) +-+{ +-+ u32 val; +-+ +-+ return readl_poll_timeout_atomic(nfc->nfi_regs + NFI_STA, val, +-+ !(val & STA_BUSY), 10, +-+ timeout_ms * 1000); +-+} +-+ +-+static int mt7621_nfc_exec_instr(struct nand_chip *nand, +-+ const struct nand_op_instr *instr) +-+{ +-+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); +-+ +-+ switch (instr->type) { +-+ case NAND_OP_CMD_INSTR: +-+ mt7621_nfc_hw_reset(nfc); +-+ nfi_write16(nfc, NFI_CNFG, CNFG_OP_CUSTOM << CNFG_OP_MODE_S); +-+ return mt7621_nfc_send_command(nfc, instr->ctx.cmd.opcode); +-+ case NAND_OP_ADDR_INSTR: +-+ return mt7621_nfc_send_address(nfc, instr->ctx.addr.addrs, +-+ instr->ctx.addr.naddrs); +-+ case NAND_OP_DATA_IN_INSTR: +-+ mt7621_nfc_read_data(nfc, instr->ctx.data.buf.in, +-+ instr->ctx.data.len); +-+ return 0; +-+ case NAND_OP_DATA_OUT_INSTR: +-+ mt7621_nfc_write_data(nfc, instr->ctx.data.buf.out, +-+ instr->ctx.data.len); +-+ return 0; +-+ case NAND_OP_WAITRDY_INSTR: +-+ return mt7621_nfc_dev_ready(nfc, +-+ instr->ctx.waitrdy.timeout_ms); +-+ default: +-+ WARN_ONCE(1, "unsupported NAND instruction type: %d\n", +-+ instr->type); +-+ +-+ return -EINVAL; +-+ } +-+} +-+ +-+static int mt7621_nfc_exec_op(struct nand_chip *nand, +-+ const struct nand_operation *op, bool check_only) +-+{ +-+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); +-+ int i, ret; +-+ +-+ if (check_only) +-+ return 0; +-+ +-+ /* Only CS0 available */ +-+ nfi_write16(nfc, NFI_CSEL, 0); +-+ +-+ for (i = 0; i < op->ninstrs; i++) { +-+ ret = mt7621_nfc_exec_instr(nand, &op->instrs[i]); +-+ if (ret) +-+ return ret; +-+ } +-+ +-+ return 0; +-+} +-+ +-+static int mt7621_nfc_setup_data_interface(struct nand_chip *nand, int csline, +-+ const struct nand_data_interface *conf) +-+{ +-+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); +-+ const struct nand_sdr_timings *timings; +-+ u32 acccon, temp, rate, tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt; +-+ +-+ if (!nfc->nfi_clk) +-+ return -ENOTSUPP; +-+ +-+ timings = nand_get_sdr_timings(conf); +-+ if (IS_ERR(timings)) +-+ return -ENOTSUPP; +-+ +-+ rate = clk_get_rate(nfc->nfi_clk); +-+ +-+ /* turn clock rate into KHZ */ +-+ rate /= 1000; +-+ +-+ tpoecs = max(timings->tALH_min, timings->tCLH_min) / 1000; +-+ tpoecs = DIV_ROUND_UP(tpoecs * rate, 1000000); +-+ tpoecs = min_t(u32, tpoecs, ACCCON_POECS_MAX); +-+ +-+ tprecs = max(timings->tCLS_min, timings->tALS_min) / 1000; +-+ tprecs = DIV_ROUND_UP(tprecs * rate, 1000000); +-+ tprecs = min_t(u32, tprecs, ACCCON_PRECS_MAX); +-+ +-+ /* sdr interface has no tCR which means CE# low to RE# low */ +-+ tc2r = 0; +-+ +-+ tw2r = timings->tWHR_min / 1000; +-+ tw2r = DIV_ROUND_UP(tw2r * rate, 1000000); +-+ tw2r = DIV_ROUND_UP(tw2r - 1, 2); +-+ tw2r = min_t(u32, tw2r, ACCCON_W2R_MAX); +-+ +-+ twh = max(timings->tREH_min, timings->tWH_min) / 1000; +-+ twh = DIV_ROUND_UP(twh * rate, 1000000) - 1; +-+ twh = min_t(u32, twh, ACCCON_WH_MAX); +-+ +-+ /* Calculate real WE#/RE# hold time in nanosecond */ +-+ temp = (twh + 1) * 1000000 / rate; +-+ /* nanosecond to picosecond */ +-+ temp *= 1000; +-+ +-+ /* +-+ * WE# low level time should be expaned to meet WE# pulse time +-+ * and WE# cycle time at the same time. +-+ */ +-+ if (temp < timings->tWC_min) +-+ twst = timings->tWC_min - temp; +-+ else +-+ twst = 0; +-+ twst = max(timings->tWP_min, twst) / 1000; +-+ twst = DIV_ROUND_UP(twst * rate, 1000000) - 1; +-+ twst = min_t(u32, twst, ACCCON_WST_MAX); +-+ +-+ /* +-+ * RE# low level time should be expaned to meet RE# pulse time +-+ * and RE# cycle time at the same time. +-+ */ +-+ if (temp < timings->tRC_min) +-+ trlt = timings->tRC_min - temp; +-+ else +-+ trlt = 0; +-+ trlt = max(trlt, timings->tRP_min) / 1000; +-+ trlt = DIV_ROUND_UP(trlt * rate, 1000000) - 1; +-+ trlt = min_t(u32, trlt, ACCCON_RLT_MAX); +-+ +-+ if (csline == NAND_DATA_IFACE_CHECK_ONLY) { +-+ if (twst < ACCCON_WST_MIN || trlt < ACCCON_RLT_MIN) +-+ return -ENOTSUPP; +-+ } +-+ +-+ acccon = ACCTIMING(tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt); +-+ +-+ dev_info(nfc->dev, "Using programmed access timing: %08x\n", acccon); +-+ +-+ nfi_write32(nfc, NFI_ACCCON, acccon); +-+ +-+ return 0; +-+} +-+ +-+static int mt7621_nfc_calc_ecc_strength(struct mt7621_nfc *nfc, +-+ u32 avail_ecc_bytes) +-+{ +-+ struct nand_chip *nand = &nfc->nand; +-+ struct mtd_info *mtd = nand_to_mtd(nand); +-+ u32 strength; +-+ int i; +-+ +-+ strength = avail_ecc_bytes * 8 / ECC_PARITY_BITS; +-+ +-+ /* Find the closest supported ecc strength */ +-+ for (i = ARRAY_SIZE(mt7621_ecc_strength) - 1; i >= 0; i--) { +-+ if (mt7621_ecc_strength[i] <= strength) +-+ break; +-+ } +-+ +-+ if (unlikely(i < 0)) { +-+ dev_err(nfc->dev, "OOB size (%u) is not supported\n", +-+ mtd->oobsize); +-+ return -EINVAL; +-+ } +-+ +-+ nand->ecc.strength = mt7621_ecc_strength[i]; +-+ nand->ecc.bytes = +-+ DIV_ROUND_UP(nand->ecc.strength * ECC_PARITY_BITS, 8); +-+ +-+ dev_info(nfc->dev, "ECC strength adjusted to %u bits\n", +-+ nand->ecc.strength); +-+ +-+ return i; +-+} +-+ +-+static int mt7621_nfc_set_spare_per_sector(struct mt7621_nfc *nfc) +-+{ +-+ struct nand_chip *nand = &nfc->nand; +-+ struct mtd_info *mtd = nand_to_mtd(nand); +-+ u32 size; +-+ int i; +-+ +-+ size = nand->ecc.bytes + NFI_FDM_SIZE; +-+ +-+ /* Find the closest supported spare size */ +-+ for (i = 0; i < ARRAY_SIZE(mt7621_nfi_spare_size); i++) { +-+ if (mt7621_nfi_spare_size[i] >= size) +-+ break; +-+ } +-+ +-+ if (unlikely(i >= ARRAY_SIZE(mt7621_nfi_spare_size))) { +-+ dev_err(nfc->dev, "OOB size (%u) is not supported\n", +-+ mtd->oobsize); +-+ return -EINVAL; +-+ } +-+ +-+ nfc->spare_per_sector = mt7621_nfi_spare_size[i]; +-+ +-+ return i; +-+} +-+ +-+static int mt7621_nfc_ecc_init(struct mt7621_nfc *nfc) +-+{ +-+ struct nand_chip *nand = &nfc->nand; +-+ struct mtd_info *mtd = nand_to_mtd(nand); +-+ u32 spare_per_sector, encode_block_size, decode_block_size; +-+ u32 ecc_enccfg, ecc_deccfg; +-+ int ecc_cap; +-+ +-+ /* Only hardware ECC mode is supported */ +-+ if (nand->ecc.mode != NAND_ECC_HW_SYNDROME) { +-+ dev_err(nfc->dev, "Only hardware ECC mode is supported\n"); +-+ return -EINVAL; +-+ } +-+ +-+ nand->ecc.size = ECC_SECTOR_SIZE; +-+ nand->ecc.steps = mtd->writesize / nand->ecc.size; +-+ +-+ spare_per_sector = mtd->oobsize / nand->ecc.steps; +-+ +-+ ecc_cap = mt7621_nfc_calc_ecc_strength(nfc, +-+ spare_per_sector - NFI_FDM_SIZE); +-+ if (ecc_cap < 0) +-+ return ecc_cap; +-+ +-+ /* Sector + FDM */ +-+ encode_block_size = (nand->ecc.size + NFI_FDM_SIZE) * 8; +-+ ecc_enccfg = ecc_cap | (ENC_MODE_NFI << ENC_MODE_S) | +-+ (encode_block_size << ENC_CNFG_MSG_S); +-+ +-+ /* Sector + FDM + ECC parity bits */ +-+ decode_block_size = ((nand->ecc.size + NFI_FDM_SIZE) * 8) + +-+ nand->ecc.strength * ECC_PARITY_BITS; +-+ ecc_deccfg = ecc_cap | (DEC_MODE_NFI << DEC_MODE_S) | +-+ (decode_block_size << DEC_CS_S) | +-+ (DEC_CON_EL << DEC_CON_S) | DEC_EMPTY_EN; +-+ +-+ mt7621_ecc_encoder_op(nfc, false); +-+ ecc_write32(nfc, ECC_ENCCNFG, ecc_enccfg); +-+ +-+ mt7621_ecc_decoder_op(nfc, false); +-+ ecc_write32(nfc, ECC_DECCNFG, ecc_deccfg); +-+ +-+ return 0; +-+} +-+ +-+static int mt7621_nfc_set_page_format(struct mt7621_nfc *nfc) +-+{ +-+ struct nand_chip *nand = &nfc->nand; +-+ struct mtd_info *mtd = nand_to_mtd(nand); +-+ int i, spare_size; +-+ u32 pagefmt; +-+ +-+ spare_size = mt7621_nfc_set_spare_per_sector(nfc); +-+ if (spare_size < 0) +-+ return spare_size; +-+ +-+ for (i = 0; i < ARRAY_SIZE(mt7621_nfi_page_size); i++) { +-+ if (mt7621_nfi_page_size[i] == mtd->writesize) +-+ break; +-+ } +-+ +-+ if (unlikely(i >= ARRAY_SIZE(mt7621_nfi_page_size))) { +-+ dev_err(nfc->dev, "Page size (%u) is not supported\n", +-+ mtd->writesize); +-+ return -EINVAL; +-+ } +-+ +-+ pagefmt = i | (spare_size << PAGEFMT_SPARE_S) | +-+ (NFI_FDM_SIZE << PAGEFMT_FDM_S) | +-+ (NFI_FDM_SIZE << PAGEFMT_FDM_ECC_S); +-+ +-+ nfi_write16(nfc, NFI_PAGEFMT, pagefmt); +-+ +-+ return 0; +-+} +-+ +-+static int mt7621_nfc_attach_chip(struct nand_chip *nand) +-+{ +-+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); +-+ int ret; +-+ +-+ if (nand->options & NAND_BUSWIDTH_16) { +-+ dev_err(nfc->dev, "16-bit buswidth is not supported"); +-+ return -EINVAL; +-+ } +-+ +-+ ret = mt7621_nfc_ecc_init(nfc); +-+ if (ret) +-+ return ret; +-+ +-+ return mt7621_nfc_set_page_format(nfc); +-+} +-+ +-+static const struct nand_controller_ops mt7621_nfc_controller_ops = { +-+ .attach_chip = mt7621_nfc_attach_chip, +-+ .exec_op = mt7621_nfc_exec_op, +-+ .setup_data_interface = mt7621_nfc_setup_data_interface, +-+}; +-+ +-+static int mt7621_nfc_ooblayout_free(struct mtd_info *mtd, int section, +-+ struct mtd_oob_region *oob_region) +-+{ +-+ struct nand_chip *nand = mtd_to_nand(mtd); +-+ +-+ if (section >= nand->ecc.steps) +-+ return -ERANGE; +-+ +-+ oob_region->length = NFI_FDM_SIZE - 1; +-+ oob_region->offset = section * NFI_FDM_SIZE + 1; +-+ +-+ return 0; +-+} +-+ +-+static int mt7621_nfc_ooblayout_ecc(struct mtd_info *mtd, int section, +-+ struct mtd_oob_region *oob_region) +-+{ +-+ struct nand_chip *nand = mtd_to_nand(mtd); +-+ +-+ if (section) +-+ return -ERANGE; +-+ +-+ oob_region->offset = NFI_FDM_SIZE * nand->ecc.steps; +-+ oob_region->length = mtd->oobsize - oob_region->offset; +-+ +-+ return 0; +-+} +-+ +-+static const struct mtd_ooblayout_ops mt7621_nfc_ooblayout_ops = { +-+ .free = mt7621_nfc_ooblayout_free, +-+ .ecc = mt7621_nfc_ooblayout_ecc, +-+}; +-+ +-+static void mt7621_nfc_write_fdm(struct mt7621_nfc *nfc) +-+{ +-+ struct nand_chip *nand = &nfc->nand; +-+ u32 vall, valm; +-+ u8 *oobptr; +-+ int i, j; +-+ +-+ for (i = 0; i < nand->ecc.steps; i++) { +-+ vall = 0; +-+ valm = 0; +-+ oobptr = oob_fdm_ptr(nand, i); +-+ +-+ for (j = 0; j < 4; j++) +-+ vall |= (u32)oobptr[j] << (j * 8); +-+ +-+ for (j = 0; j < 4; j++) +-+ valm |= (u32)oobptr[j + 4] << ((j - 4) * 8); +-+ +-+ nfi_write32(nfc, NFI_FDML(i), vall); +-+ nfi_write32(nfc, NFI_FDMM(i), valm); +-+ } +-+} +-+ +-+static void mt7621_nfc_read_sector_fdm(struct mt7621_nfc *nfc, u32 sect) +-+{ +-+ struct nand_chip *nand = &nfc->nand; +-+ u32 vall, valm; +-+ u8 *oobptr; +-+ int i; +-+ +-+ vall = nfi_read32(nfc, NFI_FDML(sect)); +-+ valm = nfi_read32(nfc, NFI_FDMM(sect)); +-+ oobptr = oob_fdm_ptr(nand, sect); +-+ +-+ for (i = 0; i < 4; i++) +-+ oobptr[i] = (vall >> (i * 8)) & 0xff; +-+ +-+ for (i = 0; i < 4; i++) +-+ oobptr[i + 4] = (valm >> (i * 8)) & 0xff; +-+} +-+ +-+static int mt7621_nfc_read_page_hwecc(struct nand_chip *nand, uint8_t *buf, +-+ int oob_required, int page) +-+{ +-+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); +-+ struct mtd_info *mtd = nand_to_mtd(nand); +-+ int bitflips = 0; +-+ int rc, i; +-+ +-+ nand_read_page_op(nand, page, 0, NULL, 0); +-+ +-+ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S) | +-+ CNFG_READ_MODE | CNFG_AUTO_FMT_EN | CNFG_HW_ECC_EN); +-+ +-+ mt7621_ecc_decoder_op(nfc, true); +-+ +-+ nfi_write16(nfc, NFI_CON, +-+ CON_NFI_BRD | (nand->ecc.steps << CON_NFI_SEC_S)); +-+ +-+ for (i = 0; i < nand->ecc.steps; i++) { +-+ if (buf) +-+ mt7621_nfc_read_data(nfc, page_data_ptr(nand, buf, i), +-+ nand->ecc.size); +-+ else +-+ mt7621_nfc_read_data_discard(nfc, nand->ecc.size); +-+ +-+ rc = mt7621_ecc_decoder_wait_done(nfc, i); +-+ +-+ mt7621_nfc_read_sector_fdm(nfc, i); +-+ +-+ if (rc < 0) { +-+ bitflips = -EIO; +-+ continue; +-+ } +-+ +-+ rc = mt7621_ecc_correct_check(nfc, +-+ buf ? page_data_ptr(nand, buf, i) : NULL, +-+ oob_fdm_ptr(nand, i), i); +-+ +-+ if (rc < 0) { +-+ dev_warn(nfc->dev, +-+ "Uncorrectable ECC error at page %d.%d\n", +-+ page, i); +-+ bitflips = -EBADMSG; +-+ mtd->ecc_stats.failed++; +-+ } else if (bitflips >= 0) { +-+ bitflips += rc; +-+ mtd->ecc_stats.corrected += rc; +-+ } +-+ } +-+ +-+ mt7621_ecc_decoder_op(nfc, false); +-+ +-+ nfi_write16(nfc, NFI_CON, 0); +-+ +-+ return bitflips; +-+} +-+ +-+static int mt7621_nfc_read_page_raw(struct nand_chip *nand, uint8_t *buf, +-+ int oob_required, int page) +-+{ +-+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); +-+ int i; +-+ +-+ nand_read_page_op(nand, page, 0, NULL, 0); +-+ +-+ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S) | +-+ CNFG_READ_MODE); +-+ +-+ nfi_write16(nfc, NFI_CON, +-+ CON_NFI_BRD | (nand->ecc.steps << CON_NFI_SEC_S)); +-+ +-+ for (i = 0; i < nand->ecc.steps; i++) { +-+ /* Read data */ +-+ if (buf) +-+ mt7621_nfc_read_data(nfc, page_data_ptr(nand, buf, i), +-+ nand->ecc.size); +-+ else +-+ mt7621_nfc_read_data_discard(nfc, nand->ecc.size); +-+ +-+ /* Read FDM */ +-+ mt7621_nfc_read_data(nfc, oob_fdm_ptr(nand, i), NFI_FDM_SIZE); +-+ +-+ /* Read ECC parity data */ +-+ mt7621_nfc_read_data(nfc, oob_ecc_ptr(nfc, i), +-+ nfc->spare_per_sector - NFI_FDM_SIZE); +-+ } +-+ +-+ nfi_write16(nfc, NFI_CON, 0); +-+ +-+ return 0; +-+} +-+ +-+static int mt7621_nfc_read_oob_hwecc(struct nand_chip *nand, int page) +-+{ +-+ return mt7621_nfc_read_page_hwecc(nand, NULL, 1, page); +-+} +-+ +-+static int mt7621_nfc_read_oob_raw(struct nand_chip *nand, int page) +-+{ +-+ return mt7621_nfc_read_page_raw(nand, NULL, 1, page); +-+} +-+ +-+static int mt7621_nfc_check_empty_page(struct nand_chip *nand, const u8 *buf) +-+{ +-+ struct mtd_info *mtd = nand_to_mtd(nand); +-+ uint32_t i, j; +-+ u8 *oobptr; +-+ +-+ if (buf) { +-+ for (i = 0; i < mtd->writesize; i++) +-+ if (buf[i] != 0xff) +-+ return 0; +-+ } +-+ +-+ for (i = 0; i < nand->ecc.steps; i++) { +-+ oobptr = oob_fdm_ptr(nand, i); +-+ for (j = 0; j < NFI_FDM_SIZE; j++) +-+ if (oobptr[j] != 0xff) +-+ return 0; +-+ } +-+ +-+ return 1; +-+} +-+ +-+static int mt7621_nfc_write_page_hwecc(struct nand_chip *nand, +-+ const uint8_t *buf, int oob_required, +-+ int page) +-+{ +-+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); +-+ struct mtd_info *mtd = nand_to_mtd(nand); +-+ +-+ if (mt7621_nfc_check_empty_page(nand, buf)) { +-+ /* +-+ * MT7621 ECC engine always generates parity code for input +-+ * pages, even for empty pages. Doing so will write back ECC +-+ * parity code to the oob region, which means such pages will +-+ * no longer be empty pages. +-+ * +-+ * To avoid this, stop write operation if current page is an +-+ * empty page. +-+ */ +-+ return 0; +-+ } +-+ +-+ nand_prog_page_begin_op(nand, page, 0, NULL, 0); +-+ +-+ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S) | +-+ CNFG_AUTO_FMT_EN | CNFG_HW_ECC_EN); +-+ +-+ mt7621_ecc_encoder_op(nfc, true); +-+ +-+ mt7621_nfc_write_fdm(nfc); +-+ +-+ nfi_write16(nfc, NFI_CON, +-+ CON_NFI_BWR | (nand->ecc.steps << CON_NFI_SEC_S)); +-+ +-+ if (buf) +-+ mt7621_nfc_write_data(nfc, buf, mtd->writesize); +-+ else +-+ mt7621_nfc_write_data_empty(nfc, mtd->writesize); +-+ +-+ mt7621_nfc_wait_write_completion(nfc, nand); +-+ +-+ mt7621_ecc_encoder_op(nfc, false); +-+ +-+ nfi_write16(nfc, NFI_CON, 0); +-+ +-+ return nand_prog_page_end_op(nand); +-+} +-+ +-+static int mt7621_nfc_write_page_raw(struct nand_chip *nand, +-+ const uint8_t *buf, int oob_required, +-+ int page) +-+{ +-+ struct mt7621_nfc *nfc = nand_get_controller_data(nand); +-+ int i; +-+ +-+ nand_prog_page_begin_op(nand, page, 0, NULL, 0); +-+ +-+ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S)); +-+ +-+ nfi_write16(nfc, NFI_CON, +-+ CON_NFI_BWR | (nand->ecc.steps << CON_NFI_SEC_S)); +-+ +-+ for (i = 0; i < nand->ecc.steps; i++) { +-+ /* Write data */ +-+ if (buf) +-+ mt7621_nfc_write_data(nfc, page_data_ptr(nand, buf, i), +-+ nand->ecc.size); +-+ else +-+ mt7621_nfc_write_data_empty(nfc, nand->ecc.size); +-+ +-+ /* Write FDM */ +-+ mt7621_nfc_write_data(nfc, oob_fdm_ptr(nand, i), +-+ NFI_FDM_SIZE); +-+ +-+ /* Write dummy ECC parity data */ +-+ mt7621_nfc_write_data_empty(nfc, nfc->spare_per_sector - +-+ NFI_FDM_SIZE); +-+ } +-+ +-+ mt7621_nfc_wait_write_completion(nfc, nand); +-+ +-+ nfi_write16(nfc, NFI_CON, 0); +-+ +-+ return nand_prog_page_end_op(nand); +-+} +-+ +-+static int mt7621_nfc_write_oob_hwecc(struct nand_chip *nand, int page) +-+{ +-+ return mt7621_nfc_write_page_hwecc(nand, NULL, 1, page); +-+} +-+ +-+static int mt7621_nfc_write_oob_raw(struct nand_chip *nand, int page) +-+{ +-+ return mt7621_nfc_write_page_raw(nand, NULL, 1, page); +-+} +-+ +-+static int mt7621_nfc_init_chip(struct mt7621_nfc *nfc) +-+{ +-+ struct nand_chip *nand = &nfc->nand; +-+ struct mtd_info *mtd; +-+ int ret; +-+ +-+ nand->controller = &nfc->controller; +-+ nand_set_controller_data(nand, (void *)nfc); +-+ nand_set_flash_node(nand, nfc->dev->of_node); +-+ +-+ nand->options |= NAND_USE_BOUNCE_BUFFER | NAND_NO_SUBPAGE_WRITE; +-+ if (!nfc->nfi_clk) +-+ nand->options |= NAND_KEEP_TIMINGS; +-+ +-+ nand->ecc.mode = NAND_ECC_HW_SYNDROME; +-+ nand->ecc.read_page = mt7621_nfc_read_page_hwecc; +-+ nand->ecc.read_page_raw = mt7621_nfc_read_page_raw; +-+ nand->ecc.write_page = mt7621_nfc_write_page_hwecc; +-+ nand->ecc.write_page_raw = mt7621_nfc_write_page_raw; +-+ nand->ecc.read_oob = mt7621_nfc_read_oob_hwecc; +-+ nand->ecc.read_oob_raw = mt7621_nfc_read_oob_raw; +-+ nand->ecc.write_oob = mt7621_nfc_write_oob_hwecc; +-+ nand->ecc.write_oob_raw = mt7621_nfc_write_oob_raw; +-+ +-+ mtd = nand_to_mtd(nand); +-+ mtd->owner = THIS_MODULE; +-+ mtd->dev.parent = nfc->dev; +-+ mtd->name = MT7621_NFC_NAME; +-+ mtd_set_ooblayout(mtd, &mt7621_nfc_ooblayout_ops); +-+ +-+ mt7621_nfc_hw_init(nfc); +-+ +-+ ret = nand_scan(nand, 1); +-+ if (ret) +-+ return ret; +-+ +-+ ret = mtd_device_register(mtd, NULL, 0); +-+ if (ret) { +-+ dev_err(nfc->dev, "Failed to register MTD: %d\n", ret); +-+ nand_release(nand); +-+ return ret; +-+ } +-+ +-+ return 0; +-+} +-+ +-+static int mt7621_nfc_probe(struct platform_device *pdev) +-+{ +-+ struct device *dev = &pdev->dev; +-+ struct mt7621_nfc *nfc; +-+ struct resource *res; +-+ int ret; +-+ +-+ nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL); +-+ if (!nfc) +-+ return -ENOMEM; +-+ +-+ nand_controller_init(&nfc->controller); +-+ nfc->controller.ops = &mt7621_nfc_controller_ops; +-+ nfc->dev = dev; +-+ +-+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nfi"); +-+ nfc->nfi_regs = devm_ioremap_resource(dev, res); +-+ if (IS_ERR(nfc->nfi_regs)) { +-+ ret = PTR_ERR(nfc->nfi_regs); +-+ return ret; +-+ } +-+ +-+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ecc"); +-+ nfc->ecc_regs = devm_ioremap_resource(dev, res); +-+ if (IS_ERR(nfc->ecc_regs)) { +-+ ret = PTR_ERR(nfc->ecc_regs); +-+ return ret; +-+ } +-+ +-+ nfc->nfi_clk = devm_clk_get(dev, "nfi_clk"); +-+ if (IS_ERR(nfc->nfi_clk)) { +-+ dev_warn(dev, "nfi clk not provided\n"); +-+ nfc->nfi_clk = NULL; +-+ } else { +-+ ret = clk_prepare_enable(nfc->nfi_clk); +-+ if (ret) { +-+ dev_err(dev, "Failed to enable nfi core clock\n"); +-+ return ret; +-+ } +-+ } +-+ +-+ platform_set_drvdata(pdev, nfc); +-+ +-+ ret = mt7621_nfc_init_chip(nfc); +-+ if (ret) { +-+ dev_err(dev, "Failed to initialize nand chip\n"); +-+ goto clk_disable; +-+ } +-+ +-+ return 0; +-+ +-+clk_disable: +-+ clk_disable_unprepare(nfc->nfi_clk); +-+ +-+ return ret; +-+} +-+ +-+static int mt7621_nfc_remove(struct platform_device *pdev) +-+{ +-+ struct mt7621_nfc *nfc = platform_get_drvdata(pdev); +-+ +-+ nand_release(&nfc->nand); +-+ clk_disable_unprepare(nfc->nfi_clk); +-+ +-+ return 0; +-+} +-+ +-+static const struct of_device_id mt7621_nfc_id_table[] = { +-+ { .compatible = "mediatek,mt7621-nfc" }, +-+ { }, +-+}; +-+MODULE_DEVICE_TABLE(of, match); +-+ +-+static struct platform_driver mt7621_nfc_driver = { +-+ .probe = mt7621_nfc_probe, +-+ .remove = mt7621_nfc_remove, +-+ .driver = { +-+ .name = MT7621_NFC_NAME, +-+ .owner = THIS_MODULE, +-+ .of_match_table = mt7621_nfc_id_table, +-+ }, +-+}; +-+module_platform_driver(mt7621_nfc_driver); +-+ +-+MODULE_LICENSE("GPL"); +-+MODULE_AUTHOR("Weijie Gao "); +-+MODULE_DESCRIPTION("MediaTek MT7621 NAND Flash Controller driver"); ++ +-- +2.25.1 + diff --git a/patches/ramips/0009-ramips-mt7621_nand-reduce-log-verbosity.patch b/patches/ramips/0009-ramips-mt7621_nand-reduce-log-verbosity.patch new file mode 100644 index 000000000..3c4809e29 --- /dev/null +++ b/patches/ramips/0009-ramips-mt7621_nand-reduce-log-verbosity.patch @@ -0,0 +1,31 @@ +From 8ff3f57a88d867d48c2a38876baf9b389d7441df Mon Sep 17 00:00:00 2001 +From: Stijn Tintel +Date: Wed, 19 Jan 2022 17:59:51 +0200 +Subject: [PATCH 09/14] ramips: mt7621_nand: reduce log verbosity + +Avoid flooding the log with the message below by increasing the log +level to debug: + + mt7621-nand 1e003000.nand: Using programmed access timing: 31c07388 + +Signed-off-by: Stijn Tintel +--- + target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c +index 678295a68d..16493a2e97 100644 +--- a/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c ++++ b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c +@@ -759,7 +759,7 @@ static int mt7621_nfc_setup_data_interface(struct nand_chip *nand, int csline, + + acccon = ACCTIMING(tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt); + +- dev_info(nfc->dev, "Using programmed access timing: %08x\n", acccon); ++ dev_dbg(nfc->dev, "Using programmed access timing: %08x\n", acccon); + + nfi_write32(nfc, NFI_ACCCON, acccon); + +-- +2.25.1 + diff --git a/patches/ramips/0010-ramips-mt7621_nand-initialize-ECC_FDMADDR.patch b/patches/ramips/0010-ramips-mt7621_nand-initialize-ECC_FDMADDR.patch new file mode 100644 index 000000000..876d813b6 --- /dev/null +++ b/patches/ramips/0010-ramips-mt7621_nand-initialize-ECC_FDMADDR.patch @@ -0,0 +1,44 @@ +From 10d63c9973b9598a065244e9752db7aa3d78a93f Mon Sep 17 00:00:00 2001 +From: Felix Fietkau +Date: Wed, 9 Mar 2022 20:46:21 +0100 +Subject: [PATCH 10/14] ramips: mt7621_nand: initialize ECC_FDMADDR + +This is needed for the ECC controller to access FDM data + +Signed-off-by: Felix Fietkau +--- + target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c +index 16493a2e97..0751e59c37 100644 +--- a/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c ++++ b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c +@@ -214,6 +214,7 @@ struct mt7621_nfc { + struct clk *nfi_clk; + struct device *dev; + ++ u32 nfi_base; + void __iomem *nfi_regs; + void __iomem *ecc_regs; + +@@ -860,6 +861,8 @@ static int mt7621_nfc_ecc_init(struct mt7621_nfc *nfc) + (decode_block_size << DEC_CS_S) | + (DEC_CON_EL << DEC_CON_S) | DEC_EMPTY_EN; + ++ ecc_write32(nfc, ECC_FDMADDR, nfc->nfi_base + NFI_FDML(0)); ++ + mt7621_ecc_encoder_op(nfc, false); + ecc_write32(nfc, ECC_ENCCNFG, ecc_enccfg); + +@@ -1277,6 +1280,7 @@ static int mt7621_nfc_probe(struct platform_device *pdev) + nfc->dev = dev; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nfi"); ++ nfc->nfi_base = res->start; + nfc->nfi_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(nfc->nfi_regs)) { + ret = PTR_ERR(nfc->nfi_regs); +-- +2.25.1 + diff --git a/patches/ramips/0011-ramips-enable-support-for-mtk_bmt-in-the-nand-flash-.patch b/patches/ramips/0011-ramips-enable-support-for-mtk_bmt-in-the-nand-flash-.patch new file mode 100644 index 000000000..1f6ae94f9 --- /dev/null +++ b/patches/ramips/0011-ramips-enable-support-for-mtk_bmt-in-the-nand-flash-.patch @@ -0,0 +1,51 @@ +From bee84b6606374bb887e70e263419b19e112f1b5c Mon Sep 17 00:00:00 2001 +From: Felix Fietkau +Date: Thu, 10 Mar 2022 11:45:00 +0100 +Subject: [PATCH 11/14] ramips: enable support for mtk_bmt in the nand flash + driver + +Signed-off-by: Felix Fietkau +--- + .../linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c +index 0751e59c37..1072450898 100644 +--- a/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c ++++ b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c +@@ -18,6 +18,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -1254,9 +1255,12 @@ static int mt7621_nfc_init_chip(struct mt7621_nfc *nfc) + if (ret) + return ret; + ++ mtk_bmt_attach(mtd); ++ + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + dev_err(nfc->dev, "Failed to register MTD: %d\n", ret); ++ mtk_bmt_detach(mtd); + nand_release(nand); + return ret; + } +@@ -1325,7 +1329,11 @@ clk_disable: + static int mt7621_nfc_remove(struct platform_device *pdev) + { + struct mt7621_nfc *nfc = platform_get_drvdata(pdev); ++ struct nand_chip *nand = &nfc->nand; ++ struct mtd_info *mtd = nand_to_mtd(nand); + ++ mtk_bmt_detach(mtd); ++ mtd_device_unregister(mtd); + nand_release(&nfc->nand); + clk_disable_unprepare(nfc->nfi_clk); + +-- +2.25.1 + diff --git a/patches/ramips/0012-ramips-skip-bbt-scan-on-mt7621.patch b/patches/ramips/0012-ramips-skip-bbt-scan-on-mt7621.patch new file mode 100644 index 000000000..5b1d1332b --- /dev/null +++ b/patches/ramips/0012-ramips-skip-bbt-scan-on-mt7621.patch @@ -0,0 +1,29 @@ +From 806708eed0809c9ee07cbcff1285a64c9767242f Mon Sep 17 00:00:00 2001 +From: Felix Fietkau +Date: Thu, 10 Mar 2022 15:20:29 +0100 +Subject: [PATCH 12/14] ramips: skip bbt scan on mt7621 + +reduces unnecessary flash reads and speeds up boot time + +Signed-off-by: Felix Fietkau +--- + target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c +index 1072450898..f01e1e8a8e 100644 +--- a/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c ++++ b/target/linux/ramips/files/drivers/mtd/nand/raw/mt7621_nand.c +@@ -1229,7 +1229,8 @@ static int mt7621_nfc_init_chip(struct mt7621_nfc *nfc) + nand_set_controller_data(nand, (void *)nfc); + nand_set_flash_node(nand, nfc->dev->of_node); + +- nand->options |= NAND_USE_BOUNCE_BUFFER | NAND_NO_SUBPAGE_WRITE; ++ nand->options |= NAND_USE_BOUNCE_BUFFER | NAND_NO_SUBPAGE_WRITE | ++ NAND_SKIP_BBTSCAN; + if (!nfc->nfi_clk) + nand->options |= NAND_KEEP_TIMINGS; + +-- +2.25.1 + diff --git a/patches/ramips/0013-kernel-mtdsplit-support-UBI-after-FIT-images.patch b/patches/ramips/0013-kernel-mtdsplit-support-UBI-after-FIT-images.patch new file mode 100644 index 000000000..124d2309f --- /dev/null +++ b/patches/ramips/0013-kernel-mtdsplit-support-UBI-after-FIT-images.patch @@ -0,0 +1,48 @@ +From 9f51c3956a80205006dc6a686d80969bd3b42ecb Mon Sep 17 00:00:00 2001 +From: Felix Fietkau +Date: Thu, 10 Mar 2022 17:32:20 +0100 +Subject: [PATCH 13/14] kernel: mtdsplit: support UBI after FIT images + +Change the partition name accordingly. Same behavior as mtdsplit_uimage + +Signed-off-by: Felix Fietkau +--- + .../generic/files/drivers/mtd/mtdsplit/mtdsplit_fit.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_fit.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_fit.c +index 5cc1658dbd..f043428949 100644 +--- a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_fit.c ++++ b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_fit.c +@@ -57,6 +57,7 @@ mtdsplit_fit_parse(struct mtd_info *mtd, + size_t fit_offset, fit_size; + size_t rootfs_offset, rootfs_size; + struct mtd_partition *parts; ++ enum mtdsplit_part_type type; + int ret; + + of_property_read_string(np, "openwrt,cmdline-match", &cmdline_match); +@@ -101,7 +102,7 @@ mtdsplit_fit_parse(struct mtd_info *mtd, + + /* Search for the rootfs partition after the FIT image */ + ret = mtd_find_rootfs_from(mtd, fit_offset + fit_size, mtd->size, +- &rootfs_offset, NULL); ++ &rootfs_offset, &type); + if (ret) { + pr_info("no rootfs found after FIT image in \"%s\"\n", + mtd->name); +@@ -118,7 +119,10 @@ mtdsplit_fit_parse(struct mtd_info *mtd, + parts[0].offset = fit_offset; + parts[0].size = mtd_rounddown_to_eb(fit_size, mtd) + mtd->erasesize; + +- parts[1].name = ROOTFS_PART_NAME; ++ if (type == MTDSPLIT_PART_TYPE_UBI) ++ parts[1].name = UBI_PART_NAME; ++ else ++ parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = rootfs_size; + +-- +2.25.1 + diff --git a/patches/ramips/0014-update-actiontec-web7200-support.patch b/patches/ramips/0014-update-actiontec-web7200-support.patch new file mode 100644 index 000000000..26a6f0c67 --- /dev/null +++ b/patches/ramips/0014-update-actiontec-web7200-support.patch @@ -0,0 +1,4429 @@ +From dccf411274a126c0f12d16ec220fbbb518858d84 Mon Sep 17 00:00:00 2001 +From: Felix Fietkau +Date: Thu, 10 Mar 2022 18:09:15 +0100 +Subject: [PATCH 14/14] update actiontec web7200 support + +Signed-off-by: Felix Fietkau +--- + .../ramips/dts/mt7621_actiontec_web7200.dts | 108 +- + .../ramips/files/drivers/mtd/nmbm/Kconfig | 35 - + .../ramips/files/drivers/mtd/nmbm/Makefile | 6 - + .../ramips/files/drivers/mtd/nmbm/nmbm-core.c | 2936 ----------------- + .../files/drivers/mtd/nmbm/nmbm-debug.h | 20 - + .../files/drivers/mtd/nmbm/nmbm-debug.inl | 0 + .../ramips/files/drivers/mtd/nmbm/nmbm-mtd.c | 795 ----- + .../files/drivers/mtd/nmbm/nmbm-private.h | 137 - + .../linux/ramips/files/include/nmbm/nmbm-os.h | 69 - + target/linux/ramips/files/include/nmbm/nmbm.h | 102 - + target/linux/ramips/image/mt7621.mk | 20 +- + .../mt7621/base-files/lib/upgrade/platform.sh | 7 +- + target/linux/ramips/mt7621/config-5.4 | 8 - + .../499-mtd-add-nmbm-support.patch | 21 - + 14 files changed, 79 insertions(+), 4185 deletions(-) + delete mode 100644 target/linux/ramips/files/drivers/mtd/nmbm/Kconfig + delete mode 100644 target/linux/ramips/files/drivers/mtd/nmbm/Makefile + delete mode 100644 target/linux/ramips/files/drivers/mtd/nmbm/nmbm-core.c + delete mode 100644 target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.h + delete mode 100644 target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.inl + delete mode 100644 target/linux/ramips/files/drivers/mtd/nmbm/nmbm-mtd.c + delete mode 100644 target/linux/ramips/files/drivers/mtd/nmbm/nmbm-private.h + delete mode 100644 target/linux/ramips/files/include/nmbm/nmbm-os.h + delete mode 100644 target/linux/ramips/files/include/nmbm/nmbm.h + delete mode 100644 target/linux/ramips/patches-5.4/499-mtd-add-nmbm-support.patch + +diff --git a/target/linux/ramips/dts/mt7621_actiontec_web7200.dts b/target/linux/ramips/dts/mt7621_actiontec_web7200.dts +index f112354ec3..a4002f01a8 100644 +--- a/target/linux/ramips/dts/mt7621_actiontec_web7200.dts ++++ b/target/linux/ramips/dts/mt7621_actiontec_web7200.dts +@@ -51,68 +51,70 @@ + }; + }; + +- nmbm { +- compatible = "generic,nmbm"; +- lower-mtd-device = <&nand>; +- forced-create; +- #size-cells = <0x01>; +- #address-cells = <0x01>; +- +- partitions { +- compatible = "fixed-partitions"; +- #size-cells = <0x01>; +- #address-cells = <0x01>; +- +- partition@0 { +- label = "u-boot"; +- reg = <0x00 0x80000>; +- read-only; +- }; +- +- partition@80000 { +- label = "u-boot-env"; +- reg = <0x80000 0x80000>; +- }; +- +- factory: partition@100000 { +- label = "factory"; +- reg = <0x100000 0x80000>; +- }; +- +- partition@180000 { +- compatible = "denx,fit"; +- label = "firmware"; +- openwrt,cmdline-match = "bootselect=0"; +- reg = <0x180000 0x3000000>; +- }; +- +- partition@3180000 { +- compatible = "denx,fit"; +- label = "firmware2"; +- openwrt,cmdline-match = "bootselect=1"; +- reg = <0x3180000 0x3000000>; +- }; +- +- partition@6980000 { +- label = "aei_data"; +- reg = <0x6980000 0x400000>; +- }; +- +- partition@6180000 { +- label = "aei_data_ext"; +- reg = <0x6180000 0x800000>; +- }; +- }; +- }; + }; + + &nand { + status = "okay"; + ++ mediatek,nmbm; ++ mediatek,bmt-remap-range = ++ <0x0 0x580000>, ++ <0x3180000 0x3580000>; ++ + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; ++ ++ partition@0 { ++ label = "u-boot"; ++ reg = <0x00 0x80000>; ++ read-only; ++ }; ++ ++ partition@80000 { ++ label = "u-boot-env"; ++ reg = <0x80000 0x80000>; ++ }; ++ ++ factory: partition@100000 { ++ label = "factory"; ++ reg = <0x100000 0x80000>; ++ }; ++ ++ partition@180000 { ++ compatible = "denx,fit"; ++ label = "firmware"; ++ openwrt,cmdline-match = "bootselect=0"; ++ reg = <0x180000 0x3000000>; ++ }; ++ ++ partition@580000 { ++ label = "ubi_1"; ++ reg = <0x580000 0x2c00000>; ++ }; ++ ++ partition@3180000 { ++ compatible = "denx,fit"; ++ label = "firmware2"; ++ openwrt,cmdline-match = "bootselect=1"; ++ reg = <0x3180000 0x3000000>; ++ }; ++ ++ partition@3580000 { ++ label = "ubi_2"; ++ reg = <0x3580000 0x2c00000>; ++ }; ++ ++ partition@6980000 { ++ label = "aei_data"; ++ reg = <0x6980000 0x400000>; ++ }; ++ ++ partition@6180000 { ++ label = "aei_data_ext"; ++ reg = <0x6180000 0x800000>; ++ }; + }; + }; + +diff --git a/target/linux/ramips/files/drivers/mtd/nmbm/Kconfig b/target/linux/ramips/files/drivers/mtd/nmbm/Kconfig +deleted file mode 100644 +index 98df305728..0000000000 +--- a/target/linux/ramips/files/drivers/mtd/nmbm/Kconfig ++++ /dev/null +@@ -1,35 +0,0 @@ +- +-config NMBM +- bool "Enable NAND mapping block management" +- default n +- select CRC32 +- +-choice +- prompt "Default log level" +- depends on NMBM +- default NMBM_LOG_LEVEL_INFO +- +-config NMBM_LOG_LEVEL_DEBUG +- bool "0 - Debug" +- +-config NMBM_LOG_LEVEL_INFO +- bool "1 - Info" +- +-config NMBM_LOG_LEVEL_WARN +- bool "2 - Warn" +- +-config NMBM_LOG_LEVEL_ERR +- bool "3 - Error" +- +-config NMBM_LOG_LEVEL_EMERG +- bool "4 - Emergency" +- +-config NMBM_LOG_LEVEL_NONE +- bool "5 - None" +- +-endchoice +- +-config NMBM_MTD +- bool "Enable MTD based NAND mapping block management" +- default n +- depends on NMBM +diff --git a/target/linux/ramips/files/drivers/mtd/nmbm/Makefile b/target/linux/ramips/files/drivers/mtd/nmbm/Makefile +deleted file mode 100644 +index 46e6d50a80..0000000000 +--- a/target/linux/ramips/files/drivers/mtd/nmbm/Makefile ++++ /dev/null +@@ -1,6 +0,0 @@ +-# SPDX-License-Identifier: GPL-2.0 +-# +-# (C) Copyright 2020 MediaTek Inc. All rights reserved. +- +-obj-$(CONFIG_NMBM) += nmbm-core.o +-obj-$(CONFIG_NMBM_MTD) += nmbm-mtd.o +diff --git a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-core.c b/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-core.c +deleted file mode 100644 +index 18dfb6adda..0000000000 +--- a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-core.c ++++ /dev/null +@@ -1,2936 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +-/* +- * Copyright (C) 2021 MediaTek Inc. All Rights Reserved. +- * +- * Author: Weijie Gao +- */ +- +-#include "nmbm-private.h" +- +-#include "nmbm-debug.h" +- +-#define NMBM_VER_MAJOR 1 +-#define NMBM_VER_MINOR 0 +-#define NMBM_VER NMBM_VERSION_MAKE(NMBM_VER_MAJOR, \ +- NMBM_VER_MINOR) +- +-#define NMBM_ALIGN(v, a) (((v) + (a) - 1) & ~((a) - 1)) +- +-/*****************************************************************************/ +-/* Logging related functions */ +-/*****************************************************************************/ +- +-/* +- * nmbm_log_lower - Print log using OS specific routine +- * @nld: NMBM lower device structure +- * @level: log level +- * @fmt: format string +- */ +-static void nmbm_log_lower(struct nmbm_lower_device *nld, +- enum nmbm_log_category level, const char *fmt, ...) +-{ +- va_list ap; +- +- if (!nld->logprint) +- return; +- +- va_start(ap, fmt); +- nld->logprint(nld->arg, level, fmt, ap); +- va_end(ap); +-} +- +-/* +- * nmbm_log - Print log using OS specific routine +- * @ni: NMBM instance structure +- * @level: log level +- * @fmt: format string +- */ +-static void nmbm_log(struct nmbm_instance *ni, enum nmbm_log_category level, +- const char *fmt, ...) +-{ +- va_list ap; +- +- if (!ni) +- return; +- +- if (!ni->lower.logprint || level < ni->log_display_level) +- return; +- +- va_start(ap, fmt); +- ni->lower.logprint(ni->lower.arg, level, fmt, ap); +- va_end(ap); +-} +- +-/* +- * nmbm_set_log_level - Set log display level +- * @ni: NMBM instance structure +- * @level: log display level +- */ +-enum nmbm_log_category nmbm_set_log_level(struct nmbm_instance *ni, +- enum nmbm_log_category level) +-{ +- enum nmbm_log_category old; +- +- if (!ni) +- return __NMBM_LOG_MAX; +- +- old = ni->log_display_level; +- ni->log_display_level = level; +- return old; +-} +- +-/* +- * nlog_table_creation - Print log of table creation event +- * @ni: NMBM instance structure +- * @main_table: whether the table is main info table +- * @start_ba: start block address of the table +- * @end_ba: block address after the end of the table +- */ +-static void nlog_table_creation(struct nmbm_instance *ni, bool main_table, +- uint32_t start_ba, uint32_t end_ba) +-{ +- if (start_ba == end_ba - 1) +- nlog_info(ni, "%s info table has been written to block %u\n", +- main_table ? "Main" : "Backup", start_ba); +- else +- nlog_info(ni, "%s info table has been written to block %u-%u\n", +- main_table ? "Main" : "Backup", start_ba, end_ba - 1); +- +- nmbm_mark_block_color_info_table(ni, start_ba, end_ba - 1); +-} +- +-/* +- * nlog_table_update - Print log of table update event +- * @ni: NMBM instance structure +- * @main_table: whether the table is main info table +- * @start_ba: start block address of the table +- * @end_ba: block address after the end of the table +- */ +-static void nlog_table_update(struct nmbm_instance *ni, bool main_table, +- uint32_t start_ba, uint32_t end_ba) +-{ +- if (start_ba == end_ba - 1) +- nlog_debug(ni, "%s info table has been updated in block %u\n", +- main_table ? "Main" : "Backup", start_ba); +- else +- nlog_debug(ni, "%s info table has been updated in block %u-%u\n", +- main_table ? "Main" : "Backup", start_ba, end_ba - 1); +- +- nmbm_mark_block_color_info_table(ni, start_ba, end_ba - 1); +-} +- +-/* +- * nlog_table_found - Print log of table found event +- * @ni: NMBM instance structure +- * @first_table: whether the table is first found info table +- * @write_count: write count of the info table +- * @start_ba: start block address of the table +- * @end_ba: block address after the end of the table +- */ +-static void nlog_table_found(struct nmbm_instance *ni, bool first_table, +- uint32_t write_count, uint32_t start_ba, +- uint32_t end_ba) +-{ +- if (start_ba == end_ba - 1) +- nlog_info(ni, "%s info table with writecount %u found in block %u\n", +- first_table ? "First" : "Second", write_count, +- start_ba); +- else +- nlog_info(ni, "%s info table with writecount %u found in block %u-%u\n", +- first_table ? "First" : "Second", write_count, +- start_ba, end_ba - 1); +- +- nmbm_mark_block_color_info_table(ni, start_ba, end_ba - 1); +-} +- +-/*****************************************************************************/ +-/* Address conversion functions */ +-/*****************************************************************************/ +- +-/* +- * addr2ba - Convert a linear address to block address +- * @ni: NMBM instance structure +- * @addr: Linear address +- */ +-static uint32_t addr2ba(struct nmbm_instance *ni, uint64_t addr) +-{ +- return addr >> ni->erasesize_shift; +-} +- +-/* +- * ba2addr - Convert a block address to linear address +- * @ni: NMBM instance structure +- * @ba: Block address +- */ +-static uint64_t ba2addr(struct nmbm_instance *ni, uint32_t ba) +-{ +- return (uint64_t)ba << ni->erasesize_shift; +-} +-/* +- * size2blk - Get minimum required blocks for storing specific size of data +- * @ni: NMBM instance structure +- * @size: size for storing +- */ +-static uint32_t size2blk(struct nmbm_instance *ni, uint64_t size) +-{ +- return (size + ni->lower.erasesize - 1) >> ni->erasesize_shift; +-} +- +-/*****************************************************************************/ +-/* High level NAND chip APIs */ +-/*****************************************************************************/ +- +-/* +- * nmbm_reset_chip - Reset NAND device +- * @nld: Lower NAND chip structure +- */ +-static void nmbm_reset_chip(struct nmbm_instance *ni) +-{ +- if (ni->lower.reset_chip) +- ni->lower.reset_chip(ni->lower.arg); +-} +- +-/* +- * nmbm_read_phys_page - Read page with retry +- * @ni: NMBM instance structure +- * @addr: linear address where the data will be read from +- * @data: the main data to be read +- * @oob: the oob data to be read +- * @mode: mode for processing oob data +- * +- * Read a page for at most NMBM_TRY_COUNT times. +- * +- * Return 0 for success, positive value for corrected bitflip count, +- * -EBADMSG for ecc error, other negative values for other errors +- */ +-static int nmbm_read_phys_page(struct nmbm_instance *ni, uint64_t addr, +- void *data, void *oob, enum nmbm_oob_mode mode) +-{ +- int tries, ret; +- +- for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { +- ret = ni->lower.read_page(ni->lower.arg, addr, data, oob, mode); +- if (ret >= 0) +- return ret; +- +- nmbm_reset_chip(ni); +- } +- +- if (ret != -EBADMSG) +- nlog_err(ni, "Page read failed at address 0x%08llx\n", addr); +- +- return ret; +-} +- +-/* +- * nmbm_write_phys_page - Write page with retry +- * @ni: NMBM instance structure +- * @addr: linear address where the data will be written to +- * @data: the main data to be written +- * @oob: the oob data to be written +- * @mode: mode for processing oob data +- * +- * Write a page for at most NMBM_TRY_COUNT times. +- */ +-static bool nmbm_write_phys_page(struct nmbm_instance *ni, uint64_t addr, +- const void *data, const void *oob, +- enum nmbm_oob_mode mode) +-{ +- int tries, ret; +- +- if (ni->lower.flags & NMBM_F_READ_ONLY) { +- nlog_err(ni, "%s called with NMBM_F_READ_ONLY set\n", addr); +- return false; +- } +- +- for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { +- ret = ni->lower.write_page(ni->lower.arg, addr, data, oob, mode); +- if (!ret) +- return true; +- +- nmbm_reset_chip(ni); +- } +- +- nlog_err(ni, "Page write failed at address 0x%08llx\n", addr); +- +- return false; +-} +- +-/* +- * nmbm_erase_phys_block - Erase a block with retry +- * @ni: NMBM instance structure +- * @addr: Linear address +- * +- * Erase a block for at most NMBM_TRY_COUNT times. +- */ +-static bool nmbm_erase_phys_block(struct nmbm_instance *ni, uint64_t addr) +-{ +- int tries, ret; +- +- if (ni->lower.flags & NMBM_F_READ_ONLY) { +- nlog_err(ni, "%s called with NMBM_F_READ_ONLY set\n", addr); +- return false; +- } +- +- for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { +- ret = ni->lower.erase_block(ni->lower.arg, addr); +- if (!ret) +- return true; +- +- nmbm_reset_chip(ni); +- } +- +- nlog_err(ni, "Block erasure failed at address 0x%08llx\n", addr); +- +- return false; +-} +- +-/* +- * nmbm_check_bad_phys_block - Check whether a block is marked bad in OOB +- * @ni: NMBM instance structure +- * @ba: block address +- */ +-static bool nmbm_check_bad_phys_block(struct nmbm_instance *ni, uint32_t ba) +-{ +- uint64_t addr = ba2addr(ni, ba); +- int ret; +- +- if (ni->lower.is_bad_block) +- return ni->lower.is_bad_block(ni->lower.arg, addr); +- +- /* Treat ECC error as read success */ +- ret = nmbm_read_phys_page(ni, addr, NULL, +- ni->page_cache + ni->lower.writesize, +- NMBM_MODE_RAW); +- if (ret < 0 && ret != -EBADMSG) +- return true; +- +- return ni->page_cache[ni->lower.writesize] != 0xff; +-} +- +-/* +- * nmbm_mark_phys_bad_block - Mark a block bad +- * @ni: NMBM instance structure +- * @addr: Linear address +- */ +-static int nmbm_mark_phys_bad_block(struct nmbm_instance *ni, uint32_t ba) +-{ +- uint64_t addr = ba2addr(ni, ba); +- enum nmbm_log_category level; +- uint32_t off; +- +- if (ni->lower.flags & NMBM_F_READ_ONLY) { +- nlog_err(ni, "%s called with NMBM_F_READ_ONLY set\n", addr); +- return false; +- } +- +- nlog_info(ni, "Block %u [0x%08llx] will be marked bad\n", ba, addr); +- +- if (ni->lower.mark_bad_block) +- return ni->lower.mark_bad_block(ni->lower.arg, addr); +- +- /* Whole page set to 0x00 */ +- memset(ni->page_cache, 0, ni->rawpage_size); +- +- /* Write to all pages within this block, disable all errors */ +- level = nmbm_set_log_level(ni, __NMBM_LOG_MAX); +- +- for (off = 0; off < ni->lower.erasesize; off += ni->lower.writesize) { +- nmbm_write_phys_page(ni, addr + off, ni->page_cache, +- ni->page_cache + ni->lower.writesize, +- NMBM_MODE_RAW); +- } +- +- nmbm_set_log_level(ni, level); +- +- return 0; +-} +- +-/*****************************************************************************/ +-/* NMBM related functions */ +-/*****************************************************************************/ +- +-/* +- * nmbm_check_header - Check whether a NMBM structure is valid +- * @data: pointer to a NMBM structure with a NMBM header at beginning +- * @size: Size of the buffer pointed by @header +- * +- * The size of the NMBM structure may be larger than NMBM header, +- * e.g. block mapping table and block state table. +- */ +-static bool nmbm_check_header(const void *data, uint32_t size) +-{ +- const struct nmbm_header *header = data; +- struct nmbm_header nhdr; +- uint32_t new_checksum; +- +- /* +- * Make sure expected structure size is equal or smaller than +- * buffer size. +- */ +- if (header->size > size) +- return false; +- +- memcpy(&nhdr, data, sizeof(nhdr)); +- +- nhdr.checksum = 0; +- new_checksum = nmbm_crc32(0, &nhdr, sizeof(nhdr)); +- if (header->size > sizeof(nhdr)) +- new_checksum = nmbm_crc32(new_checksum, +- (const uint8_t *)data + sizeof(nhdr), +- header->size - sizeof(nhdr)); +- +- if (header->checksum != new_checksum) +- return false; +- +- return true; +-} +- +-/* +- * nmbm_update_checksum - Update checksum of a NMBM structure +- * @header: pointer to a NMBM structure with a NMBM header at beginning +- * +- * The size of the NMBM structure must be specified by @header->size +- */ +-static void nmbm_update_checksum(struct nmbm_header *header) +-{ +- header->checksum = 0; +- header->checksum = nmbm_crc32(0, header, header->size); +-} +- +-/* +- * nmbm_get_spare_block_count - Calculate number of blocks should be reserved +- * @block_count: number of blocks of data +- * +- * Calculate number of blocks should be reserved for data +- */ +-static uint32_t nmbm_get_spare_block_count(uint32_t block_count) +-{ +- uint32_t val; +- +- val = (block_count + NMBM_SPARE_BLOCK_DIV / 2) / NMBM_SPARE_BLOCK_DIV; +- val *= NMBM_SPARE_BLOCK_MULTI; +- +- if (val < NMBM_SPARE_BLOCK_MIN) +- val = NMBM_SPARE_BLOCK_MIN; +- +- return val; +-} +- +-/* +- * nmbm_get_block_state_raw - Get state of a block from raw block state table +- * @block_state: pointer to raw block state table (bitmap) +- * @ba: block address +- */ +-static uint32_t nmbm_get_block_state_raw(nmbm_bitmap_t *block_state, +- uint32_t ba) +-{ +- uint32_t unit, shift; +- +- unit = ba / NMBM_BITMAP_BLOCKS_PER_UNIT; +- shift = (ba % NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_BITS_PER_BLOCK; +- +- return (block_state[unit] >> shift) & BLOCK_ST_MASK; +-} +- +-/* +- * nmbm_get_block_state - Get state of a block from block state table +- * @ni: NMBM instance structure +- * @ba: block address +- */ +-static uint32_t nmbm_get_block_state(struct nmbm_instance *ni, uint32_t ba) +-{ +- return nmbm_get_block_state_raw(ni->block_state, ba); +-} +- +-/* +- * nmbm_set_block_state - Set state of a block to block state table +- * @ni: NMBM instance structure +- * @ba: block address +- * @state: block state +- * +- * Set state of a block. If the block state changed, ni->block_state_changed +- * will be increased. +- */ +-static bool nmbm_set_block_state(struct nmbm_instance *ni, uint32_t ba, +- uint32_t state) +-{ +- uint32_t unit, shift, orig; +- nmbm_bitmap_t uv; +- +- unit = ba / NMBM_BITMAP_BLOCKS_PER_UNIT; +- shift = (ba % NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_BITS_PER_BLOCK; +- +- orig = (ni->block_state[unit] >> shift) & BLOCK_ST_MASK; +- state &= BLOCK_ST_MASK; +- +- uv = ni->block_state[unit] & (~(BLOCK_ST_MASK << shift)); +- uv |= state << shift; +- ni->block_state[unit] = uv; +- +- if (state == BLOCK_ST_BAD) +- nmbm_mark_block_color_bad(ni, ba); +- +- if (orig != state) { +- ni->block_state_changed++; +- return true; +- } +- +- return false; +-} +- +-/* +- * nmbm_block_walk_asc - Skip specified number of good blocks, ascending addr. +- * @ni: NMBM instance structure +- * @ba: start physical block address +- * @nba: return physical block address after walk +- * @count: number of good blocks to be skipped +- * @limit: highest block address allowed for walking +- * +- * Start from @ba, skipping any bad blocks, counting @count good blocks, and +- * return the next good block address. +- * +- * If no enough good blocks counted while @limit reached, false will be returned. +- * +- * If @count == 0, nearest good block address will be returned. +- * @limit is not counted in walking. +- */ +-static bool nmbm_block_walk_asc(struct nmbm_instance *ni, uint32_t ba, +- uint32_t *nba, uint32_t count, +- uint32_t limit) +-{ +- int32_t nblock = count; +- +- if (limit >= ni->block_count) +- limit = ni->block_count - 1; +- +- while (ba < limit) { +- if (nmbm_get_block_state(ni, ba) == BLOCK_ST_GOOD) +- nblock--; +- +- if (nblock < 0) { +- *nba = ba; +- return true; +- } +- +- ba++; +- } +- +- return false; +-} +- +-/* +- * nmbm_block_walk_desc - Skip specified number of good blocks, descending addr +- * @ni: NMBM instance structure +- * @ba: start physical block address +- * @nba: return physical block address after walk +- * @count: number of good blocks to be skipped +- * @limit: lowest block address allowed for walking +- * +- * Start from @ba, skipping any bad blocks, counting @count good blocks, and +- * return the next good block address. +- * +- * If no enough good blocks counted while @limit reached, false will be returned. +- * +- * If @count == 0, nearest good block address will be returned. +- * @limit is not counted in walking. +- */ +-static bool nmbm_block_walk_desc(struct nmbm_instance *ni, uint32_t ba, +- uint32_t *nba, uint32_t count, uint32_t limit) +-{ +- int32_t nblock = count; +- +- if (limit >= ni->block_count) +- limit = ni->block_count - 1; +- +- while (ba > limit) { +- if (nmbm_get_block_state(ni, ba) == BLOCK_ST_GOOD) +- nblock--; +- +- if (nblock < 0) { +- *nba = ba; +- return true; +- } +- +- ba--; +- } +- +- return false; +-} +- +-/* +- * nmbm_block_walk - Skip specified number of good blocks from curr. block addr +- * @ni: NMBM instance structure +- * @ascending: whether to walk ascending +- * @ba: start physical block address +- * @nba: return physical block address after walk +- * @count: number of good blocks to be skipped +- * @limit: highest/lowest block address allowed for walking +- * +- * Start from @ba, skipping any bad blocks, counting @count good blocks, and +- * return the next good block address. +- * +- * If no enough good blocks counted while @limit reached, false will be returned. +- * +- * If @count == 0, nearest good block address will be returned. +- * @limit can be set to negative if no limit required. +- * @limit is not counted in walking. +- */ +-static bool nmbm_block_walk(struct nmbm_instance *ni, bool ascending, +- uint32_t ba, uint32_t *nba, int32_t count, +- int32_t limit) +-{ +- if (ascending) +- return nmbm_block_walk_asc(ni, ba, nba, count, limit); +- +- return nmbm_block_walk_desc(ni, ba, nba, count, limit); +-} +- +-/* +- * nmbm_scan_badblocks - Scan and record all bad blocks +- * @ni: NMBM instance structure +- * +- * Scan the entire lower NAND chip and record all bad blocks in to block state +- * table. +- */ +-static void nmbm_scan_badblocks(struct nmbm_instance *ni) +-{ +- uint32_t ba; +- +- for (ba = 0; ba < ni->block_count; ba++) { +- if (nmbm_check_bad_phys_block(ni, ba)) { +- nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); +- nlog_info(ni, "Bad block %u [0x%08llx]\n", ba, +- ba2addr(ni, ba)); +- } +- } +-} +- +-/* +- * nmbm_build_mapping_table - Build initial block mapping table +- * @ni: NMBM instance structure +- * +- * The initial mapping table will be compatible with the stratage of +- * factory production. +- */ +-static void nmbm_build_mapping_table(struct nmbm_instance *ni) +-{ +- uint32_t pb, lb; +- +- for (pb = 0, lb = 0; pb < ni->mgmt_start_ba; pb++) { +- if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) +- continue; +- +- /* Always map to the next good block */ +- ni->block_mapping[lb++] = pb; +- } +- +- ni->data_block_count = lb; +- +- /* Unusable/Management blocks */ +- for (pb = lb; pb < ni->block_count; pb++) +- ni->block_mapping[pb] = -1; +-} +- +-/* +- * nmbm_erase_block_and_check - Erase a block and check its usability +- * @ni: NMBM instance structure +- * @ba: block address to be erased +- * +- * Erase a block anc check its usability +- * +- * Return true if the block is usable, false if erasure failure or the block +- * has too many bitflips. +- */ +-static bool nmbm_erase_block_and_check(struct nmbm_instance *ni, uint32_t ba) +-{ +- uint64_t addr, off; +- bool success; +- int ret; +- +- success = nmbm_erase_phys_block(ni, ba2addr(ni, ba)); +- if (!success) +- return false; +- +- if (!(ni->lower.flags & NMBM_F_EMPTY_PAGE_ECC_OK)) +- return true; +- +- /* Check every page to make sure there aren't too many bitflips */ +- +- addr = ba2addr(ni, ba); +- +- for (off = 0; off < ni->lower.erasesize; off += ni->lower.writesize) { +- WATCHDOG_RESET(); +- +- ret = nmbm_read_phys_page(ni, addr + off, ni->page_cache, NULL, +- NMBM_MODE_PLACE_OOB); +- if (ret == -EBADMSG) { +- /* +- * NMBM_F_EMPTY_PAGE_ECC_OK means the empty page is +- * still protected by ECC. So reading pages with ECC +- * enabled and -EBADMSG means there are too many +- * bitflips that can't be recovered, and the block +- * containing the page should be marked bad. +- */ +- nlog_err(ni, +- "Too many bitflips in empty page at 0x%llx\n", +- addr + off); +- return false; +- } +- } +- +- return true; +-} +- +-/* +- * nmbm_erase_range - Erase a range of blocks +- * @ni: NMBM instance structure +- * @ba: block address where the erasure will start +- * @limit: top block address allowed for erasure +- * +- * Erase blocks within the specific range. Newly-found bad blocks will be +- * marked. +- * +- * @limit is not counted into the allowed erasure address. +- */ +-static void nmbm_erase_range(struct nmbm_instance *ni, uint32_t ba, +- uint32_t limit) +-{ +- bool success; +- +- while (ba < limit) { +- WATCHDOG_RESET(); +- +- if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) +- goto next_block; +- +- /* Insurance to detect unexpected bad block marked by user */ +- if (nmbm_check_bad_phys_block(ni, ba)) { +- nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); +- goto next_block; +- } +- +- success = nmbm_erase_block_and_check(ni, ba); +- if (success) +- goto next_block; +- +- nmbm_mark_phys_bad_block(ni, ba); +- nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); +- +- next_block: +- ba++; +- } +-} +- +-/* +- * nmbm_write_repeated_data - Write critical data to a block with retry +- * @ni: NMBM instance structure +- * @ba: block address where the data will be written to +- * @data: the data to be written +- * @size: size of the data +- * +- * Write data to every page of the block. Success only if all pages within +- * this block have been successfully written. +- * +- * Make sure data size is not bigger than one page. +- * +- * This function will write and verify every page for at most +- * NMBM_TRY_COUNT times. +- */ +-static bool nmbm_write_repeated_data(struct nmbm_instance *ni, uint32_t ba, +- const void *data, uint32_t size) +-{ +- uint64_t addr, off; +- bool success; +- int ret; +- +- if (size > ni->lower.writesize) +- return false; +- +- addr = ba2addr(ni, ba); +- +- for (off = 0; off < ni->lower.erasesize; off += ni->lower.writesize) { +- WATCHDOG_RESET(); +- +- /* Prepare page data. fill 0xff to unused region */ +- memcpy(ni->page_cache, data, size); +- memset(ni->page_cache + size, 0xff, ni->rawpage_size - size); +- +- success = nmbm_write_phys_page(ni, addr + off, ni->page_cache, +- NULL, NMBM_MODE_PLACE_OOB); +- if (!success) +- return false; +- +- /* Verify the data just written. ECC error indicates failure */ +- ret = nmbm_read_phys_page(ni, addr + off, ni->page_cache, NULL, +- NMBM_MODE_PLACE_OOB); +- if (ret < 0) +- return false; +- +- if (memcmp(ni->page_cache, data, size)) +- return false; +- } +- +- return true; +-} +- +-/* +- * nmbm_write_signature - Write signature to NAND chip +- * @ni: NMBM instance structure +- * @limit: top block address allowed for writing +- * @signature: the signature to be written +- * @signature_ba: the actual block address where signature is written to +- * +- * Write signature within a specific range, from chip bottom to limit. +- * At most one block will be written. +- * +- * @limit is not counted into the allowed write address. +- */ +-static bool nmbm_write_signature(struct nmbm_instance *ni, uint32_t limit, +- const struct nmbm_signature *signature, +- uint32_t *signature_ba) +-{ +- uint32_t ba = ni->block_count - 1; +- bool success; +- +- while (ba > limit) { +- WATCHDOG_RESET(); +- +- if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) +- goto next_block; +- +- /* Insurance to detect unexpected bad block marked by user */ +- if (nmbm_check_bad_phys_block(ni, ba)) { +- nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); +- goto next_block; +- } +- +- success = nmbm_erase_block_and_check(ni, ba); +- if (!success) +- goto skip_bad_block; +- +- success = nmbm_write_repeated_data(ni, ba, signature, +- sizeof(*signature)); +- if (success) { +- *signature_ba = ba; +- return true; +- } +- +- skip_bad_block: +- nmbm_mark_phys_bad_block(ni, ba); +- nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); +- +- next_block: +- ba--; +- }; +- +- return false; +-} +- +-/* +- * nmbn_read_data - Read data +- * @ni: NMBM instance structure +- * @addr: linear address where the data will be read from +- * @data: the data to be read +- * @size: the size of data +- * +- * Read data range. +- * Every page will be tried for at most NMBM_TRY_COUNT times. +- * +- * Return 0 for success, positive value for corrected bitflip count, +- * -EBADMSG for ecc error, other negative values for other errors +- */ +-static int nmbn_read_data(struct nmbm_instance *ni, uint64_t addr, void *data, +- uint32_t size) +-{ +- uint64_t off = addr; +- uint8_t *ptr = data; +- uint32_t sizeremain = size, chunksize, leading; +- int ret; +- +- while (sizeremain) { +- WATCHDOG_RESET(); +- +- leading = off & ni->writesize_mask; +- chunksize = ni->lower.writesize - leading; +- if (chunksize > sizeremain) +- chunksize = sizeremain; +- +- if (chunksize == ni->lower.writesize) { +- ret = nmbm_read_phys_page(ni, off - leading, ptr, NULL, +- NMBM_MODE_PLACE_OOB); +- if (ret < 0) +- return ret; +- } else { +- ret = nmbm_read_phys_page(ni, off - leading, +- ni->page_cache, NULL, +- NMBM_MODE_PLACE_OOB); +- if (ret < 0) +- return ret; +- +- memcpy(ptr, ni->page_cache + leading, chunksize); +- } +- +- off += chunksize; +- ptr += chunksize; +- sizeremain -= chunksize; +- } +- +- return 0; +-} +- +-/* +- * nmbn_write_verify_data - Write data with validation +- * @ni: NMBM instance structure +- * @addr: linear address where the data will be written to +- * @data: the data to be written +- * @size: the size of data +- * +- * Write data and verify. +- * Every page will be tried for at most NMBM_TRY_COUNT times. +- */ +-static bool nmbn_write_verify_data(struct nmbm_instance *ni, uint64_t addr, +- const void *data, uint32_t size) +-{ +- uint64_t off = addr; +- const uint8_t *ptr = data; +- uint32_t sizeremain = size, chunksize, leading; +- bool success; +- int ret; +- +- while (sizeremain) { +- WATCHDOG_RESET(); +- +- leading = off & ni->writesize_mask; +- chunksize = ni->lower.writesize - leading; +- if (chunksize > sizeremain) +- chunksize = sizeremain; +- +- /* Prepare page data. fill 0xff to unused region */ +- memset(ni->page_cache, 0xff, ni->rawpage_size); +- memcpy(ni->page_cache + leading, ptr, chunksize); +- +- success = nmbm_write_phys_page(ni, off - leading, +- ni->page_cache, NULL, +- NMBM_MODE_PLACE_OOB); +- if (!success) +- return false; +- +- /* Verify the data just written. ECC error indicates failure */ +- ret = nmbm_read_phys_page(ni, off - leading, ni->page_cache, +- NULL, NMBM_MODE_PLACE_OOB); +- if (ret < 0) +- return false; +- +- if (memcmp(ni->page_cache + leading, ptr, chunksize)) +- return false; +- +- off += chunksize; +- ptr += chunksize; +- sizeremain -= chunksize; +- } +- +- return true; +-} +- +-/* +- * nmbm_write_mgmt_range - Write management data into NAND within a range +- * @ni: NMBM instance structure +- * @addr: preferred start block address for writing +- * @limit: highest block address allowed for writing +- * @data: the data to be written +- * @size: the size of data +- * @actual_start_ba: actual start block address of data +- * @actual_end_ba: block address after the end of data +- * +- * @limit is not counted into the allowed write address. +- */ +-static bool nmbm_write_mgmt_range(struct nmbm_instance *ni, uint32_t ba, +- uint32_t limit, const void *data, +- uint32_t size, uint32_t *actual_start_ba, +- uint32_t *actual_end_ba) +-{ +- const uint8_t *ptr = data; +- uint32_t sizeremain = size, chunksize; +- bool success; +- +- while (sizeremain && ba < limit) { +- WATCHDOG_RESET(); +- +- chunksize = sizeremain; +- if (chunksize > ni->lower.erasesize) +- chunksize = ni->lower.erasesize; +- +- if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) +- goto next_block; +- +- /* Insurance to detect unexpected bad block marked by user */ +- if (nmbm_check_bad_phys_block(ni, ba)) { +- nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); +- goto next_block; +- } +- +- success = nmbm_erase_block_and_check(ni, ba); +- if (!success) +- goto skip_bad_block; +- +- success = nmbn_write_verify_data(ni, ba2addr(ni, ba), ptr, +- chunksize); +- if (!success) +- goto skip_bad_block; +- +- if (sizeremain == size) +- *actual_start_ba = ba; +- +- ptr += chunksize; +- sizeremain -= chunksize; +- +- goto next_block; +- +- skip_bad_block: +- nmbm_mark_phys_bad_block(ni, ba); +- nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); +- +- next_block: +- ba++; +- } +- +- if (sizeremain) +- return false; +- +- *actual_end_ba = ba; +- +- return true; +-} +- +-/* +- * nmbm_generate_info_table_cache - Generate info table cache data +- * @ni: NMBM instance structure +- * +- * Generate info table cache data to be written into flash. +- */ +-static bool nmbm_generate_info_table_cache(struct nmbm_instance *ni) +-{ +- bool changed = false; +- +- memset(ni->info_table_cache, 0xff, ni->info_table_size); +- +- memcpy(ni->info_table_cache + ni->info_table.state_table_off, +- ni->block_state, ni->state_table_size); +- +- memcpy(ni->info_table_cache + ni->info_table.mapping_table_off, +- ni->block_mapping, ni->mapping_table_size); +- +- ni->info_table.header.magic = NMBM_MAGIC_INFO_TABLE; +- ni->info_table.header.version = NMBM_VER; +- ni->info_table.header.size = ni->info_table_size; +- +- if (ni->block_state_changed || ni->block_mapping_changed) { +- ni->info_table.write_count++; +- changed = true; +- } +- +- memcpy(ni->info_table_cache, &ni->info_table, sizeof(ni->info_table)); +- +- nmbm_update_checksum((struct nmbm_header *)ni->info_table_cache); +- +- return changed; +-} +- +-/* +- * nmbm_write_info_table - Write info table into NAND within a range +- * @ni: NMBM instance structure +- * @ba: preferred start block address for writing +- * @limit: highest block address allowed for writing +- * @actual_start_ba: actual start block address of info table +- * @actual_end_ba: block address after the end of info table +- * +- * @limit is counted into the allowed write address. +- */ +-static bool nmbm_write_info_table(struct nmbm_instance *ni, uint32_t ba, +- uint32_t limit, uint32_t *actual_start_ba, +- uint32_t *actual_end_ba) +-{ +- return nmbm_write_mgmt_range(ni, ba, limit, ni->info_table_cache, +- ni->info_table_size, actual_start_ba, +- actual_end_ba); +-} +- +-/* +- * nmbm_mark_tables_clean - Mark info table `clean' +- * @ni: NMBM instance structure +- */ +-static void nmbm_mark_tables_clean(struct nmbm_instance *ni) +-{ +- ni->block_state_changed = 0; +- ni->block_mapping_changed = 0; +-} +- +-/* +- * nmbm_try_reserve_blocks - Reserve blocks with compromisation +- * @ni: NMBM instance structure +- * @ba: start physical block address +- * @nba: return physical block address after reservation +- * @count: number of good blocks to be skipped +- * @min_count: minimum number of good blocks to be skipped +- * @limit: highest/lowest block address allowed for walking +- * +- * Reserve specific blocks. If failed, try to reserve as many as possible. +- */ +-static bool nmbm_try_reserve_blocks(struct nmbm_instance *ni, uint32_t ba, +- uint32_t *nba, uint32_t count, +- int32_t min_count, int32_t limit) +-{ +- int32_t nblocks = count; +- bool success; +- +- while (nblocks >= min_count) { +- success = nmbm_block_walk(ni, true, ba, nba, nblocks, limit); +- if (success) +- return true; +- +- nblocks--; +- } +- +- return false; +-} +- +-/* +- * nmbm_rebuild_info_table - Build main & backup info table from scratch +- * @ni: NMBM instance structure +- * @allow_no_gap: allow no spare blocks between two tables +- */ +-static bool nmbm_rebuild_info_table(struct nmbm_instance *ni) +-{ +- uint32_t table_start_ba, table_end_ba, next_start_ba; +- uint32_t main_table_end_ba; +- bool success; +- +- /* Set initial value */ +- ni->main_table_ba = 0; +- ni->backup_table_ba = 0; +- ni->mapping_blocks_ba = ni->mapping_blocks_top_ba; +- +- /* Write main table */ +- success = nmbm_write_info_table(ni, ni->mgmt_start_ba, +- ni->mapping_blocks_top_ba, +- &table_start_ba, &table_end_ba); +- if (!success) { +- /* Failed to write main table, data will be lost */ +- nlog_emerg(ni, "Unable to write at least one info table!\n"); +- nlog_emerg(ni, "Please save your data before power off!\n"); +- ni->protected = 1; +- return false; +- } +- +- /* Main info table is successfully written, record its offset */ +- ni->main_table_ba = table_start_ba; +- main_table_end_ba = table_end_ba; +- +- /* Adjust mapping_blocks_ba */ +- ni->mapping_blocks_ba = table_end_ba; +- +- nmbm_mark_tables_clean(ni); +- +- nlog_table_creation(ni, true, table_start_ba, table_end_ba); +- +- /* Reserve spare blocks for main info table. */ +- success = nmbm_try_reserve_blocks(ni, table_end_ba, +- &next_start_ba, +- ni->info_table_spare_blocks, 0, +- ni->mapping_blocks_top_ba - +- size2blk(ni, ni->info_table_size)); +- if (!success) { +- /* There is no spare block. */ +- nlog_debug(ni, "No room for backup info table\n"); +- return true; +- } +- +- /* Write backup info table. */ +- success = nmbm_write_info_table(ni, next_start_ba, +- ni->mapping_blocks_top_ba, +- &table_start_ba, &table_end_ba); +- if (!success) { +- /* There is no enough blocks for backup table. */ +- nlog_debug(ni, "No room for backup info table\n"); +- return true; +- } +- +- /* Backup table is successfully written, record its offset */ +- ni->backup_table_ba = table_start_ba; +- +- /* Adjust mapping_blocks_off */ +- ni->mapping_blocks_ba = table_end_ba; +- +- /* Erase spare blocks of main table to clean possible interference data */ +- nmbm_erase_range(ni, main_table_end_ba, ni->backup_table_ba); +- +- nlog_table_creation(ni, false, table_start_ba, table_end_ba); +- +- return true; +-} +- +-/* +- * nmbm_rescue_single_info_table - Rescue when there is only one info table +- * @ni: NMBM instance structure +- * +- * This function is called when there is only one info table exists. +- * This function may fail if we can't write new info table +- */ +-static bool nmbm_rescue_single_info_table(struct nmbm_instance *ni) +-{ +- uint32_t table_start_ba, table_end_ba, write_ba; +- bool success; +- +- /* Try to write new info table in front of existing table */ +- success = nmbm_write_info_table(ni, ni->mgmt_start_ba, +- ni->main_table_ba, +- &table_start_ba, +- &table_end_ba); +- if (success) { +- /* +- * New table becomes the main table, existing table becomes +- * the backup table. +- */ +- ni->backup_table_ba = ni->main_table_ba; +- ni->main_table_ba = table_start_ba; +- +- nmbm_mark_tables_clean(ni); +- +- /* Erase spare blocks of main table to clean possible interference data */ +- nmbm_erase_range(ni, table_end_ba, ni->backup_table_ba); +- +- nlog_table_creation(ni, true, table_start_ba, table_end_ba); +- +- return true; +- } +- +- /* Try to reserve spare blocks for existing table */ +- success = nmbm_try_reserve_blocks(ni, ni->mapping_blocks_ba, &write_ba, +- ni->info_table_spare_blocks, 0, +- ni->mapping_blocks_top_ba - +- size2blk(ni, ni->info_table_size)); +- if (!success) { +- nlog_warn(ni, "Failed to rescue single info table\n"); +- return false; +- } +- +- /* Try to write new info table next to the existing table */ +- while (write_ba >= ni->mapping_blocks_ba) { +- WATCHDOG_RESET(); +- +- success = nmbm_write_info_table(ni, write_ba, +- ni->mapping_blocks_top_ba, +- &table_start_ba, +- &table_end_ba); +- if (success) +- break; +- +- write_ba--; +- } +- +- if (success) { +- /* Erase spare blocks of main table to clean possible interference data */ +- nmbm_erase_range(ni, ni->mapping_blocks_ba, table_start_ba); +- +- /* New table becomes the backup table */ +- ni->backup_table_ba = table_start_ba; +- ni->mapping_blocks_ba = table_end_ba; +- +- nmbm_mark_tables_clean(ni); +- +- nlog_table_creation(ni, false, table_start_ba, table_end_ba); +- +- return true; +- } +- +- nlog_warn(ni, "Failed to rescue single info table\n"); +- return false; +-} +- +-/* +- * nmbm_update_single_info_table - Update specific one info table +- * @ni: NMBM instance structure +- */ +-static bool nmbm_update_single_info_table(struct nmbm_instance *ni, +- bool update_main_table) +-{ +- uint32_t write_start_ba, write_limit, table_start_ba, table_end_ba; +- bool success; +- +- /* Determine the write range */ +- if (update_main_table) { +- write_start_ba = ni->main_table_ba; +- write_limit = ni->backup_table_ba; +- } else { +- write_start_ba = ni->backup_table_ba; +- write_limit = ni->mapping_blocks_top_ba; +- } +- +- nmbm_mark_block_color_mgmt(ni, write_start_ba, write_limit - 1); +- +- success = nmbm_write_info_table(ni, write_start_ba, write_limit, +- &table_start_ba, &table_end_ba); +- if (success) { +- if (update_main_table) { +- ni->main_table_ba = table_start_ba; +- } else { +- ni->backup_table_ba = table_start_ba; +- ni->mapping_blocks_ba = table_end_ba; +- } +- +- nmbm_mark_tables_clean(ni); +- +- nlog_table_update(ni, update_main_table, table_start_ba, +- table_end_ba); +- +- return true; +- } +- +- if (update_main_table) { +- /* +- * If failed to update main table, make backup table the new +- * main table, and call nmbm_rescue_single_info_table() +- */ +- nlog_warn(ni, "Unable to update %s info table\n", +- update_main_table ? "Main" : "Backup"); +- +- ni->main_table_ba = ni->backup_table_ba; +- ni->backup_table_ba = 0; +- return nmbm_rescue_single_info_table(ni); +- } +- +- /* Only one table left */ +- ni->mapping_blocks_ba = ni->backup_table_ba; +- ni->backup_table_ba = 0; +- +- return false; +-} +- +-/* +- * nmbm_rescue_main_info_table - Rescue when failed to write main info table +- * @ni: NMBM instance structure +- * +- * This function is called when main info table failed to be written, and +- * backup info table exists. +- */ +-static bool nmbm_rescue_main_info_table(struct nmbm_instance *ni) +-{ +- uint32_t tmp_table_start_ba, tmp_table_end_ba, main_table_start_ba; +- uint32_t main_table_end_ba, write_ba; +- uint32_t info_table_erasesize = size2blk(ni, ni->info_table_size); +- bool success; +- +- /* Try to reserve spare blocks for existing backup info table */ +- success = nmbm_try_reserve_blocks(ni, ni->mapping_blocks_ba, &write_ba, +- ni->info_table_spare_blocks, 0, +- ni->mapping_blocks_top_ba - +- info_table_erasesize); +- if (!success) { +- /* There is no spare block. Backup info table becomes the main table. */ +- nlog_err(ni, "No room for temporary info table\n"); +- ni->main_table_ba = ni->backup_table_ba; +- ni->backup_table_ba = 0; +- return true; +- } +- +- /* Try to write temporary info table into spare unmapped blocks */ +- while (write_ba >= ni->mapping_blocks_ba) { +- WATCHDOG_RESET(); +- +- success = nmbm_write_info_table(ni, write_ba, +- ni->mapping_blocks_top_ba, +- &tmp_table_start_ba, +- &tmp_table_end_ba); +- if (success) +- break; +- +- write_ba--; +- } +- +- if (!success) { +- /* Backup info table becomes the main table */ +- nlog_err(ni, "Failed to update main info table\n"); +- ni->main_table_ba = ni->backup_table_ba; +- ni->backup_table_ba = 0; +- return true; +- } +- +- /* Adjust mapping_blocks_off */ +- ni->mapping_blocks_ba = tmp_table_end_ba; +- +- nmbm_mark_block_color_mgmt(ni, ni->backup_table_ba, +- tmp_table_end_ba - 1); +- +- /* +- * Now write main info table at the beginning of management area. +- * This operation will generally destroy the original backup info +- * table. +- */ +- success = nmbm_write_info_table(ni, ni->mgmt_start_ba, +- tmp_table_start_ba, +- &main_table_start_ba, +- &main_table_end_ba); +- if (!success) { +- /* Temporary info table becomes the main table */ +- ni->main_table_ba = tmp_table_start_ba; +- ni->backup_table_ba = 0; +- +- nmbm_mark_tables_clean(ni); +- +- nlog_err(ni, "Failed to update main info table\n"); +- nmbm_mark_block_color_info_table(ni, tmp_table_start_ba, +- tmp_table_end_ba - 1); +- +- return true; +- } +- +- /* Main info table has been successfully written, record its offset */ +- ni->main_table_ba = main_table_start_ba; +- +- nmbm_mark_tables_clean(ni); +- +- nlog_table_creation(ni, true, main_table_start_ba, main_table_end_ba); +- +- /* +- * Temporary info table becomes the new backup info table if it's +- * not overwritten. +- */ +- if (main_table_end_ba <= tmp_table_start_ba) { +- ni->backup_table_ba = tmp_table_start_ba; +- +- nlog_table_creation(ni, false, tmp_table_start_ba, +- tmp_table_end_ba); +- +- return true; +- } +- +- /* Adjust mapping_blocks_off */ +- ni->mapping_blocks_ba = main_table_end_ba; +- +- /* Try to reserve spare blocks for new main info table */ +- success = nmbm_try_reserve_blocks(ni, main_table_end_ba, &write_ba, +- ni->info_table_spare_blocks, 0, +- ni->mapping_blocks_top_ba - +- info_table_erasesize); +- if (!success) { +- /* There is no spare block. Only main table exists. */ +- nlog_err(ni, "No room for backup info table\n"); +- ni->backup_table_ba = 0; +- return true; +- } +- +- /* Write new backup info table. */ +- while (write_ba >= main_table_end_ba) { +- WATCHDOG_RESET(); +- +- success = nmbm_write_info_table(ni, write_ba, +- ni->mapping_blocks_top_ba, +- &tmp_table_start_ba, +- &tmp_table_end_ba); +- if (success) +- break; +- +- write_ba--; +- } +- +- if (!success) { +- nlog_err(ni, "No room for backup info table\n"); +- ni->backup_table_ba = 0; +- return true; +- } +- +- /* Backup info table has been successfully written, record its offset */ +- ni->backup_table_ba = tmp_table_start_ba; +- +- /* Adjust mapping_blocks_off */ +- ni->mapping_blocks_ba = tmp_table_end_ba; +- +- /* Erase spare blocks of main table to clean possible interference data */ +- nmbm_erase_range(ni, main_table_end_ba, ni->backup_table_ba); +- +- nlog_table_creation(ni, false, tmp_table_start_ba, tmp_table_end_ba); +- +- return true; +-} +- +-/* +- * nmbm_update_info_table_once - Update info table once +- * @ni: NMBM instance structure +- * @force: force update +- * +- * Update both main and backup info table. Return true if at least one info +- * table has been successfully written. +- * This function only try to update info table once regard less of the result. +- */ +-static bool nmbm_update_info_table_once(struct nmbm_instance *ni, bool force) +-{ +- uint32_t table_start_ba, table_end_ba; +- uint32_t main_table_limit; +- bool success; +- +- /* Do nothing if there is no change */ +- if (!nmbm_generate_info_table_cache(ni) && !force) +- return true; +- +- /* Check whether both two tables exist */ +- if (!ni->backup_table_ba) { +- main_table_limit = ni->mapping_blocks_top_ba; +- goto write_main_table; +- } +- +- nmbm_mark_block_color_mgmt(ni, ni->backup_table_ba, +- ni->mapping_blocks_ba - 1); +- +- /* +- * Write backup info table in its current range. +- * Note that limit is set to mapping_blocks_top_off to provide as many +- * spare blocks as possible for the backup table. If at last +- * unmapped blocks are used by backup table, mapping_blocks_off will +- * be adjusted. +- */ +- success = nmbm_write_info_table(ni, ni->backup_table_ba, +- ni->mapping_blocks_top_ba, +- &table_start_ba, &table_end_ba); +- if (!success) { +- /* +- * There is nothing to do if failed to write backup table. +- * Write the main table now. +- */ +- nlog_err(ni, "No room for backup table\n"); +- ni->mapping_blocks_ba = ni->backup_table_ba; +- ni->backup_table_ba = 0; +- main_table_limit = ni->mapping_blocks_top_ba; +- goto write_main_table; +- } +- +- /* Backup table is successfully written, record its offset */ +- ni->backup_table_ba = table_start_ba; +- +- /* Adjust mapping_blocks_off */ +- ni->mapping_blocks_ba = table_end_ba; +- +- nmbm_mark_tables_clean(ni); +- +- /* The normal limit of main table */ +- main_table_limit = ni->backup_table_ba; +- +- nlog_table_update(ni, false, table_start_ba, table_end_ba); +- +-write_main_table: +- if (!ni->main_table_ba) +- goto rebuild_tables; +- +- if (!ni->backup_table_ba) +- nmbm_mark_block_color_mgmt(ni, ni->mgmt_start_ba, +- ni->mapping_blocks_ba - 1); +- else +- nmbm_mark_block_color_mgmt(ni, ni->mgmt_start_ba, +- ni->backup_table_ba - 1); +- +- /* Write main info table in its current range */ +- success = nmbm_write_info_table(ni, ni->main_table_ba, +- main_table_limit, &table_start_ba, +- &table_end_ba); +- if (!success) { +- /* If failed to write main table, go rescue procedure */ +- if (!ni->backup_table_ba) +- goto rebuild_tables; +- +- return nmbm_rescue_main_info_table(ni); +- } +- +- /* Main info table is successfully written, record its offset */ +- ni->main_table_ba = table_start_ba; +- +- /* Adjust mapping_blocks_off */ +- if (!ni->backup_table_ba) +- ni->mapping_blocks_ba = table_end_ba; +- +- nmbm_mark_tables_clean(ni); +- +- nlog_table_update(ni, true, table_start_ba, table_end_ba); +- +- return true; +- +-rebuild_tables: +- return nmbm_rebuild_info_table(ni); +-} +- +-/* +- * nmbm_update_info_table - Update info table +- * @ni: NMBM instance structure +- * +- * Update both main and backup info table. Return true if at least one table +- * has been successfully written. +- * This function will try to update info table repeatedly until no new bad +- * block found during updating. +- */ +-static bool nmbm_update_info_table(struct nmbm_instance *ni) +-{ +- bool success; +- +- if (ni->protected) +- return true; +- +- while (ni->block_state_changed || ni->block_mapping_changed) { +- success = nmbm_update_info_table_once(ni, false); +- if (!success) { +- nlog_err(ni, "Failed to update info table\n"); +- return false; +- } +- } +- +- return true; +-} +- +-/* +- * nmbm_map_block - Map a bad block to a unused spare block +- * @ni: NMBM instance structure +- * @lb: logic block addr to map +- */ +-static bool nmbm_map_block(struct nmbm_instance *ni, uint32_t lb) +-{ +- uint32_t pb; +- bool success; +- +- if (ni->mapping_blocks_ba == ni->mapping_blocks_top_ba) { +- nlog_warn(ni, "No spare unmapped blocks.\n"); +- return false; +- } +- +- success = nmbm_block_walk(ni, false, ni->mapping_blocks_top_ba, &pb, 0, +- ni->mapping_blocks_ba); +- if (!success) { +- nlog_warn(ni, "No spare unmapped blocks.\n"); +- nmbm_update_info_table(ni); +- ni->mapping_blocks_top_ba = ni->mapping_blocks_ba; +- return false; +- } +- +- ni->block_mapping[lb] = pb; +- ni->mapping_blocks_top_ba--; +- ni->block_mapping_changed++; +- +- nlog_info(ni, "Logic block %u mapped to physical blcok %u\n", lb, pb); +- nmbm_mark_block_color_mapped(ni, pb); +- +- return true; +-} +- +-/* +- * nmbm_create_info_table - Create info table(s) +- * @ni: NMBM instance structure +- * +- * This function assumes that the chip has no existing info table(s) +- */ +-static bool nmbm_create_info_table(struct nmbm_instance *ni) +-{ +- uint32_t lb; +- bool success; +- +- /* Set initial mapping_blocks_top_off */ +- success = nmbm_block_walk(ni, false, ni->signature_ba, +- &ni->mapping_blocks_top_ba, 1, +- ni->mgmt_start_ba); +- if (!success) { +- nlog_err(ni, "No room for spare blocks\n"); +- return false; +- } +- +- /* Generate info table cache */ +- nmbm_generate_info_table_cache(ni); +- +- /* Write info table */ +- success = nmbm_rebuild_info_table(ni); +- if (!success) { +- nlog_err(ni, "Failed to build info tables\n"); +- return false; +- } +- +- /* Remap bad block(s) at end of data area */ +- for (lb = ni->data_block_count; lb < ni->mgmt_start_ba; lb++) { +- success = nmbm_map_block(ni, lb); +- if (!success) +- break; +- +- ni->data_block_count++; +- } +- +- /* If state table and/or mapping table changed, update info table. */ +- success = nmbm_update_info_table(ni); +- if (!success) +- return false; +- +- return true; +-} +- +-/* +- * nmbm_create_new - Create NMBM on a new chip +- * @ni: NMBM instance structure +- */ +-static bool nmbm_create_new(struct nmbm_instance *ni) +-{ +- bool success; +- +- /* Determine the boundary of management blocks */ +- ni->mgmt_start_ba = ni->block_count * (NMBM_MGMT_DIV - ni->lower.max_ratio) / NMBM_MGMT_DIV; +- +- if (ni->lower.max_reserved_blocks && ni->block_count - ni->mgmt_start_ba > ni->lower.max_reserved_blocks) +- ni->mgmt_start_ba = ni->block_count - ni->lower.max_reserved_blocks; +- +- nlog_info(ni, "NMBM management region starts at block %u [0x%08llx]\n", +- ni->mgmt_start_ba, ba2addr(ni, ni->mgmt_start_ba)); +- nmbm_mark_block_color_mgmt(ni, ni->mgmt_start_ba, ni->block_count - 1); +- +- /* Fill block state table & mapping table */ +- nmbm_scan_badblocks(ni); +- nmbm_build_mapping_table(ni); +- +- /* Write signature */ +- ni->signature.header.magic = NMBM_MAGIC_SIGNATURE; +- ni->signature.header.version = NMBM_VER; +- ni->signature.header.size = sizeof(ni->signature); +- ni->signature.nand_size = ni->lower.size; +- ni->signature.block_size = ni->lower.erasesize; +- ni->signature.page_size = ni->lower.writesize; +- ni->signature.spare_size = ni->lower.oobsize; +- ni->signature.mgmt_start_pb = ni->mgmt_start_ba; +- ni->signature.max_try_count = NMBM_TRY_COUNT; +- nmbm_update_checksum(&ni->signature.header); +- +- if (ni->lower.flags & NMBM_F_READ_ONLY) { +- nlog_info(ni, "NMBM has been initialized in read-only mode\n"); +- return true; +- } +- +- success = nmbm_write_signature(ni, ni->mgmt_start_ba, +- &ni->signature, &ni->signature_ba); +- if (!success) { +- nlog_err(ni, "Failed to write signature to a proper offset\n"); +- return false; +- } +- +- nlog_info(ni, "Signature has been written to block %u [0x%08llx]\n", +- ni->signature_ba, ba2addr(ni, ni->signature_ba)); +- nmbm_mark_block_color_signature(ni, ni->signature_ba); +- +- /* Write info table(s) */ +- success = nmbm_create_info_table(ni); +- if (success) { +- nlog_info(ni, "NMBM has been successfully created\n"); +- return true; +- } +- +- return false; +-} +- +-/* +- * nmbm_check_info_table_header - Check if a info table header is valid +- * @ni: NMBM instance structure +- * @data: pointer to the info table header +- */ +-static bool nmbm_check_info_table_header(struct nmbm_instance *ni, void *data) +-{ +- struct nmbm_info_table_header *ifthdr = data; +- +- if (ifthdr->header.magic != NMBM_MAGIC_INFO_TABLE) +- return false; +- +- if (ifthdr->header.size != ni->info_table_size) +- return false; +- +- if (ifthdr->mapping_table_off - ifthdr->state_table_off < ni->state_table_size) +- return false; +- +- if (ni->info_table_size - ifthdr->mapping_table_off < ni->mapping_table_size) +- return false; +- +- return true; +-} +- +-/* +- * nmbm_check_info_table - Check if a whole info table is valid +- * @ni: NMBM instance structure +- * @start_ba: start block address of this table +- * @end_ba: end block address of this table +- * @data: pointer to the info table header +- * @mapping_blocks_top_ba: return the block address of top remapped block +- */ +-static bool nmbm_check_info_table(struct nmbm_instance *ni, uint32_t start_ba, +- uint32_t end_ba, void *data, +- uint32_t *mapping_blocks_top_ba) +-{ +- struct nmbm_info_table_header *ifthdr = data; +- int32_t *block_mapping = (int32_t *)((uintptr_t)data + ifthdr->mapping_table_off); +- nmbm_bitmap_t *block_state = (nmbm_bitmap_t *)((uintptr_t)data + ifthdr->state_table_off); +- uint32_t minimum_mapping_pb = ni->signature_ba; +- uint32_t ba; +- +- for (ba = 0; ba < ni->data_block_count; ba++) { +- if ((block_mapping[ba] >= ni->data_block_count && block_mapping[ba] < end_ba) || +- block_mapping[ba] == ni->signature_ba) +- return false; +- +- if (block_mapping[ba] >= end_ba && block_mapping[ba] < minimum_mapping_pb) +- minimum_mapping_pb = block_mapping[ba]; +- } +- +- for (ba = start_ba; ba < end_ba; ba++) { +- if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) +- continue; +- +- if (nmbm_get_block_state_raw(block_state, ba) != BLOCK_ST_GOOD) +- return false; +- } +- +- *mapping_blocks_top_ba = minimum_mapping_pb - 1; +- +- return true; +-} +- +-/* +- * nmbm_try_load_info_table - Try to load info table from a address +- * @ni: NMBM instance structure +- * @ba: start block address of the info table +- * @eba: return the block address after end of the table +- * @write_count: return the write count of this table +- * @mapping_blocks_top_ba: return the block address of top remapped block +- * @table_loaded: used to record whether ni->info_table has valid data +- */ +-static bool nmbm_try_load_info_table(struct nmbm_instance *ni, uint32_t ba, +- uint32_t *eba, uint32_t *write_count, +- uint32_t *mapping_blocks_top_ba, +- bool table_loaded) +-{ +- struct nmbm_info_table_header *ifthdr = (void *)ni->info_table_cache; +- uint8_t *off = ni->info_table_cache; +- uint32_t limit = ba + size2blk(ni, ni->info_table_size); +- uint32_t start_ba = 0, chunksize, sizeremain = ni->info_table_size; +- bool success, checkhdr = true; +- int ret; +- +- while (sizeremain && ba < limit) { +- WATCHDOG_RESET(); +- +- if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) +- goto next_block; +- +- if (nmbm_check_bad_phys_block(ni, ba)) { +- nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); +- goto next_block; +- } +- +- chunksize = sizeremain; +- if (chunksize > ni->lower.erasesize) +- chunksize = ni->lower.erasesize; +- +- /* Assume block with ECC error has no info table data */ +- ret = nmbn_read_data(ni, ba2addr(ni, ba), off, chunksize); +- if (ret < 0) +- goto skip_bad_block; +- else if (ret > 0) +- return false; +- +- if (checkhdr) { +- success = nmbm_check_info_table_header(ni, off); +- if (!success) +- return false; +- +- start_ba = ba; +- checkhdr = false; +- } +- +- off += chunksize; +- sizeremain -= chunksize; +- +- goto next_block; +- +- skip_bad_block: +- /* Only mark bad in memory */ +- nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); +- +- next_block: +- ba++; +- } +- +- if (sizeremain) +- return false; +- +- success = nmbm_check_header(ni->info_table_cache, ni->info_table_size); +- if (!success) +- return false; +- +- *eba = ba; +- *write_count = ifthdr->write_count; +- +- success = nmbm_check_info_table(ni, start_ba, ba, ni->info_table_cache, +- mapping_blocks_top_ba); +- if (!success) +- return false; +- +- if (!table_loaded || ifthdr->write_count > ni->info_table.write_count) { +- memcpy(&ni->info_table, ifthdr, sizeof(ni->info_table)); +- memcpy(ni->block_state, +- (uint8_t *)ifthdr + ifthdr->state_table_off, +- ni->state_table_size); +- memcpy(ni->block_mapping, +- (uint8_t *)ifthdr + ifthdr->mapping_table_off, +- ni->mapping_table_size); +- ni->info_table.write_count = ifthdr->write_count; +- } +- +- return true; +-} +- +-/* +- * nmbm_search_info_table - Search info table from specific address +- * @ni: NMBM instance structure +- * @ba: start block address to search +- * @limit: highest block address allowed for searching +- * @table_start_ba: return the start block address of this table +- * @table_end_ba: return the block address after end of this table +- * @write_count: return the write count of this table +- * @mapping_blocks_top_ba: return the block address of top remapped block +- * @table_loaded: used to record whether ni->info_table has valid data +- */ +-static bool nmbm_search_info_table(struct nmbm_instance *ni, uint32_t ba, +- uint32_t limit, uint32_t *table_start_ba, +- uint32_t *table_end_ba, +- uint32_t *write_count, +- uint32_t *mapping_blocks_top_ba, +- bool table_loaded) +-{ +- bool success; +- +- while (ba < limit - size2blk(ni, ni->info_table_size)) { +- WATCHDOG_RESET(); +- +- success = nmbm_try_load_info_table(ni, ba, table_end_ba, +- write_count, +- mapping_blocks_top_ba, +- table_loaded); +- if (success) { +- *table_start_ba = ba; +- return true; +- } +- +- ba++; +- } +- +- return false; +-} +- +-/* +- * nmbm_load_info_table - Load info table(s) from a chip +- * @ni: NMBM instance structure +- * @ba: start block address to search info table +- * @limit: highest block address allowed for searching +- */ +-static bool nmbm_load_info_table(struct nmbm_instance *ni, uint32_t ba, +- uint32_t limit) +-{ +- uint32_t main_table_end_ba, backup_table_end_ba, table_end_ba; +- uint32_t main_mapping_blocks_top_ba, backup_mapping_blocks_top_ba; +- uint32_t main_table_write_count, backup_table_write_count; +- uint32_t i; +- bool success; +- +- /* Set initial value */ +- ni->main_table_ba = 0; +- ni->backup_table_ba = 0; +- ni->info_table.write_count = 0; +- ni->mapping_blocks_top_ba = ni->signature_ba - 1; +- ni->data_block_count = ni->signature.mgmt_start_pb; +- +- /* Find first info table */ +- success = nmbm_search_info_table(ni, ba, limit, &ni->main_table_ba, +- &main_table_end_ba, &main_table_write_count, +- &main_mapping_blocks_top_ba, false); +- if (!success) { +- nlog_warn(ni, "No valid info table found\n"); +- return false; +- } +- +- table_end_ba = main_table_end_ba; +- +- nlog_table_found(ni, true, main_table_write_count, ni->main_table_ba, +- main_table_end_ba); +- +- /* Find second info table */ +- success = nmbm_search_info_table(ni, main_table_end_ba, limit, +- &ni->backup_table_ba, &backup_table_end_ba, +- &backup_table_write_count, &backup_mapping_blocks_top_ba, true); +- if (!success) { +- nlog_warn(ni, "Second info table not found\n"); +- } else { +- table_end_ba = backup_table_end_ba; +- +- nlog_table_found(ni, false, backup_table_write_count, +- ni->backup_table_ba, backup_table_end_ba); +- } +- +- /* Pick mapping_blocks_top_ba */ +- if (!ni->backup_table_ba) { +- ni->mapping_blocks_top_ba= main_mapping_blocks_top_ba; +- } else { +- if (main_table_write_count >= backup_table_write_count) +- ni->mapping_blocks_top_ba = main_mapping_blocks_top_ba; +- else +- ni->mapping_blocks_top_ba = backup_mapping_blocks_top_ba; +- } +- +- /* Set final mapping_blocks_ba */ +- ni->mapping_blocks_ba = table_end_ba; +- +- /* Set final data_block_count */ +- for (i = ni->signature.mgmt_start_pb; i > 0; i--) { +- if (ni->block_mapping[i - 1] >= 0) { +- ni->data_block_count = i; +- break; +- } +- } +- +- /* Debug purpose: mark mapped blocks and bad blocks */ +- for (i = 0; i < ni->data_block_count; i++) { +- if (ni->block_mapping[i] > ni->mapping_blocks_top_ba) +- nmbm_mark_block_color_mapped(ni, ni->block_mapping[i]); +- } +- +- for (i = 0; i < ni->block_count; i++) { +- if (nmbm_get_block_state(ni, i) == BLOCK_ST_BAD) +- nmbm_mark_block_color_bad(ni, i); +- } +- +- /* Regenerate the info table cache from the final selected info table */ +- nmbm_generate_info_table_cache(ni); +- +- if (ni->lower.flags & NMBM_F_READ_ONLY) +- return true; +- +- /* +- * If only one table exists, try to write another table. +- * If two tables have different write count, try to update info table +- */ +- if (!ni->backup_table_ba) { +- success = nmbm_rescue_single_info_table(ni); +- } else if (main_table_write_count != backup_table_write_count) { +- /* Mark state & mapping tables changed */ +- ni->block_state_changed = 1; +- ni->block_mapping_changed = 1; +- +- success = nmbm_update_single_info_table(ni, +- main_table_write_count < backup_table_write_count); +- } else { +- success = true; +- } +- +- /* +- * If there is no spare unmapped blocks, or still only one table +- * exists, set the chip to read-only +- */ +- if (ni->mapping_blocks_ba == ni->mapping_blocks_top_ba) { +- nlog_warn(ni, "No spare unmapped blocks. Device is now read-only\n"); +- ni->protected = 1; +- } else if (!success) { +- nlog_warn(ni, "Only one info table found. Device is now read-only\n"); +- ni->protected = 1; +- } +- +- return true; +-} +- +-/* +- * nmbm_load_existing - Load NMBM from a new chip +- * @ni: NMBM instance structure +- */ +-static bool nmbm_load_existing(struct nmbm_instance *ni) +-{ +- bool success; +- +- /* Calculate the boundary of management blocks */ +- ni->mgmt_start_ba = ni->signature.mgmt_start_pb; +- +- nlog_debug(ni, "NMBM management region starts at block %u [0x%08llx]\n", +- ni->mgmt_start_ba, ba2addr(ni, ni->mgmt_start_ba)); +- nmbm_mark_block_color_mgmt(ni, ni->mgmt_start_ba, +- ni->signature_ba - 1); +- +- /* Look for info table(s) */ +- success = nmbm_load_info_table(ni, ni->mgmt_start_ba, +- ni->signature_ba); +- if (success) { +- nlog_info(ni, "NMBM has been successfully attached %s\n", +- (ni->lower.flags & NMBM_F_READ_ONLY) ? "in read-only mode" : ""); +- return true; +- } +- +- if (!(ni->lower.flags & NMBM_F_CREATE)) +- return false; +- +- /* Fill block state table & mapping table */ +- nmbm_scan_badblocks(ni); +- nmbm_build_mapping_table(ni); +- +- if (ni->lower.flags & NMBM_F_READ_ONLY) { +- nlog_info(ni, "NMBM has been initialized in read-only mode\n"); +- return true; +- } +- +- /* Write info table(s) */ +- success = nmbm_create_info_table(ni); +- if (success) { +- nlog_info(ni, "NMBM has been successfully created\n"); +- return true; +- } +- +- return false; +-} +- +-/* +- * nmbm_find_signature - Find signature in the lower NAND chip +- * @ni: NMBM instance structure +- * @signature_ba: used for storing block address of the signature +- * @signature_ba: return the actual block address of signature block +- * +- * Find a valid signature from a specific range in the lower NAND chip, +- * from bottom (highest address) to top (lowest address) +- * +- * Return true if found. +- */ +-static bool nmbm_find_signature(struct nmbm_instance *ni, +- struct nmbm_signature *signature, +- uint32_t *signature_ba) +-{ +- struct nmbm_signature sig; +- uint64_t off, addr; +- uint32_t block_count, ba, limit; +- bool success; +- int ret; +- +- /* Calculate top and bottom block address */ +- block_count = ni->lower.size >> ni->erasesize_shift; +- ba = block_count; +- limit = (block_count / NMBM_MGMT_DIV) * (NMBM_MGMT_DIV - ni->lower.max_ratio); +- if (ni->lower.max_reserved_blocks && block_count - limit > ni->lower.max_reserved_blocks) +- limit = block_count - ni->lower.max_reserved_blocks; +- +- while (ba >= limit) { +- WATCHDOG_RESET(); +- +- ba--; +- addr = ba2addr(ni, ba); +- +- if (nmbm_check_bad_phys_block(ni, ba)) +- continue; +- +- /* Check every page. +- * As long as at leaset one page contains valid signature, +- * the block is treated as a valid signature block. +- */ +- for (off = 0; off < ni->lower.erasesize; +- off += ni->lower.writesize) { +- WATCHDOG_RESET(); +- +- ret = nmbn_read_data(ni, addr + off, &sig, +- sizeof(sig)); +- if (ret) +- continue; +- +- /* Check for header size and checksum */ +- success = nmbm_check_header(&sig, sizeof(sig)); +- if (!success) +- continue; +- +- /* Check for header magic */ +- if (sig.header.magic == NMBM_MAGIC_SIGNATURE) { +- /* Found it */ +- memcpy(signature, &sig, sizeof(sig)); +- *signature_ba = ba; +- return true; +- } +- } +- }; +- +- return false; +-} +- +-/* +- * is_power_of_2_u64 - Check whether a 64-bit integer is power of 2 +- * @n: number to check +- */ +-static bool is_power_of_2_u64(uint64_t n) +-{ +- return (n != 0 && ((n & (n - 1)) == 0)); +-} +- +-/* +- * nmbm_check_lower_members - Validate the members of lower NAND device +- * @nld: Lower NAND chip structure +- */ +-static bool nmbm_check_lower_members(struct nmbm_lower_device *nld) +-{ +- +- if (!nld->size || !is_power_of_2_u64(nld->size)) { +- nmbm_log_lower(nld, NMBM_LOG_ERR, +- "Chip size %llu is not valid\n", nld->size); +- return false; +- } +- +- if (!nld->erasesize || !is_power_of_2(nld->erasesize)) { +- nmbm_log_lower(nld, NMBM_LOG_ERR, +- "Block size %u is not valid\n", nld->erasesize); +- return false; +- } +- +- if (!nld->writesize || !is_power_of_2(nld->writesize)) { +- nmbm_log_lower(nld, NMBM_LOG_ERR, +- "Page size %u is not valid\n", nld->writesize); +- return false; +- } +- +- if (!nld->oobsize || !is_power_of_2(nld->oobsize)) { +- nmbm_log_lower(nld, NMBM_LOG_ERR, +- "Page spare size %u is not valid\n", nld->oobsize); +- return false; +- } +- +- if (!nld->read_page) { +- nmbm_log_lower(nld, NMBM_LOG_ERR, "read_page() is required\n"); +- return false; +- } +- +- if (!(nld->flags & NMBM_F_READ_ONLY) && (!nld->write_page || !nld->erase_block)) { +- nmbm_log_lower(nld, NMBM_LOG_ERR, +- "write_page() and erase_block() are required\n"); +- return false; +- } +- +- /* Data sanity check */ +- if (!nld->max_ratio) +- nld->max_ratio = 1; +- +- if (nld->max_ratio >= NMBM_MGMT_DIV - 1) { +- nmbm_log_lower(nld, NMBM_LOG_ERR, +- "max ratio %u is invalid\n", nld->max_ratio); +- return false; +- } +- +- if (nld->max_reserved_blocks && nld->max_reserved_blocks < NMBM_MGMT_BLOCKS_MIN) { +- nmbm_log_lower(nld, NMBM_LOG_ERR, +- "max reserved blocks %u is too small\n", nld->max_reserved_blocks); +- return false; +- } +- +- return true; +-} +- +-/* +- * nmbm_calc_structure_size - Calculate the instance structure size +- * @nld: NMBM lower device structure +- */ +-size_t nmbm_calc_structure_size(struct nmbm_lower_device *nld) +-{ +- uint32_t state_table_size, mapping_table_size, info_table_size; +- uint32_t block_count; +- +- block_count = nmbm_lldiv(nld->size, nld->erasesize); +- +- /* Calculate info table size */ +- state_table_size = ((block_count + NMBM_BITMAP_BLOCKS_PER_UNIT - 1) / +- NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_UNIT_SIZE; +- mapping_table_size = block_count * sizeof(int32_t); +- +- info_table_size = NMBM_ALIGN(sizeof(struct nmbm_info_table_header), +- nld->writesize); +- info_table_size += NMBM_ALIGN(state_table_size, nld->writesize); +- info_table_size += NMBM_ALIGN(mapping_table_size, nld->writesize); +- +- return info_table_size + state_table_size + mapping_table_size + +- nld->writesize + nld->oobsize + sizeof(struct nmbm_instance); +-} +- +-/* +- * nmbm_init_structure - Initialize members of instance structure +- * @ni: NMBM instance structure +- */ +-static void nmbm_init_structure(struct nmbm_instance *ni) +-{ +- uint32_t pages_per_block, blocks_per_chip; +- uintptr_t ptr; +- +- pages_per_block = ni->lower.erasesize / ni->lower.writesize; +- blocks_per_chip = nmbm_lldiv(ni->lower.size, ni->lower.erasesize); +- +- ni->rawpage_size = ni->lower.writesize + ni->lower.oobsize; +- ni->rawblock_size = pages_per_block * ni->rawpage_size; +- ni->rawchip_size = blocks_per_chip * ni->rawblock_size; +- +- ni->writesize_mask = ni->lower.writesize - 1; +- ni->erasesize_mask = ni->lower.erasesize - 1; +- +- ni->writesize_shift = ffs(ni->lower.writesize) - 1; +- ni->erasesize_shift = ffs(ni->lower.erasesize) - 1; +- +- /* Calculate number of block this chip */ +- ni->block_count = ni->lower.size >> ni->erasesize_shift; +- +- /* Calculate info table size */ +- ni->state_table_size = ((ni->block_count + NMBM_BITMAP_BLOCKS_PER_UNIT - 1) / +- NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_UNIT_SIZE; +- ni->mapping_table_size = ni->block_count * sizeof(*ni->block_mapping); +- +- ni->info_table_size = NMBM_ALIGN(sizeof(ni->info_table), +- ni->lower.writesize); +- ni->info_table.state_table_off = ni->info_table_size; +- +- ni->info_table_size += NMBM_ALIGN(ni->state_table_size, +- ni->lower.writesize); +- ni->info_table.mapping_table_off = ni->info_table_size; +- +- ni->info_table_size += NMBM_ALIGN(ni->mapping_table_size, +- ni->lower.writesize); +- +- ni->info_table_spare_blocks = nmbm_get_spare_block_count( +- size2blk(ni, ni->info_table_size)); +- +- /* Assign memory to members */ +- ptr = (uintptr_t)ni + sizeof(*ni); +- +- ni->info_table_cache = (void *)ptr; +- ptr += ni->info_table_size; +- +- ni->block_state = (void *)ptr; +- ptr += ni->state_table_size; +- +- ni->block_mapping = (void *)ptr; +- ptr += ni->mapping_table_size; +- +- ni->page_cache = (uint8_t *)ptr; +- +- /* Initialize block state table */ +- ni->block_state_changed = 0; +- memset(ni->block_state, 0xff, ni->state_table_size); +- +- /* Initialize block mapping table */ +- ni->block_mapping_changed = 0; +-} +- +-/* +- * nmbm_attach - Attach to a lower device +- * @nld: NMBM lower device structure +- * @ni: NMBM instance structure +- */ +-int nmbm_attach(struct nmbm_lower_device *nld, struct nmbm_instance *ni) +-{ +- bool success; +- +- if (!nld || !ni) +- return -EINVAL; +- +- /* Set default log level */ +- ni->log_display_level = NMBM_DEFAULT_LOG_LEVEL; +- +- /* Check lower members */ +- success = nmbm_check_lower_members(nld); +- if (!success) +- return -EINVAL; +- +- /* Initialize NMBM instance */ +- memcpy(&ni->lower, nld, sizeof(struct nmbm_lower_device)); +- nmbm_init_structure(ni); +- +- success = nmbm_find_signature(ni, &ni->signature, &ni->signature_ba); +- if (!success) { +- if (!(nld->flags & NMBM_F_CREATE)) { +- nlog_err(ni, "Signature not found\n"); +- return -ENODEV; +- } +- +- success = nmbm_create_new(ni); +- if (!success) +- return -ENODEV; +- +- return 0; +- } +- +- nlog_info(ni, "Signature found at block %u [0x%08llx]\n", +- ni->signature_ba, ba2addr(ni, ni->signature_ba)); +- nmbm_mark_block_color_signature(ni, ni->signature_ba); +- +- if (ni->signature.header.version != NMBM_VER) { +- nlog_err(ni, "NMBM version %u.%u is not supported\n", +- NMBM_VERSION_MAJOR_GET(ni->signature.header.version), +- NMBM_VERSION_MINOR_GET(ni->signature.header.version)); +- return -EINVAL; +- } +- +- if (ni->signature.nand_size != nld->size || +- ni->signature.block_size != nld->erasesize || +- ni->signature.page_size != nld->writesize || +- ni->signature.spare_size != nld->oobsize) { +- nlog_err(ni, "NMBM configuration mismatch\n"); +- return -EINVAL; +- } +- +- success = nmbm_load_existing(ni); +- if (!success) +- return -ENODEV; +- +- return 0; +-} +- +-/* +- * nmbm_detach - Detach from a lower device, and save all tables +- * @ni: NMBM instance structure +- */ +-int nmbm_detach(struct nmbm_instance *ni) +-{ +- if (!ni) +- return -EINVAL; +- +- if (!(ni->lower.flags & NMBM_F_READ_ONLY)) +- nmbm_update_info_table(ni); +- +- nmbm_mark_block_color_normal(ni, 0, ni->block_count - 1); +- +- return 0; +-} +- +-/* +- * nmbm_erase_logic_block - Erase a logic block +- * @ni: NMBM instance structure +- * @nmbm_erase_logic_block: logic block address +- * +- * Logic block will be mapped to physical block before erasing. +- * Bad block found during erasinh will be remapped to a good block if there is +- * still at least one good spare block available. +- */ +-static int nmbm_erase_logic_block(struct nmbm_instance *ni, uint32_t block_addr) +-{ +- uint32_t pb; +- bool success; +- +-retry: +- /* Map logic block to physical block */ +- pb = ni->block_mapping[block_addr]; +- +- /* Whether the logic block is good (has valid mapping) */ +- if ((int32_t)pb < 0) { +- nlog_debug(ni, "Logic block %u is a bad block\n", block_addr); +- return -EIO; +- } +- +- /* Remap logic block if current physical block is a bad block */ +- if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD || +- nmbm_get_block_state(ni, pb) == BLOCK_ST_NEED_REMAP) +- goto remap_logic_block; +- +- /* Insurance to detect unexpected bad block marked by user */ +- if (nmbm_check_bad_phys_block(ni, pb)) { +- nlog_warn(ni, "Found unexpected bad block possibly marked by user\n"); +- nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); +- goto remap_logic_block; +- } +- +- success = nmbm_erase_block_and_check(ni, pb); +- if (success) +- return 0; +- +- /* Mark bad block */ +- nmbm_mark_phys_bad_block(ni, pb); +- nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); +- +-remap_logic_block: +- /* Try to assign a new block */ +- success = nmbm_map_block(ni, block_addr); +- if (!success) { +- /* Mark logic block unusable, and update info table */ +- ni->block_mapping[block_addr] = -1; +- if (nmbm_get_block_state(ni, pb) != BLOCK_ST_NEED_REMAP) +- nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); +- nmbm_update_info_table(ni); +- return -EIO; +- } +- +- /* Update info table before erasing */ +- if (nmbm_get_block_state(ni, pb) != BLOCK_ST_NEED_REMAP) +- nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); +- nmbm_update_info_table(ni); +- +- goto retry; +-} +- +-/* +- * nmbm_erase_block_range - Erase logic blocks +- * @ni: NMBM instance structure +- * @addr: logic linear address +- * @size: erase range +- * @failed_addr: return failed block address if error occurs +- */ +-int nmbm_erase_block_range(struct nmbm_instance *ni, uint64_t addr, +- uint64_t size, uint64_t *failed_addr) +-{ +- uint32_t start_ba, end_ba; +- int ret; +- +- if (!ni) +- return -EINVAL; +- +- /* Sanity check */ +- if (ni->protected || (ni->lower.flags & NMBM_F_READ_ONLY)) { +- nlog_debug(ni, "Device is forced read-only\n"); +- return -EROFS; +- } +- +- if (addr >= ba2addr(ni, ni->data_block_count)) { +- nlog_err(ni, "Address 0x%llx is invalid\n", addr); +- return -EINVAL; +- } +- +- if (addr + size > ba2addr(ni, ni->data_block_count)) { +- nlog_err(ni, "Erase range 0xllxu is too large\n", size); +- return -EINVAL; +- } +- +- if (!size) { +- nlog_warn(ni, "No blocks to be erased\n"); +- return 0; +- } +- +- start_ba = addr2ba(ni, addr); +- end_ba = addr2ba(ni, addr + size - 1); +- +- while (start_ba <= end_ba) { +- WATCHDOG_RESET(); +- +- ret = nmbm_erase_logic_block(ni, start_ba); +- if (ret) { +- if (failed_addr) +- *failed_addr = ba2addr(ni, start_ba); +- return ret; +- } +- +- start_ba++; +- } +- +- return 0; +-} +- +-/* +- * nmbm_read_logic_page - Read page based on logic address +- * @ni: NMBM instance structure +- * @addr: logic linear address +- * @data: buffer to store main data. optional. +- * @oob: buffer to store oob data. optional. +- * @mode: read mode +- * +- * Return 0 for success, positive value for corrected bitflip count, +- * -EBADMSG for ecc error, other negative values for other errors +- */ +-static int nmbm_read_logic_page(struct nmbm_instance *ni, uint64_t addr, +- void *data, void *oob, enum nmbm_oob_mode mode) +-{ +- uint32_t lb, pb, offset; +- uint64_t paddr; +- +- /* Extract block address and in-block offset */ +- lb = addr2ba(ni, addr); +- offset = addr & ni->erasesize_mask; +- +- /* Map logic block to physical block */ +- pb = ni->block_mapping[lb]; +- +- /* Whether the logic block is good (has valid mapping) */ +- if ((int32_t)pb < 0) { +- nlog_debug(ni, "Logic block %u is a bad block\n", lb); +- return -EIO; +- } +- +- /* Fail if physical block is marked bad */ +- if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) +- return -EIO; +- +- /* Assemble new address */ +- paddr = ba2addr(ni, pb) + offset; +- +- return nmbm_read_phys_page(ni, paddr, data, oob, mode); +-} +- +-/* +- * nmbm_read_single_page - Read one page based on logic address +- * @ni: NMBM instance structure +- * @addr: logic linear address +- * @data: buffer to store main data. optional. +- * @oob: buffer to store oob data. optional. +- * @mode: read mode +- * +- * Return 0 for success, positive value for corrected bitflip count, +- * -EBADMSG for ecc error, other negative values for other errors +- */ +-int nmbm_read_single_page(struct nmbm_instance *ni, uint64_t addr, void *data, +- void *oob, enum nmbm_oob_mode mode) +-{ +- if (!ni) +- return -EINVAL; +- +- /* Sanity check */ +- if (ni->protected) { +- nlog_debug(ni, "Device is forced read-only\n"); +- return -EROFS; +- } +- +- if (addr >= ba2addr(ni, ni->data_block_count)) { +- nlog_err(ni, "Address 0x%llx is invalid\n", addr); +- return -EINVAL; +- } +- +- return nmbm_read_logic_page(ni, addr, data, oob, mode); +-} +- +-/* +- * nmbm_read_range - Read data without oob +- * @ni: NMBM instance structure +- * @addr: logic linear address +- * @size: data size to read +- * @data: buffer to store main data to be read +- * @mode: read mode +- * @retlen: return actual data size read +- * +- * Return 0 for success, positive value for corrected bitflip count, +- * -EBADMSG for ecc error, other negative values for other errors +- */ +-int nmbm_read_range(struct nmbm_instance *ni, uint64_t addr, size_t size, +- void *data, enum nmbm_oob_mode mode, size_t *retlen) +-{ +- uint64_t off = addr; +- uint8_t *ptr = data; +- size_t sizeremain = size, chunksize, leading; +- bool has_ecc_err = false; +- int ret, max_bitflips = 0; +- +- if (!ni) +- return -EINVAL; +- +- /* Sanity check */ +- if (ni->protected) { +- nlog_debug(ni, "Device is forced read-only\n"); +- return -EROFS; +- } +- +- if (addr >= ba2addr(ni, ni->data_block_count)) { +- nlog_err(ni, "Address 0x%llx is invalid\n", addr); +- return -EINVAL; +- } +- +- if (addr + size > ba2addr(ni, ni->data_block_count)) { +- nlog_err(ni, "Read range 0x%llx is too large\n", size); +- return -EINVAL; +- } +- +- if (!size) { +- nlog_warn(ni, "No data to be read\n"); +- return 0; +- } +- +- while (sizeremain) { +- WATCHDOG_RESET(); +- +- leading = off & ni->writesize_mask; +- chunksize = ni->lower.writesize - leading; +- if (chunksize > sizeremain) +- chunksize = sizeremain; +- +- if (chunksize == ni->lower.writesize) { +- ret = nmbm_read_logic_page(ni, off - leading, ptr, +- NULL, mode); +- if (ret < 0 && ret != -EBADMSG) +- break; +- } else { +- ret = nmbm_read_logic_page(ni, off - leading, +- ni->page_cache, NULL, +- mode); +- if (ret < 0 && ret != -EBADMSG) +- break; +- +- memcpy(ptr, ni->page_cache + leading, chunksize); +- } +- +- if (ret == -EBADMSG) +- has_ecc_err = true; +- +- if (ret > max_bitflips) +- max_bitflips = ret; +- +- off += chunksize; +- ptr += chunksize; +- sizeremain -= chunksize; +- } +- +- if (retlen) +- *retlen = size - sizeremain; +- +- if (ret < 0 && ret != -EBADMSG) +- return ret; +- +- if (has_ecc_err) +- return -EBADMSG; +- +- return max_bitflips; +-} +- +-/* +- * nmbm_write_logic_page - Read page based on logic address +- * @ni: NMBM instance structure +- * @addr: logic linear address +- * @data: buffer contains main data. optional. +- * @oob: buffer contains oob data. optional. +- * @mode: write mode +- */ +-static int nmbm_write_logic_page(struct nmbm_instance *ni, uint64_t addr, +- const void *data, const void *oob, +- enum nmbm_oob_mode mode) +-{ +- uint32_t lb, pb, offset; +- uint64_t paddr; +- bool success; +- +- /* Extract block address and in-block offset */ +- lb = addr2ba(ni, addr); +- offset = addr & ni->erasesize_mask; +- +- /* Map logic block to physical block */ +- pb = ni->block_mapping[lb]; +- +- /* Whether the logic block is good (has valid mapping) */ +- if ((int32_t)pb < 0) { +- nlog_debug(ni, "Logic block %u is a bad block\n", lb); +- return -EIO; +- } +- +- /* Fail if physical block is marked bad */ +- if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) +- return -EIO; +- +- /* Assemble new address */ +- paddr = ba2addr(ni, pb) + offset; +- +- success = nmbm_write_phys_page(ni, paddr, data, oob, mode); +- if (success) +- return 0; +- +- /* +- * Do not remap bad block here. Just mark this block in state table. +- * Remap this block on erasing. +- */ +- nmbm_set_block_state(ni, pb, BLOCK_ST_NEED_REMAP); +- nmbm_update_info_table(ni); +- +- return -EIO; +-} +- +-/* +- * nmbm_write_single_page - Write one page based on logic address +- * @ni: NMBM instance structure +- * @addr: logic linear address +- * @data: buffer contains main data. optional. +- * @oob: buffer contains oob data. optional. +- * @mode: write mode +- */ +-int nmbm_write_single_page(struct nmbm_instance *ni, uint64_t addr, +- const void *data, const void *oob, +- enum nmbm_oob_mode mode) +-{ +- if (!ni) +- return -EINVAL; +- +- /* Sanity check */ +- if (ni->protected || (ni->lower.flags & NMBM_F_READ_ONLY)) { +- nlog_debug(ni, "Device is forced read-only\n"); +- return -EROFS; +- } +- +- if (addr >= ba2addr(ni, ni->data_block_count)) { +- nlog_err(ni, "Address 0x%llx is invalid\n", addr); +- return -EINVAL; +- } +- +- return nmbm_write_logic_page(ni, addr, data, oob, mode); +-} +- +-/* +- * nmbm_write_range - Write data without oob +- * @ni: NMBM instance structure +- * @addr: logic linear address +- * @size: data size to write +- * @data: buffer contains data to be written +- * @mode: write mode +- * @retlen: return actual data size written +- */ +-int nmbm_write_range(struct nmbm_instance *ni, uint64_t addr, size_t size, +- const void *data, enum nmbm_oob_mode mode, +- size_t *retlen) +-{ +- uint64_t off = addr; +- const uint8_t *ptr = data; +- size_t sizeremain = size, chunksize, leading; +- int ret; +- +- if (!ni) +- return -EINVAL; +- +- /* Sanity check */ +- if (ni->protected || (ni->lower.flags & NMBM_F_READ_ONLY)) { +- nlog_debug(ni, "Device is forced read-only\n"); +- return -EROFS; +- } +- +- if (addr >= ba2addr(ni, ni->data_block_count)) { +- nlog_err(ni, "Address 0x%llx is invalid\n", addr); +- return -EINVAL; +- } +- +- if (addr + size > ba2addr(ni, ni->data_block_count)) { +- nlog_err(ni, "Write size 0x%zx is too large\n", size); +- return -EINVAL; +- } +- +- if (!size) { +- nlog_warn(ni, "No data to be written\n"); +- return 0; +- } +- +- while (sizeremain) { +- WATCHDOG_RESET(); +- +- leading = off & ni->writesize_mask; +- chunksize = ni->lower.writesize - leading; +- if (chunksize > sizeremain) +- chunksize = sizeremain; +- +- if (chunksize == ni->lower.writesize) { +- ret = nmbm_write_logic_page(ni, off - leading, ptr, +- NULL, mode); +- if (ret) +- break; +- } else { +- memset(ni->page_cache, 0xff, leading); +- memcpy(ni->page_cache + leading, ptr, chunksize); +- +- ret = nmbm_write_logic_page(ni, off - leading, +- ni->page_cache, NULL, +- mode); +- if (ret) +- break; +- } +- +- off += chunksize; +- ptr += chunksize; +- sizeremain -= chunksize; +- } +- +- if (retlen) +- *retlen = size - sizeremain; +- +- return ret; +-} +- +-/* +- * nmbm_check_bad_block - Check whether a logic block is usable +- * @ni: NMBM instance structure +- * @addr: logic linear address +- */ +-int nmbm_check_bad_block(struct nmbm_instance *ni, uint64_t addr) +-{ +- uint32_t lb, pb; +- +- if (!ni) +- return -EINVAL; +- +- if (addr >= ba2addr(ni, ni->data_block_count)) { +- nlog_err(ni, "Address 0x%llx is invalid\n", addr); +- return -EINVAL; +- } +- +- lb = addr2ba(ni, addr); +- +- /* Map logic block to physical block */ +- pb = ni->block_mapping[lb]; +- +- if ((int32_t)pb < 0) +- return 1; +- +- if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) +- return 1; +- +- return 0; +-} +- +-/* +- * nmbm_mark_bad_block - Mark a logic block unusable +- * @ni: NMBM instance structure +- * @addr: logic linear address +- */ +-int nmbm_mark_bad_block(struct nmbm_instance *ni, uint64_t addr) +-{ +- uint32_t lb, pb; +- +- if (!ni) +- return -EINVAL; +- +- /* Sanity check */ +- if (ni->protected || (ni->lower.flags & NMBM_F_READ_ONLY)) { +- nlog_debug(ni, "Device is forced read-only\n"); +- return -EROFS; +- } +- +- if (addr >= ba2addr(ni, ni->data_block_count)) { +- nlog_err(ni, "Address 0x%llx is invalid\n", addr); +- return -EINVAL; +- } +- +- lb = addr2ba(ni, addr); +- +- /* Map logic block to physical block */ +- pb = ni->block_mapping[lb]; +- +- if ((int32_t)pb < 0) +- return 0; +- +- ni->block_mapping[lb] = -1; +- nmbm_mark_phys_bad_block(ni, pb); +- nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); +- nmbm_update_info_table(ni); +- +- return 0; +-} +- +-/* +- * nmbm_get_avail_size - Get available user data size +- * @ni: NMBM instance structure +- */ +-uint64_t nmbm_get_avail_size(struct nmbm_instance *ni) +-{ +- if (!ni) +- return 0; +- +- return (uint64_t)ni->data_block_count << ni->erasesize_shift; +-} +- +-/* +- * nmbm_get_lower_device - Get lower device structure +- * @ni: NMBM instance structure +- * @nld: pointer to hold the data of lower device structure +- */ +-int nmbm_get_lower_device(struct nmbm_instance *ni, struct nmbm_lower_device *nld) +-{ +- if (!ni) +- return -EINVAL; +- +- if (nld) +- memcpy(nld, &ni->lower, sizeof(*nld)); +- +- return 0; +-} +- +-#include "nmbm-debug.inl" +diff --git a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.h b/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.h +deleted file mode 100644 +index 7f9dfed9e1..0000000000 +--- a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.h ++++ /dev/null +@@ -1,20 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0 */ +-/* +- * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. +- * +- * Debug addons for NAND Mapped-block Management (NMBM) +- * +- * Author: Weijie Gao +- */ +- +-#ifndef _NMBM_DEBUG_H_ +-#define _NMBM_DEBUG_H_ +- +-#define nmbm_mark_block_color_normal(ni, start_ba, end_ba) +-#define nmbm_mark_block_color_bad(ni, ba) +-#define nmbm_mark_block_color_mgmt(ni, start_ba, end_ba) +-#define nmbm_mark_block_color_signature(ni, ba) +-#define nmbm_mark_block_color_info_table(ni, start_ba, end_ba) +-#define nmbm_mark_block_color_mapped(ni, ba) +- +-#endif /* _NMBM_DEBUG_H_ */ +diff --git a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.inl b/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-debug.inl +deleted file mode 100644 +index e69de29bb2..0000000000 +diff --git a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-mtd.c b/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-mtd.c +deleted file mode 100644 +index a3e9e1832d..0000000000 +--- a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-mtd.c ++++ /dev/null +@@ -1,795 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0 +-/* +- * MTD layer for NAND Mapped-block Management (NMBM) +- * +- * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. +- * +- * Author: Weijie Gao +- */ +- +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +- +-#include "nmbm-private.h" +-#include "nmbm-debug.h" +- +-#define NMBM_MAX_RATIO_DEFAULT 1 +-#define NMBM_MAX_BLOCKS_DEFAULT 256 +- +-struct nmbm_mtd { +- struct mtd_info upper; +- struct mtd_info *lower; +- +- struct nmbm_instance *ni; +- uint8_t *page_cache; +- +- flstate_t state; +- spinlock_t lock; +- wait_queue_head_t wq; +- +- struct device *dev; +- struct list_head node; +-}; +- +-struct list_head nmbm_devs; +-static DEFINE_MUTEX(nmbm_devs_lock); +- +-static int nmbm_lower_read_page(void *arg, uint64_t addr, void *buf, void *oob, +- enum nmbm_oob_mode mode) +-{ +- struct nmbm_mtd *nm = arg; +- struct mtd_oob_ops ops; +- int ret; +- +- memset(&ops, 0, sizeof(ops)); +- +- switch (mode) { +- case NMBM_MODE_PLACE_OOB: +- ops.mode = MTD_OPS_PLACE_OOB; +- break; +- case NMBM_MODE_AUTO_OOB: +- ops.mode = MTD_OPS_AUTO_OOB; +- break; +- case NMBM_MODE_RAW: +- ops.mode = MTD_OPS_RAW; +- break; +- default: +- pr_debug("%s: unsupported NMBM mode: %u\n", __func__, mode); +- return -ENOTSUPP; +- } +- +- if (buf) { +- ops.datbuf = buf; +- ops.len = nm->lower->writesize; +- } +- +- if (oob) { +- ops.oobbuf = oob; +- ops.ooblen = mtd_oobavail(nm->lower, &ops); +- } +- +- ret = mtd_read_oob(nm->lower, addr, &ops); +- nm->upper.ecc_stats.corrected = nm->lower->ecc_stats.corrected; +- nm->upper.ecc_stats.failed = nm->lower->ecc_stats.failed; +- +- /* Report error on failure (including ecc error) */ +- if (ret < 0 && ret != -EUCLEAN) +- return ret; +- +- /* +- * Since mtd_read_oob() won't report exact bitflips, what we can know +- * is whether bitflips exceeds the threshold. +- * We want the -EUCLEAN to be passed to the upper layer, but not the +- * error value itself. To achieve this, report bitflips above the +- * threshold. +- */ +- +- if (ret == -EUCLEAN) { +- return min_t(u32, nm->lower->bitflip_threshold + 1, +- nm->lower->ecc_strength); +- } +- +- /* For bitflips less than the threshold, return 0 */ +- return 0; +-} +- +-static int nmbm_lower_write_page(void *arg, uint64_t addr, const void *buf, +- const void *oob, enum nmbm_oob_mode mode) +-{ +- struct nmbm_mtd *nm = arg; +- struct mtd_oob_ops ops; +- +- memset(&ops, 0, sizeof(ops)); +- +- switch (mode) { +- case NMBM_MODE_PLACE_OOB: +- ops.mode = MTD_OPS_PLACE_OOB; +- break; +- case NMBM_MODE_AUTO_OOB: +- ops.mode = MTD_OPS_AUTO_OOB; +- break; +- case NMBM_MODE_RAW: +- ops.mode = MTD_OPS_RAW; +- break; +- default: +- pr_debug("%s: unsupported NMBM mode: %u\n", __func__, mode); +- return -ENOTSUPP; +- } +- +- if (buf) { +- ops.datbuf = (uint8_t *)buf; +- ops.len = nm->lower->writesize; +- } +- +- if (oob) { +- ops.oobbuf = (uint8_t *)oob; +- ops.ooblen = mtd_oobavail(nm->lower, &ops); +- } +- +- return mtd_write_oob(nm->lower, addr, &ops); +-} +- +-static int nmbm_lower_erase_block(void *arg, uint64_t addr) +-{ +- struct nmbm_mtd *nm = arg; +- struct erase_info ei; +- +- memset(&ei, 0, sizeof(ei)); +- +- ei.addr = addr; +- ei.len = nm->lower->erasesize; +- +- return mtd_erase(nm->lower, &ei); +-} +- +-static int nmbm_lower_is_bad_block(void *arg, uint64_t addr) +-{ +- struct nmbm_mtd *nm = arg; +- +- return mtd_block_isbad(nm->lower, addr); +-} +- +-static int nmbm_lower_mark_bad_block(void *arg, uint64_t addr) +-{ +- struct nmbm_mtd *nm = arg; +- +- return mtd_block_markbad(nm->lower, addr); +-} +- +-static void nmbm_lower_log(void *arg, enum nmbm_log_category level, +- const char *fmt, va_list ap) +-{ +- struct nmbm_mtd *nm = arg; +- char *msg; +- char *kl; +- +- msg = kvasprintf(GFP_KERNEL, fmt, ap); +- if (!msg) { +- dev_warn(nm->dev, "unable to print log\n"); +- return; +- } +- +- switch (level) { +- case NMBM_LOG_DEBUG: +- kl = KERN_DEBUG; +- break; +- case NMBM_LOG_WARN: +- kl = KERN_WARNING; +- break; +- case NMBM_LOG_ERR: +- kl = KERN_ERR; +- break; +- case NMBM_LOG_EMERG: +- kl = KERN_EMERG; +- break; +- default: +- kl = KERN_INFO ; +- } +- +- dev_printk(kl, nm->dev, "%s", msg); +- +- kfree(msg); +-} +- +-static int nmbm_get_device(struct nmbm_mtd *nm, int new_state) +-{ +- DECLARE_WAITQUEUE(wait, current); +- +-retry: +- spin_lock(&nm->lock); +- +- if (nm->state == FL_READY) { +- nm->state = new_state; +- spin_unlock(&nm->lock); +- return 0; +- } +- +- if (new_state == FL_PM_SUSPENDED) { +- if (nm->state == FL_PM_SUSPENDED) { +- spin_unlock(&nm->lock); +- return 0; +- } +- } +- +- set_current_state(TASK_UNINTERRUPTIBLE); +- add_wait_queue(&nm->wq, &wait); +- spin_unlock(&nm->lock); +- schedule(); +- remove_wait_queue(&nm->wq, &wait); +- goto retry; +-} +- +-static void nmbm_release_device(struct nmbm_mtd *nm) +-{ +- spin_lock(&nm->lock); +- nm->state = FL_READY; +- wake_up(&nm->wq); +- spin_unlock(&nm->lock); +-} +- +-static int nmbm_mtd_erase(struct mtd_info *mtd, struct erase_info *instr) +-{ +- struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); +- int ret; +- +- nmbm_get_device(nm, FL_ERASING); +- +- ret = nmbm_erase_block_range(nm->ni, instr->addr, instr->len, +- &instr->fail_addr); +- +- nmbm_release_device(nm); +- +- if (!ret) +- return 0; +- +- return -EIO; +-} +- +-static int nmbm_mtd_read_data(struct nmbm_mtd *nm, uint64_t addr, +- struct mtd_oob_ops *ops, enum nmbm_oob_mode mode) +-{ +- size_t len, ooblen, maxooblen, chklen; +- uint32_t col, ooboffs; +- uint8_t *datcache, *oobcache; +- bool has_ecc_err = false; +- int ret, max_bitflips = 0; +- +- col = addr & nm->lower->writesize_mask; +- addr &= ~nm->lower->writesize_mask; +- maxooblen = mtd_oobavail(nm->lower, ops); +- ooboffs = ops->ooboffs; +- ooblen = ops->ooblen; +- len = ops->len; +- +- datcache = len ? nm->page_cache : NULL; +- oobcache = ooblen ? nm->page_cache + nm->lower->writesize : NULL; +- +- ops->oobretlen = 0; +- ops->retlen = 0; +- +- while (len || ooblen) { +- ret = nmbm_read_single_page(nm->ni, addr, datcache, oobcache, +- mode); +- if (ret < 0 && ret != -EBADMSG) +- return ret; +- +- /* Continue reading on ecc error */ +- if (ret == -EBADMSG) +- has_ecc_err = true; +- +- /* Record the maximum bitflips between pages */ +- if (ret > max_bitflips) +- max_bitflips = ret; +- +- if (len) { +- /* Move data */ +- chklen = nm->lower->writesize - col; +- if (chklen > len) +- chklen = len; +- +- memcpy(ops->datbuf + ops->retlen, datcache + col, +- chklen); +- len -= chklen; +- col = 0; /* (col + chklen) % */ +- ops->retlen += chklen; +- } +- +- if (ooblen) { +- /* Move oob */ +- chklen = maxooblen - ooboffs; +- if (chklen > ooblen) +- chklen = ooblen; +- +- memcpy(ops->oobbuf + ops->oobretlen, oobcache + ooboffs, +- chklen); +- ooblen -= chklen; +- ooboffs = 0; /* (ooboffs + chklen) % maxooblen; */ +- ops->oobretlen += chklen; +- } +- +- addr += nm->lower->writesize; +- } +- +- if (has_ecc_err) +- return -EBADMSG; +- +- return max_bitflips; +-} +- +-static int nmbm_mtd_read_oob(struct mtd_info *mtd, loff_t from, +- struct mtd_oob_ops *ops) +-{ +- struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); +- uint32_t maxooblen; +- enum nmbm_oob_mode mode; +- int ret; +- +- if (!ops->oobbuf && !ops->datbuf) { +- if (ops->ooblen || ops->len) +- return -EINVAL; +- +- return 0; +- } +- +- switch (ops->mode) { +- case MTD_OPS_PLACE_OOB: +- mode = NMBM_MODE_PLACE_OOB; +- break; +- case MTD_OPS_AUTO_OOB: +- mode = NMBM_MODE_AUTO_OOB; +- break; +- case MTD_OPS_RAW: +- mode = NMBM_MODE_RAW; +- break; +- default: +- pr_debug("%s: unsupported oob mode: %u\n", __func__, ops->mode); +- return -ENOTSUPP; +- } +- +- maxooblen = mtd_oobavail(mtd, ops); +- +- /* Do not allow read past end of device */ +- if (ops->datbuf && (from + ops->len) > mtd->size) { +- pr_debug("%s: attempt to read beyond end of device\n", +- __func__); +- return -EINVAL; +- } +- +- if (!ops->oobbuf) { +- nmbm_get_device(nm, FL_READING); +- +- /* Optimized for reading data only */ +- ret = nmbm_read_range(nm->ni, from, ops->len, ops->datbuf, +- mode, &ops->retlen); +- +- nmbm_release_device(nm); +- +- return ret; +- } +- +- if (unlikely(ops->ooboffs >= maxooblen)) { +- pr_debug("%s: attempt to start read outside oob\n", +- __func__); +- return -EINVAL; +- } +- +- if (unlikely(from >= mtd->size || +- ops->ooboffs + ops->ooblen > ((mtd->size >> mtd->writesize_shift) - +- (from >> mtd->writesize_shift)) * maxooblen)) { +- pr_debug("%s: attempt to read beyond end of device\n", +- __func__); +- return -EINVAL; +- } +- +- nmbm_get_device(nm, FL_READING); +- ret = nmbm_mtd_read_data(nm, from, ops, mode); +- nmbm_release_device(nm); +- +- return ret; +-} +- +-static int nmbm_mtd_write_data(struct nmbm_mtd *nm, uint64_t addr, +- struct mtd_oob_ops *ops, enum nmbm_oob_mode mode) +-{ +- size_t len, ooblen, maxooblen, chklen; +- uint32_t col, ooboffs; +- uint8_t *datcache, *oobcache; +- int ret; +- +- col = addr & nm->lower->writesize_mask; +- addr &= ~nm->lower->writesize_mask; +- maxooblen = mtd_oobavail(nm->lower, ops); +- ooboffs = ops->ooboffs; +- ooblen = ops->ooblen; +- len = ops->len; +- +- datcache = len ? nm->page_cache : NULL; +- oobcache = ooblen ? nm->page_cache + nm->lower->writesize : NULL; +- +- ops->oobretlen = 0; +- ops->retlen = 0; +- +- while (len || ooblen) { +- if (len) { +- /* Move data */ +- chklen = nm->lower->writesize - col; +- if (chklen > len) +- chklen = len; +- +- memset(datcache, 0xff, col); +- memcpy(datcache + col, ops->datbuf + ops->retlen, +- chklen); +- memset(datcache + col + chklen, 0xff, +- nm->lower->writesize - col - chklen); +- len -= chklen; +- col = 0; /* (col + chklen) % */ +- ops->retlen += chklen; +- } +- +- if (ooblen) { +- /* Move oob */ +- chklen = maxooblen - ooboffs; +- if (chklen > ooblen) +- chklen = ooblen; +- +- memset(oobcache, 0xff, ooboffs); +- memcpy(oobcache + ooboffs, +- ops->oobbuf + ops->oobretlen, chklen); +- memset(oobcache + ooboffs + chklen, 0xff, +- nm->lower->oobsize - ooboffs - chklen); +- ooblen -= chklen; +- ooboffs = 0; /* (ooboffs + chklen) % maxooblen; */ +- ops->oobretlen += chklen; +- } +- +- ret = nmbm_write_single_page(nm->ni, addr, datcache, oobcache, +- mode); +- if (ret) +- return ret; +- +- addr += nm->lower->writesize; +- } +- +- return 0; +-} +- +-static int nmbm_mtd_write_oob(struct mtd_info *mtd, loff_t to, +- struct mtd_oob_ops *ops) +-{ +- struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); +- enum nmbm_oob_mode mode; +- uint32_t maxooblen; +- int ret; +- +- if (!ops->oobbuf && !ops->datbuf) { +- if (ops->ooblen || ops->len) +- return -EINVAL; +- +- return 0; +- } +- +- switch (ops->mode) { +- case MTD_OPS_PLACE_OOB: +- mode = NMBM_MODE_PLACE_OOB; +- break; +- case MTD_OPS_AUTO_OOB: +- mode = NMBM_MODE_AUTO_OOB; +- break; +- case MTD_OPS_RAW: +- mode = NMBM_MODE_RAW; +- break; +- default: +- pr_debug("%s: unsupported oob mode: %u\n", __func__, +- ops->mode); +- return -ENOTSUPP; +- } +- +- maxooblen = mtd_oobavail(mtd, ops); +- +- /* Do not allow write past end of device */ +- if (ops->datbuf && (to + ops->len) > mtd->size) { +- pr_debug("%s: attempt to write beyond end of device\n", +- __func__); +- return -EINVAL; +- } +- +- if (!ops->oobbuf) { +- nmbm_get_device(nm, FL_WRITING); +- +- /* Optimized for writing data only */ +- ret = nmbm_write_range(nm->ni, to, ops->len, ops->datbuf, +- mode, &ops->retlen); +- +- nmbm_release_device(nm); +- +- return ret; +- } +- +- if (unlikely(ops->ooboffs >= maxooblen)) { +- pr_debug("%s: attempt to start write outside oob\n", +- __func__); +- return -EINVAL; +- } +- +- if (unlikely(to >= mtd->size || +- ops->ooboffs + ops->ooblen > ((mtd->size >> mtd->writesize_shift) - +- (to >> mtd->writesize_shift)) * maxooblen)) { +- pr_debug("%s: attempt to write beyond end of device\n", +- __func__); +- return -EINVAL; +- } +- +- nmbm_get_device(nm, FL_WRITING); +- ret = nmbm_mtd_write_data(nm, to, ops, mode); +- nmbm_release_device(nm); +- +- return ret; +-} +- +-static int nmbm_mtd_block_isbad(struct mtd_info *mtd, loff_t offs) +-{ +- struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); +- int ret; +- +- nmbm_get_device(nm, FL_READING); +- ret = nmbm_check_bad_block(nm->ni, offs); +- nmbm_release_device(nm); +- +- return ret; +-} +- +-static int nmbm_mtd_block_markbad(struct mtd_info *mtd, loff_t offs) +-{ +- struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); +- int ret; +- +- nmbm_get_device(nm, FL_WRITING); +- ret = nmbm_mark_bad_block(nm->ni, offs); +- nmbm_release_device(nm); +- +- return ret; +-} +- +-static void nmbm_mtd_shutdown(struct mtd_info *mtd) +-{ +- struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); +- +- nmbm_get_device(nm, FL_PM_SUSPENDED); +-} +- +-static int nmbm_probe(struct platform_device *pdev) +-{ +- struct device_node *mtd_np, *np = pdev->dev.of_node; +- uint32_t max_ratio, max_reserved_blocks, alloc_size; +- bool forced_create, empty_page_ecc_ok; +- struct nmbm_lower_device nld; +- struct mtd_info *lower, *mtd; +- struct nmbm_mtd *nm; +- const char *mtdname; +- int ret; +- +- mtd_np = of_parse_phandle(np, "lower-mtd-device", 0); +- if (mtd_np) { +- lower = get_mtd_device_by_node(mtd_np); +- if (!IS_ERR(lower)) +- goto do_attach_mtd; +- +- dev_dbg(&pdev->dev, "failed to find mtd device by phandle\n"); +- return -EPROBE_DEFER; +- } +- +- ret = of_property_read_string(np, "lower-mtd-name", &mtdname); +- if (!ret) { +- lower = get_mtd_device_nm(mtdname); +- if (!IS_ERR(lower)) +- goto do_attach_mtd; +- +- dev_dbg(&pdev->dev, "failed to find mtd device by name '%s'\n", +- mtdname); +- return -EPROBE_DEFER; +- } +- +-do_attach_mtd: +- if (of_property_read_u32(np, "max-ratio", &max_ratio)) +- max_ratio = NMBM_MAX_RATIO_DEFAULT; +- +- if (of_property_read_u32(np, "max-reserved-blocks", +- &max_reserved_blocks)) +- max_reserved_blocks = NMBM_MAX_BLOCKS_DEFAULT; +- +- forced_create = of_property_read_bool(np, "forced-create"); +- empty_page_ecc_ok = of_property_read_bool(np, +- "empty-page-ecc-protected"); +- +- memset(&nld, 0, sizeof(nld)); +- +- nld.flags = 0; +- +- if (forced_create) +- nld.flags |= NMBM_F_CREATE; +- +- if (empty_page_ecc_ok) +- nld.flags |= NMBM_F_EMPTY_PAGE_ECC_OK; +- +- nld.max_ratio = max_ratio; +- nld.max_reserved_blocks = max_reserved_blocks; +- +- nld.size = lower->size; +- nld.erasesize = lower->erasesize; +- nld.writesize = lower->writesize; +- nld.oobsize = lower->oobsize; +- nld.oobavail = lower->oobavail; +- +- nld.read_page = nmbm_lower_read_page; +- nld.write_page = nmbm_lower_write_page; +- nld.erase_block = nmbm_lower_erase_block; +- nld.is_bad_block = nmbm_lower_is_bad_block; +- nld.mark_bad_block = nmbm_lower_mark_bad_block; +- +- nld.logprint = nmbm_lower_log; +- +- alloc_size = nmbm_calc_structure_size(&nld); +- +- nm = devm_kzalloc(&pdev->dev, sizeof(*nm) + alloc_size + +- lower->writesize + lower->oobsize, GFP_KERNEL); +- if (!nm) { +- ret = -ENOMEM; +- goto out; +- } +- +- nm->ni = (void *)nm + sizeof(*nm); +- nm->page_cache = (uint8_t *)nm->ni + alloc_size; +- nm->lower = lower; +- nm->dev = &pdev->dev; +- +- INIT_LIST_HEAD(&nm->node); +- spin_lock_init(&nm->lock); +- init_waitqueue_head(&nm->wq); +- +- nld.arg = nm; +- +- ret = nmbm_attach(&nld, nm->ni); +- if (ret) +- goto out; +- +- /* Initialize upper mtd */ +- mtd = &nm->upper; +- +- mtd->owner = THIS_MODULE; +- mtd->dev.parent = &pdev->dev; +- mtd->type = lower->type; +- mtd->flags = lower->flags; +- +- mtd->size = (uint64_t)nm->ni->data_block_count * lower->erasesize; +- mtd->erasesize = lower->erasesize; +- mtd->writesize = lower->writesize; +- mtd->writebufsize = lower->writesize; +- mtd->oobsize = lower->oobsize; +- mtd->oobavail = lower->oobavail; +- +- mtd->erasesize_shift = lower->erasesize_shift; +- mtd->writesize_shift = lower->writesize_shift; +- mtd->erasesize_mask = lower->erasesize_mask; +- mtd->writesize_mask = lower->writesize_mask; +- +- mtd->bitflip_threshold = lower->bitflip_threshold; +- +- mtd->ooblayout = lower->ooblayout; +- +- mtd->ecc_step_size = lower->ecc_step_size; +- mtd->ecc_strength = lower->ecc_strength; +- +- mtd->numeraseregions = lower->numeraseregions; +- mtd->eraseregions = lower->eraseregions; +- +- mtd->_erase = nmbm_mtd_erase; +- mtd->_read_oob = nmbm_mtd_read_oob; +- mtd->_write_oob = nmbm_mtd_write_oob; +- mtd->_block_isbad = nmbm_mtd_block_isbad; +- mtd->_block_markbad = nmbm_mtd_block_markbad; +- mtd->_reboot = nmbm_mtd_shutdown; +- +- mtd_set_of_node(mtd, np); +- +- ret = mtd_device_register(mtd, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "failed to register mtd device\n"); +- nmbm_detach(nm->ni); +- goto out; +- } +- +- platform_set_drvdata(pdev, nm); +- +- mutex_lock(&nmbm_devs_lock); +- list_add_tail(&nm->node, &nmbm_devs); +- mutex_unlock(&nmbm_devs_lock); +- +- return 0; +- +-out: +- if (nm) +- devm_kfree(&pdev->dev, nm); +- +- put_mtd_device(lower); +- +- return ret; +-} +- +-static int nmbm_remove(struct platform_device *pdev) +-{ +- struct nmbm_mtd *nm = platform_get_drvdata(pdev); +- struct mtd_info *lower = nm->lower; +- int ret; +- +- ret = mtd_device_unregister(&nm->upper); +- if (ret) +- return ret; +- +- nmbm_detach(nm->ni); +- +- mutex_lock(&nmbm_devs_lock); +- list_add_tail(&nm->node, &nmbm_devs); +- mutex_unlock(&nmbm_devs_lock); +- +- devm_kfree(&pdev->dev, nm); +- +- put_mtd_device(lower); +- +- platform_set_drvdata(pdev, NULL); +- +- return 0; +-} +- +-static const struct of_device_id nmbm_ids[] = { +- { .compatible = "generic,nmbm" }, +- { }, +-}; +- +-MODULE_DEVICE_TABLE(of, nmbm_ids); +- +-static struct platform_driver nmbm_driver = { +- .probe = nmbm_probe, +- .remove = nmbm_remove, +- .driver = { +- .name = "nmbm", +- .of_match_table = nmbm_ids, +- }, +-}; +- +-static int __init nmbm_init(void) +-{ +- int ret; +- +- INIT_LIST_HEAD(&nmbm_devs); +- +- ret = platform_driver_register(&nmbm_driver); +- if (ret) { +- pr_err("failed to register nmbm driver\n"); +- return ret; +- } +- +- return 0; +-} +-module_init(nmbm_init); +- +-static void __exit nmbm_exit(void) +-{ +- platform_driver_unregister(&nmbm_driver); +-} +-module_exit(nmbm_exit); +- +-MODULE_LICENSE("GPL"); +-MODULE_AUTHOR("Weijie Gao "); +-MODULE_DESCRIPTION("NAND mapping block management"); +diff --git a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-private.h b/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-private.h +deleted file mode 100644 +index c285aeb9dd..0000000000 +--- a/target/linux/ramips/files/drivers/mtd/nmbm/nmbm-private.h ++++ /dev/null +@@ -1,137 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +-/* +- * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. +- * +- * Definitions for NAND Mapped-block Management (NMBM) +- * +- * Author: Weijie Gao +- */ +- +-#ifndef _NMBM_PRIVATE_H_ +-#define _NMBM_PRIVATE_H_ +- +-#include +- +-#define NMBM_MAGIC_SIGNATURE 0x304d4d4e /* NMM0 */ +-#define NMBM_MAGIC_INFO_TABLE 0x314d4d4e /* NMM1 */ +- +-#define NMBM_VERSION_MAJOR_S 0 +-#define NMBM_VERSION_MAJOR_M 0xffff +-#define NMBM_VERSION_MINOR_S 16 +-#define NMBM_VERSION_MINOR_M 0xffff +-#define NMBM_VERSION_MAKE(major, minor) (((major) & NMBM_VERSION_MAJOR_M) | \ +- (((minor) & NMBM_VERSION_MINOR_M) << \ +- NMBM_VERSION_MINOR_S)) +-#define NMBM_VERSION_MAJOR_GET(ver) (((ver) >> NMBM_VERSION_MAJOR_S) & \ +- NMBM_VERSION_MAJOR_M) +-#define NMBM_VERSION_MINOR_GET(ver) (((ver) >> NMBM_VERSION_MINOR_S) & \ +- NMBM_VERSION_MINOR_M) +- +-typedef uint32_t nmbm_bitmap_t; +-#define NMBM_BITMAP_UNIT_SIZE (sizeof(nmbm_bitmap_t)) +-#define NMBM_BITMAP_BITS_PER_BLOCK 2 +-#define NMBM_BITMAP_BITS_PER_UNIT (8 * sizeof(nmbm_bitmap_t)) +-#define NMBM_BITMAP_BLOCKS_PER_UNIT (NMBM_BITMAP_BITS_PER_UNIT / \ +- NMBM_BITMAP_BITS_PER_BLOCK) +- +-#define NMBM_SPARE_BLOCK_MULTI 1 +-#define NMBM_SPARE_BLOCK_DIV 2 +-#define NMBM_SPARE_BLOCK_MIN 2 +- +-#define NMBM_MGMT_DIV 16 +-#define NMBM_MGMT_BLOCKS_MIN 32 +- +-#define NMBM_TRY_COUNT 3 +- +-#define BLOCK_ST_BAD 0 +-#define BLOCK_ST_NEED_REMAP 2 +-#define BLOCK_ST_GOOD 3 +-#define BLOCK_ST_MASK 3 +- +-struct nmbm_header { +- uint32_t magic; +- uint32_t version; +- uint32_t size; +- uint32_t checksum; +-}; +- +-struct nmbm_signature { +- struct nmbm_header header; +- uint64_t nand_size; +- uint32_t block_size; +- uint32_t page_size; +- uint32_t spare_size; +- uint32_t mgmt_start_pb; +- uint8_t max_try_count; +- uint8_t padding[3]; +-}; +- +-struct nmbm_info_table_header { +- struct nmbm_header header; +- uint32_t write_count; +- uint32_t state_table_off; +- uint32_t mapping_table_off; +- uint32_t padding; +-}; +- +-struct nmbm_instance { +- struct nmbm_lower_device lower; +- +- uint32_t rawpage_size; +- uint32_t rawblock_size; +- uint32_t rawchip_size; +- +- uint32_t writesize_mask; +- uint32_t erasesize_mask; +- uint16_t writesize_shift; +- uint16_t erasesize_shift; +- +- struct nmbm_signature signature; +- +- uint8_t *info_table_cache; +- uint32_t info_table_size; +- uint32_t info_table_spare_blocks; +- struct nmbm_info_table_header info_table; +- +- nmbm_bitmap_t *block_state; +- uint32_t block_state_changed; +- uint32_t state_table_size; +- +- int32_t *block_mapping; +- uint32_t block_mapping_changed; +- uint32_t mapping_table_size; +- +- uint8_t *page_cache; +- +- int protected; +- +- uint32_t block_count; +- uint32_t data_block_count; +- +- uint32_t mgmt_start_ba; +- uint32_t main_table_ba; +- uint32_t backup_table_ba; +- uint32_t mapping_blocks_ba; +- uint32_t mapping_blocks_top_ba; +- uint32_t signature_ba; +- +- enum nmbm_log_category log_display_level; +-}; +- +-/* Log utilities */ +-#define nlog_debug(ni, fmt, ...) \ +- nmbm_log(ni, NMBM_LOG_DEBUG, fmt, ##__VA_ARGS__) +- +-#define nlog_info(ni, fmt, ...) \ +- nmbm_log(ni, NMBM_LOG_INFO, fmt, ##__VA_ARGS__) +- +-#define nlog_warn(ni, fmt, ...) \ +- nmbm_log(ni, NMBM_LOG_WARN, fmt, ##__VA_ARGS__) +- +-#define nlog_err(ni, fmt, ...) \ +- nmbm_log(ni, NMBM_LOG_ERR, fmt, ##__VA_ARGS__) +- +-#define nlog_emerg(ni, fmt, ...) \ +- nmbm_log(ni, NMBM_LOG_EMERG, fmt, ##__VA_ARGS__) +- +-#endif /* _NMBM_PRIVATE_H_ */ +diff --git a/target/linux/ramips/files/include/nmbm/nmbm-os.h b/target/linux/ramips/files/include/nmbm/nmbm-os.h +deleted file mode 100644 +index 1cae854df1..0000000000 +--- a/target/linux/ramips/files/include/nmbm/nmbm-os.h ++++ /dev/null +@@ -1,69 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0 */ +-/* +- * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. +- * +- * OS-dependent definitions for NAND Mapped-block Management (NMBM) +- * +- * Author: Weijie Gao +- */ +- +-#ifndef _NMBM_OS_H_ +-#define _NMBM_OS_H_ +- +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +- +-static inline uint32_t nmbm_crc32(uint32_t crcval, const void *buf, size_t size) +-{ +- uint chksz; +- const unsigned char *p = buf; +- +- while (size) { +- if (size > UINT_MAX) +- chksz = UINT_MAX; +- else +- chksz = (uint)size; +- +- crcval = crc32_le(crcval, p, chksz); +- size -= chksz; +- p += chksz; +- } +- +- return crcval; +-} +- +-static inline uint32_t nmbm_lldiv(uint64_t dividend, uint32_t divisor) +-{ +-#if BITS_PER_LONG == 64 +- return dividend / divisor; +-#else +- do_div(dividend, divisor); +- return dividend; +-#endif +-} +- +-#define WATCHDOG_RESET() +- +-#ifdef CONFIG_NMBM_LOG_LEVEL_DEBUG +-#define NMBM_DEFAULT_LOG_LEVEL 0 +-#elif defined(NMBM_LOG_LEVEL_INFO) +-#define NMBM_DEFAULT_LOG_LEVEL 1 +-#elif defined(NMBM_LOG_LEVEL_WARN) +-#define NMBM_DEFAULT_LOG_LEVEL 2 +-#elif defined(NMBM_LOG_LEVEL_ERR) +-#define NMBM_DEFAULT_LOG_LEVEL 3 +-#elif defined(NMBM_LOG_LEVEL_EMERG) +-#define NMBM_DEFAULT_LOG_LEVEL 4 +-#elif defined(NMBM_LOG_LEVEL_NONE) +-#define NMBM_DEFAULT_LOG_LEVEL 5 +-#else +-#define NMBM_DEFAULT_LOG_LEVEL 1 +-#endif +- +-#endif /* _NMBM_OS_H_ */ +diff --git a/target/linux/ramips/files/include/nmbm/nmbm.h b/target/linux/ramips/files/include/nmbm/nmbm.h +deleted file mode 100644 +index c0400988b1..0000000000 +--- a/target/linux/ramips/files/include/nmbm/nmbm.h ++++ /dev/null +@@ -1,102 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +-/* +- * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. +- * +- * Definitions for NAND Mapped-block Management (NMBM) +- * +- * Author: Weijie Gao +- */ +- +-#ifndef _NMBM_H_ +-#define _NMBM_H_ +- +-#include +- +-enum nmbm_log_category { +- NMBM_LOG_DEBUG, +- NMBM_LOG_INFO, +- NMBM_LOG_WARN, +- NMBM_LOG_ERR, +- NMBM_LOG_EMERG, +- +- __NMBM_LOG_MAX +-}; +- +-enum nmbm_oob_mode { +- NMBM_MODE_PLACE_OOB, +- NMBM_MODE_AUTO_OOB, +- NMBM_MODE_RAW, +- +- __NMBM_MODE_MAX +-}; +- +-struct nmbm_lower_device { +- uint32_t max_ratio; +- uint32_t max_reserved_blocks; +- int flags; +- +- uint64_t size; +- uint32_t erasesize; +- uint32_t writesize; +- uint32_t oobsize; +- uint32_t oobavail; +- +- void *arg; +- int (*reset_chip)(void *arg); +- +- /* +- * read_page: +- * return 0 if succeeds +- * return positive number for ecc error +- * return negative number for other errors +- */ +- int (*read_page)(void *arg, uint64_t addr, void *buf, void *oob, enum nmbm_oob_mode mode); +- int (*write_page)(void *arg, uint64_t addr, const void *buf, const void *oob, enum nmbm_oob_mode mode); +- int (*erase_block)(void *arg, uint64_t addr); +- +- int (*is_bad_block)(void *arg, uint64_t addr); +- int (*mark_bad_block)(void *arg, uint64_t addr); +- +- /* OS-dependent logging function */ +- void (*logprint)(void *arg, enum nmbm_log_category level, const char *fmt, va_list ap); +-}; +- +-struct nmbm_instance; +- +-/* Create NMBM if management area not found, or not complete */ +-#define NMBM_F_CREATE 0x01 +- +-/* Empty page is also protected by ECC, and bitflip(s) can be corrected */ +-#define NMBM_F_EMPTY_PAGE_ECC_OK 0x02 +- +-/* Do not write anything back to flash */ +-#define NMBM_F_READ_ONLY 0x04 +- +-size_t nmbm_calc_structure_size(struct nmbm_lower_device *nld); +-int nmbm_attach(struct nmbm_lower_device *nld, struct nmbm_instance *ni); +-int nmbm_detach(struct nmbm_instance *ni); +- +-enum nmbm_log_category nmbm_set_log_level(struct nmbm_instance *ni, +- enum nmbm_log_category level); +- +-int nmbm_erase_block_range(struct nmbm_instance *ni, uint64_t addr, +- uint64_t size, uint64_t *failed_addr); +-int nmbm_read_single_page(struct nmbm_instance *ni, uint64_t addr, void *data, +- void *oob, enum nmbm_oob_mode mode); +-int nmbm_read_range(struct nmbm_instance *ni, uint64_t addr, size_t size, +- void *data, enum nmbm_oob_mode mode, size_t *retlen); +-int nmbm_write_single_page(struct nmbm_instance *ni, uint64_t addr, +- const void *data, const void *oob, +- enum nmbm_oob_mode mode); +-int nmbm_write_range(struct nmbm_instance *ni, uint64_t addr, size_t size, +- const void *data, enum nmbm_oob_mode mode, +- size_t *retlen); +- +-int nmbm_check_bad_block(struct nmbm_instance *ni, uint64_t addr); +-int nmbm_mark_bad_block(struct nmbm_instance *ni, uint64_t addr); +- +-uint64_t nmbm_get_avail_size(struct nmbm_instance *ni); +- +-int nmbm_get_lower_device(struct nmbm_instance *ni, struct nmbm_lower_device *nld); +- +-#endif /* _NMBM_H_ */ +diff --git a/target/linux/ramips/image/mt7621.mk b/target/linux/ramips/image/mt7621.mk +index 57c3efcf2b..81ef6bbd14 100644 +--- a/target/linux/ramips/image/mt7621.mk ++++ b/target/linux/ramips/image/mt7621.mk +@@ -111,11 +111,20 @@ define Device/dsa-migration + endef + + define Device/actiontec_web7200 ++ $(Device/dsa-migration) + DEVICE_VENDOR := Actiontec + DEVICE_MODEL := EB7200 + DEVICE_PACKAGES += kmod-mt7603 kmod-mt7915e kmod-usb3 uboot-envtools kmod-i2c-core ++ LOADER_TYPE := bin ++ KERNEL_SIZE := 4096k ++ BLOCKSIZE := 128k ++ PAGESIZE := 2048 ++ UBINIZE_OPTS := -E 5 ++ KERNEL_INITRAMFS := kernel-bin | lzma | loader-kernel | gzip | fit-relocate gzip $$(KDIR)/image-$$(firstword $$(DEVICE_DTS)).dtb + KERNEL := kernel-bin | relocate-kernel | lzma | fit-relocate lzma $$(KDIR)/image-$$(firstword $$(DEVICE_DTS)).dtb +- IMAGE_SIZE := 15552k ++ IMAGES += factory.bin ++ IMAGE/sysupgrade.bin := sysupgrade-tar | append-metadata ++ IMAGE/factory.bin := append-kernel | pad-to $$(KERNEL_SIZE) | append-ubi + endef + TARGET_DEVICES += actiontec_web7200 + +@@ -1404,6 +1413,15 @@ define Device/ubnt_unifi-6-lite + endef + TARGET_DEVICES += ubnt_unifi-6-lite + ++define Device/ubnt_unifi-nanohd ++ $(Device/dsa-migration) ++ DEVICE_VENDOR := Ubiquiti ++ DEVICE_MODEL := UniFi nanoHD ++ DEVICE_PACKAGES += kmod-mt7603 kmod-mt7615e kmod-mt7615-firmware ++ IMAGE_SIZE := 15552k ++endef ++TARGET_DEVICES += ubnt_unifi-nanohd ++ + define Device/unielec_u7621-01-16m + $(Device/dsa-migration) + $(Device/uimage-lzma-loader) +diff --git a/target/linux/ramips/mt7621/base-files/lib/upgrade/platform.sh b/target/linux/ramips/mt7621/base-files/lib/upgrade/platform.sh +index 924f259e25..93f6e836dc 100755 +--- a/target/linux/ramips/mt7621/base-files/lib/upgrade/platform.sh ++++ b/target/linux/ramips/mt7621/base-files/lib/upgrade/platform.sh +@@ -48,16 +48,19 @@ platform_do_upgrade() { + ;; + actiontec,web7200) + if grep -q bootselect=0 /proc/cmdline; then +- PART_NAME=firmware2 ++ CI_KERNPART=firmware2 ++ CI_UBIPART=ubi_2 + fw_setenv bootselect 1 || exit 1 + else +- PART_NAME=firmware1 ++ CI_KERNPART=firmware ++ CI_UBIPART=ubi_1 + fw_setenv bootselect 0 || exit 1 + fi + ;; + esac + + case "$board" in ++ actiontec,web7200|\ + ampedwireless,ally-00x19k|\ + ampedwireless,ally-r1900k|\ + asus,rt-ac65p|\ +diff --git a/target/linux/ramips/mt7621/config-5.4 b/target/linux/ramips/mt7621/config-5.4 +index ae4a0679bd..eada0ff602 100644 +--- a/target/linux/ramips/mt7621/config-5.4 ++++ b/target/linux/ramips/mt7621/config-5.4 +@@ -185,14 +185,6 @@ CONFIG_NET_MEDIATEK_SOC=y + CONFIG_NET_SWITCHDEV=y + CONFIG_NET_VENDOR_MEDIATEK=y + # CONFIG_NET_VENDOR_RALINK is not set +-CONFIG_NMBM=y +-# CONFIG_NMBM_LOG_LEVEL_DEBUG is not set +-# CONFIG_NMBM_LOG_LEVEL_EMERG is not set +-# CONFIG_NMBM_LOG_LEVEL_ERR is not set +-CONFIG_NMBM_LOG_LEVEL_INFO=y +-# CONFIG_NMBM_LOG_LEVEL_NONE is not set +-# CONFIG_NMBM_LOG_LEVEL_WARN is not set +-CONFIG_NMBM_MTD=y + CONFIG_NR_CPUS=4 + CONFIG_OF=y + CONFIG_OF_ADDRESS=y +diff --git a/target/linux/ramips/patches-5.4/499-mtd-add-nmbm-support.patch b/target/linux/ramips/patches-5.4/499-mtd-add-nmbm-support.patch +deleted file mode 100644 +index 5cbaae2b7b..0000000000 +--- a/target/linux/ramips/patches-5.4/499-mtd-add-nmbm-support.patch ++++ /dev/null +@@ -1,21 +0,0 @@ +---- a/drivers/mtd/Kconfig +-+++ b/drivers/mtd/Kconfig +-@@ -228,6 +228,8 @@ source "drivers/mtd/ubi/Kconfig" +- +- source "drivers/mtd/hyperbus/Kconfig" +- +-+source "drivers/mtd/nmbm/Kconfig" +-+ +- source "drivers/mtd/composite/Kconfig" +- +- endif # MTD +---- a/drivers/mtd/Makefile +-+++ b/drivers/mtd/Makefile +-@@ -33,5 +33,7 @@ obj-$(CONFIG_MTD_SPI_NOR) += spi-nor/ +- obj-$(CONFIG_MTD_UBI) += ubi/ +- obj-$(CONFIG_MTD_HYPERBUS) += hyperbus/ +- +-+obj-y += nmbm/ +-+ +- # Composite drivers must be loaded last +- obj-y += composite/ +-- +2.25.1 +