Skip to content

Commit

Permalink
mtd: nand: add generic helpers to check, match, maximize ECC settings
Browse files Browse the repository at this point in the history
Driver are responsible for setting up ECC parameters correctly.
Those include:
  - Check if ECC parameters specified (usually by DT) are valid
  - Meet the chip's ECC requirement
  - Maximize ECC strength if NAND_ECC_MAXIMIZE flag is set

The logic can be generalized by factoring out common code.

This commit adds 3 helpers to the NAND framework:
nand_check_ecc_caps - Check if preset step_size and strength are valid
nand_match_ecc_req - Match the chip's requirement
nand_maximize_ecc - Maximize the ECC strength

To use the helpers above, a driver needs to provide:
  - Data array of supported ECC step size and strength
  - A hook that calculates ECC bytes from the combination of
    step_size and strength.

By using those helpers, code duplication among drivers will be
reduced.

Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
  • Loading branch information
Masahiro Yamada authored and Boris Brezillon committed Jun 10, 2017
1 parent df8b970 commit 2c8f8af
Show file tree
Hide file tree
Showing 2 changed files with 253 additions and 0 deletions.
220 changes: 220 additions & 0 deletions drivers/mtd/nand/nand_base.c
Original file line number Diff line number Diff line change
Expand Up @@ -4523,6 +4523,226 @@ static int nand_set_ecc_soft_ops(struct mtd_info *mtd)
}
}

/**
* nand_check_ecc_caps - check the sanity of preset ECC settings
* @chip: nand chip info structure
* @caps: ECC caps info structure
* @oobavail: OOB size that the ECC engine can use
*
* When ECC step size and strength are already set, check if they are supported
* by the controller and the calculated ECC bytes fit within the chip's OOB.
* On success, the calculated ECC bytes is set.
*/
int nand_check_ecc_caps(struct nand_chip *chip,
const struct nand_ecc_caps *caps, int oobavail)
{
struct mtd_info *mtd = nand_to_mtd(chip);
const struct nand_ecc_step_info *stepinfo;
int preset_step = chip->ecc.size;
int preset_strength = chip->ecc.strength;
int nsteps, ecc_bytes;
int i, j;

if (WARN_ON(oobavail < 0))
return -EINVAL;

if (!preset_step || !preset_strength)
return -ENODATA;

nsteps = mtd->writesize / preset_step;

for (i = 0; i < caps->nstepinfos; i++) {
stepinfo = &caps->stepinfos[i];

if (stepinfo->stepsize != preset_step)
continue;

for (j = 0; j < stepinfo->nstrengths; j++) {
if (stepinfo->strengths[j] != preset_strength)
continue;

ecc_bytes = caps->calc_ecc_bytes(preset_step,
preset_strength);
if (WARN_ON_ONCE(ecc_bytes < 0))
return ecc_bytes;

if (ecc_bytes * nsteps > oobavail) {
pr_err("ECC (step, strength) = (%d, %d) does not fit in OOB",
preset_step, preset_strength);
return -ENOSPC;
}

chip->ecc.bytes = ecc_bytes;

return 0;
}
}

pr_err("ECC (step, strength) = (%d, %d) not supported on this controller",
preset_step, preset_strength);

return -ENOTSUPP;
}
EXPORT_SYMBOL_GPL(nand_check_ecc_caps);

/**
* nand_match_ecc_req - meet the chip's requirement with least ECC bytes
* @chip: nand chip info structure
* @caps: ECC engine caps info structure
* @oobavail: OOB size that the ECC engine can use
*
* If a chip's ECC requirement is provided, try to meet it with the least
* number of ECC bytes (i.e. with the largest number of OOB-free bytes).
* On success, the chosen ECC settings are set.
*/
int nand_match_ecc_req(struct nand_chip *chip,
const struct nand_ecc_caps *caps, int oobavail)
{
struct mtd_info *mtd = nand_to_mtd(chip);
const struct nand_ecc_step_info *stepinfo;
int req_step = chip->ecc_step_ds;
int req_strength = chip->ecc_strength_ds;
int req_corr, step_size, strength, nsteps, ecc_bytes, ecc_bytes_total;
int best_step, best_strength, best_ecc_bytes;
int best_ecc_bytes_total = INT_MAX;
int i, j;

if (WARN_ON(oobavail < 0))
return -EINVAL;

/* No information provided by the NAND chip */
if (!req_step || !req_strength)
return -ENOTSUPP;

/* number of correctable bits the chip requires in a page */
req_corr = mtd->writesize / req_step * req_strength;

for (i = 0; i < caps->nstepinfos; i++) {
stepinfo = &caps->stepinfos[i];
step_size = stepinfo->stepsize;

for (j = 0; j < stepinfo->nstrengths; j++) {
strength = stepinfo->strengths[j];

/*
* If both step size and strength are smaller than the
* chip's requirement, it is not easy to compare the
* resulted reliability.
*/
if (step_size < req_step && strength < req_strength)
continue;

if (mtd->writesize % step_size)
continue;

nsteps = mtd->writesize / step_size;

ecc_bytes = caps->calc_ecc_bytes(step_size, strength);
if (WARN_ON_ONCE(ecc_bytes < 0))
continue;
ecc_bytes_total = ecc_bytes * nsteps;

if (ecc_bytes_total > oobavail ||
strength * nsteps < req_corr)
continue;

/*
* We assume the best is to meet the chip's requrement
* with the least number of ECC bytes.
*/
if (ecc_bytes_total < best_ecc_bytes_total) {
best_ecc_bytes_total = ecc_bytes_total;
best_step = step_size;
best_strength = strength;
best_ecc_bytes = ecc_bytes;
}
}
}

if (best_ecc_bytes_total == INT_MAX)
return -ENOTSUPP;

chip->ecc.size = best_step;
chip->ecc.strength = best_strength;
chip->ecc.bytes = best_ecc_bytes;

return 0;
}
EXPORT_SYMBOL_GPL(nand_match_ecc_req);

/**
* nand_maximize_ecc - choose the max ECC strength available
* @chip: nand chip info structure
* @caps: ECC engine caps info structure
* @oobavail: OOB size that the ECC engine can use
*
* Choose the max ECC strength that is supported on the controller, and can fit
* within the chip's OOB. On success, the chosen ECC settings are set.
*/
int nand_maximize_ecc(struct nand_chip *chip,
const struct nand_ecc_caps *caps, int oobavail)
{
struct mtd_info *mtd = nand_to_mtd(chip);
const struct nand_ecc_step_info *stepinfo;
int step_size, strength, nsteps, ecc_bytes, corr;
int best_corr = 0;
int best_step = 0;
int best_strength, best_ecc_bytes;
int i, j;

if (WARN_ON(oobavail < 0))
return -EINVAL;

for (i = 0; i < caps->nstepinfos; i++) {
stepinfo = &caps->stepinfos[i];
step_size = stepinfo->stepsize;

/* If chip->ecc.size is already set, respect it */
if (chip->ecc.size && step_size != chip->ecc.size)
continue;

for (j = 0; j < stepinfo->nstrengths; j++) {
strength = stepinfo->strengths[j];

if (mtd->writesize % step_size)
continue;

nsteps = mtd->writesize / step_size;

ecc_bytes = caps->calc_ecc_bytes(step_size, strength);
if (WARN_ON_ONCE(ecc_bytes < 0))
continue;

if (ecc_bytes * nsteps > oobavail)
continue;

corr = strength * nsteps;

/*
* If the number of correctable bits is the same,
* bigger step_size has more reliability.
*/
if (corr > best_corr ||
(corr == best_corr && step_size > best_step)) {
best_corr = corr;
best_step = step_size;
best_strength = strength;
best_ecc_bytes = ecc_bytes;
}
}
}

if (!best_corr)
return -ENOTSUPP;

chip->ecc.size = best_step;
chip->ecc.strength = best_strength;
chip->ecc.bytes = best_ecc_bytes;

return 0;
}
EXPORT_SYMBOL_GPL(nand_maximize_ecc);

/*
* Check if the chip configuration meet the datasheet requirements.
Expand Down
33 changes: 33 additions & 0 deletions include/linux/mtd/nand.h
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,30 @@ static inline void nand_hw_control_init(struct nand_hw_control *nfc)
init_waitqueue_head(&nfc->wq);
}

/**
* struct nand_ecc_step_info - ECC step information of ECC engine
* @stepsize: data bytes per ECC step
* @strengths: array of supported strengths
* @nstrengths: number of supported strengths
*/
struct nand_ecc_step_info {
int stepsize;
const int *strengths;
int nstrengths;
};

/**
* struct nand_ecc_caps - capability of ECC engine
* @stepinfos: array of ECC step information
* @nstepinfos: number of ECC step information
* @calc_ecc_bytes: driver's hook to calculate ECC bytes per step
*/
struct nand_ecc_caps {
const struct nand_ecc_step_info *stepinfos;
int nstepinfos;
int (*calc_ecc_bytes)(int step_size, int strength);
};

/**
* struct nand_ecc_ctrl - Control structure for ECC
* @mode: ECC mode
Expand Down Expand Up @@ -1246,6 +1270,15 @@ int nand_check_erased_ecc_chunk(void *data, int datalen,
void *extraoob, int extraooblen,
int threshold);

int nand_check_ecc_caps(struct nand_chip *chip,
const struct nand_ecc_caps *caps, int oobavail);

int nand_match_ecc_req(struct nand_chip *chip,
const struct nand_ecc_caps *caps, int oobavail);

int nand_maximize_ecc(struct nand_chip *chip,
const struct nand_ecc_caps *caps, int oobavail);

/* Default write_oob implementation */
int nand_write_oob_std(struct mtd_info *mtd, struct nand_chip *chip, int page);

Expand Down

0 comments on commit 2c8f8af

Please sign in to comment.