soc/tegra: regulators: Bump voltages on system reboot
Ensure that SoC voltages are at a level suitable for a system reboot. This is important for some devices that use CPU reset method for the rebooting. SoC CPU and core voltages now are be restored to a level that is suitable for rebooting. This patch fixes hang on reboot on Asus Transformer TF101, it was also reported as fixing some of reboot issues on Toshiba AC100. Reported-by: Nikola Milosavljević <mnidza@outlook.com> Tested-by: Nikola Milosavljević <mnidza@outlook.com> # TF101 Signed-off-by: Dmitry Osipenko <digetx@gmail.com> Signed-off-by: Thierry Reding <treding@nvidia.com>
This commit is contained in:
parent
241ed23c4d
commit
03978d42ed
@ -12,6 +12,7 @@
|
|||||||
#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>
|
||||||
@ -21,7 +22,10 @@ struct tegra_regulator_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 *
|
||||||
@ -242,6 +246,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 +258,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 +302,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 +317,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 +394,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 +426,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,6 +12,7 @@
|
|||||||
#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>
|
||||||
@ -22,7 +23,10 @@ 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 *
|
||||||
@ -172,6 +176,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 +203,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 +275,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 +351,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 +378,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);
|
||||||
|
Loading…
Reference in New Issue
Block a user