162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2017 Free Electrons
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Authors:
662306a36Sopenharmony_ci *	Boris Brezillon <boris.brezillon@free-electrons.com>
762306a36Sopenharmony_ci *	Peter Pan <peterpandong@micron.com>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#define pr_fmt(fmt)	"nand: " fmt
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/mtd/nand.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci/**
1662306a36Sopenharmony_ci * nanddev_isbad() - Check if a block is bad
1762306a36Sopenharmony_ci * @nand: NAND device
1862306a36Sopenharmony_ci * @pos: position pointing to the block we want to check
1962306a36Sopenharmony_ci *
2062306a36Sopenharmony_ci * Return: true if the block is bad, false otherwise.
2162306a36Sopenharmony_ci */
2262306a36Sopenharmony_cibool nanddev_isbad(struct nand_device *nand, const struct nand_pos *pos)
2362306a36Sopenharmony_ci{
2462306a36Sopenharmony_ci	if (mtd_check_expert_analysis_mode())
2562306a36Sopenharmony_ci		return false;
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	if (nanddev_bbt_is_initialized(nand)) {
2862306a36Sopenharmony_ci		unsigned int entry;
2962306a36Sopenharmony_ci		int status;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci		entry = nanddev_bbt_pos_to_entry(nand, pos);
3262306a36Sopenharmony_ci		status = nanddev_bbt_get_block_status(nand, entry);
3362306a36Sopenharmony_ci		/* Lazy block status retrieval */
3462306a36Sopenharmony_ci		if (status == NAND_BBT_BLOCK_STATUS_UNKNOWN) {
3562306a36Sopenharmony_ci			if (nand->ops->isbad(nand, pos))
3662306a36Sopenharmony_ci				status = NAND_BBT_BLOCK_FACTORY_BAD;
3762306a36Sopenharmony_ci			else
3862306a36Sopenharmony_ci				status = NAND_BBT_BLOCK_GOOD;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci			nanddev_bbt_set_block_status(nand, entry, status);
4162306a36Sopenharmony_ci		}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci		if (status == NAND_BBT_BLOCK_WORN ||
4462306a36Sopenharmony_ci		    status == NAND_BBT_BLOCK_FACTORY_BAD)
4562306a36Sopenharmony_ci			return true;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci		return false;
4862306a36Sopenharmony_ci	}
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	return nand->ops->isbad(nand, pos);
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_isbad);
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci/**
5562306a36Sopenharmony_ci * nanddev_markbad() - Mark a block as bad
5662306a36Sopenharmony_ci * @nand: NAND device
5762306a36Sopenharmony_ci * @pos: position of the block to mark bad
5862306a36Sopenharmony_ci *
5962306a36Sopenharmony_ci * Mark a block bad. This function is updating the BBT if available and
6062306a36Sopenharmony_ci * calls the low-level markbad hook (nand->ops->markbad()).
6162306a36Sopenharmony_ci *
6262306a36Sopenharmony_ci * Return: 0 in case of success, a negative error code otherwise.
6362306a36Sopenharmony_ci */
6462306a36Sopenharmony_ciint nanddev_markbad(struct nand_device *nand, const struct nand_pos *pos)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	struct mtd_info *mtd = nanddev_to_mtd(nand);
6762306a36Sopenharmony_ci	unsigned int entry;
6862306a36Sopenharmony_ci	int ret = 0;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	if (nanddev_isbad(nand, pos))
7162306a36Sopenharmony_ci		return 0;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	ret = nand->ops->markbad(nand, pos);
7462306a36Sopenharmony_ci	if (ret)
7562306a36Sopenharmony_ci		pr_warn("failed to write BBM to block @%llx (err = %d)\n",
7662306a36Sopenharmony_ci			nanddev_pos_to_offs(nand, pos), ret);
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	if (!nanddev_bbt_is_initialized(nand))
7962306a36Sopenharmony_ci		goto out;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	entry = nanddev_bbt_pos_to_entry(nand, pos);
8262306a36Sopenharmony_ci	ret = nanddev_bbt_set_block_status(nand, entry, NAND_BBT_BLOCK_WORN);
8362306a36Sopenharmony_ci	if (ret)
8462306a36Sopenharmony_ci		goto out;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	ret = nanddev_bbt_update(nand);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ciout:
8962306a36Sopenharmony_ci	if (!ret)
9062306a36Sopenharmony_ci		mtd->ecc_stats.badblocks++;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	return ret;
9362306a36Sopenharmony_ci}
9462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_markbad);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci/**
9762306a36Sopenharmony_ci * nanddev_isreserved() - Check whether an eraseblock is reserved or not
9862306a36Sopenharmony_ci * @nand: NAND device
9962306a36Sopenharmony_ci * @pos: NAND position to test
10062306a36Sopenharmony_ci *
10162306a36Sopenharmony_ci * Checks whether the eraseblock pointed by @pos is reserved or not.
10262306a36Sopenharmony_ci *
10362306a36Sopenharmony_ci * Return: true if the eraseblock is reserved, false otherwise.
10462306a36Sopenharmony_ci */
10562306a36Sopenharmony_cibool nanddev_isreserved(struct nand_device *nand, const struct nand_pos *pos)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	unsigned int entry;
10862306a36Sopenharmony_ci	int status;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	if (!nanddev_bbt_is_initialized(nand))
11162306a36Sopenharmony_ci		return false;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	/* Return info from the table */
11462306a36Sopenharmony_ci	entry = nanddev_bbt_pos_to_entry(nand, pos);
11562306a36Sopenharmony_ci	status = nanddev_bbt_get_block_status(nand, entry);
11662306a36Sopenharmony_ci	return status == NAND_BBT_BLOCK_RESERVED;
11762306a36Sopenharmony_ci}
11862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_isreserved);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci/**
12162306a36Sopenharmony_ci * nanddev_erase() - Erase a NAND portion
12262306a36Sopenharmony_ci * @nand: NAND device
12362306a36Sopenharmony_ci * @pos: position of the block to erase
12462306a36Sopenharmony_ci *
12562306a36Sopenharmony_ci * Erases the block if it's not bad.
12662306a36Sopenharmony_ci *
12762306a36Sopenharmony_ci * Return: 0 in case of success, a negative error code otherwise.
12862306a36Sopenharmony_ci */
12962306a36Sopenharmony_cistatic int nanddev_erase(struct nand_device *nand, const struct nand_pos *pos)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	if (nanddev_isbad(nand, pos) || nanddev_isreserved(nand, pos)) {
13262306a36Sopenharmony_ci		pr_warn("attempt to erase a bad/reserved block @%llx\n",
13362306a36Sopenharmony_ci			nanddev_pos_to_offs(nand, pos));
13462306a36Sopenharmony_ci		return -EIO;
13562306a36Sopenharmony_ci	}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	return nand->ops->erase(nand, pos);
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci/**
14162306a36Sopenharmony_ci * nanddev_mtd_erase() - Generic mtd->_erase() implementation for NAND devices
14262306a36Sopenharmony_ci * @mtd: MTD device
14362306a36Sopenharmony_ci * @einfo: erase request
14462306a36Sopenharmony_ci *
14562306a36Sopenharmony_ci * This is a simple mtd->_erase() implementation iterating over all blocks
14662306a36Sopenharmony_ci * concerned by @einfo and calling nand->ops->erase() on each of them.
14762306a36Sopenharmony_ci *
14862306a36Sopenharmony_ci * Note that mtd->_erase should not be directly assigned to this helper,
14962306a36Sopenharmony_ci * because there's no locking here. NAND specialized layers should instead
15062306a36Sopenharmony_ci * implement there own wrapper around nanddev_mtd_erase() taking the
15162306a36Sopenharmony_ci * appropriate lock before calling nanddev_mtd_erase().
15262306a36Sopenharmony_ci *
15362306a36Sopenharmony_ci * Return: 0 in case of success, a negative error code otherwise.
15462306a36Sopenharmony_ci */
15562306a36Sopenharmony_ciint nanddev_mtd_erase(struct mtd_info *mtd, struct erase_info *einfo)
15662306a36Sopenharmony_ci{
15762306a36Sopenharmony_ci	struct nand_device *nand = mtd_to_nanddev(mtd);
15862306a36Sopenharmony_ci	struct nand_pos pos, last;
15962306a36Sopenharmony_ci	int ret;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	nanddev_offs_to_pos(nand, einfo->addr, &pos);
16262306a36Sopenharmony_ci	nanddev_offs_to_pos(nand, einfo->addr + einfo->len - 1, &last);
16362306a36Sopenharmony_ci	while (nanddev_pos_cmp(&pos, &last) <= 0) {
16462306a36Sopenharmony_ci		ret = nanddev_erase(nand, &pos);
16562306a36Sopenharmony_ci		if (ret) {
16662306a36Sopenharmony_ci			einfo->fail_addr = nanddev_pos_to_offs(nand, &pos);
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci			return ret;
16962306a36Sopenharmony_ci		}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci		nanddev_pos_next_eraseblock(nand, &pos);
17262306a36Sopenharmony_ci	}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	return 0;
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_mtd_erase);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci/**
17962306a36Sopenharmony_ci * nanddev_mtd_max_bad_blocks() - Get the maximum number of bad eraseblock on
18062306a36Sopenharmony_ci *				  a specific region of the NAND device
18162306a36Sopenharmony_ci * @mtd: MTD device
18262306a36Sopenharmony_ci * @offs: offset of the NAND region
18362306a36Sopenharmony_ci * @len: length of the NAND region
18462306a36Sopenharmony_ci *
18562306a36Sopenharmony_ci * Default implementation for mtd->_max_bad_blocks(). Only works if
18662306a36Sopenharmony_ci * nand->memorg.max_bad_eraseblocks_per_lun is > 0.
18762306a36Sopenharmony_ci *
18862306a36Sopenharmony_ci * Return: a positive number encoding the maximum number of eraseblocks on a
18962306a36Sopenharmony_ci * portion of memory, a negative error code otherwise.
19062306a36Sopenharmony_ci */
19162306a36Sopenharmony_ciint nanddev_mtd_max_bad_blocks(struct mtd_info *mtd, loff_t offs, size_t len)
19262306a36Sopenharmony_ci{
19362306a36Sopenharmony_ci	struct nand_device *nand = mtd_to_nanddev(mtd);
19462306a36Sopenharmony_ci	struct nand_pos pos, end;
19562306a36Sopenharmony_ci	unsigned int max_bb = 0;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	if (!nand->memorg.max_bad_eraseblocks_per_lun)
19862306a36Sopenharmony_ci		return -ENOTSUPP;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	nanddev_offs_to_pos(nand, offs, &pos);
20162306a36Sopenharmony_ci	nanddev_offs_to_pos(nand, offs + len, &end);
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	for (nanddev_offs_to_pos(nand, offs, &pos);
20462306a36Sopenharmony_ci	     nanddev_pos_cmp(&pos, &end) < 0;
20562306a36Sopenharmony_ci	     nanddev_pos_next_lun(nand, &pos))
20662306a36Sopenharmony_ci		max_bb += nand->memorg.max_bad_eraseblocks_per_lun;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	return max_bb;
20962306a36Sopenharmony_ci}
21062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_mtd_max_bad_blocks);
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci/**
21362306a36Sopenharmony_ci * nanddev_get_ecc_engine() - Find and get a suitable ECC engine
21462306a36Sopenharmony_ci * @nand: NAND device
21562306a36Sopenharmony_ci */
21662306a36Sopenharmony_cistatic int nanddev_get_ecc_engine(struct nand_device *nand)
21762306a36Sopenharmony_ci{
21862306a36Sopenharmony_ci	int engine_type;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	/* Read the user desires in terms of ECC engine/configuration */
22162306a36Sopenharmony_ci	of_get_nand_ecc_user_config(nand);
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	engine_type = nand->ecc.user_conf.engine_type;
22462306a36Sopenharmony_ci	if (engine_type == NAND_ECC_ENGINE_TYPE_INVALID)
22562306a36Sopenharmony_ci		engine_type = nand->ecc.defaults.engine_type;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	switch (engine_type) {
22862306a36Sopenharmony_ci	case NAND_ECC_ENGINE_TYPE_NONE:
22962306a36Sopenharmony_ci		return 0;
23062306a36Sopenharmony_ci	case NAND_ECC_ENGINE_TYPE_SOFT:
23162306a36Sopenharmony_ci		nand->ecc.engine = nand_ecc_get_sw_engine(nand);
23262306a36Sopenharmony_ci		break;
23362306a36Sopenharmony_ci	case NAND_ECC_ENGINE_TYPE_ON_DIE:
23462306a36Sopenharmony_ci		nand->ecc.engine = nand_ecc_get_on_die_hw_engine(nand);
23562306a36Sopenharmony_ci		break;
23662306a36Sopenharmony_ci	case NAND_ECC_ENGINE_TYPE_ON_HOST:
23762306a36Sopenharmony_ci		nand->ecc.engine = nand_ecc_get_on_host_hw_engine(nand);
23862306a36Sopenharmony_ci		if (PTR_ERR(nand->ecc.engine) == -EPROBE_DEFER)
23962306a36Sopenharmony_ci			return -EPROBE_DEFER;
24062306a36Sopenharmony_ci		break;
24162306a36Sopenharmony_ci	default:
24262306a36Sopenharmony_ci		pr_err("Missing ECC engine type\n");
24362306a36Sopenharmony_ci	}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	if (!nand->ecc.engine)
24662306a36Sopenharmony_ci		return  -EINVAL;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	return 0;
24962306a36Sopenharmony_ci}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci/**
25262306a36Sopenharmony_ci * nanddev_put_ecc_engine() - Dettach and put the in-use ECC engine
25362306a36Sopenharmony_ci * @nand: NAND device
25462306a36Sopenharmony_ci */
25562306a36Sopenharmony_cistatic int nanddev_put_ecc_engine(struct nand_device *nand)
25662306a36Sopenharmony_ci{
25762306a36Sopenharmony_ci	switch (nand->ecc.ctx.conf.engine_type) {
25862306a36Sopenharmony_ci	case NAND_ECC_ENGINE_TYPE_ON_HOST:
25962306a36Sopenharmony_ci		nand_ecc_put_on_host_hw_engine(nand);
26062306a36Sopenharmony_ci		break;
26162306a36Sopenharmony_ci	case NAND_ECC_ENGINE_TYPE_NONE:
26262306a36Sopenharmony_ci	case NAND_ECC_ENGINE_TYPE_SOFT:
26362306a36Sopenharmony_ci	case NAND_ECC_ENGINE_TYPE_ON_DIE:
26462306a36Sopenharmony_ci	default:
26562306a36Sopenharmony_ci		break;
26662306a36Sopenharmony_ci	}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	return 0;
26962306a36Sopenharmony_ci}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci/**
27262306a36Sopenharmony_ci * nanddev_find_ecc_configuration() - Find a suitable ECC configuration
27362306a36Sopenharmony_ci * @nand: NAND device
27462306a36Sopenharmony_ci */
27562306a36Sopenharmony_cistatic int nanddev_find_ecc_configuration(struct nand_device *nand)
27662306a36Sopenharmony_ci{
27762306a36Sopenharmony_ci	int ret;
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	if (!nand->ecc.engine)
28062306a36Sopenharmony_ci		return -ENOTSUPP;
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	ret = nand_ecc_init_ctx(nand);
28362306a36Sopenharmony_ci	if (ret)
28462306a36Sopenharmony_ci		return ret;
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	if (!nand_ecc_is_strong_enough(nand))
28762306a36Sopenharmony_ci		pr_warn("WARNING: %s: the ECC used on your system is too weak compared to the one required by the NAND chip\n",
28862306a36Sopenharmony_ci			nand->mtd.name);
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	return 0;
29162306a36Sopenharmony_ci}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci/**
29462306a36Sopenharmony_ci * nanddev_ecc_engine_init() - Initialize an ECC engine for the chip
29562306a36Sopenharmony_ci * @nand: NAND device
29662306a36Sopenharmony_ci */
29762306a36Sopenharmony_ciint nanddev_ecc_engine_init(struct nand_device *nand)
29862306a36Sopenharmony_ci{
29962306a36Sopenharmony_ci	int ret;
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	/* Look for the ECC engine to use */
30262306a36Sopenharmony_ci	ret = nanddev_get_ecc_engine(nand);
30362306a36Sopenharmony_ci	if (ret) {
30462306a36Sopenharmony_ci		if (ret != -EPROBE_DEFER)
30562306a36Sopenharmony_ci			pr_err("No ECC engine found\n");
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci		return ret;
30862306a36Sopenharmony_ci	}
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	/* No ECC engine requested */
31162306a36Sopenharmony_ci	if (!nand->ecc.engine)
31262306a36Sopenharmony_ci		return 0;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	/* Configure the engine: balance user input and chip requirements */
31562306a36Sopenharmony_ci	ret = nanddev_find_ecc_configuration(nand);
31662306a36Sopenharmony_ci	if (ret) {
31762306a36Sopenharmony_ci		pr_err("No suitable ECC configuration\n");
31862306a36Sopenharmony_ci		nanddev_put_ecc_engine(nand);
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci		return ret;
32162306a36Sopenharmony_ci	}
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	return 0;
32462306a36Sopenharmony_ci}
32562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_ecc_engine_init);
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci/**
32862306a36Sopenharmony_ci * nanddev_ecc_engine_cleanup() - Cleanup ECC engine initializations
32962306a36Sopenharmony_ci * @nand: NAND device
33062306a36Sopenharmony_ci */
33162306a36Sopenharmony_civoid nanddev_ecc_engine_cleanup(struct nand_device *nand)
33262306a36Sopenharmony_ci{
33362306a36Sopenharmony_ci	if (nand->ecc.engine)
33462306a36Sopenharmony_ci		nand_ecc_cleanup_ctx(nand);
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	nanddev_put_ecc_engine(nand);
33762306a36Sopenharmony_ci}
33862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_ecc_engine_cleanup);
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci/**
34162306a36Sopenharmony_ci * nanddev_init() - Initialize a NAND device
34262306a36Sopenharmony_ci * @nand: NAND device
34362306a36Sopenharmony_ci * @ops: NAND device operations
34462306a36Sopenharmony_ci * @owner: NAND device owner
34562306a36Sopenharmony_ci *
34662306a36Sopenharmony_ci * Initializes a NAND device object. Consistency checks are done on @ops and
34762306a36Sopenharmony_ci * @nand->memorg. Also takes care of initializing the BBT.
34862306a36Sopenharmony_ci *
34962306a36Sopenharmony_ci * Return: 0 in case of success, a negative error code otherwise.
35062306a36Sopenharmony_ci */
35162306a36Sopenharmony_ciint nanddev_init(struct nand_device *nand, const struct nand_ops *ops,
35262306a36Sopenharmony_ci		 struct module *owner)
35362306a36Sopenharmony_ci{
35462306a36Sopenharmony_ci	struct mtd_info *mtd = nanddev_to_mtd(nand);
35562306a36Sopenharmony_ci	struct nand_memory_organization *memorg = nanddev_get_memorg(nand);
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	if (!nand || !ops)
35862306a36Sopenharmony_ci		return -EINVAL;
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci	if (!ops->erase || !ops->markbad || !ops->isbad)
36162306a36Sopenharmony_ci		return -EINVAL;
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	if (!memorg->bits_per_cell || !memorg->pagesize ||
36462306a36Sopenharmony_ci	    !memorg->pages_per_eraseblock || !memorg->eraseblocks_per_lun ||
36562306a36Sopenharmony_ci	    !memorg->planes_per_lun || !memorg->luns_per_target ||
36662306a36Sopenharmony_ci	    !memorg->ntargets)
36762306a36Sopenharmony_ci		return -EINVAL;
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci	nand->rowconv.eraseblock_addr_shift =
37062306a36Sopenharmony_ci					fls(memorg->pages_per_eraseblock - 1);
37162306a36Sopenharmony_ci	nand->rowconv.lun_addr_shift = fls(memorg->eraseblocks_per_lun - 1) +
37262306a36Sopenharmony_ci				       nand->rowconv.eraseblock_addr_shift;
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	nand->ops = ops;
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci	mtd->type = memorg->bits_per_cell == 1 ?
37762306a36Sopenharmony_ci		    MTD_NANDFLASH : MTD_MLCNANDFLASH;
37862306a36Sopenharmony_ci	mtd->flags = MTD_CAP_NANDFLASH;
37962306a36Sopenharmony_ci	mtd->erasesize = memorg->pagesize * memorg->pages_per_eraseblock;
38062306a36Sopenharmony_ci	mtd->writesize = memorg->pagesize;
38162306a36Sopenharmony_ci	mtd->writebufsize = memorg->pagesize;
38262306a36Sopenharmony_ci	mtd->oobsize = memorg->oobsize;
38362306a36Sopenharmony_ci	mtd->size = nanddev_size(nand);
38462306a36Sopenharmony_ci	mtd->owner = owner;
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	return nanddev_bbt_init(nand);
38762306a36Sopenharmony_ci}
38862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_init);
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci/**
39162306a36Sopenharmony_ci * nanddev_cleanup() - Release resources allocated in nanddev_init()
39262306a36Sopenharmony_ci * @nand: NAND device
39362306a36Sopenharmony_ci *
39462306a36Sopenharmony_ci * Basically undoes what has been done in nanddev_init().
39562306a36Sopenharmony_ci */
39662306a36Sopenharmony_civoid nanddev_cleanup(struct nand_device *nand)
39762306a36Sopenharmony_ci{
39862306a36Sopenharmony_ci	if (nanddev_bbt_is_initialized(nand))
39962306a36Sopenharmony_ci		nanddev_bbt_cleanup(nand);
40062306a36Sopenharmony_ci}
40162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nanddev_cleanup);
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_ciMODULE_DESCRIPTION("Generic NAND framework");
40462306a36Sopenharmony_ciMODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>");
40562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
406