+From 862bdedd7f4b8aebf00fdb422062e64896e97809 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 16 Jun 2022 02:18:34 +0200
+Subject: [PATCH 2/2] mtd: nand: raw: qcom_nandc: add support for unprotected
+ spare data pages
+
+IPQ8064 nand have special pages where a different layout scheme is used.
+These special page are used by boot partition and on reading them
+lots of warning are reported about wrong ECC data and if written to
+results in broken data and not bootable device.
+
+The layout scheme used by these special page consist in using 512 bytes
+as the codeword size (even for the last codeword) while writing to CFG0
+register. This forces the NAND controller to unprotect the 4 bytes of
+spare data.
+
+Since the kernel is unaware of this different layout for these special
+page, it does try to protect the spare data too during read/write and
+warn about CRC errors.
+
+Add support for this by permitting the user to declare these special
+pages in dts by declaring offset and size of the partition. The driver
+internally will convert these value to nand pages.
+
+On user read/write the page is checked and if it's a boot page the
+correct layout is used.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Manivannan Sadhasivam <mani@kernel.org>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Link: https://lore.kernel.org/linux-mtd/20220616001835.24393-3-ansuelsmth@gmail.com
+---
+ drivers/mtd/nand/raw/qcom_nandc.c | 199 +++++++++++++++++++++++++++++-
+ 1 file changed, 194 insertions(+), 5 deletions(-)
+
+--- a/drivers/mtd/nand/raw/qcom_nandc.c
++++ b/drivers/mtd/nand/raw/qcom_nandc.c
+@@ -79,8 +79,10 @@
+ #define DISABLE_STATUS_AFTER_WRITE 4
+ #define CW_PER_PAGE 6
+ #define UD_SIZE_BYTES 9
++#define UD_SIZE_BYTES_MASK GENMASK(18, 9)
+ #define ECC_PARITY_SIZE_BYTES_RS 19
+ #define SPARE_SIZE_BYTES 23
++#define SPARE_SIZE_BYTES_MASK GENMASK(26, 23)
+ #define NUM_ADDR_CYCLES 27
+ #define STATUS_BFR_READ 30
+ #define SET_RD_MODE_AFTER_STATUS 31
+@@ -101,6 +103,7 @@
+ #define ECC_MODE 4
+ #define ECC_PARITY_SIZE_BYTES_BCH 8
+ #define ECC_NUM_DATA_BYTES 16
++#define ECC_NUM_DATA_BYTES_MASK GENMASK(25, 16)
+ #define ECC_FORCE_CLK_OPEN 30
+
+ /* NAND_DEV_CMD1 bits */
+@@ -431,12 +434,31 @@ struct qcom_nand_controller {
+ };
+
+ /*
++ * NAND special boot partitions
++ *
++ * @page_offset: offset of the partition where spare data is not protected
++ * by ECC (value in pages)
++ * @page_offset: size of the partition where spare data is not protected
++ * by ECC (value in pages)
++ */
++struct qcom_nand_boot_partition {
++ u32 page_offset;
++ u32 page_size;
++};
++
++/*
+ * NAND chip structure
+ *
++ * @boot_partitions: array of boot partitions where offset and size of the
++ * boot partitions are stored
++ *
+ * @chip: base NAND chip structure
+ * @node: list node to add itself to host_list in
+ * qcom_nand_controller
+ *
++ * @nr_boot_partitions: count of the boot partitions where spare data is not
++ * protected by ECC
++ *
+ * @cs: chip select value for this chip
+ * @cw_size: the number of bytes in a single step/codeword
+ * of a page, consisting of all data, ecc, spare
+@@ -455,14 +477,20 @@ struct qcom_nand_controller {
+ *
+ * @status: value to be returned if NAND_CMD_STATUS command
+ * is executed
++ * @codeword_fixup: keep track of the current layout used by
++ * the driver for read/write operation.
+ * @use_ecc: request the controller to use ECC for the
+ * upcoming read/write
+ * @bch_enabled: flag to tell whether BCH ECC mode is used
+ */
+ struct qcom_nand_host {
++ struct qcom_nand_boot_partition *boot_partitions;
++
+ struct nand_chip chip;
+ struct list_head node;
+
++ int nr_boot_partitions;
++
+ int cs;
+ int cw_size;
+ int cw_data;
+@@ -480,6 +508,7 @@ struct qcom_nand_host {
+ u32 clrreadstatus;
+
+ u8 status;
++ bool codeword_fixup;
+ bool use_ecc;
+ bool bch_enabled;
+ };
+@@ -492,6 +521,7 @@ struct qcom_nand_host {
+ * @is_bam - whether NAND controller is using BAM
+ * @is_qpic - whether NAND CTRL is part of qpic IP
+ * @qpic_v2 - flag to indicate QPIC IP version 2
++ * @use_codeword_fixup - whether NAND has different layout for boot partitions
+ */
+ struct qcom_nandc_props {
+ u32 ecc_modes;
+@@ -499,6 +529,7 @@ struct qcom_nandc_props {
+ bool is_bam;
+ bool is_qpic;
+ bool qpic_v2;
++ bool use_codeword_fixup;
+ };
+
+ /* Frees the BAM transaction memory */
+@@ -1708,7 +1739,7 @@ qcom_nandc_read_cw_raw(struct mtd_info *
+ data_size1 = mtd->writesize - host->cw_size * (ecc->steps - 1);
+ oob_size1 = host->bbm_size;
+
+- if (qcom_nandc_is_last_cw(ecc, cw)) {
++ if (qcom_nandc_is_last_cw(ecc, cw) && !host->codeword_fixup) {
+ data_size2 = ecc->size - data_size1 -
+ ((ecc->steps - 1) * 4);
+ oob_size2 = (ecc->steps * 4) + host->ecc_bytes_hw +
+@@ -1789,7 +1820,7 @@ check_for_erased_page(struct qcom_nand_h
+ }
+
+ for_each_set_bit(cw, &uncorrectable_cws, ecc->steps) {
+- if (qcom_nandc_is_last_cw(ecc, cw)) {
++ if (qcom_nandc_is_last_cw(ecc, cw) && !host->codeword_fixup) {
+ data_size = ecc->size - ((ecc->steps - 1) * 4);
+ oob_size = (ecc->steps * 4) + host->ecc_bytes_hw;
+ } else {
+@@ -1947,7 +1978,7 @@ static int read_page_ecc(struct qcom_nan
+ for (i = 0; i < ecc->steps; i++) {
+ int data_size, oob_size;
+
+- if (qcom_nandc_is_last_cw(ecc, i)) {
++ if (qcom_nandc_is_last_cw(ecc, i) && !host->codeword_fixup) {
+ data_size = ecc->size - ((ecc->steps - 1) << 2);
+ oob_size = (ecc->steps << 2) + host->ecc_bytes_hw +
+ host->spare_bytes;
+@@ -2044,6 +2075,69 @@ static int copy_last_cw(struct qcom_nand
+ return ret;
+ }
+
++static bool qcom_nandc_is_boot_partition(struct qcom_nand_host *host, int page)
++{
++ struct qcom_nand_boot_partition *boot_partition;
++ u32 start, end;
++ int i;
++
++ /*
++ * Since the frequent access will be to the non-boot partitions like rootfs,
++ * optimize the page check by:
++ *
++ * 1. Checking if the page lies after the last boot partition.
++ * 2. Checking from the boot partition end.
++ */
++
++ /* First check the last boot partition */
++ boot_partition = &host->boot_partitions[host->nr_boot_partitions - 1];
++ start = boot_partition->page_offset;
++ end = start + boot_partition->page_size;
++
++ /* Page is after the last boot partition end. This is NOT a boot partition */
++ if (page > end)
++ return false;
++
++ /* Actually check if it's a boot partition */
++ if (page < end && page >= start)
++ return true;
++
++ /* Check the other boot partitions starting from the second-last partition */
++ for (i = host->nr_boot_partitions - 2; i >= 0; i--) {
++ boot_partition = &host->boot_partitions[i];
++ start = boot_partition->page_offset;
++ end = start + boot_partition->page_size;
++
++ if (page < end && page >= start)
++ return true;
++ }
++
++ return false;
++}
++
++static void qcom_nandc_codeword_fixup(struct qcom_nand_host *host, int page)
++{
++ bool codeword_fixup = qcom_nandc_is_boot_partition(host, page);
++
++ /* Skip conf write if we are already in the correct mode */
++ if (codeword_fixup == host->codeword_fixup)
++ return;
++
++ host->codeword_fixup = codeword_fixup;
++
++ host->cw_data = codeword_fixup ? 512 : 516;
++ host->spare_bytes = host->cw_size - host->ecc_bytes_hw -
++ host->bbm_size - host->cw_data;
++
++ host->cfg0 &= ~(SPARE_SIZE_BYTES_MASK | UD_SIZE_BYTES_MASK);
++ host->cfg0 |= host->spare_bytes << SPARE_SIZE_BYTES |
++ host->cw_data << UD_SIZE_BYTES;
++
++ host->ecc_bch_cfg &= ~ECC_NUM_DATA_BYTES_MASK;
++ host->ecc_bch_cfg |= host->cw_data << ECC_NUM_DATA_BYTES;
++ host->ecc_buf_cfg = (host->cw_data - 1) << NUM_STEPS;
++}
++
+ /* implements ecc->read_page() */
+ static int qcom_nandc_read_page(struct nand_chip *chip, uint8_t *buf,
+ int oob_required, int page)
+@@ -2052,6 +2146,9 @@ static int qcom_nandc_read_page(struct n
+ struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
+ u8 *data_buf, *oob_buf = NULL;
+
++ if (host->nr_boot_partitions)
++ qcom_nandc_codeword_fixup(host, page);
++
+ nand_read_page_op(chip, page, 0, NULL, 0);
+ data_buf = buf;
+ oob_buf = oob_required ? chip->oob_poi : NULL;
+@@ -2071,6 +2168,9 @@ static int qcom_nandc_read_page_raw(stru
+ int cw, ret;
+ u8 *data_buf = buf, *oob_buf = chip->oob_poi;
+
++ if (host->nr_boot_partitions)
++ qcom_nandc_codeword_fixup(host, page);
++
+ for (cw = 0; cw < ecc->steps; cw++) {
+ ret = qcom_nandc_read_cw_raw(mtd, chip, data_buf, oob_buf,
+ page, cw);
+@@ -2091,6 +2191,9 @@ static int qcom_nandc_read_oob(struct na
+ struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
+ struct nand_ecc_ctrl *ecc = &chip->ecc;
+
++ if (host->nr_boot_partitions)
++ qcom_nandc_codeword_fixup(host, page);
++
+ clear_read_regs(nandc);
+ clear_bam_transaction(nandc);
+
+@@ -2111,6 +2214,9 @@ static int qcom_nandc_write_page(struct
+ u8 *data_buf, *oob_buf;
+ int i, ret;
+
++ if (host->nr_boot_partitions)
++ qcom_nandc_codeword_fixup(host, page);
++
+ nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
+ clear_read_regs(nandc);
+@@ -2126,7 +2232,7 @@ static int qcom_nandc_write_page(struct
+ for (i = 0; i < ecc->steps; i++) {
+ int data_size, oob_size;
+
+- if (qcom_nandc_is_last_cw(ecc, i)) {
++ if (qcom_nandc_is_last_cw(ecc, i) && !host->codeword_fixup) {
+ data_size = ecc->size - ((ecc->steps - 1) << 2);
+ oob_size = (ecc->steps << 2) + host->ecc_bytes_hw +
+ host->spare_bytes;
+@@ -2183,6 +2289,9 @@ static int qcom_nandc_write_page_raw(str
+ u8 *data_buf, *oob_buf;
+ int i, ret;
+
++ if (host->nr_boot_partitions)
++ qcom_nandc_codeword_fixup(host, page);
++
+ nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+ clear_read_regs(nandc);
+ clear_bam_transaction(nandc);
+@@ -2201,7 +2310,7 @@ static int qcom_nandc_write_page_raw(str
+ data_size1 = mtd->writesize - host->cw_size * (ecc->steps - 1);
+ oob_size1 = host->bbm_size;
+
+- if (qcom_nandc_is_last_cw(ecc, i)) {
++ if (qcom_nandc_is_last_cw(ecc, i) && !host->codeword_fixup) {
+ data_size2 = ecc->size - data_size1 -
+ ((ecc->steps - 1) << 2);
+ oob_size2 = (ecc->steps << 2) + host->ecc_bytes_hw +
+@@ -2261,6 +2370,9 @@ static int qcom_nandc_write_oob(struct n
+ int data_size, oob_size;
+ int ret;
+
++ if (host->nr_boot_partitions)
++ qcom_nandc_codeword_fixup(host, page);
++
+ host->use_ecc = true;
+ clear_bam_transaction(nandc);
+
+@@ -2922,6 +3034,74 @@ static int qcom_nandc_setup(struct qcom_
+
+ static const char * const probes[] = { "cmdlinepart", "ofpart", "qcomsmem", NULL };
+
++static int qcom_nand_host_parse_boot_partitions(struct qcom_nand_controller *nandc,
++ struct qcom_nand_host *host,
++ struct device_node *dn)
++{
++ struct nand_chip *chip = &host->chip;
++ struct mtd_info *mtd = nand_to_mtd(chip);
++ struct qcom_nand_boot_partition *boot_partition;
++ struct device *dev = nandc->dev;
++ int partitions_count, i, j, ret;
++
++ if (!of_find_property(dn, "qcom,boot-partitions", NULL))
++ return 0;
++
++ partitions_count = of_property_count_u32_elems(dn, "qcom,boot-partitions");
++ if (partitions_count <= 0) {
++ dev_err(dev, "Error parsing boot partition\n");
++ return partitions_count ? partitions_count : -EINVAL;
++ }
++
++ host->nr_boot_partitions = partitions_count / 2;
++ host->boot_partitions = devm_kcalloc(dev, host->nr_boot_partitions,
++ sizeof(*host->boot_partitions), GFP_KERNEL);
++ if (!host->boot_partitions) {
++ host->nr_boot_partitions = 0;
++ return -ENOMEM;
++ }
++
++ for (i = 0, j = 0; i < host->nr_boot_partitions; i++, j += 2) {
++ boot_partition = &host->boot_partitions[i];
++
++ ret = of_property_read_u32_index(dn, "qcom,boot-partitions", j,
++ &boot_partition->page_offset);
++ if (ret) {
++ dev_err(dev, "Error parsing boot partition offset at index %d\n", i);
++ host->nr_boot_partitions = 0;
++ return ret;
++ }
++
++ if (boot_partition->page_offset % mtd->writesize) {
++ dev_err(dev, "Boot partition offset not multiple of writesize at index %i\n",
++ i);
++ host->nr_boot_partitions = 0;
++ return -EINVAL;
++ }
++ /* Convert offset to nand pages */
++ boot_partition->page_offset /= mtd->writesize;
++
++ ret = of_property_read_u32_index(dn, "qcom,boot-partitions", j + 1,
++ &boot_partition->page_size);
++ if (ret) {
++ dev_err(dev, "Error parsing boot partition size at index %d\n", i);
++ host->nr_boot_partitions = 0;
++ return ret;
++ }
++
++ if (boot_partition->page_size % mtd->writesize) {
++ dev_err(dev, "Boot partition size not multiple of writesize at index %i\n",
++ i);
++ host->nr_boot_partitions = 0;
++ return -EINVAL;
++ }
++ /* Convert size to nand pages */
++ boot_partition->page_size /= mtd->writesize;
++ }
++
++ return 0;
++}
++
+ static int qcom_nand_host_init_and_register(struct qcom_nand_controller *nandc,
+ struct qcom_nand_host *host,
+ struct device_node *dn)
+@@ -2979,6 +3159,14 @@ static int qcom_nand_host_init_and_regis
+ if (ret)
+ nand_cleanup(chip);
+
++ if (nandc->props->use_codeword_fixup) {
++ ret = qcom_nand_host_parse_boot_partitions(nandc, host, dn);
++ if (ret) {
++ nand_cleanup(chip);
++ return ret;
++ }
++ }
++
+ return ret;
+ }
+
+@@ -3144,6 +3332,7 @@ static int qcom_nandc_remove(struct plat
+ static const struct qcom_nandc_props ipq806x_nandc_props = {
+ .ecc_modes = (ECC_RS_4BIT | ECC_BCH_8BIT),
+ .is_bam = false,
++ .use_codeword_fixup = true,
+ .dev_cmd_reg_start = 0x0,
+ };
+