summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra/tegra_odm_fuses.c
diff options
context:
space:
mode:
authorVarun Wadekar <vwadekar@nvidia.com>2011-06-16 16:38:30 +0530
committerDan Willemsen <dwillemsen@nvidia.com>2012-03-22 23:28:14 -0700
commit15cf6cd771831c02b1af3168c5c3bf366b45ee2e (patch)
tree3e367f3a6ad2c3e7931f61ed16e597ac7a4da7da /arch/arm/mach-tegra/tegra_odm_fuses.c
parent456e670d79d09614a22d71173ae95deaa41b41d5 (diff)
arm: tegra: fuse: support to burn fuses on the field
- follow the new sequence shared by the hardware team - merge Tegra2 and Tegra3.0 odm fuse burning into a single file Bug 796825 Original-Change-Id: Ia06d589eba95254a410016dce244375f27e22be0 Signed-off-by: Varun Wadekar <vwadekar@nvidia.com> Reviewed-on: http://git-master/r/38404 Reviewed-by: Varun Colbert <vcolbert@nvidia.com> Tested-by: Varun Colbert <vcolbert@nvidia.com> Rebase-Id: R740d7bd47eaa6231954ae98686272a755a4bce14
Diffstat (limited to 'arch/arm/mach-tegra/tegra_odm_fuses.c')
-rw-r--r--arch/arm/mach-tegra/tegra_odm_fuses.c937
1 files changed, 937 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/tegra_odm_fuses.c b/arch/arm/mach-tegra/tegra_odm_fuses.c
new file mode 100644
index 000000000000..9cd7aa86a224
--- /dev/null
+++ b/arch/arm/mach-tegra/tegra_odm_fuses.c
@@ -0,0 +1,937 @@
+/*
+ * arch/arm/mach-tegra/tegra_odm_fuses.c
+ *
+ * Copyright (c) 2010-2011, NVIDIA Corporation.
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/*
+ * Fuses are one time programmable bits on the chip which are used by
+ * the chip manufacturer and device manufacturers to store chip/device
+ * configurations. The fuse bits are encapsulated in a 32 x 64 array.
+ * If a fuse bit is programmed to 1, it cannot be reverted to 0. Either
+ * another fuse bit has to be used for the same purpose or a new chip
+ * needs to be used.
+ *
+ * Each and every fuse word has its own shadow word which resides adjacent to
+ * a particular fuse word. e.g. Fuse words 0-1 form a fuse-shadow pair.
+ * So in theory we have only 32 fuse words to work with.
+ * The shadow fuse word is a mirror of the actual fuse word at all times
+ * and this is maintained while programming a particular fuse.
+ */
+
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/kobject.h>
+#include <linux/regulator/consumer.h>
+#include <linux/ctype.h>
+#include <linux/clk.h>
+
+#include <mach/tegra_odm_fuses.h>
+#include <mach/iomap.h>
+
+#include "fuse.h"
+
+#define NFUSES 64
+#define STATE_IDLE (0x4 << 16)
+
+/* since fuse burning is irreversible, use this for testing */
+#define ENABLE_FUSE_BURNING 1
+
+/* fuse registers */
+#define FUSE_CTRL 0x000
+#define FUSE_REG_ADDR 0x004
+#define FUSE_REG_READ 0x008
+#define FUSE_REG_WRITE 0x00C
+#define FUSE_TIME_PGM 0x01C
+#define FUSE_PRIV2INTFC 0x020
+#define FUSE_DIS_PGM 0x02C
+#define FUSE_WRITE_ACCESS 0x030
+#define FUSE_PWR_GOOD_SW 0x034
+
+static struct kobject *fuse_kobj;
+
+static ssize_t fuse_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
+static ssize_t fuse_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count);
+
+static struct kobj_attribute devkey_attr =
+ __ATTR(device_key, 0440, fuse_show, fuse_store);
+
+static struct kobj_attribute jtagdis_attr =
+ __ATTR(jtag_disable, 0440, fuse_show, fuse_store);
+
+static struct kobj_attribute odm_prod_mode_attr =
+ __ATTR(odm_production_mode, 0440, fuse_show, fuse_store);
+
+static struct kobj_attribute sec_boot_dev_cfg_attr =
+ __ATTR(sec_boot_dev_cfg, 0440, fuse_show, fuse_store);
+
+static struct kobj_attribute sec_boot_dev_sel_attr =
+ __ATTR(sec_boot_dev_sel, 0440, fuse_show, fuse_store);
+
+static struct kobj_attribute sbk_attr =
+ __ATTR(secure_boot_key, 0440, fuse_show, fuse_store);
+
+static struct kobj_attribute sw_rsvd_attr =
+ __ATTR(sw_reserved, 0440, fuse_show, fuse_store);
+
+static struct kobj_attribute ignore_dev_sel_straps_attr =
+ __ATTR(ignore_dev_sel_straps, 0440, fuse_show, fuse_store);
+
+static struct kobj_attribute odm_rsvd_attr =
+ __ATTR(odm_reserved, 0440, fuse_show, fuse_store);
+
+static u32 fuse_pgm_data[NFUSES / 2];
+static u32 fuse_pgm_mask[NFUSES / 2];
+static u32 tmp_fuse_pgm_data[NFUSES / 2];
+
+DEFINE_MUTEX(fuse_lock);
+
+static struct fuse_data fuse_info;
+struct regulator *vdd_fuse;
+struct clk *clk_fuse;
+
+#define FUSE_NAME_LEN 30
+
+struct param_info {
+ u32 *addr;
+ int sz;
+ u32 start_off;
+ int start_bit;
+ int nbits;
+ int data_offset;
+ char sysfs_name[FUSE_NAME_LEN];
+};
+
+static struct param_info fuse_info_tbl[] = {
+ [DEVKEY] = {
+ .addr = &fuse_info.devkey,
+ .sz = sizeof(fuse_info.devkey),
+#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
+ .start_off = 0x12,
+ .start_bit = 8,
+#else
+ .start_off = 0x16,
+ .start_bit = 22,
+#endif
+ .nbits = 32,
+ .data_offset = 0,
+ .sysfs_name = "device_key",
+ },
+ [JTAG_DIS] = {
+ .addr = &fuse_info.jtag_dis,
+ .sz = sizeof(fuse_info.jtag_dis),
+ .start_off = 0x0,
+ .start_bit = 24,
+ .nbits = 1,
+ .data_offset = 1,
+ .sysfs_name = "jtag_disable",
+ },
+ [ODM_PROD_MODE] = {
+ .addr = &fuse_info.odm_prod_mode,
+ .sz = sizeof(fuse_info.odm_prod_mode),
+ .start_off = 0x0,
+ .start_bit = 23,
+ .nbits = 1,
+ .data_offset = 2,
+ .sysfs_name = "odm_production_mode",
+ },
+ [SEC_BOOT_DEV_CFG] = {
+ .addr = &fuse_info.bootdev_cfg,
+ .sz = sizeof(fuse_info.bootdev_cfg),
+#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
+ .start_off = 0x14,
+ .start_bit = 8,
+#else
+ .start_off = 0x18,
+ .start_bit = 22,
+#endif
+ .nbits = 16,
+ .data_offset = 3,
+ .sysfs_name = "sec_boot_dev_cfg",
+ },
+ [SEC_BOOT_DEV_SEL] = {
+ .addr = &fuse_info.bootdev_sel,
+ .sz = sizeof(fuse_info.bootdev_sel),
+#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
+ .start_off = 0x14,
+ .start_bit = 24,
+#else
+ .start_off = 0x1A,
+ .start_bit = 6,
+#endif
+ .nbits = 3,
+ .data_offset = 4,
+ .sysfs_name = "sec_boot_dev_sel",
+ },
+ [SBK] = {
+ .addr = fuse_info.sbk,
+ .sz = sizeof(fuse_info.sbk),
+#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
+ .start_off = 0x0A,
+ .start_bit = 8,
+#else
+ .start_off = 0x0E,
+ .start_bit = 22,
+#endif
+ .nbits = 128,
+ .data_offset = 5,
+ .sysfs_name = "secure_boot_key",
+ },
+ [SW_RSVD] = {
+ .addr = &fuse_info.sw_rsvd,
+ .sz = sizeof(fuse_info.sw_rsvd),
+#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
+ .start_off = 0x14,
+ .start_bit = 28,
+#else
+ .start_off = 0x1A,
+ .start_bit = 10,
+#endif
+ .nbits = 4,
+ .data_offset = 9,
+ .sysfs_name = "sw_reserved",
+ },
+ [IGNORE_DEV_SEL_STRAPS] = {
+ .addr = &fuse_info.ignore_devsel_straps,
+ .sz = sizeof(fuse_info.ignore_devsel_straps),
+#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
+ .start_off = 0x14,
+ .start_bit = 27,
+#else
+ .start_off = 0x1A,
+ .start_bit = 9,
+#endif
+ .nbits = 1,
+ .data_offset = 10,
+ .sysfs_name = "ignore_dev_sel_straps",
+ },
+ [ODM_RSVD] = {
+ .addr = fuse_info.odm_rsvd,
+ .sz = sizeof(fuse_info.odm_rsvd),
+#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
+ .start_off = 0x16,
+ .start_bit = 4,
+#else
+ .start_off = 0x1A,
+ .start_bit = 14,
+#endif
+ .nbits = 256,
+ .data_offset = 11,
+ .sysfs_name = "odm_reserved",
+ },
+ [SBK_DEVKEY_STATUS] = {
+ .sz = SBK_DEVKEY_STATUS_SZ,
+ },
+};
+
+static void wait_for_idle(void)
+{
+ u32 reg;
+
+ do {
+ udelay(1);
+ reg = tegra_fuse_readl(FUSE_CTRL);
+ } while ((reg & (0xF << 16)) != STATE_IDLE);
+}
+
+#define FUSE_READ 0x1
+#define FUSE_WRITE 0x2
+#define FUSE_SENSE 0x3
+#define FUSE_CMD_MASK 0x3
+
+static u32 fuse_cmd_read(u32 addr)
+{
+ u32 reg;
+
+ wait_for_idle();
+ tegra_fuse_writel(addr, FUSE_REG_ADDR);
+ reg = tegra_fuse_readl(FUSE_CTRL);
+ reg &= ~FUSE_CMD_MASK;
+ reg |= FUSE_READ;
+ tegra_fuse_writel(reg, FUSE_CTRL);
+ wait_for_idle();
+
+ reg = tegra_fuse_readl(FUSE_REG_READ);
+ return reg;
+}
+
+static void fuse_cmd_write(u32 value, u32 addr)
+{
+ u32 reg;
+
+ wait_for_idle();
+ tegra_fuse_writel(addr, FUSE_REG_ADDR);
+ tegra_fuse_writel(value, FUSE_REG_WRITE);
+
+ reg = tegra_fuse_readl(FUSE_CTRL);
+ reg &= ~FUSE_CMD_MASK;
+ reg |= FUSE_WRITE;
+ tegra_fuse_writel(reg, FUSE_CTRL);
+ wait_for_idle();
+}
+
+static void fuse_cmd_sense(void)
+{
+ u32 reg;
+
+ wait_for_idle();
+ reg = tegra_fuse_readl(FUSE_CTRL);
+ reg &= ~FUSE_CMD_MASK;
+ reg |= FUSE_SENSE;
+ tegra_fuse_writel(reg, FUSE_CTRL);
+ wait_for_idle();
+}
+
+static void fuse_reg_hide(void)
+{
+ u32 reg = tegra_fuse_readl(0x48);
+ reg &= ~(1 << 28);
+ tegra_fuse_writel(reg, 0x48);
+}
+
+static void fuse_reg_unhide(void)
+{
+ u32 reg = tegra_fuse_readl(0x48);
+ reg |= (1 << 28);
+ tegra_fuse_writel(reg, 0x48);
+}
+
+static void get_fuse(enum fuse_io_param io_param, u32 *out)
+{
+ int start_bit = fuse_info_tbl[io_param].start_bit;
+ int nbits = fuse_info_tbl[io_param].nbits;
+ int offset = fuse_info_tbl[io_param].start_off;
+ u32 *dst = fuse_info_tbl[io_param].addr;
+ int dst_bit = 0;
+ int i;
+ u32 val;
+ int loops;
+
+ if (out)
+ dst = out;
+
+ do {
+ val = fuse_cmd_read(offset);
+ loops = min(nbits, 32 - start_bit);
+ for (i = 0; i < loops; i++) {
+ if (val & (BIT(start_bit + i)))
+ *dst |= BIT(dst_bit);
+ else
+ *dst &= ~BIT(dst_bit);
+ dst_bit++;
+ if (dst_bit == 32) {
+ dst++;
+ dst_bit = 0;
+ }
+ }
+ nbits -= loops;
+ offset += 2;
+ start_bit = 0;
+ } while (nbits > 0);
+}
+
+int tegra_fuse_read(enum fuse_io_param io_param, u32 *data, int size)
+{
+ int nbits;
+ u32 sbk[4], devkey = 0;
+
+ if (IS_ERR_OR_NULL(clk_fuse)) {
+ pr_err("fuse read disabled");
+ return -ENODEV;
+ }
+
+ if (!data)
+ return -EINVAL;
+
+ if (size != fuse_info_tbl[io_param].sz) {
+ pr_err("%s: size mismatch(%d), %d vs %d\n", __func__,
+ (int)io_param, size, fuse_info_tbl[io_param].sz);
+ return -EINVAL;
+ }
+
+ mutex_lock(&fuse_lock);
+
+ clk_enable(clk_fuse);
+ fuse_reg_unhide();
+ fuse_cmd_sense();
+
+ if (io_param == SBK_DEVKEY_STATUS) {
+ *data = 0;
+
+ get_fuse(SBK, sbk);
+ get_fuse(DEVKEY, &devkey);
+ nbits = sizeof(sbk) * BITS_PER_BYTE;
+ if (find_first_bit((unsigned long *)sbk, nbits) != nbits)
+ *data = 1;
+ else if (devkey)
+ *data = 1;
+ } else {
+ get_fuse(io_param, data);
+ }
+
+ fuse_reg_hide();
+ clk_disable(clk_fuse);
+ mutex_unlock(&fuse_lock);
+
+ return 0;
+}
+
+static bool fuse_odm_prod_mode(void)
+{
+ u32 odm_prod_mode = 0;
+
+ clk_enable(clk_fuse);
+ get_fuse(ODM_PROD_MODE, &odm_prod_mode);
+ clk_disable(clk_fuse);
+ return (odm_prod_mode ? true : false);
+}
+
+static void set_fuse(enum fuse_io_param io_param, u32 *data)
+{
+ int i, start_bit = fuse_info_tbl[io_param].start_bit;
+ int nbits = fuse_info_tbl[io_param].nbits, loops;
+ int offset = fuse_info_tbl[io_param].start_off >> 1;
+ int src_bit = 0;
+ u32 val;
+
+ do {
+ val = *data;
+ loops = min(nbits, 32 - start_bit);
+ for (i = 0; i < loops; i++) {
+ fuse_pgm_mask[offset] |= BIT(start_bit + i);
+ if (val & BIT(src_bit))
+ fuse_pgm_data[offset] |= BIT(start_bit + i);
+ else
+ fuse_pgm_data[offset] &= ~BIT(start_bit + i);
+ src_bit++;
+ if (src_bit == 32) {
+ data++;
+ val = *data;
+ src_bit = 0;
+ }
+ }
+ nbits -= loops;
+ offset++;
+ start_bit = 0;
+ } while (nbits > 0);
+}
+
+static void populate_fuse_arrs(struct fuse_data *info, u32 flags)
+{
+ u32 *src = (u32 *)info;
+ int i;
+
+ memset(fuse_pgm_data, 0, sizeof(fuse_pgm_data));
+ memset(fuse_pgm_mask, 0, sizeof(fuse_pgm_mask));
+
+ if ((flags & FLAGS_ODMRSVD)) {
+ set_fuse(ODM_RSVD, info->odm_rsvd);
+ flags &= ~FLAGS_ODMRSVD;
+ }
+
+ /* do not burn any more if secure mode is set */
+ if (fuse_odm_prod_mode())
+ goto out;
+
+ for_each_set_bit(i, (unsigned long *)&flags, MAX_PARAMS)
+ set_fuse(i, src + fuse_info_tbl[i].data_offset);
+
+out:
+ pr_debug("ready to program");
+}
+
+static void fuse_power_enable(void)
+{
+#if ENABLE_FUSE_BURNING
+ tegra_fuse_writel(0x1, FUSE_PWR_GOOD_SW);
+ udelay(1);
+#endif
+}
+
+static void fuse_power_disable(void)
+{
+#if ENABLE_FUSE_BURNING
+ tegra_fuse_writel(0, FUSE_PWR_GOOD_SW);
+ udelay(1);
+#endif
+}
+
+static void fuse_program_array(int pgm_cycles)
+{
+ u32 reg, fuse_val[2];
+ u32 *data = tmp_fuse_pgm_data, addr = 0, *mask = fuse_pgm_mask;
+ int i = 0;
+
+ fuse_cmd_sense();
+
+ /* get the first 2 fuse bytes */
+ fuse_val[0] = fuse_cmd_read(0);
+ fuse_val[1] = fuse_cmd_read(1);
+
+ fuse_power_enable();
+
+ /*
+ * The fuse macro is a high density macro. Fuses are
+ * burned using an addressing mechanism, so no need to prepare
+ * the full list, but more write to control registers are needed.
+ * The only bit that can be written at first is bit 0, a special write
+ * protection bit by assumptions all other bits are at 0
+ *
+ * The programming pulse must have a precise width of
+ * [9000, 11000] ns.
+ */
+ if (pgm_cycles > 0) {
+ reg = pgm_cycles;
+ tegra_fuse_writel(reg, FUSE_TIME_PGM);
+ }
+ fuse_val[0] = (0x1 & ~fuse_val[0]);
+ fuse_val[1] = (0x1 & ~fuse_val[1]);
+ fuse_cmd_write(fuse_val[0], 0);
+ fuse_cmd_write(fuse_val[1], 1);
+
+ fuse_power_disable();
+
+ /*
+ * this will allow programming of other fuses
+ * and the reading of the existing fuse values
+ */
+ fuse_cmd_sense();
+
+ /* Clear out all bits that have already been burned or masked out */
+ memcpy(data, fuse_pgm_data, sizeof(fuse_pgm_data));
+
+ for (addr = 0; addr < NFUSES; addr += 2, data++, mask++) {
+ reg = fuse_cmd_read(addr);
+ pr_debug("%d: 0x%x 0x%x 0x%x\n", addr, (u32)(*data),
+ ~reg, (u32)(*mask));
+ *data = (*data & ~reg) & *mask;
+ }
+
+ fuse_power_enable();
+
+ /*
+ * Finally loop on all fuses, program the non zero ones.
+ * Words 0 and 1 are written last and they contain control fuses. We
+ * need to invalidate after writing to a control word (with the exception
+ * of the master enable). This is also the reason we write them last.
+ */
+ for (i = ARRAY_SIZE(fuse_pgm_data) - 1; i >= 0; i--) {
+ if (tmp_fuse_pgm_data[i]) {
+ fuse_cmd_write(tmp_fuse_pgm_data[i], i * 2);
+ fuse_cmd_write(tmp_fuse_pgm_data[i], (i * 2) + 1);
+ }
+
+ if (i < 2) {
+ wait_for_idle();
+ fuse_power_disable();
+ fuse_cmd_sense();
+ fuse_power_enable();
+ }
+ }
+
+ fuse_power_disable();
+}
+
+static int fuse_set(enum fuse_io_param io_param, u32 *param, int size)
+{
+ int i, nwords = size / sizeof(u32);
+ u32 *data;
+
+ if (io_param > MAX_PARAMS)
+ return -EINVAL;
+
+ data = (u32*)kzalloc(size, GFP_KERNEL);
+ if (!data) {
+ pr_err("failed to alloc %d bytes\n", size);
+ return -ENOMEM;
+ }
+
+ get_fuse(io_param, data);
+
+ /* set only new fuse bits */
+ for (i = 0; i < nwords; i++) {
+ param[i] = (~data[i] & param[i]);
+ }
+
+ kfree(data);
+ return 0;
+}
+
+/*
+ * Function pointer to optional board specific function
+ */
+int (*tegra_fuse_regulator_en)(int);
+EXPORT_SYMBOL(tegra_fuse_regulator_en);
+
+#define CAR_OSC_CTRL 0x50
+#define PMC_PLLP_OVERRIDE 0xF8
+#define PMC_OSC_OVERRIDE BIT(0)
+#define PMC_OSC_FREQ_MASK (BIT(2) | BIT(3))
+#define PMC_OSC_FREQ_SHIFT 2
+#define CAR_OSC_FREQ_SHIFT 30
+
+#define FUSE_SENSE_DONE_BIT BIT(30)
+#define START_DATA BIT(0)
+#define SKIP_RAMREPAIR BIT(1)
+#define FUSE_PGM_TIMEOUT_MS 50
+
+#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
+/* cycles corresponding to 13MHz, 19.2MHz, 12MHz, 26MHz */
+static int fuse_pgm_cycles[] = {130, 192, 120, 260};
+#else
+/* cycles corresponding to 13MHz, 16.8MHz, 19.2MHz, 38.4MHz, 12MHz, 48MHz, 26MHz */
+static int fuse_pgm_cycles[] = {130, 168, 0, 0, 192, 384, 0, 0, 120, 480, 0, 0, 260};
+#endif
+
+int tegra_fuse_program(struct fuse_data *pgm_data, u32 flags)
+{
+ u32 reg;
+ int i = 0;
+ int index;
+ int ret;
+ int delay = FUSE_PGM_TIMEOUT_MS;
+
+ if (!pgm_data || !flags) {
+ pr_err("invalid parameter");
+ return -EINVAL;
+ }
+
+ if (IS_ERR_OR_NULL(clk_fuse) ||
+ (!tegra_fuse_regulator_en && IS_ERR_OR_NULL(vdd_fuse))) {
+ pr_err("fuse write disabled");
+ return -ENODEV;
+ }
+
+ if (fuse_odm_prod_mode() && (flags != FLAGS_ODMRSVD)) {
+ pr_err("reserved odm fuses aren't allowed in secure mode");
+ return -EPERM;
+ }
+
+ if ((flags & FLAGS_ODM_PROD_MODE) &&
+ (flags & (FLAGS_SBK | FLAGS_DEVKEY))) {
+ pr_err("odm production mode and sbk/devkey not allowed");
+ return -EPERM;
+ }
+
+ clk_enable(clk_fuse);
+
+ /* make all the fuse registers visible */
+ fuse_reg_unhide();
+
+ /* check that fuse options write access hasn't been disabled */
+ mutex_lock(&fuse_lock);
+ reg = tegra_fuse_readl(FUSE_DIS_PGM);
+ mutex_unlock(&fuse_lock);
+ if (reg) {
+ pr_err("fuse programming disabled");
+ fuse_reg_hide();
+ clk_disable(clk_fuse);
+ return -EACCES;
+ }
+
+ /* enable software writes to the fuse registers */
+ tegra_fuse_writel(0, FUSE_WRITE_ACCESS);
+
+ mutex_lock(&fuse_lock);
+ memcpy(&fuse_info, pgm_data, sizeof(fuse_info));
+ for_each_set_bit(i, (unsigned long *)&flags, MAX_PARAMS) {
+ fuse_set((u32)i, fuse_info_tbl[i].addr,
+ fuse_info_tbl[i].sz);
+ }
+
+#if ENABLE_FUSE_BURNING
+ if (tegra_fuse_regulator_en)
+ ret = tegra_fuse_regulator_en(1);
+ else
+ ret = regulator_enable(vdd_fuse);
+
+ if (ret)
+ BUG_ON("regulator enable fail\n");
+
+ populate_fuse_arrs(&fuse_info, flags);
+
+ /* calculate the number of program cycles from the oscillator freq */
+ reg = readl(IO_ADDRESS(TEGRA_PMC_BASE) + PMC_PLLP_OVERRIDE);
+ if (reg & PMC_OSC_OVERRIDE) {
+ index = (reg & PMC_OSC_FREQ_MASK) >> PMC_OSC_FREQ_SHIFT;
+ } else {
+ reg = readl(IO_ADDRESS(TEGRA_CLK_RESET_BASE) + CAR_OSC_CTRL);
+ index = reg >> CAR_OSC_FREQ_SHIFT;
+ }
+
+ pr_debug("%s: use %d programming cycles\n", __func__, fuse_pgm_cycles[index]);
+ fuse_program_array(fuse_pgm_cycles[index]);
+
+ memset(&fuse_info, 0, sizeof(fuse_info));
+
+ if (tegra_fuse_regulator_en)
+ tegra_fuse_regulator_en(0);
+ else
+ regulator_disable(vdd_fuse);
+#endif
+
+ mutex_unlock(&fuse_lock);
+
+ /* disable software writes to the fuse registers */
+ tegra_fuse_writel(1, FUSE_WRITE_ACCESS);
+
+ /* make all the fuse registers invisible */
+ fuse_reg_hide();
+
+ /* apply the fuse values immediately instead of resetting the chip */
+ fuse_cmd_sense();
+
+ tegra_fuse_writel(START_DATA | SKIP_RAMREPAIR, FUSE_PRIV2INTFC);
+
+ /* check sense and shift done in addition to IDLE */
+ do {
+ mdelay(1);
+ reg = tegra_fuse_readl(FUSE_CTRL);
+ reg &= (FUSE_SENSE_DONE_BIT | STATE_IDLE);
+ } while ((reg != (FUSE_SENSE_DONE_BIT | STATE_IDLE)) && (--delay > 0));
+
+ clk_disable(clk_fuse);
+
+ return ((delay > 0) ? 0 : -ETIMEDOUT);
+}
+
+static int fuse_name_to_param(const char *str)
+{
+ int i;
+
+ for (i = DEVKEY; i < ARRAY_SIZE(fuse_info_tbl); i++) {
+ if (!strcmp(str, fuse_info_tbl[i].sysfs_name))
+ return i;
+ }
+
+ return -ENODATA;
+}
+
+static int char_to_xdigit(char c)
+{
+ return (c>='0' && c<='9') ? c - '0' :
+ (c>='a' && c<='f') ? c - 'a' + 10 :
+ (c>='A' && c<='F') ? c - 'A' + 10 : -1;
+}
+
+#define CHK_ERR(x) \
+{ \
+ if (x) \
+ { \
+ pr_err("%s: sysfs_create_file fail(%d)!", __func__, x); \
+ return x; \
+ } \
+}
+
+static ssize_t fuse_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ enum fuse_io_param param = fuse_name_to_param(attr->attr.name);
+ int ret, i = 0;
+ int orig_count = count;
+ struct fuse_data data = {0};
+ u32 *raw_data = ((u32 *)&data) + fuse_info_tbl[param].data_offset;
+ u8 *raw_byte_data = (u8 *)raw_data;
+
+ if ((param == -1) || (param == -ENODATA)) {
+ pr_err("%s: invalid fuse\n", __func__);
+ return -EINVAL;
+ }
+
+ if (!isxdigit(*buf))
+ return -EINVAL;
+
+ if (fuse_odm_prod_mode()) {
+ pr_err("%s: device locked. odm fuse already blown\n", __func__);
+ return -EPERM;
+ }
+
+ count--;
+ if (DIV_ROUND_UP(count, 2) > fuse_info_tbl[param].sz) {
+ pr_err("%s: fuse parameter too long, should be %d character(s)\n",
+ __func__, fuse_info_tbl[param].sz * 2);
+ return -EINVAL;
+ }
+
+ /* we need to fit each character into a single nibble */
+ raw_byte_data += DIV_ROUND_UP(count, 2) - 1;
+
+ /* in case of odd number of writes, write the first one here */
+ if (count & BIT(0)) {
+ *raw_byte_data = char_to_xdigit(*buf);
+ buf++;
+ raw_byte_data--;
+ count--;
+ }
+
+ for (i = 1; i <= count; i++, buf++) {
+ if (i & BIT(0)) {
+ *raw_byte_data = char_to_xdigit(*buf);
+ } else {
+ *raw_byte_data <<= 4;
+ *raw_byte_data |= char_to_xdigit(*buf);
+ raw_byte_data--;
+ }
+ }
+
+ ret = tegra_fuse_program(&data, BIT(param));
+ if (ret) {
+ pr_err("%s: fuse program fail(%d)\n", __func__, ret);
+ orig_count = ret;
+ goto done;
+ }
+
+ /* if odm prodn mode fuse is burnt, change file permissions to 0440 */
+ if (param == ODM_PROD_MODE) {
+ CHK_ERR(sysfs_chmod_file(kobj, &attr->attr, 0440));
+ CHK_ERR(sysfs_chmod_file(kobj, &devkey_attr.attr, 0440));
+ CHK_ERR(sysfs_chmod_file(kobj, &jtagdis_attr.attr, 0440));
+ CHK_ERR(sysfs_chmod_file(kobj, &sec_boot_dev_cfg_attr.attr, 0440));
+ CHK_ERR(sysfs_chmod_file(kobj, &sec_boot_dev_sel_attr.attr, 0440));
+ CHK_ERR(sysfs_chmod_file(kobj, &sbk_attr.attr, 0440));
+ CHK_ERR(sysfs_chmod_file(kobj, &sw_rsvd_attr.attr, 0440));
+ CHK_ERR(sysfs_chmod_file(kobj, &ignore_dev_sel_straps_attr.attr, 0440));
+ CHK_ERR(sysfs_chmod_file(kobj, &odm_rsvd_attr.attr, 0440));
+ }
+
+done:
+ return orig_count;
+}
+
+static ssize_t fuse_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ enum fuse_io_param param = fuse_name_to_param(attr->attr.name);
+ u32 data[8];
+ char str[8];
+ int ret, i;
+
+ if ((param == -1) || (param == -ENODATA)) {
+ pr_err("%s: invalid fuse\n", __func__);
+ return -EINVAL;
+ }
+
+ if ((param == SBK) && fuse_odm_prod_mode()) {
+ pr_err("device locked. sbk read not allowed\n");
+ return 0;
+ }
+
+ memset(data, 0, sizeof(data));
+ ret = tegra_fuse_read(param, data, fuse_info_tbl[param].sz);
+ if (ret) {
+ pr_err("%s: read fail(%d)\n", __func__, ret);
+ return ret;
+ }
+
+ strcpy(buf, "");
+ for (i = (fuse_info_tbl[param].sz/sizeof(u32)) - 1; i >= 0 ; i--) {
+ sprintf(str, "%08x", data[i]);
+ strcat(buf, str);
+ }
+
+ strcat(buf, "\n");
+ return strlen(buf);
+}
+
+static int __init tegra_fuse_program_init(void)
+{
+ if (!tegra_fuse_regulator_en) {
+ /* get vdd_fuse regulator */
+ vdd_fuse = regulator_get(NULL, "vdd_fuse");
+ if (IS_ERR_OR_NULL(vdd_fuse))
+ pr_err("%s: no vdd_fuse. fuse write disabled\n", __func__);
+ }
+
+ clk_fuse = clk_get_sys("fuse-tegra", "fuse_burn");
+ if (IS_ERR_OR_NULL(clk_fuse)) {
+ pr_err("%s: no clk_fuse. fuse read/write disabled\n", __func__);
+ if (!IS_ERR_OR_NULL(vdd_fuse)) {
+ regulator_put(vdd_fuse);
+ vdd_fuse = NULL;
+ }
+ return -ENODEV;
+ }
+
+ fuse_kobj = kobject_create_and_add("fuse", firmware_kobj);
+ if (!fuse_kobj) {
+ pr_err("%s: fuse_kobj create fail\n", __func__);
+ regulator_put(vdd_fuse);
+ clk_put(clk_fuse);
+ return -ENODEV;
+ }
+
+ mutex_init(&fuse_lock);
+
+ /* change fuse file permissions, if ODM production fuse is not blown */
+ if (!fuse_odm_prod_mode())
+ {
+ devkey_attr.attr.mode = 0640;
+ jtagdis_attr.attr.mode = 0640;
+ odm_prod_mode_attr.attr.mode = 0640;
+ sec_boot_dev_cfg_attr.attr.mode = 0640;
+ sec_boot_dev_sel_attr.attr.mode = 0640;
+ sbk_attr.attr.mode = 0640;
+ sw_rsvd_attr.attr.mode = 0640;
+ ignore_dev_sel_straps_attr.attr.mode = 0640;
+ odm_rsvd_attr.attr.mode = 0640;
+ odm_prod_mode_attr.attr.mode = 0640;
+ }
+
+ CHK_ERR(sysfs_create_file(fuse_kobj, &odm_prod_mode_attr.attr));
+ CHK_ERR(sysfs_create_file(fuse_kobj, &devkey_attr.attr));
+ CHK_ERR(sysfs_create_file(fuse_kobj, &jtagdis_attr.attr));
+ CHK_ERR(sysfs_create_file(fuse_kobj, &sec_boot_dev_cfg_attr.attr));
+ CHK_ERR(sysfs_create_file(fuse_kobj, &sec_boot_dev_sel_attr.attr));
+ CHK_ERR(sysfs_create_file(fuse_kobj, &sbk_attr.attr));
+ CHK_ERR(sysfs_create_file(fuse_kobj, &sw_rsvd_attr.attr));
+ CHK_ERR(sysfs_create_file(fuse_kobj, &ignore_dev_sel_straps_attr.attr));
+ CHK_ERR(sysfs_create_file(fuse_kobj, &odm_rsvd_attr.attr));
+
+ return 0;
+}
+
+static void __exit tegra_fuse_program_exit(void)
+{
+
+ fuse_power_disable();
+ fuse_reg_hide();
+
+ if (!IS_ERR_OR_NULL(vdd_fuse))
+ regulator_put(vdd_fuse);
+
+ if (!IS_ERR_OR_NULL(clk_fuse))
+ clk_put(clk_fuse);
+
+ sysfs_remove_file(fuse_kobj, &odm_prod_mode_attr.attr);
+ sysfs_remove_file(fuse_kobj, &devkey_attr.attr);
+ sysfs_remove_file(fuse_kobj, &jtagdis_attr.attr);
+ sysfs_remove_file(fuse_kobj, &sec_boot_dev_cfg_attr.attr);
+ sysfs_remove_file(fuse_kobj, &sec_boot_dev_sel_attr.attr);
+ sysfs_remove_file(fuse_kobj, &sbk_attr.attr);
+ sysfs_remove_file(fuse_kobj, &sw_rsvd_attr.attr);
+ sysfs_remove_file(fuse_kobj, &ignore_dev_sel_straps_attr.attr);
+ sysfs_remove_file(fuse_kobj, &odm_rsvd_attr.attr);
+ kobject_del(fuse_kobj);
+}
+
+late_initcall(tegra_fuse_program_init);
+module_exit(tegra_fuse_program_exit);