linux/drivers/soc/tegra/fuse/fuse-tegra.c
Linus Torvalds eab3540562 ARM: SoC-related driver updates
Various driver updates for platforms:
 
  - Nvidia: Fuse support for Tegra194, continued memory controller pieces
    for Tegra30
 
  - NXP/FSL: Refactorings of QuickEngine drivers to support ARM/ARM64/PPC
 
  - NXP/FSL: i.MX8MP SoC driver pieces
 
  - TI Keystone: ring accelerator driver
 
  - Qualcomm: SCM driver cleanup/refactoring + support for new SoCs.
 
  - Xilinx ZynqMP: feature checking interface for firmware. Mailbox
    communication for power management
 
  - Overall support patch set for cpuidle on more complex hierarchies
    (PSCI-based)
 
 + Misc cleanups, refactorings of Marvell, TI, other platforms.
 -----BEGIN PGP SIGNATURE-----
 
 iQJDBAABCAAtFiEElf+HevZ4QCAJmMQ+jBrnPN6EHHcFAl4+lTYPHG9sb2ZAbGl4
 b20ubmV0AAoJEIwa5zzehBx3nQcQAJm91+6hZbmMjlBySGS7ISjYvOcrI/hMgiOl
 uhhEP0Dcylvf9A9x3wcIbLwixe+2pvie9DQh2u5F80ShYimidtFi/2xCfuTb9fKu
 sxxKjrXWyVKhkpW0z+tedY08ftVhkwwcyD4m2C7uVl6AwTP7c367vFeU7XjF2APn
 drfgmgbjm8U3XbSyAqv+k6z6tyqaCnFM7vbPupSKHgHJ3mfByxOa+XyBN2RdgBbs
 0KrVfbXGv80zFIFrMPwaWG7G52bu7K68nVdgy44MpKdRZ6QTjhnR+kerFxHsYgV4
 bM55Fya52nTCSTGdKaQakDtKwbAUdCDTSkxgOHGcQoyFi0R/VaEUJtcysnvLbI6c
 +n/yFIzGyEdXcvIzfv2SoDYhogw19I6RR/M9K5Ni29eazkDVYx2z3rI+2QYeqCiF
 u7cq52gW6JLP0SI/9kuUrRFiR8v19Ixap7qokAxgqQwYB3NzT8a7WsYPkzdpDZGQ
 ETSDFMyBWT6UvBe/HWkQluBabbet53rG8BF0OHFrQuMK0u/ieKgSGuTB9XN2djEW
 PHMOMz2vhi+8XTfpkskhF2tTxlA/k4R6QwCdIMpIkMRVnVQCh1XdPr3Fi2NrgB+S
 kIXHD4vV6zLYh04zHyKewSPHAXWgraFpg2qKnvL5+KWMTnW6QH+RNjOt9xKDNXOd
 +iDXpOad
 =ONtb
 -----END PGP SIGNATURE-----

Merge tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc

Pull ARM SoC-related driver updates from Olof Johansson:
 "Various driver updates for platforms:

   - Nvidia: Fuse support for Tegra194, continued memory controller
     pieces for Tegra30

   - NXP/FSL: Refactorings of QuickEngine drivers to support
     ARM/ARM64/PPC

   - NXP/FSL: i.MX8MP SoC driver pieces

   - TI Keystone: ring accelerator driver

   - Qualcomm: SCM driver cleanup/refactoring + support for new SoCs.

   - Xilinx ZynqMP: feature checking interface for firmware. Mailbox
     communication for power management

   - Overall support patch set for cpuidle on more complex hierarchies
     (PSCI-based)

  and misc cleanups, refactorings of Marvell, TI, other platforms"

* tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc: (166 commits)
  drivers: soc: xilinx: Use mailbox IPI callback
  dt-bindings: power: reset: xilinx: Add bindings for ipi mailbox
  drivers: soc: ti: knav_qmss_queue: Pass lockdep expression to RCU lists
  MAINTAINERS: Add brcmstb PCIe controller entry
  soc/tegra: fuse: Unmap registers once they are not needed anymore
  soc/tegra: fuse: Correct straps' address for older Tegra124 device trees
  soc/tegra: fuse: Warn if straps are not ready
  soc/tegra: fuse: Cache values of straps and Chip ID registers
  memory: tegra30-emc: Correct error message for timed out auto calibration
  memory: tegra30-emc: Firm up hardware programming sequence
  memory: tegra30-emc: Firm up suspend/resume sequence
  soc/tegra: regulators: Do nothing if voltage is unchanged
  memory: tegra: Correct reset value of xusb_hostr
  soc/tegra: fuse: Add APB DMA dependency for Tegra20
  bus: tegra-aconnect: Remove PM_CLK dependency
  dt-bindings: mediatek: add MT6765 power dt-bindings
  soc: mediatek: cmdq: delete not used define
  memory: tegra: Add support for the Tegra194 memory controller
  memory: tegra: Only include support for enabled SoCs
  memory: tegra: Support DVFS on Tegra186 and later
  ...
2020-02-08 14:04:19 -08:00

466 lines
10 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2013-2014, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/kobject.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/nvmem-consumer.h>
#include <linux/nvmem-provider.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 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_194_SOC
{ .compatible = "nvidia,tegra194-efuse", .data = &tegra194_fuse_soc },
#endif
#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_read(void *priv, unsigned int offset, void *value,
size_t bytes)
{
unsigned int count = bytes / 4, i;
struct tegra_fuse *fuse = priv;
u32 *buffer = value;
for (i = 0; i < count; i++)
buffer[i] = fuse->read(fuse, offset + i * 4);
return 0;
}
static const struct nvmem_cell_info tegra_fuse_cells[] = {
{
.name = "tsensor-cpu1",
.offset = 0x084,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "tsensor-cpu2",
.offset = 0x088,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "tsensor-cpu0",
.offset = 0x098,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "xusb-pad-calibration",
.offset = 0x0f0,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "tsensor-cpu3",
.offset = 0x12c,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "sata-calibration",
.offset = 0x124,
.bytes = 1,
.bit_offset = 0,
.nbits = 2,
}, {
.name = "tsensor-gpu",
.offset = 0x154,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "tsensor-mem0",
.offset = 0x158,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "tsensor-mem1",
.offset = 0x15c,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "tsensor-pllx",
.offset = 0x160,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "tsensor-common",
.offset = 0x180,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "tsensor-realignment",
.offset = 0x1fc,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "gpu-calibration",
.offset = 0x204,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "xusb-pad-calibration-ext",
.offset = 0x250,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
},
};
static int tegra_fuse_probe(struct platform_device *pdev)
{
void __iomem *base = fuse->base;
struct nvmem_config nvmem;
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)) {
if (PTR_ERR(fuse->clk) != -EPROBE_DEFER)
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)
goto restore;
}
memset(&nvmem, 0, sizeof(nvmem));
nvmem.dev = &pdev->dev;
nvmem.name = "fuse";
nvmem.id = -1;
nvmem.owner = THIS_MODULE;
nvmem.cells = tegra_fuse_cells;
nvmem.ncells = ARRAY_SIZE(tegra_fuse_cells);
nvmem.type = NVMEM_TYPE_OTP;
nvmem.read_only = true;
nvmem.root_only = true;
nvmem.reg_read = tegra_fuse_read;
nvmem.size = fuse->soc->info->size;
nvmem.word_size = 4;
nvmem.stride = 4;
nvmem.priv = fuse;
fuse->nvmem = devm_nvmem_register(&pdev->dev, &nvmem);
if (IS_ERR(fuse->nvmem)) {
err = PTR_ERR(fuse->nvmem);
dev_err(&pdev->dev, "failed to register NVMEM device: %d\n",
err);
goto restore;
}
/* release the early I/O memory mapping */
iounmap(base);
return 0;
restore:
fuse->base = base;
return err;
}
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);
bool __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 || !fuse->clk)
return -EPROBE_DEFER;
if (IS_ERR(fuse->clk))
return PTR_ERR(fuse->clk);
*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, &regs) < 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(regs.start, resource_size(&regs));
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);
if (fuse->soc->lookups) {
size_t size = sizeof(*fuse->lookups) * fuse->soc->num_lookups;
fuse->lookups = kmemdup(fuse->soc->lookups, size, GFP_KERNEL);
if (!fuse->lookups)
return -ENOMEM;
nvmem_add_cell_lookups(fuse->lookups, fuse->soc->num_lookups);
}
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