diff options
author | Peter De Schrijver <pdeschrijver@nvidia.com> | 2014-06-12 18:36:37 +0300 |
---|---|---|
committer | Thierry Reding <treding@nvidia.com> | 2014-07-17 14:36:01 +0200 |
commit | 783c8f4c84451bc444e314a71b447239c6ef6fd9 (patch) | |
tree | ccf8ed545ac850e06d3e781a99769bd0ea43c597 /drivers/soc/tegra/fuse/fuse-tegra20.c | |
parent | 35874f3617b38e0c1f72163407c41d554a8f5939 (diff) |
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: Peter De Schrijver <pdeschrijver@nvidia.com>
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Signed-off-by: Thierry Reding <treding@nvidia.com>
Diffstat (limited to 'drivers/soc/tegra/fuse/fuse-tegra20.c')
-rw-r--r-- | drivers/soc/tegra/fuse/fuse-tegra20.c | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/drivers/soc/tegra/fuse/fuse-tegra20.c b/drivers/soc/tegra/fuse/fuse-tegra20.c new file mode 100644 index 000000000000..7a9d0e045490 --- /dev/null +++ b/drivers/soc/tegra/fuse/fuse-tegra20.c @@ -0,0 +1,142 @@ +/* + * 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/of_device.h> +#include <linux/platform_device.h> +#include <linux/random.h> + +#include <soc/tegra/fuse.h> + +#include "fuse.h" + +#define FUSE_BEGIN 0x100 +#define FUSE_SIZE 0x1f8 +#define FUSE_UID_LOW 0x08 +#define FUSE_UID_HIGH 0x0c + +static phys_addr_t fuse_phys; +static struct clk *fuse_clk; +static void __iomem __initdata *fuse_base; + +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 const struct of_device_id tegra20_fuse_of_match[] = { + { .compatible = "nvidia,tegra20-efuse" }, + {}, +}; + +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; + + if (tegra_fuse_create_sysfs(&pdev->dev, FUSE_SIZE, tegra20_fuse_readl)) + 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); + +/* Early boot code. This code is called before the devices are created */ + +u32 __init tegra20_fuse_early(const unsigned int offset) +{ + return readl_relaxed(fuse_base + FUSE_BEGIN + offset); +} + +bool __init tegra20_spare_fuse_early(int spare_bit) +{ + u32 offset = spare_bit * 4; + bool value; + + value = tegra20_fuse_early(offset + 0x100); + + return value; +} + +static void __init tegra20_fuse_add_randomness(void) +{ + u32 randomness[7]; + + randomness[0] = tegra_sku_info.sku_id; + randomness[1] = tegra_read_straps(); + randomness[2] = tegra_read_chipid(); + randomness[3] = tegra_sku_info.cpu_process_id << 16; + randomness[3] |= tegra_sku_info.core_process_id; + randomness[4] = tegra_sku_info.cpu_speedo_id << 16; + randomness[4] |= tegra_sku_info.soc_speedo_id; + randomness[5] = tegra20_fuse_early(FUSE_UID_LOW); + randomness[6] = tegra20_fuse_early(FUSE_UID_HIGH); + + add_device_randomness(randomness, sizeof(randomness)); +} + +void __init tegra20_init_fuse_early(void) +{ + fuse_base = ioremap(TEGRA_FUSE_BASE, TEGRA_FUSE_SIZE); + + tegra_init_revision(); + tegra20_init_speedo_data(&tegra_sku_info); + tegra20_fuse_add_randomness(); + + iounmap(fuse_base); +} |