summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra
diff options
context:
space:
mode:
authorVarun Wadekar <vwadekar@nvidia.com>2011-06-21 15:00:22 +0530
committerYuhao Ding <yding@nvidia.com>2011-06-30 13:00:24 -0700
commit8430b859578af1c0a25954d7b018430941943892 (patch)
treed52a23c8cc0f2a79886071451714231e3e5af4e9 /arch/arm/mach-tegra
parent9402a0b2e6ef33a6d28ad894c9cc8c4d4ed77f9f (diff)
arm: tegra: fuse: new programming sequence
- vdd_core needs to be 1.14V min before fuse write/read - add wait_for_idle before accessing fuses - add proper programming of PRIV2INTFS field Bug 841766 Change-Id: Ie9ec27bdbc975a4e3ce40a0ce60fe7049f7c2429 Signed-off-by: Varun Wadekar <vwadekar@nvidia.com> Reviewed-on: http://git-master/r/37618 Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com> Reviewed-by: Venu Byravarasu <vbyravarasu@nvidia.com>
Diffstat (limited to 'arch/arm/mach-tegra')
-rw-r--r--arch/arm/mach-tegra/include/mach/tegra2_fuse.h3
-rw-r--r--arch/arm/mach-tegra/tegra2_fuse.c184
2 files changed, 134 insertions, 53 deletions
diff --git a/arch/arm/mach-tegra/include/mach/tegra2_fuse.h b/arch/arm/mach-tegra/include/mach/tegra2_fuse.h
index 21be82a8630b..c657ac0929a2 100644
--- a/arch/arm/mach-tegra/include/mach/tegra2_fuse.h
+++ b/arch/arm/mach-tegra/include/mach/tegra2_fuse.h
@@ -43,11 +43,10 @@ enum fuse_io_param {
IGNORE_DEV_SEL_STRAPS, /* 1 bit long */
ODM_RSVD,
SBK_DEVKEY_STATUS,
- MASTER_ENB,
_PARAMS_U32 = 0x7FFFFFFF
};
-#define MAX_PARAMS ODM_RSVD
+#define MAX_PARAMS SBK_DEVKEY_STATUS
/* the order of the members is pre-decided. please do not change */
struct fuse_data {
diff --git a/arch/arm/mach-tegra/tegra2_fuse.c b/arch/arm/mach-tegra/tegra2_fuse.c
index 4210b0d9ef33..e62a3357dd85 100644
--- a/arch/arm/mach-tegra/tegra2_fuse.c
+++ b/arch/arm/mach-tegra/tegra2_fuse.c
@@ -1,7 +1,7 @@
/*
* arch/arm/mach-tegra/tegra2_fuse.c
*
- * Copyright (c) 2010, NVIDIA Corporation.
+ * Copyright (c) 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
@@ -44,6 +44,7 @@
#include <linux/regulator/consumer.h>
#include <linux/ctype.h>
#include <linux/wakelock.h>
+#include <linux/clk.h>
#include <mach/tegra2_fuse.h>
#include <mach/iomap.h>
@@ -64,6 +65,7 @@
#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;
@@ -102,12 +104,12 @@ static struct kobj_attribute odm_rsvd_attr =
static u32 fuse_pgm_data[NFUSES / 2];
static u32 fuse_pgm_mask[NFUSES / 2];
static u32 tmp_fuse_pgm_data[NFUSES / 2];
-static u32 master_enable;
DEFINE_MUTEX(fuse_lock);
static struct fuse_data fuse_info;
-struct regulator *vdd_fuse = NULL;
+struct regulator *vdd_fuse;
+struct clk *clk_fuse;
#define FUSE_NAME_LEN 30
@@ -213,6 +215,7 @@ static void wait_for_idle(void)
u32 reg;
do {
+ udelay(1);
reg = tegra_fuse_readl(FUSE_CTRL);
} while ((reg & (0xF << 16)) != STATE_IDLE);
}
@@ -315,9 +318,14 @@ static void get_fuse(enum fuse_io_param io_param, u32 *out)
int tegra_fuse_read(enum fuse_io_param io_param, u32 *data, int size)
{
- int ret = 0, nbits;
+ 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;
@@ -328,6 +336,8 @@ int tegra_fuse_read(enum fuse_io_param io_param, u32 *data, int size)
}
mutex_lock(&fuse_lock);
+
+ clk_enable(clk_fuse);
fuse_reg_unhide();
fuse_cmd_sense();
@@ -346,15 +356,19 @@ int tegra_fuse_read(enum fuse_io_param io_param, u32 *data, int size)
}
fuse_reg_hide();
+ clk_disable(clk_fuse);
mutex_unlock(&fuse_lock);
- return ret;
+
+ 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);
}
@@ -390,7 +404,6 @@ static void set_fuse(enum fuse_io_param io_param, u32 *data)
static void populate_fuse_arrs(struct fuse_data *info, u32 flags)
{
- u32 data = 0;
u32 *src = (u32 *)info;
int i;
@@ -435,7 +448,6 @@ static void fuse_program_array(int pgm_cycles)
u32 *data = tmp_fuse_pgm_data, addr = 0, *mask = fuse_pgm_mask;
int i = 0;
- fuse_reg_unhide();
fuse_cmd_sense();
/* get the first 2 fuse bytes */
@@ -496,20 +508,13 @@ static void fuse_program_array(int pgm_cycles)
}
if (i < 2) {
+ wait_for_idle();
fuse_power_disable();
fuse_cmd_sense();
fuse_power_enable();
}
}
- /* Read all data into the chip options */
- tegra_fuse_writel(0x1, FUSE_PRIV2INTFC);
- udelay(1);
- tegra_fuse_writel(0, FUSE_PRIV2INTFC);
-
- while (!(tegra_fuse_readl(FUSE_CTRL) & (1 << 30)));
-
- fuse_reg_hide();
fuse_power_disable();
}
@@ -529,17 +534,21 @@ static int fuse_set(enum fuse_io_param io_param, u32 *param, int size)
get_fuse(io_param, data);
+ /* set only new fuse bits */
for (i = 0; i < nwords; i++) {
- if ((data[i] | param[i]) != param[i]) {
- pr_info("hw_val: 0x%x, sw_val: 0x%x, final: 0x%x\n",
- data[i], param[i], (data[i] | param[i]));
- param[i] = (data[i] | param[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)
@@ -547,6 +556,11 @@ static int fuse_set(enum fuse_io_param io_param, u32 *param, int size)
#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
+
/* cycles corresponding to 13MHz, 19.2MHz, 12MHz, 26MHz */
static int fuse_pgm_cycles[] = {130, 192, 120, 260};
@@ -555,17 +569,22 @@ 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;
- mutex_lock(&fuse_lock);
- reg = tegra_fuse_readl(FUSE_DIS_PGM);
- mutex_unlock(&fuse_lock);
- if (reg) {
- pr_err("fuse programming disabled");
- return -EACCES;
+ 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 are allowed in secure mode");
+ pr_err("reserved odm fuses aren't allowed in secure mode");
return -EPERM;
}
@@ -575,11 +594,25 @@ int tegra_fuse_program(struct fuse_data *pgm_data, u32 flags)
return -EPERM;
}
- if (IS_ERR_OR_NULL(vdd_fuse)) {
- pr_err("no regulator. fuse programming disabled\n");
- 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) {
@@ -587,7 +620,15 @@ int tegra_fuse_program(struct fuse_data *pgm_data, u32 flags)
fuse_info_tbl[i].sz);
}
- regulator_enable(vdd_fuse);
+#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 */
@@ -603,17 +644,38 @@ int tegra_fuse_program(struct fuse_data *pgm_data, u32 flags)
fuse_program_array(fuse_pgm_cycles[index]);
memset(&fuse_info, 0, sizeof(fuse_info));
- regulator_disable(vdd_fuse);
- mutex_unlock(&fuse_lock);
- return 0;
-}
+ if (tegra_fuse_regulator_en)
+ tegra_fuse_regulator_en(0);
+ else
+ regulator_disable(vdd_fuse);
+#endif
-void tegra_fuse_program_disable(void)
-{
- mutex_lock(&fuse_lock);
- tegra_fuse_writel(0x1, FUSE_DIS_PGM);
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);
+ udelay(1);
+
+ /* 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));
+
+ tegra_fuse_writel(0, FUSE_PRIV2INTFC);
+
+ clk_disable(clk_fuse);
+ return 0;
}
static int fuse_name_to_param(const char *str)
@@ -644,8 +706,6 @@ static int char_to_xdigit(int c)
} \
}
-#define CHARS_PER_WORD 8
-
static ssize_t fuse_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
@@ -663,11 +723,11 @@ static ssize_t fuse_store(struct kobject *kobj, struct kobj_attribute *attr,
}
if (!isxdigit(*buf))
- return count;
+ return -EINVAL;
if (fuse_odm_prod_mode()) {
pr_err("%s: device locked. odm fuse already blown\n", __func__);
- return 0;
+ return -EPERM;
}
count--;
@@ -684,6 +744,7 @@ static ssize_t fuse_store(struct kobject *kobj, struct kobj_attribute *attr,
/* 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 % 2) {
*raw_byte_data = char_to_xdigit(*buf);
buf++;
@@ -703,10 +764,9 @@ static ssize_t fuse_store(struct kobject *kobj, struct kobj_attribute *attr,
ret = tegra_fuse_program(&data, BIT(param));
if (ret) {
- wake_unlock(&fuse_wk_lock);
- wake_lock_destroy(&fuse_wk_lock);
pr_err("%s: fuse program fail(%d)\n", __func__, ret);
- return ret;
+ orig_count = ret;
+ goto done;
}
/* if odm prodn mode fuse is burnt, change file permissions to 0440 */
@@ -722,6 +782,7 @@ static ssize_t fuse_store(struct kobject *kobj, struct kobj_attribute *attr,
CHK_ERR(sysfs_chmod_file(kobj, &odm_rsvd_attr.attr, 0440));
}
+done:
wake_unlock(&fuse_wk_lock);
wake_lock_destroy(&fuse_wk_lock);
return orig_count;
@@ -763,14 +824,28 @@ static ssize_t fuse_show(struct kobject *kobj, struct kobj_attribute *attr, char
static int __init tegra_fuse_program_init(void)
{
- /* get vfuse regulator */
- vdd_fuse = regulator_get(NULL, "vdd_fuse");
- if (IS_ERR_OR_NULL(vdd_fuse))
- pr_err("%s: could not get vdd_fuse. fuse programming disabled\n", __func__);
+ 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;
}
@@ -806,9 +881,16 @@ static int __init tegra_fuse_program_init(void)
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);
@@ -821,5 +903,5 @@ static void __exit tegra_fuse_program_exit(void)
kobject_del(fuse_kobj);
}
-module_init(tegra_fuse_program_init);
+late_initcall(tegra_fuse_program_init);
module_exit(tegra_fuse_program_exit);