[PATCH 2/2] mtd: spi-nor: spansion: Discover current address mode by CRC

tkuw584924 at gmail.com tkuw584924 at gmail.com
Fri Aug 12 01:06:33 PDT 2022


From: Takahiro Kuwano <Takahiro.Kuwano at infineon.com>

In some Infineon devices, the address mode (3- or 4-byte) is configurable
in both volatile and non-volatile registers. Determining current address
mode prior to SMPT parse is crucial because Read Any Reg op used in SMPT
takes 3- or 4-byte address depending on current address mode. The current
address mode is found in CFR2V[7] but CFR2V can be read by Read Any Reg
op (chicken-and-egg). This patch introduce a way to discover the current
address mode by using on-die CRC feature in Infineon SEMPER family.

The data integrity CRC (on-die CRC) calculation is conducted by issuing
a specific command followed by start and end addresses on which CRC is
calculated. The flash becomes busy state during calculation so we need to
poll status and wait for the completion. The calculated CRC value is
stored in Data Integrity Check CRC registers (DCRV) and can be read by
Read Any Reg op. We can try with 3- and 4-byte addresses to read DCRV then
compare it with expected CRC value calculated by offline. Both CRC should
match only when we perform Read Any Reg op with correct number of address
bytes.

The offline CRC calculation is done by CRC32C algorithm with 0x1edc6f41
polynomial. The data is read by READ_4B (13h) opcode.

BFPT DWORD16 is used to determine factory default of address mode,
assuming factory default is 3-byte in case power cycle exits 4-byte
address mode.

Signed-off-by: Takahiro Kuwano <Takahiro.Kuwano at infineon.com>
---
 drivers/mtd/spi-nor/sfdp.h     |   1 +
 drivers/mtd/spi-nor/spansion.c | 130 +++++++++++++++++++++++++++++++++
 2 files changed, 131 insertions(+)

diff --git a/drivers/mtd/spi-nor/sfdp.h b/drivers/mtd/spi-nor/sfdp.h
index bbf80d2990ab..d7e1620bd870 100644
--- a/drivers/mtd/spi-nor/sfdp.h
+++ b/drivers/mtd/spi-nor/sfdp.h
@@ -90,6 +90,7 @@ struct sfdp_bfpt {
 #define BFPT_DWORD15_QER_SR2_BIT1_NO_RD		(0x4UL << 20)
 #define BFPT_DWORD15_QER_SR2_BIT1		(0x5UL << 20) /* Spansion */
 
+#define BFPT_DWORD16_EX4B_PWRCYC		BIT(21)
 #define BFPT_DWORD16_SWRST_EN_RST		BIT(12)
 
 #define BFPT_DWORD18_CMD_EXT_MASK		GENMASK(30, 29)
diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c
index 676ffd6d12ec..8ed4b2d53403 100644
--- a/drivers/mtd/spi-nor/spansion.c
+++ b/drivers/mtd/spi-nor/spansion.c
@@ -23,7 +23,9 @@
 #define SPINOR_REG_CYPRESS_CFR5V		0x00800006
 #define SPINOR_REG_CYPRESS_CFR5V_OCT_DTR_EN	0x3
 #define SPINOR_REG_CYPRESS_CFR5V_OCT_DTR_DS	0
+#define SPINOR_REG_CYPRESS_DCRV			0x00800095
 #define SPINOR_OP_CYPRESS_RD_FAST		0xee
+#define SPINOR_OP_CYPRESS_DI_CRC		0x5b
 
 /* Cypress SPI NOR flash operations. */
 #define CYPRESS_NOR_WR_ANY_REG_OP(naddr, addr, ndata, buf)		\
@@ -213,11 +215,139 @@ static int cypress_nor_set_page_size(struct spi_nor *nor)
 	return 0;
 }
 
+#define CRC32C_POLY	0x1edc6f41
+static u32 cypress_nor_calc_crc32c(u32 len, u8 *buf)
+{
+	u32 crc = 0;
+	int i;
+
+	while (len--) {
+		crc ^= *buf++ << 24;
+		for (i = 0; i < 8; i++)
+			crc = (crc << 1) ^ ((crc & 0x80000000) ? CRC32C_POLY :
+					    0);
+	}
+
+	return crc;
+}
+
+static int cypress_nor_data_integrity_crc(struct spi_nor *nor, u32 addr,
+					  u32 len)
+{
+	struct spi_mem_op op =
+			SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CYPRESS_DI_CRC, 0),
+				   SPI_MEM_OP_ADDR(4, addr, 0),
+				   SPI_MEM_OP_NO_DUMMY,
+				   SPI_MEM_OP_DATA_OUT(4, nor->bouncebuf, 0));
+	u32 end_addr = addr + len - 1;
+	int ret;
+
+	nor->bouncebuf[0] = (end_addr >> 24) & 0xff;
+	nor->bouncebuf[1] = (end_addr >> 16) & 0xff;
+	nor->bouncebuf[2] = (end_addr >> 8) & 0xff;
+	nor->bouncebuf[3] = end_addr & 0xff;
+
+	spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
+
+	ret = spi_mem_exec_op(nor->spimem, &op);
+	if (ret)
+		return ret;
+
+	return spi_nor_wait_till_ready(nor);
+}
+
+/**
+ * cypress_nor_verify_crc() - Compare CRC and data integrity CRC registers.
+ * @nor:		pointer to 'struct spi_nor'.
+ * @addr_nbytes:	number of address bytes used in Read Any Reg op
+ * @crc:		CRC value to compare with registers
+ *
+ * Return: 1 if match, 0 if not match, -errno on errors.
+ */
+static int cypress_nor_verify_crc(struct spi_nor *nor, u8 addr_nbytes, u32 crc)
+{
+	struct spi_mem_op op =
+			CYPRESS_NOR_RD_ANY_REG_OP(addr_nbytes,
+						  SPINOR_REG_CYPRESS_DCRV,
+						  nor->bouncebuf);
+	int i, ret;
+
+	for (i = 0; i < 4; i++) {
+		ret = spi_nor_read_any_reg(nor, &op, nor->reg_proto);
+		if (ret)
+			return ret;
+
+		if ((crc & 0xff) != nor->bouncebuf[0])
+			return 0;
+
+		crc >>= 8;
+		op.addr.val++;
+	}
+
+	return 1;
+}
+
+static int cypress_nor_discover_addr_mode(struct spi_nor *nor)
+{
+	u32 addr = 0;
+	u32 len = 4;
+	u32 crc;
+	u8 addr_nbytes;
+	int ret;
+
+	/* Read flash memory array */
+	ret = spi_nor_alt_read(nor, addr, len, nor->bouncebuf,
+			       SPINOR_OP_READ_4B, 4, 0);
+	if (ret)
+		return ret;
+
+	/* Calculate CRC32C of read data */
+	crc = cypress_nor_calc_crc32c(len, nor->bouncebuf);
+
+	/* Perform on-die CRC calculation */
+	ret = cypress_nor_data_integrity_crc(nor, addr, len);
+	if (ret)
+		return ret;
+
+	/* Read and verify CRC registers with current addr_mode_nbytes */
+	ret = cypress_nor_verify_crc(nor, nor->params->addr_mode_nbytes, crc);
+	if (ret < 0)
+		return ret;
+	if (ret)
+		return 0;
+
+	/* Read and verify CRC registers with another number of address bytes */
+	addr_nbytes = nor->params->addr_mode_nbytes == 3 ? 4 : 3;
+	ret = cypress_nor_verify_crc(nor, addr_nbytes, crc);
+	if (ret < 0)
+		return ret;
+	if (ret)
+		nor->params->addr_mode_nbytes = addr_nbytes;
+	else
+		dev_warn(nor->dev, "Failed to discover current address mode. Assuming default %d-byte.\n",
+			 nor->params->addr_mode_nbytes);
+
+	return 0;
+}
+
 static int
 s25hx_t_post_bfpt_fixup(struct spi_nor *nor,
 			const struct sfdp_parameter_header *bfpt_header,
 			const struct sfdp_bfpt *bfpt)
 {
+	int ret;
+
+	/*
+	 * Discover flash's current address mode. Determine factory default
+	 * address mode in advance by checking if power cycle exits 4-byte
+	 * address mode, meaning factory default is 3-byte.
+	 */
+	nor->params->addr_mode_nbytes =
+		bfpt->dwords[BFPT_DWORD(16)] & BFPT_DWORD16_EX4B_PWRCYC ? 3 : 4;
+	ret = cypress_nor_discover_addr_mode(nor);
+	if (ret)
+		return ret;
+
 	/* Replace Quad Enable with volatile version */
 	nor->params->quad_enable = cypress_nor_quad_enable_volatile;
 
-- 
2.25.1




More information about the linux-mtd mailing list