162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2017 Free Electrons
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Authors:
662306a36Sopenharmony_ci *	Boris Brezillon <boris.brezillon@free-electrons.com>
762306a36Sopenharmony_ci *	Peter Pan <peterpandong@micron.com>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#define pr_fmt(fmt)	"nand-bbt: " fmt
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/mtd/nand.h>
1362306a36Sopenharmony_ci#include <linux/slab.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci/**
1662306a36Sopenharmony_ci * nanddev_bbt_init() - Initialize the BBT (Bad Block Table)
1762306a36Sopenharmony_ci * @nand: NAND device
1862306a36Sopenharmony_ci *
1962306a36Sopenharmony_ci * Initialize the in-memory BBT.
2062306a36Sopenharmony_ci *
2162306a36Sopenharmony_ci * Return: 0 in case of success, a negative error code otherwise.
2262306a36Sopenharmony_ci */
2362306a36Sopenharmony_ciint nanddev_bbt_init(struct nand_device *nand)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	unsigned int bits_per_block = fls(NAND_BBT_BLOCK_NUM_STATUS);
2662306a36Sopenharmony_ci	unsigned int nblocks = nanddev_neraseblocks(nand);
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	nand->bbt.cache = bitmap_zalloc(nblocks * bits_per_block, GFP_KERNEL);
2962306a36Sopenharmony_ci	if (!nand->bbt.cache)
3062306a36Sopenharmony_ci		return -ENOMEM;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	return 0;
3362306a36Sopenharmony_ci}
3462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_bbt_init);
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci/**
3762306a36Sopenharmony_ci * nanddev_bbt_cleanup() - Cleanup the BBT (Bad Block Table)
3862306a36Sopenharmony_ci * @nand: NAND device
3962306a36Sopenharmony_ci *
4062306a36Sopenharmony_ci * Undoes what has been done in nanddev_bbt_init()
4162306a36Sopenharmony_ci */
4262306a36Sopenharmony_civoid nanddev_bbt_cleanup(struct nand_device *nand)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	bitmap_free(nand->bbt.cache);
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_bbt_cleanup);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci/**
4962306a36Sopenharmony_ci * nanddev_bbt_update() - Update a BBT
5062306a36Sopenharmony_ci * @nand: nand device
5162306a36Sopenharmony_ci *
5262306a36Sopenharmony_ci * Update the BBT. Currently a NOP function since on-flash bbt is not yet
5362306a36Sopenharmony_ci * supported.
5462306a36Sopenharmony_ci *
5562306a36Sopenharmony_ci * Return: 0 in case of success, a negative error code otherwise.
5662306a36Sopenharmony_ci */
5762306a36Sopenharmony_ciint nanddev_bbt_update(struct nand_device *nand)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	return 0;
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_bbt_update);
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci/**
6462306a36Sopenharmony_ci * nanddev_bbt_get_block_status() - Return the status of an eraseblock
6562306a36Sopenharmony_ci * @nand: nand device
6662306a36Sopenharmony_ci * @entry: the BBT entry
6762306a36Sopenharmony_ci *
6862306a36Sopenharmony_ci * Return: a positive number nand_bbt_block_status status or -%ERANGE if @entry
6962306a36Sopenharmony_ci *	   is bigger than the BBT size.
7062306a36Sopenharmony_ci */
7162306a36Sopenharmony_ciint nanddev_bbt_get_block_status(const struct nand_device *nand,
7262306a36Sopenharmony_ci				 unsigned int entry)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	unsigned int bits_per_block = fls(NAND_BBT_BLOCK_NUM_STATUS);
7562306a36Sopenharmony_ci	unsigned long *pos = nand->bbt.cache +
7662306a36Sopenharmony_ci			     ((entry * bits_per_block) / BITS_PER_LONG);
7762306a36Sopenharmony_ci	unsigned int offs = (entry * bits_per_block) % BITS_PER_LONG;
7862306a36Sopenharmony_ci	unsigned long status;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	if (entry >= nanddev_neraseblocks(nand))
8162306a36Sopenharmony_ci		return -ERANGE;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	status = pos[0] >> offs;
8462306a36Sopenharmony_ci	if (bits_per_block + offs > BITS_PER_LONG)
8562306a36Sopenharmony_ci		status |= pos[1] << (BITS_PER_LONG - offs);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	return status & GENMASK(bits_per_block - 1, 0);
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_bbt_get_block_status);
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci/**
9262306a36Sopenharmony_ci * nanddev_bbt_set_block_status() - Update the status of an eraseblock in the
9362306a36Sopenharmony_ci *				    in-memory BBT
9462306a36Sopenharmony_ci * @nand: nand device
9562306a36Sopenharmony_ci * @entry: the BBT entry to update
9662306a36Sopenharmony_ci * @status: the new status
9762306a36Sopenharmony_ci *
9862306a36Sopenharmony_ci * Update an entry of the in-memory BBT. If you want to push the updated BBT
9962306a36Sopenharmony_ci * the NAND you should call nanddev_bbt_update().
10062306a36Sopenharmony_ci *
10162306a36Sopenharmony_ci * Return: 0 in case of success or -%ERANGE if @entry is bigger than the BBT
10262306a36Sopenharmony_ci *	   size.
10362306a36Sopenharmony_ci */
10462306a36Sopenharmony_ciint nanddev_bbt_set_block_status(struct nand_device *nand, unsigned int entry,
10562306a36Sopenharmony_ci				 enum nand_bbt_block_status status)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	unsigned int bits_per_block = fls(NAND_BBT_BLOCK_NUM_STATUS);
10862306a36Sopenharmony_ci	unsigned long *pos = nand->bbt.cache +
10962306a36Sopenharmony_ci			     ((entry * bits_per_block) / BITS_PER_LONG);
11062306a36Sopenharmony_ci	unsigned int offs = (entry * bits_per_block) % BITS_PER_LONG;
11162306a36Sopenharmony_ci	unsigned long val = status & GENMASK(bits_per_block - 1, 0);
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	if (entry >= nanddev_neraseblocks(nand))
11462306a36Sopenharmony_ci		return -ERANGE;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	pos[0] &= ~GENMASK(offs + bits_per_block - 1, offs);
11762306a36Sopenharmony_ci	pos[0] |= val << offs;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	if (bits_per_block + offs > BITS_PER_LONG) {
12062306a36Sopenharmony_ci		unsigned int rbits = bits_per_block + offs - BITS_PER_LONG;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci		pos[1] &= ~GENMASK(rbits - 1, 0);
12362306a36Sopenharmony_ci		pos[1] |= val >> (bits_per_block - rbits);
12462306a36Sopenharmony_ci	}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	return 0;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_bbt_set_block_status);
129