#define pr_fmt(fmt) "nand: " fmt
#include <linux/module.h>
#include <linux/mtd/nand.h>
bool nanddev_isbad(struct nand_device *nand, const struct nand_pos *pos)
{
if (mtd_check_expert_analysis_mode())
return false;
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);
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);
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);
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;
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);
static 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);
}
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);
return ret;
}
nanddev_pos_next_eraseblock(nand, &pos);
}
return 0;
}
EXPORT_SYMBOL_GPL(nanddev_mtd_erase);
int nanddev_mtd_max_bad_blocks(struct mtd_info *mtd, loff_t offs, size_t len)
{
struct nand_device *nand = mtd_to_nanddev(mtd);
struct nand_pos pos, end;
unsigned int max_bb = 0;
if (!nand->memorg.max_bad_eraseblocks_per_lun)
return -ENOTSUPP;
nanddev_offs_to_pos(nand, offs, &pos);
nanddev_offs_to_pos(nand, offs + len, &end);
for (nanddev_offs_to_pos(nand, offs, &pos);
nanddev_pos_cmp(&pos, &end) < 0;
nanddev_pos_next_lun(nand, &pos))
max_bb += nand->memorg.max_bad_eraseblocks_per_lun;
return max_bb;
}
EXPORT_SYMBOL_GPL(nanddev_mtd_max_bad_blocks);
static int nanddev_get_ecc_engine(struct nand_device *nand)
{
int engine_type;
of_get_nand_ecc_user_config(nand);
engine_type = nand->ecc.user_conf.engine_type;
if (engine_type == NAND_ECC_ENGINE_TYPE_INVALID)
engine_type = nand->ecc.defaults.engine_type;
switch (engine_type) {
case NAND_ECC_ENGINE_TYPE_NONE:
return 0;
case NAND_ECC_ENGINE_TYPE_SOFT:
nand->ecc.engine = nand_ecc_get_sw_engine(nand);
break;
case NAND_ECC_ENGINE_TYPE_ON_DIE:
nand->ecc.engine = nand_ecc_get_on_die_hw_engine(nand);
break;
case NAND_ECC_ENGINE_TYPE_ON_HOST:
nand->ecc.engine = nand_ecc_get_on_host_hw_engine(nand);
if (PTR_ERR(nand->ecc.engine) == -EPROBE_DEFER)
return -EPROBE_DEFER;
break;
default:
pr_err("Missing ECC engine type\n");
}
if (!nand->ecc.engine)
return -EINVAL;
return 0;
}
static int nanddev_put_ecc_engine(struct nand_device *nand)
{
switch (nand->ecc.ctx.conf.engine_type) {
case NAND_ECC_ENGINE_TYPE_ON_HOST:
nand_ecc_put_on_host_hw_engine(nand);
break;
case NAND_ECC_ENGINE_TYPE_NONE:
case NAND_ECC_ENGINE_TYPE_SOFT:
case NAND_ECC_ENGINE_TYPE_ON_DIE:
default:
break;
}
return 0;
}
static int nanddev_find_ecc_configuration(struct nand_device *nand)
{
int ret;
if (!nand->ecc.engine)
return -ENOTSUPP;
ret = nand_ecc_init_ctx(nand);
if (ret)
return ret;
if (!nand_ecc_is_strong_enough(nand))
pr_warn("WARNING: %s: the ECC used on your system is too weak compared to the one required by the NAND chip\n",
nand->mtd.name);
return 0;
}
int nanddev_ecc_engine_init(struct nand_device *nand)
{
int ret;
ret = nanddev_get_ecc_engine(nand);
if (ret) {
if (ret != -EPROBE_DEFER)
pr_err("No ECC engine found\n");
return ret;
}
if (!nand->ecc.engine)
return 0;
ret = nanddev_find_ecc_configuration(nand);
if (ret) {
pr_err("No suitable ECC configuration\n");
nanddev_put_ecc_engine(nand);
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(nanddev_ecc_engine_init);
void nanddev_ecc_engine_cleanup(struct nand_device *nand)
{
if (nand->ecc.engine)
nand_ecc_cleanup_ctx(nand);
nanddev_put_ecc_engine(nand);
}
EXPORT_SYMBOL_GPL(nanddev_ecc_engine_cleanup);
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);
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"