[PATCH] imx6: ocotp: Add On-Chip OTP registers write support

Uladzimir Bely u.bely at sam-solutions.net
Fri Apr 11 05:06:24 PDT 2014


FUSEs (OTP registers) can be written via /dev/imx-ocotp character device.

For example, writing MAC 12:34:56:78:9A:BC can be performed as
    > mw -l -d /dev/imx-ocotp 0x8c 0x00001234
    > mw -l -d /dev/imx-ocotp 0x88 0x56789ABC
and reading as
    > md -l -s /dev/imx-ocotp 0x88+8
    00000088: 56789ABC 00001234
, where 0x88 (0x22*4) and 0x8C (0x23*4) are offsets of MAC OTP registers.

Notice: FUSEs are PROM, so "0" (unprogrammed) bits
can be replaced with "1" (but not vice versa) only once.

Also, for MAC there are convinient parameters:
    > ocotp0.permanent_write_enable=1
    > ocotp0.mac_addr=12:34:56:78:9A:BC
    imx_ocotp 21bc000.ocotp: reloading shadow registers...
    imx_ocotp 21bc000.ocotp: reloading shadow registers...
    > echo $ocotp0.mac_addr
    12:34:56:78:9A:BC

Signed-off-by: Uladzimir Bely <u.bely at sam-solutions.net>
---
 arch/arm/mach-imx/Kconfig    |  10 ++
 arch/arm/mach-imx/clk-imx6.c |   1 +
 arch/arm/mach-imx/ocotp.c    | 332 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 343 insertions(+)

diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig
index d40c944..c2613bf 100644
--- a/arch/arm/mach-imx/Kconfig
+++ b/arch/arm/mach-imx/Kconfig
@@ -621,6 +621,16 @@ config IMX_OCOTP
 	  only supported functionality is reading the MAC address and assigning
 	  it to an ethernet device.
 
+config IMX_OCOTP_WRITE
+	bool
+	  prompt "Enable write support of i.MX6 CPUs OTP fuses"
+	  depends on IMX_OCOTP
+	help
+	  This adds write support to IMX6 On-Chip OTP registers.
+	  Example of set MAC to 12:34:56:78:9A:BC (2 words with offset 0x22 * 4):
+		mw -l -d /dev/imx-ocotp 0x8C 0x00001234
+		mw -l -d /dev/imx-ocotp 0x88 0x56789ABC
+
 endmenu
 
 endif
diff --git a/arch/arm/mach-imx/clk-imx6.c b/arch/arm/mach-imx/clk-imx6.c
index 518fc00..4598a62 100644
--- a/arch/arm/mach-imx/clk-imx6.c
+++ b/arch/arm/mach-imx/clk-imx6.c
@@ -433,6 +433,7 @@ static int imx6_ccm_probe(struct device_d *dev)
 	clkdev_add_physbase(clks[ecspi_root], MX6_ECSPI5_BASE_ADDR, NULL);
 	clkdev_add_physbase(clks[ipg_per], MX6_GPT_BASE_ADDR, NULL);
 	clkdev_add_physbase(clks[ipg], MX6_ENET_BASE_ADDR, NULL);
+	clkdev_add_physbase(clks[ipg], MX6_OCOTP_BASE_ADDR, NULL);
 	clkdev_add_physbase(clks[usdhc1_podf], MX6_USDHC1_BASE_ADDR, NULL);
 	clkdev_add_physbase(clks[usdhc2_podf], MX6_USDHC2_BASE_ADDR, NULL);
 	clkdev_add_physbase(clks[usdhc3_podf], MX6_USDHC3_BASE_ADDR, NULL);
diff --git a/arch/arm/mach-imx/ocotp.c b/arch/arm/mach-imx/ocotp.c
index e36b484..0ad5b46 100644
--- a/arch/arm/mach-imx/ocotp.c
+++ b/arch/arm/mach-imx/ocotp.c
@@ -25,6 +25,8 @@
 #include <net.h>
 #include <io.h>
 #include <of.h>
+#include <clock.h>
+#include <linux/clk.h>
 
 /*
  * a single MAC address reference has the form
@@ -32,6 +34,270 @@
  */
 #define MAC_ADDRESS_PROPLEN	(2 * sizeof(__be32))
 
+/* OCOTP Registers offsets */
+#define OCOTP_CTRL_SET			0x04
+#define OCOTP_CTRL_CLR			0x08
+#define OCOTP_TIMING			0x10
+#define OCOTP_DATA			0x20
+#define OCOTP_READ_CTRL			0x30
+#define OCOTP_READ_FUSE_DATA		0x40
+
+/* OCOTP Registers bits and masks */
+#define OCOTP_CTRL_WR_UNLOCK		16
+#define OCOTP_CTRL_WR_UNLOCK_KEY	0x3E77
+#define OCOTP_CTRL_WR_UNLOCK_MASK	0xFFFF0000
+#define OCOTP_CTRL_ADDR			0
+#define OCOTP_CTRL_ADDR_MASK		0x0000007F
+#define OCOTP_CTRL_BUSY			(1 << 8)
+#define OCOTP_CTRL_ERROR		(1 << 9)
+#define OCOTP_CTRL_RELOAD_SHADOWS	(1 << 10)
+
+#define OCOTP_TIMING_STROBE_READ	16
+#define OCOTP_TIMING_STROBE_READ_MASK	0x003F0000
+#define OCOTP_TIMING_RELAX		12
+#define OCOTP_TIMING_RELAX_MASK		0x0000F000
+#define OCOTP_TIMING_STROBE_PROG	0
+#define OCOTP_TIMING_STROBE_PROG_MASK	0x00000FFF
+
+#define OCOTP_READ_CTRL_READ_FUSE	0x00000001
+
+#define BF(value, field)		(((value) << field) & field##_MASK)
+
+/* Other definitions */
+#define FUSE_REGS_COUNT			(16 * 8)
+#define IMX6_OTP_DATA_ERROR_VAL		0xBADABADA
+#define DEF_RELAX			20
+#define MAC_OFFSET			(0x22 * 4)
+#define MAC_BYTES			8
+
+struct ocotp_priv {
+	struct cdev cdev;
+	void __iomem *base;
+	struct clk *clk;
+	struct device_d dev;
+	int write_enable;
+	char ethaddr[6];
+};
+
+static int imx6_ocotp_set_timing(struct ocotp_priv *priv)
+{
+	u32 clk_rate;
+	u32 relax, strobe_read, strobe_prog;
+	u32 timing;
+
+	clk_rate = clk_get_rate(priv->clk);
+
+	relax = clk_rate / (1000000000 / DEF_RELAX) - 1;
+	strobe_prog = clk_rate / (1000000000 / 10000) + 2 * (DEF_RELAX + 1) - 1;
+	strobe_read = clk_rate / (1000000000 / 40) + 2 * (DEF_RELAX + 1) - 1;
+
+	timing = BF(relax, OCOTP_TIMING_RELAX);
+	timing |= BF(strobe_read, OCOTP_TIMING_STROBE_READ);
+	timing |= BF(strobe_prog, OCOTP_TIMING_STROBE_PROG);
+
+	writel(timing, priv->base + OCOTP_TIMING);
+
+	return 0;
+}
+
+static int imx6_ocotp_wait_busy(u32 flags, struct ocotp_priv *priv)
+{
+
+	uint64_t start = get_time_ns();
+	while ((OCOTP_CTRL_BUSY | OCOTP_CTRL_ERROR | flags) &
+			readl(priv->base)) {
+		if (is_timeout(start, MSECOND)) {
+			/* Clear ERROR bit */
+			writel(OCOTP_CTRL_ERROR, priv->base + OCOTP_CTRL_CLR);
+			return -ETIMEDOUT;
+		}
+	}
+
+	return 0;
+}
+
+static int imx6_ocotp_prepare(struct ocotp_priv *priv)
+{
+	int ret;
+
+	ret = imx6_ocotp_set_timing(priv);
+	if (ret)
+		return ret;
+
+	ret = imx6_ocotp_wait_busy(0, priv);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int fuse_read_addr(u32 addr, u32 *pdata, struct ocotp_priv *priv)
+{
+	u32 ctrl_reg;
+	int ret;
+
+	ctrl_reg = readl(priv->base);
+	ctrl_reg &= ~OCOTP_CTRL_ADDR_MASK;
+	ctrl_reg &= ~OCOTP_CTRL_WR_UNLOCK_MASK;
+	ctrl_reg |= BF(addr, OCOTP_CTRL_ADDR);
+	writel(ctrl_reg, priv->base);
+
+	writel(OCOTP_READ_CTRL_READ_FUSE, priv->base + OCOTP_READ_CTRL);
+	ret = imx6_ocotp_wait_busy(0, priv);
+	if (ret)
+		return ret;
+
+	*pdata = readl(priv->base + OCOTP_READ_FUSE_DATA);
+
+	return 0;
+}
+
+int imx6_ocotp_read_one_u32(u32 index, u32 *pdata, struct ocotp_priv *priv)
+{
+	int ret;
+
+	ret = imx6_ocotp_prepare(priv);
+	if (ret) {
+		dev_err(priv->cdev.dev, "prepare to read failed\n");
+		return ret;
+	}
+
+	ret = fuse_read_addr(index, pdata, priv);
+	if (ret) {
+		dev_err(priv->cdev.dev, "read failed\n");
+		return ret;
+	}
+
+	if (readl(priv->base) & OCOTP_CTRL_ERROR) {
+		dev_err(priv->cdev.dev, "bad read status\n");
+		return -EFAULT;
+	}
+	return 0;
+}
+
+static ssize_t imx6_ocotp_cdev_read(struct cdev *cdev, void *buf,
+		size_t count, loff_t offset, ulong flags)
+{
+	ulong i;
+	u32 index;
+	size_t read_count = 0;
+
+	index = offset >> 2;
+	count >>= 2;
+	if (count > (FUSE_REGS_COUNT - index))
+		count = FUSE_REGS_COUNT - index - 1;
+
+	for (i = index; i < (index + count); i++) {
+		imx6_ocotp_read_one_u32(i, buf, cdev->priv);
+		buf += 4;
+		read_count++;
+	}
+	read_count <<= 2;
+
+	return read_count;
+}
+
+static int fuse_blow_addr(u32 addr, u32 value, struct ocotp_priv *priv)
+{
+	u32 ctrl_reg;
+	int ret;
+
+	/* Control register */
+	ctrl_reg = readl(priv->base);
+	ctrl_reg &= ~OCOTP_CTRL_ADDR_MASK;
+	ctrl_reg |= BF(addr, OCOTP_CTRL_ADDR);
+	ctrl_reg |= BF(OCOTP_CTRL_WR_UNLOCK_KEY, OCOTP_CTRL_WR_UNLOCK);
+	writel(ctrl_reg, priv->base);
+
+	writel(value, priv->base + OCOTP_DATA);
+	ret = imx6_ocotp_wait_busy(0, priv);
+	if (ret)
+		return ret;
+
+	/* Write postamble */
+	udelay(2000);
+	return 0;
+}
+
+static int imx6_ocotp_reload_shadow(struct ocotp_priv *priv)
+{
+	dev_info(priv->cdev.dev, "reloading shadow registers...\n");
+	writel(OCOTP_CTRL_RELOAD_SHADOWS, priv->base + OCOTP_CTRL_SET);
+	udelay(1);
+
+	return imx6_ocotp_wait_busy(OCOTP_CTRL_RELOAD_SHADOWS, priv);
+}
+
+int imx6_ocotp_blow_one_u32(u32 index, u32 data, u32 *pfused_value,
+		struct ocotp_priv *priv)
+{
+	int ret;
+
+	ret = imx6_ocotp_prepare(priv);
+	if (ret) {
+		dev_err(priv->cdev.dev, "prepare to write failed\n");
+		return ret;
+	}
+
+	ret = fuse_blow_addr(index, data, priv);
+	if (ret) {
+		dev_err(priv->cdev.dev, "fuse blow failed\n");
+		return ret;
+	}
+
+	ret = imx6_ocotp_reload_shadow(priv);
+	if (ret) {
+		dev_err(priv->cdev.dev, "reload shadow registers failed\n");
+		return ret;
+	}
+
+	if (readl(priv->base) & OCOTP_CTRL_ERROR) {
+		dev_err(priv->cdev.dev, "bad write status\n");
+		return -EFAULT;
+	}
+
+	ret = imx6_ocotp_read_one_u32(index, pfused_value, priv);
+
+	return ret;
+}
+
+static ssize_t imx6_ocotp_cdev_write(struct cdev *cdev, const void *buf,
+		size_t count, loff_t offset, ulong flags)
+{
+	ulong size, index, i;
+	u32 data, pfuse;
+
+	struct ocotp_priv *priv = cdev->priv;
+
+	index = offset >> 2;
+	size = count >> 2;
+
+	if (!priv->write_enable) {
+		dev_err(cdev->dev, "operation not permitted\n");
+		return -EPERM;
+	}
+
+	if (size > (FUSE_REGS_COUNT - index))
+		size = FUSE_REGS_COUNT - index - 1;
+
+	for (i = 0; i < size; i++) {
+		int ret;
+
+		memcpy(&data, buf + i * 4, 4);
+		ret = imx6_ocotp_blow_one_u32(index + i, data,
+				&pfuse, cdev->priv);
+		if (ret < 0)
+			return ret;
+	}
+
+	return count;
+}
+
+static struct file_operations imx6_ocotp_ops = {
+	.read	= imx6_ocotp_cdev_read,
+	.lseek	= dev_lseek_default,
+};
+
 static void imx_ocotp_init_dt(struct device_d *dev, void __iomem *base)
 {
 	char mac[6];
@@ -70,16 +336,82 @@ static void imx_ocotp_init_dt(struct device_d *dev, void __iomem *base)
 	}
 }
 
+static int imx_ocotp_get_mac(struct param_d *param, void *priv)
+{
+	struct ocotp_priv *ocotp_priv = priv;
+	char buf[8];
+	int i;
+
+	imx6_ocotp_cdev_read(&ocotp_priv->cdev, buf, MAC_BYTES, MAC_OFFSET, 0);
+
+	for (i = 0; i < 6; i++)
+		ocotp_priv->ethaddr[i] = buf[5 - i];
+
+	return 0;
+}
+
+static int imx_ocotp_set_mac(struct param_d *param, void *priv)
+{
+	struct ocotp_priv *ocotp_priv = priv;
+	char buf[8];
+	int i;
+
+	for (i = 0; i < 6; i++)
+		buf[5 - i] = ocotp_priv->ethaddr[i];
+	buf[6] = 0; buf[7] = 0;
+
+	imx6_ocotp_cdev_write(&ocotp_priv->cdev, buf, MAC_BYTES, MAC_OFFSET, 0);
+
+	return 0;
+}
+
 static int imx_ocotp_probe(struct device_d *dev)
 {
 	void __iomem *base;
 
+	struct ocotp_priv *priv;
+	struct cdev *cdev;
+	int ret = 0;
+
 	base = dev_request_mem_region(dev, 0);
 	if (!base)
 		return -EBUSY;
 
 	imx_ocotp_init_dt(dev, base);
 
+	priv = xzalloc(sizeof(*priv));
+
+	priv->base	= base;
+	priv->clk	= clk_get(dev, NULL);
+	if (IS_ERR(priv->clk))
+		return PTR_ERR(priv->clk);
+
+	cdev		= &priv->cdev;
+	cdev->dev	= dev;
+	cdev->ops	= &imx6_ocotp_ops;
+	cdev->priv	= priv;
+	cdev->size	= 192;
+	cdev->name	= "imx-ocotp";
+	if (cdev->name == NULL)
+		return -ENOMEM;
+
+	ret = devfs_create(cdev);
+
+	if (ret < 0)
+		return ret;
+
+	strcpy(priv->dev.name, "ocotp");
+	priv->dev.parent = dev;
+	register_device(&priv->dev);
+
+	if (IS_ENABLED(CONFIG_IMX_OCOTP_WRITE)) {
+		imx6_ocotp_ops.write = imx6_ocotp_cdev_write;
+		dev_add_param_bool(&(priv->dev), "permanent_write_enable",
+				NULL, NULL, &priv->write_enable, NULL);
+		dev_add_param_mac(&(priv->dev), "mac_addr", imx_ocotp_set_mac,
+				imx_ocotp_get_mac, priv->ethaddr, priv);
+	}
+
 	return 0;
 }
 
-- 
1.8.3.2




More information about the barebox mailing list