* [PATCH v7 00/16] mtd: rawnand: sunxi: fix transfers and support randomized OOB
@ 2026-10-15 1:33 James Hilliard
2026-10-15 1:33 ` [PATCH v7 01/16] mtd: rawnand: sunxi: centralize OOB handling James Hilliard
` (15 more replies)
0 siblings, 16 replies; 18+ messages in thread
From: James Hilliard @ 2026-10-15 1:33 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Maxime Ripard,
Richard Genoud, Boris Brezillon, Masahiro Yamada, Brian Norris,
linux-mtd, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-rt-devel, devicetree, James Hilliard, Conor Dooley
Patches 1-14 address the existing driver independently of the Allwinner
randomized-OOB format in patches 15-16. Please review and apply that prefix
regardless of whether the format extension is accepted. It preserves the
existing on-flash ECC geometry, bad-block-marker representation and raw
callbacks.
Patch 1 centralizes OOB handling without changing behavior. Patches 2-10
fix logical-step lengths, read and write error handling, completion reuse,
pattern-ID selection, duplicate program confirms, small-page read commands
and the H6/H616 user-data register limit. Patches 11-14 then reduce repeated
status reads, column changes, chip setup and length-register accesses.
The pattern-ID fix precedes status caching so correctness can be reviewed
before the register-access optimization.
Here, "user data" means ECC-protected metadata in OOB, not the page's main
data. PIO reuses hardware slot zero for each logical ECC step; DMA can
transfer several steps at once. The patches distinguish logical steps,
hardware slots and packed register offsets, and explain their triggering
conditions independently of the format work that prompted the review.
The IRQ fix retains timeout-only disable_irq() and explains both the
forced-threaded PREEMPT_RT race and the unbounded cleanup/no-priority-
inheritance tradeoff. The small-page fix is limited to read-side
repositioning within existing geometry restrictions. DMA batching preserves
the installed OOB layout rather than capping its allocation.
For devices provisioned with the Allwinner normal-page format, support is
required compatibility with existing flash contents, not a preference for
another layout. Its bad-block marker is randomized: applying the existing
plain-marker compensation can make normal OOB reads and bad-block scans
interpret good blocks as bad. Switching formats is not a transparent
kernel upgrade.
Patch 15 documents the opt-in binding; patch 16 implements the format
through separate OOB callbacks and an attach helper. Its geometry,
page-wide classification and contiguous parity reads stay together.
Without the property, existing format selection and callbacks are unchanged.
Assisted-by: Codex:gpt-6-astra
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
Changes in v7:
- Put a behavior-preserving OOB refactor first, the correctness fixes next,
register-access optimizations after them, and the binding and Allwinner
format last. Keep 16 patches with no external prerequisites.
- Keep logical-step length, read recovery, IRQ lifetime, write failures,
pattern IDs and DMA limits independently reviewable. Correct pattern-ID
selection before introducing per-transfer status caching.
- Update both DMA callers with the protected-OOB helper contract in the
logical-step length fix, before the later batching change.
- Isolate format policy behind its own callbacks and one opt-in attach
path. Fold packed H6/H616 geometry and contiguous parity reads into the
final patch, leaving existing format callbacks unchanged.
- Use scrambled_plain_bbm_oob for the existing scrambled profile and
allwinner_oob for the firmware profile. Keep shared randomizer helpers
generic and name the H6-generation spare predicate after H6.
- Clarify protected-OOB terminology, logical/hardware indices, transfer
failures and the limited small-page read scope. Explain the RT timeout
mechanism and disable_irq() tradeoff discussed with Miquel Raynal.
- Explain installed-format compatibility and that the spare check re-encodes
ECC-decoded protected OOB rather than rereading physical bytes.
- Drop the standalone OOB-helper renaming cleanup and explicit stable Cc
trailers; retain Fixes tags and Conor Dooley's binding Acked-by.
- Rebase onto current MTD nand/next.
- Link to v6: https://patch.msgid.link/20260914-submit-sunxi-nand-vendor-oob-layout-v1-v6-0-2fb084fe0751@gmail.com
Changes in v6:
- retain the bank-sized first DMA batch, but transfer later logical steps
individually to avoid PAGE_OP's absolute internally generated main columns
- keep ECC exception mode enabled for randomized reads on all supported
controllers, using the shared physical-pattern classifier
- classify uniform-pattern steps even when hardware suppresses their ECC
errors; skip their correction counters and preserve decoded buffers unless
the page-wide zero override or vendor spare predicate accepts the page
- Link to v5: https://patch.msgid.link/20260913-submit-sunxi-nand-vendor-oob-layout-v1-v5-0-7d711076a6f7@gmail.com
Changes in v5:
- add a separate small-page command-handling fix: use READ0/READ1/READOOB
with the page address for rereads and normal OOB access, and select PIO
instead of the large-page DMA sequencer
- keep physical reread errors visible, preserve the OOB cursor and retain
the existing ECC geometry requirements, including rejection of 512+16
with controller ECC
- group the small-page fix and the DMA register-bank fix with the opening
fixes, ahead of cleanups, bindings, format support and optimizations
- make the DMA register-bank fix independent of randomized-OOB support
and the later register-access optimizations
- Link to v4: https://patch.msgid.link/20260912-submit-sunxi-nand-vendor-oob-layout-v1-v4-0-4a64bed94229@gmail.com
Changes in v4:
- prepend an independent fix for completion reuse and IRQ timeout races:
initialize the completion once, finish IRQ register updates before
signalling success, and drain timed-out handlers before clearing their
interrupt state
- add a separate fix for aggregate protected-user-data register overflow
by splitting DMA transfers into bounded batches, retaining the existing
OOB layout, ECC offsets and DMA support
- distinguish logical page steps from batch-local hardware slots, retain
page-wide ECC accounting and avoid retrying partially transferred writes
- Link to v3: https://patch.msgid.link/20260909-submit-sunxi-nand-vendor-oob-layout-v1-v3-0-838cb0ba1547@gmail.com
Changes in v3:
- add a prerequisite fix for the logical OOB length used by PIO transfers
- clarify logical ECC steps versus hardware slots and share protected-OOB
register indexing
- select the controller-specific vendor spare-byte erased-page check from
the SoC capabilities, only in randomized-OOB mode and without rereading
the main data
- retain hardware-decoded data and protected OOB on other ECC failures
for bad-block and BBT pattern scans (reported by Miquel Raynal)
- retain a bad marker and ECC failure for all-zero physical pages, and
disable the ECC exception for the vendor format
- share page classification between PIO and DMA, reading complete pages
for randomized-format subpage requests
- propagate OOB read errors and defer randomized-format ECC accounting
until those reads have succeeded
- propagate read/program setup, column-change and buffer-transfer errors,
including extra OOB; stop failed writes and disable ECC and randomization
- discard partial DMA ECC statistics before retrying in PIO, and keep
correction counts separate from successful OOB-transfer status
- select the current hardware step's pattern ID instead of slot zero
- avoid a second program confirm after an OOB-only write
- avoid redundant column changes before writing extra OOB bytes
- reject oversized ECC steps in randomized-OOB mode before the core can
fall back to software ECC
- combine adjacent parity and trailing OOB reads in randomized-OOB mode
- remove duplicate chip setup immediately before core page commands
- program each packed DMA user-data length register once per operation,
and write PIO slot zero directly without read-modify-write
- reuse pattern IDs and packed error counters within a DMA read, while
refreshing the snapshot after every PIO ECC operation
- Link to v2: https://patch.msgid.link/20260904-submit-sunxi-nand-vendor-oob-layout-v1-v2-0-b12074f4aca7@gmail.com
Changes in v2:
- rebase on the current MTD nand/next branch
- retain the merged protected-OOB allocation, BBM reservation and
stack-buffer fixes
- clarify that randomization is part of the normal hardware-ECC page
format while MTD_OPS_RAW continues to expose physical bytes
- explain why a BSP-compatible BBM remains randomized in physical raw data
- document the decoded bad-block and flash-BBT access paths
- reject the firmware OOB format with software or disabled ECC
- document the BSP page-format compatibility contract and the
older-controller format audit
- Link to v1: https://patch.msgid.link/20260810-submit-sunxi-nand-vendor-oob-layout-v1-v1-0-463853a14ad9@gmail.com
---
James Hilliard (16):
mtd: rawnand: sunxi: centralize OOB handling
mtd: rawnand: sunxi: use the logical step's OOB length in PIO
mtd: rawnand: sunxi: propagate page-setup and erased-check errors
mtd: rawnand: sunxi: drain interrupts before reusing the completion
mtd: rawnand: sunxi: stop failed program operations and disable ECC
mtd: rawnand: sunxi: select the pattern ID for the current ECC step
mtd: rawnand: sunxi: propagate buffer and column transfer errors
mtd: rawnand: sunxi: avoid a second program confirm for OOB writes
mtd: rawnand: sunxi: use page reads to reposition small-page NAND
mtd: rawnand: sunxi: bound DMA batches by the user-data register bank
mtd: rawnand: sunxi: reuse ECC status within each transfer
mtd: rawnand: sunxi: avoid redundant column changes for extra OOB
mtd: rawnand: sunxi: avoid duplicate chip setup before page commands
mtd: rawnand: sunxi: reduce user-data length register accesses
dt-bindings: mtd: sunxi: Add randomized OOB flag
mtd: rawnand: sunxi: support the Allwinner randomized OOB format
.../bindings/mtd/allwinner,sun4i-a10-nand.yaml | 10 +
drivers/mtd/nand/raw/sunxi_nand.c | 1537 ++++++++++++++------
2 files changed, 1065 insertions(+), 482 deletions(-)
---
base-commit: 30daaf3e7897b4a8d9b72e5b0c86dd6e2a4d7d85
change-id: 20260810-submit-sunxi-nand-vendor-oob-layout-v1-e3114d10cc9c
Best regards,
--
James Hilliard <james.hilliard1@gmail.com>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v7 01/16] mtd: rawnand: sunxi: centralize OOB handling
2026-10-15 1:33 [PATCH v7 00/16] mtd: rawnand: sunxi: fix transfers and support randomized OOB James Hilliard
@ 2026-10-15 1:33 ` James Hilliard
2026-10-15 1:33 ` [PATCH v7 02/16] mtd: rawnand: sunxi: use the logical step's OOB length in PIO James Hilliard
` (14 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: James Hilliard @ 2026-10-15 1:33 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Maxime Ripard,
Richard Genoud, Boris Brezillon, Masahiro Yamada, Brian Norris,
linux-mtd, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-rt-devel, devicetree, James Hilliard
PIO and DMA interpret the same hardware-ECC results, retrieve protected
OOB bytes and check unreadable chunks for erased data in separate paths.
Keeping that policy in both transfer paths makes fixes easy to apply to
one path but miss in the other.
Share the processing through per-chip OOB operations selected at attach.
A read callback consumes one PIO step or a completed DMA batch. A finish
callback handles the OOB tail and returns the largest correction count.
Keep command sequencing, DMA ownership and randomizer control in the
transfer functions; carry the buffer, page, correction count and whether
any chunk was erased in a per-read context.
Move user-data reservation and layout setup behind the same operations.
The existing plain and scrambled-with-plain-BBM modes share geometry and
read handling. Only the scrambled mode transforms the first two user bytes
to compensate hardware randomization and keep the on-flash marker plain.
Preserve fixed and variable user-data allocation, including the older
controllers' extra two-byte reservation when maximizing ECC strength.
This preserves the existing register accesses, transfer ordering, OOB
length selection, error handling and ECC accounting. In particular,
retain DMA's two passes: consume corrected OOB before raw erased-page
rereads. PIO keeps its current parity representation and physical reread
behavior. OOB-length fixes, read-error handling and register-read caching
are separate changes.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 545 ++++++++++++++++++++------------------
1 file changed, 294 insertions(+), 251 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index f41feebe4257..27d6ae635e9c 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -256,12 +256,30 @@ struct sunxi_nfc_timings {
s32 tRHW[SUNXI_NFC_TIMING_STEPS];
};
+struct sunxi_nfc_ecc_read;
+
+/* OOB interpretation is independent of PIO/DMA command sequencing. */
+struct sunxi_nfc_oob_ops {
+ /* Reserve user data before maximizing ECC strength, then set its layout. */
+ unsigned int (*min_user_data)(struct nand_chip *nand, int nsteps);
+ int (*init_user_data)(struct nand_chip *nand, int nsteps);
+ /* Optional symmetric conversion of the on-flash bad-block marker. */
+ void (*transform_bbm)(struct nand_chip *nand, int page, u8 *bbm);
+ /* Consume one PIO step or a DMA batch; negative values are I/O errors. */
+ int (*read_batch)(struct nand_chip *nand, struct sunxi_nfc_ecc_read *read,
+ u32 status, u32 pattern_found, int first_step, int nsteps);
+ /* Read any remaining OOB and return the maximum correction count. */
+ int (*finish_read)(struct nand_chip *nand, struct sunxi_nfc_ecc_read *read,
+ int *cur_off);
+};
+
/**
* struct sunxi_nand_chip - stores NAND chip device related information
*
* @node: used to store NAND chips into a list
* @nand: base NAND chip structure
* @ecc: ECC controller structure
+ * @oob_ops: OOB geometry and ECC-result interpretation
* @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
@@ -273,6 +291,7 @@ struct sunxi_nand_chip {
struct list_head node;
struct nand_chip nand;
struct sunxi_nand_hw_ecc ecc;
+ const struct sunxi_nfc_oob_ops *oob_ops;
unsigned long clk_rate;
u32 timing_cfg;
u32 timing_ctl;
@@ -908,6 +927,7 @@ static u8 sunxi_nfc_user_data_sz(struct sunxi_nand_chip *sunxi_nand, int step)
return sunxi_nand->user_data_bytes[step];
}
+/* PIO uses register zero; DMA concatenates user data within each transfer. */
static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob,
int step, bool bbm, int page,
unsigned int user_data_sz)
@@ -946,8 +966,8 @@ static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob,
}
/* De-randomize the Bad Block Marker. */
- if (bbm && (nand->options & NAND_NEED_SCRAMBLING))
- sunxi_nfc_randomize_bbm(nand, page, oob);
+ if (bbm && sunxi_nand->oob_ops->transform_bbm)
+ sunxi_nand->oob_ops->transform_bbm(nand, page, oob);
}
/*
@@ -1007,9 +1027,9 @@ static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
u8 user_data[SUNXI_NFC_MAX_USER_DATA_SZ] = {};
/* Randomize the Bad Block Marker. */
- if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) {
+ if (bbm && sunxi_nand->oob_ops->transform_bbm) {
memcpy(user_data, oob, user_data_sz);
- sunxi_nfc_randomize_bbm(nand, page, user_data);
+ sunxi_nand->oob_ops->transform_bbm(nand, page, user_data);
oob = user_data;
}
@@ -1055,6 +1075,40 @@ static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand,
}
}
+/* Return whether the chunk is erased, and account for its ECC result. */
+static int sunxi_nfc_hw_ecc_read_error(struct nand_chip *nand,
+ u8 *data, int data_off,
+ u8 *oob, int oob_off,
+ unsigned int user_data_sz,
+ unsigned int *max_bitflips)
+{
+ struct nand_ecc_ctrl *ecc = &nand->ecc;
+ unsigned int oob_len = ecc->bytes + user_data_sz;
+ int ret;
+
+ /* Check the physical representation for bitflips in erased pages. */
+ if (nand->options & NAND_NEED_SCRAMBLING)
+ nand_change_read_column_op(nand, data_off, data, ecc->size, false);
+
+ nand_change_read_column_op(nand, oob_off, oob, oob_len, false);
+
+ ret = nand_check_erased_ecc_chunk(data, ecc->size, oob, oob_len, NULL, 0,
+ ecc->strength);
+
+ sunxi_nfc_hw_ecc_update_stats(nand, max_bitflips, ret);
+
+ return ret >= 0;
+}
+
+struct sunxi_nfc_ecc_read {
+ u8 *buf;
+ int page;
+ unsigned int max_bitflips;
+ bool oob_required;
+ bool dma;
+ bool has_erased_chunk;
+};
+
static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
int step, u32 status, u32 pattern_found,
bool *erased)
@@ -1094,98 +1148,6 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
return NFC_ECC_ERR_CNT(step, tmp);
}
-static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
- u8 *data, int data_off,
- u8 *oob, int oob_off,
- int *cur_off,
- unsigned int *max_bitflips,
- int step, bool oob_required, int page)
-{
- struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
- struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
- unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
- struct nand_ecc_ctrl *ecc = &nand->ecc;
- int raw_mode = 0;
- u32 pattern_found;
- bool bbm = !step;
- bool erased;
- int ret;
- /* From the controller point of view, we are at step 0 */
- const int nfc_step = 0;
-
- if (*cur_off != data_off)
- nand_change_read_column_op(nand, data_off, NULL, 0, false);
-
- sunxi_nfc_randomizer_read_buf(nand, NULL, ecc->size, false, page);
-
- if (data_off + ecc->size != oob_off)
- nand_change_read_column_op(nand, oob_off, NULL, 0, false);
-
- ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
- if (ret)
- return ret;
-
- sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step);
- sunxi_nfc_randomizer_config(nand, page, false);
- sunxi_nfc_randomizer_enable(nand);
- writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP,
- nfc->regs + NFC_REG_CMD);
-
- ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, false, 0);
- sunxi_nfc_randomizer_disable(nand);
- if (ret)
- return ret;
-
- *cur_off = oob_off + ecc->bytes + user_data_sz;
-
- pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found);
- pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found);
-
- ret = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL,
- nfc_step, readl(nfc->regs + NFC_REG_ECC_ST),
- pattern_found, &erased);
- if (erased)
- return 1;
-
- if (ret < 0) {
- /*
- * Re-read the data with the randomizer disabled to identify
- * bitflips in erased pages.
- */
- if (nand->options & NAND_NEED_SCRAMBLING)
- nand_change_read_column_op(nand, data_off, data,
- ecc->size, false);
- else
- memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE,
- ecc->size);
-
- nand_change_read_column_op(nand, oob_off, oob,
- ecc->bytes + user_data_sz, false);
-
- ret = nand_check_erased_ecc_chunk(data, ecc->size, oob,
- ecc->bytes + user_data_sz,
- NULL, 0, ecc->strength);
- if (ret >= 0)
- raw_mode = 1;
- } else {
- memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, ecc->size);
-
- if (oob_required) {
- nand_change_read_column_op(nand, oob_off, NULL, 0,
- false);
- sunxi_nfc_randomizer_read_buf(nand, oob, ecc->bytes + user_data_sz,
- true, page);
-
- sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, nfc_step,
- bbm, page, user_data_sz);
- }
- }
-
- sunxi_nfc_hw_ecc_update_stats(nand, max_bitflips, ret);
-
- return raw_mode;
-}
-
/*
* Returns the offset of the OOB for each step.
* (it includes the user data before the ECC data.)
@@ -1241,18 +1203,166 @@ static void sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand,
*cur_off = mtd->oobsize + mtd->writesize;
}
-static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf,
- int oob_required, int page,
- int nchunks)
+/* Interpret one PIO step or a completed DMA batch before reusing its status. */
+static int sunxi_nfc_oob_read_batch(struct nand_chip *nand,
+ struct sunxi_nfc_ecc_read *read,
+ u32 status, u32 pattern_found,
+ int first_step, int nsteps)
{
- bool randomized = nand->options & NAND_NEED_SCRAMBLING;
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
- unsigned int max_bitflips = 0;
- int ret, i, raw_mode = 0;
+ bool randomized = nand->options & NAND_NEED_SCRAMBLING;
+ unsigned int user_data_sz;
+ int i, ret;
+
+ for (i = 0; i < nsteps; i++) {
+ int step = first_step + i;
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, step);
+ u8 *data = read->buf + step * ecc->size;
+ u8 *oob = nand->oob_poi + oob_off;
+ bool erased;
+ int bitflips;
+
+ user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
+ bitflips = sunxi_nfc_hw_ecc_correct(nand,
+ !read->dma || randomized ? data : NULL,
+ read->oob_required ? oob : NULL,
+ i, status, pattern_found, &erased);
+ if (bitflips < 0) {
+ /* Preserve PIO data before the raw erased-page check. */
+ if (!read->dma && !randomized)
+ memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, ecc->size);
+ continue;
+ }
+
+ if (erased) {
+ read->has_erased_chunk = true;
+ continue;
+ }
+
+ if (!read->dma)
+ memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, ecc->size);
+
+ if (read->oob_required) {
+ /* Keep the existing PIO and DMA representations of parity. */
+ nand_change_read_column_op(nand, mtd->writesize + oob_off,
+ read->dma ? oob : NULL,
+ read->dma ? ecc->bytes + user_data_sz : 0,
+ false);
+
+ if (!read->dma) {
+ sunxi_nfc_randomizer_read_buf(nand, oob,
+ ecc->bytes + user_data_sz,
+ true, read->page);
+ }
+
+ sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, !step,
+ read->page, user_data_sz);
+ }
+
+ sunxi_nfc_hw_ecc_update_stats(nand, &read->max_bitflips, bitflips);
+ }
+
+ if (!(status & NFC_ECC_ERR_MSK(nfc)))
+ return 0;
+
+ /* Finish consuming the batch's corrected OOB before raw error rereads. */
+ for (i = 0; i < nsteps; i++) {
+ int step = first_step + i;
+ int data_off, oob_off;
+
+ if (!(status & NFC_ECC_ERR(i)))
+ continue;
+
+ data_off = step * ecc->size;
+ oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, step);
+ user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
+ ret = sunxi_nfc_hw_ecc_read_error(nand, read->buf + data_off, data_off,
+ nand->oob_poi + oob_off,
+ mtd->writesize + oob_off, user_data_sz,
+ &read->max_bitflips);
+ if (ret < 0)
+ return ret;
+ if (ret)
+ read->has_erased_chunk = true;
+ }
+
+ return 0;
+}
+
+static int sunxi_nfc_oob_finish_read(struct nand_chip *nand,
+ struct sunxi_nfc_ecc_read *read, int *cur_off)
+{
+ if (read->oob_required)
+ sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi, cur_off,
+ !read->has_erased_chunk, read->page);
+
+ return read->max_bitflips;
+}
+
+static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
+ struct sunxi_nfc_ecc_read *read,
+ int step, int *cur_off)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct nand_ecc_ctrl *ecc = &nand->ecc;
+ int data_off = step * ecc->size;
+ int oob_off = nand_to_mtd(nand)->writesize + sunxi_get_oob_offset(sunxi_nand, ecc, step);
+ int page = read->page;
+ u32 pattern_found;
+ int ret;
+
+ if (*cur_off != data_off)
+ nand_change_read_column_op(nand, data_off, NULL, 0, false);
+
+ sunxi_nfc_randomizer_read_buf(nand, NULL, ecc->size, false, page);
+
+ if (data_off + ecc->size != oob_off)
+ nand_change_read_column_op(nand, oob_off, NULL, 0, false);
+
+ ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
+ if (ret)
+ return ret;
+
+ sunxi_nfc_set_user_data_len(nfc, user_data_sz, 0);
+ sunxi_nfc_randomizer_config(nand, page, false);
+ sunxi_nfc_randomizer_enable(nand);
+ writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP,
+ nfc->regs + NFC_REG_CMD);
+
+ ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, false, 0);
+ sunxi_nfc_randomizer_disable(nand);
+ if (ret)
+ return ret;
+
+ *cur_off = oob_off + ecc->bytes + user_data_sz;
+
+ pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found);
+ pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found);
+
+ return sunxi_nand->oob_ops->read_batch(nand, read,
+ readl(nfc->regs + NFC_REG_ECC_ST),
+ pattern_found, step, 1);
+}
+
+static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, u8 *buf,
+ int oob_required, int page, int nchunks)
+{
+ 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;
+ struct sunxi_nfc_ecc_read read = {
+ .buf = buf,
+ .page = page,
+ .oob_required = oob_required,
+ .dma = true,
+ };
struct scatterlist sg;
+ int ret, i;
u32 status, pattern_found, wait;
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
@@ -1299,83 +1409,12 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
status = readl(nfc->regs + NFC_REG_ECC_ST);
pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found);
pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found);
+ ret = sunxi_nand->oob_ops->read_batch(nand, &read, status, pattern_found,
+ 0, nchunks);
+ if (ret)
+ return ret;
- for (i = 0; i < nchunks; i++) {
- int data_off = i * ecc->size;
- unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i);
- int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
- u8 *data = buf + data_off;
- u8 *oob = nand->oob_poi + oob_off;
- bool erased;
-
- ret = sunxi_nfc_hw_ecc_correct(nand, randomized ? data : NULL,
- oob_required ? oob : NULL,
- i, status, pattern_found,
- &erased);
-
- /* ECC errors are handled in the second loop. */
- if (ret < 0)
- continue;
-
- if (oob_required && !erased) {
- /* TODO: use DMA to retrieve OOB */
- nand_change_read_column_op(nand,
- mtd->writesize + oob_off,
- oob, ecc->bytes + user_data_sz, false);
-
- sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, !i,
- page, user_data_sz);
- }
-
- if (erased)
- raw_mode = 1;
-
- sunxi_nfc_hw_ecc_update_stats(nand, &max_bitflips, ret);
- }
-
- if (status & NFC_ECC_ERR_MSK(nfc)) {
- for (i = 0; i < nchunks; i++) {
- int data_off = i * ecc->size;
- unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i);
- int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
- u8 *data = buf + data_off;
- u8 *oob = nand->oob_poi + oob_off;
-
- if (!(status & NFC_ECC_ERR(i)))
- continue;
-
- /*
- * Re-read the data with the randomizer disabled to
- * identify bitflips in erased pages.
- * TODO: use DMA to read page in raw mode
- */
- if (randomized)
- nand_change_read_column_op(nand, data_off,
- data, ecc->size,
- false);
-
- /* TODO: use DMA to retrieve OOB */
- nand_change_read_column_op(nand,
- mtd->writesize + oob_off,
- oob, ecc->bytes + user_data_sz, false);
-
- ret = nand_check_erased_ecc_chunk(data, ecc->size, oob,
- ecc->bytes + user_data_sz,
- NULL, 0,
- ecc->strength);
- if (ret >= 0)
- raw_mode = 1;
-
- sunxi_nfc_hw_ecc_update_stats(nand, &max_bitflips, ret);
- }
- }
-
- if (oob_required)
- sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi,
- NULL, !raw_mode,
- page);
-
- return max_bitflips;
+ return sunxi_nand->oob_ops->finish_read(nand, &read, NULL);
}
static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
@@ -1452,11 +1491,13 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
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 max_bitflips = 0;
+ struct sunxi_nfc_ecc_read read = {
+ .buf = buf,
+ .page = page,
+ .oob_required = oob_required,
+ };
int ret, i, cur_off = 0;
- bool raw_mode = false;
sunxi_nfc_select_chip(nand, nand->cur_cs);
@@ -1466,28 +1507,15 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
sunxi_nfc_reset_user_data_len(nfc);
for (i = 0; i < ecc->steps; i++) {
- int data_off = i * ecc->size;
- int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
- u8 *data = buf + data_off;
- u8 *oob = nand->oob_poi + oob_off;
-
- ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off, oob,
- oob_off + mtd->writesize,
- &cur_off, &max_bitflips,
- i, oob_required, page);
+ ret = sunxi_nfc_hw_ecc_read_chunk(nand, &read, i, &cur_off);
if (ret < 0)
return ret;
- else if (ret)
- raw_mode = true;
}
- if (oob_required)
- sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi, &cur_off,
- !raw_mode, page);
-
+ ret = sunxi_nand->oob_ops->finish_read(nand, &read, &cur_off);
sunxi_nfc_hw_ecc_disable(nand);
- return max_bitflips;
+ return ret;
}
static int sunxi_nfc_hw_ecc_read_page_dma(struct nand_chip *nand, u8 *buf,
@@ -1513,11 +1541,12 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
u8 *bufpoi, int page)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
- 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_read read = {
+ .buf = bufpoi,
+ .page = page,
+ };
int ret, i, cur_off = 0;
- unsigned int max_bitflips = 0;
sunxi_nfc_select_chip(nand, nand->cur_cs);
@@ -1528,23 +1557,15 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
sunxi_nfc_reset_user_data_len(nfc);
for (i = data_offs / ecc->size;
i < DIV_ROUND_UP(data_offs + readlen, ecc->size); i++) {
- int data_off = i * ecc->size;
- int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
- u8 *data = bufpoi + data_off;
- u8 *oob = nand->oob_poi + oob_off;
-
- ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off,
- oob,
- oob_off + mtd->writesize,
- &cur_off, &max_bitflips, i,
- false, page);
+ ret = sunxi_nfc_hw_ecc_read_chunk(nand, &read, i, &cur_off);
if (ret < 0)
return ret;
}
+ ret = read.max_bitflips;
sunxi_nfc_hw_ecc_disable(nand);
- return max_bitflips;
+ return ret;
}
static int sunxi_nfc_hw_ecc_read_subpage_dma(struct nand_chip *nand,
@@ -2066,6 +2087,58 @@ static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t oobsize
return 0;
}
+static unsigned int sunxi_nfc_oob_min_user_data(struct nand_chip *nand, int nsteps)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ unsigned int size = 0;
+ int i;
+
+ /* Variable lengths reserve the first word, which contains the BBM. */
+ if (nfc->caps->reg_user_data_len)
+ return USER_DATA_SZ;
+
+ for (i = 0; i < nsteps; i++)
+ size += sunxi_nfc_user_data_sz(sunxi_nand, i);
+
+ /* Preserve the older platforms' extra BBM reservation and ECC strength. */
+ if (nfc->caps->legacy_max_strength)
+ size += 2;
+
+ return size;
+}
+
+static int sunxi_nfc_oob_init_user_data(struct nand_chip *nand, int nsteps)
+{
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+
+ if (!nfc->caps->reg_user_data_len)
+ return 0;
+
+ /* Prioritize ECC strength, then use the remaining space for user data. */
+ return sunxi_nfc_maximize_user_data(nand, nand_to_mtd(nand)->oobsize,
+ nand->ecc.bytes, nsteps);
+}
+
+/*
+ * Both modes use the same layout and ECC policy. Scrambled transfers also
+ * compensate the first two user bytes to keep the on-flash BBM unmodified.
+ */
+static const struct sunxi_nfc_oob_ops sunxi_nfc_plain_oob_ops = {
+ .min_user_data = sunxi_nfc_oob_min_user_data,
+ .init_user_data = sunxi_nfc_oob_init_user_data,
+ .read_batch = sunxi_nfc_oob_read_batch,
+ .finish_read = sunxi_nfc_oob_finish_read,
+};
+
+static const struct sunxi_nfc_oob_ops sunxi_nfc_scrambled_plain_bbm_oob_ops = {
+ .min_user_data = sunxi_nfc_oob_min_user_data,
+ .init_user_data = sunxi_nfc_oob_init_user_data,
+ .transform_bbm = sunxi_nfc_randomize_bbm,
+ .read_batch = sunxi_nfc_oob_read_batch,
+ .finish_read = sunxi_nfc_oob_finish_read,
+};
+
static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
struct nand_ecc_ctrl *ecc,
struct device_node *np)
@@ -2090,33 +2163,7 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
ecc->size = 1024;
nsectors = mtd->writesize / ecc->size;
- if (!nfc->caps->reg_user_data_len) {
- /*
- * If there's a fixed user data length, subtract it before
- * computing the max ECC strength
- */
-
- for (i = 0; i < nsectors; i++)
- total_user_data_sz += sunxi_nfc_user_data_sz(sunxi_nand, i);
-
- /*
- * The 2 BBM bytes should not be removed from the grand total,
- * because they are part of the USER_DATA_SZ.
- * But we can't modify that for older platform since it may
- * result in a stronger ECC at the end, and break the
- * compatibility.
- */
- if (nfc->caps->legacy_max_strength)
- bytes -= 2;
-
- bytes -= total_user_data_sz;
- } else {
- /*
- * User-data lengths are encoded in four-byte units. Reserve
- * the first word because it contains the two BBM bytes.
- */
- bytes -= USER_DATA_SZ;
- }
+ bytes -= sunxi_nand->oob_ops->min_user_data(nand, nsectors);
/*
* Once all user data has been subtracted, the rest can be used
@@ -2175,20 +2222,12 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
nsectors = mtd->writesize / ecc->size;
- /*
- * The rationale for variable data length is to prioritize maximum ECC
- * strength, and then use the remaining space for user data.
- */
- if (nfc->caps->reg_user_data_len) {
- ret = sunxi_nfc_maximize_user_data(nand, mtd->oobsize,
- ecc->bytes, nsectors);
- if (ret)
- return ret;
- }
+ ret = sunxi_nand->oob_ops->init_user_data(nand, nsectors);
+ if (ret)
+ return ret;
- if (total_user_data_sz == 0)
- for (i = 0; i < nsectors; i++)
- total_user_data_sz += sunxi_nfc_user_data_sz(sunxi_nand, i);
+ for (i = 0; i < nsectors; i++)
+ total_user_data_sz += sunxi_nfc_user_data_sz(sunxi_nand, i);
if (mtd->oobsize < (ecc->bytes * nsectors + total_user_data_sz))
return -EINVAL;
@@ -2232,6 +2271,7 @@ 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);
const struct nand_ecc_props *requirements =
nanddev_get_ecc_requirements(&nand->base);
struct nand_ecc_ctrl *ecc = &nand->ecc;
@@ -2254,6 +2294,9 @@ static int sunxi_nand_attach_chip(struct nand_chip *nand)
if (!ecc->size || !ecc->strength)
return -EINVAL;
+ sunxi_nand->oob_ops = nand->options & NAND_NEED_SCRAMBLING ?
+ &sunxi_nfc_scrambled_plain_bbm_oob_ops : &sunxi_nfc_plain_oob_ops;
+
switch (ecc->engine_type) {
case NAND_ECC_ENGINE_TYPE_ON_HOST:
ret = sunxi_nand_hw_ecc_ctrl_init(nand, ecc, np);
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v7 02/16] mtd: rawnand: sunxi: use the logical step's OOB length in PIO
2026-10-15 1:33 [PATCH v7 00/16] mtd: rawnand: sunxi: fix transfers and support randomized OOB James Hilliard
2026-10-15 1:33 ` [PATCH v7 01/16] mtd: rawnand: sunxi: centralize OOB handling James Hilliard
@ 2026-10-15 1:33 ` James Hilliard
2026-10-15 1:33 ` [PATCH v7 03/16] mtd: rawnand: sunxi: propagate page-setup and erased-check errors James Hilliard
` (13 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: James Hilliard @ 2026-10-15 1:33 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Maxime Ripard,
Richard Genoud, Boris Brezillon, Masahiro Yamada, Brian Norris,
linux-mtd, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-rt-devel, devicetree, James Hilliard
PIO transfers each logical ECC step through hardware slot zero. On
H6/H616, user-data lengths can differ between logical steps: step zero
can have 32 bytes while a later step has only 16 or none.
sunxi_nfc_hw_ecc_correct() uses the hardware slot to select that length
when filling pattern data. A pattern detected in a later PIO step can
therefore overwrite the following OOB region with step zero's longer
length. The protected-OOB write helper makes the same mistake when
loading slot zero, reading bytes beyond the current step's user data.
Pass the logical step's user-data length explicitly to both helpers.
Separate that length from the user-data register offset: PIO always
starts at register zero, while DMA advances by each preceding step's
length in four-byte words. Update both DMA callers with the helper
contract so variable-length steps continue to use the same registers.
Fixed four-byte layouts and the on-flash geometry are unchanged.
The mismatched logical and hardware indices were found by code inspection
while consolidating the PIO and DMA OOB paths.
Fixes: 54dcd6aa69db ("mtd: rawnand: sunxi: introduce maximize variable user data length")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 106 ++++++++++++--------------------------
1 file changed, 32 insertions(+), 74 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 27d6ae635e9c..c2e97b8656f7 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -929,45 +929,22 @@ static u8 sunxi_nfc_user_data_sz(struct sunxi_nand_chip *sunxi_nand, int step)
/* PIO uses register zero; DMA concatenates user data within each transfer. */
static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob,
- int step, bool bbm, int page,
+ unsigned int reg_index, bool bbm, int page,
unsigned int user_data_sz)
{
- struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ const struct sunxi_nfc_oob_ops *ops = to_sunxi_nand(nand)->oob_ops;
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
u32 user_data;
+ unsigned int i;
- if (!nfc->caps->reg_user_data_len) {
- /*
- * For A10, the user data for step n is in the nth
- * REG_USER_DATA
- */
- user_data = readl(nfc->regs + NFC_REG_USER_DATA(nfc, step));
- sunxi_nfc_user_data_to_buf(user_data, oob);
- } else {
- /*
- * For H6 NAND controller, the user data for all steps is
- * contained in 32 user data registers, but not at a specific
- * offset for each step, they are just concatenated.
- */
- unsigned int user_data_off = 0;
- unsigned int reg_off;
- u8 *ptr = oob;
- unsigned int i;
-
- for (i = 0; i < step; i++)
- user_data_off += sunxi_nfc_user_data_sz(sunxi_nand, i);
-
- user_data_off /= 4;
- for (i = 0; i < user_data_sz / 4; i++, ptr += 4) {
- reg_off = NFC_REG_USER_DATA(nfc, user_data_off + i);
- user_data = readl(nfc->regs + reg_off);
- sunxi_nfc_user_data_to_buf(user_data, ptr);
- }
+ for (i = 0; i < user_data_sz / 4; i++) {
+ user_data = readl(nfc->regs + NFC_REG_USER_DATA(nfc, reg_index + i));
+ sunxi_nfc_user_data_to_buf(user_data, oob + i * 4);
}
- /* De-randomize the Bad Block Marker. */
- if (bbm && sunxi_nand->oob_ops->transform_bbm)
- sunxi_nand->oob_ops->transform_bbm(nand, page, oob);
+ /* Undo hardware de-randomization of the plain on-flash BBM. */
+ if (bbm && ops->transform_bbm)
+ ops->transform_bbm(nand, page, oob);
}
/*
@@ -1018,48 +995,26 @@ static void sunxi_nfc_set_user_data_len(struct sunxi_nfc *nfc,
}
static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
- const u8 *oob, int step,
- bool bbm, int page)
+ const u8 *oob, unsigned int reg_index,
+ bool bbm, int page,
+ unsigned int user_data_sz)
{
+ const struct sunxi_nfc_oob_ops *ops = to_sunxi_nand(nand)->oob_ops;
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
- struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
- unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
u8 user_data[SUNXI_NFC_MAX_USER_DATA_SZ] = {};
+ unsigned int i;
- /* Randomize the Bad Block Marker. */
- if (bbm && sunxi_nand->oob_ops->transform_bbm) {
+ /* Compensate hardware randomization so the BBM stays plain on flash. */
+ if (bbm && ops->transform_bbm) {
memcpy(user_data, oob, user_data_sz);
- sunxi_nand->oob_ops->transform_bbm(nand, page, user_data);
+ ops->transform_bbm(nand, page, user_data);
oob = user_data;
}
- if (!nfc->caps->reg_user_data_len) {
- /*
- * For A10, the user data for step n is in the nth
- * REG_USER_DATA
- */
- writel(sunxi_nfc_buf_to_user_data(oob),
- nfc->regs + NFC_REG_USER_DATA(nfc, step));
- } else {
- /*
- * For H6 NAND controller, the user data for all steps is
- * contained in 32 user data registers, but not at a specific
- * offset for each step, they are just concatenated.
- */
- unsigned int user_data_off = 0;
- const u8 *ptr = oob;
- unsigned int i;
-
- for (i = 0; i < step; i++)
- user_data_off += sunxi_nfc_user_data_sz(sunxi_nand, i);
-
- user_data_off /= 4;
- for (i = 0; i < user_data_sz / 4; i++, ptr += 4) {
- writel(sunxi_nfc_buf_to_user_data(ptr),
- nfc->regs + NFC_REG_USER_DATA(nfc, user_data_off + i));
- }
+ for (i = 0; i < user_data_sz / 4; i++) {
+ writel(sunxi_nfc_buf_to_user_data(oob + i * 4),
+ nfc->regs + NFC_REG_USER_DATA(nfc, reg_index + i));
}
-
}
static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand,
@@ -1111,11 +1066,9 @@ struct sunxi_nfc_ecc_read {
static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
int step, u32 status, u32 pattern_found,
- bool *erased)
+ unsigned int user_data_sz, bool *erased)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
- struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
- unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
struct nand_ecc_ctrl *ecc = &nand->ecc;
u32 tmp;
@@ -1214,10 +1167,10 @@ static int sunxi_nfc_oob_read_batch(struct nand_chip *nand,
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
bool randomized = nand->options & NAND_NEED_SCRAMBLING;
- unsigned int user_data_sz;
+ unsigned int reg_index = 0, user_data_sz;
int i, ret;
- for (i = 0; i < nsteps; i++) {
+ for (i = 0; i < nsteps; i++, reg_index += user_data_sz / 4) {
int step = first_step + i;
int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, step);
u8 *data = read->buf + step * ecc->size;
@@ -1229,7 +1182,8 @@ static int sunxi_nfc_oob_read_batch(struct nand_chip *nand,
bitflips = sunxi_nfc_hw_ecc_correct(nand,
!read->dma || randomized ? data : NULL,
read->oob_required ? oob : NULL,
- i, status, pattern_found, &erased);
+ i, status, pattern_found,
+ user_data_sz, &erased);
if (bitflips < 0) {
/* Preserve PIO data before the raw erased-page check. */
if (!read->dma && !randomized)
@@ -1258,7 +1212,7 @@ static int sunxi_nfc_oob_read_batch(struct nand_chip *nand,
true, read->page);
}
- sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, !step,
+ sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, reg_index, !step,
read->page, user_data_sz);
}
@@ -1447,7 +1401,8 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
sunxi_nfc_randomizer_config(nand, page, false);
sunxi_nfc_randomizer_enable(nand);
sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step);
- sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, nfc_step, bbm, page);
+ sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, nfc_step, bbm, page,
+ user_data_sz);
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
NFC_ACCESS_DIR | NFC_ECC_OP,
@@ -1673,6 +1628,7 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
struct scatterlist sg;
+ unsigned int reg_index = 0;
u32 wait;
int ret, i;
@@ -1693,8 +1649,10 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
const u8 *oob = nand->oob_poi + oob_off;
- sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, i, !i, page);
+ sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, reg_index, !i, page,
+ user_data_sz);
sunxi_nfc_set_user_data_len(nfc, user_data_sz, i);
+ reg_index += user_data_sz / 4;
}
nand_prog_page_begin_op(nand, page, 0, NULL, 0);
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v7 03/16] mtd: rawnand: sunxi: propagate page-setup and erased-check errors
2026-10-15 1:33 [PATCH v7 00/16] mtd: rawnand: sunxi: fix transfers and support randomized OOB James Hilliard
2026-10-15 1:33 ` [PATCH v7 01/16] mtd: rawnand: sunxi: centralize OOB handling James Hilliard
2026-10-15 1:33 ` [PATCH v7 02/16] mtd: rawnand: sunxi: use the logical step's OOB length in PIO James Hilliard
@ 2026-10-15 1:33 ` James Hilliard
2026-10-15 1:33 ` [PATCH v7 04/16] mtd: rawnand: sunxi: drain interrupts before reusing the completion James Hilliard
` (12 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: James Hilliard @ 2026-10-15 1:33 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Maxime Ripard,
Richard Genoud, Boris Brezillon, Masahiro Yamada, Brian Norris,
linux-mtd, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-rt-devel, devicetree, James Hilliard
The ECC read callbacks ignore nand_read_page_op() failures. A command or
ready timeout can therefore leave the requested page unselected, yet the
driver continues with data transfers and can report success for stale
data. Return the setup error before starting PIO or DMA.
After an ECC error, the driver rereads the physical data and OOB with
randomization disabled to distinguish an erased chunk from corrupt data.
Those rereads can also fail. Do not pass stale buffers to
nand_check_erased_ecc_chunk() or count a transport failure as an ECC
result: return the reread error instead. Successful erased checks retain
their existing statistics and correction-count behavior.
Route PIO read errors through ECC disable. DMA has already released its
transfer resources before interpreting the results, and its caller
retries a failed read in PIO. Restore the corrected/failed counters from
before that DMA attempt so earlier steps are not counted again by the
retry; leave bad-block counters unchanged.
These unchecked returns were found by inspecting the read callbacks and
their NAND-operation helpers.
Fixes: 97d90da8a886 ("mtd: nand: provide several helpers to do common NAND operations")
Fixes: 25f815f66a14 ("mtd: nand: force drivers to explicitly send READ/PROG commands")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 56 ++++++++++++++++++++++++++++++---------
1 file changed, 44 insertions(+), 12 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index c2e97b8656f7..6c0d3e2d2be2 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1030,7 +1030,10 @@ static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand,
}
}
-/* Return whether the chunk is erased, and account for its ECC result. */
+/*
+ * Return 1 for an erased chunk or 0 for an uncorrectable chunk, with ECC
+ * statistics updated in either case. Negative values report transport errors.
+ */
static int sunxi_nfc_hw_ecc_read_error(struct nand_chip *nand,
u8 *data, int data_off,
u8 *oob, int oob_off,
@@ -1042,10 +1045,16 @@ static int sunxi_nfc_hw_ecc_read_error(struct nand_chip *nand,
int ret;
/* Check the physical representation for bitflips in erased pages. */
- if (nand->options & NAND_NEED_SCRAMBLING)
- nand_change_read_column_op(nand, data_off, data, ecc->size, false);
+ if (nand->options & NAND_NEED_SCRAMBLING) {
+ ret = nand_change_read_column_op(nand, data_off, data,
+ ecc->size, false);
+ if (ret)
+ return ret;
+ }
- nand_change_read_column_op(nand, oob_off, oob, oob_len, false);
+ ret = nand_change_read_column_op(nand, oob_off, oob, oob_len, false);
+ if (ret)
+ return ret;
ret = nand_check_erased_ecc_chunk(data, ecc->size, oob, oob_len, NULL, 0,
ecc->strength);
@@ -1308,6 +1317,7 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, u8 *buf,
{
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
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_read read = {
.buf = buf,
@@ -1315,6 +1325,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, u8 *buf,
.oob_required = oob_required,
.dma = true,
};
+ unsigned int corrected = mtd->ecc_stats.corrected;
+ unsigned int failed = mtd->ecc_stats.failed;
struct scatterlist sg;
int ret, i;
u32 status, pattern_found, wait;
@@ -1366,9 +1378,19 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, u8 *buf,
ret = sunxi_nand->oob_ops->read_batch(nand, &read, status, pattern_found,
0, nchunks);
if (ret)
- return ret;
+ goto err_stats;
+
+ ret = sunxi_nand->oob_ops->finish_read(nand, &read, NULL);
+ if (ret < 0)
+ goto err_stats;
+
+ return ret;
- return sunxi_nand->oob_ops->finish_read(nand, &read, NULL);
+err_stats:
+ /* The caller retries the whole read in PIO mode. */
+ mtd->ecc_stats.corrected = corrected;
+ mtd->ecc_stats.failed = failed;
+ return ret;
}
static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
@@ -1456,7 +1478,9 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
sunxi_nfc_select_chip(nand, nand->cur_cs);
- nand_read_page_op(nand, page, 0, NULL, 0);
+ ret = nand_read_page_op(nand, page, 0, NULL, 0);
+ if (ret)
+ return ret;
sunxi_nfc_hw_ecc_enable(nand);
@@ -1464,10 +1488,11 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
for (i = 0; i < ecc->steps; i++) {
ret = sunxi_nfc_hw_ecc_read_chunk(nand, &read, i, &cur_off);
if (ret < 0)
- return ret;
+ goto out;
}
ret = sunxi_nand->oob_ops->finish_read(nand, &read, &cur_off);
+out:
sunxi_nfc_hw_ecc_disable(nand);
return ret;
@@ -1480,7 +1505,9 @@ static int sunxi_nfc_hw_ecc_read_page_dma(struct nand_chip *nand, u8 *buf,
sunxi_nfc_select_chip(nand, nand->cur_cs);
- nand_read_page_op(nand, page, 0, NULL, 0);
+ ret = nand_read_page_op(nand, page, 0, NULL, 0);
+ if (ret)
+ return ret;
ret = sunxi_nfc_hw_ecc_read_chunks_dma(nand, buf, oob_required, page,
nand->ecc.steps);
@@ -1505,7 +1532,9 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
sunxi_nfc_select_chip(nand, nand->cur_cs);
- nand_read_page_op(nand, page, 0, NULL, 0);
+ ret = nand_read_page_op(nand, page, 0, NULL, 0);
+ if (ret)
+ return ret;
sunxi_nfc_hw_ecc_enable(nand);
@@ -1514,10 +1543,11 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
i < DIV_ROUND_UP(data_offs + readlen, ecc->size); i++) {
ret = sunxi_nfc_hw_ecc_read_chunk(nand, &read, i, &cur_off);
if (ret < 0)
- return ret;
+ goto out;
}
ret = read.max_bitflips;
+out:
sunxi_nfc_hw_ecc_disable(nand);
return ret;
@@ -1532,7 +1562,9 @@ static int sunxi_nfc_hw_ecc_read_subpage_dma(struct nand_chip *nand,
sunxi_nfc_select_chip(nand, nand->cur_cs);
- nand_read_page_op(nand, page, 0, NULL, 0);
+ ret = nand_read_page_op(nand, page, 0, NULL, 0);
+ if (ret)
+ return ret;
ret = sunxi_nfc_hw_ecc_read_chunks_dma(nand, buf, false, page, nchunks);
if (ret >= 0)
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v7 04/16] mtd: rawnand: sunxi: drain interrupts before reusing the completion
2026-10-15 1:33 [PATCH v7 00/16] mtd: rawnand: sunxi: fix transfers and support randomized OOB James Hilliard
` (2 preceding siblings ...)
2026-10-15 1:33 ` [PATCH v7 03/16] mtd: rawnand: sunxi: propagate page-setup and erased-check errors James Hilliard
@ 2026-10-15 1:33 ` James Hilliard
2026-10-15 1:33 ` [PATCH v7 05/16] mtd: rawnand: sunxi: stop failed program operations and disable ECC James Hilliard
` (11 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: James Hilliard @ 2026-10-15 1:33 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Maxime Ripard,
Richard Genoud, Boris Brezillon, Masahiro Yamada, Brian Norris,
linux-mtd, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-rt-devel, devicetree, James Hilliard
The controller uses one completion for successive interrupt-driven
waits. Some transfers wait for both command completion and DMA
completion. The handler acknowledges each event and masks it, leaving
any outstanding event enabled; it calls complete() when all requested
events have arrived.
sunxi_nfc_interrupt() currently calls complete() before updating those
registers. A successful waiter can wake on another CPU and start the
next operation before the old handler acknowledges status or rewrites
the mask. Move complete() after those updates so waking the waiter
cannot precede the handler's last controller access.
A timeout also needs to exclude an unfinished handler. With PREEMPT_RT,
the flags-zero IRQ request is forced threaded. Near the timeout
boundary, that thread can be preempted after reading status and enable
bits while the waiter times out, clears the device mask and rearms the
next wait. The saved snapshot can then restore an obsolete
remaining-event mask or, if it satisfies the old wait, complete the new
wait. Masking the device does not invalidate the running handler's local
values. Repeating init_completion() can also reset its waitqueue lock
during complete(). This lifetime problem was identified by code
inspection during review.
Initialize the completion once in probe and use reinit_completion() for
each wait. On timeout, call disable_irq() before clearing controller
state so existing handling finishes and no new handler can enter during
cleanup. Then mask and acknowledge the events, read back the mask to
flush those writes, and re-enable the IRQ line. Keep partial-event
handling in the IRQ handler and leave polling and controller/DMA abort
handling unchanged.
This keeps additional synchronization on the timeout path, without a
driver lock in every interrupt and wait. The caller can sleep, this IRQ
is not registered as shared, and the handler does not acquire the
controller mutex held by the caller. The tradeoff is unbounded cleanup
latency and no priority inheritance: a starved IRQ thread can prevent
timeout cleanup from returning and block other operations on the
controller. This is a handler-lifetime boundary, not an RT
recovery-latency guarantee.
Fixes: 1fef62c1423b ("mtd: nand: add sunxi NAND flash controller support")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 26 ++++++++++++++++++++------
1 file changed, 20 insertions(+), 6 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 6c0d3e2d2be2..bc38330c0e36 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -393,6 +393,7 @@ struct sunxi_nfc_caps {
* @chips: a list containing all the NAND chips attached to this NAND
* controller
* @complete: a completion object used to wait for NAND controller events
+ * @irq: NAND controller interrupt
* @dmac: the DMA channel attached to the NAND controller
* @use_mdma: use an internal MBUS DMA backend
* @mdma_desc: H6-style MBUS DMA descriptor
@@ -412,6 +413,7 @@ struct sunxi_nfc {
unsigned long clk_rate;
struct list_head chips;
struct completion complete;
+ int irq;
struct dma_chan *dmac;
bool use_mdma;
struct sunxi_nfc_mdma_desc *mdma_desc;
@@ -433,12 +435,13 @@ static irqreturn_t sunxi_nfc_interrupt(int irq, void *dev_id)
if (!(ien & st))
return IRQ_NONE;
- if ((ien & st) == ien)
- complete(&nfc->complete);
-
+ /* Finish updating the interrupt state before waking the next operation. */
writel(st & NFC_INT_MASK, nfc->regs + NFC_REG_ST);
writel(~st & ien & NFC_INT_MASK, nfc->regs + NFC_REG_INT);
+ if ((ien & st) == ien)
+ complete(&nfc->complete);
+
return IRQ_HANDLED;
}
@@ -454,16 +457,19 @@ static int sunxi_nfc_wait_events(struct sunxi_nfc *nfc, u32 events,
timeout_ms = NFC_DEFAULT_TIMEOUT_MS;
if (!use_polling) {
- init_completion(&nfc->complete);
+ reinit_completion(&nfc->complete);
writel(events, nfc->regs + NFC_REG_INT);
ret = wait_for_completion_timeout(&nfc->complete,
msecs_to_jiffies(timeout_ms));
- if (!ret)
+ if (!ret) {
+ /* Drain the handler before it can restore an old IRQ mask. */
+ disable_irq(nfc->irq);
ret = -ETIMEDOUT;
- else
+ } else {
ret = 0;
+ }
writel(0, nfc->regs + NFC_REG_INT);
} else {
@@ -476,6 +482,12 @@ static int sunxi_nfc_wait_events(struct sunxi_nfc *nfc, u32 events,
writel(events & NFC_INT_MASK, nfc->regs + NFC_REG_ST);
+ if (!use_polling && ret) {
+ /* Flush the mask and acknowledgment before re-enabling the IRQ. */
+ readl(nfc->regs + NFC_REG_INT);
+ enable_irq(nfc->irq);
+ }
+
if (ret)
dev_err(nfc->dev, "wait interrupt timedout\n");
@@ -2650,6 +2662,7 @@ static int sunxi_nfc_probe(struct platform_device *pdev)
nfc->dev = dev;
nand_controller_init(&nfc->controller);
INIT_LIST_HEAD(&nfc->chips);
+ init_completion(&nfc->complete);
nfc->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &r);
if (IS_ERR(nfc->regs))
@@ -2658,6 +2671,7 @@ static int sunxi_nfc_probe(struct platform_device *pdev)
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
+ nfc->irq = irq;
nfc->caps = of_device_get_match_data(dev);
if (!nfc->caps)
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v7 05/16] mtd: rawnand: sunxi: stop failed program operations and disable ECC
2026-10-15 1:33 [PATCH v7 00/16] mtd: rawnand: sunxi: fix transfers and support randomized OOB James Hilliard
` (3 preceding siblings ...)
2026-10-15 1:33 ` [PATCH v7 04/16] mtd: rawnand: sunxi: drain interrupts before reusing the completion James Hilliard
@ 2026-10-15 1:33 ` James Hilliard
2026-10-15 1:33 ` [PATCH v7 06/16] mtd: rawnand: sunxi: select the pattern ID for the current ECC step James Hilliard
` (10 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: James Hilliard @ 2026-10-15 1:33 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Maxime Ripard,
Richard Genoud, Boris Brezillon, Masahiro Yamada, Brian Norris,
linux-mtd, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-rt-devel, devicetree, James Hilliard
A page program has three stages: send the setup command and address,
transfer the data with ECC, then send PAGEPROG to commit it and check
the NAND status. The PIO page/subpage and DMA page writers ignore the
return from nand_prog_page_begin_op(). A FIFO or command-completion
timeout can therefore be followed by data transfers and PAGEPROG, with a
later success hiding the original setup failure.
Check setup before enabling ECC or transferring data. DMA preparation
occurs before setup and may already have queued a descriptor and mapped
the buffer, so use its existing abort and cleanup helpers before
returning that error.
The PIO writers also return directly when a chunk reports failure,
leaving ECC enabled for the next controller operation. Route those exits
through ECC disable, preserve the reported error and skip program-end.
Keep the successful setup/transfer/confirm sequence unchanged; this
patch does not add a new controller-reset or NAND-abort policy.
Fixes: 1fef62c1423b ("mtd: nand: add sunxi NAND flash controller support")
Fixes: 25f815f66a14 ("mtd: nand: force drivers to explicitly send READ/PROG commands")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 29 ++++++++++++++++++++++-------
1 file changed, 22 insertions(+), 7 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index bc38330c0e36..57df88099206 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1595,11 +1595,13 @@ static int sunxi_nfc_hw_ecc_write_page(struct nand_chip *nand,
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;
- int ret, i, cur_off = 0;
+ int ret = 0, i, cur_off = 0;
sunxi_nfc_select_chip(nand, nand->cur_cs);
- nand_prog_page_begin_op(nand, page, 0, NULL, 0);
+ ret = nand_prog_page_begin_op(nand, page, 0, NULL, 0);
+ if (ret)
+ return ret;
sunxi_nfc_hw_ecc_enable(nand);
@@ -1614,14 +1616,17 @@ static int sunxi_nfc_hw_ecc_write_page(struct nand_chip *nand,
oob_off + mtd->writesize,
&cur_off, i, page);
if (ret)
- return ret;
+ goto out;
}
if (oob_required || (nand->options & NAND_NEED_SCRAMBLING))
sunxi_nfc_hw_ecc_write_extra_oob(nand, nand->oob_poi,
&cur_off, page);
+out:
sunxi_nfc_hw_ecc_disable(nand);
+ if (ret)
+ return ret;
return nand_prog_page_end_op(nand);
}
@@ -1635,11 +1640,13 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct nand_chip *nand,
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;
- int ret, i, cur_off = 0;
+ int ret = 0, i, cur_off = 0;
sunxi_nfc_select_chip(nand, nand->cur_cs);
- nand_prog_page_begin_op(nand, page, 0, NULL, 0);
+ ret = nand_prog_page_begin_op(nand, page, 0, NULL, 0);
+ if (ret)
+ return ret;
sunxi_nfc_hw_ecc_enable(nand);
@@ -1655,10 +1662,13 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct nand_chip *nand,
oob_off + mtd->writesize,
&cur_off, i, page);
if (ret)
- return ret;
+ goto out;
}
+out:
sunxi_nfc_hw_ecc_disable(nand);
+ if (ret)
+ return ret;
return nand_prog_page_end_op(nand);
}
@@ -1699,7 +1709,12 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
reg_index += user_data_sz / 4;
}
- nand_prog_page_begin_op(nand, page, 0, NULL, 0);
+ ret = nand_prog_page_begin_op(nand, page, 0, NULL, 0);
+ if (ret) {
+ sunxi_nfc_dma_op_abort(nfc);
+ sunxi_nfc_dma_op_cleanup(nfc, DMA_TO_DEVICE, &sg);
+ return ret;
+ }
sunxi_nfc_hw_ecc_enable(nand);
sunxi_nfc_randomizer_config(nand, page, false);
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v7 06/16] mtd: rawnand: sunxi: select the pattern ID for the current ECC step
2026-10-15 1:33 [PATCH v7 00/16] mtd: rawnand: sunxi: fix transfers and support randomized OOB James Hilliard
` (4 preceding siblings ...)
2026-10-15 1:33 ` [PATCH v7 05/16] mtd: rawnand: sunxi: stop failed program operations and disable ECC James Hilliard
@ 2026-10-15 1:33 ` James Hilliard
2026-10-15 1:50 ` sashiko-bot
2026-10-15 1:33 ` [PATCH v7 07/16] mtd: rawnand: sunxi: propagate buffer and column transfer errors James Hilliard
` (9 subsequent siblings)
15 siblings, 1 reply; 18+ messages in thread
From: James Hilliard @ 2026-10-15 1:33 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Maxime Ripard,
Richard Genoud, Boris Brezillon, Masahiro Yamada, Brian Norris,
linux-mtd, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-rt-devel, devicetree, James Hilliard
ECC exception mode recognizes physical all-zero and all-ones data
blocks. For each hardware ECC step, a pattern-found bit says whether
this happened and the corresponding pattern-ID bit distinguishes zero
from 0xff. The correction helper checks the current step's pattern-found
bit but always reads pattern-ID bit zero.
PIO processes one step at a time in hardware slot zero, so it is
unaffected. DMA processes several steps at once. For example, if step
one is all 0xff but pattern-ID bit zero is clear, the helper treats step
one as all zero. On scrambled DMA reads it fills that step's main data
with zeros and reports success. Requested OOB is also filled using the
wrong pattern. Conversely, a zero step can be classified as erased when
bit zero is set.
Test BIT(step) in the pattern-ID register, matching the error and
pattern-found bits already selected for that hardware step.
Fixes: 614049a8d904 ("mtd: nand: sunxi: add support for DMA assisted operations")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 57df88099206..8256982d1d56 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1101,7 +1101,7 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
if (pattern_found & BIT(step)) {
u8 pattern;
- if (unlikely(!(readl(nfc->regs + NFC_REG_PAT_ID(nfc)) & 0x1))) {
+ if (unlikely(!(readl(nfc->regs + NFC_REG_PAT_ID(nfc)) & BIT(step)))) {
pattern = 0x0;
} else {
pattern = 0xff;
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v7 07/16] mtd: rawnand: sunxi: propagate buffer and column transfer errors
2026-10-15 1:33 [PATCH v7 00/16] mtd: rawnand: sunxi: fix transfers and support randomized OOB James Hilliard
` (5 preceding siblings ...)
2026-10-15 1:33 ` [PATCH v7 06/16] mtd: rawnand: sunxi: select the pattern ID for the current ECC step James Hilliard
@ 2026-10-15 1:33 ` James Hilliard
2026-10-15 1:33 ` [PATCH v7 08/16] mtd: rawnand: sunxi: avoid a second program confirm for OOB writes James Hilliard
` (8 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: James Hilliard @ 2026-10-15 1:33 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Maxime Ripard,
Richard Genoud, Boris Brezillon, Masahiro Yamada, Brian Norris,
linux-mtd, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-rt-devel, devicetree, James Hilliard
sunxi_nfc_read_buf() and sunxi_nfc_write_buf() move data through
controller SRAM, waiting for FIFO space and transfer completion. On
timeout they stop their loop but return void, so their callers cannot
distinguish a complete transfer from a partial one. The hardware-ECC
paths also ignore some NAND column-change errors, including extra-OOB
transfers performed after DMA. Reads can thus succeed with incomplete
data or bytes from the wrong column; writes can issue PAGEPROG after
failing to transfer all the intended bytes.
Return the transfer status through the randomizer and OOB helpers to the
page callbacks. A read with a NULL destination still fills controller
SRAM for ECC and must report errors even though it does not copy to
memory. Disable the randomizer after either transfer outcome, and use
the page callbacks' error exits to disable ECC and skip program-end.
Update a tracked OOB column only after the requested transfer succeeds.
Keep transport status separate from the maximum corrected-bit count:
zero from a successful OOB read must not erase a positive correction
result. If an OOB read after DMA fails, restore that attempt's
corrected/failed counters before the caller retries the page in PIO.
Leave successful command sequences and the returned OOB representation
unchanged.
Fixes: 1fef62c1423b ("mtd: nand: add sunxi NAND flash controller support")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 170 +++++++++++++++++++++++++-------------
1 file changed, 113 insertions(+), 57 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 8256982d1d56..dfdd8d8aaa6f 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -663,7 +663,7 @@ static void sunxi_nfc_select_chip(struct nand_chip *nand, unsigned int cs)
writel(ctl, nfc->regs + NFC_REG_CTL);
}
-static void sunxi_nfc_read_buf(struct nand_chip *nand, uint8_t *buf, int len)
+static int sunxi_nfc_read_buf(struct nand_chip *nand, u8 *buf, int len)
{
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
@@ -679,7 +679,7 @@ static void sunxi_nfc_read_buf(struct nand_chip *nand, uint8_t *buf, int len)
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
if (ret)
- break;
+ return ret;
writel(cnt, nfc->regs + NFC_REG_CNT);
tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD;
@@ -691,17 +691,19 @@ static void sunxi_nfc_read_buf(struct nand_chip *nand, uint8_t *buf, int len)
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, poll, 0);
if (ret)
- break;
+ return ret;
if (buf)
memcpy_fromio(buf + offs, nfc->regs + NFC_RAM0_BASE,
cnt);
offs += cnt;
}
+
+ return 0;
}
-static void sunxi_nfc_write_buf(struct nand_chip *nand, const uint8_t *buf,
- int len)
+static int sunxi_nfc_write_buf(struct nand_chip *nand, const u8 *buf,
+ int len)
{
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
@@ -717,7 +719,7 @@ static void sunxi_nfc_write_buf(struct nand_chip *nand, const uint8_t *buf,
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
if (ret)
- break;
+ return ret;
writel(cnt, nfc->regs + NFC_REG_CNT);
memcpy_toio(nfc->regs + NFC_RAM0_BASE, buf + offs, cnt);
@@ -731,10 +733,12 @@ static void sunxi_nfc_write_buf(struct nand_chip *nand, const uint8_t *buf,
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, poll, 0);
if (ret)
- break;
+ return ret;
offs += cnt;
}
+
+ return 0;
}
/* These seed values have been extracted from Allwinner's BSP */
@@ -884,23 +888,31 @@ static void sunxi_nfc_randomize_bbm(struct nand_chip *nand, int page, u8 *bbm)
bbm[1] ^= sunxi_nfc_randomizer_step(state, 8);
}
-static void sunxi_nfc_randomizer_write_buf(struct nand_chip *nand,
- const uint8_t *buf, int len,
- bool ecc, int page)
+static int sunxi_nfc_randomizer_write_buf(struct nand_chip *nand,
+ const u8 *buf, int len,
+ bool ecc, int page)
{
+ int ret;
+
sunxi_nfc_randomizer_config(nand, page, ecc);
sunxi_nfc_randomizer_enable(nand);
- sunxi_nfc_write_buf(nand, buf, len);
+ ret = sunxi_nfc_write_buf(nand, buf, len);
sunxi_nfc_randomizer_disable(nand);
+
+ return ret;
}
-static void sunxi_nfc_randomizer_read_buf(struct nand_chip *nand, uint8_t *buf,
- int len, bool ecc, int page)
+static int sunxi_nfc_randomizer_read_buf(struct nand_chip *nand, u8 *buf,
+ int len, bool ecc, int page)
{
+ int ret;
+
sunxi_nfc_randomizer_config(nand, page, ecc);
sunxi_nfc_randomizer_enable(nand);
- sunxi_nfc_read_buf(nand, buf, len);
+ ret = sunxi_nfc_read_buf(nand, buf, len);
sunxi_nfc_randomizer_disable(nand);
+
+ return ret;
}
static void sunxi_nfc_hw_ecc_enable(struct nand_chip *nand)
@@ -1150,31 +1162,39 @@ static int sunxi_get_ecc_offset(struct sunxi_nand_chip *sunxi_nand,
sunxi_nfc_user_data_sz(sunxi_nand, step);
}
-static void sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand,
- u8 *oob, int *cur_off,
- bool randomize, int page)
+static int sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand,
+ u8 *oob, int *cur_off,
+ bool randomize, 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;
int offset = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps);
int len = mtd->oobsize - offset;
+ int ret;
if (len <= 0)
- return;
+ return 0;
- if (!cur_off || *cur_off != (offset + mtd->writesize))
- nand_change_read_column_op(nand, mtd->writesize + offset,
- NULL, 0, false);
+ if (!cur_off || *cur_off != (offset + mtd->writesize)) {
+ ret = nand_change_read_column_op(nand, mtd->writesize + offset,
+ NULL, 0, false);
+ if (ret)
+ return ret;
+ }
if (!randomize)
- sunxi_nfc_read_buf(nand, oob + offset, len);
+ ret = sunxi_nfc_read_buf(nand, oob + offset, len);
else
- sunxi_nfc_randomizer_read_buf(nand, oob + offset, len,
- false, page);
+ ret = sunxi_nfc_randomizer_read_buf(nand, oob + offset, len,
+ false, page);
+ if (ret)
+ return ret;
if (cur_off)
*cur_off = mtd->oobsize + mtd->writesize;
+
+ return 0;
}
/* Interpret one PIO step or a completed DMA batch before reusing its status. */
@@ -1222,15 +1242,19 @@ static int sunxi_nfc_oob_read_batch(struct nand_chip *nand,
if (read->oob_required) {
/* Keep the existing PIO and DMA representations of parity. */
- nand_change_read_column_op(nand, mtd->writesize + oob_off,
- read->dma ? oob : NULL,
- read->dma ? ecc->bytes + user_data_sz : 0,
- false);
+ ret = nand_change_read_column_op(nand, mtd->writesize + oob_off,
+ read->dma ? oob : NULL,
+ read->dma ? ecc->bytes + user_data_sz : 0,
+ false);
+ if (ret)
+ return ret;
if (!read->dma) {
- sunxi_nfc_randomizer_read_buf(nand, oob,
- ecc->bytes + user_data_sz,
- true, read->page);
+ ret = sunxi_nfc_randomizer_read_buf(nand, oob,
+ ecc->bytes + user_data_sz,
+ true, read->page);
+ if (ret)
+ return ret;
}
sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, reg_index, !step,
@@ -1270,9 +1294,14 @@ static int sunxi_nfc_oob_read_batch(struct nand_chip *nand,
static int sunxi_nfc_oob_finish_read(struct nand_chip *nand,
struct sunxi_nfc_ecc_read *read, int *cur_off)
{
- if (read->oob_required)
- sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi, cur_off,
- !read->has_erased_chunk, read->page);
+ int ret;
+
+ if (read->oob_required) {
+ ret = sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi, cur_off,
+ !read->has_erased_chunk, read->page);
+ if (ret)
+ return ret;
+ }
return read->max_bitflips;
}
@@ -1291,13 +1320,21 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
u32 pattern_found;
int ret;
- if (*cur_off != data_off)
- nand_change_read_column_op(nand, data_off, NULL, 0, false);
+ if (*cur_off != data_off) {
+ ret = nand_change_read_column_op(nand, data_off, NULL, 0, false);
+ if (ret)
+ return ret;
+ }
- sunxi_nfc_randomizer_read_buf(nand, NULL, ecc->size, false, page);
+ ret = sunxi_nfc_randomizer_read_buf(nand, NULL, ecc->size, false, page);
+ if (ret)
+ return ret;
- if (data_off + ecc->size != oob_off)
- nand_change_read_column_op(nand, oob_off, NULL, 0, false);
+ if (data_off + ecc->size != oob_off) {
+ ret = nand_change_read_column_op(nand, oob_off, NULL, 0, false);
+ if (ret)
+ return ret;
+ }
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
if (ret)
@@ -1420,13 +1457,21 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
/* From the controller point of view, we are at step 0 */
const int nfc_step = 0;
- if (data_off != *cur_off)
- nand_change_write_column_op(nand, data_off, NULL, 0, false);
+ if (data_off != *cur_off) {
+ ret = nand_change_write_column_op(nand, data_off, NULL, 0, false);
+ if (ret)
+ return ret;
+ }
- sunxi_nfc_randomizer_write_buf(nand, data, ecc->size, false, page);
+ ret = sunxi_nfc_randomizer_write_buf(nand, data, ecc->size, false, page);
+ if (ret)
+ return ret;
- if (data_off + ecc->size != oob_off)
- nand_change_write_column_op(nand, oob_off, NULL, 0, false);
+ if (data_off + ecc->size != oob_off) {
+ ret = nand_change_write_column_op(nand, oob_off, NULL, 0, false);
+ if (ret)
+ return ret;
+ }
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
if (ret)
@@ -1452,27 +1497,35 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
return 0;
}
-static void sunxi_nfc_hw_ecc_write_extra_oob(struct nand_chip *nand,
- u8 *oob, int *cur_off,
- int page)
+static int sunxi_nfc_hw_ecc_write_extra_oob(struct nand_chip *nand,
+ u8 *oob, int *cur_off,
+ int page)
{
struct mtd_info *mtd = nand_to_mtd(nand);
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
int offset = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps);
int len = mtd->oobsize - offset;
+ int ret;
if (len <= 0)
- return;
+ return 0;
- if (!cur_off || *cur_off != offset)
- nand_change_write_column_op(nand, offset + mtd->writesize,
- NULL, 0, false);
+ if (!cur_off || *cur_off != offset) {
+ ret = nand_change_write_column_op(nand, offset + mtd->writesize,
+ NULL, 0, false);
+ if (ret)
+ return ret;
+ }
- sunxi_nfc_randomizer_write_buf(nand, oob + offset, len, false, page);
+ ret = sunxi_nfc_randomizer_write_buf(nand, oob + offset, len, false, page);
+ if (ret)
+ return ret;
if (cur_off)
*cur_off = mtd->oobsize + mtd->writesize;
+
+ return 0;
}
static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
@@ -1620,8 +1673,8 @@ static int sunxi_nfc_hw_ecc_write_page(struct nand_chip *nand,
}
if (oob_required || (nand->options & NAND_NEED_SCRAMBLING))
- sunxi_nfc_hw_ecc_write_extra_oob(nand, nand->oob_poi,
- &cur_off, page);
+ ret = sunxi_nfc_hw_ecc_write_extra_oob(nand, nand->oob_poi,
+ &cur_off, page);
out:
sunxi_nfc_hw_ecc_disable(nand);
@@ -1746,10 +1799,13 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
if (ret)
return ret;
- if (oob_required || (nand->options & NAND_NEED_SCRAMBLING))
+ if (oob_required || (nand->options & NAND_NEED_SCRAMBLING)) {
/* TODO: use DMA to transfer extra OOB bytes ? */
- sunxi_nfc_hw_ecc_write_extra_oob(nand, nand->oob_poi,
- NULL, page);
+ ret = sunxi_nfc_hw_ecc_write_extra_oob(nand, nand->oob_poi,
+ NULL, page);
+ if (ret)
+ return ret;
+ }
return nand_prog_page_end_op(nand);
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v7 08/16] mtd: rawnand: sunxi: avoid a second program confirm for OOB writes
2026-10-15 1:33 [PATCH v7 00/16] mtd: rawnand: sunxi: fix transfers and support randomized OOB James Hilliard
` (6 preceding siblings ...)
2026-10-15 1:33 ` [PATCH v7 07/16] mtd: rawnand: sunxi: propagate buffer and column transfer errors James Hilliard
@ 2026-10-15 1:33 ` James Hilliard
2026-10-15 1:33 ` [PATCH v7 09/16] mtd: rawnand: sunxi: use page reads to reposition small-page NAND James Hilliard
` (7 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: James Hilliard @ 2026-10-15 1:33 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Maxime Ripard,
Richard Genoud, Boris Brezillon, Masahiro Yamada, Brian Norris,
linux-mtd, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-rt-devel, devicetree, James Hilliard
The hardware-ECC OOB writer fills the main-data buffer with 0xff and
calls the selected page writer to program it together with the requested
OOB. Both the PIO and DMA page writers already finish with
nand_prog_page_end_op(), which sends PAGEPROG, waits and checks NAND
status. The OOB wrapper then calls that helper again, sending a second
program confirm without another setup or data transfer. A failure of
that extra command is returned even though the actual write has already
succeeded.
Return the page writer's result directly. It remains responsible for the
single program confirm and status check, including on the
bad-block-marker write path. Raw OOB writes use separate callbacks and
are unchanged.
Fixes: 25f815f66a14 ("mtd: nand: force drivers to explicitly send READ/PROG commands")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index dfdd8d8aaa6f..6c4e1740a245 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1824,15 +1824,10 @@ static int sunxi_nfc_hw_ecc_write_oob(struct nand_chip *nand, int page)
{
struct mtd_info *mtd = nand_to_mtd(nand);
u8 *buf = nand_get_data_buf(nand);
- int ret;
memset(buf, 0xff, mtd->writesize);
- ret = nand->ecc.write_page(nand, buf, 1, page);
- if (ret)
- return ret;
- /* Send command to program the OOB data */
- return nand_prog_page_end_op(nand);
+ return nand->ecc.write_page(nand, buf, 1, page);
}
static const struct sunxi_nfc_timings sun4i_a10_nfc_timings = {
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v7 09/16] mtd: rawnand: sunxi: use page reads to reposition small-page NAND
2026-10-15 1:33 [PATCH v7 00/16] mtd: rawnand: sunxi: fix transfers and support randomized OOB James Hilliard
` (7 preceding siblings ...)
2026-10-15 1:33 ` [PATCH v7 08/16] mtd: rawnand: sunxi: avoid a second program confirm for OOB writes James Hilliard
@ 2026-10-15 1:33 ` James Hilliard
2026-10-15 1:33 ` [PATCH v7 10/16] mtd: rawnand: sunxi: bound DMA batches by the user-data register bank James Hilliard
` (6 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: James Hilliard @ 2026-10-15 1:33 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Maxime Ripard,
Richard Genoud, Boris Brezillon, Masahiro Yamada, Brian Norris,
linux-mtd, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-rt-devel, devicetree, James Hilliard
The hardware-ECC reader moves between main data and OOB, and rereads raw
bytes when checking a possibly erased ECC step. It uses
nand_change_read_column_op(), which sends large-page random-output
commands and rejects NAND pages of 512 bytes or less. For a small-page
geometry accepted by hardware-ECC setup, these reads therefore fail when
they need to reposition, rather than using the small-page read commands.
Add a read-column helper that uses nand_read_page_op() on small pages.
The core selects READ0, READ1 or READOOB for the requested column and
sends the row address again. Pass the page number through the
erased-check path as well, and preserve errors from the resulting
operation. Keep the existing random-column operation for larger pages.
Select PIO for small pages even when DMA is available: the DMA PAGE_OP
sequencer uses large-page random-column commands internally.
This corrects read-side repositioning only; the write-column helper still
rejects small pages. Software-ECC and raw callbacks are unchanged.
This is a command-path correction found by inspection, not a claim that
ordinary 512+16 NAND becomes supported. The existing controller-ECC
limits still apply: it needs 512-byte ECC support and at least 28 parity
bytes plus four user-data bytes, so 512+16 remains rejected. No
small-page hardware validation is claimed.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 37 +++++++++++++++++++++++--------------
1 file changed, 23 insertions(+), 14 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 6c4e1740a245..f8abcb46f7f0 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1054,6 +1054,16 @@ static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand,
}
}
+static int sunxi_nfc_read_column(struct nand_chip *nand, int page,
+ unsigned int column, void *buf, unsigned int len)
+{
+ /* Small-page NAND needs a new READ0/READ1/READOOB and the page address. */
+ if (nand_to_mtd(nand)->writesize <= 512)
+ return nand_read_page_op(nand, page, column, buf, len);
+
+ return nand_change_read_column_op(nand, column, buf, len, false);
+}
+
/*
* Return 1 for an erased chunk or 0 for an uncorrectable chunk, with ECC
* statistics updated in either case. Negative values report transport errors.
@@ -1062,7 +1072,7 @@ static int sunxi_nfc_hw_ecc_read_error(struct nand_chip *nand,
u8 *data, int data_off,
u8 *oob, int oob_off,
unsigned int user_data_sz,
- unsigned int *max_bitflips)
+ unsigned int *max_bitflips, int page)
{
struct nand_ecc_ctrl *ecc = &nand->ecc;
unsigned int oob_len = ecc->bytes + user_data_sz;
@@ -1070,13 +1080,12 @@ static int sunxi_nfc_hw_ecc_read_error(struct nand_chip *nand,
/* Check the physical representation for bitflips in erased pages. */
if (nand->options & NAND_NEED_SCRAMBLING) {
- ret = nand_change_read_column_op(nand, data_off, data,
- ecc->size, false);
+ ret = sunxi_nfc_read_column(nand, page, data_off, data, ecc->size);
if (ret)
return ret;
}
- ret = nand_change_read_column_op(nand, oob_off, oob, oob_len, false);
+ ret = sunxi_nfc_read_column(nand, page, oob_off, oob, oob_len);
if (ret)
return ret;
@@ -1177,8 +1186,8 @@ static int sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand,
return 0;
if (!cur_off || *cur_off != (offset + mtd->writesize)) {
- ret = nand_change_read_column_op(nand, mtd->writesize + offset,
- NULL, 0, false);
+ ret = sunxi_nfc_read_column(nand, page, mtd->writesize + offset,
+ NULL, 0);
if (ret)
return ret;
}
@@ -1242,10 +1251,9 @@ static int sunxi_nfc_oob_read_batch(struct nand_chip *nand,
if (read->oob_required) {
/* Keep the existing PIO and DMA representations of parity. */
- ret = nand_change_read_column_op(nand, mtd->writesize + oob_off,
- read->dma ? oob : NULL,
- read->dma ? ecc->bytes + user_data_sz : 0,
- false);
+ ret = sunxi_nfc_read_column(nand, read->page, mtd->writesize + oob_off,
+ read->dma ? oob : NULL,
+ read->dma ? ecc->bytes + user_data_sz : 0);
if (ret)
return ret;
@@ -1281,7 +1289,7 @@ static int sunxi_nfc_oob_read_batch(struct nand_chip *nand,
ret = sunxi_nfc_hw_ecc_read_error(nand, read->buf + data_off, data_off,
nand->oob_poi + oob_off,
mtd->writesize + oob_off, user_data_sz,
- &read->max_bitflips);
+ &read->max_bitflips, read->page);
if (ret < 0)
return ret;
if (ret)
@@ -1321,7 +1329,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
int ret;
if (*cur_off != data_off) {
- ret = nand_change_read_column_op(nand, data_off, NULL, 0, false);
+ ret = sunxi_nfc_read_column(nand, page, data_off, NULL, 0);
if (ret)
return ret;
}
@@ -1331,7 +1339,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
return ret;
if (data_off + ecc->size != oob_off) {
- ret = nand_change_read_column_op(nand, oob_off, NULL, 0, false);
+ ret = sunxi_nfc_read_column(nand, page, oob_off, NULL, 0);
if (ret)
return ret;
}
@@ -2304,7 +2312,8 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
ecc->write_oob = sunxi_nfc_hw_ecc_write_oob;
mtd_set_ooblayout(mtd, &sunxi_nand_ooblayout_ops);
- if (nfc->dmac || nfc->use_mdma) {
+ /* The DMA page sequencer uses large-page random-column commands. */
+ if (mtd->writesize > 512 && (nfc->dmac || nfc->use_mdma)) {
ecc->read_page = sunxi_nfc_hw_ecc_read_page_dma;
ecc->read_subpage = sunxi_nfc_hw_ecc_read_subpage_dma;
ecc->write_page = sunxi_nfc_hw_ecc_write_page_dma;
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v7 10/16] mtd: rawnand: sunxi: bound DMA batches by the user-data register bank
2026-10-15 1:33 [PATCH v7 00/16] mtd: rawnand: sunxi: fix transfers and support randomized OOB James Hilliard
` (8 preceding siblings ...)
2026-10-15 1:33 ` [PATCH v7 09/16] mtd: rawnand: sunxi: use page reads to reposition small-page NAND James Hilliard
@ 2026-10-15 1:33 ` James Hilliard
2026-10-15 1:33 ` [PATCH v7 11/16] mtd: rawnand: sunxi: reuse ECC status within each transfer James Hilliard
` (5 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: James Hilliard @ 2026-10-15 1:33 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Maxime Ripard,
Richard Genoud, Boris Brezillon, Masahiro Yamada, Brian Norris,
linux-mtd, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-rt-devel, devicetree, James Hilliard
H6/H616 store ECC-protected OOB metadata, called user data, in 32
four-byte registers. The fields for successive ECC steps are
concatenated in that 128-byte bank. However, the existing allocator
fills the OOB space left after parity, allowing up to 32 user-data bytes
per step without limiting the page total. A whole-page DMA transfer can
exceed the bank. For example, 16 KiB main data plus 1280 OOB bytes with
BCH40/1024 leaves 160 user-data bytes; with 1664 OOB bytes it allocates
512. Register accesses then extend beyond the user-data bank.
PIO can handle these layouts because it reuses register zero for each
step. Capping the page's allocation would instead move parity offsets on
flash and break compatibility with pages written through PIO. Split DMA
transfers without changing that allocation or the logical main-data and
OOB offsets.
Fill the first batch up to the 128-byte limit. Transfer each remaining
step in a separate batch, using hardware slot and user-data register
zero after explicitly selecting its main column and spare-area base.
PAGE_OP generates later main-data columns from hardware slot numbers,
not relative to the selected initial column. A multi-slot batch starting
partway through a page would therefore revisit an earlier column.
Single-slot later batches avoid those generated column changes. Pages
that already fit keep one DMA batch.
Program length fields from the batch's logical starting step, and
consume its protected OOB and ECC status before reusing the registers.
Keep erased state, correction counts and trailing OOB handling
page-wide. On a failed DMA read, discard all partial statistics before
the existing PIO retry.
Use one page-read setup or program-begin for the whole page, and send
program-end only after every batch and any trailing OOB succeed. A write
may fall back to PIO only if DMA preparation fails before its first
batch; never retry a write after part of the page has been transferred.
Raw access and the OOB layout reported to MTD remain unchanged.
Fixes: 54dcd6aa69db ("mtd: rawnand: sunxi: introduce maximize variable user data length")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 202 ++++++++++++++++++++++++++------------
1 file changed, 139 insertions(+), 63 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index f8abcb46f7f0..c27cd8e04739 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -54,6 +54,7 @@
#define NFC_REG_H6_RDATA_1 0x004C
#define NFC_REG_A10_USER_DATA 0x0050
#define NFC_REG_H6_USER_DATA 0x0080
+#define NFC_H6_USER_DATA_REGS 32
#define NFC_REG_USER_DATA(nfc, x) (nfc->caps->reg_user_data + ((x) * 4))
#define NFC_REG_H6_USER_DATA_LEN 0x0070
/* A USER_DATA_LEN register can hold the length of 8 USER_DATA registers */
@@ -951,7 +952,36 @@ static u8 sunxi_nfc_user_data_sz(struct sunxi_nand_chip *sunxi_nand, int step)
return sunxi_nand->user_data_bytes[step];
}
-/* PIO uses register zero; DMA concatenates user data within each transfer. */
+/* Keep the on-flash layout, but fit each DMA batch in the user-data bank. */
+static int sunxi_nfc_dma_batch_steps(struct nand_chip *nand,
+ int first_step, int end_step)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ unsigned int user_data_sz = 0;
+ int i;
+
+ if (!nfc->caps->reg_user_data_len)
+ return end_step - first_step;
+
+ /*
+ * PAGE_OP generates main-data columns from hardware slot zero, not
+ * from the initial column. Use only slot zero after the first batch,
+ * so the explicit column change selects each remaining logical step.
+ */
+ if (first_step)
+ return 1;
+
+ for (i = first_step; i < end_step; i++) {
+ user_data_sz += sunxi_nfc_user_data_sz(sunxi_nand, i);
+ if (user_data_sz > NFC_H6_USER_DATA_REGS * sizeof(u32))
+ break;
+ }
+
+ return i - first_step;
+}
+
+/* PIO uses register zero; DMA concatenates user data within each batch. */
static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob,
unsigned int reg_index, bool bbm, int page,
unsigned int user_data_sz)
@@ -1369,30 +1399,32 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
pattern_found, step, 1);
}
-static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, u8 *buf,
- int oob_required, int page, int nchunks)
+static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand,
+ struct sunxi_nfc_ecc_read *read,
+ int first_step, int nchunks)
{
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
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_read read = {
- .buf = buf,
- .page = page,
- .oob_required = oob_required,
- .dma = true,
- };
- unsigned int corrected = mtd->ecc_stats.corrected;
- unsigned int failed = mtd->ecc_stats.failed;
struct scatterlist sg;
+ int page = read->page;
int ret, i;
u32 status, pattern_found, wait;
+ if (first_step) {
+ ret = sunxi_nfc_read_column(nand, page, first_step * ecc->size,
+ NULL, 0);
+ if (ret)
+ return ret;
+ }
+
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
if (ret)
return ret;
- ret = sunxi_nfc_dma_op_prepare(nfc, buf, ecc->size, nchunks,
+ ret = sunxi_nfc_dma_op_prepare(nfc, read->buf + first_step * ecc->size,
+ ecc->size, nchunks,
DMA_FROM_DEVICE, &sg);
if (ret)
return ret;
@@ -1400,7 +1432,12 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, u8 *buf,
sunxi_nfc_hw_ecc_enable(nand);
sunxi_nfc_reset_user_data_len(nfc);
for (i = 0; i < nchunks; i++)
- sunxi_nfc_set_user_data_len(nfc, sunxi_nfc_user_data_sz(sunxi_nand, i), i);
+ sunxi_nfc_set_user_data_len(nfc,
+ sunxi_nfc_user_data_sz(sunxi_nand, first_step + i), i);
+ /* exec_op() restores the page's spare base during column changes. */
+ if (first_step)
+ writel(mtd->writesize + sunxi_get_oob_offset(sunxi_nand, ecc, first_step),
+ nfc->regs + NFC_REG_SPARE_AREA(nfc));
sunxi_nfc_randomizer_config(nand, page, false);
sunxi_nfc_randomizer_enable(nand);
@@ -1432,10 +1469,32 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, u8 *buf,
status = readl(nfc->regs + NFC_REG_ECC_ST);
pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found);
pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found);
- ret = sunxi_nand->oob_ops->read_batch(nand, &read, status, pattern_found,
- 0, nchunks);
- if (ret)
- goto err_stats;
+
+ return sunxi_nand->oob_ops->read_batch(nand, read, status, pattern_found,
+ first_step, nchunks);
+}
+
+static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, u8 *buf,
+ int oob_required, int page, int nchunks)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ struct sunxi_nfc_ecc_read read = {
+ .buf = buf,
+ .page = page,
+ .oob_required = oob_required,
+ .dma = true,
+ };
+ unsigned int corrected = mtd->ecc_stats.corrected;
+ unsigned int failed = mtd->ecc_stats.failed;
+ int first_step, batch_steps, ret;
+
+ for (first_step = 0; first_step < nchunks; first_step += batch_steps) {
+ batch_steps = sunxi_nfc_dma_batch_steps(nand, first_step, nchunks);
+ ret = sunxi_nfc_hw_ecc_read_batch_dma(nand, &read, first_step, batch_steps);
+ if (ret)
+ goto err_stats;
+ }
ret = sunxi_nand->oob_ops->finish_read(nand, &read, NULL);
if (ret < 0)
@@ -1741,71 +1800,88 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
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 scatterlist sg;
- unsigned int reg_index = 0;
u32 wait;
- int ret, i;
+ int first_step, batch_steps, ret, i;
sunxi_nfc_select_chip(nand, nand->cur_cs);
- ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
- if (ret)
- return ret;
+ for (first_step = 0; first_step < ecc->steps; first_step += batch_steps) {
+ unsigned int reg_index = 0;
- ret = sunxi_nfc_dma_op_prepare(nfc, buf, ecc->size, ecc->steps,
- DMA_TO_DEVICE, &sg);
- if (ret)
- goto pio_fallback;
+ batch_steps = sunxi_nfc_dma_batch_steps(nand, first_step, ecc->steps);
+ ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
+ if (ret)
+ return ret;
- sunxi_nfc_reset_user_data_len(nfc);
- for (i = 0; i < ecc->steps; i++) {
- unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i);
- int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
- const u8 *oob = nand->oob_poi + oob_off;
+ ret = sunxi_nfc_dma_op_prepare(nfc, buf + first_step * ecc->size,
+ ecc->size, batch_steps, DMA_TO_DEVICE, &sg);
+ if (ret) {
+ /* Only retry before any part of the page has been transferred. */
+ if (first_step)
+ return ret;
+ goto pio_fallback;
+ }
- sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, reg_index, !i, page,
- user_data_sz);
- sunxi_nfc_set_user_data_len(nfc, user_data_sz, i);
- reg_index += user_data_sz / 4;
- }
+ sunxi_nfc_reset_user_data_len(nfc);
+ for (i = first_step; i < first_step + batch_steps; i++) {
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
+ const u8 *oob = nand->oob_poi + oob_off;
- ret = nand_prog_page_begin_op(nand, page, 0, NULL, 0);
- if (ret) {
- sunxi_nfc_dma_op_abort(nfc);
- sunxi_nfc_dma_op_cleanup(nfc, DMA_TO_DEVICE, &sg);
- return ret;
- }
+ sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, reg_index, !i,
+ page, user_data_sz);
+ sunxi_nfc_set_user_data_len(nfc, user_data_sz, i - first_step);
+ reg_index += user_data_sz / 4;
+ }
- sunxi_nfc_hw_ecc_enable(nand);
- sunxi_nfc_randomizer_config(nand, page, false);
- sunxi_nfc_randomizer_enable(nand);
+ if (first_step)
+ ret = nand_change_write_column_op(nand, first_step * ecc->size,
+ NULL, 0, false);
+ else
+ ret = nand_prog_page_begin_op(nand, page, 0, NULL, 0);
+ if (ret) {
+ sunxi_nfc_dma_op_abort(nfc);
+ sunxi_nfc_dma_op_cleanup(nfc, DMA_TO_DEVICE, &sg);
+ return ret;
+ }
- writel((NAND_CMD_RNDIN << 8) | NAND_CMD_PAGEPROG,
- nfc->regs + NFC_REG_WCMD_SET);
+ /* exec_op() restores the page's spare base during column changes. */
+ if (first_step)
+ writel(mtd->writesize + sunxi_get_oob_offset(sunxi_nand, ecc, first_step),
+ nfc->regs + NFC_REG_SPARE_AREA(nfc));
+ sunxi_nfc_hw_ecc_enable(nand);
+ sunxi_nfc_randomizer_config(nand, page, false);
+ sunxi_nfc_randomizer_enable(nand);
- wait = NFC_CMD_INT_FLAG;
+ writel((NAND_CMD_RNDIN << 8) | NAND_CMD_PAGEPROG,
+ nfc->regs + NFC_REG_WCMD_SET);
- if (nfc->use_mdma)
- wait |= NFC_DMA_INT_FLAG;
- else
- dma_async_issue_pending(nfc->dmac);
+ wait = NFC_CMD_INT_FLAG;
- writel(NFC_PAGE_OP | NFC_DATA_SWAP_METHOD |
- NFC_DATA_TRANS | NFC_ACCESS_DIR,
- nfc->regs + NFC_REG_CMD);
+ if (nfc->use_mdma)
+ wait |= NFC_DMA_INT_FLAG;
+ else
+ dma_async_issue_pending(nfc->dmac);
- ret = sunxi_nfc_wait_events(nfc, wait, false, 0);
- if (ret)
- sunxi_nfc_dma_op_abort(nfc);
+ writel(NFC_PAGE_OP | NFC_DATA_SWAP_METHOD |
+ NFC_DATA_TRANS | NFC_ACCESS_DIR,
+ nfc->regs + NFC_REG_CMD);
- sunxi_nfc_randomizer_disable(nand);
- sunxi_nfc_hw_ecc_disable(nand);
+ ret = sunxi_nfc_wait_events(nfc, wait, false, 0);
+ if (ret)
+ sunxi_nfc_dma_op_abort(nfc);
- sunxi_nfc_dma_op_cleanup(nfc, DMA_TO_DEVICE, &sg);
+ sunxi_nfc_randomizer_disable(nand);
+ sunxi_nfc_hw_ecc_disable(nand);
- if (ret)
- return ret;
+ sunxi_nfc_dma_op_cleanup(nfc, DMA_TO_DEVICE, &sg);
+
+ if (ret)
+ return ret;
+ }
if (oob_required || (nand->options & NAND_NEED_SCRAMBLING)) {
/* TODO: use DMA to transfer extra OOB bytes ? */
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v7 11/16] mtd: rawnand: sunxi: reuse ECC status within each transfer
2026-10-15 1:33 [PATCH v7 00/16] mtd: rawnand: sunxi: fix transfers and support randomized OOB James Hilliard
` (9 preceding siblings ...)
2026-10-15 1:33 ` [PATCH v7 10/16] mtd: rawnand: sunxi: bound DMA batches by the user-data register bank James Hilliard
@ 2026-10-15 1:33 ` James Hilliard
2026-10-15 1:33 ` [PATCH v7 12/16] mtd: rawnand: sunxi: avoid redundant column changes for extra OOB James Hilliard
` (4 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: James Hilliard @ 2026-10-15 1:33 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Maxime Ripard,
Richard Genoud, Boris Brezillon, Masahiro Yamada, Brian Norris,
linux-mtd, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-rt-devel, devicetree, James Hilliard
A completed DMA batch leaves one ECC status bitmap and one pattern
bitmap for all its steps. Four consecutive steps also share a packed
correction-counter register. Reading the pattern ID for every matching
step and the counter word for every corrected step repeats MMIO accesses
to the same completed result.
Snapshot status and pattern information once per completed transfer.
Reuse the status read when the controller places pattern-found bits in
the same register, and read the pattern ID only when a non-error step
needs it. Cache each packed counter word while consuming its steps.
Keep the snapshot local to a single transfer and invalidate the cached
counter before processing it. Each PIO step gets a fresh snapshot, as
does each DMA batch. The ECC decisions and returned correction counts
are unchanged, as is the page layout; only redundant register reads are
removed.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 88 +++++++++++++++++++++++++++------------
1 file changed, 61 insertions(+), 27 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index c27cd8e04739..8d83882592de 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -258,6 +258,7 @@ struct sunxi_nfc_timings {
};
struct sunxi_nfc_ecc_read;
+struct sunxi_nfc_ecc_snapshot;
/* OOB interpretation is independent of PIO/DMA command sequencing. */
struct sunxi_nfc_oob_ops {
@@ -268,7 +269,7 @@ struct sunxi_nfc_oob_ops {
void (*transform_bbm)(struct nand_chip *nand, int page, u8 *bbm);
/* Consume one PIO step or a DMA batch; negative values are I/O errors. */
int (*read_batch)(struct nand_chip *nand, struct sunxi_nfc_ecc_read *read,
- u32 status, u32 pattern_found, int first_step, int nsteps);
+ struct sunxi_nfc_ecc_snapshot *snapshot, int first_step, int nsteps);
/* Read any remaining OOB and return the maximum correction count. */
int (*finish_read)(struct nand_chip *nand, struct sunxi_nfc_ecc_read *read,
int *cur_off);
@@ -1136,23 +1137,64 @@ struct sunxi_nfc_ecc_read {
bool has_erased_chunk;
};
+struct sunxi_nfc_ecc_snapshot {
+ u32 status;
+ u32 pattern_found;
+ u32 pattern_id;
+ u32 count_reg;
+ u32 count;
+};
+
+static void sunxi_nfc_hw_ecc_read_status(struct nand_chip *nand,
+ struct sunxi_nfc_ecc_snapshot *snapshot)
+{
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ u32 pattern_found;
+
+ pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found);
+ snapshot->pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found);
+ if (nfc->caps->reg_pat_found == NFC_REG_ECC_ST)
+ snapshot->status = pattern_found;
+ else
+ snapshot->status = readl(nfc->regs + NFC_REG_ECC_ST);
+ snapshot->pattern_id = 0;
+ if (snapshot->pattern_found & ~snapshot->status)
+ snapshot->pattern_id = readl(nfc->regs + NFC_REG_PAT_ID(nfc));
+ /* A new operation, including every PIO step, invalidates the count word. */
+ snapshot->count_reg = ~0U;
+}
+
+static unsigned int
+sunxi_nfc_hw_ecc_read_count(struct nand_chip *nand,
+ struct sunxi_nfc_ecc_snapshot *snapshot, int step)
+{
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ u32 reg = NFC_REG_ECC_ERR_CNT(nfc, step);
+
+ /* Four consecutive DMA steps share one error-counter register. */
+ if (snapshot->count_reg != reg) {
+ snapshot->count = readl(nfc->regs + reg);
+ snapshot->count_reg = reg;
+ }
+
+ return NFC_ECC_ERR_CNT(step, snapshot->count);
+}
+
static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
- int step, u32 status, u32 pattern_found,
+ int step, struct sunxi_nfc_ecc_snapshot *snapshot,
unsigned int user_data_sz, bool *erased)
{
- struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
struct nand_ecc_ctrl *ecc = &nand->ecc;
- u32 tmp;
*erased = false;
- if (status & NFC_ECC_ERR(step))
+ if (snapshot->status & NFC_ECC_ERR(step))
return -EBADMSG;
- if (pattern_found & BIT(step)) {
+ if (snapshot->pattern_found & BIT(step)) {
u8 pattern;
- if (unlikely(!(readl(nfc->regs + NFC_REG_PAT_ID(nfc)) & BIT(step)))) {
+ if (unlikely(!(snapshot->pattern_id & BIT(step)))) {
pattern = 0x0;
} else {
pattern = 0xff;
@@ -1168,9 +1210,7 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
return 0;
}
- tmp = readl(nfc->regs + NFC_REG_ECC_ERR_CNT(nfc, step));
-
- return NFC_ECC_ERR_CNT(step, tmp);
+ return sunxi_nfc_hw_ecc_read_count(nand, snapshot, step);
}
/*
@@ -1239,7 +1279,7 @@ static int sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand,
/* Interpret one PIO step or a completed DMA batch before reusing its status. */
static int sunxi_nfc_oob_read_batch(struct nand_chip *nand,
struct sunxi_nfc_ecc_read *read,
- u32 status, u32 pattern_found,
+ struct sunxi_nfc_ecc_snapshot *snapshot,
int first_step, int nsteps)
{
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
@@ -1262,8 +1302,7 @@ static int sunxi_nfc_oob_read_batch(struct nand_chip *nand,
bitflips = sunxi_nfc_hw_ecc_correct(nand,
!read->dma || randomized ? data : NULL,
read->oob_required ? oob : NULL,
- i, status, pattern_found,
- user_data_sz, &erased);
+ i, snapshot, user_data_sz, &erased);
if (bitflips < 0) {
/* Preserve PIO data before the raw erased-page check. */
if (!read->dma && !randomized)
@@ -1302,7 +1341,7 @@ static int sunxi_nfc_oob_read_batch(struct nand_chip *nand,
sunxi_nfc_hw_ecc_update_stats(nand, &read->max_bitflips, bitflips);
}
- if (!(status & NFC_ECC_ERR_MSK(nfc)))
+ if (!(snapshot->status & NFC_ECC_ERR_MSK(nfc)))
return 0;
/* Finish consuming the batch's corrected OOB before raw error rereads. */
@@ -1310,7 +1349,7 @@ static int sunxi_nfc_oob_read_batch(struct nand_chip *nand,
int step = first_step + i;
int data_off, oob_off;
- if (!(status & NFC_ECC_ERR(i)))
+ if (!(snapshot->status & NFC_ECC_ERR(i)))
continue;
data_off = step * ecc->size;
@@ -1352,10 +1391,10 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
struct nand_ecc_ctrl *ecc = &nand->ecc;
+ struct sunxi_nfc_ecc_snapshot snapshot;
int data_off = step * ecc->size;
int oob_off = nand_to_mtd(nand)->writesize + sunxi_get_oob_offset(sunxi_nand, ecc, step);
int page = read->page;
- u32 pattern_found;
int ret;
if (*cur_off != data_off) {
@@ -1391,12 +1430,9 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
*cur_off = oob_off + ecc->bytes + user_data_sz;
- pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found);
- pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found);
+ sunxi_nfc_hw_ecc_read_status(nand, &snapshot);
- return sunxi_nand->oob_ops->read_batch(nand, read,
- readl(nfc->regs + NFC_REG_ECC_ST),
- pattern_found, step, 1);
+ return sunxi_nand->oob_ops->read_batch(nand, read, &snapshot, step, 1);
}
static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand,
@@ -1407,10 +1443,11 @@ static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand,
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_snapshot snapshot;
struct scatterlist sg;
int page = read->page;
int ret, i;
- u32 status, pattern_found, wait;
+ u32 wait;
if (first_step) {
ret = sunxi_nfc_read_column(nand, page, first_step * ecc->size,
@@ -1466,12 +1503,9 @@ static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand,
if (ret)
return ret;
- status = readl(nfc->regs + NFC_REG_ECC_ST);
- pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found);
- pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found);
+ sunxi_nfc_hw_ecc_read_status(nand, &snapshot);
- return sunxi_nand->oob_ops->read_batch(nand, read, status, pattern_found,
- first_step, nchunks);
+ return sunxi_nand->oob_ops->read_batch(nand, read, &snapshot, first_step, nchunks);
}
static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, u8 *buf,
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v7 12/16] mtd: rawnand: sunxi: avoid redundant column changes for extra OOB
2026-10-15 1:33 [PATCH v7 00/16] mtd: rawnand: sunxi: fix transfers and support randomized OOB James Hilliard
` (10 preceding siblings ...)
2026-10-15 1:33 ` [PATCH v7 11/16] mtd: rawnand: sunxi: reuse ECC status within each transfer James Hilliard
@ 2026-10-15 1:33 ` James Hilliard
2026-10-15 1:34 ` [PATCH v7 13/16] mtd: rawnand: sunxi: avoid duplicate chip setup before page commands James Hilliard
` (3 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: James Hilliard @ 2026-10-15 1:33 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Maxime Ripard,
Richard Genoud, Boris Brezillon, Masahiro Yamada, Brian Norris,
linux-mtd, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-rt-devel, devicetree, James Hilliard
Extra OOB is the spare-area tail after the last ECC step's protected
user data and parity. The PIO write loop tracks cur_off as an absolute
NAND column, starting at zero in the main-data area. The extra-OOB
helper instead compares it with an offset relative to the OOB start. It
therefore requests a column change even when the last step has already
reached the tail.
Add mtd->writesize to the comparison, as the read helper already does.
The comparison and the column-change command then refer to the same
byte. Keep the command when the cursor differs or is unknown, including
the DMA path, which passes no cursor. No transferred data or OOB layout
changes.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 8d83882592de..74fdca27ba10 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1612,7 +1612,7 @@ static int sunxi_nfc_hw_ecc_write_extra_oob(struct nand_chip *nand,
if (len <= 0)
return 0;
- if (!cur_off || *cur_off != offset) {
+ if (!cur_off || *cur_off != (offset + mtd->writesize)) {
ret = nand_change_write_column_op(nand, offset + mtd->writesize,
NULL, 0, false);
if (ret)
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v7 13/16] mtd: rawnand: sunxi: avoid duplicate chip setup before page commands
2026-10-15 1:33 [PATCH v7 00/16] mtd: rawnand: sunxi: fix transfers and support randomized OOB James Hilliard
` (11 preceding siblings ...)
2026-10-15 1:33 ` [PATCH v7 12/16] mtd: rawnand: sunxi: avoid redundant column changes for extra OOB James Hilliard
@ 2026-10-15 1:34 ` James Hilliard
2026-10-15 1:34 ` [PATCH v7 14/16] mtd: rawnand: sunxi: reduce user-data length register accesses James Hilliard
` (2 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: James Hilliard @ 2026-10-15 1:34 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Maxime Ripard,
Richard Genoud, Boris Brezillon, Masahiro Yamada, Brian Norris,
linux-mtd, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-rt-devel, devicetree, James Hilliard
sunxi_nfc_select_chip() programs chip selection, timing and the
spare-area base. The page-read callbacks and PIO page/subpage writers
call it directly, then immediately call nand_read_page_op() or
nand_prog_page_begin_op(). Those helpers dispatch through
sunxi_nfc_exec_op(), which performs the same selection before executing
the command. No controller access between the two selections needs the
first one.
Remove the duplicate calls from those callbacks. Keep selection in the
DMA writer: it accesses registers and prepares DMA before program-begin,
so it cannot rely on the later exec_op() call. This removes redundant
setup only; selection still runs for each executed operation, with no
new cached state to invalidate after resets or chip switches.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 12 ------------
1 file changed, 12 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 74fdca27ba10..4e6fcf1e7aaf 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1642,8 +1642,6 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
};
int ret, i, cur_off = 0;
- sunxi_nfc_select_chip(nand, nand->cur_cs);
-
ret = nand_read_page_op(nand, page, 0, NULL, 0);
if (ret)
return ret;
@@ -1669,8 +1667,6 @@ static int sunxi_nfc_hw_ecc_read_page_dma(struct nand_chip *nand, u8 *buf,
{
int ret;
- sunxi_nfc_select_chip(nand, nand->cur_cs);
-
ret = nand_read_page_op(nand, page, 0, NULL, 0);
if (ret)
return ret;
@@ -1696,8 +1692,6 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
};
int ret, i, cur_off = 0;
- sunxi_nfc_select_chip(nand, nand->cur_cs);
-
ret = nand_read_page_op(nand, page, 0, NULL, 0);
if (ret)
return ret;
@@ -1726,8 +1720,6 @@ 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;
- sunxi_nfc_select_chip(nand, nand->cur_cs);
-
ret = nand_read_page_op(nand, page, 0, NULL, 0);
if (ret)
return ret;
@@ -1751,8 +1743,6 @@ static int sunxi_nfc_hw_ecc_write_page(struct nand_chip *nand,
struct nand_ecc_ctrl *ecc = &nand->ecc;
int ret = 0, i, cur_off = 0;
- sunxi_nfc_select_chip(nand, nand->cur_cs);
-
ret = nand_prog_page_begin_op(nand, page, 0, NULL, 0);
if (ret)
return ret;
@@ -1796,8 +1786,6 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct nand_chip *nand,
struct nand_ecc_ctrl *ecc = &nand->ecc;
int ret = 0, i, cur_off = 0;
- sunxi_nfc_select_chip(nand, nand->cur_cs);
-
ret = nand_prog_page_begin_op(nand, page, 0, NULL, 0);
if (ret)
return ret;
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v7 14/16] mtd: rawnand: sunxi: reduce user-data length register accesses
2026-10-15 1:33 [PATCH v7 00/16] mtd: rawnand: sunxi: fix transfers and support randomized OOB James Hilliard
` (12 preceding siblings ...)
2026-10-15 1:34 ` [PATCH v7 13/16] mtd: rawnand: sunxi: avoid duplicate chip setup before page commands James Hilliard
@ 2026-10-15 1:34 ` James Hilliard
2026-10-15 1:34 ` [PATCH v7 15/16] dt-bindings: mtd: sunxi: Add randomized OOB flag James Hilliard
2026-10-15 1:34 ` [PATCH v7 16/16] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
15 siblings, 0 replies; 18+ messages in thread
From: James Hilliard @ 2026-10-15 1:34 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Maxime Ripard,
Richard Genoud, Boris Brezillon, Masahiro Yamada, Brian Norris,
linux-mtd, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-rt-devel, devicetree, James Hilliard
H6/H616 pack eight ECC steps' user-data length codes into each 32-bit
register. DMA currently clears all these registers, then
read-modify-writes one field per step, repeatedly accessing the same
words. Code zero means no user data; the other codes select lengths from
the controller's table.
Build each packed value in memory and write it once per DMA batch,
including zeros for unused slots. Take each length from the batch's
logical step but encode it in the corresponding batch-local hardware
slot. Rewrite every register on each batch rather than caching values
across resets or changes of chip.
Share the encoding with a PIO setter. PIO always uses hardware slot
zero, and its page callbacks clear all length registers before the chunk
loop. It can therefore write the slot-zero code directly, without a
read-modify-write. Keep the page-level clearing so unused slots are also
cleared after a DMA-to-PIO fallback. Older controllers without length
registers remain unaffected, and the chosen lengths and on-flash layout
do not change.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 79 +++++++++++++++++++++++++--------------
1 file changed, 50 insertions(+), 29 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 4e6fcf1e7aaf..601c272d8c54 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -57,7 +57,7 @@
#define NFC_H6_USER_DATA_REGS 32
#define NFC_REG_USER_DATA(nfc, x) (nfc->caps->reg_user_data + ((x) * 4))
#define NFC_REG_H6_USER_DATA_LEN 0x0070
-/* A USER_DATA_LEN register can hold the length of 8 USER_DATA registers */
+/* Each USER_DATA_LEN register holds eight 4-bit ECC-step user-data length codes. */
#define NFC_REG_USER_DATA_LEN_CAPACITY 8
#define NFC_REG_USER_DATA_LEN(nfc, step) \
(nfc->caps->reg_user_data_len + \
@@ -1018,35 +1018,60 @@ static void sunxi_nfc_reset_user_data_len(struct sunxi_nfc *nfc)
writel(0, nfc->regs + NFC_REG_USER_DATA_LEN(nfc, i));
}
-static void sunxi_nfc_set_user_data_len(struct sunxi_nfc *nfc,
- int len, int step)
+static int sunxi_nfc_user_data_len_code(struct sunxi_nfc *nfc, int len)
{
- bool found = false;
- u32 val;
int i;
+ for (i = 0; i < nfc->caps->nuser_data_tab; i++) {
+ if (len == nfc->caps->user_data_len_tab[i])
+ return i;
+ }
+
+ dev_warn(nfc->dev, "Unsupported length for user data reg: %d\n", len);
+ return -EINVAL;
+}
+
+static void sunxi_nfc_set_user_data_len_pio(struct sunxi_nfc *nfc, int len)
+{
+ int code;
+
/* not all SoCs have this register */
if (!nfc->caps->reg_user_data_len)
return;
- for (i = 0; i < nfc->caps->nuser_data_tab; i++) {
- if (len == nfc->caps->user_data_len_tab[i]) {
- found = true;
- break;
- }
- }
-
- if (!found) {
- dev_warn(nfc->dev,
- "Unsupported length for user data reg: %d\n", len);
+ code = sunxi_nfc_user_data_len_code(nfc, len);
+ if (code < 0)
return;
- }
- val = readl(nfc->regs + NFC_REG_USER_DATA_LEN(nfc, step));
+ /* PIO uses slot zero; the page callback clears all other slots. */
+ writel(FIELD_PREP(NFC_USER_DATA_LEN_MSK(0), code),
+ nfc->regs + NFC_REG_USER_DATA_LEN(nfc, 0));
+}
+
+static void sunxi_nfc_set_user_data_len_dma(struct nand_chip *nand,
+ int first_step, int nchunks)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ int first, step, len, code;
+ u32 val;
+
+ if (!nfc->caps->reg_user_data_len)
+ return;
- val &= ~NFC_USER_DATA_LEN_MSK(step);
- val |= field_prep(NFC_USER_DATA_LEN_MSK(step), i);
- writel(val, nfc->regs + NFC_REG_USER_DATA_LEN(nfc, step));
+ /* Write each packed register once, including zeroes for unused slots. */
+ for (first = 0; first < nfc->caps->max_ecc_steps;
+ first += NFC_REG_USER_DATA_LEN_CAPACITY) {
+ val = 0;
+ for (step = first; step < nchunks &&
+ step < first + NFC_REG_USER_DATA_LEN_CAPACITY; step++) {
+ len = sunxi_nfc_user_data_sz(sunxi_nand, first_step + step);
+ code = sunxi_nfc_user_data_len_code(nfc, len);
+ if (code >= 0)
+ val |= field_prep(NFC_USER_DATA_LEN_MSK(step), code);
+ }
+ writel(val, nfc->regs + NFC_REG_USER_DATA_LEN(nfc, first));
+ }
}
static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
@@ -1417,7 +1442,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
if (ret)
return ret;
- sunxi_nfc_set_user_data_len(nfc, user_data_sz, 0);
+ sunxi_nfc_set_user_data_len_pio(nfc, user_data_sz);
sunxi_nfc_randomizer_config(nand, page, false);
sunxi_nfc_randomizer_enable(nand);
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP,
@@ -1446,7 +1471,7 @@ static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand,
struct sunxi_nfc_ecc_snapshot snapshot;
struct scatterlist sg;
int page = read->page;
- int ret, i;
+ int ret;
u32 wait;
if (first_step) {
@@ -1467,10 +1492,7 @@ static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand,
return ret;
sunxi_nfc_hw_ecc_enable(nand);
- sunxi_nfc_reset_user_data_len(nfc);
- for (i = 0; i < nchunks; i++)
- sunxi_nfc_set_user_data_len(nfc,
- sunxi_nfc_user_data_sz(sunxi_nand, first_step + i), i);
+ sunxi_nfc_set_user_data_len_dma(nand, first_step, nchunks);
/* exec_op() restores the page's spare base during column changes. */
if (first_step)
writel(mtd->writesize + sunxi_get_oob_offset(sunxi_nand, ecc, first_step),
@@ -1580,7 +1602,7 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
sunxi_nfc_randomizer_config(nand, page, false);
sunxi_nfc_randomizer_enable(nand);
- sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step);
+ sunxi_nfc_set_user_data_len_pio(nfc, user_data_sz);
sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, nfc_step, bbm, page,
user_data_sz);
@@ -1847,7 +1869,7 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
goto pio_fallback;
}
- sunxi_nfc_reset_user_data_len(nfc);
+ sunxi_nfc_set_user_data_len_dma(nand, first_step, batch_steps);
for (i = first_step; i < first_step + batch_steps; i++) {
unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i);
int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
@@ -1855,7 +1877,6 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, reg_index, !i,
page, user_data_sz);
- sunxi_nfc_set_user_data_len(nfc, user_data_sz, i - first_step);
reg_index += user_data_sz / 4;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v7 15/16] dt-bindings: mtd: sunxi: Add randomized OOB flag
2026-10-15 1:33 [PATCH v7 00/16] mtd: rawnand: sunxi: fix transfers and support randomized OOB James Hilliard
` (13 preceding siblings ...)
2026-10-15 1:34 ` [PATCH v7 14/16] mtd: rawnand: sunxi: reduce user-data length register accesses James Hilliard
@ 2026-10-15 1:34 ` James Hilliard
2026-10-15 1:34 ` [PATCH v7 16/16] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
15 siblings, 0 replies; 18+ messages in thread
From: James Hilliard @ 2026-10-15 1:34 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Maxime Ripard,
Richard Genoud, Boris Brezillon, Masahiro Yamada, Brian Norris,
linux-mtd, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-rt-devel, devicetree, James Hilliard, Conor Dooley
Allwinner NAND firmware writes the bad-block marker as part of the
controller's randomizer stream. The mainline driver compensates those
two bytes so that the physical marker instead remains plain, even when
the rest of the page is scrambled. On H6/H616, the firmware also places
a fixed amount of ECC-protected user data before the first parity
region, whereas mainline distributes available OOB space among the ECC
steps.
Add an opt-in property selecting that firmware page format for the
configured controller hardware-ECC geometry. It identifies how existing
flash contents must be interpreted, rather than changing the format of
all NAND attached to a controller compatible. Without it, retain the
mainline marker representation and protected-OOB placement.
The property applies to normal ECC transfers. Raw operations still
bypass ECC and randomization and expose physical main and OOB bytes,
including the physical randomized marker.
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
.../devicetree/bindings/mtd/allwinner,sun4i-a10-nand.yaml | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/Documentation/devicetree/bindings/mtd/allwinner,sun4i-a10-nand.yaml b/Documentation/devicetree/bindings/mtd/allwinner,sun4i-a10-nand.yaml
index 9d061e2216cb..8ecafc40ddcb 100644
--- a/Documentation/devicetree/bindings/mtd/allwinner,sun4i-a10-nand.yaml
+++ b/Documentation/devicetree/bindings/mtd/allwinner,sun4i-a10-nand.yaml
@@ -79,6 +79,16 @@ patternProperties:
minimum: 0
maximum: 1
+ allwinner,randomized-oob:
+ type: boolean
+ description:
+ Select the normal-page format used by Allwinner NAND firmware for
+ the configured controller hardware-ECC geometry. Normal page
+ transfers use the controller randomizer, the bad block marker remains
+ in its data stream instead of being compensated to stay plain on
+ flash, and protected user data uses the controller-specific placement
+ expected by the firmware.
+
unevaluatedProperties: false
required:
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v7 16/16] mtd: rawnand: sunxi: support the Allwinner randomized OOB format
2026-10-15 1:33 [PATCH v7 00/16] mtd: rawnand: sunxi: fix transfers and support randomized OOB James Hilliard
` (14 preceding siblings ...)
2026-10-15 1:34 ` [PATCH v7 15/16] dt-bindings: mtd: sunxi: Add randomized OOB flag James Hilliard
@ 2026-10-15 1:34 ` James Hilliard
15 siblings, 0 replies; 18+ messages in thread
From: James Hilliard @ 2026-10-15 1:34 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Maxime Ripard,
Richard Genoud, Boris Brezillon, Masahiro Yamada, Brian Norris,
linux-mtd, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-rt-devel, devicetree, James Hilliard
Normal hardware-ECC reads also decode protected OOB, including the
bad-block marker and metadata used in bad-block-table scans. Allwinner
firmware leaves the marker in the randomizer stream, but the existing
driver compensates it to keep a plain physical marker. On H6/H616,
firmware also packs protected user data before the first parity region
rather than maximizing its allocation across the page. Those mismatches
prevent normal ECC reads from interpreting firmware-formatted pages and
their markers.
Honor allwinner,randomized-oob through a separate operations table
selected by an opt-in attach helper. Enable scrambling for normal
controller-ECC transfers and omit marker compensation. Reject software
or disabled ECC, and reject a step larger than the page before the core
can fall back to software ECC. Without the property, keep the existing
operations and geometry, including the plain marker on otherwise
scrambled NAND.
For H6/H616, reserve four user-data bytes per ECC step, capped at 16
bytes per page, and put the entire reservation in step zero. Later steps
have only parity. Apply the same reservation when maximizing ECC
strength so strength selection and actual layout agree. Older
controllers retain their fixed four-byte user-data field per step.
An erased page physically contains 0xff, which the randomizer turns into
non-0xff decoded bytes. Select the Allwinner firmware's
controller-specific erased-page predicate instead of the generic
bit-count check. Apply it to ECC-decoded protected bytes re-encoded with
the OOB randomizer stream. This is not a raw reread: bytes corrected by
ECC retain their corrected values. Older controllers require eight exact
0xff bytes, with a four-byte shortcut at block page zero and pages 127
modulo 128 for 1 KiB steps. The H6-generation predicate requires byte zero
and at least nine of ten bytes to be 0xff.
ECC exception mode can suppress errors for uniform physical data.
Include pattern-found steps in classification even without an ECC-error
bit, and ignore their correction counters. Keep decoded bytes until the
page-wide decision: an all-zero physical page takes precedence over the
spare check and returns zeroed data/OOB with ECC failures; an accepted
erased page returns all 0xff. Other failures retain decoded main and
protected OOB so bad-block and table scans can still inspect the marker
or signature.
Read complete pages for subpage requests because these checks classify a
whole page. Collect status during each PIO step or DMA batch, and defer
statistics until requested OOB reads succeed. Read unprotected OOB only
when requested, combining adjacent parity/tail ranges without crossing
protected user data. Preserve each step's randomizer phase and the
existing PIO/DMA representation of parity. Raw callbacks remain
unchanged.
All format policy is in the new callbacks and attach helper. Apart from
the opt-in dispatch, existing functions are unchanged: geometry
allocation, marker compensation, status collection and PIO/DMA transfer
sequencing continue to serve the existing operations tables as before.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 332 ++++++++++++++++++++++++++++++++++++++
1 file changed, 332 insertions(+)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 601c272d8c54..0b75713c04fa 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -349,6 +349,8 @@ 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: Allwinner randomized-OOB erased-page check on
+ * re-encoded protected OOB bytes
*/
struct sunxi_nfc_caps {
bool has_mdma;
@@ -377,6 +379,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);
};
/**
@@ -1153,7 +1156,15 @@ static int sunxi_nfc_hw_ecc_read_error(struct nand_chip *nand,
return ret >= 0;
}
+struct sunxi_nfc_allwinner_oob_status {
+ u32 error_steps;
+ u32 zero_steps;
+ unsigned int corrected;
+ unsigned int max_bitflips;
+};
+
struct sunxi_nfc_ecc_read {
+ struct sunxi_nfc_allwinner_oob_status allwinner_oob;
u8 *buf;
int page;
unsigned int max_bitflips;
@@ -2334,6 +2345,299 @@ static const struct sunxi_nfc_oob_ops sunxi_nfc_scrambled_plain_bbm_oob_ops = {
.finish_read = sunxi_nfc_oob_finish_read,
};
+/* Allwinner randomized-OOB format, selected only by allwinner,randomized-oob. */
+#define SUNXI_NFC_H6_ALLWINNER_OOB_MAX_USER_DATA_SZ 16
+
+static void sunxi_nfc_randomize_buf(u16 state, u8 *buf, unsigned int len)
+{
+ while (len--) {
+ *buf++ ^= state;
+ state = sunxi_nfc_randomizer_step(state, 8);
+ }
+}
+
+static void
+sunxi_nfc_allwinner_oob_record_status(struct nand_chip *nand,
+ struct sunxi_nfc_allwinner_oob_status *result,
+ int logical_step, int hw_step,
+ struct sunxi_nfc_ecc_snapshot *snapshot)
+{
+ u32 count;
+
+ if ((snapshot->pattern_found & BIT(hw_step)) &&
+ !(snapshot->pattern_id & BIT(hw_step)))
+ result->zero_steps |= BIT(logical_step);
+
+ /*
+ * Exception mode can suppress the ECC error for a physical pattern.
+ * Classify these steps with the Allwinner randomized-OOB page check too,
+ * and do not use their correction counters. Keep their decoded data until
+ * then.
+ */
+ if ((snapshot->status & NFC_ECC_ERR(hw_step)) ||
+ (snapshot->pattern_found & BIT(hw_step))) {
+ result->error_steps |= BIT(logical_step);
+ return;
+ }
+
+ count = sunxi_nfc_hw_ecc_read_count(nand, snapshot, hw_step);
+ result->corrected += count;
+ result->max_bitflips = max(result->max_bitflips, count);
+}
+
+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);
+
+ /*
+ * Older Allwinner firmware recognizes 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_h6_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_allwinner_oob_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;
+
+ /*
+ * Re-encode ECC-decoded protected OOB for the firmware's spare check.
+ * This is not a raw reread: ECC-corrected bytes stay corrected. Each
+ * 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_allwinner_oob_read_unprotected(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++) {
+ off = sunxi_get_ecc_offset(sunxi_nand, ecc, i);
+ len = ecc->bytes;
+ /* Keep decoded user data, but combine adjacent parity regions. */
+ while (i + 1 < ecc->steps &&
+ !sunxi_nfc_user_data_sz(sunxi_nand, i + 1)) {
+ len += ecc->bytes;
+ i++;
+ }
+ if (i + 1 == ecc->steps)
+ len = mtd->oobsize - off;
+
+ ret = sunxi_nfc_read_column(nand, page, mtd->writesize + off,
+ nand->oob_poi + off, len);
+ if (ret)
+ return ret;
+ }
+
+ /* Preserve each path's normal representation of ECC bytes. */
+ if (!dma) {
+ 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);
+ 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) {
+ /* 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_allwinner_oob_read_batch(struct nand_chip *nand,
+ struct sunxi_nfc_ecc_read *read,
+ struct sunxi_nfc_ecc_snapshot *snapshot,
+ int first_step, int nsteps)
+{
+ 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;
+ unsigned int reg_index = 0, user_data_sz;
+ int i;
+
+ /*
+ * The normal snapshot skips pattern IDs when every pattern has an
+ * ECC error. Allwinner randomized-OOB page classification needs those IDs
+ * as well.
+ */
+ if (snapshot->pattern_found &&
+ !(snapshot->pattern_found & ~snapshot->status))
+ snapshot->pattern_id = readl(nfc->regs + NFC_REG_PAT_ID(nfc));
+
+ for (i = 0; i < nsteps; i++, reg_index += user_data_sz / 4) {
+ int step = first_step + i;
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, step);
+ u8 *oob = nand->oob_poi + oob_off;
+
+ user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
+ sunxi_nfc_allwinner_oob_record_status(nand, &read->allwinner_oob,
+ step, i, snapshot);
+ if (!read->dma)
+ memcpy_fromio(read->buf + step * ecc->size,
+ nfc->regs + NFC_RAM0_BASE, ecc->size);
+ /* Page classification needs protected OOB even for data-only reads. */
+ sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, reg_index, !step,
+ read->page, user_data_sz);
+ }
+
+ return 0;
+}
+
+static int sunxi_nfc_allwinner_oob_finish_read(struct nand_chip *nand,
+ struct sunxi_nfc_ecc_read *read,
+ int *cur_off)
+{
+ struct sunxi_nfc_allwinner_oob_status *result = &read->allwinner_oob;
+ u8 *buf = read->buf;
+ bool oob_required = read->oob_required;
+ bool dma = read->dma;
+ int page = read->page;
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ struct nand_ecc_ctrl *ecc = &nand->ecc;
+ int ret;
+
+ /*
+ * Allwinner firmware 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_allwinner_oob_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_allwinner_oob_read_unprotected(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_allwinner_oob_read_subpage(struct nand_chip *nand,
+ u32 data_offs, u32 readlen,
+ u8 *buf, int page)
+{
+ return nand->ecc.read_page(nand, buf, false, page);
+}
+
+static unsigned int sunxi_nfc_h6_allwinner_oob_user_data_sz(int nsectors)
+{
+ return min(nsectors * USER_DATA_SZ,
+ SUNXI_NFC_H6_ALLWINNER_OOB_MAX_USER_DATA_SZ);
+}
+
+static unsigned int sunxi_nfc_allwinner_oob_min_user_data(struct nand_chip *nand,
+ int nsteps)
+{
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+
+ if (!nfc->caps->reg_user_data_len)
+ return sunxi_nfc_oob_min_user_data(nand, nsteps);
+
+ return sunxi_nfc_h6_allwinner_oob_user_data_sz(nsteps);
+}
+
+static int sunxi_nfc_allwinner_oob_init_user_data(struct nand_chip *nand, int nsteps)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+
+ /* Do not let the core fall back to software ECC for this format. */
+ if (nsteps <= 0)
+ return -EINVAL;
+
+ if (!nfc->caps->reg_user_data_len)
+ return 0;
+
+ sunxi_nand->user_data_bytes = devm_kzalloc(nfc->dev, nsteps, GFP_KERNEL);
+ if (!sunxi_nand->user_data_bytes)
+ return -ENOMEM;
+
+ /* H6/H616 pack all protected user data before step-zero parity. */
+ sunxi_nand->user_data_bytes[0] = sunxi_nfc_h6_allwinner_oob_user_data_sz(nsteps);
+ return 0;
+}
+
+static const struct sunxi_nfc_oob_ops sunxi_nfc_allwinner_oob_ops = {
+ .min_user_data = sunxi_nfc_allwinner_oob_min_user_data,
+ .init_user_data = sunxi_nfc_allwinner_oob_init_user_data,
+ .read_batch = sunxi_nfc_allwinner_oob_read_batch,
+ .finish_read = sunxi_nfc_allwinner_oob_finish_read,
+};
+
static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
struct nand_ecc_ctrl *ecc,
struct device_node *np)
@@ -2465,6 +2769,28 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
return 0;
}
+static int sunxi_nfc_allwinner_oob_attach_chip(struct nand_chip *nand,
+ struct device_node *np)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ int ret;
+
+ if (nand->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST)
+ return dev_err_probe(nfc->dev, -EINVAL,
+ "Allwinner OOB format requires controller ECC\n");
+
+ sunxi_nand->oob_ops = &sunxi_nfc_allwinner_oob_ops;
+ nand->options |= NAND_NEED_SCRAMBLING | NAND_NO_SUBPAGE_WRITE;
+ ret = sunxi_nand_hw_ecc_ctrl_init(nand, &nand->ecc, np);
+ if (ret)
+ return ret;
+
+ /* This format's spare and all-zero checks classify the whole page. */
+ nand->ecc.read_subpage = sunxi_nfc_allwinner_oob_read_subpage;
+ return 0;
+}
+
static int sunxi_nand_attach_chip(struct nand_chip *nand)
{
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
@@ -2490,6 +2816,9 @@ static int sunxi_nand_attach_chip(struct nand_chip *nand)
if (!ecc->size || !ecc->strength)
return -EINVAL;
+ if (of_property_read_bool(np, "allwinner,randomized-oob"))
+ return sunxi_nfc_allwinner_oob_attach_chip(nand, np);
+
sunxi_nand->oob_ops = nand->options & NAND_NEED_SCRAMBLING ?
&sunxi_nfc_scrambled_plain_bbm_oob_ops : &sunxi_nfc_plain_oob_ops;
@@ -2987,6 +3316,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 = {
@@ -3010,6 +3340,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 = {
@@ -3036,6 +3367,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_h6_nfc_spare_is_erased,
};
static const struct of_device_id sunxi_nfc_ids[] = {
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH v7 06/16] mtd: rawnand: sunxi: select the pattern ID for the current ECC step
2026-10-15 1:33 ` [PATCH v7 06/16] mtd: rawnand: sunxi: select the pattern ID for the current ECC step James Hilliard
@ 2026-10-15 1:50 ` sashiko-bot
0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-10-15 1:50 UTC (permalink / raw)
To: James Hilliard, Miquel Raynal, Richard Weinberger,
Vignesh Raghavendra, Sebastian Andrzej Siewior, Clark Williams,
Steven Rostedt, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Maxime Ripard,
Richard Genoud, Boris Brezillon, Masahiro Yamada, Brian Norris,
linux-mtd, linux-sunxi, linux-rt-devel, devicetree, linux-kernel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] mtd: rawnand: sunxi: PIO mode overwrites zero pattern with SRAM garbage
--
commit 6c354ef3bc88ac95cdf40b092e5c146de75aa789
Author: James Hilliard <james.hilliard1@gmail.com>
Subject: mtd: rawnand: sunxi: select the pattern ID for the current ECC step
This commit fixes the pattern-ID register check during ECC exception mode.
It updates the correction helper to test the bit corresponding to the
current hardware step rather than always checking bit zero, which ensures
DMA processing classifies physical all-zero and all-ones data blocks
correctly.
> diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
> index 57df88099206a..8256982d1d565 100644
> --- a/drivers/mtd/nand/raw/sunxi_nand.c
> +++ b/drivers/mtd/nand/raw/sunxi_nand.c
> @@ -1101,7 +1101,7 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
> if (pattern_found & BIT(step)) {
> u8 pattern;
>
> - if (unlikely(!(readl(nfc->regs + NFC_REG_PAT_ID(nfc)) & 0x1))) {
> + if (unlikely(!(readl(nfc->regs + NFC_REG_PAT_ID(nfc)) & BIT(step)))) {
> pattern = 0x0;
[Severity: Medium]
When the pattern is identified as 0x0, the *erased out parameter remains
false. Later in sunxi_nfc_oob_read_batch(), the code checks this boolean
and handles PIO mode (!read->dma) by copying directly from SRAM:
sunxi_nfc_oob_read_batch() {
...
if (erased) {
read->has_erased_chunk = true;
continue;
}
if (!read->dma)
memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, ecc->size);
...
}
If scrambling is enabled for a physical all-zero block read in PIO mode,
will this memcpy_fromio() overwrite the valid zeros populated in
sunxi_nfc_hw_ecc_correct() with descrambled pseudo-random garbage from
the SRAM?
It appears this regression leaves PIO broken while fixing DMA zero
pattern reads.
> } else {
> pattern = 0xff;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-submit-sunxi-nand-vendor-oob-layout-v1-v7-0-5d3fc6ad2da1@gmail.com?part=6
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-10-15 1:50 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-15 1:33 [PATCH v7 00/16] mtd: rawnand: sunxi: fix transfers and support randomized OOB James Hilliard
2026-10-15 1:33 ` [PATCH v7 01/16] mtd: rawnand: sunxi: centralize OOB handling James Hilliard
2026-10-15 1:33 ` [PATCH v7 02/16] mtd: rawnand: sunxi: use the logical step's OOB length in PIO James Hilliard
2026-10-15 1:33 ` [PATCH v7 03/16] mtd: rawnand: sunxi: propagate page-setup and erased-check errors James Hilliard
2026-10-15 1:33 ` [PATCH v7 04/16] mtd: rawnand: sunxi: drain interrupts before reusing the completion James Hilliard
2026-10-15 1:33 ` [PATCH v7 05/16] mtd: rawnand: sunxi: stop failed program operations and disable ECC James Hilliard
2026-10-15 1:33 ` [PATCH v7 06/16] mtd: rawnand: sunxi: select the pattern ID for the current ECC step James Hilliard
2026-10-15 1:50 ` sashiko-bot
2026-10-15 1:33 ` [PATCH v7 07/16] mtd: rawnand: sunxi: propagate buffer and column transfer errors James Hilliard
2026-10-15 1:33 ` [PATCH v7 08/16] mtd: rawnand: sunxi: avoid a second program confirm for OOB writes James Hilliard
2026-10-15 1:33 ` [PATCH v7 09/16] mtd: rawnand: sunxi: use page reads to reposition small-page NAND James Hilliard
2026-10-15 1:33 ` [PATCH v7 10/16] mtd: rawnand: sunxi: bound DMA batches by the user-data register bank James Hilliard
2026-10-15 1:33 ` [PATCH v7 11/16] mtd: rawnand: sunxi: reuse ECC status within each transfer James Hilliard
2026-10-15 1:33 ` [PATCH v7 12/16] mtd: rawnand: sunxi: avoid redundant column changes for extra OOB James Hilliard
2026-10-15 1:34 ` [PATCH v7 13/16] mtd: rawnand: sunxi: avoid duplicate chip setup before page commands James Hilliard
2026-10-15 1:34 ` [PATCH v7 14/16] mtd: rawnand: sunxi: reduce user-data length register accesses James Hilliard
2026-10-15 1:34 ` [PATCH v7 15/16] dt-bindings: mtd: sunxi: Add randomized OOB flag James Hilliard
2026-10-15 1:34 ` [PATCH v7 16/16] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox