Commit b4067b10 authored by Olof Johansson's avatar Olof Johansson

Merge tag 'tegra-for-5.5-soc' of...

Merge tag 'tegra-for-5.5-soc' of git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux into arm/drivers

soc/tegra: Changes for v5.5-rc1

Adds wake event support on Tegra210, implements the NVMEM API for the
Tegra FUSE block and adds coupled regulators support for Tegra20 and
Tegra30.

* tag 'tegra-for-5.5-soc' of git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux:
  soc/tegra: pmc: Remove unnecessary memory barrier
  soc/tegra: pmc: Query PCLK clock rate at probe time
  soc/tegra: regulators: Add regulators coupler for Tegra30
  soc/tegra: regulators: Add regulators coupler for Tegra20
  soc/tegra: pmc: Configure deep sleep control settings
  soc/tegra: pmc: Configure core power request polarity
  soc/tegra: pmc: Add wake event support on Tegra210
  soc/tegra: pmc: Support wake events on more Tegra SoCs
  soc/tegra: fuse: Register cell lookups for compatibility
  soc/tegra: fuse: Add cell information
  soc/tegra: fuse: Implement nvmem device
  soc/tegra: fuse: Restore base on sysfs failure
  soc/tegra: pmc: Fix crashes for hierarchical interrupts
  soc/tegra: fuse: Add FUSE clock check in tegra_fuse_readl()

Link: https://lore.kernel.org/r/20191102144521.3863321-4-thierry.reding@gmail.comSigned-off-by: default avatarOlof Johansson <olof@lixom.net>
parents f76b6a4c 69dfb3d4
......@@ -15,6 +15,7 @@ config ARCH_TEGRA_2x_SOC
select PL310_ERRATA_769419 if CACHE_L2X0
select SOC_TEGRA_FLOWCTRL
select SOC_TEGRA_PMC
select SOC_TEGRA20_VOLTAGE_COUPLER
select TEGRA_TIMER
help
Support for NVIDIA Tegra AP20 and T20 processors, based on the
......@@ -28,6 +29,7 @@ config ARCH_TEGRA_3x_SOC
select PL310_ERRATA_769419 if CACHE_L2X0
select SOC_TEGRA_FLOWCTRL
select SOC_TEGRA_PMC
select SOC_TEGRA30_VOLTAGE_COUPLER
select TEGRA_TIMER
help
Support for NVIDIA Tegra T30 processor family, based on the
......@@ -135,3 +137,11 @@ config SOC_TEGRA_POWERGATE_BPMP
def_bool y
depends on PM_GENERIC_DOMAINS
depends on TEGRA_BPMP
config SOC_TEGRA20_VOLTAGE_COUPLER
bool "Voltage scaling support for Tegra20 SoCs"
depends on ARCH_TEGRA_2x_SOC || COMPILE_TEST
config SOC_TEGRA30_VOLTAGE_COUPLER
bool "Voltage scaling support for Tegra30 SoCs"
depends on ARCH_TEGRA_3x_SOC || COMPILE_TEST
......@@ -5,3 +5,5 @@ obj-y += common.o
obj-$(CONFIG_SOC_TEGRA_FLOWCTRL) += flowctrl.o
obj-$(CONFIG_SOC_TEGRA_PMC) += pmc.o
obj-$(CONFIG_SOC_TEGRA_POWERGATE_BPMP) += powergate-bpmp.o
obj-$(CONFIG_SOC_TEGRA20_VOLTAGE_COUPLER) += regulators-tegra20.o
obj-$(CONFIG_SOC_TEGRA30_VOLTAGE_COUPLER) += regulators-tegra30.o
......@@ -8,6 +8,8 @@
#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>
......@@ -31,50 +33,6 @@ static const char *tegra_revision_name[TEGRA_REVISION_MAX] = {
[TEGRA_REVISION_A04] = "A04",
};
static u8 fuse_readb(struct tegra_fuse *fuse, unsigned int offset)
{
u32 val;
val = fuse->read(fuse, round_down(offset, 4));
val >>= (offset % 4) * 8;
val &= 0xff;
return val;
}
static ssize_t fuse_read(struct file *fd, struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t pos, size_t size)
{
struct device *dev = kobj_to_dev(kobj);
struct tegra_fuse *fuse = dev_get_drvdata(dev);
int i;
if (pos < 0 || pos >= attr->size)
return 0;
if (size > attr->size - pos)
size = attr->size - pos;
for (i = 0; i < size; i++)
buf[i] = fuse_readb(fuse, pos + i);
return i;
}
static struct bin_attribute fuse_bin_attr = {
.attr = { .name = "fuse", .mode = S_IRUGO, },
.read = fuse_read,
};
static int tegra_fuse_create_sysfs(struct device *dev, unsigned int size,
const struct tegra_fuse_info *info)
{
fuse_bin_attr.size = size;
return device_create_bin_file(dev, &fuse_bin_attr);
}
static const struct of_device_id car_match[] __initconst = {
{ .compatible = "nvidia,tegra20-car", },
{ .compatible = "nvidia,tegra30-car", },
......@@ -115,9 +73,111 @@ static const struct of_device_id tegra_fuse_match[] = {
{ /* sentinel */ }
};
static int tegra_fuse_read(void *priv, unsigned int offset, void *value,
size_t bytes)
{
unsigned int count = bytes / 4, i;
struct tegra_fuse *fuse = priv;
u32 *buffer = value;
for (i = 0; i < count; i++)
buffer[i] = fuse->read(fuse, offset + i * 4);
return 0;
}
static const struct nvmem_cell_info tegra_fuse_cells[] = {
{
.name = "tsensor-cpu1",
.offset = 0x084,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "tsensor-cpu2",
.offset = 0x088,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "tsensor-cpu0",
.offset = 0x098,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "xusb-pad-calibration",
.offset = 0x0f0,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "tsensor-cpu3",
.offset = 0x12c,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "sata-calibration",
.offset = 0x124,
.bytes = 1,
.bit_offset = 0,
.nbits = 2,
}, {
.name = "tsensor-gpu",
.offset = 0x154,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "tsensor-mem0",
.offset = 0x158,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "tsensor-mem1",
.offset = 0x15c,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "tsensor-pllx",
.offset = 0x160,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "tsensor-common",
.offset = 0x180,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "tsensor-realignment",
.offset = 0x1fc,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "gpu-calibration",
.offset = 0x204,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
}, {
.name = "xusb-pad-calibration-ext",
.offset = 0x250,
.bytes = 4,
.bit_offset = 0,
.nbits = 32,
},
};
static int tegra_fuse_probe(struct platform_device *pdev)
{
void __iomem *base = fuse->base;
struct nvmem_config nvmem;
struct resource *res;
int err;
......@@ -146,20 +206,42 @@ static int tegra_fuse_probe(struct platform_device *pdev)
if (fuse->soc->probe) {
err = fuse->soc->probe(fuse);
if (err < 0) {
fuse->base = base;
return err;
}
if (err < 0)
goto restore;
}
if (tegra_fuse_create_sysfs(&pdev->dev, fuse->soc->info->size,
fuse->soc->info))
return -ENODEV;
memset(&nvmem, 0, sizeof(nvmem));
nvmem.dev = &pdev->dev;
nvmem.name = "fuse";
nvmem.id = -1;
nvmem.owner = THIS_MODULE;
nvmem.cells = tegra_fuse_cells;
nvmem.ncells = ARRAY_SIZE(tegra_fuse_cells);
nvmem.type = NVMEM_TYPE_OTP;
nvmem.read_only = true;
nvmem.root_only = true;
nvmem.reg_read = tegra_fuse_read;
nvmem.size = fuse->soc->info->size;
nvmem.word_size = 4;
nvmem.stride = 4;
nvmem.priv = fuse;
fuse->nvmem = devm_nvmem_register(&pdev->dev, &nvmem);
if (IS_ERR(fuse->nvmem)) {
err = PTR_ERR(fuse->nvmem);
dev_err(&pdev->dev, "failed to register NVMEM device: %d\n",
err);
goto restore;
}
/* release the early I/O memory mapping */
iounmap(base);
return 0;
restore:
fuse->base = base;
return err;
}
static struct platform_driver tegra_fuse_driver = {
......@@ -186,9 +268,12 @@ u32 __init tegra_fuse_read_early(unsigned int offset)
int tegra_fuse_readl(unsigned long offset, u32 *value)
{
if (!fuse->read)
if (!fuse->read || !fuse->clk)
return -EPROBE_DEFER;
if (IS_ERR(fuse->clk))
return PTR_ERR(fuse->clk);
*value = fuse->read(fuse, offset);
return 0;
......@@ -338,6 +423,15 @@ static int __init tegra_init_fuse(void)
pr_debug("Tegra CPU Speedo ID %d, SoC Speedo ID %d\n",
tegra_sku_info.cpu_speedo_id, tegra_sku_info.soc_speedo_id);
if (fuse->soc->lookups) {
size_t size = sizeof(*fuse->lookups) * fuse->soc->num_lookups;
fuse->lookups = kmemdup(fuse->soc->lookups, size, GFP_KERNEL);
if (!fuse->lookups)
return -ENOMEM;
nvmem_add_cell_lookups(fuse->lookups, fuse->soc->num_lookups);
}
return 0;
}
......
......@@ -8,6 +8,7 @@
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/nvmem-consumer.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
......@@ -127,6 +128,70 @@ const struct tegra_fuse_soc tegra114_fuse_soc = {
#endif
#if defined(CONFIG_ARCH_TEGRA_124_SOC) || defined(CONFIG_ARCH_TEGRA_132_SOC)
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,
......@@ -137,10 +202,81 @@ 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),
};
#endif
#if defined(CONFIG_ARCH_TEGRA_210_SOC)
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,
......@@ -151,10 +287,26 @@ 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,
.num_lookups = ARRAY_SIZE(tegra210_fuse_lookups),
};
#endif
#if defined(CONFIG_ARCH_TEGRA_186_SOC)
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 tegra_fuse_info tegra186_fuse_info = {
.read = tegra30_fuse_read,
.size = 0x300,
......@@ -164,5 +316,7 @@ static const struct tegra_fuse_info tegra186_fuse_info = {
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),
};
#endif
......@@ -13,6 +13,8 @@
#include <linux/dmaengine.h>
#include <linux/types.h>
struct nvmem_cell_lookup;
struct nvmem_device;
struct tegra_fuse;
struct tegra_fuse_info {
......@@ -27,6 +29,9 @@ struct tegra_fuse_soc {
int (*probe)(struct tegra_fuse *fuse);
const struct tegra_fuse_info *info;
const struct nvmem_cell_lookup *lookups;
unsigned int num_lookups;
};
struct tegra_fuse {
......@@ -48,6 +53,9 @@ struct tegra_fuse {
dma_addr_t phys;
u32 *virt;
} apbdma;
struct nvmem_device *nvmem;
struct nvmem_cell_lookup *lookups;
};
void tegra_init_revision(void);
......
This diff is collapsed.
// 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/regulator/coupler.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
struct tegra_regulator_coupler {
struct regulator_coupler coupler;
struct regulator_dev *core_rdev;
struct regulator_dev *cpu_rdev;
struct regulator_dev *rtc_rdev;
int core_min_uV;
};
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;
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 minimum voltage limited 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_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;
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);
}
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;
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);
}
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;
/*
* 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;
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;
}
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_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);
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,
},
};
static int __init tegra_regulator_coupler_init(void)
{
if (!of_machine_is_compatible("nvidia,tegra20"))
return 0;
return regulator_coupler_register(&tegra20_coupler.coupler);
}
arch_initcall(tegra_regulator_coupler_init);
// 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/regulator/coupler.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <soc/tegra/fuse.h>
struct tegra_regulator_coupler {
struct regulator_coupler coupler;
struct regulator_dev *core_rdev;
struct regulator_dev *cpu_rdev;
int core_min_uV;
};
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;
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 minimum voltage limited 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_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;
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;
/*
* 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;
/*
* 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;
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);
}
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);
}
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;
}
return tegra30_voltage_update(tegra, cpu_rdev, core_rdev);
}
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);
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,
},
};
static int __init tegra_regulator_coupler_init(void)
{
if (!of_machine_is_compatible("nvidia,tegra30"))
return 0;
return regulator_coupler_register(&tegra30_coupler.coupler);
}
arch_initcall(tegra_regulator_coupler_init);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment