From nobody Sun Apr 5 13:12:55 2026 Received: from smtpout-03.galae.net (smtpout-03.galae.net [185.246.85.4]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E8C2F2765F5 for ; Fri, 20 Feb 2026 16:10:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.246.85.4 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771603848; cv=none; b=petWW4A76ZyuOJKWHBnY+ZfhYna10+6gPbP76aVQUlIENdFHZs+tYVpZBCjDM2L8yB2975dcBgg1tHyGYhhNa1JAOU7vfyb6fkYWfZieNOYLwRNSmYpL1QBkEv0DwCycKuqlbWy/PBn+2TIaMBin6wWPdOgTCKnLsY3dwNxF5mc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771603848; c=relaxed/simple; bh=BiZdS9RgwOHOUtBfydEGsSsjYoAMncUEaaGdTaxbCyM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ed8z+QujBdo7/lYFJ1JItrVsk6UQGwV7Dd00clFa4X2baLHAxeEIOabIA6wXcgH4Nl4r9ohc26dPy6kc3mZLN5vLCE9zxOjjQxDi3z4SV3Re+Kt3mQfK9dqkG1W5VnSNmIYs0kZiWvRGZSDLiYDUfM0DZ02oYvo1kkiZEjt79Yo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=yhJmCVCP; arc=none smtp.client-ip=185.246.85.4 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="yhJmCVCP" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-03.galae.net (Postfix) with ESMTPS id A8AF54E4096B; Fri, 20 Feb 2026 16:10:41 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id 795C05FA8F; Fri, 20 Feb 2026 16:10:41 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id 8E30D10368D4B; Fri, 20 Feb 2026 17:10:39 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1771603840; h=from:subject:date:message-id:to:cc:mime-version: content-transfer-encoding:in-reply-to:references; bh=UqpZTvvt0fMC9dbVr1f07YoDPUQAVg+MQ+VVonZm8Rg=; b=yhJmCVCP4qGYwVk0PIeSeCC4BajSstWTbNZUl9vo/aqAc8S8AmHnbGcj2djGfofCrIcg1t yKj+sLKvZ3huTs1myiFylrcqIgvEv/JD8yRhGrOA3Yiu79ojkERc07e6iH0mad2DfiJULo T5xqOCkFQYNiOjwm7pXBfyN2M8oAZMxtGfsK9MFNMQDRF1CoZXKUGWHsZTjjF2t54gh748 abBuKQK9uPFvZfN7pl2Dp8AyS6jkj9BHGAjfz01HspZY0JTtwKiWCqQttpy4tcDbBceL4E N5ouJfQ/9Qj8kbTRFG25pm9yGiTqCEurEMJsTmcs6XJI7FqXMLOsXX8uov390Q== From: Richard Genoud To: Miquel Raynal , Richard Weinberger , Vignesh Raghavendra , Chen-Yu Tsai , Jernej Skrabec , Samuel Holland Cc: Wentao Liang , Maxime Ripard , Thomas Petazzoni , linux-mtd@lists.infradead.org, linux-arm-kernel@lists.infradead.org, linux-sunxi@lists.linux.dev, linux-kernel@vger.kernel.org, Richard Genoud Subject: [PATCH 6/6] mtd: rawnand: sunxi: introduce variable user data length Date: Fri, 20 Feb 2026 17:10:11 +0100 Message-ID: <20260220161011.999642-7-richard.genoud@bootlin.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260220161011.999642-1-richard.genoud@bootlin.com> References: <20260220161011.999642-1-richard.genoud@bootlin.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Last-TLS-Session-Version: TLSv1.3 Content-Type: text/plain; charset="utf-8" In Allwinner SoCs, user data can be added in OOB before each ECC data. For older SoCs like A10, the user data size was the size of a register (4 bytes) and was mandatory before each ECC step. So, the A10 OOB Layout is: [4Bytes USER_DATA_STEP0] [ECC_STEP0 bytes] [4bytes USER_DATA_STEP1] [ECC_STEP1 bytes] ... NB: the BBM is stored at the beginning of the USER_DATA_STEP0. Now, for H6/H616 NAND flash controller, this user data can have a different size for each step. And the vendor has chosen a different layout from the one on A10, using 8 bytes for step 0 and nothing for further steps: [8bytes USER_DATA_STEP0] [ECC_STEP0 bytes] [ECC_STEP1 bytes]... (Still with BBM stored at the beginning of the USER_DATA_STEP0) In order to be compatible with this layout, the current one for H6/H616 has to be changed. Fixes: 88fd4e4deae8 ("mtd: rawnand: sunxi: Add support for H616 nand contro= ller") Signed-off-by: Richard Genoud --- drivers/mtd/nand/raw/sunxi_nand.c | 267 ++++++++++++++++++++++-------- 1 file changed, 201 insertions(+), 66 deletions(-) diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi= _nand.c index b3a0f70dd4e9..0b6be18a0aaa 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -213,17 +213,6 @@ =20 #define NFC_MAX_CS 7 =20 -/* - * On A10/A23, this is the size of the NDFC User Data Register, containing= the - * mandatory user data bytes following the ECC for each ECC step. - * Thus, for each ECC step, we need the ECC bytes + USER_DATA_SZ. - * Those bits are currently unsused, and kept as default value 0xffffffff. - * - * On H6/H616, this size became configurable, from 0 bytes to 32, via the - * USER_DATA_LEN registers. - */ -#define USER_DATA_SZ 4 - /** * struct sunxi_nand_chip_sel - stores information related to NAND Chip Se= lect * @@ -306,6 +295,7 @@ static inline struct sunxi_nand_chip *to_sunxi_nand(str= uct nand_chip *nand) * bytes to write * @nuser_data_tab: Size of @user_data_len_tab * @sram_size: Size of the NAND controller SRAM + * @user_data_len Function returning the user data length for a step */ struct sunxi_nfc_caps { bool has_mdma; @@ -332,6 +322,7 @@ struct sunxi_nfc_caps { unsigned int nuser_data_tab; unsigned int max_ecc_steps; int sram_size; + unsigned int (*user_data_len)(int step); }; =20 /** @@ -830,11 +821,40 @@ static inline u32 sunxi_nfc_buf_to_user_data(const u8= *buf) } =20 static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8= *oob, - int step, bool bbm, int page) + int step, bool bbm, int page, + unsigned int user_data_sz) { struct sunxi_nfc *nfc =3D to_sunxi_nfc(nand->controller); + u32 user_data; =20 - sunxi_nfc_user_data_to_buf(readl(nfc->regs + NFC_REG_USER_DATA(nfc, step)= ), oob); + if (!nfc->caps->reg_user_data_len) { + /* + * For A10, the user data for step n is in the nth + * REG_USER_DATA + */ + user_data =3D 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 =3D 0; + unsigned int reg_off; + u8 *ptr =3D oob; + unsigned int i; + + for (i =3D 0; i < step; i++) + user_data_off +=3D nfc->caps->user_data_len(i); + + user_data_off /=3D 4; + for (i =3D 0; i < user_data_sz / 4; i++, ptr +=3D 4) { + reg_off =3D NFC_REG_USER_DATA(nfc, user_data_off + i); + user_data =3D readl(nfc->regs + reg_off); + sunxi_nfc_user_data_to_buf(user_data, ptr); + } + } =20 /* De-randomize the Bad Block Marker. */ if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) @@ -893,17 +913,45 @@ static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struc= t nand_chip *nand, bool bbm, int page) { struct sunxi_nfc *nfc =3D to_sunxi_nfc(nand->controller); - u8 user_data[USER_DATA_SZ]; + unsigned int user_data_sz =3D nfc->caps->user_data_len(step); + u8 *user_data =3D NULL; =20 /* Randomize the Bad Block Marker. */ if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) { - memcpy(user_data, oob, sizeof(user_data)); + user_data =3D kmalloc(user_data_sz, GFP_KERNEL); + memcpy(user_data, oob, user_data_sz); sunxi_nfc_randomize_bbm(nand, page, user_data); oob =3D user_data; } =20 - writel(sunxi_nfc_buf_to_user_data(oob), - nfc->regs + NFC_REG_USER_DATA(nfc, step)); + 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 =3D 0; + const u8 *ptr =3D oob; + unsigned int i; + + for (i =3D 0; i < step; i++) + user_data_off +=3D nfc->caps->user_data_len(i); + + user_data_off /=3D 4; + for (i =3D 0; i < user_data_sz / 4; i++, ptr +=3D 4) { + writel(sunxi_nfc_buf_to_user_data(ptr), + nfc->regs + NFC_REG_USER_DATA(nfc, user_data_off + i)); + } + } + + kfree(user_data); } =20 static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand, @@ -924,6 +972,7 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *n= and, u8 *data, u8 *oob, bool *erased) { struct sunxi_nfc *nfc =3D to_sunxi_nfc(nand->controller); + unsigned int user_data_sz =3D nfc->caps->user_data_len(step); struct nand_ecc_ctrl *ecc =3D &nand->ecc; u32 tmp; =20 @@ -946,7 +995,7 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *n= and, u8 *data, u8 *oob, memset(data, pattern, ecc->size); =20 if (oob) - memset(oob, pattern, ecc->bytes + USER_DATA_SZ); + memset(oob, pattern, ecc->bytes + user_data_sz); =20 return 0; } @@ -961,12 +1010,14 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_c= hip *nand, u8 *oob, int oob_off, int *cur_off, unsigned int *max_bitflips, - bool bbm, bool oob_required, int page) + int step, bool oob_required, int page) { struct sunxi_nfc *nfc =3D to_sunxi_nfc(nand->controller); + unsigned int user_data_sz =3D nfc->caps->user_data_len(step); struct nand_ecc_ctrl *ecc =3D &nand->ecc; int raw_mode =3D 0; u32 pattern_found; + bool bbm =3D !step; bool erased; int ret; /* From the controller point of view, we are at step 0 */ @@ -984,8 +1035,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chi= p *nand, if (ret) return ret; =20 - sunxi_nfc_reset_user_data_len(nfc); - sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, nfc_step); + sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step); sunxi_nfc_randomizer_config(nand, page, false); sunxi_nfc_randomizer_enable(nand); writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP, @@ -996,7 +1046,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chi= p *nand, if (ret) return ret; =20 - *cur_off =3D oob_off + ecc->bytes + USER_DATA_SZ; + *cur_off =3D oob_off + ecc->bytes + user_data_sz; =20 pattern_found =3D readl(nfc->regs + nfc->caps->reg_pat_found); pattern_found =3D field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found); @@ -1020,10 +1070,10 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_= chip *nand, ecc->size); =20 nand_change_read_column_op(nand, oob_off, oob, - ecc->bytes + USER_DATA_SZ, false); + ecc->bytes + user_data_sz, false); =20 ret =3D nand_check_erased_ecc_chunk(data, ecc->size, oob, - ecc->bytes + USER_DATA_SZ, + ecc->bytes + user_data_sz, NULL, 0, ecc->strength); if (ret >=3D 0) raw_mode =3D 1; @@ -1033,11 +1083,11 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_= chip *nand, 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, + sunxi_nfc_randomizer_read_buf(nand, oob, ecc->bytes + user_data_sz, true, page); =20 sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, nfc_step, - bbm, page); + bbm, page, user_data_sz); } } =20 @@ -1046,13 +1096,41 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_= chip *nand, return raw_mode; } =20 +/* + * Returns the offset of the OOB for each step. + * (it includes the user data before the ECC data.) + */ +static int sunxi_get_oob_offset(struct sunxi_nfc *nfc, + struct nand_ecc_ctrl *ecc, int step) +{ + int ecc_off =3D step * ecc->bytes; + int i; + + for (i =3D 0; i < step; i++) + ecc_off +=3D nfc->caps->user_data_len(i); + + return ecc_off; +} + +/* + * Returns the offset of the ECC for each step. + * So, it's the same as sunxi_get_oob_offset(), + * but it skips the next user data. + */ +static int sunxi_get_ecc_offset(struct sunxi_nfc *nfc, + struct nand_ecc_ctrl *ecc, int step) +{ + return sunxi_get_oob_offset(nfc, ecc, step) + nfc->caps->user_data_len(st= ep); +} + static void sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand, u8 *oob, int *cur_off, bool randomize, int page) { + struct sunxi_nfc *nfc =3D to_sunxi_nfc(nand->controller); struct mtd_info *mtd =3D nand_to_mtd(nand); struct nand_ecc_ctrl *ecc =3D &nand->ecc; - int offset =3D ((ecc->bytes + USER_DATA_SZ) * ecc->steps); + int offset =3D sunxi_get_oob_offset(nfc, ecc, ecc->steps); int len =3D mtd->oobsize - offset; =20 if (len <=3D 0) @@ -1096,7 +1174,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct na= nd_chip *nand, uint8_t *buf =20 sunxi_nfc_hw_ecc_enable(nand); sunxi_nfc_reset_user_data_len(nfc); - sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, 0); + for (i =3D 0; i < nchunks; i++) + sunxi_nfc_set_user_data_len(nfc, nfc->caps->user_data_len(i), i); sunxi_nfc_randomizer_config(nand, page, false); sunxi_nfc_randomizer_enable(nand); =20 @@ -1131,7 +1210,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct na= nd_chip *nand, uint8_t *buf =20 for (i =3D 0; i < nchunks; i++) { int data_off =3D i * ecc->size; - int oob_off =3D i * (ecc->bytes + USER_DATA_SZ); + unsigned int user_data_sz =3D nfc->caps->user_data_len(i); + int oob_off =3D sunxi_get_oob_offset(nfc, ecc, i); u8 *data =3D buf + data_off; u8 *oob =3D nand->oob_poi + oob_off; bool erased; @@ -1149,10 +1229,10 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct = nand_chip *nand, uint8_t *buf /* TODO: use DMA to retrieve OOB */ nand_change_read_column_op(nand, mtd->writesize + oob_off, - oob, ecc->bytes + USER_DATA_SZ, false); + oob, ecc->bytes + user_data_sz, false); =20 - sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, - !i, page); + sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, !i, + page, user_data_sz); } =20 if (erased) @@ -1164,7 +1244,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct na= nd_chip *nand, uint8_t *buf if (status & NFC_ECC_ERR_MSK(nfc)) { for (i =3D 0; i < nchunks; i++) { int data_off =3D i * ecc->size; - int oob_off =3D i * (ecc->bytes + USER_DATA_SZ); + unsigned int user_data_sz =3D nfc->caps->user_data_len(i); + int oob_off =3D sunxi_get_oob_offset(nfc, ecc, i); u8 *data =3D buf + data_off; u8 *oob =3D nand->oob_poi + oob_off; =20 @@ -1184,10 +1265,10 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct = nand_chip *nand, uint8_t *buf /* TODO: use DMA to retrieve OOB */ nand_change_read_column_op(nand, mtd->writesize + oob_off, - oob, ecc->bytes + USER_DATA_SZ, false); + oob, ecc->bytes + user_data_sz, false); =20 ret =3D nand_check_erased_ecc_chunk(data, ecc->size, oob, - ecc->bytes + USER_DATA_SZ, + ecc->bytes + user_data_sz, NULL, 0, ecc->strength); if (ret >=3D 0) @@ -1208,11 +1289,13 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct = nand_chip *nand, uint8_t *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, bool bbm, + int *cur_off, int step, int page) { struct sunxi_nfc *nfc =3D to_sunxi_nfc(nand->controller); + unsigned int user_data_sz =3D nfc->caps->user_data_len(step); struct nand_ecc_ctrl *ecc =3D &nand->ecc; + bool bbm =3D !step; int ret; /* From the controller point of view, we are at step 0 */ const int nfc_step =3D 0; @@ -1231,8 +1314,7 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_c= hip *nand, =20 sunxi_nfc_randomizer_config(nand, page, false); sunxi_nfc_randomizer_enable(nand); - sunxi_nfc_reset_user_data_len(nfc); - sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, nfc_step); + 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); =20 writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | @@ -1244,7 +1326,7 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_c= hip *nand, if (ret) return ret; =20 - *cur_off =3D oob_off + ecc->bytes + USER_DATA_SZ; + *cur_off =3D oob_off + ecc->bytes + user_data_sz; =20 return 0; } @@ -1255,7 +1337,8 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct n= and_chip *nand, { struct mtd_info *mtd =3D nand_to_mtd(nand); struct nand_ecc_ctrl *ecc =3D &nand->ecc; - int offset =3D ((ecc->bytes + USER_DATA_SZ) * ecc->steps); + struct sunxi_nfc *nfc =3D to_sunxi_nfc(nand->controller); + int offset =3D sunxi_get_oob_offset(nfc, ecc, ecc->steps); int len =3D mtd->oobsize - offset; =20 if (len <=3D 0) @@ -1274,6 +1357,7 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct n= and_chip *nand, static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf, int oob_required, int page) { + struct sunxi_nfc *nfc =3D to_sunxi_nfc(nand->controller); struct mtd_info *mtd =3D nand_to_mtd(nand); struct nand_ecc_ctrl *ecc =3D &nand->ecc; unsigned int max_bitflips =3D 0; @@ -1286,16 +1370,17 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_c= hip *nand, uint8_t *buf, =20 sunxi_nfc_hw_ecc_enable(nand); =20 + sunxi_nfc_reset_user_data_len(nfc); for (i =3D 0; i < ecc->steps; i++) { int data_off =3D i * ecc->size; - int oob_off =3D i * (ecc->bytes + USER_DATA_SZ); + int oob_off =3D sunxi_get_oob_offset(nfc, ecc, i); u8 *data =3D buf + data_off; u8 *oob =3D nand->oob_poi + oob_off; =20 ret =3D 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); if (ret < 0) return ret; else if (ret) @@ -1333,6 +1418,7 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_= chip *nand, u32 data_offs, u32 readlen, u8 *bufpoi, int page) { + struct sunxi_nfc *nfc =3D to_sunxi_nfc(nand->controller); struct mtd_info *mtd =3D nand_to_mtd(nand); struct nand_ecc_ctrl *ecc =3D &nand->ecc; int ret, i, cur_off =3D 0; @@ -1344,17 +1430,18 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nan= d_chip *nand, =20 sunxi_nfc_hw_ecc_enable(nand); =20 + sunxi_nfc_reset_user_data_len(nfc); for (i =3D data_offs / ecc->size; i < DIV_ROUND_UP(data_offs + readlen, ecc->size); i++) { int data_off =3D i * ecc->size; - int oob_off =3D i * (ecc->bytes + USER_DATA_SZ); + int oob_off =3D sunxi_get_oob_offset(nfc, ecc, i); u8 *data =3D bufpoi + data_off; u8 *oob =3D nand->oob_poi + oob_off; =20 ret =3D sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off, oob, oob_off + mtd->writesize, - &cur_off, &max_bitflips, !i, + &cur_off, &max_bitflips, i, false, page); if (ret < 0) return ret; @@ -1389,6 +1476,7 @@ static int sunxi_nfc_hw_ecc_write_page(struct nand_ch= ip *nand, const uint8_t *buf, int oob_required, int page) { + struct sunxi_nfc *nfc =3D to_sunxi_nfc(nand->controller); struct mtd_info *mtd =3D nand_to_mtd(nand); struct nand_ecc_ctrl *ecc =3D &nand->ecc; int ret, i, cur_off =3D 0; @@ -1399,15 +1487,16 @@ static int sunxi_nfc_hw_ecc_write_page(struct nand_= chip *nand, =20 sunxi_nfc_hw_ecc_enable(nand); =20 + sunxi_nfc_reset_user_data_len(nfc); for (i =3D 0; i < ecc->steps; i++) { int data_off =3D i * ecc->size; - int oob_off =3D i * (ecc->bytes + USER_DATA_SZ); + int oob_off =3D sunxi_get_oob_offset(nfc, ecc, i); const u8 *data =3D buf + data_off; const u8 *oob =3D nand->oob_poi + oob_off; =20 ret =3D sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob, oob_off + mtd->writesize, - &cur_off, !i, page); + &cur_off, i, page); if (ret) return ret; } @@ -1426,6 +1515,7 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct nand= _chip *nand, const u8 *buf, int oob_required, int page) { + struct sunxi_nfc *nfc =3D to_sunxi_nfc(nand->controller); struct mtd_info *mtd =3D nand_to_mtd(nand); struct nand_ecc_ctrl *ecc =3D &nand->ecc; int ret, i, cur_off =3D 0; @@ -1436,16 +1526,17 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct na= nd_chip *nand, =20 sunxi_nfc_hw_ecc_enable(nand); =20 + sunxi_nfc_reset_user_data_len(nfc); for (i =3D data_offs / ecc->size; i < DIV_ROUND_UP(data_offs + data_len, ecc->size); i++) { int data_off =3D i * ecc->size; - int oob_off =3D i * (ecc->bytes + USER_DATA_SZ); + int oob_off =3D sunxi_get_oob_offset(nfc, ecc, i); const u8 *data =3D buf + data_off; const u8 *oob =3D nand->oob_poi + oob_off; =20 ret =3D sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob, oob_off + mtd->writesize, - &cur_off, !i, page); + &cur_off, i, page); if (ret) return ret; } @@ -1479,10 +1570,12 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct n= and_chip *nand, =20 sunxi_nfc_reset_user_data_len(nfc); for (i =3D 0; i < ecc->steps; i++) { - const u8 *oob =3D nand->oob_poi + (i * (ecc->bytes + USER_DATA_SZ)); + unsigned int user_data_sz =3D nfc->caps->user_data_len(i); + int oob_off =3D sunxi_get_oob_offset(nfc, ecc, i); + const u8 *oob =3D nand->oob_poi + oob_off; =20 sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, i, !i, page); - sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, i); + sunxi_nfc_set_user_data_len(nfc, user_data_sz, i); } =20 nand_prog_page_begin_op(nand, page, 0, NULL, 0); @@ -1746,11 +1839,12 @@ static int sunxi_nand_ooblayout_ecc(struct mtd_info= *mtd, int section, { struct nand_chip *nand =3D mtd_to_nand(mtd); struct nand_ecc_ctrl *ecc =3D &nand->ecc; + struct sunxi_nfc *nfc =3D to_sunxi_nfc(nand->controller); =20 if (section >=3D ecc->steps) return -ERANGE; =20 - oobregion->offset =3D section * (ecc->bytes + USER_DATA_SZ) + USER_DATA_S= Z; + oobregion->offset =3D sunxi_get_ecc_offset(nfc, ecc, section); oobregion->length =3D ecc->bytes; =20 return 0; @@ -1761,18 +1855,20 @@ static int sunxi_nand_ooblayout_free(struct mtd_inf= o *mtd, int section, { struct nand_chip *nand =3D mtd_to_nand(mtd); struct nand_ecc_ctrl *ecc =3D &nand->ecc; + struct sunxi_nfc *nfc =3D to_sunxi_nfc(nand->controller); + unsigned int user_data_sz =3D nfc->caps->user_data_len(section); =20 if (section > ecc->steps) return -ERANGE; =20 /* * The first 2 bytes are used for BB markers, hence we - * only have USER_DATA_SZ - 2 bytes available in the first user data + * only have user_data_sz - 2 bytes available in the first user data * section. */ if (!section && ecc->engine_type =3D=3D NAND_ECC_ENGINE_TYPE_ON_HOST) { oobregion->offset =3D 2; - oobregion->length =3D USER_DATA_SZ - 2; + oobregion->length =3D user_data_sz - 2; =20 return 0; } @@ -1784,10 +1880,10 @@ static int sunxi_nand_ooblayout_free(struct mtd_inf= o *mtd, int section, if (section =3D=3D ecc->steps && ecc->engine_type =3D=3D NAND_ECC_ENGINE_= TYPE_ON_HOST) return -ERANGE; =20 - oobregion->offset =3D section * (ecc->bytes + USER_DATA_SZ); + oobregion->offset =3D sunxi_get_ecc_offset(nfc, ecc, section); =20 if (section < ecc->steps) - oobregion->length =3D USER_DATA_SZ; + oobregion->length =3D user_data_sz; else oobregion->length =3D mtd->oobsize - oobregion->offset; =20 @@ -1808,14 +1904,18 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_= chip *nand, const u8 *strengths =3D nfc->caps->ecc_strengths; struct mtd_info *mtd =3D nand_to_mtd(nand); struct nand_device *nanddev =3D mtd_to_nanddev(mtd); + int total_user_data_sz =3D 0; int nsectors; int i; =20 - if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) { - int bytes; + ecc->size =3D 1024; + nsectors =3D mtd->writesize / ecc->size; =20 - ecc->size =3D 1024; - nsectors =3D mtd->writesize / ecc->size; + for (i =3D 0; i < nsectors; i++) + total_user_data_sz +=3D nfc->caps->user_data_len(i); + + if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) { + int bytes =3D mtd->oobsize; =20 /* * The 2 BBM bytes should not be removed from the grand total, @@ -1825,15 +1925,15 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_= chip *nand, * compatibility. */ if (nfc->caps->legacy_max_strength) - bytes =3D (mtd->oobsize - 2) / nsectors; - else - bytes =3D mtd->oobsize / nsectors; + bytes -=3D 2; + + bytes -=3D total_user_data_sz; =20 /* - * USER_DATA_SZ non-ECC bytes are added before each ECC bytes - * section, they contain the 2 BBM bytes + * Once all user data has been subtracted, the rest can be used + * for ECC bytes */ - bytes -=3D USER_DATA_SZ; + bytes /=3D nsectors; =20 /* and bytes has to be even. */ if (bytes % 2) @@ -1886,7 +1986,7 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_ch= ip *nand, =20 nsectors =3D mtd->writesize / ecc->size; =20 - if (mtd->oobsize < ((ecc->bytes + USER_DATA_SZ) * nsectors)) + if (mtd->oobsize < (ecc->bytes * nsectors + total_user_data_sz)) return -EINVAL; =20 ecc->read_oob =3D sunxi_nfc_hw_ecc_read_oob; @@ -2383,6 +2483,38 @@ static void sunxi_nfc_remove(struct platform_device = *pdev) dma_release_channel(nfc->dmac); } =20 +static unsigned int sunxi_user_data_len_h616(int step) +{ + /* + * On H6/H616, the user data size became configurable, + * from 0 bytes to 32, via the USER_DATA_LEN registers. + * + * In H616 vendor image, the user data length is 8 byte on step 0 + * (that includes the BBM) and 0 bytes for the rest. + * So the OOB layout is: + * [BBM] [BBM] [6bytes USER_DATA_STEP0] [ECC_STEP0 bytes] [ECC_STEP1 byte= s]... + */ + if (step =3D=3D 0) + return 8; + return 0; +} + +static unsigned int sunxi_user_data_len_a10(int step) +{ + /* + * On A10/A23, this is the size of the NDFC User Data Register, + * containing the mandatory user data bytes preceding the ECC for each + * ECC step (and including the BBM) + * Thus, for each ECC step, we need USER_DATA_SZ + ECC bytes. + * + * So the layout is: + * [BBM] [BBM] [2Bytes USER_DATA_STEP0] [ECC_STEP0 bytes] + * [4bytes USER_DATA_STEP1] [ECC_step1 bytes]... + */ + + return 4; +} + static const u8 sunxi_ecc_strengths_a10[] =3D { 16, 24, 28, 32, 40, 48, 56, 60, 64 }; @@ -2414,6 +2546,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_a10_caps= =3D { .nstrengths =3D ARRAY_SIZE(sunxi_ecc_strengths_a10), .max_ecc_steps =3D 16, .sram_size =3D 1024, + .user_data_len =3D &sunxi_user_data_len_a10, }; =20 static const struct sunxi_nfc_caps sunxi_nfc_a23_caps =3D { @@ -2436,6 +2569,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_a23_caps= =3D { .nstrengths =3D ARRAY_SIZE(sunxi_ecc_strengths_a10), .max_ecc_steps =3D 16, .sram_size =3D 1024, + .user_data_len =3D &sunxi_user_data_len_a10, }; =20 static const struct sunxi_nfc_caps sunxi_nfc_h616_caps =3D { @@ -2460,6 +2594,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_h616_cap= s =3D { .nuser_data_tab =3D ARRAY_SIZE(sunxi_user_data_len_h6), .max_ecc_steps =3D 32, .sram_size =3D 8192, + .user_data_len =3D &sunxi_user_data_len_h616, }; =20 static const struct of_device_id sunxi_nfc_ids[] =3D {