mail archive of the barebox mailing list
 help / color / mirror / Atom feed
* [PATCH] imx6: ocotp: Add On-Chip OTP registers write support
       [not found] <3C20140403064122.GC17250@pengutronix.de>
@ 2014-04-09 12:11 ` Uladzimir Bely
  2014-04-09 12:39   ` Uladzimir Bely
  2014-04-10  6:16   ` Sascha Hauer
  0 siblings, 2 replies; 9+ messages in thread
From: Uladzimir Bely @ 2014-04-09 12:11 UTC (permalink / raw)
  To: barebox; +Cc: Uladzimir Bely

OCOTP imx6 driver updated and ocotptool command added.
MAC address can be written to PROM. "0" (unprogrammed) bits
of MAC can be replaced with "1" (but not vice versa) only once.

Tested on phytec-phyflex-imx6 board.

Signed-off-by: Uladzimir Bely <u.bely@sam-solutions.net>
---
 arch/arm/mach-imx/Kconfig |   7 ++
 arch/arm/mach-imx/ocotp.c | 304 ++++++++++++++++++++++++++++++++++++++++++++++
 commands/Kconfig          |  10 ++
 commands/Makefile         |   1 +
 commands/ocotptool.c      | 123 +++++++++++++++++++
 5 files changed, 445 insertions(+)
 create mode 100644 commands/ocotptool.c

diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig
index 81d16e1..0c81560 100644
--- a/arch/arm/mach-imx/Kconfig
+++ b/arch/arm/mach-imx/Kconfig
@@ -593,6 +593,13 @@ 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 support for writing to On-Chip OTP registers.
+
 endmenu
 
 endif
diff --git a/arch/arm/mach-imx/ocotp.c b/arch/arm/mach-imx/ocotp.c
index e36b484..1fa66a3 100644
--- a/arch/arm/mach-imx/ocotp.c
+++ b/arch/arm/mach-imx/ocotp.c
@@ -26,12 +26,298 @@
 #include <io.h>
 #include <of.h>
 
+#include <linux/clk.h>
+#include <mach/imx6-regs.h>
+
 /*
  * a single MAC address reference has the form
  * <&phandle regoffset>
  */
 #define MAC_ADDRESS_PROPLEN	(2 * sizeof(__be32))
 
+/* 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_RELOAD_SHADOWS	10
+#define OCOTP_CTRL_ERROR		9
+#define OCOTP_CTRL_BUSY			8
+
+#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
+
+struct ocotp_priv {
+	struct cdev cdev;
+	void __iomem *base;
+};
+
+struct ocotp_regs {
+	u32 ctrl;
+	u32 ctrl_set;
+	u32 ctrl_clr;
+	u32 ctrl_tog;
+	u32 timing;
+	u32 rsvd0[3];
+	u32 data;
+	u32 rsvd1[3];
+	u32 read_ctrl;
+	u32 rsvd2[3];
+	u32 fuse_data;
+};
+
+static int imx6_ocotp_set_timing(void)
+{
+	u32 clk_rate;
+	u32 relax, strobe_read, strobe_prog;
+	u32 timing;
+	struct ocotp_regs *otp = (struct ocotp_regs *)MX6_OCOTP_BASE_ADDR;
+
+	/* Get clock */
+	clk_rate = clk_get_rate(clk_lookup("ipg"));
+	if (clk_rate == -1) {
+		printf("ERROR: imx6_get_gptclk failed\n");
+		return -1;
+	}
+
+	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, &otp->timing);
+
+	return 0;
+}
+
+static int imx6_ocotp_wait_busy(u32 flags)
+{
+	int count;
+	u32 cf;
+	struct ocotp_regs *otp = (struct ocotp_regs *)MX6_OCOTP_BASE_ADDR;
+
+	for (count = 10000; count >= 0; count--) {
+		cf = (1 << OCOTP_CTRL_BUSY) | (1 << OCOTP_CTRL_ERROR) | flags;
+		cf &= readl(&otp->ctrl);
+		if (!cf)
+			break;
+	}
+
+	if (count < 0) {
+		printf("ERROR: otp_wait_busy timeout. 0x%X\n", cf);
+		/* Clear ERROR bit, BUSY bit will be cleared by controller */
+		writel(1 << OCOTP_CTRL_ERROR, &otp->ctrl_clr);
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+static int imx6_ocotp_prepare(void)
+{
+	int ret;
+
+	ret = imx6_ocotp_set_timing();
+	if (ret)
+		return ret;
+
+	ret = imx6_ocotp_wait_busy(0);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int fuse_read_addr(u32 addr, u32 *pdata)
+{
+	u32 ctrl_reg;
+	int ret;
+	struct ocotp_regs *otp = (struct ocotp_regs *)MX6_OCOTP_BASE_ADDR;
+
+	ctrl_reg = readl(&otp->ctrl);
+	ctrl_reg &= ~OCOTP_CTRL_ADDR_MASK;
+	ctrl_reg &= ~OCOTP_CTRL_WR_UNLOCK_MASK;
+	ctrl_reg |= BF(addr, OCOTP_CTRL_ADDR);
+	writel(ctrl_reg, &otp->ctrl);
+
+	writel(OCOTP_READ_CTRL_READ_FUSE, &otp->read_ctrl);
+	ret = imx6_ocotp_wait_busy(0);
+	if (ret)
+		return ret;
+
+	*pdata = readl(&otp->fuse_data);
+
+	return 0;
+}
+
+int imx6_ocotp_read_one_u32(u32 index, u32 *pdata)
+{
+	int ret;
+	struct ocotp_regs *otp = (struct ocotp_regs *)MX6_OCOTP_BASE_ADDR;
+
+	ret = imx6_ocotp_prepare();
+	if (ret) {
+		printf("ERROR: read preparation failed\n");
+		return ret;
+	}
+
+	ret = fuse_read_addr(index, pdata);
+	if (ret) {
+		printf("ERROR: read operation failed\n");
+		return ret;
+	}
+
+	if (readl(&otp->ctrl) & (1 << OCOTP_CTRL_ERROR)) {
+		printf("ERROR: 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);
+		buf += 4;
+		read_count++;
+	}
+	read_count <<= 2;
+
+	return read_count;
+}
+
+static int fuse_blow_addr(u32 addr, u32 value)
+{
+	u32 ctrl_reg;
+	int ret;
+	struct ocotp_regs *otp = (struct ocotp_regs *)MX6_OCOTP_BASE_ADDR;
+
+	/* Control register */
+	ctrl_reg = readl(&otp->ctrl);
+	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, &otp->ctrl);
+
+	writel(value, &otp->data);
+	ret = imx6_ocotp_wait_busy(0);
+	if (ret)
+		return ret;
+
+	/* Write postamble */
+	udelay(2000);
+	return 0;
+}
+
+static int imx6_ocotp_blow_post(void)
+{
+	struct ocotp_regs *otp = (struct ocotp_regs *)MX6_OCOTP_BASE_ADDR;
+
+	printf("Reloading shadow registers...\n");
+	writel(1 << OCOTP_CTRL_RELOAD_SHADOWS, &otp->ctrl_set);
+	udelay(1);
+
+	return imx6_ocotp_wait_busy(1 << OCOTP_CTRL_RELOAD_SHADOWS);
+}
+
+int imx6_ocotp_blow_one_u32(u32 index, u32 data, u32 *pfused_value)
+{
+	int ret;
+	struct ocotp_regs *otp = (struct ocotp_regs *)MX6_OCOTP_BASE_ADDR;
+
+	ret = imx6_ocotp_prepare();
+	if (ret) {
+		printf("ERROR: write preparation failed\n");
+		return ret;
+	}
+
+	ret = fuse_blow_addr(index, data);
+	if (ret) {
+		printf("ERROR: blow fuse failed\n");
+		return ret;
+	}
+
+	ret = imx6_ocotp_blow_post();
+	if (ret) {
+		printf("ERROR: blow post operation failed\n");
+		return ret;
+	}
+
+	if (readl(&otp->ctrl) & (1 << OCOTP_CTRL_ERROR)) {
+		printf("ERROR: bad write status\n");
+		return -EFAULT;
+	}
+
+	ret = imx6_ocotp_read_one_u32(index, pfused_value);
+	if (ret) {
+		printf("ERROR: read value written\n");
+		return ret;
+	}
+	return 0;
+}
+
+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;
+
+	index = offset >> 2;
+	size = count >> 2;
+
+	if (!IS_ENABLED(CONFIG_IMX_OCOTP_WRITE))
+		return -ENOSYS;
+
+	if (size > (FUSE_REGS_COUNT - index))
+		size = FUSE_REGS_COUNT - index - 1;
+
+	if (IS_ENABLED(CONFIG_IMX_OCOTP_WRITE)) {
+		for (i = 0; i < size; i++) {
+			int ret;
+
+			memcpy(&data, buf + i * 4, 4);
+			ret = imx6_ocotp_blow_one_u32(index + i, data, &pfuse);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	return count;
+}
+
+static struct file_operations imx6_ocotp_ops = {
+	.read	= imx6_ocotp_cdev_read,
+	.write	= imx6_ocotp_cdev_write,
+	.lseek	= dev_lseek_default,
+};
+
 static void imx_ocotp_init_dt(struct device_d *dev, void __iomem *base)
 {
 	char mac[6];
@@ -74,12 +360,30 @@ 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;
+	cdev		= &priv->cdev;
+	cdev->dev	= dev;
+	cdev->ops	= &imx6_ocotp_ops;
+	cdev->priv	= priv;
+	cdev->size	= 32;
+	cdev->name	= asprintf("imx-ocotp");
+	if (cdev->name == NULL)
+		return -ENOMEM;
+
+	ret = devfs_create(cdev);
+
 	return 0;
 }
 
diff --git a/commands/Kconfig b/commands/Kconfig
index 1e07b5b..560e426 100644
--- a/commands/Kconfig
+++ b/commands/Kconfig
@@ -733,6 +733,16 @@ config CMD_MIITOOL
 	  detailed MII status information, such as MII capabilities,
 	  current advertising mode, and link partner capabilities.
 
+config CMD_OCOTPTOOL
+	bool
+	depends on IMX_OCOTP
+	prompt "ocotptool"
+	help
+	  The ocotptool command allows to read and write to i.MX6 On-Chip
+	  OTP registers from barebox shell. Usage:
+		"ocotptool read mac" to read MAC
+		"ocotptool blow mac xx:xx:xx:xx:xx:xx" to write MAC
+
 config CMD_CLK
 	tristate
 	depends on COMMON_CLK
diff --git a/commands/Makefile b/commands/Makefile
index 58d27fa..01de356 100644
--- a/commands/Makefile
+++ b/commands/Makefile
@@ -93,3 +93,4 @@ obj-$(CONFIG_CMD_MIITOOL)	+= miitool.o
 obj-$(CONFIG_CMD_DETECT)	+= detect.o
 obj-$(CONFIG_CMD_BOOT)		+= boot.o
 obj-$(CONFIG_CMD_DEVINFO)	+= devinfo.o
+obj-$(CONFIG_CMD_OCOTPTOOL)	+= ocotptool.o
diff --git a/commands/ocotptool.c b/commands/ocotptool.c
new file mode 100644
index 0000000..468db4e
--- /dev/null
+++ b/commands/ocotptool.c
@@ -0,0 +1,123 @@
+/*
+ * ocotptool.c
+ *
+ * Copyright (c) 2014 Phytec Messtechnik GmbH
+ *
+ * Uladzimir Bely <u.bely@sam-solutions.com>,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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 <common.h>
+#include <command.h>
+#include <complete.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <net.h>
+
+#define DEVICENAME	"imx-ocotp"
+
+#define MAC_OFFSET	(0x22 * 4)
+#define MAC_BYTES	8
+
+static int read_mac(void)
+{
+	struct cdev *cdev;
+	char *name = asprintf(DEVICENAME);
+	int ret = 0;
+	char buf[8];
+
+	cdev = cdev_open(name, O_RDONLY);
+	if (!cdev)
+		return -ENODEV;
+	ret = cdev_read(cdev, buf, MAC_BYTES, MAC_OFFSET, 0);
+	if (ret == MAC_BYTES) {
+		printf("MAC is %02x:%02x:%02x:%02x:%02x:%02x\n",
+			buf[5], buf[4], buf[3], buf[2], buf[1], buf[0]);
+	}
+	cdev_close(cdev);
+	free(name);
+
+	return 0;
+}
+
+static int write_mac(char *enetaddr)
+{
+	struct cdev *cdev;
+	char *name = asprintf(DEVICENAME);
+	int ret = 0;
+	char buf[8];
+
+	buf[0] = enetaddr[5]; buf[1] = enetaddr[4];
+	buf[2] = enetaddr[3]; buf[3] = enetaddr[2];
+	buf[4] = enetaddr[1]; buf[5] = enetaddr[0];
+	buf[6] = 0; buf[7] = 0;
+	cdev = cdev_open(name, O_WRONLY);
+	if (!cdev)
+		return -ENODEV;
+	printf("Writing MAC to OCOTP registers...\n");
+	ret = cdev_write(cdev, buf, MAC_BYTES, MAC_OFFSET, 0);
+
+	cdev_close(cdev);
+	free(name);
+
+	return 0;
+}
+
+static int do_ocotptool(int argc, char *argv[])
+{
+	int opt, ret = COMMAND_ERROR_USAGE;
+	char enetaddr[12];
+
+	while ((opt = getopt(argc, argv, "r:w:")) > 0) {
+		switch (opt) {
+		case 'r':
+			if (!strcmp(optarg, "mac")) {
+				ret = read_mac();
+				return ret;
+			}
+		case 'w':
+			if (!strcmp(optarg, "mac")) {
+				if (optind >= argc) { /* If no more args */
+					printf("%s: specify MAC\n", argv[0]);
+					return COMMAND_ERROR_USAGE;
+				}
+				/* Check for next arg is MAC-address */
+				if (string_to_ethaddr(argv[optind], enetaddr)) {
+					printf("%s: incorrect MAC\n", argv[0]);
+					return COMMAND_ERROR_USAGE;
+				}
+
+				ret = write_mac(enetaddr);
+			} else
+				return COMMAND_ERROR_USAGE;
+			break;
+		default:
+			break;
+		}
+	}
+
+
+	return ret;
+}
+
+BAREBOX_CMD_HELP_START(ocotptool)
+BAREBOX_CMD_HELP_USAGE("ocotptool [OPTIONS]\n")
+BAREBOX_CMD_HELP_SHORT("Example:\n")
+BAREBOX_CMD_HELP_SHORT("  Reading MAC: ocotptool -r mac\n")
+BAREBOX_CMD_HELP_SHORT("  Writing MAC: ocotptool -w mac xx:xx:xx:xx:xx:xx\n")
+BAREBOX_CMD_HELP_END
+
+BAREBOX_CMD_START(ocotptool)
+	.cmd		= do_ocotptool,
+	.usage		= "read/write i.MX6 fuses (MAC)",
+	BAREBOX_CMD_HELP(cmd_ocotptool_help)
+BAREBOX_CMD_END
-- 
1.8.3.2


_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: Re: [PATCH] imx6: ocotp: Add On-Chip OTP registers write support
  2014-04-09 12:11 ` [PATCH] imx6: ocotp: Add On-Chip OTP registers write support Uladzimir Bely
@ 2014-04-09 12:39   ` Uladzimir Bely
  2014-04-10  6:16   ` Sascha Hauer
  1 sibling, 0 replies; 9+ messages in thread
From: Uladzimir Bely @ 2014-04-09 12:39 UTC (permalink / raw)
  To: barebox

Hello, Sasha.

It's a new version of patch, considering most of your remarks.
But I still have some questions.

> +
> +	/* Get clock */
> +	clk_rate = clk_get_rate(clk_lookup("ipg"));
> +	if (clk_rate == -1) {
> +		printf("ERROR: imx6_get_gptclk failed\n");
> +		return -1;
> +	}
> +
> +	relax = clk_rate / (1000000000 / DEF_RELAX) - 1;

I tried use clk_get(), as you proposed, but didn't understand, how to get
clk rate value in this case.
clkdev_add_physbase() just creates structure clk_lookup, but how to get pointer to it, to
be able to use its ->clk?

-- 
With regards,
Uladzimir Bely.
09.04.2014 15:11, Uladzimir Bely пишет:

_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] imx6: ocotp: Add On-Chip OTP registers write support
  2014-04-09 12:11 ` [PATCH] imx6: ocotp: Add On-Chip OTP registers write support Uladzimir Bely
  2014-04-09 12:39   ` Uladzimir Bely
@ 2014-04-10  6:16   ` Sascha Hauer
  2014-04-10 14:07     ` Uladzimir Bely
  2014-04-11  7:39     ` Uladzimir Bely
  1 sibling, 2 replies; 9+ messages in thread
From: Sascha Hauer @ 2014-04-10  6:16 UTC (permalink / raw)
  To: Uladzimir Bely; +Cc: barebox

Hello Uladzimir,

> +	struct cdev cdev;
> +	void __iomem *base;
> +};
> +
> +struct ocotp_regs {
> +	u32 ctrl;
> +	u32 ctrl_set;
> +	u32 ctrl_clr;
> +	u32 ctrl_tog;
> +	u32 timing;
> +	u32 rsvd0[3];
> +	u32 data;
> +	u32 rsvd1[3];
> +	u32 read_ctrl;
> +	u32 rsvd2[3];
> +	u32 fuse_data;
> +};
> +
> +static int imx6_ocotp_set_timing(void)
> +{
> +	u32 clk_rate;
> +	u32 relax, strobe_read, strobe_prog;
> +	u32 timing;
> +	struct ocotp_regs *otp = (struct ocotp_regs *)MX6_OCOTP_BASE_ADDR;

You're still using MX6_OCOTP_BASE_ADDR. All functions in this driver need a struct
ocotp_priv * as argument. Then use priv->base for getting the ocotp base
address.

> +static int imx6_ocotp_wait_busy(u32 flags)
> +{
> +	int count;
> +	u32 cf;
> +	struct ocotp_regs *otp = (struct ocotp_regs *)MX6_OCOTP_BASE_ADDR;
> +
> +	for (count = 10000; count >= 0; count--) {
> +		cf = (1 << OCOTP_CTRL_BUSY) | (1 << OCOTP_CTRL_ERROR) | flags;
> +		cf &= readl(&otp->ctrl);
> +		if (!cf)
> +			break;
> +	}

No counting loop please. grep for is_timeout in the barebox code for
examples how to program timeout loops.

> +
> +	if (count < 0) {
> +		printf("ERROR: otp_wait_busy timeout. 0x%X\n", cf);
> +		/* Clear ERROR bit, BUSY bit will be cleared by controller */
> +		writel(1 << OCOTP_CTRL_ERROR, &otp->ctrl_clr);
> +		return -EBUSY;
> +	}

count will never be smaller than 0.

> +int imx6_ocotp_read_one_u32(u32 index, u32 *pdata)
> +{
> +	int ret;
> +	struct ocotp_regs *otp = (struct ocotp_regs *)MX6_OCOTP_BASE_ADDR;
> +
> +	ret = imx6_ocotp_prepare();
> +	if (ret) {
> +		printf("ERROR: read preparation failed\n");

Driver should always use dev_err and friends for printing messages.
Also, it often helps printing the actual error number aswell, because
that's the next thing people want to know when they get an error.

> @@ -74,12 +360,30 @@ static int imx_ocotp_probe(struct device_d *dev)
>  {
>  	void __iomem *base;
>  
> +	struct ocotp_priv *priv;
> +	struct cdev *cdev;
> +	int ret = 0;
> +

Add the following line to arch/arm/mach-imx/clk-imx6.c:

clkdev_add_physbase(clks[ipg], MX6_OCOTP_BASE_ADDR, NULL);

And then in this function do a:

	priv->clk = clk_get(dev, NULL);

ocotp_priv needs a struct clk *clk member. You have to pass a pointer to
this around to the function using it.

>  	base = dev_request_mem_region(dev, 0);
>  	if (!base)
>  		return -EBUSY;
>  
>  	imx_ocotp_init_dt(dev, base);
>  
> +	priv = xzalloc(sizeof(*priv));
> +
> +	priv->base	= base;
> +	cdev		= &priv->cdev;
> +	cdev->dev	= dev;
> +	cdev->ops	= &imx6_ocotp_ops;
> +	cdev->priv	= priv;
> +	cdev->size	= 32;
> +	cdev->name	= asprintf("imx-ocotp");
> +	if (cdev->name == NULL)
> +		return -ENOMEM;
> +
> +	ret = devfs_create(cdev);

return ret.

> +
>  	return 0;
>  }
>  
> diff --git a/commands/Kconfig b/commands/Kconfig
> index 1e07b5b..560e426 100644
> --- a/commands/Kconfig
> +++ b/commands/Kconfig
> @@ -733,6 +733,16 @@ config CMD_MIITOOL
>  	  detailed MII status information, such as MII capabilities,
>  	  current advertising mode, and link partner capabilities.
>  
> +config CMD_OCOTPTOOL
> +	bool
> +	depends on IMX_OCOTP
> +	prompt "ocotptool"
> +	help
> +	  The ocotptool command allows to read and write to i.MX6 On-Chip
> +	  OTP registers from barebox shell. Usage:
> +		"ocotptool read mac" to read MAC
> +		"ocotptool blow mac xx:xx:xx:xx:xx:xx" to write MAC

As suggested in my last mail:

Do we need this tool at all? We can add a .macaddr parameter to the
ocotp device using dev_add_param_mac() (This function is new and
currently only in the -next branch)

Sascha


-- 
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 |

_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: Re: [PATCH] imx6: ocotp: Add On-Chip OTP registers write support
  2014-04-10  6:16   ` Sascha Hauer
@ 2014-04-10 14:07     ` Uladzimir Bely
  2014-04-11  7:39       ` Sascha Hauer
  2014-04-11  7:39     ` Uladzimir Bely
  1 sibling, 1 reply; 9+ messages in thread
From: Uladzimir Bely @ 2014-04-10 14:07 UTC (permalink / raw)
  To: barebox

Hello, Sasha.

It seems I've fixed all according your remarks instead of one question.

10.04.2014 09:16, Sascha Hauer пишет:
> 
> As suggested in my last mail:
> 
> Do we need this tool at all? We can add a .macaddr parameter to the
> ocotp device using dev_add_param_mac() (This function is new and
> currently only in the -next branch)
> 

How can we to use such parameters (even not macaddr, just bool)?

For example, I'm trying just to add bool parameter in imx_ocotp_probe(),
for additional FUSE write protection:

> if (IS_ENABLED(CONFIG_IMX_OCOTP_WRITE)) {
> 	dev_add_param_bool(dev, "permanent_write_enable",
> 		NULL, NULL, &priv->write_enable, NULL);
> }

and check priv->write_enable instead of CONFIG_IMX_OCOTP_WRITE in write function.

But how to set permanent_write_enable to 1 in barebox shell?
As I understand, it should be something like ocotp0.permanent_write_enable=1,
but I don't see any similar.

If I add

> add_generic_device("ocotp", 0, NULL, MX6_OCOTP_BASE_ADDR, 0x2000,
> 		   IORESOURCE_MEM, NULL);

to board.c, I see something like 21bc000.ocotp.write_enable= available, but
21bc000.ocotp.write_enable=1 or 21bc000.ocotp.write_enable=0 doesn't work,
saying "No such file or directory".

Also, as I understand, it's an incorrect in field of using devicetree.


One more question: is there any way in barebox shell to write to character device
with offset? For example, ocotptool just writes (reads) 8 bytes to ocotp cdev with offset
0x22*4 (MAC offset). How to do it from shell?

-- 
With regards,
Uladzimir Bely.


_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: Re: [PATCH] imx6: ocotp: Add On-Chip OTP registers write support
  2014-04-10 14:07     ` Uladzimir Bely
@ 2014-04-11  7:39       ` Sascha Hauer
  2014-04-11 12:06         ` Uladzimir Bely
  0 siblings, 1 reply; 9+ messages in thread
From: Sascha Hauer @ 2014-04-11  7:39 UTC (permalink / raw)
  To: Uladzimir Bely; +Cc: barebox

On Thu, Apr 10, 2014 at 05:07:19PM +0300, Uladzimir Bely wrote:
> Hello, Sasha.
> 
> It seems I've fixed all according your remarks instead of one question.
> 
> 10.04.2014 09:16, Sascha Hauer пишет:
> > 
> > As suggested in my last mail:
> > 
> > Do we need this tool at all? We can add a .macaddr parameter to the
> > ocotp device using dev_add_param_mac() (This function is new and
> > currently only in the -next branch)
> > 
> 
> How can we to use such parameters (even not macaddr, just bool)?
> 
> For example, I'm trying just to add bool parameter in imx_ocotp_probe(),
> for additional FUSE write protection:
> 
> > if (IS_ENABLED(CONFIG_IMX_OCOTP_WRITE)) {
> > 	dev_add_param_bool(dev, "permanent_write_enable",
> > 		NULL, NULL, &priv->write_enable, NULL);
> > }
> 
> and check priv->write_enable instead of CONFIG_IMX_OCOTP_WRITE in write function.
> 
> But how to set permanent_write_enable to 1 in barebox shell?
> As I understand, it should be something like ocotp0.permanent_write_enable=1,
> but I don't see any similar.
> 
> If I add
> 
> > add_generic_device("ocotp", 0, NULL, MX6_OCOTP_BASE_ADDR, 0x2000,
> > 		   IORESOURCE_MEM, NULL);
> 
> to board.c, I see something like 21bc000.ocotp.write_enable= available, but
> 21bc000.ocotp.write_enable=1 or 21bc000.ocotp.write_enable=0 doesn't work,
> saying "No such file or directory".
> 
> Also, as I understand, it's an incorrect in field of using devicetree.

Sorry, I didn't remember this. Normally this is correct. We use
devname.parametername=value. With devicetree probing the devicename
itself has a dot in it and barebox doesn't handle this propertly. I'm
unsure if we can fix this. What you can do is register a logical device
in the probe function:

struct ocotp_priv {
	...
	struct device dev;
	...
};

int ocotp_probe(struct device_d *dev)
{
	...
	strcpy(priv->dev.name, "ocotp");
	priv->dev.parent = dev;
	register_device(&priv->dev);
}

Then register the parameters on the new device and you can access them
with ocotp.write_enable=1.

> 
> One more question: is there any way in barebox shell to write to character device
> with offset? For example, ocotptool just writes (reads) 8 bytes to ocotp cdev with offset
> 0x22*4 (MAC offset). How to do it from shell?

You can use md/mw to access offsets. Reading at an offset is:

md -s /dev/ocotp -b 0x10+1

reads one byte at offset 0x10 and:

mw -d /dev/ocotp -b 0x10 0xde

writes 0xde at offset 0x10. See 'help md' or 'help mw' for more
information.

What you can't do is reading bytes into variables.

That said, of cause it's cumbersome to handle MAC addresses with these
commands.

Sascha


-- 
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 |

_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH] imx6: ocotp: Add On-Chip OTP registers write support
  2014-04-10  6:16   ` Sascha Hauer
  2014-04-10 14:07     ` Uladzimir Bely
@ 2014-04-11  7:39     ` Uladzimir Bely
  1 sibling, 0 replies; 9+ messages in thread
From: Uladzimir Bely @ 2014-04-11  7:39 UTC (permalink / raw)
  To: barebox; +Cc: Uladzimir Bely

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.

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

diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig
index 81d16e1..556c7dc 100644
--- a/arch/arm/mach-imx/Kconfig
+++ b/arch/arm/mach-imx/Kconfig
@@ -593,6 +593,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 c32b6cc..5d2d9d0 100644
--- a/arch/arm/mach-imx/clk-imx6.c
+++ b/arch/arm/mach-imx/clk-imx6.c
@@ -294,6 +294,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..ec5cca2 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,261 @@
  */
 #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
+
+struct ocotp_priv {
+	struct cdev cdev;
+	void __iomem *base;
+	struct clk *clk;
+};
+
+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;
+
+	index = offset >> 2;
+	size = count >> 2;
+
+	if (!IS_ENABLED(CONFIG_IMX_OCOTP_WRITE))
+		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];
@@ -74,12 +331,40 @@ 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;
+
+	if (IS_ENABLED(CONFIG_IMX_OCOTP_WRITE))
+		imx6_ocotp_ops.write = imx6_ocotp_cdev_write;
+
 	return 0;
 }
 
-- 
1.8.3.2


_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH] imx6: ocotp: Add On-Chip OTP registers write support
  2014-04-11  7:39       ` Sascha Hauer
@ 2014-04-11 12:06         ` Uladzimir Bely
  2014-04-24 11:07           ` Sascha Hauer
  0 siblings, 1 reply; 9+ messages in thread
From: Uladzimir Bely @ 2014-04-11 12:06 UTC (permalink / raw)
  To: barebox; +Cc: Uladzimir Bely

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@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


_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] imx6: ocotp: Add On-Chip OTP registers write support
  2014-04-11 12:06         ` Uladzimir Bely
@ 2014-04-24 11:07           ` Sascha Hauer
  0 siblings, 0 replies; 9+ messages in thread
From: Sascha Hauer @ 2014-04-24 11:07 UTC (permalink / raw)
  To: Uladzimir Bely; +Cc: barebox

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@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 |

_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: Re: [PATCH] imx6: ocotp: Add On-Chip OTP registers write support
  2014-04-03  6:41 Sascha Hauer
@ 2014-04-03  7:07 ` Uladzimir Bely
  0 siblings, 0 replies; 9+ messages in thread
From: Uladzimir Bely @ 2014-04-03  7:07 UTC (permalink / raw)
  To: barebox

OK, thanks for your notes.

This code was originally written by another person and I've just tried to make it little better.
I'll try to accomodate your remarks and improve the code.

-- 
With regards,
Uladzimir Bely.

_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2014-04-24 11:08 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <3C20140403064122.GC17250@pengutronix.de>
2014-04-09 12:11 ` [PATCH] imx6: ocotp: Add On-Chip OTP registers write support Uladzimir Bely
2014-04-09 12:39   ` Uladzimir Bely
2014-04-10  6:16   ` Sascha Hauer
2014-04-10 14:07     ` Uladzimir Bely
2014-04-11  7:39       ` Sascha Hauer
2014-04-11 12:06         ` Uladzimir Bely
2014-04-24 11:07           ` Sascha Hauer
2014-04-11  7:39     ` Uladzimir Bely
2014-04-03  6:41 Sascha Hauer
2014-04-03  7:07 ` Uladzimir Bely

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox