From nobody Fri Apr 3 14:18:12 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 4DDC13E7142 for ; Tue, 17 Mar 2026 14:25:13 +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=1773757515; cv=none; b=HbzdPdpJ8sKXQHIK6cw3y967p+7gjq3bJyRMbThkbkMrEcuSlwnjHMCkEg1KOZNjL/Ms4htYBUUAXrzywEsloEMfrl4Ly26t6ktfyFj/kXu1wYw7yK/Y4LRQCjxw9cB9W9eraovfMWBTyFhNLny93yUqOrR5sC315BBL9o66UVo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773757515; c=relaxed/simple; bh=e/EC7uhBMg46w4RgVECb7Y2cJdigel5GDEukQpMkRGY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=kVl2I9GYIPEXwQeSj6iXj5Qw+9qDxB69WmxhDn0ilyFZpyLDQYRyb2gyghMuvENjb49zc7zRCek2lVa1CFhcJp5+6legt6sF92dAAHQUrMbsbgBsa6f+ptfnBv3n7in8Joeoqkk7DhEVgZLBo9fGqmQ51iJplzULX65BfilK9MM= 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=uzugZXaz; 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="uzugZXaz" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-03.galae.net (Postfix) with ESMTPS id 0A1ED4E42655; Tue, 17 Mar 2026 14:25:12 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id D428A5FC9A; Tue, 17 Mar 2026 14:25:11 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id 7CFCC104505FF; Tue, 17 Mar 2026 15:25:08 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1773757510; h=from:subject:date:message-id:to:cc:mime-version: content-transfer-encoding:in-reply-to:references; bh=SdGL0v851iMAfKHdLoaD/OZ4A1ToHP3079kGC0oRrE8=; b=uzugZXazrccAQaFo5vzX9JhFnOmCv++y7NuK+u2eAqeSN82UTMzimrjBsdEU0IP3WU4jpm AXVp+Mt1NKG/6WRW4tp9cdV6ytMBr4R7RgNNHmTAQkhyjWW6pxLkZBmwfsid+ECJbPYBox shsbMfQz84kAMo648QTwBt01s0/onzdtDeZk3+wJ94Dl5TNwiXZ/WvmDUCGtlx7YcDT3AS wUKxfRWGumSgpJwpX3iuNEDiJUn0LYKy+r0XrSqW2P9Dk13hLo5YSykZZ4KLPFCyiRBbG/ oB0YVKSMe/km08X2KXwReicznwU5JINLcRf+wwizNP+TrRMOBBU65oiFQHpZ1A== From: Richard Genoud To: Miquel Raynal , Richard Weinberger , Vignesh Raghavendra , Chen-Yu Tsai , Jernej Skrabec , Samuel Holland Cc: Wentao Liang , Maxime Ripard , Boris Brezillon , 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 v3 9/9] mtd: rawnand: sunxi: introduce maximize variable user data length Date: Tue, 17 Mar 2026 15:24:37 +0100 Message-ID: <20260317142437.580204-10-richard.genoud@bootlin.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260317142437.580204-1-richard.genoud@bootlin.com> References: <20260317142437.580204-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. So, we are maximizing the user data length to use as many OOB bytes as possible. Fixes: 88fd4e4deae8 ("mtd: rawnand: sunxi: Add support for H616 nand contro= ller") Signed-off-by: Richard Genoud --- drivers/mtd/nand/raw/sunxi_nand.c | 321 ++++++++++++++++++++++++------ 1 file changed, 257 insertions(+), 64 deletions(-) diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi= _nand.c index 3d2580d39e70..02647565c8ba 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -209,9 +209,8 @@ =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. + * mandatory user data bytes preceding 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. @@ -249,6 +248,7 @@ struct sunxi_nand_hw_ecc { * @timing_ctl: TIMING_CTL register value for this NAND chip * @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 */ struct sunxi_nand_chip { struct list_head node; @@ -257,6 +257,7 @@ struct sunxi_nand_chip { unsigned long clk_rate; u32 timing_cfg; u32 timing_ctl; + u8 *user_data_bytes; int nsels; struct sunxi_nand_chip_sel sels[] __counted_by(nsels); }; @@ -823,12 +824,50 @@ static inline u32 sunxi_nfc_buf_to_user_data(const u8= *buf) return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); } =20 -static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8= *oob, - int step, bool bbm, int page) +static u8 sunxi_nfc_user_data_sz(struct sunxi_nand_chip *sunxi_nand, int s= tep) { - struct sunxi_nfc *nfc =3D to_sunxi_nfc(nand->controller); + if (!sunxi_nand->user_data_bytes) + return USER_DATA_SZ; =20 - sunxi_nfc_user_data_to_buf(readl(nfc->regs + NFC_REG_USER_DATA(nfc, step)= ), oob); + return sunxi_nand->user_data_bytes[step]; +} + +static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8= *oob, + int step, bool bbm, int page, + unsigned int user_data_sz) +{ + struct sunxi_nand_chip *sunxi_nand =3D to_sunxi_nand(nand); + struct sunxi_nfc *nfc =3D to_sunxi_nfc(nand->controller); + u32 user_data; + + 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 sunxi_nfc_user_data_sz(sunxi_nand, 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)) @@ -887,17 +926,46 @@ 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]; + struct sunxi_nand_chip *sunxi_nand =3D to_sunxi_nand(nand); + unsigned int user_data_sz =3D sunxi_nfc_user_data_sz(sunxi_nand, 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 sunxi_nfc_user_data_sz(sunxi_nand, 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, @@ -918,6 +986,8 @@ 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); + struct sunxi_nand_chip *sunxi_nand =3D to_sunxi_nand(nand); + unsigned int user_data_sz =3D sunxi_nfc_user_data_sz(sunxi_nand, step); struct nand_ecc_ctrl *ecc =3D &nand->ecc; u32 tmp; =20 @@ -940,7 +1010,7 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *= nand, 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; } @@ -955,12 +1025,15 @@ 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); + struct sunxi_nand_chip *sunxi_nand =3D to_sunxi_nand(nand); + unsigned int user_data_sz =3D sunxi_nfc_user_data_sz(sunxi_nand, 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 */ @@ -978,8 +1051,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, @@ -990,7 +1062,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); @@ -1014,10 +1086,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; @@ -1027,11 +1099,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 @@ -1040,13 +1112,42 @@ 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_nand_chip *sunxi_nand, + 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 sunxi_nfc_user_data_sz(sunxi_nand, 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_nand_chip *sunxi_nand, + struct nand_ecc_ctrl *ecc, int step) +{ + return sunxi_get_oob_offset(sunxi_nand, ecc, step) + + 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) { + struct sunxi_nand_chip *sunxi_nand =3D to_sunxi_nand(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); + int offset =3D sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps); int len =3D mtd->oobsize - offset; =20 if (len <=3D 0) @@ -1071,6 +1172,7 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct na= nd_chip *nand, uint8_t *buf int nchunks) { bool randomized =3D nand->options & NAND_NEED_SCRAMBLING; + struct sunxi_nand_chip *sunxi_nand =3D to_sunxi_nand(nand); 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; @@ -1090,7 +1192,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, sunxi_nfc_user_data_sz(sunxi_nand, i), = i); sunxi_nfc_randomizer_config(nand, page, false); sunxi_nfc_randomizer_enable(nand); =20 @@ -1125,7 +1228,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 sunxi_nfc_user_data_sz(sunxi_nand, i); + int oob_off =3D sunxi_get_oob_offset(sunxi_nand, ecc, i); u8 *data =3D buf + data_off; u8 *oob =3D nand->oob_poi + oob_off; bool erased; @@ -1143,10 +1247,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) @@ -1158,7 +1262,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 sunxi_nfc_user_data_sz(sunxi_nand, i); + int oob_off =3D sunxi_get_oob_offset(sunxi_nand, ecc, i); u8 *data =3D buf + data_off; u8 *oob =3D nand->oob_poi + oob_off; =20 @@ -1178,10 +1283,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) @@ -1202,11 +1307,14 @@ 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); + struct sunxi_nand_chip *sunxi_nand =3D to_sunxi_nand(nand); + unsigned int user_data_sz =3D sunxi_nfc_user_data_sz(sunxi_nand, 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; @@ -1225,8 +1333,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 | @@ -1238,7 +1345,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; } @@ -1248,8 +1355,9 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct n= and_chip *nand, int page) { struct mtd_info *mtd =3D nand_to_mtd(nand); + struct sunxi_nand_chip *sunxi_nand =3D to_sunxi_nand(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(sunxi_nand, ecc, ecc->steps); int len =3D mtd->oobsize - offset; =20 if (len <=3D 0) @@ -1268,6 +1376,8 @@ 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 sunxi_nand_chip *sunxi_nand =3D to_sunxi_nand(nand); struct mtd_info *mtd =3D nand_to_mtd(nand); struct nand_ecc_ctrl *ecc =3D &nand->ecc; unsigned int max_bitflips =3D 0; @@ -1280,16 +1390,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(sunxi_nand, 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) @@ -1327,6 +1438,8 @@ 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 sunxi_nand_chip *sunxi_nand =3D to_sunxi_nand(nand); struct mtd_info *mtd =3D nand_to_mtd(nand); struct nand_ecc_ctrl *ecc =3D &nand->ecc; int ret, i, cur_off =3D 0; @@ -1338,17 +1451,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(sunxi_nand, 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; @@ -1383,6 +1497,8 @@ 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 sunxi_nand_chip *sunxi_nand =3D to_sunxi_nand(nand); struct mtd_info *mtd =3D nand_to_mtd(nand); struct nand_ecc_ctrl *ecc =3D &nand->ecc; int ret, i, cur_off =3D 0; @@ -1393,15 +1509,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(sunxi_nand, 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; } @@ -1420,6 +1537,8 @@ 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 sunxi_nand_chip *sunxi_nand =3D to_sunxi_nand(nand); struct mtd_info *mtd =3D nand_to_mtd(nand); struct nand_ecc_ctrl *ecc =3D &nand->ecc; int ret, i, cur_off =3D 0; @@ -1430,16 +1549,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(sunxi_nand, 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; } @@ -1455,6 +1575,7 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nan= d_chip *nand, int page) { struct sunxi_nfc *nfc =3D to_sunxi_nfc(nand->controller); + struct sunxi_nand_chip *sunxi_nand =3D to_sunxi_nand(nand); struct nand_ecc_ctrl *ecc =3D &nand->ecc; struct scatterlist sg; u32 wait; @@ -1473,10 +1594,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 sunxi_nfc_user_data_sz(sunxi_nand, i); + int oob_off =3D sunxi_get_oob_offset(sunxi_nand, 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); @@ -1740,11 +1863,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_nand_chip *sunxi_nand =3D to_sunxi_nand(nand); =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(sunxi_nand, ecc, section); oobregion->length =3D ecc->bytes; =20 return 0; @@ -1755,6 +1879,8 @@ static int sunxi_nand_ooblayout_free(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_nand_chip *sunxi_nand =3D to_sunxi_nand(nand); + unsigned int user_data_sz =3D sunxi_nfc_user_data_sz(sunxi_nand, section); =20 /* * The controller does not provide access to OOB bytes @@ -1765,18 +1891,18 @@ static int sunxi_nand_ooblayout_free(struct mtd_inf= o *mtd, int section, =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 =3D=3D 0) { oobregion->offset =3D 2; - oobregion->length =3D USER_DATA_SZ - 2; + oobregion->length =3D user_data_sz - 2; =20 return 0; } =20 - oobregion->offset =3D section * (ecc->bytes + USER_DATA_SZ); - oobregion->length =3D USER_DATA_SZ; + oobregion->offset =3D sunxi_get_ecc_offset(sunxi_nand, ecc, section); + oobregion->length =3D user_data_sz; =20 return 0; } @@ -1786,6 +1912,43 @@ static const struct mtd_ooblayout_ops sunxi_nand_oob= layout_ops =3D { .free =3D sunxi_nand_ooblayout_free, }; =20 +static void sunxi_nand_detach_chip(struct nand_chip *nand) +{ + struct sunxi_nand_chip *sunxi_nand =3D to_sunxi_nand(nand); + struct sunxi_nfc *nfc =3D to_sunxi_nfc(nand->controller); + + devm_kfree(nfc->dev, sunxi_nand->user_data_bytes); + sunxi_nand->user_data_bytes =3D NULL; +} + +static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t o= obsize, + int ecc_bytes, int nsectors) +{ + struct sunxi_nand_chip *sunxi_nand =3D to_sunxi_nand(nand); + struct sunxi_nfc *nfc =3D to_sunxi_nfc(nand->controller); + const struct sunxi_nfc_caps *c =3D nfc->caps; + int remaining_bytes =3D oobsize - (ecc_bytes * nsectors); + int i, step; + + sunxi_nand->user_data_bytes =3D devm_kzalloc(nfc->dev, nsectors, + GFP_KERNEL); + if (!sunxi_nand->user_data_bytes) + return -ENOMEM; + + for (step =3D 0; (step < nsectors) && (remaining_bytes > 0); step++) { + for (i =3D 0; i < c->nuser_data_tab; i++) { + if (c->user_data_len_tab[i] > remaining_bytes) + break; + sunxi_nand->user_data_bytes[step] =3D c->user_data_len_tab[i]; + } + remaining_bytes -=3D sunxi_nand->user_data_bytes[step]; + if (sunxi_nand->user_data_bytes[step] =3D=3D 0) + break; + } + + return 0; +} + static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, struct nand_ecc_ctrl *ecc, struct device_node *np) @@ -1795,33 +1958,50 @@ 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 ecc_mode; int i; =20 if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) { - int bytes; + int bytes =3D mtd->oobsize; =20 ecc->size =3D 1024; nsectors =3D mtd->writesize / ecc->size; =20 - /* - * The 2 BBM bytes should not be removed from the grand total, - * because they are part of the USER_DATA_SZ. - * But we can't modify that for older platform since it may - * result in a stronger ECC at the end, and break the - * compatibility. - */ - if (nfc->caps->legacy_max_strength) - bytes =3D (mtd->oobsize - 2) / nsectors; - else - bytes =3D mtd->oobsize / nsectors; + if (!nfc->caps->reg_user_data_len) { + /* + * If there's a fixed user data length, subtract it before + * computing the max ECC strength + */ + + for (i =3D 0; i < nsectors; i++) + total_user_data_sz +=3D sunxi_nfc_user_data_sz(sunxi_nand, i); + + /* + * The 2 BBM bytes should not be removed from the grand total, + * because they are part of the USER_DATA_SZ. + * But we can't modify that for older platform since it may + * result in a stronger ECC at the end, and break the + * compatibility. + */ + if (nfc->caps->legacy_max_strength) + bytes -=3D 2; + + bytes -=3D total_user_data_sz; + } else { + /* + * remove at least the BBM size before computing the + * max ECC + */ + bytes -=3D 2; + } =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) @@ -1874,7 +2054,19 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_c= hip *nand, =20 nsectors =3D mtd->writesize / ecc->size; =20 - if (mtd->oobsize < ((ecc->bytes + USER_DATA_SZ) * nsectors)) + /* + * The rationale for variable data length is to prioritize maximum ECC + * strength, and then use the remaining space for user data. + */ + if (nfc->caps->reg_user_data_len) + sunxi_nfc_maximize_user_data(nand, mtd->oobsize, ecc->bytes, + nsectors); + + if (total_user_data_sz =3D=3D 0) + for (i =3D 0; i < nsectors; i++) + total_user_data_sz +=3D sunxi_nfc_user_data_sz(sunxi_nand, i); + + if (mtd->oobsize < (ecc->bytes * nsectors + total_user_data_sz)) return -EINVAL; =20 ecc->read_oob =3D sunxi_nfc_hw_ecc_read_oob; @@ -2104,6 +2296,7 @@ static int sunxi_nfc_exec_op(struct nand_chip *nand, =20 static const struct nand_controller_ops sunxi_nand_controller_ops =3D { .attach_chip =3D sunxi_nand_attach_chip, + .detach_chip =3D sunxi_nand_detach_chip, .setup_interface =3D sunxi_nfc_setup_interface, .exec_op =3D sunxi_nfc_exec_op, };