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.

Signed-off-by: Dmitry Osipenko <dig...@gmail.com>
---
 drivers/soc/tegra/regulators-tegra20.c |  99 ++++++++++++++++++++
 drivers/soc/tegra/regulators-tegra30.c | 122 +++++++++++++++++++++++++
 2 files changed, 221 insertions(+)

diff --git a/drivers/soc/tegra/regulators-tegra20.c 
b/drivers/soc/tegra/regulators-tegra20.c
index b8ce9fd0650d..6a2f90ab9d3e 100644
--- a/drivers/soc/tegra/regulators-tegra20.c
+++ b/drivers/soc/tegra/regulators-tegra20.c
@@ -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);
diff --git a/drivers/soc/tegra/regulators-tegra30.c 
b/drivers/soc/tegra/regulators-tegra30.c
index e74bbc9c7859..8fd43c689134 100644
--- a/drivers/soc/tegra/regulators-tegra30.c
+++ b/drivers/soc/tegra/regulators-tegra30.c
@@ -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);
-- 
2.33.1

Reply via email to