// SPDX-License-Identifier: GPL-2.0 /* * Copyright 2017 ATMEL * Copyright 2017 Free Electrons * * Author: Boris Brezillon <boris.brezillon@free-electrons.com> * * Derived from the atmel_nand.c driver which contained the following * copyrights: * * Copyright 2003 Rick Bronson * * Derived from drivers/mtd/nand/autcpu12.c (removed in v3.8) * Copyright 2001 Thomas Gleixner (gleixner@autronix.de) * * Derived from drivers/mtd/spia.c (removed in v3.8) * Copyright 2000 Steven J. Hill (sjhill@cotw.com) * * Add Hardware ECC support for AT91SAM9260 / AT91SAM9263 * Richard Genoud (richard.genoud@gmail.com), Adeneo Copyright 2007 * * Derived from Das U-Boot source code * (u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c) * Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas * * Add Programmable Multibit ECC support for various AT91 SoC * Copyright 2012 ATMEL, Hong Xu * * Add Nand Flash Controller support for SAMA5 SoC * Copyright 2013 ATMEL, Josh Wu (josh.wu@atmel.com) * * The PMECC is an hardware assisted BCH engine, which means part of the * ECC algorithm is left to the software. The hardware/software repartition * is explained in the "PMECC Controller Functional Description" chapter in * Atmel datasheets, and some of the functions in this file are directly * implementing the algorithms described in the "Software Implementation" * sub-section. * * TODO: it seems that the software BCH implementation in lib/bch.c is already * providing some of the logic we are implementing here. It would be smart * to expose the needed lib/bch.c helpers/functions and re-use them here.
*/
staticinlineint deg(unsignedint poly)
{ /* polynomial degree is the most-significant bit index */ return fls(poly) - 1;
}
staticint atmel_pmecc_build_gf_tables(int mm, unsignedint poly, struct atmel_pmecc_gf_tables *gf_tables)
{ unsignedint i, x = 1; constunsignedint k = BIT(deg(poly)); unsignedint nn = BIT(mm) - 1;
/* primitive polynomial must be of degree m */ if (k != (1u << mm)) return -EINVAL;
for (i = 0; i < nn; i++) {
gf_tables->alpha_to[i] = x;
gf_tables->index_of[x] = i; if (i && (x == 1)) /* polynomial is not primitive (a^i=1 with 0<i<2^m-1) */ return -EINVAL;
x <<= 1; if (x & k)
x ^= poly;
}
gf_tables->alpha_to[nn] = 1;
gf_tables->index_of[0] = 0;
staticvoid atmel_pmecc_gen_syndrome(struct atmel_pmecc_user *user, int sector)
{ int strength = get_strength(user);
u32 value; int i;
/* Fill odd syndromes */ for (i = 0; i < strength; i++) {
value = readl_relaxed(user->pmecc->regs.base +
ATMEL_PMECC_REM(sector, i / 2)); if (i & 1)
value >>= 16;
user->partial_syn[(2 * i) + 1] = value;
}
}
staticvoid atmel_pmecc_substitute(struct atmel_pmecc_user *user)
{ int degree = get_sectorsize(user) == 512 ? 13 : 14; int cw_len = BIT(degree) - 1; int strength = get_strength(user);
s16 *alpha_to = user->gf_tables->alpha_to;
s16 *index_of = user->gf_tables->index_of;
s16 *partial_syn = user->partial_syn;
s16 *si; int i, j;
/* * si[] is a table that holds the current syndrome value, * an element of that table belongs to the field
*/
si = user->si;
/* index of largest delta */ int ro; int largest; int diff;
dmu_0_count = 0;
/* First Row */
/* Mu */
mu[0] = -1;
memset(smu, 0, sizeof(s16) * num);
smu[0] = 1;
/* discrepancy set to 1 */
dmu[0] = 1; /* polynom order set to 0 */
lmu[0] = 0;
delta[0] = (mu[0] * 2 - lmu[0]) >> 1;
/* Second Row */
/* Mu */
mu[1] = 0; /* Sigma(x) set to 1 */
memset(&smu[num], 0, sizeof(s16) * num);
smu[num] = 1;
/* discrepancy set to S1 */
dmu[1] = si[1];
/* polynom order set to 0 */
lmu[1] = 0;
delta[1] = (mu[1] * 2 - lmu[1]) >> 1;
/* Init the Sigma(x) last row */
memset(&smu[(strength + 1) * num], 0, sizeof(s16) * num);
for (i = 1; i <= strength; i++) {
mu[i + 1] = i << 1; /* Begin Computing Sigma (Mu+1) and L(mu) */ /* check if discrepancy is set to 0 */ if (dmu[i] == 0) {
dmu_0_count++;
if (dmu_0_count == tmp) { for (j = 0; j <= (lmu[i] >> 1) + 1; j++)
smu[(strength + 1) * num + j] =
smu[i * num + j];
lmu[strength + 1] = lmu[i]; return;
}
/* copy polynom */ for (j = 0; j <= lmu[i] >> 1; j++)
smu[(i + 1) * num + j] = smu[i * num + j];
/* copy previous polynom order to the next */
lmu[i + 1] = lmu[i];
} else {
ro = 0;
largest = -1; /* find largest delta with dmu != 0 */ for (j = 0; j < i; j++) { if ((dmu[j]) && (delta[j] > largest)) {
largest = delta[j];
ro = j;
}
}
/* compute difference */
diff = (mu[i] - mu[ro]);
/* Compute degree of the new smu polynomial */ if ((lmu[i] >> 1) > ((lmu[ro] >> 1) + diff))
lmu[i + 1] = lmu[i]; else
lmu[i + 1] = ((lmu[ro] >> 1) + diff) * 2;
/* Init smu[i+1] with 0 */ for (k = 0; k < num; k++)
smu[(i + 1) * num + k] = 0;
/* Compute smu[i+1] */ for (k = 0; k <= lmu[ro] >> 1; k++) {
s16 a, b, c;
if (!(smu[ro * num + k] && dmu[i])) continue;
a = index_of[dmu[i]];
b = index_of[dmu[ro]];
c = index_of[smu[ro * num + k]];
tmp = a + (cw_len - b) + c;
a = alpha_to[tmp % cw_len];
smu[(i + 1) * num + (k + diff)] = a;
}
for (k = 0; k <= lmu[i] >> 1; k++)
smu[(i + 1) * num + k] ^= smu[i * num + k];
}
/* End Computing Sigma (Mu+1) and L(mu) */ /* In either case compute delta */
delta[i + 1] = (mu[i + 1] * 2 - lmu[i + 1]) >> 1;
/* Do not compute discrepancy for the last iteration */ if (i >= strength) continue;
for (k = 0; k <= (lmu[i + 1] >> 1); k++) {
tmp = 2 * (i - 1); if (k == 0) {
dmu[i + 1] = si[tmp + 3];
} elseif (smu[(i + 1) * num + k] && si[tmp + 3 - k]) {
s16 a, b, c;
a = index_of[smu[(i + 1) * num + k]];
b = si[2 * (i - 1) + 3 - k];
c = index_of[b];
tmp = a + c;
tmp %= cw_len;
dmu[i + 1] = alpha_to[tmp] ^ dmu[i + 1];
}
}
}
}
staticint atmel_pmecc_err_location(struct atmel_pmecc_user *user)
{ int sector_size = get_sectorsize(user); int degree = sector_size == 512 ? 13 : 14; struct atmel_pmecc *pmecc = user->pmecc; int strength = get_strength(user); int ret, roots_nbr, i, err_nbr = 0; int num = (2 * strength) + 1;
s16 *smu = user->smu;
u32 val;
ret = readl_relaxed_poll_timeout(pmecc->regs.errloc +
ATMEL_PMERRLOC_ELISR,
val, val & PMERRLOC_CALC_DONE, 0,
PMECC_MAX_TIMEOUT_MS * 1000); if (ret) {
dev_err(pmecc->dev, "PMECC: Timeout to calculate error location.\n"); return ret;
}
roots_nbr = (val & PMERRLOC_ERR_NUM_MASK) >> 8; /* Number of roots == degree of smu hence <= cap */ if (roots_nbr == user->lmu[strength + 1] >> 1) return err_nbr - 1;
/* * Number of roots does not match the degree of smu * unable to correct error.
*/ return -EBADMSG;
}
int atmel_pmecc_correct_sector(struct atmel_pmecc_user *user, int sector, void *data, void *ecc)
{ struct atmel_pmecc *pmecc = user->pmecc; int sectorsize = get_sectorsize(user); int eccbytes = user->eccbytes; int i, nerrors;
np = of_parse_phandle(userdev->of_node, "ecc-engine", 0); if (np) {
pmecc = atmel_pmecc_get_by_node(userdev, np);
of_node_put(np);
} else { /* * Support old DT bindings: in this case the PMECC iomem * resources are directly defined in the user pdev at position * 1 and 2. Extract all relevant information from there.
*/ struct platform_device *pdev = to_platform_device(userdev); conststruct atmel_pmecc_caps *caps; conststruct of_device_id *match;
/* No PMECC engine available. */ if (!of_property_read_bool(userdev->of_node, "atmel,has-pmecc")) return NULL;
caps = &at91sam9g45_caps;
/* Find the caps associated to the NAND dev node. */
match = of_match_node(atmel_pmecc_legacy_match,
userdev->of_node); if (match && match->data)
caps = match->data;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.