* [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format
@ 2026-09-14 21:09 James Hilliard
2026-09-14 21:09 ` [PATCH v6 01/18] mtd: rawnand: sunxi: drain interrupts before reusing the completion James Hilliard
` (17 more replies)
0 siblings, 18 replies; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:09 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard, stable
Allwinner NAND firmware leaves the bad-block marker in the randomizer
data stream. On H6/H616 it also places all protected user data before the
first ECC step. These choices differ from the mainline format, which
keeps the physical marker plain and maximizes the H6/H616 user-data area.
Add allwinner,randomized-oob to select the firmware format for the
configured hardware-ECC geometry. Older controllers keep their fixed
four-byte-per-step user-data layout; H6/H616 use four bytes per 1 KiB
step, capped at 16 bytes, packed before the first ECC step. Without the
property, retain the existing marker handling and OOB layout.
Normal hardware-ECC accesses use the controller randomizer. MTD_OPS_RAW
continues to bypass both ECC and randomization and expose physical data
and OOB, including randomized markers stored by firmware.
Address the ECC-error paths as well. In randomized-OOB mode, use the
controller-specific vendor spare-byte erased-page heuristic on the
protected user data from the original hardware read. Older controllers
use exact erased-spare checks, including their first-page and page-127
signatures; H616 requires byte zero and at least nine of ten spare bytes
to be 0xff. Accepted erased pages return all-0xff data and OOB without a
raw reread. An all-zero physical page instead returns a bad marker and
an ECC failure.
Keep ECC exception mode enabled for physical-pattern reporting. Uniform
physical steps can suppress hardware ECC errors, so classify them with the
vendor page check too and exclude their correction counters.
Other ECC failures retain the original decoded data and protected OOB for
bad-block and flash-BBT pattern scans. PIO and DMA share this page-wide
classification, including randomized-format subpage reads. Plain-marker
mode keeps its existing physical erased-chunk check.
Start with fixes for interrupt/completion ordering, PIO OOB lengths,
per-step pattern IDs, read/write error handling, duplicate OOB program
confirms and the extra-OOB cursor. Follow these with the small-page
command fix and the DMA register-bank fix, then the OOB-helper cleanup,
binding and randomized-format support.
Use page-addressed reads to reposition small-page NAND in hardware-ECC
read paths, retaining transport errors and the existing geometry checks.
Select PIO for those pages because the DMA sequencer uses large-page
random-column commands. Large-page DMA and the on-flash layout are unchanged.
Bound H6/H616 DMA batches by the 128-byte user-data register bank.
Keep the existing default OOB allocation and ECC offsets. Fill the first
batch to the bank limit, then use one hardware slot per remaining logical
step: PAGE_OP generates later main-data columns independently of the initial
column. Preserve page-wide ECC accounting, DMA support and a single final
program confirm.
All of these fixes apply without the randomized-OOB property.
Finish with optimizations to combine contiguous unprotected OOB reads and
reduce repeated chip setup and register accesses without changing the
page format.
Assisted-by: Codex:gpt-6-astra
Signed-off-by: James Hilliard <james.hilliard1@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
To: Miquel Raynal <miquel.raynal@bootlin.com>
To: Richard Weinberger <richard@nod.at>
To: Vignesh Raghavendra <vigneshr@ti.com>
To: Chen-Yu Tsai <wens@kernel.org>
To: Jernej Skrabec <jernej.skrabec@gmail.com>
To: Samuel Holland <samuel@sholland.org>
To: Richard Genoud <richard.genoud@bootlin.com>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
To: Maxime Ripard <mripard@kernel.org>
To: Masahiro Yamada <yamada.masahiro@socionext.com>
To: Boris Brezillon <bbrezillon@kernel.org>
To: Brian Norris <computersforpeace@gmail.com>
Cc: linux-mtd@lists.infradead.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-sunxi@lists.linux.dev
Cc: linux-kernel@vger.kernel.org
Cc: devicetree@vger.kernel.org
---
James Hilliard (18):
mtd: rawnand: sunxi: drain interrupts before reusing the completion
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: 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: avoid redundant column changes for extra OOB
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: clarify OOB register and step handling
dt-bindings: mtd: sunxi: Add randomized OOB flag
mtd: rawnand: sunxi: support randomized OOB formats
mtd: rawnand: sunxi: select the packed H6/H616 OOB layout
mtd: rawnand: sunxi: combine contiguous unprotected OOB reads
mtd: rawnand: sunxi: avoid duplicate chip setup before page commands
mtd: rawnand: sunxi: reduce user-data length register accesses
mtd: rawnand: sunxi: reuse ECC status within each DMA read
.../bindings/mtd/allwinner,sun4i-a10-nand.yaml | 10 +
drivers/mtd/nand/raw/sunxi_nand.c | 1178 ++++++++++++++------
2 files changed, 846 insertions(+), 342 deletions(-)
---
base-commit: 7e874b1750a40f3dc9a629aeb72eba09c77f77e9
change-id: 20260810-submit-sunxi-nand-vendor-oob-layout-v1-e3114d10cc9c
Best regards,
--
James Hilliard <james.hilliard1@gmail.com>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6 01/18] mtd: rawnand: sunxi: drain interrupts before reusing the completion
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
@ 2026-09-14 21:09 ` James Hilliard
2026-09-14 21:09 ` [PATCH v6 02/18] mtd: rawnand: sunxi: use the logical step's OOB length in PIO James Hilliard
` (16 subsequent siblings)
17 siblings, 0 replies; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:09 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard, stable
A timed-out wait can return while the interrupt handler is still using
the previous operation's interrupt status and enable mask. Starting the
next wait calls init_completion() on the same object, which can reset its
waitqueue lock while the old handler calls complete(). The old handler
can also complete the new wait or restore an obsolete interrupt mask.
Initialize the completion once in probe and use reinit_completion() for
each interrupt-driven wait. On timeout, disable and synchronize the IRQ
before masking controller interrupts and acknowledging the waited-for
events. Draining the handler first also prevents a partial-event handler
from rewriting the enable mask after it has been cleared. Flush these
register writes before re-enabling the IRQ line, leaving the controller's
interrupt sources masked.
For successful waits, move complete() after the handler's register updates
so that the next operation cannot race with those updates. Keep polling
unchanged and leave controller and DMA abort handling to the existing
callers.
Fixes: 1fef62c1423b ("mtd: nand: add sunxi NAND flash controller support")
Cc: stable@vger.kernel.org
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 f41feebe4257..e50cf5b6a730 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -374,6 +374,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
@@ -393,6 +394,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;
@@ -414,12 +416,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;
}
@@ -435,16 +438,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 {
@@ -457,6 +463,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");
@@ -2617,6 +2629,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))
@@ -2625,6 +2638,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 [flat|nested] 20+ messages in thread
* [PATCH v6 02/18] mtd: rawnand: sunxi: use the logical step's OOB length in PIO
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
2026-09-14 21:09 ` [PATCH v6 01/18] mtd: rawnand: sunxi: drain interrupts before reusing the completion James Hilliard
@ 2026-09-14 21:09 ` James Hilliard
2026-09-14 21:09 ` [PATCH v6 03/18] mtd: rawnand: sunxi: propagate page-setup and erased-check errors James Hilliard
` (15 subsequent siblings)
17 siblings, 0 replies; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:09 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard, stable
PIO transfers always use hardware ECC slot zero, even when transferring a
later logical step. The user-data length programmed into that slot can
differ from the first step's length on H6/H616 controllers.
The ECC correction and protected-OOB write helpers instead derive the
length from the hardware slot. Pattern handling can consequently fill
beyond the current step's OOB region, including beyond the page's OOB
buffer for its last step. Writes can also fetch bytes outside the current
step's protected user data.
Pass the logical step's user-data length to both helpers, as the protected
OOB read helper already does. Keep the hardware slot for register access.
DMA callers pass their existing per-step length, without changing the
on-flash layout.
Fixes: 54dcd6aa69db ("mtd: rawnand: sunxi: introduce maximize variable user data length")
Cc: stable@vger.kernel.org
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index e50cf5b6a730..172c7c4b0ba6 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1011,11 +1011,11 @@ 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)
+ bool bbm, int page,
+ unsigned int user_data_sz)
{
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] = {};
/* Randomize the Bad Block Marker. */
@@ -1069,11 +1069,9 @@ static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand,
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;
@@ -1155,7 +1153,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
ret = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL,
nfc_step, readl(nfc->regs + NFC_REG_ECC_ST),
- pattern_found, &erased);
+ pattern_found, user_data_sz, &erased);
if (erased)
return 1;
@@ -1323,7 +1321,7 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
ret = sunxi_nfc_hw_ecc_correct(nand, randomized ? data : NULL,
oob_required ? oob : NULL,
i, status, pattern_found,
- &erased);
+ user_data_sz, &erased);
/* ECC errors are handled in the second loop. */
if (ret < 0)
@@ -1420,7 +1418,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,
@@ -1684,7 +1683,8 @@ 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, i, !i, page,
+ user_data_sz);
sunxi_nfc_set_user_data_len(nfc, user_data_sz, i);
}
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6 03/18] mtd: rawnand: sunxi: propagate page-setup and erased-check errors
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
2026-09-14 21:09 ` [PATCH v6 01/18] mtd: rawnand: sunxi: drain interrupts before reusing the completion James Hilliard
2026-09-14 21:09 ` [PATCH v6 02/18] mtd: rawnand: sunxi: use the logical step's OOB length in PIO James Hilliard
@ 2026-09-14 21:09 ` James Hilliard
2026-09-14 21:09 ` [PATCH v6 04/18] mtd: rawnand: sunxi: stop failed program operations and disable ECC James Hilliard
` (14 subsequent siblings)
17 siblings, 0 replies; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:09 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard, stable
The hardware-ECC read callbacks ignore errors from nand_read_page_op().
A failed page-setup command can leave the previous page available, so a
later successful transfer can return stale data instead of the requested
page. Stop before starting the PIO or DMA transfer when page setup fails.
The physical rereads used to check an uncorrectable chunk for erased
contents also ignore errors from nand_change_read_column_op(). A failed
transfer can therefore leave stale data in the buffers used for the
erased check and turn an I/O failure into an ECC result.
Share the erased-chunk fallback between PIO and DMA and stop before
checking the buffers when either reread fails. Preserve the existing
erased-chunk test and corrected-bit accounting when both reads succeed.
Before retrying a failed DMA read in PIO, restore only its corrected and
failed counters so partial results are not counted twice. Leave bad-block
and BBT counters alone. Route PIO page and subpage error exits through
ECC disable before returning the transfer error.
Fixes: 97d90da8a886 ("mtd: nand: provide several helpers to do common NAND operations")
Fixes: 25f815f66a14 ("mtd: nand: force drivers to explicitly send READ/PROG commands")
Cc: stable@vger.kernel.org
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 120 +++++++++++++++++++++++---------------
1 file changed, 72 insertions(+), 48 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 172c7c4b0ba6..d5d8d383b6d1 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1067,6 +1067,40 @@ static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand,
}
}
+/*
+ * 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,
+ 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) {
+ ret = nand_change_read_column_op(nand, data_off, data,
+ ecc->size, false);
+ if (ret)
+ return ret;
+ }
+
+ 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);
+
+ sunxi_nfc_hw_ecc_update_stats(nand, max_bitflips, ret);
+
+ return ret >= 0;
+}
+
static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
int step, u32 status, u32 pattern_found,
unsigned int user_data_sz, bool *erased)
@@ -1115,7 +1149,6 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
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;
@@ -1158,25 +1191,13 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
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
+ if (!(nand->options & NAND_NEED_SCRAMBLING))
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;
+ return sunxi_nfc_hw_ecc_read_error(nand, data, data_off,
+ oob, oob_off, user_data_sz,
+ max_bitflips);
} else {
memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, ecc->size);
@@ -1193,7 +1214,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
sunxi_nfc_hw_ecc_update_stats(nand, max_bitflips, ret);
- return raw_mode;
+ return 0;
}
/*
@@ -1260,6 +1281,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
+ unsigned int corrected = mtd->ecc_stats.corrected;
+ unsigned int failed = mtd->ecc_stats.failed;
unsigned int max_bitflips = 0;
int ret, i, raw_mode = 0;
struct scatterlist sg;
@@ -1354,29 +1377,18 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
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)
+ ret = sunxi_nfc_hw_ecc_read_error(nand, data, data_off, oob,
+ mtd->writesize + oob_off,
+ user_data_sz,
+ &max_bitflips);
+ if (ret < 0) {
+ /* The caller retries the whole read in PIO mode. */
+ mtd->ecc_stats.corrected = corrected;
+ mtd->ecc_stats.failed = failed;
+ return ret;
+ }
+ if (ret)
raw_mode = 1;
-
- sunxi_nfc_hw_ecc_update_stats(nand, &max_bitflips, ret);
}
}
@@ -1471,7 +1483,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);
@@ -1487,7 +1501,7 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
&cur_off, &max_bitflips,
i, oob_required, page);
if (ret < 0)
- return ret;
+ goto out;
else if (ret)
raw_mode = true;
}
@@ -1496,9 +1510,11 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi, &cur_off,
!raw_mode, page);
+ ret = max_bitflips;
+out:
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,
@@ -1508,7 +1524,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);
@@ -1532,7 +1550,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);
@@ -1550,12 +1570,14 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
&cur_off, &max_bitflips, i,
false, page);
if (ret < 0)
- return ret;
+ goto out;
}
+ ret = max_bitflips;
+out:
sunxi_nfc_hw_ecc_disable(nand);
- return max_bitflips;
+ return ret;
}
static int sunxi_nfc_hw_ecc_read_subpage_dma(struct nand_chip *nand,
@@ -1567,7 +1589,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 [flat|nested] 20+ messages in thread
* [PATCH v6 04/18] mtd: rawnand: sunxi: stop failed program operations and disable ECC
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
` (2 preceding siblings ...)
2026-09-14 21:09 ` [PATCH v6 03/18] mtd: rawnand: sunxi: propagate page-setup and erased-check errors James Hilliard
@ 2026-09-14 21:09 ` James Hilliard
2026-09-14 21:09 ` [PATCH v6 05/18] mtd: rawnand: sunxi: select the pattern ID for the current ECC step James Hilliard
` (13 subsequent siblings)
17 siblings, 0 replies; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:09 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard, stable
The PIO page and subpage write callbacks leave ECC enabled if a chunk
transfer fails. They also ignore program-setup errors, as does the DMA
page writer, and can continue transferring data and issuing PAGEPROG
after the setup command has failed.
Check program setup before enabling ECC or transferring data. If DMA
preparation has already succeeded, abort the queued operation and unmap
its buffer before returning the setup error.
Route PIO chunk failures through ECC disable and return the original
error. Issue the program-end command only after the transfers succeed,
preserving the existing successful-write sequence.
Fixes: 1fef62c1423b ("mtd: nand: add sunxi NAND flash controller support")
Fixes: 25f815f66a14 ("mtd: nand: force drivers to explicitly send READ/PROG commands")
Cc: stable@vger.kernel.org
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 d5d8d383b6d1..5d88ad3b8f70 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1610,11 +1610,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);
@@ -1629,14 +1631,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);
}
@@ -1650,11 +1655,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);
@@ -1670,10 +1677,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);
}
@@ -1712,7 +1722,12 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
sunxi_nfc_set_user_data_len(nfc, user_data_sz, i);
}
- 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 [flat|nested] 20+ messages in thread
* [PATCH v6 05/18] mtd: rawnand: sunxi: select the pattern ID for the current ECC step
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
` (3 preceding siblings ...)
2026-09-14 21:09 ` [PATCH v6 04/18] mtd: rawnand: sunxi: stop failed program operations and disable ECC James Hilliard
@ 2026-09-14 21:09 ` James Hilliard
2026-09-14 21:09 ` [PATCH v6 06/18] mtd: rawnand: sunxi: propagate buffer and column transfer errors James Hilliard
` (12 subsequent siblings)
17 siblings, 0 replies; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:09 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard, stable
The pattern ID register has one bit per hardware ECC step, distinguishing
all-zero from all-ones data. The correction helper checks the current
step's pattern-found bit but always reads pattern ID bit zero.
PIO reuses hardware step zero, so this works there. DMA processes several
steps at once: a later all-ones step can be filled with zeros and reported
as a successful read if pattern ID bit zero is clear. Conversely, a later
all-zero step can be treated as erased when bit zero is set.
Use the current hardware step's pattern ID bit, matching the error and
pattern-found bits already checked by the helper.
Fixes: 614049a8d904 ("mtd: nand: sunxi: add support for DMA assisted operations")
Cc: stable@vger.kernel.org
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 5d88ad3b8f70..3f506d0666a1 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1117,7 +1117,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 [flat|nested] 20+ messages in thread
* [PATCH v6 06/18] mtd: rawnand: sunxi: propagate buffer and column transfer errors
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
` (4 preceding siblings ...)
2026-09-14 21:09 ` [PATCH v6 05/18] mtd: rawnand: sunxi: select the pattern ID for the current ECC step James Hilliard
@ 2026-09-14 21:09 ` James Hilliard
2026-09-14 21:09 ` [PATCH v6 07/18] mtd: rawnand: sunxi: avoid a second program confirm for OOB writes James Hilliard
` (11 subsequent siblings)
17 siblings, 0 replies; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:09 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard, stable
The PIO buffer helpers stop on a FIFO or completion timeout but return no
status. Their hardware-ECC callers also ignore column-change failures,
including the extra-OOB paths used after DMA. A read can therefore report
success with incomplete data, and a write can issue PAGEPROG after a
failed transfer.
Return transfer errors through the randomizer and OOB helpers to the
page callbacks. Always disable the randomizer after a buffer transfer,
and let the existing page error exits disable ECC and skip program-end.
Only advance the extra-OOB cursor after a successful transfer.
Keep the corrected-bit count separate from transport status so successful
OOB reads do not overwrite it. If any legacy DMA OOB read fails, discard
the ECC statistics from that attempt before the caller retries in PIO.
Leave the successful transfer sequence and OOB representation unchanged.
Fixes: 1fef62c1423b ("mtd: nand: add sunxi NAND flash controller support")
Cc: stable@vger.kernel.org
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 220 ++++++++++++++++++++++++--------------
1 file changed, 141 insertions(+), 79 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 3f506d0666a1..72b79f893e29 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -644,7 +644,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);
@@ -660,7 +660,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;
@@ -672,17 +672,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);
@@ -698,7 +700,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);
@@ -712,10 +714,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 */
@@ -865,23 +869,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)
@@ -1152,17 +1164,25 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
u32 pattern_found;
bool bbm = !step;
bool erased;
- int ret;
+ int ret, bitflips;
/* 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);
+ 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)
@@ -1184,13 +1204,13 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found);
pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found);
- ret = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL,
- nfc_step, readl(nfc->regs + NFC_REG_ECC_ST),
- pattern_found, user_data_sz, &erased);
+ bitflips = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL,
+ nfc_step, readl(nfc->regs + NFC_REG_ECC_ST),
+ pattern_found, user_data_sz, &erased);
if (erased)
return 1;
- if (ret < 0) {
+ if (bitflips < 0) {
if (!(nand->options & NAND_NEED_SCRAMBLING))
memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE,
ecc->size);
@@ -1202,17 +1222,22 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
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);
+ ret = nand_change_read_column_op(nand, oob_off, NULL, 0,
+ false);
+ if (ret)
+ return ret;
+ ret = sunxi_nfc_randomizer_read_buf(nand, oob,
+ ecc->bytes + user_data_sz,
+ true, page);
+ if (ret)
+ return ret;
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);
+ sunxi_nfc_hw_ecc_update_stats(nand, max_bitflips, bitflips);
return 0;
}
@@ -1245,31 +1270,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;
}
static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf,
@@ -1340,21 +1373,24 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
u8 *data = buf + data_off;
u8 *oob = nand->oob_poi + oob_off;
bool erased;
+ int bitflips;
- ret = sunxi_nfc_hw_ecc_correct(nand, randomized ? data : NULL,
- oob_required ? oob : NULL,
- i, status, pattern_found,
- user_data_sz, &erased);
+ bitflips = sunxi_nfc_hw_ecc_correct(nand, randomized ? data : NULL,
+ oob_required ? oob : NULL,
+ i, status, pattern_found,
+ user_data_sz, &erased);
/* ECC errors are handled in the second loop. */
- if (ret < 0)
+ if (bitflips < 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);
+ ret = nand_change_read_column_op(nand, mtd->writesize + oob_off,
+ oob, ecc->bytes + user_data_sz,
+ false);
+ if (ret)
+ goto err_stats;
sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, !i,
page, user_data_sz);
@@ -1363,7 +1399,7 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
if (erased)
raw_mode = 1;
- sunxi_nfc_hw_ecc_update_stats(nand, &max_bitflips, ret);
+ sunxi_nfc_hw_ecc_update_stats(nand, &max_bitflips, bitflips);
}
if (status & NFC_ECC_ERR_MSK(nfc)) {
@@ -1381,23 +1417,27 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
mtd->writesize + oob_off,
user_data_sz,
&max_bitflips);
- if (ret < 0) {
- /* The caller retries the whole read in PIO mode. */
- mtd->ecc_stats.corrected = corrected;
- mtd->ecc_stats.failed = failed;
- return ret;
- }
+ if (ret < 0)
+ goto err_stats;
if (ret)
raw_mode = 1;
}
}
- if (oob_required)
- sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi,
- NULL, !raw_mode,
- page);
+ if (oob_required) {
+ ret = sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi,
+ NULL, !raw_mode, page);
+ if (ret)
+ goto err_stats;
+ }
return max_bitflips;
+
+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,
@@ -1415,13 +1455,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)
@@ -1447,27 +1495,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,
@@ -1506,9 +1562,12 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
raw_mode = true;
}
- if (oob_required)
- sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi, &cur_off,
- !raw_mode, page);
+ if (oob_required) {
+ ret = sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi, &cur_off,
+ !raw_mode, page);
+ if (ret)
+ goto out;
+ }
ret = max_bitflips;
out:
@@ -1635,8 +1694,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);
@@ -1759,10 +1818,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 [flat|nested] 20+ messages in thread
* [PATCH v6 07/18] mtd: rawnand: sunxi: avoid a second program confirm for OOB writes
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
` (5 preceding siblings ...)
2026-09-14 21:09 ` [PATCH v6 06/18] mtd: rawnand: sunxi: propagate buffer and column transfer errors James Hilliard
@ 2026-09-14 21:09 ` James Hilliard
2026-09-14 21:09 ` [PATCH v6 08/18] mtd: rawnand: sunxi: avoid redundant column changes for extra OOB James Hilliard
` (10 subsequent siblings)
17 siblings, 0 replies; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:09 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard, stable
The hardware-ECC OOB writer delegates to the page writer, which already
issues PAGEPROG, waits for completion and checks the NAND status. The OOB
wrapper then calls nand_prog_page_end_op() again, sending an extra program
confirm without a corresponding program setup. An error on this redundant
operation is returned even though the page writer has already succeeded.
Return the page writer's result directly. This leaves both PIO and DMA
responsible for completing their own program operation and preserves their
error results, including for bad-block-marker writes.
Fixes: 25f815f66a14 ("mtd: nand: force drivers to explicitly send READ/PROG commands")
Cc: stable@vger.kernel.org
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 72b79f893e29..41d6560d7df7 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1843,15 +1843,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 [flat|nested] 20+ messages in thread
* [PATCH v6 08/18] mtd: rawnand: sunxi: avoid redundant column changes for extra OOB
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
` (6 preceding siblings ...)
2026-09-14 21:09 ` [PATCH v6 07/18] mtd: rawnand: sunxi: avoid a second program confirm for OOB writes James Hilliard
@ 2026-09-14 21:09 ` James Hilliard
2026-09-14 21:09 ` [PATCH v6 09/18] mtd: rawnand: sunxi: use page reads to reposition small-page NAND James Hilliard
` (9 subsequent siblings)
17 siblings, 0 replies; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:09 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard
The PIO write loop tracks the absolute NAND column in cur_off, while the
extra-OOB helper computes an offset relative to the start of OOB. Comparing
these values directly always requests a column change, even when the last
ECC step has already left the cursor at the start of the extra OOB bytes.
Include the page size in the comparison, matching the extra-OOB read
helper. Keep the explicit column change when the caller does not track the
cursor, as in the DMA path, or when the current column differs.
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 41d6560d7df7..13dc0af56eb3 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1509,7 +1509,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 [flat|nested] 20+ messages in thread
* [PATCH v6 09/18] mtd: rawnand: sunxi: use page reads to reposition small-page NAND
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
` (7 preceding siblings ...)
2026-09-14 21:09 ` [PATCH v6 08/18] mtd: rawnand: sunxi: avoid redundant column changes for extra OOB James Hilliard
@ 2026-09-14 21:09 ` James Hilliard
2026-09-14 21:09 ` [PATCH v6 10/18] mtd: rawnand: sunxi: bound DMA batches by the user-data register bank James Hilliard
` (8 subsequent siblings)
17 siblings, 0 replies; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:09 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard
The hardware-ECC read paths use random-column commands to revisit main
data and OOB. Small-page NAND does not support these commands, and
nand_change_read_column_op() rejects pages of 512 bytes or less. Ignoring
that error does not provide a physical reread: the erased-chunk check can
instead inspect stale buffer contents.
Introduce a read-column helper which starts a new page read for small-page
NAND. Let the NAND core select READ0, READ1 or READOOB and send the row
address. Keep random-column commands for large pages. Pass the page number
through the erased-chunk fallback and use the helper for normal OOB reads
as well.
Retain transport errors and only classify a physical chunk after all its
rereads succeed. Read exactly the requested region so the tracked column
still matches the start of any trailing OOB.
Also select PIO callbacks for small-page hardware ECC even when DMA is
available. The DMA page sequencer uses large-page random-column commands;
PIO can stream the single 512-byte ECC step and its OOB without them.
Keep the ECC geometry checks unchanged. In particular, the usual 512+16
geometry still cannot fit the controller's minimum 28 parity bytes and
four user-data bytes. This fixes the command handling for small-page
geometries that pass those checks, not a demonstrated regression on a
previously working 512+16 device. Software-ECC and raw callbacks,
large-page DMA eligibility and the on-flash layout are unchanged.
Fixes: 1fef62c1423b ("mtd: nand: add sunxi NAND flash controller support")
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 | 40 +++++++++++++++++++++++----------------
1 file changed, 24 insertions(+), 16 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 13dc0af56eb3..e29638c828ca 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1079,6 +1079,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.
@@ -1087,7 +1097,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;
@@ -1095,13 +1105,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;
@@ -1169,7 +1178,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
const int nfc_step = 0;
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;
}
@@ -1179,7 +1188,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;
}
@@ -1217,13 +1226,12 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
return sunxi_nfc_hw_ecc_read_error(nand, data, data_off,
oob, oob_off, user_data_sz,
- max_bitflips);
+ max_bitflips, page);
} else {
memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, ecc->size);
if (oob_required) {
- 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;
ret = sunxi_nfc_randomizer_read_buf(nand, oob,
@@ -1285,8 +1293,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;
}
@@ -1386,9 +1394,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
if (oob_required && !erased) {
/* TODO: use DMA to retrieve OOB */
- ret = nand_change_read_column_op(nand, mtd->writesize + oob_off,
- oob, ecc->bytes + user_data_sz,
- false);
+ ret = sunxi_nfc_read_column(nand, page, mtd->writesize + oob_off,
+ oob, ecc->bytes + user_data_sz);
if (ret)
goto err_stats;
@@ -1416,7 +1423,7 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
ret = sunxi_nfc_hw_ecc_read_error(nand, data, data_off, oob,
mtd->writesize + oob_off,
user_data_sz,
- &max_bitflips);
+ &max_bitflips, page);
if (ret < 0)
goto err_stats;
if (ret)
@@ -2305,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 [flat|nested] 20+ messages in thread
* [PATCH v6 10/18] mtd: rawnand: sunxi: bound DMA batches by the user-data register bank
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
` (8 preceding siblings ...)
2026-09-14 21:09 ` [PATCH v6 09/18] mtd: rawnand: sunxi: use page reads to reposition small-page NAND James Hilliard
@ 2026-09-14 21:09 ` James Hilliard
2026-09-14 21:09 ` [PATCH v6 11/18] mtd: rawnand: sunxi: clarify OOB register and step handling James Hilliard
` (7 subsequent siblings)
17 siblings, 0 replies; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:09 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard
The H6/H616 controller concatenates protected user data in a bank of 32
four-byte registers. The default layout can allocate more than 128 bytes
across a page, even though each ECC step fits its individual length limit.
DMA transfers then access beyond the user-data register bank. For example,
a 16 KiB page with 1280 OOB bytes and BCH40/1024 has 160 user-data bytes,
while a 16 KiB page with 1664 OOB bytes can allocate 512 bytes.
Do not cap that allocation: changing the lengths would move ECC offsets
and make existing pages written through PIO incompatible. Instead, split
DMA transfers while retaining the original logical main-data and OOB
offsets. Fill the first batch up to the register-bank limit. Transfer each
remaining logical step separately using hardware slot and user-data
register zero, repositioning the main column and spare-area base first.
PAGE_OP generates the second and subsequent main-data columns from its
hardware slot index, not relative to the initial column. Read and write
probes on H616 confirm this behavior. A multi-slot batch starting partway
through the page would therefore return to the wrong main-data column.
Single-slot later batches avoid those internally generated column changes.
Pages whose user data already fits retain the existing single-batch path.
Pass the user-data register index directly to the protected-OOB helpers
and program length fields from the batch's logical starting step. Collect
each batch's ECC status and protected OOB before reusing the registers.
Keep ECC accounting and trailing-OOB handling page-wide, remembering
whether any batch found an erased chunk. Discard all partial DMA statistics
before a PIO read retry.
Use one page-read setup or program-begin command for the whole page. Only
issue program-end after every batch and any trailing OOB transfer succeed.
Do not retry a write in PIO after a batch has already been transferred.
The allocator, free-OOB layout and raw-access callbacks are 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 | 284 ++++++++++++++++++++++----------------
1 file changed, 163 insertions(+), 121 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index e29638c828ca..18a4d1cc4246 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 */
@@ -932,41 +933,47 @@ static u8 sunxi_nfc_user_data_sz(struct sunxi_nand_chip *sunxi_nand, int step)
return sunxi_nand->user_data_bytes[step];
}
+/* 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,
- 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);
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. */
@@ -1022,13 +1029,13 @@ 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,
+ const u8 *oob, unsigned int reg_index,
bool bbm, int page,
unsigned int user_data_sz)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
- struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
u8 user_data[SUNXI_NFC_MAX_USER_DATA_SZ] = {};
+ unsigned int i;
/* Randomize the Bad Block Marker. */
if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) {
@@ -1037,33 +1044,10 @@ static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
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,
@@ -1313,27 +1297,35 @@ static int sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand,
return 0;
}
-static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf,
- int oob_required, int page,
- int nchunks)
+static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand, u8 *buf,
+ int oob_required, int page,
+ int first_step, int nchunks,
+ int *raw_mode)
{
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 corrected = mtd->ecc_stats.corrected;
- unsigned int failed = mtd->ecc_stats.failed;
+ unsigned int reg_index = 0, user_data_sz;
unsigned int max_bitflips = 0;
- int ret, i, raw_mode = 0;
+ int ret, i;
struct scatterlist sg;
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, buf + first_step * ecc->size,
+ ecc->size, nchunks,
DMA_FROM_DEVICE, &sg);
if (ret)
return ret;
@@ -1341,7 +1333,12 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *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);
@@ -1374,15 +1371,17 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found);
pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found);
- 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);
+ for (i = 0; i < nchunks; i++, reg_index += user_data_sz / 4) {
+ int logical_step = first_step + i;
+ int data_off = logical_step * ecc->size;
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, logical_step);
u8 *data = buf + data_off;
u8 *oob = nand->oob_poi + oob_off;
bool erased;
int bitflips;
+ user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, logical_step);
+
bitflips = sunxi_nfc_hw_ecc_correct(nand, randomized ? data : NULL,
oob_required ? oob : NULL,
i, status, pattern_found,
@@ -1397,40 +1396,64 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
ret = sunxi_nfc_read_column(nand, page, mtd->writesize + oob_off,
oob, ecc->bytes + user_data_sz);
if (ret)
- goto err_stats;
+ return ret;
- sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, !i,
+ sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, reg_index, !logical_step,
page, user_data_sz);
}
if (erased)
- raw_mode = 1;
+ *raw_mode = 1;
sunxi_nfc_hw_ecc_update_stats(nand, &max_bitflips, bitflips);
}
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);
+ int logical_step = first_step + i;
+ int data_off = logical_step * ecc->size;
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, logical_step);
u8 *data = buf + data_off;
u8 *oob = nand->oob_poi + oob_off;
if (!(status & NFC_ECC_ERR(i)))
continue;
+ user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, logical_step);
ret = sunxi_nfc_hw_ecc_read_error(nand, data, data_off, oob,
mtd->writesize + oob_off,
user_data_sz,
&max_bitflips, page);
if (ret < 0)
- goto err_stats;
+ return ret;
if (ret)
- raw_mode = 1;
+ *raw_mode = 1;
}
}
+ return max_bitflips;
+}
+
+static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, u8 *buf,
+ int oob_required, int page, int nchunks)
+{
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ unsigned int corrected = mtd->ecc_stats.corrected;
+ unsigned int failed = mtd->ecc_stats.failed;
+ unsigned int max_bitflips = 0;
+ int raw_mode = 0;
+ 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, buf, oob_required, page,
+ first_step, batch_steps,
+ &raw_mode);
+ if (ret < 0)
+ goto err_stats;
+ max_bitflips = max_t(unsigned int, max_bitflips, ret);
+ }
+
if (oob_required) {
ret = sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi,
NULL, !raw_mode, page);
@@ -1761,69 +1784,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;
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, i, !i, page,
- user_data_sz);
- sunxi_nfc_set_user_data_len(nfc, user_data_sz, i);
- }
+ 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 [flat|nested] 20+ messages in thread
* [PATCH v6 11/18] mtd: rawnand: sunxi: clarify OOB register and step handling
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
` (9 preceding siblings ...)
2026-09-14 21:09 ` [PATCH v6 10/18] mtd: rawnand: sunxi: bound DMA batches by the user-data register bank James Hilliard
@ 2026-09-14 21:09 ` James Hilliard
2026-09-14 21:09 ` [PATCH v6 12/18] dt-bindings: mtd: sunxi: Add randomized OOB flag James Hilliard
` (6 subsequent siblings)
17 siblings, 0 replies; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:09 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard
PIO reuses hardware slot zero while the user-data length and OOB offset
belong to the logical page step. Name the two indices explicitly in the
helpers so their different roles are visible at each call site. The
protected-OOB helpers already take a batch-local register index separately.
Clarify why the plain-marker format compensates the bad-block marker
around hardware randomization.
Rename raw_mode to erased_chunk_found in the page readers and DMA batch
helper, using a boolean in both paths. The flag suppresses randomization
of the trailing OOB after an erased chunk; it does not select MTD_OPS_RAW.
No on-flash format, register-access sequence or ECC accounting changes.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 87 ++++++++++++++++++++-------------------
1 file changed, 44 insertions(+), 43 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 18a4d1cc4246..c55537bc71e1 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -925,12 +925,13 @@ static inline u32 sunxi_nfc_buf_to_user_data(const u8 *buf)
return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
}
-static u8 sunxi_nfc_user_data_sz(struct sunxi_nand_chip *sunxi_nand, int step)
+static u8 sunxi_nfc_user_data_sz(struct sunxi_nand_chip *sunxi_nand,
+ int logical_step)
{
if (!sunxi_nand->user_data_bytes)
return USER_DATA_SZ;
- return sunxi_nand->user_data_bytes[step];
+ return sunxi_nand->user_data_bytes[logical_step];
}
/* Keep the on-flash layout, but fit each DMA batch in the user-data bank. */
@@ -976,7 +977,7 @@ static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob,
sunxi_nfc_user_data_to_buf(user_data, oob + i * 4);
}
- /* De-randomize the Bad Block Marker. */
+ /* Undo hardware de-randomization for a plain on-flash BBM. */
if (bbm && (nand->options & NAND_NEED_SCRAMBLING))
sunxi_nfc_randomize_bbm(nand, page, oob);
}
@@ -998,7 +999,7 @@ static void sunxi_nfc_reset_user_data_len(struct sunxi_nfc *nfc)
}
static void sunxi_nfc_set_user_data_len(struct sunxi_nfc *nfc,
- int len, int step)
+ int len, int hw_step)
{
bool found = false;
u32 val;
@@ -1021,11 +1022,11 @@ static void sunxi_nfc_set_user_data_len(struct sunxi_nfc *nfc,
return;
}
- val = readl(nfc->regs + NFC_REG_USER_DATA_LEN(nfc, step));
+ val = readl(nfc->regs + NFC_REG_USER_DATA_LEN(nfc, hw_step));
- 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));
+ val &= ~NFC_USER_DATA_LEN_MSK(hw_step);
+ val |= field_prep(NFC_USER_DATA_LEN_MSK(hw_step), i);
+ writel(val, nfc->regs + NFC_REG_USER_DATA_LEN(nfc, hw_step));
}
static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
@@ -1037,7 +1038,7 @@ static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
u8 user_data[SUNXI_NFC_MAX_USER_DATA_SZ] = {};
unsigned int i;
- /* Randomize the Bad Block Marker. */
+ /* Pre-randomize the BBM so the hardware writes it plain on flash. */
if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) {
memcpy(user_data, oob, user_data_sz);
sunxi_nfc_randomize_bbm(nand, page, user_data);
@@ -1107,7 +1108,7 @@ static int sunxi_nfc_hw_ecc_read_error(struct nand_chip *nand,
}
static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
- int step, u32 status, u32 pattern_found,
+ int hw_step, u32 status, u32 pattern_found,
unsigned int user_data_sz, bool *erased)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
@@ -1116,13 +1117,13 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
*erased = false;
- if (status & NFC_ECC_ERR(step))
+ if (status & NFC_ECC_ERR(hw_step))
return -EBADMSG;
- if (pattern_found & BIT(step)) {
+ if (pattern_found & BIT(hw_step)) {
u8 pattern;
- if (unlikely(!(readl(nfc->regs + NFC_REG_PAT_ID(nfc)) & BIT(step)))) {
+ if (unlikely(!(readl(nfc->regs + NFC_REG_PAT_ID(nfc)) & BIT(hw_step)))) {
pattern = 0x0;
} else {
pattern = 0xff;
@@ -1138,9 +1139,9 @@ 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));
+ tmp = readl(nfc->regs + NFC_REG_ECC_ERR_CNT(nfc, hw_step));
- return NFC_ECC_ERR_CNT(step, tmp);
+ return NFC_ECC_ERR_CNT(hw_step, tmp);
}
static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
@@ -1148,18 +1149,18 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
u8 *oob, int oob_off,
int *cur_off,
unsigned int *max_bitflips,
- int step, bool oob_required, int page)
+ int logical_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);
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, logical_step);
struct nand_ecc_ctrl *ecc = &nand->ecc;
u32 pattern_found;
- bool bbm = !step;
+ bool bbm = !logical_step;
bool erased;
int ret, bitflips;
/* From the controller point of view, we are at step 0 */
- const int nfc_step = 0;
+ const int hw_step = 0;
if (*cur_off != data_off) {
ret = sunxi_nfc_read_column(nand, page, data_off, NULL, 0);
@@ -1181,7 +1182,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, nfc_step);
+ sunxi_nfc_set_user_data_len(nfc, user_data_sz, hw_step);
sunxi_nfc_randomizer_config(nand, page, false);
sunxi_nfc_randomizer_enable(nand);
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP,
@@ -1198,7 +1199,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found);
bitflips = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL,
- nfc_step, readl(nfc->regs + NFC_REG_ECC_ST),
+ hw_step, readl(nfc->regs + NFC_REG_ECC_ST),
pattern_found, user_data_sz, &erased);
if (erased)
return 1;
@@ -1224,7 +1225,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
if (ret)
return ret;
- sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, nfc_step,
+ sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, hw_step,
bbm, page, user_data_sz);
}
}
@@ -1239,12 +1240,12 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
* (it includes the user data before the ECC data.)
*/
static int sunxi_get_oob_offset(struct sunxi_nand_chip *sunxi_nand,
- struct nand_ecc_ctrl *ecc, int step)
+ struct nand_ecc_ctrl *ecc, int logical_step)
{
- int ecc_off = step * ecc->bytes;
+ int ecc_off = logical_step * ecc->bytes;
int i;
- for (i = 0; i < step; i++)
+ for (i = 0; i < logical_step; i++)
ecc_off += sunxi_nfc_user_data_sz(sunxi_nand, i);
return ecc_off;
@@ -1256,10 +1257,10 @@ static int sunxi_get_oob_offset(struct sunxi_nand_chip *sunxi_nand,
* but it skips the next user data.
*/
static int sunxi_get_ecc_offset(struct sunxi_nand_chip *sunxi_nand,
- struct nand_ecc_ctrl *ecc, int step)
+ struct nand_ecc_ctrl *ecc, int logical_step)
{
- return sunxi_get_oob_offset(sunxi_nand, ecc, step) +
- sunxi_nfc_user_data_sz(sunxi_nand, step);
+ return sunxi_get_oob_offset(sunxi_nand, ecc, logical_step) +
+ sunxi_nfc_user_data_sz(sunxi_nand, logical_step);
}
static int sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand,
@@ -1300,7 +1301,7 @@ static int sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand,
static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand, u8 *buf,
int oob_required, int page,
int first_step, int nchunks,
- int *raw_mode)
+ bool *erased_chunk_found)
{
bool randomized = nand->options & NAND_NEED_SCRAMBLING;
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
@@ -1403,7 +1404,7 @@ static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand, u8 *buf,
}
if (erased)
- *raw_mode = 1;
+ *erased_chunk_found = true;
sunxi_nfc_hw_ecc_update_stats(nand, &max_bitflips, bitflips);
}
@@ -1427,7 +1428,7 @@ static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand, u8 *buf,
if (ret < 0)
return ret;
if (ret)
- *raw_mode = 1;
+ *erased_chunk_found = true;
}
}
@@ -1441,14 +1442,14 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, u8 *buf,
unsigned int corrected = mtd->ecc_stats.corrected;
unsigned int failed = mtd->ecc_stats.failed;
unsigned int max_bitflips = 0;
- int raw_mode = 0;
+ bool erased_chunk_found = false;
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, buf, oob_required, page,
first_step, batch_steps,
- &raw_mode);
+ &erased_chunk_found);
if (ret < 0)
goto err_stats;
max_bitflips = max_t(unsigned int, max_bitflips, ret);
@@ -1456,7 +1457,7 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, u8 *buf,
if (oob_required) {
ret = sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi,
- NULL, !raw_mode, page);
+ NULL, !erased_chunk_found, page);
if (ret)
goto err_stats;
}
@@ -1473,17 +1474,17 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, u8 *buf,
static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
const u8 *data, int data_off,
const u8 *oob, int oob_off,
- int *cur_off, int step,
+ int *cur_off, int logical_step,
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);
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, logical_step);
struct nand_ecc_ctrl *ecc = &nand->ecc;
- bool bbm = !step;
+ bool bbm = !logical_step;
int ret;
/* From the controller point of view, we are at step 0 */
- const int nfc_step = 0;
+ const int hw_step = 0;
if (data_off != *cur_off) {
ret = nand_change_write_column_op(nand, data_off, NULL, 0, false);
@@ -1507,8 +1508,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_set_user_data_len(nfc, user_data_sz, hw_step);
+ sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, hw_step, bbm, page,
user_data_sz);
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
@@ -1565,7 +1566,7 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
struct nand_ecc_ctrl *ecc = &nand->ecc;
unsigned int max_bitflips = 0;
int ret, i, cur_off = 0;
- bool raw_mode = false;
+ bool erased_chunk_found = false;
sunxi_nfc_select_chip(nand, nand->cur_cs);
@@ -1589,12 +1590,12 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
if (ret < 0)
goto out;
else if (ret)
- raw_mode = true;
+ erased_chunk_found = true;
}
if (oob_required) {
ret = sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi, &cur_off,
- !raw_mode, page);
+ !erased_chunk_found, page);
if (ret)
goto out;
}
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6 12/18] dt-bindings: mtd: sunxi: Add randomized OOB flag
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
` (10 preceding siblings ...)
2026-09-14 21:09 ` [PATCH v6 11/18] mtd: rawnand: sunxi: clarify OOB register and step handling James Hilliard
@ 2026-09-14 21:09 ` James Hilliard
2026-09-15 16:56 ` Conor Dooley
2026-09-14 21:09 ` [PATCH v6 13/18] mtd: rawnand: sunxi: support randomized OOB formats James Hilliard
` (5 subsequent siblings)
17 siblings, 1 reply; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:09 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard
Allwinner NAND firmware stores the bad block marker through the
controller randomizer. The mainline driver instead compensates the first
two randomized bytes so that the marker remains plain on flash.
On H6 and H616, the firmware also uses a fixed protected user-data
placement while mainline fills the OOB space left after ECC. Add an
opt-in property selecting the firmware's normal-page format for the
configured controller hardware-ECC geometry. This covers the
vendor-specific randomizer, bad block marker representation and protected
user-data placement needed to access BSP-derived images.
The property does not change raw-access semantics. Raw operations still
bypass ECC and randomization and expose physical main and OOB bytes.
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 [flat|nested] 20+ messages in thread
* [PATCH v6 13/18] mtd: rawnand: sunxi: support randomized OOB formats
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
` (11 preceding siblings ...)
2026-09-14 21:09 ` [PATCH v6 12/18] dt-bindings: mtd: sunxi: Add randomized OOB flag James Hilliard
@ 2026-09-14 21:09 ` James Hilliard
2026-09-14 21:10 ` [PATCH v6 14/18] mtd: rawnand: sunxi: select the packed H6/H616 OOB layout James Hilliard
` (4 subsequent siblings)
17 siblings, 0 replies; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:09 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard
The controller randomizer covers the bad block marker along with the
rest of the OOB data. The driver currently compensates the marker bytes
before writes and after reads so that they remain plain on flash.
Allwinner NAND firmware instead stores the marker through the
randomizer. Media using that format appears to contain bad blocks unless
the randomizer is enabled while reading the marker.
Honor the allwinner,randomized-oob property by marking the NAND as
requiring scrambling during normal hardware-ECC transfers and leaving
the marker in the randomizer data stream. Keep the existing plain marker
behavior when the property is absent. Reject the property with software
or disabled ECC because those paths do not operate the controller
randomizer.
Also reject an ECC step larger than the page so the NAND core cannot
fall back to software ECC after the driver's initial engine-type check.
Select the vendor's erased-spare predicate through the SoC capabilities
when the randomized format encounters an ECC error or physical pattern.
Older controllers require eight exact 0xff spare bytes, with the vendor
spare-scan shortcut for four exact bytes on the first page and pages 127
modulo 128 within each eraseblock. Apply that shortcut only with the
vendor's 1 KiB ECC steps. H616 requires byte zero and at least nine of ten
bytes to be 0xff.
Reconstruct the physical spare prefix from the protected user-data
registers, padding unavailable bytes with 0xff. Each ECC step restarts its
OOB randomizer; account for this when gathering the prefix from fixed
four-byte user-data fields. Accepted erased pages return all-0xff main
data and OOB without an ECC failure or a raw reread.
Also follow the vendor's all-zero-page override: when every ECC step
reports an all-zero physical pattern, return zeroed data and OOB with an
ECC failure, even if the hardware did not report one.
Keep ECC exception mode enabled for physical-pattern reporting, as in the
plain-marker format. Uniform physical steps can suppress hardware ECC
errors, so treat them as requiring page classification and do not use
their correction counters. Otherwise physically erased pages or mixed
uniform/encoded pages could incorrectly appear to have decoded
successfully.
Keep the original hardware-decoded main data and protected OOB on other
ECC failures, and report the failure through the ECC statistics. Bad-block
and BBT pattern scans inspect these buffers even after an ECC error; they
must not see the physical randomized representation. Preserve the normal
per-path representation of unprotected OOB bytes when they are requested.
Share page classification between PIO and DMA, including subpage reads.
Read the whole page for randomized-format subpage requests so that both
paths see the same spare prefix and all-zero-page status. Defer statistics
until OOB reads have succeeded, so a DMA-to-PIO retry is accounted once.
The plain-marker format keeps its existing physical erased-chunk check.
MTD_OPS_RAW behavior remains unchanged: raw accesses bypass both ECC and
randomization and expose the physical representation. This changes marker
handling on all supported controllers; the H6/H616 protected user-data
placement is handled separately.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 280 ++++++++++++++++++++++++++++++++++++--
1 file changed, 270 insertions(+), 10 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index c55537bc71e1..42c5f782db40 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -266,6 +266,7 @@ struct sunxi_nfc_timings {
* @clk_rate: clk_rate required for this NAND chip
* @timing_cfg: TIMING_CFG register value for this NAND chip
* @timing_ctl: TIMING_CTL register value for this NAND chip
+ * @randomized_oob: use the randomized normal-page OOB format
* @nsels: number of CS lines required by the NAND chip
* @sels: array of CS lines descriptions
* @user_data_bytes: array of user data lengths for all ECC steps
@@ -278,6 +279,7 @@ struct sunxi_nand_chip {
u32 timing_cfg;
u32 timing_ctl;
u8 *user_data_bytes;
+ bool randomized_oob;
int nsels;
struct sunxi_nand_chip_sel sels[] __counted_by(nsels);
};
@@ -329,6 +331,7 @@ struct sunxi_nfc_mdma_desc {
* @nuser_data_tab: Size of @user_data_len_tab
* @sram_size: Size of the NAND controller SRAM
* @timings: Controller timing characteristics
+ * @spare_is_erased: Vendor erased-page check on the physical spare prefix
*/
struct sunxi_nfc_caps {
bool has_mdma;
@@ -357,6 +360,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);
};
/**
@@ -862,12 +866,18 @@ static void sunxi_nfc_randomizer_disable(struct nand_chip *nand)
nfc->regs + NFC_REG_ECC_CTL);
}
-static void sunxi_nfc_randomize_bbm(struct nand_chip *nand, int page, u8 *bbm)
+static void sunxi_nfc_randomize_buf(u16 state, u8 *buf, unsigned int len)
{
- u16 state = sunxi_nfc_randomizer_state(nand, page, true);
+ while (len--) {
+ *buf++ ^= state;
+ state = sunxi_nfc_randomizer_step(state, 8);
+ }
+}
- bbm[0] ^= state;
- bbm[1] ^= sunxi_nfc_randomizer_step(state, 8);
+static void sunxi_nfc_randomize_bbm(struct nand_chip *nand, int page, u8 *bbm)
+{
+ sunxi_nfc_randomize_buf(sunxi_nfc_randomizer_state(nand, page, true),
+ bbm, 2);
}
static int sunxi_nfc_randomizer_write_buf(struct nand_chip *nand,
@@ -968,6 +978,7 @@ 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)
{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
u32 user_data;
unsigned int i;
@@ -978,7 +989,8 @@ static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob,
}
/* Undo hardware de-randomization for a plain on-flash BBM. */
- if (bbm && (nand->options & NAND_NEED_SCRAMBLING))
+ if (bbm && (nand->options & NAND_NEED_SCRAMBLING) &&
+ !sunxi_nand->randomized_oob)
sunxi_nfc_randomize_bbm(nand, page, oob);
}
@@ -1035,11 +1047,13 @@ static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
unsigned int user_data_sz)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
u8 user_data[SUNXI_NFC_MAX_USER_DATA_SZ] = {};
unsigned int i;
/* Pre-randomize the BBM so the hardware writes it plain on flash. */
- if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) {
+ if (bbm && (nand->options & NAND_NEED_SCRAMBLING) &&
+ !sunxi_nand->randomized_oob) {
memcpy(user_data, oob, user_data_sz);
sunxi_nfc_randomize_bbm(nand, page, user_data);
oob = user_data;
@@ -1107,6 +1121,43 @@ static int sunxi_nfc_hw_ecc_read_error(struct nand_chip *nand,
return ret >= 0;
}
+/* Accumulate a whole randomized-OOB page before classifying its spare data. */
+struct sunxi_nfc_ecc_status {
+ u32 error_steps;
+ u32 zero_steps;
+ unsigned int corrected;
+ unsigned int max_bitflips;
+};
+
+static void sunxi_nfc_hw_ecc_record_status(struct nand_chip *nand,
+ struct sunxi_nfc_ecc_status *result,
+ int logical_step, int hw_step, u32 status,
+ u32 pattern_found)
+{
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ u32 count;
+
+ if ((pattern_found & BIT(hw_step)) &&
+ !(readl(nfc->regs + NFC_REG_PAT_ID(nfc)) & BIT(hw_step)))
+ result->zero_steps |= BIT(logical_step);
+
+ /*
+ * Exception mode can suppress the ECC error for a physical pattern.
+ * Classify these steps with the vendor page check too, and do not use
+ * their correction counters. Keep their decoded data until then.
+ */
+ if ((status & NFC_ECC_ERR(hw_step)) ||
+ (pattern_found & BIT(hw_step))) {
+ result->error_steps |= BIT(logical_step);
+ return;
+ }
+
+ count = readl(nfc->regs + NFC_REG_ECC_ERR_CNT(nfc, hw_step));
+ count = NFC_ECC_ERR_CNT(hw_step, count);
+ result->corrected += count;
+ result->max_bitflips = max(result->max_bitflips, count);
+}
+
static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
int hw_step, u32 status, u32 pattern_found,
unsigned int user_data_sz, bool *erased)
@@ -1149,7 +1200,8 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
u8 *oob, int oob_off,
int *cur_off,
unsigned int *max_bitflips,
- int logical_step, bool oob_required, int page)
+ int logical_step, bool oob_required, int page,
+ struct sunxi_nfc_ecc_status *result)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
@@ -1198,6 +1250,16 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found);
pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found);
+ if (sunxi_nand->randomized_oob) {
+ sunxi_nfc_hw_ecc_record_status(nand, result, logical_step, hw_step,
+ readl(nfc->regs + NFC_REG_ECC_ST),
+ pattern_found);
+ memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, ecc->size);
+ sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, hw_step, bbm,
+ page, user_data_sz);
+ return 0;
+ }
+
bitflips = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL,
hw_step, readl(nfc->regs + NFC_REG_ECC_ST),
pattern_found, user_data_sz, &erased);
@@ -1298,9 +1360,156 @@ static int sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand,
return 0;
}
+static bool sun4i_a10_nfc_spare_is_erased(struct nand_chip *nand,
+ const u8 *spare, int page)
+{
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ unsigned int block_page = page % mtd_div_by_ws(mtd->erasesize, mtd);
+
+ /*
+ * The older vendor spare scans recognize exact four-byte erased
+ * signatures on page zero and pages 127 modulo 128. With their
+ * 1 KiB ECC steps these correspond to four physical 0xff bytes.
+ * Other pages require all eight spare bytes to be 0xff.
+ */
+ if (nand->ecc.size == 1024 &&
+ (!block_page || block_page % 128 == 127) &&
+ !memchr_inv(spare, 0xff, USER_DATA_SZ))
+ return true;
+
+ return !memchr_inv(spare, 0xff, 8);
+}
+
+static bool sun50i_h616_nfc_spare_is_erased(struct nand_chip *nand,
+ const u8 *spare, int page)
+{
+ unsigned int erased = 0;
+ int i;
+
+ /* Byte zero and at least nine of ten bytes must be 0xff. */
+ for (i = 0; i < 10; i++)
+ erased += spare[i] == 0xff;
+
+ return spare[0] == 0xff && erased >= 9;
+}
+
+static bool sunxi_nfc_hw_ecc_spare_is_erased(struct nand_chip *nand, int page)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct nand_ecc_ctrl *ecc = &nand->ecc;
+ u16 state = sunxi_nfc_randomizer_state(nand, page, true);
+ u8 spare[10];
+ unsigned int len, pos = 0;
+ int i, off;
+
+ /*
+ * Reconstruct the physical spare prefix from the hardware's decoded
+ * user data. Each ECC step restarts the OOB randomizer; H6/H616 pack
+ * the entire prefix in step zero. Pad unavailable bytes with 0xff.
+ */
+ memset(spare, 0xff, sizeof(spare));
+ for (i = 0; i < ecc->steps && pos < sizeof(spare); i++) {
+ len = min_t(unsigned int, sunxi_nfc_user_data_sz(sunxi_nand, i),
+ sizeof(spare) - pos);
+ off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
+ memcpy(spare + pos, nand->oob_poi + off, len);
+ sunxi_nfc_randomize_buf(state, spare + pos, len);
+ pos += len;
+ }
+
+ return nfc->caps->spare_is_erased(nand, spare, page);
+}
+
+static int sunxi_nfc_hw_ecc_read_unprotected_oob(struct nand_chip *nand,
+ bool dma, int page)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ struct nand_ecc_ctrl *ecc = &nand->ecc;
+ unsigned int len, off;
+ u16 state;
+ int ret, i;
+
+ for (i = 0; i < ecc->steps; i++) {
+ len = sunxi_nfc_user_data_sz(sunxi_nand, i);
+ off = sunxi_get_ecc_offset(sunxi_nand, ecc, i);
+ ret = sunxi_nfc_read_column(nand, page, mtd->writesize + off,
+ nand->oob_poi + off, ecc->bytes);
+ if (ret)
+ return ret;
+ /* Preserve each path's normal representation of ECC bytes. */
+ if (!dma) {
+ state = sunxi_nfc_randomizer_state(nand, page, true);
+ state = sunxi_nfc_randomizer_step(state, len * 8 + 15);
+ sunxi_nfc_randomize_buf(state, nand->oob_poi + off,
+ ecc->bytes);
+ }
+ }
+
+ off = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps);
+ len = mtd->oobsize - off;
+ if (len) {
+ ret = sunxi_nfc_read_column(nand, page, mtd->writesize + off,
+ nand->oob_poi + off, len);
+ if (ret)
+ return ret;
+ /* The unprotected tail uses the page seed and its 15-bit advance. */
+ state = sunxi_nfc_randomizer_state(nand, page, false);
+ state = sunxi_nfc_randomizer_step(state, 15);
+ sunxi_nfc_randomize_buf(state, nand->oob_poi + off, len);
+ }
+
+ return 0;
+}
+
+static int
+sunxi_nfc_hw_ecc_finish_randomized_read(struct nand_chip *nand, u8 *buf,
+ struct sunxi_nfc_ecc_status *result,
+ bool oob_required, bool dma, int page)
+{
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ struct nand_ecc_ctrl *ecc = &nand->ecc;
+ int ret;
+
+ /*
+ * The vendor treats an all-zero physical page as bad, even without
+ * ECC errors. This takes precedence over the spare-byte heuristic.
+ */
+ if (result->zero_steps == GENMASK(ecc->steps - 1, 0)) {
+ memset(buf, 0, mtd->writesize);
+ memset(nand->oob_poi, 0, mtd->oobsize);
+ mtd->ecc_stats.failed += ecc->steps;
+ return 0;
+ }
+
+ if (result->error_steps && sunxi_nfc_hw_ecc_spare_is_erased(nand, page)) {
+ memset(buf, 0xff, mtd->writesize);
+ memset(nand->oob_poi, 0xff, mtd->oobsize);
+ return 0;
+ }
+
+ /*
+ * Keep the original decoded main and protected OOB bytes on ECC failure.
+ * BBT pattern scans inspect them even when an ECC error is reported.
+ * Read the remaining OOB only when requested, not to classify the page.
+ */
+ if (oob_required) {
+ ret = sunxi_nfc_hw_ecc_read_unprotected_oob(nand, dma, page);
+ if (ret)
+ return ret;
+ }
+
+ mtd->ecc_stats.corrected += result->corrected;
+ mtd->ecc_stats.failed += hweight32(result->error_steps);
+
+ return result->max_bitflips;
+}
+
static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand, u8 *buf,
int oob_required, int page,
int first_step, int nchunks,
+ struct sunxi_nfc_ecc_status *result,
bool *erased_chunk_found)
{
bool randomized = nand->options & NAND_NEED_SCRAMBLING;
@@ -1383,6 +1592,14 @@ static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand, u8 *buf,
user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, logical_step);
+ if (sunxi_nand->randomized_oob) {
+ sunxi_nfc_hw_ecc_record_status(nand, result, logical_step, i, status,
+ pattern_found);
+ sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, reg_index, !logical_step,
+ page, user_data_sz);
+ continue;
+ }
+
bitflips = sunxi_nfc_hw_ecc_correct(nand, randomized ? data : NULL,
oob_required ? oob : NULL,
i, status, pattern_found,
@@ -1409,6 +1626,9 @@ static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand, u8 *buf,
sunxi_nfc_hw_ecc_update_stats(nand, &max_bitflips, bitflips);
}
+ if (sunxi_nand->randomized_oob)
+ return 0;
+
if (status & NFC_ECC_ERR_MSK(nfc)) {
for (i = 0; i < nchunks; i++) {
int logical_step = first_step + i;
@@ -1438,7 +1658,9 @@ static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand, u8 *buf,
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_status result = {};
unsigned int corrected = mtd->ecc_stats.corrected;
unsigned int failed = mtd->ecc_stats.failed;
unsigned int max_bitflips = 0;
@@ -1448,13 +1670,17 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, u8 *buf,
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, buf, oob_required, page,
- first_step, batch_steps,
+ first_step, batch_steps, &result,
&erased_chunk_found);
if (ret < 0)
goto err_stats;
max_bitflips = max_t(unsigned int, max_bitflips, ret);
}
+ if (sunxi_nand->randomized_oob)
+ return sunxi_nfc_hw_ecc_finish_randomized_read(nand, buf, &result,
+ oob_required, true, page);
+
if (oob_required) {
ret = sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi,
NULL, !erased_chunk_found, page);
@@ -1564,6 +1790,7 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
+ struct sunxi_nfc_ecc_status result = {};
unsigned int max_bitflips = 0;
int ret, i, cur_off = 0;
bool erased_chunk_found = false;
@@ -1586,13 +1813,19 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off, oob,
oob_off + mtd->writesize,
&cur_off, &max_bitflips,
- i, oob_required, page);
+ i, oob_required, page, &result);
if (ret < 0)
goto out;
else if (ret)
erased_chunk_found = true;
}
+ if (sunxi_nand->randomized_oob) {
+ ret = sunxi_nfc_hw_ecc_finish_randomized_read(nand, buf, &result,
+ oob_required, false, page);
+ goto out;
+ }
+
if (oob_required) {
ret = sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi, &cur_off,
!erased_chunk_found, page);
@@ -1638,6 +1871,10 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
int ret, i, cur_off = 0;
unsigned int max_bitflips = 0;
+ /* The vendor spare test and all-zero detection classify a whole page. */
+ if (sunxi_nand->randomized_oob)
+ return sunxi_nfc_hw_ecc_read_page(nand, bufpoi, false, page);
+
sunxi_nfc_select_chip(nand, nand->cur_cs);
ret = nand_read_page_op(nand, page, 0, NULL, 0);
@@ -1658,7 +1895,7 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
oob,
oob_off + mtd->writesize,
&cur_off, &max_bitflips, i,
- false, page);
+ false, page, NULL);
if (ret < 0)
goto out;
}
@@ -1677,6 +1914,9 @@ static int sunxi_nfc_hw_ecc_read_subpage_dma(struct nand_chip *nand,
int nchunks = DIV_ROUND_UP(data_offs + readlen, nand->ecc.size);
int ret;
+ if (to_sunxi_nand(nand)->randomized_oob)
+ return sunxi_nfc_hw_ecc_read_page_dma(nand, buf, false, page);
+
sunxi_nfc_select_chip(nand, nand->cur_cs);
ret = nand_read_page_op(nand, page, 0, NULL, 0);
@@ -2308,6 +2548,10 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
ecc->strength *= 2;
}
+ /* This format requires an ECC step that fits in the page. */
+ if (sunxi_nand->randomized_oob && mtd->writesize < ecc->size)
+ return -EINVAL;
+
/* Add ECC info retrieval from DT */
for (ecc_mode = 0; ecc_mode < nfc->caps->nstrengths; ecc_mode++) {
if (ecc->strength <= strengths[ecc_mode]) {
@@ -2391,6 +2635,8 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
static int sunxi_nand_attach_chip(struct nand_chip *nand)
{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
const struct nand_ecc_props *requirements =
nanddev_get_ecc_requirements(&nand->base);
struct nand_ecc_ctrl *ecc = &nand->ecc;
@@ -2400,6 +2646,14 @@ static int sunxi_nand_attach_chip(struct nand_chip *nand)
if (nand->bbt_options & NAND_BBT_USE_FLASH)
nand->bbt_options |= NAND_BBT_NO_OOB;
+ if (sunxi_nand->randomized_oob &&
+ ecc->engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST)
+ return dev_err_probe(nfc->dev, -EINVAL,
+ "Allwinner OOB format requires controller ECC\n");
+
+ if (sunxi_nand->randomized_oob)
+ nand->options |= NAND_NEED_SCRAMBLING;
+
if (nand->options & NAND_NEED_SCRAMBLING)
nand->options |= NAND_NO_SUBPAGE_WRITE;
@@ -2629,6 +2883,9 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
if (!sunxi_nand)
return -ENOMEM;
+ sunxi_nand->randomized_oob =
+ of_property_read_bool(np, "allwinner,randomized-oob");
+
sunxi_nand->nsels = nsels;
for (i = 0; i < nsels; i++) {
@@ -2907,6 +3164,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 = {
@@ -2930,6 +3188,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 = {
@@ -2956,6 +3215,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_h616_caps = {
.max_ecc_steps = 32,
.sram_size = 8192,
.timings = &sun50i_h616_nfc_timings,
+ .spare_is_erased = sun50i_h616_nfc_spare_is_erased,
};
static const struct of_device_id sunxi_nfc_ids[] = {
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6 14/18] mtd: rawnand: sunxi: select the packed H6/H616 OOB layout
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
` (12 preceding siblings ...)
2026-09-14 21:09 ` [PATCH v6 13/18] mtd: rawnand: sunxi: support randomized OOB formats James Hilliard
@ 2026-09-14 21:10 ` James Hilliard
2026-09-14 21:10 ` [PATCH v6 15/18] mtd: rawnand: sunxi: combine contiguous unprotected OOB reads James Hilliard
` (3 subsequent siblings)
17 siblings, 0 replies; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:10 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard
The H6/H616 controller can configure a separate protected user-data
length for every ECC step. Mainline fills all space left after ECC with
user data. This changes the physical ECC offsets from those used by
Allwinner NAND firmware, so pages written by one layout cannot be
decoded with the other.
The allwinner,randomized-oob property selects the firmware's normal-page
format for the configured controller hardware-ECC geometry. Use four
protected user-data bytes per 1 KiB ECC step, cap the total at 16 bytes,
and assign the entire total to ECC step zero. Reserve the same total before
maximizing ECC strength so the selected strength leaves enough room.
Keep the existing maximized mainline user-data layout when the property
is absent. Older controllers have fixed four-byte user-data registers
whose placement already matches the firmware and need no additional
change.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 31 +++++++++++++++++++++++++------
1 file changed, 25 insertions(+), 6 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 42c5f782db40..8b0d0620e1d9 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -225,6 +225,9 @@
#define USER_DATA_SZ 4
#define SUNXI_NFC_MAX_USER_DATA_SZ 32
+/* The randomized H6/H616 layout packs at most 16 bytes before ECC step 0. */
+#define SUNXI_NFC_H6_MAX_USER_DATA_SZ 16
+
/**
* struct sunxi_nand_chip_sel - stores information related to NAND Chip Select
*
@@ -2431,8 +2434,14 @@ static void sunxi_nand_detach_chip(struct nand_chip *nand)
sunxi_nand->user_data_bytes = NULL;
}
-static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t oobsize,
- int ecc_bytes, int nsectors)
+static unsigned int sunxi_nfc_h6_user_data_sz(int nsectors)
+{
+ return min(nsectors * USER_DATA_SZ,
+ SUNXI_NFC_H6_MAX_USER_DATA_SZ);
+}
+
+static int sunxi_nfc_init_user_data(struct nand_chip *nand, uint32_t oobsize,
+ int ecc_bytes, int nsectors)
{
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
@@ -2448,6 +2457,12 @@ static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t oobsize
if (!sunxi_nand->user_data_bytes)
return -ENOMEM;
+ if (sunxi_nand->randomized_oob) {
+ sunxi_nand->user_data_bytes[0] =
+ sunxi_nfc_h6_user_data_sz(nsectors);
+ return 0;
+ }
+
for (step = 0; (step < nsectors) && (remaining_bytes > 0); step++) {
for (i = 0; i < c->nuser_data_tab; i++) {
if (c->user_data_len_tab[i] > remaining_bytes)
@@ -2508,6 +2523,10 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
bytes -= 2;
bytes -= total_user_data_sz;
+ } else if (sunxi_nand->randomized_oob) {
+ total_user_data_sz =
+ sunxi_nfc_h6_user_data_sz(nsectors);
+ bytes -= total_user_data_sz;
} else {
/*
* User-data lengths are encoded in four-byte units. Reserve
@@ -2578,12 +2597,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.
+ * The default variable-length layout prioritizes maximum ECC strength,
+ * then uses 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);
+ ret = sunxi_nfc_init_user_data(nand, mtd->oobsize,
+ ecc->bytes, nsectors);
if (ret)
return ret;
}
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6 15/18] mtd: rawnand: sunxi: combine contiguous unprotected OOB reads
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
` (13 preceding siblings ...)
2026-09-14 21:10 ` [PATCH v6 14/18] mtd: rawnand: sunxi: select the packed H6/H616 OOB layout James Hilliard
@ 2026-09-14 21:10 ` James Hilliard
2026-09-14 21:10 ` [PATCH v6 16/18] mtd: rawnand: sunxi: avoid duplicate chip setup before page commands James Hilliard
` (2 subsequent siblings)
17 siblings, 0 replies; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:10 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard
The randomized-format OOB reader fetches each parity region and the
unprotected tail separately. H6/H616 pack protected user data before the
first ECC step, leaving a contiguous range of parity bytes and tail data.
Combine adjacent parity regions without crossing protected user data,
and include the tail in the final transfer. Keep software de-randomization
separate so each ECC step and the tail retain their existing seed phases
and PIO/DMA representations. The NAND operation parser splits transfers
which exceed the controller SRAM size.
This reduces column commands for OOB reads without changing erased-page
classification, data-only reads or the plain-marker paths.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 26 ++++++++++++++++++--------
1 file changed, 18 insertions(+), 8 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 8b0d0620e1d9..f3234d2831ea 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1435,14 +1435,28 @@ static int sunxi_nfc_hw_ecc_read_unprotected_oob(struct nand_chip *nand,
int ret, i;
for (i = 0; i < ecc->steps; i++) {
- len = sunxi_nfc_user_data_sz(sunxi_nand, i);
off = sunxi_get_ecc_offset(sunxi_nand, ecc, i);
+ 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, ecc->bytes);
+ nand->oob_poi + off, len);
if (ret)
return ret;
- /* Preserve each path's normal representation of ECC bytes. */
- if (!dma) {
+ }
+
+ /* 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,
@@ -1453,10 +1467,6 @@ static int sunxi_nfc_hw_ecc_read_unprotected_oob(struct nand_chip *nand,
off = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps);
len = mtd->oobsize - off;
if (len) {
- ret = sunxi_nfc_read_column(nand, page, mtd->writesize + off,
- nand->oob_poi + off, len);
- if (ret)
- return ret;
/* The unprotected tail uses the page seed and its 15-bit advance. */
state = sunxi_nfc_randomizer_state(nand, page, false);
state = sunxi_nfc_randomizer_step(state, 15);
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6 16/18] mtd: rawnand: sunxi: avoid duplicate chip setup before page commands
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
` (14 preceding siblings ...)
2026-09-14 21:10 ` [PATCH v6 15/18] mtd: rawnand: sunxi: combine contiguous unprotected OOB reads James Hilliard
@ 2026-09-14 21:10 ` James Hilliard
2026-09-14 21:10 ` [PATCH v6 17/18] mtd: rawnand: sunxi: reduce user-data length register accesses James Hilliard
2026-09-14 21:10 ` [PATCH v6 18/18] mtd: rawnand: sunxi: reuse ECC status within each DMA read James Hilliard
17 siblings, 0 replies; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:10 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard
The page helpers invoke exec_op(), which selects the chip and programs
its timing and spare-area registers. Remove the identical setup directly
before those helpers in the read callbacks and PIO write callbacks.
Keep explicit selection in the DMA write callback: it accesses controller
registers and prepares DMA before issuing the program-begin operation.
Do not cache controller state or change selection after a controller reset.
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 f3234d2831ea..773ba55ecc07 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1808,8 +1808,6 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
int ret, i, cur_off = 0;
bool erased_chunk_found = false;
- sunxi_nfc_select_chip(nand, nand->cur_cs);
-
ret = nand_read_page_op(nand, page, 0, NULL, 0);
if (ret)
return ret;
@@ -1858,8 +1856,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;
@@ -1888,8 +1884,6 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
if (sunxi_nand->randomized_oob)
return sunxi_nfc_hw_ecc_read_page(nand, bufpoi, false, page);
- sunxi_nfc_select_chip(nand, nand->cur_cs);
-
ret = nand_read_page_op(nand, page, 0, NULL, 0);
if (ret)
return ret;
@@ -1930,8 +1924,6 @@ static int sunxi_nfc_hw_ecc_read_subpage_dma(struct nand_chip *nand,
if (to_sunxi_nand(nand)->randomized_oob)
return sunxi_nfc_hw_ecc_read_page_dma(nand, buf, false, page);
- sunxi_nfc_select_chip(nand, nand->cur_cs);
-
ret = nand_read_page_op(nand, page, 0, NULL, 0);
if (ret)
return ret;
@@ -1955,8 +1947,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;
@@ -2000,8 +1990,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 [flat|nested] 20+ messages in thread
* [PATCH v6 17/18] mtd: rawnand: sunxi: reduce user-data length register accesses
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
` (15 preceding siblings ...)
2026-09-14 21:10 ` [PATCH v6 16/18] mtd: rawnand: sunxi: avoid duplicate chip setup before page commands James Hilliard
@ 2026-09-14 21:10 ` James Hilliard
2026-09-14 21:10 ` [PATCH v6 18/18] mtd: rawnand: sunxi: reuse ECC status within each DMA read James Hilliard
17 siblings, 0 replies; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:10 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard
DMA reads and writes clear every user-data length register and then
read-modify-write one field at a time. Multiple ECC steps share each
register, so this repeats MMIO accesses to the same words.
Build each register value in memory and write it once, including zero
lengths for unused slots. Reprogram every word for every DMA operation
instead of caching hardware state, so controller resets and switches
between chips need no special cache invalidation.
Share the length encoding with a PIO-specific setter. PIO always uses
hardware slot zero, and its page callbacks clear all length registers
before the chunk loop. Write the slot-zero value directly instead of
reading and modifying the same register for every chunk. Keep the
per-operation clearing so other slots remain zero after DMA fallback.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 75 +++++++++++++++++++++++++--------------
1 file changed, 48 insertions(+), 27 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 773ba55ecc07..41d6ef31ea3d 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1013,35 +1013,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 hw_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, hw_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(hw_step);
- val |= field_prep(NFC_USER_DATA_LEN_MSK(hw_step), i);
- writel(val, nfc->regs + NFC_REG_USER_DATA_LEN(nfc, hw_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,
@@ -1237,7 +1262,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, hw_step);
+ 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,
@@ -1554,10 +1579,7 @@ static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand, u8 *buf,
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),
@@ -1747,7 +1769,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, hw_step);
+ sunxi_nfc_set_user_data_len_pio(nfc, user_data_sz);
sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, hw_step, bbm, page,
user_data_sz);
@@ -2051,7 +2073,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);
@@ -2059,7 +2081,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 [flat|nested] 20+ messages in thread
* [PATCH v6 18/18] mtd: rawnand: sunxi: reuse ECC status within each DMA read
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
` (16 preceding siblings ...)
2026-09-14 21:10 ` [PATCH v6 17/18] mtd: rawnand: sunxi: reduce user-data length register accesses James Hilliard
@ 2026-09-14 21:10 ` James Hilliard
17 siblings, 0 replies; 20+ messages in thread
From: James Hilliard @ 2026-09-14 21:10 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris
Cc: linux-mtd, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, James Hilliard
The DMA completion loop reads the pattern ID repeatedly and reads the
same packed error-counter word for each of its four ECC steps.
Snapshot the completed operation status and pattern ID, and retain the
last counter word while visiting consecutive DMA steps. Read counters
only for steps which use them. Reuse the shared status register value on
older controllers which place the pattern flags in its upper half.
Keep the existing error and pattern priority in both the plain-marker
and randomized-format paths.
Start a fresh snapshot after every PIO ECC operation so reuse of hardware
slot zero cannot reuse status from a previous step. The snapshot is local
to one callback and does not survive controller resets or DMA-to-PIO
retries.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 103 +++++++++++++++++++++++++-------------
1 file changed, 69 insertions(+), 34 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 41d6ef31ea3d..b79bf13f3ff0 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1157,16 +1157,60 @@ struct sunxi_nfc_ecc_status {
unsigned int max_bitflips;
};
+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 &&
+ (to_sunxi_nand(nand)->randomized_oob ||
+ (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 hw_step)
+{
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ u32 reg = NFC_REG_ECC_ERR_CNT(nfc, hw_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(hw_step, snapshot->count);
+}
+
static void sunxi_nfc_hw_ecc_record_status(struct nand_chip *nand,
struct sunxi_nfc_ecc_status *result,
- int logical_step, int hw_step, u32 status,
- u32 pattern_found)
+ int logical_step, int hw_step,
+ struct sunxi_nfc_ecc_snapshot *snapshot)
{
- struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
u32 count;
- if ((pattern_found & BIT(hw_step)) &&
- !(readl(nfc->regs + NFC_REG_PAT_ID(nfc)) & BIT(hw_step)))
+ if ((snapshot->pattern_found & BIT(hw_step)) &&
+ !(snapshot->pattern_id & BIT(hw_step)))
result->zero_steps |= BIT(logical_step);
/*
@@ -1174,35 +1218,32 @@ static void sunxi_nfc_hw_ecc_record_status(struct nand_chip *nand,
* Classify these steps with the vendor page check too, and do not use
* their correction counters. Keep their decoded data until then.
*/
- if ((status & NFC_ECC_ERR(hw_step)) ||
- (pattern_found & BIT(hw_step))) {
+ if ((snapshot->status & NFC_ECC_ERR(hw_step)) ||
+ (snapshot->pattern_found & BIT(hw_step))) {
result->error_steps |= BIT(logical_step);
return;
}
- count = readl(nfc->regs + NFC_REG_ECC_ERR_CNT(nfc, hw_step));
- count = NFC_ECC_ERR_CNT(hw_step, count);
+ count = sunxi_nfc_hw_ecc_read_count(nand, snapshot, hw_step);
result->corrected += count;
result->max_bitflips = max(result->max_bitflips, count);
}
static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
- int hw_step, u32 status, u32 pattern_found,
+ int hw_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(hw_step))
+ if (snapshot->status & NFC_ECC_ERR(hw_step))
return -EBADMSG;
- if (pattern_found & BIT(hw_step)) {
+ if (snapshot->pattern_found & BIT(hw_step)) {
u8 pattern;
- if (unlikely(!(readl(nfc->regs + NFC_REG_PAT_ID(nfc)) & BIT(hw_step)))) {
+ if (unlikely(!(snapshot->pattern_id & BIT(hw_step)))) {
pattern = 0x0;
} else {
pattern = 0xff;
@@ -1218,9 +1259,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, hw_step));
-
- return NFC_ECC_ERR_CNT(hw_step, tmp);
+ return sunxi_nfc_hw_ecc_read_count(nand, snapshot, hw_step);
}
static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
@@ -1235,7 +1274,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, logical_step);
struct nand_ecc_ctrl *ecc = &nand->ecc;
- u32 pattern_found;
+ struct sunxi_nfc_ecc_snapshot snapshot;
bool bbm = !logical_step;
bool erased;
int ret, bitflips;
@@ -1275,13 +1314,11 @@ 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);
if (sunxi_nand->randomized_oob) {
sunxi_nfc_hw_ecc_record_status(nand, result, logical_step, hw_step,
- readl(nfc->regs + NFC_REG_ECC_ST),
- pattern_found);
+ &snapshot);
memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, ecc->size);
sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, hw_step, bbm,
page, user_data_sz);
@@ -1289,8 +1326,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
}
bitflips = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL,
- hw_step, readl(nfc->regs + NFC_REG_ECC_ST),
- pattern_found, user_data_sz, &erased);
+ hw_step, &snapshot, user_data_sz, &erased);
if (erased)
return 1;
@@ -1555,11 +1591,12 @@ static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand, u8 *buf,
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
+ struct sunxi_nfc_ecc_snapshot snapshot;
unsigned int reg_index = 0, user_data_sz;
unsigned int max_bitflips = 0;
int ret, i;
struct scatterlist sg;
- u32 status, pattern_found, wait;
+ u32 wait;
if (first_step) {
ret = sunxi_nfc_read_column(nand, page, first_step * ecc->size,
@@ -1612,9 +1649,7 @@ static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand, u8 *buf,
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);
for (i = 0; i < nchunks; i++, reg_index += user_data_sz / 4) {
int logical_step = first_step + i;
@@ -1628,8 +1663,8 @@ static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand, u8 *buf,
user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, logical_step);
if (sunxi_nand->randomized_oob) {
- sunxi_nfc_hw_ecc_record_status(nand, result, logical_step, i, status,
- pattern_found);
+ sunxi_nfc_hw_ecc_record_status(nand, result, logical_step, i,
+ &snapshot);
sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, reg_index, !logical_step,
page, user_data_sz);
continue;
@@ -1637,7 +1672,7 @@ static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand, u8 *buf,
bitflips = sunxi_nfc_hw_ecc_correct(nand, randomized ? data : NULL,
oob_required ? oob : NULL,
- i, status, pattern_found,
+ i, &snapshot,
user_data_sz, &erased);
/* ECC errors are handled in the second loop. */
@@ -1664,7 +1699,7 @@ static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand, u8 *buf,
if (sunxi_nand->randomized_oob)
return 0;
- if (status & NFC_ECC_ERR_MSK(nfc)) {
+ if (snapshot.status & NFC_ECC_ERR_MSK(nfc)) {
for (i = 0; i < nchunks; i++) {
int logical_step = first_step + i;
int data_off = logical_step * ecc->size;
@@ -1672,7 +1707,7 @@ static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand, u8 *buf,
u8 *data = buf + data_off;
u8 *oob = nand->oob_poi + oob_off;
- if (!(status & NFC_ECC_ERR(i)))
+ if (!(snapshot.status & NFC_ECC_ERR(i)))
continue;
user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, logical_step);
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v6 12/18] dt-bindings: mtd: sunxi: Add randomized OOB flag
2026-09-14 21:09 ` [PATCH v6 12/18] dt-bindings: mtd: sunxi: Add randomized OOB flag James Hilliard
@ 2026-09-15 16:56 ` Conor Dooley
0 siblings, 0 replies; 20+ messages in thread
From: Conor Dooley @ 2026-09-15 16:56 UTC (permalink / raw)
To: James Hilliard
Cc: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Maxime Ripard, Richard Genoud,
Masahiro Yamada, Boris Brezillon, Brian Norris, linux-mtd,
devicetree, linux-arm-kernel, linux-sunxi, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 75 bytes --]
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2026-09-15 16:56 UTC | newest]
Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-14 21:09 [PATCH v6 00/18] mtd: rawnand: sunxi: support the Allwinner randomized OOB format James Hilliard
2026-09-14 21:09 ` [PATCH v6 01/18] mtd: rawnand: sunxi: drain interrupts before reusing the completion James Hilliard
2026-09-14 21:09 ` [PATCH v6 02/18] mtd: rawnand: sunxi: use the logical step's OOB length in PIO James Hilliard
2026-09-14 21:09 ` [PATCH v6 03/18] mtd: rawnand: sunxi: propagate page-setup and erased-check errors James Hilliard
2026-09-14 21:09 ` [PATCH v6 04/18] mtd: rawnand: sunxi: stop failed program operations and disable ECC James Hilliard
2026-09-14 21:09 ` [PATCH v6 05/18] mtd: rawnand: sunxi: select the pattern ID for the current ECC step James Hilliard
2026-09-14 21:09 ` [PATCH v6 06/18] mtd: rawnand: sunxi: propagate buffer and column transfer errors James Hilliard
2026-09-14 21:09 ` [PATCH v6 07/18] mtd: rawnand: sunxi: avoid a second program confirm for OOB writes James Hilliard
2026-09-14 21:09 ` [PATCH v6 08/18] mtd: rawnand: sunxi: avoid redundant column changes for extra OOB James Hilliard
2026-09-14 21:09 ` [PATCH v6 09/18] mtd: rawnand: sunxi: use page reads to reposition small-page NAND James Hilliard
2026-09-14 21:09 ` [PATCH v6 10/18] mtd: rawnand: sunxi: bound DMA batches by the user-data register bank James Hilliard
2026-09-14 21:09 ` [PATCH v6 11/18] mtd: rawnand: sunxi: clarify OOB register and step handling James Hilliard
2026-09-14 21:09 ` [PATCH v6 12/18] dt-bindings: mtd: sunxi: Add randomized OOB flag James Hilliard
2026-09-15 16:56 ` Conor Dooley
2026-09-14 21:09 ` [PATCH v6 13/18] mtd: rawnand: sunxi: support randomized OOB formats James Hilliard
2026-09-14 21:10 ` [PATCH v6 14/18] mtd: rawnand: sunxi: select the packed H6/H616 OOB layout James Hilliard
2026-09-14 21:10 ` [PATCH v6 15/18] mtd: rawnand: sunxi: combine contiguous unprotected OOB reads James Hilliard
2026-09-14 21:10 ` [PATCH v6 16/18] mtd: rawnand: sunxi: avoid duplicate chip setup before page commands James Hilliard
2026-09-14 21:10 ` [PATCH v6 17/18] mtd: rawnand: sunxi: reduce user-data length register accesses James Hilliard
2026-09-14 21:10 ` [PATCH v6 18/18] mtd: rawnand: sunxi: reuse ECC status within each DMA read James Hilliard
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®