diff mbox

[V4,3/3] cpufreq: Tegra: implement intermediate frequency callbacks

Message ID dc2702973028425e3ded689a6da227658fb914c4.1400662383.git.viresh.kumar@linaro.org
State New
Headers show

Commit Message

Viresh Kumar May 21, 2014, 8:59 a.m. UTC
Tegra had always been switching to intermediate frequency (pll_p_clk) since
ever. CPUFreq core has better support for handling notifications for these
frequencies and so we can adapt Tegra's driver to it.

Also do a WARN() if clk_set_parent() fails while moving back to pll_x as we
should have atleast restored to earlier frequency on error.

Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
---
 drivers/cpufreq/tegra-cpufreq.c | 81 ++++++++++++++++++++++++-----------------
 1 file changed, 47 insertions(+), 34 deletions(-)

Comments

Stephen Warren May 22, 2014, 4:39 p.m. UTC | #1
On 05/21/2014 02:59 AM, Viresh Kumar wrote:
> Tegra had always been switching to intermediate frequency (pll_p_clk) since
> ever. CPUFreq core has better support for handling notifications for these
> frequencies and so we can adapt Tegra's driver to it.
> 
> Also do a WARN() if clk_set_parent() fails while moving back to pll_x as we
> should have atleast restored to earlier frequency on error.

> diff --git a/drivers/cpufreq/tegra-cpufreq.c b/drivers/cpufreq/tegra-cpufreq.c

> @@ -98,10 +96,23 @@ static int tegra_target(struct cpufreq_policy *policy, unsigned int index)
>  	else
>  		clk_set_rate(emc_clk, 100000000);  /* emc 50Mhz */
>  
> -	ret = tegra_cpu_clk_set_rate(rate * 1000);
> +	/* target freq == pll_p */
> +	if (rate * 1000 == clk_get_rate(pll_p_clk)) {
> +		ret = tegra_target_intermediate(policy, index);
> +		goto disable_pll_x;
> +	}

I think the call to tegra_target_intermediate() is wrong here; shouldn't
the cpufreq core guarantee that tegra_target_intermediate() has always
been called before tegra_target(), so there's no need to repeat that
call here?

Also, tegra_target() doesn't seem to follow the rule documented by patch
2/3 that states ->target() should restore the orignal frequency on
error. I'm not even sure if that's possible in general.
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Viresh Kumar May 23, 2014, 4:05 a.m. UTC | #2
On 22 May 2014 22:09, Stephen Warren <swarren@wwwdotorg.org> wrote:
> I think the call to tegra_target_intermediate() is wrong here; shouldn't
> the cpufreq core guarantee that tegra_target_intermediate() has always
> been called before tegra_target(), so there's no need to repeat that
> call here?

That's what Doug requested in the previous version. get_intermediate()
can return zero in case drivers don't want to switch to intermediate
frequency for some target frequency.

Core should rather guarantee that target_index() is always called, if you
want core to guarantee that target_intermediate() is also always called,
then don't ever return zero from get_intermediate.

I did it that way for tegra as both target_intermediate() & target_index()
would have tried to set the same frequency for this particular case,
i.e. when target freq == intermediate frequency.

And both would have sent notification and the last notification wouldn't
have made any sense, both old-freq & new-freq would have been
intermediate freqs.

So, yes I see the feature suggested by Doug quite useful. Like in your
case.

> Also, tegra_target() doesn't seem to follow the rule documented by patch
> 2/3 that states ->target() should restore the orignal frequency on
> error. I'm not even sure if that's possible in general.

I thought I took care of that. Can you please give some example when
we aren't restoring original frequency on failure ?

About the rule, that has to be the expectation from core as there is no
way out that for core to know what happened at end of target_index()..

It can call get_rate() but that would be over engineering it looks ..
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Stephen Warren May 29, 2014, 5:40 p.m. UTC | #3
On 05/21/2014 02:59 AM, Viresh Kumar wrote:
> Tegra had always been switching to intermediate frequency (pll_p_clk) since
> ever. CPUFreq core has better support for handling notifications for these
> frequencies and so we can adapt Tegra's driver to it.
> 
> Also do a WARN() if clk_set_parent() fails while moving back to pll_x as we
> should have atleast restored to earlier frequency on error.

This patch breaks Tegra. The reason is below.

> diff --git a/drivers/cpufreq/tegra-cpufreq.c b/drivers/cpufreq/tegra-cpufreq.c

> -static int tegra_cpu_clk_set_rate(unsigned long rate)
> +static unsigned int
> +tegra_get_intermediate(struct cpufreq_policy *policy, unsigned int index)

(BTW, can we please not put the return type on a separate line; it's
inconsistent with the rest of the code in this file)

> +{
> +	unsigned int ifreq = clk_get_rate(pll_p_clk) / 1000;
> +
> +	/*
> +	 * Don't switch to intermediate freq if:
> +	 * - we are already at it, i.e. policy->cur == ifreq
> +	 * - index corresponds to ifreq
> +	 */
> +	if ((freq_table[index].frequency == ifreq) || (policy->cur == ifreq))
> +		return 0;

If policy->cur == ifreq here, then tegra_target_intermediate() isn't
called by the cpufreq core, so ...

> +static int
> +tegra_target_intermediate(struct cpufreq_policy *policy, unsigned int index)
>  {
>  	int ret;
>  
> 	/*
> 	 * Take an extra reference to the main pll so it doesn't turn
> 	 * off when we move the cpu off of it
> 	 */
> 	clk_prepare_enable(pll_x_clk);

... that reference isn't added...

> @@ -98,10 +96,23 @@ static int tegra_target(struct cpufreq_policy *policy, unsigned int index)
>  	else
>  		clk_set_rate(emc_clk, 100000000);  /* emc 50Mhz */
>  
> -	ret = tegra_cpu_clk_set_rate(rate * 1000);
> +	/* target freq == pll_p */
> +	if (rate * 1000 == clk_get_rate(pll_p_clk)) {
> +		ret = tegra_target_intermediate(policy, index);
> +		goto disable_pll_x;
> +	}

... and this code doesn't call it either, since we could be switching
from the pll_p rate to something faster ...

> +
> +	ret = clk_set_rate(pll_x_clk, rate * 1000);
> +	/* Restore to earlier frequency on error, i.e. pll_x */
>  	if (ret)
> -		pr_err("cpu-tegra: Failed to set cpu frequency to %lu kHz\n",
> -			rate);
> +		pr_err("Failed to change pll_x to %lu\n", rate);
> +
> +	ret = clk_set_parent(cpu_clk, pll_x_clk);
> +	/* This shouldn't fail while changing or restoring */
> +	WARN_ON(ret);
> +
> +disable_pll_x:
> +	clk_disable_unprepare(pll_x_clk);

... so this turns off pll_x even though we're running from it.

It would be simpler if Tegra *always* used an intermediate frequency,
and hence the core *always* called tegra_target_intermediate().
Admittedly, this would result in tegra_target() sometimes (when
switching CPU clock rate to the pll_p rate) doing nothing other than
removing the extra reference on pll_x, but I think that the code would
be simpler to follow and more robust.
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Stephen Warren May 29, 2014, 5:42 p.m. UTC | #4
On 05/22/2014 10:05 PM, Viresh Kumar wrote:
> On 22 May 2014 22:09, Stephen Warren <swarren@wwwdotorg.org> wrote:
>> I think the call to tegra_target_intermediate() is wrong here; shouldn't
>> the cpufreq core guarantee that tegra_target_intermediate() has always
>> been called before tegra_target(), so there's no need to repeat that
>> call here?

>> Also, tegra_target() doesn't seem to follow the rule documented by patch
>> 2/3 that states ->target() should restore the orignal frequency on
>> error. I'm not even sure if that's possible in general.
> 
> I thought I took care of that. Can you please give some example when
> we aren't restoring original frequency on failure ?
> 
> About the rule, that has to be the expectation from core as there is no
> way out that for core to know what happened at end of target_index()..
> 
> It can call get_rate() but that would be over engineering it looks ..

E.g. in the following code after the series is applied:

> 	ret = clk_set_rate(pll_x_clk, rate * 1000);
> 	/* Restore to earlier frequency on error, i.e. pll_x */
> 	if (ret)
> 		pr_err("Failed to change pll_x to %lu\n", rate);
> 
> 	ret = clk_set_parent(cpu_clk, pll_x_clk);

If the clk_set_rate() failed, we have no idea what the pll_x rate is; I
don't think the clock API guarantees that zero HW changes have been made
if the function fails. Yet the code switches the CPU clock back to pll_x
without attempting to fix the pll_x rate to be the old rate. Perhaps
there's not much that can be done here though, since if one
clk_set_rate() failed, perhaps the one to fix it will too.

I suspect there are other issues, like switching between pll_p/pllx
might not be restored on error, and the EMC frequency isn't switched
back, etc.
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Viresh Kumar May 30, 2014, 1:56 a.m. UTC | #5
On 29 May 2014 23:10, Stephen Warren <swarren@wwwdotorg.org> wrote:
> This patch breaks Tegra. The reason is below.

Lets see what blunder I made :)

>> diff --git a/drivers/cpufreq/tegra-cpufreq.c b/drivers/cpufreq/tegra-cpufreq.c
>
>> -static int tegra_cpu_clk_set_rate(unsigned long rate)
>> +static unsigned int
>> +tegra_get_intermediate(struct cpufreq_policy *policy, unsigned int index)
>
> (BTW, can we please not put the return type on a separate line; it's
> inconsistent with the rest of the code in this file)

Sure.

>> +{
>> +     unsigned int ifreq = clk_get_rate(pll_p_clk) / 1000;
>> +
>> +     /*
>> +      * Don't switch to intermediate freq if:
>> +      * - we are already at it, i.e. policy->cur == ifreq
>> +      * - index corresponds to ifreq
>> +      */
>> +     if ((freq_table[index].frequency == ifreq) || (policy->cur == ifreq))
>> +             return 0;
>
> If policy->cur == ifreq here, then tegra_target_intermediate() isn't
> called by the cpufreq core, so ...
>
>> +static int
>> +tegra_target_intermediate(struct cpufreq_policy *policy, unsigned int index)
>>  {
>>       int ret;
>>
>>       /*
>>        * Take an extra reference to the main pll so it doesn't turn
>>        * off when we move the cpu off of it
>>        */
>>       clk_prepare_enable(pll_x_clk);
>
> ... that reference isn't added...
>
>> @@ -98,10 +96,23 @@ static int tegra_target(struct cpufreq_policy *policy, unsigned int index)
>>       else
>>               clk_set_rate(emc_clk, 100000000);  /* emc 50Mhz */
>>
>> -     ret = tegra_cpu_clk_set_rate(rate * 1000);
>> +     /* target freq == pll_p */
>> +     if (rate * 1000 == clk_get_rate(pll_p_clk)) {
>> +             ret = tegra_target_intermediate(policy, index);
>> +             goto disable_pll_x;
>> +     }
>
> ... and this code doesn't call it either, since we could be switching
> from the pll_p rate to something faster ...
>
>> +
>> +     ret = clk_set_rate(pll_x_clk, rate * 1000);
>> +     /* Restore to earlier frequency on error, i.e. pll_x */
>>       if (ret)
>> -             pr_err("cpu-tegra: Failed to set cpu frequency to %lu kHz\n",
>> -                     rate);
>> +             pr_err("Failed to change pll_x to %lu\n", rate);
>> +
>> +     ret = clk_set_parent(cpu_clk, pll_x_clk);
>> +     /* This shouldn't fail while changing or restoring */
>> +     WARN_ON(ret);
>> +
>> +disable_pll_x:
>> +     clk_disable_unprepare(pll_x_clk);
>
> ... so this turns off pll_x even though we're running from it.

Can you describe the role of the enable/disable of this pll_x_clk please?
Which all clocks depend on it, etc? So that I understand why its important
to enable it and for which clocks. And also if we need to disable it after
changing to any freq..

> It would be simpler if Tegra *always* used an intermediate frequency,
> and hence the core *always* called tegra_target_intermediate().
> Admittedly, this would result in tegra_target() sometimes (when
> switching CPU clock rate to the pll_p rate) doing nothing other than
> removing the extra reference on pll_x, but I think that the code would
> be simpler to follow and more robust.

Ok, will check what suits best.
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Stephen Warren May 30, 2014, 4:26 p.m. UTC | #6
On 05/29/2014 07:56 PM, Viresh Kumar wrote:
> On 29 May 2014 23:10, Stephen Warren <swarren@wwwdotorg.org> wrote:
>> This patch breaks Tegra. The reason is below.
...
>>> +disable_pll_x:
>>> +     clk_disable_unprepare(pll_x_clk);
>>
>> ... so this turns off pll_x even though we're running from it.
> 
> Can you describe the role of the enable/disable of this pll_x_clk please?
> Which all clocks depend on it, etc? So that I understand why its important
> to enable it and for which clocks. And also if we need to disable it after
> changing to any freq..

I believe the issue is this:

We can't change the rate of pll_x while it's being used as a source for
something. I'm not 100% sure why. I assume the PLL output simply isn't
stable enough while changing rates; perhaps it can go out-of-range, or
generate glitches.

This means we must switch the CPU clock source to something else (we use
pll_p) while changing the pll_x rate.

Since the CPU is the only thing that uses pll_x, if we switch the CPU to
some other clock source, pll_x will become unused, so it will be
automatically disabled.

Disabling the PLL, and then re-enabling it later when switching the CPU
back to it, presumably takes some extra time (e.g. waiting for PLL lock
when it starts back up), so the code takes an extra reference to the
clock (prepare_enable) before switching the CPU away from it, and drops
that (disable_unprepare) after switching the CPU back to it.

The only case pll_x is disabled is when we use a cpufreq of 216MHz; that
frequency is provided by pll_p itself, so we never switch back to pll_x
in this case, and do want to shut down pll_x to save some power.

Now, in your patch when switching from 216MHz to pll_x, the initial
prepare_enable(pll_x) never happens, then the CPU is switched back to
pll_x which turns it on, then the unpaired disable_unprepare(pll_x)
happens which turns off pll_x even though the CPU is using it as clock
source. Arguably the clock API has a bug in that it shouldn't allow
these unpaired calls to break the reference counting, but that's the way
the API is right now.
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Viresh Kumar June 2, 2014, 10:01 a.m. UTC | #7
On 29 May 2014 23:12, Stephen Warren <swarren@wwwdotorg.org> wrote:

> E.g. in the following code after the series is applied:
>
>>       ret = clk_set_rate(pll_x_clk, rate * 1000);
>>       /* Restore to earlier frequency on error, i.e. pll_x */
>>       if (ret)
>>               pr_err("Failed to change pll_x to %lu\n", rate);
>>
>>       ret = clk_set_parent(cpu_clk, pll_x_clk);
>
> If the clk_set_rate() failed, we have no idea what the pll_x rate is; I
> don't think the clock API guarantees that zero HW changes have been made
> if the function fails. Yet the code switches the CPU clock back to pll_x
> without attempting to fix the pll_x rate to be the old rate. Perhaps
> there's not much that can be done here though, since if one
> clk_set_rate() failed, perhaps the one to fix it will too.

This was the case here with the existing driver as well. CPUFreq core
expects this today as well and so sends reverse notification in case of
failures.

> I suspect there are other issues, like switching between pll_p/pllx
> might not be restored on error

Another example would be useful..

> and the EMC frequency isn't switched back, etc.

It wasn't in the existing code as well and so left it as is..
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/cpufreq/tegra-cpufreq.c b/drivers/cpufreq/tegra-cpufreq.c
index 6e774c6..a64b970 100644
--- a/drivers/cpufreq/tegra-cpufreq.c
+++ b/drivers/cpufreq/tegra-cpufreq.c
@@ -46,7 +46,24 @@  static struct clk *pll_x_clk;
 static struct clk *pll_p_clk;
 static struct clk *emc_clk;
 
-static int tegra_cpu_clk_set_rate(unsigned long rate)
+static unsigned int
+tegra_get_intermediate(struct cpufreq_policy *policy, unsigned int index)
+{
+	unsigned int ifreq = clk_get_rate(pll_p_clk) / 1000;
+
+	/*
+	 * Don't switch to intermediate freq if:
+	 * - we are already at it, i.e. policy->cur == ifreq
+	 * - index corresponds to ifreq
+	 */
+	if ((freq_table[index].frequency == ifreq) || (policy->cur == ifreq))
+		return 0;
+
+	return ifreq;
+}
+
+static int
+tegra_target_intermediate(struct cpufreq_policy *policy, unsigned int index)
 {
 	int ret;
 
@@ -57,28 +74,9 @@  static int tegra_cpu_clk_set_rate(unsigned long rate)
 	clk_prepare_enable(pll_x_clk);
 
 	ret = clk_set_parent(cpu_clk, pll_p_clk);
-	if (ret) {
-		pr_err("Failed to switch cpu to clock pll_p\n");
-		goto out;
-	}
-
-	if (rate == clk_get_rate(pll_p_clk))
-		goto out;
-
-	ret = clk_set_rate(pll_x_clk, rate);
-	if (ret) {
-		pr_err("Failed to change pll_x to %lu\n", rate);
-		goto out;
-	}
-
-	ret = clk_set_parent(cpu_clk, pll_x_clk);
-	if (ret) {
-		pr_err("Failed to switch cpu to clock pll_x\n");
-		goto out;
-	}
+	if (ret)
+		clk_disable_unprepare(pll_x_clk);
 
-out:
-	clk_disable_unprepare(pll_x_clk);
 	return ret;
 }
 
@@ -98,10 +96,23 @@  static int tegra_target(struct cpufreq_policy *policy, unsigned int index)
 	else
 		clk_set_rate(emc_clk, 100000000);  /* emc 50Mhz */
 
-	ret = tegra_cpu_clk_set_rate(rate * 1000);
+	/* target freq == pll_p */
+	if (rate * 1000 == clk_get_rate(pll_p_clk)) {
+		ret = tegra_target_intermediate(policy, index);
+		goto disable_pll_x;
+	}
+
+	ret = clk_set_rate(pll_x_clk, rate * 1000);
+	/* Restore to earlier frequency on error, i.e. pll_x */
 	if (ret)
-		pr_err("cpu-tegra: Failed to set cpu frequency to %lu kHz\n",
-			rate);
+		pr_err("Failed to change pll_x to %lu\n", rate);
+
+	ret = clk_set_parent(cpu_clk, pll_x_clk);
+	/* This shouldn't fail while changing or restoring */
+	WARN_ON(ret);
+
+disable_pll_x:
+	clk_disable_unprepare(pll_x_clk);
 
 	return ret;
 }
@@ -137,16 +148,18 @@  static int tegra_cpu_exit(struct cpufreq_policy *policy)
 }
 
 static struct cpufreq_driver tegra_cpufreq_driver = {
-	.flags		= CPUFREQ_NEED_INITIAL_FREQ_CHECK,
-	.verify		= cpufreq_generic_frequency_table_verify,
-	.target_index	= tegra_target,
-	.get		= cpufreq_generic_get,
-	.init		= tegra_cpu_init,
-	.exit		= tegra_cpu_exit,
-	.name		= "tegra",
-	.attr		= cpufreq_generic_attr,
+	.flags			= CPUFREQ_NEED_INITIAL_FREQ_CHECK,
+	.verify			= cpufreq_generic_frequency_table_verify,
+	.get_intermediate	= tegra_get_intermediate,
+	.target_intermediate	= tegra_target_intermediate,
+	.target_index		= tegra_target,
+	.get			= cpufreq_generic_get,
+	.init			= tegra_cpu_init,
+	.exit			= tegra_cpu_exit,
+	.name			= "tegra",
+	.attr			= cpufreq_generic_attr,
 #ifdef CONFIG_PM
-	.suspend	= cpufreq_generic_suspend,
+	.suspend		= cpufreq_generic_suspend,
 #endif
 };