Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 783c8f4c authored by Peter De Schrijver's avatar Peter De Schrijver Committed by Thierry Reding
Browse files

soc/tegra: Add efuse driver for Tegra



Implement fuse driver for Tegra20, Tegra30, Tegra114 and Tegra124. This
replaces functionality previously provided in arch/arm/mach-tegra, which
is removed in this patch.

While at it, move the only user of the global tegra_revision variable
over to tegra_sku_info.revision and export tegra_fuse_readl() to allow
drivers to read calibration fuses.

Signed-off-by: default avatarPeter De Schrijver <pdeschrijver@nvidia.com>
Signed-off-by: default avatarStephen Warren <swarren@nvidia.com>
Signed-off-by: default avatarThierry Reding <treding@nvidia.com>
parent 35874f36
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
What:		/sys/devices/*/<our-device>/fuse
Date:		February 2014
Contact:	Peter De Schrijver <pdeschrijver@nvidia.com>
Description:	read-only access to the efuses on Tegra20, Tegra30, Tegra114
		and Tegra124 SoC's from NVIDIA. The efuses contain write once
		data programmed at the factory. The data is layed out in 32bit
		words in LSB first format. Each bit represents a single value
		as decoded from the fuse registers. Bits order/assignment
		exactly matches the HW registers, including any unused bits.
Users:		any user space application which wants to read the efuses on
		Tegra SoC's
+0 −4
Original line number Diff line number Diff line
@@ -2,7 +2,6 @@ asflags-y += -march=armv7-a

obj-y                                   += io.o
obj-y                                   += irq.o
obj-y					+= fuse.o
obj-y					+= pmc.o
obj-y					+= flowctrl.o
obj-y					+= powergate.o
@@ -13,13 +12,11 @@ obj-y += reset-handler.o
obj-y					+= sleep.o
obj-y					+= tegra.o
obj-$(CONFIG_CPU_IDLE)			+= cpuidle.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC)		+= tegra20_speedo.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC)		+= sleep-tegra20.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC)		+= pm-tegra20.o
ifeq ($(CONFIG_CPU_IDLE),y)
obj-$(CONFIG_ARCH_TEGRA_2x_SOC)		+= cpuidle-tegra20.o
endif
obj-$(CONFIG_ARCH_TEGRA_3x_SOC)		+= tegra30_speedo.o
obj-$(CONFIG_ARCH_TEGRA_3x_SOC)		+= sleep-tegra30.o
obj-$(CONFIG_ARCH_TEGRA_3x_SOC)		+= pm-tegra30.o
ifeq ($(CONFIG_CPU_IDLE),y)
@@ -28,7 +25,6 @@ endif
obj-$(CONFIG_SMP)			+= platsmp.o headsmp.o
obj-$(CONFIG_HOTPLUG_CPU)               += hotplug.o

obj-$(CONFIG_ARCH_TEGRA_114_SOC)	+= tegra114_speedo.o
obj-$(CONFIG_ARCH_TEGRA_114_SOC)	+= sleep-tegra30.o
obj-$(CONFIG_ARCH_TEGRA_114_SOC)	+= pm-tegra30.o
ifeq ($(CONFIG_CPU_IDLE),y)

arch/arm/mach-tegra/fuse.c

deleted100644 → 0
+0 −260
Original line number Diff line number Diff line
/*
 * 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);
}
+0 −1
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@
#include <asm/firmware.h>
#include <asm/hardware/cache-l2x0.h>

#include "fuse.h"
#include "iomap.h"
#include "irammap.h"
#include "reset.h"
+2 −1
Original line number Diff line number Diff line
@@ -104,7 +104,8 @@ static void __init tegra_dt_init(void)
		goto out;

	soc_dev_attr->family = kasprintf(GFP_KERNEL, "Tegra");
	soc_dev_attr->revision = kasprintf(GFP_KERNEL, "%d", tegra_revision);
	soc_dev_attr->revision = kasprintf(GFP_KERNEL, "%d",
					   tegra_sku_info.revision);
	soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "%u", tegra_get_chip_id());

	soc_dev = soc_device_register(soc_dev_attr);
Loading