[][OpenWrt Dev][Add empty page bitflips detection and fix for mtk-snand driver]

[Description]
Add support for mtk-snand driver to detect and fix bitflips of empty pages

Also, change to ecc strength of mtd layer to ecc strength of a sector,
and report max bitflips of sectors within a page.

[Release-log]
N/A

Change-Id: I0cb71808076ab809bdbbe98b59859394c79c59f9
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/4520684
diff --git a/target/linux/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-def.h b/target/linux/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-def.h
index 95c4bb3..1a93d93 100644
--- a/target/linux/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-def.h
+++ b/target/linux/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-def.h
@@ -174,6 +174,7 @@
 
 	uint8_t *page_cache;	/* Used by read/write page */
 	uint8_t *buf_cache;	/* Used by block bad/markbad & auto_oob */
+	int *sect_bf;		/* Used by ECC correction */
 };
 
 enum mtk_snand_log_category {
@@ -192,7 +193,8 @@
 int mtk_snand_ecc_decoder_start(struct mtk_snand *snf);
 void mtk_snand_ecc_decoder_stop(struct mtk_snand *snf);
 int mtk_ecc_wait_decoder_done(struct mtk_snand *snf);
-int mtk_ecc_check_decode_error(struct mtk_snand *snf, uint32_t page);
+int mtk_ecc_check_decode_error(struct mtk_snand *snf);
+int mtk_ecc_fixup_empty_sector(struct mtk_snand *snf, uint32_t sect);
 
 int mtk_snand_mac_io(struct mtk_snand *snf, const uint8_t *out, uint32_t outlen,
 		     uint8_t *in, uint32_t inlen);
diff --git a/target/linux/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-ecc.c b/target/linux/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-ecc.c
index 57ba611..4094339 100644
--- a/target/linux/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-ecc.c
+++ b/target/linux/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-ecc.c
@@ -232,7 +232,7 @@
 	return ret;
 }
 
-int mtk_ecc_check_decode_error(struct mtk_snand *snf, uint32_t page)
+int mtk_ecc_check_decode_error(struct mtk_snand *snf)
 {
 	uint32_t i, regi, fi, errnum;
 	uint32_t errnum_shift = snf->ecc_soc->errnum_shift;
@@ -245,20 +245,151 @@
 
 		errnum = ecc_read32(snf, ECC_DECENUM(regi));
 		errnum = (errnum >> (fi * errnum_shift)) & errnum_mask;
-		if (!errnum)
-			continue;
 
 		if (errnum <= snf->ecc_strength) {
-			if (ret >= 0)
-				ret += errnum;
-			continue;
+			snf->sect_bf[i] = errnum;
+		} else {
+			snf->sect_bf[i] = -1;
+			ret = -EBADMSG;
 		}
-
-		snand_log_ecc(snf->pdev,
-			      "Uncorrectable bitflips in page %u sect %u\n",
-			      page, i);
-		ret = -EBADMSG;
 	}
 
 	return ret;
 }
+
+static int mtk_ecc_check_buf_bitflips(struct mtk_snand *snf, const void *buf,
+				      size_t len, uint32_t bitflips)
+{
+	const uint8_t *buf8 = buf;
+	const uint32_t *buf32;
+	uint32_t d, weight;
+
+	while (len && ((uintptr_t)buf8) % sizeof(uint32_t)) {
+		weight = hweight8(*buf8);
+		bitflips += BITS_PER_BYTE - weight;
+		buf8++;
+		len--;
+
+		if (bitflips > snf->ecc_strength)
+			return -EBADMSG;
+	}
+
+	buf32 = (const uint32_t *)buf8;
+	while (len >= sizeof(uint32_t)) {
+		d = *buf32;
+
+		if (d != ~0) {
+			weight = hweight32(d);
+			bitflips += sizeof(uint32_t) * BITS_PER_BYTE - weight;
+		}
+
+		buf32++;
+		len -= sizeof(uint32_t);
+
+		if (bitflips > snf->ecc_strength)
+			return -EBADMSG;
+	}
+
+	buf8 = (const uint8_t *)buf32;
+	while (len) {
+		weight = hweight8(*buf8);
+		bitflips += BITS_PER_BYTE - weight;
+		buf8++;
+		len--;
+
+		if (bitflips > snf->ecc_strength)
+			return -EBADMSG;
+	}
+
+	return bitflips;
+}
+
+static int mtk_ecc_check_parity_bitflips(struct mtk_snand *snf, const void *buf,
+					 uint32_t bits, uint32_t bitflips)
+{
+	uint32_t len, i;
+	uint8_t b;
+	int rc;
+
+	len = bits >> 3;
+	bits &= 7;
+
+	rc = mtk_ecc_check_buf_bitflips(snf, buf, len, bitflips);
+	if (!bits || rc < 0)
+		return rc;
+
+	bitflips = rc;
+
+	/* We want a precise count of bits */
+	b = ((const uint8_t *)buf)[len];
+	for (i = 0; i < bits; i++) {
+		if (!(b & BIT(i)))
+			bitflips++;
+	}
+
+	if (bitflips > snf->ecc_strength)
+		return -EBADMSG;
+
+	return bitflips;
+}
+
+static void mtk_ecc_reset_parity(void *buf, uint32_t bits)
+{
+	uint32_t len;
+
+	len = bits >> 3;
+	bits &= 7;
+
+	memset(buf, 0xff, len);
+
+	/* Only reset bits protected by ECC to 1 */
+	if (bits)
+		((uint8_t *)buf)[len] |= GENMASK(bits - 1, 0);
+}
+
+int mtk_ecc_fixup_empty_sector(struct mtk_snand *snf, uint32_t sect)
+{
+	uint32_t ecc_bytes = snf->spare_per_sector - snf->nfi_soc->fdm_size;
+	uint8_t *oob = snf->page_cache + snf->writesize;
+	uint8_t *data_ptr, *fdm_ptr, *ecc_ptr;
+	int bitflips = 0, ecc_bits, parity_bits;
+
+	parity_bits = fls(snf->nfi_soc->sector_size * 8);
+	ecc_bits = snf->ecc_strength * parity_bits;
+
+	data_ptr = snf->page_cache + sect * snf->nfi_soc->sector_size;
+	fdm_ptr = oob + sect * snf->nfi_soc->fdm_size;
+	ecc_ptr = oob + snf->ecc_steps * snf->nfi_soc->fdm_size +
+		  sect * ecc_bytes;
+
+	/*
+	 * Check whether DATA + FDM + ECC of a sector contains correctable
+	 * bitflips
+	 */
+	bitflips = mtk_ecc_check_buf_bitflips(snf, data_ptr,
+					      snf->nfi_soc->sector_size,
+					      bitflips);
+	if (bitflips < 0)
+		return -EBADMSG;
+
+	bitflips = mtk_ecc_check_buf_bitflips(snf, fdm_ptr,
+					      snf->nfi_soc->fdm_ecc_size,
+					      bitflips);
+	if (bitflips < 0)
+		return -EBADMSG;
+
+	bitflips = mtk_ecc_check_parity_bitflips(snf, ecc_ptr, ecc_bits,
+						 bitflips);
+	if (bitflips < 0)
+		return -EBADMSG;
+
+	if (!bitflips)
+		return 0;
+
+	/* Reset the data of this sector to 0xff */
+	memset(data_ptr, 0xff, snf->nfi_soc->sector_size);
+	memset(fdm_ptr, 0xff, snf->nfi_soc->fdm_ecc_size);
+	mtk_ecc_reset_parity(ecc_ptr, ecc_bits);
+
+	return bitflips;
+}
diff --git a/target/linux/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-mtd.c b/target/linux/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-mtd.c
index 27cfb18..949a3de 100644
--- a/target/linux/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-mtd.c
+++ b/target/linux/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-mtd.c
@@ -592,7 +592,7 @@
 
 	mtd->ooblayout = &mtk_snand_ooblayout;
 
-	mtd->ecc_strength = msm->cinfo.ecc_strength * msm->cinfo.num_sectors;
+	mtd->ecc_strength = msm->cinfo.ecc_strength;
 	mtd->bitflip_threshold = (mtd->ecc_strength * 3) / 4;
 	mtd->ecc_step_size = msm->cinfo.sector_size;
 
diff --git a/target/linux/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand.c b/target/linux/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand.c
index 17254a3..f305bcb 100644
--- a/target/linux/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand.c
+++ b/target/linux/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand.c
@@ -645,6 +645,72 @@
 	}
 }
 
+static int mtk_snand_read_ecc_parity(struct mtk_snand *snf, uint32_t page,
+				     uint32_t sect, uint8_t *oob)
+{
+	uint32_t ecc_bytes = snf->spare_per_sector - snf->nfi_soc->fdm_size;
+	uint32_t coladdr, raw_offs, offs;
+	uint8_t op[4];
+
+	if (sizeof(op) + ecc_bytes > SNF_GPRAM_SIZE) {
+		snand_log_snfi(snf->pdev,
+			       "ECC parity size does not fit the GPRAM\n");
+		return -ENOTSUPP;
+	}
+
+	raw_offs = sect * snf->raw_sector_size + snf->nfi_soc->sector_size +
+		   snf->nfi_soc->fdm_size;
+	offs = snf->ecc_steps * snf->nfi_soc->fdm_size + sect * ecc_bytes;
+
+	/* Column address with plane bit */
+	coladdr = raw_offs | mtk_snand_get_plane_address(snf, page);
+
+	op[0] = SNAND_CMD_READ_FROM_CACHE;
+	op[1] = (coladdr >> 8) & 0xff;
+	op[2] = coladdr & 0xff;
+	op[3] = 0;
+
+	return mtk_snand_mac_io(snf, op, sizeof(op), oob + offs, ecc_bytes);
+}
+
+static int mtk_snand_check_ecc_result(struct mtk_snand *snf, uint32_t page)
+{
+	uint8_t *oob = snf->page_cache + snf->writesize;
+	int i, rc, ret = 0, max_bitflips = 0;
+
+	for (i = 0; i < snf->ecc_steps; i++) {
+		if (snf->sect_bf[i] >= 0) {
+			if (snf->sect_bf[i] > max_bitflips)
+				max_bitflips = snf->sect_bf[i];
+			continue;
+		}
+
+		rc = mtk_snand_read_ecc_parity(snf, page, i, oob);
+		if (rc)
+			return rc;
+
+		rc = mtk_ecc_fixup_empty_sector(snf, i);
+		if (rc < 0) {
+			ret = -EBADMSG;
+
+			snand_log_ecc(snf->pdev,
+			      "Uncorrectable bitflips in page %u sect %u\n",
+			      page, i);
+		} else {
+			snf->sect_bf[i] = rc;
+
+			if (snf->sect_bf[i] > max_bitflips)
+				max_bitflips = snf->sect_bf[i];
+
+			snand_log_ecc(snf->pdev,
+			      "%u bitflip%s corrected in page %u sect %u\n",
+			      rc, rc > 1 ? "s" : "", page, i);
+		}
+	}
+
+	return ret ? ret : max_bitflips;
+}
+
 static int mtk_snand_read_cache(struct mtk_snand *snf, uint32_t page, bool raw)
 {
 	uint32_t coladdr, rwbytes, mode, len;
@@ -722,17 +788,10 @@
 
 		mtk_snand_read_fdm(snf, snf->page_cache + snf->writesize);
 
-		/*
-		 * For new IPs, ecc error may occure on empty pages.
-		 * Use an specific indication bit to check empty page.
-		 */
-		if (snf->nfi_soc->empty_page_check &&
-		    (nfi_read32(snf, NFI_STA) & READ_EMPTY))
-			ret = 0;
-		else
-			ret = mtk_ecc_check_decode_error(snf, page);
-
+		mtk_ecc_check_decode_error(snf);
 		mtk_snand_ecc_decoder_stop(snf);
+
+		ret = mtk_snand_check_ecc_result(snf, page);
 	}
 
 cleanup:
@@ -1683,8 +1742,8 @@
 		   struct mtk_snand **psnf)
 {
 	const struct snand_flash_info *snand_info;
+	uint32_t rawpage_size, sect_bf_size;
 	struct mtk_snand tmpsnf, *snf;
-	uint32_t rawpage_size;
 	int ret;
 
 	if (!pdata || !psnf)
@@ -1725,14 +1784,19 @@
 	rawpage_size = snand_info->memorg.pagesize +
 		       snand_info->memorg.sparesize;
 
+	sect_bf_size = mtk_snand_socs[pdata->soc].max_sectors *
+		       sizeof(*snf->sect_bf);
+
 	/* Allocate memory for instance and cache */
-	snf = generic_mem_alloc(dev, sizeof(*snf) + rawpage_size);
+	snf = generic_mem_alloc(dev,
+				sizeof(*snf) + rawpage_size + sect_bf_size);
 	if (!snf) {
 		snand_log_chip(dev, "Failed to allocate memory for instance\n");
 		return -ENOMEM;
 	}
 
-	snf->buf_cache = (uint8_t *)((uintptr_t)snf + sizeof(*snf));
+	snf->sect_bf = (int *)((uintptr_t)snf + sizeof(*snf));
+	snf->buf_cache = (uint8_t *)((uintptr_t)snf->sect_bf + sect_bf_size);
 
 	/* Allocate memory for DMA buffer */
 	snf->page_cache = dma_mem_alloc(dev, rawpage_size);