summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorRyan QIAN <b32804@freescale.com>2011-11-02 10:22:16 +0800
committerRyan QIAN <b32804@freescale.com>2011-11-02 10:22:16 +0800
commit822c9266e666d64f5570c5bfcd4d5460fc635b9e (patch)
tree06956ee46b1536de7dc1a74c0aef75937ab19101 /drivers
parent3dc5c067ea894b909bd6404530e399f90e78b8fe (diff)
ENGR00139215 iMX61 Uboot support blow fuse
1. add force option to blow operation 2. add blown value check 3. add simple validation for zeros returned by 'simple_strtoul' call Signed-off-by: Ryan QIAN <b32804@freescale.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/imx_otp.c256
2 files changed, 257 insertions, 0 deletions
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 5bbafea781..685ae8c729 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -29,6 +29,7 @@ COBJS-$(CONFIG_ALI152X) += ali512x.o
COBJS-$(CONFIG_DS4510) += ds4510.o
COBJS-$(CONFIG_FSL_LAW) += fsl_law.o
COBJS-$(CONFIG_IMX_IIM) += imx_iim.o
+COBJS-$(CONFIG_IMX_OTP) += imx_otp.o
COBJS-$(CONFIG_IMX_PWM) += imx_pwm.o
COBJS-$(CONFIG_NS87308) += ns87308.o
COBJS-$(CONFIG_STATUS_LED) += status_led.o
diff --git a/drivers/misc/imx_otp.c b/drivers/misc/imx_otp.c
new file mode 100644
index 0000000000..917924d6ee
--- /dev/null
+++ b/drivers/misc/imx_otp.c
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2010-2011 Freescale Semiconductor, Inc.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <linux/types.h>
+#include <asm/io.h>
+#include <common.h>
+#include <asm-arm/arch/regs-ocotp.h>
+#include <imx_otp.h>
+
+#define HW_OCOTP_CUSTn(n) (0x00000400 + (n) * 0x10)
+#define BF(value, field) (((value) << BP_##field) & BM_##field)
+#define DEF_RELAX 20
+
+#ifdef CONFIG_IMX_OTP_DEBUG
+#define log(a, ...) printf("[%s,%3d]:"a"\n", __func__, __LINE__, ## __VA_ARGS__)
+#else
+#define log(a, ...)
+#endif
+
+static int otp_wait_busy(u32 flags)
+{
+ int count;
+ u32 c;
+
+ for (count = 10000; count >= 0; count--) {
+ c = readl(IMX_OTP_BASE + HW_OCOTP_CTRL);
+ if (!(c & (BM_OCOTP_CTRL_BUSY | BM_OCOTP_CTRL_ERROR | flags)))
+ break;
+ }
+
+ if (count < 0) {
+ printf("ERROR: otp_wait_busy timeout. 0x%X\n", c);
+ /* clear ERROR bit, busy bit will be cleared by controller */
+ writel(BM_OCOTP_CTRL_ERROR, IMX_OTP_BASE + HW_OCOTP_CTRL_CLR);
+ return -1;
+ }
+
+ log("wait busy successful.");
+ return 0;
+}
+
+static int set_otp_timing(void)
+{
+ u32 clk_rate = 0;
+ u32 relax, strobe_read, strobe_prog;
+ u32 timing = 0;
+
+ /* get clock */
+ clk_rate = mxc_get_clock(MXC_IPG_CLK);
+ if (clk_rate == -1) {
+ printf("ERROR: mxc_get_clock failed\n");
+ return -1;
+ }
+
+ log("clk_rate: %d.", clk_rate);
+
+ 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);
+ log("timing: 0x%X", timing);
+
+ writel(timing, IMX_OTP_BASE + HW_OCOTP_TIMING);
+
+ return 0;
+}
+
+static int otp_read_prep(void)
+{
+ return (!set_otp_timing()) ? otp_wait_busy(0) : -1;
+}
+
+static int otp_read_post(void)
+{
+ return 0;
+}
+
+static int otp_blow_prep(void)
+{
+ return (!set_otp_timing()) ? otp_wait_busy(0) : -1;
+}
+
+static int otp_blow_post(void)
+{
+ printf("Reloading shadow registers...\n");
+ /* reload all the shadow registers */
+ writel(BM_OCOTP_CTRL_RELOAD_SHADOWS,
+ IMX_OTP_BASE + HW_OCOTP_CTRL_SET);
+ udelay(1);
+
+ return otp_wait_busy(BM_OCOTP_CTRL_RELOAD_SHADOWS);
+}
+
+static int fuse_read_addr(u32 addr, u32 *pdata)
+{
+ u32 ctrl_reg = 0;
+
+#ifdef CONFIG_IMX_OTP_READ_SHADOW_REG
+ *pdata = readl(IMX_OTP_BASE + HW_OCOTP_CUSTn(addr));
+ printf("Shadow register data: 0x%X\n", *pdata);
+#endif
+
+ ctrl_reg = readl(IMX_OTP_BASE + HW_OCOTP_CTRL);
+ ctrl_reg &= ~BM_OCOTP_CTRL_ADDR;
+ ctrl_reg &= ~BM_OCOTP_CTRL_WR_UNLOCK;
+ ctrl_reg |= BF(addr, OCOTP_CTRL_ADDR);
+ writel(ctrl_reg, IMX_OTP_BASE + HW_OCOTP_CTRL);
+
+ writel(BM_OCOTP_READ_CTRL_READ_FUSE, IMX_OTP_BASE + HW_OCOTP_READ_CTRL);
+ if (otp_wait_busy(0))
+ return -1;
+
+ *pdata = readl(IMX_OTP_BASE + HW_OCOTP_READ_FUSE_DATA);
+ *pdata = BF_OCOTP_READ_FUSE_DATA_DATA(*pdata);
+ return 0;
+}
+
+static int fuse_blow_addr(u32 addr, u32 value)
+{
+ u32 ctrl_reg = 0;
+
+ log("blowing...");
+
+ /* control register */
+ ctrl_reg = readl(IMX_OTP_BASE + HW_OCOTP_CTRL);
+ ctrl_reg &= ~BM_OCOTP_CTRL_ADDR;
+ ctrl_reg |= BF(addr, OCOTP_CTRL_ADDR);
+ ctrl_reg |= BF(BV_OCOTP_CTRL_WR_UNLOCK__KEY, OCOTP_CTRL_WR_UNLOCK);
+ writel(ctrl_reg, IMX_OTP_BASE + HW_OCOTP_CTRL);
+
+ writel(BF_OCOTP_DATA_DATA(value), IMX_OTP_BASE + HW_OCOTP_DATA);
+ if (otp_wait_busy(0))
+ return -1;
+
+ /* write postamble */
+ udelay(2000);
+ return 0;
+}
+
+/*
+ * read one u32 to indexed fuse
+ */
+int imx_otp_read_one_u32(u32 index, u32 *pdata)
+{
+ u32 ctrl_reg = 0;
+ int ret = 0;
+
+ log("index: 0x%X", index);
+ if (index > IMX_OTP_ADDR_MAX) {
+ printf("ERROR: invalid address.\n");
+ ret = -1;
+ goto exit_nop;
+ }
+
+ if (otp_clk_enable()) {
+ ret = -1;
+ printf("ERROR: failed to initialize OTP\n");
+ goto exit_nop;
+ }
+
+ if (otp_read_prep()) {
+ ret = -1;
+ printf("ERROR: read preparation failed\n");
+ goto exit_cleanup;
+ }
+ if (fuse_read_addr(index, pdata)) {
+ ret = -1;
+ printf("ERROR: read failed\n");
+ goto exit_cleanup;
+ }
+ if (otp_read_post()) {
+ ret = -1;
+ printf("ERROR: read post operation failed\n");
+ goto exit_cleanup;
+ }
+
+ if (*pdata == IMX_OTP_DATA_ERROR_VAL) {
+ ctrl_reg = readl(IMX_OTP_BASE + HW_OCOTP_CTRL);
+ if (ctrl_reg & BM_OCOTP_CTRL_ERROR) {
+ printf("ERROR: read fuse failed\n");
+ ret = -1;
+ goto exit_cleanup;
+ }
+ }
+
+exit_cleanup:
+ otp_clk_disable();
+exit_nop:
+ return ret;
+}
+
+/*
+ * blow one u32 to indexed fuse
+ */
+int imx_otp_blow_one_u32(u32 index, u32 data, u32 *pfused_value)
+{
+ u32 ctrl_reg = 0;
+ int ret = 0;
+
+ if (otp_clk_enable()) {
+ ret = -1;
+ goto exit_nop;
+ }
+
+ if (otp_blow_prep()) {
+ ret = -1;
+ printf("ERROR: blow preparation failed\n");
+ goto exit_cleanup;
+ }
+ if (fuse_blow_addr(index, data)) {
+ ret = -1;
+ printf("ERROR: blow fuse failed\n");
+ goto exit_cleanup;
+ }
+ if (otp_blow_post()) {
+ ret = -1;
+ printf("ERROR: blow post operation failed\n");
+ goto exit_cleanup;
+ }
+
+ ctrl_reg = readl(IMX_OTP_BASE + HW_OCOTP_CTRL);
+ if (ctrl_reg & BM_OCOTP_CTRL_ERROR) {
+ ret = -1;
+ goto exit_cleanup;
+ }
+
+ if (imx_otp_read_one_u32(index, pfused_value)) {
+ ret = -1;
+ goto exit_cleanup;
+ }
+
+exit_cleanup:
+ otp_clk_disable();
+exit_nop:
+ return ret;
+}
+