[v4,3/5] soc/tegra: pmc: Ensure that clock rates aren't too high
diff mbox series

Message ID 20210302122502.20874-4-digetx@gmail.com
State New, archived
Headers show
Series
  • Tegra PMC driver fixes and improvements
Related show

Commit Message

Dmitry Osipenko March 2, 2021, 12:25 p.m. UTC
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>
---
 drivers/soc/tegra/pmc.c | 92 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 90 insertions(+), 2 deletions(-)

Comments

Thierry Reding March 25, 2021, 2:39 p.m. UTC | #1
On Tue, Mar 02, 2021 at 03:25:00PM +0300, Dmitry Osipenko wrote:
> 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>
> ---
>  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 f970b615ee27..a87645fac735 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;
>  };
>  
> @@ -641,6 +642,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;

This seems a bit arbitrary. Where did you come up with that value?

I'm going to apply this to see how it fares in our testing.

Thierry
Dmitry Osipenko March 25, 2021, 3:02 p.m. UTC | #2
25.03.2021 17:39, Thierry Reding пишет:
> On Tue, Mar 02, 2021 at 03:25:00PM +0300, Dmitry Osipenko wrote:
>> 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>
>> ---
>>  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 f970b615ee27..a87645fac735 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;
>>  };
>>  
>> @@ -641,6 +642,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;
> 
> This seems a bit arbitrary. Where did you come up with that value?
> 
> I'm going to apply this to see how it fares in our testing.

This value is chosen based on the OPP table voltages and frequencies.

Maybe you could add this clarifying comment to the code(?):

"There is no hardware unit on any of Tegra SoCs which requires higher
voltages for the rates above 100MHz."
Dmitry Osipenko March 25, 2021, 3:05 p.m. UTC | #3
25.03.2021 18:02, Dmitry Osipenko пишет:
> 25.03.2021 17:39, Thierry Reding пишет:
>> On Tue, Mar 02, 2021 at 03:25:00PM +0300, Dmitry Osipenko wrote:
>>> 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>
>>> ---
>>>  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 f970b615ee27..a87645fac735 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;
>>>  };
>>>  
>>> @@ -641,6 +642,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;
>>
>> This seems a bit arbitrary. Where did you come up with that value?
>>
>> I'm going to apply this to see how it fares in our testing.
> 
> This value is chosen based on the OPP table voltages and frequencies.
> 
> Maybe you could add this clarifying comment to the code(?):
> 
> "There is no hardware unit on any of Tegra SoCs which requires higher
> voltages for the rates above 100MHz."

I meant below, of course :)

Patch
diff mbox series

diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index f970b615ee27..a87645fac735 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;
 };
 
@@ -641,6 +642,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;
@@ -691,10 +743,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);
@@ -717,12 +773,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);
 
@@ -733,10 +796,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);
@@ -753,6 +820,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:
@@ -764,6 +835,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;
 }
 
@@ -881,6 +955,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;
@@ -892,6 +972,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;
@@ -1042,6 +1123,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])) {
@@ -1058,6 +1145,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;