soc/tegra: pmc: Ensure that clock rates aren't too high
authorDmitry Osipenko <digetx@gmail.com>
Tue, 2 Mar 2021 12:25:00 +0000 (15:25 +0300)
committerThierry Reding <treding@nvidia.com>
Fri, 26 Mar 2021 12:10:25 +0000 (13:10 +0100)
Switch all clocks of a power domain to a safe rate which is suitable
for all possible voltages in order to ensure that hardware constraints
aren't violated when power domain state toggles.

Tested-by: Peter Geis <pgwipeout@gmail.com> # Ouya T30
Tested-by: Nicolas Chauvet <kwizart@gmail.com> # PAZ00 T20 and TK1 T124
Tested-by: Matt Merhar <mattmerhar@protonmail.com> # Ouya T30
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
Signed-off-by: Thierry Reding <treding@nvidia.com>
drivers/soc/tegra/pmc.c

index 1c601dc..8245fba 100644 (file)
@@ -258,6 +258,7 @@ struct tegra_powergate {
        unsigned int id;
        struct clk **clks;
        unsigned int num_clks;
+       unsigned long *clk_rates;
        struct reset_control *reset;
 };
 
@@ -663,6 +664,57 @@ out:
        return 0;
 }
 
+static int tegra_powergate_prepare_clocks(struct tegra_powergate *pg)
+{
+       unsigned long safe_rate = 100 * 1000 * 1000;
+       unsigned int i;
+       int err;
+
+       for (i = 0; i < pg->num_clks; i++) {
+               pg->clk_rates[i] = clk_get_rate(pg->clks[i]);
+
+               if (!pg->clk_rates[i]) {
+                       err = -EINVAL;
+                       goto out;
+               }
+
+               if (pg->clk_rates[i] <= safe_rate)
+                       continue;
+
+               /*
+                * We don't know whether voltage state is okay for the
+                * current clock rate, hence it's better to temporally
+                * switch clock to a safe rate which is suitable for
+                * all voltages, before enabling the clock.
+                */
+               err = clk_set_rate(pg->clks[i], safe_rate);
+               if (err)
+                       goto out;
+       }
+
+       return 0;
+
+out:
+       while (i--)
+               clk_set_rate(pg->clks[i], pg->clk_rates[i]);
+
+       return err;
+}
+
+static int tegra_powergate_unprepare_clocks(struct tegra_powergate *pg)
+{
+       unsigned int i;
+       int err;
+
+       for (i = 0; i < pg->num_clks; i++) {
+               err = clk_set_rate(pg->clks[i], pg->clk_rates[i]);
+               if (err)
+                       return err;
+       }
+
+       return 0;
+}
+
 static void tegra_powergate_disable_clocks(struct tegra_powergate *pg)
 {
        unsigned int i;
@@ -713,10 +765,14 @@ static int tegra_powergate_power_up(struct tegra_powergate *pg,
 
        usleep_range(10, 20);
 
-       err = tegra_powergate_enable_clocks(pg);
+       err = tegra_powergate_prepare_clocks(pg);
        if (err)
                goto powergate_off;
 
+       err = tegra_powergate_enable_clocks(pg);
+       if (err)
+               goto unprepare_clks;
+
        usleep_range(10, 20);
 
        err = __tegra_powergate_remove_clamping(pg->pmc, pg->id);
@@ -739,12 +795,19 @@ static int tegra_powergate_power_up(struct tegra_powergate *pg,
        if (disable_clocks)
                tegra_powergate_disable_clocks(pg);
 
+       err = tegra_powergate_unprepare_clocks(pg);
+       if (err)
+               return err;
+
        return 0;
 
 disable_clks:
        tegra_powergate_disable_clocks(pg);
        usleep_range(10, 20);
 
+unprepare_clks:
+       tegra_powergate_unprepare_clocks(pg);
+
 powergate_off:
        tegra_powergate_set(pg->pmc, pg->id, false);
 
@@ -755,10 +818,14 @@ static int tegra_powergate_power_down(struct tegra_powergate *pg)
 {
        int err;
 
-       err = tegra_powergate_enable_clocks(pg);
+       err = tegra_powergate_prepare_clocks(pg);
        if (err)
                return err;
 
+       err = tegra_powergate_enable_clocks(pg);
+       if (err)
+               goto unprepare_clks;
+
        usleep_range(10, 20);
 
        err = reset_control_assert(pg->reset);
@@ -775,6 +842,10 @@ static int tegra_powergate_power_down(struct tegra_powergate *pg)
        if (err)
                goto assert_resets;
 
+       err = tegra_powergate_unprepare_clocks(pg);
+       if (err)
+               return err;
+
        return 0;
 
 assert_resets:
@@ -786,6 +857,9 @@ assert_resets:
 disable_clks:
        tegra_powergate_disable_clocks(pg);
 
+unprepare_clks:
+       tegra_powergate_unprepare_clocks(pg);
+
        return err;
 }
 
@@ -903,6 +977,12 @@ int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk,
        if (!pg)
                return -ENOMEM;
 
+       pg->clk_rates = kzalloc(sizeof(*pg->clk_rates), GFP_KERNEL);
+       if (!pg->clk_rates) {
+               kfree(pg->clks);
+               return -ENOMEM;
+       }
+
        pg->id = id;
        pg->clks = &clk;
        pg->num_clks = 1;
@@ -914,6 +994,7 @@ int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk,
                dev_err(pmc->dev, "failed to turn on partition %d: %d\n", id,
                        err);
 
+       kfree(pg->clk_rates);
        kfree(pg);
 
        return err;
@@ -1064,6 +1145,12 @@ static int tegra_powergate_of_get_clks(struct tegra_powergate *pg,
        if (!pg->clks)
                return -ENOMEM;
 
+       pg->clk_rates = kcalloc(count, sizeof(*pg->clk_rates), GFP_KERNEL);
+       if (!pg->clk_rates) {
+               kfree(pg->clks);
+               return -ENOMEM;
+       }
+
        for (i = 0; i < count; i++) {
                pg->clks[i] = of_clk_get(np, i);
                if (IS_ERR(pg->clks[i])) {
@@ -1080,6 +1167,7 @@ err:
        while (i--)
                clk_put(pg->clks[i]);
 
+       kfree(pg->clk_rates);
        kfree(pg->clks);
 
        return err;