Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > linux.kernel > #1603280 > unrolled thread
| Started by | Rahul Bedarkar <rahul.bedarkar@imgtec.com> |
|---|---|
| First post | 2017-03-17 13:50 +0100 |
| Last post | 2017-03-17 13:50 +0100 |
| Articles | 5 — 1 participant |
Back to article view | Back to linux.kernel
[RFC 0/6] Add user OTP support in SPI-NOR Rahul Bedarkar <rahul.bedarkar@imgtec.com> - 2017-03-17 13:50 +0100
[RFC 1/6] Revert "mtd: spi-nor: remove unused read_xfer/write_xfer hooks" Rahul Bedarkar <rahul.bedarkar@imgtec.com> - 2017-03-17 13:50 +0100
[RFC 2/6] mtd: spi-nor: change return value of read_xfer and write_xfer Rahul Bedarkar <rahul.bedarkar@imgtec.com> - 2017-03-17 13:50 +0100
[RFC 3/6] mtd: m25p80: don't pass spi_nor to helper methods Rahul Bedarkar <rahul.bedarkar@imgtec.com> - 2017-03-17 13:50 +0100
[RFC 5/6] mtd: spi-nor: add support to read/write user OTP Rahul Bedarkar <rahul.bedarkar@imgtec.com> - 2017-03-17 13:50 +0100
| From | Rahul Bedarkar <rahul.bedarkar@imgtec.com> |
|---|---|
| Date | 2017-03-17 13:50 +0100 |
| Subject | [RFC 0/6] Add user OTP support in SPI-NOR |
| Message-ID | <tlZup-6vs-3@gated-at.bofh.it> |
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 <dwmw2@infradead.org> Cc: Brian Norris <computersforpeace@gmail.com> Cc: Boris Brezillon <boris.brezillon@free-electrons.com> Cc: Marek Vasut <marek.vasut@gmail.com> Cc: Richard Weinberger <richard@nod.at> Cc: Cyrille Pitchen <cyrille.pitchen@atmel.com> 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
[toc] | [next] | [standalone]
| From | Rahul Bedarkar <rahul.bedarkar@imgtec.com> |
|---|---|
| Date | 2017-03-17 13:50 +0100 |
| Subject | [RFC 1/6] Revert "mtd: spi-nor: remove unused read_xfer/write_xfer hooks" |
| Message-ID | <tlZuq-6vs-11@gated-at.bofh.it> |
| In reply to | #1603280 |
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 <rahul.bedarkar@imgtec.com>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: Brian Norris <computersforpeace@gmail.com>
Cc: Boris Brezillon <boris.brezillon@free-electrons.com>
Cc: Marek Vasut <marek.vasut@gmail.com>
Cc: Richard Weinberger <richard@nod.at>
Cc: Cyrille Pitchen <cyrille.pitchen@atmel.com>
---
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
[toc] | [prev] | [next] | [standalone]
| From | Rahul Bedarkar <rahul.bedarkar@imgtec.com> |
|---|---|
| Date | 2017-03-17 13:50 +0100 |
| Subject | [RFC 2/6] mtd: spi-nor: change return value of read_xfer and write_xfer |
| Message-ID | <tlZuq-6vs-15@gated-at.bofh.it> |
| In reply to | #1603280 |
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 <rahul.bedarkar@imgtec.com>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: Brian Norris <computersforpeace@gmail.com>
Cc: Boris Brezillon <boris.brezillon@free-electrons.com>
Cc: Marek Vasut <marek.vasut@gmail.com>
Cc: Richard Weinberger <richard@nod.at>
Cc: Cyrille Pitchen <cyrille.pitchen@atmel.com>
---
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
[toc] | [prev] | [next] | [standalone]
| From | Rahul Bedarkar <rahul.bedarkar@imgtec.com> |
|---|---|
| Date | 2017-03-17 13:50 +0100 |
| Subject | [RFC 3/6] mtd: m25p80: don't pass spi_nor to helper methods |
| Message-ID | <tlZuq-6vs-17@gated-at.bofh.it> |
| In reply to | #1603280 |
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 <rahul.bedarkar@imgtec.com>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: Brian Norris <computersforpeace@gmail.com>
Cc: Boris Brezillon <boris.brezillon@free-electrons.com>
Cc: Marek Vasut <marek.vasut@gmail.com>
Cc: Richard Weinberger <richard@nod.at>
Cc: Cyrille Pitchen <cyrille.pitchen@atmel.com>
---
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
[toc] | [prev] | [next] | [standalone]
| From | Rahul Bedarkar <rahul.bedarkar@imgtec.com> |
|---|---|
| Date | 2017-03-17 13:50 +0100 |
| Subject | [RFC 5/6] mtd: spi-nor: add support to read/write user OTP |
| Message-ID | <tlZuq-6vs-25@gated-at.bofh.it> |
| In reply to | #1603280 |
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 <rahul.bedarkar@imgtec.com>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: Brian Norris <computersforpeace@gmail.com>
Cc: Boris Brezillon <boris.brezillon@free-electrons.com>
Cc: Marek Vasut <marek.vasut@gmail.com>
Cc: Richard Weinberger <richard@nod.at>
Cc: Cyrille Pitchen <cyrille.pitchen@atmel.com>
---
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
[toc] | [prev] | [standalone]
Back to top | Article view | linux.kernel
csiph-web