mirror of
https://github.com/torvalds/linux.git
synced 2024-12-23 19:31:53 +00:00
304664eab9
Instead of using a simple variable access to get at the Tegra chip ID, use a function so that we can run additional code. This can be used to determine where the chip ID is being accessed without being available. That in turn will be handy for resolving boot sequence dependencies in order to convert more code to regular initcalls rather than a sequence fixed by Tegra SoC setup code. Signed-off-by: Thierry Reding <treding@nvidia.com>
261 lines
6.2 KiB
C
261 lines
6.2 KiB
C
/*
|
|
* arch/arm/mach-tegra/fuse.c
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/export.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/random.h>
|
|
|
|
#include <soc/tegra/fuse.h>
|
|
|
|
#include "apbio.h"
|
|
#include "fuse.h"
|
|
#include "iomap.h"
|
|
|
|
/* Tegra20 only */
|
|
#define FUSE_UID_LOW 0x108
|
|
#define FUSE_UID_HIGH 0x10c
|
|
|
|
/* Tegra30 and later */
|
|
#define FUSE_VENDOR_CODE 0x200
|
|
#define FUSE_FAB_CODE 0x204
|
|
#define FUSE_LOT_CODE_0 0x208
|
|
#define FUSE_LOT_CODE_1 0x20c
|
|
#define FUSE_WAFER_ID 0x210
|
|
#define FUSE_X_COORDINATE 0x214
|
|
#define FUSE_Y_COORDINATE 0x218
|
|
|
|
#define FUSE_SKU_INFO 0x110
|
|
|
|
#define TEGRA20_FUSE_SPARE_BIT 0x200
|
|
#define TEGRA30_FUSE_SPARE_BIT 0x244
|
|
|
|
int tegra_sku_id;
|
|
int tegra_cpu_process_id;
|
|
int tegra_core_process_id;
|
|
int tegra_cpu_speedo_id; /* only exist in Tegra30 and later */
|
|
int tegra_soc_speedo_id;
|
|
enum tegra_revision tegra_revision;
|
|
|
|
static struct clk *fuse_clk;
|
|
static int tegra_fuse_spare_bit;
|
|
static void (*tegra_init_speedo_data)(void);
|
|
|
|
/* 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 STRAP_OPT 0x008
|
|
#define GMI_AD0 (1 << 4)
|
|
#define GMI_AD1 (1 << 5)
|
|
#define RAM_ID_MASK (GMI_AD0 | GMI_AD1)
|
|
#define RAM_CODE_SHIFT 4
|
|
|
|
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 void tegra_fuse_enable_clk(void)
|
|
{
|
|
if (IS_ERR(fuse_clk))
|
|
fuse_clk = clk_get_sys(NULL, "fuse");
|
|
if (IS_ERR(fuse_clk))
|
|
return;
|
|
clk_prepare_enable(fuse_clk);
|
|
}
|
|
|
|
static void tegra_fuse_disable_clk(void)
|
|
{
|
|
if (IS_ERR(fuse_clk))
|
|
return;
|
|
clk_disable_unprepare(fuse_clk);
|
|
}
|
|
|
|
u32 tegra_fuse_readl(unsigned long offset)
|
|
{
|
|
return tegra_apb_readl(TEGRA_FUSE_BASE + offset);
|
|
}
|
|
|
|
bool tegra_spare_fuse(int bit)
|
|
{
|
|
bool ret;
|
|
|
|
tegra_fuse_enable_clk();
|
|
|
|
ret = tegra_fuse_readl(tegra_fuse_spare_bit + bit * 4);
|
|
|
|
tegra_fuse_disable_clk();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static enum tegra_revision tegra_get_revision(u32 id)
|
|
{
|
|
u32 minor_rev = (id >> 16) & 0xf;
|
|
|
|
switch (minor_rev) {
|
|
case 1:
|
|
return TEGRA_REVISION_A01;
|
|
case 2:
|
|
return TEGRA_REVISION_A02;
|
|
case 3:
|
|
if (tegra_get_chip_id() == TEGRA20 &&
|
|
(tegra_spare_fuse(18) || tegra_spare_fuse(19)))
|
|
return TEGRA_REVISION_A03p;
|
|
else
|
|
return TEGRA_REVISION_A03;
|
|
case 4:
|
|
return TEGRA_REVISION_A04;
|
|
default:
|
|
return TEGRA_REVISION_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
static void tegra_get_process_id(void)
|
|
{
|
|
u32 reg;
|
|
|
|
tegra_fuse_enable_clk();
|
|
|
|
reg = tegra_fuse_readl(tegra_fuse_spare_bit);
|
|
tegra_cpu_process_id = (reg >> 6) & 3;
|
|
reg = tegra_fuse_readl(tegra_fuse_spare_bit);
|
|
tegra_core_process_id = (reg >> 12) & 3;
|
|
|
|
tegra_fuse_disable_clk();
|
|
}
|
|
|
|
u32 tegra_read_chipid(void)
|
|
{
|
|
return readl_relaxed(IO_ADDRESS(TEGRA_APB_MISC_BASE) + 0x804);
|
|
}
|
|
|
|
u8 tegra_get_chip_id(void)
|
|
{
|
|
u32 id = tegra_read_chipid();
|
|
|
|
return (id >> 8) & 0xff;
|
|
}
|
|
|
|
static void __init tegra20_fuse_init_randomness(void)
|
|
{
|
|
u32 randomness[2];
|
|
|
|
randomness[0] = tegra_fuse_readl(FUSE_UID_LOW);
|
|
randomness[1] = tegra_fuse_readl(FUSE_UID_HIGH);
|
|
|
|
add_device_randomness(randomness, sizeof(randomness));
|
|
}
|
|
|
|
/* Applies to Tegra30 or later */
|
|
static void __init tegra30_fuse_init_randomness(void)
|
|
{
|
|
u32 randomness[7];
|
|
|
|
randomness[0] = tegra_fuse_readl(FUSE_VENDOR_CODE);
|
|
randomness[1] = tegra_fuse_readl(FUSE_FAB_CODE);
|
|
randomness[2] = tegra_fuse_readl(FUSE_LOT_CODE_0);
|
|
randomness[3] = tegra_fuse_readl(FUSE_LOT_CODE_1);
|
|
randomness[4] = tegra_fuse_readl(FUSE_WAFER_ID);
|
|
randomness[5] = tegra_fuse_readl(FUSE_X_COORDINATE);
|
|
randomness[6] = tegra_fuse_readl(FUSE_Y_COORDINATE);
|
|
|
|
add_device_randomness(randomness, sizeof(randomness));
|
|
}
|
|
|
|
void __init tegra_init_fuse(void)
|
|
{
|
|
u32 id;
|
|
u32 randomness[5];
|
|
u8 chip_id;
|
|
|
|
u32 reg = readl(IO_ADDRESS(TEGRA_CLK_RESET_BASE + 0x48));
|
|
reg |= 1 << 28;
|
|
writel(reg, IO_ADDRESS(TEGRA_CLK_RESET_BASE + 0x48));
|
|
|
|
/*
|
|
* Enable FUSE clock. This needs to be hardcoded because the clock
|
|
* subsystem is not active during early boot.
|
|
*/
|
|
reg = readl(IO_ADDRESS(TEGRA_CLK_RESET_BASE + 0x14));
|
|
reg |= 1 << 7;
|
|
writel(reg, IO_ADDRESS(TEGRA_CLK_RESET_BASE + 0x14));
|
|
fuse_clk = ERR_PTR(-EINVAL);
|
|
|
|
reg = tegra_fuse_readl(FUSE_SKU_INFO);
|
|
randomness[0] = reg;
|
|
tegra_sku_id = reg & 0xFF;
|
|
|
|
reg = tegra_apb_readl(TEGRA_APB_MISC_BASE + STRAP_OPT);
|
|
randomness[1] = reg;
|
|
tegra_bct_strapping = (reg & RAM_ID_MASK) >> RAM_CODE_SHIFT;
|
|
|
|
id = tegra_read_chipid();
|
|
randomness[2] = id;
|
|
chip_id = (id >> 8) & 0xff;
|
|
|
|
switch (chip_id) {
|
|
case TEGRA20:
|
|
tegra_fuse_spare_bit = TEGRA20_FUSE_SPARE_BIT;
|
|
tegra_init_speedo_data = &tegra20_init_speedo_data;
|
|
break;
|
|
case TEGRA30:
|
|
tegra_fuse_spare_bit = TEGRA30_FUSE_SPARE_BIT;
|
|
tegra_init_speedo_data = &tegra30_init_speedo_data;
|
|
break;
|
|
case TEGRA114:
|
|
tegra_init_speedo_data = &tegra114_init_speedo_data;
|
|
break;
|
|
default:
|
|
pr_warn("Tegra: unknown chip id %d\n", chip_id);
|
|
tegra_fuse_spare_bit = TEGRA20_FUSE_SPARE_BIT;
|
|
tegra_init_speedo_data = &tegra_get_process_id;
|
|
}
|
|
|
|
tegra_revision = tegra_get_revision(id);
|
|
tegra_init_speedo_data();
|
|
randomness[3] = (tegra_cpu_process_id << 16) | tegra_core_process_id;
|
|
randomness[4] = (tegra_cpu_speedo_id << 16) | tegra_soc_speedo_id;
|
|
|
|
add_device_randomness(randomness, sizeof(randomness));
|
|
switch (chip_id) {
|
|
case TEGRA20:
|
|
tegra20_fuse_init_randomness();
|
|
break;
|
|
case TEGRA30:
|
|
case TEGRA114:
|
|
default:
|
|
tegra30_fuse_init_randomness();
|
|
break;
|
|
}
|
|
|
|
pr_info("Tegra Revision: %s SKU: %d CPU Process: %d Core Process: %d\n",
|
|
tegra_revision_name[tegra_revision],
|
|
tegra_sku_id, tegra_cpu_process_id,
|
|
tegra_core_process_id);
|
|
}
|