[PATCH 4/8] mtd: spinand: enabled parameter page support

From: shiva . linuxworks
Date: Mon Jul 22 2019 - 01:57:19 EST


From: Shivamurthy Shastri <sshivamurthy@xxxxxxxxxx>

Some of the SPI NAND devices has parameter page, which is similar to
ONFI table.

But, it may not be self sufficient to propagate all the required
parameters. Fixup function has been added in struct manufacturer to
accommodate this.

Signed-off-by: Shivamurthy Shastri <sshivamurthy@xxxxxxxxxx>
---
drivers/mtd/nand/spi/core.c | 134 ++++++++++++++++++++++++++++++++++++
include/linux/mtd/spinand.h | 3 +
2 files changed, 137 insertions(+)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 89f6beefb01c..7ae76dab9141 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -400,6 +400,131 @@ static int spinand_lock_block(struct spinand_device *spinand, u8 lock)
return spinand_write_reg_op(spinand, REG_BLOCK_LOCK, lock);
}

+/**
+ * spinand_read_param_page_op - Read parameter page operation
+ * @spinand: the spinand
+ * @page: page number where parameter page tables can be found
+ * @buf: buffer used to store the parameter page
+ * @len: length of the buffer
+ *
+ * Read parameter page
+ *
+ * Returns 0 on success, a negative error code otherwise.
+ */
+static int spinand_parameter_page_read(struct spinand_device *spinand,
+ u8 page, void *buf, unsigned int len)
+{
+ struct spi_mem_op pread_op = SPINAND_PAGE_READ_OP(page);
+ struct spi_mem_op pread_cache_op =
+ SPINAND_PAGE_READ_FROM_CACHE_OP(false,
+ 0,
+ 1,
+ buf,
+ len);
+ u8 feature;
+ u8 status;
+ int ret;
+
+ if (len && !buf)
+ return -EINVAL;
+
+ ret = spinand_read_reg_op(spinand, REG_CFG,
+ &feature);
+ if (ret)
+ return ret;
+
+ /* CFG_OTP_ENABLE is used to enable parameter page access */
+ feature |= CFG_OTP_ENABLE;
+
+ spinand_write_reg_op(spinand, REG_CFG, feature);
+
+ ret = spi_mem_exec_op(spinand->spimem, &pread_op);
+ if (ret)
+ return ret;
+
+ ret = spinand_wait(spinand, &status);
+ if (ret < 0)
+ return ret;
+
+ ret = spi_mem_exec_op(spinand->spimem, &pread_cache_op);
+ if (ret)
+ return ret;
+
+ ret = spinand_read_reg_op(spinand, REG_CFG,
+ &feature);
+ if (ret)
+ return ret;
+
+ feature &= ~CFG_OTP_ENABLE;
+
+ spinand_write_reg_op(spinand, REG_CFG, feature);
+
+ return 0;
+}
+
+static int spinand_param_page_detect(struct spinand_device *spinand)
+{
+ struct mtd_info *mtd = spinand_to_mtd(spinand);
+ struct nand_memory_organization *memorg;
+ struct nand_onfi_params *p;
+ struct nand_device *base = spinand_to_nand(spinand);
+ int i, ret;
+
+ memorg = nanddev_get_memorg(base);
+
+ /* Allocate buffer to hold parameter page */
+ p = kzalloc((sizeof(*p) * 3), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ ret = spinand_parameter_page_read(spinand, 0x01, p, sizeof(*p) * 3);
+ if (ret) {
+ ret = 0;
+ goto free_param_page;
+ }
+
+ for (i = 0; i < 3; i++) {
+ if (onfi_crc16(ONFI_CRC_BASE, (u8 *)&p[i], 254) ==
+ le16_to_cpu(p->crc)) {
+ if (i)
+ memcpy(p, &p[i], sizeof(*p));
+ break;
+ }
+ }
+
+ if (i == 3) {
+ const void *srcbufs[3] = {p, p + 1, p + 2};
+
+ pr_warn("Could not find a valid ONFI parameter page, trying bit-wise majority to recover it\n");
+ nand_bit_wise_majority(srcbufs, ARRAY_SIZE(srcbufs), p,
+ sizeof(*p));
+
+ if (onfi_crc16(ONFI_CRC_BASE, (u8 *)p, 254) !=
+ le16_to_cpu(p->crc)) {
+ pr_err("ONFI parameter recovery failed, aborting\n");
+ goto free_param_page;
+ }
+ }
+
+ parse_onfi_params(memorg, p);
+
+ mtd->writesize = memorg->pagesize;
+ mtd->erasesize = memorg->pages_per_eraseblock * memorg->pagesize;
+ mtd->oobsize = memorg->oobsize;
+
+ /* Manufacturers may interpret the parameter page differently */
+ if (spinand->manufacturer->ops->fixup_param_page)
+ spinand->manufacturer->ops->fixup_param_page(spinand, p);
+
+ /* Identification done, free the full parameter page and exit */
+ ret = 1;
+
+free_param_page:
+ kfree(p);
+
+ return ret;
+}
+
static int spinand_check_ecc_status(struct spinand_device *spinand, u8 status)
{
struct nand_device *nand = spinand_to_nand(spinand);
@@ -911,6 +1036,15 @@ static int spinand_detect(struct spinand_device *spinand)
return ret;
}

+ if (!spinand->base.memorg.pagesize) {
+ ret = spinand_param_page_detect(spinand);
+ if (ret <= 0) {
+ dev_err(dev, "no parameter page for %*phN\n",
+ SPINAND_MAX_ID_LEN, spinand->id.data);
+ return -ENODEV;
+ }
+ }
+
if (nand->memorg.ntargets > 1 && !spinand->select_target) {
dev_err(dev,
"SPI NANDs with more than one die must implement ->select_target()\n");
diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
index 4ea558bd3c46..fea820a20bc9 100644
--- a/include/linux/mtd/spinand.h
+++ b/include/linux/mtd/spinand.h
@@ -15,6 +15,7 @@
#include <linux/mtd/nand.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>
+#include <linux/mtd/onfi.h>

/**
* Standard SPI NAND flash operations
@@ -209,6 +210,8 @@ struct spinand_manufacturer_ops {
int (*detect)(struct spinand_device *spinand);
int (*init)(struct spinand_device *spinand);
void (*cleanup)(struct spinand_device *spinand);
+ void (*fixup_param_page)(struct spinand_device *spinand,
+ struct nand_onfi_params *p);
};

/**
--
2.17.1