[PATCH v1 05/12] mtd: spi-nand: add EN75 BMT bad-block management support
AK Sharma <[email protected]>
| Newsgroups | org.u-boot-project.lists.u-boot |
|---|---|
| Message-ID | <[email protected]> |
Add read support for the EcoNet/Airoha BMT/BBT (bad-block management table) layout used by the EN75xx vendor bootloaders, so U-Boot maps the SPI-NAND logical blocks the same way as the vendor firmware. Signed-off-by: AK Sharma <[email protected]> --- drivers/mtd/nand/spi/Kconfig | 8 + drivers/mtd/nand/spi/Makefile | 1 + drivers/mtd/nand/spi/core.c | 9 +- drivers/mtd/nand/spi/en75_bmt.c | 665 ++++++++++++++++++++++++++++++++ drivers/mtd/nand/spi/en75_bmt.h | 24 ++ 5 files changed, 706 insertions(+), 1 deletion(-) create mode 100644 drivers/mtd/nand/spi/en75_bmt.c create mode 100644 drivers/mtd/nand/spi/en75_bmt.h diff --git a/drivers/mtd/nand/spi/Kconfig b/drivers/mtd/nand/spi/Kconfig index 1124dada..09531914 100644 --- a/drivers/mtd/nand/spi/Kconfig +++ b/drivers/mtd/nand/spi/Kconfig @@ -6,3 +6,11 @@ menuconfig MTD_SPI_NAND select SPI_MEM help This is the framework for the SPI NAND device drivers. + +config MTD_EN75_BMT + bool "EcoNet EN75xx vendor BMT/BBT translation (read-only)" + depends on MTD_SPI_NAND + help + Honour the EcoNet/Airoha factory BBT and BMT so logical NAND + addresses match the bootbase and Linux. Read-only: never rewrite + the tables. diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile index a7a0b2cb..20aa718d 100644 --- a/drivers/mtd/nand/spi/Makefile +++ b/drivers/mtd/nand/spi/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 spinand-objs := core.o otp.o +spinand-objs += $(if $(CONFIG_MTD_EN75_BMT),en75_bmt.o) spinand-objs += alliancememory.o ato.o esmt.o fmsh.o foresee.o gigadevice.o macronix.o spinand-objs += micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o obj-$(CONFIG_MTD_SPI_NAND) += spinand.o diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 14af4264..29fdefe2 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -33,6 +33,7 @@ #include <linux/mtd/spinand.h> #include <linux/printk.h> #include <linux/delay.h> +#include "en75_bmt.h" #endif struct spinand_plat { @@ -1703,15 +1704,21 @@ static int spinand_probe(struct udevice *dev) #ifndef __UBOOT__ ret = mtd_device_register(mtd, NULL, 0); #else + ret = en75_bmt_attach(mtd); + if (ret) + goto err_spinand_cleanup; + ret = add_mtd_device(mtd); #endif if (ret) - goto err_spinand_cleanup; + goto err_bmt_detach; plat->mtd = mtd; return 0; +err_bmt_detach: + en75_bmt_detach(mtd); err_spinand_cleanup: spinand_cleanup(spinand); diff --git a/drivers/mtd/nand/spi/en75_bmt.c b/drivers/mtd/nand/spi/en75_bmt.c new file mode 100644 index 00000000..1cb3ca01 --- /dev/null +++ b/drivers/mtd/nand/spi/en75_bmt.c @@ -0,0 +1,665 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * EcoNet/Airoha EN75xx vendor BMT/BBT translation layer (READ-ONLY). + * + * The vendor bootbase and the Linux en75_bmt driver do not "skip" bad NAND + * blocks the way stock MTD does - they *remap* them into a reserve pool kept at + * the top of the chip, so that a logical block keeps a stable address after a + * block goes bad. A bootloader that ignores this reads the wrong data on any + * device that has ever remapped a block. + * + * This is a deliberately read-only port of that scheme: + * + * - the factory Bad Block Table ("RAWB") and the Block Mapping Table ("BMT") + * are located, checksum-verified and parsed, + * - every MTD access is translated logical -> physical exactly as the vendor + * does (BBT shift-up, then BMT lookup), + * - mtd->size is reduced to the user area so partitions land where Linux and + * the bootbase expect them, + * - *nothing is ever written to the tables*. If a block turns bad we report + * the error upward instead of allocating a replacement; leave repair to + * Linux, which implements the full scheme. + * + * Deliberately NOT ported: BBT reconstruction / factory-BBT writing. Rewriting + * the factory BBT shifts the physical address of every block above the first + * bad one, which in a bootloader means bricking the board. Omission is the + * safest form of "disabled". + * + * Derived from the Linux drivers by Caleb James DeLisle: + * target/linux/econet/files/drivers/mtd/nand/en75_bmt.c (back end) + * target/linux/generic/files/drivers/mtd/nand/mtk_bmt.c (MTD glue) + */ + +#include <malloc.h> +#include <asm/cache.h> +#include <dm/ofnode.h> +#include <linux/bitops.h> +#include <linux/bug.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/mtd/mtd.h> +#include <linux/printk.h> +#include <linux/sizes.h> +#include <linux/string.h> +#include <linux/types.h> + +#include "en75_bmt.h" + +#define LOG_PFX "en75_bmt" + +/* Vendor firmware calls this POOL_GOOD_BLOCK_PERCENT. Must agree with the + * bootbase, which prints the resulting figure as "bmt pool size: N". + */ +#define REQUIRED_GOOD_BLOCKS(total) ((total) * 8 / 100) + +#define MAX_BMT_SIZE 256 +#define MAX_BBT_SIZE 1000 +#define DEFAULT_BBT_TABLE_SIZE 1000 +#define INITIAL_READ_TRIES 3 + +/* Marked bad by the Linux driver (the bootbase uses 0x00 for everything). */ +#define BLOCK_WORN_MARK 0x55 + +/* + * On-disk structures. All multi-byte fields are host-order little-endian. + */ + +struct bmt_table_header { + char signature[3]; /* "BMT" */ + u8 version; /* 1 */ + u8 bad_count; /* unused */ + u8 size; /* number of mappings */ + u8 checksum; /* bmt_checksum() */ + u8 reserved[13]; +}; + +static_assert(sizeof(struct bmt_table_header) == 20); + +struct bmt_entry { + u16 from; /* physical (post-BBT-shift) block being replaced */ + u16 to; /* reserve block standing in for it */ +}; + +struct bmt_table { + struct bmt_table_header header; + struct bmt_entry table[MAX_BMT_SIZE]; +}; + +struct bbt_table_header { + char signature[4]; /* "RAWB" */ + u32 checksum; /* bbt_checksum(), only 16 bits used */ + u8 version; /* 1 */ + u8 size; /* number of bad blocks */ + u8 reserved[2]; /* 0xffff */ +}; + +static_assert(sizeof(struct bbt_table_header) == 12); + +struct bbt_table { + struct bbt_table_header header; + u16 table[MAX_BBT_SIZE]; /* ascending order required */ +}; + +static_assert(sizeof(struct bbt_table) == 2012); + +/* + * Runtime state. Single instance: there is one NAND chip on these boards, and + * the Linux driver makes the same assumption. + */ +struct en75_bmt { + struct mtd_info *mtd; + + /* saved raw ops - internal I/O must bypass our own translation */ + 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); + + struct bbt_table bbt; + struct bmt_table bmt; + + u16 bbt_table_size; + u16 total_blks; + u16 reserve_area_begin; + u32 blk_size; + u32 pg_size; + u16 blk_shift; + u16 pg_shift; + + u8 *data_buf; + bool attached; +}; + +static struct en75_bmt bmtd; + +static inline u32 blk_pg(u16 block) +{ + return (u32)(block << (bmtd.blk_shift - bmtd.pg_shift)); +} + +/* Read via the *saved* op: reserve-area offsets sit above the shrunken + * mtd->size and would be rejected by the bounds-checked wrappers, and going + * through our own translation here would recurse. + */ +static int bbt_nand_read(u32 page, u8 *dat, int dat_len, u8 *fdm, int fdm_len) +{ + struct mtd_oob_ops ops = { + .mode = MTD_OPS_PLACE_OOB, + .ooboffs = 0, + .oobbuf = fdm, + .ooblen = fdm_len, + .datbuf = dat, + .len = dat_len, + }; + int ret; + + ret = bmtd._read_oob(bmtd.mtd, (loff_t)page << bmtd.pg_shift, &ops); + if (ret < 0) + return ret; + return 0; +} + +/* + * In-memory helpers (no I/O) + */ + +static u16 bbt_checksum(const struct bbt_table *bbt, u16 table_size) +{ + const u8 *data = (const u8 *)bbt->table; + u16 checksum = bbt->header.version + bbt->header.size; + int i; + + for (i = 0; i < table_size * (int)sizeof(bbt->table[0]); i++) + checksum += data[i]; + + return checksum; +} + +static u8 bmt_checksum(const struct bmt_table *bmt, int check_entries) +{ + const u8 *data = (const u8 *)&bmt->table; + u8 checksum = bmt->header.version + bmt->header.size; + int length, i; + + if (check_entries > MAX_BMT_SIZE) + check_entries = MAX_BMT_SIZE; + length = check_entries * (int)sizeof(bmt->table[0]); + for (i = 0; i < length; i++) + checksum += data[i]; + + return checksum; +} + +static void sort_bbt(struct bbt_table *bbt) +{ + int i, j; + + /* Insertion sort: at most a few hundred u16s, and it avoids pulling in + * qsort() for something this small. Ascending order is required both by + * the shift algorithm below and by the vendor firmware. + */ + for (i = 1; i < bbt->header.size; i++) { + u16 key = bbt->table[i]; + + for (j = i - 1; j >= 0 && bbt->table[j] > key; j--) + bbt->table[j + 1] = bbt->table[j]; + bbt->table[j + 1] = key; + } +} + +/* + * Stage 1: each factory-bad block shifts everything above it up by one. This + * is a shift, not a skip-list, which is why the BBT must be sorted and why + * changing it after the fact moves every block above the change. + */ +static int get_mapping_block_bbt(int block) +{ + int size = bmtd.bbt.header.size; + int i; + + for (i = 0; i < size; i++) + if (bmtd.bbt.table[i] <= block) + block++; + + if (block >= bmtd.reserve_area_begin || block < 0) + return -EINVAL; + + return block; +} + +/* Stage 2: BMT lookup, scanned backwards so a later duplicate 'from' wins. */ +static int get_mapping_block(int block) +{ + int i; + + block = get_mapping_block_bbt(block); + if (block < 0) + return block; + + for (i = bmtd.bmt.header.size - 1; i >= 0; i--) + if (bmtd.bmt.table[i].from == block) + return bmtd.bmt.table[i].to; + + return block; +} + +enum block_is_bad { + BB_GOOD, + BB_FACTORY_BAD, + BB_WORN, + BB_UNKNOWN_BAD, +}; + +static enum block_is_bad fdm_is_bad(const u8 fdm[4]) +{ + if (fdm[0] == 0xff && fdm[1] == 0xff) + return BB_GOOD; + if (fdm[0] == BLOCK_WORN_MARK) + return BB_WORN; + if (fdm[0] == 0x00 || fdm[1] == 0x00) + return BB_FACTORY_BAD; + return BB_UNKNOWN_BAD; +} + +static int try_parse_bbt(struct bbt_table *out, const u8 *buf, int len, u16 table_size) +{ + static struct bbt_table workspace; + size_t bbt_size = sizeof(workspace.header) + + table_size * sizeof(workspace.table[0]); + + if (len < (int)bbt_size) + return -EINVAL; + + memcpy(&workspace, buf, bbt_size); + + if (strncmp(workspace.header.signature, "RAWB", 4)) + return -EINVAL; + + if ((u16)workspace.header.checksum != bbt_checksum(&workspace, table_size)) + return -EINVAL; + + sort_bbt(&workspace); + memcpy(out, &workspace, bbt_size); + + return 0; +} + +static int try_parse_bmt(struct bmt_table *out, const u8 *buf, int len) +{ + static struct bmt_table workspace; + + if (len < (int)sizeof(*out)) + return -EINVAL; + + memcpy(&workspace, buf, sizeof(workspace)); + + if (strncmp(workspace.header.signature, "BMT", 3)) + return -EINVAL; + + if (workspace.header.checksum != + bmt_checksum(&workspace, workspace.header.size)) + return -EINVAL; + + memcpy(out, &workspace, sizeof(workspace)); + + return 0; +} + +/* + * Walk down from the last block until REQUIRED_GOOD_BLOCKS good ones have been + * seen; that boundary is where the reserve pool starts. The BBT and BMT are + * found by signature+checksum along the way, not at fixed addresses. + */ +static int scan_reserve(void) +{ + u16 total_blks = bmtd.total_blks; + int required = REQUIRED_GOOD_BLOCKS(total_blks); + int good_blocks = 0; + bool found_bbt = false, found_bmt = false; + int cursor; + + for (cursor = total_blks - 1; cursor > 0; cursor--) { + u8 fdm[4] = { 0xff, 0xff, 0xff, 0xff }; + int ret = -EIO; + int i; + + for (i = 0; i < INITIAL_READ_TRIES; i++) { + ret = bbt_nand_read(blk_pg(cursor), bmtd.data_buf, + bmtd.pg_size, fdm, sizeof(fdm)); + if (!ret) + break; + } + + if (ret || fdm_is_bad(fdm)) { + pr_info("%s: skipping bad block %d in reserve area\n", + LOG_PFX, cursor); + } else { + good_blocks++; + if (!found_bbt && + !try_parse_bbt(&bmtd.bbt, bmtd.data_buf, + bmtd.pg_size, bmtd.bbt_table_size)) { + printf("%s: found BBT in block %d\n", LOG_PFX, cursor); + found_bbt = true; + } else if (!found_bmt && + !try_parse_bmt(&bmtd.bmt, bmtd.data_buf, + bmtd.pg_size)) { + printf("%s: found BMT in block %d\n", LOG_PFX, cursor); + found_bmt = true; + } + } + + if (good_blocks >= required) + break; + } + + if (!cursor) { + pr_err("%s: not enough valid blocks found, need %d got %d\n", + LOG_PFX, required, good_blocks); + return -ENOSPC; + } + + bmtd.reserve_area_begin = cursor; + + /* + * Refuse to run without a valid BBT. The alternative - reconstructing + * it - is exactly the operation that can shift every block on the chip, + * so a bootloader must not attempt it. + */ + if (!found_bbt) { + pr_err("%s: no valid BBT found, refusing to translate\n", LOG_PFX); + return -ENOENT; + } + if (!found_bmt) + pr_info("%s: no BMT found, assuming no remapped blocks\n", LOG_PFX); + + return 0; +} + +/* + * MTD operation wrappers. Each translates then defers to the saved raw op. + * Unlike the Linux driver these never allocate a replacement block: a failure + * is reported to the caller instead. + */ + +static int en75_bmt_read(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) +{ + struct mtd_oob_ops cur_ops = *ops; + int max_bitflips = 0; + int ret = 0; + + ops->retlen = 0; + ops->oobretlen = 0; + + while (ops->retlen < ops->len || ops->oobretlen < ops->ooblen) { + u32 offset = from & (bmtd.blk_size - 1); + u32 block = from >> bmtd.blk_shift; + int cur_block, cur_ret; + loff_t cur_from; + + cur_block = 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; + if (!mtd_is_bitflip(cur_ret)) + goto out; + } else { + max_bitflips = max_t(int, max_bitflips, cur_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; + + from += cur_ops.len; + } + +out: + if (ret < 0 && !mtd_is_bitflip(ret)) + return ret; + + return max_bitflips; +} + +static int en75_bmt_write(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops) +{ + struct mtd_oob_ops cur_ops = *ops; + 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; + loff_t cur_to; + + cur_block = 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) { + /* No remap-on-failure here: the block stays bad and the + * caller finds out. Repairing it is Linux's job. + */ + pr_err("%s: write failed at block %u (physical %d); " + "not remapping (read-only BMT)\n", + LOG_PFX, block, cur_block); + 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; + } + + return 0; +} + +static int en75_bmt_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + struct erase_info mapped_instr = { + .len = bmtd.blk_size, + }; + u64 start_addr, end_addr; + int ret = 0; + + start_addr = instr->addr & (~mtd->erasesize_mask); + end_addr = instr->addr + instr->len; + + while (start_addr < end_addr) { + u16 orig_block = start_addr >> bmtd.blk_shift; + int block = 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) { + pr_err("%s: erase failed at block %u (physical %d); " + "not remapping (read-only BMT)\n", + LOG_PFX, orig_block, block); + instr->fail_addr = start_addr; + break; + } + start_addr += mtd->erasesize; + } + + return ret; +} + +static int en75_bmt_block_isbad(struct mtd_info *mtd, loff_t ofs) +{ + u16 orig_block = ofs >> bmtd.blk_shift; + int block = get_mapping_block(orig_block); + + if (block < 0) + return -EIO; + + return bmtd._block_isbad(mtd, (loff_t)block << bmtd.blk_shift); +} + +static int en75_bmt_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + u16 orig_block = ofs >> bmtd.blk_shift; + int block = get_mapping_block(orig_block); + + if (block < 0) + return -EIO; + + return bmtd._block_markbad(mtd, (loff_t)block << bmtd.blk_shift); +} + +static void en75_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 = en75_bmt_read; + mtd->_write_oob = en75_bmt_write; + mtd->_erase = en75_bmt_erase; + mtd->_block_isbad = en75_bmt_block_isbad; + mtd->_block_markbad = en75_bmt_block_markbad; +} + +static void en75_bmt_restore_ops(struct mtd_info *mtd) +{ + 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; +} + +int en75_bmt_attach(struct mtd_info *mtd) +{ + ofnode node = mtd_get_ofnode(mtd); + u32 assert_reserve = 0; + u32 val; + int ret; + + if (!ofnode_valid(node) || !ofnode_read_bool(node, "econet,bmt")) + return 0; + + if (bmtd.attached) { + pr_err("%s: already attached to an MTD device\n", LOG_PFX); + return -EBUSY; + } + + memset(&bmtd, 0, sizeof(bmtd)); + bmtd.mtd = mtd; + bmtd.blk_size = mtd->erasesize; + bmtd.pg_size = mtd->writesize; + bmtd.blk_shift = ffs(mtd->erasesize) - 1; + bmtd.pg_shift = ffs(mtd->writesize) - 1; + /* Must be taken before the size is shrunk below. */ + bmtd.total_blks = mtd->size >> bmtd.blk_shift; + + bmtd.bbt_table_size = DEFAULT_BBT_TABLE_SIZE; + if (!ofnode_read_u32(node, "econet,bbt-table-size", &val)) { + if (val == 0 || val > MAX_BBT_SIZE) { + pr_err("%s: econet,bbt-table-size %u out of range\n", + LOG_PFX, val); + return -EINVAL; + } + bmtd.bbt_table_size = val; + } + ofnode_read_u32(node, "econet,assert-reserve-size", &assert_reserve); + + bmtd.data_buf = memalign(ARCH_DMA_MINALIGN, bmtd.pg_size); + if (!bmtd.data_buf) + return -ENOMEM; + + en75_bmt_replace_ops(mtd); + + ret = scan_reserve(); + if (ret) + goto err_restore; + + if (assert_reserve && + assert_reserve != bmtd.total_blks - bmtd.reserve_area_begin) { + /* Mismatch means our idea of the pool boundary differs from the + * bootbase's, so every translation would be wrong. + */ + pr_err("%s: reserve size %d != asserted %u, refusing\n", LOG_PFX, + bmtd.total_blks - bmtd.reserve_area_begin, assert_reserve); + ret = -EINVAL; + goto err_restore; + } + + /* Hide the reserve area and the factory-bad shift from everything + * above, so DT partitions are validated against the same size Linux + * sees. Must happen before partitions are parsed. + */ + mtd->size = (loff_t)(bmtd.reserve_area_begin - bmtd.bbt.header.size) + << bmtd.blk_shift; + + bmtd.attached = true; + + printf("%s: blocks: total: %d, user: %d, factory_bad: %d, worn: %d reserve: %d\n", + LOG_PFX, bmtd.total_blks, bmtd.reserve_area_begin, + bmtd.bbt.header.size, bmtd.bmt.header.size, + bmtd.total_blks - bmtd.reserve_area_begin); + printf("%s: %llu MiB usable space (read-only BMT: no remapping)\n", + LOG_PFX, (u64)mtd->size >> 20); + + return 0; + +err_restore: + en75_bmt_restore_ops(mtd); + free(bmtd.data_buf); + bmtd.data_buf = NULL; + bmtd.mtd = NULL; + return ret; +} + +void en75_bmt_detach(struct mtd_info *mtd) +{ + if (!bmtd.attached || bmtd.mtd != mtd) + return; + + en75_bmt_restore_ops(mtd); + free(bmtd.data_buf); + bmtd.data_buf = NULL; + bmtd.mtd = NULL; + bmtd.attached = false; +} diff --git a/drivers/mtd/nand/spi/en75_bmt.h b/drivers/mtd/nand/spi/en75_bmt.h new file mode 100644 index 00000000..e9d50b8d --- /dev/null +++ b/drivers/mtd/nand/spi/en75_bmt.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * EcoNet/Airoha EN75xx vendor BMT/BBT translation layer (read-only). + */ +#ifndef __EN75_BMT_H +#define __EN75_BMT_H + +struct mtd_info; + +#if CONFIG_IS_ENABLED(MTD_EN75_BMT) +/* + * Install the translation layer on a NAND whose DT node carries "econet,bmt". + * Must be called before add_mtd_device(), because it shrinks mtd->size to the + * user area and partitions are validated against that. + * Returns 0 when attached *or* when the node opts out; negative on error. + */ +int en75_bmt_attach(struct mtd_info *mtd); +void en75_bmt_detach(struct mtd_info *mtd); +#else +static inline int en75_bmt_attach(struct mtd_info *mtd) { return 0; } +static inline void en75_bmt_detach(struct mtd_info *mtd) { } +#endif + +#endif /* __EN75_BMT_H */ -- 2.53.0