summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/mtd/spi-nor/core.c2
-rw-r--r--drivers/mtd/spi-nor/core.h6
-rw-r--r--drivers/mtd/spi-nor/otp.c164
-rw-r--r--include/linux/mtd/spi-nor.h8
4 files changed, 179 insertions, 1 deletions
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 9e27ea727628..8cf3cf92129e 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 cfbc43c5cc57..e9b6b2e76cdb 100644
--- a/drivers/mtd/spi-nor/core.h
+++ b/drivers/mtd/spi-nor/core.h
@@ -495,6 +495,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,
@@ -502,6 +503,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 075b7290a95d..5021d40dffbf 100644
--- a/drivers/mtd/spi-nor/otp.c
+++ b/drivers/mtd/spi-nor/otp.c
@@ -14,6 +14,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
+ *
+ * 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;
+ 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, unsigned 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..98ed91b529ea 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,6 +143,9 @@
/* 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)
/* Supported SPI protocols */