diff options
author | Sylwester Nawrocki <s.nawrocki@samsung.com> | 2019-03-01 15:18:15 +0100 |
---|---|---|
committer | Sylwester Nawrocki <s.nawrocki@samsung.com> | 2019-03-27 12:32:01 +0100 |
commit | c3d2a4eb760a606324e5ab8c7f205d4a4e6dc097 (patch) | |
tree | 0b67c993a1251bedec31457053e050b23d46412f | |
parent | 24f72ba8b9c06d9c2964a583f2817c4219f36eda (diff) | |
download | linux-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/Kconfig | 6 | ||||
-rw-r--r-- | drivers/mtd/devices/Makefile | 1 | ||||
-rw-r--r-- | drivers/mtd/devices/exynos_otp.c | 270 |
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"); |