2020-09-11 22:28:11

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 0/4] mtd: spi-nor: OTP support

This patchset implements the MTD OTP functions to allow access to the SPI
OTP data. Specific support is added for the Adesto, Macronix and Winbond
flash chips.

In the past there was already an attempt by Rahul Bedarkar to add this, but
there was no response. These patches are slightly based on his work.

https://lore.kernel.org/linux-mtd/[email protected]/

Michael Walle (4):
mtd: spi-nor: cleanup common code
mtd: spi-nor: add OTP support
mtd: spi-nor: implement OTP support for Macronix and similar flashes
mtd: spi-nor: implement OTP support for Winbond and similar flashes

drivers/mtd/chips/Kconfig | 2 +-
drivers/mtd/spi-nor/atmel.c | 13 +-
drivers/mtd/spi-nor/core.c | 769 ++++++++++++++++++++++++---------
drivers/mtd/spi-nor/core.h | 61 +++
drivers/mtd/spi-nor/macronix.c | 13 +-
drivers/mtd/spi-nor/winbond.c | 18 +-
include/linux/mtd/spi-nor.h | 16 +
7 files changed, 676 insertions(+), 216 deletions(-)

--
2.20.1


2020-09-11 22:28:33

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 4/4] mtd: spi-nor: implement OTP support for Winbond and similar flashes

Use the new OTP ops to implement OTP access on Winbond flashes. Most
Winbond flashes provides up to four different OTP areas ("Security
Registers"). Newer flashes uses the first OTP area for SFDP data. Thus,
for these flashes only the last three areas are handled and the first
one is left untouched.

This was tested on a Winbond W25Q32JW as well as on a W25Q32FW.

Signed-off-by: Michael Walle <[email protected]>
---
drivers/mtd/spi-nor/core.c | 161 ++++++++++++++++++++++++++++++++++
drivers/mtd/spi-nor/core.h | 4 +
drivers/mtd/spi-nor/winbond.c | 18 +++-
include/linux/mtd/spi-nor.h | 10 +++
4 files changed, 191 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 348db19958e9..c150e3b6ee44 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -2997,6 +2997,167 @@ int spi_nor_otp_is_locked_scur(struct spi_nor *nor, unsigned int region)
return *scur & SCUR_LDSO;
}

+/**
+ * spi_nor_otp_read_secr() - read OTP data
+ * @nor: pointer to 'struct spi_nor'
+ * @from: offset to read from
+ * @len: number of bytes to read
+ * @buf: pointer to dst buffer
+ *
+ * Read OTP data by using the SPINOR_OP_RSECR commands. This method is used on
+ * GigaDevice and Winbond flashes.
+ *
+ * Return: number of bytes read successfully, -errno otherwise
+ */
+int spi_nor_otp_read_secr(struct spi_nor *nor, loff_t addr, uint64_t len, u8 *buf)
+{
+ u8 addr_width, read_opcode, read_dummy;
+ struct spi_mem_dirmap_desc *rdesc;
+ enum spi_nor_protocol read_proto;
+ int ret;
+
+ read_opcode = nor->read_opcode;
+ addr_width = nor->addr_width;
+ read_dummy = nor->read_dummy;
+ read_proto = nor->read_proto;
+ rdesc = nor->dirmap.rdesc;
+
+ nor->read_opcode = SPINOR_OP_RSECR;
+ nor->addr_width = 3;
+ nor->read_dummy = 8;
+ nor->read_proto = SNOR_PROTO_1_1_1;
+ nor->dirmap.rdesc = NULL;
+
+ ret = spi_nor_read_data(nor, addr, len, buf);
+
+ nor->read_opcode = read_opcode;
+ nor->addr_width = addr_width;
+ nor->read_dummy = read_dummy;
+ nor->read_proto = read_proto;
+ nor->dirmap.rdesc = rdesc;
+
+ return ret;
+}
+
+/**
+ * spi_nor_otp_write_secr() - write OTP data
+ * @nor: pointer to 'struct spi_nor'
+ * @to: offset to write to
+ * @len: number of bytes to write
+ * @buf: pointer to src buffer
+ *
+ * Write OTP data by using the SPINOR_OP_PSECR commands. This method is used on
+ * GigaDevice and Winbond flashes.
+ *
+ * Return: number of bytes written successfully, -errno otherwise
+ */
+int spi_nor_otp_write_secr(struct spi_nor *nor, loff_t addr, uint64_t len, u8 *buf)
+{
+ enum spi_nor_protocol write_proto;
+ struct spi_mem_dirmap_desc *wdesc;
+ u8 addr_width, program_opcode;
+ int ret;
+
+ program_opcode = nor->program_opcode;
+ addr_width = nor->addr_width;
+ write_proto = nor->write_proto;
+ wdesc = nor->dirmap.wdesc;
+
+ nor->program_opcode = SPINOR_OP_PSECR;
+ nor->addr_width = 3;
+ nor->write_proto = SNOR_PROTO_1_1_1;
+ nor->dirmap.wdesc = NULL;
+
+ /*
+ * We only support a write to one single page. For now all winbond
+ * flashes only have one page per OTP region.
+ */
+ ret = spi_nor_write_enable(nor);
+ if (ret)
+ goto out;
+
+ ret = spi_nor_write_data(nor, addr, len, buf);
+ if (ret < 0)
+ goto out;
+
+ ret = spi_nor_wait_till_ready(nor);
+
+out:
+ nor->program_opcode = program_opcode;
+ nor->addr_width = addr_width;
+ nor->write_proto = write_proto;
+ nor->dirmap.wdesc = wdesc;
+
+ return ret;
+}
+
+static int spi_nor_otp_lock_bit_cr(unsigned int region)
+{
+ static const int lock_bits[] = { SR2_LB1, SR2_LB2, SR2_LB3 };
+
+ if (region >= ARRAY_SIZE(lock_bits))
+ return -EINVAL;
+
+ return lock_bits[region];
+}
+
+/**
+ * spi_nor_otp_lock_sr2() - lock the OTP region
+ * @nor: pointer to 'struct spi_nor'
+ * @region: OTP region
+ *
+ * Lock the OTP region by writing the status register-2. This method is used on
+ * GigaDevice and Winbond flashes.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+int spi_nor_otp_lock_sr2(struct spi_nor *nor, unsigned int region)
+{
+ int lock_bit;
+ u8 *sr2 = nor->bouncebuf;
+ int ret;
+
+ lock_bit = spi_nor_otp_lock_bit_cr(region);
+ if (lock_bit < 0)
+ return lock_bit;
+
+ ret = spi_nor_read_cr(nor, sr2);
+ if (ret)
+ return ret;
+
+ /* check if its already locked */
+ if (*sr2 & lock_bit)
+ return 0;
+
+ return spi_nor_write_16bit_cr_and_check(nor, *sr2 | lock_bit);
+}
+
+/**
+ * spi_nor_otp_is_locked_sr2() - get the OTP region lock status
+ * @nor: pointer to 'struct spi_nor'
+ * @region: OTP region
+ *
+ * Retrieve the OTP region lock bit by reading the status register-2. This
+ * method is used on GigaDevice and Winbond flashes.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+int spi_nor_otp_is_locked_sr2(struct spi_nor *nor, unsigned int region)
+{
+ int lock_bit;
+ u8 *sr2 = nor->bouncebuf;
+ int ret;
+
+ lock_bit = spi_nor_otp_lock_bit_cr(region);
+ if (lock_bit < 0)
+ return lock_bit;
+
+ ret = spi_nor_read_cr(nor, sr2);
+ if (ret)
+ return ret;
+
+ return (*sr2 & lock_bit);
+}

static int spi_nor_init(struct spi_nor *nor)
{
diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
index 7ec4add17b72..74bbb7aef1f1 100644
--- a/drivers/mtd/spi-nor/core.h
+++ b/drivers/mtd/spi-nor/core.h
@@ -472,6 +472,10 @@ int spi_nor_otp_read_otp_mode(struct spi_nor *nor, loff_t from, uint64_t len, u8
int spi_nor_otp_write_otp_mode(struct spi_nor *nor, loff_t to, uint64_t len, u8 *buf);
int spi_nor_otp_lock_scur(struct spi_nor *nor, unsigned int region);
int spi_nor_otp_is_locked_scur(struct spi_nor *nor, unsigned int region);
+int spi_nor_otp_read_secr(struct spi_nor *nor, loff_t addr, uint64_t len, u8 *buf);
+int spi_nor_otp_write_secr(struct spi_nor *nor, loff_t addr, uint64_t len, u8 *buf);
+int spi_nor_otp_lock_sr2(struct spi_nor *nor, unsigned int region);
+int spi_nor_otp_is_locked_sr2(struct spi_nor *nor, unsigned int region);

int spi_nor_hwcaps_read2cmd(u32 hwcaps);
u8 spi_nor_convert_3to4_read(u8 opcode);
diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 6dcde15fb1aa..3b14e96e993f 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -55,14 +55,19 @@ static const struct flash_info winbond_parts[] = {
{ "w25q32", INFO(0xef4016, 0, 64 * 1024, 64, SECT_4K) },
{ "w25q32dw", INFO(0xef6016, 0, 64 * 1024, 64,
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
- SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
+ SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
+ OTP_INFO(256, 3, 0x1000, 0x1000)
+ },
+
{ "w25q32jv", INFO(0xef7016, 0, 64 * 1024, 64,
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
},
{ "w25q32jwm", INFO(0xef8016, 0, 64 * 1024, 64,
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
- SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
+ SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
+ OTP_INFO(256, 3, 0x1000, 0x1000)
+ },
{ "w25x64", INFO(0xef3017, 0, 64 * 1024, 128, SECT_4K) },
{ "w25q64", INFO(0xef4017, 0, 64 * 1024, 128,
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
@@ -122,9 +127,18 @@ static int winbond_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
return spi_nor_write_disable(nor);
}

+static const struct spi_nor_otp_ops winbond_otp_ops = {
+ .read = spi_nor_otp_read_secr,
+ .write = spi_nor_otp_write_secr,
+ .lock = spi_nor_otp_lock_sr2,
+ .is_locked = spi_nor_otp_is_locked_sr2,
+};
+
static void winbond_default_init(struct spi_nor *nor)
{
nor->params->set_4byte_addr_mode = winbond_set_4byte_addr_mode;
+ if (nor->params->otp_info.n_otps)
+ nor->params->otp_ops = &winbond_otp_ops;
}

static const struct spi_nor_fixups winbond_fixups = {
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 04195d3e43b8..4750fb631c96 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -110,6 +110,11 @@
#define SPINOR_OP_RD_EVCR 0x65 /* Read EVCR register */
#define SPINOR_OP_WD_EVCR 0x61 /* Write EVCR register */

+/* Used for GigaDevices and Winbond flashes. */
+#define SPINOR_OP_ESECR 0x44 /* Erase Security registers */
+#define SPINOR_OP_PSECR 0x42 /* Program Security registers */
+#define SPINOR_OP_RSECR 0x48 /* Read Security registers */
+
/* Status Register bits. */
#define SR_WIP BIT(0) /* Write in progress */
#define SR_WEL BIT(1) /* Write enable latch */
@@ -141,8 +146,13 @@

/* Status Register 2 bits. */
#define SR2_QUAD_EN_BIT1 BIT(1)
+#define SR2_LB0 BIT(2) /* Security Register Lock Bit 0 */
+#define SR2_LB1 BIT(3) /* Security Register Lock Bit 1 */
+#define SR2_LB2 BIT(4) /* Security Register Lock Bit 2 */
+#define SR2_LB3 BIT(5) /* Security Register Lock Bit 3 */
#define SR2_QUAD_EN_BIT7 BIT(7)

+
/* Supported SPI protocols */
#define SNOR_PROTO_INST_MASK GENMASK(23, 16)
#define SNOR_PROTO_INST_SHIFT 16
--
2.20.1

2020-09-11 22:28:51

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 1/4] mtd: spi-nor: cleanup common code

Introduce a spi_nor_simple_cmd() function which executes a SPI command
with no additional argument bits. This function is then used to simplify
many other functions.

Signed-off-by: Michael Walle <[email protected]>
---
drivers/mtd/spi-nor/core.c | 295 +++++++++++--------------------------
1 file changed, 84 insertions(+), 211 deletions(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 3f76eb391719..b06b160a5c9c 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -210,6 +210,72 @@ ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len,
return nor->controller_ops->write(nor, to, len, buf);
}

+/**
+ * spi_nor_simple_cmd() - send command byte
+ * @nor: pointer to 'struct spi_nor'.
+ * @cmd: command code
+ *
+ * Return: 0 on success, -errno otherwise
+ */
+static int spi_nor_simple_cmd(struct spi_nor *nor, u8 cmd)
+{
+ struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(cmd, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_NO_DATA);
+
+ if (nor->spimem)
+ return spi_mem_exec_op(nor->spimem, &op);
+ else
+ return nor->controller_ops->write_reg(nor, cmd, NULL, 0);
+}
+
+/**
+ * spi_nor_simple_cmd_dout() - send command byte and data
+ * @nor: pointer to 'struct spi_nor'.
+ * @cmd: command code
+ * @data: pointer to the data to be send, must be DMA-capable
+ * @len: length of the data
+ *
+ * Return: 0 on success, -errno otherwise
+ */
+static int spi_nor_simple_cmd_dout(struct spi_nor *nor, u8 cmd, const u8 *data,
+ size_t len)
+{
+ struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(cmd, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_OUT(len, data, 1));
+
+ if (nor->spimem)
+ return spi_mem_exec_op(nor->spimem, &op);
+ else
+ return nor->controller_ops->write_reg(nor, cmd, data, len);
+}
+
+/**
+ * spi_nor_simple_cmd_din() - send command byte and receive data
+ * @nor: pointer to 'struct spi_nor'.
+ * @cmd: command code
+ * @data: pointer to the receive buffer, must be DMA-capable
+ * @len: length of the data to be received
+ *
+ * Return: 0 on success, -errno otherwise
+ */
+static int spi_nor_simple_cmd_din(struct spi_nor *nor, u8 cmd, u8 *data,
+ size_t len)
+{
+ struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(cmd, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_IN(len, data, 1));
+
+ if (nor->spimem)
+ return spi_mem_exec_op(nor->spimem, &op);
+ else
+ return nor->controller_ops->read_reg(nor, cmd, data, len);
+}
+
/**
* spi_nor_write_enable() - Set write enable latch with Write Enable command.
* @nor: pointer to 'struct spi_nor'.
@@ -220,19 +286,7 @@ int spi_nor_write_enable(struct spi_nor *nor)
{
int ret;

- if (nor->spimem) {
- struct spi_mem_op op =
- SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREN, 1),
- SPI_MEM_OP_NO_ADDR,
- SPI_MEM_OP_NO_DUMMY,
- SPI_MEM_OP_NO_DATA);
-
- ret = spi_mem_exec_op(nor->spimem, &op);
- } else {
- ret = nor->controller_ops->write_reg(nor, SPINOR_OP_WREN,
- NULL, 0);
- }
-
+ ret = spi_nor_simple_cmd(nor, SPINOR_OP_WREN);
if (ret)
dev_dbg(nor->dev, "error %d on Write Enable\n", ret);

@@ -249,19 +303,7 @@ int spi_nor_write_disable(struct spi_nor *nor)
{
int ret;

- if (nor->spimem) {
- struct spi_mem_op op =
- SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRDI, 1),
- SPI_MEM_OP_NO_ADDR,
- SPI_MEM_OP_NO_DUMMY,
- SPI_MEM_OP_NO_DATA);
-
- ret = spi_mem_exec_op(nor->spimem, &op);
- } else {
- ret = nor->controller_ops->write_reg(nor, SPINOR_OP_WRDI,
- NULL, 0);
- }
-
+ ret = spi_nor_simple_cmd(nor, SPINOR_OP_WRDI);
if (ret)
dev_dbg(nor->dev, "error %d on Write Disable\n", ret);

@@ -280,19 +322,7 @@ static int spi_nor_read_sr(struct spi_nor *nor, u8 *sr)
{
int ret;

- if (nor->spimem) {
- struct spi_mem_op op =
- SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 1),
- SPI_MEM_OP_NO_ADDR,
- SPI_MEM_OP_NO_DUMMY,
- SPI_MEM_OP_DATA_IN(1, sr, 1));
-
- ret = spi_mem_exec_op(nor->spimem, &op);
- } else {
- ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDSR,
- sr, 1);
- }
-
+ ret = spi_nor_simple_cmd_din(nor, SPINOR_OP_RDSR, sr, 1);
if (ret)
dev_dbg(nor->dev, "error %d reading SR\n", ret);

@@ -311,19 +341,7 @@ static int spi_nor_read_fsr(struct spi_nor *nor, u8 *fsr)
{
int ret;

- if (nor->spimem) {
- struct spi_mem_op op =
- SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDFSR, 1),
- SPI_MEM_OP_NO_ADDR,
- SPI_MEM_OP_NO_DUMMY,
- SPI_MEM_OP_DATA_IN(1, fsr, 1));
-
- ret = spi_mem_exec_op(nor->spimem, &op);
- } else {
- ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDFSR,
- fsr, 1);
- }
-
+ ret = spi_nor_simple_cmd_din(nor, SPINOR_OP_RDFSR, fsr, 1);
if (ret)
dev_dbg(nor->dev, "error %d reading FSR\n", ret);

@@ -343,18 +361,7 @@ static int spi_nor_read_cr(struct spi_nor *nor, u8 *cr)
{
int ret;

- if (nor->spimem) {
- struct spi_mem_op op =
- SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDCR, 1),
- SPI_MEM_OP_NO_ADDR,
- SPI_MEM_OP_NO_DUMMY,
- SPI_MEM_OP_DATA_IN(1, cr, 1));
-
- ret = spi_mem_exec_op(nor->spimem, &op);
- } else {
- ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDCR, cr, 1);
- }
-
+ ret = spi_nor_simple_cmd_din(nor, SPINOR_OP_RDCR, cr, 1);
if (ret)
dev_dbg(nor->dev, "error %d reading CR\n", ret);

@@ -371,26 +378,10 @@ static int spi_nor_read_cr(struct spi_nor *nor, u8 *cr)
*/
int spi_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
{
+ u8 cmd = enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B;
int ret;

- if (nor->spimem) {
- struct spi_mem_op op =
- SPI_MEM_OP(SPI_MEM_OP_CMD(enable ?
- SPINOR_OP_EN4B :
- SPINOR_OP_EX4B,
- 1),
- SPI_MEM_OP_NO_ADDR,
- SPI_MEM_OP_NO_DUMMY,
- SPI_MEM_OP_NO_DATA);
-
- ret = spi_mem_exec_op(nor->spimem, &op);
- } else {
- ret = nor->controller_ops->write_reg(nor,
- enable ? SPINOR_OP_EN4B :
- SPINOR_OP_EX4B,
- NULL, 0);
- }
-
+ ret = spi_nor_simple_cmd(nor, cmd);
if (ret)
dev_dbg(nor->dev, "error %d setting 4-byte mode\n", ret);

@@ -412,19 +403,7 @@ static int spansion_set_4byte_addr_mode(struct spi_nor *nor, bool enable)

nor->bouncebuf[0] = enable << 7;

- if (nor->spimem) {
- struct spi_mem_op op =
- SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_BRWR, 1),
- SPI_MEM_OP_NO_ADDR,
- SPI_MEM_OP_NO_DUMMY,
- SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1));
-
- ret = spi_mem_exec_op(nor->spimem, &op);
- } else {
- ret = nor->controller_ops->write_reg(nor, SPINOR_OP_BRWR,
- nor->bouncebuf, 1);
- }
-
+ ret = spi_nor_simple_cmd_dout(nor, SPINOR_OP_BRWR, nor->bouncebuf, 1);
if (ret)
dev_dbg(nor->dev, "error %d setting 4-byte mode\n", ret);

@@ -444,19 +423,7 @@ int spi_nor_write_ear(struct spi_nor *nor, u8 ear)

nor->bouncebuf[0] = ear;

- if (nor->spimem) {
- struct spi_mem_op op =
- SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREAR, 1),
- SPI_MEM_OP_NO_ADDR,
- SPI_MEM_OP_NO_DUMMY,
- SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1));
-
- ret = spi_mem_exec_op(nor->spimem, &op);
- } else {
- ret = nor->controller_ops->write_reg(nor, SPINOR_OP_WREAR,
- nor->bouncebuf, 1);
- }
-
+ ret = spi_nor_simple_cmd_dout(nor, SPINOR_OP_WREAR, nor->bouncebuf, 1);
if (ret)
dev_dbg(nor->dev, "error %d writing EAR\n", ret);

@@ -475,19 +442,7 @@ int spi_nor_xread_sr(struct spi_nor *nor, u8 *sr)
{
int ret;

- if (nor->spimem) {
- struct spi_mem_op op =
- SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_XRDSR, 1),
- SPI_MEM_OP_NO_ADDR,
- SPI_MEM_OP_NO_DUMMY,
- SPI_MEM_OP_DATA_IN(1, sr, 1));
-
- ret = spi_mem_exec_op(nor->spimem, &op);
- } else {
- ret = nor->controller_ops->read_reg(nor, SPINOR_OP_XRDSR,
- sr, 1);
- }
-
+ ret = spi_nor_simple_cmd_din(nor, SPINOR_OP_XRDSR, sr, 1);
if (ret)
dev_dbg(nor->dev, "error %d reading XRDSR\n", ret);

@@ -520,19 +475,7 @@ static void spi_nor_clear_sr(struct spi_nor *nor)
{
int ret;

- if (nor->spimem) {
- struct spi_mem_op op =
- SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLSR, 1),
- SPI_MEM_OP_NO_ADDR,
- SPI_MEM_OP_NO_DUMMY,
- SPI_MEM_OP_NO_DATA);
-
- ret = spi_mem_exec_op(nor->spimem, &op);
- } else {
- ret = nor->controller_ops->write_reg(nor, SPINOR_OP_CLSR,
- NULL, 0);
- }
-
+ ret = spi_nor_simple_cmd(nor, SPINOR_OP_CLSR);
if (ret)
dev_dbg(nor->dev, "error %d clearing SR\n", ret);
}
@@ -584,19 +527,7 @@ static void spi_nor_clear_fsr(struct spi_nor *nor)
{
int ret;

- if (nor->spimem) {
- struct spi_mem_op op =
- SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLFSR, 1),
- SPI_MEM_OP_NO_ADDR,
- SPI_MEM_OP_NO_DUMMY,
- SPI_MEM_OP_NO_DATA);
-
- ret = spi_mem_exec_op(nor->spimem, &op);
- } else {
- ret = nor->controller_ops->write_reg(nor, SPINOR_OP_CLFSR,
- NULL, 0);
- }
-
+ ret = spi_nor_simple_cmd(nor, SPINOR_OP_CLFSR);
if (ret)
dev_dbg(nor->dev, "error %d clearing FSR\n", ret);
}
@@ -728,19 +659,7 @@ static int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len)
if (ret)
return ret;

- if (nor->spimem) {
- struct spi_mem_op op =
- SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 1),
- SPI_MEM_OP_NO_ADDR,
- SPI_MEM_OP_NO_DUMMY,
- SPI_MEM_OP_DATA_OUT(len, sr, 1));
-
- ret = spi_mem_exec_op(nor->spimem, &op);
- } else {
- ret = nor->controller_ops->write_reg(nor, SPINOR_OP_WRSR,
- sr, len);
- }
-
+ ret = spi_nor_simple_cmd_dout(nor, SPINOR_OP_WRSR, sr, len);
if (ret) {
dev_dbg(nor->dev, "error %d writing SR\n", ret);
return ret;
@@ -930,19 +849,7 @@ static int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2)
if (ret)
return ret;

- if (nor->spimem) {
- struct spi_mem_op op =
- SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR2, 1),
- SPI_MEM_OP_NO_ADDR,
- SPI_MEM_OP_NO_DUMMY,
- SPI_MEM_OP_DATA_OUT(1, sr2, 1));
-
- ret = spi_mem_exec_op(nor->spimem, &op);
- } else {
- ret = nor->controller_ops->write_reg(nor, SPINOR_OP_WRSR2,
- sr2, 1);
- }
-
+ ret = spi_nor_simple_cmd_dout(nor, SPINOR_OP_WRSR2, sr2, 1);
if (ret) {
dev_dbg(nor->dev, "error %d writing SR2\n", ret);
return ret;
@@ -964,19 +871,7 @@ static int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2)
{
int ret;

- if (nor->spimem) {
- struct spi_mem_op op =
- SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR2, 1),
- SPI_MEM_OP_NO_ADDR,
- SPI_MEM_OP_NO_DUMMY,
- SPI_MEM_OP_DATA_IN(1, sr2, 1));
-
- ret = spi_mem_exec_op(nor->spimem, &op);
- } else {
- ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDSR2,
- sr2, 1);
- }
-
+ ret = spi_nor_simple_cmd_din(nor, SPINOR_OP_RDSR2, sr2, 1);
if (ret)
dev_dbg(nor->dev, "error %d reading SR2\n", ret);

@@ -995,19 +890,7 @@ static int spi_nor_erase_chip(struct spi_nor *nor)

dev_dbg(nor->dev, " %lldKiB\n", (long long)(nor->mtd.size >> 10));

- if (nor->spimem) {
- struct spi_mem_op op =
- SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CHIP_ERASE, 1),
- SPI_MEM_OP_NO_ADDR,
- SPI_MEM_OP_NO_DUMMY,
- SPI_MEM_OP_NO_DATA);
-
- ret = spi_mem_exec_op(nor->spimem, &op);
- } else {
- ret = nor->controller_ops->write_reg(nor, SPINOR_OP_CHIP_ERASE,
- NULL, 0);
- }
-
+ ret = spi_nor_simple_cmd(nor, SPINOR_OP_CHIP_ERASE);
if (ret)
dev_dbg(nor->dev, "error %d erasing chip\n", ret);

@@ -2064,18 +1947,8 @@ static const struct flash_info *spi_nor_read_id(struct spi_nor *nor)
unsigned int i;
int ret;

- if (nor->spimem) {
- struct spi_mem_op op =
- SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 1),
- SPI_MEM_OP_NO_ADDR,
- SPI_MEM_OP_NO_DUMMY,
- SPI_MEM_OP_DATA_IN(SPI_NOR_MAX_ID_LEN, id, 1));
-
- ret = spi_mem_exec_op(nor->spimem, &op);
- } else {
- ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDID, id,
- SPI_NOR_MAX_ID_LEN);
- }
+ ret = spi_nor_simple_cmd_din(nor, SPINOR_OP_RDID, id,
+ SPI_NOR_MAX_ID_LEN);
if (ret) {
dev_dbg(nor->dev, "error %d reading JEDEC ID\n", ret);
return ERR_PTR(ret);
--
2.20.1

2020-09-11 22:29:23

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 2/4] mtd: spi-nor: add OTP support

Implement the MTD callbacks for the OTP methods for the SPI NOR
subsystem.

Usually, the OTP area of a SPI flash can be accessed like the normal
memory, eg by offset addressing; except that you either have to use
special read/write commands (Winbond) or you have to enter (and exit) a
specific OTP mode (Macronix, Micron). Sometimes there are individual
regions, which might have individual offsets. Therefore, it is possible
to specify the starting address of the first regions as well as the
distance between two regions (Winbond).

Additionally, the regions might be locked down. Once locked, no further
write access is possible.

Cc: Rahul Bedarkar <[email protected]>
Signed-off-by: Michael Walle <[email protected]>
---
drivers/mtd/chips/Kconfig | 2 +-
drivers/mtd/spi-nor/core.c | 143 +++++++++++++++++++++++++++++++++++++
drivers/mtd/spi-nor/core.h | 48 +++++++++++++
3 files changed, 192 insertions(+), 1 deletion(-)

diff --git a/drivers/mtd/chips/Kconfig b/drivers/mtd/chips/Kconfig
index aef14990e5f7..3b7ba9448118 100644
--- a/drivers/mtd/chips/Kconfig
+++ b/drivers/mtd/chips/Kconfig
@@ -152,7 +152,7 @@ config MTD_CFI_I8

config MTD_OTP
bool "Protection Registers aka one-time programmable (OTP) bits"
- depends on MTD_CFI_ADV_OPTIONS
+ depends on MTD_CFI_ADV_OPTIONS || MTD_SPI_NOR
default n
help
This enables support for reading, writing and locking so called
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index b06b160a5c9c..4244f98e4948 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -2689,6 +2689,12 @@ static void spi_nor_info_init_params(struct spi_nor *nor)
spi_nor_set_erase_type(&map->erase_type[i], info->sector_size,
SPINOR_OP_SE);
spi_nor_init_uniform_erase_map(map, erase_mask, params->size);
+
+ /* OTP parameters */
+ nor->params->otp_info.otp_size = info->otp_size;
+ nor->params->otp_info.n_otps = info->n_otps;
+ nor->params->otp_info.otp_start_addr = info->otp_start_addr;
+ nor->params->otp_info.otp_addr_offset = info->otp_addr_offset;
}

/**
@@ -2972,6 +2978,127 @@ static const struct flash_info *spi_nor_get_flash_info(struct spi_nor *nor,
return info;
}

+static loff_t spi_nor_otp_region_start(struct spi_nor *nor, int region)
+{
+ struct spi_nor_otp_info *info = &nor->params->otp_info;
+
+ return info->otp_start_addr + region * info->otp_addr_offset;
+}
+
+static loff_t spi_nor_otp_region_end(struct spi_nor *nor, int region)
+{
+ struct spi_nor_otp_info *info = &nor->params->otp_info;
+
+ return spi_nor_otp_region_start(nor, region) + info->otp_size - 1;
+}
+
+static int spi_nor_mtd_otp_info(struct mtd_info *mtd, size_t len,
+ size_t *retlen, struct otp_info *buf)
+{
+ struct spi_nor *nor = mtd_to_spi_nor(mtd);
+ int locked;
+ int i;
+
+ for (i = 0; i < nor->params->otp_info.n_otps; i++) {
+ buf[i].start = spi_nor_otp_region_start(nor, i);
+ buf[i].length = nor->params->otp_info.otp_size;
+
+ locked = nor->params->otp_ops->is_locked(nor, i);
+ if (locked < 0)
+ return locked;
+
+ buf[i].locked = !!locked;
+ }
+
+ *retlen = nor->params->otp_info.n_otps * sizeof(*buf);
+
+ return 0;
+}
+
+static int spi_nor_otp_addr_to_region(struct spi_nor *nor, loff_t addr)
+{
+ int i;
+
+ for (i = 0; i < nor->params->otp_info.n_otps; i++)
+ if (addr >= spi_nor_otp_region_start(nor, i) &&
+ addr <= spi_nor_otp_region_end(nor, i))
+ return i;
+
+ return -EINVAL;
+}
+
+static int spi_nor_mtd_otp_read_write(struct mtd_info *mtd, loff_t ofs,
+ size_t len, size_t *retlen, u_char *buf,
+ bool is_write)
+{
+ struct spi_nor *nor = mtd_to_spi_nor(mtd);
+ int region;
+ int ret;
+
+ *retlen = 0;
+
+ region = spi_nor_otp_addr_to_region(nor, ofs);
+ if (region < 0)
+ return 0;
+
+ if (ofs < spi_nor_otp_region_start(nor, region))
+ return 0;
+
+ if ((ofs + len - 1) > spi_nor_otp_region_end(nor, region))
+ return 0;
+
+ ret = spi_nor_lock_and_prep(nor);
+
+ if (is_write)
+ ret = nor->params->otp_ops->write(nor, ofs, len, buf);
+ else
+ ret = nor->params->otp_ops->read(nor, ofs, len, buf);
+
+ spi_nor_unlock_and_unprep(nor);
+
+ if (ret < 0)
+ return ret;
+
+ *retlen = len;
+ return 0;
+}
+
+static int spi_nor_mtd_otp_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ return spi_nor_mtd_otp_read_write(mtd, from, len, retlen, buf, false);
+}
+
+static int spi_nor_mtd_otp_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ return spi_nor_mtd_otp_read_write(mtd, to, len, retlen, buf, true);
+}
+
+static int spi_nor_mtd_otp_lock(struct mtd_info *mtd, loff_t from, size_t len)
+{
+ struct spi_nor *nor = mtd_to_spi_nor(mtd);
+ int region;
+ int ret;
+
+ region = spi_nor_otp_addr_to_region(nor, from);
+ if (region < 0)
+ return -EINVAL;
+
+ if (len != nor->params->otp_info.otp_size)
+ return -EINVAL;
+
+ ret = spi_nor_lock_and_prep(nor);
+ if (ret)
+ return ret;
+
+ ret = nor->params->otp_ops->lock(nor, region);
+
+ spi_nor_unlock_and_unprep(nor);
+
+ return ret;
+}
+
int spi_nor_scan(struct spi_nor *nor, const char *name,
const struct spi_nor_hwcaps *hwcaps)
{
@@ -3050,6 +3177,22 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
mtd->_is_locked = spi_nor_is_locked;
}

+ if (nor->params->otp_ops) {
+ /*
+ * We only support user_prot callbacks (yet).
+ *
+ * Some SPI flashes like Macronix ones support factory locked
+ * OTP areas, usually they are preprogrammed with an
+ * "electrical serial number".
+ * Most of the time the OTP area is unprogrammed and left up to
+ * the user. This is what we support at the moment.
+ */
+ mtd->_get_user_prot_info = spi_nor_mtd_otp_info;
+ mtd->_read_user_prot_reg = spi_nor_mtd_otp_read;
+ mtd->_write_user_prot_reg = spi_nor_mtd_otp_write;
+ mtd->_lock_user_prot_reg = spi_nor_mtd_otp_lock;
+ }
+
if (info->flags & USE_FSR)
nor->flags |= SNOR_F_USE_FSR;
if (info->flags & SPI_NOR_HAS_TB) {
diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
index fb3ef86e350c..516c5973bf88 100644
--- a/drivers/mtd/spi-nor/core.h
+++ b/drivers/mtd/spi-nor/core.h
@@ -170,6 +170,21 @@ struct spi_nor_erase_map {
u8 uniform_erase_type;
};

+/**
+ * struct spi_nor_otp_info - Structure to describe the SPI NOR OTP region
+ * @otp_size: size of one OTP region in bytes.
+ * @n_otps: number of individual OTP regions.
+ * @otp_start_addr: start address of the OTP area.
+ * @otp_addr_offset: offset between consecutive OTP regions if there are
+ * more than one.
+ */
+struct spi_nor_otp_info {
+ u32 otp_size;
+ int n_otps;
+ u32 otp_start_addr;
+ u32 otp_addr_offset;
+};
+
/**
* struct spi_nor_locking_ops - SPI NOR locking methods
* @lock: lock a region of the SPI NOR.
@@ -182,6 +197,20 @@ struct spi_nor_locking_ops {
int (*is_locked)(struct spi_nor *nor, loff_t ofs, uint64_t len);
};

+/**
+ * struct spi_nor_otp_ops - SPI NOR OTP methods
+ * @read: read from the SPI NOR OTP area.
+ * @write: write to the SPI NOR OTP area.
+ * @lock: lock an OTP region.
+ * @is_locked: check if an OTP region of the SPI NOR is locked.
+ */
+struct spi_nor_otp_ops {
+ int (*read)(struct spi_nor *nor, loff_t ofs, uint64_t len, u8 *buf);
+ int (*write)(struct spi_nor *nor, loff_t ofs, uint64_t len, u8 *buf);
+ int (*lock)(struct spi_nor *nor, unsigned int region);
+ int (*is_locked)(struct spi_nor *nor, unsigned int region);
+};
+
/**
* struct spi_nor_flash_parameter - SPI NOR flash parameters and settings.
* Includes legacy flash parameters and settings that can be overwritten
@@ -198,6 +227,7 @@ struct spi_nor_locking_ops {
* higher index in the array, the higher priority.
* @erase_map: the erase map parsed from the SFDP Sector Map Parameter
* Table.
+ * @otp_info: describes the OTP regions.
* @quad_enable: enables/disables SPI NOR Quad mode.
* @set_4byte_addr_mode: puts the SPI NOR in 4 byte addressing mode.
* @convert_addr: converts an absolute address into something the flash
@@ -208,6 +238,7 @@ struct spi_nor_locking_ops {
* e.g. different opcodes, specific address calculation,
* page size, etc.
* @locking_ops: SPI NOR locking methods.
+ * @otp_ops: SPI NOR OTP methods.
*/
struct spi_nor_flash_parameter {
u64 size;
@@ -218,6 +249,7 @@ struct spi_nor_flash_parameter {
struct spi_nor_pp_command page_programs[SNOR_CMD_PP_MAX];

struct spi_nor_erase_map erase_map;
+ struct spi_nor_otp_info otp_info;

int (*quad_enable)(struct spi_nor *nor, bool enable);
int (*set_4byte_addr_mode)(struct spi_nor *nor, bool enable);
@@ -225,6 +257,7 @@ struct spi_nor_flash_parameter {
int (*setup)(struct spi_nor *nor, const struct spi_nor_hwcaps *hwcaps);

const struct spi_nor_locking_ops *locking_ops;
+ const struct spi_nor_otp_ops *otp_ops;
};

/**
@@ -314,6 +347,15 @@ struct flash_info {

/* Part specific fixup hooks. */
const struct spi_nor_fixups *fixups;
+
+ /* OTP size in bytes */
+ u16 otp_size;
+ /* Number of OTP banks */
+ u16 n_otps;
+ /* Start address of OTP area */
+ u32 otp_start_addr;
+ /* Offset between consecutive OTP banks if there are more than one */
+ u32 otp_addr_offset;
};

/* Used when the "_ext_id" is two bytes at most */
@@ -366,6 +408,12 @@ struct flash_info {
.addr_width = 3, \
.flags = SPI_NOR_NO_FR | SPI_NOR_XSR_RDY,

+#define OTP_INFO(_otp_size, _n_otps, _otp_start_addr, _otp_addr_offset) \
+ .otp_size = (_otp_size), \
+ .n_otps = (_n_otps), \
+ .otp_start_addr = (_otp_start_addr), \
+ .otp_addr_offset = (_otp_addr_offset),
+
/**
* struct spi_nor_manufacturer - SPI NOR manufacturer object
* @name: manufacturer name
--
2.20.1

2020-09-11 22:29:49

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 3/4] mtd: spi-nor: implement OTP support for Macronix and similar flashes

Use the new OTP ops to implement OTP access on Macronix flashes. The
Macronix flashes provides one OTP area which is either programmed with
an electrical serial number and locked by the factory or is empty and can
be locked by the user. To keep things simple and because most devices
will have unprogrammed OTP areas, we treat both options as user regions.
If there will actually be an ESN preprogrammed, it will appear as a
locked user region.

This was tested on a Macronix MX25L6405D as well as on a Adesto
AT25SL321.

Signed-off-by: Michael Walle <[email protected]>
---
drivers/mtd/spi-nor/atmel.c | 13 ++-
drivers/mtd/spi-nor/core.c | 170 +++++++++++++++++++++++++++++++++
drivers/mtd/spi-nor/core.h | 9 ++
drivers/mtd/spi-nor/macronix.c | 13 ++-
include/linux/mtd/spi-nor.h | 6 ++
5 files changed, 209 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/spi-nor/atmel.c b/drivers/mtd/spi-nor/atmel.c
index 3f5f21a473a6..1688c9989c6b 100644
--- a/drivers/mtd/spi-nor/atmel.c
+++ b/drivers/mtd/spi-nor/atmel.c
@@ -19,7 +19,8 @@ static const struct flash_info atmel_parts[] = {
{ "at25df641", INFO(0x1f4800, 0, 64 * 1024, 128, SECT_4K) },

{ "at25sl321", INFO(0x1f4216, 0, 64 * 1024, 64,
- SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
+ SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
+ OTP_INFO1(512, 0) },

{ "at26f004", INFO(0x1f0400, 0, 64 * 1024, 8, SECT_4K) },
{ "at26df081a", INFO(0x1f4501, 0, 64 * 1024, 16, SECT_4K) },
@@ -29,9 +30,19 @@ static const struct flash_info atmel_parts[] = {
{ "at45db081d", INFO(0x1f2500, 0, 64 * 1024, 16, SECT_4K) },
};

+static const struct spi_nor_otp_ops atmel_otp_ops = {
+ .read = spi_nor_otp_read_otp_mode,
+ .write = spi_nor_otp_write_otp_mode,
+ .lock = spi_nor_otp_lock_scur,
+ .is_locked = spi_nor_otp_is_locked_scur,
+};
+
static void atmel_default_init(struct spi_nor *nor)
{
nor->flags |= SNOR_F_HAS_LOCK;
+
+ if (nor->params->otp_info.n_otps)
+ nor->params->otp_ops = &atmel_otp_ops;
}

static const struct spi_nor_fixups atmel_fixups = {
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 4244f98e4948..348db19958e9 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -2828,6 +2828,176 @@ static int spi_nor_unlock_all(struct spi_nor *nor)
return 0;
}

+/**
+ * spi_nor_set_secured_otp_mode() - Set secured OTP mode
+ * @nor: pointer to 'struct spi_nor'.
+ * @enable: true to enter the secured OTP mode, false to exit the secured
+ * OTP mode.
+ *
+ * Enter and exit OTP mode by using the command SPINOR_OP_ENSO (B1h) and
+ * SPINOR_EP_EXSO (C1h) command.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int spi_nor_set_secured_otp_mode(struct spi_nor *nor, bool enable)
+{
+ u8 cmd = enable ? SPINOR_OP_ENSO : SPINOR_OP_EXSO;
+ int ret;
+
+ ret = spi_nor_simple_cmd(nor, cmd);
+ if (ret)
+ dev_dbg(nor->dev, "error %d setting secured OTP mode\n", ret);
+
+ return ret;
+}
+
+/**
+ * spi_nor_read_scur() - Read the Security Register using the SPINOR_OP_RDSCUR (2Bh) command.
+ * @nor: pointer to 'struct spi_nor'
+ * @scur: pointer to a DMA-able buffer where the value of the
+ * Security Register will be written.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int spi_nor_read_scur(struct spi_nor *nor, u8 *scur)
+{
+ int ret;
+
+ ret = spi_nor_simple_cmd_din(nor, SPINOR_OP_RDSCUR, scur, 1);
+ if (ret)
+ dev_dbg(nor->dev, "error %d reading SCUR\n", ret);
+
+ return ret;
+}
+
+/**
+ * spi_nor_write_scur() - Write the Security Register using the SPINOR_OP_WRSCUR (2Fh) command.
+ * @nor: pointer to 'struct spi_nor'
+ *
+ * This register contains only one OTP bit. The command doesn't take any
+ * arguments. In fact it _must not_ take any arugments. Otherwise the command
+ * is ignored.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int spi_nor_write_scur(struct spi_nor *nor)
+{
+ int ret;
+
+ ret = spi_nor_simple_cmd(nor, SPINOR_OP_WRSCUR);
+ if (ret)
+ dev_dbg(nor->dev, "error %d writing SCUR\n", ret);
+
+ return ret;
+}
+
+/**
+ * spi_nor_otp_read_otp_mode() - read OTP data
+ * @nor: pointer to 'struct spi_nor'
+ * @from: offset to read from
+ * @len: number of bytes to read
+ * @buf: pointer to dst buffer
+ *
+ * Read OTP data by using the ENSO and EXSO commands. This method is used on
+ * Adesto, Atmel, Macronix and Micron SPI flashes.
+ *
+ * Return: number of bytes read successfully, -errno otherwise
+ */
+int spi_nor_otp_read_otp_mode(struct spi_nor *nor, loff_t from, uint64_t len, u8 *buf)
+{
+ int ret;
+
+ ret = spi_nor_set_secured_otp_mode(nor, true);
+ if (ret)
+ return ret;
+
+ ret = spi_nor_read_data(nor, from, len, buf);
+
+ spi_nor_set_secured_otp_mode(nor, false);
+
+ return ret;
+}
+
+/**
+ * spi_nor_otp_write_otp_mode() - write OTP data
+ * @nor: pointer to 'struct spi_nor'
+ * @to: offset to write to
+ * @len: number of bytes to write
+ * @buf: pointer to src buffer
+ *
+ * Write OTP data by using the ENSO and EXSO commands. This method is used on
+ * Adesto, Atmel, Macronix and Micron SPI flashes.
+ *
+ * Return: number of bytes written successfully, -errno otherwise
+ */
+int spi_nor_otp_write_otp_mode(struct spi_nor *nor, loff_t to, uint64_t len, u8 *buf)
+{
+ int ret;
+
+ ret = spi_nor_set_secured_otp_mode(nor, true);
+ if (ret)
+ return ret;
+
+ ret = spi_nor_write_enable(nor);
+ if (ret)
+ goto out;
+
+ ret = spi_nor_write_data(nor, to, len, buf);
+ if (ret < 0)
+ goto out;
+
+ ret = spi_nor_wait_till_ready(nor);
+
+out:
+ spi_nor_set_secured_otp_mode(nor, false);
+
+ return ret;
+}
+
+/**
+ * spi_nor_otp_lock_scur() - lock the OTP region
+ * @nor: pointer to 'struct spi_nor'
+ * @region: OTP region
+ *
+ * Lock the OTP region by writing the security register. This method is used on
+ * Adesto, Atmel, Macronix and Micron SPI flashes.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+int spi_nor_otp_lock_scur(struct spi_nor *nor, unsigned int region)
+{
+ if (region != 0)
+ return -EINVAL;
+
+ return spi_nor_write_scur(nor);
+}
+
+/**
+ * spi_nor_otp_is_locked_otp_mode() - get the OTP region lock status
+ * @nor: pointer to 'struct spi_nor'
+ * @region: OTP region
+ *
+ * Retrieve the OTP region lock bit by reading the security register. This
+ * method is used on Adesto, Atmel, Macronix and Micron SPI flashes.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+int spi_nor_otp_is_locked_scur(struct spi_nor *nor, unsigned int region)
+{
+ u8 *scur = nor->bouncebuf;
+ int ret;
+
+ if (region != 0)
+ return -EINVAL;
+
+ ret = spi_nor_read_scur(nor, scur);
+ if (ret)
+ return ret;
+
+ return *scur & SCUR_LDSO;
+}
+
+
static int spi_nor_init(struct spi_nor *nor)
{
int err;
diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
index 516c5973bf88..7ec4add17b72 100644
--- a/drivers/mtd/spi-nor/core.h
+++ b/drivers/mtd/spi-nor/core.h
@@ -414,6 +414,10 @@ struct flash_info {
.otp_start_addr = (_otp_start_addr), \
.otp_addr_offset = (_otp_addr_offset),

+#define OTP_INFO1(_otp_size, _otp_start_addr) \
+ OTP_INFO(_otp_size, 1, _otp_start_addr, 0)
+
+
/**
* struct spi_nor_manufacturer - SPI NOR manufacturer object
* @name: manufacturer name
@@ -464,6 +468,11 @@ ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len,
ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len,
const u8 *buf);

+int spi_nor_otp_read_otp_mode(struct spi_nor *nor, loff_t from, uint64_t len, u8 *buf);
+int spi_nor_otp_write_otp_mode(struct spi_nor *nor, loff_t to, uint64_t len, u8 *buf);
+int spi_nor_otp_lock_scur(struct spi_nor *nor, unsigned int region);
+int spi_nor_otp_is_locked_scur(struct spi_nor *nor, unsigned int region);
+
int spi_nor_hwcaps_read2cmd(u32 hwcaps);
u8 spi_nor_convert_3to4_read(u8 opcode);
void spi_nor_set_pp_settings(struct spi_nor_pp_command *pp, u8 opcode,
diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c
index f97f3d127575..31198527f963 100644
--- a/drivers/mtd/spi-nor/macronix.c
+++ b/drivers/mtd/spi-nor/macronix.c
@@ -42,7 +42,8 @@ static const struct flash_info macronix_parts[] = {
{ "mx25l1606e", INFO(0xc22015, 0, 64 * 1024, 32, SECT_4K) },
{ "mx25l3205d", INFO(0xc22016, 0, 64 * 1024, 64, SECT_4K) },
{ "mx25l3255e", INFO(0xc29e16, 0, 64 * 1024, 64, SECT_4K) },
- { "mx25l6405d", INFO(0xc22017, 0, 64 * 1024, 128, SECT_4K) },
+ { "mx25l6405d", INFO(0xc22017, 0, 64 * 1024, 128, SECT_4K)
+ OTP_INFO1(64, 0) },
{ "mx25u2033e", INFO(0xc22532, 0, 64 * 1024, 4, SECT_4K) },
{ "mx25u3235f", INFO(0xc22536, 0, 64 * 1024, 64,
SECT_4K | SPI_NOR_DUAL_READ |
@@ -92,10 +93,20 @@ static const struct flash_info macronix_parts[] = {
SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
};

+static const struct spi_nor_otp_ops macronix_otp_ops = {
+ .read = spi_nor_otp_read_otp_mode,
+ .write = spi_nor_otp_write_otp_mode,
+ .lock = spi_nor_otp_lock_scur,
+ .is_locked = spi_nor_otp_is_locked_scur,
+};
+
static void macronix_default_init(struct spi_nor *nor)
{
nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable;
nor->params->set_4byte_addr_mode = spi_nor_set_4byte_addr_mode;
+
+ if (nor->params->otp_info.n_otps)
+ nor->params->otp_ops = &macronix_otp_ops;
}

static const struct spi_nor_fixups macronix_fixups = {
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 081dbd386944..04195d3e43b8 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -95,6 +95,12 @@
/* Used for Macronix and Winbond flashes. */
#define SPINOR_OP_EN4B 0xb7 /* Enter 4-byte mode */
#define SPINOR_OP_EX4B 0xe9 /* Exit 4-byte mode */
+#define SPINOR_OP_ENSO 0xb1 /* Enter secured OTP mode */
+#define SPINOR_OP_EXSO 0xc1 /* Exit secured OTP mode */
+#define SPINOR_OP_RDSCUR 0x2b /* Read security register */
+#define SPINOR_OP_WRSCUR 0x2f /* Write security register */
+#define SCUR_SO BIT(0) /* OTP factory secured */
+#define SCUR_LDSO BIT(1) /* OTP user lock-down */

/* Used for Spansion flashes only. */
#define SPINOR_OP_BRWR 0x17 /* Bank register write */
--
2.20.1

2020-09-14 09:43:59

by Pratyush Yadav

[permalink] [raw]
Subject: Re: [PATCH v2 1/4] mtd: spi-nor: cleanup common code

Hi Michael,

*sigh* This patch will cause a _lot_ of conflicts with my series [0]. It
should hopefully go in the next merge window so maybe you can rebase on
top of it? Dunno.

On 12/09/20 12:26AM, Michael Walle wrote:
> Introduce a spi_nor_simple_cmd() function which executes a SPI command
> with no additional argument bits. This function is then used to simplify
> many other functions.
>
> Signed-off-by: Michael Walle <[email protected]>
> ---
> drivers/mtd/spi-nor/core.c | 295 +++++++++++--------------------------
> 1 file changed, 84 insertions(+), 211 deletions(-)
>
> diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
> index 3f76eb391719..b06b160a5c9c 100644
> --- a/drivers/mtd/spi-nor/core.c
> +++ b/drivers/mtd/spi-nor/core.c
> @@ -210,6 +210,72 @@ ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len,
> return nor->controller_ops->write(nor, to, len, buf);
> }
>

Comments on the spi_nor_simple_cmd* functions are assuming my series
lands before yours.

> +/**
> + * spi_nor_simple_cmd() - send command byte
> + * @nor: pointer to 'struct spi_nor'.
> + * @cmd: command code
> + *
> + * Return: 0 on success, -errno otherwise
> + */
> +static int spi_nor_simple_cmd(struct spi_nor *nor, u8 cmd)
> +{
> + struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(cmd, 1),
> + SPI_MEM_OP_NO_ADDR,
> + SPI_MEM_OP_NO_DUMMY,
> + SPI_MEM_OP_NO_DATA);

You need to add a call to spi_nor_spimem_setup_op() here. See [1].

> +
> + if (nor->spimem)
> + return spi_mem_exec_op(nor->spimem, &op);
> + else
> + return nor->controller_ops->write_reg(nor, cmd, NULL, 0);

Use spi_nor_write_reg(). See [2].

> +}
> +
> +/**
> + * spi_nor_simple_cmd_dout() - send command byte and data
> + * @nor: pointer to 'struct spi_nor'.
> + * @cmd: command code
> + * @data: pointer to the data to be send, must be DMA-capable
> + * @len: length of the data
> + *
> + * Return: 0 on success, -errno otherwise
> + */
> +static int spi_nor_simple_cmd_dout(struct spi_nor *nor, u8 cmd, const u8 *data,
> + size_t len)
> +{
> + struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(cmd, 1),
> + SPI_MEM_OP_NO_ADDR,
> + SPI_MEM_OP_NO_DUMMY,
> + SPI_MEM_OP_DATA_OUT(len, data, 1));

You need to add a call to spi_nor_spimem_setup_op() here. See [1].

> +
> + if (nor->spimem)
> + return spi_mem_exec_op(nor->spimem, &op);
> + else
> + return nor->controller_ops->write_reg(nor, cmd, data, len);

Use spi_nor_write_reg(). See [2].

> +}
> +
> +/**
> + * spi_nor_simple_cmd_din() - send command byte and receive data
> + * @nor: pointer to 'struct spi_nor'.
> + * @cmd: command code
> + * @data: pointer to the receive buffer, must be DMA-capable
> + * @len: length of the data to be received
> + *
> + * Return: 0 on success, -errno otherwise
> + */
> +static int spi_nor_simple_cmd_din(struct spi_nor *nor, u8 cmd, u8 *data,
> + size_t len)
> +{
> + struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(cmd, 1),
> + SPI_MEM_OP_NO_ADDR,
> + SPI_MEM_OP_NO_DUMMY,
> + SPI_MEM_OP_DATA_IN(len, data, 1));
> +

You need to add a call to spi_nor_spimem_setup_op() here. See [1].

> + if (nor->spimem)
> + return spi_mem_exec_op(nor->spimem, &op);
> + else
> + return nor->controller_ops->read_reg(nor, cmd, data, len);

Use spi_nor_read_reg(). See [2].

> +}
> +
> /**
> * spi_nor_write_enable() - Set write enable latch with Write Enable command.
> * @nor: pointer to 'struct spi_nor'.

> @@ -280,19 +322,7 @@ static int spi_nor_read_sr(struct spi_nor *nor,
> u8 *sr)
> {
> int ret;
>
> - if (nor->spimem) {
> - struct spi_mem_op op =
> - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 1),
> - SPI_MEM_OP_NO_ADDR,
> - SPI_MEM_OP_NO_DUMMY,
> - SPI_MEM_OP_DATA_IN(1, sr, 1));
> -
> - ret = spi_mem_exec_op(nor->spimem, &op);
> - } else {
> - ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDSR,
> - sr, 1);
> - }
> -
> + ret = spi_nor_simple_cmd_din(nor, SPINOR_OP_RDSR, sr, 1);

NACK! xSPI compliant flashes like Cypress S28HS512T can use address and
dummy bytes for Read SR/SR2 (and I don't have an example in mind but
likely FSR too) commands in 8D-8D-8D mode. See [3].

> if (ret)
> dev_dbg(nor->dev, "error %d reading SR\n", ret);
>
> @@ -311,19 +341,7 @@ static int spi_nor_read_fsr(struct spi_nor *nor, u8 *fsr)
> {
> int ret;
>
> - if (nor->spimem) {
> - struct spi_mem_op op =
> - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDFSR, 1),
> - SPI_MEM_OP_NO_ADDR,
> - SPI_MEM_OP_NO_DUMMY,
> - SPI_MEM_OP_DATA_IN(1, fsr, 1));
> -
> - ret = spi_mem_exec_op(nor->spimem, &op);
> - } else {
> - ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDFSR,
> - fsr, 1);
> - }
> -
> + ret = spi_nor_simple_cmd_din(nor, SPINOR_OP_RDFSR, fsr, 1);

Same.

> if (ret)
> dev_dbg(nor->dev, "error %d reading FSR\n", ret);
>
> @@ -343,18 +361,7 @@ static int spi_nor_read_cr(struct spi_nor *nor, u8 *cr)
> {
> int ret;
>
> - if (nor->spimem) {
> - struct spi_mem_op op =
> - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDCR, 1),
> - SPI_MEM_OP_NO_ADDR,
> - SPI_MEM_OP_NO_DUMMY,
> - SPI_MEM_OP_DATA_IN(1, cr, 1));
> -
> - ret = spi_mem_exec_op(nor->spimem, &op);
> - } else {
> - ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDCR, cr, 1);
> - }
> -
> + ret = spi_nor_simple_cmd_din(nor, SPINOR_OP_RDCR, cr, 1);
> if (ret)
> dev_dbg(nor->dev, "error %d reading CR\n", ret);
>
> @@ -371,26 +378,10 @@ static int spi_nor_read_cr(struct spi_nor *nor, u8 *cr)
> */
> int spi_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
> {
> + u8 cmd = enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B;
> int ret;
>
> - if (nor->spimem) {
> - struct spi_mem_op op =
> - SPI_MEM_OP(SPI_MEM_OP_CMD(enable ?
> - SPINOR_OP_EN4B :
> - SPINOR_OP_EX4B,
> - 1),
> - SPI_MEM_OP_NO_ADDR,
> - SPI_MEM_OP_NO_DUMMY,
> - SPI_MEM_OP_NO_DATA);
> -
> - ret = spi_mem_exec_op(nor->spimem, &op);
> - } else {
> - ret = nor->controller_ops->write_reg(nor,
> - enable ? SPINOR_OP_EN4B :
> - SPINOR_OP_EX4B,
> - NULL, 0);
> - }
> -
> + ret = spi_nor_simple_cmd(nor, cmd);
> if (ret)
> dev_dbg(nor->dev, "error %d setting 4-byte mode\n", ret);
>
> @@ -412,19 +403,7 @@ static int spansion_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
>
> nor->bouncebuf[0] = enable << 7;
>
> - if (nor->spimem) {
> - struct spi_mem_op op =
> - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_BRWR, 1),
> - SPI_MEM_OP_NO_ADDR,
> - SPI_MEM_OP_NO_DUMMY,
> - SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1));
> -
> - ret = spi_mem_exec_op(nor->spimem, &op);
> - } else {
> - ret = nor->controller_ops->write_reg(nor, SPINOR_OP_BRWR,
> - nor->bouncebuf, 1);
> - }
> -
> + ret = spi_nor_simple_cmd_dout(nor, SPINOR_OP_BRWR, nor->bouncebuf, 1);
> if (ret)
> dev_dbg(nor->dev, "error %d setting 4-byte mode\n", ret);
>
> @@ -444,19 +423,7 @@ int spi_nor_write_ear(struct spi_nor *nor, u8 ear)
>
> nor->bouncebuf[0] = ear;
>
> - if (nor->spimem) {
> - struct spi_mem_op op =
> - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREAR, 1),
> - SPI_MEM_OP_NO_ADDR,
> - SPI_MEM_OP_NO_DUMMY,
> - SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1));
> -
> - ret = spi_mem_exec_op(nor->spimem, &op);
> - } else {
> - ret = nor->controller_ops->write_reg(nor, SPINOR_OP_WREAR,
> - nor->bouncebuf, 1);
> - }
> -
> + ret = spi_nor_simple_cmd_dout(nor, SPINOR_OP_WREAR, nor->bouncebuf, 1);
> if (ret)
> dev_dbg(nor->dev, "error %d writing EAR\n", ret);
>
> @@ -475,19 +442,7 @@ int spi_nor_xread_sr(struct spi_nor *nor, u8 *sr)
> {
> int ret;
>
> - if (nor->spimem) {
> - struct spi_mem_op op =
> - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_XRDSR, 1),
> - SPI_MEM_OP_NO_ADDR,
> - SPI_MEM_OP_NO_DUMMY,
> - SPI_MEM_OP_DATA_IN(1, sr, 1));
> -
> - ret = spi_mem_exec_op(nor->spimem, &op);
> - } else {
> - ret = nor->controller_ops->read_reg(nor, SPINOR_OP_XRDSR,
> - sr, 1);
> - }
> -
> + ret = spi_nor_simple_cmd_din(nor, SPINOR_OP_XRDSR, sr, 1);
> if (ret)
> dev_dbg(nor->dev, "error %d reading XRDSR\n", ret);
>
> @@ -520,19 +475,7 @@ static void spi_nor_clear_sr(struct spi_nor *nor)
> {
> int ret;
>
> - if (nor->spimem) {
> - struct spi_mem_op op =
> - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLSR, 1),
> - SPI_MEM_OP_NO_ADDR,
> - SPI_MEM_OP_NO_DUMMY,
> - SPI_MEM_OP_NO_DATA);
> -
> - ret = spi_mem_exec_op(nor->spimem, &op);
> - } else {
> - ret = nor->controller_ops->write_reg(nor, SPINOR_OP_CLSR,
> - NULL, 0);
> - }
> -
> + ret = spi_nor_simple_cmd(nor, SPINOR_OP_CLSR);
> if (ret)
> dev_dbg(nor->dev, "error %d clearing SR\n", ret);
> }
> @@ -584,19 +527,7 @@ static void spi_nor_clear_fsr(struct spi_nor *nor)
> {
> int ret;
>
> - if (nor->spimem) {
> - struct spi_mem_op op =
> - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLFSR, 1),
> - SPI_MEM_OP_NO_ADDR,
> - SPI_MEM_OP_NO_DUMMY,
> - SPI_MEM_OP_NO_DATA);
> -
> - ret = spi_mem_exec_op(nor->spimem, &op);
> - } else {
> - ret = nor->controller_ops->write_reg(nor, SPINOR_OP_CLFSR,
> - NULL, 0);
> - }
> -
> + ret = spi_nor_simple_cmd(nor, SPINOR_OP_CLFSR);
> if (ret)
> dev_dbg(nor->dev, "error %d clearing FSR\n", ret);
> }
> @@ -728,19 +659,7 @@ static int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len)
> if (ret)
> return ret;
>
> - if (nor->spimem) {
> - struct spi_mem_op op =
> - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 1),
> - SPI_MEM_OP_NO_ADDR,
> - SPI_MEM_OP_NO_DUMMY,
> - SPI_MEM_OP_DATA_OUT(len, sr, 1));
> -
> - ret = spi_mem_exec_op(nor->spimem, &op);
> - } else {
> - ret = nor->controller_ops->write_reg(nor, SPINOR_OP_WRSR,
> - sr, len);
> - }
> -
> + ret = spi_nor_simple_cmd_dout(nor, SPINOR_OP_WRSR, sr, len);
> if (ret) {
> dev_dbg(nor->dev, "error %d writing SR\n", ret);
> return ret;
> @@ -930,19 +849,7 @@ static int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2)
> if (ret)
> return ret;
>
> - if (nor->spimem) {
> - struct spi_mem_op op =
> - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR2, 1),
> - SPI_MEM_OP_NO_ADDR,
> - SPI_MEM_OP_NO_DUMMY,
> - SPI_MEM_OP_DATA_OUT(1, sr2, 1));
> -
> - ret = spi_mem_exec_op(nor->spimem, &op);
> - } else {
> - ret = nor->controller_ops->write_reg(nor, SPINOR_OP_WRSR2,
> - sr2, 1);
> - }
> -
> + ret = spi_nor_simple_cmd_dout(nor, SPINOR_OP_WRSR2, sr2, 1);
> if (ret) {
> dev_dbg(nor->dev, "error %d writing SR2\n", ret);
> return ret;
> @@ -964,19 +871,7 @@ static int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2)
> {
> int ret;
>
> - if (nor->spimem) {
> - struct spi_mem_op op =
> - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR2, 1),
> - SPI_MEM_OP_NO_ADDR,
> - SPI_MEM_OP_NO_DUMMY,
> - SPI_MEM_OP_DATA_IN(1, sr2, 1));
> -
> - ret = spi_mem_exec_op(nor->spimem, &op);
> - } else {
> - ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDSR2,
> - sr2, 1);
> - }
> -
> + ret = spi_nor_simple_cmd_din(nor, SPINOR_OP_RDSR2, sr2, 1);

Same.

> if (ret)
> dev_dbg(nor->dev, "error %d reading SR2\n", ret);
>
> @@ -995,19 +890,7 @@ static int spi_nor_erase_chip(struct spi_nor *nor)
>
> dev_dbg(nor->dev, " %lldKiB\n", (long long)(nor->mtd.size >> 10));
>
> - if (nor->spimem) {
> - struct spi_mem_op op =
> - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CHIP_ERASE, 1),
> - SPI_MEM_OP_NO_ADDR,
> - SPI_MEM_OP_NO_DUMMY,
> - SPI_MEM_OP_NO_DATA);
> -
> - ret = spi_mem_exec_op(nor->spimem, &op);
> - } else {
> - ret = nor->controller_ops->write_reg(nor, SPINOR_OP_CHIP_ERASE,
> - NULL, 0);
> - }
> -
> + ret = spi_nor_simple_cmd(nor, SPINOR_OP_CHIP_ERASE);
> if (ret)
> dev_dbg(nor->dev, "error %d erasing chip\n", ret);
>
> @@ -2064,18 +1947,8 @@ static const struct flash_info *spi_nor_read_id(struct spi_nor *nor)
> unsigned int i;
> int ret;
>
> - if (nor->spimem) {
> - struct spi_mem_op op =
> - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 1),
> - SPI_MEM_OP_NO_ADDR,
> - SPI_MEM_OP_NO_DUMMY,
> - SPI_MEM_OP_DATA_IN(SPI_NOR_MAX_ID_LEN, id, 1));
> -
> - ret = spi_mem_exec_op(nor->spimem, &op);
> - } else {
> - ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDID, id,
> - SPI_NOR_MAX_ID_LEN);
> - }
> + ret = spi_nor_simple_cmd_din(nor, SPINOR_OP_RDID, id,
> + SPI_NOR_MAX_ID_LEN);

Same here but as of now SPI NOR does not read flash ID in 8D mode so
this is OK for now I think.

> if (ret) {
> dev_dbg(nor->dev, "error %d reading JEDEC ID\n", ret);
> return ERR_PTR(ret);

[0] https://lore.kernel.org/linux-mtd/[email protected]/
[1] https://lore.kernel.org/linux-mtd/[email protected]/
[2] https://lore.kernel.org/linux-mtd/[email protected]/
[3] https://lore.kernel.org/linux-mtd/[email protected]/

--
Regards,
Pratyush Yadav

2020-09-14 11:13:28

by Michael Walle

[permalink] [raw]
Subject: Re: [PATCH v2 1/4] mtd: spi-nor: cleanup common code

Hi Pratyush,

Am 2020-09-14 11:40, schrieb Pratyush Yadav:
> Hi Michael,
>
> *sigh* This patch will cause a _lot_ of conflicts with my series [0].
> It
> should hopefully go in the next merge window so maybe you can rebase on
> top of it? Dunno.

Could you pick up this patch in some form in your series? It looks like
your
series touches most/some of the functions. The patch just removes code
repetitions.
TBH I don't need this patch as I just can implement my new one-byte
command
using the current "if (nor->spimem) {} else {}" pattern.

-michael

2020-09-14 11:26:35

by Michael Walle

[permalink] [raw]
Subject: Re: [PATCH v2 0/4] mtd: spi-nor: OTP support

Am 2020-09-12 00:26, schrieb Michael Walle:
> This patchset implements the MTD OTP functions to allow access to the
> SPI
> OTP data. Specific support is added for the Adesto, Macronix and
> Winbond
> flash chips.
>
> In the past there was already an attempt by Rahul Bedarkar to add this,
> but
> there was no response. These patches are slightly based on his work.
>
> https://lore.kernel.org/linux-mtd/[email protected]/
>

I've missed appending the changelog:

changes since v1:
- rebased to latest tree
- correctly check return code of otp_ops->is_locked() in
spi_nor_mtd_otp_info()
- use correct bouncebuf for spi command in spi_nor_otp_lock_sr2()
- add cleanup patch to consolidate some of the "if (spimem) {} else {}"
patterns
- add otp support for macronix and similar flashes
- moved otp code into core.c because it is used across different
vendors
- use generic naming, eg. spi_nor_otp_lock_sr2() instead of
winbond_otp_lock()

-michael

> Michael Walle (4):
> mtd: spi-nor: cleanup common code
> mtd: spi-nor: add OTP support
> mtd: spi-nor: implement OTP support for Macronix and similar flashes
> mtd: spi-nor: implement OTP support for Winbond and similar flashes
>
> drivers/mtd/chips/Kconfig | 2 +-
> drivers/mtd/spi-nor/atmel.c | 13 +-
> drivers/mtd/spi-nor/core.c | 769 ++++++++++++++++++++++++---------
> drivers/mtd/spi-nor/core.h | 61 +++
> drivers/mtd/spi-nor/macronix.c | 13 +-
> drivers/mtd/spi-nor/winbond.c | 18 +-
> include/linux/mtd/spi-nor.h | 16 +
> 7 files changed, 676 insertions(+), 216 deletions(-)

2020-09-14 14:53:10

by Pratyush Yadav

[permalink] [raw]
Subject: Re: [PATCH v2 1/4] mtd: spi-nor: cleanup common code

On 14/09/20 01:09PM, Michael Walle wrote:
> Hi Pratyush,
>
> Am 2020-09-14 11:40, schrieb Pratyush Yadav:
> > Hi Michael,
> >
> > *sigh* This patch will cause a _lot_ of conflicts with my series [0]. It
> > should hopefully go in the next merge window so maybe you can rebase on
> > top of it? Dunno.
>
> Could you pick up this patch in some form in your series? It looks like your
> series touches most/some of the functions. The patch just removes code
> repetitions.

I don't want to make any last-minute changes that could end up blocking
the series. Its been pending for many months now.

> TBH I don't need this patch as I just can implement my new one-byte command
> using the current "if (nor->spimem) {} else {}" pattern.

It is up to you what you want to do about the patch. Maybe you can drop
it now and send it later when things have settled down? Dunno.

--
Regards,
Pratyush Yadav

2020-10-01 05:36:35

by Heiko Thiery

[permalink] [raw]
Subject: Re: [PATCH v2 2/4] mtd: spi-nor: add OTP support

Hi Michael,

Am Sa., 12. Sept. 2020 um 00:26 Uhr schrieb Michael Walle <[email protected]>:
>
> Implement the MTD callbacks for the OTP methods for the SPI NOR
> subsystem.
>
> Usually, the OTP area of a SPI flash can be accessed like the normal
> memory, eg by offset addressing; except that you either have to use
> special read/write commands (Winbond) or you have to enter (and exit) a
> specific OTP mode (Macronix, Micron). Sometimes there are individual
> regions, which might have individual offsets. Therefore, it is possible
> to specify the starting address of the first regions as well as the
> distance between two regions (Winbond).
>
> Additionally, the regions might be locked down. Once locked, no further
> write access is possible.
>
> Cc: Rahul Bedarkar <[email protected]>
> Signed-off-by: Michael Walle <[email protected]>

Reviewed-by: Heiko Thiery <[email protected]>

> ---
> drivers/mtd/chips/Kconfig | 2 +-
> drivers/mtd/spi-nor/core.c | 143 +++++++++++++++++++++++++++++++++++++
> drivers/mtd/spi-nor/core.h | 48 +++++++++++++
> 3 files changed, 192 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/mtd/chips/Kconfig b/drivers/mtd/chips/Kconfig
> index aef14990e5f7..3b7ba9448118 100644
> --- a/drivers/mtd/chips/Kconfig
> +++ b/drivers/mtd/chips/Kconfig
> @@ -152,7 +152,7 @@ config MTD_CFI_I8
>
> config MTD_OTP
> bool "Protection Registers aka one-time programmable (OTP) bits"
> - depends on MTD_CFI_ADV_OPTIONS
> + depends on MTD_CFI_ADV_OPTIONS || MTD_SPI_NOR
> default n
> help
> This enables support for reading, writing and locking so called
> diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
> index b06b160a5c9c..4244f98e4948 100644
> --- a/drivers/mtd/spi-nor/core.c
> +++ b/drivers/mtd/spi-nor/core.c
> @@ -2689,6 +2689,12 @@ static void spi_nor_info_init_params(struct spi_nor *nor)
> spi_nor_set_erase_type(&map->erase_type[i], info->sector_size,
> SPINOR_OP_SE);
> spi_nor_init_uniform_erase_map(map, erase_mask, params->size);
> +
> + /* OTP parameters */
> + nor->params->otp_info.otp_size = info->otp_size;
> + nor->params->otp_info.n_otps = info->n_otps;
> + nor->params->otp_info.otp_start_addr = info->otp_start_addr;
> + nor->params->otp_info.otp_addr_offset = info->otp_addr_offset;
> }
>
> /**
> @@ -2972,6 +2978,127 @@ static const struct flash_info *spi_nor_get_flash_info(struct spi_nor *nor,
> return info;
> }
>
> +static loff_t spi_nor_otp_region_start(struct spi_nor *nor, int region)
> +{
> + struct spi_nor_otp_info *info = &nor->params->otp_info;
> +
> + return info->otp_start_addr + region * info->otp_addr_offset;
> +}
> +
> +static loff_t spi_nor_otp_region_end(struct spi_nor *nor, int region)
> +{
> + struct spi_nor_otp_info *info = &nor->params->otp_info;
> +
> + return spi_nor_otp_region_start(nor, region) + info->otp_size - 1;
> +}
> +
> +static int spi_nor_mtd_otp_info(struct mtd_info *mtd, size_t len,
> + size_t *retlen, struct otp_info *buf)
> +{
> + struct spi_nor *nor = mtd_to_spi_nor(mtd);
> + int locked;
> + int i;
> +
> + for (i = 0; i < nor->params->otp_info.n_otps; i++) {
> + buf[i].start = spi_nor_otp_region_start(nor, i);
> + buf[i].length = nor->params->otp_info.otp_size;
> +
> + locked = nor->params->otp_ops->is_locked(nor, i);
> + if (locked < 0)
> + return locked;
> +
> + buf[i].locked = !!locked;
> + }
> +
> + *retlen = nor->params->otp_info.n_otps * sizeof(*buf);
> +
> + return 0;
> +}
> +
> +static int spi_nor_otp_addr_to_region(struct spi_nor *nor, loff_t addr)
> +{
> + int i;
> +
> + for (i = 0; i < nor->params->otp_info.n_otps; i++)
> + if (addr >= spi_nor_otp_region_start(nor, i) &&
> + addr <= spi_nor_otp_region_end(nor, i))
> + return i;
> +
> + return -EINVAL;
> +}
> +
> +static int spi_nor_mtd_otp_read_write(struct mtd_info *mtd, loff_t ofs,
> + size_t len, size_t *retlen, u_char *buf,
> + bool is_write)
> +{
> + struct spi_nor *nor = mtd_to_spi_nor(mtd);
> + int region;
> + int ret;
> +
> + *retlen = 0;
> +
> + region = spi_nor_otp_addr_to_region(nor, ofs);
> + if (region < 0)
> + return 0;
> +
> + if (ofs < spi_nor_otp_region_start(nor, region))
> + return 0;
> +
> + if ((ofs + len - 1) > spi_nor_otp_region_end(nor, region))
> + return 0;
> +
> + ret = spi_nor_lock_and_prep(nor);
> +
> + if (is_write)
> + ret = nor->params->otp_ops->write(nor, ofs, len, buf);
> + else
> + ret = nor->params->otp_ops->read(nor, ofs, len, buf);
> +
> + spi_nor_unlock_and_unprep(nor);
> +
> + if (ret < 0)
> + return ret;
> +
> + *retlen = len;
> + return 0;
> +}
> +
> +static int spi_nor_mtd_otp_read(struct mtd_info *mtd, loff_t from, size_t len,
> + size_t *retlen, u_char *buf)
> +{
> + return spi_nor_mtd_otp_read_write(mtd, from, len, retlen, buf, false);
> +}
> +
> +static int spi_nor_mtd_otp_write(struct mtd_info *mtd, loff_t to, size_t len,
> + size_t *retlen, u_char *buf)
> +{
> + return spi_nor_mtd_otp_read_write(mtd, to, len, retlen, buf, true);
> +}
> +
> +static int spi_nor_mtd_otp_lock(struct mtd_info *mtd, loff_t from, size_t len)
> +{
> + struct spi_nor *nor = mtd_to_spi_nor(mtd);
> + int region;
> + int ret;
> +
> + region = spi_nor_otp_addr_to_region(nor, from);
> + if (region < 0)
> + return -EINVAL;
> +
> + if (len != nor->params->otp_info.otp_size)
> + return -EINVAL;
> +
> + ret = spi_nor_lock_and_prep(nor);
> + if (ret)
> + return ret;
> +
> + ret = nor->params->otp_ops->lock(nor, region);
> +
> + spi_nor_unlock_and_unprep(nor);
> +
> + return ret;
> +}
> +
> int spi_nor_scan(struct spi_nor *nor, const char *name,
> const struct spi_nor_hwcaps *hwcaps)
> {
> @@ -3050,6 +3177,22 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
> mtd->_is_locked = spi_nor_is_locked;
> }
>
> + if (nor->params->otp_ops) {
> + /*
> + * We only support user_prot callbacks (yet).
> + *
> + * Some SPI flashes like Macronix ones support factory locked
> + * OTP areas, usually they are preprogrammed with an
> + * "electrical serial number".
> + * Most of the time the OTP area is unprogrammed and left up to
> + * the user. This is what we support at the moment.
> + */
> + mtd->_get_user_prot_info = spi_nor_mtd_otp_info;
> + mtd->_read_user_prot_reg = spi_nor_mtd_otp_read;
> + mtd->_write_user_prot_reg = spi_nor_mtd_otp_write;
> + mtd->_lock_user_prot_reg = spi_nor_mtd_otp_lock;
> + }
> +
> if (info->flags & USE_FSR)
> nor->flags |= SNOR_F_USE_FSR;
> if (info->flags & SPI_NOR_HAS_TB) {
> diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
> index fb3ef86e350c..516c5973bf88 100644
> --- a/drivers/mtd/spi-nor/core.h
> +++ b/drivers/mtd/spi-nor/core.h
> @@ -170,6 +170,21 @@ struct spi_nor_erase_map {
> u8 uniform_erase_type;
> };
>
> +/**
> + * struct spi_nor_otp_info - Structure to describe the SPI NOR OTP region
> + * @otp_size: size of one OTP region in bytes.
> + * @n_otps: number of individual OTP regions.
> + * @otp_start_addr: start address of the OTP area.
> + * @otp_addr_offset: offset between consecutive OTP regions if there are
> + * more than one.
> + */
> +struct spi_nor_otp_info {
> + u32 otp_size;
> + int n_otps;
> + u32 otp_start_addr;
> + u32 otp_addr_offset;
> +};
> +
> /**
> * struct spi_nor_locking_ops - SPI NOR locking methods
> * @lock: lock a region of the SPI NOR.
> @@ -182,6 +197,20 @@ struct spi_nor_locking_ops {
> int (*is_locked)(struct spi_nor *nor, loff_t ofs, uint64_t len);
> };
>
> +/**
> + * struct spi_nor_otp_ops - SPI NOR OTP methods
> + * @read: read from the SPI NOR OTP area.
> + * @write: write to the SPI NOR OTP area.
> + * @lock: lock an OTP region.
> + * @is_locked: check if an OTP region of the SPI NOR is locked.
> + */
> +struct spi_nor_otp_ops {
> + int (*read)(struct spi_nor *nor, loff_t ofs, uint64_t len, u8 *buf);
> + int (*write)(struct spi_nor *nor, loff_t ofs, uint64_t len, u8 *buf);
> + int (*lock)(struct spi_nor *nor, unsigned int region);
> + int (*is_locked)(struct spi_nor *nor, unsigned int region);
> +};
> +
> /**
> * struct spi_nor_flash_parameter - SPI NOR flash parameters and settings.
> * Includes legacy flash parameters and settings that can be overwritten
> @@ -198,6 +227,7 @@ struct spi_nor_locking_ops {
> * higher index in the array, the higher priority.
> * @erase_map: the erase map parsed from the SFDP Sector Map Parameter
> * Table.
> + * @otp_info: describes the OTP regions.
> * @quad_enable: enables/disables SPI NOR Quad mode.
> * @set_4byte_addr_mode: puts the SPI NOR in 4 byte addressing mode.
> * @convert_addr: converts an absolute address into something the flash
> @@ -208,6 +238,7 @@ struct spi_nor_locking_ops {
> * e.g. different opcodes, specific address calculation,
> * page size, etc.
> * @locking_ops: SPI NOR locking methods.
> + * @otp_ops: SPI NOR OTP methods.
> */
> struct spi_nor_flash_parameter {
> u64 size;
> @@ -218,6 +249,7 @@ struct spi_nor_flash_parameter {
> struct spi_nor_pp_command page_programs[SNOR_CMD_PP_MAX];
>
> struct spi_nor_erase_map erase_map;
> + struct spi_nor_otp_info otp_info;
>
> int (*quad_enable)(struct spi_nor *nor, bool enable);
> int (*set_4byte_addr_mode)(struct spi_nor *nor, bool enable);
> @@ -225,6 +257,7 @@ struct spi_nor_flash_parameter {
> int (*setup)(struct spi_nor *nor, const struct spi_nor_hwcaps *hwcaps);
>
> const struct spi_nor_locking_ops *locking_ops;
> + const struct spi_nor_otp_ops *otp_ops;
> };
>
> /**
> @@ -314,6 +347,15 @@ struct flash_info {
>
> /* Part specific fixup hooks. */
> const struct spi_nor_fixups *fixups;
> +
> + /* OTP size in bytes */
> + u16 otp_size;
> + /* Number of OTP banks */
> + u16 n_otps;
> + /* Start address of OTP area */
> + u32 otp_start_addr;
> + /* Offset between consecutive OTP banks if there are more than one */
> + u32 otp_addr_offset;
> };
>
> /* Used when the "_ext_id" is two bytes at most */
> @@ -366,6 +408,12 @@ struct flash_info {
> .addr_width = 3, \
> .flags = SPI_NOR_NO_FR | SPI_NOR_XSR_RDY,
>
> +#define OTP_INFO(_otp_size, _n_otps, _otp_start_addr, _otp_addr_offset) \
> + .otp_size = (_otp_size), \
> + .n_otps = (_n_otps), \
> + .otp_start_addr = (_otp_start_addr), \
> + .otp_addr_offset = (_otp_addr_offset),
> +
> /**
> * struct spi_nor_manufacturer - SPI NOR manufacturer object
> * @name: manufacturer name
>

2020-10-01 05:37:42

by Heiko Thiery

[permalink] [raw]
Subject: Re: [PATCH v2 4/4] mtd: spi-nor: implement OTP support for Winbond and similar flashes

HI Michael,

Am Sa., 12. Sept. 2020 um 00:26 Uhr schrieb Michael Walle <[email protected]>:
>
> Use the new OTP ops to implement OTP access on Winbond flashes. Most
> Winbond flashes provides up to four different OTP areas ("Security
> Registers"). Newer flashes uses the first OTP area for SFDP data. Thus,
> for these flashes only the last three areas are handled and the first
> one is left untouched.
>
> This was tested on a Winbond W25Q32JW as well as on a W25Q32FW.
>
> Signed-off-by: Michael Walle <[email protected]>

Reviewed-by: Heiko Thiery <[email protected]>

> ---
> drivers/mtd/spi-nor/core.c | 161 ++++++++++++++++++++++++++++++++++
> drivers/mtd/spi-nor/core.h | 4 +
> drivers/mtd/spi-nor/winbond.c | 18 +++-
> include/linux/mtd/spi-nor.h | 10 +++
> 4 files changed, 191 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
> index 348db19958e9..c150e3b6ee44 100644
> --- a/drivers/mtd/spi-nor/core.c
> +++ b/drivers/mtd/spi-nor/core.c
> @@ -2997,6 +2997,167 @@ int spi_nor_otp_is_locked_scur(struct spi_nor *nor, unsigned int region)
> return *scur & SCUR_LDSO;
> }
>
> +/**
> + * spi_nor_otp_read_secr() - read OTP data
> + * @nor: pointer to 'struct spi_nor'
> + * @from: offset to read from
> + * @len: number of bytes to read
> + * @buf: pointer to dst buffer
> + *
> + * Read OTP data by using the SPINOR_OP_RSECR commands. This method is used on
> + * GigaDevice and Winbond flashes.
> + *
> + * Return: number of bytes read successfully, -errno otherwise
> + */
> +int spi_nor_otp_read_secr(struct spi_nor *nor, loff_t addr, uint64_t len, u8 *buf)
> +{
> + u8 addr_width, read_opcode, read_dummy;
> + struct spi_mem_dirmap_desc *rdesc;
> + enum spi_nor_protocol read_proto;
> + int ret;
> +
> + read_opcode = nor->read_opcode;
> + addr_width = nor->addr_width;
> + read_dummy = nor->read_dummy;
> + read_proto = nor->read_proto;
> + rdesc = nor->dirmap.rdesc;
> +
> + nor->read_opcode = SPINOR_OP_RSECR;
> + nor->addr_width = 3;
> + nor->read_dummy = 8;
> + nor->read_proto = SNOR_PROTO_1_1_1;
> + nor->dirmap.rdesc = NULL;
> +
> + ret = spi_nor_read_data(nor, addr, len, buf);
> +
> + nor->read_opcode = read_opcode;
> + nor->addr_width = addr_width;
> + nor->read_dummy = read_dummy;
> + nor->read_proto = read_proto;
> + nor->dirmap.rdesc = rdesc;
> +
> + return ret;
> +}
> +
> +/**
> + * spi_nor_otp_write_secr() - write OTP data
> + * @nor: pointer to 'struct spi_nor'
> + * @to: offset to write to
> + * @len: number of bytes to write
> + * @buf: pointer to src buffer
> + *
> + * Write OTP data by using the SPINOR_OP_PSECR commands. This method is used on
> + * GigaDevice and Winbond flashes.
> + *
> + * Return: number of bytes written successfully, -errno otherwise
> + */
> +int spi_nor_otp_write_secr(struct spi_nor *nor, loff_t addr, uint64_t len, u8 *buf)
> +{
> + enum spi_nor_protocol write_proto;
> + struct spi_mem_dirmap_desc *wdesc;
> + u8 addr_width, program_opcode;
> + int ret;
> +
> + program_opcode = nor->program_opcode;
> + addr_width = nor->addr_width;
> + write_proto = nor->write_proto;
> + wdesc = nor->dirmap.wdesc;
> +
> + nor->program_opcode = SPINOR_OP_PSECR;
> + nor->addr_width = 3;
> + nor->write_proto = SNOR_PROTO_1_1_1;
> + nor->dirmap.wdesc = NULL;
> +
> + /*
> + * We only support a write to one single page. For now all winbond
> + * flashes only have one page per OTP region.
> + */
> + ret = spi_nor_write_enable(nor);
> + if (ret)
> + goto out;
> +
> + ret = spi_nor_write_data(nor, addr, len, buf);
> + if (ret < 0)
> + goto out;
> +
> + ret = spi_nor_wait_till_ready(nor);
> +
> +out:
> + nor->program_opcode = program_opcode;
> + nor->addr_width = addr_width;
> + nor->write_proto = write_proto;
> + nor->dirmap.wdesc = wdesc;
> +
> + return ret;
> +}
> +
> +static int spi_nor_otp_lock_bit_cr(unsigned int region)
> +{
> + static const int lock_bits[] = { SR2_LB1, SR2_LB2, SR2_LB3 };
> +
> + if (region >= ARRAY_SIZE(lock_bits))
> + return -EINVAL;
> +
> + return lock_bits[region];
> +}
> +
> +/**
> + * spi_nor_otp_lock_sr2() - lock the OTP region
> + * @nor: pointer to 'struct spi_nor'
> + * @region: OTP region
> + *
> + * Lock the OTP region by writing the status register-2. This method is used on
> + * GigaDevice and Winbond flashes.
> + *
> + * Return: 0 on success, -errno otherwise.
> + */
> +int spi_nor_otp_lock_sr2(struct spi_nor *nor, unsigned int region)
> +{
> + int lock_bit;
> + u8 *sr2 = nor->bouncebuf;
> + int ret;
> +
> + lock_bit = spi_nor_otp_lock_bit_cr(region);
> + if (lock_bit < 0)
> + return lock_bit;
> +
> + ret = spi_nor_read_cr(nor, sr2);
> + if (ret)
> + return ret;
> +
> + /* check if its already locked */
> + if (*sr2 & lock_bit)
> + return 0;
> +
> + return spi_nor_write_16bit_cr_and_check(nor, *sr2 | lock_bit);
> +}
> +
> +/**
> + * spi_nor_otp_is_locked_sr2() - get the OTP region lock status
> + * @nor: pointer to 'struct spi_nor'
> + * @region: OTP region
> + *
> + * Retrieve the OTP region lock bit by reading the status register-2. This
> + * method is used on GigaDevice and Winbond flashes.
> + *
> + * Return: 0 on success, -errno otherwise.
> + */
> +int spi_nor_otp_is_locked_sr2(struct spi_nor *nor, unsigned int region)
> +{
> + int lock_bit;
> + u8 *sr2 = nor->bouncebuf;
> + int ret;
> +
> + lock_bit = spi_nor_otp_lock_bit_cr(region);
> + if (lock_bit < 0)
> + return lock_bit;
> +
> + ret = spi_nor_read_cr(nor, sr2);
> + if (ret)
> + return ret;
> +
> + return (*sr2 & lock_bit);
> +}
>
> static int spi_nor_init(struct spi_nor *nor)
> {
> diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
> index 7ec4add17b72..74bbb7aef1f1 100644
> --- a/drivers/mtd/spi-nor/core.h
> +++ b/drivers/mtd/spi-nor/core.h
> @@ -472,6 +472,10 @@ int spi_nor_otp_read_otp_mode(struct spi_nor *nor, loff_t from, uint64_t len, u8
> int spi_nor_otp_write_otp_mode(struct spi_nor *nor, loff_t to, uint64_t len, u8 *buf);
> int spi_nor_otp_lock_scur(struct spi_nor *nor, unsigned int region);
> int spi_nor_otp_is_locked_scur(struct spi_nor *nor, unsigned int region);
> +int spi_nor_otp_read_secr(struct spi_nor *nor, loff_t addr, uint64_t len, u8 *buf);
> +int spi_nor_otp_write_secr(struct spi_nor *nor, loff_t addr, uint64_t len, u8 *buf);
> +int spi_nor_otp_lock_sr2(struct spi_nor *nor, unsigned int region);
> +int spi_nor_otp_is_locked_sr2(struct spi_nor *nor, unsigned int region);
>
> int spi_nor_hwcaps_read2cmd(u32 hwcaps);
> u8 spi_nor_convert_3to4_read(u8 opcode);
> diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
> index 6dcde15fb1aa..3b14e96e993f 100644
> --- a/drivers/mtd/spi-nor/winbond.c
> +++ b/drivers/mtd/spi-nor/winbond.c
> @@ -55,14 +55,19 @@ static const struct flash_info winbond_parts[] = {
> { "w25q32", INFO(0xef4016, 0, 64 * 1024, 64, SECT_4K) },
> { "w25q32dw", INFO(0xef6016, 0, 64 * 1024, 64,
> SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
> - SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
> + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
> + OTP_INFO(256, 3, 0x1000, 0x1000)
> + },
> +
> { "w25q32jv", INFO(0xef7016, 0, 64 * 1024, 64,
> SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
> SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
> },
> { "w25q32jwm", INFO(0xef8016, 0, 64 * 1024, 64,
> SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
> - SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
> + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
> + OTP_INFO(256, 3, 0x1000, 0x1000)
> + },
> { "w25x64", INFO(0xef3017, 0, 64 * 1024, 128, SECT_4K) },
> { "w25q64", INFO(0xef4017, 0, 64 * 1024, 128,
> SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
> @@ -122,9 +127,18 @@ static int winbond_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
> return spi_nor_write_disable(nor);
> }
>
> +static const struct spi_nor_otp_ops winbond_otp_ops = {
> + .read = spi_nor_otp_read_secr,
> + .write = spi_nor_otp_write_secr,
> + .lock = spi_nor_otp_lock_sr2,
> + .is_locked = spi_nor_otp_is_locked_sr2,
> +};
> +
> static void winbond_default_init(struct spi_nor *nor)
> {
> nor->params->set_4byte_addr_mode = winbond_set_4byte_addr_mode;
> + if (nor->params->otp_info.n_otps)
> + nor->params->otp_ops = &winbond_otp_ops;
> }
>
> static const struct spi_nor_fixups winbond_fixups = {
> diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
> index 04195d3e43b8..4750fb631c96 100644
> --- a/include/linux/mtd/spi-nor.h
> +++ b/include/linux/mtd/spi-nor.h
> @@ -110,6 +110,11 @@
> #define SPINOR_OP_RD_EVCR 0x65 /* Read EVCR register */
> #define SPINOR_OP_WD_EVCR 0x61 /* Write EVCR register */
>
> +/* Used for GigaDevices and Winbond flashes. */
> +#define SPINOR_OP_ESECR 0x44 /* Erase Security registers */
> +#define SPINOR_OP_PSECR 0x42 /* Program Security registers */
> +#define SPINOR_OP_RSECR 0x48 /* Read Security registers */
> +
> /* Status Register bits. */
> #define SR_WIP BIT(0) /* Write in progress */
> #define SR_WEL BIT(1) /* Write enable latch */
> @@ -141,8 +146,13 @@
>
> /* Status Register 2 bits. */
> #define SR2_QUAD_EN_BIT1 BIT(1)
> +#define SR2_LB0 BIT(2) /* Security Register Lock Bit 0 */
> +#define SR2_LB1 BIT(3) /* Security Register Lock Bit 1 */
> +#define SR2_LB2 BIT(4) /* Security Register Lock Bit 2 */
> +#define SR2_LB3 BIT(5) /* Security Register Lock Bit 3 */
> #define SR2_QUAD_EN_BIT7 BIT(7)
>
> +
> /* Supported SPI protocols */
> #define SNOR_PROTO_INST_MASK GENMASK(23, 16)
> #define SNOR_PROTO_INST_SHIFT 16
>

2020-10-01 05:39:39

by Heiko Thiery

[permalink] [raw]
Subject: Re: [PATCH v2 3/4] mtd: spi-nor: implement OTP support for Macronix and similar flashes

Hi Michael,

Am Sa., 12. Sept. 2020 um 00:26 Uhr schrieb Michael Walle <[email protected]>:
>
> Use the new OTP ops to implement OTP access on Macronix flashes. The
> Macronix flashes provides one OTP area which is either programmed with
> an electrical serial number and locked by the factory or is empty and can
> be locked by the user. To keep things simple and because most devices
> will have unprogrammed OTP areas, we treat both options as user regions.
> If there will actually be an ESN preprogrammed, it will appear as a
> locked user region.
>
> This was tested on a Macronix MX25L6405D as well as on a Adesto
> AT25SL321.
>
> Signed-off-by: Michael Walle <[email protected]>

Reviewed-by: Heiko Thiery <[email protected]>

> ---
> drivers/mtd/spi-nor/atmel.c | 13 ++-
> drivers/mtd/spi-nor/core.c | 170 +++++++++++++++++++++++++++++++++
> drivers/mtd/spi-nor/core.h | 9 ++
> drivers/mtd/spi-nor/macronix.c | 13 ++-
> include/linux/mtd/spi-nor.h | 6 ++
> 5 files changed, 209 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/mtd/spi-nor/atmel.c b/drivers/mtd/spi-nor/atmel.c
> index 3f5f21a473a6..1688c9989c6b 100644
> --- a/drivers/mtd/spi-nor/atmel.c
> +++ b/drivers/mtd/spi-nor/atmel.c
> @@ -19,7 +19,8 @@ static const struct flash_info atmel_parts[] = {
> { "at25df641", INFO(0x1f4800, 0, 64 * 1024, 128, SECT_4K) },
>
> { "at25sl321", INFO(0x1f4216, 0, 64 * 1024, 64,
> - SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
> + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
> + OTP_INFO1(512, 0) },
>
> { "at26f004", INFO(0x1f0400, 0, 64 * 1024, 8, SECT_4K) },
> { "at26df081a", INFO(0x1f4501, 0, 64 * 1024, 16, SECT_4K) },
> @@ -29,9 +30,19 @@ static const struct flash_info atmel_parts[] = {
> { "at45db081d", INFO(0x1f2500, 0, 64 * 1024, 16, SECT_4K) },
> };
>
> +static const struct spi_nor_otp_ops atmel_otp_ops = {
> + .read = spi_nor_otp_read_otp_mode,
> + .write = spi_nor_otp_write_otp_mode,
> + .lock = spi_nor_otp_lock_scur,
> + .is_locked = spi_nor_otp_is_locked_scur,
> +};
> +
> static void atmel_default_init(struct spi_nor *nor)
> {
> nor->flags |= SNOR_F_HAS_LOCK;
> +
> + if (nor->params->otp_info.n_otps)
> + nor->params->otp_ops = &atmel_otp_ops;
> }
>
> static const struct spi_nor_fixups atmel_fixups = {
> diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
> index 4244f98e4948..348db19958e9 100644
> --- a/drivers/mtd/spi-nor/core.c
> +++ b/drivers/mtd/spi-nor/core.c
> @@ -2828,6 +2828,176 @@ static int spi_nor_unlock_all(struct spi_nor *nor)
> return 0;
> }
>
> +/**
> + * spi_nor_set_secured_otp_mode() - Set secured OTP mode
> + * @nor: pointer to 'struct spi_nor'.
> + * @enable: true to enter the secured OTP mode, false to exit the secured
> + * OTP mode.
> + *
> + * Enter and exit OTP mode by using the command SPINOR_OP_ENSO (B1h) and
> + * SPINOR_EP_EXSO (C1h) command.
> + *
> + * Return: 0 on success, -errno otherwise.
> + */
> +static int spi_nor_set_secured_otp_mode(struct spi_nor *nor, bool enable)
> +{
> + u8 cmd = enable ? SPINOR_OP_ENSO : SPINOR_OP_EXSO;
> + int ret;
> +
> + ret = spi_nor_simple_cmd(nor, cmd);
> + if (ret)
> + dev_dbg(nor->dev, "error %d setting secured OTP mode\n", ret);
> +
> + return ret;
> +}
> +
> +/**
> + * spi_nor_read_scur() - Read the Security Register using the SPINOR_OP_RDSCUR (2Bh) command.
> + * @nor: pointer to 'struct spi_nor'
> + * @scur: pointer to a DMA-able buffer where the value of the
> + * Security Register will be written.
> + *
> + * Return: 0 on success, -errno otherwise.
> + */
> +static int spi_nor_read_scur(struct spi_nor *nor, u8 *scur)
> +{
> + int ret;
> +
> + ret = spi_nor_simple_cmd_din(nor, SPINOR_OP_RDSCUR, scur, 1);
> + if (ret)
> + dev_dbg(nor->dev, "error %d reading SCUR\n", ret);
> +
> + return ret;
> +}
> +
> +/**
> + * spi_nor_write_scur() - Write the Security Register using the SPINOR_OP_WRSCUR (2Fh) command.
> + * @nor: pointer to 'struct spi_nor'
> + *
> + * This register contains only one OTP bit. The command doesn't take any
> + * arguments. In fact it _must not_ take any arugments. Otherwise the command
> + * is ignored.
> + *
> + * Return: 0 on success, -errno otherwise.
> + */
> +static int spi_nor_write_scur(struct spi_nor *nor)
> +{
> + int ret;
> +
> + ret = spi_nor_simple_cmd(nor, SPINOR_OP_WRSCUR);
> + if (ret)
> + dev_dbg(nor->dev, "error %d writing SCUR\n", ret);
> +
> + return ret;
> +}
> +
> +/**
> + * spi_nor_otp_read_otp_mode() - read OTP data
> + * @nor: pointer to 'struct spi_nor'
> + * @from: offset to read from
> + * @len: number of bytes to read
> + * @buf: pointer to dst buffer
> + *
> + * Read OTP data by using the ENSO and EXSO commands. This method is used on
> + * Adesto, Atmel, Macronix and Micron SPI flashes.
> + *
> + * Return: number of bytes read successfully, -errno otherwise
> + */
> +int spi_nor_otp_read_otp_mode(struct spi_nor *nor, loff_t from, uint64_t len, u8 *buf)
> +{
> + int ret;
> +
> + ret = spi_nor_set_secured_otp_mode(nor, true);
> + if (ret)
> + return ret;
> +
> + ret = spi_nor_read_data(nor, from, len, buf);
> +
> + spi_nor_set_secured_otp_mode(nor, false);
> +
> + return ret;
> +}
> +
> +/**
> + * spi_nor_otp_write_otp_mode() - write OTP data
> + * @nor: pointer to 'struct spi_nor'
> + * @to: offset to write to
> + * @len: number of bytes to write
> + * @buf: pointer to src buffer
> + *
> + * Write OTP data by using the ENSO and EXSO commands. This method is used on
> + * Adesto, Atmel, Macronix and Micron SPI flashes.
> + *
> + * Return: number of bytes written successfully, -errno otherwise
> + */
> +int spi_nor_otp_write_otp_mode(struct spi_nor *nor, loff_t to, uint64_t len, u8 *buf)
> +{
> + int ret;
> +
> + ret = spi_nor_set_secured_otp_mode(nor, true);
> + if (ret)
> + return ret;
> +
> + ret = spi_nor_write_enable(nor);
> + if (ret)
> + goto out;
> +
> + ret = spi_nor_write_data(nor, to, len, buf);
> + if (ret < 0)
> + goto out;
> +
> + ret = spi_nor_wait_till_ready(nor);
> +
> +out:
> + spi_nor_set_secured_otp_mode(nor, false);
> +
> + return ret;
> +}
> +
> +/**
> + * spi_nor_otp_lock_scur() - lock the OTP region
> + * @nor: pointer to 'struct spi_nor'
> + * @region: OTP region
> + *
> + * Lock the OTP region by writing the security register. This method is used on
> + * Adesto, Atmel, Macronix and Micron SPI flashes.
> + *
> + * Return: 0 on success, -errno otherwise.
> + */
> +int spi_nor_otp_lock_scur(struct spi_nor *nor, unsigned int region)
> +{
> + if (region != 0)
> + return -EINVAL;
> +
> + return spi_nor_write_scur(nor);
> +}
> +
> +/**
> + * spi_nor_otp_is_locked_otp_mode() - get the OTP region lock status
> + * @nor: pointer to 'struct spi_nor'
> + * @region: OTP region
> + *
> + * Retrieve the OTP region lock bit by reading the security register. This
> + * method is used on Adesto, Atmel, Macronix and Micron SPI flashes.
> + *
> + * Return: 0 on success, -errno otherwise.
> + */
> +int spi_nor_otp_is_locked_scur(struct spi_nor *nor, unsigned int region)
> +{
> + u8 *scur = nor->bouncebuf;
> + int ret;
> +
> + if (region != 0)
> + return -EINVAL;
> +
> + ret = spi_nor_read_scur(nor, scur);
> + if (ret)
> + return ret;
> +
> + return *scur & SCUR_LDSO;
> +}
> +
> +
> static int spi_nor_init(struct spi_nor *nor)
> {
> int err;
> diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
> index 516c5973bf88..7ec4add17b72 100644
> --- a/drivers/mtd/spi-nor/core.h
> +++ b/drivers/mtd/spi-nor/core.h
> @@ -414,6 +414,10 @@ struct flash_info {
> .otp_start_addr = (_otp_start_addr), \
> .otp_addr_offset = (_otp_addr_offset),
>
> +#define OTP_INFO1(_otp_size, _otp_start_addr) \
> + OTP_INFO(_otp_size, 1, _otp_start_addr, 0)
> +
> +
> /**
> * struct spi_nor_manufacturer - SPI NOR manufacturer object
> * @name: manufacturer name
> @@ -464,6 +468,11 @@ ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len,
> ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len,
> const u8 *buf);
>
> +int spi_nor_otp_read_otp_mode(struct spi_nor *nor, loff_t from, uint64_t len, u8 *buf);
> +int spi_nor_otp_write_otp_mode(struct spi_nor *nor, loff_t to, uint64_t len, u8 *buf);
> +int spi_nor_otp_lock_scur(struct spi_nor *nor, unsigned int region);
> +int spi_nor_otp_is_locked_scur(struct spi_nor *nor, unsigned int region);
> +
> int spi_nor_hwcaps_read2cmd(u32 hwcaps);
> u8 spi_nor_convert_3to4_read(u8 opcode);
> void spi_nor_set_pp_settings(struct spi_nor_pp_command *pp, u8 opcode,
> diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c
> index f97f3d127575..31198527f963 100644
> --- a/drivers/mtd/spi-nor/macronix.c
> +++ b/drivers/mtd/spi-nor/macronix.c
> @@ -42,7 +42,8 @@ static const struct flash_info macronix_parts[] = {
> { "mx25l1606e", INFO(0xc22015, 0, 64 * 1024, 32, SECT_4K) },
> { "mx25l3205d", INFO(0xc22016, 0, 64 * 1024, 64, SECT_4K) },
> { "mx25l3255e", INFO(0xc29e16, 0, 64 * 1024, 64, SECT_4K) },
> - { "mx25l6405d", INFO(0xc22017, 0, 64 * 1024, 128, SECT_4K) },
> + { "mx25l6405d", INFO(0xc22017, 0, 64 * 1024, 128, SECT_4K)
> + OTP_INFO1(64, 0) },
> { "mx25u2033e", INFO(0xc22532, 0, 64 * 1024, 4, SECT_4K) },
> { "mx25u3235f", INFO(0xc22536, 0, 64 * 1024, 64,
> SECT_4K | SPI_NOR_DUAL_READ |
> @@ -92,10 +93,20 @@ static const struct flash_info macronix_parts[] = {
> SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
> };
>
> +static const struct spi_nor_otp_ops macronix_otp_ops = {
> + .read = spi_nor_otp_read_otp_mode,
> + .write = spi_nor_otp_write_otp_mode,
> + .lock = spi_nor_otp_lock_scur,
> + .is_locked = spi_nor_otp_is_locked_scur,
> +};
> +
> static void macronix_default_init(struct spi_nor *nor)
> {
> nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable;
> nor->params->set_4byte_addr_mode = spi_nor_set_4byte_addr_mode;
> +
> + if (nor->params->otp_info.n_otps)
> + nor->params->otp_ops = &macronix_otp_ops;
> }
>
> static const struct spi_nor_fixups macronix_fixups = {
> diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
> index 081dbd386944..04195d3e43b8 100644
> --- a/include/linux/mtd/spi-nor.h
> +++ b/include/linux/mtd/spi-nor.h
> @@ -95,6 +95,12 @@
> /* Used for Macronix and Winbond flashes. */
> #define SPINOR_OP_EN4B 0xb7 /* Enter 4-byte mode */
> #define SPINOR_OP_EX4B 0xe9 /* Exit 4-byte mode */
> +#define SPINOR_OP_ENSO 0xb1 /* Enter secured OTP mode */
> +#define SPINOR_OP_EXSO 0xc1 /* Exit secured OTP mode */
> +#define SPINOR_OP_RDSCUR 0x2b /* Read security register */
> +#define SPINOR_OP_WRSCUR 0x2f /* Write security register */
> +#define SCUR_SO BIT(0) /* OTP factory secured */
> +#define SCUR_LDSO BIT(1) /* OTP user lock-down */
>
> /* Used for Spansion flashes only. */
> #define SPINOR_OP_BRWR 0x17 /* Bank register write */
>