[PATCH v9 16/18] mtd: rawnand: sunxi: reduce user-data length register accesses

From: James Hilliard

Date: Sat Oct 17 2026 - 15:54:55 EST


H6/H616 pack eight ECC steps' user-data length codes into each 32-bit
register. DMA currently clears all these registers, then
read-modify-writes one field per step, repeatedly accessing the same
words. Code zero means no user data; the other codes select lengths from
the controller's table.

Build each packed value in memory and write it once per DMA batch,
including zeros for unused slots. Take each length from the batch's
logical step but encode it in the corresponding batch-local hardware
slot. Rewrite every register on each batch rather than caching values
across resets or changes of chip.

Share the encoding with a PIO setter. PIO always uses hardware slot
zero, and its page callbacks clear all length registers before the chunk
loop. It can therefore write the slot-zero code directly, without a
read-modify-write. Keep the page-level clearing so unused slots are also
cleared after a DMA-to-PIO fallback. Older controllers without length
registers remain unaffected, and the chosen lengths and on-flash layout
do not change.

Signed-off-by: James Hilliard <james.hilliard1@xxxxxxxxx>
---
drivers/mtd/nand/raw/sunxi_nand.c | 79 +++++++++++++++++++++++++--------------
1 file changed, 50 insertions(+), 29 deletions(-)

diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 45172175d4e6..a20886991975 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -57,7 +57,7 @@
#define NFC_H6_USER_DATA_REGS 32
#define NFC_REG_USER_DATA(nfc, x) (nfc->caps->reg_user_data + ((x) * 4))
#define NFC_REG_H6_USER_DATA_LEN 0x0070
-/* A USER_DATA_LEN register can hold the length of 8 USER_DATA registers */
+/* Each USER_DATA_LEN register holds eight 4-bit ECC-step user-data length codes. */
#define NFC_REG_USER_DATA_LEN_CAPACITY 8
#define NFC_REG_USER_DATA_LEN(nfc, step) \
(nfc->caps->reg_user_data_len + \
@@ -1036,35 +1036,60 @@ static void sunxi_nfc_reset_user_data_len(struct sunxi_nfc *nfc)
writel(0, nfc->regs + NFC_REG_USER_DATA_LEN(nfc, i));
}

-static void sunxi_nfc_set_user_data_len(struct sunxi_nfc *nfc,
- int len, int step)
+static int sunxi_nfc_user_data_len_code(struct sunxi_nfc *nfc, int len)
{
- bool found = false;
- u32 val;
int i;

+ for (i = 0; i < nfc->caps->nuser_data_tab; i++) {
+ if (len == nfc->caps->user_data_len_tab[i])
+ return i;
+ }
+
+ dev_warn(nfc->dev, "Unsupported length for user data reg: %d\n", len);
+ return -EINVAL;
+}
+
+static void sunxi_nfc_set_user_data_len_pio(struct sunxi_nfc *nfc, int len)
+{
+ int code;
+
/* not all SoCs have this register */
if (!nfc->caps->reg_user_data_len)
return;

- for (i = 0; i < nfc->caps->nuser_data_tab; i++) {
- if (len == nfc->caps->user_data_len_tab[i]) {
- found = true;
- break;
- }
- }
-
- if (!found) {
- dev_warn(nfc->dev,
- "Unsupported length for user data reg: %d\n", len);
+ code = sunxi_nfc_user_data_len_code(nfc, len);
+ if (code < 0)
return;
- }

- val = readl(nfc->regs + NFC_REG_USER_DATA_LEN(nfc, step));
+ /* PIO uses slot zero; the page callback clears all other slots. */
+ writel(FIELD_PREP(NFC_USER_DATA_LEN_MSK(0), code),
+ nfc->regs + NFC_REG_USER_DATA_LEN(nfc, 0));
+}
+
+static void sunxi_nfc_set_user_data_len_dma(struct nand_chip *nand,
+ int first_step, int nchunks)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ int first, step, len, code;
+ u32 val;
+
+ if (!nfc->caps->reg_user_data_len)
+ return;

- val &= ~NFC_USER_DATA_LEN_MSK(step);
- val |= field_prep(NFC_USER_DATA_LEN_MSK(step), i);
- writel(val, nfc->regs + NFC_REG_USER_DATA_LEN(nfc, step));
+ /* Write each packed register once, including zeroes for unused slots. */
+ for (first = 0; first < nfc->caps->max_ecc_steps;
+ first += NFC_REG_USER_DATA_LEN_CAPACITY) {
+ val = 0;
+ for (step = first; step < nchunks &&
+ step < first + NFC_REG_USER_DATA_LEN_CAPACITY; step++) {
+ len = sunxi_nfc_user_data_sz(sunxi_nand, first_step + step);
+ code = sunxi_nfc_user_data_len_code(nfc, len);
+ if (code >= 0)
+ val |= field_prep(NFC_USER_DATA_LEN_MSK(step), code);
+ }
+ writel(val, nfc->regs + NFC_REG_USER_DATA_LEN(nfc, first));
+ }
}

static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
@@ -1460,7 +1485,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
if (ret)
return ret;

- sunxi_nfc_set_user_data_len(nfc, user_data_sz, 0);
+ sunxi_nfc_set_user_data_len_pio(nfc, user_data_sz);
sunxi_nfc_randomizer_config(nand, page, false);
sunxi_nfc_randomizer_enable(nand);
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP,
@@ -1489,7 +1514,7 @@ static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand,
struct sunxi_nfc_ecc_snapshot snapshot;
struct scatterlist sg;
int page = read->page;
- int ret, i;
+ int ret;
u32 wait;

if (first_step) {
@@ -1510,10 +1535,7 @@ static int sunxi_nfc_hw_ecc_read_batch_dma(struct nand_chip *nand,
return ret;

sunxi_nfc_hw_ecc_enable(nand);
- sunxi_nfc_reset_user_data_len(nfc);
- for (i = 0; i < nchunks; i++)
- sunxi_nfc_set_user_data_len(nfc,
- sunxi_nfc_user_data_sz(sunxi_nand, first_step + i), i);
+ sunxi_nfc_set_user_data_len_dma(nand, first_step, nchunks);
/* exec_op() restores the page's spare base during column changes. */
if (first_step)
writel(mtd->writesize + sunxi_get_oob_offset(sunxi_nand, ecc, first_step),
@@ -1623,7 +1645,7 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,

sunxi_nfc_randomizer_config(nand, page, false);
sunxi_nfc_randomizer_enable(nand);
- sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step);
+ sunxi_nfc_set_user_data_len_pio(nfc, user_data_sz);
sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, nfc_step, bbm, page,
user_data_sz);

@@ -1890,7 +1912,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);
@@ -1898,7 +1920,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