Commit 777cf27f authored by Olof Johansson's avatar Olof Johansson

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

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.comSigned-off-by: default avatarOlof Johansson <olof@lixom.net>
parents 805be5c9 e2d0ee22
...@@ -4105,6 +4105,29 @@ int regulator_set_voltage_time_sel(struct regulator_dev *rdev, ...@@ -4105,6 +4105,29 @@ int regulator_set_voltage_time_sel(struct regulator_dev *rdev,
} }
EXPORT_SYMBOL_GPL(regulator_set_voltage_time_sel); 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_sync_voltage - re-apply last regulator output voltage
* @regulator: regulator source * @regulator: regulator source
......
...@@ -144,6 +144,8 @@ config SOC_TEGRA_FLOWCTRL ...@@ -144,6 +144,8 @@ config SOC_TEGRA_FLOWCTRL
config SOC_TEGRA_PMC config SOC_TEGRA_PMC
bool bool
select GENERIC_PINCONF select GENERIC_PINCONF
select PM_OPP
select PM_GENERIC_DOMAINS
config SOC_TEGRA_POWERGATE_BPMP config SOC_TEGRA_POWERGATE_BPMP
def_bool y def_bool y
......
...@@ -3,9 +3,16 @@ ...@@ -3,9 +3,16 @@
* Copyright (C) 2014 NVIDIA CORPORATION. All rights reserved. * 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/of.h>
#include <linux/pm_opp.h>
#include <soc/tegra/common.h> #include <soc/tegra/common.h>
#include <soc/tegra/fuse.h>
static const struct of_device_id tegra_machine_match[] = { static const struct of_device_id tegra_machine_match[] = {
{ .compatible = "nvidia,tegra20", }, { .compatible = "nvidia,tegra20", },
...@@ -31,3 +38,93 @@ bool soc_is_tegra(void) ...@@ -31,3 +38,93 @@ bool soc_is_tegra(void)
return match != NULL; 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,9 +489,7 @@ static int __init tegra_init_fuse(void) ...@@ -489,9 +489,7 @@ static int __init tegra_init_fuse(void)
size_t size = sizeof(*fuse->lookups) * fuse->soc->num_lookups; size_t size = sizeof(*fuse->lookups) * fuse->soc->num_lookups;
fuse->lookups = kmemdup(fuse->soc->lookups, size, GFP_KERNEL); fuse->lookups = kmemdup(fuse->soc->lookups, size, GFP_KERNEL);
if (!fuse->lookups) if (fuse->lookups)
return -ENOMEM;
nvmem_add_cell_lookups(fuse->lookups, fuse->soc->num_lookups); nvmem_add_cell_lookups(fuse->lookups, fuse->soc->num_lookups);
} }
......
...@@ -37,7 +37,8 @@ ...@@ -37,7 +37,8 @@
defined(CONFIG_ARCH_TEGRA_132_SOC) || \ defined(CONFIG_ARCH_TEGRA_132_SOC) || \
defined(CONFIG_ARCH_TEGRA_210_SOC) || \ defined(CONFIG_ARCH_TEGRA_210_SOC) || \
defined(CONFIG_ARCH_TEGRA_186_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) static u32 tegra30_fuse_read_early(struct tegra_fuse *fuse, unsigned int offset)
{ {
if (WARN_ON(!fuse->base)) if (WARN_ON(!fuse->base))
......
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
#include <linux/pinctrl/pinctrl.h> #include <linux/pinctrl/pinctrl.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/pm_domain.h> #include <linux/pm_domain.h>
#include <linux/pm_opp.h>
#include <linux/reboot.h> #include <linux/reboot.h>
#include <linux/regmap.h> #include <linux/regmap.h>
#include <linux/reset.h> #include <linux/reset.h>
...@@ -428,6 +429,9 @@ struct tegra_pmc { ...@@ -428,6 +429,9 @@ struct tegra_pmc {
struct irq_chip irq; struct irq_chip irq;
struct notifier_block clk_nb; struct notifier_block clk_nb;
bool core_domain_state_synced;
bool core_domain_registered;
}; };
static struct tegra_pmc *pmc = &(struct tegra_pmc) { static struct tegra_pmc *pmc = &(struct tegra_pmc) {
...@@ -1302,12 +1306,107 @@ static int tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np) ...@@ -1302,12 +1306,107 @@ static int tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np)
return err; 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, static int tegra_powergate_init(struct tegra_pmc *pmc,
struct device_node *parent) struct device_node *parent)
{ {
struct of_phandle_args child_args, parent_args;
struct device_node *np, *child; struct device_node *np, *child;
int err = 0; 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"); np = of_get_child_by_name(parent, "powergates");
if (!np) if (!np)
return 0; return 0;
...@@ -1318,6 +1417,21 @@ static int tegra_powergate_init(struct tegra_pmc *pmc, ...@@ -1318,6 +1417,21 @@ static int tegra_powergate_init(struct tegra_pmc *pmc,
of_node_put(child); of_node_put(child);
break; 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); of_node_put(np);
...@@ -1361,6 +1475,12 @@ static void tegra_powergate_remove_all(struct device_node *parent) ...@@ -1361,6 +1475,12 @@ static void tegra_powergate_remove_all(struct device_node *parent)
} }
of_node_put(np); 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 * static const struct tegra_io_pad_soc *
...@@ -3672,6 +3792,29 @@ static const struct of_device_id tegra_pmc_match[] = { ...@@ -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 = { static struct platform_driver tegra_pmc_driver = {
.driver = { .driver = {
.name = "tegra-pmc", .name = "tegra-pmc",
...@@ -3680,6 +3823,7 @@ static struct platform_driver tegra_pmc_driver = { ...@@ -3680,6 +3823,7 @@ static struct platform_driver tegra_pmc_driver = {
#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_ARM) #if defined(CONFIG_PM_SLEEP) && defined(CONFIG_ARM)
.pm = &tegra_pmc_pm_ops, .pm = &tegra_pmc_pm_ops,
#endif #endif
.sync_state = tegra_pmc_sync_state,
}, },
.probe = tegra_pmc_probe, .probe = tegra_pmc_probe,
}; };
......
...@@ -12,16 +12,22 @@ ...@@ -12,16 +12,22 @@
#include <linux/init.h> #include <linux/init.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/reboot.h>
#include <linux/regulator/coupler.h> #include <linux/regulator/coupler.h>
#include <linux/regulator/driver.h> #include <linux/regulator/driver.h>
#include <linux/regulator/machine.h> #include <linux/regulator/machine.h>
#include <soc/tegra/pmc.h>
struct tegra_regulator_coupler { struct tegra_regulator_coupler {
struct regulator_coupler coupler; struct regulator_coupler coupler;
struct regulator_dev *core_rdev; struct regulator_dev *core_rdev;
struct regulator_dev *cpu_rdev; struct regulator_dev *cpu_rdev;
struct regulator_dev *rtc_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 * static inline struct tegra_regulator_coupler *
...@@ -38,6 +44,21 @@ static int tegra20_core_limit(struct tegra_regulator_coupler *tegra, ...@@ -38,6 +44,21 @@ static int tegra20_core_limit(struct tegra_regulator_coupler *tegra,
int core_cur_uV; int core_cur_uV;
int err; 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) if (tegra->core_min_uV > 0)
return tegra->core_min_uV; return tegra->core_min_uV;
...@@ -58,7 +79,7 @@ static int tegra20_core_limit(struct tegra_regulator_coupler *tegra, ...@@ -58,7 +79,7 @@ static int tegra20_core_limit(struct tegra_regulator_coupler *tegra,
*/ */
tegra->core_min_uV = core_max_uV; 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; return tegra->core_min_uV;
} }
...@@ -242,6 +263,10 @@ static int tegra20_cpu_voltage_update(struct tegra_regulator_coupler *tegra, ...@@ -242,6 +263,10 @@ static int tegra20_cpu_voltage_update(struct tegra_regulator_coupler *tegra,
if (cpu_uV < 0) if (cpu_uV < 0)
return cpu_uV; 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 * CPU's regulator may not have any consumers, hence the voltage
* must not be changed in that case because CPU simply won't * 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, ...@@ -250,6 +275,10 @@ static int tegra20_cpu_voltage_update(struct tegra_regulator_coupler *tegra,
if (!cpu_min_uV_consumers) if (!cpu_min_uV_consumers)
cpu_min_uV = cpu_uV; 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) { if (cpu_min_uV > cpu_uV) {
err = tegra20_core_rtc_update(tegra, core_rdev, rtc_rdev, err = tegra20_core_rtc_update(tegra, core_rdev, rtc_rdev,
cpu_uV, cpu_min_uV); cpu_uV, cpu_min_uV);
...@@ -290,6 +319,8 @@ static int tegra20_regulator_balance_voltage(struct regulator_coupler *coupler, ...@@ -290,6 +319,8 @@ static int tegra20_regulator_balance_voltage(struct regulator_coupler *coupler,
return -EINVAL; return -EINVAL;
} }
tegra->sys_reboot_mode = READ_ONCE(tegra->sys_reboot_mode_req);
if (rdev == cpu_rdev) if (rdev == cpu_rdev)
return tegra20_cpu_voltage_update(tegra, cpu_rdev, return tegra20_cpu_voltage_update(tegra, cpu_rdev,
core_rdev, rtc_rdev); core_rdev, rtc_rdev);
...@@ -303,6 +334,51 @@ static int tegra20_regulator_balance_voltage(struct regulator_coupler *coupler, ...@@ -303,6 +334,51 @@ static int tegra20_regulator_balance_voltage(struct regulator_coupler *coupler,
return -EPERM; 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, static int tegra20_regulator_attach(struct regulator_coupler *coupler,
struct regulator_dev *rdev) struct regulator_dev *rdev)
{ {
...@@ -335,6 +411,14 @@ static int tegra20_regulator_detach(struct regulator_coupler *coupler, ...@@ -335,6 +411,14 @@ static int tegra20_regulator_detach(struct regulator_coupler *coupler,
{ {
struct tegra_regulator_coupler *tegra = to_tegra_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) { if (tegra->core_rdev == rdev) {
tegra->core_rdev = NULL; tegra->core_rdev = NULL;
return 0; return 0;
...@@ -359,13 +443,19 @@ static struct tegra_regulator_coupler tegra20_coupler = { ...@@ -359,13 +443,19 @@ static struct tegra_regulator_coupler tegra20_coupler = {
.detach_regulator = tegra20_regulator_detach, .detach_regulator = tegra20_regulator_detach,
.balance_voltage = tegra20_regulator_balance_voltage, .balance_voltage = tegra20_regulator_balance_voltage,
}, },
.reboot_notifier.notifier_call = tegra20_regulator_reboot,
}; };
static int __init tegra_regulator_coupler_init(void) static int __init tegra_regulator_coupler_init(void)
{ {
int err;
if (!of_machine_is_compatible("nvidia,tegra20")) if (!of_machine_is_compatible("nvidia,tegra20"))
return 0; return 0;
err = register_reboot_notifier(&tegra20_coupler.reboot_notifier);
WARN_ON(err);
return regulator_coupler_register(&tegra20_coupler.coupler); return regulator_coupler_register(&tegra20_coupler.coupler);
} }
arch_initcall(tegra_regulator_coupler_init); arch_initcall(tegra_regulator_coupler_init);
...@@ -12,17 +12,22 @@ ...@@ -12,17 +12,22 @@
#include <linux/init.h> #include <linux/init.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/reboot.h>
#include <linux/regulator/coupler.h> #include <linux/regulator/coupler.h>
#include <linux/regulator/driver.h> #include <linux/regulator/driver.h>
#include <linux/regulator/machine.h> #include <linux/regulator/machine.h>
#include <soc/tegra/fuse.h> #include <soc/tegra/fuse.h>
#include <soc/tegra/pmc.h>
struct tegra_regulator_coupler { struct tegra_regulator_coupler {
struct regulator_coupler coupler; struct regulator_coupler coupler;
struct regulator_dev *core_rdev; struct regulator_dev *core_rdev;
struct regulator_dev *cpu_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 * static inline struct tegra_regulator_coupler *
...@@ -39,6 +44,21 @@ static int tegra30_core_limit(struct tegra_regulator_coupler *tegra, ...@@ -39,6 +44,21 @@ static int tegra30_core_limit(struct tegra_regulator_coupler *tegra,
int core_cur_uV; int core_cur_uV;
int err; 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) if (tegra->core_min_uV > 0)
return tegra->core_min_uV; return tegra->core_min_uV;
...@@ -59,7 +79,7 @@ static int tegra30_core_limit(struct tegra_regulator_coupler *tegra, ...@@ -59,7 +79,7 @@ static int tegra30_core_limit(struct tegra_regulator_coupler *tegra,
*/ */
tegra->core_min_uV = core_max_uV; 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; return tegra->core_min_uV;
} }
...@@ -172,6 +192,10 @@ static int tegra30_voltage_update(struct tegra_regulator_coupler *tegra, ...@@ -172,6 +192,10 @@ static int tegra30_voltage_update(struct tegra_regulator_coupler *tegra,
if (cpu_uV < 0) if (cpu_uV < 0)
return cpu_uV; 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 * CPU's regulator may not have any consumers, hence the voltage
* must not be changed in that case because CPU simply won't * 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, ...@@ -195,6 +219,10 @@ static int tegra30_voltage_update(struct tegra_regulator_coupler *tegra,
if (err) if (err)
return 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) { if (core_min_limited_uV > core_uV) {
pr_err("core voltage constraint violated: %d %d %d\n", pr_err("core voltage constraint violated: %d %d %d\n",
core_uV, core_min_limited_uV, cpu_uV); core_uV, core_min_limited_uV, cpu_uV);
...@@ -263,9 +291,56 @@ static int tegra30_regulator_balance_voltage(struct regulator_coupler *coupler, ...@@ -263,9 +291,56 @@ static int tegra30_regulator_balance_voltage(struct regulator_coupler *coupler,
return -EINVAL; return -EINVAL;
} }
tegra->sys_reboot_mode = READ_ONCE(tegra->sys_reboot_mode_req);
return tegra30_voltage_update(tegra, cpu_rdev, core_rdev); 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, static int tegra30_regulator_attach(struct regulator_coupler *coupler,
struct regulator_dev *rdev) struct regulator_dev *rdev)
{ {
...@@ -292,6 +367,14 @@ static int tegra30_regulator_detach(struct regulator_coupler *coupler, ...@@ -292,6 +367,14 @@ static int tegra30_regulator_detach(struct regulator_coupler *coupler,
{ {
struct tegra_regulator_coupler *tegra = to_tegra_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) { if (tegra->core_rdev == rdev) {
tegra->core_rdev = NULL; tegra->core_rdev = NULL;
return 0; return 0;
...@@ -311,13 +394,19 @@ static struct tegra_regulator_coupler tegra30_coupler = { ...@@ -311,13 +394,19 @@ static struct tegra_regulator_coupler tegra30_coupler = {
.detach_regulator = tegra30_regulator_detach, .detach_regulator = tegra30_regulator_detach,
.balance_voltage = tegra30_regulator_balance_voltage, .balance_voltage = tegra30_regulator_balance_voltage,
}, },
.reboot_notifier.notifier_call = tegra30_regulator_reboot,
}; };
static int __init tegra_regulator_coupler_init(void) static int __init tegra_regulator_coupler_init(void)
{ {
int err;
if (!of_machine_is_compatible("nvidia,tegra30")) if (!of_machine_is_compatible("nvidia,tegra30"))
return 0; return 0;
err = register_reboot_notifier(&tegra30_coupler.reboot_notifier);
WARN_ON(err);
return regulator_coupler_register(&tegra30_coupler.coupler); return regulator_coupler_register(&tegra30_coupler.coupler);
} }
arch_initcall(tegra_regulator_coupler_init); arch_initcall(tegra_regulator_coupler_init);
...@@ -540,6 +540,7 @@ int regulator_set_current_limit_regmap(struct regulator_dev *rdev, ...@@ -540,6 +540,7 @@ int regulator_set_current_limit_regmap(struct regulator_dev *rdev,
int regulator_get_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); 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_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 * Helper functions intended to be used by regulator drivers prior registering
......
...@@ -6,6 +6,37 @@ ...@@ -6,6 +6,37 @@
#ifndef __SOC_TEGRA_COMMON_H__ #ifndef __SOC_TEGRA_COMMON_H__
#define __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); 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__ */ #endif /* __SOC_TEGRA_COMMON_H__ */
...@@ -52,14 +52,28 @@ struct tegra_sku_info { ...@@ -52,14 +52,28 @@ struct tegra_sku_info {
enum tegra_revision revision; enum tegra_revision revision;
}; };
#ifdef CONFIG_ARCH_TEGRA
extern struct tegra_sku_info tegra_sku_info;
u32 tegra_read_straps(void); u32 tegra_read_straps(void);
u32 tegra_read_ram_code(void); u32 tegra_read_ram_code(void);
int tegra_fuse_readl(unsigned long offset, u32 *value); int tegra_fuse_readl(unsigned long offset, u32 *value);
#ifdef CONFIG_ARCH_TEGRA
extern struct tegra_sku_info tegra_sku_info;
#else #else
static struct tegra_sku_info tegra_sku_info __maybe_unused; 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 #endif
struct device *tegra_soc_device_register(void); struct device *tegra_soc_device_register(void);
......
...@@ -171,6 +171,8 @@ int tegra_io_rail_power_off(unsigned int id); ...@@ -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_set_suspend_mode(enum tegra_suspend_mode mode);
void tegra_pmc_enter_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 #else
static inline int tegra_powergate_power_on(unsigned int id) 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) ...@@ -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 */ #endif /* CONFIG_SOC_TEGRA_PMC */
#if defined(CONFIG_SOC_TEGRA_PMC) && defined(CONFIG_PM_SLEEP) #if defined(CONFIG_SOC_TEGRA_PMC) && defined(CONFIG_PM_SLEEP)
......
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