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

From: Tudor.Ambarus
Date: Mon Mar 15 2021 - 04:21:44 EST


On 3/6/21 2:05 AM, Michael Walle wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
>
> Use the new OTP ops to implement OTP access on Winbond flashes. Most
> Winbond flashes provides up to four different OTP regions ("Security
> Registers").
>
> Winbond devices use a special opcode to read and write to the OTP
> regions, just like the RDSFDP opcode. In fact, it seems that the
> (undocumented) first OTP area of the newer flashes is the actual SFDP
> table.
>
> On a side note, Winbond devices also allow erasing the OTP regions as
> long as the area isn't locked down.
>
> Signed-off-by: Michael Walle <michael@xxxxxxxx>
> ---
> drivers/mtd/spi-nor/core.c | 2 +-
> drivers/mtd/spi-nor/core.h | 6 ++
> drivers/mtd/spi-nor/otp.c | 164 ++++++++++++++++++++++++++++++++++++
> include/linux/mtd/spi-nor.h | 9 ++
> 4 files changed, 180 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
> index 0c5c757fa95b..ef7df26896f1 100644
> --- a/drivers/mtd/spi-nor/core.c
> +++ b/drivers/mtd/spi-nor/core.c
> @@ -1034,7 +1034,7 @@ static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 sr1)
> *
> * Return: 0 on success, -errno otherwise.
> */
> -static int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr)
> +int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr)
> {
> int ret;
> u8 *sr_cr = nor->bouncebuf;
> diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
> index ec8da1243846..dfbf6ba42b57 100644
> --- a/drivers/mtd/spi-nor/core.h
> +++ b/drivers/mtd/spi-nor/core.h
> @@ -496,6 +496,7 @@ int spi_nor_read_sr(struct spi_nor *nor, u8 *sr);
> int spi_nor_read_cr(struct spi_nor *nor, u8 *cr);
> int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len);
> int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1);
> +int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr);
>
> int spi_nor_xread_sr(struct spi_nor *nor, u8 *sr);
> ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len,
> @@ -503,6 +504,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_secr(struct spi_nor *nor, loff_t addr, size_t len, u8 *buf);
> +int spi_nor_otp_write_secr(struct spi_nor *nor, loff_t addr, size_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);
> void spi_nor_set_read_settings(struct spi_nor_read_command *read,
> diff --git a/drivers/mtd/spi-nor/otp.c b/drivers/mtd/spi-nor/otp.c
> index 4e301fd5156b..4e8da9108c77 100644
> --- a/drivers/mtd/spi-nor/otp.c
> +++ b/drivers/mtd/spi-nor/otp.c
> @@ -15,6 +15,170 @@
> #define spi_nor_otp_region_len(nor) ((nor)->params->otp.org->len)
> #define spi_nor_otp_n_regions(nor) ((nor)->params->otp.org->n_regions)
>
> +/**
> + * 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

is buf DMA-able?

> + *
> + * Read OTP data from one region 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, size_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;

any winbond/gigadevice flashes with octal dtr support? Do they
provide SEC Register opcodes for octal dtr?

> + 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 to one region by using the SPINOR_OP_PSECR commands. This
> + * method is used on GigaDevice and Winbond flashes.
> + *
> + * Please note, the write must not span multiple OTP regions.
> + *
> + * Return: number of bytes written successfully, -errno otherwise
> + */
> +int spi_nor_otp_write_secr(struct spi_nor *nor, loff_t addr, size_t len, u8 *buf)
> +{
> + enum spi_nor_protocol write_proto;
> + struct spi_mem_dirmap_desc *wdesc;
> + u8 addr_width, program_opcode;
> + int ret, written;
> +
> + 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;
> +
> + written = spi_nor_write_data(nor, addr, len, buf);
> + if (written < 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 ?: written;
> +}
> +
> +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)
> +{
> + u8 *cr = nor->bouncebuf;
> + int ret, lock_bit;
> +
> + lock_bit = spi_nor_otp_lock_bit_cr(region);
> + if (lock_bit < 0)
> + return lock_bit;
> +
> + ret = spi_nor_read_cr(nor, cr);
> + if (ret)
> + return ret;
> +
> + /* no need to write the register if region is already locked */
> + if (cr[0] & lock_bit)
> + return 0;
> +
> + cr[0] |= lock_bit;
> +
> + return spi_nor_write_16bit_cr_and_check(nor, cr[0]);
> +}
> +
> +/**
> + * 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)
> +{
> + u8 *cr = nor->bouncebuf;
> + int ret, lock_bit;
> +
> + lock_bit = spi_nor_otp_lock_bit_cr(region);
> + if (lock_bit < 0)
> + return lock_bit;
> +
> + ret = spi_nor_read_cr(nor, cr);
> + if (ret)
> + return ret;
> +
> + return cr[0] & lock_bit;
> +}
> +
> static loff_t spi_nor_otp_region_start(const struct spi_nor *nor, int region)
> {
> const struct spi_nor_otp_organization *org = nor->params->otp.org;
> diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
> index a0d572855444..6d1956049e90 100644
> --- a/include/linux/mtd/spi-nor.h
> +++ b/include/linux/mtd/spi-nor.h
> @@ -107,6 +107,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 */
> @@ -138,8 +143,12 @@
>
> /* Status Register 2 bits. */
> #define SR2_QUAD_EN_BIT1 BIT(1)
> +#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)
>
> +

not needed. You can catch this when running ./scripts/checkpatch --strict patch-name

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