aboutsummaryrefslogtreecommitdiffstats
path: root/package/boot/uboot-mediatek/patches/100-07-mtd-nmbm-add-support-for-mtd.patch
diff options
context:
space:
mode:
Diffstat (limited to 'package/boot/uboot-mediatek/patches/100-07-mtd-nmbm-add-support-for-mtd.patch')
-rw-r--r--package/boot/uboot-mediatek/patches/100-07-mtd-nmbm-add-support-for-mtd.patch958
1 files changed, 958 insertions, 0 deletions
diff --git a/package/boot/uboot-mediatek/patches/100-07-mtd-nmbm-add-support-for-mtd.patch b/package/boot/uboot-mediatek/patches/100-07-mtd-nmbm-add-support-for-mtd.patch
new file mode 100644
index 0000000000..644ac8f105
--- /dev/null
+++ b/package/boot/uboot-mediatek/patches/100-07-mtd-nmbm-add-support-for-mtd.patch
@@ -0,0 +1,958 @@
+From 0524995f07fcd216a1a7e267fdb5cf2b0ede8489 Mon Sep 17 00:00:00 2001
+From: Weijie Gao <weijie.gao@mediatek.com>
+Date: Mon, 25 Jul 2022 10:42:12 +0800
+Subject: [PATCH 41/71] mtd: nmbm: add support for mtd
+
+Add support to create NMBM based on MTD devices
+
+Signed-off-by: Weijie Gao <weijie.gao@mediatek.com>
+---
+ drivers/mtd/nmbm/Kconfig | 5 +
+ drivers/mtd/nmbm/Makefile | 1 +
+ drivers/mtd/nmbm/nmbm-mtd.c | 890 ++++++++++++++++++++++++++++++++++++
+ include/nmbm/nmbm-mtd.h | 27 ++
+ 4 files changed, 923 insertions(+)
+ create mode 100644 drivers/mtd/nmbm/nmbm-mtd.c
+ create mode 100644 include/nmbm/nmbm-mtd.h
+
+--- a/drivers/mtd/nmbm/Kconfig
++++ b/drivers/mtd/nmbm/Kconfig
+@@ -27,3 +27,8 @@ 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
+--- a/drivers/mtd/nmbm/Makefile
++++ b/drivers/mtd/nmbm/Makefile
+@@ -3,3 +3,4 @@
+ # (C) Copyright 2020 MediaTek Inc. All rights reserved.
+
+ obj-$(CONFIG_NMBM) += nmbm-core.o
++obj-$(CONFIG_NMBM_MTD) += nmbm-mtd.o
+--- /dev/null
++++ b/drivers/mtd/nmbm/nmbm-mtd.c
+@@ -0,0 +1,890 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved.
++ *
++ * Author: Weijie Gao <weijie.gao@mediatek.com>
++ */
++
++#include <linux/list.h>
++#include <linux/bitops.h>
++#include <linux/kernel.h>
++#include <linux/types.h>
++#include <linux/mtd/mtd.h>
++#include <jffs2/load_kernel.h>
++#include <watchdog.h>
++
++#include "nmbm-debug.h"
++
++#define NMBM_UPPER_MTD_NAME "nmbm"
++
++static uint32_t nmbm_id_cnt;
++static LIST_HEAD(nmbm_devs);
++
++struct nmbm_mtd {
++ struct mtd_info upper;
++ char *name;
++ uint32_t id;
++
++ struct mtd_info *lower;
++
++ struct nmbm_instance *ni;
++ uint8_t *page_cache;
++
++ struct list_head node;
++};
++
++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.mtd = nm->lower;
++ 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)
++{
++ vprintf(fmt, ap);
++}
++
++static int nmbm_mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
++ size_t *retlen, u_char *buf)
++{
++ struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper);
++
++ /* Do not allow read past end of device */
++ if ((from + len) > mtd->size) {
++ pr_debug("%s: attempt to write beyond end of device\n",
++ __func__);
++ return -EINVAL;
++ }
++
++ return nmbm_read_range(nm->ni, from, len, buf, MTD_OPS_PLACE_OOB,
++ retlen);
++}
++
++static int nmbm_mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
++ size_t *retlen, const u_char *buf)
++{
++ struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper);
++
++ /* Do not allow write past end of device */
++ if ((to + len) > mtd->size) {
++ pr_debug("%s: attempt to write beyond end of device\n",
++ __func__);
++ return -EINVAL;
++ }
++
++ return nmbm_write_range(nm->ni, to, len, buf, MTD_OPS_PLACE_OOB,
++ retlen);
++}
++
++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;
++
++ instr->state = MTD_ERASING;
++
++ ret = nmbm_erase_block_range(nm->ni, instr->addr, instr->len,
++ &instr->fail_addr);
++ if (ret)
++ instr->state = MTD_ERASE_FAILED;
++ else
++ instr->state = MTD_ERASE_DONE;
++
++ if (!ret)
++ /* FIXME */
++ /* mtd_erase_callback(instr); */
++ return ret;
++ else
++ ret = -EIO;
++
++ return ret;
++}
++
++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) {
++ WATCHDOG_RESET();
++
++ 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;
++
++ 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) {
++ /* Optimized for reading data only */
++ return nmbm_read_range(nm->ni, from, ops->len, ops->datbuf,
++ mode, &ops->retlen);
++ }
++
++ 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;
++ }
++
++ return nmbm_mtd_read_data(nm, from, ops, mode);
++}
++
++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) {
++ WATCHDOG_RESET();
++
++ 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;
++
++ 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) {
++ /* Optimized for writing data only */
++ return nmbm_write_range(nm->ni, to, ops->len, ops->datbuf,
++ mode, &ops->retlen);
++ }
++
++ 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;
++ }
++
++ return nmbm_mtd_write_data(nm, to, ops, mode);
++}
++
++static int nmbm_mtd_block_isbad(struct mtd_info *mtd, loff_t offs)
++{
++ struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper);
++
++ return nmbm_check_bad_block(nm->ni, offs);
++}
++
++static int nmbm_mtd_block_markbad(struct mtd_info *mtd, loff_t offs)
++{
++ struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper);
++
++ return nmbm_mark_bad_block(nm->ni, offs);
++}
++
++int nmbm_attach_mtd(struct mtd_info *lower, int flags, uint32_t max_ratio,
++ uint32_t max_reserved_blocks, struct mtd_info **upper)
++{
++ struct nmbm_lower_device nld;
++ struct nmbm_instance *ni;
++ struct mtd_info *mtd;
++ struct nmbm_mtd *nm;
++ size_t namelen, alloc_size;
++ int ret;
++
++ if (!lower)
++ return -EINVAL;
++
++ if (lower->type != MTD_NANDFLASH || lower->flags != MTD_CAP_NANDFLASH)
++ return -ENOTSUPP;
++
++ namelen = strlen(NMBM_UPPER_MTD_NAME) + 16;
++
++ nm = calloc(sizeof(*nm) + lower->writesize + lower->oobsize + namelen + 1, 1);
++ if (!nm)
++ return -ENOMEM;
++
++ nm->lower = lower;
++ nm->name = (char *)nm + sizeof(*nm);
++ nm->page_cache = (uint8_t *)nm->name + namelen + 1;
++
++ nm->id = nmbm_id_cnt++;
++ snprintf(nm->name, namelen + 1, "%s%u", NMBM_UPPER_MTD_NAME, nm->id);
++
++ memset(&nld, 0, sizeof(nld));
++
++ nld.flags = flags;
++ 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.arg = nm;
++ 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);
++ ni = calloc(alloc_size, 1);
++ if (!ni) {
++ free(nm);
++ return -ENOMEM;
++ }
++
++ ret = nmbm_attach(&nld, ni);
++ if (ret) {
++ free(ni);
++ free(nm);
++ return ret;
++ }
++
++ nm->ni = ni;
++
++ /* Initialize upper mtd */
++ mtd = &nm->upper;
++
++ mtd->name = nm->name;
++ mtd->type = MTD_DEV_TYPE_NMBM;
++ mtd->flags = lower->flags;
++
++ mtd->size = (uint64_t)ni->data_block_count * ni->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;
++
++ /* XXX: should this be duplicated? */
++ mtd->ooblayout = lower->ooblayout;
++ mtd->ecclayout = lower->ecclayout;
++
++ mtd->ecc_step_size = lower->ecc_step_size;
++ mtd->ecc_strength = lower->ecc_strength;
++
++ mtd->numeraseregions = lower->numeraseregions;
++ mtd->eraseregions = lower->eraseregions;
++
++ mtd->_read = nmbm_mtd_read;
++ mtd->_write = nmbm_mtd_write;
++ 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;
++
++ *upper = mtd;
++
++ list_add_tail(&nm->node, &nmbm_devs);
++
++ return 0;
++}
++
++int nmbm_free_mtd(struct mtd_info *upper)
++{
++ struct nmbm_mtd *pos;
++
++ if (!upper)
++ return -EINVAL;
++
++ list_for_each_entry(pos, &nmbm_devs, node) {
++ if (&pos->upper == upper) {
++ list_del(&pos->node);
++
++ nmbm_detach(pos->ni);
++ free(pos->ni);
++ free(pos);
++
++ return 0;
++ }
++ }
++
++ return -ENODEV;
++}
++
++struct mtd_info *nmbm_mtd_get_upper_by_index(uint32_t index)
++{
++ struct nmbm_mtd *nm;
++
++ list_for_each_entry(nm, &nmbm_devs, node) {
++ if (nm->id == index)
++ return &nm->upper;
++ }
++
++ return NULL;
++}
++
++struct mtd_info *nmbm_mtd_get_upper(struct mtd_info *lower)
++{
++ struct nmbm_mtd *nm;
++
++ list_for_each_entry(nm, &nmbm_devs, node) {
++ if (nm->lower == lower)
++ return &nm->upper;
++ }
++
++ return NULL;
++}
++
++void nmbm_mtd_list_devices(void)
++{
++ struct nmbm_mtd *nm;
++
++ printf("Index NMBM device Lower device\n");
++ printf("========================================\n");
++
++ list_for_each_entry(nm, &nmbm_devs, node) {
++ printf("%-8u%-20s%s\n", nm->id, nm->name, nm->lower->name);
++ }
++}
++
++int nmbm_mtd_print_info(const char *name)
++{
++ struct nmbm_mtd *nm;
++ bool found = false;
++
++ list_for_each_entry(nm, &nmbm_devs, node) {
++ if (!strcmp(nm->name, name)) {
++ found = true;
++ break;
++ }
++ }
++
++ if (!found) {
++ printf("Error: NMBM device '%s' not found\n", name);
++ return -ENODEV;
++ }
++
++ printf("%s:\n", name);
++ printf("Total blocks: %u\n", nm->ni->block_count);
++ printf("Data blocks: %u\n", nm->ni->data_block_count);
++ printf("Management start block: %u\n", nm->ni->mgmt_start_ba);
++ printf("Info table size: 0x%x\n", nm->ni->info_table_size);
++
++ if (nm->ni->main_table_ba)
++ printf("Main info table start block: %u\n", nm->ni->main_table_ba);
++ else
++ printf("Main info table start block: Not exist\n");
++
++ if (nm->ni->backup_table_ba)
++ printf("Backup info table start block: %u\n", nm->ni->backup_table_ba);
++ else
++ printf("Backup info table start block: Not exist\n");
++
++ printf("Signature block: %u\n", nm->ni->signature_ba);
++ printf("Mapping blocks top address: %u\n", nm->ni->mapping_blocks_top_ba);
++ printf("Mapping blocks limit address: %u\n", nm->ni->mapping_blocks_ba);
++
++ return 0;
++}
++
++static const char nmbm_block_legends[] = {
++ [NMBM_BLOCK_GOOD_DATA] = '-',
++ [NMBM_BLOCK_GOOD_MGMT] = '+',
++ [NMBM_BLOCK_BAD] = 'B',
++ [NMBM_BLOCK_MAIN_INFO_TABLE] = 'I',
++ [NMBM_BLOCK_BACKUP_INFO_TABLE] = 'i',
++ [NMBM_BLOCK_REMAPPED] = 'M',
++ [NMBM_BLOCK_SIGNATURE] = 'S',
++};
++
++int nmbm_mtd_print_states(const char *name)
++{
++ struct nmbm_mtd *nm;
++ enum nmmb_block_type bt;
++ bool found = false;
++ uint32_t i;
++
++ list_for_each_entry(nm, &nmbm_devs, node) {
++ if (!strcmp(nm->name, name)) {
++ found = true;
++ break;
++ }
++ }
++
++ if (!found) {
++ printf("Error: NMBM device '%s' not found\n", name);
++ return -ENODEV;
++ }
++
++ printf("Physical blocks:\n");
++ printf("\n");
++
++ printf("Legends:\n");
++ printf(" - Good data block\n");
++ printf(" + Good management block\n");
++ printf(" B Bad block\n");
++ printf(" I Main info table\n");
++ printf(" i Backup info table\n");
++ printf(" M Remapped spare block\n");
++ printf(" S Signature block\n");
++ printf("\n");
++
++ for (i = 0; i < nm->ni->block_count; i++) {
++ if (i % 64 == 0)
++ printf(" ");
++
++ bt = nmbm_debug_get_phys_block_type(nm->ni, i);
++ if (bt < __NMBM_BLOCK_TYPE_MAX)
++ putc(nmbm_block_legends[bt]);
++ else
++ putc('?');
++
++ if (i % 64 == 63)
++ printf("\n");
++ }
++
++ printf("\n");
++ printf("Logical blocks:\n");
++ printf("\n");
++
++ printf("Legends:\n");
++ printf(" - Good block\n");
++ printf(" + Initially remapped block\n");
++ printf(" M Remapped block\n");
++ printf(" B Bad/Unmapped block\n");
++ printf("\n");
++
++ for (i = 0; i < nm->ni->data_block_count; i++) {
++ if (i % 64 == 0)
++ printf(" ");
++
++ if (nm->ni->block_mapping[i] < 0)
++ putc('B');
++ else if (nm->ni->block_mapping[i] == i)
++ putc('-');
++ else if (nm->ni->block_mapping[i] < nm->ni->data_block_count)
++ putc('+');
++ else if (nm->ni->block_mapping[i] > nm->ni->mapping_blocks_top_ba &&
++ nm->ni->block_mapping[i] < nm->ni->signature_ba)
++ putc('M');
++ else
++ putc('?');
++
++ if (i % 64 == 63)
++ printf("\n");
++ }
++
++ return 0;
++}
++
++int nmbm_mtd_print_bad_blocks(const char *name)
++{
++ struct nmbm_mtd *nm;
++ bool found = false;
++ uint32_t i;
++
++ list_for_each_entry(nm, &nmbm_devs, node) {
++ if (!strcmp(nm->name, name)) {
++ found = true;
++ break;
++ }
++ }
++
++ if (!found) {
++ printf("Error: NMBM device '%s' not found\n", name);
++ return -ENODEV;
++ }
++
++ printf("Physical blocks:\n");
++
++ for (i = 0; i < nm->ni->block_count; i++) {
++ switch (nmbm_debug_get_block_state(nm->ni, i)) {
++ case BLOCK_ST_BAD:
++ printf("%-12u [0x%08llx] - Bad\n", i,
++ (uint64_t)i << nm->ni->erasesize_shift);
++ break;
++ case BLOCK_ST_NEED_REMAP:
++ printf("%-12u [0x%08llx] - Awaiting remapping\n", i,
++ (uint64_t)i << nm->ni->erasesize_shift);
++ break;
++ }
++ }
++
++ printf("\n");
++ printf("Logical blocks:\n");
++
++ for (i = 0; i < nm->ni->data_block_count; i++) {
++ if (nm->ni->block_mapping[i] < 0) {
++ printf("%-12u [0x%08llx] - Bad\n", i,
++ (uint64_t)i << nm->ni->erasesize_shift);
++ }
++ }
++
++ return 0;
++}
++
++int nmbm_mtd_print_mappings(const char *name, int printall)
++{
++ struct nmbm_mtd *nm;
++ bool found = false;
++ int32_t pb;
++ uint32_t i;
++
++ list_for_each_entry(nm, &nmbm_devs, node) {
++ if (!strcmp(nm->name, name)) {
++ found = true;
++ break;
++ }
++ }
++
++ if (!found) {
++ printf("Error: NMBM device '%s' not found\n", name);
++ return -ENODEV;
++ }
++
++ printf("Logical Block Physical Block\n");
++ printf("==================================\n");
++
++ if (!printall) {
++ for (i = 0; i < nm->ni->data_block_count; i++) {
++ pb = nm->ni->block_mapping[i];
++ if (pb < 0)
++ printf("%-20uUnmapped\n", i);
++ else if ((uint32_t)pb > nm->ni->mapping_blocks_top_ba &&
++ (uint32_t)pb < nm->ni->signature_ba)
++ printf("%-20u%u\n", i, pb);
++ }
++
++ return 0;
++ }
++
++ for (i = 0; i < nm->ni->data_block_count; i++) {
++ pb = nm->ni->block_mapping[i];
++
++ if (pb >= 0)
++ printf("%-20u%u\n", i, pb);
++ else
++ printf("%-20uUnmapped\n", i);
++ }
++
++ return 0;
++}
+--- /dev/null
++++ b/include/nmbm/nmbm-mtd.h
+@@ -0,0 +1,27 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++/*
++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved.
++ *
++ * Author: Weijie Gao <weijie.gao@mediatek.com>
++ */
++
++#ifndef _NMBM_MTD_H_
++#define _NMBM_MTD_H_
++
++#include <linux/mtd/mtd.h>
++
++int nmbm_attach_mtd(struct mtd_info *lower, int flags, uint32_t max_ratio,
++ uint32_t max_reserved_blocks, struct mtd_info **upper);
++
++int nmbm_free_mtd(struct mtd_info *upper);
++
++struct mtd_info *nmbm_mtd_get_upper_by_index(uint32_t index);
++struct mtd_info *nmbm_mtd_get_upper(struct mtd_info *lower);
++
++void nmbm_mtd_list_devices(void);
++int nmbm_mtd_print_info(const char *name);
++int nmbm_mtd_print_states(const char *name);
++int nmbm_mtd_print_bad_blocks(const char *name);
++int nmbm_mtd_print_mappings(const char *name, int printall);
++
++#endif /* _NMBM_MTD_H_ */