[PATCH v2 24/48] soc/tegra: pmc: Ensure that clock rates aren't too high

From: Dmitry Osipenko
Date: Thu Dec 17 2020 - 13:13:19 EST


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 <digetx@xxxxxxxxx>
---
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