/* * Copyright (C) 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; }