1
1
openwrt/package/boot/uboot-mediatek/patches/100-21-mtd-spi-nand-add-CASN-page-support.patch
Shiji Yang b94de14baf uboot-mediatek: update to v2026.01
Remove upstreamed patches:
  100-08-cmd-mtd-add-markbad-subcommand-for-NMBM-testing.patch [1]
  101-01-mtd-spinand-add-support-for-FORESEE-F35SQA002G.patch [2]
  101-02-mtd-spinand-add-support-for-FORESEE-F35SQA001G.patch [3]
  110-mtd-spi-nand-add-support-for-FudanMicro-FM25S01A.patch [4]

Some SPI-NAND driver macro definitions and function parameters have
been changed in the latest release[3]. Hence we also had to rework
the related local patches to follow the upstream changes.

Tested on MT7981 SPI-NOR/EMMC.

[1] 21c1098cf4
[2] 2a0f8e7da0
[3] 2cbdd3e449
[4] 8b984b5a39

Signed-off-by: Shiji Yang <yangshiji66@outlook.com>
[daniel@makrotopia.org: tested MT7622 SNAND and SPI-NOR]
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
2026-01-24 00:17:22 +00:00

1227 lines
35 KiB
Diff

From ce3ecc9bc5d22327dca2ba91fb0bfbf50ce4e573 Mon Sep 17 00:00:00 2001
From: Weijie Gao <weijie.gao@mediatek.com>
Date: Wed, 15 Jan 2025 14:51:40 +0800
Subject: [PATCH 21/30] mtd: spi-nand: add CASN page support
CASN page implements full description for SPI-NAND.
Software driver can utililize it to address the whole NAND chip.
Also, it can handle ECC information as well.
CASN page is designed to solve the following problems:
1. Increasing size of SPI-NAND flash tables, which exist in various
drivers/mtd/nand/spi/*.c files.
2. Decreasing efforts on adding new SPI-NAND's flash tables.
3. Extract correct flash on-die ECC's bitflip numbers if it supports
advanced ECC status registers. The on-die ECC engine design
varies from vendor to vendor. CASN can transform them into
unified format.
4. A single Uboot image can deal with most of SPI-NAND devices.
Signed-off-by: SkyLake.Huang <skylake.huang@mediatek.com>
Signed-off-by: Weijie Gao <weijie.gao@mediatek.com>
---
drivers/mtd/nand/spi/core.c | 800 +++++++++++++++++++++++++++++++++++-
include/linux/mtd/casn.h | 191 +++++++++
include/linux/mtd/spinand.h | 97 +++++
3 files changed, 1074 insertions(+), 14 deletions(-)
create mode 100644 include/linux/mtd/casn.h
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -10,10 +10,12 @@
#define pr_fmt(fmt) "spi-nand: " fmt
#ifndef __UBOOT__
+#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/mtd/casn.h>
#include <linux/mtd/spinand.h>
#include <linux/of.h>
#include <linux/slab.h>
@@ -28,8 +30,10 @@
#include <ubi_uboot.h>
#include <dm/device_compat.h>
#include <dm/devres.h>
+#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/bug.h>
+#include <linux/mtd/casn.h>
#include <linux/mtd/spinand.h>
#include <linux/printk.h>
#include <linux/delay.h>
@@ -229,6 +233,62 @@ static int spinand_cont_read_enable(stru
return spinand->set_cont_read(spinand, enable);
}
+static size_t eccsr_none_op(size_t val, size_t mask) { return val; }
+static size_t eccsr_and_op(size_t val, size_t mask) { return val & mask; }
+static size_t eccsr_add_op(size_t val, size_t mask) { return val + mask; }
+static size_t eccsr_minus_op(size_t val, size_t mask) { return val - mask; }
+static size_t eccsr_mul_op(size_t val, size_t mask) { return val * mask; }
+
+static void spinand_read_adv_ecc(struct spinand_device *spinand,
+ struct spi_mem_op *ops, u16 *eccsr,
+ u16 mask, u8 shift,
+ u8 pre_op, u8 pre_mask)
+{
+ u8 *p = spinand->scratchbuf;
+
+ spi_mem_exec_op(spinand->slave, ops);
+
+ if (likely(mask <= 0xff))
+ *eccsr += (*p & mask) >> shift;
+ else
+ *eccsr += (((*p << 8) | (*p+1)) & mask) >> shift;
+
+ *eccsr = spinand->eccsr_math_op[pre_op](*eccsr, pre_mask);
+}
+
+static int spinand_casn_get_ecc_status(struct spinand_device *spinand, u8 status)
+{
+ struct mtd_info *mtd = spinand_to_mtd(spinand);
+ struct CASN_ADVECC *ah = spinand->advecc_high;
+ struct CASN_ADVECC *al = spinand->advecc_low;
+ u16 eccsr_high = 0;
+ u16 eccsr_low = 0;
+ u32 eccsr = 0;
+
+ if (al->cmd) {
+ spinand_read_adv_ecc(spinand,
+ spinand->advecc_low_ops, &eccsr_low,
+ al->mask, al->shift,
+ al->pre_op, al->pre_mask);
+ eccsr += eccsr_low;
+ }
+ if (ah->cmd) {
+ spinand_read_adv_ecc(spinand,
+ spinand->advecc_high_ops, &eccsr_high,
+ ah->mask, ah->shift,
+ ah->pre_op, ah->pre_mask);
+ eccsr += eccsr_high << spinand->advecc_low_bitcnt;
+ }
+
+ if (eccsr == spinand->advecc_noerr_status)
+ return 0;
+ else if (eccsr == spinand->advecc_uncor_status)
+ return -EBADMSG;
+ eccsr = spinand->eccsr_math_op[spinand->advecc_post_op](eccsr, spinand->advecc_post_mask);
+
+ return eccsr > mtd->ecc_strength ? mtd->ecc_strength : eccsr;
+}
+
static int spinand_check_ecc_status(struct spinand_device *spinand, u8 status)
{
struct nand_device *nand = spinand_to_nand(spinand);
@@ -1267,6 +1327,301 @@ static int spinand_manufacturer_match(st
return -EOPNOTSUPP;
}
+static u16 nanddev_crc16(u16 crc, u8 const *p, size_t len)
+{
+ int i;
+ while (len--) {
+ crc ^= *p++ << 8;
+ for (i = 0; i < 8; i++)
+ crc = (crc << 1) ^ ((crc & 0x8000) ? 0x8005 : 0);
+ }
+
+ return crc;
+}
+
+/* Sanitize ONFI strings so we can safely print them */
+static void sanitize_string(char *s, size_t len)
+{
+ ssize_t i;
+
+ /* Null terminate */
+ s[len - 1] = 0;
+
+ /* Remove non printable chars */
+ for (i = 0; i < len - 1; i++) {
+ if (s[i] < ' ' || s[i] > 127)
+ s[i] = '?';
+ }
+
+ /* Remove trailing spaces */
+ strim(s);
+}
+
+/*
+ * Recover data with bit-wise majority
+ */
+static void nanddev_bit_wise_majority(const void **srcbufs,
+ unsigned int nsrcbufs,
+ void *dstbuf,
+ unsigned int bufsize)
+{
+ int i, j, k;
+
+ for (i = 0; i < bufsize; i++) {
+ u8 val = 0;
+
+ for (j = 0; j < 8; j++) {
+ unsigned int cnt = 0;
+
+ for (k = 0; k < nsrcbufs; k++) {
+ const u8 *srcbuf = srcbufs[k];
+
+ if (srcbuf[i] & BIT(j))
+ cnt++;
+ }
+
+ if (cnt > nsrcbufs / 2)
+ val |= BIT(j);
+ }
+
+ ((u8 *)dstbuf)[i] = val;
+ }
+}
+
+static int spinand_check_casn_validity(struct spinand_device *spinand,
+ struct nand_casn *casn)
+{
+ struct udevice *dev = spinand->slave->dev;
+
+ if (be32_to_cpu(casn->bits_per_cell) != 1) {
+ dev_err(dev, "[CASN] bits-per-cell must be 1\n");
+ return -EINVAL;
+ }
+
+ switch (be32_to_cpu(casn->bytes_per_page)) {
+ case 2048:
+ case 4096:
+ break;
+ default:
+ dev_err(dev, "[CASN] page size must be 2048/4096\n");
+ return -EINVAL;
+ }
+
+ switch (be32_to_cpu(casn->spare_bytes_per_page)) {
+ case 64:
+ case 96:
+ case 128:
+ case 256:
+ break;
+ default:
+ dev_err(dev, "[CASN] spare size must be 64/128/256\n");
+ return -EINVAL;
+ }
+
+ switch (be32_to_cpu(casn->pages_per_block)) {
+ case 64:
+ case 128:
+ break;
+ default:
+ dev_err(dev, "[CASN] pages_per_block must be 64/128\n");
+ return -EINVAL;
+ }
+
+ switch (be32_to_cpu(casn->blocks_per_lun)) {
+ case 1024:
+ if (be32_to_cpu(casn->max_bb_per_lun) != 20) {
+ dev_err(dev, "[CASN] max_bb_per_lun must be 20 when blocks_per_lun is 1024\n");
+ return -EINVAL;
+ }
+ break;
+ case 2048:
+ if (be32_to_cpu(casn->max_bb_per_lun) != 40) {
+ dev_err(dev, "[CASN] max_bb_per_lun must be 40 when blocks_per_lun is 2048\n");
+ return -EINVAL;
+ }
+ break;
+ case 4096:
+ if (be32_to_cpu(casn->max_bb_per_lun) != 80) {
+ dev_err(dev, "[CASN] max_bb_per_lun must be 80 when blocks_per_lun is 4096\n");
+ return -EINVAL;
+ }
+ break;
+ default:
+ dev_err(dev, "[CASN] blocks_per_lun must be 1024/2048/4096\n");
+ return -EINVAL;
+ }
+
+ switch (be32_to_cpu(casn->planes_per_lun)) {
+ case 1:
+ case 2:
+ break;
+ default:
+ dev_err(dev, "[CASN] planes_per_lun must be 1/2\n");
+ return -EINVAL;
+ }
+
+ switch (be32_to_cpu(casn->luns_per_target)) {
+ case 1:
+ case 2:
+ break;
+ default:
+ dev_err(dev, "[CASN] luns_per_target must be 1/2\n");
+ return -EINVAL;
+ }
+
+ switch (be32_to_cpu(casn->total_target)) {
+ case 1:
+ case 2:
+ break;
+ default:
+ dev_err(dev, "[CASN] ntargets must be 1/2\n");
+ return -EINVAL;
+ }
+
+ if (casn->casn_oob.layout_type != OOB_CONTINUOUS &&
+ casn->casn_oob.layout_type != OOB_DISCRETE) {
+ dev_err(dev, "[CASN] OOB layout type isn't correct.\n");
+ return -EINVAL;
+ }
+
+ if (casn->ecc_status_high.status_nbytes > 2 ||
+ casn->ecc_status_low.status_nbytes > 2) {
+ dev_err(dev, "[CASN] ADVECC status nbytes must be no more than 2\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int spinand_check_casn(struct spinand_device *spinand,
+ struct nand_casn *casn, unsigned int *sel)
+{
+ struct udevice *dev = spinand->slave->dev;
+ uint16_t crc = be16_to_cpu(casn->crc);
+ uint16_t crc_compute;
+ int ret = 0;
+ int i;
+
+ /* There are 3 copies of CASN Pages V1. Choose one avabilable copy
+ * first. If none of the copies is available, try to recover.
+ */
+ for (i = 0; i < CASN_PAGE_V1_COPIES; i++) {
+ if (be32_to_cpu(casn[i].signature) != CASN_SIGNATURE) {
+ ret = -EINVAL;
+ continue;
+ }
+ crc_compute = nanddev_crc16(CASN_CRC_BASE, (u8 *)(casn + i),
+ SPINAND_CASN_V1_CRC_OFS);
+ dev_dbg(dev, "CASN COPY %d CRC read: 0x%x, compute: 0x%x\n",
+ i, crc, crc_compute);
+ if (crc != crc_compute) {
+ ret = -EBADMSG;
+ continue;
+ }
+ ret = spinand_check_casn_validity(spinand, casn + i);
+ if (ret < 0)
+ continue;
+ *sel = i;
+ break;
+ }
+
+ if (i == CASN_PAGE_V1_COPIES && ret == -EBADMSG) {
+ const void *srcbufs[CASN_PAGE_V1_COPIES];
+ int j;
+
+ for (j = 0; j < CASN_PAGE_V1_COPIES; j++)
+ srcbufs[j] = casn + j;
+ dev_info(dev, "Couldn't find a valid CASN page, try bitwise majority to recover it\n");
+ nanddev_bit_wise_majority(srcbufs, CASN_PAGE_V1_COPIES, casn,
+ sizeof(*casn));
+ crc_compute = nanddev_crc16(CASN_CRC_BASE, (uint8_t *)casn,
+ SPINAND_CASN_V1_CRC_OFS);
+ if (crc_compute != crc) {
+ dev_err(dev, "CASN page recovery failed, aborting\n");
+ return -EBADMSG;
+ }
+ ret = spinand_check_casn_validity(spinand, casn + i);
+ if (ret < 0)
+ return ret;
+ dev_info(dev, "CASN page recovery succeeded\n");
+ *sel = 0;
+ }
+
+ return ret;
+}
+
+static int spinand_casn_detect(struct spinand_device *spinand,
+ struct nand_casn *casn, unsigned int *sel)
+{
+ struct udevice *dev = spinand->slave->dev;
+ uint8_t casn_offset[3] = {0x0, 0x1, 0x4};
+ struct nand_page_io_req req;
+ struct spi_mem_op op;
+ struct nand_pos pos;
+ int check_ret = 0;
+ uint8_t status;
+ int final_ret;
+ int ret = 0;
+ u8 cfg_reg;
+ int i;
+
+ ret = spinand_read_reg_op(spinand, REG_CFG, &cfg_reg);
+ if (ret)
+ return ret;
+
+ ret = spinand_write_reg_op(spinand, REG_CFG, cfg_reg | BIT(6));
+ if (ret)
+ return ret;
+
+ memset(&pos, 0, sizeof(pos));
+
+ req = (struct nand_page_io_req){
+ .pos = pos,
+ .dataoffs = 0,
+ .datalen = 256 * CASN_PAGE_V1_COPIES,
+ .databuf.in = (u8 *)casn,
+ .mode = MTD_OPS_AUTO_OOB,
+ };
+
+ for (i = 0; i < sizeof(casn_offset)/sizeof(uint8_t); i++) {
+ req.pos.page = casn_offset[i];
+ ret = spinand_load_page_op(spinand, &req);
+ if (ret)
+ goto finish;
+
+ ret = spinand_wait(spinand, SPINAND_READ_INITIAL_DELAY_US,
+ SPINAND_READ_POLL_DELAY_US, &status);
+ if (ret < 0)
+ goto finish;
+
+ op = (struct spi_mem_op) SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(
+ 768, 1, (u8 *)casn, 256 * CASN_PAGE_V1_COPIES, 0);
+ ret = spi_mem_exec_op(spinand->slave, &op);
+ if (ret < 0)
+ goto finish;
+
+ check_ret = spinand_check_casn(spinand, casn, sel);
+ if (!check_ret)
+ break;
+ }
+
+finish:
+ /* We need to restore configuration register. */
+ final_ret = spinand_write_reg_op(spinand, REG_CFG, cfg_reg);
+ if (final_ret)
+ return final_ret;
+
+ if (check_ret) {
+ dev_err(dev, "CASN page check failed\n");
+ return check_ret;
+ }
+
+ if (ret)
+ dev_err(dev, "CASN page read failed\n");
+
+ return ret;
+}
+
static int spinand_id_detect(struct spinand_device *spinand)
{
u8 *id = spinand->id.data;
@@ -1300,7 +1655,7 @@ static int spinand_manufacturer_init(str
{
int ret;
- if (spinand->manufacturer->ops->init) {
+ if (!spinand->use_casn && spinand->manufacturer->ops->init) {
ret = spinand->manufacturer->ops->init(spinand);
if (ret)
return ret;
@@ -1318,7 +1673,7 @@ static int spinand_manufacturer_init(str
static void spinand_manufacturer_cleanup(struct spinand_device *spinand)
{
/* Release manufacturer private data */
- if (spinand->manufacturer->ops->cleanup)
+ if (!spinand->use_casn && spinand->manufacturer->ops->cleanup)
return spinand->manufacturer->ops->cleanup(spinand);
}
@@ -1452,37 +1807,455 @@ int spinand_match_and_init(struct spinan
return -ENOTSUPP;
}
+static int spinand_casn_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ struct spinand_device *spinand = mtd_to_spinand(mtd);
+ int sectionp;
+ struct CASN_OOB *co = spinand->casn_oob;
+
+ sectionp = spinand->base.memorg.pagesize/mtd->ecc_step_size;
+ if (section >= sectionp)
+ return -ERANGE;
+
+ if (co->layout_type == OOB_DISCRETE) {
+ region->offset = co->ecc_parity_start +
+ (co->free_length + co->ecc_parity_space)
+ * section;
+ } else if (co->layout_type == OOB_CONTINUOUS) {
+ region->offset = co->ecc_parity_start + co->ecc_parity_space * section;
+ }
+ region->length = co->ecc_parity_real_length;
+
+ return 0;
+}
+
+static int spinand_casn_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ struct spinand_device *spinand = mtd_to_spinand(mtd);
+ int sectionp;
+ struct CASN_OOB *co = spinand->casn_oob;
+
+ sectionp = spinand->base.memorg.pagesize/mtd->ecc_step_size;
+ if (section >= sectionp)
+ return -ERANGE;
+
+ if (!section) {
+ region->offset = co->free_start + co->bbm_length;
+ region->length = co->free_length - co->bbm_length;
+ } else {
+ if (co->layout_type == OOB_DISCRETE) {
+ region->offset = co->free_start +
+ (co->free_length +
+ co->ecc_parity_space) * section;
+ } else if (co->layout_type == OOB_CONTINUOUS) {
+ region->offset = co->free_start +
+ co->free_length * section;
+ }
+ region->length = co->free_length;
+ }
+
+ return 0;
+}
+
+static const struct mtd_ooblayout_ops spinand_casn_ooblayout = {
+ .ecc = spinand_casn_ooblayout_ecc,
+ .rfree = spinand_casn_ooblayout_free,
+};
+
+static int spinand_set_read_op_variants(struct spinand_device *spinand,
+ struct nand_casn *casn)
+{
+ struct spinand_op_variants casn_read_cache_variants;
+ u16 sdr_read_cap = be16_to_cpu(casn->sdr_read_cap);
+ struct spi_mem_op *read_ops;
+ const struct spi_mem_op *op;
+ int i = 0;
+
+ read_ops = devm_kzalloc(spinand->slave->dev,
+ sizeof(struct spi_mem_op) *
+ hweight16(sdr_read_cap),
+ GFP_KERNEL);
+ if (!read_ops)
+ return -ENOMEM;
+
+ if (FIELD_GET(SDR_READ_1_4_4, sdr_read_cap)) {
+ read_ops[i] = (struct spi_mem_op)
+ SPINAND_CASN_PAGE_READ_FROM_CACHE_QUADIO_OP(
+ casn->sdr_read_1_4_4.addr_nbytes, 0,
+ casn->sdr_read_1_4_4.dummy_nbytes, NULL, 0
+ );
+ i++;
+ }
+ if (FIELD_GET(SDR_READ_1_1_4, sdr_read_cap)) {
+ read_ops[i] = (struct spi_mem_op)
+ SPINAND_CASN_PAGE_READ_FROM_CACHE_X4_OP(
+ casn->sdr_read_1_1_4.addr_nbytes, 0,
+ casn->sdr_read_1_1_4.dummy_nbytes, NULL, 0
+ );
+ i++;
+ }
+ if (FIELD_GET(SDR_READ_1_2_2, sdr_read_cap)) {
+ read_ops[i] = (struct spi_mem_op)
+ SPINAND_CASN_PAGE_READ_FROM_CACHE_DUALIO_OP(
+ casn->sdr_read_1_2_2.addr_nbytes, 0,
+ casn->sdr_read_1_2_2.dummy_nbytes, NULL, 0
+ );
+ i++;
+ }
+ if (FIELD_GET(SDR_READ_1_1_2, sdr_read_cap)) {
+ read_ops[i] = (struct spi_mem_op)
+ SPINAND_CASN_PAGE_READ_FROM_CACHE_X2_OP(
+ casn->sdr_read_1_1_2.addr_nbytes, 0,
+ casn->sdr_read_1_1_2.dummy_nbytes, NULL, 0
+ );
+ i++;
+ }
+ if (FIELD_GET(SDR_READ_1_1_1_FAST, sdr_read_cap)) {
+ read_ops[i] = (struct spi_mem_op)
+ SPINAND_CASN_PAGE_READ_FROM_CACHE_OP(
+ true, casn->sdr_read_1_1_1_fast.addr_nbytes, 0,
+ casn->sdr_read_1_1_1_fast.dummy_nbytes, NULL, 0
+ );
+ i++;
+ }
+ if (FIELD_GET(SDR_READ_1_1_1, sdr_read_cap)) {
+ read_ops[i] = (struct spi_mem_op)
+ SPINAND_CASN_PAGE_READ_FROM_CACHE_OP(
+ false, casn->sdr_read_1_1_1.addr_nbytes, 0,
+ casn->sdr_read_1_1_1.dummy_nbytes, NULL, 0
+ );
+ i++;
+ }
+
+ casn_read_cache_variants = (struct spinand_op_variants){
+ .ops = read_ops,
+ .nops = hweight16(sdr_read_cap),
+ };
+
+ op = spinand_select_op_variant(spinand, &casn_read_cache_variants);
+ if (!op) {
+ devm_kfree(spinand->slave->dev, read_ops);
+ return -ENOTSUPP;
+ }
+ spinand->op_templates.read_cache = op;
+
+ return 0;
+}
+
+static int spinand_set_write_op_variants(struct spinand_device *spinand,
+ struct nand_casn *casn)
+{
+ struct spinand_op_variants casn_write_cache_variants;
+ struct spi_mem_op *write_ops;
+ const struct spi_mem_op *op;
+ int i = 0;
+
+ write_ops = devm_kzalloc(spinand->slave->dev,
+ sizeof(struct spi_mem_op) *
+ hweight8(casn->sdr_write_cap),
+ GFP_KERNEL);
+ if (!write_ops)
+ return -ENOMEM;
+
+ if (FIELD_GET(SDR_WRITE_1_1_4, casn->sdr_write_cap)) {
+ write_ops[i] = (struct spi_mem_op)
+ SPINAND_CASN_PROG_LOAD_X4(
+ true, casn->sdr_write_1_1_4.addr_nbytes, 0,
+ NULL, 0);
+ i++;
+ }
+ if (FIELD_GET(SDR_WRITE_1_1_1, casn->sdr_write_cap)) {
+ write_ops[i] = (struct spi_mem_op)
+ SPINAND_CASN_PROG_LOAD(
+ true, casn->sdr_write_1_1_1.addr_nbytes, 0,
+ NULL, 0);
+ i++;
+ }
+
+ casn_write_cache_variants = (struct spinand_op_variants){
+ .ops = write_ops,
+ .nops = hweight8(casn->sdr_write_cap),
+ };
+
+ op = spinand_select_op_variant(spinand, &casn_write_cache_variants);
+ if (!op) {
+ devm_kfree(spinand->slave->dev, write_ops);
+ return -ENOTSUPP;
+ }
+ spinand->op_templates.write_cache = op;
+
+ return 0;
+}
+
+static int spinand_set_update_op_variants(struct spinand_device *spinand,
+ struct nand_casn *casn)
+{
+ struct spinand_op_variants casn_update_cache_variants;
+ struct spi_mem_op *update_ops;
+ const struct spi_mem_op *op;
+ int i = 0;
+
+ update_ops = devm_kzalloc(spinand->slave->dev,
+ sizeof(struct spi_mem_op) *
+ hweight8(casn->sdr_update_cap),
+ GFP_KERNEL);
+ if (!update_ops)
+ return -ENOMEM;
+
+ if (FIELD_GET(SDR_UPDATE_1_1_4, casn->sdr_update_cap)) {
+ update_ops[i] = (struct spi_mem_op)
+ SPINAND_CASN_PROG_LOAD_X4(
+ false, casn->sdr_update_1_1_4.addr_nbytes, 0,
+ NULL, 0);
+ i++;
+ }
+ if (FIELD_GET(SDR_UPDATE_1_1_1, casn->sdr_update_cap)) {
+ update_ops[i] = (struct spi_mem_op)
+ SPINAND_CASN_PROG_LOAD(
+ false, casn->sdr_update_1_1_1.addr_nbytes, 0,
+ NULL, 0);
+ i++;
+ }
+
+ casn_update_cache_variants = (struct spinand_op_variants){
+ .ops = update_ops,
+ .nops = hweight8(casn->sdr_update_cap),
+ };
+
+ op = spinand_select_op_variant(spinand, &casn_update_cache_variants);
+ if (!op) {
+ devm_kfree(spinand->slave->dev, update_ops);
+ return -ENOTSUPP;
+ }
+ spinand->op_templates.update_cache = op;
+
+ return 0;
+}
+
+static int spinand_init_via_casn(struct spinand_device *spinand,
+ struct nand_casn *casn)
+{
+ struct nand_device *nand = spinand_to_nand(spinand);
+ u32 val;
+ int ret;
+ int i;
+
+ /* Set members of nand->memorg via CASN. */
+ for (i = 0; i < 9; i++) {
+ val = be32_to_cpu(*(&casn->bits_per_cell + i));
+ memcpy((u32 *)&nand->memorg.bits_per_cell + i, &val, sizeof(u32));
+ }
+ nand->eccreq.strength = be32_to_cpu(casn->ecc_strength);
+ nand->eccreq.step_size = be32_to_cpu(casn->ecc_step_size);
+ spinand->flags = casn->flags;
+
+ if (spinand->flags & SPINAND_SUP_ADV_ECC_STATUS) {
+ spinand->eccinfo = (struct spinand_ecc_info) {
+ &spinand_casn_get_ecc_status, &spinand_casn_ooblayout};
+ } else {
+ spinand->eccinfo = (struct spinand_ecc_info) {
+ NULL, &spinand_casn_ooblayout };
+ }
+
+ spinand->advecc_high_ops = devm_kzalloc(spinand->slave->dev,
+ sizeof(struct spi_mem_op),
+ GFP_KERNEL);
+ if (!spinand->advecc_high_ops)
+ return -ENOMEM;
+ spinand->advecc_low_ops = devm_kzalloc(spinand->slave->dev,
+ sizeof(struct spi_mem_op),
+ GFP_KERNEL);
+ if (!spinand->advecc_low_ops)
+ return -ENOMEM;
+ spinand->casn_oob = devm_kzalloc(spinand->slave->dev,
+ sizeof(struct CASN_OOB),
+ GFP_KERNEL);
+ if (!spinand->casn_oob)
+ return -ENOMEM;
+ spinand->advecc_high = devm_kzalloc(spinand->slave->dev,
+ sizeof(struct CASN_ADVECC),
+ GFP_KERNEL);
+ if (!spinand->advecc_high)
+ return -ENOMEM;
+ spinand->advecc_low = devm_kzalloc(spinand->slave->dev,
+ sizeof(struct CASN_ADVECC),
+ GFP_KERNEL);
+ if (!spinand->advecc_low)
+ return -ENOMEM;
+
+ *spinand->advecc_high_ops = (struct spi_mem_op)
+ SPINAND_CASN_ADVECC_OP(casn->ecc_status_high, spinand->scratchbuf);
+ *spinand->advecc_low_ops = (struct spi_mem_op)
+ SPINAND_CASN_ADVECC_OP(casn->ecc_status_low, spinand->scratchbuf);
+
+ memcpy(spinand->casn_oob, &casn->casn_oob, sizeof(struct CASN_OOB));
+
+ spinand->advecc_high->cmd = casn->ecc_status_high.cmd;
+ spinand->advecc_high->mask = be16_to_cpu(casn->ecc_status_high.status_mask);
+ spinand->advecc_high->shift = spinand->advecc_high->mask ?
+ ffs(spinand->advecc_high->mask)-1 : 0;
+ spinand->advecc_high->pre_op = casn->ecc_status_high.pre_op;
+ spinand->advecc_high->pre_mask = casn->ecc_status_high.pre_mask;
+
+ spinand->advecc_low->cmd = casn->ecc_status_low.cmd;
+ spinand->advecc_low->mask = be16_to_cpu(casn->ecc_status_low.status_mask);
+ spinand->advecc_low->shift = spinand->advecc_low->mask ?
+ ffs(spinand->advecc_low->mask)-1 : 0;
+ spinand->advecc_low->pre_op = casn->ecc_status_low.pre_op;
+ spinand->advecc_low->pre_mask = casn->ecc_status_low.pre_mask;
+
+ spinand->advecc_low_bitcnt = hweight16(spinand->advecc_low->mask);
+
+ spinand->advecc_noerr_status = casn->advecc_noerr_status;
+ spinand->advecc_uncor_status = casn->advecc_uncor_status;
+ spinand->advecc_post_op = casn->advecc_post_op;
+ spinand->advecc_post_mask = casn->advecc_post_mask;
+ spinand->eccsr_math_op[0] = eccsr_none_op;
+ spinand->eccsr_math_op[1] = eccsr_and_op;
+ spinand->eccsr_math_op[2] = eccsr_add_op;
+ spinand->eccsr_math_op[3] = eccsr_minus_op;
+ spinand->eccsr_math_op[4] = eccsr_mul_op;
+
+ ret = spinand_set_read_op_variants(spinand, casn);
+ if (ret < 0)
+ return ret;
+ ret = spinand_set_write_op_variants(spinand, casn);
+ if (ret < 0)
+ return ret;
+ ret = spinand_set_update_op_variants(spinand, casn);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static void spinand_dump_casn(struct spinand_device *spinand, struct nand_casn *casn)
+{
+ int i;
+
+ dev_dbg(spinand->slave->dev,
+ "---Start dumping full CASN page---\n");
+ for (i = 0; i < 64; i++)
+ pr_debug("0x%08x", *((u32 *)casn + i));
+
+ pr_debug("** Dump critical fields **\n");
+ pr_debug("signature: 0x%04x\n", be32_to_cpu(casn->signature));
+ pr_debug("version: v%u.%u\n", casn->version >> 4, casn->version & 0xf);
+ pr_debug("[Memory Organization]\n");
+ pr_debug(" bits_per_cell: %d\n", be32_to_cpu(casn->bits_per_cell));
+ pr_debug(" bytes_per_page: %d\n", be32_to_cpu(casn->bytes_per_page));
+ pr_debug(" spare_bytes_per_page: %d\n",
+ be32_to_cpu(casn->spare_bytes_per_page));
+ pr_debug(" pages_per_block: %d\n",
+ be32_to_cpu(casn->pages_per_block));
+ pr_debug(" blocks_per_lun: %d\n", be32_to_cpu(casn->blocks_per_lun));
+ pr_debug(" max_bb_per_lun: %d\n", be32_to_cpu(casn->max_bb_per_lun));
+ pr_debug(" planes_per_lun: %d\n", be32_to_cpu(casn->planes_per_lun));
+ pr_debug(" luns_per_target: %d\n",
+ be32_to_cpu(casn->luns_per_target));
+ pr_debug(" total_target: %d\n", be32_to_cpu(casn->total_target));
+ pr_debug("[flags]\n");
+ pr_debug(" 0. Have QE bit? %s\n",
+ casn->flags & SPINAND_HAS_QE_BIT ? "Yes" : "No");
+ pr_debug(" 1. Have continuous read feature bit? %s\n",
+ casn->flags & SPINAND_HAS_CR_FEAT_BIT ? "Yes" : "No");
+ pr_debug(" 2. Support continuous read? %s\n",
+ casn->flags & SPINAND_SUP_CR ? "Yes" : "No");
+ pr_debug(" 3. Support on-die ECC? %s\n",
+ casn->flags & SPINAND_SUP_ON_DIE_ECC ? "Yes" : "No");
+ pr_debug(" 4. Support legacy ECC status? %s\n",
+ casn->flags & SPINAND_SUP_LEGACY_ECC_STATUS ? "Yes" : "No");
+ pr_debug(" 5. Support advanced ECC status? %s\n",
+ casn->flags & SPINAND_SUP_ADV_ECC_STATUS ? "Yes" : "No");
+ pr_debug(" 6. ECC parity readable? %s\n",
+ casn->flags & SPINAND_ECC_PARITY_READABLE ? "Yes" : "No");
+ pr_debug("[R/W ability]\n");
+ pr_debug(" read ability: %x\n", be16_to_cpu(casn->sdr_read_cap));
+ pr_debug(" write ability: %x\n", casn->sdr_write_cap);
+ pr_debug(" update ability: %x\n", casn->sdr_update_cap);
+ pr_debug("advanced ECC no error state: %x\n",
+ casn->advecc_noerr_status);
+ pr_debug("advecced ECC uncorrectable state: %x\n",
+ casn->advecc_uncor_status);
+ pr_debug("CRC: 0x%04x\n", be16_to_cpu(casn->crc));
+
+ dev_dbg(spinand->slave->dev,
+ "---Dumping full CASN page ends here.---\n");
+}
+
static int spinand_detect(struct spinand_device *spinand)
{
struct nand_device *nand = spinand_to_nand(spinand);
+ struct udevice *dev = spinand->slave->dev;
+ struct nand_casn *casn;
+ char manufacturer[14];
+ unsigned int sel = 0;
+ char model[17];
int ret;
ret = spinand_reset_op(spinand);
if (ret)
return ret;
- ret = spinand_id_detect(spinand);
- if (ret) {
- dev_err(spinand->slave->dev, "unknown raw ID %02x %02x %02x %02x\n",
- spinand->id.data[0], spinand->id.data[1],
- spinand->id.data[2], spinand->id.data[3]);
- return ret;
+ spinand->use_casn = false;
+ casn = kzalloc((sizeof(struct nand_casn) * CASN_PAGE_V1_COPIES), GFP_KERNEL);
+ if (!casn)
+ return -ENOMEM;
+
+ ret = spinand_casn_detect(spinand, casn, &sel);
+ if (!ret) {
+ spinand->use_casn = true;
+ strncpy(manufacturer, casn[sel].manufacturer, sizeof(manufacturer)-1);
+ sanitize_string(manufacturer, sizeof(manufacturer));
+ strncpy(model, casn[sel].model, sizeof(model)-1);
+ sanitize_string(model, sizeof(model));
+
+ spinand_dump_casn(spinand, casn + sel);
+
+ ret = spinand_init_via_casn(spinand, casn + sel);
+ if (ret)
+ dev_err(dev, "Initilize spinand via CASN failed: %d\n", ret);
+ }
+
+ if (ret < 0) {
+ dev_warn(dev, "Fallback to read ID\n");
+
+ ret = spinand_reset_op(spinand);
+ if (ret)
+ goto free_casn;
+ ret = spinand_id_detect(spinand);
+ if (ret) {
+ dev_err(dev, "unknown raw ID %*phN\n", SPINAND_MAX_ID_LEN,
+ spinand->id.data);
+ goto free_casn;
+ }
}
if (nand->memorg.ntargets > 1 && !spinand->select_target) {
- dev_err(spinand->slave->dev,
+ dev_err(dev,
"SPI NANDs with more than one die must implement ->select_target()\n");
return -EINVAL;
+ goto free_casn;
+ }
+
+ if (spinand->use_casn) {
+ dev_info(spinand->slave->dev,
+ "%s %s SPI NAND was found.\n", manufacturer, model);
+ } else {
+ dev_info(spinand->slave->dev,
+ "%s SPI NAND was found.\n", spinand->manufacturer->name);
}
- dev_info(spinand->slave->dev,
- "%s SPI NAND was found.\n", spinand->manufacturer->name);
- dev_info(spinand->slave->dev,
- "%llu MiB, block size: %zu KiB, page size: %zu, OOB size: %u\n",
+ dev_info(dev, "%llu MiB, block size: %zu KiB, page size: %zu, OOB size: %u\n",
nanddev_size(nand) >> 20, nanddev_eraseblock_size(nand) >> 10,
nanddev_page_size(nand), nanddev_per_page_oobsize(nand));
- return 0;
+free_casn:
+ kfree(casn);
+
+ return ret;
}
static int spinand_init_flash(struct spinand_device *spinand)
--- /dev/null
+++ b/include/linux/mtd/casn.h
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: SkyLake.Huang <skylake.huang@mediatek.com>
+ */
+
+#ifndef __LINUX_MTD_CASN_H
+#define __LINUX_MTD_CASN_H
+
+#define CASN_CRC_BASE 0x4341
+#define CASN_SIGNATURE 0x4341534EU
+#define SPINAND_CASN_V1_CRC_OFS (254)
+#define CASN_PAGE_V1_COPIES (3)
+
+#define SDR_READ_1_1_1 BIT(0)
+#define SDR_READ_1_1_1_FAST BIT(1)
+#define SDR_READ_1_1_2 BIT(2)
+#define SDR_READ_1_2_2 BIT(3)
+#define SDR_READ_1_1_4 BIT(4)
+#define SDR_READ_1_4_4 BIT(5)
+#define SDR_READ_1_1_8 BIT(6)
+#define SDR_READ_1_8_8 BIT(7)
+
+#define SDR_WRITE_1_1_1 BIT(0)
+#define SDR_WRITE_1_1_4 BIT(1)
+
+#define SDR_UPDATE_1_1_1 BIT(0)
+#define SDR_UPDATE_1_1_4 BIT(1)
+
+struct op_slice {
+ u8 cmd_opcode;
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ u8 dummy_nbytes : 4;
+ u8 addr_nbytes : 4;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ u8 addr_nbytes : 4;
+ u8 dummy_nbytes : 4;
+#endif
+};
+
+struct SPINAND_FLAGS {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ u8 has_qe_bit : 1;
+ u8 has_cr_feat_bit : 1;
+ u8 conti_read_cap : 1;
+ u8 on_die_ecc : 1;
+ u8 legacy_ecc_status : 1;
+ u8 adv_ecc_status : 1;
+ u8 ecc_parity_readable : 1;
+ u8 ecc_alg : 1; /* ECC algorithm */
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ u8 ecc_alg : 1; /* ECC algorithm */
+ u8 ecc_parity_readable : 1;
+ u8 adv_ecc_status : 1;
+ u8 legacy_ecc_status : 1;
+ u8 on_die_ecc : 1;
+ u8 conti_read_cap : 1;
+ u8 has_cr_feat_bit : 1;
+ u8 has_qe_bit : 1;
+#endif
+};
+
+struct ADV_ECC_STATUS {
+ u8 cmd;
+ u8 addr;
+ u8 addr_nbytes;
+ u8 addr_buswidth;
+ u8 dummy_nbytes;
+ u8 dummy_buswidth;
+ u8 status_nbytes;
+ u16 status_mask;
+ u8 pre_op; /* pre-process operator */
+ u8 pre_mask; /* pre-process mask */
+} __packed;
+
+struct CASN_OOB {
+ u8 layout_type;
+
+ /* OOB free layout */
+ u8 free_start;
+ u8 free_length;
+ u8 bbm_length;
+
+ /* ECC parity layout */
+ u8 ecc_parity_start;
+ u8 ecc_parity_space;
+ u8 ecc_parity_real_length;
+};
+
+enum oob_overall {
+ OOB_DISCRETE = 0,
+ OOB_CONTINUOUS,
+};
+
+struct nand_casn {
+ /* CASN signature must be 4 chars: 'C','A','S','N' */
+ union {
+ u8 sig[4];
+ u32 signature;
+ };
+
+ u8 version;
+ char manufacturer[13];
+ char model[16];
+
+ __be32 bits_per_cell;
+ __be32 bytes_per_page;
+ __be32 spare_bytes_per_page;
+ __be32 pages_per_block;
+ __be32 blocks_per_lun;
+ __be32 max_bb_per_lun;
+ __be32 planes_per_lun;
+ __be32 luns_per_target;
+ __be32 total_target;
+
+ __be32 ecc_strength;
+ __be32 ecc_step_size;
+
+ u8 flags;
+ u8 reserved1;
+
+ __be16 sdr_read_cap;
+ struct op_slice sdr_read_1_1_1;
+ struct op_slice sdr_read_1_1_1_fast;
+ struct op_slice sdr_read_1_1_2;
+ struct op_slice sdr_read_1_2_2;
+ struct op_slice sdr_read_1_1_4;
+ struct op_slice sdr_read_1_4_4;
+ struct op_slice sdr_read_1_1_8;
+ struct op_slice sdr_read_1_8_8;
+
+ struct op_slice sdr_cont_read_1_1_1;
+ struct op_slice sdr_cont_read_1_1_1_fast;
+ struct op_slice sdr_cont_read_1_1_2;
+ struct op_slice sdr_cont_read_1_2_2;
+ struct op_slice sdr_cont_read_1_1_4;
+ struct op_slice sdr_cont_read_1_4_4;
+ struct op_slice sdr_cont_read_1_1_8;
+ struct op_slice sdr_cont_read_1_8_8;
+
+ __be16 ddr_read_cap;
+ struct op_slice ddr_read_1_1_1;
+ struct op_slice ddr_read_1_1_1_fast;
+ struct op_slice ddr_read_1_1_2;
+ struct op_slice ddr_read_1_2_2;
+ struct op_slice ddr_read_1_1_4;
+ struct op_slice ddr_read_1_4_4;
+ struct op_slice ddr_read_1_1_8;
+ struct op_slice ddr_read_1_8_8;
+
+ struct op_slice ddr_cont_read_1_1_1;
+ struct op_slice ddr_cont_read_1_1_1_fast;
+ struct op_slice ddr_cont_read_1_1_2;
+ struct op_slice ddr_cont_read_1_2_2;
+ struct op_slice ddr_cont_read_1_1_4;
+ struct op_slice ddr_cont_read_1_4_4;
+ struct op_slice ddr_cont_read_1_1_8;
+ struct op_slice ddr_cont_read_1_8_8;
+
+ u8 sdr_write_cap;
+ struct op_slice sdr_write_1_1_1;
+ struct op_slice sdr_write_1_1_4;
+ struct op_slice reserved2[6];
+ u8 ddr_write_cap;
+ struct op_slice reserved3[8];
+
+ u8 sdr_update_cap;
+ struct op_slice sdr_update_1_1_1;
+ struct op_slice sdr_update_1_1_4;
+ struct op_slice reserved4[6];
+ u8 ddr_update_cap;
+ struct op_slice reserved5[8];
+
+ struct CASN_OOB casn_oob;
+
+ /* Advanced ECC status CMD0 (higher bits) */
+ struct ADV_ECC_STATUS ecc_status_high;
+ /* Advanced ECC status CMD1 (lower bits) */
+ struct ADV_ECC_STATUS ecc_status_low;
+
+ u8 advecc_noerr_status;
+ u8 advecc_uncor_status;
+ u8 advecc_post_op;
+ u8 advecc_post_mask;
+
+ u8 reserved6[5];
+ __be16 crc;
+} __packed;
+
+#endif /* __LINUX_MTD_CASN_H */
--- a/include/linux/mtd/spinand.h
+++ b/include/linux/mtd/spinand.h
@@ -68,6 +68,59 @@
SPI_MEM_OP_NO_DUMMY, \
SPI_MEM_OP_NO_DATA)
+/* Macros for CASN */
+#define SPINAND_CASN_PAGE_READ_FROM_CACHE_OP(fast, naddr, addr, ndummy, buf, len) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(fast ? 0x0b : 0x03, 1), \
+ SPI_MEM_OP_ADDR(naddr, addr, 1), \
+ SPI_MEM_OP_DUMMY(ndummy, 1), \
+ SPI_MEM_OP_DATA_IN(len, buf, 1))
+
+#define SPINAND_CASN_PAGE_READ_FROM_CACHE_X2_OP(naddr, addr, ndummy, buf, len) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0x3b, 1), \
+ SPI_MEM_OP_ADDR(naddr, addr, 1), \
+ SPI_MEM_OP_DUMMY(ndummy, 1), \
+ SPI_MEM_OP_DATA_IN(len, buf, 2))
+
+#define SPINAND_CASN_PAGE_READ_FROM_CACHE_DUALIO_OP(naddr, addr, ndummy, buf, len) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0xbb, 1), \
+ SPI_MEM_OP_ADDR(naddr, addr, 2), \
+ SPI_MEM_OP_DUMMY(ndummy, 2), \
+ SPI_MEM_OP_DATA_IN(len, buf, 2))
+
+#define SPINAND_CASN_PAGE_READ_FROM_CACHE_X4_OP(naddr, addr, ndummy, buf, len) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0x6b, 1), \
+ SPI_MEM_OP_ADDR(naddr, addr, 1), \
+ SPI_MEM_OP_DUMMY(ndummy, 1), \
+ SPI_MEM_OP_DATA_IN(len, buf, 4))
+
+#define SPINAND_CASN_PAGE_READ_FROM_CACHE_QUADIO_OP(naddr, addr, ndummy, buf, len) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0xeb, 1), \
+ SPI_MEM_OP_ADDR(naddr, addr, 4), \
+ SPI_MEM_OP_DUMMY(ndummy, 4), \
+ SPI_MEM_OP_DATA_IN(len, buf, 4))
+
+#define SPINAND_CASN_PROG_LOAD(reset, naddr, addr, buf, len) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(reset ? 0x02 : 0x84, 1), \
+ SPI_MEM_OP_ADDR(naddr, addr, 1), \
+ SPI_MEM_OP_NO_DUMMY, \
+ SPI_MEM_OP_DATA_OUT(len, buf, 1))
+
+#define SPINAND_CASN_PROG_LOAD_X4(reset, naddr, addr, buf, len) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(reset ? 0x32 : 0x34, 1), \
+ SPI_MEM_OP_ADDR(naddr, addr, 1), \
+ SPI_MEM_OP_NO_DUMMY, \
+ SPI_MEM_OP_DATA_OUT(len, buf, 4))
+
+#define SPINAND_CASN_ADVECC_OP(casn_adv_ecc_status, buf) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(casn_adv_ecc_status.cmd, 1), \
+ SPI_MEM_OP_ADDR(casn_adv_ecc_status.addr_nbytes, \
+ casn_adv_ecc_status.addr, \
+ casn_adv_ecc_status.addr_buswidth), \
+ SPI_MEM_OP_DUMMY(casn_adv_ecc_status.dummy_nbytes, \
+ casn_adv_ecc_status.dummy_buswidth), \
+ SPI_MEM_OP_DATA_IN(casn_adv_ecc_status.status_nbytes, buf, 1))
+/* Macros for CASN end */
+
#define SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(addr, ndummy, buf, len, freq) \
SPI_MEM_OP(SPI_MEM_OP_CMD(0x03, 1), \
SPI_MEM_OP_ADDR(2, addr, 1), \
@@ -416,6 +469,11 @@ struct spinand_ecc_info {
#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)
+#define SPINAND_SUP_CR BIT(5)
+#define SPINAND_SUP_ON_DIE_ECC BIT(6)
+#define SPINAND_SUP_LEGACY_ECC_STATUS BIT(7)
+#define SPINAND_SUP_ADV_ECC_STATUS BIT(8)
+#define SPINAND_ECC_PARITY_READABLE BIT(9)
/**
* struct spinand_otp_layout - structure to describe the SPI NAND OTP area
@@ -598,6 +656,28 @@ struct spinand_dirmap {
};
/**
+ * struct CASN_ADVECC - CASN's advanced ECC description
+ * @cmd: Command to access SPI-NAND on-chip ECC status registers
+ * @mask: Mask to access SPI-NAND on-chip ECC status registers.
+ * ADV_ECC_STATUS->status_nbytes | CASN_ADVECC->mask
+ * 1 | 0 to 0xff
+ * 2 | 0 to 0xffff
+ * @shift: How many bits to shift to get on-chip ECC status
+ * @pre_op: This comes from CASN page's ADV_ECC_STATUS's pre_op.
+ * After reading on-chip ECC status, we need to do some math
+ * operations if this is specified.
+ * @pre_mask: This comes from CASN page's ADV_ECC_STATUS's pre_mask.
+ * This is used in companion with pre_op above.
+ */
+struct CASN_ADVECC {
+ u8 cmd;
+ u16 mask;
+ u8 shift;
+ u8 pre_op;
+ u8 pre_mask;
+};
+
+/**
* struct spinand_device - SPI NAND device instance
* @base: NAND device instance
* @slave: pointer to the SPI slave object
@@ -666,6 +746,23 @@ struct spinand_device {
u8 *oobbuf;
u8 *scratchbuf;
const struct spinand_manufacturer *manufacturer;
+
+ bool use_casn;
+ struct nand_casn *casn;
+ struct spi_mem_op *advecc_high_ops; /* ops to read higher part of advanced ECC status*/
+ struct spi_mem_op *advecc_low_ops;
+ struct CASN_OOB *casn_oob;
+ struct CASN_ADVECC *advecc_high;
+ struct CASN_ADVECC *advecc_low;
+
+ u8 advecc_low_bitcnt;
+ u8 advecc_noerr_status;
+ u8 advecc_uncor_status;
+ u8 advecc_post_op;
+ u8 advecc_post_mask;
+
+ size_t (*eccsr_math_op[4])(size_t, size_t);
+
void *priv;
u8 last_wait_status;