summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSylwester Nawrocki <s.nawrocki@samsung.com>2019-03-01 15:18:15 +0100
committerSylwester Nawrocki <s.nawrocki@samsung.com>2019-03-27 12:32:01 +0100
commitc3d2a4eb760a606324e5ab8c7f205d4a4e6dc097 (patch)
tree0b67c993a1251bedec31457053e050b23d46412f
parent24f72ba8b9c06d9c2964a583f2817c4219f36eda (diff)
downloadlinux-exynos-c3d2a4eb760a606324e5ab8c7f205d4a4e6dc097.tar.gz
linux-exynos-c3d2a4eb760a606324e5ab8c7f205d4a4e6dc097.tar.bz2
linux-exynos-c3d2a4eb760a606324e5ab8c7f205d4a4e6dc097.zip
mtd: Add exynos OTP memory driver
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>
-rw-r--r--drivers/mtd/devices/Kconfig6
-rw-r--r--drivers/mtd/devices/Makefile1
-rw-r--r--drivers/mtd/devices/exynos_otp.c270
3 files changed, 277 insertions, 0 deletions
diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
index 64731e3221ef..f5ee5aa50ec1 100644
--- a/drivers/mtd/devices/Kconfig
+++ b/drivers/mtd/devices/Kconfig
@@ -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
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
index 94895eab3066..9de0e15dec47 100644
--- a/drivers/mtd/devices/Makefile
+++ b/drivers/mtd/devices/Makefile
@@ -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
index 000000000000..80063f9e22cd
--- /dev/null
+++ b/drivers/mtd/devices/exynos_otp.c
@@ -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");