Subject: [PATCH V6 2/3] nvmem: add driver for ocotp in i.MX23 and i.MX28

From: [email-masked] (Srinivas Kandagatla)
Date: Mon, 17 Aug 2015 13:34:58 +0100

On 12/08/15 23:21, Stefan Wahren wrote:
> This patch brings read-only support for the On-Chip OTP cells
> in the i.MX23 and i.MX28 processor. The driver implements the
> new NVMEM provider API.
>
> Signed-off-by: Stefan Wahren <[email-masked]>
> Reviewed-by: Marek Vasut <[email-masked]>
> ---
>   drivers/nvmem/Kconfig     |   11 ++
>   drivers/nvmem/Makefile    |    2 +
>   drivers/nvmem/mxs-ocotp.c |  257 +++++++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 270 insertions(+)
>   create mode 100644 drivers/nvmem/mxs-ocotp.c
>
Looks good to me,

Acked-by: Srinivas Kandagatla <[email-masked]>

> diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
> index 8db2978..a7b43b9 100644
> --- a/drivers/nvmem/Kconfig
> +++ b/drivers/nvmem/Kconfig
> @@ -36,4 +36,15 @@ config NVMEM_SUNXI_SID
>   	  This driver can also be built as a module. If so, the module
>   	  will be called nvmem_sunxi_sid.
>
> +config NVMEM_MXS_OCOTP
> +	tristate "Freescale MXS On-Chip OTP Memory Support"
> +	depends on ARCH_MXS || COMPILE_TEST
> +	help
> +	  If you say Y here, you will get readonly access to the
> +	  One Time Programmable memory pages that are stored
> +	  on the Freescale i.MX23/i.MX28 processor.
> +
> +	  This driver can also be built as a module. If so, the module
> +	  will be called nvmem-mxs-ocotp.
> +
>   endif
> diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
> index 4328b93..e9b6f3c 100644
> --- a/drivers/nvmem/Makefile
> +++ b/drivers/nvmem/Makefile
> @@ -10,3 +10,5 @@ obj-$(CONFIG_QCOM_QFPROM)	+= nvmem_qfprom.o
>   nvmem_qfprom-y			:= qfprom.o
>   obj-$(CONFIG_NVMEM_SUNXI_SID)	+= nvmem_sunxi_sid.o
>   nvmem_sunxi_sid-y		:= sunxi_sid.o
> +obj-$(CONFIG_NVMEM_MXS_OCOTP)	+= nvmem-mxs-ocotp.o
> +nvmem-mxs-ocotp-y		:= mxs-ocotp.o
> diff --git a/drivers/nvmem/mxs-ocotp.c b/drivers/nvmem/mxs-ocotp.c
> new file mode 100644
> index 0000000..8ba19bb
> --- /dev/null
> +++ b/drivers/nvmem/mxs-ocotp.c
> @@ -0,0 +1,257 @@
> +/*
> + * Freescale MXS On-Chip OTP driver
> + *
> + * Copyright (C) 2015 Stefan Wahren <[email-masked]>
> + *
> + * Based on the driver from Huang Shijie and Christoph G. Baumann
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + */
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/nvmem-provider.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/stmp_device.h>
> +
> +/* OCOTP registers and bits */
> +
> +#define BM_OCOTP_CTRL_RD_BANK_OPEN	BIT(12)
> +#define BM_OCOTP_CTRL_ERROR		BIT(9)
> +#define BM_OCOTP_CTRL_BUSY		BIT(8)
> +
> +#define OCOTP_TIMEOUT		10000
> +#define OCOTP_DATA_OFFSET	0x20
> +
> +struct mxs_ocotp {
> +	struct clk *clk;
> +	void __iomem *base;
> +	struct nvmem_device *nvmem;
> +};
> +
> +static int mxs_ocotp_wait(struct mxs_ocotp *otp)
> +{
> +	int timeout = OCOTP_TIMEOUT;
> +	unsigned int status = 0;
> +
> +	while (timeout--) {
> +		status = readl(otp->base);
> +
> +		if (!(status & (BM_OCOTP_CTRL_BUSY | BM_OCOTP_CTRL_ERROR)))
> +			break;
> +
> +		cpu_relax();
> +	}
> +
> +	if (status & BM_OCOTP_CTRL_BUSY)
> +		return -EBUSY;
> +	else if (status & BM_OCOTP_CTRL_ERROR)
> +		return -EIO;
> +
> +	return 0;
> +}
> +
> +static int mxs_ocotp_read(void *context, const void *reg, size_t reg_size,
> +			  void *val, size_t val_size)
> +{
> +	struct mxs_ocotp *otp = context;
> +	unsigned int offset = *(u32 *)reg;
> +	u32 *buf = val;
> +	int ret;
> +
> +	ret = clk_enable(otp->clk);
> +	if (ret)
> +		return ret;
> +
> +	writel(BM_OCOTP_CTRL_ERROR, otp->base + STMP_OFFSET_REG_CLR);
> +
> +	ret = mxs_ocotp_wait(otp);
> +	if (ret)
> +		goto disable_clk;
> +
> +	/* open OCOTP banks for read */
> +	writel(BM_OCOTP_CTRL_RD_BANK_OPEN, otp->base + STMP_OFFSET_REG_SET);
> +
> +	/* approximately wait 33 hclk cycles */
> +	udelay(1);
> +
> +	ret = mxs_ocotp_wait(otp);
> +	if (ret)
> +		goto close_banks;
> +
> +	while (val_size) {
> +		if ((offset < OCOTP_DATA_OFFSET) || (offset % 16)) {
> +			/* fill up non-data register */
> +			*buf = 0;
> +		} else {
> +			*buf = readl(otp->base + offset);
> +		}
> +
> +		buf++;
> +		val_size--;
> +		offset += reg_size;
> +	}
> +
> +close_banks:
> +	/* close banks for power saving */
> +	writel(BM_OCOTP_CTRL_RD_BANK_OPEN, otp->base + STMP_OFFSET_REG_CLR);
> +
> +disable_clk:
> +	clk_disable(otp->clk);
> +
> +	return ret;
> +}
> +
> +static int mxs_ocotp_write(void *context, const void *data, size_t count)
> +{
> +	/* We don't want to support writing */
> +	return 0;
> +}
> +
> +static bool mxs_ocotp_writeable_reg(struct device *dev, unsigned int reg)
> +{
> +	return false;
> +}
> +
> +static struct nvmem_config ocotp_config = {
> +	.name = "mxs-ocotp",
> +	.owner = THIS_MODULE,
> +};
> +
> +static const struct regmap_range imx23_ranges[] = {
> +	regmap_reg_range(OCOTP_DATA_OFFSET, 0x210),
> +};
> +
> +static const struct regmap_access_table imx23_access = {
> +	.yes_ranges = imx23_ranges,
> +	.n_yes_ranges = ARRAY_SIZE(imx23_ranges),
> +};
> +
> +static const struct regmap_range imx28_ranges[] = {
> +	regmap_reg_range(OCOTP_DATA_OFFSET, 0x290),
> +};
> +
> +static const struct regmap_access_table imx28_access = {
> +	.yes_ranges = imx28_ranges,
> +	.n_yes_ranges = ARRAY_SIZE(imx28_ranges),
> +};
> +
> +static struct regmap_bus mxs_ocotp_bus = {
> +	.read = mxs_ocotp_read,
> +	.write = mxs_ocotp_write, /* make regmap_init() happy */
> +	.reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
> +	.val_format_endian_default = REGMAP_ENDIAN_NATIVE,
> +};
> +
> +static struct regmap_config mxs_ocotp_config = {
> +	.reg_bits = 32,
> +	.val_bits = 32,
> +	.reg_stride = 16,
> +	.writeable_reg = mxs_ocotp_writeable_reg,
> +};
> +
> +static const struct of_device_id mxs_ocotp_match[] = {
> +	{ .compatible = "fsl,imx23-ocotp", .data = &imx23_access },
> +	{ .compatible = "fsl,imx28-ocotp", .data = &imx28_access },
> +	{ /* sentinel */},
> +};
> +MODULE_DEVICE_TABLE(of, mxs_ocotp_match);
> +
> +static int mxs_ocotp_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct mxs_ocotp *otp;
> +	struct resource *res;
> +	const struct of_device_id *match;
> +	struct regmap *regmap;
> +	const struct regmap_access_table *access;
> +	int ret;
> +
> +	match = of_match_device(dev->driver->of_match_table, dev);
> +	if (!match || !match->data)
> +		return -EINVAL;
> +
> +	otp = devm_kzalloc(dev, sizeof(*otp), GFP_KERNEL);
> +	if (!otp)
> +		return -ENOMEM;
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	otp->base = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(otp->base))
> +		return PTR_ERR(otp->base);
> +
> +	otp->clk = devm_clk_get(&pdev->dev, NULL);
> +	if (IS_ERR(otp->clk))
> +		return PTR_ERR(otp->clk);
> +
> +	ret = clk_prepare(otp->clk);
> +	if (ret < 0) {
> +		dev_err(dev, "failed to prepare clk: %d\n", ret);
> +		return ret;
> +	}
> +
> +	access = match->data;
> +	mxs_ocotp_config.rd_table = access;
> +	mxs_ocotp_config.max_register = access->yes_ranges[0].range_max;
> +
> +	regmap = devm_regmap_init(dev, &mxs_ocotp_bus, otp, &mxs_ocotp_config);
> +	if (IS_ERR(regmap)) {
> +		dev_err(dev, "regmap init failed\n");
> +		ret = PTR_ERR(regmap);
> +		goto err_clk;
> +	}
> +
> +	ocotp_config.dev = dev;
> +	otp->nvmem = nvmem_register(&ocotp_config);
> +	if (IS_ERR(otp->nvmem)) {
> +		ret = PTR_ERR(otp->nvmem);
> +		goto err_clk;
> +	}
> +
> +	platform_set_drvdata(pdev, otp);
> +
> +	return 0;
> +
> +err_clk:
> +	clk_unprepare(otp->clk);
> +
> +	return ret;
> +}
> +
> +static int mxs_ocotp_remove(struct platform_device *pdev)
> +{
> +	struct mxs_ocotp *otp = platform_get_drvdata(pdev);
> +
> +	clk_unprepare(otp->clk);
> +
> +	return nvmem_unregister(otp->nvmem);
> +}
> +
> +static struct platform_driver mxs_ocotp_driver = {
> +	.probe = mxs_ocotp_probe,
> +	.remove = mxs_ocotp_remove,
> +	.driver = {
> +		.name = "mxs-ocotp",
> +		.of_match_table = mxs_ocotp_match,
> +	},
> +};
> +
> +module_platform_driver(mxs_ocotp_driver);
> +MODULE_AUTHOR("Stefan Wahren <[email-masked]>");
> +MODULE_DESCRIPTION("driver for OCOTP in i.MX23/i.MX28");
> +MODULE_LICENSE("GPL v2");
>