Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+197
View File
@@ -0,0 +1,197 @@
# SPDX-License-Identifier: GPL-2.0-only
if ARCH_TEGRA
# 32-bit ARM SoCs
if ARM
config ARCH_TEGRA_2x_SOC
bool "Enable support for Tegra20 family"
default ARCH_TEGRA
select ARCH_NEEDS_CPU_IDLE_COUPLED if SMP
select ARM_ERRATA_720789
select ARM_ERRATA_754327 if SMP
select ARM_ERRATA_764369 if SMP
select PINCTRL_TEGRA20
select PL310_ERRATA_727915 if CACHE_L2X0
select PL310_ERRATA_769419 if CACHE_L2X0
select SOC_TEGRA_FLOWCTRL
select SOC_TEGRA_PMC
select SOC_TEGRA20_VOLTAGE_COUPLER if REGULATOR
select TEGRA_TIMER
help
Support for NVIDIA Tegra AP20 and T20 processors, based on the
ARM CortexA9MP CPU and the ARM PL310 L2 cache controller
config ARCH_TEGRA_3x_SOC
bool "Enable support for Tegra30 family"
default ARCH_TEGRA
select ARM_ERRATA_754322
select ARM_ERRATA_764369 if SMP
select PINCTRL_TEGRA30
select PL310_ERRATA_769419 if CACHE_L2X0
select SOC_TEGRA_FLOWCTRL
select SOC_TEGRA_PMC
select SOC_TEGRA30_VOLTAGE_COUPLER if REGULATOR
select TEGRA_TIMER
help
Support for NVIDIA Tegra T30 processor family, based on the
ARM CortexA9MP CPU and the ARM PL310 L2 cache controller
config ARCH_TEGRA_114_SOC
bool "Enable support for Tegra114 family"
default ARCH_TEGRA
select ARM_ERRATA_798181 if SMP
select HAVE_ARM_ARCH_TIMER
select PINCTRL_TEGRA114
select SOC_TEGRA_FLOWCTRL
select SOC_TEGRA_PMC
select TEGRA_TIMER
help
Support for NVIDIA Tegra T114 processor family, based on the
ARM CortexA15MP CPU
config ARCH_TEGRA_124_SOC
bool "Enable support for Tegra124 family"
default ARCH_TEGRA
select HAVE_ARM_ARCH_TIMER
select PINCTRL_TEGRA124
select SOC_TEGRA_FLOWCTRL
select SOC_TEGRA_PMC
select TEGRA_TIMER
help
Support for NVIDIA Tegra T124 processor family, based on the
ARM CortexA15MP CPU
endif
# 64-bit ARM SoCs
if ARM64
config ARCH_TEGRA_132_SOC
bool "NVIDIA Tegra132 SoC"
default ARCH_TEGRA
select PINCTRL_TEGRA124
select SOC_TEGRA_FLOWCTRL
select SOC_TEGRA_PMC
help
Enable support for NVIDIA Tegra132 SoC, based on the Denver
ARMv8 CPU. The Tegra132 SoC is similar to the Tegra124 SoC,
but contains an NVIDIA Denver CPU complex in place of
Tegra124's "4+1" Cortex-A15 CPU complex.
config ARCH_TEGRA_210_SOC
bool "NVIDIA Tegra210 SoC"
default ARCH_TEGRA
select PINCTRL_TEGRA210
select SOC_TEGRA_FLOWCTRL
select SOC_TEGRA_PMC
select TEGRA_TIMER
help
Enable support for the NVIDIA Tegra210 SoC. Also known as Tegra X1,
the Tegra210 has four Cortex-A57 cores paired with four Cortex-A53
cores in a switched configuration. It features a GPU of the Maxwell
architecture with support for DX11, SM4, OpenGL 4.5, OpenGL ES 3.1
and providing 256 CUDA cores. It supports hardware-accelerated en-
and decoding of various video standards including H.265, H.264 and
VP8 at 4K resolution and up to 60 fps.
Besides the multimedia features it also comes with a variety of I/O
controllers, such as GPIO, I2C, SPI, SDHCI, PCIe, SATA and XHCI, to
name only a few.
config ARCH_TEGRA_186_SOC
bool "NVIDIA Tegra186 SoC"
default ARCH_TEGRA
depends on !CPU_BIG_ENDIAN
select PINCTRL_TEGRA186
select MAILBOX
select SOC_TEGRA_PMC
help
Enable support for the NVIDIA Tegar186 SoC. The Tegra186 features a
combination of Denver and Cortex-A57 CPU cores and a GPU based on
the Pascal architecture. It contains an ADSP with a Cortex-A9 CPU
used for audio processing, hardware video encoders/decoders with
multi-format support, ISP for image capture processing and BPMP for
power management.
config ARCH_TEGRA_194_SOC
bool "NVIDIA Tegra194 SoC"
default ARCH_TEGRA
depends on !CPU_BIG_ENDIAN
select MAILBOX
select PINCTRL_TEGRA194
select SOC_TEGRA_PMC
help
Enable support for the NVIDIA Tegra194 SoC.
config ARCH_TEGRA_234_SOC
bool "NVIDIA Tegra234 SoC"
default ARCH_TEGRA
depends on !CPU_BIG_ENDIAN
select MAILBOX
select PINCTRL_TEGRA234
select SOC_TEGRA_PMC
help
Enable support for the NVIDIA Tegra234 SoC.
config ARCH_TEGRA_238_SOC
bool "NVIDIA Tegra238 SoC"
default ARCH_TEGRA
depends on !CPU_BIG_ENDIAN
select MAILBOX
select SOC_TEGRA_PMC
help
Enable support for the NVIDIA Tegra238 SoC.
config ARCH_TEGRA_241_SOC
bool "NVIDIA Tegra241 SoC"
default ARCH_TEGRA
help
Enable support for the NVIDIA Tegra241 SoC.
config ARCH_TEGRA_264_SOC
bool "NVIDIA Tegra264 SoC"
default ARCH_TEGRA
depends on !CPU_BIG_ENDIAN
select MAILBOX
select SOC_TEGRA_PMC
help
Enable support for the NVIDIA Tegra264 SoC.
endif
endif
config SOC_TEGRA_FUSE
def_bool y
depends on ARCH_TEGRA
select SOC_BUS
config SOC_TEGRA_FLOWCTRL
bool
config SOC_TEGRA_PMC
bool
select GENERIC_PINCONF
select IRQ_DOMAIN_HIERARCHY
select PM_OPP
select PM_GENERIC_DOMAINS
select REGMAP
config SOC_TEGRA20_VOLTAGE_COUPLER
bool "Voltage scaling support for Tegra20 SoCs"
depends on ARCH_TEGRA_2x_SOC || COMPILE_TEST
depends on REGULATOR
config SOC_TEGRA30_VOLTAGE_COUPLER
bool "Voltage scaling support for Tegra30 SoCs"
depends on ARCH_TEGRA_3x_SOC || COMPILE_TEST
depends on REGULATOR
config SOC_TEGRA_CBB
tristate "Tegra driver to handle error from CBB"
depends on ARCH_TEGRA_194_SOC || ARCH_TEGRA_234_SOC
default y
help
Support for handling error from Tegra Control Backbone(CBB).
This driver handles the errors from CBB and prints debug
information about the failed transactions.
+10
View File
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0
obj-y += fuse/
obj-y += cbb/
obj-y += common.o
obj-$(CONFIG_SOC_TEGRA_FLOWCTRL) += flowctrl.o
obj-$(CONFIG_SOC_TEGRA_PMC) += pmc.o
obj-$(CONFIG_SOC_TEGRA20_VOLTAGE_COUPLER) += regulators-tegra20.o
obj-$(CONFIG_SOC_TEGRA30_VOLTAGE_COUPLER) += regulators-tegra30.o
obj-$(CONFIG_ARCH_TEGRA_186_SOC) += ari-tegra186.o
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/arm-smccc.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/panic_notifier.h>
#define SMC_SIP_INVOKE_MCE 0xc2ffff00
#define MCE_SMC_READ_MCA 12
#define MCA_ARI_CMD_RD_SERR 1
#define MCA_ARI_RW_SUBIDX_STAT 1
#define SERR_STATUS_VAL BIT_ULL(63)
#define MCA_ARI_RW_SUBIDX_ADDR 2
#define MCA_ARI_RW_SUBIDX_MSC1 3
#define MCA_ARI_RW_SUBIDX_MSC2 4
static const char * const bank_names[] = {
"SYS:DPMU", "ROC:IOB", "ROC:MCB", "ROC:CCE", "ROC:CQX", "ROC:CTU",
};
static void read_uncore_mca(u8 cmd, u8 idx, u8 subidx, u8 inst, u64 *data)
{
struct arm_smccc_res res;
arm_smccc_smc(SMC_SIP_INVOKE_MCE | MCE_SMC_READ_MCA,
((u64)inst << 24) | ((u64)idx << 16) |
((u64)subidx << 8) | ((u64)cmd << 0),
0, 0, 0, 0, 0, 0, &res);
*data = res.a2;
}
static int tegra186_ari_panic_handler(struct notifier_block *nb,
unsigned long code, void *unused)
{
u64 status;
int i;
for (i = 0; i < ARRAY_SIZE(bank_names); i++) {
read_uncore_mca(MCA_ARI_CMD_RD_SERR, i, MCA_ARI_RW_SUBIDX_STAT,
0, &status);
if (status & SERR_STATUS_VAL) {
u64 addr, misc1, misc2;
read_uncore_mca(MCA_ARI_CMD_RD_SERR, i,
MCA_ARI_RW_SUBIDX_ADDR, 0, &addr);
read_uncore_mca(MCA_ARI_CMD_RD_SERR, i,
MCA_ARI_RW_SUBIDX_MSC1, 0, &misc1);
read_uncore_mca(MCA_ARI_CMD_RD_SERR, i,
MCA_ARI_RW_SUBIDX_MSC2, 0, &misc2);
pr_crit("Machine Check Error in %s\n"
" status=0x%llx addr=0x%llx\n"
" msc1=0x%llx msc2=0x%llx\n",
bank_names[i], status, addr, misc1, misc2);
}
}
return NOTIFY_DONE;
}
static struct notifier_block tegra186_ari_panic_nb = {
.notifier_call = tegra186_ari_panic_handler,
};
static int __init tegra186_ari_init(void)
{
if (of_machine_is_compatible("nvidia,tegra186"))
atomic_notifier_chain_register(&panic_notifier_list, &tegra186_ari_panic_nb);
return 0;
}
early_initcall(tegra186_ari_init);
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0
#
# Control Backbone Driver code.
#
ifdef CONFIG_SOC_TEGRA_CBB
obj-y += tegra-cbb.o
obj-$(CONFIG_ARCH_TEGRA_194_SOC) += tegra194-cbb.o
obj-$(CONFIG_ARCH_TEGRA_234_SOC) += tegra234-cbb.o
endif
@@ -0,0 +1,158 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2021-2022, NVIDIA CORPORATION. All rights reserved
*/
#include <linux/clk.h>
#include <linux/cpufeature.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <soc/tegra/fuse.h>
#include <soc/tegra/tegra-cbb.h>
void tegra_cbb_print_err(struct seq_file *file, const char *fmt, ...)
{
struct va_format vaf;
va_list args;
va_start(args, fmt);
if (file) {
seq_vprintf(file, fmt, args);
} else {
vaf.fmt = fmt;
vaf.va = &args;
pr_crit("%pV", &vaf);
}
va_end(args);
}
void tegra_cbb_print_cache(struct seq_file *file, u32 cache)
{
const char *buff_str, *mod_str, *rd_str, *wr_str;
buff_str = (cache & BIT(0)) ? "Bufferable " : "";
mod_str = (cache & BIT(1)) ? "Modifiable " : "";
rd_str = (cache & BIT(2)) ? "Read-Allocate " : "";
wr_str = (cache & BIT(3)) ? "Write-Allocate" : "";
if (cache == 0x0)
buff_str = "Device Non-Bufferable";
tegra_cbb_print_err(file, "\t Cache\t\t\t: 0x%x -- %s%s%s%s\n",
cache, buff_str, mod_str, rd_str, wr_str);
}
void tegra_cbb_print_prot(struct seq_file *file, u32 prot)
{
const char *data_str, *secure_str, *priv_str;
data_str = (prot & 0x4) ? "Instruction" : "Data";
secure_str = (prot & 0x2) ? "Non-Secure" : "Secure";
priv_str = (prot & 0x1) ? "Privileged" : "Unprivileged";
tegra_cbb_print_err(file, "\t Protection\t\t: 0x%x -- %s, %s, %s Access\n",
prot, priv_str, secure_str, data_str);
}
static int tegra_cbb_err_show(struct seq_file *file, void *data)
{
struct tegra_cbb *cbb = file->private;
return cbb->ops->debugfs_show(cbb, file, data);
}
DEFINE_SHOW_ATTRIBUTE(tegra_cbb_err);
static void tegra_cbb_err_debugfs_init(struct tegra_cbb *cbb)
{
static struct dentry *root;
if (!root)
root = debugfs_create_file("tegra_cbb_err", 0444, NULL, cbb, &tegra_cbb_err_fops);
}
void tegra_cbb_stall_enable(struct tegra_cbb *cbb)
{
if (cbb->ops->stall_enable)
cbb->ops->stall_enable(cbb);
}
void tegra_cbb_fault_enable(struct tegra_cbb *cbb)
{
if (cbb->ops->fault_enable)
cbb->ops->fault_enable(cbb);
}
void tegra_cbb_error_clear(struct tegra_cbb *cbb)
{
if (cbb->ops->error_clear)
cbb->ops->error_clear(cbb);
}
u32 tegra_cbb_get_status(struct tegra_cbb *cbb)
{
if (cbb->ops->get_status)
return cbb->ops->get_status(cbb);
return 0;
}
int tegra_cbb_get_irq(struct platform_device *pdev, unsigned int *nonsec_irq,
unsigned int *sec_irq)
{
unsigned int index = 0;
int num_intr = 0, irq;
num_intr = platform_irq_count(pdev);
if (!num_intr)
return -EINVAL;
if (num_intr == 2) {
irq = platform_get_irq(pdev, index);
if (irq <= 0)
return -ENOENT;
*nonsec_irq = irq;
index++;
}
irq = platform_get_irq(pdev, index);
if (irq <= 0)
return -ENOENT;
*sec_irq = irq;
if (num_intr == 1)
dev_dbg(&pdev->dev, "secure IRQ: %u\n", *sec_irq);
if (num_intr == 2)
dev_dbg(&pdev->dev, "secure IRQ: %u, non-secure IRQ: %u\n", *sec_irq, *nonsec_irq);
return 0;
}
int tegra_cbb_register(struct tegra_cbb *cbb)
{
int ret;
if (IS_ENABLED(CONFIG_DEBUG_FS))
tegra_cbb_err_debugfs_init(cbb);
/* register interrupt handler for errors due to different initiators */
ret = cbb->ops->interrupt_enable(cbb);
if (ret < 0) {
dev_err(cbb->dev, "Failed to register CBB Interrupt ISR");
return ret;
}
cbb->ops->error_enable(cbb);
dsb(sy);
return 0;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+161
View File
@@ -0,0 +1,161 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2014 NVIDIA CORPORATION. All rights reserved.
*/
#define dev_fmt(fmt) "tegra-soc: " fmt
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/export.h>
#include <linux/of.h>
#include <linux/pm_opp.h>
#include <linux/pm_runtime.h>
#include <soc/tegra/common.h>
#include <soc/tegra/fuse.h>
static const struct of_device_id tegra_machine_match[] = {
{ .compatible = "nvidia,tegra20", },
{ .compatible = "nvidia,tegra30", },
{ .compatible = "nvidia,tegra114", },
{ .compatible = "nvidia,tegra124", },
{ .compatible = "nvidia,tegra132", },
{ .compatible = "nvidia,tegra210", },
{ }
};
bool soc_is_tegra(void)
{
return of_machine_device_match(tegra_machine_match);
}
static int tegra_core_dev_init_opp_state(struct device *dev)
{
unsigned long rate;
struct clk *clk;
bool rpm_enabled;
int err;
clk = devm_clk_get(dev, NULL);
if (IS_ERR(clk)) {
dev_err(dev, "failed to get clk: %pe\n", clk);
return PTR_ERR(clk);
}
rate = clk_get_rate(clk);
if (!rate) {
dev_err(dev, "failed to get clk rate\n");
return -EINVAL;
}
/*
* Runtime PM of the device must be enabled in order to set up
* GENPD's performance properly because GENPD core checks whether
* device is suspended and this check doesn't work while RPM is
* disabled. This makes sure the OPP vote below gets cached in
* GENPD for the device. Instead, the vote is done the next time
* the device gets runtime resumed.
*/
rpm_enabled = pm_runtime_enabled(dev);
if (!rpm_enabled)
pm_runtime_enable(dev);
/* should never happen in practice */
if (!pm_runtime_enabled(dev)) {
dev_WARN(dev, "failed to enable runtime PM\n");
pm_runtime_disable(dev);
return -EINVAL;
}
/* first dummy rate-setting initializes voltage vote */
err = dev_pm_opp_set_rate(dev, rate);
if (!rpm_enabled)
pm_runtime_disable(dev);
if (err) {
dev_err(dev, "failed to initialize OPP clock: %d\n", err);
return err;
}
return 0;
}
/**
* devm_tegra_core_dev_init_opp_table() - initialize OPP table
* @dev: device for which OPP table is initialized
* @params: pointer to the OPP table configuration
*
* This function will initialize OPP table and sync OPP state of a Tegra SoC
* core device.
*
* Return: 0 on success or errorno.
*/
int devm_tegra_core_dev_init_opp_table(struct device *dev,
struct tegra_core_opp_params *params)
{
u32 hw_version;
int err;
/*
* The clk's connection id to set is NULL and this is a NULL terminated
* array, hence two NULL entries.
*/
const char *clk_names[] = { NULL, NULL };
struct dev_pm_opp_config config = {
/*
* For some devices we don't have any OPP table in the DT, and
* in order to use the same code path for all the devices, we
* create a dummy OPP table for them via this. The dummy OPP
* table is only capable of doing clk_set_rate() on invocation
* of dev_pm_opp_set_rate() and doesn't provide any other
* functionality.
*/
.clk_names = clk_names,
};
if (of_machine_is_compatible("nvidia,tegra20")) {
hw_version = BIT(tegra_sku_info.soc_process_id);
config.supported_hw = &hw_version;
config.supported_hw_count = 1;
} else if (of_machine_is_compatible("nvidia,tegra30") ||
of_machine_is_compatible("nvidia,tegra114")) {
hw_version = BIT(tegra_sku_info.soc_speedo_id);
config.supported_hw = &hw_version;
config.supported_hw_count = 1;
}
err = devm_pm_opp_set_config(dev, &config);
if (err) {
dev_err(dev, "failed to set OPP config: %d\n", err);
return err;
}
/*
* Tegra124+ doesn't support OPP yet, return early for pre-Tegra124
* case.
*/
if (!config.supported_hw)
return -ENODEV;
/*
* Older device-trees have an empty OPP table, we will get
* -ENODEV from devm_pm_opp_of_add_table() in this case.
*/
err = devm_pm_opp_of_add_table(dev);
if (err) {
if (err != -ENODEV)
dev_err(dev, "failed to add OPP table: %d\n", err);
return err;
}
if (params->init_state) {
err = tegra_core_dev_init_opp_state(dev);
if (err)
return err;
}
return 0;
}
EXPORT_SYMBOL_GPL(devm_tegra_core_dev_init_opp_table);
+226
View File
@@ -0,0 +1,226 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* drivers/soc/tegra/flowctrl.c
*
* Functions and macros to control the flowcontroller
*
* Copyright (c) 2010-2012, NVIDIA Corporation. All rights reserved.
*/
#include <linux/cpumask.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <soc/tegra/common.h>
#include <soc/tegra/flowctrl.h>
#include <soc/tegra/fuse.h>
static u8 flowctrl_offset_halt_cpu[] = {
FLOW_CTRL_HALT_CPU0_EVENTS,
FLOW_CTRL_HALT_CPU1_EVENTS,
FLOW_CTRL_HALT_CPU1_EVENTS + 8,
FLOW_CTRL_HALT_CPU1_EVENTS + 16,
};
static u8 flowctrl_offset_cpu_csr[] = {
FLOW_CTRL_CPU0_CSR,
FLOW_CTRL_CPU1_CSR,
FLOW_CTRL_CPU1_CSR + 8,
FLOW_CTRL_CPU1_CSR + 16,
};
static void __iomem *tegra_flowctrl_base;
static void flowctrl_update(u8 offset, u32 value)
{
if (WARN_ONCE(IS_ERR_OR_NULL(tegra_flowctrl_base),
"Tegra flowctrl not initialised!\n"))
return;
writel(value, tegra_flowctrl_base + offset);
/* ensure the update has reached the flow controller */
wmb();
readl_relaxed(tegra_flowctrl_base + offset);
}
u32 flowctrl_read_cpu_csr(unsigned int cpuid)
{
u8 offset = flowctrl_offset_cpu_csr[cpuid];
if (WARN_ONCE(IS_ERR_OR_NULL(tegra_flowctrl_base),
"Tegra flowctrl not initialised!\n"))
return 0;
return readl(tegra_flowctrl_base + offset);
}
void flowctrl_write_cpu_csr(unsigned int cpuid, u32 value)
{
return flowctrl_update(flowctrl_offset_cpu_csr[cpuid], value);
}
void flowctrl_write_cpu_halt(unsigned int cpuid, u32 value)
{
return flowctrl_update(flowctrl_offset_halt_cpu[cpuid], value);
}
void flowctrl_cpu_suspend_enter(unsigned int cpuid)
{
unsigned int reg;
int i;
reg = flowctrl_read_cpu_csr(cpuid);
switch (tegra_get_chip_id()) {
case TEGRA20:
/* clear wfe bitmap */
reg &= ~TEGRA20_FLOW_CTRL_CSR_WFE_BITMAP;
/* clear wfi bitmap */
reg &= ~TEGRA20_FLOW_CTRL_CSR_WFI_BITMAP;
/* pwr gating on wfe */
reg |= TEGRA20_FLOW_CTRL_CSR_WFE_CPU0 << cpuid;
break;
case TEGRA30:
case TEGRA114:
case TEGRA124:
/* clear wfe bitmap */
reg &= ~TEGRA30_FLOW_CTRL_CSR_WFE_BITMAP;
/* clear wfi bitmap */
reg &= ~TEGRA30_FLOW_CTRL_CSR_WFI_BITMAP;
if (tegra_get_chip_id() == TEGRA30) {
/*
* The wfi doesn't work well on Tegra30 because
* CPU hangs under some odd circumstances after
* power-gating (like memory running off PLLP),
* hence use wfe that is working perfectly fine.
* Note that Tegra30 TRM doc clearly stands that
* wfi should be used for the "Cluster Switching",
* while wfe for the power-gating, just like it
* is done on Tegra20.
*/
reg |= TEGRA20_FLOW_CTRL_CSR_WFE_CPU0 << cpuid;
} else {
/* pwr gating on wfi */
reg |= TEGRA30_FLOW_CTRL_CSR_WFI_CPU0 << cpuid;
}
break;
}
reg |= FLOW_CTRL_CSR_INTR_FLAG; /* clear intr flag */
reg |= FLOW_CTRL_CSR_EVENT_FLAG; /* clear event flag */
reg |= FLOW_CTRL_CSR_ENABLE; /* pwr gating */
flowctrl_write_cpu_csr(cpuid, reg);
for (i = 0; i < num_possible_cpus(); i++) {
if (i == cpuid)
continue;
reg = flowctrl_read_cpu_csr(i);
reg |= FLOW_CTRL_CSR_EVENT_FLAG;
reg |= FLOW_CTRL_CSR_INTR_FLAG;
flowctrl_write_cpu_csr(i, reg);
}
}
void flowctrl_cpu_suspend_exit(unsigned int cpuid)
{
unsigned int reg;
/* Disable powergating via flow controller for CPU0 */
reg = flowctrl_read_cpu_csr(cpuid);
switch (tegra_get_chip_id()) {
case TEGRA20:
/* clear wfe bitmap */
reg &= ~TEGRA20_FLOW_CTRL_CSR_WFE_BITMAP;
/* clear wfi bitmap */
reg &= ~TEGRA20_FLOW_CTRL_CSR_WFI_BITMAP;
break;
case TEGRA30:
case TEGRA114:
case TEGRA124:
/* clear wfe bitmap */
reg &= ~TEGRA30_FLOW_CTRL_CSR_WFE_BITMAP;
/* clear wfi bitmap */
reg &= ~TEGRA30_FLOW_CTRL_CSR_WFI_BITMAP;
break;
}
reg &= ~FLOW_CTRL_CSR_ENABLE; /* clear enable */
reg |= FLOW_CTRL_CSR_INTR_FLAG; /* clear intr */
reg |= FLOW_CTRL_CSR_EVENT_FLAG; /* clear event */
flowctrl_write_cpu_csr(cpuid, reg);
}
static int tegra_flowctrl_probe(struct platform_device *pdev)
{
void __iomem *base = tegra_flowctrl_base;
tegra_flowctrl_base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
if (IS_ERR(tegra_flowctrl_base))
return PTR_ERR(tegra_flowctrl_base);
iounmap(base);
return 0;
}
static const struct of_device_id tegra_flowctrl_match[] = {
{ .compatible = "nvidia,tegra210-flowctrl" },
{ .compatible = "nvidia,tegra124-flowctrl" },
{ .compatible = "nvidia,tegra114-flowctrl" },
{ .compatible = "nvidia,tegra30-flowctrl" },
{ .compatible = "nvidia,tegra20-flowctrl" },
{ }
};
static struct platform_driver tegra_flowctrl_driver = {
.driver = {
.name = "tegra-flowctrl",
.suppress_bind_attrs = true,
.of_match_table = tegra_flowctrl_match,
},
.probe = tegra_flowctrl_probe,
};
builtin_platform_driver(tegra_flowctrl_driver);
static int __init tegra_flowctrl_init(void)
{
struct resource res;
struct device_node *np;
if (!soc_is_tegra())
return 0;
np = of_find_matching_node(NULL, tegra_flowctrl_match);
if (np) {
if (of_address_to_resource(np, 0, &res) < 0) {
pr_err("failed to get flowctrl register\n");
return -ENXIO;
}
of_node_put(np);
} else if (IS_ENABLED(CONFIG_ARM)) {
/*
* Hardcoded fallback for 32-bit Tegra
* devices if device tree node is missing.
*/
res.start = 0x60007000;
res.end = 0x60007fff;
res.flags = IORESOURCE_MEM;
} else {
/*
* At this point we're running on a Tegra,
* that doesn't support the flow controller
* (eg. Tegra186), so just return.
*/
return 0;
}
tegra_flowctrl_base = ioremap(res.start, resource_size(&res));
if (!tegra_flowctrl_base)
return -ENXIO;
return 0;
}
early_initcall(tegra_flowctrl_init);
@@ -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
@@ -0,0 +1,594 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2013-2023, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/acpi.h>
#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/pm_runtime.h>
#include <linux/reset.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 char *tegra_platform_name[TEGRA_PLATFORM_MAX] = {
[TEGRA_PLATFORM_SILICON] = "Silicon",
[TEGRA_PLATFORM_QT] = "QT",
[TEGRA_PLATFORM_SYSTEM_FPGA] = "System FPGA",
[TEGRA_PLATFORM_UNIT_FPGA] = "Unit FPGA",
[TEGRA_PLATFORM_ASIM_QT] = "Asim QT",
[TEGRA_PLATFORM_ASIM_LINSIM] = "Asim Linsim",
[TEGRA_PLATFORM_DSIM_ASIM_LINSIM] = "Dsim Asim Linsim",
[TEGRA_PLATFORM_VERIFICATION_SIMULATION] = "Verification Simulation",
[TEGRA_PLATFORM_VDK] = "VDK",
[TEGRA_PLATFORM_VSP] = "VSP",
};
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_234_SOC
{ .compatible = "nvidia,tegra234-efuse", .data = &tegra234_fuse_soc },
#endif
#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 void tegra_fuse_restore(void *base)
{
fuse->base = (void __iomem *)base;
fuse->clk = NULL;
}
static void tegra_fuse_print_sku_info(struct tegra_sku_info *tegra_sku_info)
{
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);
}
static int tegra_fuse_add_lookups(struct tegra_fuse *fuse)
{
fuse->lookups = kmemdup_array(fuse->soc->lookups, fuse->soc->num_lookups,
sizeof(*fuse->lookups), GFP_KERNEL);
if (!fuse->lookups)
return -ENOMEM;
nvmem_add_cell_lookups(fuse->lookups, fuse->soc->num_lookups);
return 0;
}
static int tegra_fuse_probe(struct platform_device *pdev)
{
void __iomem *base = fuse->base;
struct nvmem_config nvmem;
struct resource *res;
int err;
err = devm_add_action(&pdev->dev, tegra_fuse_restore, (void __force *)base);
if (err)
return err;
/* take over the memory region from the early initialization */
fuse->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(fuse->base))
return PTR_ERR(fuse->base);
fuse->phys = res->start;
/* Initialize the soc data and lookups if using ACPI boot. */
if (is_acpi_node(dev_fwnode(&pdev->dev)) && !fuse->soc) {
u8 chip;
tegra_acpi_init_apbmisc();
chip = tegra_get_chip_id();
switch (chip) {
#if defined(CONFIG_ARCH_TEGRA_194_SOC)
case TEGRA194:
fuse->soc = &tegra194_fuse_soc;
break;
#endif
#if defined(CONFIG_ARCH_TEGRA_234_SOC)
case TEGRA234:
fuse->soc = &tegra234_fuse_soc;
break;
#endif
#if defined(CONFIG_ARCH_TEGRA_241_SOC)
case TEGRA241:
fuse->soc = &tegra241_fuse_soc;
break;
#endif
default:
return dev_err_probe(&pdev->dev, -EINVAL, "Unsupported SoC: %02x\n", chip);
}
fuse->soc->init(fuse);
}
fuse->clk = devm_clk_get_optional(&pdev->dev, "fuse");
if (IS_ERR(fuse->clk))
return dev_err_probe(&pdev->dev, PTR_ERR(fuse->clk), "failed to get FUSE clock\n");
platform_set_drvdata(pdev, fuse);
fuse->dev = &pdev->dev;
err = devm_pm_runtime_enable(&pdev->dev);
if (err)
return err;
if (fuse->soc->probe) {
err = fuse->soc->probe(fuse);
if (err < 0)
return err;
}
memset(&nvmem, 0, sizeof(nvmem));
nvmem.dev = &pdev->dev;
nvmem.name = "fuse";
nvmem.id = -1;
nvmem.owner = THIS_MODULE;
nvmem.cells = fuse->soc->cells;
nvmem.ncells = fuse->soc->num_cells;
nvmem.keepout = fuse->soc->keepouts;
nvmem.nkeepout = fuse->soc->num_keepouts;
nvmem.type = NVMEM_TYPE_OTP;
nvmem.read_only = true;
nvmem.root_only = false;
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);
return err;
}
err = tegra_fuse_add_lookups(fuse);
if (err)
return dev_err_probe(&pdev->dev, err, "failed to add FUSE lookups\n");
fuse->rst = devm_reset_control_get_optional(&pdev->dev, "fuse");
if (IS_ERR(fuse->rst))
return dev_err_probe(&pdev->dev, PTR_ERR(fuse->rst), "failed to get FUSE reset\n");
/*
* FUSE clock is enabled at a boot time, hence this resume/suspend
* disables the clock besides the h/w resetting.
*/
err = pm_runtime_resume_and_get(&pdev->dev);
if (err)
return err;
err = reset_control_reset(fuse->rst);
pm_runtime_put(&pdev->dev);
if (err < 0) {
dev_err(&pdev->dev, "failed to reset FUSE: %d\n", err);
return err;
}
/* release the early I/O memory mapping */
iounmap(base);
return 0;
}
static int __maybe_unused tegra_fuse_runtime_resume(struct device *dev)
{
int err;
err = clk_prepare_enable(fuse->clk);
if (err < 0) {
dev_err(dev, "failed to enable FUSE clock: %d\n", err);
return err;
}
return 0;
}
static int __maybe_unused tegra_fuse_runtime_suspend(struct device *dev)
{
clk_disable_unprepare(fuse->clk);
return 0;
}
static int __maybe_unused tegra_fuse_suspend(struct device *dev)
{
int ret;
/*
* Critical for RAM re-repair operation, which must occur on resume
* from LP1 system suspend and as part of CCPLEX cluster switching.
*/
if (fuse->soc->clk_suspend_on)
ret = pm_runtime_resume_and_get(dev);
else
ret = pm_runtime_force_suspend(dev);
return ret;
}
static int __maybe_unused tegra_fuse_resume(struct device *dev)
{
int ret = 0;
if (fuse->soc->clk_suspend_on)
pm_runtime_put(dev);
else
ret = pm_runtime_force_resume(dev);
return ret;
}
static const struct dev_pm_ops tegra_fuse_pm = {
SET_RUNTIME_PM_OPS(tegra_fuse_runtime_suspend, tegra_fuse_runtime_resume,
NULL)
SET_SYSTEM_SLEEP_PM_OPS(tegra_fuse_suspend, tegra_fuse_resume)
};
static const struct acpi_device_id tegra_fuse_acpi_match[] = {
{ "NVDA200F" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(acpi, tegra_fuse_acpi_match);
static struct platform_driver tegra_fuse_driver = {
.driver = {
.name = "tegra-fuse",
.of_match_table = tegra_fuse_match,
.acpi_match_table = tegra_fuse_acpi_match,
.pm = &tegra_fuse_pm,
.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->dev)
return -EPROBE_DEFER;
/*
* Wait for fuse->clk to be initialized if device-tree boot is used.
*/
if (is_of_node(dev_fwnode(fuse->dev)) && !fuse->clk)
return -EPROBE_DEFER;
if (!fuse->read)
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);
}
static ssize_t major_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d\n", tegra_get_major_rev());
}
static DEVICE_ATTR_RO(major);
static ssize_t minor_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d\n", tegra_get_minor_rev());
}
static DEVICE_ATTR_RO(minor);
static struct attribute *tegra_soc_attr[] = {
&dev_attr_major.attr,
&dev_attr_minor.attr,
NULL,
};
const struct attribute_group tegra_soc_attr_group = {
.attrs = tegra_soc_attr,
};
#if IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC) || \
IS_ENABLED(CONFIG_ARCH_TEGRA_234_SOC) || \
IS_ENABLED(CONFIG_ARCH_TEGRA_241_SOC)
static ssize_t platform_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
/*
* Displays the value in the 'pre_si_platform' field of the HIDREV
* register for Tegra194 devices. A value of 0 indicates that the
* platform type is silicon and all other non-zero values indicate
* the type of simulation platform is being used.
*/
return sprintf(buf, "%d\n", tegra_get_platform());
}
static DEVICE_ATTR_RO(platform);
static struct attribute *tegra194_soc_attr[] = {
&dev_attr_major.attr,
&dev_attr_minor.attr,
&dev_attr_platform.attr,
NULL,
};
const struct attribute_group tegra194_soc_attr_group = {
.attrs = tegra194_soc_attr,
};
#endif
struct device *tegra_soc_device_register(void)
{
struct soc_device_attribute *attr;
struct soc_device *dev;
attr = kzalloc_obj(*attr);
if (!attr)
return NULL;
attr->family = kasprintf(GFP_KERNEL, "Tegra");
if (tegra_is_silicon())
attr->revision = kasprintf(GFP_KERNEL, "%s %s",
tegra_platform_name[tegra_sku_info.platform],
tegra_revision_name[tegra_sku_info.revision]);
else
attr->revision = kasprintf(GFP_KERNEL, "%s",
tegra_platform_name[tegra_sku_info.platform]);
attr->soc_id = kasprintf(GFP_KERNEL, "%u", tegra_get_chip_id());
attr->custom_attr_group = fuse->soc->soc_attr_group;
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;
int err = 0;
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);
of_node_put(np);
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);
tegra_fuse_print_sku_info(&tegra_sku_info);
return err;
}
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
@@ -0,0 +1,198 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2013-2014, NVIDIA CORPORATION. All rights reserved.
*
* 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.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.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;
err = pm_runtime_resume_and_get(fuse->dev);
if (err)
return 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);
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;
out:
mutex_unlock(&fuse->apbdma.lock);
pm_runtime_put(fuse->dev);
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 void tegra20_fuse_release_channel(void *data)
{
struct tegra_fuse *fuse = data;
dma_release_channel(fuse->apbdma.chan);
fuse->apbdma.chan = NULL;
}
static void tegra20_fuse_free_coherent(void *data)
{
struct tegra_fuse *fuse = data;
dma_free_coherent(fuse->dev, sizeof(u32), fuse->apbdma.virt,
fuse->apbdma.phys);
fuse->apbdma.virt = NULL;
fuse->apbdma.phys = 0x0;
}
static int tegra20_fuse_probe(struct tegra_fuse *fuse)
{
dma_cap_mask_t mask;
int err;
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;
err = devm_add_action_or_reset(fuse->dev, tegra20_fuse_release_channel,
fuse);
if (err)
return err;
fuse->apbdma.virt = dma_alloc_coherent(fuse->dev, sizeof(u32),
&fuse->apbdma.phys,
GFP_KERNEL);
if (!fuse->apbdma.virt)
return -ENOMEM;
err = devm_add_action_or_reset(fuse->dev, tegra20_fuse_free_coherent,
fuse);
if (err)
return err;
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,
.soc_attr_group = &tegra_soc_attr_group,
.clk_suspend_on = false,
};
@@ -0,0 +1,828 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2013-2022, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/device.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/nvmem-consumer.h>
#include <linux/nvmem-provider.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.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) || \
defined(CONFIG_ARCH_TEGRA_241_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 = pm_runtime_resume_and_get(fuse->dev);
if (err)
return 0;
value = readl_relaxed(fuse->base + FUSE_BEGIN + offset);
pm_runtime_put(fuse->dev);
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,
.soc_attr_group = &tegra_soc_attr_group,
.clk_suspend_on = false,
};
#endif
#ifdef CONFIG_ARCH_TEGRA_114_SOC
static const struct nvmem_cell_info tegra114_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-common",
.offset = 0x08c,
.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 = "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,
},
};
static const struct nvmem_cell_lookup tegra114_fuse_lookups[] = {
{
.nvmem_name = "fuse",
.cell_name = "xusb-pad-calibration",
.dev_id = "7009f000.padctl",
.con_id = "calibration",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-common",
.dev_id = "700e2000.thermal-sensor",
.con_id = "common",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-cpu0",
.dev_id = "700e2000.thermal-sensor",
.con_id = "cpu0",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-cpu1",
.dev_id = "700e2000.thermal-sensor",
.con_id = "cpu1",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-cpu2",
.dev_id = "700e2000.thermal-sensor",
.con_id = "cpu2",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-cpu3",
.dev_id = "700e2000.thermal-sensor",
.con_id = "cpu3",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-mem0",
.dev_id = "700e2000.thermal-sensor",
.con_id = "mem0",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-mem1",
.dev_id = "700e2000.thermal-sensor",
.con_id = "mem1",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-gpu",
.dev_id = "700e2000.thermal-sensor",
.con_id = "gpu",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-pllx",
.dev_id = "700e2000.thermal-sensor",
.con_id = "pllx",
},
};
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,
.lookups = tegra114_fuse_lookups,
.num_lookups = ARRAY_SIZE(tegra114_fuse_lookups),
.cells = tegra114_fuse_cells,
.num_cells = ARRAY_SIZE(tegra114_fuse_cells),
.soc_attr_group = &tegra_soc_attr_group,
.clk_suspend_on = false,
};
#endif
#if defined(CONFIG_ARCH_TEGRA_124_SOC) || defined(CONFIG_ARCH_TEGRA_132_SOC)
static const struct nvmem_cell_info tegra124_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 = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.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,
},
};
static const struct nvmem_cell_lookup tegra124_fuse_lookups[] = {
{
.nvmem_name = "fuse",
.cell_name = "xusb-pad-calibration",
.dev_id = "7009f000.padctl",
.con_id = "calibration",
}, {
.nvmem_name = "fuse",
.cell_name = "sata-calibration",
.dev_id = "70020000.sata",
.con_id = "calibration",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-common",
.dev_id = "700e2000.thermal-sensor",
.con_id = "common",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-realignment",
.dev_id = "700e2000.thermal-sensor",
.con_id = "realignment",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-cpu0",
.dev_id = "700e2000.thermal-sensor",
.con_id = "cpu0",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-cpu1",
.dev_id = "700e2000.thermal-sensor",
.con_id = "cpu1",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-cpu2",
.dev_id = "700e2000.thermal-sensor",
.con_id = "cpu2",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-cpu3",
.dev_id = "700e2000.thermal-sensor",
.con_id = "cpu3",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-mem0",
.dev_id = "700e2000.thermal-sensor",
.con_id = "mem0",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-mem1",
.dev_id = "700e2000.thermal-sensor",
.con_id = "mem1",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-gpu",
.dev_id = "700e2000.thermal-sensor",
.con_id = "gpu",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-pllx",
.dev_id = "700e2000.thermal-sensor",
.con_id = "pllx",
},
};
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,
.lookups = tegra124_fuse_lookups,
.num_lookups = ARRAY_SIZE(tegra124_fuse_lookups),
.cells = tegra124_fuse_cells,
.num_cells = ARRAY_SIZE(tegra124_fuse_cells),
.soc_attr_group = &tegra_soc_attr_group,
.clk_suspend_on = true,
};
#endif
#if defined(CONFIG_ARCH_TEGRA_210_SOC)
static const struct nvmem_cell_info tegra210_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 = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.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 = "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 const struct nvmem_cell_lookup tegra210_fuse_lookups[] = {
{
.nvmem_name = "fuse",
.cell_name = "tsensor-cpu1",
.dev_id = "700e2000.thermal-sensor",
.con_id = "cpu1",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-cpu2",
.dev_id = "700e2000.thermal-sensor",
.con_id = "cpu2",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-cpu0",
.dev_id = "700e2000.thermal-sensor",
.con_id = "cpu0",
}, {
.nvmem_name = "fuse",
.cell_name = "xusb-pad-calibration",
.dev_id = "7009f000.padctl",
.con_id = "calibration",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-cpu3",
.dev_id = "700e2000.thermal-sensor",
.con_id = "cpu3",
}, {
.nvmem_name = "fuse",
.cell_name = "sata-calibration",
.dev_id = "70020000.sata",
.con_id = "calibration",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-gpu",
.dev_id = "700e2000.thermal-sensor",
.con_id = "gpu",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-mem0",
.dev_id = "700e2000.thermal-sensor",
.con_id = "mem0",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-mem1",
.dev_id = "700e2000.thermal-sensor",
.con_id = "mem1",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-pllx",
.dev_id = "700e2000.thermal-sensor",
.con_id = "pllx",
}, {
.nvmem_name = "fuse",
.cell_name = "tsensor-common",
.dev_id = "700e2000.thermal-sensor",
.con_id = "common",
}, {
.nvmem_name = "fuse",
.cell_name = "gpu-calibration",
.dev_id = "57000000.gpu",
.con_id = "calibration",
}, {
.nvmem_name = "fuse",
.cell_name = "xusb-pad-calibration-ext",
.dev_id = "7009f000.padctl",
.con_id = "calibration-ext",
},
};
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,
.lookups = tegra210_fuse_lookups,
.cells = tegra210_fuse_cells,
.num_cells = ARRAY_SIZE(tegra210_fuse_cells),
.num_lookups = ARRAY_SIZE(tegra210_fuse_lookups),
.soc_attr_group = &tegra_soc_attr_group,
.clk_suspend_on = false,
};
#endif
#if defined(CONFIG_ARCH_TEGRA_186_SOC)
static const struct nvmem_cell_info tegra186_fuse_cells[] = {
{
.name = "xusb-pad-calibration",
.offset = 0x0f0,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "xusb-pad-calibration-ext",
.offset = 0x250,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
},
};
static const struct nvmem_cell_lookup tegra186_fuse_lookups[] = {
{
.nvmem_name = "fuse",
.cell_name = "xusb-pad-calibration",
.dev_id = "3520000.padctl",
.con_id = "calibration",
}, {
.nvmem_name = "fuse",
.cell_name = "xusb-pad-calibration-ext",
.dev_id = "3520000.padctl",
.con_id = "calibration-ext",
},
};
static const struct nvmem_keepout tegra186_fuse_keepouts[] = {
{ .start = 0x01c, .end = 0x0f0 },
{ .start = 0x138, .end = 0x198 },
{ .start = 0x1d8, .end = 0x250 },
{ .start = 0x280, .end = 0x290 },
{ .start = 0x340, .end = 0x344 }
};
static const struct tegra_fuse_info tegra186_fuse_info = {
.read = tegra30_fuse_read,
.size = 0x478,
.spare = 0x280,
};
const struct tegra_fuse_soc tegra186_fuse_soc = {
.init = tegra30_fuse_init,
.info = &tegra186_fuse_info,
.lookups = tegra186_fuse_lookups,
.num_lookups = ARRAY_SIZE(tegra186_fuse_lookups),
.cells = tegra186_fuse_cells,
.num_cells = ARRAY_SIZE(tegra186_fuse_cells),
.keepouts = tegra186_fuse_keepouts,
.num_keepouts = ARRAY_SIZE(tegra186_fuse_keepouts),
.soc_attr_group = &tegra_soc_attr_group,
.clk_suspend_on = false,
};
#endif
#if defined(CONFIG_ARCH_TEGRA_194_SOC)
static const struct nvmem_cell_info tegra194_fuse_cells[] = {
{
.name = "xusb-pad-calibration",
.offset = 0x0f0,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "gpu-gcplex-config-fuse",
.offset = 0x1c8,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "xusb-pad-calibration-ext",
.offset = 0x250,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "gpu-pdi0",
.offset = 0x300,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "gpu-pdi1",
.offset = 0x304,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
},
};
static const struct nvmem_cell_lookup tegra194_fuse_lookups[] = {
{
.nvmem_name = "fuse",
.cell_name = "xusb-pad-calibration",
.dev_id = "3520000.padctl",
.con_id = "calibration",
}, {
.nvmem_name = "fuse",
.cell_name = "xusb-pad-calibration-ext",
.dev_id = "3520000.padctl",
.con_id = "calibration-ext",
}, {
.nvmem_name = "fuse",
.cell_name = "gpu-gcplex-config-fuse",
.dev_id = "17000000.gpu",
.con_id = "gcplex-config-fuse",
}, {
.nvmem_name = "fuse",
.cell_name = "gpu-pdi0",
.dev_id = "17000000.gpu",
.con_id = "pdi0",
}, {
.nvmem_name = "fuse",
.cell_name = "gpu-pdi1",
.dev_id = "17000000.gpu",
.con_id = "pdi1",
},
};
static const struct nvmem_keepout tegra194_fuse_keepouts[] = {
{ .start = 0x01c, .end = 0x0b8 },
{ .start = 0x12c, .end = 0x198 },
{ .start = 0x1a0, .end = 0x1bc },
{ .start = 0x1d8, .end = 0x250 },
{ .start = 0x270, .end = 0x290 },
{ .start = 0x310, .end = 0x45c }
};
static const struct tegra_fuse_info tegra194_fuse_info = {
.read = tegra30_fuse_read,
.size = 0x650,
.spare = 0x280,
};
const struct tegra_fuse_soc tegra194_fuse_soc = {
.init = tegra30_fuse_init,
.info = &tegra194_fuse_info,
.lookups = tegra194_fuse_lookups,
.num_lookups = ARRAY_SIZE(tegra194_fuse_lookups),
.cells = tegra194_fuse_cells,
.num_cells = ARRAY_SIZE(tegra194_fuse_cells),
.keepouts = tegra194_fuse_keepouts,
.num_keepouts = ARRAY_SIZE(tegra194_fuse_keepouts),
.soc_attr_group = &tegra194_soc_attr_group,
.clk_suspend_on = false,
};
#endif
#if defined(CONFIG_ARCH_TEGRA_234_SOC)
static const struct nvmem_cell_info tegra234_fuse_cells[] = {
{
.name = "xusb-pad-calibration",
.offset = 0x0f0,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "xusb-pad-calibration-ext",
.offset = 0x250,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
},
};
static const struct nvmem_cell_lookup tegra234_fuse_lookups[] = {
{
.nvmem_name = "fuse",
.cell_name = "xusb-pad-calibration",
.dev_id = "3520000.padctl",
.con_id = "calibration",
}, {
.nvmem_name = "fuse",
.cell_name = "xusb-pad-calibration-ext",
.dev_id = "3520000.padctl",
.con_id = "calibration-ext",
},
};
static const struct nvmem_keepout tegra234_fuse_keepouts[] = {
{ .start = 0x01c, .end = 0x064 },
{ .start = 0x084, .end = 0x0a0 },
{ .start = 0x0a4, .end = 0x0c8 },
{ .start = 0x12c, .end = 0x164 },
{ .start = 0x16c, .end = 0x184 },
{ .start = 0x190, .end = 0x198 },
{ .start = 0x1a0, .end = 0x204 },
{ .start = 0x21c, .end = 0x2f0 },
{ .start = 0x310, .end = 0x3d8 },
{ .start = 0x400, .end = 0x420 },
{ .start = 0x444, .end = 0x490 },
{ .start = 0x4bc, .end = 0x4f0 },
{ .start = 0x4f8, .end = 0x54c },
{ .start = 0x57c, .end = 0x7e8 },
{ .start = 0x8d0, .end = 0x8d8 },
{ .start = 0xacc, .end = 0xf00 }
};
static const struct tegra_fuse_info tegra234_fuse_info = {
.read = tegra30_fuse_read,
.size = 0xf90,
.spare = 0x280,
};
const struct tegra_fuse_soc tegra234_fuse_soc = {
.init = tegra30_fuse_init,
.info = &tegra234_fuse_info,
.lookups = tegra234_fuse_lookups,
.num_lookups = ARRAY_SIZE(tegra234_fuse_lookups),
.cells = tegra234_fuse_cells,
.num_cells = ARRAY_SIZE(tegra234_fuse_cells),
.keepouts = tegra234_fuse_keepouts,
.num_keepouts = ARRAY_SIZE(tegra234_fuse_keepouts),
.soc_attr_group = &tegra194_soc_attr_group,
.clk_suspend_on = false,
};
#endif
#if defined(CONFIG_ARCH_TEGRA_241_SOC)
static const struct tegra_fuse_info tegra241_fuse_info = {
.read = tegra30_fuse_read,
.size = 0x16008,
.spare = 0xcf0,
};
static const struct nvmem_keepout tegra241_fuse_keepouts[] = {
{ .start = 0xc, .end = 0x1600c }
};
const struct tegra_fuse_soc tegra241_fuse_soc = {
.init = tegra30_fuse_init,
.info = &tegra241_fuse_info,
.keepouts = tegra241_fuse_keepouts,
.num_keepouts = ARRAY_SIZE(tegra241_fuse_keepouts),
.soc_attr_group = &tegra194_soc_attr_group,
};
#endif
+144
View File
@@ -0,0 +1,144 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2010 Google, Inc.
* Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
*
* Author:
* Colin Cross <ccross@android.com>
*/
#ifndef __DRIVERS_MISC_TEGRA_FUSE_H
#define __DRIVERS_MISC_TEGRA_FUSE_H
#include <linux/dmaengine.h>
#include <linux/types.h>
struct nvmem_cell_lookup;
struct nvmem_device;
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;
const struct nvmem_cell_lookup *lookups;
unsigned int num_lookups;
const struct nvmem_cell_info *cells;
unsigned int num_cells;
const struct nvmem_keepout *keepouts;
unsigned int num_keepouts;
const struct attribute_group *soc_attr_group;
bool clk_suspend_on;
};
struct tegra_fuse {
struct device *dev;
void __iomem *base;
phys_addr_t phys;
struct clk *clk;
struct reset_control *rst;
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;
struct nvmem_device *nvmem;
struct nvmem_cell_lookup *lookups;
};
void tegra_init_revision(void);
void tegra_init_apbmisc(void);
void tegra_acpi_init_apbmisc(void);
u32 __init tegra_fuse_read_spare(unsigned int spare);
u32 __init tegra_fuse_read_early(unsigned int offset);
u8 tegra_get_major_rev(void);
u8 tegra_get_minor_rev(void);
extern const struct attribute_group tegra_soc_attr_group;
#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
#if IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC) || \
IS_ENABLED(CONFIG_ARCH_TEGRA_234_SOC) || \
IS_ENABLED(CONFIG_ARCH_TEGRA_241_SOC)
extern const struct attribute_group tegra194_soc_attr_group;
#endif
#ifdef CONFIG_ARCH_TEGRA_194_SOC
extern const struct tegra_fuse_soc tegra194_fuse_soc;
#endif
#ifdef CONFIG_ARCH_TEGRA_234_SOC
extern const struct tegra_fuse_soc tegra234_fuse_soc;
#endif
#ifdef CONFIG_ARCH_TEGRA_241_SOC
extern const struct tegra_fuse_soc tegra241_fuse_soc;
#endif
#endif
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2013-2014, NVIDIA CORPORATION. All rights reserved.
*/
#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;
}
@@ -0,0 +1,148 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2013-2014, NVIDIA CORPORATION. All rights reserved.
*/
#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, soc_speedo_0_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);
sku_info->cpu_speedo_value = tegra_fuse_read_early(FUSE_CPU_SPEEDO_0);
if (sku_info->cpu_speedo_value == 0) {
pr_warn("Tegra Warning: Speedo value not fused.\n");
WARN_ON(1);
return;
}
/* 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);
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);
}
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012-2014, NVIDIA CORPORATION. All rights reserved.
*/
#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;
}
@@ -0,0 +1,194 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2013-2015, NVIDIA CORPORATION. All rights reserved.
*/
#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;
if (sku_info->revision >= TEGRA_REVISION_A02) {
switch (sku) {
case 0x00: /* Engineering SKU */
case 0x01: /* Engineering SKU */
case 0x13:
sku_info->cpu_speedo_id = 5;
sku_info->gpu_speedo_id = 2;
break;
case 0x07:
case 0x17:
case 0x1F:
sku_info->cpu_speedo_id = 7;
sku_info->gpu_speedo_id = 2;
break;
case 0x27:
sku_info->cpu_speedo_id = 1;
sku_info->gpu_speedo_id = 2;
break;
case 0x83:
sku_info->cpu_speedo_id = 3;
sku_info->gpu_speedo_id = 3;
break;
case 0x87:
sku_info->cpu_speedo_id = 2;
sku_info->gpu_speedo_id = 1;
break;
case 0x8F:
sku_info->soc_speedo_id = 2;
sku_info->cpu_speedo_id = 9;
sku_info->gpu_speedo_id = 2;
break;
default:
pr_err("Tegra210: unknown revision 2 or newer SKU %#04x\n", sku);
/* Using the default for the error case */
break;
}
} else if (sku == 0x00 || sku == 0x01 || sku == 0x07 || sku == 0x13 || sku == 0x17) {
sku_info->gpu_speedo_id = 1;
} else {
pr_err("Tegra210: unknown SKU %#04x\n", sku);
}
}
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];
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_SOC_SPEEDO_2);
/*
* 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);
}
@@ -0,0 +1,277 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012-2014, NVIDIA CORPORATION. All rights reserved.
*/
#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;
}
}
@@ -0,0 +1,351 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2014-2023, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/acpi.h>
#include <linux/arm-smccc.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <soc/tegra/common.h>
#include <soc/tegra/fuse.h>
#include "fuse.h"
#define FUSE_SKU_INFO 0x10
#define ERD_ERR_CONFIG 0x120c
#define ERD_MASK_INBAND_ERR 0x1
#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)
#define TEGRA_SMCCC_PLATFORM(x) (((x) >> 8) & 0xff)
#define TEGRA_SMCCC_CHIP_ID(x) (((x) >> 4) & 0xff)
#define TEGRA_SMCCC_MAJOR_REV(x) ((x) & 0xf)
#define TEGRA_SMCCC_MINOR_REV(x) ((x) & 0xf)
static void __iomem *apbmisc_base;
static bool long_ram_code;
static u32 strapping;
static u32 chipid;
u32 tegra_read_chipid(void)
{
WARN(!apbmisc_base, "Tegra APB MISC not yet available\n");
if (!chipid)
chipid = readl_relaxed(apbmisc_base + 4);
return chipid;
}
u8 tegra_get_chip_id(void)
{
#if IS_ENABLED(CONFIG_HAVE_ARM_SMCCC_DISCOVERY)
s32 soc_id = arm_smccc_get_soc_id_version();
if (soc_id >= 0)
return TEGRA_SMCCC_CHIP_ID(soc_id);
#endif
return (tegra_read_chipid() >> 8) & 0xff;
}
u8 tegra_get_major_rev(void)
{
#if IS_ENABLED(CONFIG_HAVE_ARM_SMCCC_DISCOVERY)
s32 soc_id = arm_smccc_get_soc_id_version();
if (soc_id >= 0)
return TEGRA_SMCCC_MAJOR_REV(soc_id);
#endif
return (tegra_read_chipid() >> 4) & 0xf;
}
u8 tegra_get_minor_rev(void)
{
#if IS_ENABLED(CONFIG_HAVE_ARM_SMCCC_DISCOVERY)
s32 revision = arm_smccc_get_soc_id_revision();
if (revision >= 0)
return TEGRA_SMCCC_MINOR_REV(revision);
#endif
return (tegra_read_chipid() >> 16) & 0xf;
}
u8 tegra_get_platform(void)
{
#if IS_ENABLED(CONFIG_HAVE_ARM_SMCCC_DISCOVERY)
s32 revision = arm_smccc_get_soc_id_revision();
if (revision >= 0)
return TEGRA_SMCCC_PLATFORM(revision);
#endif
return (tegra_read_chipid() >> 20) & 0xf;
}
bool tegra_is_silicon(void)
{
switch (tegra_get_chip_id()) {
case TEGRA194:
case TEGRA234:
case TEGRA241:
case TEGRA264:
if (tegra_get_platform() == 0)
return true;
return false;
}
/*
* Chips prior to Tegra194 have a different way of determining whether
* they are silicon or not. Since we never supported simulation on the
* older Tegra chips, don't bother extracting the information and just
* report that we're running on silicon.
*/
return true;
}
u32 tegra_read_straps(void)
{
WARN(!apbmisc_base, "Tegra ABP MISC not yet available\n");
return strapping;
}
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;
}
EXPORT_SYMBOL_GPL(tegra_read_ram_code);
/*
* The function sets ERD(Error Response Disable) bit.
* This allows to mask inband errors and always send an
* OKAY response from CBB to the master which caused error.
*/
int tegra194_miscreg_mask_serror(void)
{
if (!apbmisc_base)
return -EPROBE_DEFER;
if (!of_machine_is_compatible("nvidia,tegra194")) {
WARN(1, "Only supported for Tegra194 devices!\n");
return -EOPNOTSUPP;
}
writel_relaxed(ERD_MASK_INBAND_ERR,
apbmisc_base + ERD_ERR_CONFIG);
return 0;
}
EXPORT_SYMBOL(tegra194_miscreg_mask_serror);
static const struct of_device_id apbmisc_match[] __initconst = {
{ .compatible = "nvidia,tegra20-apbmisc", },
{ .compatible = "nvidia,tegra186-misc", },
{ .compatible = "nvidia,tegra194-misc", },
{ .compatible = "nvidia,tegra234-misc", },
{ .compatible = "nvidia,tegra264-misc", },
{},
};
void __init tegra_init_revision(void)
{
u8 chip_id, minor_rev;
chip_id = tegra_get_chip_id();
minor_rev = tegra_get_minor_rev();
switch (minor_rev) {
case 1:
tegra_sku_info.revision = TEGRA_REVISION_A01;
break;
case 2:
tegra_sku_info.revision = TEGRA_REVISION_A02;
break;
case 3:
if (chip_id == TEGRA20 && (tegra_fuse_read_spare(18) ||
tegra_fuse_read_spare(19)))
tegra_sku_info.revision = TEGRA_REVISION_A03p;
else
tegra_sku_info.revision = TEGRA_REVISION_A03;
break;
case 4:
tegra_sku_info.revision = TEGRA_REVISION_A04;
break;
default:
tegra_sku_info.revision = TEGRA_REVISION_UNKNOWN;
}
tegra_sku_info.sku_id = tegra_fuse_read_early(FUSE_SKU_INFO);
tegra_sku_info.platform = tegra_get_platform();
}
static void tegra_init_apbmisc_resources(struct resource *apbmisc,
struct resource *straps)
{
void __iomem *strapping_base;
apbmisc_base = ioremap(apbmisc->start, resource_size(apbmisc));
if (!apbmisc_base)
pr_err("failed to map APBMISC registers\n");
strapping_base = ioremap(straps->start, resource_size(straps));
if (strapping_base) {
strapping = readl_relaxed(strapping_base);
iounmap(strapping_base);
} else {
pr_err("failed to map strapping options registers\n");
}
}
/**
* tegra_init_apbmisc - Initializes Tegra APBMISC and Strapping registers.
*
* This is called during early init as some of the old 32-bit ARM code needs
* information from the APBMISC registers very early during boot.
*/
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");
goto put;
}
if (of_address_to_resource(np, 1, &straps) < 0) {
pr_err("failed to get strapping options registers\n");
goto put;
}
}
tegra_init_apbmisc_resources(&apbmisc, &straps);
long_ram_code = of_property_read_bool(np, "nvidia,long-ram-code");
put:
of_node_put(np);
}
#ifdef CONFIG_ACPI
static const struct acpi_device_id apbmisc_acpi_match[] = {
{ "NVDA2010" },
{ /* sentinel */ }
};
void tegra_acpi_init_apbmisc(void)
{
struct resource *resources[2] = { NULL };
struct resource_entry *rentry;
struct acpi_device *adev = NULL;
struct list_head resource_list;
int rcount = 0;
int ret;
adev = acpi_dev_get_first_match_dev(apbmisc_acpi_match[0].id, NULL, -1);
if (!adev)
return;
INIT_LIST_HEAD(&resource_list);
ret = acpi_dev_get_memory_resources(adev, &resource_list);
if (ret < 0) {
pr_err("failed to get APBMISC memory resources");
goto out_put_acpi_dev;
}
/*
* Get required memory resources.
*
* resources[0]: apbmisc.
* resources[1]: straps.
*/
resource_list_for_each_entry(rentry, &resource_list) {
if (rcount >= ARRAY_SIZE(resources))
break;
resources[rcount++] = rentry->res;
}
if (!resources[0]) {
pr_err("failed to get APBMISC registers\n");
goto out_free_resource_list;
}
if (!resources[1]) {
pr_err("failed to get strapping options registers\n");
goto out_free_resource_list;
}
tegra_init_apbmisc_resources(resources[0], resources[1]);
out_free_resource_list:
acpi_dev_free_resource_list(&resource_list);
out_put_acpi_dev:
acpi_dev_put(adev);
}
#else
void tegra_acpi_init_apbmisc(void)
{
}
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,560 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Voltage regulators coupler for NVIDIA Tegra20
* Copyright (C) 2019 GRATE-DRIVER project
*
* Voltage constraints borrowed from downstream kernel sources
* Copyright (C) 2010-2011 NVIDIA Corporation
*/
#define pr_fmt(fmt) "tegra voltage-coupler: " fmt
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/reboot.h>
#include <linux/regulator/coupler.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/suspend.h>
#include <soc/tegra/fuse.h>
#include <soc/tegra/pmc.h>
struct tegra_regulator_coupler {
struct regulator_coupler coupler;
struct regulator_dev *core_rdev;
struct regulator_dev *cpu_rdev;
struct regulator_dev *rtc_rdev;
struct notifier_block reboot_notifier;
struct notifier_block suspend_notifier;
int core_min_uV, cpu_min_uV;
bool sys_reboot_mode_req;
bool sys_reboot_mode;
bool sys_suspend_mode_req;
bool sys_suspend_mode;
};
static inline struct tegra_regulator_coupler *
to_tegra_coupler(struct regulator_coupler *coupler)
{
return container_of(coupler, struct tegra_regulator_coupler, coupler);
}
static int tegra20_core_limit(struct tegra_regulator_coupler *tegra,
struct regulator_dev *core_rdev)
{
int core_min_uV = 0;
int core_max_uV;
int core_cur_uV;
int err;
/*
* Tegra20 SoC has critical DVFS-capable devices that are
* permanently-active or active at a boot time, like EMC
* (DRAM controller) or Display controller for example.
*
* The voltage of a CORE SoC power domain shall not be dropped below
* a minimum level, which is determined by device's clock rate.
* This means that we can't fully allow CORE voltage scaling until
* the state of all DVFS-critical CORE devices is synced.
*/
if (tegra_pmc_core_domain_state_synced() && !tegra->sys_reboot_mode) {
pr_info_once("voltage state synced\n");
return 0;
}
if (tegra->core_min_uV > 0)
return tegra->core_min_uV;
core_cur_uV = regulator_get_voltage_rdev(core_rdev);
if (core_cur_uV < 0)
return core_cur_uV;
core_max_uV = max(core_cur_uV, 1200000);
err = regulator_check_voltage(core_rdev, &core_min_uV, &core_max_uV);
if (err)
return err;
/*
* Limit minimum CORE voltage to a value left from bootloader or,
* if it's unreasonably low value, to the most common 1.2v or to
* whatever maximum value defined via board's device-tree.
*/
tegra->core_min_uV = core_max_uV;
pr_info("core voltage initialized to %duV\n", tegra->core_min_uV);
return tegra->core_min_uV;
}
static int tegra20_core_rtc_max_spread(struct regulator_dev *core_rdev,
struct regulator_dev *rtc_rdev)
{
struct coupling_desc *c_desc = &core_rdev->coupling_desc;
struct regulator_dev *rdev;
int max_spread;
unsigned int i;
for (i = 1; i < c_desc->n_coupled; i++) {
max_spread = core_rdev->constraints->max_spread[i - 1];
rdev = c_desc->coupled_rdevs[i];
if (rdev == rtc_rdev && max_spread)
return max_spread;
}
pr_err_once("rtc-core max-spread is undefined in device-tree\n");
return 150000;
}
static int tegra20_cpu_nominal_uV(void)
{
switch (tegra_sku_info.soc_speedo_id) {
case 0:
return 1100000;
case 1:
return 1025000;
default:
return 1125000;
}
}
static int tegra20_core_nominal_uV(void)
{
switch (tegra_sku_info.soc_speedo_id) {
default:
return 1225000;
case 2:
return 1300000;
}
}
static int tegra20_core_rtc_update(struct tegra_regulator_coupler *tegra,
struct regulator_dev *core_rdev,
struct regulator_dev *rtc_rdev,
int cpu_uV, int cpu_min_uV)
{
int core_min_uV, core_max_uV = INT_MAX;
int rtc_min_uV, rtc_max_uV = INT_MAX;
int core_target_uV;
int rtc_target_uV;
int max_spread;
int core_uV;
int rtc_uV;
int err;
/*
* RTC and CORE voltages should be no more than 170mV from each other,
* CPU should be below RTC and CORE by at least 120mV. This applies
* to all Tegra20 SoC's.
*/
max_spread = tegra20_core_rtc_max_spread(core_rdev, rtc_rdev);
/*
* The core voltage scaling is currently not hooked up in drivers,
* hence we will limit the minimum core voltage to a reasonable value.
* This should be good enough for the time being.
*/
core_min_uV = tegra20_core_limit(tegra, core_rdev);
if (core_min_uV < 0)
return core_min_uV;
err = regulator_check_voltage(core_rdev, &core_min_uV, &core_max_uV);
if (err)
return err;
err = regulator_check_consumers(core_rdev, &core_min_uV, &core_max_uV,
PM_SUSPEND_ON);
if (err)
return err;
/* prepare voltage level for suspend */
if (tegra->sys_suspend_mode)
core_min_uV = clamp(tegra20_core_nominal_uV(),
core_min_uV, core_max_uV);
core_uV = regulator_get_voltage_rdev(core_rdev);
if (core_uV < 0)
return core_uV;
core_min_uV = max(cpu_min_uV + 125000, core_min_uV);
if (core_min_uV > core_max_uV)
return -EINVAL;
if (cpu_uV + 120000 > core_uV)
pr_err("core-cpu voltage constraint violated: %d %d\n",
core_uV, cpu_uV + 120000);
rtc_uV = regulator_get_voltage_rdev(rtc_rdev);
if (rtc_uV < 0)
return rtc_uV;
if (cpu_uV + 120000 > rtc_uV)
pr_err("rtc-cpu voltage constraint violated: %d %d\n",
rtc_uV, cpu_uV + 120000);
if (abs(core_uV - rtc_uV) > 170000)
pr_err("core-rtc voltage constraint violated: %d %d\n",
core_uV, rtc_uV);
rtc_min_uV = max(cpu_min_uV + 125000, core_min_uV - max_spread);
err = regulator_check_voltage(rtc_rdev, &rtc_min_uV, &rtc_max_uV);
if (err)
return err;
while (core_uV != core_min_uV || rtc_uV != rtc_min_uV) {
if (core_uV < core_min_uV) {
core_target_uV = min(core_uV + max_spread, core_min_uV);
core_target_uV = min(rtc_uV + max_spread, core_target_uV);
} else {
core_target_uV = max(core_uV - max_spread, core_min_uV);
core_target_uV = max(rtc_uV - max_spread, core_target_uV);
}
if (core_uV == core_target_uV)
goto update_rtc;
err = regulator_set_voltage_rdev(core_rdev,
core_target_uV,
core_max_uV,
PM_SUSPEND_ON);
if (err)
return err;
core_uV = core_target_uV;
update_rtc:
if (rtc_uV < rtc_min_uV) {
rtc_target_uV = min(rtc_uV + max_spread, rtc_min_uV);
rtc_target_uV = min(core_uV + max_spread, rtc_target_uV);
} else {
rtc_target_uV = max(rtc_uV - max_spread, rtc_min_uV);
rtc_target_uV = max(core_uV - max_spread, rtc_target_uV);
}
if (rtc_uV == rtc_target_uV)
continue;
err = regulator_set_voltage_rdev(rtc_rdev,
rtc_target_uV,
rtc_max_uV,
PM_SUSPEND_ON);
if (err)
return err;
rtc_uV = rtc_target_uV;
}
return 0;
}
static int tegra20_core_voltage_update(struct tegra_regulator_coupler *tegra,
struct regulator_dev *cpu_rdev,
struct regulator_dev *core_rdev,
struct regulator_dev *rtc_rdev)
{
int cpu_uV;
cpu_uV = regulator_get_voltage_rdev(cpu_rdev);
if (cpu_uV < 0)
return cpu_uV;
return tegra20_core_rtc_update(tegra, core_rdev, rtc_rdev,
cpu_uV, cpu_uV);
}
static int tegra20_cpu_voltage_update(struct tegra_regulator_coupler *tegra,
struct regulator_dev *cpu_rdev,
struct regulator_dev *core_rdev,
struct regulator_dev *rtc_rdev)
{
int cpu_min_uV_consumers = 0;
int cpu_max_uV = INT_MAX;
int cpu_min_uV = 0;
int cpu_uV;
int err;
err = regulator_check_voltage(cpu_rdev, &cpu_min_uV, &cpu_max_uV);
if (err)
return err;
err = regulator_check_consumers(cpu_rdev, &cpu_min_uV, &cpu_max_uV,
PM_SUSPEND_ON);
if (err)
return err;
err = regulator_check_consumers(cpu_rdev, &cpu_min_uV_consumers,
&cpu_max_uV, PM_SUSPEND_ON);
if (err)
return err;
cpu_uV = regulator_get_voltage_rdev(cpu_rdev);
if (cpu_uV < 0)
return cpu_uV;
/* store boot voltage level */
if (!tegra->cpu_min_uV)
tegra->cpu_min_uV = cpu_uV;
/*
* CPU's regulator may not have any consumers, hence the voltage
* must not be changed in that case because CPU simply won't
* survive the voltage drop if it's running on a higher frequency.
*/
if (!cpu_min_uV_consumers)
cpu_min_uV = cpu_uV;
/* restore boot voltage level */
if (tegra->sys_reboot_mode)
cpu_min_uV = max(cpu_min_uV, tegra->cpu_min_uV);
/* prepare voltage level for suspend */
if (tegra->sys_suspend_mode)
cpu_min_uV = clamp(tegra20_cpu_nominal_uV(),
cpu_min_uV, cpu_max_uV);
if (cpu_min_uV > cpu_uV) {
err = tegra20_core_rtc_update(tegra, core_rdev, rtc_rdev,
cpu_uV, cpu_min_uV);
if (err)
return err;
err = regulator_set_voltage_rdev(cpu_rdev, cpu_min_uV,
cpu_max_uV, PM_SUSPEND_ON);
if (err)
return err;
} else if (cpu_min_uV < cpu_uV) {
err = regulator_set_voltage_rdev(cpu_rdev, cpu_min_uV,
cpu_max_uV, PM_SUSPEND_ON);
if (err)
return err;
err = tegra20_core_rtc_update(tegra, core_rdev, rtc_rdev,
cpu_uV, cpu_min_uV);
if (err)
return err;
}
return 0;
}
static int tegra20_regulator_balance_voltage(struct regulator_coupler *coupler,
struct regulator_dev *rdev,
suspend_state_t state)
{
struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler);
struct regulator_dev *core_rdev = tegra->core_rdev;
struct regulator_dev *cpu_rdev = tegra->cpu_rdev;
struct regulator_dev *rtc_rdev = tegra->rtc_rdev;
if ((core_rdev != rdev && cpu_rdev != rdev && rtc_rdev != rdev) ||
state != PM_SUSPEND_ON) {
pr_err("regulators are not coupled properly\n");
return -EINVAL;
}
tegra->sys_reboot_mode = READ_ONCE(tegra->sys_reboot_mode_req);
tegra->sys_suspend_mode = READ_ONCE(tegra->sys_suspend_mode_req);
if (rdev == cpu_rdev)
return tegra20_cpu_voltage_update(tegra, cpu_rdev,
core_rdev, rtc_rdev);
if (rdev == core_rdev)
return tegra20_core_voltage_update(tegra, cpu_rdev,
core_rdev, rtc_rdev);
pr_err("changing %s voltage not permitted\n", rdev_get_name(rtc_rdev));
return -EPERM;
}
static int tegra20_regulator_prepare_suspend(struct tegra_regulator_coupler *tegra,
bool sys_suspend_mode)
{
int err;
if (!tegra->core_rdev || !tegra->rtc_rdev || !tegra->cpu_rdev)
return 0;
/*
* All power domains are enabled early during resume from suspend
* by GENPD core. Domains like VENC may require a higher voltage
* when enabled during resume from suspend. This also prepares
* hardware for resuming from LP0.
*/
WRITE_ONCE(tegra->sys_suspend_mode_req, sys_suspend_mode);
err = regulator_sync_voltage_rdev(tegra->cpu_rdev);
if (err)
return err;
err = regulator_sync_voltage_rdev(tegra->core_rdev);
if (err)
return err;
return 0;
}
static int tegra20_regulator_suspend(struct notifier_block *notifier,
unsigned long mode, void *arg)
{
struct tegra_regulator_coupler *tegra;
int ret = 0;
tegra = container_of(notifier, struct tegra_regulator_coupler,
suspend_notifier);
switch (mode) {
case PM_HIBERNATION_PREPARE:
case PM_RESTORE_PREPARE:
case PM_SUSPEND_PREPARE:
ret = tegra20_regulator_prepare_suspend(tegra, true);
break;
case PM_POST_HIBERNATION:
case PM_POST_RESTORE:
case PM_POST_SUSPEND:
ret = tegra20_regulator_prepare_suspend(tegra, false);
break;
}
if (ret)
pr_err("failed to prepare regulators: %d\n", ret);
return notifier_from_errno(ret);
}
static int tegra20_regulator_prepare_reboot(struct tegra_regulator_coupler *tegra,
bool sys_reboot_mode)
{
int err;
if (!tegra->core_rdev || !tegra->rtc_rdev || !tegra->cpu_rdev)
return 0;
WRITE_ONCE(tegra->sys_reboot_mode_req, true);
/*
* Some devices use CPU soft-reboot method and in this case we
* should ensure that voltages are sane for the reboot by restoring
* the minimum boot levels.
*/
err = regulator_sync_voltage_rdev(tegra->cpu_rdev);
if (err)
return err;
err = regulator_sync_voltage_rdev(tegra->core_rdev);
if (err)
return err;
WRITE_ONCE(tegra->sys_reboot_mode_req, sys_reboot_mode);
return 0;
}
static int tegra20_regulator_reboot(struct notifier_block *notifier,
unsigned long event, void *cmd)
{
struct tegra_regulator_coupler *tegra;
int ret;
if (event != SYS_RESTART)
return NOTIFY_DONE;
tegra = container_of(notifier, struct tegra_regulator_coupler,
reboot_notifier);
ret = tegra20_regulator_prepare_reboot(tegra, true);
return notifier_from_errno(ret);
}
static int tegra20_regulator_attach(struct regulator_coupler *coupler,
struct regulator_dev *rdev)
{
struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler);
struct device_node *np = rdev->dev.of_node;
if (of_property_read_bool(np, "nvidia,tegra-core-regulator") &&
!tegra->core_rdev) {
tegra->core_rdev = rdev;
return 0;
}
if (of_property_read_bool(np, "nvidia,tegra-rtc-regulator") &&
!tegra->rtc_rdev) {
tegra->rtc_rdev = rdev;
return 0;
}
if (of_property_read_bool(np, "nvidia,tegra-cpu-regulator") &&
!tegra->cpu_rdev) {
tegra->cpu_rdev = rdev;
return 0;
}
return -EINVAL;
}
static int tegra20_regulator_detach(struct regulator_coupler *coupler,
struct regulator_dev *rdev)
{
struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler);
/*
* We don't expect regulators to be decoupled during reboot,
* this may race with the reboot handler and shouldn't ever
* happen in practice.
*/
if (WARN_ON_ONCE(system_state > SYSTEM_RUNNING))
return -EPERM;
if (tegra->core_rdev == rdev) {
tegra->core_rdev = NULL;
return 0;
}
if (tegra->rtc_rdev == rdev) {
tegra->rtc_rdev = NULL;
return 0;
}
if (tegra->cpu_rdev == rdev) {
tegra->cpu_rdev = NULL;
return 0;
}
return -EINVAL;
}
static struct tegra_regulator_coupler tegra20_coupler = {
.coupler = {
.attach_regulator = tegra20_regulator_attach,
.detach_regulator = tegra20_regulator_detach,
.balance_voltage = tegra20_regulator_balance_voltage,
},
.reboot_notifier.notifier_call = tegra20_regulator_reboot,
.suspend_notifier.notifier_call = tegra20_regulator_suspend,
};
static int __init tegra_regulator_coupler_init(void)
{
int err;
if (!of_machine_is_compatible("nvidia,tegra20"))
return 0;
err = register_reboot_notifier(&tegra20_coupler.reboot_notifier);
WARN_ON(err);
err = register_pm_notifier(&tegra20_coupler.suspend_notifier);
WARN_ON(err);
return regulator_coupler_register(&tegra20_coupler.coupler);
}
arch_initcall(tegra_regulator_coupler_init);
@@ -0,0 +1,534 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Voltage regulators coupler for NVIDIA Tegra30
* Copyright (C) 2019 GRATE-DRIVER project
*
* Voltage constraints borrowed from downstream kernel sources
* Copyright (C) 2010-2011 NVIDIA Corporation
*/
#define pr_fmt(fmt) "tegra voltage-coupler: " fmt
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/reboot.h>
#include <linux/regulator/coupler.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/suspend.h>
#include <soc/tegra/fuse.h>
#include <soc/tegra/pmc.h>
struct tegra_regulator_coupler {
struct regulator_coupler coupler;
struct regulator_dev *core_rdev;
struct regulator_dev *cpu_rdev;
struct notifier_block reboot_notifier;
struct notifier_block suspend_notifier;
int core_min_uV, cpu_min_uV;
bool sys_reboot_mode_req;
bool sys_reboot_mode;
bool sys_suspend_mode_req;
bool sys_suspend_mode;
};
static inline struct tegra_regulator_coupler *
to_tegra_coupler(struct regulator_coupler *coupler)
{
return container_of(coupler, struct tegra_regulator_coupler, coupler);
}
static int tegra30_core_limit(struct tegra_regulator_coupler *tegra,
struct regulator_dev *core_rdev)
{
int core_min_uV = 0;
int core_max_uV;
int core_cur_uV;
int err;
/*
* Tegra30 SoC has critical DVFS-capable devices that are
* permanently-active or active at a boot time, like EMC
* (DRAM controller) or Display controller for example.
*
* The voltage of a CORE SoC power domain shall not be dropped below
* a minimum level, which is determined by device's clock rate.
* This means that we can't fully allow CORE voltage scaling until
* the state of all DVFS-critical CORE devices is synced.
*/
if (tegra_pmc_core_domain_state_synced() && !tegra->sys_reboot_mode) {
pr_info_once("voltage state synced\n");
return 0;
}
if (tegra->core_min_uV > 0)
return tegra->core_min_uV;
core_cur_uV = regulator_get_voltage_rdev(core_rdev);
if (core_cur_uV < 0)
return core_cur_uV;
core_max_uV = max(core_cur_uV, 1200000);
err = regulator_check_voltage(core_rdev, &core_min_uV, &core_max_uV);
if (err)
return err;
/*
* Limit minimum CORE voltage to a value left from bootloader or,
* if it's unreasonably low value, to the most common 1.2v or to
* whatever maximum value defined via board's device-tree.
*/
tegra->core_min_uV = core_max_uV;
pr_info("core voltage initialized to %duV\n", tegra->core_min_uV);
return tegra->core_min_uV;
}
static int tegra30_core_cpu_limit(int cpu_uV)
{
if (cpu_uV < 800000)
return 950000;
if (cpu_uV < 900000)
return 1000000;
if (cpu_uV < 1000000)
return 1100000;
if (cpu_uV < 1100000)
return 1200000;
if (cpu_uV < 1250000) {
switch (tegra_sku_info.cpu_speedo_id) {
case 0 ... 1:
case 4:
case 7 ... 8:
return 1200000;
default:
return 1300000;
}
}
return -EINVAL;
}
static int tegra30_cpu_nominal_uV(void)
{
switch (tegra_sku_info.cpu_speedo_id) {
case 10 ... 11:
return 850000;
case 9:
return 912000;
case 1 ... 3:
case 7 ... 8:
return 1050000;
default:
return 1125000;
case 4 ... 6:
case 12 ... 13:
return 1237000;
}
}
static int tegra30_core_nominal_uV(void)
{
switch (tegra_sku_info.soc_speedo_id) {
case 0:
return 1200000;
case 1:
if (tegra_sku_info.cpu_speedo_id != 7 &&
tegra_sku_info.cpu_speedo_id != 8)
return 1200000;
fallthrough;
case 2:
if (tegra_sku_info.cpu_speedo_id != 13)
return 1300000;
return 1350000;
default:
return 1250000;
}
}
static int tegra30_voltage_update(struct tegra_regulator_coupler *tegra,
struct regulator_dev *cpu_rdev,
struct regulator_dev *core_rdev)
{
int core_min_uV, core_max_uV = INT_MAX;
int cpu_min_uV, cpu_max_uV = INT_MAX;
int cpu_min_uV_consumers = 0;
int core_min_limited_uV;
int core_target_uV;
int cpu_target_uV;
int core_max_step;
int cpu_max_step;
int max_spread;
int core_uV;
int cpu_uV;
int err;
/*
* CPU voltage should not got lower than 300mV from the CORE.
* CPU voltage should stay below the CORE by 100mV+, depending
* by the CORE voltage. This applies to all Tegra30 SoC's.
*/
max_spread = cpu_rdev->constraints->max_spread[0];
cpu_max_step = cpu_rdev->constraints->max_uV_step;
core_max_step = core_rdev->constraints->max_uV_step;
if (!max_spread) {
pr_err_once("cpu-core max-spread is undefined in device-tree\n");
max_spread = 300000;
}
if (!cpu_max_step) {
pr_err_once("cpu max-step is undefined in device-tree\n");
cpu_max_step = 150000;
}
if (!core_max_step) {
pr_err_once("core max-step is undefined in device-tree\n");
core_max_step = 150000;
}
/*
* The CORE voltage scaling is currently not hooked up in drivers,
* hence we will limit the minimum CORE voltage to a reasonable value.
* This should be good enough for the time being.
*/
core_min_uV = tegra30_core_limit(tegra, core_rdev);
if (core_min_uV < 0)
return core_min_uV;
err = regulator_check_consumers(core_rdev, &core_min_uV, &core_max_uV,
PM_SUSPEND_ON);
if (err)
return err;
/* prepare voltage level for suspend */
if (tegra->sys_suspend_mode)
core_min_uV = clamp(tegra30_core_nominal_uV(),
core_min_uV, core_max_uV);
core_uV = regulator_get_voltage_rdev(core_rdev);
if (core_uV < 0)
return core_uV;
cpu_min_uV = core_min_uV - max_spread;
err = regulator_check_consumers(cpu_rdev, &cpu_min_uV, &cpu_max_uV,
PM_SUSPEND_ON);
if (err)
return err;
err = regulator_check_consumers(cpu_rdev, &cpu_min_uV_consumers,
&cpu_max_uV, PM_SUSPEND_ON);
if (err)
return err;
err = regulator_check_voltage(cpu_rdev, &cpu_min_uV, &cpu_max_uV);
if (err)
return err;
cpu_uV = regulator_get_voltage_rdev(cpu_rdev);
if (cpu_uV < 0)
return cpu_uV;
/* store boot voltage level */
if (!tegra->cpu_min_uV)
tegra->cpu_min_uV = cpu_uV;
/*
* CPU's regulator may not have any consumers, hence the voltage
* must not be changed in that case because CPU simply won't
* survive the voltage drop if it's running on a higher frequency.
*/
if (!cpu_min_uV_consumers)
cpu_min_uV = max(cpu_uV, cpu_min_uV);
/*
* Bootloader shall set up voltages correctly, but if it
* happens that there is a violation, then try to fix it
* at first.
*/
core_min_limited_uV = tegra30_core_cpu_limit(cpu_uV);
if (core_min_limited_uV < 0)
return core_min_limited_uV;
core_min_uV = max(core_min_uV, tegra30_core_cpu_limit(cpu_min_uV));
err = regulator_check_voltage(core_rdev, &core_min_uV, &core_max_uV);
if (err)
return err;
/* restore boot voltage level */
if (tegra->sys_reboot_mode)
cpu_min_uV = max(cpu_min_uV, tegra->cpu_min_uV);
/* prepare voltage level for suspend */
if (tegra->sys_suspend_mode)
cpu_min_uV = clamp(tegra30_cpu_nominal_uV(),
cpu_min_uV, cpu_max_uV);
if (core_min_limited_uV > core_uV) {
pr_err("core voltage constraint violated: %d %d %d\n",
core_uV, core_min_limited_uV, cpu_uV);
goto update_core;
}
while (cpu_uV != cpu_min_uV || core_uV != core_min_uV) {
if (cpu_uV < cpu_min_uV) {
cpu_target_uV = min(cpu_uV + cpu_max_step, cpu_min_uV);
} else {
cpu_target_uV = max(cpu_uV - cpu_max_step, cpu_min_uV);
cpu_target_uV = max(core_uV - max_spread, cpu_target_uV);
}
if (cpu_uV == cpu_target_uV)
goto update_core;
err = regulator_set_voltage_rdev(cpu_rdev,
cpu_target_uV,
cpu_max_uV,
PM_SUSPEND_ON);
if (err)
return err;
cpu_uV = cpu_target_uV;
update_core:
core_min_limited_uV = tegra30_core_cpu_limit(cpu_uV);
if (core_min_limited_uV < 0)
return core_min_limited_uV;
core_target_uV = max(core_min_limited_uV, core_min_uV);
if (core_uV < core_target_uV) {
core_target_uV = min(core_target_uV, core_uV + core_max_step);
core_target_uV = min(core_target_uV, cpu_uV + max_spread);
} else {
core_target_uV = max(core_target_uV, core_uV - core_max_step);
}
if (core_uV == core_target_uV)
continue;
err = regulator_set_voltage_rdev(core_rdev,
core_target_uV,
core_max_uV,
PM_SUSPEND_ON);
if (err)
return err;
core_uV = core_target_uV;
}
return 0;
}
static int tegra30_regulator_balance_voltage(struct regulator_coupler *coupler,
struct regulator_dev *rdev,
suspend_state_t state)
{
struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler);
struct regulator_dev *core_rdev = tegra->core_rdev;
struct regulator_dev *cpu_rdev = tegra->cpu_rdev;
if ((core_rdev != rdev && cpu_rdev != rdev) || state != PM_SUSPEND_ON) {
pr_err("regulators are not coupled properly\n");
return -EINVAL;
}
tegra->sys_reboot_mode = READ_ONCE(tegra->sys_reboot_mode_req);
tegra->sys_suspend_mode = READ_ONCE(tegra->sys_suspend_mode_req);
return tegra30_voltage_update(tegra, cpu_rdev, core_rdev);
}
static int tegra30_regulator_prepare_suspend(struct tegra_regulator_coupler *tegra,
bool sys_suspend_mode)
{
int err;
if (!tegra->core_rdev || !tegra->cpu_rdev)
return 0;
/*
* All power domains are enabled early during resume from suspend
* by GENPD core. Domains like VENC may require a higher voltage
* when enabled during resume from suspend. This also prepares
* hardware for resuming from LP0.
*/
WRITE_ONCE(tegra->sys_suspend_mode_req, sys_suspend_mode);
err = regulator_sync_voltage_rdev(tegra->cpu_rdev);
if (err)
return err;
err = regulator_sync_voltage_rdev(tegra->core_rdev);
if (err)
return err;
return 0;
}
static int tegra30_regulator_suspend(struct notifier_block *notifier,
unsigned long mode, void *arg)
{
struct tegra_regulator_coupler *tegra;
int ret = 0;
tegra = container_of(notifier, struct tegra_regulator_coupler,
suspend_notifier);
switch (mode) {
case PM_HIBERNATION_PREPARE:
case PM_RESTORE_PREPARE:
case PM_SUSPEND_PREPARE:
ret = tegra30_regulator_prepare_suspend(tegra, true);
break;
case PM_POST_HIBERNATION:
case PM_POST_RESTORE:
case PM_POST_SUSPEND:
ret = tegra30_regulator_prepare_suspend(tegra, false);
break;
}
if (ret)
pr_err("failed to prepare regulators: %d\n", ret);
return notifier_from_errno(ret);
}
static int tegra30_regulator_prepare_reboot(struct tegra_regulator_coupler *tegra,
bool sys_reboot_mode)
{
int err;
if (!tegra->core_rdev || !tegra->cpu_rdev)
return 0;
WRITE_ONCE(tegra->sys_reboot_mode_req, true);
/*
* Some devices use CPU soft-reboot method and in this case we
* should ensure that voltages are sane for the reboot by restoring
* the minimum boot levels.
*/
err = regulator_sync_voltage_rdev(tegra->cpu_rdev);
if (err)
return err;
err = regulator_sync_voltage_rdev(tegra->core_rdev);
if (err)
return err;
WRITE_ONCE(tegra->sys_reboot_mode_req, sys_reboot_mode);
return 0;
}
static int tegra30_regulator_reboot(struct notifier_block *notifier,
unsigned long event, void *cmd)
{
struct tegra_regulator_coupler *tegra;
int ret;
if (event != SYS_RESTART)
return NOTIFY_DONE;
tegra = container_of(notifier, struct tegra_regulator_coupler,
reboot_notifier);
ret = tegra30_regulator_prepare_reboot(tegra, true);
return notifier_from_errno(ret);
}
static int tegra30_regulator_attach(struct regulator_coupler *coupler,
struct regulator_dev *rdev)
{
struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler);
struct device_node *np = rdev->dev.of_node;
if (of_property_read_bool(np, "nvidia,tegra-core-regulator") &&
!tegra->core_rdev) {
tegra->core_rdev = rdev;
return 0;
}
if (of_property_read_bool(np, "nvidia,tegra-cpu-regulator") &&
!tegra->cpu_rdev) {
tegra->cpu_rdev = rdev;
return 0;
}
return -EINVAL;
}
static int tegra30_regulator_detach(struct regulator_coupler *coupler,
struct regulator_dev *rdev)
{
struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler);
/*
* We don't expect regulators to be decoupled during reboot,
* this may race with the reboot handler and shouldn't ever
* happen in practice.
*/
if (WARN_ON_ONCE(system_state > SYSTEM_RUNNING))
return -EPERM;
if (tegra->core_rdev == rdev) {
tegra->core_rdev = NULL;
return 0;
}
if (tegra->cpu_rdev == rdev) {
tegra->cpu_rdev = NULL;
return 0;
}
return -EINVAL;
}
static struct tegra_regulator_coupler tegra30_coupler = {
.coupler = {
.attach_regulator = tegra30_regulator_attach,
.detach_regulator = tegra30_regulator_detach,
.balance_voltage = tegra30_regulator_balance_voltage,
},
.reboot_notifier.notifier_call = tegra30_regulator_reboot,
.suspend_notifier.notifier_call = tegra30_regulator_suspend,
};
static int __init tegra_regulator_coupler_init(void)
{
int err;
if (!of_machine_is_compatible("nvidia,tegra30"))
return 0;
err = register_reboot_notifier(&tegra30_coupler.reboot_notifier);
WARN_ON(err);
err = register_pm_notifier(&tegra30_coupler.suspend_notifier);
WARN_ON(err);
return regulator_coupler_register(&tegra30_coupler.coupler);
}
arch_initcall(tegra_regulator_coupler_init);