soc/tegra: regulators: Bump voltages on system reboot
authorDmitry Osipenko <digetx@gmail.com>
Thu, 27 May 2021 23:54:01 +0000 (02:54 +0300)
committerThierry Reding <treding@nvidia.com>
Tue, 1 Jun 2021 10:14:22 +0000 (12:14 +0200)
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>
drivers/soc/tegra/regulators-tegra20.c
drivers/soc/tegra/regulators-tegra30.c

index 367a71a..3479be5 100644 (file)
@@ -12,6 +12,7 @@
 #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>
@@ -21,7 +22,10 @@ struct tegra_regulator_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 *
@@ -242,6 +246,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 +258,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 +302,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 +317,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 +394,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 +426,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);
index 0e776b2..18fe53d 100644 (file)
@@ -12,6 +12,7 @@
 #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>
@@ -22,7 +23,10 @@ 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 *
@@ -172,6 +176,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 +203,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 +275,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 +351,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 +378,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);