diff options
author | Varun Wadekar <vwadekar@nvidia.com> | 2011-06-16 16:38:30 +0530 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2011-11-30 21:47:33 -0800 |
commit | 1633a03903468c7288841fa9299b24f49dfc3c52 (patch) | |
tree | 7ae7c8819a9c4b45e3ea7022489a692a67dccf64 | |
parent | 5b1b7057aafddc10ebb92b047841ff68e14baa53 (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
-rw-r--r-- | arch/arm/mach-tegra/Makefile | 2 | ||||
-rw-r--r-- | arch/arm/mach-tegra/include/mach/tegra_odm_fuses.h (renamed from arch/arm/mach-tegra/include/mach/tegra2_fuse.h) | 11 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra_odm_fuses.c (renamed from arch/arm/mach-tegra/tegra2_fuse.c) | 266 |
3 files changed, 200 insertions, 79 deletions
diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile index e8f6a9b6bddd..f33bc1ea2386 100644 --- a/arch/arm/mach-tegra/Makefile +++ b/arch/arm/mach-tegra/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += sleep-t2.o obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += sleep-t3.o obj-y += fuse.o obj-y += kfuse.o +obj-y += tegra_odm_fuses.o obj-$(CONFIG_TEGRA_LEGACY_AUDIO) += tegra_i2s_audio.o obj-$(CONFIG_TEGRA_LEGACY_AUDIO) += tegra_spdif_audio.o obj-y += mc.o @@ -39,7 +40,6 @@ obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra2_dvfs.o obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += tegra3_dvfs.o obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += latency_allowance.o endif -obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra2_fuse.o ifeq ($(CONFIG_TEGRA_SILICON_PLATFORM),y) obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra2_speedo.o obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += tegra3_speedo.o diff --git a/arch/arm/mach-tegra/include/mach/tegra2_fuse.h b/arch/arm/mach-tegra/include/mach/tegra_odm_fuses.h index 21be82a8630b..364e66caea00 100644 --- a/arch/arm/mach-tegra/include/mach/tegra2_fuse.h +++ b/arch/arm/mach-tegra/include/mach/tegra_odm_fuses.h @@ -1,7 +1,7 @@ /* - * arch/arm/mach-tegra/include/mach/tegra2_fuse.h + * arch/arm/mach-tegra/include/mach/tegra_odm_fuses.h * - * 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 @@ -18,8 +18,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef __MACH_TEGRA2_FUSE_H -#define __MACH_TEGRA2_FUSE_H +#ifndef __MACH_TEGRA_ODM_FUSES_H +#define __MACH_TEGRA_ODM_FUSES_H #define SBK_DEVKEY_STATUS_SZ sizeof(u32) @@ -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/tegra_odm_fuses.c index e38e707b478c..9cd7aa86a224 100644 --- a/arch/arm/mach-tegra/tegra2_fuse.c +++ b/arch/arm/mach-tegra/tegra_odm_fuses.c @@ -1,7 +1,7 @@ /* - * arch/arm/mach-tegra/tegra2_fuse.c + * arch/arm/mach-tegra/tegra_odm_fuses.c * - * Copyright (c) 2010, NVIDIA Corporation. + * 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 @@ -43,8 +43,9 @@ #include <linux/kobject.h> #include <linux/regulator/consumer.h> #include <linux/ctype.h> +#include <linux/clk.h> -#include <mach/tegra2_fuse.h> +#include <mach/tegra_odm_fuses.h> #include <mach/iomap.h> #include "fuse.h" @@ -63,6 +64,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; @@ -101,12 +103,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 @@ -124,8 +126,13 @@ 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", @@ -151,8 +158,13 @@ static struct param_info fuse_info_tbl[] = { [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", @@ -160,8 +172,13 @@ static struct param_info fuse_info_tbl[] = { [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", @@ -169,8 +186,13 @@ static struct param_info fuse_info_tbl[] = { [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", @@ -178,8 +200,13 @@ static struct param_info fuse_info_tbl[] = { [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", @@ -187,8 +214,13 @@ static struct param_info fuse_info_tbl[] = { [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", @@ -196,8 +228,13 @@ static struct param_info fuse_info_tbl[] = { [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", @@ -205,13 +242,6 @@ static struct param_info fuse_info_tbl[] = { [SBK_DEVKEY_STATUS] = { .sz = SBK_DEVKEY_STATUS_SZ, }, - [MASTER_ENB] = { - .addr = &master_enable, - .sz = sizeof(u8), - .start_off = 0x0, - .start_bit = 0, - .nbits = 1, - }, }; static void wait_for_idle(void) @@ -219,6 +249,7 @@ static void wait_for_idle(void) u32 reg; do { + udelay(1); reg = tegra_fuse_readl(FUSE_CTRL); } while ((reg & (0xF << 16)) != STATE_IDLE); } @@ -232,6 +263,7 @@ 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; @@ -247,6 +279,7 @@ 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); @@ -261,6 +294,7 @@ static void fuse_cmd_sense(void) { u32 reg; + wait_for_idle(); reg = tegra_fuse_readl(FUSE_CTRL); reg &= ~FUSE_CMD_MASK; reg |= FUSE_SENSE; @@ -318,9 +352,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; @@ -331,6 +370,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(); @@ -349,15 +390,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); } @@ -393,17 +438,12 @@ 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; memset(fuse_pgm_data, 0, sizeof(fuse_pgm_data)); memset(fuse_pgm_mask, 0, sizeof(fuse_pgm_mask)); - /* enable program bit */ - data = 1; - set_fuse(MASTER_ENB, &data); - if ((flags & FLAGS_ODMRSVD)) { set_fuse(ODM_RSVD, info->odm_rsvd); flags &= ~FLAGS_ODMRSVD; @@ -442,7 +482,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 */ @@ -503,20 +542,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(); } @@ -530,23 +562,27 @@ static int fuse_set(enum fuse_io_param io_param, u32 *param, int size) data = (u32*)kzalloc(size, GFP_KERNEL); if (!data) { - pr_err("failed to alloc %d bytes\n", nwords); + 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++) { - 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) @@ -554,25 +590,40 @@ 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 + +#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; - 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; } @@ -582,11 +633,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) { @@ -594,7 +659,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 */ @@ -609,22 +682,37 @@ int tegra_fuse_program(struct fuse_data *pgm_data, u32 flags) pr_debug("%s: use %d programming cycles\n", __func__, fuse_pgm_cycles[index]); fuse_program_array(fuse_pgm_cycles[index]); - /* disable program bit */ - reg = 0; - set_fuse(MASTER_ENB, ®); - 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); + + /* 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) @@ -639,7 +727,7 @@ static int fuse_name_to_param(const char *str) return -ENODATA; } -static int char_to_xdigit(int c) +static int char_to_xdigit(char c) { return (c>='0' && c<='9') ? c - '0' : (c>='a' && c<='f') ? c - 'a' + 10 : @@ -655,13 +743,12 @@ 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) { 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; @@ -672,11 +759,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--; @@ -686,19 +773,32 @@ static ssize_t fuse_store(struct kobject *kobj, struct kobj_attribute *attr, return -EINVAL; } + /* we need to fit each character into a single nibble */ raw_byte_data += DIV_ROUND_UP(count, 2) - 1; - for (i = 0; i < DIV_ROUND_UP(count, 2); i++, buf++) { + + /* in case of odd number of writes, write the first one here */ + if (count & BIT(0)) { *raw_byte_data = char_to_xdigit(*buf); - *raw_byte_data <<= 4; buf++; - *raw_byte_data |= (char_to_xdigit(*buf) & 0xF); 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); - return ret; + orig_count = ret; + goto done; } /* if odm prodn mode fuse is burnt, change file permissions to 0440 */ @@ -714,7 +814,8 @@ static ssize_t fuse_store(struct kobject *kobj, struct kobj_attribute *attr, CHK_ERR(sysfs_chmod_file(kobj, &odm_rsvd_attr.attr, 0440)); } - return count; +done: + return orig_count; } static ssize_t fuse_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) @@ -753,14 +854,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; } @@ -796,9 +911,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); @@ -811,5 +933,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); |