Skip to content

Commit

Permalink
mtd: nand: Add core infrastructure to deal with NAND devices
Browse files Browse the repository at this point in the history
Add an intermediate layer to abstract NAND device interface so that
some logic can be shared between SPI NANDs, parallel/raw NANDs,
OneNANDs, ...

Signed-off-by: Boris Brezillon <boris.brezillon@bootlin.com>
  • Loading branch information
Boris Brezillon committed Feb 16, 2018
1 parent 93db446 commit 9c3736a
Show file tree
Hide file tree
Showing 5 changed files with 1,111 additions and 0 deletions.
3 changes: 3 additions & 0 deletions drivers/mtd/nand/Kconfig
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
config MTD_NAND_CORE
tristate

source "drivers/mtd/nand/raw/Kconfig"
3 changes: 3 additions & 0 deletions drivers/mtd/nand/Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-License-Identifier: GPL-2.0

nandcore-objs := core.o bbt.o
obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o

obj-y += raw/
130 changes: 130 additions & 0 deletions drivers/mtd/nand/bbt.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2017 Free Electrons
*
* Authors:
* Boris Brezillon <boris.brezillon@free-electrons.com>
* Peter Pan <peterpandong@micron.com>
*/

#define pr_fmt(fmt) "nand-bbt: " fmt

#include <linux/mtd/nand.h>
#include <linux/slab.h>

/**
* nanddev_bbt_init() - Initialize the BBT (Bad Block Table)
* @nand: NAND device
*
* Initialize the in-memory BBT.
*
* Return: 0 in case of success, a negative error code otherwise.
*/
int nanddev_bbt_init(struct nand_device *nand)
{
unsigned int bits_per_block = fls(NAND_BBT_BLOCK_NUM_STATUS);
unsigned int nblocks = nanddev_neraseblocks(nand);
unsigned int nwords = DIV_ROUND_UP(nblocks * bits_per_block,
BITS_PER_LONG);

nand->bbt.cache = kzalloc(nwords, GFP_KERNEL);
if (!nand->bbt.cache)
return -ENOMEM;

return 0;
}
EXPORT_SYMBOL_GPL(nanddev_bbt_init);

/**
* nanddev_bbt_cleanup() - Cleanup the BBT (Bad Block Table)
* @nand: NAND device
*
* Undoes what has been done in nanddev_bbt_init()
*/
void nanddev_bbt_cleanup(struct nand_device *nand)
{
kfree(nand->bbt.cache);
}
EXPORT_SYMBOL_GPL(nanddev_bbt_cleanup);

/**
* nanddev_bbt_update() - Update a BBT
* @nand: nand device
*
* Update the BBT. Currently a NOP function since on-flash bbt is not yet
* supported.
*
* Return: 0 in case of success, a negative error code otherwise.
*/
int nanddev_bbt_update(struct nand_device *nand)
{
return 0;
}
EXPORT_SYMBOL_GPL(nanddev_bbt_update);

/**
* nanddev_bbt_get_block_status() - Return the status of an eraseblock
* @nand: nand device
* @entry: the BBT entry
*
* Return: a positive number nand_bbt_block_status status or -%ERANGE if @entry
* is bigger than the BBT size.
*/
int nanddev_bbt_get_block_status(const struct nand_device *nand,
unsigned int entry)
{
unsigned int bits_per_block = fls(NAND_BBT_BLOCK_NUM_STATUS);
unsigned long *pos = nand->bbt.cache +
((entry * bits_per_block) / BITS_PER_LONG);
unsigned int offs = (entry * bits_per_block) % BITS_PER_LONG;
unsigned long status;

if (entry >= nanddev_neraseblocks(nand))
return -ERANGE;

status = pos[0] >> offs;
if (bits_per_block + offs > BITS_PER_LONG)
status |= pos[1] << (BITS_PER_LONG - offs);

return status & GENMASK(bits_per_block - 1, 0);
}
EXPORT_SYMBOL_GPL(nanddev_bbt_get_block_status);

/**
* nanddev_bbt_set_block_status() - Update the status of an eraseblock in the
* in-memory BBT
* @nand: nand device
* @entry: the BBT entry to update
* @status: the new status
*
* Update an entry of the in-memory BBT. If you want to push the updated BBT
* the NAND you should call nanddev_bbt_update().
*
* Return: 0 in case of success or -%ERANGE if @entry is bigger than the BBT
* size.
*/
int nanddev_bbt_set_block_status(struct nand_device *nand, unsigned int entry,
enum nand_bbt_block_status status)
{
unsigned int bits_per_block = fls(NAND_BBT_BLOCK_NUM_STATUS);
unsigned long *pos = nand->bbt.cache +
((entry * bits_per_block) / BITS_PER_LONG);
unsigned int offs = (entry * bits_per_block) % BITS_PER_LONG;
unsigned long val = status & GENMASK(bits_per_block - 1, 0);

if (entry >= nanddev_neraseblocks(nand))
return -ERANGE;

pos[0] &= ~GENMASK(offs + bits_per_block - 1, offs);
pos[0] |= val << offs;

if (bits_per_block + offs > BITS_PER_LONG) {
unsigned int rbits = bits_per_block + offs - BITS_PER_LONG;

pos[1] &= ~GENMASK(rbits - 1, 0);
pos[1] |= val >> rbits;
}

return 0;
}
EXPORT_SYMBOL_GPL(nanddev_bbt_set_block_status);
244 changes: 244 additions & 0 deletions drivers/mtd/nand/core.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2017 Free Electrons
*
* Authors:
* Boris Brezillon <boris.brezillon@free-electrons.com>
* Peter Pan <peterpandong@micron.com>
*/

#define pr_fmt(fmt) "nand: " fmt

#include <linux/module.h>
#include <linux/mtd/nand.h>

/**
* nanddev_isbad() - Check if a block is bad
* @nand: NAND device
* @pos: position pointing to the block we want to check
*
* Return: true if the block is bad, false otherwise.
*/
bool nanddev_isbad(struct nand_device *nand, const struct nand_pos *pos)
{
if (nanddev_bbt_is_initialized(nand)) {
unsigned int entry;
int status;

entry = nanddev_bbt_pos_to_entry(nand, pos);
status = nanddev_bbt_get_block_status(nand, entry);
/* Lazy block status retrieval */
if (status == NAND_BBT_BLOCK_STATUS_UNKNOWN) {
if (nand->ops->isbad(nand, pos))
status = NAND_BBT_BLOCK_FACTORY_BAD;
else
status = NAND_BBT_BLOCK_GOOD;

nanddev_bbt_set_block_status(nand, entry, status);
}

if (status == NAND_BBT_BLOCK_WORN ||
status == NAND_BBT_BLOCK_FACTORY_BAD)
return true;

return false;
}

return nand->ops->isbad(nand, pos);
}
EXPORT_SYMBOL_GPL(nanddev_isbad);

/**
* nanddev_markbad() - Mark a block as bad
* @nand: NAND device
* @block: block to mark bad
*
* Mark a block bad. This function is updating the BBT if available and
* calls the low-level markbad hook (nand->ops->markbad()).
*
* Return: 0 in case of success, a negative error code otherwise.
*/
int nanddev_markbad(struct nand_device *nand, const struct nand_pos *pos)
{
struct mtd_info *mtd = nanddev_to_mtd(nand);
unsigned int entry;
int ret = 0;

if (nanddev_isbad(nand, pos))
return 0;

ret = nand->ops->markbad(nand, pos);
if (ret)
pr_warn("failed to write BBM to block @%llx (err = %d)\n",
nanddev_pos_to_offs(nand, pos), ret);

if (!nanddev_bbt_is_initialized(nand))
goto out;

entry = nanddev_bbt_pos_to_entry(nand, pos);
ret = nanddev_bbt_set_block_status(nand, entry, NAND_BBT_BLOCK_WORN);
if (ret)
goto out;

ret = nanddev_bbt_update(nand);

out:
if (!ret)
mtd->ecc_stats.badblocks++;

return ret;
}
EXPORT_SYMBOL_GPL(nanddev_markbad);

/**
* nanddev_isreserved() - Check whether an eraseblock is reserved or not
* @nand: NAND device
* @pos: NAND position to test
*
* Checks whether the eraseblock pointed by @pos is reserved or not.
*
* Return: true if the eraseblock is reserved, false otherwise.
*/
bool nanddev_isreserved(struct nand_device *nand, const struct nand_pos *pos)
{
unsigned int entry;
int status;

if (!nanddev_bbt_is_initialized(nand))
return false;

/* Return info from the table */
entry = nanddev_bbt_pos_to_entry(nand, pos);
status = nanddev_bbt_get_block_status(nand, entry);
return status == NAND_BBT_BLOCK_RESERVED;
}
EXPORT_SYMBOL_GPL(nanddev_isreserved);

/**
* nanddev_erase() - Erase a NAND portion
* @nand: NAND device
* @block: eraseblock to erase
*
* Erases @block if it's not bad.
*
* Return: 0 in case of success, a negative error code otherwise.
*/
int nanddev_erase(struct nand_device *nand, const struct nand_pos *pos)
{
if (nanddev_isbad(nand, pos) || nanddev_isreserved(nand, pos)) {
pr_warn("attempt to erase a bad/reserved block @%llx\n",
nanddev_pos_to_offs(nand, pos));
return -EIO;
}

return nand->ops->erase(nand, pos);
}
EXPORT_SYMBOL_GPL(nanddev_erase);

/**
* nanddev_mtd_erase() - Generic mtd->_erase() implementation for NAND devices
* @mtd: MTD device
* @einfo: erase request
*
* This is a simple mtd->_erase() implementation iterating over all blocks
* concerned by @einfo and calling nand->ops->erase() on each of them.
*
* Note that mtd->_erase should not be directly assigned to this helper,
* because there's no locking here. NAND specialized layers should instead
* implement there own wrapper around nanddev_mtd_erase() taking the
* appropriate lock before calling nanddev_mtd_erase().
*
* Return: 0 in case of success, a negative error code otherwise.
*/
int nanddev_mtd_erase(struct mtd_info *mtd, struct erase_info *einfo)
{
struct nand_device *nand = mtd_to_nanddev(mtd);
struct nand_pos pos, last;
int ret;

nanddev_offs_to_pos(nand, einfo->addr, &pos);
nanddev_offs_to_pos(nand, einfo->addr + einfo->len - 1, &last);
while (nanddev_pos_cmp(&pos, &last) <= 0) {
ret = nanddev_erase(nand, &pos);
if (ret) {
einfo->fail_addr = nanddev_pos_to_offs(nand, &pos);
einfo->state = MTD_ERASE_FAILED;

return ret;
}

nanddev_pos_next_eraseblock(nand, &pos);
}

einfo->state = MTD_ERASE_DONE;

return 0;
}
EXPORT_SYMBOL_GPL(nanddev_mtd_erase);

/**
* nanddev_init() - Initialize a NAND device
* @nand: NAND device
* @memorg: NAND memory organization descriptor
* @ops: NAND device operations
*
* Initializes a NAND device object. Consistency checks are done on @memorg and
* @ops. Also takes care of initializing the BBT.
*
* Return: 0 in case of success, a negative error code otherwise.
*/
int nanddev_init(struct nand_device *nand, const struct nand_ops *ops,
struct module *owner)
{
struct mtd_info *mtd = nanddev_to_mtd(nand);
struct nand_memory_organization *memorg = nanddev_get_memorg(nand);

if (!nand || !ops)
return -EINVAL;

if (!ops->erase || !ops->markbad || !ops->isbad)
return -EINVAL;

if (!memorg->bits_per_cell || !memorg->pagesize ||
!memorg->pages_per_eraseblock || !memorg->eraseblocks_per_lun ||
!memorg->planes_per_lun || !memorg->luns_per_target ||
!memorg->ntargets)
return -EINVAL;

nand->rowconv.eraseblock_addr_shift =
fls(memorg->pages_per_eraseblock - 1);
nand->rowconv.lun_addr_shift = fls(memorg->eraseblocks_per_lun - 1) +
nand->rowconv.eraseblock_addr_shift;

nand->ops = ops;

mtd->type = memorg->bits_per_cell == 1 ?
MTD_NANDFLASH : MTD_MLCNANDFLASH;
mtd->flags = MTD_CAP_NANDFLASH;
mtd->erasesize = memorg->pagesize * memorg->pages_per_eraseblock;
mtd->writesize = memorg->pagesize;
mtd->writebufsize = memorg->pagesize;
mtd->oobsize = memorg->oobsize;
mtd->size = nanddev_size(nand);
mtd->owner = owner;

return nanddev_bbt_init(nand);
}
EXPORT_SYMBOL_GPL(nanddev_init);

/**
* nanddev_cleanup() - Release resources allocated in nanddev_init()
* @nand: NAND device
*
* Basically undoes what has been done in nanddev_init().
*/
void nanddev_cleanup(struct nand_device *nand)
{
if (nanddev_bbt_is_initialized(nand))
nanddev_bbt_cleanup(nand);
}
EXPORT_SYMBOL_GPL(nanddev_cleanup);

MODULE_DESCRIPTION("Generic NAND framework");
MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>");
MODULE_LICENSE("GPL v2");
Loading

0 comments on commit 9c3736a

Please sign in to comment.