soc/tegra: Changes for v5.14-rc1
These changes implement the core power domain for the PMC, and fix a couple of minor issues as well as add stubs to help some drivers be compile tested more easily. -----BEGIN PGP SIGNATURE----- iQJHBAABCAAxFiEEiOrDCAFJzPfAjcif3SOs138+s6EFAmDDjV4THHRyZWRpbmdA bnZpZGlhLmNvbQAKCRDdI6zXfz6zoXVsD/0QgZHiuEH8rU42JtYGB/rjCskWUPen BOtcrIi+QxJ7m8q4qnRrPIeE8ETjQAB92pWpZE8VQ260Pk/s8NAeIXBbP7I2lKU8 M+TkwvCQ8zZw18q9ZPinqSUHgVsh7Q+7kQggHGZcYpE4rOmlb0oTr9B2EprdSpBl hVNGtcaJ9Ovqri1ioDi/owUaxYpeJ3L8A++luIv9K8oDUpN7GBw3nV0dMOBvUOBs nT9iQFEThvo0NRNs4zPNDPLPsLJFwePRDmY9SE4PCreDKo26+VWAamooW8R6iEnU P7OOfCSZbLqMs8Sfkjrb742sqWX50okFFJQG6aW3BOln3IA5esc9uJ3l2jH6GZP1 21hestxJjK2JuZLv8uMtGHura8do7e4PrYVvxOT6Lp85AXaXA1/uieAWRyh40lcD d4S4hL3prlfgepj6eUy40MnbLbatsRyQoFYdloZtB+cZb+KxMSV/Lx+j0Y5VyOI/ OpVcTxTxwAZRrVcHTISkUjkxLProY1hQwQP9vexRMIn8SB+ZhgztjHJQuUxa25at c2Q1aOeGr+h3w1NYpOa+u1tyyKt/Vw92tA84MHaoGoWJg2DGXDAh66HFpiFrmE8q +n2wc/uyhz1bdS38ky73ibJ6iQ96ucRLxEjCIz+59hdZ57bEYnF+EUJtNdMSyqXg hGVs5fnWFlw97Q== =g0AU -----END PGP SIGNATURE----- Merge tag 'tegra-for-5.14-soc' of git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux into arm/soc soc/tegra: Changes for v5.14-rc1 These changes implement the core power domain for the PMC, and fix a couple of minor issues as well as add stubs to help some drivers be compile tested more easily. * tag 'tegra-for-5.14-soc' of git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux: soc/tegra: fuse: Fix Tegra234-only builds soc/tegra: fuse: Don't return -ENOMEM when allocate lookups failed soc/tegra: regulators: Support core domain state syncing soc/tegra: pmc: Add driver state syncing soc/tegra: pmc: Add core power domain soc/tegra: fuse: Add stubs needed for compile-testing soc/tegra: Add devm_tegra_core_dev_init_opp_table() soc/tegra: Add stub for soc_is_tegra() soc/tegra: regulators: Bump voltages on system reboot regulator: core: Add regulator_sync_voltage_rdev() Link: https://lore.kernel.org/r/20210611164437.3568059-2-thierry.reding@gmail.com Signed-off-by: Olof Johansson <olof@lixom.net>
This commit is contained in:
commit
777cf27fb2
@ -4105,6 +4105,29 @@ int regulator_set_voltage_time_sel(struct regulator_dev *rdev,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regulator_set_voltage_time_sel);
|
||||
|
||||
int regulator_sync_voltage_rdev(struct regulator_dev *rdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
regulator_lock(rdev);
|
||||
|
||||
if (!rdev->desc->ops->set_voltage &&
|
||||
!rdev->desc->ops->set_voltage_sel) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* balance only, if regulator is coupled */
|
||||
if (rdev->coupling_desc.n_coupled > 1)
|
||||
ret = regulator_balance_voltage(rdev, PM_SUSPEND_ON);
|
||||
else
|
||||
ret = -EOPNOTSUPP;
|
||||
|
||||
out:
|
||||
regulator_unlock(rdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* regulator_sync_voltage - re-apply last regulator output voltage
|
||||
* @regulator: regulator source
|
||||
|
@ -144,6 +144,8 @@ config SOC_TEGRA_FLOWCTRL
|
||||
config SOC_TEGRA_PMC
|
||||
bool
|
||||
select GENERIC_PINCONF
|
||||
select PM_OPP
|
||||
select PM_GENERIC_DOMAINS
|
||||
|
||||
config SOC_TEGRA_POWERGATE_BPMP
|
||||
def_bool y
|
||||
|
@ -3,9 +3,16 @@
|
||||
* 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 <soc/tegra/common.h>
|
||||
#include <soc/tegra/fuse.h>
|
||||
|
||||
static const struct of_device_id tegra_machine_match[] = {
|
||||
{ .compatible = "nvidia,tegra20", },
|
||||
@ -31,3 +38,93 @@ bool soc_is_tegra(void)
|
||||
|
||||
return match != NULL;
|
||||
}
|
||||
|
||||
static int tegra_core_dev_init_opp_state(struct device *dev)
|
||||
{
|
||||
unsigned long rate;
|
||||
struct clk *clk;
|
||||
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;
|
||||
}
|
||||
|
||||
/* first dummy rate-setting initializes voltage vote */
|
||||
err = dev_pm_opp_set_rate(dev, rate);
|
||||
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;
|
||||
|
||||
err = devm_pm_opp_set_clkname(dev, NULL);
|
||||
if (err) {
|
||||
dev_err(dev, "failed to set OPP clk: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Tegra114+ doesn't support OPP yet */
|
||||
if (!of_machine_is_compatible("nvidia,tegra20") &&
|
||||
!of_machine_is_compatible("nvidia,tegra30"))
|
||||
return -ENODEV;
|
||||
|
||||
if (of_machine_is_compatible("nvidia,tegra20"))
|
||||
hw_version = BIT(tegra_sku_info.soc_process_id);
|
||||
else
|
||||
hw_version = BIT(tegra_sku_info.soc_speedo_id);
|
||||
|
||||
err = devm_pm_opp_set_supported_hw(dev, &hw_version, 1);
|
||||
if (err) {
|
||||
dev_err(dev, "failed to set OPP supported HW: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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_once(dev, "OPP table not found, please update device-tree\n");
|
||||
else
|
||||
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);
|
||||
|
@ -489,10 +489,8 @@ static int __init tegra_init_fuse(void)
|
||||
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);
|
||||
if (fuse->lookups)
|
||||
nvmem_add_cell_lookups(fuse->lookups, fuse->soc->num_lookups);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -37,7 +37,8 @@
|
||||
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_194_SOC) || \
|
||||
defined(CONFIG_ARCH_TEGRA_234_SOC)
|
||||
static u32 tegra30_fuse_read_early(struct tegra_fuse *fuse, unsigned int offset)
|
||||
{
|
||||
if (WARN_ON(!fuse->base))
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include <linux/pinctrl/pinctrl.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_domain.h>
|
||||
#include <linux/pm_opp.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/reset.h>
|
||||
@ -428,6 +429,9 @@ struct tegra_pmc {
|
||||
struct irq_chip irq;
|
||||
|
||||
struct notifier_block clk_nb;
|
||||
|
||||
bool core_domain_state_synced;
|
||||
bool core_domain_registered;
|
||||
};
|
||||
|
||||
static struct tegra_pmc *pmc = &(struct tegra_pmc) {
|
||||
@ -1302,12 +1306,107 @@ free_mem:
|
||||
return err;
|
||||
}
|
||||
|
||||
bool tegra_pmc_core_domain_state_synced(void)
|
||||
{
|
||||
return pmc->core_domain_state_synced;
|
||||
}
|
||||
|
||||
static int
|
||||
tegra_pmc_core_pd_set_performance_state(struct generic_pm_domain *genpd,
|
||||
unsigned int level)
|
||||
{
|
||||
struct dev_pm_opp *opp;
|
||||
int err;
|
||||
|
||||
opp = dev_pm_opp_find_level_ceil(&genpd->dev, &level);
|
||||
if (IS_ERR(opp)) {
|
||||
dev_err(&genpd->dev, "failed to find OPP for level %u: %pe\n",
|
||||
level, opp);
|
||||
return PTR_ERR(opp);
|
||||
}
|
||||
|
||||
mutex_lock(&pmc->powergates_lock);
|
||||
err = dev_pm_opp_set_opp(pmc->dev, opp);
|
||||
mutex_unlock(&pmc->powergates_lock);
|
||||
|
||||
dev_pm_opp_put(opp);
|
||||
|
||||
if (err) {
|
||||
dev_err(&genpd->dev, "failed to set voltage to %duV: %d\n",
|
||||
level, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
tegra_pmc_core_pd_opp_to_performance_state(struct generic_pm_domain *genpd,
|
||||
struct dev_pm_opp *opp)
|
||||
{
|
||||
return dev_pm_opp_get_level(opp);
|
||||
}
|
||||
|
||||
static int tegra_pmc_core_pd_add(struct tegra_pmc *pmc, struct device_node *np)
|
||||
{
|
||||
struct generic_pm_domain *genpd;
|
||||
const char *rname = "core";
|
||||
int err;
|
||||
|
||||
genpd = devm_kzalloc(pmc->dev, sizeof(*genpd), GFP_KERNEL);
|
||||
if (!genpd)
|
||||
return -ENOMEM;
|
||||
|
||||
genpd->name = np->name;
|
||||
genpd->set_performance_state = tegra_pmc_core_pd_set_performance_state;
|
||||
genpd->opp_to_performance_state = tegra_pmc_core_pd_opp_to_performance_state;
|
||||
|
||||
err = devm_pm_opp_set_regulators(pmc->dev, &rname, 1);
|
||||
if (err)
|
||||
return dev_err_probe(pmc->dev, err,
|
||||
"failed to set core OPP regulator\n");
|
||||
|
||||
err = pm_genpd_init(genpd, NULL, false);
|
||||
if (err) {
|
||||
dev_err(pmc->dev, "failed to init core genpd: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = of_genpd_add_provider_simple(np, genpd);
|
||||
if (err) {
|
||||
dev_err(pmc->dev, "failed to add core genpd: %d\n", err);
|
||||
goto remove_genpd;
|
||||
}
|
||||
|
||||
pmc->core_domain_registered = true;
|
||||
|
||||
return 0;
|
||||
|
||||
remove_genpd:
|
||||
pm_genpd_remove(genpd);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int tegra_powergate_init(struct tegra_pmc *pmc,
|
||||
struct device_node *parent)
|
||||
{
|
||||
struct of_phandle_args child_args, parent_args;
|
||||
struct device_node *np, *child;
|
||||
int err = 0;
|
||||
|
||||
/*
|
||||
* Core power domain is the parent of powergate domains, hence it
|
||||
* should be registered first.
|
||||
*/
|
||||
np = of_get_child_by_name(parent, "core-domain");
|
||||
if (np) {
|
||||
err = tegra_pmc_core_pd_add(pmc, np);
|
||||
of_node_put(np);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
np = of_get_child_by_name(parent, "powergates");
|
||||
if (!np)
|
||||
return 0;
|
||||
@ -1318,6 +1417,21 @@ static int tegra_powergate_init(struct tegra_pmc *pmc,
|
||||
of_node_put(child);
|
||||
break;
|
||||
}
|
||||
|
||||
if (of_parse_phandle_with_args(child, "power-domains",
|
||||
"#power-domain-cells",
|
||||
0, &parent_args))
|
||||
continue;
|
||||
|
||||
child_args.np = child;
|
||||
child_args.args_count = 0;
|
||||
|
||||
err = of_genpd_add_subdomain(&parent_args, &child_args);
|
||||
of_node_put(parent_args.np);
|
||||
if (err) {
|
||||
of_node_put(child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
of_node_put(np);
|
||||
@ -1361,6 +1475,12 @@ static void tegra_powergate_remove_all(struct device_node *parent)
|
||||
}
|
||||
|
||||
of_node_put(np);
|
||||
|
||||
np = of_get_child_by_name(parent, "core-domain");
|
||||
if (np) {
|
||||
of_genpd_del_provider(np);
|
||||
of_genpd_remove_last(np);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct tegra_io_pad_soc *
|
||||
@ -3672,6 +3792,29 @@ static const struct of_device_id tegra_pmc_match[] = {
|
||||
{ }
|
||||
};
|
||||
|
||||
static void tegra_pmc_sync_state(struct device *dev)
|
||||
{
|
||||
int err;
|
||||
|
||||
/*
|
||||
* Older device-trees don't have core PD, and thus, there are
|
||||
* no dependencies that will block the state syncing. We shouldn't
|
||||
* mark the domain as synced in this case.
|
||||
*/
|
||||
if (!pmc->core_domain_registered)
|
||||
return;
|
||||
|
||||
pmc->core_domain_state_synced = true;
|
||||
|
||||
/* this is a no-op if core regulator isn't used */
|
||||
mutex_lock(&pmc->powergates_lock);
|
||||
err = dev_pm_opp_sync_regulators(dev);
|
||||
mutex_unlock(&pmc->powergates_lock);
|
||||
|
||||
if (err)
|
||||
dev_err(dev, "failed to sync regulators: %d\n", err);
|
||||
}
|
||||
|
||||
static struct platform_driver tegra_pmc_driver = {
|
||||
.driver = {
|
||||
.name = "tegra-pmc",
|
||||
@ -3680,6 +3823,7 @@ static struct platform_driver tegra_pmc_driver = {
|
||||
#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_ARM)
|
||||
.pm = &tegra_pmc_pm_ops,
|
||||
#endif
|
||||
.sync_state = tegra_pmc_sync_state,
|
||||
},
|
||||
.probe = tegra_pmc_probe,
|
||||
};
|
||||
|
@ -12,16 +12,22 @@
|
||||
#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 <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;
|
||||
int core_min_uV;
|
||||
struct notifier_block reboot_notifier;
|
||||
int core_min_uV, cpu_min_uV;
|
||||
bool sys_reboot_mode_req;
|
||||
bool sys_reboot_mode;
|
||||
};
|
||||
|
||||
static inline struct tegra_regulator_coupler *
|
||||
@ -38,6 +44,21 @@ static int tegra20_core_limit(struct tegra_regulator_coupler *tegra,
|
||||
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;
|
||||
|
||||
@ -58,7 +79,7 @@ static int tegra20_core_limit(struct tegra_regulator_coupler *tegra,
|
||||
*/
|
||||
tegra->core_min_uV = core_max_uV;
|
||||
|
||||
pr_info("core minimum voltage limited to %duV\n", tegra->core_min_uV);
|
||||
pr_info("core voltage initialized to %duV\n", tegra->core_min_uV);
|
||||
|
||||
return tegra->core_min_uV;
|
||||
}
|
||||
@ -242,6 +263,10 @@ static int tegra20_cpu_voltage_update(struct tegra_regulator_coupler *tegra,
|
||||
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
|
||||
@ -250,6 +275,10 @@ static int tegra20_cpu_voltage_update(struct tegra_regulator_coupler *tegra,
|
||||
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);
|
||||
|
||||
if (cpu_min_uV > cpu_uV) {
|
||||
err = tegra20_core_rtc_update(tegra, core_rdev, rtc_rdev,
|
||||
cpu_uV, cpu_min_uV);
|
||||
@ -290,6 +319,8 @@ static int tegra20_regulator_balance_voltage(struct regulator_coupler *coupler,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
tegra->sys_reboot_mode = READ_ONCE(tegra->sys_reboot_mode_req);
|
||||
|
||||
if (rdev == cpu_rdev)
|
||||
return tegra20_cpu_voltage_update(tegra, cpu_rdev,
|
||||
core_rdev, rtc_rdev);
|
||||
@ -303,6 +334,51 @@ static int tegra20_regulator_balance_voltage(struct regulator_coupler *coupler,
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@ -335,6 +411,14 @@ static int tegra20_regulator_detach(struct regulator_coupler *coupler,
|
||||
{
|
||||
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;
|
||||
@ -359,13 +443,19 @@ static struct tegra_regulator_coupler tegra20_coupler = {
|
||||
.detach_regulator = tegra20_regulator_detach,
|
||||
.balance_voltage = tegra20_regulator_balance_voltage,
|
||||
},
|
||||
.reboot_notifier.notifier_call = tegra20_regulator_reboot,
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
return regulator_coupler_register(&tegra20_coupler.coupler);
|
||||
}
|
||||
arch_initcall(tegra_regulator_coupler_init);
|
||||
|
@ -12,17 +12,22 @@
|
||||
#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 <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;
|
||||
int core_min_uV;
|
||||
struct notifier_block reboot_notifier;
|
||||
int core_min_uV, cpu_min_uV;
|
||||
bool sys_reboot_mode_req;
|
||||
bool sys_reboot_mode;
|
||||
};
|
||||
|
||||
static inline struct tegra_regulator_coupler *
|
||||
@ -39,6 +44,21 @@ static int tegra30_core_limit(struct tegra_regulator_coupler *tegra,
|
||||
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;
|
||||
|
||||
@ -59,7 +79,7 @@ static int tegra30_core_limit(struct tegra_regulator_coupler *tegra,
|
||||
*/
|
||||
tegra->core_min_uV = core_max_uV;
|
||||
|
||||
pr_info("core minimum voltage limited to %duV\n", tegra->core_min_uV);
|
||||
pr_info("core voltage initialized to %duV\n", tegra->core_min_uV);
|
||||
|
||||
return tegra->core_min_uV;
|
||||
}
|
||||
@ -172,6 +192,10 @@ static int tegra30_voltage_update(struct tegra_regulator_coupler *tegra,
|
||||
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
|
||||
@ -195,6 +219,10 @@ static int tegra30_voltage_update(struct tegra_regulator_coupler *tegra,
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* restore boot voltage level */
|
||||
if (tegra->sys_reboot_mode)
|
||||
cpu_min_uV = max(cpu_min_uV, tegra->cpu_min_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);
|
||||
@ -263,9 +291,56 @@ static int tegra30_regulator_balance_voltage(struct regulator_coupler *coupler,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
tegra->sys_reboot_mode = READ_ONCE(tegra->sys_reboot_mode_req);
|
||||
|
||||
return tegra30_voltage_update(tegra, cpu_rdev, core_rdev);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@ -292,6 +367,14 @@ static int tegra30_regulator_detach(struct regulator_coupler *coupler,
|
||||
{
|
||||
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;
|
||||
@ -311,13 +394,19 @@ static struct tegra_regulator_coupler tegra30_coupler = {
|
||||
.detach_regulator = tegra30_regulator_detach,
|
||||
.balance_voltage = tegra30_regulator_balance_voltage,
|
||||
},
|
||||
.reboot_notifier.notifier_call = tegra30_regulator_reboot,
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
return regulator_coupler_register(&tegra30_coupler.coupler);
|
||||
}
|
||||
arch_initcall(tegra_regulator_coupler_init);
|
||||
|
@ -540,6 +540,7 @@ int regulator_set_current_limit_regmap(struct regulator_dev *rdev,
|
||||
int regulator_get_current_limit_regmap(struct regulator_dev *rdev);
|
||||
void *regulator_get_init_drvdata(struct regulator_init_data *reg_init_data);
|
||||
int regulator_set_ramp_delay_regmap(struct regulator_dev *rdev, int ramp_delay);
|
||||
int regulator_sync_voltage_rdev(struct regulator_dev *rdev);
|
||||
|
||||
/*
|
||||
* Helper functions intended to be used by regulator drivers prior registering
|
||||
|
@ -6,6 +6,37 @@
|
||||
#ifndef __SOC_TEGRA_COMMON_H__
|
||||
#define __SOC_TEGRA_COMMON_H__
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
struct device;
|
||||
|
||||
/**
|
||||
* Tegra SoC core device OPP table configuration
|
||||
*
|
||||
* @init_state: pre-initialize OPP state of a device
|
||||
*/
|
||||
struct tegra_core_opp_params {
|
||||
bool init_state;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_ARCH_TEGRA
|
||||
bool soc_is_tegra(void);
|
||||
|
||||
int devm_tegra_core_dev_init_opp_table(struct device *dev,
|
||||
struct tegra_core_opp_params *params);
|
||||
#else
|
||||
static inline bool soc_is_tegra(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline int
|
||||
devm_tegra_core_dev_init_opp_table(struct device *dev,
|
||||
struct tegra_core_opp_params *params)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __SOC_TEGRA_COMMON_H__ */
|
||||
|
@ -52,14 +52,28 @@ struct tegra_sku_info {
|
||||
enum tegra_revision revision;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_ARCH_TEGRA
|
||||
extern struct tegra_sku_info tegra_sku_info;
|
||||
u32 tegra_read_straps(void);
|
||||
u32 tegra_read_ram_code(void);
|
||||
int tegra_fuse_readl(unsigned long offset, u32 *value);
|
||||
|
||||
#ifdef CONFIG_ARCH_TEGRA
|
||||
extern struct tegra_sku_info tegra_sku_info;
|
||||
#else
|
||||
static struct tegra_sku_info tegra_sku_info __maybe_unused;
|
||||
|
||||
static inline u32 tegra_read_straps(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u32 tegra_read_ram_code(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int tegra_fuse_readl(unsigned long offset, u32 *value)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct device *tegra_soc_device_register(void);
|
||||
|
@ -171,6 +171,8 @@ int tegra_io_rail_power_off(unsigned int id);
|
||||
void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode);
|
||||
void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode);
|
||||
|
||||
bool tegra_pmc_core_domain_state_synced(void);
|
||||
|
||||
#else
|
||||
static inline int tegra_powergate_power_on(unsigned int id)
|
||||
{
|
||||
@ -227,6 +229,11 @@ static inline void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode)
|
||||
{
|
||||
}
|
||||
|
||||
static inline bool tegra_pmc_core_domain_state_synced(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SOC_TEGRA_PMC */
|
||||
|
||||
#if defined(CONFIG_SOC_TEGRA_PMC) && defined(CONFIG_PM_SLEEP)
|
||||
|
Loading…
x
Reference in New Issue
Block a user