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

2020-09-30 Thread Heiko Thiery
HI Michael,

Am Sa., 12. Sept. 2020 um 00:26 Uhr schrieb Michael Walle :
>
> 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 

Reviewed-by: Heiko Thiery 

> ---
>  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 

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

2020-09-11 Thread Michael Walle
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 
---
 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