/* * Copyright (c) 2013-2014, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "fuse.h" #define FUSE_CTRL 0x00 #define FUSE_REG_ADDR 0x04 #define FUSE_REG_READ 0x08 #define FUSE_REG_WRITE 0x0c #define FUSE_TIME_PGM2 0x1c #define FUSE_PRIV2INTFC_START 0x20 #define FUSE_DIS_PGM 0x2c #define FUSE_WRITE_ACCESS 0x30 #define FUSE_PWR_GOOD_SW 0x34 #define FUSE_BEGIN 0x100 #define FUSE_SKU_INFO 0x10 /* Tegra30 and later */ #define FUSE_VENDOR_CODE 0x100 #define FUSE_FAB_CODE 0x104 #define FUSE_LOT_CODE_0 0x108 #define FUSE_LOT_CODE_1 0x10c #define FUSE_WAFER_ID 0x110 #define FUSE_X_COORDINATE 0x114 #define FUSE_Y_COORDINATE 0x118 #define FUSE_HAS_REVISION_INFO BIT(0) #define FUSE_READ 0x1 #define FUSE_WRITE 0x2 #define FUSE_SENSE 0x3 #define FUSE_CMD_MASK 0x3 #define STATE_IDLE (0x4 << 16) #define SENSE_DONE (0x1 << 30) #define FUSETIME_PGM2_TWIDTH_PGM_MASK 0xffff #define PRIV2INTFC_START_DATA BIT(0) #define PRIV2INTFC_SKIP_RAMREPAIR BIT(1) struct tegra_fuse_info { int size; int spare_bit; void (*init_speedo_data)(struct tegra_sku_info *sku_info, struct device *dev); }; struct tegra_fuse_location { int fuse_addr; int start_bit; int end_bit; }; struct tegra_fuse_data { int regular_addr; struct tegra_fuse_location loc_high; struct tegra_fuse_location loc_low; int bits_num; }; static struct tegra_fuse_data tegra30_fuse_array[] = { {0x0a0, { 0, 0, 0}, { 0, 23, 23}, 1}, {0x0b8, { 0, 0, 0}, { 0, 24, 24}, 1}, {0x0bc, {26, 0, 5}, {24, 22, 31}, 16}, {0x0c0, { 0, 0, 0}, {26, 6, 13}, 8}, }; static struct tegra_fuse_data tegra114_fuse_array[] = { {0x0a0, { 0, 0, 0}, { 0, 7, 7}, 1}, {0x0b8, { 0, 0, 0}, { 0, 8, 8}, 1}, {0x0bc, { 0, 0, 0}, {46, 7, 22}, 16}, {0x0c0, { 0, 0, 0}, {46, 23, 30}, 8}, {0x168, { 0, 0, 0}, {90, 22, 22}, 1}, }; static struct tegra_fuse_data tegra124_fuse_array[] = { {0x0a0, { 0, 0, 0}, { 0, 11, 11}, 1}, {0x0b8, { 0, 0, 0}, { 0, 12, 12}, 1}, {0x0bc, { 0, 0, 0}, {44, 12, 27}, 16}, {0x0c0, {46, 0, 3}, {44, 28, 31}, 8}, {0x0c8, {48, 0, 4}, {46, 5, 31}, 32}, {0x0cc, {50, 0, 4}, {48, 5, 31}, 32}, {0x0d0, {52, 0, 4}, {50, 5, 31}, 32}, {0x0d4, {54, 0, 4}, {52, 5, 31}, 32}, {0x0d8, {56, 0, 4}, {54, 5, 31}, 32}, {0x0e0, {60, 0, 4}, {58, 5, 31}, 32}, {0x0e4, {62, 0, 4}, {60, 5, 31}, 32}, {0x168, { 0, 0, 0}, {90, 9, 9}, 1}, }; static struct tegra_fuse_data *fuse_array; static int fuse_array_size; static void __iomem *fuse_base; static struct clk *fuse_clk; static struct tegra_fuse_info *fuse_info; static struct tegra_sku_info sku_info; static struct regulator *vpp_reg; static struct platform_device *fuse_pdev; static u32 pgm_cycles; static bool need_sense_done; int tegra_get_cpu_process_id(void) { return sku_info.cpu_process_id; } int tegra_get_core_process_id(void) { return sku_info.core_process_id; } int tegra_get_gpu_process_id(void) { return sku_info.gpu_process_id; } int tegra_get_cpu_speedo_id(void) { if (tegra_chip_id == TEGRA20) return -EINVAL; return sku_info.cpu_speedo_id; } int tegra_get_soc_speedo_id(void) { return sku_info.soc_speedo_id; } int tegra_get_gpu_speedo_id(void) { return sku_info.gpu_speedo_id; } int tegra_get_cpu_speedo_value(void) { return sku_info.cpu_speedo_value; } int tegra_get_gpu_speedo_value(void) { return sku_info.gpu_speedo_value; } int tegra_get_cpu_iddq_value(void) { return sku_info.cpu_iddq_value; } void tegra_gpu_get_info(struct gpu_info *pinfo) { if (tegra_chip_id == TEGRA114) { pinfo->num_pixel_pipes = 4; pinfo->num_alus_per_pixel_pipe = 3; } else { pinfo->num_pixel_pipes = 1; pinfo->num_alus_per_pixel_pipe = 1; } } static inline void wait_for_idle(void) { u32 reg; do { udelay(1); reg = readl_relaxed(fuse_base + FUSE_CTRL); } while ((reg & (0x1F << 16)) != STATE_IDLE); } static inline void wait_for_sense_done(void) { u32 reg; writel_relaxed(PRIV2INTFC_START_DATA | PRIV2INTFC_SKIP_RAMREPAIR, fuse_base + FUSE_PRIV2INTFC_START); do { udelay(1); reg = readl_relaxed(fuse_base + FUSE_CTRL); } while ((reg & BIT(30)) != SENSE_DONE || (reg & (0x1F << 16)) != STATE_IDLE); } static u32 fuse_cmd_read(int addr) { u32 reg; wait_for_idle(); writel_relaxed(addr, fuse_base + FUSE_REG_ADDR); reg = readl_relaxed(fuse_base + FUSE_CTRL); reg &= ~FUSE_CMD_MASK; reg |= FUSE_READ; writel_relaxed(reg, fuse_base + FUSE_CTRL); wait_for_idle(); return readl_relaxed(fuse_base + FUSE_REG_READ); } /* Must be called by fuse_set_value() */ static void fuse_cmd_write(int addr, u32 value) { u32 reg; wait_for_idle(); writel_relaxed(addr, fuse_base + FUSE_REG_ADDR); writel_relaxed(value, fuse_base + FUSE_REG_WRITE); reg = readl_relaxed(fuse_base + FUSE_CTRL); reg &= ~FUSE_CMD_MASK; reg |= FUSE_WRITE; writel_relaxed(reg, fuse_base + FUSE_CTRL); wait_for_idle(); } static u32 fuse_get_value(int index) { u32 low_bits, high_bits, val; struct tegra_fuse_location *low, *high; int bits_num[2] = {0, 0}; if (index >= fuse_array_size || !fuse_array[index].bits_num) return ~0; low = &fuse_array[index].loc_low; high = &fuse_array[index].loc_high; bits_num[0] = low->end_bit + 1 - low->start_bit; low_bits = fuse_cmd_read(low->fuse_addr); low_bits >>= low->start_bit; if (bits_num[0] < 32) low_bits &= BIT(bits_num[0]) - 1; high_bits = 0; if (fuse_array[index].bits_num - bits_num[0] != 0) { bits_num[1] = high->end_bit + 1 - high->start_bit; high_bits = fuse_cmd_read(high->fuse_addr); high_bits >>= high->start_bit; if (bits_num[1] < 32) high_bits &= BIT(bits_num[1]) - 1; } val = (high_bits << bits_num[0]) | low_bits; return val; } /* Must be called by tegra30_fuse_program() */ static void fuse_set_value(int index, u32 value) { u32 low_bits, high_bits; struct tegra_fuse_location *low, *high; int bits_num[2] = {0, 0}; if (index >= fuse_array_size || !fuse_array[index].bits_num) return; low = &fuse_array[index].loc_low; high = &fuse_array[index].loc_high; bits_num[0] = low->end_bit + 1 - low->start_bit; if (fuse_array[index].bits_num - bits_num[0] != 0) bits_num[1] = high->end_bit + 1 - high->start_bit; if (bits_num[0] < 32) low_bits = value & (BIT(bits_num[0]) - 1); else low_bits = value; low_bits <<= low->start_bit; if (low_bits) { fuse_cmd_write(low->fuse_addr, low_bits); /* also write to redundant fuse */ fuse_cmd_write(low->fuse_addr + 1, low_bits); } if (bits_num[1]) { high_bits = value >> bits_num[0]; high_bits <<= high->start_bit; if (high_bits) { fuse_cmd_write(high->fuse_addr, high_bits); /* also write to redundant fuse */ fuse_cmd_write(high->fuse_addr + 1, high_bits); } } } u32 tegra30_fuse_readl(const unsigned int offset) { u32 val; clk_prepare_enable(fuse_clk); val = readl_relaxed(fuse_base + FUSE_BEGIN + offset); clk_disable_unprepare(fuse_clk); return val; } static void tegra30_fuse_sense(void) { u32 reg; wait_for_idle(); reg = readl_relaxed(fuse_base + FUSE_CTRL); reg &= ~FUSE_CMD_MASK; reg |= FUSE_SENSE; writel_relaxed(reg, fuse_base + FUSE_CTRL); wait_for_idle(); } static bool regulator_is_available(void) { /* First time initialization */ if (!vpp_reg) { /* Get power supply for fuse programming */ vpp_reg = devm_regulator_get(&fuse_pdev->dev, "vdd"); if (IS_ERR(vpp_reg)) return false; } else if (IS_ERR(vpp_reg)) return false; return true; } /* * Make the programming function as static and only allowed to be called * from fuse-tegra.c by sysfs call. */ static u32 tegra30_fuse_program(const unsigned int offset, const char *buf, u32 size) { int ret; u32 val; int index = 0; int result = 0; int i, j; if (!regulator_is_available()) return -EPERM; clk_prepare_enable(fuse_clk); /* * Confirm fuse option write access hasn't already been permanenttly * disabled */ val = readl_relaxed(fuse_base + FUSE_DIS_PGM); if (val) goto err_pgm_disabled; /* Enable software writes to fuse registers */ writel_relaxed(0x0, fuse_base + FUSE_WRITE_ACCESS); /* Set the fuse strobe programming width */ if (pgm_cycles) writel_relaxed(pgm_cycles, fuse_base + FUSE_TIME_PGM2); /* Turn on 1.8V power supply */ ret = regulator_enable(vpp_reg); if (ret) { WARN(1, "failed to enable vpp_fuse power supply\n"); goto err_reg; } /* Enable power */ writel_relaxed(0x1, fuse_base + FUSE_PWR_GOOD_SW); udelay(1); for (i = 0; i < size; ) { int addr = offset + i; int remainder = addr % 4; u32 data = 0; int k; for (j = remainder; j < 4; j++) { data |= buf[index++] << (j * 8); if (index >= size) break; } /* * Only set the bits that should be burned, not any bits that * have alreardy been burned. */ for (k = 0; k < fuse_array_size; k++) { if (round_down(addr, 4) == fuse_array[k].regular_addr) { val = fuse_get_value(k); break; } } if (k == fuse_array_size) break; data &= ~val; if (data) fuse_set_value(k, data); i += 4 - remainder; } result = size; /* Disable power */ writel_relaxed(0x0, fuse_base + FUSE_PWR_GOOD_SW); udelay(1); regulator_disable(vpp_reg); tegra30_fuse_sense(); if (need_sense_done) wait_for_sense_done(); err_reg: /* Disable software writes to fuse registers */ writel_relaxed(0x1, fuse_base + FUSE_WRITE_ACCESS); err_pgm_disabled: clk_disable_unprepare(fuse_clk); return result; } bool tegra30_spare_fuse(int spare_bit) { u32 offset = fuse_info->spare_bit + spare_bit * 4; return tegra30_fuse_readl(offset) & 1; } static void tegra30_fuse_add_randomness(void) { u32 randomness[12]; randomness[0] = tegra30_fuse_readl(FUSE_SKU_INFO); randomness[1] = tegra_read_straps(); randomness[2] = tegra_read_chipid(); randomness[3] = sku_info.cpu_process_id << 16; randomness[3] |= sku_info.core_process_id; randomness[4] = sku_info.cpu_speedo_id << 16; randomness[4] |= sku_info.soc_speedo_id; randomness[5] = tegra30_fuse_readl(FUSE_VENDOR_CODE); randomness[6] = tegra30_fuse_readl(FUSE_FAB_CODE); randomness[7] = tegra30_fuse_readl(FUSE_LOT_CODE_0); randomness[8] = tegra30_fuse_readl(FUSE_LOT_CODE_1); randomness[9] = tegra30_fuse_readl(FUSE_WAFER_ID); randomness[10] = tegra30_fuse_readl(FUSE_X_COORDINATE); randomness[11] = tegra30_fuse_readl(FUSE_Y_COORDINATE); add_device_randomness(randomness, sizeof(randomness)); } static struct tegra_fuse_info tegra30_info = { .size = 0x2a4, .spare_bit = 0x144, .init_speedo_data = tegra30_init_speedo_data, }; static struct tegra_fuse_info tegra114_info = { .size = 0x2a0, .init_speedo_data = tegra114_init_speedo_data, }; static struct tegra_fuse_info tegra124_info = { .size = 0x300, .init_speedo_data = tegra124_init_speedo_data, }; static const struct of_device_id tegra30_fuse_of_match[] = { { .compatible = "nvidia,tegra30-efuse", .data = &tegra30_info }, { .compatible = "nvidia,tegra114-efuse", .data = &tegra114_info }, { .compatible = "nvidia,tegra124-efuse", .data = &tegra124_info }, {}, }; static int tegra30_fuse_probe(struct platform_device *pdev) { const struct of_device_id *of_dev_id; struct resource *res; struct clk *osc_clk; of_dev_id = of_match_device(tegra30_fuse_of_match, &pdev->dev); if (!of_dev_id) return -ENODEV; fuse_info = (struct tegra_fuse_info *)of_dev_id->data; if (!strcmp(of_dev_id->compatible, "nvidia,tegra30-efuse")) need_sense_done = true; if (!strcmp(of_dev_id->compatible, "nvidia,tegra30-efuse")) { fuse_array = tegra30_fuse_array; fuse_array_size = ARRAY_SIZE(tegra30_fuse_array); } else if (!strcmp(of_dev_id->compatible, "nvidia,tegra114-efuse")) { fuse_array = tegra114_fuse_array; fuse_array_size = ARRAY_SIZE(tegra114_fuse_array); } else { fuse_array = tegra124_fuse_array; fuse_array_size = ARRAY_SIZE(tegra124_fuse_array); } fuse_clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(fuse_clk)) { dev_err(&pdev->dev, "missing clock"); return PTR_ERR(fuse_clk); } osc_clk = devm_clk_get(&pdev->dev, "clk_m"); if (IS_ERR(osc_clk)) { /* Should be impossible to see this */ dev_err(&pdev->dev, "failed to get clk_m"); return PTR_ERR(osc_clk); } /* * The strobe programming pulse is 12us based on the osc clock * frequency */ pgm_cycles = DIV_ROUND_UP(clk_get_rate(osc_clk) * 12, 1000 * 1000); pgm_cycles &= FUSETIME_PGM2_TWIDTH_PGM_MASK; dev_dbg(&pdev->dev, "pgm_cycles is %d\n", pgm_cycles); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); fuse_base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(fuse_base)) { dev_err(&pdev->dev, "unable to map base address"); return PTR_ERR(fuse_base); } tegra_sku_id = tegra30_fuse_readl(FUSE_SKU_INFO) & 0xff; sku_info.sku_id = tegra_sku_id; sku_info.revision = tegra_revision; fuse_info->init_speedo_data(&sku_info, &pdev->dev); dev_dbg(&pdev->dev, "CPU Speedo ID %d, Soc Speedo ID %d", sku_info.cpu_speedo_id, sku_info.soc_speedo_id); tegra30_fuse_add_randomness(); platform_set_drvdata(pdev, NULL); fuse_pdev = pdev; if (tegra_fuse_create_sysfs(&pdev->dev, fuse_info->size, tegra30_fuse_readl, tegra30_fuse_program, &sku_info)) return -ENODEV; dev_dbg(&pdev->dev, "loaded\n"); return 0; } static struct platform_driver tegra30_fuse_driver = { .probe = tegra30_fuse_probe, .driver = { .name = "tegra_fuse", .owner = THIS_MODULE, .of_match_table = tegra30_fuse_of_match, } }; static int __init tegra30_fuse_init(void) { return platform_driver_register(&tegra30_fuse_driver); } postcore_initcall(tegra30_fuse_init);