From patchwork Fri Oct 16 03:45:32 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Hilliard X-Patchwork-Id: 3641 Return-Path: X-Original-To: noreply@patchwork.local Delivered-To: noreply@patchwork.local Received: from sto.lore.kernel.org (sto.lore.kernel.org [172.232.135.74]) by mxe881.netcup.net (Postfix) with ESMTPS id B0BF31C1CBB for ; Fri, 16 Oct 2026 05:48:47 +0200 (CEST) Authentication-Results: mxe881; dkim=pass header.d=gmail.com; spf=pass (sender IP is 172.232.135.74) smtp.mailfrom=linux-sunxi+bounces-26692-noreply=patchwork.local@lists.linux.dev smtp.helo=sto.lore.kernel.org Received-SPF: pass (mxe881: domain of lists.linux.dev designates 172.232.135.74 as permitted sender) client-ip=172.232.135.74; envelope-from=linux-sunxi+bounces-26692-noreply=patchwork.local@lists.linux.dev; helo=sto.lore.kernel.org; Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by sto.lore.kernel.org (Postfix) with ESMTP id 32D0F6080DA for ; Fri, 16 Oct 2026 03:47:31 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 33EA63B27D3; Fri, 16 Oct 2026 03:46:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="o3RBZG21" X-Original-To: linux-sunxi@lists.linux.dev Received: from mail-ot1-f54.google.com (mail-ot1-f54.google.com [209.85.210.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 482723C417E for ; Fri, 16 Oct 2026 03:45:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792122362; cv=none; b=OycNj6wbi3dyYy9aO+pB3KICjNXMQeKEu2F4/RYSJJ1bY34hPkvZEybQ/egWbSC82WEqbWxvgmgtDSKj4cSbEJHVBWx4WeAHk6bwZeE/GFbKHpG9uKTn/vYUb19eCQGrWF7hJnx4i9vX7p8wd/56BJHHPmtwfJBfpVrv2G922DM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792122362; c=relaxed/simple; bh=pp/7egmqL+3cGtYqJnr9xv+dPjdJAkMPSG5BNPLG7Zg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Z5qbL/xCL6jj/wAzCNnONAmHfHN6C/emkDdkrExEwJoCaW6AIQsSRoBEFno/tuhmKRK0Du06+Wm4+G0SuhbdvrgCeV/Zv4Hs0KSKtPBCpDaltOsh02qJXVATX5SIa8KYLJrtyV+POagjNWZjyCF4euDsuaJZgH/2GG4K7/E9Rc4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=o3RBZG21; arc=none smtp.client-ip=209.85.210.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-ot1-f54.google.com with SMTP id 46e09a7af769-82478760557so2035429a34.3 for ; Thu, 15 Oct 2026 20:45:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1792122358; x=1792727158; darn=lists.linux.dev; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=QbSWlHvRSkwrntnAwnz2EiQPWPQUyhRA77LwoZKqBYc=; b=o3RBZG21oygWV8U8ljbVKynneLLpMhyqTnueXxqQehcTe43aaNHKsdz80rdxMhamfQ Zdkg1EGInSdFplla6FXzmOPk1ovUkNgmJemA2qnLjEbyoRCnK64pqMdo53aLRAyte4TN /nk5wuNSHoemJS3eXWX/z6X6WCC9jWwPpKIPVG1gDhh/T3eEj5HboBs3wPAiYpowST9C qA3IVtJI5K0TzMXc+xlbaVhdn0RYIYOcSatGSqmOVg/x/wsOpIBxU9Srg5jhnzQlTy8U npVZ/mI8pMb20i92hCSNw/OxWWbjfzIE28N32hBDT3j/UiYV+OZe2C/G2RrFzP8nHQwG Xa1g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792122358; x=1792727158; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=QbSWlHvRSkwrntnAwnz2EiQPWPQUyhRA77LwoZKqBYc=; b=K0Uj4ijjofcvvbUKC1CJUuIrgCTq/rBU3ncfbgYURyzglGYWboNTQJEvQ0zvWd24aC tzRh+HukW05A0aAlO+DMSdlu5/ZoQU+w8rPuNRpgRgR3F351z2wQJRYqUa9uqi+w8+Jb fW/jD4WZ9jborayFqa183T+f1/R8ocwDGkPqvTGFJLaaqhY0osbA8WhcXVsixWcn9rs0 fC1SO50fYfmp9Hga+cYc8tymuhsty9h0L20b8JRhj09bf1Svq0NxrO7MJsqZLt0bHPXO ERa4Ud4dtePLPvOM6w0rMjHtB1UOZCT5M7CrdmIUfAc8DXspaDzaQVpJ7tOpzrdi3AHM Fz1A== X-Forwarded-Encrypted: i=1; AKwUvBzaGo2xEWBuqqOmXAQNuIm+ULicAGwtLIdu4+5wQjLEOm5qymJRa170m9fOIMb4WskcLGgJAjh2jYy1Wg==@lists.linux.dev X-Gm-Message-State: AFq9FYJrW5lcI5ti14lFGr3kAmHBf5Om4tdIkqjnkLx3/Kc2N5IpOGWv PZZPRJkb6b64ZDdnKmKcM88u3hqCox7wzkRLTFP5TrVD1DGBaO6n2TT8 X-Gm-Gg: AYBFou1TaFi1jsVTnbE/gdr4pnE4ki1JFRszDZDIuM5/xkHYpqwSuK8wFaPC9XpY6Jv R8b/ZBiPu+fUBWrxTEEtkALpq9edl5EpfNI0NxsXPcYlOT2JRANqxCr1huhJKY1Pzq7AXp540iE dSO/06IsnTGaPIYf2k34VjHxCuq2zi8dEmzWLC92a8AX6ltKyyFABa3QNZOZpwxfa2fdi6GAVoG ZMulzWNdmFIG2NOWEovU3z7/9R/JUMapV/0+HuIW70ysQEPBy61LOGSjaSJcT+g73nXzq3wQCEM xgGxEQZHAWNa9JMdZL0nKwZoryLvYXtENDPiw6+48zUZXWB01sAuUr8JSrBWLlzi2w/akON/1Qp oyvjdUDIxcII4+JSNyTAaepwqXBh/IiPUjL5AAsnX4Gpvs0kg3dAriS9B+2U9dKM02x2A4kWrSg /9pTfhiaMy6MZ3w8a9LmbDPfLUSx4Lth708Z46Ba01CqtqhImowLr7lIEw4IQXwAH9Xib6LIKlj aIql52IHevyXUvSUBl1klsl8baGfBi0obh3b2Xx42WNp6nd8TZrgF/hDtD73bXrVz0qSyg7ypnZ 764VGmmYuDGdyu/k1uhRJMwAo0Dq0xzH9V3ggJnP1jT7X9Awu3a1CIiyFs3MiYss2FKXT0oYydX sCxqrPr+AohYzHvpl0QQ6cw== X-Received: by 2002:a05:6830:6d45:b0:806:3072:240c with SMTP id 46e09a7af769-8309815e745mr20040213a34.29.1792122357925; Thu, 15 Oct 2026 20:45:57 -0700 (PDT) Received: from [127.0.1.1] (174-29-1-49.hlrn.qwest.net. [174.29.1.49]) by smtp.gmail.com with ESMTPSA id 46e09a7af769-8429b57870fsm834402a34.0.2026.10.15.20.45.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 15 Oct 2026 20:45:57 -0700 (PDT) From: James Hilliard Date: Thu, 15 Oct 2026 21:45:32 -0600 Subject: [PATCH v8 17/17] mtd: rawnand: sunxi: support the Allwinner randomized OOB format Precedence: bulk X-Mailing-List: linux-sunxi@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20261015-submit-sunxi-nand-vendor-oob-layout-v1-v8-17-8ba4881fbee8@gmail.com> References: <20261015-submit-sunxi-nand-vendor-oob-layout-v1-v8-0-8ba4881fbee8@gmail.com> In-Reply-To: <20261015-submit-sunxi-nand-vendor-oob-layout-v1-v8-0-8ba4881fbee8@gmail.com> To: Miquel Raynal , Richard Weinberger , Vignesh Raghavendra , Sebastian Andrzej Siewior , Clark Williams , Steven Rostedt , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: Chen-Yu Tsai , Jernej Skrabec , Samuel Holland , Maxime Ripard , Richard Genoud , Boris Brezillon , Masahiro Yamada , Brian Norris , linux-mtd@lists.infradead.org, linux-arm-kernel@lists.infradead.org, linux-sunxi@lists.linux.dev, linux-kernel@vger.kernel.org, linux-rt-devel@lists.linux.dev, devicetree@vger.kernel.org, James Hilliard X-Mailer: b4 0.15.2 X-Rspamd-Server: rspamd-worker-8404 X-Spamd-Result: default: False [4.34 / 15.00]; RBL_SENDERSCORE(2.00)[172.232.135.74:from]; SUSPICIOUS_RECIPS(1.50)[]; DMARC_POLICY_SOFTFAIL(1.00)[gmail.com : SPF not aligned (relaxed), No valid DKIM,none]; MAILLIST(-0.15)[generic]; MIME_GOOD(-0.10)[text/plain]; BAD_REP_POLICIES(0.10)[]; HAS_LIST_UNSUB(-0.01)[]; FROM_HAS_DN(0.00)[]; PRECEDENCE_BULK(0.00)[]; ARC_ALLOW(0.00)[subspace.kernel.org:s=arc-20240116:i=1]; RCPT_COUNT_TWELVE(0.00)[24]; TAGGED_RCPT(0.00)[dt]; FREEMAIL_CC(0.00)[kernel.org,gmail.com,sholland.org,bootlin.com,socionext.com,lists.infradead.org,lists.linux.dev,vger.kernel.org]; DBL_BLOCKED_OPENRESOLVER(0.00)[sto.lore.kernel.org:rdns,sto.lore.kernel.org:helo]; FORGED_SENDER_MAILLIST(0.00)[]; RCVD_COUNT_FIVE(0.00)[6]; ASN(0.00)[asn:63949, ipnet:172.232.128.0/19, country:SG]; TO_DN_SOME(0.00)[]; R_SPF_ALLOW(0.00)[+ip4:172.232.135.74:c]; RECEIVED_SPAMHAUS_BLOCKED_OPENRESOLVER(0.00)[174.29.1.49:received,100.90.174.1:received,209.85.210.54:received]; RBL_SPAMHAUS_BLOCKED_OPENRESOLVER(0.00)[172.232.135.74:from]; FROM_NEQ_ENVFROM(0.00)[jameshilliard1@gmail.com,linux-sunxi@lists.linux.dev]; FREEMAIL_FROM(0.00)[gmail.com]; MIME_TRACE(0.00)[0:+]; TAGGED_FROM(0.00)[bounces-26692-noreply=patchwork.local]; FORGED_RECIPIENTS_MAILLIST(0.00)[]; MID_RHS_MATCH_FROM(0.00)[]; RCVD_TLS_LAST(0.00)[]; RCVD_VIA_SMTP_AUTH(0.00)[] X-Rspamd-Queue-Id: B0BF31C1CBB X-MORS-Enabled: yes X-MORS-DOMAIN: patchwork.local X-MORS-HOSTING: hosting172546 X-MORS-USER: hosting172546 X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= Normal hardware-ECC reads also decode protected OOB, including the bad-block marker and metadata used in bad-block-table scans. Allwinner firmware leaves the marker in the randomizer stream, but the existing driver compensates it to keep a plain physical marker. On H6/H616, firmware also packs protected user data before the first parity region rather than maximizing its allocation across the page. Those mismatches prevent normal ECC reads from interpreting firmware-formatted pages and their markers. Honor allwinner,randomized-oob through a separate operations table selected by an opt-in attach helper. Enable scrambling for normal controller-ECC transfers and omit marker compensation. Reject software or disabled ECC, and reject a step larger than the page before the core can fall back to software ECC. Without the property, keep the existing operations and geometry, including the plain marker on otherwise scrambled NAND. For H6/H616, reserve four user-data bytes per ECC step, capped at 16 bytes per page, and put the entire reservation in step zero. Later steps have only parity. Apply the same reservation when maximizing ECC strength so strength selection and actual layout agree. Older controllers retain their fixed four-byte user-data field per step. An erased page physically contains 0xff, which the randomizer turns into non-0xff decoded bytes. Select the Allwinner firmware's controller-specific erased-page predicate instead of the generic bit-count check. Apply it to ECC-decoded protected bytes re-encoded with the OOB randomizer stream. This is not a raw reread: bytes corrected by ECC retain their corrected values. Older controllers require eight exact 0xff bytes, with a four-byte shortcut at block page zero and pages 127 modulo 128 for 1 KiB steps. The H6-generation predicate requires byte zero and at least nine of ten bytes to be 0xff. ECC exception mode can suppress errors for uniform physical data. Include pattern-found steps in classification even without an ECC-error bit, and ignore their correction counters. Keep decoded bytes until the page-wide decision: an all-zero physical page takes precedence over the spare check and returns zeroed data/OOB with ECC failures; an accepted erased page returns all 0xff. Other failures retain decoded main and protected OOB so bad-block and table scans can still inspect the marker or signature. Read complete pages for subpage requests because these checks classify a whole page. Collect status during each PIO step or DMA batch, and defer statistics until requested OOB reads succeed. Read unprotected OOB only when requested, combining adjacent parity/tail ranges without crossing protected user data. Preserve each step's randomizer phase and the existing PIO/DMA representation of parity. Raw callbacks remain unchanged. All format policy is in the new callbacks and attach helper. Apart from the opt-in dispatch, existing functions are unchanged: geometry allocation, marker compensation, status collection and PIO/DMA transfer sequencing continue to serve the existing operations tables as before. Signed-off-by: James Hilliard --- drivers/mtd/nand/raw/sunxi_nand.c | 332 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 332 insertions(+) diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index 56594bb41193..0e2ff1c59120 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -349,6 +349,8 @@ struct sunxi_nfc_mdma_desc { * @nuser_data_tab: Size of @user_data_len_tab * @sram_size: Size of the NAND controller SRAM * @timings: Controller timing characteristics + * @spare_is_erased: Allwinner randomized-OOB erased-page check on + * re-encoded protected OOB bytes */ struct sunxi_nfc_caps { bool has_mdma; @@ -377,6 +379,7 @@ struct sunxi_nfc_caps { unsigned int max_ecc_steps; int sram_size; const struct sunxi_nfc_timings *timings; + bool (*spare_is_erased)(struct nand_chip *nand, const u8 *spare, int page); }; /** @@ -1160,7 +1163,15 @@ static int sunxi_nfc_hw_ecc_read_error(struct nand_chip *nand, return ret >= 0; } +struct sunxi_nfc_allwinner_oob_status { + u32 error_steps; + u32 zero_steps; + unsigned int corrected; + unsigned int max_bitflips; +}; + struct sunxi_nfc_ecc_read { + struct sunxi_nfc_allwinner_oob_status allwinner_oob; u8 *buf; int page; unsigned int max_bitflips; @@ -2342,6 +2353,299 @@ static const struct sunxi_nfc_oob_ops sunxi_nfc_scrambled_plain_bbm_oob_ops = { .finish_read = sunxi_nfc_oob_finish_read, }; +/* Allwinner randomized-OOB format, selected only by allwinner,randomized-oob. */ +#define SUNXI_NFC_H6_ALLWINNER_OOB_MAX_USER_DATA_SZ 16 + +static void sunxi_nfc_randomize_buf(u16 state, u8 *buf, unsigned int len) +{ + while (len--) { + *buf++ ^= state; + state = sunxi_nfc_randomizer_step(state, 8); + } +} + +static void +sunxi_nfc_allwinner_oob_record_status(struct nand_chip *nand, + struct sunxi_nfc_allwinner_oob_status *result, + int logical_step, int hw_step, + struct sunxi_nfc_ecc_snapshot *snapshot) +{ + u32 count; + + if ((snapshot->pattern_found & BIT(hw_step)) && + !(snapshot->pattern_id & BIT(hw_step))) + result->zero_steps |= BIT(logical_step); + + /* + * Exception mode can suppress the ECC error for a physical pattern. + * Classify these steps with the Allwinner randomized-OOB page check too, + * and do not use their correction counters. Keep their decoded data until + * then. + */ + if ((snapshot->status & NFC_ECC_ERR(hw_step)) || + (snapshot->pattern_found & BIT(hw_step))) { + result->error_steps |= BIT(logical_step); + return; + } + + count = sunxi_nfc_hw_ecc_read_count(nand, snapshot, hw_step); + result->corrected += count; + result->max_bitflips = max(result->max_bitflips, count); +} + +static bool sun4i_a10_nfc_spare_is_erased(struct nand_chip *nand, + const u8 *spare, int page) +{ + struct mtd_info *mtd = nand_to_mtd(nand); + unsigned int block_page = page % mtd_div_by_ws(mtd->erasesize, mtd); + + /* + * Older Allwinner firmware recognizes exact four-byte erased + * signatures on page zero and pages 127 modulo 128. With their + * 1 KiB ECC steps these correspond to four physical 0xff bytes. + * Other pages require all eight spare bytes to be 0xff. + */ + if (nand->ecc.size == 1024 && + (!block_page || block_page % 128 == 127) && + !memchr_inv(spare, 0xff, USER_DATA_SZ)) + return true; + + return !memchr_inv(spare, 0xff, 8); +} + +static bool sun50i_h6_nfc_spare_is_erased(struct nand_chip *nand, + const u8 *spare, int page) +{ + unsigned int erased = 0; + int i; + + /* Byte zero and at least nine of ten bytes must be 0xff. */ + for (i = 0; i < 10; i++) + erased += spare[i] == 0xff; + + return spare[0] == 0xff && erased >= 9; +} + +static bool sunxi_nfc_allwinner_oob_spare_is_erased(struct nand_chip *nand, + int page) +{ + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + struct nand_ecc_ctrl *ecc = &nand->ecc; + u16 state = sunxi_nfc_randomizer_state(nand, page, true); + u8 spare[10]; + unsigned int len, pos = 0; + int i, off; + + /* + * Re-encode ECC-decoded protected OOB for the firmware's spare check. + * This is not a raw reread: ECC-corrected bytes stay corrected. Each + * step restarts the OOB randomizer; H6/H616 pack the entire prefix in + * step zero. Pad unavailable bytes with 0xff. + */ + memset(spare, 0xff, sizeof(spare)); + for (i = 0; i < ecc->steps && pos < sizeof(spare); i++) { + len = min_t(unsigned int, sunxi_nfc_user_data_sz(sunxi_nand, i), + sizeof(spare) - pos); + off = sunxi_get_oob_offset(sunxi_nand, ecc, i); + memcpy(spare + pos, nand->oob_poi + off, len); + sunxi_nfc_randomize_buf(state, spare + pos, len); + pos += len; + } + + return nfc->caps->spare_is_erased(nand, spare, page); +} + +static int sunxi_nfc_allwinner_oob_read_unprotected(struct nand_chip *nand, + bool dma, int page) +{ + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + struct mtd_info *mtd = nand_to_mtd(nand); + struct nand_ecc_ctrl *ecc = &nand->ecc; + unsigned int len, off; + u16 state; + int ret, i; + + for (i = 0; i < ecc->steps; i++) { + off = sunxi_get_ecc_offset(sunxi_nand, ecc, i); + len = ecc->bytes; + /* Keep decoded user data, but combine adjacent parity regions. */ + while (i + 1 < ecc->steps && + !sunxi_nfc_user_data_sz(sunxi_nand, i + 1)) { + len += ecc->bytes; + i++; + } + if (i + 1 == ecc->steps) + len = mtd->oobsize - off; + + ret = sunxi_nfc_read_column(nand, page, mtd->writesize + off, + nand->oob_poi + off, len); + if (ret) + return ret; + } + + /* Preserve each path's normal representation of ECC bytes. */ + if (!dma) { + for (i = 0; i < ecc->steps; i++) { + len = sunxi_nfc_user_data_sz(sunxi_nand, i); + off = sunxi_get_ecc_offset(sunxi_nand, ecc, i); + state = sunxi_nfc_randomizer_state(nand, page, true); + state = sunxi_nfc_randomizer_step(state, len * 8 + 15); + sunxi_nfc_randomize_buf(state, nand->oob_poi + off, + ecc->bytes); + } + } + + off = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps); + len = mtd->oobsize - off; + if (len) { + /* The unprotected tail uses the page seed and its 15-bit advance. */ + state = sunxi_nfc_randomizer_state(nand, page, false); + state = sunxi_nfc_randomizer_step(state, 15); + sunxi_nfc_randomize_buf(state, nand->oob_poi + off, len); + } + + return 0; +} + +static int sunxi_nfc_allwinner_oob_read_batch(struct nand_chip *nand, + struct sunxi_nfc_ecc_read *read, + struct sunxi_nfc_ecc_snapshot *snapshot, + int first_step, int nsteps) +{ + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + struct nand_ecc_ctrl *ecc = &nand->ecc; + unsigned int reg_index = 0, user_data_sz; + int i; + + /* + * The normal snapshot skips pattern IDs when every pattern has an + * ECC error. Allwinner randomized-OOB page classification needs those IDs + * as well. + */ + if (snapshot->pattern_found && + !(snapshot->pattern_found & ~snapshot->status)) + snapshot->pattern_id = readl(nfc->regs + NFC_REG_PAT_ID(nfc)); + + for (i = 0; i < nsteps; i++, reg_index += user_data_sz / 4) { + int step = first_step + i; + int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, step); + u8 *oob = nand->oob_poi + oob_off; + + user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step); + sunxi_nfc_allwinner_oob_record_status(nand, &read->allwinner_oob, + step, i, snapshot); + if (!read->dma) + memcpy_fromio(read->buf + step * ecc->size, + nfc->regs + NFC_RAM0_BASE, ecc->size); + /* Page classification needs protected OOB even for data-only reads. */ + sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, reg_index, !step, + read->page, user_data_sz); + } + + return 0; +} + +static int sunxi_nfc_allwinner_oob_finish_read(struct nand_chip *nand, + struct sunxi_nfc_ecc_read *read, + int *cur_off) +{ + struct sunxi_nfc_allwinner_oob_status *result = &read->allwinner_oob; + u8 *buf = read->buf; + bool oob_required = read->oob_required; + bool dma = read->dma; + int page = read->page; + struct mtd_info *mtd = nand_to_mtd(nand); + struct nand_ecc_ctrl *ecc = &nand->ecc; + int ret; + + /* + * Allwinner firmware treats an all-zero physical page as bad, even without + * ECC errors. This takes precedence over the spare-byte heuristic. + */ + if (result->zero_steps == GENMASK(ecc->steps - 1, 0)) { + memset(buf, 0, mtd->writesize); + memset(nand->oob_poi, 0, mtd->oobsize); + mtd->ecc_stats.failed += ecc->steps; + return 0; + } + + if (result->error_steps && sunxi_nfc_allwinner_oob_spare_is_erased(nand, page)) { + memset(buf, 0xff, mtd->writesize); + memset(nand->oob_poi, 0xff, mtd->oobsize); + return 0; + } + + /* + * Keep the original decoded main and protected OOB bytes on ECC failure. + * BBT pattern scans inspect them even when an ECC error is reported. + * Read the remaining OOB only when requested, not to classify the page. + */ + if (oob_required) { + ret = sunxi_nfc_allwinner_oob_read_unprotected(nand, dma, page); + if (ret) + return ret; + } + + mtd->ecc_stats.corrected += result->corrected; + mtd->ecc_stats.failed += hweight32(result->error_steps); + + return result->max_bitflips; +} + +static int sunxi_nfc_allwinner_oob_read_subpage(struct nand_chip *nand, + u32 data_offs, u32 readlen, + u8 *buf, int page) +{ + return nand->ecc.read_page(nand, buf, false, page); +} + +static unsigned int sunxi_nfc_h6_allwinner_oob_user_data_sz(int nsectors) +{ + return min(nsectors * USER_DATA_SZ, + SUNXI_NFC_H6_ALLWINNER_OOB_MAX_USER_DATA_SZ); +} + +static unsigned int sunxi_nfc_allwinner_oob_min_user_data(struct nand_chip *nand, + int nsteps) +{ + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + + if (!nfc->caps->reg_user_data_len) + return sunxi_nfc_oob_min_user_data(nand, nsteps); + + return sunxi_nfc_h6_allwinner_oob_user_data_sz(nsteps); +} + +static int sunxi_nfc_allwinner_oob_init_user_data(struct nand_chip *nand, int nsteps) +{ + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + + /* Do not let the core fall back to software ECC for this format. */ + if (nsteps <= 0) + return -EINVAL; + + if (!nfc->caps->reg_user_data_len) + return 0; + + sunxi_nand->user_data_bytes = devm_kzalloc(nfc->dev, nsteps, GFP_KERNEL); + if (!sunxi_nand->user_data_bytes) + return -ENOMEM; + + /* H6/H616 pack all protected user data before step-zero parity. */ + sunxi_nand->user_data_bytes[0] = sunxi_nfc_h6_allwinner_oob_user_data_sz(nsteps); + return 0; +} + +static const struct sunxi_nfc_oob_ops sunxi_nfc_allwinner_oob_ops = { + .min_user_data = sunxi_nfc_allwinner_oob_min_user_data, + .init_user_data = sunxi_nfc_allwinner_oob_init_user_data, + .read_batch = sunxi_nfc_allwinner_oob_read_batch, + .finish_read = sunxi_nfc_allwinner_oob_finish_read, +}; + static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, struct nand_ecc_ctrl *ecc, struct device_node *np) @@ -2473,6 +2777,28 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, return 0; } +static int sunxi_nfc_allwinner_oob_attach_chip(struct nand_chip *nand, + struct device_node *np) +{ + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + int ret; + + if (nand->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) + return dev_err_probe(nfc->dev, -EINVAL, + "Allwinner OOB format requires controller ECC\n"); + + sunxi_nand->oob_ops = &sunxi_nfc_allwinner_oob_ops; + nand->options |= NAND_NEED_SCRAMBLING | NAND_NO_SUBPAGE_WRITE; + ret = sunxi_nand_hw_ecc_ctrl_init(nand, &nand->ecc, np); + if (ret) + return ret; + + /* This format's spare and all-zero checks classify the whole page. */ + nand->ecc.read_subpage = sunxi_nfc_allwinner_oob_read_subpage; + return 0; +} + static int sunxi_nand_attach_chip(struct nand_chip *nand) { struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); @@ -2498,6 +2824,9 @@ static int sunxi_nand_attach_chip(struct nand_chip *nand) if (!ecc->size || !ecc->strength) return -EINVAL; + if (of_property_read_bool(np, "allwinner,randomized-oob")) + return sunxi_nfc_allwinner_oob_attach_chip(nand, np); + sunxi_nand->oob_ops = nand->options & NAND_NEED_SCRAMBLING ? &sunxi_nfc_scrambled_plain_bbm_oob_ops : &sunxi_nfc_plain_oob_ops; @@ -2995,6 +3324,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 = { @@ -3018,6 +3348,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 = { @@ -3044,6 +3375,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_h616_caps = { .max_ecc_steps = 32, .sram_size = 8192, .timings = &sun50i_h616_nfc_timings, + .spare_is_erased = sun50i_h6_nfc_spare_is_erased, }; static const struct of_device_id sunxi_nfc_ids[] = {