diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
commit | 76cb841cb886eef6b3bee341a2266c76578724ad (patch) | |
tree | f5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/soc/tegra/fuse | |
parent | Initial commit. (diff) | |
download | linux-upstream.tar.xz linux-upstream.zip |
Adding upstream version 4.19.249.upstream/4.19.249upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/soc/tegra/fuse')
-rw-r--r-- | drivers/soc/tegra/fuse/Makefile | 11 | ||||
-rw-r--r-- | drivers/soc/tegra/fuse/fuse-tegra.c | 378 | ||||
-rw-r--r-- | drivers/soc/tegra/fuse/fuse-tegra20.c | 178 | ||||
-rw-r--r-- | drivers/soc/tegra/fuse/fuse-tegra30.c | 182 | ||||
-rw-r--r-- | drivers/soc/tegra/fuse/fuse.h | 112 | ||||
-rw-r--r-- | drivers/soc/tegra/fuse/speedo-tegra114.c | 110 | ||||
-rw-r--r-- | drivers/soc/tegra/fuse/speedo-tegra124.c | 168 | ||||
-rw-r--r-- | drivers/soc/tegra/fuse/speedo-tegra20.c | 110 | ||||
-rw-r--r-- | drivers/soc/tegra/fuse/speedo-tegra210.c | 184 | ||||
-rw-r--r-- | drivers/soc/tegra/fuse/speedo-tegra30.c | 288 | ||||
-rw-r--r-- | drivers/soc/tegra/fuse/tegra-apbmisc.c | 183 |
11 files changed, 1904 insertions, 0 deletions
diff --git a/drivers/soc/tegra/fuse/Makefile b/drivers/soc/tegra/fuse/Makefile new file mode 100644 index 000000000..ea8332cc3 --- /dev/null +++ b/drivers/soc/tegra/fuse/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-y += fuse-tegra.o +obj-y += fuse-tegra30.o +obj-y += tegra-apbmisc.o +obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += fuse-tegra20.o +obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += speedo-tegra20.o +obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += speedo-tegra30.o +obj-$(CONFIG_ARCH_TEGRA_114_SOC) += speedo-tegra114.o +obj-$(CONFIG_ARCH_TEGRA_124_SOC) += speedo-tegra124.o +obj-$(CONFIG_ARCH_TEGRA_132_SOC) += speedo-tegra124.o +obj-$(CONFIG_ARCH_TEGRA_210_SOC) += speedo-tegra210.o diff --git a/drivers/soc/tegra/fuse/fuse-tegra.c b/drivers/soc/tegra/fuse/fuse-tegra.c new file mode 100644 index 000000000..52130ec8c --- /dev/null +++ b/drivers/soc/tegra/fuse/fuse-tegra.c @@ -0,0 +1,378 @@ +/* + * 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/clk.h> +#include <linux/device.h> +#include <linux/kobject.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/sys_soc.h> + +#include <soc/tegra/common.h> +#include <soc/tegra/fuse.h> + +#include "fuse.h" + +struct tegra_sku_info tegra_sku_info; +EXPORT_SYMBOL(tegra_sku_info); + +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(struct tegra_fuse *fuse, unsigned int offset) +{ + u32 val; + + val = fuse->read(fuse, 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) +{ + struct device *dev = kobj_to_dev(kobj); + struct tegra_fuse *fuse = dev_get_drvdata(dev); + int i; + + if (pos < 0 || pos >= attr->size) + return 0; + + if (size > attr->size - pos) + size = attr->size - pos; + + for (i = 0; i < size; i++) + buf[i] = fuse_readb(fuse, pos + i); + + return i; +} + +static struct bin_attribute fuse_bin_attr = { + .attr = { .name = "fuse", .mode = S_IRUGO, }, + .read = fuse_read, +}; + +static int tegra_fuse_create_sysfs(struct device *dev, unsigned int size, + const struct tegra_fuse_info *info) +{ + fuse_bin_attr.size = size; + + return device_create_bin_file(dev, &fuse_bin_attr); +} + +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", }, + { .compatible = "nvidia,tegra132-car", }, + { .compatible = "nvidia,tegra210-car", }, + {}, +}; + +static struct tegra_fuse *fuse = &(struct tegra_fuse) { + .base = NULL, + .soc = NULL, +}; + +static const struct of_device_id tegra_fuse_match[] = { +#ifdef CONFIG_ARCH_TEGRA_186_SOC + { .compatible = "nvidia,tegra186-efuse", .data = &tegra186_fuse_soc }, +#endif +#ifdef CONFIG_ARCH_TEGRA_210_SOC + { .compatible = "nvidia,tegra210-efuse", .data = &tegra210_fuse_soc }, +#endif +#ifdef CONFIG_ARCH_TEGRA_132_SOC + { .compatible = "nvidia,tegra132-efuse", .data = &tegra124_fuse_soc }, +#endif +#ifdef CONFIG_ARCH_TEGRA_124_SOC + { .compatible = "nvidia,tegra124-efuse", .data = &tegra124_fuse_soc }, +#endif +#ifdef CONFIG_ARCH_TEGRA_114_SOC + { .compatible = "nvidia,tegra114-efuse", .data = &tegra114_fuse_soc }, +#endif +#ifdef CONFIG_ARCH_TEGRA_3x_SOC + { .compatible = "nvidia,tegra30-efuse", .data = &tegra30_fuse_soc }, +#endif +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + { .compatible = "nvidia,tegra20-efuse", .data = &tegra20_fuse_soc }, +#endif + { /* sentinel */ } +}; + +static int tegra_fuse_probe(struct platform_device *pdev) +{ + void __iomem *base = fuse->base; + struct resource *res; + int err; + + /* take over the memory region from the early initialization */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + fuse->phys = res->start; + fuse->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(fuse->base)) { + err = PTR_ERR(fuse->base); + fuse->base = base; + return err; + } + + fuse->clk = devm_clk_get(&pdev->dev, "fuse"); + if (IS_ERR(fuse->clk)) { + dev_err(&pdev->dev, "failed to get FUSE clock: %ld", + PTR_ERR(fuse->clk)); + fuse->base = base; + return PTR_ERR(fuse->clk); + } + + platform_set_drvdata(pdev, fuse); + fuse->dev = &pdev->dev; + + if (fuse->soc->probe) { + err = fuse->soc->probe(fuse); + if (err < 0) { + fuse->base = base; + return err; + } + } + + if (tegra_fuse_create_sysfs(&pdev->dev, fuse->soc->info->size, + fuse->soc->info)) + return -ENODEV; + + /* release the early I/O memory mapping */ + iounmap(base); + + return 0; +} + +static struct platform_driver tegra_fuse_driver = { + .driver = { + .name = "tegra-fuse", + .of_match_table = tegra_fuse_match, + .suppress_bind_attrs = true, + }, + .probe = tegra_fuse_probe, +}; +builtin_platform_driver(tegra_fuse_driver); + +u32 __init tegra_fuse_read_spare(unsigned int spare) +{ + unsigned int offset = fuse->soc->info->spare + spare * 4; + + return fuse->read_early(fuse, offset) & 1; +} + +u32 __init tegra_fuse_read_early(unsigned int offset) +{ + return fuse->read_early(fuse, offset); +} + +int tegra_fuse_readl(unsigned long offset, u32 *value) +{ + if (!fuse->read) + return -EPROBE_DEFER; + + *value = fuse->read(fuse, offset); + + return 0; +} +EXPORT_SYMBOL(tegra_fuse_readl); + +static void tegra_enable_fuse_clk(void __iomem *base) +{ + u32 reg; + + reg = readl_relaxed(base + 0x48); + reg |= 1 << 28; + writel(reg, base + 0x48); + + /* + * Enable FUSE clock. This needs to be hardcoded because the clock + * subsystem is not active during early boot. + */ + reg = readl(base + 0x14); + reg |= 1 << 7; + writel(reg, base + 0x14); +} + +struct device * __init tegra_soc_device_register(void) +{ + struct soc_device_attribute *attr; + struct soc_device *dev; + + attr = kzalloc(sizeof(*attr), GFP_KERNEL); + if (!attr) + return NULL; + + attr->family = kasprintf(GFP_KERNEL, "Tegra"); + attr->revision = kasprintf(GFP_KERNEL, "%d", tegra_sku_info.revision); + attr->soc_id = kasprintf(GFP_KERNEL, "%u", tegra_get_chip_id()); + + dev = soc_device_register(attr); + if (IS_ERR(dev)) { + kfree(attr->soc_id); + kfree(attr->revision); + kfree(attr->family); + kfree(attr); + return ERR_CAST(dev); + } + + return soc_device_to_device(dev); +} + +static int __init tegra_init_fuse(void) +{ + const struct of_device_id *match; + struct device_node *np; + struct resource regs; + + tegra_init_apbmisc(); + + np = of_find_matching_node_and_match(NULL, tegra_fuse_match, &match); + if (!np) { + /* + * Fall back to legacy initialization for 32-bit ARM only. All + * 64-bit ARM device tree files for Tegra are required to have + * a FUSE node. + * + * This is for backwards-compatibility with old device trees + * that didn't contain a FUSE node. + */ + if (IS_ENABLED(CONFIG_ARM) && soc_is_tegra()) { + u8 chip = tegra_get_chip_id(); + + regs.start = 0x7000f800; + regs.end = 0x7000fbff; + regs.flags = IORESOURCE_MEM; + + switch (chip) { +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + case TEGRA20: + fuse->soc = &tegra20_fuse_soc; + break; +#endif + +#ifdef CONFIG_ARCH_TEGRA_3x_SOC + case TEGRA30: + fuse->soc = &tegra30_fuse_soc; + break; +#endif + +#ifdef CONFIG_ARCH_TEGRA_114_SOC + case TEGRA114: + fuse->soc = &tegra114_fuse_soc; + break; +#endif + +#ifdef CONFIG_ARCH_TEGRA_124_SOC + case TEGRA124: + fuse->soc = &tegra124_fuse_soc; + break; +#endif + + default: + pr_warn("Unsupported SoC: %02x\n", chip); + break; + } + } else { + /* + * At this point we're not running on Tegra, so play + * nice with multi-platform kernels. + */ + return 0; + } + } else { + /* + * Extract information from the device tree if we've found a + * matching node. + */ + if (of_address_to_resource(np, 0, ®s) < 0) { + pr_err("failed to get FUSE register\n"); + return -ENXIO; + } + + fuse->soc = match->data; + } + + np = of_find_matching_node(NULL, car_match); + if (np) { + void __iomem *base = of_iomap(np, 0); + if (base) { + tegra_enable_fuse_clk(base); + iounmap(base); + } else { + pr_err("failed to map clock registers\n"); + return -ENXIO; + } + } + + fuse->base = ioremap_nocache(regs.start, resource_size(®s)); + if (!fuse->base) { + pr_err("failed to map FUSE registers\n"); + return -ENXIO; + } + + fuse->soc->init(fuse); + + pr_info("Tegra Revision: %s SKU: %d CPU Process: %d SoC Process: %d\n", + tegra_revision_name[tegra_sku_info.revision], + tegra_sku_info.sku_id, tegra_sku_info.cpu_process_id, + tegra_sku_info.soc_process_id); + pr_debug("Tegra CPU Speedo ID %d, SoC Speedo ID %d\n", + tegra_sku_info.cpu_speedo_id, tegra_sku_info.soc_speedo_id); + + + return 0; +} +early_initcall(tegra_init_fuse); + +#ifdef CONFIG_ARM64 +static int __init tegra_init_soc(void) +{ + struct device_node *np; + struct device *soc; + + /* make sure we're running on Tegra */ + np = of_find_matching_node(NULL, tegra_fuse_match); + if (!np) + return 0; + + of_node_put(np); + + soc = tegra_soc_device_register(); + if (IS_ERR(soc)) { + pr_err("failed to register SoC device: %ld\n", PTR_ERR(soc)); + return PTR_ERR(soc); + } + + return 0; +} +device_initcall(tegra_init_soc); +#endif diff --git a/drivers/soc/tegra/fuse/fuse-tegra20.c b/drivers/soc/tegra/fuse/fuse-tegra20.c new file mode 100644 index 000000000..49ff017f3 --- /dev/null +++ b/drivers/soc/tegra/fuse/fuse-tegra20.c @@ -0,0 +1,178 @@ +/* + * 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/completion.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.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_UID_LOW 0x08 +#define FUSE_UID_HIGH 0x0c + +static u32 tegra20_fuse_read_early(struct tegra_fuse *fuse, unsigned int offset) +{ + return readl_relaxed(fuse->base + FUSE_BEGIN + offset); +} + +static void apb_dma_complete(void *args) +{ + struct tegra_fuse *fuse = args; + + complete(&fuse->apbdma.wait); +} + +static u32 tegra20_fuse_read(struct tegra_fuse *fuse, unsigned int offset) +{ + unsigned long flags = DMA_PREP_INTERRUPT | DMA_CTRL_ACK; + struct dma_async_tx_descriptor *dma_desc; + unsigned long time_left; + u32 value = 0; + int err; + + mutex_lock(&fuse->apbdma.lock); + + fuse->apbdma.config.src_addr = fuse->phys + FUSE_BEGIN + offset; + + err = dmaengine_slave_config(fuse->apbdma.chan, &fuse->apbdma.config); + if (err) + goto out; + + dma_desc = dmaengine_prep_slave_single(fuse->apbdma.chan, + fuse->apbdma.phys, + sizeof(u32), DMA_DEV_TO_MEM, + flags); + if (!dma_desc) + goto out; + + dma_desc->callback = apb_dma_complete; + dma_desc->callback_param = fuse; + + reinit_completion(&fuse->apbdma.wait); + + clk_prepare_enable(fuse->clk); + + dmaengine_submit(dma_desc); + dma_async_issue_pending(fuse->apbdma.chan); + time_left = wait_for_completion_timeout(&fuse->apbdma.wait, + msecs_to_jiffies(50)); + + if (WARN(time_left == 0, "apb read dma timed out")) + dmaengine_terminate_all(fuse->apbdma.chan); + else + value = *fuse->apbdma.virt; + + clk_disable_unprepare(fuse->clk); + +out: + mutex_unlock(&fuse->apbdma.lock); + return value; +} + +static bool dma_filter(struct dma_chan *chan, void *filter_param) +{ + struct device_node *np = chan->device->dev->of_node; + + return of_device_is_compatible(np, "nvidia,tegra20-apbdma"); +} + +static int tegra20_fuse_probe(struct tegra_fuse *fuse) +{ + dma_cap_mask_t mask; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + fuse->apbdma.chan = __dma_request_channel(&mask, dma_filter, NULL); + if (!fuse->apbdma.chan) + return -EPROBE_DEFER; + + fuse->apbdma.virt = dma_alloc_coherent(fuse->dev, sizeof(u32), + &fuse->apbdma.phys, + GFP_KERNEL); + if (!fuse->apbdma.virt) { + dma_release_channel(fuse->apbdma.chan); + return -ENOMEM; + } + + fuse->apbdma.config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + fuse->apbdma.config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + fuse->apbdma.config.src_maxburst = 1; + fuse->apbdma.config.dst_maxburst = 1; + fuse->apbdma.config.direction = DMA_DEV_TO_MEM; + fuse->apbdma.config.device_fc = false; + + init_completion(&fuse->apbdma.wait); + mutex_init(&fuse->apbdma.lock); + fuse->read = tegra20_fuse_read; + + return 0; +} + +static const struct tegra_fuse_info tegra20_fuse_info = { + .read = tegra20_fuse_read, + .size = 0x1f8, + .spare = 0x100, +}; + +/* Early boot code. This code is called before the devices are created */ + +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.soc_process_id; + randomness[4] = tegra_sku_info.cpu_speedo_id << 16; + randomness[4] |= tegra_sku_info.soc_speedo_id; + randomness[5] = tegra_fuse_read_early(FUSE_UID_LOW); + randomness[6] = tegra_fuse_read_early(FUSE_UID_HIGH); + + add_device_randomness(randomness, sizeof(randomness)); +} + +static void __init tegra20_fuse_init(struct tegra_fuse *fuse) +{ + fuse->read_early = tegra20_fuse_read_early; + + tegra_init_revision(); + fuse->soc->speedo_init(&tegra_sku_info); + tegra20_fuse_add_randomness(); +} + +const struct tegra_fuse_soc tegra20_fuse_soc = { + .init = tegra20_fuse_init, + .speedo_init = tegra20_init_speedo_data, + .probe = tegra20_fuse_probe, + .info = &tegra20_fuse_info, +}; diff --git a/drivers/soc/tegra/fuse/fuse-tegra30.c b/drivers/soc/tegra/fuse/fuse-tegra30.c new file mode 100644 index 000000000..7c47a0ceb --- /dev/null +++ b/drivers/soc/tegra/fuse/fuse-tegra30.c @@ -0,0 +1,182 @@ +/* + * 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/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/random.h> + +#include <soc/tegra/fuse.h> + +#include "fuse.h" + +#define FUSE_BEGIN 0x100 + +/* 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) + +#if defined(CONFIG_ARCH_TEGRA_3x_SOC) || \ + defined(CONFIG_ARCH_TEGRA_114_SOC) || \ + defined(CONFIG_ARCH_TEGRA_124_SOC) || \ + defined(CONFIG_ARCH_TEGRA_132_SOC) || \ + defined(CONFIG_ARCH_TEGRA_210_SOC) || \ + defined(CONFIG_ARCH_TEGRA_186_SOC) || \ + defined(CONFIG_ARCH_TEGRA_194_SOC) || \ + defined(CONFIG_ARCH_TEGRA_234_SOC) +static u32 tegra30_fuse_read_early(struct tegra_fuse *fuse, unsigned int offset) +{ + if (WARN_ON(!fuse->base)) + return 0; + + return readl_relaxed(fuse->base + FUSE_BEGIN + offset); +} + +static u32 tegra30_fuse_read(struct tegra_fuse *fuse, unsigned int offset) +{ + u32 value; + int err; + + err = clk_prepare_enable(fuse->clk); + if (err < 0) { + dev_err(fuse->dev, "failed to enable FUSE clock: %d\n", err); + return 0; + } + + value = readl_relaxed(fuse->base + FUSE_BEGIN + offset); + + clk_disable_unprepare(fuse->clk); + + return value; +} + +static void __init tegra30_fuse_add_randomness(void) +{ + u32 randomness[12]; + + 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.soc_process_id; + randomness[4] = tegra_sku_info.cpu_speedo_id << 16; + randomness[4] |= tegra_sku_info.soc_speedo_id; + randomness[5] = tegra_fuse_read_early(FUSE_VENDOR_CODE); + randomness[6] = tegra_fuse_read_early(FUSE_FAB_CODE); + randomness[7] = tegra_fuse_read_early(FUSE_LOT_CODE_0); + randomness[8] = tegra_fuse_read_early(FUSE_LOT_CODE_1); + randomness[9] = tegra_fuse_read_early(FUSE_WAFER_ID); + randomness[10] = tegra_fuse_read_early(FUSE_X_COORDINATE); + randomness[11] = tegra_fuse_read_early(FUSE_Y_COORDINATE); + + add_device_randomness(randomness, sizeof(randomness)); +} + +static void __init tegra30_fuse_init(struct tegra_fuse *fuse) +{ + fuse->read_early = tegra30_fuse_read_early; + fuse->read = tegra30_fuse_read; + + tegra_init_revision(); + + if (fuse->soc->speedo_init) + fuse->soc->speedo_init(&tegra_sku_info); + + tegra30_fuse_add_randomness(); +} +#endif + +#ifdef CONFIG_ARCH_TEGRA_3x_SOC +static const struct tegra_fuse_info tegra30_fuse_info = { + .read = tegra30_fuse_read, + .size = 0x2a4, + .spare = 0x144, +}; + +const struct tegra_fuse_soc tegra30_fuse_soc = { + .init = tegra30_fuse_init, + .speedo_init = tegra30_init_speedo_data, + .info = &tegra30_fuse_info, +}; +#endif + +#ifdef CONFIG_ARCH_TEGRA_114_SOC +static const struct tegra_fuse_info tegra114_fuse_info = { + .read = tegra30_fuse_read, + .size = 0x2a0, + .spare = 0x180, +}; + +const struct tegra_fuse_soc tegra114_fuse_soc = { + .init = tegra30_fuse_init, + .speedo_init = tegra114_init_speedo_data, + .info = &tegra114_fuse_info, +}; +#endif + +#if defined(CONFIG_ARCH_TEGRA_124_SOC) || defined(CONFIG_ARCH_TEGRA_132_SOC) +static const struct tegra_fuse_info tegra124_fuse_info = { + .read = tegra30_fuse_read, + .size = 0x300, + .spare = 0x200, +}; + +const struct tegra_fuse_soc tegra124_fuse_soc = { + .init = tegra30_fuse_init, + .speedo_init = tegra124_init_speedo_data, + .info = &tegra124_fuse_info, +}; +#endif + +#if defined(CONFIG_ARCH_TEGRA_210_SOC) +static const struct tegra_fuse_info tegra210_fuse_info = { + .read = tegra30_fuse_read, + .size = 0x300, + .spare = 0x280, +}; + +const struct tegra_fuse_soc tegra210_fuse_soc = { + .init = tegra30_fuse_init, + .speedo_init = tegra210_init_speedo_data, + .info = &tegra210_fuse_info, +}; +#endif + +#if defined(CONFIG_ARCH_TEGRA_186_SOC) +static const struct tegra_fuse_info tegra186_fuse_info = { + .read = tegra30_fuse_read, + .size = 0x300, + .spare = 0x280, +}; + +const struct tegra_fuse_soc tegra186_fuse_soc = { + .init = tegra30_fuse_init, + .info = &tegra186_fuse_info, +}; +#endif diff --git a/drivers/soc/tegra/fuse/fuse.h b/drivers/soc/tegra/fuse/fuse.h new file mode 100644 index 000000000..bf489d50e --- /dev/null +++ b/drivers/soc/tegra/fuse/fuse.h @@ -0,0 +1,112 @@ +/* + * 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 + +#include <linux/dmaengine.h> +#include <linux/types.h> + +struct tegra_fuse; + +struct tegra_fuse_info { + u32 (*read)(struct tegra_fuse *fuse, unsigned int offset); + unsigned int size; + unsigned int spare; +}; + +struct tegra_fuse_soc { + void (*init)(struct tegra_fuse *fuse); + void (*speedo_init)(struct tegra_sku_info *info); + int (*probe)(struct tegra_fuse *fuse); + + const struct tegra_fuse_info *info; +}; + +struct tegra_fuse { + struct device *dev; + void __iomem *base; + phys_addr_t phys; + struct clk *clk; + + u32 (*read_early)(struct tegra_fuse *fuse, unsigned int offset); + u32 (*read)(struct tegra_fuse *fuse, unsigned int offset); + const struct tegra_fuse_soc *soc; + + /* APBDMA on Tegra20 */ + struct { + struct mutex lock; + struct completion wait; + struct dma_chan *chan; + struct dma_slave_config config; + dma_addr_t phys; + u32 *virt; + } apbdma; +}; + +void tegra_init_revision(void); +void tegra_init_apbmisc(void); + +u32 __init tegra_fuse_read_spare(unsigned int spare); +u32 __init tegra_fuse_read_early(unsigned int offset); + +#ifdef CONFIG_ARCH_TEGRA_2x_SOC +void tegra20_init_speedo_data(struct tegra_sku_info *sku_info); +#endif + +#ifdef CONFIG_ARCH_TEGRA_3x_SOC +void tegra30_init_speedo_data(struct tegra_sku_info *sku_info); +#endif + +#ifdef CONFIG_ARCH_TEGRA_114_SOC +void tegra114_init_speedo_data(struct tegra_sku_info *sku_info); +#endif + +#if defined(CONFIG_ARCH_TEGRA_124_SOC) || defined(CONFIG_ARCH_TEGRA_132_SOC) +void tegra124_init_speedo_data(struct tegra_sku_info *sku_info); +#endif + +#ifdef CONFIG_ARCH_TEGRA_210_SOC +void tegra210_init_speedo_data(struct tegra_sku_info *sku_info); +#endif + +#ifdef CONFIG_ARCH_TEGRA_2x_SOC +extern const struct tegra_fuse_soc tegra20_fuse_soc; +#endif + +#ifdef CONFIG_ARCH_TEGRA_3x_SOC +extern const struct tegra_fuse_soc tegra30_fuse_soc; +#endif + +#ifdef CONFIG_ARCH_TEGRA_114_SOC +extern const struct tegra_fuse_soc tegra114_fuse_soc; +#endif + +#if defined(CONFIG_ARCH_TEGRA_124_SOC) || defined(CONFIG_ARCH_TEGRA_132_SOC) +extern const struct tegra_fuse_soc tegra124_fuse_soc; +#endif + +#ifdef CONFIG_ARCH_TEGRA_210_SOC +extern const struct tegra_fuse_soc tegra210_fuse_soc; +#endif + +#ifdef CONFIG_ARCH_TEGRA_186_SOC +extern const struct tegra_fuse_soc tegra186_fuse_soc; +#endif + +#endif diff --git a/drivers/soc/tegra/fuse/speedo-tegra114.c b/drivers/soc/tegra/fuse/speedo-tegra114.c new file mode 100644 index 000000000..1ba41ebbb --- /dev/null +++ b/drivers/soc/tegra/fuse/speedo-tegra114.c @@ -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/bug.h> +#include <linux/device.h> +#include <linux/kernel.h> + +#include <soc/tegra/fuse.h> + +#include "fuse.h" + +#define SOC_PROCESS_CORNERS 2 +#define CPU_PROCESS_CORNERS 2 + +enum { + THRESHOLD_INDEX_0, + THRESHOLD_INDEX_1, + THRESHOLD_INDEX_COUNT, +}; + +static const u32 __initconst soc_process_speedos[][SOC_PROCESS_CORNERS] = { + {1123, UINT_MAX}, + {0, UINT_MAX}, +}; + +static const u32 __initconst cpu_process_speedos[][CPU_PROCESS_CORNERS] = { + {1695, UINT_MAX}, + {0, UINT_MAX}, +}; + +static void __init rev_sku_to_speedo_ids(struct tegra_sku_info *sku_info, + int *threshold) +{ + 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: + pr_err("Tegra 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 = tegra_fuse_read_early(0x270) << 1; + tmp |= tegra_fuse_read_early(0x26c); + if (!tmp) + sku_info->cpu_speedo_id = 0; + } +} + +void __init tegra114_init_speedo_data(struct tegra_sku_info *sku_info) +{ + u32 cpu_speedo_val; + u32 soc_speedo_val; + int threshold; + int i; + + BUILD_BUG_ON(ARRAY_SIZE(cpu_process_speedos) != + THRESHOLD_INDEX_COUNT); + BUILD_BUG_ON(ARRAY_SIZE(soc_process_speedos) != + THRESHOLD_INDEX_COUNT); + + rev_sku_to_speedo_ids(sku_info, &threshold); + + cpu_speedo_val = tegra_fuse_read_early(0x12c) + 1024; + soc_speedo_val = tegra_fuse_read_early(0x134); + + for (i = 0; i < CPU_PROCESS_CORNERS; i++) + if (cpu_speedo_val < cpu_process_speedos[threshold][i]) + break; + sku_info->cpu_process_id = i; + + for (i = 0; i < SOC_PROCESS_CORNERS; i++) + if (soc_speedo_val < soc_process_speedos[threshold][i]) + break; + sku_info->soc_process_id = i; +} diff --git a/drivers/soc/tegra/fuse/speedo-tegra124.c b/drivers/soc/tegra/fuse/speedo-tegra124.c new file mode 100644 index 000000000..a63a13410 --- /dev/null +++ b/drivers/soc/tegra/fuse/speedo-tegra124.c @@ -0,0 +1,168 @@ +/* + * 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 <soc/tegra/fuse.h> + +#include "fuse.h" + +#define CPU_PROCESS_CORNERS 2 +#define GPU_PROCESS_CORNERS 2 +#define SOC_PROCESS_CORNERS 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 const u32 __initconst cpu_process_speedos[][CPU_PROCESS_CORNERS] = { + {2190, UINT_MAX}, + {0, UINT_MAX}, +}; + +static const u32 __initconst gpu_process_speedos[][GPU_PROCESS_CORNERS] = { + {1965, UINT_MAX}, + {0, UINT_MAX}, +}; + +static const u32 __initconst soc_process_speedos[][SOC_PROCESS_CORNERS] = { + {2101, UINT_MAX}, + {0, UINT_MAX}, +}; + +static void __init rev_sku_to_speedo_ids(struct tegra_sku_info *sku_info, + int *threshold) +{ + 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: + case 0x23: + /* Using the default */ + break; + case 0x83: + sku_info->cpu_speedo_id = 2; + break; + + case 0x1F: + case 0x87: + case 0x27: + sku_info->cpu_speedo_id = 2; + sku_info->soc_speedo_id = 0; + sku_info->gpu_speedo_id = 1; + *threshold = THRESHOLD_INDEX_0; + break; + case 0x81: + case 0x21: + 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; + case 0x49: + case 0x4A: + case 0x48: + sku_info->cpu_speedo_id = 4; + sku_info->soc_speedo_id = 2; + sku_info->gpu_speedo_id = 3; + *threshold = THRESHOLD_INDEX_1; + break; + default: + pr_err("Tegra Unknown SKU %d\n", sku); + /* Using the default for the error case */ + break; + } +} + +void __init tegra124_init_speedo_data(struct tegra_sku_info *sku_info) +{ + int i, threshold, cpu_speedo_0_value, soc_speedo_0_value; + int cpu_iddq_value, gpu_iddq_value, soc_iddq_value; + + 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(soc_process_speedos) != + THRESHOLD_INDEX_COUNT); + + cpu_speedo_0_value = tegra_fuse_read_early(FUSE_CPU_SPEEDO_0); + + /* GPU Speedo is stored in CPU_SPEEDO_2 */ + sku_info->gpu_speedo_value = tegra_fuse_read_early(FUSE_CPU_SPEEDO_2); + + soc_speedo_0_value = tegra_fuse_read_early(FUSE_SOC_SPEEDO_0); + + cpu_iddq_value = tegra_fuse_read_early(FUSE_CPU_IDDQ); + soc_iddq_value = tegra_fuse_read_early(FUSE_SOC_IDDQ); + gpu_iddq_value = tegra_fuse_read_early(FUSE_GPU_IDDQ); + + sku_info->cpu_speedo_value = cpu_speedo_0_value; + + if (sku_info->cpu_speedo_value == 0) { + pr_warn("Tegra Warning: Speedo value not fused.\n"); + WARN_ON(1); + return; + } + + rev_sku_to_speedo_ids(sku_info, &threshold); + + sku_info->cpu_iddq_value = tegra_fuse_read_early(FUSE_CPU_IDDQ); + + for (i = 0; i < GPU_PROCESS_CORNERS; 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; i++) + if (sku_info->cpu_speedo_value < + cpu_process_speedos[threshold][i]) + break; + sku_info->cpu_process_id = i; + + for (i = 0; i < SOC_PROCESS_CORNERS; i++) + if (soc_speedo_0_value < + soc_process_speedos[threshold][i]) + break; + sku_info->soc_process_id = i; + + pr_debug("Tegra GPU Speedo ID=%d, Speedo Value=%d\n", + sku_info->gpu_speedo_id, sku_info->gpu_speedo_value); +} diff --git a/drivers/soc/tegra/fuse/speedo-tegra20.c b/drivers/soc/tegra/fuse/speedo-tegra20.c new file mode 100644 index 000000000..5f7818bf6 --- /dev/null +++ b/drivers/soc/tegra/fuse/speedo-tegra20.c @@ -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/bug.h> +#include <linux/device.h> +#include <linux/kernel.h> + +#include <soc/tegra/fuse.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 SOC_SPEEDO_LSBIT 40 +#define SOC_SPEEDO_MSBIT 47 +#define SOC_SPEEDO_REDUND_LSBIT 48 +#define SOC_SPEEDO_REDUND_MSBIT 55 +#define SOC_SPEEDO_REDUND_OFFS (SOC_SPEEDO_REDUND_MSBIT - SOC_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 __initconst cpu_process_speedos[][PROCESS_CORNERS_NUM] = { + {315, 366, 420, UINT_MAX}, + {303, 368, 419, UINT_MAX}, + {316, 331, 383, UINT_MAX}, +}; + +static const u32 __initconst soc_process_speedos[][PROCESS_CORNERS_NUM] = { + {165, 195, 224, UINT_MAX}, + {165, 195, 224, UINT_MAX}, + {165, 195, 224, UINT_MAX}, +}; + +void __init tegra20_init_speedo_data(struct tegra_sku_info *sku_info) +{ + u32 reg; + u32 val; + int i; + + BUILD_BUG_ON(ARRAY_SIZE(cpu_process_speedos) != SPEEDO_ID_COUNT); + BUILD_BUG_ON(ARRAY_SIZE(soc_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 = tegra_fuse_read_spare(i) | + tegra_fuse_read_spare(i + CPU_SPEEDO_REDUND_OFFS); + val = (val << 1) | (reg & 0x1); + } + val = val * SPEEDO_MULT; + pr_debug("Tegra 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 = SOC_SPEEDO_MSBIT; i >= SOC_SPEEDO_LSBIT; i--) { + reg = tegra_fuse_read_spare(i) | + tegra_fuse_read_spare(i + SOC_SPEEDO_REDUND_OFFS); + val = (val << 1) | (reg & 0x1); + } + val = val * SPEEDO_MULT; + pr_debug("Core speedo value %u\n", val); + + for (i = 0; i < (PROCESS_CORNERS_NUM - 1); i++) { + if (val <= soc_process_speedos[sku_info->soc_speedo_id][i]) + break; + } + sku_info->soc_process_id = i; +} diff --git a/drivers/soc/tegra/fuse/speedo-tegra210.c b/drivers/soc/tegra/fuse/speedo-tegra210.c new file mode 100644 index 000000000..4403b8956 --- /dev/null +++ b/drivers/soc/tegra/fuse/speedo-tegra210.c @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2013-2015, 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 <soc/tegra/fuse.h> + +#include "fuse.h" + +#define CPU_PROCESS_CORNERS 2 +#define GPU_PROCESS_CORNERS 2 +#define SOC_PROCESS_CORNERS 3 + +#define FUSE_CPU_SPEEDO_0 0x014 +#define FUSE_CPU_SPEEDO_1 0x02c +#define FUSE_CPU_SPEEDO_2 0x030 +#define FUSE_SOC_SPEEDO_0 0x034 +#define FUSE_SOC_SPEEDO_1 0x038 +#define FUSE_SOC_SPEEDO_2 0x03c +#define FUSE_CPU_IDDQ 0x018 +#define FUSE_SOC_IDDQ 0x040 +#define FUSE_GPU_IDDQ 0x128 +#define FUSE_FT_REV 0x028 + +enum { + THRESHOLD_INDEX_0, + THRESHOLD_INDEX_1, + THRESHOLD_INDEX_COUNT, +}; + +static const u32 __initconst cpu_process_speedos[][CPU_PROCESS_CORNERS] = { + { 2119, UINT_MAX }, + { 2119, UINT_MAX }, +}; + +static const u32 __initconst gpu_process_speedos[][GPU_PROCESS_CORNERS] = { + { UINT_MAX, UINT_MAX }, + { UINT_MAX, UINT_MAX }, +}; + +static const u32 __initconst soc_process_speedos[][SOC_PROCESS_CORNERS] = { + { 1950, 2100, UINT_MAX }, + { 1950, 2100, UINT_MAX }, +}; + +static u8 __init get_speedo_revision(void) +{ + return tegra_fuse_read_spare(4) << 2 | + tegra_fuse_read_spare(3) << 1 | + tegra_fuse_read_spare(2) << 0; +} + +static void __init rev_sku_to_speedo_ids(struct tegra_sku_info *sku_info, + u8 speedo_rev, int *threshold) +{ + 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: /* Engineering SKU */ + case 0x01: /* Engineering SKU */ + case 0x07: + case 0x17: + case 0x27: + if (speedo_rev >= 2) + sku_info->gpu_speedo_id = 1; + break; + + case 0x13: + if (speedo_rev >= 2) + sku_info->gpu_speedo_id = 1; + + sku_info->cpu_speedo_id = 1; + break; + + default: + pr_err("Tegra210: unknown SKU %#04x\n", sku); + /* Using the default for the error case */ + break; + } +} + +static int get_process_id(int value, const u32 *speedos, unsigned int num) +{ + unsigned int i; + + for (i = 0; i < num; i++) + if (value < speedos[i]) + return i; + + return -EINVAL; +} + +void __init tegra210_init_speedo_data(struct tegra_sku_info *sku_info) +{ + int cpu_speedo[3], soc_speedo[3], cpu_iddq, gpu_iddq, soc_iddq; + unsigned int index; + u8 speedo_revision; + + 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(soc_process_speedos) != + THRESHOLD_INDEX_COUNT); + + /* Read speedo/IDDQ fuses */ + cpu_speedo[0] = tegra_fuse_read_early(FUSE_CPU_SPEEDO_0); + cpu_speedo[1] = tegra_fuse_read_early(FUSE_CPU_SPEEDO_1); + cpu_speedo[2] = tegra_fuse_read_early(FUSE_CPU_SPEEDO_2); + + soc_speedo[0] = tegra_fuse_read_early(FUSE_SOC_SPEEDO_0); + soc_speedo[1] = tegra_fuse_read_early(FUSE_SOC_SPEEDO_1); + soc_speedo[2] = tegra_fuse_read_early(FUSE_CPU_SPEEDO_2); + + cpu_iddq = tegra_fuse_read_early(FUSE_CPU_IDDQ) * 4; + soc_iddq = tegra_fuse_read_early(FUSE_SOC_IDDQ) * 4; + gpu_iddq = tegra_fuse_read_early(FUSE_GPU_IDDQ) * 5; + + /* + * Determine CPU, GPU and SoC speedo values depending on speedo fusing + * revision. Note that GPU speedo value is fused in CPU_SPEEDO_2. + */ + speedo_revision = get_speedo_revision(); + pr_info("Speedo Revision %u\n", speedo_revision); + + if (speedo_revision >= 3) { + sku_info->cpu_speedo_value = cpu_speedo[0]; + sku_info->gpu_speedo_value = cpu_speedo[2]; + sku_info->soc_speedo_value = soc_speedo[0]; + } else if (speedo_revision == 2) { + sku_info->cpu_speedo_value = (-1938 + (1095 * cpu_speedo[0] / 100)) / 10; + sku_info->gpu_speedo_value = (-1662 + (1082 * cpu_speedo[2] / 100)) / 10; + sku_info->soc_speedo_value = ( -705 + (1037 * soc_speedo[0] / 100)) / 10; + } else { + sku_info->cpu_speedo_value = 2100; + sku_info->gpu_speedo_value = cpu_speedo[2] - 75; + sku_info->soc_speedo_value = 1900; + } + + if ((sku_info->cpu_speedo_value <= 0) || + (sku_info->gpu_speedo_value <= 0) || + (sku_info->soc_speedo_value <= 0)) { + WARN(1, "speedo value not fused\n"); + return; + } + + rev_sku_to_speedo_ids(sku_info, speedo_revision, &index); + + sku_info->gpu_process_id = get_process_id(sku_info->gpu_speedo_value, + gpu_process_speedos[index], + GPU_PROCESS_CORNERS); + + sku_info->cpu_process_id = get_process_id(sku_info->cpu_speedo_value, + cpu_process_speedos[index], + CPU_PROCESS_CORNERS); + + sku_info->soc_process_id = get_process_id(sku_info->soc_speedo_value, + soc_process_speedos[index], + SOC_PROCESS_CORNERS); + + pr_debug("Tegra GPU Speedo ID=%d, Speedo Value=%d\n", + sku_info->gpu_speedo_id, sku_info->gpu_speedo_value); +} diff --git a/drivers/soc/tegra/fuse/speedo-tegra30.c b/drivers/soc/tegra/fuse/speedo-tegra30.c new file mode 100644 index 000000000..9b010b3ef --- /dev/null +++ b/drivers/soc/tegra/fuse/speedo-tegra30.c @@ -0,0 +1,288 @@ +/* + * 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/bug.h> +#include <linux/device.h> +#include <linux/kernel.h> + +#include <soc/tegra/fuse.h> + +#include "fuse.h" + +#define SOC_PROCESS_CORNERS 1 +#define CPU_PROCESS_CORNERS 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 __initconst soc_process_speedos[][SOC_PROCESS_CORNERS] = { + {180}, + {170}, + {195}, + {180}, + {168}, + {192}, + {180}, + {170}, + {195}, + {180}, + {180}, + {180}, +}; + +static const u32 __initconst cpu_process_speedos[][CPU_PROCESS_CORNERS] = { + {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 __initdata; + +static void __init fuse_speedo_calib(u32 *speedo_g, u32 *speedo_lp) +{ + u32 reg; + int ate_ver; + int bit_minus1; + int bit_minus2; + + reg = tegra_fuse_read_early(FUSE_SPEEDO_CALIB_0); + + *speedo_lp = (reg & 0xFFFF) * 4; + *speedo_g = ((reg >> 16) & 0xFFFF) * 4; + + ate_ver = tegra_fuse_read_early(FUSE_TEST_PROG_VER); + pr_debug("Tegra ATE prog ver %d.%d\n", ate_ver/10, ate_ver%10); + + if (ate_ver >= 26) { + bit_minus1 = tegra_fuse_read_spare(LP_SPEEDO_BIT_MINUS1); + bit_minus1 |= tegra_fuse_read_spare(LP_SPEEDO_BIT_MINUS1_R); + bit_minus2 = tegra_fuse_read_spare(LP_SPEEDO_BIT_MINUS2); + bit_minus2 |= tegra_fuse_read_spare(LP_SPEEDO_BIT_MINUS2_R); + *speedo_lp |= (bit_minus1 << 1) | bit_minus2; + + bit_minus1 = tegra_fuse_read_spare(G_SPEEDO_BIT_MINUS1); + bit_minus1 |= tegra_fuse_read_spare(G_SPEEDO_BIT_MINUS1_R); + bit_minus2 = tegra_fuse_read_spare(G_SPEEDO_BIT_MINUS2); + bit_minus2 |= tegra_fuse_read_spare(G_SPEEDO_BIT_MINUS2_R); + *speedo_g |= (bit_minus1 << 1) | bit_minus2; + } else { + *speedo_lp |= 0x3; + *speedo_g |= 0x3; + } +} + +static void __init rev_sku_to_speedo_ids(struct tegra_sku_info *sku_info) +{ + int package_id = tegra_fuse_read_early(FUSE_PACKAGE_INFO) & 0x0F; + + 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: + pr_err("Tegra Unknown pkg %d\n", package_id); + 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: + pr_err("Tegra Unknown pkg %d\n", package_id); + 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: + pr_err("Tegra Unknown pkg %d\n", package_id); + 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: + pr_err("Tegra Unknown pkg %d\n", package_id); + break; + } + break; + default: + pr_warn("Tegra 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: + pr_warn("Tegra 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 __init tegra30_init_speedo_data(struct tegra_sku_info *sku_info) +{ + u32 cpu_speedo_val; + u32 soc_speedo_val; + int i; + + BUILD_BUG_ON(ARRAY_SIZE(cpu_process_speedos) != + THRESHOLD_INDEX_COUNT); + BUILD_BUG_ON(ARRAY_SIZE(soc_process_speedos) != + THRESHOLD_INDEX_COUNT); + + + rev_sku_to_speedo_ids(sku_info); + fuse_speedo_calib(&cpu_speedo_val, &soc_speedo_val); + pr_debug("Tegra CPU speedo value %u\n", cpu_speedo_val); + pr_debug("Tegra Core speedo value %u\n", soc_speedo_val); + + for (i = 0; i < CPU_PROCESS_CORNERS; 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) { + pr_warn("Tegra 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 < SOC_PROCESS_CORNERS; i++) { + if (soc_speedo_val < soc_process_speedos[threshold_index][i]) + break; + } + sku_info->soc_process_id = i - 1; + + if (sku_info->soc_process_id == -1) { + pr_warn("Tegra SoC speedo value %3d out of range", + soc_speedo_val); + sku_info->soc_process_id = 0; + sku_info->soc_speedo_id = 1; + } +} diff --git a/drivers/soc/tegra/fuse/tegra-apbmisc.c b/drivers/soc/tegra/fuse/tegra-apbmisc.c new file mode 100644 index 000000000..d1cbb0fe1 --- /dev/null +++ b/drivers/soc/tegra/fuse/tegra-apbmisc.c @@ -0,0 +1,183 @@ +/* + * Copyright (c) 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/of.h> +#include <linux/of_address.h> +#include <linux/io.h> + +#include <soc/tegra/fuse.h> +#include <soc/tegra/common.h> + +#include "fuse.h" + +#define FUSE_SKU_INFO 0x10 + +#define PMC_STRAPPING_OPT_A_RAM_CODE_SHIFT 4 +#define PMC_STRAPPING_OPT_A_RAM_CODE_MASK_LONG \ + (0xf << PMC_STRAPPING_OPT_A_RAM_CODE_SHIFT) +#define PMC_STRAPPING_OPT_A_RAM_CODE_MASK_SHORT \ + (0x3 << PMC_STRAPPING_OPT_A_RAM_CODE_SHIFT) + +static void __iomem *apbmisc_base; +static void __iomem *strapping_base; +static bool long_ram_code; + +u32 tegra_read_chipid(void) +{ + if (!apbmisc_base) { + WARN(1, "Tegra Chip ID not yet available\n"); + return 0; + } + + return readl_relaxed(apbmisc_base + 4); +} + +u8 tegra_get_chip_id(void) +{ + return (tegra_read_chipid() >> 8) & 0xff; +} + +u32 tegra_read_straps(void) +{ + if (strapping_base) + return readl_relaxed(strapping_base); + else + return 0; +} + +u32 tegra_read_ram_code(void) +{ + u32 straps = tegra_read_straps(); + + if (long_ram_code) + straps &= PMC_STRAPPING_OPT_A_RAM_CODE_MASK_LONG; + else + straps &= PMC_STRAPPING_OPT_A_RAM_CODE_MASK_SHORT; + + return straps >> PMC_STRAPPING_OPT_A_RAM_CODE_SHIFT; +} + +static const struct of_device_id apbmisc_match[] __initconst = { + { .compatible = "nvidia,tegra20-apbmisc", }, + { .compatible = "nvidia,tegra186-misc", }, + {}, +}; + +void __init tegra_init_revision(void) +{ + u32 id, chip_id, minor_rev; + int rev; + + id = tegra_read_chipid(); + chip_id = (id >> 8) & 0xff; + minor_rev = (id >> 16) & 0xf; + + switch (minor_rev) { + case 1: + rev = TEGRA_REVISION_A01; + break; + case 2: + rev = TEGRA_REVISION_A02; + break; + case 3: + if (chip_id == TEGRA20 && (tegra_fuse_read_spare(18) || + tegra_fuse_read_spare(19))) + rev = TEGRA_REVISION_A03p; + else + rev = TEGRA_REVISION_A03; + break; + case 4: + rev = TEGRA_REVISION_A04; + break; + default: + rev = TEGRA_REVISION_UNKNOWN; + } + + tegra_sku_info.revision = rev; + + tegra_sku_info.sku_id = tegra_fuse_read_early(FUSE_SKU_INFO); +} + +void __init tegra_init_apbmisc(void) +{ + struct resource apbmisc, straps; + struct device_node *np; + + np = of_find_matching_node(NULL, apbmisc_match); + if (!np) { + /* + * Fall back to legacy initialization for 32-bit ARM only. All + * 64-bit ARM device tree files for Tegra are required to have + * an APBMISC node. + * + * This is for backwards-compatibility with old device trees + * that didn't contain an APBMISC node. + */ + if (IS_ENABLED(CONFIG_ARM) && soc_is_tegra()) { + /* APBMISC registers (chip revision, ...) */ + apbmisc.start = 0x70000800; + apbmisc.end = 0x70000863; + apbmisc.flags = IORESOURCE_MEM; + + /* strapping options */ + if (of_machine_is_compatible("nvidia,tegra124")) { + straps.start = 0x7000e864; + straps.end = 0x7000e867; + } else { + straps.start = 0x70000008; + straps.end = 0x7000000b; + } + + straps.flags = IORESOURCE_MEM; + + pr_warn("Using APBMISC region %pR\n", &apbmisc); + pr_warn("Using strapping options registers %pR\n", + &straps); + } else { + /* + * At this point we're not running on Tegra, so play + * nice with multi-platform kernels. + */ + return; + } + } else { + /* + * Extract information from the device tree if we've found a + * matching node. + */ + if (of_address_to_resource(np, 0, &apbmisc) < 0) { + pr_err("failed to get APBMISC registers\n"); + return; + } + + if (of_address_to_resource(np, 1, &straps) < 0) { + pr_err("failed to get strapping options registers\n"); + return; + } + } + + apbmisc_base = ioremap_nocache(apbmisc.start, resource_size(&apbmisc)); + if (!apbmisc_base) + pr_err("failed to map APBMISC registers\n"); + + strapping_base = ioremap_nocache(straps.start, resource_size(&straps)); + if (!strapping_base) + pr_err("failed to map strapping options registers\n"); + + long_ram_code = of_property_read_bool(np, "nvidia,long-ram-code"); +} |