soc/tegra: regulators: Prepare for suspend

Depending on hardware version, Tegra SoC may require a higher voltages
during resume from system suspend, otherwise hardware will crash. Set
SoC voltages to a nominal levels during suspend.

Link: https://lore.kernel.org/all/a8280b5b-7347-8995-c97b-10b798cdf057@gmail.com/
Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
Signed-off-by: Thierry Reding <treding@nvidia.com>
This commit is contained in:
Dmitry Osipenko 2021-12-01 02:23:37 +03:00 committed by Thierry Reding
parent 88724b78a8
commit 80ef351c98
2 changed files with 221 additions and 0 deletions

View File

@ -16,7 +16,9 @@
#include <linux/regulator/coupler.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/suspend.h>
#include <soc/tegra/fuse.h>
#include <soc/tegra/pmc.h>
struct tegra_regulator_coupler {
@ -25,9 +27,12 @@ struct tegra_regulator_coupler {
struct regulator_dev *cpu_rdev;
struct regulator_dev *rtc_rdev;
struct notifier_block reboot_notifier;
struct notifier_block suspend_notifier;
int core_min_uV, cpu_min_uV;
bool sys_reboot_mode_req;
bool sys_reboot_mode;
bool sys_suspend_mode_req;
bool sys_suspend_mode;
};
static inline struct tegra_regulator_coupler *
@ -105,6 +110,28 @@ static int tegra20_core_rtc_max_spread(struct regulator_dev *core_rdev,
return 150000;
}
static int tegra20_cpu_nominal_uV(void)
{
switch (tegra_sku_info.soc_speedo_id) {
case 0:
return 1100000;
case 1:
return 1025000;
default:
return 1125000;
}
}
static int tegra20_core_nominal_uV(void)
{
switch (tegra_sku_info.soc_speedo_id) {
default:
return 1225000;
case 2:
return 1300000;
}
}
static int tegra20_core_rtc_update(struct tegra_regulator_coupler *tegra,
struct regulator_dev *core_rdev,
struct regulator_dev *rtc_rdev,
@ -144,6 +171,11 @@ static int tegra20_core_rtc_update(struct tegra_regulator_coupler *tegra,
if (err)
return err;
/* prepare voltage level for suspend */
if (tegra->sys_suspend_mode)
core_min_uV = clamp(tegra20_core_nominal_uV(),
core_min_uV, core_max_uV);
core_uV = regulator_get_voltage_rdev(core_rdev);
if (core_uV < 0)
return core_uV;
@ -279,6 +311,11 @@ static int tegra20_cpu_voltage_update(struct tegra_regulator_coupler *tegra,
if (tegra->sys_reboot_mode)
cpu_min_uV = max(cpu_min_uV, tegra->cpu_min_uV);
/* prepare voltage level for suspend */
if (tegra->sys_suspend_mode)
cpu_min_uV = clamp(tegra20_cpu_nominal_uV(),
cpu_min_uV, cpu_max_uV);
if (cpu_min_uV > cpu_uV) {
err = tegra20_core_rtc_update(tegra, core_rdev, rtc_rdev,
cpu_uV, cpu_min_uV);
@ -320,6 +357,7 @@ static int tegra20_regulator_balance_voltage(struct regulator_coupler *coupler,
}
tegra->sys_reboot_mode = READ_ONCE(tegra->sys_reboot_mode_req);
tegra->sys_suspend_mode = READ_ONCE(tegra->sys_suspend_mode_req);
if (rdev == cpu_rdev)
return tegra20_cpu_voltage_update(tegra, cpu_rdev,
@ -334,6 +372,63 @@ static int tegra20_regulator_balance_voltage(struct regulator_coupler *coupler,
return -EPERM;
}
static int tegra20_regulator_prepare_suspend(struct tegra_regulator_coupler *tegra,
bool sys_suspend_mode)
{
int err;
if (!tegra->core_rdev || !tegra->rtc_rdev || !tegra->cpu_rdev)
return 0;
/*
* All power domains are enabled early during resume from suspend
* by GENPD core. Domains like VENC may require a higher voltage
* when enabled during resume from suspend. This also prepares
* hardware for resuming from LP0.
*/
WRITE_ONCE(tegra->sys_suspend_mode_req, sys_suspend_mode);
err = regulator_sync_voltage_rdev(tegra->cpu_rdev);
if (err)
return err;
err = regulator_sync_voltage_rdev(tegra->core_rdev);
if (err)
return err;
return 0;
}
static int tegra20_regulator_suspend(struct notifier_block *notifier,
unsigned long mode, void *arg)
{
struct tegra_regulator_coupler *tegra;
int ret = 0;
tegra = container_of(notifier, struct tegra_regulator_coupler,
suspend_notifier);
switch (mode) {
case PM_HIBERNATION_PREPARE:
case PM_RESTORE_PREPARE:
case PM_SUSPEND_PREPARE:
ret = tegra20_regulator_prepare_suspend(tegra, true);
break;
case PM_POST_HIBERNATION:
case PM_POST_RESTORE:
case PM_POST_SUSPEND:
ret = tegra20_regulator_prepare_suspend(tegra, false);
break;
}
if (ret)
pr_err("failed to prepare regulators: %d\n", ret);
return notifier_from_errno(ret);
}
static int tegra20_regulator_prepare_reboot(struct tegra_regulator_coupler *tegra,
bool sys_reboot_mode)
{
@ -444,6 +539,7 @@ static struct tegra_regulator_coupler tegra20_coupler = {
.balance_voltage = tegra20_regulator_balance_voltage,
},
.reboot_notifier.notifier_call = tegra20_regulator_reboot,
.suspend_notifier.notifier_call = tegra20_regulator_suspend,
};
static int __init tegra_regulator_coupler_init(void)
@ -456,6 +552,9 @@ static int __init tegra_regulator_coupler_init(void)
err = register_reboot_notifier(&tegra20_coupler.reboot_notifier);
WARN_ON(err);
err = register_pm_notifier(&tegra20_coupler.suspend_notifier);
WARN_ON(err);
return regulator_coupler_register(&tegra20_coupler.coupler);
}
arch_initcall(tegra_regulator_coupler_init);

View File

@ -16,6 +16,7 @@
#include <linux/regulator/coupler.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/suspend.h>
#include <soc/tegra/fuse.h>
#include <soc/tegra/pmc.h>
@ -25,9 +26,12 @@ struct tegra_regulator_coupler {
struct regulator_dev *core_rdev;
struct regulator_dev *cpu_rdev;
struct notifier_block reboot_notifier;
struct notifier_block suspend_notifier;
int core_min_uV, cpu_min_uV;
bool sys_reboot_mode_req;
bool sys_reboot_mode;
bool sys_suspend_mode_req;
bool sys_suspend_mode;
};
static inline struct tegra_regulator_coupler *
@ -113,6 +117,52 @@ static int tegra30_core_cpu_limit(int cpu_uV)
return -EINVAL;
}
static int tegra30_cpu_nominal_uV(void)
{
switch (tegra_sku_info.cpu_speedo_id) {
case 10 ... 11:
return 850000;
case 9:
return 912000;
case 1 ... 3:
case 7 ... 8:
return 1050000;
default:
return 1125000;
case 4 ... 6:
case 12 ... 13:
return 1237000;
}
}
static int tegra30_core_nominal_uV(void)
{
switch (tegra_sku_info.soc_speedo_id) {
case 0:
return 1200000;
case 1:
if (tegra_sku_info.cpu_speedo_id != 7 &&
tegra_sku_info.cpu_speedo_id != 8)
return 1200000;
fallthrough;
case 2:
if (tegra_sku_info.cpu_speedo_id != 13)
return 1300000;
return 1350000;
default:
return 1250000;
}
}
static int tegra30_voltage_update(struct tegra_regulator_coupler *tegra,
struct regulator_dev *cpu_rdev,
struct regulator_dev *core_rdev)
@ -168,6 +218,11 @@ static int tegra30_voltage_update(struct tegra_regulator_coupler *tegra,
if (err)
return err;
/* prepare voltage level for suspend */
if (tegra->sys_suspend_mode)
core_min_uV = clamp(tegra30_core_nominal_uV(),
core_min_uV, core_max_uV);
core_uV = regulator_get_voltage_rdev(core_rdev);
if (core_uV < 0)
return core_uV;
@ -223,6 +278,11 @@ static int tegra30_voltage_update(struct tegra_regulator_coupler *tegra,
if (tegra->sys_reboot_mode)
cpu_min_uV = max(cpu_min_uV, tegra->cpu_min_uV);
/* prepare voltage level for suspend */
if (tegra->sys_suspend_mode)
cpu_min_uV = clamp(tegra30_cpu_nominal_uV(),
cpu_min_uV, cpu_max_uV);
if (core_min_limited_uV > core_uV) {
pr_err("core voltage constraint violated: %d %d %d\n",
core_uV, core_min_limited_uV, cpu_uV);
@ -292,10 +352,68 @@ static int tegra30_regulator_balance_voltage(struct regulator_coupler *coupler,
}
tegra->sys_reboot_mode = READ_ONCE(tegra->sys_reboot_mode_req);
tegra->sys_suspend_mode = READ_ONCE(tegra->sys_suspend_mode_req);
return tegra30_voltage_update(tegra, cpu_rdev, core_rdev);
}
static int tegra30_regulator_prepare_suspend(struct tegra_regulator_coupler *tegra,
bool sys_suspend_mode)
{
int err;
if (!tegra->core_rdev || !tegra->cpu_rdev)
return 0;
/*
* All power domains are enabled early during resume from suspend
* by GENPD core. Domains like VENC may require a higher voltage
* when enabled during resume from suspend. This also prepares
* hardware for resuming from LP0.
*/
WRITE_ONCE(tegra->sys_suspend_mode_req, sys_suspend_mode);
err = regulator_sync_voltage_rdev(tegra->cpu_rdev);
if (err)
return err;
err = regulator_sync_voltage_rdev(tegra->core_rdev);
if (err)
return err;
return 0;
}
static int tegra30_regulator_suspend(struct notifier_block *notifier,
unsigned long mode, void *arg)
{
struct tegra_regulator_coupler *tegra;
int ret = 0;
tegra = container_of(notifier, struct tegra_regulator_coupler,
suspend_notifier);
switch (mode) {
case PM_HIBERNATION_PREPARE:
case PM_RESTORE_PREPARE:
case PM_SUSPEND_PREPARE:
ret = tegra30_regulator_prepare_suspend(tegra, true);
break;
case PM_POST_HIBERNATION:
case PM_POST_RESTORE:
case PM_POST_SUSPEND:
ret = tegra30_regulator_prepare_suspend(tegra, false);
break;
}
if (ret)
pr_err("failed to prepare regulators: %d\n", ret);
return notifier_from_errno(ret);
}
static int tegra30_regulator_prepare_reboot(struct tegra_regulator_coupler *tegra,
bool sys_reboot_mode)
{
@ -395,6 +513,7 @@ static struct tegra_regulator_coupler tegra30_coupler = {
.balance_voltage = tegra30_regulator_balance_voltage,
},
.reboot_notifier.notifier_call = tegra30_regulator_reboot,
.suspend_notifier.notifier_call = tegra30_regulator_suspend,
};
static int __init tegra_regulator_coupler_init(void)
@ -407,6 +526,9 @@ static int __init tegra_regulator_coupler_init(void)
err = register_reboot_notifier(&tegra30_coupler.reboot_notifier);
WARN_ON(err);
err = register_pm_notifier(&tegra30_coupler.suspend_notifier);
WARN_ON(err);
return regulator_coupler_register(&tegra30_coupler.coupler);
}
arch_initcall(tegra_regulator_coupler_init);