diff options
author | Varun Wadekar <vwadekar@nvidia.com> | 2011-06-16 16:38:30 +0530 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2012-03-22 23:28:14 -0700 |
commit | 15cf6cd771831c02b1af3168c5c3bf366b45ee2e (patch) | |
tree | 3e367f3a6ad2c3e7931f61ed16e597ac7a4da7da /arch/arm/mach-tegra/tegra_odm_fuses.c | |
parent | 456e670d79d09614a22d71173ae95deaa41b41d5 (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.c | 937 |
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); |