Commit f1541773 authored by Chuanhong Guo's avatar Chuanhong Guo Committed by Miquel Raynal
Browse files

mtd: spinand: rework detect procedure for different READ_ID operation



Currently there are 3 different variants of read_id implementation:
1. opcode only. Found in GD5FxGQ4xF.
2. opcode + 1 addr byte. Found in GD5GxGQ4xA/E
3. opcode + 1 dummy byte. Found in other currently supported chips.

Original implementation was for variant 1 and let detect function
of chips with variant 2 and 3 to ignore the first byte. This isn't
robust:

1. For chips of variant 2, if SPI master doesn't keep MOSI low
during read, chip will get a random id offset, and the entire id
buffer will shift by that offset, causing detect failure.

2. For chips of variant 1, if it happens to get a devid that equals
to manufacture id of variant 2 or 3 chips, it'll get incorrectly
detected.

This patch reworks detect procedure to address problems above. New
logic do detection for all variants separatedly, in 1-2-3 order.
Since all current detect methods do exactly the same id matching
procedure, unify them into core.c and remove detect method from
manufacture_ops.

Tested on GD5F1GQ4UAYIG and W25N01GVZEIG.

Signed-off-by: default avatarChuanhong Guo <gch981213@gmail.com>
Signed-off-by: default avatarMiquel Raynal <miquel.raynal@bootlin.com>
Link: https://lore.kernel.org/linux-mtd/20200208074439.146296-1-gch981213@gmail.com
parent a91f8170
Loading
Loading
Loading
Loading
+63 −23
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
#include <linux/mtd/spinand.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>

@@ -370,10 +371,11 @@ out:
	return status & STATUS_BUSY ? -ETIMEDOUT : 0;
}

static int spinand_read_id_op(struct spinand_device *spinand, u8 *buf)
static int spinand_read_id_op(struct spinand_device *spinand, u8 naddr,
			      u8 ndummy, u8 *buf)
{
	struct spi_mem_op op = SPINAND_READID_OP(0, spinand->scratchbuf,
						 SPINAND_MAX_ID_LEN);
	struct spi_mem_op op = SPINAND_READID_OP(
		naddr, ndummy, spinand->scratchbuf, SPINAND_MAX_ID_LEN);
	int ret;

	ret = spi_mem_exec_op(spinand->spimem, &op);
@@ -762,22 +764,60 @@ static const struct spinand_manufacturer *spinand_manufacturers[] = {
	&winbond_spinand_manufacturer,
};

static int spinand_manufacturer_detect(struct spinand_device *spinand)
static int spinand_manufacturer_match(struct spinand_device *spinand,
				      enum spinand_readid_method rdid_method)
{
	u8 *id = spinand->id.data;
	unsigned int i;
	int ret;

	for (i = 0; i < ARRAY_SIZE(spinand_manufacturers); i++) {
		ret = spinand_manufacturers[i]->ops->detect(spinand);
		if (ret > 0) {
			spinand->manufacturer = spinand_manufacturers[i];
		const struct spinand_manufacturer *manufacturer =
			spinand_manufacturers[i];

		if (id[0] != manufacturer->id)
			continue;

		ret = spinand_match_and_init(spinand,
					     manufacturer->chips,
					     manufacturer->nchips,
					     rdid_method);
		if (ret < 0)
			continue;

		spinand->manufacturer = manufacturer;
		return 0;
		} else if (ret < 0) {
			return ret;
	}
	return -ENOTSUPP;
}

	return -ENOTSUPP;
static int spinand_id_detect(struct spinand_device *spinand)
{
	u8 *id = spinand->id.data;
	int ret;

	ret = spinand_read_id_op(spinand, 0, 0, id);
	if (ret)
		return ret;
	ret = spinand_manufacturer_match(spinand, SPINAND_READID_METHOD_OPCODE);
	if (!ret)
		return 0;

	ret = spinand_read_id_op(spinand, 1, 0, id);
	if (ret)
		return ret;
	ret = spinand_manufacturer_match(spinand,
					 SPINAND_READID_METHOD_OPCODE_ADDR);
	if (!ret)
		return 0;

	ret = spinand_read_id_op(spinand, 0, 1, id);
	if (ret)
		return ret;
	ret = spinand_manufacturer_match(spinand,
					 SPINAND_READID_METHOD_OPCODE_DUMMY);

	return ret;
}

static int spinand_manufacturer_init(struct spinand_device *spinand)
@@ -835,9 +875,9 @@ spinand_select_op_variant(struct spinand_device *spinand,
 * @spinand: SPI NAND object
 * @table: SPI NAND device description table
 * @table_size: size of the device description table
 * @rdid_method: read id method to match
 *
 * Should be used by SPI NAND manufacturer drivers when they want to find a
 * match between a device ID retrieved through the READ_ID command and an
 * Match between a device ID retrieved through the READ_ID command and an
 * entry in the SPI NAND description table. If a match is found, the spinand
 * object will be initialized with information provided by the matching
 * spinand_info entry.
@@ -846,8 +886,10 @@ spinand_select_op_variant(struct spinand_device *spinand,
 */
int spinand_match_and_init(struct spinand_device *spinand,
			   const struct spinand_info *table,
			   unsigned int table_size, u16 devid)
			   unsigned int table_size,
			   enum spinand_readid_method rdid_method)
{
	u8 *id = spinand->id.data;
	struct nand_device *nand = spinand_to_nand(spinand);
	unsigned int i;

@@ -855,13 +897,17 @@ int spinand_match_and_init(struct spinand_device *spinand,
		const struct spinand_info *info = &table[i];
		const struct spi_mem_op *op;

		if (devid != info->devid)
		if (rdid_method != info->devid.method)
			continue;

		if (memcmp(id + 1, info->devid.id, info->devid.len))
			continue;

		nand->memorg = table[i].memorg;
		nand->eccreq = table[i].eccreq;
		spinand->eccinfo = table[i].eccinfo;
		spinand->flags = table[i].flags;
		spinand->id.len = 1 + table[i].devid.len;
		spinand->select_target = table[i].select_target;

		op = spinand_select_op_variant(spinand,
@@ -898,13 +944,7 @@ static int spinand_detect(struct spinand_device *spinand)
	if (ret)
		return ret;

	ret = spinand_read_id_op(spinand, spinand->id.data);
	if (ret)
		return ret;

	spinand->id.len = SPINAND_MAX_ID_LEN;

	ret = spinand_manufacturer_detect(spinand);
	ret = spinand_id_detect(spinand);
	if (ret) {
		dev_err(dev, "unknown raw ID %*phN\n", SPINAND_MAX_ID_LEN,
			spinand->id.data);
+12 −33
Original line number Diff line number Diff line
@@ -195,7 +195,8 @@ static int gd5fxgq4ufxxg_ecc_get_status(struct spinand_device *spinand,
}

static const struct spinand_info gigadevice_spinand_table[] = {
	SPINAND_INFO("GD5F1GQ4xA", 0xF1,
	SPINAND_INFO("GD5F1GQ4xA",
		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xf1),
		     NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
		     NAND_ECCREQ(8, 512),
		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
@@ -204,7 +205,8 @@ static const struct spinand_info gigadevice_spinand_table[] = {
		     0,
		     SPINAND_ECCINFO(&gd5fxgq4xa_ooblayout,
				     gd5fxgq4xa_ecc_get_status)),
	SPINAND_INFO("GD5F2GQ4xA", 0xF2,
	SPINAND_INFO("GD5F2GQ4xA",
		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xf2),
		     NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1),
		     NAND_ECCREQ(8, 512),
		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
@@ -213,7 +215,8 @@ static const struct spinand_info gigadevice_spinand_table[] = {
		     0,
		     SPINAND_ECCINFO(&gd5fxgq4xa_ooblayout,
				     gd5fxgq4xa_ecc_get_status)),
	SPINAND_INFO("GD5F4GQ4xA", 0xF4,
	SPINAND_INFO("GD5F4GQ4xA",
		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xf4),
		     NAND_MEMORG(1, 2048, 64, 64, 4096, 80, 1, 1, 1),
		     NAND_ECCREQ(8, 512),
		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
@@ -222,7 +225,8 @@ static const struct spinand_info gigadevice_spinand_table[] = {
		     0,
		     SPINAND_ECCINFO(&gd5fxgq4xa_ooblayout,
				     gd5fxgq4xa_ecc_get_status)),
	SPINAND_INFO("GD5F1GQ4UExxG", 0xd1,
	SPINAND_INFO("GD5F1GQ4UExxG",
		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xd1),
		     NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
		     NAND_ECCREQ(8, 512),
		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
@@ -231,7 +235,8 @@ static const struct spinand_info gigadevice_spinand_table[] = {
		     0,
		     SPINAND_ECCINFO(&gd5fxgq4_variant2_ooblayout,
				     gd5fxgq4uexxg_ecc_get_status)),
	SPINAND_INFO("GD5F1GQ4UFxxG", 0xb148,
	SPINAND_INFO("GD5F1GQ4UFxxG",
		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE, 0xb1, 0x48),
		     NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
		     NAND_ECCREQ(8, 512),
		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants_f,
@@ -242,39 +247,13 @@ static const struct spinand_info gigadevice_spinand_table[] = {
				     gd5fxgq4ufxxg_ecc_get_status)),
};

static int gigadevice_spinand_detect(struct spinand_device *spinand)
{
	u8 *id = spinand->id.data;
	u16 did;
	int ret;

	/*
	 * Earlier GDF5-series devices (A,E) return [0][MID][DID]
	 * Later (F) devices return [MID][DID1][DID2]
	 */

	if (id[0] == SPINAND_MFR_GIGADEVICE)
		did = (id[1] << 8) + id[2];
	else if (id[0] == 0 && id[1] == SPINAND_MFR_GIGADEVICE)
		did = id[2];
	else
		return 0;

	ret = spinand_match_and_init(spinand, gigadevice_spinand_table,
				     ARRAY_SIZE(gigadevice_spinand_table),
				     did);
	if (ret)
		return ret;

	return 1;
}

static const struct spinand_manufacturer_ops gigadevice_spinand_manuf_ops = {
	.detect = gigadevice_spinand_detect,
};

const struct spinand_manufacturer gigadevice_spinand_manufacturer = {
	.id = SPINAND_MFR_GIGADEVICE,
	.name = "GigaDevice",
	.chips = gigadevice_spinand_table,
	.nchips = ARRAY_SIZE(gigadevice_spinand_table),
	.ops = &gigadevice_spinand_manuf_ops,
};
+6 −24
Original line number Diff line number Diff line
@@ -99,7 +99,8 @@ static int mx35lf1ge4ab_ecc_get_status(struct spinand_device *spinand,
}

static const struct spinand_info macronix_spinand_table[] = {
	SPINAND_INFO("MX35LF1GE4AB", 0x12,
	SPINAND_INFO("MX35LF1GE4AB",
		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x12),
		     NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
		     NAND_ECCREQ(4, 512),
		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
@@ -108,7 +109,8 @@ static const struct spinand_info macronix_spinand_table[] = {
		     SPINAND_HAS_QE_BIT,
		     SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
				     mx35lf1ge4ab_ecc_get_status)),
	SPINAND_INFO("MX35LF2GE4AB", 0x22,
	SPINAND_INFO("MX35LF2GE4AB",
		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x22),
		     NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 2, 1, 1),
		     NAND_ECCREQ(4, 512),
		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
@@ -118,33 +120,13 @@ static const struct spinand_info macronix_spinand_table[] = {
		     SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)),
};

static int macronix_spinand_detect(struct spinand_device *spinand)
{
	u8 *id = spinand->id.data;
	int ret;

	/*
	 * Macronix SPI NAND read ID needs a dummy byte, so the first byte in
	 * raw_id is garbage.
	 */
	if (id[1] != SPINAND_MFR_MACRONIX)
		return 0;

	ret = spinand_match_and_init(spinand, macronix_spinand_table,
				     ARRAY_SIZE(macronix_spinand_table),
				     id[2]);
	if (ret)
		return ret;

	return 1;
}

static const struct spinand_manufacturer_ops macronix_spinand_manuf_ops = {
	.detect = macronix_spinand_detect,
};

const struct spinand_manufacturer macronix_spinand_manufacturer = {
	.id = SPINAND_MFR_MACRONIX,
	.name = "Macronix",
	.chips = macronix_spinand_table,
	.nchips = ARRAY_SIZE(macronix_spinand_table),
	.ops = &macronix_spinand_manuf_ops,
};
+4 −22
Original line number Diff line number Diff line
@@ -91,7 +91,8 @@ static int mt29f2g01abagd_ecc_get_status(struct spinand_device *spinand,
}

static const struct spinand_info micron_spinand_table[] = {
	SPINAND_INFO("MT29F2G01ABAGD", 0x24,
	SPINAND_INFO("MT29F2G01ABAGD",
		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x24),
		     NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1),
		     NAND_ECCREQ(8, 512),
		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
@@ -102,32 +103,13 @@ static const struct spinand_info micron_spinand_table[] = {
				     mt29f2g01abagd_ecc_get_status)),
};

static int micron_spinand_detect(struct spinand_device *spinand)
{
	u8 *id = spinand->id.data;
	int ret;

	/*
	 * Micron SPI NAND read ID need a dummy byte,
	 * so the first byte in raw_id is dummy.
	 */
	if (id[1] != SPINAND_MFR_MICRON)
		return 0;

	ret = spinand_match_and_init(spinand, micron_spinand_table,
				     ARRAY_SIZE(micron_spinand_table), id[2]);
	if (ret)
		return ret;

	return 1;
}

static const struct spinand_manufacturer_ops micron_spinand_manuf_ops = {
	.detect = micron_spinand_detect,
};

const struct spinand_manufacturer micron_spinand_manufacturer = {
	.id = SPINAND_MFR_MICRON,
	.name = "Micron",
	.chips = micron_spinand_table,
	.nchips = ARRAY_SIZE(micron_spinand_table),
	.ops = &micron_spinand_manuf_ops,
};
+6 −22
Original line number Diff line number Diff line
@@ -97,7 +97,8 @@ static const struct mtd_ooblayout_ops pn26g0xa_ooblayout = {


static const struct spinand_info paragon_spinand_table[] = {
	SPINAND_INFO("PN26G01A", 0xe1,
	SPINAND_INFO("PN26G01A",
		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xe1),
		     NAND_MEMORG(1, 2048, 128, 64, 1024, 21, 1, 1, 1),
		     NAND_ECCREQ(8, 512),
		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
@@ -106,7 +107,8 @@ static const struct spinand_info paragon_spinand_table[] = {
		     0,
		     SPINAND_ECCINFO(&pn26g0xa_ooblayout,
				     pn26g0xa_ecc_get_status)),
	SPINAND_INFO("PN26G02A", 0xe2,
	SPINAND_INFO("PN26G02A",
		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xe2),
		     NAND_MEMORG(1, 2048, 128, 64, 2048, 41, 1, 1, 1),
		     NAND_ECCREQ(8, 512),
		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
@@ -117,31 +119,13 @@ static const struct spinand_info paragon_spinand_table[] = {
				     pn26g0xa_ecc_get_status)),
};

static int paragon_spinand_detect(struct spinand_device *spinand)
{
	u8 *id = spinand->id.data;
	int ret;

	/* Read ID returns [0][MID][DID] */

	if (id[1] != SPINAND_MFR_PARAGON)
		return 0;

	ret = spinand_match_and_init(spinand, paragon_spinand_table,
				     ARRAY_SIZE(paragon_spinand_table),
				     id[2]);
	if (ret)
		return ret;

	return 1;
}

static const struct spinand_manufacturer_ops paragon_spinand_manuf_ops = {
	.detect = paragon_spinand_detect,
};

const struct spinand_manufacturer paragon_spinand_manufacturer = {
	.id = SPINAND_MFR_PARAGON,
	.name = "Paragon",
	.chips = paragon_spinand_table,
	.nchips = ARRAY_SIZE(paragon_spinand_table),
	.ops = &paragon_spinand_manuf_ops,
};
Loading