mirror of https://gitee.com/openkylin/linux.git
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. -----BEGIN PGP SIGNATURE----- iQJHBAABCAAxFiEEiOrDCAFJzPfAjcif3SOs138+s6EFAl29jjQTHHRyZWRpbmdA bnZpZGlhLmNvbQAKCRDdI6zXfz6zofJuD/wPm0lAfLChLdGxTtm6rGnxpZ+fRZaX uGXMobqV2jLnag3MO8Z4yHOyEjjuRqCcBakMNVq/BTo7H2XsfCoah5whiFpox2WI M/S/obM1DrqVz2J7aM5oK/GFr19px1T1up/gRoxApsW2NPxMz31Qv3qHw2eo0Oys MgIZb+jHQXczkiVtvzMtnqs+tWVe0UwP4eD0Dc0JVDqR/caJjPXIR1WTRHcpbmVG h7nUCS7wApJdtb4BPC0duEZlAs199Safzk4ReOUuHOUhprdzvYrKeB8xG5j8ykmz hKWLtFQ8vGEtx75uPPUOJE1JYrFvqPtlI97TaQTbxwGqlckN2F5OlK1CTfRXCPGa M2AtRujMRBH69FlhJbYW+7IqnpISMNhx0tRh3JbxKspbRf0iM4Vm8XUpKOiG21Na OBYOM2Zc/p8IPkevnkYsqBbk7/sRxk99Kgf1hK7rbq4NhzU4F5XxTJ1WFgENXm8b nU5TUfvHIs/Z1DVuE6A1p+WVvbBfEfe5ThSU5cMma+ey6fJLbHJxYEa/VhCQq+mA AHbcQ1uosSAFXPkrhtHA9wMgvL/SaJNH0Jb60g5QdGGeMW3oLpu3VQTPwzrqPUev 8UBT9SV3y4PGVcNIRxXC9Crakb1XKbbu6vmA+U0zSDiH/O+mh/C0K4PGkSNh1HUc OouYcJcM16ZW9w== =CwCM -----END PGP SIGNATURE----- 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.com Signed-off-by: Olof Johansson <olof@lixom.net>
This commit is contained in:
commit
b4067b1050
|
@ -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);
|
||||
|
|
|
@ -56,8 +56,14 @@
|
|||
#define PMC_CNTRL_SIDE_EFFECT_LP0 BIT(14) /* LP0 when CPU pwr gated */
|
||||
#define PMC_CNTRL_SYSCLK_OE BIT(11) /* system clock enable */
|
||||
#define PMC_CNTRL_SYSCLK_POLARITY BIT(10) /* sys clk polarity */
|
||||
#define PMC_CNTRL_PWRREQ_POLARITY BIT(8)
|
||||
#define PMC_CNTRL_MAIN_RST BIT(4)
|
||||
|
||||
#define PMC_WAKE_MASK 0x0c
|
||||
#define PMC_WAKE_LEVEL 0x10
|
||||
#define PMC_WAKE_STATUS 0x14
|
||||
#define PMC_SW_WAKE_STATUS 0x18
|
||||
|
||||
#define DPD_SAMPLE 0x020
|
||||
#define DPD_SAMPLE_ENABLE BIT(0)
|
||||
#define DPD_SAMPLE_DISABLE (0 << 0)
|
||||
|
@ -82,11 +88,18 @@
|
|||
|
||||
#define PMC_CPUPWRGOOD_TIMER 0xc8
|
||||
#define PMC_CPUPWROFF_TIMER 0xcc
|
||||
#define PMC_COREPWRGOOD_TIMER 0x3c
|
||||
#define PMC_COREPWROFF_TIMER 0xe0
|
||||
|
||||
#define PMC_PWR_DET_VALUE 0xe4
|
||||
|
||||
#define PMC_SCRATCH41 0x140
|
||||
|
||||
#define PMC_WAKE2_MASK 0x160
|
||||
#define PMC_WAKE2_LEVEL 0x164
|
||||
#define PMC_WAKE2_STATUS 0x168
|
||||
#define PMC_SW_WAKE2_STATUS 0x16c
|
||||
|
||||
#define PMC_SENSOR_CTRL 0x1b0
|
||||
#define PMC_SENSOR_CTRL_SCRATCH_WRITE BIT(2)
|
||||
#define PMC_SENSOR_CTRL_ENABLE_RST BIT(1)
|
||||
|
@ -226,6 +239,8 @@ struct tegra_pmc_soc {
|
|||
void (*setup_irq_polarity)(struct tegra_pmc *pmc,
|
||||
struct device_node *np,
|
||||
bool invert);
|
||||
int (*irq_set_wake)(struct irq_data *data, unsigned int on);
|
||||
int (*irq_set_type)(struct irq_data *data, unsigned int type);
|
||||
|
||||
const char * const *reset_sources;
|
||||
unsigned int num_reset_sources;
|
||||
|
@ -309,6 +324,7 @@ static const char * const tegra210_reset_sources[] = {
|
|||
* @pctl_dev: pin controller exposed by the PMC
|
||||
* @domain: IRQ domain provided by the PMC
|
||||
* @irq: chip implementation for the IRQ domain
|
||||
* @clk_nb: pclk clock changes handler
|
||||
*/
|
||||
struct tegra_pmc {
|
||||
struct device *dev;
|
||||
|
@ -344,6 +360,8 @@ struct tegra_pmc {
|
|||
|
||||
struct irq_domain *domain;
|
||||
struct irq_chip irq;
|
||||
|
||||
struct notifier_block clk_nb;
|
||||
};
|
||||
|
||||
static struct tegra_pmc *pmc = &(struct tegra_pmc) {
|
||||
|
@ -1192,7 +1210,7 @@ static int tegra_io_pad_prepare(struct tegra_pmc *pmc, enum tegra_io_pad id,
|
|||
return err;
|
||||
|
||||
if (pmc->clk) {
|
||||
rate = clk_get_rate(pmc->clk);
|
||||
rate = pmc->rate;
|
||||
if (!rate) {
|
||||
dev_err(pmc->dev, "failed to get clock rate\n");
|
||||
return -ENODEV;
|
||||
|
@ -1433,6 +1451,7 @@ void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode)
|
|||
void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode)
|
||||
{
|
||||
unsigned long long rate = 0;
|
||||
u64 ticks;
|
||||
u32 value;
|
||||
|
||||
switch (mode) {
|
||||
|
@ -1441,7 +1460,7 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode)
|
|||
break;
|
||||
|
||||
case TEGRA_SUSPEND_LP2:
|
||||
rate = clk_get_rate(pmc->clk);
|
||||
rate = pmc->rate;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -1451,21 +1470,13 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode)
|
|||
if (WARN_ON_ONCE(rate == 0))
|
||||
rate = 100000000;
|
||||
|
||||
if (rate != pmc->rate) {
|
||||
u64 ticks;
|
||||
ticks = pmc->cpu_good_time * rate + USEC_PER_SEC - 1;
|
||||
do_div(ticks, USEC_PER_SEC);
|
||||
tegra_pmc_writel(pmc, ticks, PMC_CPUPWRGOOD_TIMER);
|
||||
|
||||
ticks = pmc->cpu_good_time * rate + USEC_PER_SEC - 1;
|
||||
do_div(ticks, USEC_PER_SEC);
|
||||
tegra_pmc_writel(pmc, ticks, PMC_CPUPWRGOOD_TIMER);
|
||||
|
||||
ticks = pmc->cpu_off_time * rate + USEC_PER_SEC - 1;
|
||||
do_div(ticks, USEC_PER_SEC);
|
||||
tegra_pmc_writel(pmc, ticks, PMC_CPUPWROFF_TIMER);
|
||||
|
||||
wmb();
|
||||
|
||||
pmc->rate = rate;
|
||||
}
|
||||
ticks = pmc->cpu_off_time * rate + USEC_PER_SEC - 1;
|
||||
do_div(ticks, USEC_PER_SEC);
|
||||
tegra_pmc_writel(pmc, ticks, PMC_CPUPWROFF_TIMER);
|
||||
|
||||
value = tegra_pmc_readl(pmc, PMC_CNTRL);
|
||||
value &= ~PMC_CNTRL_SIDE_EFFECT_LP0;
|
||||
|
@ -1899,6 +1910,20 @@ static int tegra_pmc_irq_alloc(struct irq_domain *domain, unsigned int virq,
|
|||
event->id,
|
||||
&pmc->irq, pmc);
|
||||
|
||||
/*
|
||||
* GPIOs don't have an equivalent interrupt in the
|
||||
* parent controller (GIC). However some code, such
|
||||
* as the one in irq_get_irqchip_state(), require a
|
||||
* valid IRQ chip to be set. Make sure that's the
|
||||
* case by passing NULL here, which will install a
|
||||
* dummy IRQ chip for the interrupt in the parent
|
||||
* domain.
|
||||
*/
|
||||
if (domain->parent)
|
||||
irq_domain_set_hwirq_and_chip(domain->parent,
|
||||
virq, 0, NULL,
|
||||
NULL);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1908,10 +1933,22 @@ static int tegra_pmc_irq_alloc(struct irq_domain *domain, unsigned int virq,
|
|||
* dummy hardware IRQ number. This is used in the ->irq_set_type()
|
||||
* and ->irq_set_wake() callbacks to return early for these IRQs.
|
||||
*/
|
||||
if (i == soc->num_wake_events)
|
||||
if (i == soc->num_wake_events) {
|
||||
err = irq_domain_set_hwirq_and_chip(domain, virq, ULONG_MAX,
|
||||
&pmc->irq, pmc);
|
||||
|
||||
/*
|
||||
* Interrupts without a wake event don't have a corresponding
|
||||
* interrupt in the parent controller (GIC). Pass NULL for the
|
||||
* chip here, which causes a dummy IRQ chip to be installed
|
||||
* for the interrupt in the parent domain, to make this
|
||||
* explicit.
|
||||
*/
|
||||
if (domain->parent)
|
||||
irq_domain_set_hwirq_and_chip(domain->parent, virq, 0,
|
||||
NULL, NULL);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -1920,7 +1957,87 @@ static const struct irq_domain_ops tegra_pmc_irq_domain_ops = {
|
|||
.alloc = tegra_pmc_irq_alloc,
|
||||
};
|
||||
|
||||
static int tegra_pmc_irq_set_wake(struct irq_data *data, unsigned int on)
|
||||
static int tegra210_pmc_irq_set_wake(struct irq_data *data, unsigned int on)
|
||||
{
|
||||
struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data);
|
||||
unsigned int offset, bit;
|
||||
u32 value;
|
||||
|
||||
if (data->hwirq == ULONG_MAX)
|
||||
return 0;
|
||||
|
||||
offset = data->hwirq / 32;
|
||||
bit = data->hwirq % 32;
|
||||
|
||||
/* clear wake status */
|
||||
tegra_pmc_writel(pmc, 0, PMC_SW_WAKE_STATUS);
|
||||
tegra_pmc_writel(pmc, 0, PMC_SW_WAKE2_STATUS);
|
||||
|
||||
tegra_pmc_writel(pmc, 0, PMC_WAKE_STATUS);
|
||||
tegra_pmc_writel(pmc, 0, PMC_WAKE2_STATUS);
|
||||
|
||||
/* enable PMC wake */
|
||||
if (data->hwirq >= 32)
|
||||
offset = PMC_WAKE2_MASK;
|
||||
else
|
||||
offset = PMC_WAKE_MASK;
|
||||
|
||||
value = tegra_pmc_readl(pmc, offset);
|
||||
|
||||
if (on)
|
||||
value |= BIT(bit);
|
||||
else
|
||||
value &= ~BIT(bit);
|
||||
|
||||
tegra_pmc_writel(pmc, value, offset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra210_pmc_irq_set_type(struct irq_data *data, unsigned int type)
|
||||
{
|
||||
struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data);
|
||||
unsigned int offset, bit;
|
||||
u32 value;
|
||||
|
||||
if (data->hwirq == ULONG_MAX)
|
||||
return 0;
|
||||
|
||||
offset = data->hwirq / 32;
|
||||
bit = data->hwirq % 32;
|
||||
|
||||
if (data->hwirq >= 32)
|
||||
offset = PMC_WAKE2_LEVEL;
|
||||
else
|
||||
offset = PMC_WAKE_LEVEL;
|
||||
|
||||
value = tegra_pmc_readl(pmc, offset);
|
||||
|
||||
switch (type) {
|
||||
case IRQ_TYPE_EDGE_RISING:
|
||||
case IRQ_TYPE_LEVEL_HIGH:
|
||||
value |= BIT(bit);
|
||||
break;
|
||||
|
||||
case IRQ_TYPE_EDGE_FALLING:
|
||||
case IRQ_TYPE_LEVEL_LOW:
|
||||
value &= ~BIT(bit);
|
||||
break;
|
||||
|
||||
case IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING:
|
||||
value ^= BIT(bit);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
tegra_pmc_writel(pmc, value, offset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra186_pmc_irq_set_wake(struct irq_data *data, unsigned int on)
|
||||
{
|
||||
struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data);
|
||||
unsigned int offset, bit;
|
||||
|
@ -1952,7 +2069,7 @@ static int tegra_pmc_irq_set_wake(struct irq_data *data, unsigned int on)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_pmc_irq_set_type(struct irq_data *data, unsigned int type)
|
||||
static int tegra186_pmc_irq_set_type(struct irq_data *data, unsigned int type)
|
||||
{
|
||||
struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data);
|
||||
u32 value;
|
||||
|
@ -2006,8 +2123,8 @@ static int tegra_pmc_irq_init(struct tegra_pmc *pmc)
|
|||
pmc->irq.irq_unmask = irq_chip_unmask_parent;
|
||||
pmc->irq.irq_eoi = irq_chip_eoi_parent;
|
||||
pmc->irq.irq_set_affinity = irq_chip_set_affinity_parent;
|
||||
pmc->irq.irq_set_type = tegra_pmc_irq_set_type;
|
||||
pmc->irq.irq_set_wake = tegra_pmc_irq_set_wake;
|
||||
pmc->irq.irq_set_type = pmc->soc->irq_set_type;
|
||||
pmc->irq.irq_set_wake = pmc->soc->irq_set_wake;
|
||||
|
||||
pmc->domain = irq_domain_add_hierarchy(parent, 0, 96, pmc->dev->of_node,
|
||||
&tegra_pmc_irq_domain_ops, pmc);
|
||||
|
@ -2019,6 +2136,33 @@ static int tegra_pmc_irq_init(struct tegra_pmc *pmc)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_pmc_clk_notify_cb(struct notifier_block *nb,
|
||||
unsigned long action, void *ptr)
|
||||
{
|
||||
struct tegra_pmc *pmc = container_of(nb, struct tegra_pmc, clk_nb);
|
||||
struct clk_notifier_data *data = ptr;
|
||||
|
||||
switch (action) {
|
||||
case PRE_RATE_CHANGE:
|
||||
mutex_lock(&pmc->powergates_lock);
|
||||
break;
|
||||
|
||||
case POST_RATE_CHANGE:
|
||||
pmc->rate = data->new_rate;
|
||||
/* fall through */
|
||||
|
||||
case ABORT_RATE_CHANGE:
|
||||
mutex_unlock(&pmc->powergates_lock);
|
||||
break;
|
||||
|
||||
default:
|
||||
WARN_ON_ONCE(1);
|
||||
return notifier_from_errno(-EINVAL);
|
||||
}
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static int tegra_pmc_probe(struct platform_device *pdev)
|
||||
{
|
||||
void __iomem *base;
|
||||
|
@ -2082,6 +2226,23 @@ static int tegra_pmc_probe(struct platform_device *pdev)
|
|||
pmc->clk = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* PCLK clock rate can't be retrieved using CLK API because it
|
||||
* causes lockup if CPU enters LP2 idle state from some other
|
||||
* CLK notifier, hence we're caching the rate's value locally.
|
||||
*/
|
||||
if (pmc->clk) {
|
||||
pmc->clk_nb.notifier_call = tegra_pmc_clk_notify_cb;
|
||||
err = clk_notifier_register(pmc->clk, &pmc->clk_nb);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev,
|
||||
"failed to register clk notifier\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
pmc->rate = clk_get_rate(pmc->clk);
|
||||
}
|
||||
|
||||
pmc->dev = &pdev->dev;
|
||||
|
||||
tegra_pmc_init(pmc);
|
||||
|
@ -2133,6 +2294,8 @@ static int tegra_pmc_probe(struct platform_device *pdev)
|
|||
cleanup_sysfs:
|
||||
device_remove_file(&pdev->dev, &dev_attr_reset_reason);
|
||||
device_remove_file(&pdev->dev, &dev_attr_reset_level);
|
||||
clk_notifier_unregister(pmc->clk, &pmc->clk_nb);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -2184,7 +2347,7 @@ static const struct tegra_pmc_regs tegra20_pmc_regs = {
|
|||
|
||||
static void tegra20_pmc_init(struct tegra_pmc *pmc)
|
||||
{
|
||||
u32 value;
|
||||
u32 value, osc, pmu, off;
|
||||
|
||||
/* Always enable CPU power request */
|
||||
value = tegra_pmc_readl(pmc, PMC_CNTRL);
|
||||
|
@ -2198,6 +2361,11 @@ static void tegra20_pmc_init(struct tegra_pmc *pmc)
|
|||
else
|
||||
value |= PMC_CNTRL_SYSCLK_POLARITY;
|
||||
|
||||
if (pmc->corereq_high)
|
||||
value &= ~PMC_CNTRL_PWRREQ_POLARITY;
|
||||
else
|
||||
value |= PMC_CNTRL_PWRREQ_POLARITY;
|
||||
|
||||
/* configure the output polarity while the request is tristated */
|
||||
tegra_pmc_writel(pmc, value, PMC_CNTRL);
|
||||
|
||||
|
@ -2205,6 +2373,16 @@ static void tegra20_pmc_init(struct tegra_pmc *pmc)
|
|||
value = tegra_pmc_readl(pmc, PMC_CNTRL);
|
||||
value |= PMC_CNTRL_SYSCLK_OE;
|
||||
tegra_pmc_writel(pmc, value, PMC_CNTRL);
|
||||
|
||||
/* program core timings which are applicable only for suspend state */
|
||||
if (pmc->suspend_mode != TEGRA_SUSPEND_NONE) {
|
||||
osc = DIV_ROUND_UP(pmc->core_osc_time * 8192, 1000000);
|
||||
pmu = DIV_ROUND_UP(pmc->core_pmu_time * 32768, 1000000);
|
||||
off = DIV_ROUND_UP(pmc->core_off_time * 32768, 1000000);
|
||||
tegra_pmc_writel(pmc, ((osc << 8) & 0xff00) | (pmu & 0xff),
|
||||
PMC_COREPWRGOOD_TIMER);
|
||||
tegra_pmc_writel(pmc, off, PMC_COREPWROFF_TIMER);
|
||||
}
|
||||
}
|
||||
|
||||
static void tegra20_pmc_setup_irq_polarity(struct tegra_pmc *pmc,
|
||||
|
@ -2538,6 +2716,10 @@ static const struct pinctrl_pin_desc tegra210_pin_descs[] = {
|
|||
TEGRA210_IO_PAD_TABLE(TEGRA_IO_PIN_DESC)
|
||||
};
|
||||
|
||||
static const struct tegra_wake_event tegra210_wake_events[] = {
|
||||
TEGRA_WAKE_IRQ("rtc", 16, 2),
|
||||
};
|
||||
|
||||
static const struct tegra_pmc_soc tegra210_pmc_soc = {
|
||||
.num_powergates = ARRAY_SIZE(tegra210_powergates),
|
||||
.powergates = tegra210_powergates,
|
||||
|
@ -2555,10 +2737,14 @@ static const struct tegra_pmc_soc tegra210_pmc_soc = {
|
|||
.regs = &tegra20_pmc_regs,
|
||||
.init = tegra20_pmc_init,
|
||||
.setup_irq_polarity = tegra20_pmc_setup_irq_polarity,
|
||||
.irq_set_wake = tegra210_pmc_irq_set_wake,
|
||||
.irq_set_type = tegra210_pmc_irq_set_type,
|
||||
.reset_sources = tegra210_reset_sources,
|
||||
.num_reset_sources = ARRAY_SIZE(tegra210_reset_sources),
|
||||
.reset_levels = NULL,
|
||||
.num_reset_levels = 0,
|
||||
.num_wake_events = ARRAY_SIZE(tegra210_wake_events),
|
||||
.wake_events = tegra210_wake_events,
|
||||
};
|
||||
|
||||
#define TEGRA186_IO_PAD_TABLE(_pad) \
|
||||
|
@ -2680,6 +2866,8 @@ static const struct tegra_pmc_soc tegra186_pmc_soc = {
|
|||
.regs = &tegra186_pmc_regs,
|
||||
.init = NULL,
|
||||
.setup_irq_polarity = tegra186_pmc_setup_irq_polarity,
|
||||
.irq_set_wake = tegra186_pmc_irq_set_wake,
|
||||
.irq_set_type = tegra186_pmc_irq_set_type,
|
||||
.reset_sources = tegra186_reset_sources,
|
||||
.num_reset_sources = ARRAY_SIZE(tegra186_reset_sources),
|
||||
.reset_levels = tegra186_reset_levels,
|
||||
|
|
|
@ -0,0 +1,365 @@
|
|||
// 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);
|
|
@ -0,0 +1,317 @@
|
|||
// 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);
|
Loading…
Reference in New Issue