[ARM] tegra: support to burn device fuses
authorVarun Wadekar <vwadekar@nvidia.com>
Mon, 25 Oct 2010 04:49:30 +0000 (10:19 +0530)
committerColin Cross <ccross@android.com>
Wed, 24 Nov 2010 00:44:00 +0000 (16:44 -0800)
Change-Id: Ic12a93d4212b5f9a7802537b8f21e288aa431005
Signed-off-by: Varun Wadekar <vwadekar@nvidia.com>
arch/arm/mach-tegra/Makefile
arch/arm/mach-tegra/include/mach/tegra2_fuse.h [new file with mode: 0644]
arch/arm/mach-tegra/tegra2_fuse.c [new file with mode: 0644]

index 8d5316b1cbd8a47965008acffae59574a669c54e..bca47ca592b0edd10562973f6a8a9589c65cfb42 100644 (file)
@@ -21,6 +21,7 @@ obj-$(CONFIG_TEGRA_PWM)                 += pwm.o
 obj-$(CONFIG_ARCH_TEGRA_2x_SOC)         += clock.o
 obj-$(CONFIG_ARCH_TEGRA_2x_SOC)         += tegra2_clocks.o
 obj-$(CONFIG_ARCH_TEGRA_2x_SOC)         += tegra2_dvfs.o
+obj-$(CONFIG_ARCH_TEGRA_2x_SOC)         += tegra2_fuse.o
 obj-$(CONFIG_ARCH_TEGRA_2x_SOC)         += suspend-t2.o
 obj-$(CONFIG_ARCH_TEGRA_2x_SOC)                += tegra2_save.o
 obj-$(CONFIG_CPU_V7)                   += cortex-a9.o
diff --git a/arch/arm/mach-tegra/include/mach/tegra2_fuse.h b/arch/arm/mach-tegra/include/mach/tegra2_fuse.h
new file mode 100644 (file)
index 0000000..510a160
--- /dev/null
@@ -0,0 +1,104 @@
+/*\r
+ * arch/arm/mach-tegra/include/mach/tegra2_fuse.h\r
+ *\r
+ * Copyright (c) 2010, NVIDIA Corporation.\r
+ *\r
+ * This program is free software; you can redistribute it and/or modify\r
+ * it under the terms of the GNU General Public License as published by\r
+ * the Free Software Foundation; either version 2 of the License, or\r
+ * (at your option) any later version.\r
+ *\r
+ * This program is distributed in the hope that it will be useful, but WITHOUT\r
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\r
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for\r
+ * more details.\r
+ *\r
+ * You should have received a copy of the GNU General Public License along\r
+ * with this program; if not, write to the Free Software Foundation, Inc.,\r
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\r
+ */\r
+\r
+#ifndef __MACH_TEGRA2_FUSE_H\r
+#define __MACH_TEGRA2_FUSE_H\r
+\r
+#define SBK_DEVKEY_STATUS_SZ   sizeof(u32)\r
+\r
+/* fuse io parameters */\r
+enum fuse_io_param {\r
+       DEVKEY,\r
+       JTAG_DIS,\r
+       /*\r
+        * Programming the odm production fuse at the same\r
+        * time as the sbk or dev_key is not allowed as it is not possible to\r
+        * verify that the sbk or dev_key were programmed correctly.\r
+        */\r
+       ODM_PROD_MODE,\r
+       SEC_BOOT_DEV_CFG,\r
+       SEC_BOOT_DEV_SEL,\r
+       SBK,\r
+       SW_RSVD,\r
+       IGNORE_DEV_SEL_STRAPS,\r
+       ODM_RSVD,\r
+       SBK_DEVKEY_STATUS,\r
+       MASTER_ENB,\r
+       _PARAMS_U32 = 0x7FFFFFFF\r
+};\r
+\r
+#define MAX_PARAMS ODM_RSVD\r
+\r
+/* the order of the members is pre-decided. please do not change */\r
+struct fuse_data {\r
+       u32 devkey;\r
+       u32 jtag_dis;\r
+       u32 odm_prod_mode;\r
+       u32 bootdev_cfg;\r
+       u32 bootdev_sel;\r
+       u32 sbk[4];\r
+       u32 sw_rsvd;\r
+       u32 ignore_devsel_straps;\r
+       u32 odm_rsvd[8];\r
+};\r
+\r
+/* secondary boot device options */\r
+enum {\r
+       SECBOOTDEV_SDMMC,\r
+       SECBOOTDEV_NOR,\r
+       SECBOOTDEV_SPI,\r
+       SECBOOTDEV_NAND,\r
+       SECBOOTDEV_LBANAND,\r
+       SECBOOTDEV_MUXONENAND,\r
+       _SECBOOTDEV_MAX,\r
+       _SECBOOTDEV_U32 = 0x7FFFFFFF\r
+};\r
+\r
+/*\r
+ * read the fuse settings\r
+ * @param: io_param_type - param type enum\r
+ * @param: size - read size in bytes\r
+ */\r
+int tegra_fuse_read(u32 io_param_type, u32 *data, int size);\r
+\r
+#define FLAGS_DEVKEY                   BIT(DEVKEY)\r
+#define FLAGS_JTAG_DIS                 BIT(JTAG_DIS)\r
+#define FLAGS_SBK_DEVKEY_STATUS        BIT(SBK_DEVKEY_STATUS)\r
+#define FLAGS_ODM_PROD_MODE            BIT(ODM_PROD_MODE)\r
+#define FLAGS_SEC_BOOT_DEV_CFG BIT(SEC_BOOT_DEV_CFG)\r
+#define FLAGS_SEC_BOOT_DEV_SEL BIT(SEC_BOOT_DEV_SEL)\r
+#define FLAGS_SBK                      BIT(SBK)\r
+#define FLAGS_SW_RSVD          BIT(SW_RSVD)\r
+#define FLAGS_IGNORE_DEV_SEL_STRAPS    BIT(IGNORE_DEV_SEL_STRAPS)\r
+#define FLAGS_ODMRSVD                  BIT(ODM_RSVD)\r
+\r
+/*\r
+ * Prior to invoking this routine, the caller is responsible for supplying\r
+ * valid fuse programming voltage.\r
+ *\r
+ * @param: pgm_data - entire data to be programmed\r
+ * @flags: program flags (e.g. FLAGS_DEVKEY)\r
+ */\r
+int tegra_fuse_program(struct fuse_data *pgm_data, u32 flags);\r
+\r
+/* Disables the fuse programming until the next system reset */\r
+void tegra_fuse_program_disable(void);\r
+\r
+#endif\r
diff --git a/arch/arm/mach-tegra/tegra2_fuse.c b/arch/arm/mach-tegra/tegra2_fuse.c
new file mode 100644 (file)
index 0000000..4af4a52
--- /dev/null
@@ -0,0 +1,556 @@
+/*\r
+ * arch/arm/mach-tegra/tegra2_fuse.c\r
+ *\r
+ * Copyright (c) 2010, NVIDIA Corporation.\r
+ *\r
+ * This program is free software; you can redistribute it and/or modify\r
+ * it under the terms of the GNU General Public License as published by\r
+ * the Free Software Foundation; either version 2 of the License, or\r
+ * (at your option) any later version.\r
+ *\r
+ * This program is distributed in the hope that it will be useful, but WITHOUT\r
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\r
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for\r
+ * more details.\r
+ *\r
+ * You should have received a copy of the GNU General Public License along\r
+ * with this program; if not, write to the Free Software Foundation, Inc.,\r
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\r
+ */\r
+\r
+/*\r
+ * Fuses are one time programmable bits on the chip which are used by\r
+ * the chip manufacturer and device manufacturers to store chip/device\r
+ * configurations. The fuse bits are encapsulated in a 32 x 64 array.\r
+ * If a fuse bit is programmed to 1, it cannot be reverted to 0. Either\r
+ * another fuse bit has to be used for the same purpose or a new chip\r
+ * needs to be used.\r
+ *\r
+ * Each and every fuse word has its own shadow word which resides adjacent to\r
+ * a particular fuse word. e.g. Fuse words 0-1 form a fuse-shadow pair.\r
+ * So in theory we have only 32 fuse words to work with.\r
+ * The shadow fuse word is a mirror of the actual fuse word at all times\r
+ * and this is maintained while programming a particular fuse.\r
+ */\r
+\r
+#include <linux/kernel.h>\r
+#include <linux/io.h>\r
+#include <linux/mutex.h>\r
+#include <linux/err.h>\r
+#include <linux/delay.h>\r
+#include <linux/slab.h>\r
+\r
+#include <mach/tegra2_fuse.h>\r
+\r
+#include "fuse.h"\r
+\r
+#define NFUSES 64\r
+#define STATE_IDLE     (0x4 << 16)\r
+\r
+/* since fuse burning is irreversible, use this for testing */\r
+#define ENABLE_FUSE_BURNING 1\r
+\r
+/* fuse registers */\r
+#define FUSE_CTRL              0x000\r
+#define FUSE_REG_ADDR          0x004\r
+#define FUSE_REG_READ          0x008\r
+#define FUSE_REG_WRITE         0x00C\r
+#define FUSE_TIME_PGM          0x01C\r
+#define FUSE_PRIV2INTFC                0x020\r
+#define FUSE_DIS_PGM           0x02C\r
+#define FUSE_PWR_GOOD_SW       0x034\r
+\r
+static u32 fuse_pgm_data[NFUSES / 2];\r
+static u32 fuse_pgm_mask[NFUSES / 2];\r
+static u32 tmp_fuse_pgm_data[NFUSES / 2];\r
+static u8 master_enable;\r
+\r
+DEFINE_MUTEX(fuse_lock);\r
+\r
+static struct fuse_data fuse_info;\r
+\r
+struct param_info {\r
+       u32 *addr;\r
+       int sz;\r
+       u32 start_off;\r
+       int start_bit;\r
+       int nbits;\r
+       int data_offset;\r
+};\r
+\r
+static struct param_info fuse_info_tbl[] = {\r
+       [DEVKEY] = {\r
+               .addr = &fuse_info.devkey,\r
+               .sz = sizeof(fuse_info.devkey),\r
+               .start_off = 0x12,\r
+               .start_bit = 8,\r
+               .nbits = 32,\r
+               .data_offset = 0,\r
+       },\r
+       [JTAG_DIS] = {\r
+               .addr = &fuse_info.jtag_dis,\r
+               .sz = sizeof(fuse_info.jtag_dis),\r
+               .start_off = 0x0,\r
+               .start_bit = 24,\r
+               .nbits = 1,\r
+               .data_offset = 1,\r
+       },\r
+       [ODM_PROD_MODE] = {\r
+               .addr = &fuse_info.odm_prod_mode,\r
+               .sz = sizeof(fuse_info.odm_prod_mode),\r
+               .start_off = 0x0,\r
+               .start_bit = 23,\r
+               .nbits = 1,\r
+               .data_offset = 2,\r
+       },\r
+       [SEC_BOOT_DEV_CFG] = {\r
+               .addr = &fuse_info.bootdev_cfg,\r
+               .sz = sizeof(fuse_info.bootdev_cfg),\r
+               .start_off = 0x14,\r
+               .start_bit = 8,\r
+               .nbits = 16,\r
+               .data_offset = 3,\r
+       },\r
+       [SEC_BOOT_DEV_SEL] = {\r
+               .addr = &fuse_info.bootdev_sel,\r
+               .sz = sizeof(fuse_info.bootdev_sel),\r
+               .start_off = 0x14,\r
+               .start_bit = 24,\r
+               .nbits = 3,\r
+               .data_offset = 4,\r
+       },\r
+       [SBK] = {\r
+               .addr = fuse_info.sbk,\r
+               .sz = sizeof(fuse_info.sbk),\r
+               .start_off = 0x0A,\r
+               .start_bit = 8,\r
+               .nbits = 128,\r
+               .data_offset = 5,\r
+       },\r
+       [SW_RSVD] = {\r
+               .addr = &fuse_info.sw_rsvd,\r
+               .sz = sizeof(fuse_info.sw_rsvd),\r
+               .start_off = 0x14,\r
+               .start_bit = 28,\r
+               .nbits = 4,\r
+               .data_offset = 9,\r
+       },\r
+       [IGNORE_DEV_SEL_STRAPS] = {\r
+               .addr = &fuse_info.ignore_devsel_straps,\r
+               .sz = sizeof(fuse_info.ignore_devsel_straps),\r
+               .start_off = 0x14,\r
+               .start_bit = 27,\r
+               .nbits = 1,\r
+               .data_offset = 10,\r
+       },\r
+       [ODM_RSVD] = {\r
+               .addr = &fuse_info.odm_rsvd,\r
+               .sz = sizeof(fuse_info.odm_rsvd),\r
+               .start_off = 0x16,\r
+               .start_bit = 4,\r
+               .nbits = 256,\r
+               .data_offset = 11,\r
+       },\r
+       [SBK_DEVKEY_STATUS] = {\r
+               .sz = SBK_DEVKEY_STATUS_SZ,\r
+       },\r
+       [MASTER_ENB] = {\r
+               .addr = &master_enable,\r
+               .sz = sizeof(u8),\r
+               .start_off = 0x0,\r
+               .start_bit = 0,\r
+               .nbits = 1,\r
+       },\r
+};\r
+\r
+static void wait_for_idle(void)\r
+{\r
+       u32 reg;\r
+\r
+       do {\r
+               reg = tegra_fuse_readl(FUSE_CTRL);\r
+       } while ((reg & (0xF << 16)) != STATE_IDLE);\r
+}\r
+\r
+#define FUSE_READ      0x1\r
+#define FUSE_WRITE     0x2\r
+#define FUSE_SENSE     0x3\r
+#define FUSE_CMD_MASK  0x3\r
+\r
+static u32 fuse_cmd_read(u32 addr)\r
+{\r
+       u32 reg;\r
+\r
+       tegra_fuse_writel(addr, FUSE_REG_ADDR);\r
+       reg = tegra_fuse_readl(FUSE_CTRL);\r
+       reg &= ~FUSE_CMD_MASK;\r
+       reg |= FUSE_READ;\r
+       tegra_fuse_writel(reg, FUSE_CTRL);\r
+       wait_for_idle();\r
+\r
+       reg = tegra_fuse_readl(FUSE_REG_READ);\r
+       return reg;\r
+}\r
+\r
+static void fuse_cmd_write(u32 value, u32 addr)\r
+{\r
+       u32 reg;\r
+\r
+       tegra_fuse_writel(addr, FUSE_REG_ADDR);\r
+       tegra_fuse_writel(value, FUSE_REG_WRITE);\r
+\r
+       reg = tegra_fuse_readl(FUSE_CTRL);\r
+       reg &= ~FUSE_CMD_MASK;\r
+       reg |= FUSE_WRITE;\r
+       tegra_fuse_writel(reg, FUSE_CTRL);\r
+       wait_for_idle();\r
+}\r
+\r
+static void fuse_cmd_sense(void)\r
+{\r
+       u32 reg;\r
+\r
+       reg = tegra_fuse_readl(FUSE_CTRL);\r
+       reg &= ~FUSE_CMD_MASK;\r
+       reg |= FUSE_SENSE;\r
+       tegra_fuse_writel(reg, FUSE_CTRL);\r
+       wait_for_idle();\r
+}\r
+\r
+static void fuse_reg_hide(void)\r
+{\r
+       u32 reg = tegra_fuse_readl(0x48);\r
+       reg &= ~(1 << 28);\r
+       tegra_fuse_writel(reg, 0x48);\r
+}\r
+\r
+static void fuse_reg_unhide(void)\r
+{\r
+       u32 reg = tegra_fuse_readl(0x48);\r
+       reg |= (1 << 28);\r
+       tegra_fuse_writel(reg, 0x48);\r
+}\r
+\r
+static void get_fuse(enum fuse_io_param io_param, u32 *out)\r
+{\r
+       int start_bit = fuse_info_tbl[io_param].start_bit;\r
+       int nbits = fuse_info_tbl[io_param].nbits;\r
+       int offset = fuse_info_tbl[io_param].start_off;\r
+       u32 *dst = fuse_info_tbl[io_param].addr;\r
+       int dst_bit = 0;\r
+       int i;\r
+       u32 val;\r
+       int loops;\r
+\r
+       if (out)\r
+               dst = out;\r
+\r
+       do {\r
+               val = fuse_cmd_read(offset);\r
+               loops = min(nbits, 32 - start_bit);\r
+               for (i = 0; i < loops; i++) {\r
+                       if (val & (BIT(start_bit + i)))\r
+                               *dst |= BIT(dst_bit);\r
+                       else\r
+                               *dst &= ~BIT(dst_bit);\r
+                       dst_bit++;\r
+                       if (dst_bit == 32) {\r
+                               dst++;\r
+                               dst_bit = 0;\r
+                       }\r
+               }\r
+               nbits -= loops;\r
+               offset += 2;\r
+               start_bit = 0;\r
+       } while (nbits > 0);\r
+}\r
+\r
+int tegra_fuse_read(enum fuse_io_param io_param, u32 *data, int size)\r
+{\r
+       int ret = 0, nbits;\r
+       u32 sbk[4], devkey = 0;\r
+\r
+       if (!data)\r
+               return -EINVAL;\r
+\r
+       if (size != fuse_info_tbl[io_param].sz) {\r
+               pr_err("%s: size mismatch(%d), %d vs %d\n", __func__,\r
+                       (int)io_param, size, fuse_info_tbl[io_param].sz);\r
+               return -EINVAL;\r
+       }\r
+\r
+       mutex_lock(&fuse_lock);\r
+       fuse_reg_unhide();\r
+       fuse_cmd_sense();\r
+\r
+       if (io_param == SBK_DEVKEY_STATUS) {\r
+               *data = 0;\r
+\r
+               get_fuse(SBK, sbk);\r
+               get_fuse(DEVKEY, &devkey);\r
+               nbits = sizeof(sbk) * BITS_PER_BYTE;\r
+               if (find_first_bit((unsigned long *)sbk, nbits) != nbits)\r
+                       *data = 1;\r
+               else if (devkey)\r
+                       *data = 1;\r
+       } else {\r
+               get_fuse(io_param, data);\r
+       }\r
+\r
+       fuse_reg_hide();\r
+       mutex_unlock(&fuse_lock);\r
+       return ret;\r
+}\r
+\r
+static bool fuse_odm_prod_mode(void)\r
+{\r
+       u32 odm_prod_mode = 0;\r
+\r
+       get_fuse(ODM_PROD_MODE, &odm_prod_mode);\r
+       return (odm_prod_mode ? true : false);\r
+}\r
+\r
+static void set_fuse(enum fuse_io_param io_param, u32 *data)\r
+{\r
+       int i, start_bit = fuse_info_tbl[io_param].start_bit;\r
+       int nbits = fuse_info_tbl[io_param].nbits, loops;\r
+       int offset = fuse_info_tbl[io_param].start_off >> 1;\r
+       int src_bit = 0;\r
+       u32 val;\r
+\r
+       do {\r
+               val = *data;\r
+               loops = min(nbits, 32 - start_bit);\r
+               for (i = 0; i < loops; i++) {\r
+                       fuse_pgm_mask[offset] |= BIT(start_bit + i);\r
+                       if (val & BIT(src_bit))\r
+                               fuse_pgm_data[offset] |= BIT(start_bit + i);\r
+                       else\r
+                               fuse_pgm_data[offset] &= ~BIT(start_bit + i);\r
+                       src_bit++;\r
+                       if (src_bit == 32) {\r
+                               data++;\r
+                               val = *data;\r
+                               src_bit = 0;\r
+                       }\r
+               }\r
+               nbits -= loops;\r
+               offset++;\r
+               start_bit = 0;\r
+       } while (nbits > 0);\r
+}\r
+\r
+static void populate_fuse_arrs(struct fuse_data *info, u32 flags)\r
+{\r
+       u32 data = 0;\r
+       u32 *src = (u32 *)info;\r
+       int i;\r
+\r
+       memset(fuse_pgm_data, 0, sizeof(fuse_pgm_data));\r
+       memset(fuse_pgm_mask, 0, sizeof(fuse_pgm_mask));\r
+\r
+       /* enable program bit */\r
+       data = 1;\r
+       set_fuse(MASTER_ENB, &data);\r
+\r
+       if ((flags & FLAGS_ODMRSVD)) {\r
+               set_fuse(ODM_RSVD, info->odm_rsvd);\r
+               flags &= ~FLAGS_ODMRSVD;\r
+       }\r
+\r
+       /* do not burn any more if secure mode is set */\r
+       if (fuse_odm_prod_mode())\r
+               goto out;\r
+\r
+       for_each_set_bit(i, (unsigned long *)&flags, MAX_PARAMS)\r
+               set_fuse(i, src + fuse_info_tbl[i].data_offset);\r
+\r
+out:\r
+       pr_debug("ready to program");\r
+}\r
+\r
+static void fuse_power_enable(void)\r
+{\r
+#if ENABLE_FUSE_BURNING\r
+       tegra_fuse_writel(0x1, FUSE_PWR_GOOD_SW);\r
+       udelay(1);\r
+#endif\r
+}\r
+\r
+static void fuse_power_disable(void)\r
+{\r
+#if ENABLE_FUSE_BURNING\r
+       tegra_fuse_writel(0, FUSE_PWR_GOOD_SW);\r
+       udelay(1);\r
+#endif\r
+}\r
+\r
+static void fuse_program_array(int pgm_cycles)\r
+{\r
+       u32 reg, fuse_val[2];\r
+       u32 *data = tmp_fuse_pgm_data, addr = 0, *mask = fuse_pgm_mask;\r
+       int i = 0;\r
+\r
+       fuse_reg_unhide();\r
+       fuse_cmd_sense();\r
+\r
+       /* get the first 2 fuse bytes */\r
+       fuse_val[0] = fuse_cmd_read(0);\r
+       fuse_val[1] = fuse_cmd_read(1);\r
+\r
+       fuse_power_enable();\r
+\r
+       /*\r
+        * The fuse macro is a high density macro. Fuses are\r
+        * burned using an addressing mechanism, so no need to prepare\r
+        * the full list, but more write to control registers are needed.\r
+        * The only bit that can be written at first is bit 0, a special write\r
+        * protection bit by assumptions all other bits are at 0\r
+        *\r
+        * The programming pulse must have a precise width of\r
+        * [9000, 11000] ns.\r
+        */\r
+       if (pgm_cycles > 0) {\r
+               reg = pgm_cycles;\r
+               tegra_fuse_writel(reg, FUSE_TIME_PGM);\r
+       }\r
+       fuse_val[0] = (0x1 & ~fuse_val[0]);\r
+       fuse_val[1] = (0x1 & ~fuse_val[1]);\r
+       fuse_cmd_write(fuse_val[0], 0);\r
+       fuse_cmd_write(fuse_val[1], 1);\r
+\r
+       fuse_power_disable();\r
+\r
+       /*\r
+        * this will allow programming of other fuses\r
+        * and the reading of the existing fuse values\r
+        */\r
+       fuse_cmd_sense();\r
+\r
+       /* Clear out all bits that have already been burned or masked out */\r
+       memcpy(data, fuse_pgm_data, sizeof(fuse_pgm_data));\r
+\r
+       for (addr = 0; addr < NFUSES; addr += 2, data++, mask++) {\r
+               reg = fuse_cmd_read(addr);\r
+               pr_debug("%d: 0x%x 0x%x 0x%x\n", addr, (u32)(*data),\r
+                       ~reg, (u32)(*mask));\r
+               *data = (*data & ~reg) & *mask;\r
+       }\r
+\r
+       fuse_power_enable();\r
+\r
+       /*\r
+        * Finally loop on all fuses, program the non zero ones.\r
+        * Words 0 and 1 are written last and they contain control fuses. We\r
+        * need to invalidate after writing to a control word (with the exception\r
+        * of the master enable). This is also the reason we write them last.\r
+        */\r
+       for (i = ARRAY_SIZE(fuse_pgm_data) - 1; i >= 0; i--) {\r
+               if (tmp_fuse_pgm_data[i]) {\r
+                       fuse_cmd_write(tmp_fuse_pgm_data[i], i * 2);\r
+                       fuse_cmd_write(tmp_fuse_pgm_data[i], (i * 2) + 1);\r
+               }\r
+\r
+               if (i < 2) {\r
+                       fuse_power_disable();\r
+                       fuse_cmd_sense();\r
+                       fuse_power_enable();\r
+               }\r
+       }\r
+\r
+       /* Read all data into the chip options */\r
+       tegra_fuse_writel(0x1, FUSE_PRIV2INTFC);\r
+       udelay(1);\r
+       tegra_fuse_writel(0, FUSE_PRIV2INTFC);\r
+\r
+       while (!(tegra_fuse_readl(FUSE_CTRL) & (1 << 30)));\r
+\r
+       fuse_reg_hide();\r
+       fuse_power_disable();\r
+}\r
+\r
+static int fuse_set(enum fuse_io_param io_param, u32 *param, int size)\r
+{\r
+       int i, nwords = size / sizeof(u32);\r
+       u32 *data;\r
+\r
+       if (io_param > MAX_PARAMS)\r
+               return -EINVAL;\r
+\r
+       data = (u32*)kzalloc(size, GFP_KERNEL);\r
+       if (!data) {\r
+               pr_err("failed to alloc %d bytes\n", nwords);\r
+               return -ENOMEM;\r
+       }\r
+\r
+       get_fuse(io_param, data);\r
+\r
+       for (i = 0; i < nwords; i++) {\r
+               if ((data[i] | param[i]) != param[i]) {\r
+                       pr_info("hw_val: 0x%x, sw_val: 0x%x, final: 0x%x\n",\r
+                               data[i], param[i], (data[i] | param[i]));\r
+                       param[i] = (data[i] | param[i]);\r
+               }\r
+       }\r
+       kfree(data);\r
+       return 0;\r
+}\r
+\r
+int tegra_fuse_program(struct fuse_data *pgm_data, u32 flags)\r
+{\r
+       u32 reg;\r
+       int i = 0;\r
+\r
+       mutex_lock(&fuse_lock);\r
+       reg = tegra_fuse_readl(FUSE_DIS_PGM);\r
+       mutex_unlock(&fuse_lock);\r
+       if (reg) {\r
+               pr_err("fuse programming disabled");\r
+               return -EACCES;\r
+       }\r
+\r
+       if (fuse_odm_prod_mode() && (flags != FLAGS_ODMRSVD)) {\r
+               pr_err("reserved odm fuses are allowed in secure mode");\r
+               return -EPERM;\r
+       }\r
+\r
+       if ((flags & FLAGS_ODM_PROD_MODE) &&\r
+               (flags & (FLAGS_SBK | FLAGS_DEVKEY))) {\r
+               pr_err("odm production mode and sbk/devkey not allowed");\r
+               return -EPERM;\r
+       }\r
+\r
+       mutex_lock(&fuse_lock);\r
+       memcpy(&fuse_info, pgm_data, sizeof(fuse_info));\r
+       for_each_set_bit(i, (unsigned long *)&flags, MAX_PARAMS) {\r
+               fuse_set((u32)i, fuse_info_tbl[i].addr,\r
+                       fuse_info_tbl[i].sz);\r
+       }\r
+\r
+       populate_fuse_arrs(&fuse_info, flags);\r
+       fuse_program_array(0);\r
+\r
+       /* disable program bit */\r
+       reg = 0;\r
+       set_fuse(MASTER_ENB, &reg);\r
+\r
+       memset(&fuse_info, 0, sizeof(fuse_info));\r
+       mutex_unlock(&fuse_lock);\r
+\r
+       return 0;\r
+}\r
+\r
+void tegra_fuse_program_disable(void)\r
+{\r
+       mutex_lock(&fuse_lock);\r
+       tegra_fuse_writel(0x1, FUSE_DIS_PGM);\r
+       mutex_unlock(&fuse_lock);\r
+}\r
+
+static int __init tegra_fuse_program_init(void)\r
+{\r
+       mutex_init(&fuse_lock);\r
+       return 0;\r
+}\r
+\r
+module_init(tegra_fuse_program_init);\r