Re: [PATCH 1/2] mtd: spinand: add support for detection with param page

From: Frieder Schrempf
Date: Mon May 16 2022 - 03:38:59 EST


Hi Chuanhong,

Am 14.04.22 um 16:34 schrieb Chuanhong Guo:
> SPI-NAND detection using chip ID isn't always reliable.
> Here are two known cases:
> 1. ESMT uses JEDEC ID from other vendors. This may collapse with future
> chips.
> 2. Winbond W25N01KV uses the exact same JEDEC ID as W25N01GV while
> having completely different chip parameters.

I think they share the same first byte of the JEDEC ID, but the second
byte actually differs and would allow to differentiate between them, right?

I have this patchset [1] that I didn't manage to send upstream yet which
adds support for the W25N02KV. I added the second ID byte to detect them.

Still your approach using the ONFI data is more flexible of course and
probably a better way to handle this. I will see if I can find some time
to add support for the W25N02KV based on your patches.

Best regards
Frieder

[1]
https://github.com/fschrempf/linux/commits/feature/upstream/winbond-spinand-support

>
> Recent SPI-NANDs have a parameter page which is stored in the same
> format as raw NAND ONFI data. There are strings for chip manufacturer
> and chip model. Chip ECC requirement and memory organization are
> available too.
> This patch adds support for detecting SPI-NANDs with the parameter page
> after ID matching failed. In this detection, memory organization and
> ECC requirements are taken from the parameter page, and other SPI-NAND
> specific parameters are obtained by matching chip model string with
> a separated table.
>
> It's common for vendors to release a series of SPI-NANDs with the same
> SPI-NAND parameters in different voltages and/or capacities. The chip
> table defined in this patch supports multiple model strings in one
> entry, and multiple chip models can be covered using only one entry.
>
> Signed-off-by: Chuanhong Guo <gch981213@xxxxxxxxx>
> ---
>
> I'm not brave enough to touch raw nand code without testing, so
> sanitize_string, onfi_crc16 and nand_bit_wise_majority are
> duplicated from raw/nand_onfi.c into spi/onfi.c
>
> drivers/mtd/nand/spi/Makefile | 2 +-
> drivers/mtd/nand/spi/core.c | 23 +--
> drivers/mtd/nand/spi/onfi.c | 296 ++++++++++++++++++++++++++++++++++
> include/linux/mtd/spinand.h | 50 ++++++
> 4 files changed, 359 insertions(+), 12 deletions(-)
> create mode 100644 drivers/mtd/nand/spi/onfi.c
>
> diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile
> index 9662b9c1d5a9..a4e057cbdaf7 100644
> --- a/drivers/mtd/nand/spi/Makefile
> +++ b/drivers/mtd/nand/spi/Makefile
> @@ -1,3 +1,3 @@
> # SPDX-License-Identifier: GPL-2.0
> -spinand-objs := core.o gigadevice.o macronix.o micron.o paragon.o toshiba.o winbond.o
> +spinand-objs := core.o gigadevice.o macronix.o micron.o onfi.o paragon.o toshiba.o winbond.o
> obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
> diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
> index ff8336870bc0..3b51ca7232d0 100644
> --- a/drivers/mtd/nand/spi/core.c
> +++ b/drivers/mtd/nand/spi/core.c
> @@ -20,7 +20,7 @@
> #include <linux/spi/spi.h>
> #include <linux/spi/spi-mem.h>
>
> -static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)
> +int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)
> {
> struct spi_mem_op op = SPINAND_GET_FEATURE_OP(reg,
> spinand->scratchbuf);
> @@ -34,7 +34,7 @@ static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)
> return 0;
> }
>
> -static int spinand_write_reg_op(struct spinand_device *spinand, u8 reg, u8 val)
> +int spinand_write_reg_op(struct spinand_device *spinand, u8 reg, u8 val)
> {
> struct spi_mem_op op = SPINAND_SET_FEATURE_OP(reg,
> spinand->scratchbuf);
> @@ -339,7 +339,7 @@ static void spinand_ondie_ecc_save_status(struct nand_device *nand, u8 status)
> engine_conf->status = status;
> }
>
> -static int spinand_write_enable_op(struct spinand_device *spinand)
> +int spinand_write_enable_op(struct spinand_device *spinand)
> {
> struct spi_mem_op op = SPINAND_WR_EN_DIS_OP(true);
>
> @@ -496,10 +496,8 @@ static int spinand_erase_op(struct spinand_device *spinand,
> return spi_mem_exec_op(spinand->spimem, &op);
> }
>
> -static int spinand_wait(struct spinand_device *spinand,
> - unsigned long initial_delay_us,
> - unsigned long poll_delay_us,
> - u8 *s)
> +int spinand_wait(struct spinand_device *spinand, unsigned long initial_delay_us,
> + unsigned long poll_delay_us, u8 *s)
> {
> struct spi_mem_op op = SPINAND_GET_FEATURE_OP(REG_STATUS,
> spinand->scratchbuf);
> @@ -1006,7 +1004,7 @@ static void spinand_manufacturer_cleanup(struct spinand_device *spinand)
> return spinand->manufacturer->ops->cleanup(spinand);
> }
>
> -static const struct spi_mem_op *
> +const struct spi_mem_op *
> spinand_select_op_variant(struct spinand_device *spinand,
> const struct spinand_op_variants *variants)
> {
> @@ -1117,9 +1115,12 @@ static int spinand_detect(struct spinand_device *spinand)
>
> ret = spinand_id_detect(spinand);
> if (ret) {
> - dev_err(dev, "unknown raw ID %*phN\n", SPINAND_MAX_ID_LEN,
> - spinand->id.data);
> - return ret;
> + ret = spinand_onfi_detect(spinand);
> + if (ret) {
> + dev_err(dev, "unknown raw ID %*phN\n",
> + SPINAND_MAX_ID_LEN, spinand->id.data);
> + return ret;
> + }
> }
>
> if (nand->memorg.ntargets > 1 && !spinand->select_target) {
> diff --git a/drivers/mtd/nand/spi/onfi.c b/drivers/mtd/nand/spi/onfi.c
> new file mode 100644
> index 000000000000..5cc888e3d038
> --- /dev/null
> +++ b/drivers/mtd/nand/spi/onfi.c
> @@ -0,0 +1,296 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Detect and match SPI-NAND info using ONFI parameter page
> + *
> + * Author:
> + * Chuanhong Guo <gch981213@xxxxxxxxx>
> + *
> + * Part of this code comes from nand_onfi.c in raw nand support.
> + *
> + */
> +#include <linux/crc16.h>
> +#include <linux/mtd/onfi.h>
> +#include <linux/mtd/spinand.h>
> +
> +#define SPINAND_IDR_EN BIT(6)
> +#define SPINAND_PARAM_PAGE 1
> +#define ONFI_PARAM_PAGES 3
> +
> +/* Sanitize ONFI strings so we can safely print them */
> +static void sanitize_string(uint8_t *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);
> +}
> +
> +static u16 onfi_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;
> +}
> +
> +static int spinand_onfi_read(struct spinand_device *spinand, void *buf,
> + size_t len)
> +{
> + const struct spi_mem_op load_page =
> + SPINAND_PAGE_READ_OP(SPINAND_PARAM_PAGE);
> + struct spi_mem_op read_cache =
> + SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, buf, len);
> + int ret;
> + u8 cfg;
> +
> + ret = spinand_read_reg_op(spinand, REG_CFG, &cfg);
> + if (ret)
> + return ret;
> +
> + ret = spinand_write_enable_op(spinand);
> + if (ret)
> + return ret;
> +
> + ret = spinand_write_reg_op(spinand, REG_CFG, cfg | SPINAND_IDR_EN);
> + if (ret)
> + return ret;
> +
> + ret = spinand_read_reg_op(spinand, REG_CFG, &cfg);
> + if (ret)
> + return ret;
> +
> + if (!(cfg & SPINAND_IDR_EN))
> + return -EINVAL;
> +
> + ret = spi_mem_exec_op(spinand->spimem, &load_page);
> + if (ret)
> + goto cleanup;
> +
> + ret = spinand_wait(spinand, SPINAND_READ_INITIAL_DELAY_US,
> + SPINAND_READ_POLL_DELAY_US, NULL);
> + if (ret)
> + goto cleanup;
> +
> + while (len) {
> + ret = spi_mem_adjust_op_size(spinand->spimem, &read_cache);
> + if (ret)
> + goto cleanup;
> + ret = spi_mem_exec_op(spinand->spimem, &read_cache);
> + if (ret)
> + goto cleanup;
> + read_cache.addr.val += read_cache.data.nbytes;
> + read_cache.data.buf.in += read_cache.data.nbytes;
> + len -= read_cache.data.nbytes;
> + read_cache.data.nbytes = len;
> + }
> +cleanup:
> + spinand_write_reg_op(spinand, REG_CFG, cfg & ~SPINAND_IDR_EN);
> + return ret;
> +}
> +
> +static bool spinand_onfi_validate(const struct nand_onfi_params *p)
> +{
> + u16 crc;
> +
> + if (strncmp("ONFI", p->sig, 4))
> + return false;
> + crc = onfi_crc16(ONFI_CRC_BASE, (u8 *)p, sizeof(*p) - 2);
> + return crc == le16_to_cpu(p->crc);
> +}
> +
> +/*
> + * Recover data with bit-wise majority
> + */
> +static void nand_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 const struct spinand_manufacturer *spinand_onfi_manufacturers[] = {};
> +
> +static const struct spinand_onfi_info *
> +spinand_onfi_chip_match(struct nand_onfi_params *p,
> + const struct spinand_manufacturer *m)
> +{
> + size_t i, j;
> +
> + for (i = 0; i < m->nchips; i++)
> + for (j = 0; m->onfi_chips[i].models[j]; j++)
> + if (!strcasecmp(m->onfi_chips[i].models[j], p->model))
> + return &m->onfi_chips[i];
> + return NULL;
> +}
> +
> +static const struct spinand_onfi_info *
> +spinand_onfi_manufacturer_match(struct spinand_device *spinand,
> + struct nand_onfi_params *p)
> +{
> + const struct spinand_onfi_info *ret;
> + size_t i;
> +
> + for (i = 0; i < ARRAY_SIZE(spinand_onfi_manufacturers); i++) {
> + if (strcasecmp(spinand_onfi_manufacturers[i]->name,
> + p->manufacturer))
> + continue;
> + spinand->manufacturer = spinand_onfi_manufacturers[i];
> + ret = spinand_onfi_chip_match(p, spinand_onfi_manufacturers[i]);
> + if (ret)
> + return ret;
> + }
> + return NULL;
> +}
> +
> +/**
> + * spinand_onfi_detect() - match SPI-NAND using ONFI parameter page
> + * @spinand: spinand private structure
> + *
> + * Return:
> + * 0: Success
> + * -EINVAL: failed to read a valid parameter page
> + * -EOPNOTSUPP: chip isn't supported
> + */
> +int spinand_onfi_detect(struct spinand_device *spinand)
> +{
> + struct nand_onfi_params *p = NULL, *pbuf;
> + size_t params_len = sizeof(*pbuf) * ONFI_PARAM_PAGES;
> + struct nand_device *nand = spinand_to_nand(spinand);
> + struct nand_memory_organization *memorg = nanddev_get_memorg(nand);
> + const struct spi_mem_op *op;
> + const struct spinand_onfi_info *info;
> + int i, ret;
> +
> + pbuf = kzalloc(params_len, GFP_KERNEL);
> + if (!pbuf)
> + return -ENOMEM;
> +
> + ret = spinand_onfi_read(spinand, pbuf, params_len);
> + if (ret)
> + goto cleanup;
> +
> + for (i = 0; i < ONFI_PARAM_PAGES; i++) {
> + if (spinand_onfi_validate(&pbuf[i])) {
> + p = &pbuf[i];
> + break;
> + }
> + }
> +
> + if (!p) {
> + const void *srcbufs[ONFI_PARAM_PAGES];
> + unsigned int j;
> +
> + for (j = 0; j < ONFI_PARAM_PAGES; j++)
> + srcbufs[j] = pbuf + j;
> + nand_bit_wise_majority(srcbufs, ONFI_PARAM_PAGES, pbuf,
> + sizeof(*pbuf));
> + if (spinand_onfi_validate(pbuf))
> + p = pbuf;
> + }
> +
> + if (!p) {
> + ret = -EINVAL;
> + goto cleanup;
> + }
> +
> + sanitize_string(p->manufacturer, sizeof(p->manufacturer));
> + sanitize_string(p->model, sizeof(p->model));
> +
> + info = spinand_onfi_manufacturer_match(spinand, p);
> + if (!info) {
> + dev_err(&spinand->spimem->spi->dev, "unknown chip: %s %s",
> + p->manufacturer, p->model);
> + ret = -EOPNOTSUPP;
> + goto cleanup;
> + }
> +
> + /* setup memory organization using info from parameter page */
> + memorg->pagesize = le32_to_cpu(p->byte_per_page);
> +
> + /*
> + * pages_per_block and blocks_per_lun may not be a power-of-2 size
> + * (don't ask me who thought of this...). MTD assumes that these
> + * dimensions will be power-of-2, so just truncate the remaining area.
> + */
> + memorg->pages_per_eraseblock =
> + 1 << (fls(le32_to_cpu(p->pages_per_block)) - 1);
> + memorg->oobsize = le16_to_cpu(p->spare_bytes_per_page);
> + memorg->luns_per_target = p->lun_count;
> + memorg->planes_per_lun = 1 << p->interleaved_bits;
> + memorg->ntargets = 1;
> +
> + /* See erasesize comment */
> + memorg->eraseblocks_per_lun =
> + 1 << (fls(le32_to_cpu(p->blocks_per_lun)) - 1);
> + memorg->max_bad_eraseblocks_per_lun = le32_to_cpu(p->blocks_per_lun);
> + memorg->bits_per_cell = p->bits_per_cell;
> +
> + if (p->ecc_bits != 0xff) {
> + struct nand_ecc_props requirements = {
> + .strength = p->ecc_bits,
> + .step_size = p->data_bytes_per_ppage,
> + };
> +
> + nanddev_set_ecc_requirements(nand, &requirements);
> + } else {
> + ret = -EINVAL;
> + }
> +
> + /* setup spi-nand specific ops */
> + spinand->eccinfo = info->eccinfo;
> + spinand->flags = info->flags;
> + spinand->id.len = 0;
> + spinand->select_target = info->select_target;
> +
> + op = spinand_select_op_variant(spinand, info->op_variants.read_cache);
> + if (!op)
> + return -EOPNOTSUPP;
> +
> + spinand->op_templates.read_cache = op;
> +
> + op = spinand_select_op_variant(spinand, info->op_variants.write_cache);
> + if (!op)
> + return -EOPNOTSUPP;
> +
> + spinand->op_templates.write_cache = op;
> +
> + op = spinand_select_op_variant(spinand, info->op_variants.update_cache);
> + spinand->op_templates.update_cache = op;
> +
> +cleanup:
> + kfree(pbuf);
> + return ret;
> +}
> diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
> index 3aa28240a77f..dc218082d773 100644
> --- a/include/linux/mtd/spinand.h
> +++ b/include/linux/mtd/spinand.h
> @@ -255,6 +255,7 @@ struct spinand_manufacturer {
> u8 id;
> char *name;
> const struct spinand_info *chips;
> + const struct spinand_onfi_info *onfi_chips;
> const size_t nchips;
> const struct spinand_manufacturer_ops *ops;
> };
> @@ -386,6 +387,46 @@ struct spinand_info {
> __VA_ARGS__ \
> }
>
> +/**
> + * struct spinand_onfi_info - Structure used to describe SPI NAND with ONFI
> + * parameter page
> + * @models: Model name array. Null terminated.
> + * @flags: OR-ing of the SPINAND_XXX flags
> + * @eccinfo: on-die ECC info
> + * @op_variants: operations variants
> + * @op_variants.read_cache: variants of the read-cache operation
> + * @op_variants.write_cache: variants of the write-cache operation
> + * @op_variants.update_cache: variants of the update-cache operation
> + * @select_target: function used to select a target/die. Required only for
> + * multi-die chips
> + *
> + * Each SPI NAND manufacturer driver should have a spinand_onfi_info table
> + * describing all the chips supported by the driver.
> + */
> +struct spinand_onfi_info {
> + const char **const models;
> + u32 flags;
> + struct spinand_ecc_info eccinfo;
> + struct {
> + const struct spinand_op_variants *read_cache;
> + const struct spinand_op_variants *write_cache;
> + const struct spinand_op_variants *update_cache;
> + } op_variants;
> + int (*select_target)(struct spinand_device *spinand,
> + unsigned int target);
> +};
> +
> +#define SPINAND_ONFI_MODELS(...) \
> + .models = (const char *[]){ __VA_ARGS__, NULL }
> +
> +#define SPINAND_ONFI_INFO(__models, __op_variants, __flags, ...) \
> + { \
> + __models, \
> + .op_variants = __op_variants, \
> + .flags = __flags, \
> + __VA_ARGS__ \
> + }
> +
> struct spinand_dirmap {
> struct spi_mem_dirmap_desc *wdesc;
> struct spi_mem_dirmap_desc *rdesc;
> @@ -511,7 +552,16 @@ int spinand_match_and_init(struct spinand_device *spinand,
> unsigned int table_size,
> enum spinand_readid_method rdid_method);
>
> +int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val);
> +int spinand_write_reg_op(struct spinand_device *spinand, u8 reg, u8 val);
> +int spinand_write_enable_op(struct spinand_device *spinand);
> int spinand_upd_cfg(struct spinand_device *spinand, u8 mask, u8 val);
> +int spinand_onfi_detect(struct spinand_device *spinand);
> +const struct spi_mem_op *
> +spinand_select_op_variant(struct spinand_device *spinand,
> + const struct spinand_op_variants *variants);
> int spinand_select_target(struct spinand_device *spinand, unsigned int target);
> +int spinand_wait(struct spinand_device *spinand, unsigned long initial_delay_us,
> + unsigned long poll_delay_us, u8 *s);
>
> #endif /* __LINUX_MTD_SPINAND_H */