MLK-21976 nvmem: imx-scu-octop: implement program support
authorPeng Fan <peng.fan@nxp.com>
Thu, 25 Jul 2019 10:41:51 +0000 (18:41 +0800)
committerPeng Fan <peng.fan@nxp.com>
Mon, 29 Jul 2019 01:13:30 +0000 (09:13 +0800)
Implement program support.
Restruct code to check hole/ecc region.
Use ATF SIP to program fuse
Add mutex lock to protect access

Signed-off-by: Peng Fan <peng.fan@nxp.com>
Reviewed-by: Ye Li <ye.li@nxp.com>
drivers/nvmem/imx-scu-ocotp.c
include/soc/imx/fsl_sip.h

index ee2d0bd..fcd8637 100644 (file)
@@ -15,6 +15,7 @@
  * http://www.gnu.org/copyleft/gpl.html
  */
 
+#include <linux/arm-smccc.h>
 #include <linux/clk.h>
 #include <linux/device.h>
 #include <linux/io.h>
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
 #include <linux/slab.h>
+#include <soc/imx/fsl_sip.h>
 #include <soc/imx8/sc/sci.h>
 
+static DEFINE_MUTEX(scu_ocotp_mutex);
+
 enum ocotp_devtype {
        IMX8QM,
        IMX8QXP,
 };
 
+#define ECC_REGION     BIT(0)
+#define HOLE_REGION    BIT(1)
+
+struct ocotp_region {
+       u32 start;
+       u32 end;
+       u32 flag;
+};
+
 struct ocotp_devtype_data {
        int devtype;
        int nregs;
+       u32 num_region;
+       struct ocotp_region region[];
 };
 
 struct ocotp_priv {
@@ -45,13 +60,58 @@ struct ocotp_priv {
 static struct ocotp_devtype_data imx8qm_data = {
        .devtype = IMX8QM,
        .nregs = 800,
+       .num_region = 2,
+       .region = {
+               {0x10, 0x10f, ECC_REGION},
+               {0x1a0, 0x1ff, ECC_REGION},
+       },
 };
 
 static struct ocotp_devtype_data imx8qxp_data = {
        .devtype = IMX8QXP,
        .nregs = 800,
+       .num_region = 3,
+       .region = {
+               {0x10, 0x10f, ECC_REGION},
+               {0x110, 0x21F, HOLE_REGION},
+               {0x220, 0x31F, ECC_REGION},
+       },
 };
 
+static bool in_hole(void *context, u32 index)
+{
+       struct ocotp_priv *priv = context;
+       struct ocotp_devtype_data *data = priv->data;
+       int i;
+
+       for (i = 0; i < data->num_region; i++) {
+               if (data->region[i].flag & HOLE_REGION) {
+                       if ((index >= data->region[i].start) &&
+                           (index <= data->region[i].end))
+                               return true;
+               }
+       }
+
+       return false;
+}
+
+static bool in_ecc(void *context, u32 index)
+{
+       struct ocotp_priv *priv = context;
+       struct ocotp_devtype_data *data = priv->data;
+       int i;
+
+       for (i = 0; i < data->num_region; i++) {
+               if (data->region[i].flag & ECC_REGION) {
+                       if ((index >= data->region[i].start) &&
+                           (index <= data->region[i].end))
+                               return true;
+               }
+       }
+
+       return false;
+}
+
 static int imx_scu_ocotp_read(void *context, unsigned int offset,
                              void *val, size_t bytes)
 {
@@ -74,19 +134,20 @@ static int imx_scu_ocotp_read(void *context, unsigned int offset,
        if (!p)
                return -ENOMEM;
 
+       mutex_lock(&scu_ocotp_mutex);
+
        buf = p;
 
        for (i = index; i < (index + count); i++) {
-               if (priv->data->devtype == IMX8QXP) {
-                       if ((i > 271) && (i < 544)) {
-                               *(u32 *)buf = 0;
-                               buf += 4;
-                               continue;
-                       }
+               if (in_hole(context, i)) {
+                       *(u32 *)buf = 0;
+                       buf += 4;
+                       continue;
                }
 
                sciErr = sc_misc_otp_fuse_read(priv->nvmem_ipc, i, (u32 *)buf);
                if (sciErr != SC_ERR_NONE) {
+                       mutex_unlock(&scu_ocotp_mutex);
                        kfree(p);
                        return -EIO;
                }
@@ -96,18 +157,62 @@ static int imx_scu_ocotp_read(void *context, unsigned int offset,
        index = offset % 4;
        memcpy(val, &p[index], bytes);
 
+       mutex_unlock(&scu_ocotp_mutex);
+
        kfree(p);
 
        return 0;
 }
 
+static int imx_scu_ocotp_write(void *context, unsigned int offset,
+                              void *val, size_t bytes)
+{
+       struct ocotp_priv *priv = context;
+       sc_err_t sciErr = SC_ERR_NONE;
+       struct arm_smccc_res res;
+       u32 *buf = val;
+       u32 tmp;
+       u32 index;
+
+       /* allow only writing one complete OTP word at a time */
+       if ((bytes != 4) || (offset % 4))
+               return -EINVAL;
+
+       index = offset >> 2;
+
+       if (in_hole(context, index))
+               return -EINVAL;
+
+       if (in_ecc(context, index)) {
+               pr_warn("ECC region, only program once\n");
+               mutex_lock(&scu_ocotp_mutex);
+               sciErr = sc_misc_otp_fuse_read(priv->nvmem_ipc, index, &tmp);
+               mutex_unlock(&scu_ocotp_mutex);
+               if (sciErr != SC_ERR_NONE)
+                       return -EIO;
+               if (tmp) {
+                       pr_warn("ECC region, already has value: %x\n", tmp);
+                       return -EIO;
+               }
+       }
+
+       mutex_lock(&scu_ocotp_mutex);
+
+       arm_smccc_smc(FSL_SIP_OTP_WRITE, index, *buf, 0, 0, 0, 0, 0, &res);
+
+       mutex_unlock(&scu_ocotp_mutex);
+
+       return res.a0;
+}
+
 static struct nvmem_config imx_scu_ocotp_nvmem_config = {
        .name = "imx-ocotp",
-       .read_only = true,
+       .read_only = false,
        .word_size = 4,
        .stride = 1,
        .owner = THIS_MODULE,
        .reg_read = imx_scu_ocotp_read,
+       .reg_write = imx_scu_ocotp_write,
 };
 
 static const struct of_device_id imx_scu_ocotp_dt_ids[] = {
index 1fe8f28..63a52c5 100644 (file)
@@ -66,4 +66,7 @@
 #define FSL_SIP_WAKEUP_SRC_SCU         0x1
 #define FSL_SIP_WAKEUP_SRC_IRQSTEER    0x2
 
+#define FSL_SIP_OTP_READ               0xc200000A
+#define FSL_SIP_OTP_WRITE              0xc200000B
+
 #endif