-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
mtd: nand: Add core infrastructure to deal with NAND devices
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
Showing
5 changed files
with
1,111 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,4 @@ | ||
config MTD_NAND_CORE | ||
tristate | ||
|
||
source "drivers/mtd/nand/raw/Kconfig" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); |
Oops, something went wrong.