diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 390782986a47..75515ad276e7 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -265,6 +265,7 @@ struct sunxi_nfc_timings {
* @clk_rate: clk_rate required for this NAND chip
* @timing_cfg: TIMING_CFG register value for this NAND chip
* @timing_ctl: TIMING_CTL register value for this NAND chip
+ * @randomized_oob: use the randomized normal-page OOB format
* @nsels: number of CS lines required by the NAND chip
* @sels: array of CS lines descriptions
* @user_data_bytes: array of user data lengths for all ECC steps@@ -277,6 +278,7 @@ struct sunxi_nand_chip {
u32 timing_cfg;
u32 timing_ctl;
u8 *user_data_bytes;
+ bool randomized_oob;
int nsels;
struct sunxi_nand_chip_sel sels[] __counted_by(nsels);
};@@ -328,6 +330,7 @@ struct sunxi_nfc_mdma_desc {
* @nuser_data_tab: Size of @user_data_len_tab
* @sram_size: Size of the NAND controller SRAM
* @timings: Controller timing characteristics
+ * @spare_is_erased: Vendor erased-page check on the physical spare prefix
*/
struct sunxi_nfc_caps {
bool has_mdma;@@ -356,6 +359,7 @@ struct sunxi_nfc_caps {
unsigned int max_ecc_steps;
int sram_size;
const struct sunxi_nfc_timings *timings;
+ bool (*spare_is_erased)(struct nand_chip *nand, const u8 *spare, int page);
};
/**@@ -849,12 +853,18 @@ static void sunxi_nfc_randomizer_disable(struct nand_chip *nand)
nfc->regs + NFC_REG_ECC_CTL);
}
-static void sunxi_nfc_randomize_bbm(struct nand_chip *nand, int page, u8 *bbm)
+static void sunxi_nfc_randomize_buf(u16 state, u8 *buf, unsigned int len)
{
- u16 state = sunxi_nfc_randomizer_state(nand, page, true);
+ while (len--) {
+ *buf++ ^= state;
+ state = sunxi_nfc_randomizer_step(state, 8);
+ }
+}
- bbm[0] ^= state;
- bbm[1] ^= sunxi_nfc_randomizer_step(state, 8);
+static void sunxi_nfc_randomize_bbm(struct nand_chip *nand, int page, u8 *bbm)
+{
+ sunxi_nfc_randomize_buf(sunxi_nfc_randomizer_state(nand, page, true),
+ bbm, 2);
}
static int sunxi_nfc_randomizer_write_buf(struct nand_chip *nand,@@ -962,7 +972,8 @@ static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob,
}
/* Undo hardware de-randomization for a plain on-flash BBM. */
- if (bbm && (nand->options & NAND_NEED_SCRAMBLING))
+ if (bbm && (nand->options & NAND_NEED_SCRAMBLING) &&
+ !sunxi_nand->randomized_oob)
sunxi_nfc_randomize_bbm(nand, page, oob);
}
@@ -1023,7 +1034,8 @@ static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
u8 user_data[SUNXI_NFC_MAX_USER_DATA_SZ] = {};
/* Pre-randomize the BBM so the hardware writes it plain on flash. */
- if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) {
+ if (bbm && (nand->options & NAND_NEED_SCRAMBLING) &&
+ !sunxi_nand->randomized_oob) {
memcpy(user_data, oob, user_data_sz);
sunxi_nfc_randomize_bbm(nand, page, user_data);
oob = user_data;@@ -1091,6 +1103,37 @@ static int sunxi_nfc_hw_ecc_read_error(struct nand_chip *nand,
return ret >= 0;
}
+/* Accumulate a whole randomized-OOB page before classifying its spare data. */
+struct sunxi_nfc_ecc_status {
+ u32 error_steps;
+ u32 zero_steps;
+ unsigned int corrected;
+ unsigned int max_bitflips;
+};
+
+static void sunxi_nfc_hw_ecc_record_status(struct nand_chip *nand,
+ struct sunxi_nfc_ecc_status *result,
+ int logical_step, int hw_step, u32 status,
+ u32 pattern_found)
+{
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ u32 count;
+
+ if ((pattern_found & BIT(hw_step)) &&
+ !(readl(nfc->regs + NFC_REG_PAT_ID(nfc)) & BIT(hw_step)))
+ result->zero_steps |= BIT(logical_step);
+
+ if (status & NFC_ECC_ERR(hw_step)) {
+ result->error_steps |= BIT(logical_step);
+ return;
+ }
+
+ count = readl(nfc->regs + NFC_REG_ECC_ERR_CNT(nfc, hw_step));
+ count = NFC_ECC_ERR_CNT(hw_step, count);
+ result->corrected += count;
+ result->max_bitflips = max(result->max_bitflips, count);
+}
+
static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
int hw_step, u32 status, u32 pattern_found,
unsigned int user_data_sz, bool *erased)@@ -1133,7 +1176,8 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
u8 *oob, int oob_off,
int *cur_off,
unsigned int *max_bitflips,
- int logical_step, bool oob_required, int page)
+ int logical_step, bool oob_required, int page,
+ struct sunxi_nfc_ecc_status *result)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);@@ -1182,6 +1226,16 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found);
pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found);
+ if (sunxi_nand->randomized_oob) {
+ sunxi_nfc_hw_ecc_record_status(nand, result, logical_step, hw_step,
+ readl(nfc->regs + NFC_REG_ECC_ST),
+ pattern_found);
+ memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, ecc->size);
+ sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, hw_step, bbm,
+ page, user_data_sz);
+ return 0;
+ }
+
bitflips = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL,
hw_step, readl(nfc->regs + NFC_REG_ECC_ST),
pattern_found, user_data_sz, &erased);@@ -1283,6 +1337,153 @@ static int sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand,
return 0;
}
+static bool sun4i_a10_nfc_spare_is_erased(struct nand_chip *nand,
+ const u8 *spare, int page)
+{
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ unsigned int block_page = page % mtd_div_by_ws(mtd->erasesize, mtd);
+
+ /*
+ * The older vendor spare scans recognize exact four-byte erased
+ * signatures on page zero and pages 127 modulo 128. With their
+ * 1 KiB ECC steps these correspond to four physical 0xff bytes.
+ * Other pages require all eight spare bytes to be 0xff.
+ */
+ if (nand->ecc.size == 1024 &&
+ (!block_page || block_page % 128 == 127) &&
+ !memchr_inv(spare, 0xff, USER_DATA_SZ))
+ return true;
+
+ return !memchr_inv(spare, 0xff, 8);
+}
+
+static bool sun50i_h616_nfc_spare_is_erased(struct nand_chip *nand,
+ const u8 *spare, int page)
+{
+ unsigned int erased = 0;
+ int i;
+
+ /* Byte zero and at least nine of ten bytes must be 0xff. */
+ for (i = 0; i < 10; i++)
+ erased += spare[i] == 0xff;
+
+ return spare[0] == 0xff && erased >= 9;
+}
+
+static bool sunxi_nfc_hw_ecc_spare_is_erased(struct nand_chip *nand, int page)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct nand_ecc_ctrl *ecc = &nand->ecc;
+ u16 state = sunxi_nfc_randomizer_state(nand, page, true);
+ u8 spare[10];
+ unsigned int len, pos = 0;
+ int i, off;
+
+ /*
+ * Reconstruct the physical spare prefix from the hardware's decoded
+ * user data. Each ECC step restarts the OOB randomizer; H6/H616 pack
+ * the entire prefix in step zero. Pad unavailable bytes with 0xff.
+ */
+ memset(spare, 0xff, sizeof(spare));
+ for (i = 0; i < ecc->steps && pos < sizeof(spare); i++) {
+ len = min_t(unsigned int, sunxi_nfc_user_data_sz(sunxi_nand, i),
+ sizeof(spare) - pos);
+ off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
+ memcpy(spare + pos, nand->oob_poi + off, len);
+ sunxi_nfc_randomize_buf(state, spare + pos, len);
+ pos += len;
+ }
+
+ return nfc->caps->spare_is_erased(nand, spare, page);
+}
+
+static int sunxi_nfc_hw_ecc_read_unprotected_oob(struct nand_chip *nand,
+ bool dma, int page)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ struct nand_ecc_ctrl *ecc = &nand->ecc;
+ unsigned int len, off;
+ u16 state;
+ int ret, i;
+
+ for (i = 0; i < ecc->steps; i++) {
+ len = sunxi_nfc_user_data_sz(sunxi_nand, i);
+ off = sunxi_get_ecc_offset(sunxi_nand, ecc, i);
+ ret = nand_change_read_column_op(nand, mtd->writesize + off,
+ nand->oob_poi + off,
+ ecc->bytes, false);
+ if (ret)
+ return ret;
+ /* Preserve each path's normal representation of ECC bytes. */
+ if (!dma) {
+ state = sunxi_nfc_randomizer_state(nand, page, true);
+ state = sunxi_nfc_randomizer_step(state, len * 8 + 15);
+ sunxi_nfc_randomize_buf(state, nand->oob_poi + off,
+ ecc->bytes);
+ }
+ }
+
+ off = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps);
+ len = mtd->oobsize - off;
+ if (len) {
+ ret = nand_change_read_column_op(nand, mtd->writesize + off,
+ nand->oob_poi + off, len, false);
+ if (ret)
+ return ret;
+ /* The unprotected tail uses the page seed and its 15-bit advance. */
+ state = sunxi_nfc_randomizer_state(nand, page, false);
+ state = sunxi_nfc_randomizer_step(state, 15);
+ sunxi_nfc_randomize_buf(state, nand->oob_poi + off, len);
+ }
+
+ return 0;
+}
+
+static int
+sunxi_nfc_hw_ecc_finish_randomized_read(struct nand_chip *nand, u8 *buf,
+ struct sunxi_nfc_ecc_status *result,
+ bool oob_required, bool dma, int page)
+{
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ struct nand_ecc_ctrl *ecc = &nand->ecc;
+ int ret;
+
+ /*
+ * The vendor treats an all-zero physical page as bad, even without
+ * ECC errors. This takes precedence over the spare-byte heuristic.
+ */
+ if (result->zero_steps == GENMASK(ecc->steps - 1, 0)) {
+ memset(buf, 0, mtd->writesize);
+ memset(nand->oob_poi, 0, mtd->oobsize);
+ mtd->ecc_stats.failed += ecc->steps;
+ return 0;
+ }
+
+ if (result->error_steps && sunxi_nfc_hw_ecc_spare_is_erased(nand, page)) {
+ memset(buf, 0xff, mtd->writesize);
+ memset(nand->oob_poi, 0xff, mtd->oobsize);
+ return 0;
+ }
+
+ /*
+ * Keep the original decoded main and protected OOB bytes on ECC failure.
+ * BBT pattern scans inspect them even when an ECC error is reported.
+ * Read the remaining OOB only when requested, not to classify the page.
+ */
+ if (oob_required) {
+ ret = sunxi_nfc_hw_ecc_read_unprotected_oob(nand, dma, page);
+ if (ret)
+ return ret;
+ }
+
+ mtd->ecc_stats.corrected += result->corrected;
+ mtd->ecc_stats.failed += hweight32(result->error_steps);
+
+ return result->max_bitflips;
+}
+
static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf,
int oob_required, int page,
int nchunks)@@ -1292,6 +1493,7 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
+ struct sunxi_nfc_ecc_status result = {};
unsigned int corrected = mtd->ecc_stats.corrected;
unsigned int failed = mtd->ecc_stats.failed;
unsigned int max_bitflips = 0;@@ -1354,6 +1556,14 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
bool erased;
int bitflips;
+ if (sunxi_nand->randomized_oob) {
+ sunxi_nfc_hw_ecc_record_status(nand, &result, i, i, status,
+ pattern_found);
+ sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, !i,
+ page, user_data_sz);
+ continue;
+ }
+
bitflips = sunxi_nfc_hw_ecc_correct(nand, randomized ? data : NULL,
oob_required ? oob : NULL,
i, status, pattern_found,@@ -1381,6 +1591,10 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
sunxi_nfc_hw_ecc_update_stats(nand, &max_bitflips, bitflips);
}
+ if (sunxi_nand->randomized_oob)
+ return sunxi_nfc_hw_ecc_finish_randomized_read(nand, buf, &result,
+ oob_required, true, page);
+
if (status & NFC_ECC_ERR_MSK(nfc)) {
for (i = 0; i < nchunks; i++) {
int data_off = i * ecc->size;@@ -1512,6 +1726,7 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
+ struct sunxi_nfc_ecc_status result = {};
unsigned int max_bitflips = 0;
int ret, i, cur_off = 0;
bool erased_chunk_found = false;@@ -1534,13 +1749,19 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off, oob,
oob_off + mtd->writesize,
&cur_off, &max_bitflips,
- i, oob_required, page);
+ i, oob_required, page, &result);
if (ret < 0)
goto out;
else if (ret)
erased_chunk_found = true;
}
+ if (sunxi_nand->randomized_oob) {
+ ret = sunxi_nfc_hw_ecc_finish_randomized_read(nand, buf, &result,
+ oob_required, false, page);
+ goto out;
+ }
+
if (oob_required) {
ret = sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi, &cur_off,
!erased_chunk_found, page);@@ -1586,6 +1807,10 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
int ret, i, cur_off = 0;
unsigned int max_bitflips = 0;
+ /* The vendor spare test and all-zero detection classify a whole page. */
+ if (sunxi_nand->randomized_oob)
+ return sunxi_nfc_hw_ecc_read_page(nand, bufpoi, false, page);
+
sunxi_nfc_select_chip(nand, nand->cur_cs);
ret = nand_read_page_op(nand, page, 0, NULL, 0);
@@ -1606,7 +1831,7 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
oob,
oob_off + mtd->writesize,
&cur_off, &max_bitflips, i,
- false, page);
+ false, page, NULL);
if (ret < 0)
goto out;
}
@@ -1625,6 +1850,9 @@ static int sunxi_nfc_hw_ecc_read_subpage_dma(struct nand_chip *nand,
int nchunks = DIV_ROUND_UP(data_offs + readlen, nand->ecc.size);
int ret;
+ if (to_sunxi_nand(nand)->randomized_oob)
+ return sunxi_nfc_hw_ecc_read_page_dma(nand, buf, false, page);
+
sunxi_nfc_select_chip(nand, nand->cur_cs);
ret = nand_read_page_op(nand, page, 0, NULL, 0);
@@ -2237,6 +2465,10 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
ecc->strength *= 2;
}
+ /* This format requires an ECC step that fits in the page. */
+ if (sunxi_nand->randomized_oob && mtd->writesize < ecc->size)
+ return -EINVAL;
+
/* Add ECC info retrieval from DT */
for (ecc_mode = 0; ecc_mode < nfc->caps->nstrengths; ecc_mode++) {
if (ecc->strength <= strengths[ecc_mode]) {@@ -2305,6 +2537,10 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
sunxi_nand->ecc.ecc_ctl = NFC_ECC_MODE(nfc, ecc_mode) | NFC_ECC_EXCEPTION |
NFC_ECC_PIPELINE | NFC_ECC_EN;
+ /* Run ECC on uniform data too, so the randomized spare bytes are decoded. */
+ if (sunxi_nand->randomized_oob)
+ sunxi_nand->ecc.ecc_ctl &= ~NFC_ECC_EXCEPTION;
+
if (ecc->size == 512) {
if (nfc->caps->has_ecc_block_512) {
sunxi_nand->ecc.ecc_ctl |= NFC_ECC_BLOCK_512;@@ -2319,6 +2555,8 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
static int sunxi_nand_attach_chip(struct nand_chip *nand)
{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
const struct nand_ecc_props *requirements =
nanddev_get_ecc_requirements(&nand->base);
struct nand_ecc_ctrl *ecc = &nand->ecc;@@ -2328,6 +2566,14 @@ static int sunxi_nand_attach_chip(struct nand_chip *nand)
if (nand->bbt_options & NAND_BBT_USE_FLASH)
nand->bbt_options |= NAND_BBT_NO_OOB;
+ if (sunxi_nand->randomized_oob &&
+ ecc->engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST)
+ return dev_err_probe(nfc->dev, -EINVAL,
+ "Allwinner OOB format requires controller ECC\n");
+
+ if (sunxi_nand->randomized_oob)
+ nand->options |= NAND_NEED_SCRAMBLING;
+
if (nand->options & NAND_NEED_SCRAMBLING)
nand->options |= NAND_NO_SUBPAGE_WRITE;
@@ -2557,6 +2803,9 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
if (!sunxi_nand)
return -ENOMEM;
+ sunxi_nand->randomized_oob =
+ of_property_read_bool(np, "allwinner,randomized-oob");
+
sunxi_nand->nsels = nsels;
for (i = 0; i < nsels; i++) {@@ -2833,6 +3082,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_a10_caps = {
.max_ecc_steps = 16,
.sram_size = 1024,
.timings = &sun4i_a10_nfc_timings,
+ .spare_is_erased = sun4i_a10_nfc_spare_is_erased,
};
static const struct sunxi_nfc_caps sunxi_nfc_a23_caps = {@@ -2856,6 +3106,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_a23_caps = {
.max_ecc_steps = 16,
.sram_size = 1024,
.timings = &sun4i_a10_nfc_timings,
+ .spare_is_erased = sun4i_a10_nfc_spare_is_erased,
};
static const struct sunxi_nfc_caps sunxi_nfc_h616_caps = {@@ -2882,6 +3133,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_h616_caps = {
.max_ecc_steps = 32,
.sram_size = 8192,
.timings = &sun50i_h616_nfc_timings,
+ .spare_is_erased = sun50i_h616_nfc_spare_is_erased,
};
static const struct of_device_id sunxi_nfc_ids[] = {
--
2.53.0