--- /dev/null
+From 6d9d6ab3a82af50e36e13e7bc8e2d1b970e39f79 Mon Sep 17 00:00:00 2001
+From: Takahiro Kuwano <Takahiro.Kuwano@infineon.com>
+Date: Tue, 3 Dec 2024 11:46:49 +0900
+Subject: [PATCH 1/1] mtd: spinand: Introduce a way to avoid raw access
+
+SkyHigh spinand device has ECC enable bit in configuration register but
+it must be always enabled. If ECC is disabled, read and write ops
+results in undetermined state. For such devices, a way to avoid raw
+access is needed.
+
+Introduce SPINAND_NO_RAW_ACCESS flag to advertise the device does not
+support raw access. In such devices, the on-die ECC engine ops returns
+error to I/O request in raw mode.
+
+Checking and marking BBM need to be cared as special case, by adding
+fallback mechanism that tries read/write OOB with ECC enabled.
+
+Signed-off-by: Takahiro Kuwano <Takahiro.Kuwano@infineon.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/spi/core.c | 22 ++++++++++++++++++++--
+ include/linux/mtd/spinand.h | 1 +
+ 2 files changed, 21 insertions(+), 2 deletions(-)
+
+--- a/drivers/mtd/nand/spi/core.c
++++ b/drivers/mtd/nand/spi/core.c
+@@ -294,6 +294,9 @@ static int spinand_ondie_ecc_prepare_io_
+ struct spinand_device *spinand = nand_to_spinand(nand);
+ bool enable = (req->mode != MTD_OPS_RAW);
+
++ if (!enable && spinand->flags & SPINAND_NO_RAW_ACCESS)
++ return -EOPNOTSUPP;
++
+ memset(spinand->oobbuf, 0xff, nanddev_per_page_oobsize(nand));
+
+ /* Only enable or disable the engine */
+@@ -921,9 +924,17 @@ static bool spinand_isbad(struct nand_de
+ .oobbuf.in = marker,
+ .mode = MTD_OPS_RAW,
+ };
++ int ret;
+
+ spinand_select_target(spinand, pos->target);
+- spinand_read_page(spinand, &req);
++
++ ret = spinand_read_page(spinand, &req);
++ if (ret == -EOPNOTSUPP) {
++ /* Retry with ECC in case raw access is not supported */
++ req.mode = MTD_OPS_PLACE_OOB;
++ spinand_read_page(spinand, &req);
++ }
++
+ if (marker[0] != 0xff || marker[1] != 0xff)
+ return true;
+
+@@ -966,7 +977,14 @@ static int spinand_markbad(struct nand_d
+ if (ret)
+ return ret;
+
+- return spinand_write_page(spinand, &req);
++ ret = spinand_write_page(spinand, &req);
++ if (ret == -EOPNOTSUPP) {
++ /* Retry with ECC in case raw access is not supported */
++ req.mode = MTD_OPS_PLACE_OOB;
++ ret = spinand_write_page(spinand, &req);
++ }
++
++ return ret;
+ }
+
+ static int spinand_mtd_block_markbad(struct mtd_info *mtd, loff_t offs)
+--- a/include/linux/mtd/spinand.h
++++ b/include/linux/mtd/spinand.h
+@@ -315,6 +315,7 @@ struct spinand_ecc_info {
+ #define SPINAND_HAS_CR_FEAT_BIT BIT(1)
+ #define SPINAND_HAS_PROG_PLANE_SELECT_BIT BIT(2)
+ #define SPINAND_HAS_READ_PLANE_SELECT_BIT BIT(3)
++#define SPINAND_NO_RAW_ACCESS BIT(4)
+
+ /**
+ * struct spinand_ondie_ecc_conf - private SPI-NAND on-die ECC engine structure
--- /dev/null
+From 1a50e3612de9187857f55ee14a573f7f8e7d4ebc Mon Sep 17 00:00:00 2001
+From: Takahiro Kuwano <Takahiro.Kuwano@infineon.com>
+Date: Tue, 3 Dec 2024 11:46:50 +0900
+Subject: [PATCH] mtd: spinand: Add support for SkyHigh S35ML-3 family
+
+SkyHigh S35ML01G300, S35ML01G301, S35ML02G300, and S35ML04G300 are 1Gb,
+2Gb, and 4Gb SLC SPI NAND flash family. This family of devices has
+on-die ECC which parity bits are stored to hidden area. In this family
+the on-die ECC cannot be disabled so raw access needs to be prevented.
+
+Link: https://www.skyhighmemory.com/download/SPI_S35ML01_04G3_002_19205.pdf?v=P
+Co-developed-by: KR Kim <kr.kim@skyhighmemory.com>
+Signed-off-by: KR Kim <kr.kim@skyhighmemory.com>
+Signed-off-by: Takahiro Kuwano <Takahiro.Kuwano@infineon.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/spi/Makefile | 2 +-
+ drivers/mtd/nand/spi/core.c | 1 +
+ drivers/mtd/nand/spi/skyhigh.c | 147 +++++++++++++++++++++++++++++++++
+ include/linux/mtd/spinand.h | 1 +
+ 4 files changed, 150 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/mtd/nand/spi/skyhigh.c
+
+--- a/drivers/mtd/nand/spi/Makefile
++++ b/drivers/mtd/nand/spi/Makefile
+@@ -1,4 +1,4 @@
+ # SPDX-License-Identifier: GPL-2.0
+ spinand-objs := core.o alliancememory.o ato.o esmt.o fmsh.o foresee.o gigadevice.o macronix.o
+-spinand-objs += micron.o paragon.o toshiba.o winbond.o xtx.o
++spinand-objs += micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o
+ obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
+--- a/drivers/mtd/nand/spi/core.c
++++ b/drivers/mtd/nand/spi/core.c
+@@ -1183,6 +1183,7 @@ static const struct spinand_manufacturer
+ ¯onix_spinand_manufacturer,
+ µn_spinand_manufacturer,
+ ¶gon_spinand_manufacturer,
++ &skyhigh_spinand_manufacturer,
+ &toshiba_spinand_manufacturer,
+ &winbond_spinand_manufacturer,
+ &xtx_spinand_manufacturer,
+--- /dev/null
++++ b/drivers/mtd/nand/spi/skyhigh.c
+@@ -0,0 +1,147 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Copyright (c) 2024 SkyHigh Memory Limited
++ *
++ * Author: Takahiro Kuwano <takahiro.kuwano@infineon.com>
++ * Co-Author: KR Kim <kr.kim@skyhighmemory.com>
++ */
++
++#include <linux/device.h>
++#include <linux/kernel.h>
++#include <linux/mtd/spinand.h>
++
++#define SPINAND_MFR_SKYHIGH 0x01
++#define SKYHIGH_STATUS_ECC_1TO2_BITFLIPS (1 << 4)
++#define SKYHIGH_STATUS_ECC_3TO6_BITFLIPS (2 << 4)
++#define SKYHIGH_STATUS_ECC_UNCOR_ERROR (3 << 4)
++#define SKYHIGH_CONFIG_PROTECT_EN BIT(1)
++
++static SPINAND_OP_VARIANTS(read_cache_variants,
++ SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 4, NULL, 0),
++ SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
++ SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 2, NULL, 0),
++ SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
++ SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
++ SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
++
++static SPINAND_OP_VARIANTS(write_cache_variants,
++ SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
++ SPINAND_PROG_LOAD(true, 0, NULL, 0));
++
++static SPINAND_OP_VARIANTS(update_cache_variants,
++ SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
++ SPINAND_PROG_LOAD(false, 0, NULL, 0));
++
++static int skyhigh_spinand_ooblayout_ecc(struct mtd_info *mtd, int section,
++ struct mtd_oob_region *region)
++{
++ /* ECC bytes are stored in hidden area. */
++ return -ERANGE;
++}
++
++static int skyhigh_spinand_ooblayout_free(struct mtd_info *mtd, int section,
++ struct mtd_oob_region *region)
++{
++ if (section)
++ return -ERANGE;
++
++ /* ECC bytes are stored in hidden area. Reserve 2 bytes for the BBM. */
++ region->offset = 2;
++ region->length = mtd->oobsize - 2;
++
++ return 0;
++}
++
++static const struct mtd_ooblayout_ops skyhigh_spinand_ooblayout = {
++ .ecc = skyhigh_spinand_ooblayout_ecc,
++ .free = skyhigh_spinand_ooblayout_free,
++};
++
++static int skyhigh_spinand_ecc_get_status(struct spinand_device *spinand,
++ u8 status)
++{
++ switch (status & STATUS_ECC_MASK) {
++ case STATUS_ECC_NO_BITFLIPS:
++ return 0;
++
++ case SKYHIGH_STATUS_ECC_UNCOR_ERROR:
++ return -EBADMSG;
++
++ case SKYHIGH_STATUS_ECC_1TO2_BITFLIPS:
++ return 2;
++
++ case SKYHIGH_STATUS_ECC_3TO6_BITFLIPS:
++ return 6;
++
++ default:
++ break;
++ }
++
++ return -EINVAL;
++}
++
++static const struct spinand_info skyhigh_spinand_table[] = {
++ SPINAND_INFO("S35ML01G301",
++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x15),
++ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
++ NAND_ECCREQ(6, 32),
++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
++ &write_cache_variants,
++ &update_cache_variants),
++ SPINAND_NO_RAW_ACCESS,
++ SPINAND_ECCINFO(&skyhigh_spinand_ooblayout,
++ skyhigh_spinand_ecc_get_status)),
++ SPINAND_INFO("S35ML01G300",
++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x14),
++ NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
++ NAND_ECCREQ(6, 32),
++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
++ &write_cache_variants,
++ &update_cache_variants),
++ SPINAND_NO_RAW_ACCESS,
++ SPINAND_ECCINFO(&skyhigh_spinand_ooblayout,
++ skyhigh_spinand_ecc_get_status)),
++ SPINAND_INFO("S35ML02G300",
++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x25),
++ NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1),
++ NAND_ECCREQ(6, 32),
++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
++ &write_cache_variants,
++ &update_cache_variants),
++ SPINAND_NO_RAW_ACCESS,
++ SPINAND_ECCINFO(&skyhigh_spinand_ooblayout,
++ skyhigh_spinand_ecc_get_status)),
++ SPINAND_INFO("S35ML04G300",
++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x35),
++ NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 2, 1, 1),
++ NAND_ECCREQ(6, 32),
++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
++ &write_cache_variants,
++ &update_cache_variants),
++ SPINAND_NO_RAW_ACCESS,
++ SPINAND_ECCINFO(&skyhigh_spinand_ooblayout,
++ skyhigh_spinand_ecc_get_status)),
++};
++
++static int skyhigh_spinand_init(struct spinand_device *spinand)
++{
++ /*
++ * Config_Protect_En (bit 1 in Block Lock register) must be set to 1
++ * before writing other bits. Do it here before core unlocks all blocks
++ * by writing block protection bits.
++ */
++ return spinand_write_reg_op(spinand, REG_BLOCK_LOCK,
++ SKYHIGH_CONFIG_PROTECT_EN);
++}
++
++static const struct spinand_manufacturer_ops skyhigh_spinand_manuf_ops = {
++ .init = skyhigh_spinand_init,
++};
++
++const struct spinand_manufacturer skyhigh_spinand_manufacturer = {
++ .id = SPINAND_MFR_SKYHIGH,
++ .name = "SkyHigh",
++ .chips = skyhigh_spinand_table,
++ .nchips = ARRAY_SIZE(skyhigh_spinand_table),
++ .ops = &skyhigh_spinand_manuf_ops,
++};
+--- a/include/linux/mtd/spinand.h
++++ b/include/linux/mtd/spinand.h
+@@ -269,6 +269,7 @@ extern const struct spinand_manufacturer
+ extern const struct spinand_manufacturer macronix_spinand_manufacturer;
+ extern const struct spinand_manufacturer micron_spinand_manufacturer;
+ extern const struct spinand_manufacturer paragon_spinand_manufacturer;
++extern const struct spinand_manufacturer skyhigh_spinand_manufacturer;
+ extern const struct spinand_manufacturer toshiba_spinand_manufacturer;
+ extern const struct spinand_manufacturer winbond_spinand_manufacturer;
+ extern const struct spinand_manufacturer xtx_spinand_manufacturer;