18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2017 Free Electrons 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Authors: 68c2ecf20Sopenharmony_ci * Boris Brezillon <boris.brezillon@free-electrons.com> 78c2ecf20Sopenharmony_ci * Peter Pan <peterpandong@micron.com> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "nand: " fmt 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <linux/mtd/nand.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci/** 168c2ecf20Sopenharmony_ci * nanddev_isbad() - Check if a block is bad 178c2ecf20Sopenharmony_ci * @nand: NAND device 188c2ecf20Sopenharmony_ci * @pos: position pointing to the block we want to check 198c2ecf20Sopenharmony_ci * 208c2ecf20Sopenharmony_ci * Return: true if the block is bad, false otherwise. 218c2ecf20Sopenharmony_ci */ 228c2ecf20Sopenharmony_cibool nanddev_isbad(struct nand_device *nand, const struct nand_pos *pos) 238c2ecf20Sopenharmony_ci{ 248c2ecf20Sopenharmony_ci if (nanddev_bbt_is_initialized(nand)) { 258c2ecf20Sopenharmony_ci unsigned int entry; 268c2ecf20Sopenharmony_ci int status; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci entry = nanddev_bbt_pos_to_entry(nand, pos); 298c2ecf20Sopenharmony_ci status = nanddev_bbt_get_block_status(nand, entry); 308c2ecf20Sopenharmony_ci /* Lazy block status retrieval */ 318c2ecf20Sopenharmony_ci if (status == NAND_BBT_BLOCK_STATUS_UNKNOWN) { 328c2ecf20Sopenharmony_ci if (nand->ops->isbad(nand, pos)) 338c2ecf20Sopenharmony_ci status = NAND_BBT_BLOCK_FACTORY_BAD; 348c2ecf20Sopenharmony_ci else 358c2ecf20Sopenharmony_ci status = NAND_BBT_BLOCK_GOOD; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci nanddev_bbt_set_block_status(nand, entry, status); 388c2ecf20Sopenharmony_ci } 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci if (status == NAND_BBT_BLOCK_WORN || 418c2ecf20Sopenharmony_ci status == NAND_BBT_BLOCK_FACTORY_BAD) 428c2ecf20Sopenharmony_ci return true; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci return false; 458c2ecf20Sopenharmony_ci } 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci return nand->ops->isbad(nand, pos); 488c2ecf20Sopenharmony_ci} 498c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_isbad); 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci/** 528c2ecf20Sopenharmony_ci * nanddev_markbad() - Mark a block as bad 538c2ecf20Sopenharmony_ci * @nand: NAND device 548c2ecf20Sopenharmony_ci * @pos: position of the block to mark bad 558c2ecf20Sopenharmony_ci * 568c2ecf20Sopenharmony_ci * Mark a block bad. This function is updating the BBT if available and 578c2ecf20Sopenharmony_ci * calls the low-level markbad hook (nand->ops->markbad()). 588c2ecf20Sopenharmony_ci * 598c2ecf20Sopenharmony_ci * Return: 0 in case of success, a negative error code otherwise. 608c2ecf20Sopenharmony_ci */ 618c2ecf20Sopenharmony_ciint nanddev_markbad(struct nand_device *nand, const struct nand_pos *pos) 628c2ecf20Sopenharmony_ci{ 638c2ecf20Sopenharmony_ci struct mtd_info *mtd = nanddev_to_mtd(nand); 648c2ecf20Sopenharmony_ci unsigned int entry; 658c2ecf20Sopenharmony_ci int ret = 0; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci if (nanddev_isbad(nand, pos)) 688c2ecf20Sopenharmony_ci return 0; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci ret = nand->ops->markbad(nand, pos); 718c2ecf20Sopenharmony_ci if (ret) 728c2ecf20Sopenharmony_ci pr_warn("failed to write BBM to block @%llx (err = %d)\n", 738c2ecf20Sopenharmony_ci nanddev_pos_to_offs(nand, pos), ret); 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci if (!nanddev_bbt_is_initialized(nand)) 768c2ecf20Sopenharmony_ci goto out; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci entry = nanddev_bbt_pos_to_entry(nand, pos); 798c2ecf20Sopenharmony_ci ret = nanddev_bbt_set_block_status(nand, entry, NAND_BBT_BLOCK_WORN); 808c2ecf20Sopenharmony_ci if (ret) 818c2ecf20Sopenharmony_ci goto out; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci ret = nanddev_bbt_update(nand); 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ciout: 868c2ecf20Sopenharmony_ci if (!ret) 878c2ecf20Sopenharmony_ci mtd->ecc_stats.badblocks++; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci return ret; 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_markbad); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci/** 948c2ecf20Sopenharmony_ci * nanddev_isreserved() - Check whether an eraseblock is reserved or not 958c2ecf20Sopenharmony_ci * @nand: NAND device 968c2ecf20Sopenharmony_ci * @pos: NAND position to test 978c2ecf20Sopenharmony_ci * 988c2ecf20Sopenharmony_ci * Checks whether the eraseblock pointed by @pos is reserved or not. 998c2ecf20Sopenharmony_ci * 1008c2ecf20Sopenharmony_ci * Return: true if the eraseblock is reserved, false otherwise. 1018c2ecf20Sopenharmony_ci */ 1028c2ecf20Sopenharmony_cibool nanddev_isreserved(struct nand_device *nand, const struct nand_pos *pos) 1038c2ecf20Sopenharmony_ci{ 1048c2ecf20Sopenharmony_ci unsigned int entry; 1058c2ecf20Sopenharmony_ci int status; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci if (!nanddev_bbt_is_initialized(nand)) 1088c2ecf20Sopenharmony_ci return false; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci /* Return info from the table */ 1118c2ecf20Sopenharmony_ci entry = nanddev_bbt_pos_to_entry(nand, pos); 1128c2ecf20Sopenharmony_ci status = nanddev_bbt_get_block_status(nand, entry); 1138c2ecf20Sopenharmony_ci return status == NAND_BBT_BLOCK_RESERVED; 1148c2ecf20Sopenharmony_ci} 1158c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_isreserved); 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci/** 1188c2ecf20Sopenharmony_ci * nanddev_erase() - Erase a NAND portion 1198c2ecf20Sopenharmony_ci * @nand: NAND device 1208c2ecf20Sopenharmony_ci * @pos: position of the block to erase 1218c2ecf20Sopenharmony_ci * 1228c2ecf20Sopenharmony_ci * Erases the block if it's not bad. 1238c2ecf20Sopenharmony_ci * 1248c2ecf20Sopenharmony_ci * Return: 0 in case of success, a negative error code otherwise. 1258c2ecf20Sopenharmony_ci */ 1268c2ecf20Sopenharmony_ciint nanddev_erase(struct nand_device *nand, const struct nand_pos *pos) 1278c2ecf20Sopenharmony_ci{ 1288c2ecf20Sopenharmony_ci if (nanddev_isbad(nand, pos) || nanddev_isreserved(nand, pos)) { 1298c2ecf20Sopenharmony_ci pr_warn("attempt to erase a bad/reserved block @%llx\n", 1308c2ecf20Sopenharmony_ci nanddev_pos_to_offs(nand, pos)); 1318c2ecf20Sopenharmony_ci return -EIO; 1328c2ecf20Sopenharmony_ci } 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci return nand->ops->erase(nand, pos); 1358c2ecf20Sopenharmony_ci} 1368c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_erase); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci/** 1398c2ecf20Sopenharmony_ci * nanddev_mtd_erase() - Generic mtd->_erase() implementation for NAND devices 1408c2ecf20Sopenharmony_ci * @mtd: MTD device 1418c2ecf20Sopenharmony_ci * @einfo: erase request 1428c2ecf20Sopenharmony_ci * 1438c2ecf20Sopenharmony_ci * This is a simple mtd->_erase() implementation iterating over all blocks 1448c2ecf20Sopenharmony_ci * concerned by @einfo and calling nand->ops->erase() on each of them. 1458c2ecf20Sopenharmony_ci * 1468c2ecf20Sopenharmony_ci * Note that mtd->_erase should not be directly assigned to this helper, 1478c2ecf20Sopenharmony_ci * because there's no locking here. NAND specialized layers should instead 1488c2ecf20Sopenharmony_ci * implement there own wrapper around nanddev_mtd_erase() taking the 1498c2ecf20Sopenharmony_ci * appropriate lock before calling nanddev_mtd_erase(). 1508c2ecf20Sopenharmony_ci * 1518c2ecf20Sopenharmony_ci * Return: 0 in case of success, a negative error code otherwise. 1528c2ecf20Sopenharmony_ci */ 1538c2ecf20Sopenharmony_ciint nanddev_mtd_erase(struct mtd_info *mtd, struct erase_info *einfo) 1548c2ecf20Sopenharmony_ci{ 1558c2ecf20Sopenharmony_ci struct nand_device *nand = mtd_to_nanddev(mtd); 1568c2ecf20Sopenharmony_ci struct nand_pos pos, last; 1578c2ecf20Sopenharmony_ci int ret; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci nanddev_offs_to_pos(nand, einfo->addr, &pos); 1608c2ecf20Sopenharmony_ci nanddev_offs_to_pos(nand, einfo->addr + einfo->len - 1, &last); 1618c2ecf20Sopenharmony_ci while (nanddev_pos_cmp(&pos, &last) <= 0) { 1628c2ecf20Sopenharmony_ci ret = nanddev_erase(nand, &pos); 1638c2ecf20Sopenharmony_ci if (ret) { 1648c2ecf20Sopenharmony_ci einfo->fail_addr = nanddev_pos_to_offs(nand, &pos); 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci return ret; 1678c2ecf20Sopenharmony_ci } 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci nanddev_pos_next_eraseblock(nand, &pos); 1708c2ecf20Sopenharmony_ci } 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci return 0; 1738c2ecf20Sopenharmony_ci} 1748c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_mtd_erase); 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci/** 1778c2ecf20Sopenharmony_ci * nanddev_mtd_max_bad_blocks() - Get the maximum number of bad eraseblock on 1788c2ecf20Sopenharmony_ci * a specific region of the NAND device 1798c2ecf20Sopenharmony_ci * @mtd: MTD device 1808c2ecf20Sopenharmony_ci * @offs: offset of the NAND region 1818c2ecf20Sopenharmony_ci * @len: length of the NAND region 1828c2ecf20Sopenharmony_ci * 1838c2ecf20Sopenharmony_ci * Default implementation for mtd->_max_bad_blocks(). Only works if 1848c2ecf20Sopenharmony_ci * nand->memorg.max_bad_eraseblocks_per_lun is > 0. 1858c2ecf20Sopenharmony_ci * 1868c2ecf20Sopenharmony_ci * Return: a positive number encoding the maximum number of eraseblocks on a 1878c2ecf20Sopenharmony_ci * portion of memory, a negative error code otherwise. 1888c2ecf20Sopenharmony_ci */ 1898c2ecf20Sopenharmony_ciint nanddev_mtd_max_bad_blocks(struct mtd_info *mtd, loff_t offs, size_t len) 1908c2ecf20Sopenharmony_ci{ 1918c2ecf20Sopenharmony_ci struct nand_device *nand = mtd_to_nanddev(mtd); 1928c2ecf20Sopenharmony_ci struct nand_pos pos, end; 1938c2ecf20Sopenharmony_ci unsigned int max_bb = 0; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci if (!nand->memorg.max_bad_eraseblocks_per_lun) 1968c2ecf20Sopenharmony_ci return -ENOTSUPP; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci nanddev_offs_to_pos(nand, offs, &pos); 1998c2ecf20Sopenharmony_ci nanddev_offs_to_pos(nand, offs + len, &end); 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci for (nanddev_offs_to_pos(nand, offs, &pos); 2028c2ecf20Sopenharmony_ci nanddev_pos_cmp(&pos, &end) < 0; 2038c2ecf20Sopenharmony_ci nanddev_pos_next_lun(nand, &pos)) 2048c2ecf20Sopenharmony_ci max_bb += nand->memorg.max_bad_eraseblocks_per_lun; 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci return max_bb; 2078c2ecf20Sopenharmony_ci} 2088c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_mtd_max_bad_blocks); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci/** 2118c2ecf20Sopenharmony_ci * nanddev_init() - Initialize a NAND device 2128c2ecf20Sopenharmony_ci * @nand: NAND device 2138c2ecf20Sopenharmony_ci * @ops: NAND device operations 2148c2ecf20Sopenharmony_ci * @owner: NAND device owner 2158c2ecf20Sopenharmony_ci * 2168c2ecf20Sopenharmony_ci * Initializes a NAND device object. Consistency checks are done on @ops and 2178c2ecf20Sopenharmony_ci * @nand->memorg. Also takes care of initializing the BBT. 2188c2ecf20Sopenharmony_ci * 2198c2ecf20Sopenharmony_ci * Return: 0 in case of success, a negative error code otherwise. 2208c2ecf20Sopenharmony_ci */ 2218c2ecf20Sopenharmony_ciint nanddev_init(struct nand_device *nand, const struct nand_ops *ops, 2228c2ecf20Sopenharmony_ci struct module *owner) 2238c2ecf20Sopenharmony_ci{ 2248c2ecf20Sopenharmony_ci struct mtd_info *mtd = nanddev_to_mtd(nand); 2258c2ecf20Sopenharmony_ci struct nand_memory_organization *memorg = nanddev_get_memorg(nand); 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci if (!nand || !ops) 2288c2ecf20Sopenharmony_ci return -EINVAL; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci if (!ops->erase || !ops->markbad || !ops->isbad) 2318c2ecf20Sopenharmony_ci return -EINVAL; 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci if (!memorg->bits_per_cell || !memorg->pagesize || 2348c2ecf20Sopenharmony_ci !memorg->pages_per_eraseblock || !memorg->eraseblocks_per_lun || 2358c2ecf20Sopenharmony_ci !memorg->planes_per_lun || !memorg->luns_per_target || 2368c2ecf20Sopenharmony_ci !memorg->ntargets) 2378c2ecf20Sopenharmony_ci return -EINVAL; 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci nand->rowconv.eraseblock_addr_shift = 2408c2ecf20Sopenharmony_ci fls(memorg->pages_per_eraseblock - 1); 2418c2ecf20Sopenharmony_ci nand->rowconv.lun_addr_shift = fls(memorg->eraseblocks_per_lun - 1) + 2428c2ecf20Sopenharmony_ci nand->rowconv.eraseblock_addr_shift; 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci nand->ops = ops; 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci mtd->type = memorg->bits_per_cell == 1 ? 2478c2ecf20Sopenharmony_ci MTD_NANDFLASH : MTD_MLCNANDFLASH; 2488c2ecf20Sopenharmony_ci mtd->flags = MTD_CAP_NANDFLASH; 2498c2ecf20Sopenharmony_ci mtd->erasesize = memorg->pagesize * memorg->pages_per_eraseblock; 2508c2ecf20Sopenharmony_ci mtd->writesize = memorg->pagesize; 2518c2ecf20Sopenharmony_ci mtd->writebufsize = memorg->pagesize; 2528c2ecf20Sopenharmony_ci mtd->oobsize = memorg->oobsize; 2538c2ecf20Sopenharmony_ci mtd->size = nanddev_size(nand); 2548c2ecf20Sopenharmony_ci mtd->owner = owner; 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci return nanddev_bbt_init(nand); 2578c2ecf20Sopenharmony_ci} 2588c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_init); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci/** 2618c2ecf20Sopenharmony_ci * nanddev_cleanup() - Release resources allocated in nanddev_init() 2628c2ecf20Sopenharmony_ci * @nand: NAND device 2638c2ecf20Sopenharmony_ci * 2648c2ecf20Sopenharmony_ci * Basically undoes what has been done in nanddev_init(). 2658c2ecf20Sopenharmony_ci */ 2668c2ecf20Sopenharmony_civoid nanddev_cleanup(struct nand_device *nand) 2678c2ecf20Sopenharmony_ci{ 2688c2ecf20Sopenharmony_ci if (nanddev_bbt_is_initialized(nand)) 2698c2ecf20Sopenharmony_ci nanddev_bbt_cleanup(nand); 2708c2ecf20Sopenharmony_ci} 2718c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_cleanup); 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Generic NAND framework"); 2748c2ecf20Sopenharmony_ciMODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); 2758c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 276