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

Sascha Hauer s.hauer at pengutronix.de
Thu Apr 24 04:07:46 PDT 2014


Hi Uladzimir,

Sorry for the delay, I was on holidays.

On Fri, Apr 11, 2014 at 03:06:24PM +0300, Uladzimir Bely wrote:
> 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
> 

This version looks mostly fine. I'd like to include the following into
your patch. This changes the behaviour for the shadow fuse registers. It
introduces a 'sense_enable' boolean parameter. If it is false the driver
will read from the shadow registers, otherwise it explicitly senses the
fuses. Also, writing is always allowed. If permanent_write_enable is
false, we will write to the shadow registers instead of the fuses. This
allows for some operations without burning the precious fuses.

Sascha

>From be9d6004d0bfd2dd403c94b33ba00186b173b250 Mon Sep 17 00:00:00 2001
From: Sascha Hauer <s.hauer at pengutronix.de>
Date: Thu, 24 Apr 2014 12:58:23 +0200
Subject: [PATCH] fixup! imx6: ocotp: Add On-Chip OTP registers write support

---
 arch/arm/mach-imx/ocotp.c | 110 ++++++++++++++++++++++++++++------------------
 1 file changed, 67 insertions(+), 43 deletions(-)

diff --git a/arch/arm/mach-imx/ocotp.c b/arch/arm/mach-imx/ocotp.c
index 0ad5b46..476b376 100644
--- a/arch/arm/mach-imx/ocotp.c
+++ b/arch/arm/mach-imx/ocotp.c
@@ -75,7 +75,8 @@ struct ocotp_priv {
 	void __iomem *base;
 	struct clk *clk;
 	struct device_d dev;
-	int write_enable;
+	int permanent_write_enable;
+	int sense_enable;
 	char ethaddr[6];
 };
 
@@ -102,8 +103,8 @@ static int imx6_ocotp_set_timing(struct ocotp_priv *priv)
 
 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)) {
@@ -158,43 +159,53 @@ int imx6_ocotp_read_one_u32(u32 index, u32 *pdata, struct ocotp_priv *priv)
 
 	ret = imx6_ocotp_prepare(priv);
 	if (ret) {
-		dev_err(priv->cdev.dev, "prepare to read failed\n");
+		dev_err(priv->cdev.dev, "failed to prepare read fuse 0x%08x\n",
+				index);
 		return ret;
 	}
 
 	ret = fuse_read_addr(index, pdata, priv);
 	if (ret) {
-		dev_err(priv->cdev.dev, "read failed\n");
+		dev_err(priv->cdev.dev, "failed to read fuse 0x%08x\n", index);
 		return ret;
 	}
 
 	if (readl(priv->base) & OCOTP_CTRL_ERROR) {
-		dev_err(priv->cdev.dev, "bad read status\n");
+		dev_err(priv->cdev.dev, "bad read status at fuse 0x%08x\n", index);
 		return -EFAULT;
 	}
+
 	return 0;
 }
 
 static ssize_t imx6_ocotp_cdev_read(struct cdev *cdev, void *buf,
-		size_t count, loff_t offset, ulong flags)
+		size_t count, loff_t offset, unsigned long flags)
 {
-	ulong i;
 	u32 index;
-	size_t read_count = 0;
+	ssize_t read_count = 0;
+	int ret, i;
+	struct ocotp_priv *priv = container_of(cdev, struct ocotp_priv, cdev);
 
 	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);
+		if (priv->sense_enable) {
+			ret = imx6_ocotp_read_one_u32(i, buf, priv);
+			if (ret)
+				return ret;
+		} else {
+			*(u32 *)buf = readl(priv->base + 0x400 + i * 0x10);
+		}
+
 		buf += 4;
 		read_count++;
 	}
-	read_count <<= 2;
 
-	return read_count;
+	return read_count << 2;
 }
 
 static int fuse_blow_addr(u32 addr, u32 value, struct ocotp_priv *priv)
@@ -245,12 +256,6 @@ int imx6_ocotp_blow_one_u32(u32 index, u32 data, u32 *pfused_value,
 		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;
@@ -262,39 +267,56 @@ int imx6_ocotp_blow_one_u32(u32 index, u32 data, u32 *pfused_value,
 }
 
 static ssize_t imx6_ocotp_cdev_write(struct cdev *cdev, const void *buf,
-		size_t count, loff_t offset, ulong flags)
+		size_t count, loff_t offset, unsigned long flags)
 {
-	ulong size, index, i;
-	u32 data, pfuse;
-
 	struct ocotp_priv *priv = cdev->priv;
+	int index, i;
+	ssize_t write_count = 0;
+	const u32 *data;
+	u32 pfuse;
+	int ret;
+
+	/* We could do better, but currently this is what's implemented */
+	if (offset & 0x3 || count & 0x3) {
+		dev_err(cdev->dev, "only u32 aligned writes allowed\n");
+		return -EINVAL;
+	}
 
 	index = offset >> 2;
-	size = count >> 2;
+	count >>= 2;
 
-	if (!priv->write_enable) {
-		dev_err(cdev->dev, "operation not permitted\n");
-		return -EPERM;
-	}
+	if (count > (FUSE_REGS_COUNT - index))
+		count = FUSE_REGS_COUNT - index - 1;
 
-	if (size > (FUSE_REGS_COUNT - index))
-		size = FUSE_REGS_COUNT - index - 1;
+	data = buf;
 
-	for (i = 0; i < size; i++) {
-		int ret;
+	for (i = index; i < (index + count); i++) {
+		if (priv->permanent_write_enable) {
+			ret = imx6_ocotp_blow_one_u32(i, *data,
+					&pfuse, priv);
+			if (ret < 0) {
+				goto out;
+			}
+		} else {
+			writel(*data, priv->base + 0x400 + i * 0x10);
+		}
 
-		memcpy(&data, buf + i * 4, 4);
-		ret = imx6_ocotp_blow_one_u32(index + i, data,
-				&pfuse, cdev->priv);
-		if (ret < 0)
-			return ret;
+		data++;
+		write_count++;
 	}
 
-	return count;
+	ret = 0;
+
+out:
+	if (priv->permanent_write_enable)
+		imx6_ocotp_reload_shadow(priv);
+
+	return ret < 0 ? ret : (write_count << 2);
 }
 
 static struct file_operations imx6_ocotp_ops = {
 	.read	= imx6_ocotp_cdev_read,
+	.write	= imx6_ocotp_cdev_write,
 	.lseek	= dev_lseek_default,
 };
 
@@ -354,13 +376,15 @@ static int imx_ocotp_set_mac(struct param_d *param, void *priv)
 {
 	struct ocotp_priv *ocotp_priv = priv;
 	char buf[8];
-	int i;
+	int i, ret;
 
 	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);
+	ret = imx6_ocotp_cdev_write(&ocotp_priv->cdev, buf, MAC_BYTES, MAC_OFFSET, 0);
+	if (ret < 0)
+		return ret;
 
 	return 0;
 }
@@ -368,7 +392,6 @@ static int imx_ocotp_set_mac(struct param_d *param, void *priv)
 static int imx_ocotp_probe(struct device_d *dev)
 {
 	void __iomem *base;
-
 	struct ocotp_priv *priv;
 	struct cdev *cdev;
 	int ret = 0;
@@ -405,13 +428,14 @@ static int imx_ocotp_probe(struct device_d *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);
+				NULL, NULL, &priv->permanent_write_enable, NULL);
 	}
 
+	dev_add_param_mac(&(priv->dev), "mac_addr", imx_ocotp_set_mac,
+			imx_ocotp_get_mac, priv->ethaddr, priv);
+	dev_add_param_bool(&(priv->dev), "sense_enable", NULL, NULL, &priv->sense_enable, priv);
+
 	return 0;
 }
 
-- 
1.9.1

-- 
Pengutronix e.K.                           |                             |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |



More information about the barebox mailing list