Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

View File

@@ -0,0 +1 @@
source "drivers/misc/fuse/tegra/Kconfig"

View File

@@ -0,0 +1 @@
obj-$(CONFIG_ARCH_TEGRA) += tegra/

View File

@@ -0,0 +1,9 @@
config TEGRA_KFUSE
tristate "Tegra KFuse Support"
depends on ARCH_TEGRA_124_SOC
help
Tegra KFuse support.
Say Y here to enable the Tegra Kfuse driver. The kfuse block
stores both downstream and upstream HDCP keys for use by the
HDMI module on Tegra.

View File

@@ -0,0 +1,8 @@
obj-y += fuse-tegra.o
obj-y += fuse-tegra30.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += fuse-tegra20.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra20_speedo.o
obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += tegra30_speedo.o
obj-$(CONFIG_ARCH_TEGRA_114_SOC) += tegra114_speedo.o
obj-$(CONFIG_ARCH_TEGRA_124_SOC) += tegra124_speedo.o
obj-$(CONFIG_TEGRA_KFUSE) += tegra-kfuse.o

View File

@@ -0,0 +1,257 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#include <linux/device.h>
#include <linux/kobject.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/io.h>
#include <linux/stat.h>
#include <linux/tegra-soc.h>
#include "fuse.h"
int tegra_chip_id;
enum tegra_revision tegra_revision;
int tegra_sku_id;
/*
* The BCT to use at boot is specified by board straps that can be read
* through a APB misc register and decoded. 2 bits, i.e. 4 possible BCTs.
*/
int tegra_bct_strapping;
#define TEGRA_STRAP_OPT 0x8
#define TEGRA_RAM_ID_SHIFT 4
#define TEGRA_RAM_ID_MASK 3
static u32 (*fuse_readl)(const unsigned int offset);
static u32 (*_fuse_write)(const unsigned int offset, const char *buf, u32 size);
static int fuse_size;
static void __iomem *fuse_base;
static void __iomem *apbmisc_base;
static void __iomem *car_base;
static const char *tegra_revision_name[TEGRA_REVISION_MAX] = {
[TEGRA_REVISION_UNKNOWN] = "unknown",
[TEGRA_REVISION_A01] = "A01",
[TEGRA_REVISION_A02] = "A02",
[TEGRA_REVISION_A03] = "A03",
[TEGRA_REVISION_A03p] = "A03 prime",
[TEGRA_REVISION_A04] = "A04",
};
static u8 fuse_readb(const unsigned int offset)
{
u32 val;
val = fuse_readl(round_down(offset, 4));
val >>= (offset % 4) * 8;
val &= 0xff;
return val;
}
static ssize_t fuse_read(struct file *fd, struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t pos, size_t size)
{
int i;
if (pos < 0 || pos >= fuse_size)
return 0;
if (size > fuse_size - pos)
size = fuse_size - pos;
for (i = 0; i < size; i++)
buf[i] = fuse_readb(pos + i);
return i;
}
static ssize_t fuse_write(struct file *fd, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t pos, size_t size)
{
int ret;
if (!_fuse_write)
return -EPERM;
if (pos < 0 || pos >= fuse_size)
return 0;
if (size > fuse_size - pos)
size = fuse_size - pos;
ret = _fuse_write(pos, buf, size);
if (IS_ERR_VALUE(ret))
return -EPERM;
return ret;
}
static struct bin_attribute fuse_bin_attr = {
.attr = { .name = "fuse", .mode = S_IRUGO | S_IWUSR, },
.read = fuse_read,
.write = fuse_write,
};
static const struct of_device_id tegra_fuse_match[] __initconst = {
{ .compatible = "nvidia,tegra20-efuse", },
{ .compatible = "nvidia,tegra30-efuse", },
{ .compatible = "nvidia,tegra114-efuse", },
{ .compatible = "nvidia,tegra124-efuse", },
{},
};
static const struct of_device_id car_match[] __initconst = {
{ .compatible = "nvidia,tegra20-car", },
{ .compatible = "nvidia,tegra30-car", },
{ .compatible = "nvidia,tegra114-car", },
{ .compatible = "nvidia,tegra124-car", },
{},
};
static void tegra_read_bct_strapping(void)
{
tegra_bct_strapping = readl_relaxed(apbmisc_base +
TEGRA_STRAP_OPT);
tegra_bct_strapping >>= TEGRA_RAM_ID_SHIFT;
tegra_bct_strapping &= TEGRA_RAM_ID_MASK;
}
static void tegra_get_revision(u32 id)
{
u32 minor_rev = (id >> 16) & 0xf;
switch (minor_rev) {
case 1:
tegra_revision = TEGRA_REVISION_A01;
break;
case 2:
tegra_revision = TEGRA_REVISION_A02;
break;
case 3:
if (tegra_chip_id == TEGRA20 &&
(tegra20_spare_fuse_early(18, fuse_base) ||
tegra20_spare_fuse_early(19, fuse_base)))
tegra_revision = TEGRA_REVISION_A03p;
else
tegra_revision = TEGRA_REVISION_A03;
break;
case 4:
tegra_revision = TEGRA_REVISION_A04;
break;
default:
tegra_revision = TEGRA_REVISION_UNKNOWN;
}
}
u32 tegra_read_straps(void)
{
return readl(apbmisc_base + TEGRA_STRAP_OPT);
}
u32 tegra_read_chipid(void)
{
return readl_relaxed(apbmisc_base + 0x804);
}
int tegra_fuse_create_sysfs(struct device *dev, int size,
u32 (*readl)(const unsigned int offset),
u32 (*write)(const unsigned int offset, const char *buf, u32 size),
struct tegra_sku_info *sku_info)
{
int err;
if (fuse_size)
return -ENODEV;
fuse_bin_attr.size = size;
fuse_bin_attr.read = fuse_read;
fuse_bin_attr.write = fuse_write;
fuse_size = size;
fuse_readl = readl;
_fuse_write = write;
err = device_create_bin_file(dev, &fuse_bin_attr);
if (err)
return err;
dev_info(dev,
"Tegra Revision: %s SKU: %d CPU Process: %d Core Process: %d\n",
tegra_revision_name[sku_info->revision],
sku_info->sku_id, sku_info->cpu_process_id,
sku_info->core_process_id);
return 0;
}
void __init tegra_init_fuse(void)
{
struct device_node *np;
u32 id, reg;
np = of_find_matching_node(NULL, tegra_fuse_match);
fuse_base = of_iomap(np, 0);
if (!fuse_base) {
pr_err("ioremap tegra fuse failed\n");
return;
}
apbmisc_base = of_iomap(np, 1);
if (!apbmisc_base) {
pr_err("ioremap tegra apbmisc failed\n");
iounmap(fuse_base);
return;
}
np = of_find_matching_node(NULL, car_match);
car_base = of_iomap(np, 0);
if (!car_base) {
pr_err("ioremap tegra car failed\n");
iounmap(fuse_base);
iounmap(apbmisc_base);
return;
}
reg = readl_relaxed(car_base + 0x48);
reg |= 1 << 28;
writel(reg, car_base + 0x48);
/*
* Enable FUSE clock. This needs to be hardcoded because the clock
* subsystem is not active during early boot.
*/
reg = readl(car_base + 0x14);
reg |= 1 << 7;
writel(reg, car_base + 0x14);
iounmap(car_base);
id = tegra_read_chipid();
tegra_chip_id = (id >> 8) & 0xff;
tegra_read_bct_strapping();
tegra_get_revision(id);
}

View File

@@ -0,0 +1,138 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Based on drivers/misc/eeprom/sunxi_sid.c
*/
#include <linux/device.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/random.h>
#include <linux/tegra-soc.h>
#include "fuse.h"
#define FUSE_BEGIN 0x100
#define FUSE_SIZE 0x1f8
#define FUSE_SKU_INFO 0x10
#define FUSE_UID_LOW 0x08
#define FUSE_UID_HIGH 0x0c
static phys_addr_t fuse_phys;
static struct clk *fuse_clk;
static struct tegra_sku_info sku_info;
static u32 tegra20_fuse_readl(const unsigned int offset)
{
int ret;
u32 val;
clk_prepare_enable(fuse_clk);
ret = tegra_apb_readl_using_dma(fuse_phys + FUSE_BEGIN + offset, &val);
clk_disable_unprepare(fuse_clk);
return (ret < 0) ? 0 : val;
}
static void tegra20_fuse_add_randomness(void)
{
u32 randomness[7];
randomness[0] = tegra20_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 | sku_info.soc_speedo_id;
randomness[5] = tegra20_fuse_readl(FUSE_UID_LOW);
randomness[6] = tegra20_fuse_readl(FUSE_UID_HIGH);
add_device_randomness(randomness, sizeof(randomness));
}
bool tegra20_spare_fuse(int spare_bit)
{
u32 offset = spare_bit * 4 + 0x100;
return tegra20_fuse_readl(offset) & 1;
}
bool tegra20_spare_fuse_early(int spare_bit, void *fuse_base)
{
u32 offset = spare_bit * 4 + 0x100;
return readl_relaxed(fuse_base + FUSE_BEGIN + offset);
}
static const struct of_device_id tegra20_fuse_of_match[] = {
{ .compatible = "nvidia,tegra20-efuse" },
{},
}
MODULE_DEVICE_TABLE(of, tegra20_fuse_of_match);
static int tegra20_fuse_probe(struct platform_device *pdev)
{
struct resource *res;
fuse_clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(fuse_clk)) {
dev_err(&pdev->dev, "missing clock");
return PTR_ERR(fuse_clk);
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -EINVAL;
fuse_phys = res->start;
tegra_sku_id = tegra20_fuse_readl(FUSE_SKU_INFO) & 0xff;
sku_info.sku_id = tegra_sku_id;
sku_info.revision = tegra_revision;
tegra20_init_speedo_data(&sku_info, &pdev->dev);
dev_dbg(&pdev->dev, "Soc Speedo ID %d", sku_info.soc_speedo_id);
tegra20_fuse_add_randomness();
if (tegra_fuse_create_sysfs(&pdev->dev, FUSE_SIZE, tegra20_fuse_readl,
NULL, &sku_info))
return -ENODEV;
dev_dbg(&pdev->dev, "loaded\n");
return 0;
}
static struct platform_driver tegra20_fuse_driver = {
.probe = tegra20_fuse_probe,
.driver = {
.name = "tegra20_fuse",
.owner = THIS_MODULE,
.of_match_table = tegra20_fuse_of_match,
}
};
static int __init tegra20_fuse_init(void)
{
return platform_driver_register(&tegra20_fuse_driver);
}
postcore_initcall(tegra20_fuse_init);

View File

@@ -0,0 +1,611 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#include <linux/device.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/random.h>
#include <linux/regulator/consumer.h>
#include <linux/tegra-soc.h>
#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);

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2010 Google, Inc.
* Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
*
* Author:
* Colin Cross <ccross@android.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#ifndef __DRIVERS_MISC_TEGRA_FUSE_H
#define __DRIVERS_MISC_TEGRA_FUSE_H
struct tegra_sku_info {
int sku_id;
int cpu_process_id;
int cpu_speedo_id;
int cpu_speedo_value;
int cpu_iddq_value;
int core_process_id;
int soc_speedo_id;
int gpu_speedo_id;
int gpu_process_id;
int gpu_speedo_value;
enum tegra_revision revision;
};
int tegra_fuse_create_sysfs(struct device *dev, int size,
u32 (*readl)(const unsigned int offset),
u32 (*write)(const unsigned int offset, const char *buf, u32 size),
struct tegra_sku_info *sku_info);
bool tegra30_spare_fuse(int bit);
u32 tegra30_fuse_readl(const unsigned int offset);
#ifdef CONFIG_ARCH_TEGRA_2x_SOC
void tegra20_init_speedo_data(struct tegra_sku_info *sku_info,
struct device *dev);
bool tegra20_spare_fuse(int bit);
bool tegra20_spare_fuse_early(int spare_bit, void *fuse_base);
#else
static inline void tegra20_init_speedo_data(struct tegra_sku_info *sku_info,
struct device *dev) {}
static inline bool tegra20_spare_fuse(int bit) { return false; }
static inline bool tegra20_spare_fuse_early(int spare_bit, void *fuse_base)
{
return false;
}
#endif
#ifdef CONFIG_ARCH_TEGRA_3x_SOC
void tegra30_init_speedo_data(struct tegra_sku_info *sku_info,
struct device *dev);
#else
static inline void tegra30_init_speedo_data(struct tegra_sku_info *sku_info,
struct device *dev) {}
#endif
#ifdef CONFIG_ARCH_TEGRA_114_SOC
void tegra114_init_speedo_data(struct tegra_sku_info *sku_info,
struct device *dev);
#else
static inline void tegra114_init_speedo_data(struct tegra_sku_info *sku_info,
struct device *dev) {}
#endif
#ifdef CONFIG_ARCH_TEGRA_124_SOC
void tegra124_init_speedo_data(struct tegra_sku_info *sku_info,
struct device *dev);
#else
static inline void tegra124_init_speedo_data(struct tegra_sku_info *sku_info,
struct device *dev) {}
#endif
#endif

View File

@@ -0,0 +1,156 @@
/*
* Copyright (c) 2011-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 <http://www.gnu.org/licenses/>.
*
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#include <linux/tegra-soc.h>
/* register definition */
#define KFUSE_STATE 0x80
#define KFUSE_STATE_DONE (1u << 16)
#define KFUSE_STATE_CRCPASS (1u << 17)
#define KFUSE_KEYADDR 0x88
#define KFUSE_KEYADDR_AUTOINC (1u << 16)
#define KFUSE_KEYS 0x8c
struct tegra_kfuse_info {
struct platform_device *pdev;
void __iomem *base;
struct clk *clk;
};
static struct tegra_kfuse_info *kfuse_info;
static inline u32 tegra_kfuse_readl(unsigned long offset)
{
return readl_relaxed(kfuse_info->base + offset);
}
static inline void tegra_kfuse_writel(u32 value, unsigned long offset)
{
writel_relaxed(value, kfuse_info->base + offset);
}
static int tegra_kfuse_wait_for_done(void)
{
u32 reg;
int retries = 50;
do {
reg = tegra_kfuse_readl(KFUSE_STATE);
if (reg & KFUSE_STATE_DONE)
return 0;
usleep_range(10000, 11000);
} while (--retries);
return -ETIMEDOUT;
}
/* read up to TEGRA_KFUSE_DATA_SZ bytes into dest.
* always starts at the first kfuse.
*/
int tegra_kfuse_read(void *dest, size_t len)
{
int err;
u32 v;
unsigned cnt;
if (len > TEGRA_KFUSE_DATA_SZ)
return -EINVAL;
err = clk_prepare_enable(kfuse_info->clk);
if (err)
return err;
tegra_kfuse_writel(KFUSE_KEYADDR_AUTOINC, KFUSE_KEYADDR);
err = tegra_kfuse_wait_for_done();
if (err) {
dev_err(&kfuse_info->pdev->dev, "Read timeout");
clk_disable_unprepare(kfuse_info->clk);
return err;
}
if ((tegra_kfuse_readl(KFUSE_STATE) & KFUSE_STATE_CRCPASS) == 0) {
dev_err(&kfuse_info->pdev->dev, "CRC failed\n");
clk_disable_unprepare(kfuse_info->clk);
return -EIO;
}
for (cnt = 0; cnt < len; cnt += 4) {
v = tegra_kfuse_readl(KFUSE_KEYS);
memcpy(dest + cnt, &v, sizeof(v));
}
clk_disable_unprepare(kfuse_info->clk);
return 0;
}
EXPORT_SYMBOL_GPL(tegra_kfuse_read);
static int tegra_kfuse_probe(struct platform_device *pdev)
{
struct clk *fuse_clk;
struct resource *res;
int err = 0;
kfuse_info = devm_kzalloc(&pdev->dev,
sizeof(struct tegra_kfuse_info), GFP_KERNEL);
if (!kfuse_info)
return -ENOMEM;
fuse_clk = devm_clk_get(&pdev->dev, "kfuse");
if (IS_ERR(fuse_clk)) {
dev_err(&pdev->dev, "Could not obtain clk");
return PTR_ERR(fuse_clk);
}
kfuse_info->clk = fuse_clk;
kfuse_info->pdev = pdev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
kfuse_info->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(kfuse_info->base)) {
dev_err(&pdev->dev, "Could not obtain base address");
return PTR_ERR(kfuse_info->base);
}
return err;
}
static const struct of_device_id tegra_kfuse_of_match[] = {
{ .compatible = "nvidia,tegra-kfuse" },
{},
};
static struct platform_driver tegra_kfuse_driver = {
.probe = tegra_kfuse_probe,
.driver = {
.name = "tegra_kfuse",
.owner = THIS_MODULE,
.of_match_table = tegra_kfuse_of_match,
}
};
module_platform_driver(tegra_kfuse_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Tegra KFuse driver for accessing HDCP key stores");

View File

@@ -0,0 +1,110 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/bug.h>
#include <linux/tegra-soc.h>
#include "fuse.h"
#define CORE_PROCESS_CORNERS_NUM 2
#define CPU_PROCESS_CORNERS_NUM 2
enum {
THRESHOLD_INDEX_0,
THRESHOLD_INDEX_1,
THRESHOLD_INDEX_COUNT,
};
static const u32 core_process_speedos[][CORE_PROCESS_CORNERS_NUM] = {
{1123, UINT_MAX},
{0, UINT_MAX},
};
static const u32 cpu_process_speedos[][CPU_PROCESS_CORNERS_NUM] = {
{1695, UINT_MAX},
{0, UINT_MAX},
};
static void rev_sku_to_speedo_ids(struct tegra_sku_info *sku_info,
int *threshold, struct device *dev)
{
u32 tmp;
u32 sku = sku_info->sku_id;
enum tegra_revision rev = sku_info->revision;
switch (sku) {
case 0x00:
case 0x10:
case 0x05:
case 0x06:
sku_info->cpu_speedo_id = 1;
sku_info->soc_speedo_id = 0;
*threshold = THRESHOLD_INDEX_0;
break;
case 0x03:
case 0x04:
sku_info->cpu_speedo_id = 2;
sku_info->soc_speedo_id = 1;
*threshold = THRESHOLD_INDEX_1;
break;
default:
dev_err(dev, "Unknown SKU %d\n", sku);
sku_info->cpu_speedo_id = 0;
sku_info->soc_speedo_id = 0;
*threshold = THRESHOLD_INDEX_0;
break;
}
if (rev == TEGRA_REVISION_A01) {
tmp = tegra30_fuse_readl(0x170) << 1;
tmp |= tegra30_fuse_readl(0x16c);
if (!tmp)
sku_info->cpu_speedo_id = 0;
}
}
void tegra114_init_speedo_data(struct tegra_sku_info *sku_info,
struct device *dev)
{
u32 cpu_speedo_val;
u32 core_speedo_val;
int threshold;
int i;
BUILD_BUG_ON(ARRAY_SIZE(cpu_process_speedos) !=
THRESHOLD_INDEX_COUNT);
BUILD_BUG_ON(ARRAY_SIZE(core_process_speedos) !=
THRESHOLD_INDEX_COUNT);
rev_sku_to_speedo_ids(sku_info, &threshold, dev);
cpu_speedo_val = tegra30_fuse_readl(0x2c) + 1024;
core_speedo_val = tegra30_fuse_readl(0x34);
for (i = 0; i < CPU_PROCESS_CORNERS_NUM; i++)
if (cpu_speedo_val < cpu_process_speedos[threshold][i])
break;
sku_info->cpu_process_id = i;
for (i = 0; i < CORE_PROCESS_CORNERS_NUM; i++)
if (core_speedo_val < core_process_speedos[threshold][i])
break;
sku_info->core_process_id = i;
}

View File

@@ -0,0 +1,183 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/bug.h>
#include <linux/tegra-soc.h>
#include "fuse.h"
#define CPU_PROCESS_CORNERS_NUM 2
#define GPU_PROCESS_CORNERS_NUM 2
#define CORE_PROCESS_CORNERS_NUM 2
#define FUSE_CPU_SPEEDO_0 0x14
#define FUSE_CPU_SPEEDO_1 0x2c
#define FUSE_CPU_SPEEDO_2 0x30
#define FUSE_SOC_SPEEDO_0 0x34
#define FUSE_SOC_SPEEDO_1 0x38
#define FUSE_SOC_SPEEDO_2 0x3c
#define FUSE_CPU_IDDQ 0x18
#define FUSE_SOC_IDDQ 0x40
#define FUSE_GPU_IDDQ 0x128
#define FUSE_FT_REV 0x28
enum {
THRESHOLD_INDEX_0,
THRESHOLD_INDEX_1,
THRESHOLD_INDEX_COUNT,
};
static int cpu_speedo_0_value;
static int cpu_speedo_1_value;
static int soc_speedo_0_value;
static int soc_speedo_1_value;
static int soc_speedo_2_value;
static int soc_speedo_id;
static int cpu_iddq_value;
static int gpu_iddq_value;
static int soc_iddq_value;
static const u32 cpu_process_speedos[][CPU_PROCESS_CORNERS_NUM] = {
{2190, UINT_MAX},
{0, UINT_MAX},
};
static const u32 gpu_process_speedos[][GPU_PROCESS_CORNERS_NUM] = {
{1965, UINT_MAX},
{0, UINT_MAX},
};
static const u32 core_process_speedos[][CORE_PROCESS_CORNERS_NUM] = {
{2061, UINT_MAX},
{0, UINT_MAX},
};
static void rev_sku_to_speedo_ids(struct tegra_sku_info *sku_info,
int *threshold, struct device *dev)
{
int sku = sku_info->sku_id;
/* Assign to default */
sku_info->cpu_speedo_id = 0;
sku_info->soc_speedo_id = 0;
sku_info->gpu_speedo_id = 0;
*threshold = THRESHOLD_INDEX_0;
switch (sku) {
case 0x00: /* Eng sku */
case 0x0F:
/* Using the default */
break;
case 0x81:
case 0x83:
sku_info->cpu_speedo_id = 2;
break;
case 0x87:
sku_info->cpu_speedo_id = 5;
sku_info->gpu_speedo_id = 1;
break;
case 0x07:
sku_info->cpu_speedo_id = 1;
sku_info->soc_speedo_id = 1;
sku_info->gpu_speedo_id = 1;
*threshold = THRESHOLD_INDEX_1;
break;
default:
dev_err(dev, "Unknown SKU %d\n", sku);
/* Using the default for the error case */
break;
}
soc_speedo_id = sku_info->soc_speedo_id;
}
void tegra124_init_speedo_data(struct tegra_sku_info *sku_info,
struct device *dev)
{
int i;
int threshold;
BUILD_BUG_ON(ARRAY_SIZE(cpu_process_speedos) !=
THRESHOLD_INDEX_COUNT);
BUILD_BUG_ON(ARRAY_SIZE(gpu_process_speedos) !=
THRESHOLD_INDEX_COUNT);
BUILD_BUG_ON(ARRAY_SIZE(core_process_speedos) !=
THRESHOLD_INDEX_COUNT);
cpu_speedo_0_value = tegra30_fuse_readl(FUSE_CPU_SPEEDO_0);
cpu_speedo_1_value = tegra30_fuse_readl(FUSE_CPU_SPEEDO_1);
/* GPU Speedo is stored in CPU_SPEEDO_2 */
sku_info->gpu_speedo_value = tegra30_fuse_readl(FUSE_CPU_SPEEDO_2);
soc_speedo_0_value = tegra30_fuse_readl(FUSE_SOC_SPEEDO_0);
soc_speedo_1_value = tegra30_fuse_readl(FUSE_SOC_SPEEDO_1);
soc_speedo_2_value = tegra30_fuse_readl(FUSE_SOC_SPEEDO_2);
cpu_iddq_value = tegra30_fuse_readl(FUSE_CPU_IDDQ);
soc_iddq_value = tegra30_fuse_readl(FUSE_SOC_IDDQ);
gpu_iddq_value = tegra30_fuse_readl(FUSE_GPU_IDDQ);
sku_info->cpu_speedo_value = cpu_speedo_0_value;
if (sku_info->cpu_speedo_value == 0) {
dev_warn(dev, "Warning: Speedo value not fused.\n");
WARN_ON(1);
return;
}
rev_sku_to_speedo_ids(sku_info, &threshold, dev);
sku_info->cpu_iddq_value = tegra30_fuse_readl(FUSE_CPU_IDDQ);
for (i = 0; i < GPU_PROCESS_CORNERS_NUM; i++)
if (sku_info->gpu_speedo_value <
gpu_process_speedos[threshold][i])
break;
sku_info->gpu_process_id = i;
for (i = 0; i < CPU_PROCESS_CORNERS_NUM; i++)
if (sku_info->cpu_speedo_value <
cpu_process_speedos[threshold][i])
break;
sku_info->cpu_process_id = i;
for (i = 0; i < CORE_PROCESS_CORNERS_NUM; i++)
if (soc_speedo_0_value <
core_process_speedos[threshold][i])
break;
sku_info->core_process_id = i;
dev_dbg(dev, "GPU Speedo ID=%d, Speedo Value=%d\n",
sku_info->gpu_speedo_id, sku_info->gpu_speedo_value);
}
int tegra124_get_core_speedo_mv(void)
{
switch (soc_speedo_id) {
case 0:
case 1:
return 1150;
default:
BUG();
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright (c) 2012-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 <http://www.gnu.org/licenses/>.
*/
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/bug.h>
#include <linux/tegra-soc.h>
#include "fuse.h"
#define CPU_SPEEDO_LSBIT 20
#define CPU_SPEEDO_MSBIT 29
#define CPU_SPEEDO_REDUND_LSBIT 30
#define CPU_SPEEDO_REDUND_MSBIT 39
#define CPU_SPEEDO_REDUND_OFFS (CPU_SPEEDO_REDUND_MSBIT - CPU_SPEEDO_MSBIT)
#define CORE_SPEEDO_LSBIT 40
#define CORE_SPEEDO_MSBIT 47
#define CORE_SPEEDO_REDUND_LSBIT 48
#define CORE_SPEEDO_REDUND_MSBIT 55
#define CORE_SPEEDO_REDUND_OFFS (CORE_SPEEDO_REDUND_MSBIT - CORE_SPEEDO_MSBIT)
#define SPEEDO_MULT 4
#define PROCESS_CORNERS_NUM 4
#define SPEEDO_ID_SELECT_0(rev) ((rev) <= 2)
#define SPEEDO_ID_SELECT_1(sku) \
(((sku) != 20) && ((sku) != 23) && ((sku) != 24) && \
((sku) != 27) && ((sku) != 28))
enum {
SPEEDO_ID_0,
SPEEDO_ID_1,
SPEEDO_ID_2,
SPEEDO_ID_COUNT,
};
static const u32 cpu_process_speedos[][PROCESS_CORNERS_NUM] = {
{315, 366, 420, UINT_MAX},
{303, 368, 419, UINT_MAX},
{316, 331, 383, UINT_MAX},
};
static const u32 core_process_speedos[][PROCESS_CORNERS_NUM] = {
{165, 195, 224, UINT_MAX},
{165, 195, 224, UINT_MAX},
{165, 195, 224, UINT_MAX},
};
void tegra20_init_speedo_data(struct tegra_sku_info *sku_info,
struct device *dev)
{
u32 reg;
u32 val;
int i;
BUILD_BUG_ON(ARRAY_SIZE(cpu_process_speedos) != SPEEDO_ID_COUNT);
BUILD_BUG_ON(ARRAY_SIZE(core_process_speedos) != SPEEDO_ID_COUNT);
if (SPEEDO_ID_SELECT_0(sku_info->revision))
sku_info->soc_speedo_id = SPEEDO_ID_0;
else if (SPEEDO_ID_SELECT_1(sku_info->sku_id))
sku_info->soc_speedo_id = SPEEDO_ID_1;
else
sku_info->soc_speedo_id = SPEEDO_ID_2;
val = 0;
for (i = CPU_SPEEDO_MSBIT; i >= CPU_SPEEDO_LSBIT; i--) {
reg = tegra20_spare_fuse(i) |
tegra20_spare_fuse(i + CPU_SPEEDO_REDUND_OFFS);
val = (val << 1) | (reg & 0x1);
}
val = val * SPEEDO_MULT;
dev_dbg(dev, "CPU speedo value %u\n", val);
for (i = 0; i < (PROCESS_CORNERS_NUM - 1); i++) {
if (val <= cpu_process_speedos[sku_info->soc_speedo_id][i])
break;
}
sku_info->cpu_process_id = i;
val = 0;
for (i = CORE_SPEEDO_MSBIT; i >= CORE_SPEEDO_LSBIT; i--) {
reg = tegra20_spare_fuse(i) |
tegra20_spare_fuse(i + CORE_SPEEDO_REDUND_OFFS);
val = (val << 1) | (reg & 0x1);
}
val = val * SPEEDO_MULT;
dev_dbg(dev, "Core speedo value %u\n", val);
for (i = 0; i < (PROCESS_CORNERS_NUM - 1); i++) {
if (val <= core_process_speedos[sku_info->soc_speedo_id][i])
break;
}
sku_info->core_process_id = i;
}

View File

@@ -0,0 +1,294 @@
/*
* Copyright (c) 2012-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 <http://www.gnu.org/licenses/>.
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/bug.h>
#include <linux/tegra-soc.h>
#include "fuse.h"
#define CORE_PROCESS_CORNERS_NUM 1
#define CPU_PROCESS_CORNERS_NUM 6
#define FUSE_SPEEDO_CALIB_0 0x14
#define FUSE_PACKAGE_INFO 0XFC
#define FUSE_TEST_PROG_VER 0X28
#define G_SPEEDO_BIT_MINUS1 58
#define G_SPEEDO_BIT_MINUS1_R 59
#define G_SPEEDO_BIT_MINUS2 60
#define G_SPEEDO_BIT_MINUS2_R 61
#define LP_SPEEDO_BIT_MINUS1 62
#define LP_SPEEDO_BIT_MINUS1_R 63
#define LP_SPEEDO_BIT_MINUS2 64
#define LP_SPEEDO_BIT_MINUS2_R 65
enum {
THRESHOLD_INDEX_0,
THRESHOLD_INDEX_1,
THRESHOLD_INDEX_2,
THRESHOLD_INDEX_3,
THRESHOLD_INDEX_4,
THRESHOLD_INDEX_5,
THRESHOLD_INDEX_6,
THRESHOLD_INDEX_7,
THRESHOLD_INDEX_8,
THRESHOLD_INDEX_9,
THRESHOLD_INDEX_10,
THRESHOLD_INDEX_11,
THRESHOLD_INDEX_COUNT,
};
static const u32 core_process_speedos[][CORE_PROCESS_CORNERS_NUM] = {
{180},
{170},
{195},
{180},
{168},
{192},
{180},
{170},
{195},
{180},
{180},
{180},
};
static const u32 cpu_process_speedos[][CPU_PROCESS_CORNERS_NUM] = {
{306, 338, 360, 376, UINT_MAX},
{295, 336, 358, 375, UINT_MAX},
{325, 325, 358, 375, UINT_MAX},
{325, 325, 358, 375, UINT_MAX},
{292, 324, 348, 364, UINT_MAX},
{324, 324, 348, 364, UINT_MAX},
{324, 324, 348, 364, UINT_MAX},
{295, 336, 358, 375, UINT_MAX},
{358, 358, 358, 358, 397, UINT_MAX},
{364, 364, 364, 364, 397, UINT_MAX},
{295, 336, 358, 375, 391, UINT_MAX},
{295, 336, 358, 375, 391, UINT_MAX},
};
static int threshold_index;
static int package_id;
static void fuse_speedo_calib(u32 *speedo_g, u32 *speedo_lp,
struct device *dev)
{
u32 reg;
int ate_ver;
int bit_minus1;
int bit_minus2;
reg = tegra30_fuse_readl(FUSE_SPEEDO_CALIB_0);
*speedo_lp = (reg & 0xFFFF) * 4;
*speedo_g = ((reg >> 16) & 0xFFFF) * 4;
ate_ver = tegra30_fuse_readl(FUSE_TEST_PROG_VER);
dev_dbg(dev, "ATE prog ver %d.%d\n", ate_ver/10, ate_ver%10);
if (ate_ver >= 26) {
bit_minus1 = tegra30_spare_fuse(LP_SPEEDO_BIT_MINUS1);
bit_minus1 |= tegra30_spare_fuse(LP_SPEEDO_BIT_MINUS1_R);
bit_minus2 = tegra30_spare_fuse(LP_SPEEDO_BIT_MINUS2);
bit_minus2 |= tegra30_spare_fuse(LP_SPEEDO_BIT_MINUS2_R);
*speedo_lp |= (bit_minus1 << 1) | bit_minus2;
bit_minus1 = tegra30_spare_fuse(G_SPEEDO_BIT_MINUS1);
bit_minus1 |= tegra30_spare_fuse(G_SPEEDO_BIT_MINUS1_R);
bit_minus2 = tegra30_spare_fuse(G_SPEEDO_BIT_MINUS2);
bit_minus2 |= tegra30_spare_fuse(G_SPEEDO_BIT_MINUS2_R);
*speedo_g |= (bit_minus1 << 1) | bit_minus2;
} else {
*speedo_lp |= 0x3;
*speedo_g |= 0x3;
}
}
static void rev_sku_to_speedo_ids(struct tegra_sku_info *sku_info,
struct device *dev)
{
switch (sku_info->revision) {
case TEGRA_REVISION_A01:
sku_info->cpu_speedo_id = 0;
sku_info->soc_speedo_id = 0;
threshold_index = THRESHOLD_INDEX_0;
break;
case TEGRA_REVISION_A02:
case TEGRA_REVISION_A03:
switch (sku_info->sku_id) {
case 0x87:
case 0x82:
sku_info->cpu_speedo_id = 1;
sku_info->soc_speedo_id = 1;
threshold_index = THRESHOLD_INDEX_1;
break;
case 0x81:
switch (package_id) {
case 1:
sku_info->cpu_speedo_id = 2;
sku_info->soc_speedo_id = 2;
threshold_index = THRESHOLD_INDEX_2;
break;
case 2:
sku_info->cpu_speedo_id = 4;
sku_info->soc_speedo_id = 1;
threshold_index = THRESHOLD_INDEX_7;
break;
default:
dev_err(dev, "Unknown pkg %d\n", package_id);
BUG();
break;
}
break;
case 0x80:
switch (package_id) {
case 1:
sku_info->cpu_speedo_id = 5;
sku_info->soc_speedo_id = 2;
threshold_index = THRESHOLD_INDEX_8;
break;
case 2:
sku_info->cpu_speedo_id = 6;
sku_info->soc_speedo_id = 2;
threshold_index = THRESHOLD_INDEX_9;
break;
default:
dev_err(dev, "Unknown pkg %d\n", package_id);
BUG();
break;
}
break;
case 0x83:
switch (package_id) {
case 1:
sku_info->cpu_speedo_id = 7;
sku_info->soc_speedo_id = 1;
threshold_index = THRESHOLD_INDEX_10;
break;
case 2:
sku_info->cpu_speedo_id = 3;
sku_info->soc_speedo_id = 2;
threshold_index = THRESHOLD_INDEX_3;
break;
default:
dev_err(dev, "Unknown pkg %d\n", package_id);
BUG();
break;
}
break;
case 0x8F:
sku_info->cpu_speedo_id = 8;
sku_info->soc_speedo_id = 1;
threshold_index = THRESHOLD_INDEX_11;
break;
case 0x08:
sku_info->cpu_speedo_id = 1;
sku_info->soc_speedo_id = 1;
threshold_index = THRESHOLD_INDEX_4;
break;
case 0x02:
sku_info->cpu_speedo_id = 2;
sku_info->soc_speedo_id = 2;
threshold_index = THRESHOLD_INDEX_5;
break;
case 0x04:
sku_info->cpu_speedo_id = 3;
sku_info->soc_speedo_id = 2;
threshold_index = THRESHOLD_INDEX_6;
break;
case 0:
switch (package_id) {
case 1:
sku_info->cpu_speedo_id = 2;
sku_info->soc_speedo_id = 2;
threshold_index = THRESHOLD_INDEX_2;
break;
case 2:
sku_info->cpu_speedo_id = 3;
sku_info->soc_speedo_id = 2;
threshold_index = THRESHOLD_INDEX_3;
break;
default:
dev_err(dev, "Unknown pkg %d\n", package_id);
BUG();
break;
}
break;
default:
dev_warn(dev, "Unknown SKU %d\n", sku_info->sku_id);
sku_info->cpu_speedo_id = 0;
sku_info->soc_speedo_id = 0;
threshold_index = THRESHOLD_INDEX_0;
break;
}
break;
default:
dev_warn(dev, "Unknown chip rev %d\n", sku_info->revision);
sku_info->cpu_speedo_id = 0;
sku_info->soc_speedo_id = 0;
threshold_index = THRESHOLD_INDEX_0;
break;
}
}
void tegra30_init_speedo_data(struct tegra_sku_info *sku_info,
struct device *dev)
{
u32 cpu_speedo_val;
u32 core_speedo_val;
int i;
BUILD_BUG_ON(ARRAY_SIZE(cpu_process_speedos) !=
THRESHOLD_INDEX_COUNT);
BUILD_BUG_ON(ARRAY_SIZE(core_process_speedos) !=
THRESHOLD_INDEX_COUNT);
package_id = tegra30_fuse_readl(FUSE_PACKAGE_INFO) & 0x0F;
rev_sku_to_speedo_ids(sku_info, dev);
fuse_speedo_calib(&cpu_speedo_val, &core_speedo_val, dev);
dev_dbg(dev, "CPU speedo value %u\n", cpu_speedo_val);
dev_dbg(dev, "Core speedo value %u\n", core_speedo_val);
for (i = 0; i < CPU_PROCESS_CORNERS_NUM; i++) {
if (cpu_speedo_val < cpu_process_speedos[threshold_index][i])
break;
}
sku_info->cpu_process_id = i - 1;
if (sku_info->cpu_process_id == -1) {
dev_warn(dev, "CPU speedo value %3d out of range",
cpu_speedo_val);
sku_info->cpu_process_id = 0;
sku_info->cpu_speedo_id = 1;
}
for (i = 0; i < CORE_PROCESS_CORNERS_NUM; i++) {
if (core_speedo_val < core_process_speedos[threshold_index][i])
break;
}
sku_info->core_process_id = i - 1;
if (sku_info->core_process_id == -1) {
dev_warn(dev, "CORE speedo value %3d out of range",
core_speedo_val);
sku_info->core_process_id = 0;
sku_info->soc_speedo_id = 1;
}
}