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.

Signed-off-by: Dmitry Osipenko <dig...@gmail.com>
---
 drivers/soc/tegra/pmc.c | 92 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 90 insertions(+), 2 deletions(-)

diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index 985373ce52b1..8edcd5b319f6 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -237,6 +237,7 @@ struct tegra_powergate {
        unsigned int id;
        struct clk **clks;
        unsigned int num_clks;
+       unsigned long *clk_rates;
        struct reset_control *reset;
 };
 
@@ -586,6 +587,57 @@ static int __tegra_powergate_remove_clamping(struct 
tegra_pmc *pmc,
        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;
@@ -636,10 +688,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);
@@ -670,12 +726,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);
 
@@ -686,10 +749,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);
@@ -706,6 +773,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:
@@ -717,6 +788,9 @@ static int tegra_powergate_power_down(struct 
tegra_powergate *pg)
 disable_clks:
        tegra_powergate_disable_clocks(pg);
 
+unprepare_clks:
+       tegra_powergate_unprepare_clocks(pg);
+
        return err;
 }
 
@@ -834,6 +908,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;
@@ -845,6 +925,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;
@@ -995,6 +1076,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])) {
@@ -1011,6 +1098,7 @@ static int tegra_powergate_of_get_clks(struct 
tegra_powergate *pg,
        while (i--)
                clk_put(pg->clks[i]);
 
+       kfree(pg->clk_rates);
        kfree(pg->clks);
 
        return err;
-- 
2.29.2

_______________________________________________
devel mailing list
de...@linuxdriverproject.org
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel

Reply via email to