From 6741556a7dc65c859762f07d0561001e91db4bf1 Mon Sep 17 00:00:00 2001 From: Kartik Date: Fri, 3 Feb 2023 10:59:02 +0000 Subject: [PATCH] soc/tegra: fuse: Remove fuse-burn driver Burning fuse from Kernel is no longer a valid usecase and the driver is only used to add sysfs to allow reading fuse values from userspace. Upstream kernel already supports this with the nvmem interface. Deprecate and remove the fuse-burn driver as an OOT module. Bug 3934453 Change-Id: I6963531938adcefca76573d25a510268c515b34a Signed-off-by: Kartik Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2852954 Reviewed-by: Jonathan Hunter GVS: Gerrit_Virtual_Submit --- drivers/soc/tegra/fuse/Makefile | 3 +- drivers/soc/tegra/fuse/fuse-burn.c | 1145 ---------------------------- 2 files changed, 1 insertion(+), 1147 deletions(-) delete mode 100644 drivers/soc/tegra/fuse/fuse-burn.c diff --git a/drivers/soc/tegra/fuse/Makefile b/drivers/soc/tegra/fuse/Makefile index b7d0c930..990f0c14 100644 --- a/drivers/soc/tegra/fuse/Makefile +++ b/drivers/soc/tegra/fuse/Makefile @@ -1,5 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 -# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. +# Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved. -obj-m += fuse-burn.o obj-m += kfuse.o diff --git a/drivers/soc/tegra/fuse/fuse-burn.c b/drivers/soc/tegra/fuse/fuse-burn.c deleted file mode 100644 index 425765c2..00000000 --- a/drivers/soc/tegra/fuse/fuse-burn.c +++ /dev/null @@ -1,1145 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//#include -#include -#include - -#define TEGRA_FUSE_CTRL 0x0 -#define TEGRA_FUSE_CTRL_CMD_READ 0x1 -#define TEGRA_FUSE_CTRL_CMD_WRITE 0x2 -#define TEGRA_FUSE_CTRL_CMD_SENSE 0x3 -#define TEGRA_FUSE_CTRL_CMD_MASK 0x3 -#define TEGRA_FUSE_CTRL_STATE_IDLE 0x4 -#define TEGRA_FUSE_CTRL_STATE_MASK 0x1f -#define TEGRA_FUSE_CTRL_STATE_SHIFT 16 -#define TEGRA_FUSE_CTRL_PD BIT(26) -#define TEGRA_FUSE_CTRL_SENSE_DONE BIT(30) -#define TEGRA_FUSE_ADDR 0x4 -#define TEGRA_FUSE_RDATA 0x8 -#define TEGRA_FUSE_WDATA 0xc -#define TEGRA_FUSE_TIME_PGM2 0x1c -#define TEGRA_FUSE_PRIV2INTFC_START 0x20 -#define TEGRA_FUSE_PRIV2INTFC_SDATA 0x1 -#define TEGRA_FUSE_PRIV2INTFC_SKIP_RECORDS 0x2 -#define TEGRA_FUSE_DISABLE_REG_PROG 0x2c -#define TEGRA_FUSE_WRITE_ACCESS_SW 0x30 -#define TEGRA_FUSE_OPT_TPC_DISABLE 0x20c -#define TEGRA_FUSE_SLAM 0x84 -#define TEGRA_FUSE_SLAM_LOCK (0x1 << 31) - -#define TEGRA_FUSE_ENABLE_PRGM_OFFSET 0 -#define TEGRA_FUSE_ENABLE_PRGM_REDUND_OFFSET 1 -#define TEGRA_FUSE_BURN_MAX_FUSES 30 - -#define TEGRA_FUSE_ODM_PRODUCTION_MODE 0xa0 -#define H2_START_MACRO_BIT_INDEX 2167 -#define H2_END_MACRO_BIT_INDEX 3326 - -#define FPERM_R 0440 -#define FPERM_RW 0660 - -#define TEGRA_FUSE_SHUTDOWN_LIMIT_MODIFIER 2000 - -#define PMC_FUSE_CTRL_ENABLE_REDIRECTION (1 << 0) -#define PMC_FUSE_CTRL_PS18_LATCH_SET (1 << 8) -#define PMC_FUSE_CTRL_PS18_LATCH_CLEAR (1 << 9) - -struct fuse_burn_data { - char *name; - u32 start_offset; - u32 start_bit; - u32 size_bits; - u32 reg_offset; - bool is_redundant; - bool is_big_endian; - bool redundant_war; - struct device_attribute attr; -}; - -struct tegra_fuse_hw_feature { - bool power_down_mode; - bool mirroring_support; - bool skip_fuse_mirroring_logic; - bool hw_mutex_support; - bool has_power_switch; - int pgm_time; - int chip_id; - uint32_t fuse_ctrl_reg; - struct fuse_burn_data burn_data[TEGRA_FUSE_BURN_MAX_FUSES]; -}; - -struct tegra_fuse_burn_dev { - struct device *dev; - void __iomem *pmc_misc_reg; - void __iomem *fuse_base_reg; - struct tegra_fuse_hw_feature *hw; - struct clk *pgm_clk; - struct clk *fuse_clk; - struct tegra_bpmp *bpmp; - u32 pgm_width; - struct thermal_zone_device *tz; - s32 min_temp; - s32 max_temp; - u32 thermal_zone; -}; - -static DEFINE_MUTEX(fuse_lock); - -static u64 chip_uid; - -static struct tegra_fuse_burn_dev *s_fuse_dev; - -/******* MISC Register configuration */ -static u32 tegra_pmc_misc_readl(struct tegra_fuse_burn_dev *fuse_dev, unsigned long offset) -{ - return readl(fuse_dev->pmc_misc_reg + offset); -} - -static void tegra_pmc_misc_writel(struct tegra_fuse_burn_dev *fuse_dev, u32 value, - unsigned long offset) -{ - writel(value, fuse_dev->pmc_misc_reg + offset + offset); -} - -static void tegra_pmc_misc_fuse_disable_mirroring(struct tegra_fuse_burn_dev *fuse_dev) -{ - u32 val; - - if (fuse_dev->hw->skip_fuse_mirroring_logic) - return; - - val = tegra_pmc_misc_readl(fuse_dev, fuse_dev->hw->fuse_ctrl_reg); - if (val & PMC_FUSE_CTRL_ENABLE_REDIRECTION) { - val &= ~PMC_FUSE_CTRL_ENABLE_REDIRECTION; - tegra_pmc_misc_writel(fuse_dev, val, - fuse_dev->hw->fuse_ctrl_reg); - } -} - -static void tegra_pmc_misc_fuse_enable_mirroring(struct tegra_fuse_burn_dev *fuse_dev) -{ - u32 val; - - if (fuse_dev->hw->skip_fuse_mirroring_logic) - return; - - val = tegra_pmc_misc_readl(fuse_dev, fuse_dev->hw->fuse_ctrl_reg); - if (!(val & PMC_FUSE_CTRL_ENABLE_REDIRECTION)) { - val |= PMC_FUSE_CTRL_ENABLE_REDIRECTION; - tegra_pmc_misc_writel(fuse_dev, val, fuse_dev->hw->fuse_ctrl_reg); - } -} - -static void tegra_pmc_misc_fuse_control_ps18_latch_set(struct tegra_fuse_burn_dev *fuse_dev) -{ - u32 val; - - val = tegra_pmc_misc_readl(fuse_dev, fuse_dev->hw->fuse_ctrl_reg); - val &= ~(PMC_FUSE_CTRL_PS18_LATCH_CLEAR); - tegra_pmc_misc_writel(fuse_dev, val, fuse_dev->hw->fuse_ctrl_reg); - mdelay(1); - val |= PMC_FUSE_CTRL_PS18_LATCH_SET; - tegra_pmc_misc_writel(fuse_dev, val, fuse_dev->hw->fuse_ctrl_reg); - - mdelay(1); -} - -static void tegra_pmc_misc_fuse_control_ps18_latch_clear(struct tegra_fuse_burn_dev *fuse_dev) -{ - u32 val; - - val = tegra_pmc_misc_readl(fuse_dev, fuse_dev->hw->fuse_ctrl_reg); - val &= ~(PMC_FUSE_CTRL_PS18_LATCH_SET); - tegra_pmc_misc_writel(fuse_dev, val, fuse_dev->hw->fuse_ctrl_reg); - mdelay(1); - val |= PMC_FUSE_CTRL_PS18_LATCH_CLEAR; - tegra_pmc_misc_writel(fuse_dev, val, fuse_dev->hw->fuse_ctrl_reg); - mdelay(1); -} - -static u32 tegra_fuse_control_read(unsigned int offset, u32 *value) -{ - int err; - - err = clk_prepare_enable(s_fuse_dev->fuse_clk); - if (err < 0) { - dev_err(s_fuse_dev->dev, "failed to enable FUSE clock: %d\n", err); - return 0; - } - - *value = readl_relaxed(s_fuse_dev->fuse_base_reg + offset); - - clk_disable_unprepare(s_fuse_dev->fuse_clk); - - return *value; -} - -static int tegra_fuse_control_write(u32 value, unsigned int offset) -{ - int err; - - err = clk_prepare_enable(s_fuse_dev->fuse_clk); - if (err < 0) { - dev_err(s_fuse_dev->dev, "failed to enable FUSE clock: %d\n", err); - return -EIO; - } - - writel(value, s_fuse_dev->fuse_base_reg + offset); - - clk_disable_unprepare(s_fuse_dev->fuse_clk); - - return 0; -} - -static void fuse_state_wait_for_idle(void) -{ - u32 reg; - u32 idle; - - do { - tegra_fuse_control_read(TEGRA_FUSE_CTRL, ®); - idle = reg & (TEGRA_FUSE_CTRL_STATE_MASK - << TEGRA_FUSE_CTRL_STATE_SHIFT); - udelay(1); - } while (idle != (TEGRA_FUSE_CTRL_STATE_IDLE - << TEGRA_FUSE_CTRL_STATE_SHIFT)); -} - -static u32 fuse_cmd_read(u32 addr) -{ - u32 reg; - - fuse_state_wait_for_idle(); - tegra_fuse_control_write(addr, TEGRA_FUSE_ADDR); - tegra_fuse_control_read(TEGRA_FUSE_CTRL, ®); - reg &= ~TEGRA_FUSE_CTRL_CMD_MASK; - reg |= TEGRA_FUSE_CTRL_CMD_READ; - tegra_fuse_control_write(reg, TEGRA_FUSE_CTRL); - fuse_state_wait_for_idle(); - tegra_fuse_control_read(TEGRA_FUSE_RDATA, ®); - - return reg; -} - -static void fuse_cmd_write(u32 value, u32 addr) -{ - u32 reg; - - fuse_state_wait_for_idle(); - tegra_fuse_control_write(addr, TEGRA_FUSE_ADDR); - tegra_fuse_control_write(value, TEGRA_FUSE_WDATA); - - tegra_fuse_control_read(TEGRA_FUSE_CTRL, ®); - reg &= ~TEGRA_FUSE_CTRL_CMD_MASK; - reg |= TEGRA_FUSE_CTRL_CMD_WRITE; - tegra_fuse_control_write(reg, TEGRA_FUSE_CTRL); - fuse_state_wait_for_idle(); - fuse_cmd_read(addr); -} - -static inline int tegra_fuse_acquire_burn_lock( - struct tegra_fuse_burn_dev *fuse_dev) -{ - u32 reg; - int num_retries = 3; - - while (num_retries--) { - tegra_fuse_control_read(TEGRA_FUSE_SLAM, ®); - - if (reg & TEGRA_FUSE_SLAM_LOCK) { - /* If mutex is still acquired after 3 retries. - * Return -EPERM. - */ - if (!num_retries) { - dev_err(fuse_dev->dev, - "fuse burn already in progress.\n"); - return -EPERM; - } - udelay(10); - continue; - } - break; - } - - /* Acquire mutex by writing 1 into the LOCK bit. */ - tegra_fuse_control_write(reg | TEGRA_FUSE_SLAM_LOCK, TEGRA_FUSE_SLAM); - dev_info(fuse_dev->dev, "acquired write lock\n"); - - return 0; -} - -static inline void tegra_fuse_release_burn_lock( - struct tegra_fuse_burn_dev *fuse_dev) -{ - u32 reg; - - tegra_fuse_control_read(TEGRA_FUSE_SLAM, ®); - - /* Release the mutex by clearing LOCK bit. */ - if (reg & TEGRA_FUSE_SLAM_LOCK) - tegra_fuse_control_write(reg & ~TEGRA_FUSE_SLAM_LOCK, - TEGRA_FUSE_SLAM); - - dev_info(fuse_dev->dev, "released write lock\n"); -} - -static bool tegra_fuse_is_fuse_burn_allowed(struct fuse_burn_data *data) -{ - u32 reg = 0; - int ret; - - /* If odm_production_mode(security mode) fuse is burnt, then - * only allow odm reserved/lock to burn - */ - ret = tegra_fuse_readl(TEGRA_FUSE_ODM_PRODUCTION_MODE, ®); - if (!ret) { - if (reg) { - if (!strncmp(data->name, "reserved_odm", 12)) - return true; - if (!strcmp(data->name, "odm_lock")) - return true; - return false; - } - } - - return true; -} - -static int tegra_fuse_form_burn_data(struct fuse_burn_data *data, - u32 *input_data, u32 *burn_data, u32 *burn_mask) -{ - int nbits = data->size_bits; - int start_bit = data->start_bit; - int i, offset, loops; - int src_bit = 0; - u32 val; - - for (offset = 0, loops = 0; nbits > 0; offset++, nbits -= loops) { - val = *input_data; - loops = min(nbits, 32 - start_bit); - for (i = 0; i < loops; i++) { - burn_mask[offset] |= BIT(start_bit + i); - if (val & BIT(src_bit)) - burn_data[offset] |= BIT(start_bit + i); - else - burn_data[offset] &= ~BIT(start_bit + i); - src_bit++; - if (src_bit == 32) { - input_data++; - val = *input_data; - src_bit = 0; - } - } - start_bit = 0; - } - - return offset; -} - -static int tegra_fuse_get_shutdown_limit(struct tegra_fuse_burn_dev *fuse_dev, - int *shutdown_limit) -{ - struct mrq_thermal_host_to_bpmp_request req; - union mrq_thermal_bpmp_to_host_response reply; - struct tegra_bpmp_message msg; - int err = 0; - - memset(&req, 0, sizeof(req)); - memset(&reply, 0, sizeof(reply)); - - req.type = CMD_THERMAL_GET_THERMTRIP; - req.get_thermtrip.zone = fuse_dev->thermal_zone; - - memset(&msg, 0, sizeof(msg)); - msg.mrq = MRQ_THERMAL; - msg.tx.data = &req; - msg.tx.size = sizeof(req); - msg.rx.data = &reply; - msg.rx.size = sizeof(reply); - err = tegra_bpmp_transfer(fuse_dev->bpmp, &msg); - if (err) - goto out; - - *shutdown_limit = reply.get_thermtrip.thermtrip; -out: - return err; -} - -static int tegra_fuse_is_temp_under_range(struct tegra_fuse_burn_dev *fuse_dev) -{ - int temp, ret = 0; - int shutdown_limit = 0; - - /* Check if temperature is under permissible range */ - ret = thermal_zone_get_temp(fuse_dev->tz, &temp); - if (ret) - goto out; - - if (temp < fuse_dev->min_temp || - temp > fuse_dev->max_temp) { - dev_err(fuse_dev->dev, "temp-%d is not under range\n", - temp); - ret = -EPERM; - goto out; - } - - if (!fuse_dev->thermal_zone) - goto out; - - ret = tegra_fuse_get_shutdown_limit(fuse_dev, &shutdown_limit); - if (ret) { - dev_err(fuse_dev->dev, "unable to get shutdown limit: %d\n", - ret); - ret = -EPERM; - goto out; - } - - /* Check if current temperature is 2C degrees below shutdown limit*/ - if (temp > (shutdown_limit - TEGRA_FUSE_SHUTDOWN_LIMIT_MODIFIER)) { - dev_err(fuse_dev->dev, "temp-%d close to shutdown limit\n", - temp); - ret = -EPERM; - } -out: - return ret; -} - -static void tegra_fuse_set_pd(bool pd) -{ - u32 reg; - - tegra_fuse_control_read(TEGRA_FUSE_CTRL, ®); - - if (pd && !(reg & TEGRA_FUSE_CTRL_PD)) { - udelay(1); - reg |= TEGRA_FUSE_CTRL_PD; - tegra_fuse_control_write(reg, TEGRA_FUSE_CTRL); - } else if (!pd && (reg & TEGRA_FUSE_CTRL_PD)) { - reg &= ~TEGRA_FUSE_CTRL_PD; - tegra_fuse_control_write(reg, TEGRA_FUSE_CTRL); - tegra_fuse_control_read(TEGRA_FUSE_CTRL, ®); - udelay(1); - } -} - -static int tegra_fuse_pre_burn_process(struct tegra_fuse_burn_dev *fuse_dev) -{ - u32 off_0_val, off_1_val, reg; - int ret; - - if (fuse_dev->tz) { - ret = tegra_fuse_is_temp_under_range(fuse_dev); - if (ret) - return ret; - } - - /* Check if fuse burn is disabled */ - reg = tegra_fuse_control_read(TEGRA_FUSE_DISABLE_REG_PROG, ®); - if (reg) { - dev_err(fuse_dev->dev, "Fuse register programming disabled\n"); - return -EIO; - } - - if (fuse_dev->hw->hw_mutex_support) { - ret = tegra_fuse_acquire_burn_lock(fuse_dev); - if (ret) - return ret; - } - - /* Enable fuse register write access */ - tegra_fuse_control_write(0, TEGRA_FUSE_WRITE_ACCESS_SW); - - /* Disable power down mode */ - if (fuse_dev->hw->power_down_mode) - tegra_fuse_set_pd(false); - - if (fuse_dev->pgm_width) - tegra_fuse_control_write(fuse_dev->pgm_width, - TEGRA_FUSE_TIME_PGM2); - - if (fuse_dev->hw->mirroring_support) - tegra_pmc_misc_fuse_disable_mirroring(fuse_dev); - - if (fuse_dev->hw->has_power_switch) - tegra_pmc_misc_fuse_control_ps18_latch_set(fuse_dev); - - /* Enable fuse program */ - off_0_val = fuse_cmd_read(TEGRA_FUSE_ENABLE_PRGM_OFFSET); - off_1_val = fuse_cmd_read(TEGRA_FUSE_ENABLE_PRGM_REDUND_OFFSET); - off_0_val = 0x1 & ~off_0_val; - off_1_val = 0x1 & ~off_1_val; - fuse_cmd_write(off_0_val, TEGRA_FUSE_ENABLE_PRGM_OFFSET); - fuse_cmd_write(off_1_val, TEGRA_FUSE_ENABLE_PRGM_REDUND_OFFSET); - - return 0; -} - -static void tegra_fuse_post_burn_process(struct tegra_fuse_burn_dev *fuse_dev) -{ - u32 reg; - u32 sense_done; - - /* burned fuse values can take effect without reset by below steps*/ - reg = TEGRA_FUSE_PRIV2INTFC_SDATA | TEGRA_FUSE_PRIV2INTFC_SKIP_RECORDS; - tegra_fuse_control_write(reg, TEGRA_FUSE_PRIV2INTFC_START); - fuse_state_wait_for_idle(); - do { - udelay(1); - tegra_fuse_control_read(TEGRA_FUSE_CTRL, ®); - sense_done = reg & TEGRA_FUSE_CTRL_SENSE_DONE; - } while (!sense_done); - - /* Enable power down mode */ - if (fuse_dev->hw->power_down_mode) - tegra_fuse_set_pd(true); - - if (fuse_dev->hw->has_power_switch) - tegra_pmc_misc_fuse_control_ps18_latch_clear(fuse_dev); - - if (fuse_dev->hw->mirroring_support) - tegra_pmc_misc_fuse_enable_mirroring(fuse_dev); - - if (fuse_dev->hw->hw_mutex_support) - tegra_fuse_release_burn_lock(fuse_dev); - - /* Disable fuse register write access */ - tegra_fuse_control_write(1, TEGRA_FUSE_WRITE_ACCESS_SW); -} - -static int tegra_fuse_burn_fuse(struct tegra_fuse_burn_dev *fuse_dev, - struct fuse_burn_data *fuse_data, u32 *input_data) -{ - u32 reg, burn_data[17] = {0}, burn_mask[17] = {0}; - int fuse_addr = fuse_data->start_offset; - int is_redundant = fuse_data->is_redundant; - int i; - int num_words; - int ret; - - ret = tegra_fuse_pre_burn_process(fuse_dev); - if (ret) - return ret; - - /* Form burn data */ - num_words = tegra_fuse_form_burn_data(fuse_data, input_data, - burn_data, burn_mask); - - /* Burn the fuse */ - for (i = 0; i < num_words; i++) { - reg = fuse_cmd_read(fuse_addr); - burn_data[i] = (burn_data[i] & ~reg) & burn_mask[i]; - if (burn_data[i]) { - fuse_cmd_write(burn_data[i], fuse_addr); - if (is_redundant) - fuse_cmd_write(burn_data[i], fuse_addr + 1); - } - if (is_redundant) - fuse_addr += 2; - else - fuse_addr += 1; - } - - tegra_fuse_post_burn_process(fuse_dev); - - return 0; -} - -static void tegra_fuse_get_fuse(struct tegra_fuse_burn_dev *fuse_dev, - struct fuse_burn_data *data, u32 *macro_buf) -{ - int start_bit = data->start_bit; - int nbits = data->size_bits; - int offset = data->start_offset; - bool is_redundant = data->is_redundant; - bool redundant_war = data->redundant_war; - int bit_position = 0; - int i, loops; - u32 actual_val, redun_val = 0; - - /* Disable power down mode */ - if (fuse_dev->hw->power_down_mode) - tegra_fuse_set_pd(false); - - do { - actual_val = fuse_cmd_read(offset); - if (is_redundant) - redun_val = fuse_cmd_read(offset + 1); - loops = min(nbits, 32 - start_bit); - for (i = 0; i < loops; i++) { - if (actual_val & (BIT(start_bit + i))) - *macro_buf |= BIT(bit_position); - /* If redundant WAR enable, skip redun_val */ - if (is_redundant && !redundant_war) { - if (redun_val & (BIT(start_bit + i))) - *macro_buf |= BIT(bit_position); - } - bit_position++; - if (bit_position == 32) { - macro_buf++; - bit_position = 0; - } - } - nbits -= loops; - if (is_redundant) - offset += 2; - else - offset += 1; - start_bit = 0; - } while (nbits > 0); - - /* Enable power down mode */ - if (fuse_dev->hw->power_down_mode) - tegra_fuse_set_pd(true); -} - -static ssize_t tegra_fuse_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct platform_device *pdev = container_of(dev, - struct platform_device, dev); - struct tegra_fuse_burn_dev *fuse_dev = platform_get_drvdata(pdev); - struct fuse_burn_data *data; - char str[9]; - u32 *macro_buf; - int num_words, i; - u32 val; - - data = container_of(attr, struct fuse_burn_data, attr); - num_words = DIV_ROUND_UP(data->size_bits, 32); - macro_buf = kcalloc(num_words, sizeof(*macro_buf), GFP_KERNEL); - if (!macro_buf) { - dev_err(dev, "buffer allocation failed\n"); - return -ENOMEM; - } - - mutex_lock(&fuse_lock); - tegra_fuse_get_fuse(fuse_dev, data, macro_buf); - mutex_unlock(&fuse_lock); - strcpy(buf, "0x"); - if (data->is_big_endian) { - for (i = 0; i < num_words; i++) { - val = cpu_to_be32(macro_buf[i]); - sprintf(str, "%08x", val); - strcat(buf, str); - } - } else { - while (num_words--) { - sprintf(str, "%08x", macro_buf[num_words]); - strcat(buf, str); - } - } - strcat(buf, "\n"); - kfree(macro_buf); - - return strlen(buf); -} - -static ssize_t tegra_fuse_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct platform_device *pdev = container_of(dev, - struct platform_device, dev); - struct tegra_fuse_burn_dev *fuse_dev = platform_get_drvdata(pdev); - struct fuse_burn_data *fuse_data; - int len = count; - int num_nibbles; - u32 input_data[17] = {0}; - u32 temp_data[17] = {0}; - char str[9] = {0}; - int copy_cnt, copy_idx; - int burn_idx = 0, idx; - int ret; - - fuse_data = container_of(attr, struct fuse_burn_data, attr); - if (!tegra_fuse_is_fuse_burn_allowed(fuse_data)) { - dev_err(dev, "security mode fuse is burnt, burn not allowed\n"); - return -EPERM; - } - - num_nibbles = DIV_ROUND_UP(fuse_data->size_bits, 4); - - if (*buf == 'x') { - len--; - buf++; - } - if (*buf == '0' && (*(buf + 1) == 'x' || *(buf + 1) == 'X')) { - len -= 2; - buf += 2; - } - len--; - if (len > num_nibbles) { - dev_err(dev, "input data too long, max is %d characters\n", - num_nibbles); - return -EINVAL; - } - - for (burn_idx = 0; len; burn_idx++) { - copy_idx = len > 8 ? (len - 8) : 0; - copy_cnt = len > 8 ? 8 : len; - memset(str, 0, sizeof(str)); - strncpy(str, buf + copy_idx, copy_cnt); - ret = kstrtouint(str, 16, &input_data[burn_idx]); - if (ret) - return ret; - len -= copy_cnt; - } - - if (fuse_data->is_big_endian) { - for (idx = --burn_idx, copy_cnt = 0; idx >= 0; - idx--, copy_cnt++) - temp_data[copy_cnt] = cpu_to_be32(input_data[idx]); - memcpy(input_data, temp_data, sizeof(input_data)); - } - - pm_stay_awake(fuse_dev->dev); - mutex_lock(&fuse_lock); - ret = tegra_fuse_burn_fuse(fuse_dev, fuse_data, input_data); - mutex_unlock(&fuse_lock); - pm_relax(fuse_dev->dev); - if (ret) - return ret; - - return count; -} - -#define TEGRA210_INT_CID 5 -#define TEGRA186_INT_CID 6 -#define TEGRA194_INT_CID 7 -#define TEGRA234_INT_CID 8 - -#define FUSE_OPT_VENDOR_CODE 0x100 -#define FUSE_OPT_VENDOR_CODE_MASK 0xf -#define FUSE_OPT_FAB_CODE 0x104 -#define FUSE_OPT_FAB_CODE_MASK 0x3f -#define FUSE_OPT_LOT_CODE_0 0x108 -#define FUSE_OPT_LOT_CODE_1 0x10c -#define FUSE_OPT_WAFER_ID 0x110 -#define FUSE_OPT_WAFER_ID_MASK 0x3f -#define FUSE_OPT_X_COORDINATE 0x114 -#define FUSE_OPT_X_COORDINATE_MASK 0x1ff -#define FUSE_OPT_Y_COORDINATE 0x118 -#define FUSE_OPT_Y_COORDINATE_MASK 0x1ff - -unsigned long long tegra_chip_uid(void) -{ - - u64 uid = 0ull; - u32 reg; - u32 cid; - u32 vendor; - u32 fab; - u32 lot; - u32 wafer; - u32 x; - u32 y; - u32 i; - - /* - * This used to be so much easier in prior chips. Unfortunately, there - * is no one-stop shopping for the unique id anymore. It must be - * constructed from various bits of information burned into the fuses - * during the manufacturing process. The 64-bit unique id is formed - * by concatenating several bit fields. The notation used for the - * various fields is with the UID composed - * thusly: - * - * - * - * Where: - * - * Field Bits Position Data - * ------- ---- -------- ---------------------------------------- - * CID 4 60 Chip id - * VENDOR 4 56 Vendor code - * FAB 6 50 FAB code - * LOT 26 24 Lot code (5-digit base-36-coded-decimal, - * re-encoded to 26 bits binary) - * WAFER 6 18 Wafer id - * X 9 9 Wafer X-coordinate - * Y 9 0 Wafer Y-coordinate - * ------- ---- - * Total 64 - */ - - reg = s_fuse_dev->hw->chip_id; - switch (reg) { - case TEGRA210: - cid = TEGRA210_INT_CID; - break; - case TEGRA186: - cid = TEGRA186_INT_CID; - break; - case TEGRA194: - cid = TEGRA194_INT_CID; - break; - case TEGRA234: - cid = TEGRA234_INT_CID; - break; - default: - cid = 0; - break; - }; - - tegra_fuse_readl(FUSE_OPT_VENDOR_CODE, ®); - vendor = reg & FUSE_OPT_VENDOR_CODE_MASK; - tegra_fuse_readl(FUSE_OPT_FAB_CODE, ®); - fab = reg & FUSE_OPT_FAB_CODE_MASK; - - /* Lot code must be re-encoded from a 5 digit base-36 'BCD' number - * to a binary number. - */ - lot = 0; - tegra_fuse_readl(FUSE_OPT_LOT_CODE_0, ®); - reg = reg << 2; - - for (i = 0; i < 5; ++i) { - u32 digit = (reg & 0xFC000000) >> 26; - - WARN_ON(digit >= 36); - lot *= 36; - lot += digit; - reg <<= 6; - } - - tegra_fuse_readl(FUSE_OPT_WAFER_ID, ®); - wafer = reg & FUSE_OPT_WAFER_ID_MASK; - tegra_fuse_readl(FUSE_OPT_X_COORDINATE, ®); - x = reg & FUSE_OPT_X_COORDINATE_MASK; - tegra_fuse_readl(FUSE_OPT_Y_COORDINATE, ®); - y = reg & FUSE_OPT_Y_COORDINATE_MASK; - - uid = ((unsigned long long)cid << 60ull) - | ((unsigned long long)vendor << 56ull) - | ((unsigned long long)fab << 50ull) - | ((unsigned long long)lot << 24ull) - | ((unsigned long long)wafer << 18ull) - | ((unsigned long long)x << 9ull) - | ((unsigned long long)y << 0ull); - return uid; -} - -static ssize_t tegra_fuse_read_ecid(struct device *dev, - struct device_attribute *attr, char *buf) -{ - sprintf(buf, "%llu\n", tegra_chip_uid()); - return strlen(buf); -} - -static ssize_t tegra_fuse_read_opt_tpc_disable(struct device *dev, - struct device_attribute *attr, char *buf) -{ - u32 reg = 0; - int retval; - - retval = tegra_fuse_readl(TEGRA_FUSE_OPT_TPC_DISABLE, ®); - - if (unlikely(retval)) { - dev_err(dev, "sysfs read failed\n"); - return -EINVAL; - } - - sprintf(buf, "0x%x\n", reg); - return strlen(buf); -} - -#define FUSE_BURN_DATA(fname, m_off, sbit, size, c_off, is_red, is_be) \ - { \ - .name = #fname, \ - .start_offset = m_off, \ - .start_bit = sbit, \ - .size_bits = size, \ - .reg_offset = c_off, \ - .is_redundant = is_red, \ - .is_big_endian = is_be, \ - .redundant_war = false, \ - .attr.show = tegra_fuse_show, \ - .attr.store = tegra_fuse_store, \ - .attr.attr.name = #fname, \ - .attr.attr.mode = 0660, \ - } -#define FUSE_SYSFS_DATA(fname, show_func, store_func, _mode) \ - { \ - .name = #fname, \ - .attr.show = show_func, \ - .attr.store = store_func, \ - .attr.attr.name = #fname, \ - .attr.attr.mode = _mode, \ - } - -static struct tegra_fuse_hw_feature tegra194_fuse_chip_data = { - .power_down_mode = true, - .mirroring_support = true, - .skip_fuse_mirroring_logic = false, - .fuse_ctrl_reg = 0x10, - .hw_mutex_support = false, - .has_power_switch = true, - .pgm_time = 5, - .chip_id = TEGRA194, - .burn_data = { - FUSE_BURN_DATA(reserved_odm0, 0x2, 2, 32, 0xc8, true, false), - FUSE_BURN_DATA(reserved_odm1, 0x4, 2, 32, 0xcc, true, false), - FUSE_BURN_DATA(reserved_odm2, 0x6, 2, 32, 0xd0, true, false), - FUSE_BURN_DATA(reserved_odm3, 0x8, 2, 32, 0xd4, true, false), - FUSE_BURN_DATA(reserved_odm4, 0xa, 2, 32, 0xd8, true, false), - FUSE_BURN_DATA(reserved_odm5, 0xc, 2, 32, 0xdc, true, false), - FUSE_BURN_DATA(reserved_odm6, 0xe, 2, 32, 0xe0, true, false), - FUSE_BURN_DATA(reserved_odm7, 0x10, 2, 32, 0xe4, true, false), - FUSE_BURN_DATA(reserved_odm8, 0x16, 26, 32, 0x420, true, false), - FUSE_BURN_DATA(reserved_odm9, 0x18, 26, 32, 0x424, true, false), - FUSE_BURN_DATA(reserved_odm10, 0x1a, 26, 32, 0x428, true, false), - FUSE_BURN_DATA(reserved_odm11, 0x1c, 26, 32, 0x42c, true, false), - FUSE_BURN_DATA(odm_lock, 0, 6, 4, 0x8, true, false), - FUSE_BURN_DATA(arm_jtag_disable, 0x0, 12, 1, 0xb8, true, false), - FUSE_BURN_DATA(odm_production_mode, 0, 11, 1, 0xa0, true, false), - FUSE_BURN_DATA(secure_boot_key, 0x61, 1, 128, 0xa4, true, true), - FUSE_BURN_DATA(public_key, 0x59, 1, 256, 0x64, true, true), - FUSE_BURN_DATA(boot_security_info, 0x66, 21, 16, 0x168, true, false), - FUSE_BURN_DATA(debug_authentication, 0, 20, 5, 0x1e4, true, false), - FUSE_BURN_DATA(odm_info, 0x67, 5, 16, 0x19c, false, false), - FUSE_BURN_DATA(pdi, 0x40, 17, 64, 0x300, false, false), - FUSE_BURN_DATA(opt_customer_optin_fuse, 0x7e, 6, 1, 0x4a8, - false, false), - FUSE_BURN_DATA(odmid, 0x7b, 30, 64, 0x308, false, false), - FUSE_BURN_DATA(kek0, 0x6f, 30, 128, 0x2c0, false, true), - FUSE_BURN_DATA(kek1, 0x73, 30, 128, 0x2d0, false, true), - FUSE_BURN_DATA(kek2, 0x77, 30, 128, 0x2e0, false, true), - FUSE_SYSFS_DATA(ecid, tegra_fuse_read_ecid, NULL, FPERM_R), - FUSE_SYSFS_DATA(opt_tpc_disable, - tegra_fuse_read_opt_tpc_disable, NULL, FPERM_R), - {}, - }, -}; - -static struct tegra_fuse_hw_feature tegra234_fuse_chip_data = { - .power_down_mode = true, - .mirroring_support = true, - .skip_fuse_mirroring_logic = true, - .fuse_ctrl_reg = 0x10, - .hw_mutex_support = true, - .has_power_switch = true, - .pgm_time = 5, - .chip_id = TEGRA234, - .burn_data = { - FUSE_BURN_DATA(reserved_odm0, 0x2, 2, 32, 0xc8, true, false), - FUSE_BURN_DATA(reserved_odm1, 0x4, 2, 32, 0xcc, true, false), - FUSE_BURN_DATA(reserved_odm2, 0x6, 2, 32, 0xd0, true, false), - FUSE_BURN_DATA(reserved_odm3, 0x10, 0, 32, 0xd4, true, false), - FUSE_BURN_DATA(reserved_odm4, 0xc, 0, 32, 0xd8, true, false), - FUSE_BURN_DATA(reserved_odm5, 0xe, 0, 32, 0xdc, true, false), - FUSE_BURN_DATA(reserved_odm6, 0xe, 2, 32, 0xe0, true, false), - FUSE_BURN_DATA(reserved_odm7, 0x10, 2, 32, 0xe4, true, false), - FUSE_BURN_DATA(odm_lock, 0, 5, 4, 0x8, true, false), - FUSE_BURN_DATA(public_key, 0xbc, 21, 512, 0x64, false, true), - FUSE_BURN_DATA(boot_security_info, 0xc7, 0, 32, 0x168, false, false), - FUSE_BURN_DATA(debug_authentication, 0, 16, 5, 0x1e4, true, false), - FUSE_BURN_DATA(odm_info, 0xc7, 9, 16, 0x19c, false, false), - FUSE_BURN_DATA(pdi, 0x62, 29, 64, 0x300, false, false), - FUSE_BURN_DATA(opt_customer_optin_fuse, 0xca, 7, 1, 0x4a8, - false, false), - FUSE_BURN_DATA(odmid, 0xc9, 0, 64, 0x308, false, false), - FUSE_SYSFS_DATA(ecid, tegra_fuse_read_ecid, NULL, FPERM_R), - FUSE_SYSFS_DATA(opt_tpc_disable, - tegra_fuse_read_opt_tpc_disable, NULL, FPERM_R), - {}, - }, -}; - -static const struct of_device_id tegra_fuse_burn_match[] = { - { - .compatible = "nvidia,tegra194-efuse-burn", - .data = &tegra194_fuse_chip_data, - }, { - .compatible = "nvidia,tegra234-efuse-burn", - .data = &tegra234_fuse_chip_data, - }, {}, -}; - -static void tegra_fuse_parse_dt(struct tegra_fuse_burn_dev *fuse_dev, - struct device_node *np) -{ - int n_entries; - - n_entries = of_property_count_u32_elems(np, "nvidia,temp-range"); - if (n_entries == 2) { - of_property_read_u32_index(np, "nvidia,temp-range", - 0, &fuse_dev->min_temp); - of_property_read_u32_index(np, "nvidia,temp-range", - 1, &fuse_dev->max_temp); - } else { - dev_dbg(fuse_dev->dev, "invalid fuse-temp range entries\n"); - } -} - -static int tegra_fuse_burn_probe(struct platform_device *pdev) -{ - struct tegra_fuse_burn_dev *fuse_dev; - struct device_node *np = pdev->dev.of_node; - struct resource *res_misc_reg; - struct resource *fuse_base_reg; - const char *tz_type; - int i, ret; - - fuse_dev = devm_kzalloc(&pdev->dev, sizeof(*fuse_dev), GFP_KERNEL); - if (!fuse_dev) - return -ENOMEM; - - fuse_dev->hw = (struct tegra_fuse_hw_feature *)of_device_get_match_data( - &pdev->dev); - if (!fuse_dev->hw) { - dev_err(&pdev->dev, "No hw data provided\n"); - return -EINVAL; - } - - res_misc_reg = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res_misc_reg) { - dev_err(&pdev->dev, "MISC Register not found\n"); - return -ENOENT; - } - - fuse_base_reg = platform_get_resource(pdev, IORESOURCE_MEM, 1); - if (!fuse_base_reg) { - dev_err(&pdev->dev, "FUSE Base Register not found\n"); - return -ENOENT; - } - - fuse_dev->pmc_misc_reg = devm_ioremap_resource(&pdev->dev, res_misc_reg); - if (IS_ERR(fuse_dev->pmc_misc_reg)) { - dev_err(&pdev->dev, "Cannot request memregion/iomap misc_reg\n"); - return PTR_ERR(fuse_dev->pmc_misc_reg); - } - - fuse_dev->fuse_base_reg = devm_ioremap_resource(&pdev->dev, fuse_base_reg); - if (IS_ERR(fuse_dev->fuse_base_reg)) { - dev_err(&pdev->dev, "Cannot request memregion/iomap fuse base reg\n"); - return PTR_ERR(fuse_dev->fuse_base_reg); - } - - fuse_dev->fuse_clk = devm_clk_get(&pdev->dev, "fuse-clk"); - if (IS_ERR(fuse_dev->fuse_clk)) { - if (PTR_ERR(fuse_dev->fuse_clk) != -EPROBE_DEFER) - dev_err(&pdev->dev, "failed to get fuse-clk err\n"); - return PTR_ERR(fuse_dev->fuse_clk); - } - - fuse_dev->bpmp = tegra_bpmp_get(&pdev->dev); - if (IS_ERR(fuse_dev->bpmp)) { - dev_err(&pdev->dev, "Not able to get BPMP handle\n"); - return PTR_ERR(fuse_dev->bpmp); - } - - /* Since T210, we support the bit offset and we will have redundant fuse - * for some of the fuse. But one exception(AID fuse) is not redundant. - * Unfortunately, some legacy kernel(eg. Kernel v3.10) will assume the - * AID fuse as redundant and read the fuse value in redundant way, from - * address X and address X+2, which should be address X and address X+1 - * instead. - * To align the platform we release with legacy kernel and client, add - * the "redundant-aid-war" for reading the same value as in the past. - * Or the inconsistent value may cause an issue in some case. - */ - if (of_property_read_bool(np, "nvidia,redundant-aid-war")) { - for (i = 0; i < ARRAY_SIZE(fuse_dev->hw->burn_data) && - fuse_dev->hw->burn_data[i].name != NULL; i++) - if (!strcmp(fuse_dev->hw->burn_data[i].name, "aid")) { - fuse_dev->hw->burn_data[i].is_redundant = true; - fuse_dev->hw->burn_data[i].redundant_war = - true; - } - } - - fuse_dev->pgm_clk = devm_clk_get(&pdev->dev, "pgm-clk"); - if (IS_ERR(fuse_dev->pgm_clk)) { - if (PTR_ERR(fuse_dev->pgm_clk) != -EPROBE_DEFER) - dev_err(&pdev->dev, "failed to get clk_m err\n"); - ret = PTR_ERR(fuse_dev->pgm_clk); - goto bpmp_put; - } - fuse_dev->pgm_width = DIV_ROUND_UP( - clk_get_rate(fuse_dev->pgm_clk) * - fuse_dev->hw->pgm_time, - 1000 * 1000); - - fuse_dev->dev = &pdev->dev; - platform_set_drvdata(pdev, fuse_dev); - mutex_init(&fuse_lock); - for (i = 0; i < ARRAY_SIZE(fuse_dev->hw->burn_data) && - fuse_dev->hw->burn_data[i].name != NULL; i++) { - ret = sysfs_create_file(&pdev->dev.kobj, - &fuse_dev->hw->burn_data[i].attr.attr); - if (ret) { - dev_err(&pdev->dev, "sysfs create failed %d\n", ret); - goto bpmp_put; - } - } - WARN(sysfs_create_link(&platform_bus.kobj, &pdev->dev.kobj, - "tegra-fuse"), "Unable to create symlink\n"); - - device_init_wakeup(fuse_dev->dev, true); - - if (of_property_read_u32(np, "thermal-zone", &fuse_dev->thermal_zone)) - dev_info(fuse_dev->dev, "shutdown limit check disabled\n"); - - ret = of_property_read_string(np, "thermal-zone-type", &tz_type); - if (!ret) { - fuse_dev->tz = thermal_zone_get_zone_by_name(tz_type); - if (IS_ERR(fuse_dev->tz)) - dev_dbg(&pdev->dev, "temp zone type %s not available\n", tz_type); - else - tegra_fuse_parse_dt(fuse_dev, np); - } - - dev_info(&pdev->dev, "Fuse burn driver initialized\n"); - - s_fuse_dev = fuse_dev; - - return 0; - -bpmp_put: - tegra_bpmp_put(fuse_dev->bpmp); - return ret; -} - -static struct platform_driver tegra_fuse_burn_driver = { - .driver = { - .name = "tegra-fuse-burn", - .of_match_table = tegra_fuse_burn_match, - }, - .probe = tegra_fuse_burn_probe, -}; -module_platform_driver(tegra_fuse_burn_driver); - -static int get_chip_uid(char *val, const struct kernel_param *kp) -{ - chip_uid = tegra_chip_uid(); - return param_get_ulong(val, kp); -} - -static struct kernel_param_ops tegra_chip_uid_ops = { - .get = get_chip_uid, -}; -module_param_cb(tegra_chip_uid, &tegra_chip_uid_ops, &chip_uid, 0444); - -MODULE_AUTHOR("Laxman Dewangan "); -MODULE_DESCRIPTION("NVIDIA Tegra Fuse burn driver"); -MODULE_LICENSE("GPL v2"); -