]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
mtd: spinand: add OTP support
authorMartin Kurbanov <mmkurbanov@salutedevices.com>
Mon, 10 Feb 2025 14:34:14 +0000 (17:34 +0300)
committerMiquel Raynal <miquel.raynal@bootlin.com>
Mon, 10 Feb 2025 14:52:59 +0000 (15:52 +0100)
The MTD subsystem already supports accessing two OTP areas: user and
factory. User areas can be written by the user.

This patch provides the SPINAND_FACT_OTP_INFO and SPINAND_USER_OTP_INFO
macros to add parameters to spinand_info.
To implement OTP operations, the client (flash driver) is provided with
callbacks for user area:
    .read(), .write(), .info(), .lock(), .erase();
and for factory area:
    .read(), .info();

Signed-off-by: Martin Kurbanov <mmkurbanov@salutedevices.com>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
drivers/mtd/nand/spi/Makefile
drivers/mtd/nand/spi/core.c
drivers/mtd/nand/spi/otp.c [new file with mode: 0644]
include/linux/mtd/spinand.h

index 1e61ab21893accf264a57ff4a3b73a445db3e9d0..258da42451a4598d25f4ed5b28d4320031153eb7 100644 (file)
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
-spinand-objs := core.o alliancememory.o ato.o esmt.o foresee.o gigadevice.o macronix.o
+spinand-objs := core.o otp.o
+spinand-objs += alliancememory.o ato.o esmt.o foresee.o gigadevice.o macronix.o
 spinand-objs += micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o
 obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
index 2b6ffd6fc90dad9c6515fdd92eb8ed51ec941dc4..0b9bf321afb631411a2380308ce8d768448035c4 100644 (file)
@@ -1308,6 +1308,8 @@ int spinand_match_and_init(struct spinand_device *spinand,
                spinand->id.len = 1 + table[i].devid.len;
                spinand->select_target = table[i].select_target;
                spinand->set_cont_read = table[i].set_cont_read;
+               spinand->fact_otp = &table[i].fact_otp;
+               spinand->user_otp = &table[i].user_otp;
 
                op = spinand_select_op_variant(spinand,
                                               info->op_variants.read_cache);
@@ -1494,6 +1496,12 @@ static int spinand_init(struct spinand_device *spinand)
        mtd->_max_bad_blocks = nanddev_mtd_max_bad_blocks;
        mtd->_resume = spinand_mtd_resume;
 
+       if (spinand_user_otp_size(spinand) || spinand_fact_otp_size(spinand)) {
+               ret = spinand_set_mtd_otp_ops(spinand);
+               if (ret)
+                       goto err_cleanup_ecc_engine;
+       }
+
        if (nand->ecc.engine) {
                ret = mtd_ooblayout_count_freebytes(mtd);
                if (ret < 0)
diff --git a/drivers/mtd/nand/spi/otp.c b/drivers/mtd/nand/spi/otp.c
new file mode 100644 (file)
index 0000000..172e62e
--- /dev/null
@@ -0,0 +1,244 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025, SaluteDevices. All Rights Reserved.
+ *
+ * Author: Martin Kurbanov <mmkurbanov@salutedevices.com>
+ */
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/spinand.h>
+
+static size_t spinand_otp_size(struct spinand_device *spinand,
+                              const struct spinand_otp_layout *layout)
+{
+       struct nand_device *nand = spinand_to_nand(spinand);
+       size_t otp_pagesize = nanddev_page_size(nand) +
+                             nanddev_per_page_oobsize(nand);
+
+       return layout->npages * otp_pagesize;
+}
+
+/**
+ * spinand_fact_otp_size() - Get SPI-NAND factory OTP area size
+ * @spinand: the spinand device
+ *
+ * Return: the OTP size.
+ */
+size_t spinand_fact_otp_size(struct spinand_device *spinand)
+{
+       return spinand_otp_size(spinand, &spinand->fact_otp->layout);
+}
+
+/**
+ * spinand_user_otp_size() - Get SPI-NAND user OTP area size
+ * @spinand: the spinand device
+ *
+ * Return: the OTP size.
+ */
+size_t spinand_user_otp_size(struct spinand_device *spinand)
+{
+       return spinand_otp_size(spinand, &spinand->user_otp->layout);
+}
+
+static int spinand_otp_check_bounds(struct spinand_device *spinand, loff_t ofs,
+                                   size_t len,
+                                   const struct spinand_otp_layout *layout)
+{
+       if (ofs < 0 || ofs + len > spinand_otp_size(spinand, layout))
+               return -EINVAL;
+
+       return 0;
+}
+
+static int spinand_user_otp_check_bounds(struct spinand_device *spinand,
+                                        loff_t ofs, size_t len)
+{
+       return spinand_otp_check_bounds(spinand, ofs, len,
+                                       &spinand->user_otp->layout);
+}
+
+static int spinand_mtd_otp_info(struct mtd_info *mtd, size_t len,
+                               size_t *retlen, struct otp_info *buf,
+                               bool is_fact)
+{
+       struct spinand_device *spinand = mtd_to_spinand(mtd);
+       int ret;
+
+       *retlen = 0;
+
+       mutex_lock(&spinand->lock);
+
+       if (is_fact)
+               ret = spinand->fact_otp->ops->info(spinand, len, buf, retlen);
+       else
+               ret = spinand->user_otp->ops->info(spinand, len, buf, retlen);
+
+       mutex_unlock(&spinand->lock);
+
+       return ret;
+}
+
+static int spinand_mtd_fact_otp_info(struct mtd_info *mtd, size_t len,
+                                    size_t *retlen, struct otp_info *buf)
+{
+       return spinand_mtd_otp_info(mtd, len, retlen, buf, true);
+}
+
+static int spinand_mtd_user_otp_info(struct mtd_info *mtd, size_t len,
+                                    size_t *retlen, struct otp_info *buf)
+{
+       return spinand_mtd_otp_info(mtd, len, retlen, buf, false);
+}
+
+static int spinand_mtd_otp_read(struct mtd_info *mtd, loff_t ofs, size_t len,
+                               size_t *retlen, u8 *buf, bool is_fact)
+{
+       struct spinand_device *spinand = mtd_to_spinand(mtd);
+       int ret;
+
+       *retlen = 0;
+
+       if (!len)
+               return 0;
+
+       ret = spinand_otp_check_bounds(spinand, ofs, len,
+                                      is_fact ? &spinand->fact_otp->layout :
+                                                &spinand->user_otp->layout);
+       if (ret)
+               return ret;
+
+       mutex_lock(&spinand->lock);
+
+       if (is_fact)
+               ret = spinand->fact_otp->ops->read(spinand, ofs, len, retlen,
+                                                  buf);
+       else
+               ret = spinand->user_otp->ops->read(spinand, ofs, len, retlen,
+                                                  buf);
+
+       mutex_unlock(&spinand->lock);
+
+       return ret;
+}
+
+static int spinand_mtd_fact_otp_read(struct mtd_info *mtd, loff_t ofs,
+                                    size_t len, size_t *retlen, u8 *buf)
+{
+       return spinand_mtd_otp_read(mtd, ofs, len, retlen, buf, true);
+}
+
+static int spinand_mtd_user_otp_read(struct mtd_info *mtd, loff_t ofs,
+                                    size_t len, size_t *retlen, u8 *buf)
+{
+       return spinand_mtd_otp_read(mtd, ofs, len, retlen, buf, false);
+}
+
+static int spinand_mtd_user_otp_write(struct mtd_info *mtd, loff_t ofs,
+                                     size_t len, size_t *retlen, const u8 *buf)
+{
+       struct spinand_device *spinand = mtd_to_spinand(mtd);
+       const struct spinand_user_otp_ops *ops = spinand->user_otp->ops;
+       int ret;
+
+       *retlen = 0;
+
+       if (!len)
+               return 0;
+
+       ret = spinand_user_otp_check_bounds(spinand, ofs, len);
+       if (ret)
+               return ret;
+
+       mutex_lock(&spinand->lock);
+       ret = ops->write(spinand, ofs, len, retlen, buf);
+       mutex_unlock(&spinand->lock);
+
+       return ret;
+}
+
+static int spinand_mtd_user_otp_erase(struct mtd_info *mtd, loff_t ofs,
+                                     size_t len)
+{
+       struct spinand_device *spinand = mtd_to_spinand(mtd);
+       const struct spinand_user_otp_ops *ops = spinand->user_otp->ops;
+       int ret;
+
+       if (!len)
+               return 0;
+
+       ret = spinand_user_otp_check_bounds(spinand, ofs, len);
+       if (ret)
+               return ret;
+
+       mutex_lock(&spinand->lock);
+       ret = ops->erase(spinand, ofs, len);
+       mutex_unlock(&spinand->lock);
+
+       return ret;
+}
+
+static int spinand_mtd_user_otp_lock(struct mtd_info *mtd, loff_t ofs,
+                                    size_t len)
+{
+       struct spinand_device *spinand = mtd_to_spinand(mtd);
+       const struct spinand_user_otp_ops *ops = spinand->user_otp->ops;
+       int ret;
+
+       if (!len)
+               return 0;
+
+       ret = spinand_user_otp_check_bounds(spinand, ofs, len);
+       if (ret)
+               return ret;
+
+       mutex_lock(&spinand->lock);
+       ret = ops->lock(spinand, ofs, len);
+       mutex_unlock(&spinand->lock);
+
+       return ret;
+}
+
+/**
+ * spinand_set_mtd_otp_ops() - Setup OTP methods
+ * @spinand: the spinand device
+ *
+ * Setup OTP methods.
+ *
+ * Return: 0 on success, a negative error code otherwise.
+ */
+int spinand_set_mtd_otp_ops(struct spinand_device *spinand)
+{
+       struct mtd_info *mtd = spinand_to_mtd(spinand);
+       const struct spinand_fact_otp_ops *fact_ops = spinand->fact_otp->ops;
+       const struct spinand_user_otp_ops *user_ops = spinand->user_otp->ops;
+
+       if (!user_ops && !fact_ops)
+               return -EINVAL;
+
+       if (user_ops) {
+               if (user_ops->info)
+                       mtd->_get_user_prot_info = spinand_mtd_user_otp_info;
+
+               if (user_ops->read)
+                       mtd->_read_user_prot_reg = spinand_mtd_user_otp_read;
+
+               if (user_ops->write)
+                       mtd->_write_user_prot_reg = spinand_mtd_user_otp_write;
+
+               if (user_ops->lock)
+                       mtd->_lock_user_prot_reg = spinand_mtd_user_otp_lock;
+
+               if (user_ops->erase)
+                       mtd->_erase_user_prot_reg = spinand_mtd_user_otp_erase;
+       }
+
+       if (fact_ops) {
+               if (fact_ops->info)
+                       mtd->_get_fact_prot_info = spinand_mtd_fact_otp_info;
+
+               if (fact_ops->read)
+                       mtd->_read_fact_prot_reg = spinand_mtd_fact_otp_read;
+       }
+
+       return 0;
+}
index 34ddd2441fc9795551b9523497e34649910dffca..f4c965ae2f655eb0ec4ca11f0a5efb5aa2b4d792 100644 (file)
@@ -374,6 +374,67 @@ struct spinand_ondie_ecc_conf {
        u8 status;
 };
 
+/**
+ * struct spinand_otp_layout - structure to describe the SPI NAND OTP area
+ * @npages: number of pages in the OTP
+ * @start_page: start page of the user/factory OTP area.
+ */
+struct spinand_otp_layout {
+       unsigned int npages;
+       unsigned int start_page;
+};
+
+/**
+ * struct spinand_fact_otp_ops - SPI NAND OTP methods for factory area
+ * @info: get the OTP area information
+ * @read: read from the SPI NAND OTP area
+ */
+struct spinand_fact_otp_ops {
+       int (*info)(struct spinand_device *spinand, size_t len,
+                   struct otp_info *buf, size_t *retlen);
+       int (*read)(struct spinand_device *spinand, loff_t from, size_t len,
+                   size_t *retlen, u8 *buf);
+};
+
+/**
+ * struct spinand_user_otp_ops - SPI NAND OTP methods for user area
+ * @info: get the OTP area information
+ * @lock: lock an OTP region
+ * @erase: erase an OTP region
+ * @read: read from the SPI NAND OTP area
+ * @write: write to the SPI NAND OTP area
+ */
+struct spinand_user_otp_ops {
+       int (*info)(struct spinand_device *spinand, size_t len,
+                   struct otp_info *buf, size_t *retlen);
+       int (*lock)(struct spinand_device *spinand, loff_t from, size_t len);
+       int (*erase)(struct spinand_device *spinand, loff_t from, size_t len);
+       int (*read)(struct spinand_device *spinand, loff_t from, size_t len,
+                   size_t *retlen, u8 *buf);
+       int (*write)(struct spinand_device *spinand, loff_t from, size_t len,
+                    size_t *retlen, const u8 *buf);
+};
+
+/**
+ * struct spinand_fact_otp - SPI NAND OTP grouping structure for factory area
+ * @layout: OTP region layout
+ * @ops: OTP access ops
+ */
+struct spinand_fact_otp {
+       const struct spinand_otp_layout layout;
+       const struct spinand_fact_otp_ops *ops;
+};
+
+/**
+ * struct spinand_user_otp - SPI NAND OTP grouping structure for user area
+ * @layout: OTP region layout
+ * @ops: OTP access ops
+ */
+struct spinand_user_otp {
+       const struct spinand_otp_layout layout;
+       const struct spinand_user_otp_ops *ops;
+};
+
 /**
  * struct spinand_info - Structure used to describe SPI NAND chips
  * @model: model name
@@ -389,6 +450,8 @@ struct spinand_ondie_ecc_conf {
  * @select_target: function used to select a target/die. Required only for
  *                multi-die chips
  * @set_cont_read: enable/disable continuous cached reads
+ * @fact_otp: SPI NAND factory OTP info.
+ * @user_otp: SPI NAND user OTP info.
  *
  * Each SPI NAND manufacturer driver should have a spinand_info table
  * describing all the chips supported by the driver.
@@ -409,6 +472,8 @@ struct spinand_info {
                             unsigned int target);
        int (*set_cont_read)(struct spinand_device *spinand,
                             bool enable);
+       struct spinand_fact_otp fact_otp;
+       struct spinand_user_otp user_otp;
 };
 
 #define SPINAND_ID(__method, ...)                                      \
@@ -437,6 +502,24 @@ struct spinand_info {
 #define SPINAND_CONT_READ(__set_cont_read)                             \
        .set_cont_read = __set_cont_read,
 
+#define SPINAND_FACT_OTP_INFO(__npages, __start_page, __ops)           \
+       .fact_otp = {                                                   \
+               .layout = {                                             \
+                       .npages = __npages,                             \
+                       .start_page = __start_page,                     \
+               },                                                      \
+               .ops = __ops,                                           \
+       }
+
+#define SPINAND_USER_OTP_INFO(__npages, __start_page, __ops)           \
+       .user_otp = {                                                   \
+               .layout = {                                             \
+                       .npages = __npages,                             \
+                       .start_page = __start_page,                     \
+               },                                                      \
+               .ops = __ops,                                           \
+       }
+
 #define SPINAND_INFO(__model, __id, __memorg, __eccreq, __op_variants, \
                     __flags, ...)                                      \
        {                                                               \
@@ -487,6 +570,8 @@ struct spinand_dirmap {
  *             actually relevant to enable this feature.
  * @set_cont_read: Enable/disable the continuous read feature
  * @priv: manufacturer private data
+ * @fact_otp: SPI NAND factory OTP info.
+ * @user_otp: SPI NAND user OTP info.
  */
 struct spinand_device {
        struct nand_device base;
@@ -519,6 +604,9 @@ struct spinand_device {
        bool cont_read_possible;
        int (*set_cont_read)(struct spinand_device *spinand,
                             bool enable);
+
+       const struct spinand_fact_otp *fact_otp;
+       const struct spinand_user_otp *user_otp;
 };
 
 /**
@@ -594,4 +682,9 @@ int spinand_read_page(struct spinand_device *spinand,
 int spinand_write_page(struct spinand_device *spinand,
                       const struct nand_page_io_req *req);
 
+size_t spinand_fact_otp_size(struct spinand_device *spinand);
+size_t spinand_user_otp_size(struct spinand_device *spinand);
+
+int spinand_set_mtd_otp_ops(struct spinand_device *spinand);
+
 #endif /* __LINUX_MTD_SPINAND_H */