Many NOR flash chips comes with One-Time-Programmable area a.k.a
security registers. This RFC patchset aims at adding generic OTP
support in SPI-NOR to read/write user OTP area.
In SPI-NOR framework, OTP specific read/write methods will use
read_xfer/write_xfer hooks. So PATCH 1, reverts "unused read_xfer/
write_xfer hooks."
PATCH 2 changes return value of read_xfer/write_xfer to allow
returning amount of data transferred and errors as read(2)/write(2).
This makes them inline with current read/write hooks.
PATCH 3 modified helpers in m25p80.c so that those can be used
from read_xfer/write_xfer methods.
PATCH 4 implements read_xfer/write_xfer in m25p80.c
PATCH 5 implements generic support to read/write user OTP.
And finally in PATCH 6 user OTP support for s25fl016k is enabled.
Overall changes are as follows:
New flag SPI_NOR_HAS_OTP is added to specify chip has OTP area. Using
OTP_INFO macro, details of OTP area can be specified. It includes size
of area, number of OTP areas/banks, starting address and difference
between consecutive OTP banks. OTP framework will give user linear
address view from 0 to size of OTP area. User don't need to specify
actual physical address of OTP area for reading/writing.
Details which could be specific to manufacturer like opcodes for
read/write/erase can be specified based on manufacturer.
Generic OTP read/write methods are added so as it can be used for OTP
support from other manufacturer.
This complete patchset is tested with Winbond NOR flash W25Q16DV which
is detected as "s25fl016k" by SPI-NOR.
I've plans to add support for locking these OTP areas and reading
factory OTP area as well. But before that I would to get comments
on this initial part to understand if I'm going in right direction.
Cc: David Woodhouse <[email protected]>
Cc: Brian Norris <[email protected]>
Cc: Boris Brezillon <[email protected]>
Cc: Marek Vasut <[email protected]>
Cc: Richard Weinberger <[email protected]>
Cc: Cyrille Pitchen <[email protected]>
Rahul Bedarkar (6):
Revert "mtd: spi-nor: remove unused read_xfer/write_xfer hooks"
mtd: spi-nor: change return value of read_xfer and write_xfer
mtd: m25p80: don't pass spi_nor to helper methods
mtd: m25p80: implement read_xfer and write_xfer
mtd: spi-nor: add support to read/write user OTP
mtd: spi-nor: enable OTP support for s25fl016k
drivers/mtd/devices/m25p80.c | 111 ++++++++++++---
drivers/mtd/spi-nor/spi-nor.c | 314 +++++++++++++++++++++++++++++++++++++++++-
include/linux/mtd/spi-nor.h | 52 +++++++
3 files changed, 460 insertions(+), 17 deletions(-)
--
2.6.2
This reverts commit 79c452adb159dc9abc507ea13faec8d115a78758.
This was removed because hooks were never used by any driver. But
with upcoming OTP support in SPI-NOR, m25p80 driver will implement
it and will be used for reading/writing OTP area.
Signed-off-by: Rahul Bedarkar <[email protected]>
Cc: David Woodhouse <[email protected]>
Cc: Brian Norris <[email protected]>
Cc: Boris Brezillon <[email protected]>
Cc: Marek Vasut <[email protected]>
Cc: Richard Weinberger <[email protected]>
Cc: Cyrille Pitchen <[email protected]>
---
include/linux/mtd/spi-nor.h | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index f2a7180..0be3f86 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -126,6 +126,33 @@ enum read_mode {
SPI_NOR_QUAD,
};
+/**
+ * struct spi_nor_xfer_cfg - Structure for defining a Serial Flash transfer
+ * @wren: command for "Write Enable", or 0x00 for not required
+ * @cmd: command for operation
+ * @cmd_pins: number of pins to send @cmd (1, 2, 4)
+ * @addr: address for operation
+ * @addr_pins: number of pins to send @addr (1, 2, 4)
+ * @addr_width: number of address bytes
+ * (3,4, or 0 for address not required)
+ * @mode: mode data
+ * @mode_pins: number of pins to send @mode (1, 2, 4)
+ * @mode_cycles: number of mode cycles (0 for mode not required)
+ * @dummy_cycles: number of dummy cycles (0 for dummy not required)
+ */
+struct spi_nor_xfer_cfg {
+ u8 wren;
+ u8 cmd;
+ u8 cmd_pins;
+ u32 addr;
+ u8 addr_pins;
+ u8 addr_width;
+ u8 mode;
+ u8 mode_pins;
+ u8 mode_cycles;
+ u8 dummy_cycles;
+};
+
#define SPI_NOR_MAX_CMD_SIZE 8
enum spi_nor_ops {
SPI_NOR_OPS_READ = 0,
@@ -157,11 +184,14 @@ enum spi_nor_option_flags {
* @flash_read: the mode of the read
* @sst_write_second: used by the SST write operation
* @flags: flag options for the current SPI-NOR (SNOR_F_*)
+ * @cfg: used by the read_xfer/write_xfer
* @cmd_buf: used by the write_reg
* @prepare: [OPTIONAL] do some preparations for the
* read/write/erase/lock/unlock operations
* @unprepare: [OPTIONAL] do some post work after the
* read/write/erase/lock/unlock operations
+ * @read_xfer: [OPTIONAL] the read fundamental primitive
+ * @write_xfer: [OPTIONAL] the writefundamental primitive
* @read_reg: [DRIVER-SPECIFIC] read out the register
* @write_reg: [DRIVER-SPECIFIC] write data to the register
* @read: [DRIVER-SPECIFIC] read data from the SPI NOR
@@ -188,10 +218,15 @@ struct spi_nor {
enum read_mode flash_read;
bool sst_write_second;
u32 flags;
+ struct spi_nor_xfer_cfg cfg;
u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE];
int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops);
void (*unprepare)(struct spi_nor *nor, enum spi_nor_ops ops);
+ int (*read_xfer)(struct spi_nor *nor, struct spi_nor_xfer_cfg *cfg,
+ u8 *buf, size_t len);
+ int (*write_xfer)(struct spi_nor *nor, struct spi_nor_xfer_cfg *cfg,
+ u8 *buf, size_t len);
int (*read_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len);
int (*write_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len);
--
2.6.2
Change return value of read_xfer and write_xfer to allow returning
amount of data transferred and errors as read(2)/write(2) does.
This makes read_xfer/write_xfer inline with read/write hooks.
Inspired-from: Commit 59451e1233bd ("mtd: spi-nor: change return value of read/write")
Signed-off-by: Rahul Bedarkar <[email protected]>
Cc: David Woodhouse <[email protected]>
Cc: Brian Norris <[email protected]>
Cc: Boris Brezillon <[email protected]>
Cc: Marek Vasut <[email protected]>
Cc: Richard Weinberger <[email protected]>
Cc: Cyrille Pitchen <[email protected]>
---
include/linux/mtd/spi-nor.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 0be3f86..64b4a54 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -223,9 +223,9 @@ struct spi_nor {
int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops);
void (*unprepare)(struct spi_nor *nor, enum spi_nor_ops ops);
- int (*read_xfer)(struct spi_nor *nor, struct spi_nor_xfer_cfg *cfg,
+ ssize_t (*read_xfer)(struct spi_nor *nor, struct spi_nor_xfer_cfg *cfg,
u8 *buf, size_t len);
- int (*write_xfer)(struct spi_nor *nor, struct spi_nor_xfer_cfg *cfg,
+ ssize_t (*write_xfer)(struct spi_nor *nor, struct spi_nor_xfer_cfg *cfg,
u8 *buf, size_t len);
int (*read_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len);
int (*write_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len);
--
2.6.2
Helper methods m25p_addr2cmd, m25p80_rx_nbits and m25p_cmdsz accepts
spi_nor. But with upcoming implementation of read_xfer and write_xfer,
we need to pass addr_width and flash_read from cfg.
Signed-off-by: Rahul Bedarkar <[email protected]>
Cc: David Woodhouse <[email protected]>
Cc: Brian Norris <[email protected]>
Cc: Boris Brezillon <[email protected]>
Cc: Marek Vasut <[email protected]>
Cc: Richard Weinberger <[email protected]>
Cc: Cyrille Pitchen <[email protected]>
---
drivers/mtd/devices/m25p80.c | 32 ++++++++++++++++----------------
1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c
index c4df3b1..8368249 100644
--- a/drivers/mtd/devices/m25p80.c
+++ b/drivers/mtd/devices/m25p80.c
@@ -47,18 +47,18 @@ static int m25p80_read_reg(struct spi_nor *nor, u8 code, u8 *val, int len)
return ret;
}
-static void m25p_addr2cmd(struct spi_nor *nor, unsigned int addr, u8 *cmd)
+static void m25p_addr2cmd(unsigned int addr, u8 addr_width, u8 *cmd)
{
/* opcode is in cmd[0] */
- cmd[1] = addr >> (nor->addr_width * 8 - 8);
- cmd[2] = addr >> (nor->addr_width * 8 - 16);
- cmd[3] = addr >> (nor->addr_width * 8 - 24);
- cmd[4] = addr >> (nor->addr_width * 8 - 32);
+ cmd[1] = addr >> (addr_width * 8 - 8);
+ cmd[2] = addr >> (addr_width * 8 - 16);
+ cmd[3] = addr >> (addr_width * 8 - 24);
+ cmd[4] = addr >> (addr_width * 8 - 32);
}
-static int m25p_cmdsz(struct spi_nor *nor)
+static int m25p_cmdsz(u8 addr_width)
{
- return 1 + nor->addr_width;
+ return 1 + addr_width;
}
static int m25p80_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
@@ -80,7 +80,7 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
struct spi_device *spi = flash->spi;
struct spi_transfer t[2] = {};
struct spi_message m;
- int cmd_sz = m25p_cmdsz(nor);
+ int cmd_sz = m25p_cmdsz(nor->addr_width);
ssize_t ret;
spi_message_init(&m);
@@ -89,7 +89,7 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
cmd_sz = 1;
flash->command[0] = nor->program_opcode;
- m25p_addr2cmd(nor, to, flash->command);
+ m25p_addr2cmd(to, nor->addr_width, flash->command);
t[0].tx_buf = flash->command;
t[0].len = cmd_sz;
@@ -109,9 +109,9 @@ static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
return ret;
}
-static inline unsigned int m25p80_rx_nbits(struct spi_nor *nor)
+static inline unsigned int m25p80_rx_nbits(enum read_mode flash_read)
{
- switch (nor->flash_read) {
+ switch (flash_read) {
case SPI_NOR_DUAL:
return 2;
case SPI_NOR_QUAD:
@@ -152,7 +152,7 @@ static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
/* TODO: Support other combinations */
msg.opcode_nbits = SPI_NBITS_SINGLE;
msg.addr_nbits = SPI_NBITS_SINGLE;
- msg.data_nbits = m25p80_rx_nbits(nor);
+ msg.data_nbits = m25p80_rx_nbits(nor->flash_read);
ret = spi_flash_read(spi, &msg);
if (ret < 0)
@@ -164,14 +164,14 @@ static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
memset(t, 0, (sizeof t));
flash->command[0] = nor->read_opcode;
- m25p_addr2cmd(nor, from, flash->command);
+ m25p_addr2cmd(from, nor->addr_width, flash->command);
t[0].tx_buf = flash->command;
- t[0].len = m25p_cmdsz(nor) + dummy;
+ t[0].len = m25p_cmdsz(nor->addr_width) + dummy;
spi_message_add_tail(&t[0], &m);
t[1].rx_buf = buf;
- t[1].rx_nbits = m25p80_rx_nbits(nor);
+ t[1].rx_nbits = m25p80_rx_nbits(nor->flash_read);
t[1].len = min3(len, spi_max_transfer_size(spi),
spi_max_message_size(spi) - t[0].len);
spi_message_add_tail(&t[1], &m);
@@ -180,7 +180,7 @@ static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
if (ret)
return ret;
- ret = m.actual_length - m25p_cmdsz(nor) - dummy;
+ ret = m.actual_length - m25p_cmdsz(nor->addr_width) - dummy;
if (ret < 0)
return -EIO;
return ret;
--
2.6.2
Implement read_xfer and write_xfer interfaces provided by SPI-NOR.
These will be used in upcoming OTP support in SPI-NOR to read/write
OTP area.
Signed-off-by: Rahul Bedarkar <[email protected]>
Cc: David Woodhouse <[email protected]>
Cc: Brian Norris <[email protected]>
Cc: Boris Brezillon <[email protected]>
Cc: Marek Vasut <[email protected]>
Cc: Richard Weinberger <[email protected]>
Cc: Cyrille Pitchen <[email protected]>
---
drivers/mtd/devices/m25p80.c | 79 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 79 insertions(+)
diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c
index 8368249..a3ba907 100644
--- a/drivers/mtd/devices/m25p80.c
+++ b/drivers/mtd/devices/m25p80.c
@@ -186,6 +186,83 @@ static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
return ret;
}
+static ssize_t m25p80_read_xfer(struct spi_nor *nor,
+ struct spi_nor_xfer_cfg *cfg,
+ u8 *buf, size_t len)
+{
+ struct m25p *flash = nor->priv;
+ struct spi_device *spi = flash->spi;
+ struct spi_transfer t[2] = {};
+ struct spi_message m;
+ unsigned int dummy = cfg->dummy_cycles;
+ ssize_t ret;
+
+ /* convert the dummy cycles to the number of bytes */
+ dummy /= 8;
+
+ spi_message_init(&m);
+
+ flash->command[0] = cfg->cmd;
+ m25p_addr2cmd(cfg->addr, cfg->addr_width, flash->command);
+
+ t[0].tx_buf = flash->command;
+ t[0].len = m25p_cmdsz(cfg->addr_width) + dummy;
+ spi_message_add_tail(&t[0], &m);
+
+ t[1].rx_buf = buf;
+ t[1].rx_nbits = m25p80_rx_nbits(cfg->mode);
+ t[1].len = len;
+ spi_message_add_tail(&t[1], &m);
+
+ ret = spi_sync(spi, &m);
+ if (ret)
+ return ret;
+
+ ret = m.actual_length - m25p_cmdsz(cfg->addr_width) - dummy;
+ if (ret < 0)
+ return -EIO;
+ return ret;
+}
+
+static ssize_t m25p80_write_xfer(struct spi_nor *nor,
+ struct spi_nor_xfer_cfg *cfg,
+ u8 *buf, size_t len)
+{
+ struct m25p *flash = nor->priv;
+ struct spi_device *spi = flash->spi;
+ struct spi_transfer t[2] = {};
+ struct spi_message m;
+ unsigned int dummy = cfg->dummy_cycles;
+ ssize_t ret;
+
+ /* convert the dummy cycles to the number of bytes */
+ dummy /= 8;
+
+ spi_message_init(&m);
+
+ flash->command[0] = cfg->cmd;
+ m25p_addr2cmd(cfg->addr, cfg->addr_width, flash->command);
+
+ t[0].tx_buf = flash->command;
+ t[0].len = m25p_cmdsz(cfg->addr_width) + dummy;
+ spi_message_add_tail(&t[0], &m);
+
+ if (len) {
+ t[1].tx_buf = buf;
+ t[1].len = len;
+ spi_message_add_tail(&t[1], &m);
+ }
+
+ ret = spi_sync(spi, &m);
+ if (ret)
+ return ret;
+
+ ret = m.actual_length - m25p_cmdsz(cfg->addr_width) - dummy;
+ if (ret < 0)
+ return -EIO;
+ return ret;
+}
+
/*
* board specific setup should have ensured the SPI clock used here
* matches what the READ command supports, at least until this driver
@@ -213,6 +290,8 @@ static int m25p_probe(struct spi_device *spi)
nor->write = m25p80_write;
nor->write_reg = m25p80_write_reg;
nor->read_reg = m25p80_read_reg;
+ nor->read_xfer = m25p80_read_xfer;
+ nor->write_xfer = m25p80_write_xfer;
nor->dev = &spi->dev;
spi_nor_set_flash_node(nor, spi->dev.of_node);
--
2.6.2
s25fl016k has 3 OTP areas of 256 bytes each. First bank starts at
0x1000, second at 0x2000 and third at 0x3000.
When OTP area is unlocked, it can be rewritten. Lock bit for first,
second and third area is bit 3, 4, 5 of Status Register 2.
Signed-off-by: Rahul Bedarkar <[email protected]>
Cc: David Woodhouse <[email protected]>
Cc: Brian Norris <[email protected]>
Cc: Boris Brezillon <[email protected]>
Cc: Marek Vasut <[email protected]>
Cc: Richard Weinberger <[email protected]>
Cc: Cyrille Pitchen <[email protected]>
---
drivers/mtd/spi-nor/spi-nor.c | 36 +++++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index a91fa4c..0fd7573 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -1077,7 +1077,11 @@ static const struct flash_info spi_nor_ids[] = {
{ "s25sl064a", INFO(0x010216, 0, 64 * 1024, 128, 0) },
{ "s25fl004k", INFO(0xef4013, 0, 64 * 1024, 8, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
{ "s25fl008k", INFO(0xef4014, 0, 64 * 1024, 16, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
- { "s25fl016k", INFO(0xef4015, 0, 64 * 1024, 32, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
+ {
+ "s25fl016k", INFO(0xef4015, 0, 64 * 1024, 32,
+ SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_HAS_OTP)
+ OTP_INFO(256, 3, 0x1000, 0x1000)
+ },
{ "s25fl064k", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) },
{ "s25fl116k", INFO(0x014015, 0, 64 * 1024, 32, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
{ "s25fl132k", INFO(0x014016, 0, 64 * 1024, 64, SECT_4K) },
@@ -1586,6 +1590,28 @@ static loff_t spi_nor_otp_offset_to_addr(struct spi_nor *nor, loff_t offset)
return addr;
}
+static int winbond_get_user_otp_info(struct mtd_info *mtd, size_t len,
+ size_t *retlen, struct otp_info *otpinfo)
+{
+ int i, ret;
+ struct spi_nor *nor = mtd_to_spi_nor(mtd);
+
+ ret = read_cr(nor);
+ if (ret < 0)
+ return ret;
+
+#define WINBOND_SR2_LB1_BIT 3 /* Lock bit for security register 1 */
+
+ for (i = 0; i < nor->n_otps; i++) {
+ otpinfo[i].start = i * nor->otp_size;
+ otpinfo[i].length = nor->otp_size;
+ otpinfo[i].locked = !!(ret & BIT(WINBOND_SR2_LB1_BIT + i));
+ }
+
+ *retlen = nor->n_otps * sizeof(*otpinfo);
+ return 0;
+}
+
static ssize_t spi_nor_read_security_reg(struct spi_nor *nor, loff_t from,
size_t len, u_char *buf)
{
@@ -1783,6 +1809,14 @@ static int spi_nor_set_otp_info(struct spi_nor *nor,
}
switch (JEDEC_MFR(info)) {
+ case SNOR_MFR_WINBOND:
+ nor->otp_read_opcode = 0x48;
+ nor->otp_program_opcode = 0x42;
+ nor->otp_erase_opcode = 0x44;
+ nor->otp_read_dummy = 8;
+
+ mtd->_get_user_prot_info = winbond_get_user_otp_info;
+ break;
default:
return -EINVAL;
}
--
2.6.2
Many NOR flash chips have One-Time-Programmable area a.k.a security
registers. This patch adds generic support to read/write user OTP.
OTP specific read/write methods will use interfaces read_xfer/
write_xfer. Based on manufacturer, specific details like read, write,
erase opcode can be set.
SPI_NOR_HAS_OTP flag is introduced to notify that chips has OTP area.
OTP_INFO macro is introduced to specify details of OTP which may vary
from chip to chip. OTP_INFO includes size of OTP area, number of OTP
areas/banks, starting address of OTP area and difference between
consecutive OTP banks if there are many OTP areas.
At least s25fl016k chips has three OTP areas with starting addresses
0x1000, 0x2000, 0x3000. This generic support gives user space, linear
address view of OTP rather than user specifying actual physical
addresses. That is for s25fl016k chip, linear address will be
0x000-0x2FF. Then this framework will convert it to actual physical
address while reading/writing OTP area.
_get_user_prot_info hook is likely to be specific to manufacturer as
it involves reading status register to get status of lock bits for OTP.
Signed-off-by: Rahul Bedarkar <[email protected]>
Cc: David Woodhouse <[email protected]>
Cc: Brian Norris <[email protected]>
Cc: Boris Brezillon <[email protected]>
Cc: Marek Vasut <[email protected]>
Cc: Richard Weinberger <[email protected]>
Cc: Cyrille Pitchen <[email protected]>
---
drivers/mtd/spi-nor/spi-nor.c | 278 ++++++++++++++++++++++++++++++++++++++++++
include/linux/mtd/spi-nor.h | 17 +++
2 files changed, 295 insertions(+)
diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index 747645c..a91fa4c 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -17,6 +17,7 @@
#include <linux/mutex.h>
#include <linux/math64.h>
#include <linux/sizes.h>
+#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/of_platform.h>
@@ -85,6 +86,14 @@ struct flash_info {
* Use dedicated 4byte address op codes
* to support memory size above 128Mib.
*/
+#define SPI_NOR_HAS_OTP BIT(12) /* Flash supports OTP */
+
+ unsigned int otp_size; /* OTP size in bytes */
+ u16 n_otps; /* Number of OTP banks */
+ loff_t otp_start_addr; /* Starting address of OTP area */
+
+ /* Difference between consecutive OTP banks if there are many */
+ loff_t otp_addr_offset;
};
#define JEDEC_MFR(info) ((info)->id[0])
@@ -920,6 +929,12 @@ static int spi_nor_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len)
.addr_width = 3, \
.flags = SPI_NOR_NO_FR | SPI_S3AN,
+#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),
+
/* NOTE: double check command sets and memory organization when you add
* more nor chips. This current list focusses on newer chips, which
* have been converging on command sets which including JEDEC ID.
@@ -1526,6 +1541,262 @@ static int s3an_nor_scan(const struct flash_info *info, struct spi_nor *nor)
return 0;
}
+/*
+ * For given actual OTP address find the start address of OTP register/bank
+ */
+static inline loff_t spi_nor_otp_addr_to_start_addr(struct spi_nor *nor,
+ loff_t addr)
+{
+ loff_t last_bank_addr;
+
+ if (nor->otp_addr_offset)
+ last_bank_addr = nor->n_otps * nor->otp_addr_offset;
+ else
+ last_bank_addr = nor->otp_start_addr;
+
+ return addr & (last_bank_addr);
+}
+
+/*
+ * For given actual OTP address find the relative address from start of OTP
+ * register/bank
+ */
+static inline loff_t spi_nor_otp_addr_to_offset(struct spi_nor *nor,
+ loff_t addr)
+{
+ return addr & (nor->otp_size - 1);
+}
+
+/*
+ * For given linear OTP address find the actual OTP address
+ */
+static loff_t spi_nor_otp_offset_to_addr(struct spi_nor *nor, loff_t offset)
+{
+ int i;
+ loff_t addr = nor->otp_start_addr;
+
+ for (i = 0; i < nor->n_otps; i++) {
+ if (offset < ((i + 1) * nor->otp_size)) {
+ addr |= offset & (nor->otp_size - 1);
+ break;
+ }
+ addr += nor->otp_addr_offset;
+ }
+
+ return addr;
+}
+
+static ssize_t spi_nor_read_security_reg(struct spi_nor *nor, loff_t from,
+ size_t len, u_char *buf)
+{
+ int ret;
+ struct spi_nor_xfer_cfg cfg = {};
+
+ ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_READ);
+ if (ret)
+ return ret;
+
+ cfg.cmd = nor->otp_read_opcode;
+ cfg.addr = from;
+ cfg.addr_width = nor->addr_width;
+ cfg.mode = SPI_NOR_NORMAL;
+ cfg.dummy_cycles = nor->otp_read_dummy;
+
+ ret = nor->read_xfer(nor, &cfg, buf, len);
+ spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_READ);
+ return ret;
+}
+
+static int spi_nor_read_user_otp(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ int i;
+ int ret;
+ loff_t end_addr, reg_offset, new_addr;
+ size_t read_len;
+ struct spi_nor *nor = mtd_to_spi_nor(mtd);
+ size_t total_size = nor->otp_size * nor->n_otps;
+
+ if (from < 0 || from > total_size || (from + len) > total_size)
+ return -EINVAL;
+
+ if (!len)
+ return 0;
+
+ end_addr = from + len;
+ read_len = 0;
+
+ for (i = from; i < end_addr; i += read_len) {
+ reg_offset = i & (nor->otp_size - 1);
+
+ if (reg_offset) {
+ if ((reg_offset + len) <= nor->otp_size)
+ read_len = len;
+ else
+ read_len = nor->otp_size - reg_offset;
+ } else if ((end_addr - i) < nor->otp_size)
+ read_len = end_addr - i;
+ else
+ read_len = nor->otp_size;
+
+ new_addr = spi_nor_otp_offset_to_addr(nor, i);
+
+ ret = spi_nor_read_security_reg(nor, new_addr, read_len,
+ buf + (i - from));
+ if (ret < 0)
+ return ret;
+
+ *retlen += ret;
+ }
+
+ return 0;
+}
+
+static int spi_nor_erase_security_reg(struct spi_nor *nor, loff_t offset)
+{
+ int ret;
+ struct spi_nor_xfer_cfg cfg = {};
+
+ write_enable(nor);
+
+ cfg.cmd = nor->otp_erase_opcode;
+ cfg.addr = offset;
+ cfg.addr_width = nor->addr_width;
+ cfg.mode = SPI_NOR_NORMAL;
+
+ ret = nor->write_xfer(nor, &cfg, NULL, 0);
+
+ if (ret < 0)
+ return ret;
+
+ return spi_nor_wait_till_ready(nor);
+}
+
+static ssize_t spi_nor_write_security_reg(struct spi_nor *nor, loff_t to,
+ size_t len, u_char *buf)
+{
+ int ret;
+ struct spi_nor_xfer_cfg cfg = {};
+ u8 *reg_buf;
+ ssize_t written = 0;
+
+ reg_buf = kmalloc(nor->otp_size, GFP_KERNEL);
+ if (!reg_buf)
+ return -ENOMEM;
+
+ ret = spi_nor_read_security_reg(nor,
+ spi_nor_otp_addr_to_start_addr(nor, to),
+ nor->otp_size, reg_buf);
+ if (ret < 0)
+ goto free_buf;
+
+ memcpy(reg_buf + spi_nor_otp_addr_to_offset(nor, to), buf, len);
+
+ ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_WRITE);
+ if (ret)
+ goto free_buf;
+
+ ret = spi_nor_erase_security_reg(nor,
+ spi_nor_otp_addr_to_start_addr(nor, to));
+ if (ret)
+ goto unlock;
+
+ cfg.cmd = nor->otp_program_opcode;
+ cfg.addr = spi_nor_otp_addr_to_start_addr(nor, to);
+ cfg.addr_width = nor->addr_width;
+ cfg.mode = SPI_NOR_NORMAL;
+
+ write_enable(nor);
+
+ ret = nor->write_xfer(nor, &cfg, reg_buf, nor->otp_size);
+ if (ret < 0)
+ goto unlock;
+
+ written = ret;
+
+ ret = spi_nor_wait_till_ready(nor);
+ if (!ret)
+ ret = written;
+
+unlock:
+ spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE);
+free_buf:
+ kfree(reg_buf);
+
+ return ret;
+}
+
+static int spi_nor_write_user_otp(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ int ret;
+ int i;
+ loff_t end_addr, reg_offset, new_addr;
+ size_t write_len;
+ struct spi_nor *nor = mtd_to_spi_nor(mtd);
+ size_t total_size = nor->otp_size * nor->n_otps;
+
+ if (to < 0 || to > total_size || (to + len) > total_size)
+ return -EINVAL;
+
+ if (!len)
+ return 0;
+
+ end_addr = to + len;
+ write_len = 0;
+
+ for (i = to; i < end_addr; i += write_len) {
+ reg_offset = i & (nor->otp_size - 1);
+
+ if (reg_offset) {
+ if ((reg_offset + len) <= nor->otp_size)
+ write_len = len;
+ else
+ write_len = nor->otp_size - reg_offset;
+ } else if ((end_addr - i) < nor->otp_size)
+ write_len = end_addr - i;
+ else
+ write_len = nor->otp_size;
+
+ new_addr = spi_nor_otp_offset_to_addr(nor, i);
+
+ ret = spi_nor_write_security_reg(nor, new_addr, write_len,
+ buf + (i - to));
+ if (ret < 0)
+ return ret;
+
+ *retlen += ret;
+ }
+
+ return ret;
+}
+
+static int spi_nor_set_otp_info(struct spi_nor *nor,
+ const struct flash_info *info)
+{
+ struct mtd_info *mtd = &nor->mtd;
+
+ if (!nor->read_xfer || !nor->write_xfer) {
+ dev_err(nor->dev,
+ "OTP support needs read_xfer and write_xfer hooks\n");
+ return -EINVAL;
+ }
+
+ switch (JEDEC_MFR(info)) {
+ default:
+ return -EINVAL;
+ }
+
+ nor->otp_size = info->otp_size;
+ nor->n_otps = info->n_otps;
+ nor->otp_start_addr = info->otp_start_addr;
+ nor->otp_addr_offset = info->otp_addr_offset;
+
+ mtd->_read_user_prot_reg = spi_nor_read_user_otp;
+ mtd->_write_user_prot_reg = spi_nor_write_user_otp;
+ return 0;
+}
+
int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
{
const struct flash_info *info = NULL;
@@ -1728,6 +1999,13 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
return ret;
}
+ if (info->flags & SPI_NOR_HAS_OTP) {
+ ret = spi_nor_set_otp_info(nor, info);
+ if (ret)
+ dev_warn(dev, "can't enable OTP support for %s\n",
+ info->name);
+ }
+
dev_info(dev, "%s (%lld Kbytes)\n", info->name,
(long long)mtd->size >> 10);
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 64b4a54..f32b46f 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -186,6 +186,14 @@ enum spi_nor_option_flags {
* @flags: flag options for the current SPI-NOR (SNOR_F_*)
* @cfg: used by the read_xfer/write_xfer
* @cmd_buf: used by the write_reg
+ * @otp_size: size of OTP bank in bytes
+ * @n_otps: number of OTP banks
+ * @otp_start_addr: starting address of OTP
+ * @otp_addr_offset: difference between consecutive OTP banks
+ * @otp_erase_opcode: the opcode for erasing a OTP bank
+ * @otp_read_opcode: the read opcode for OTP
+ * @otp_program_opcode: the program opcode for OTP
+ * @otp_read_dummy: the dummy needed by the read operation for OTP
* @prepare: [OPTIONAL] do some preparations for the
* read/write/erase/lock/unlock operations
* @unprepare: [OPTIONAL] do some post work after the
@@ -220,6 +228,15 @@ struct spi_nor {
u32 flags;
struct spi_nor_xfer_cfg cfg;
u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE];
+ unsigned int otp_size;
+ u16 n_otps;
+ loff_t otp_start_addr;
+ loff_t otp_addr_offset;
+ u8 otp_erase_opcode;
+ u8 otp_read_opcode;
+ u8 otp_program_opcode;
+ u8 otp_read_dummy;
+
int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops);
void (*unprepare)(struct spi_nor *nor, enum spi_nor_ops ops);
--
2.6.2