summaryrefslogtreecommitdiff
path: root/drivers/mtd/spi-nor/otp.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mtd/spi-nor/otp.c')
-rw-r--r--drivers/mtd/spi-nor/otp.c164
1 files changed, 164 insertions, 0 deletions
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;