Boris Brezillon | 894380f | 2018-08-16 17:30:09 +0200 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * Copyright (c) 2017 Free Electrons |
| 4 | * |
| 5 | * Authors: |
| 6 | * Boris Brezillon <boris.brezillon@free-electrons.com> |
| 7 | * Peter Pan <peterpandong@micron.com> |
| 8 | */ |
| 9 | |
| 10 | #define pr_fmt(fmt) "nand: " fmt |
| 11 | |
Miquel Raynal | 4f64c63 | 2019-10-03 19:50:21 +0200 | [diff] [blame] | 12 | #include <common.h> |
Boris Brezillon | 894380f | 2018-08-16 17:30:09 +0200 | [diff] [blame] | 13 | #ifndef __UBOOT__ |
| 14 | #include <linux/module.h> |
| 15 | #endif |
| 16 | #include <linux/mtd/nand.h> |
| 17 | |
| 18 | /** |
| 19 | * nanddev_isbad() - Check if a block is bad |
| 20 | * @nand: NAND device |
| 21 | * @pos: position pointing to the block we want to check |
| 22 | * |
| 23 | * Return: true if the block is bad, false otherwise. |
| 24 | */ |
| 25 | bool nanddev_isbad(struct nand_device *nand, const struct nand_pos *pos) |
| 26 | { |
| 27 | if (nanddev_bbt_is_initialized(nand)) { |
| 28 | unsigned int entry; |
| 29 | int status; |
| 30 | |
| 31 | entry = nanddev_bbt_pos_to_entry(nand, pos); |
| 32 | status = nanddev_bbt_get_block_status(nand, entry); |
| 33 | /* Lazy block status retrieval */ |
| 34 | if (status == NAND_BBT_BLOCK_STATUS_UNKNOWN) { |
| 35 | if (nand->ops->isbad(nand, pos)) |
| 36 | status = NAND_BBT_BLOCK_FACTORY_BAD; |
| 37 | else |
| 38 | status = NAND_BBT_BLOCK_GOOD; |
| 39 | |
| 40 | nanddev_bbt_set_block_status(nand, entry, status); |
| 41 | } |
| 42 | |
| 43 | if (status == NAND_BBT_BLOCK_WORN || |
| 44 | status == NAND_BBT_BLOCK_FACTORY_BAD) |
| 45 | return true; |
| 46 | |
| 47 | return false; |
| 48 | } |
| 49 | |
| 50 | return nand->ops->isbad(nand, pos); |
| 51 | } |
| 52 | EXPORT_SYMBOL_GPL(nanddev_isbad); |
| 53 | |
| 54 | /** |
| 55 | * nanddev_markbad() - Mark a block as bad |
| 56 | * @nand: NAND device |
| 57 | * @pos: position of the block to mark bad |
| 58 | * |
| 59 | * Mark a block bad. This function is updating the BBT if available and |
| 60 | * calls the low-level markbad hook (nand->ops->markbad()). |
| 61 | * |
| 62 | * Return: 0 in case of success, a negative error code otherwise. |
| 63 | */ |
| 64 | int nanddev_markbad(struct nand_device *nand, const struct nand_pos *pos) |
| 65 | { |
| 66 | struct mtd_info *mtd = nanddev_to_mtd(nand); |
| 67 | unsigned int entry; |
| 68 | int ret = 0; |
| 69 | |
| 70 | if (nanddev_isbad(nand, pos)) |
| 71 | return 0; |
| 72 | |
| 73 | ret = nand->ops->markbad(nand, pos); |
| 74 | if (ret) |
| 75 | pr_warn("failed to write BBM to block @%llx (err = %d)\n", |
| 76 | nanddev_pos_to_offs(nand, pos), ret); |
| 77 | |
| 78 | if (!nanddev_bbt_is_initialized(nand)) |
| 79 | goto out; |
| 80 | |
| 81 | entry = nanddev_bbt_pos_to_entry(nand, pos); |
| 82 | ret = nanddev_bbt_set_block_status(nand, entry, NAND_BBT_BLOCK_WORN); |
| 83 | if (ret) |
| 84 | goto out; |
| 85 | |
| 86 | ret = nanddev_bbt_update(nand); |
| 87 | |
| 88 | out: |
| 89 | if (!ret) |
| 90 | mtd->ecc_stats.badblocks++; |
| 91 | |
| 92 | return ret; |
| 93 | } |
| 94 | EXPORT_SYMBOL_GPL(nanddev_markbad); |
| 95 | |
| 96 | /** |
| 97 | * nanddev_isreserved() - Check whether an eraseblock is reserved or not |
| 98 | * @nand: NAND device |
| 99 | * @pos: NAND position to test |
| 100 | * |
| 101 | * Checks whether the eraseblock pointed by @pos is reserved or not. |
| 102 | * |
| 103 | * Return: true if the eraseblock is reserved, false otherwise. |
| 104 | */ |
| 105 | bool nanddev_isreserved(struct nand_device *nand, const struct nand_pos *pos) |
| 106 | { |
| 107 | unsigned int entry; |
| 108 | int status; |
| 109 | |
| 110 | if (!nanddev_bbt_is_initialized(nand)) |
| 111 | return false; |
| 112 | |
| 113 | /* Return info from the table */ |
| 114 | entry = nanddev_bbt_pos_to_entry(nand, pos); |
| 115 | status = nanddev_bbt_get_block_status(nand, entry); |
| 116 | return status == NAND_BBT_BLOCK_RESERVED; |
| 117 | } |
| 118 | EXPORT_SYMBOL_GPL(nanddev_isreserved); |
| 119 | |
| 120 | /** |
| 121 | * nanddev_erase() - Erase a NAND portion |
| 122 | * @nand: NAND device |
| 123 | * @pos: position of the block to erase |
| 124 | * |
| 125 | * Erases the block if it's not bad. |
| 126 | * |
| 127 | * Return: 0 in case of success, a negative error code otherwise. |
| 128 | */ |
| 129 | int nanddev_erase(struct nand_device *nand, const struct nand_pos *pos) |
| 130 | { |
| 131 | if (nanddev_isbad(nand, pos) || nanddev_isreserved(nand, pos)) { |
| 132 | pr_warn("attempt to erase a bad/reserved block @%llx\n", |
| 133 | nanddev_pos_to_offs(nand, pos)); |
| 134 | return -EIO; |
| 135 | } |
| 136 | |
| 137 | return nand->ops->erase(nand, pos); |
| 138 | } |
| 139 | EXPORT_SYMBOL_GPL(nanddev_erase); |
| 140 | |
| 141 | /** |
| 142 | * nanddev_mtd_erase() - Generic mtd->_erase() implementation for NAND devices |
| 143 | * @mtd: MTD device |
| 144 | * @einfo: erase request |
| 145 | * |
| 146 | * This is a simple mtd->_erase() implementation iterating over all blocks |
| 147 | * concerned by @einfo and calling nand->ops->erase() on each of them. |
| 148 | * |
| 149 | * Note that mtd->_erase should not be directly assigned to this helper, |
| 150 | * because there's no locking here. NAND specialized layers should instead |
| 151 | * implement there own wrapper around nanddev_mtd_erase() taking the |
| 152 | * appropriate lock before calling nanddev_mtd_erase(). |
| 153 | * |
| 154 | * Return: 0 in case of success, a negative error code otherwise. |
| 155 | */ |
| 156 | int nanddev_mtd_erase(struct mtd_info *mtd, struct erase_info *einfo) |
| 157 | { |
| 158 | struct nand_device *nand = mtd_to_nanddev(mtd); |
| 159 | struct nand_pos pos, last; |
| 160 | int ret; |
| 161 | |
| 162 | nanddev_offs_to_pos(nand, einfo->addr, &pos); |
| 163 | nanddev_offs_to_pos(nand, einfo->addr + einfo->len - 1, &last); |
| 164 | while (nanddev_pos_cmp(&pos, &last) <= 0) { |
| 165 | ret = nanddev_erase(nand, &pos); |
| 166 | if (ret) { |
| 167 | einfo->fail_addr = nanddev_pos_to_offs(nand, &pos); |
| 168 | |
| 169 | return ret; |
| 170 | } |
| 171 | |
| 172 | nanddev_pos_next_eraseblock(nand, &pos); |
| 173 | } |
| 174 | |
| 175 | return 0; |
| 176 | } |
| 177 | EXPORT_SYMBOL_GPL(nanddev_mtd_erase); |
| 178 | |
| 179 | /** |
| 180 | * nanddev_init() - Initialize a NAND device |
| 181 | * @nand: NAND device |
| 182 | * @ops: NAND device operations |
| 183 | * @owner: NAND device owner |
| 184 | * |
| 185 | * Initializes a NAND device object. Consistency checks are done on @ops and |
| 186 | * @nand->memorg. Also takes care of initializing the BBT. |
| 187 | * |
| 188 | * Return: 0 in case of success, a negative error code otherwise. |
| 189 | */ |
| 190 | int nanddev_init(struct nand_device *nand, const struct nand_ops *ops, |
| 191 | struct module *owner) |
| 192 | { |
| 193 | struct mtd_info *mtd = nanddev_to_mtd(nand); |
| 194 | struct nand_memory_organization *memorg = nanddev_get_memorg(nand); |
| 195 | |
| 196 | if (!nand || !ops) |
| 197 | return -EINVAL; |
| 198 | |
| 199 | if (!ops->erase || !ops->markbad || !ops->isbad) |
| 200 | return -EINVAL; |
| 201 | |
| 202 | if (!memorg->bits_per_cell || !memorg->pagesize || |
| 203 | !memorg->pages_per_eraseblock || !memorg->eraseblocks_per_lun || |
| 204 | !memorg->planes_per_lun || !memorg->luns_per_target || |
| 205 | !memorg->ntargets) |
| 206 | return -EINVAL; |
| 207 | |
| 208 | nand->rowconv.eraseblock_addr_shift = |
| 209 | fls(memorg->pages_per_eraseblock - 1); |
| 210 | nand->rowconv.lun_addr_shift = fls(memorg->eraseblocks_per_lun - 1) + |
| 211 | nand->rowconv.eraseblock_addr_shift; |
| 212 | |
| 213 | nand->ops = ops; |
| 214 | |
| 215 | mtd->type = memorg->bits_per_cell == 1 ? |
| 216 | MTD_NANDFLASH : MTD_MLCNANDFLASH; |
| 217 | mtd->flags = MTD_CAP_NANDFLASH; |
| 218 | mtd->erasesize = memorg->pagesize * memorg->pages_per_eraseblock; |
| 219 | mtd->writesize = memorg->pagesize; |
| 220 | mtd->writebufsize = memorg->pagesize; |
| 221 | mtd->oobsize = memorg->oobsize; |
| 222 | mtd->size = nanddev_size(nand); |
| 223 | mtd->owner = owner; |
| 224 | |
| 225 | return nanddev_bbt_init(nand); |
| 226 | } |
| 227 | EXPORT_SYMBOL_GPL(nanddev_init); |
| 228 | |
| 229 | /** |
| 230 | * nanddev_cleanup() - Release resources allocated in nanddev_init() |
| 231 | * @nand: NAND device |
| 232 | * |
| 233 | * Basically undoes what has been done in nanddev_init(). |
| 234 | */ |
| 235 | void nanddev_cleanup(struct nand_device *nand) |
| 236 | { |
| 237 | if (nanddev_bbt_is_initialized(nand)) |
| 238 | nanddev_bbt_cleanup(nand); |
| 239 | } |
| 240 | EXPORT_SYMBOL_GPL(nanddev_cleanup); |
| 241 | |
| 242 | MODULE_DESCRIPTION("Generic NAND framework"); |
| 243 | MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); |
| 244 | MODULE_LICENSE("GPL v2"); |