mtd: Add exynos OTP memory driver 48/200148/4
authorSylwester Nawrocki <s.nawrocki@samsung.com>
Fri, 1 Mar 2019 14:18:15 +0000 (15:18 +0100)
committerSylwester Nawrocki <s.nawrocki@samsung.com>
Wed, 27 Mar 2019 11:32:01 +0000 (12:32 +0100)
This patch adds driver for Exynos5433 OTP memory.  Access to the OTP
memory is required, among others, to support the Adaptive Supply Voltage
feature.

Partially based on code from Android SM-N910C_LL_Opensource kernel.

Change-Id: If85363f1626d622f1559757eb06fd950e214e5ab
Signed-off-by: Sylwester Nawrocki <s.nawrocki@samsung.com>
drivers/mtd/devices/Kconfig
drivers/mtd/devices/Makefile
drivers/mtd/devices/exynos_otp.c [new file with mode: 0644]

index 64731e3..f5ee5aa 100644 (file)
@@ -224,6 +224,12 @@ config MTD_ST_SPI_FSM
          SPI Fast Sequence Mode (FSM) Serial Flash Controller and support
          for a subset of connected Serial Flash devices.
 
+config MTD_EXYNOS_OTP
+       tristate "Exynos SoC OTP memory driver"
+       depends on ARCH_EXYNOS
+       help
+         To do
+
 if MTD_DOCG3
 config BCH_CONST_M
        default 14
index 94895ea..9de0e15 100644 (file)
@@ -19,6 +19,7 @@ obj-$(CONFIG_MTD_SST25L)      += sst25l.o
 obj-$(CONFIG_MTD_BCM47XXSFLASH)        += bcm47xxsflash.o
 obj-$(CONFIG_MTD_ST_SPI_FSM)    += st_spi_fsm.o
 obj-$(CONFIG_MTD_POWERNV_FLASH)        += powernv_flash.o
+obj-$(CONFIG_MTD_EXYNOS_OTP)   += exynos_otp.o
 
 
 CFLAGS_docg3.o                 += -I$(src)
diff --git a/drivers/mtd/devices/exynos_otp.c b/drivers/mtd/devices/exynos_otp.c
new file mode 100644 (file)
index 0000000..80063f9
--- /dev/null
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd.
+ *              http://www.samsung.com/
+ *
+ * Exynos5433 OTP device support
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#define        REG_OTP_LOCK0                           0x00
+#define        REG_OTP_LOCK1                           0x04
+#define        REG_OTP_SECURE_READ_DATA                0x08
+#define        REG_OTP_NONSECURE_READ_DATA             0x0c
+#define        REG_OTP_CON_CONTROL                     0x10
+#define        REG_OTP_CON_CONFIG                      0x14
+#define        REG_OTP_IF                              0x18
+#define        REG_OTP_INT_STATUS                      0x1c
+#define        REG_OTP_INT_EN                          0x20
+#define        REG_OTP_CON_TIME_PARA_0                 0x24
+#define        REG_OTP_CON_TIME_PARA_1                 0x28
+#define        REG_OTP_CON_TIME_PARA_2                 0x2c
+#define        REG_OTP_CON_TIME_PARA_3                 0x30
+#define        REG_OTP_CON_TIME_PARA_4                 0x34
+#define        REG_OTP_CON_TIME_PARA_5                 0x38
+#define        REG_OTP_CON_TIME_PARA_6                 0x3c
+#define        REG_OTP_CON_TIME_PARA_7                 0x40
+#define        REG_OTP_ADD_LOCK                        0x44
+#define        REG_OTP_CUSTOM_LOCK0                    0x48
+#define        REG_OTP_CUSTOM_LOCK01                   0x4c
+
+struct exynos_otp_priv {
+       struct device *dev;
+       struct clk_bulk_data clks[2];
+       void __iomem *regs;
+
+       struct mtd_info mtd;
+};
+
+static unsigned int otp_reg_read(struct exynos_otp_priv *priv,
+                                unsigned int offset)
+{
+       return readl_relaxed(priv->regs + offset);
+}
+
+static void otp_reg_write(struct exynos_otp_priv *priv, unsigned int offset,
+                         unsigned int data)
+{
+       writel_relaxed(data, priv->regs + offset);
+}
+
+static int exynos_otp_cmd_init(struct exynos_otp_priv *priv)
+{
+       unsigned int error_count = 0;
+       int result = 0;
+       u32 reg;
+
+       otp_reg_write(priv, REG_OTP_CON_CONTROL, 0x01);
+
+       while (true) {
+               if (otp_reg_read(priv, REG_OTP_INT_STATUS) & 0x01)
+                       break;
+
+               error_count++;
+               if (error_count > 0xffffff) {
+                       result = -ETIMEDOUT;
+                       break;
+               }
+       }
+
+       reg = otp_reg_read(priv, REG_OTP_INT_STATUS);
+       otp_reg_write(priv, REG_OTP_INT_STATUS, (reg | 0x01));
+
+       return result;
+}
+
+static int exynos_otp_cmd_standby(struct exynos_otp_priv *priv)
+{
+       unsigned int error_count = 0;
+       int result = 0;
+       u32 reg;
+
+       /* Set standby command */
+       reg = otp_reg_read(priv, REG_OTP_CON_CONTROL);
+       otp_reg_write(priv, REG_OTP_CON_CONTROL, reg | 0x08);
+
+       while (true) {
+               if (otp_reg_read(priv, REG_OTP_INT_STATUS) & 0x08)
+                       break;
+
+               error_count++;
+               if (error_count > 0xffffff) {
+                       result = -ETIMEDOUT;
+                       break;
+               }
+       }
+
+       reg = otp_reg_read(priv, REG_OTP_INT_STATUS);
+       otp_reg_write(priv, REG_OTP_INT_STATUS, reg | 0x08);
+
+       return result;
+}
+
+int exynos_otp_cmd_read(struct exynos_otp_priv *priv, u32 addr, u32 *val)
+{
+       u32 error_count = 0;
+       u32 reg;
+
+       /* 1. Set address */
+       /* OTP_IF: program data[31], address [14:0] */
+       reg = otp_reg_read(priv, REG_OTP_IF);
+       reg = (reg & 0xffff0000) | (addr & 0x7fff);
+       otp_reg_write(priv, REG_OTP_IF, reg);
+
+       /* 2. Set read command */
+       reg = otp_reg_read(priv, REG_OTP_CON_CONTROL);
+       otp_reg_write(priv, REG_OTP_CON_CONTROL, reg | 0x02);
+
+       /* 3. Check read status */
+       while (true) {
+               reg = otp_reg_read(priv, REG_OTP_INT_STATUS);
+
+               /* Check read done */
+               if (reg & 0x02) {
+                       dev_dbg(priv->dev, "OTP read sucessfull\n");
+                       break;
+               }
+
+               /* Check secure fail */
+               if (reg & 0x80) {
+                       dev_err(priv->dev, "OTP error: SECURE_FAIL\n");
+                       return -EPERM;
+               }
+
+               error_count++;
+               if (error_count > 0xffffff) {
+                       dev_err(priv->dev, "OTP read timeout\n");
+                       return -ETIMEDOUT;
+               }
+       }
+
+       /* 4. Checking bit [14:13] */
+       reg = (otp_reg_read(priv, REG_OTP_IF) & 0x6000) >> 13;
+
+       if (reg & 0x2) {
+               /* Read SECURE DATA [bit [14:13]= 1:0 or 1:1] */
+               *val = otp_reg_read(priv, REG_OTP_SECURE_READ_DATA);
+               dev_dbg(priv->dev, "read SECURE DATA= 0x%x\n", *val);
+       } else if (reg & 0x1) {
+               /* Hardware only accessible [bit [14:13]= 0:1] */
+               dev_err(priv->dev, "UNACCESSIBLE_REGION\n");
+               return -EACCES;
+       } else if (!(reg & 0x3)) {
+               /* Read NON SECURE DATA [bit [14:13]= 0:0] */
+               *val = otp_reg_read(priv, REG_OTP_NONSECURE_READ_DATA);
+               dev_dbg(priv->dev, "read NON SECURE DATA= 0x%x\n", *val);
+       }
+
+       reg = otp_reg_read(priv, REG_OTP_INT_STATUS);
+       otp_reg_write(priv, REG_OTP_INT_STATUS, reg | 0x02);
+
+       return 0;
+}
+
+int exynos_mtd_read(struct mtd_info *mtd, loff_t addr, size_t len,
+                     size_t *bytes_read, u_char *buf)
+{
+       struct exynos_otp_priv *priv = mtd->priv;
+       u32 *data = (u32 *)buf;
+       u32 val = 0;
+       unsigned int count;
+       int result, ret;
+
+       ret = clk_bulk_prepare_enable(ARRAY_SIZE(priv->clks), priv->clks);
+       if (ret < 0)
+               return ret;
+
+       result = exynos_otp_cmd_init(priv);
+       if (result < 0) {
+               dev_err(priv->dev, "otp_cmd_init() failed: %d\n", result);
+               clk_bulk_disable_unprepare(ARRAY_SIZE(priv->clks), priv->clks);
+               return result;
+       }
+
+       len = (len + 3) / 4;
+
+       for (count = 0; count < len; count++) {
+               result = exynos_otp_cmd_read(priv, addr + count /** 32*/, &val);
+               if (result < 0) {
+                       dev_err(priv->dev, "OTP read failed: %d\n", result);
+                       break;
+               }
+               data[count] = val;
+               *bytes_read += 4;
+       }
+
+       ret = exynos_otp_cmd_standby(priv);
+       if (ret < 0)
+               dev_err(priv->dev, "otp_cmd_standby() failed: %d\n", ret);
+
+       clk_bulk_disable_unprepare(ARRAY_SIZE(priv->clks), priv->clks);
+
+       return result;
+}
+
+static int exynos_otp_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct exynos_otp_priv *priv;
+       struct resource *mem_res;
+       int ret;
+
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       priv->regs = devm_ioremap_resource(dev, mem_res);
+       if (IS_ERR(priv->regs))
+               return PTR_ERR(priv->regs);
+
+       priv->clks[0].id = "pclk";
+       priv->clks[1].id = "sclk";
+
+       priv->dev = dev;
+
+       ret = devm_clk_bulk_get(dev, ARRAY_SIZE(priv->clks), priv->clks);
+       if (ret < 0)
+               return ret;
+
+       platform_set_drvdata(pdev, priv);
+
+       priv->mtd.owner = THIS_MODULE;
+       priv->mtd.priv = priv;
+       priv->mtd.size = 0x10000; /* FIXME: Replace with real OTP size */
+       priv->mtd.writesize = 4;
+       priv->mtd.name = "exynos5433-otp";
+       priv->mtd._read = exynos_mtd_read;
+
+       return mtd_device_register(&priv->mtd, NULL, 0);
+}
+
+static const struct of_device_id exynos_otp_of_match[] = {
+       { .compatible = "samsung,exynos5433-otp" },
+       { },
+};
+MODULE_DEVICE_TABLE(of, exynos_otp_of_match);
+
+static struct platform_driver exynos_otp_driver = {
+       .probe          = exynos_otp_probe,
+       .driver         = {
+               .of_match_table = exynos_otp_of_match,
+               .name           = "exynos-otp",
+       },
+};
+module_platform_driver(exynos_otp_driver);
+
+MODULE_DESCRIPTION("Exynos SoC OTP memory driver");
+MODULE_LICENSE("GPL v2");