131 lines
4.5 KiB
Diff
131 lines
4.5 KiB
Diff
|
Content-Type: text/plain; charset="utf-8"
|
||
|
MIME-Version: 1.0
|
||
|
Content-Transfer-Encoding: 7bit
|
||
|
Subject: [v3, 03/13] clk: Avoid sending high rates to downstream clocks during
|
||
|
set_rate
|
||
|
From: Stephen Boyd <sboyd@codeaurora.org>
|
||
|
X-Patchwork-Id: 6063271
|
||
|
Message-Id: <1426920332-9340-4-git-send-email-sboyd@codeaurora.org>
|
||
|
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||
|
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||
|
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||
|
Viresh Kumar <viresh.kumar@linaro.org>
|
||
|
Date: Fri, 20 Mar 2015 23:45:22 -0700
|
||
|
|
||
|
If a clock is on and we call clk_set_rate() on it we may get into
|
||
|
a situation where the clock temporarily increases in rate
|
||
|
dramatically while we walk the tree and call .set_rate() ops. For
|
||
|
example, consider a case where a PLL feeds into a divider.
|
||
|
Initially the divider is set to divide by 1 and the PLL is
|
||
|
running fairly slow (100MHz). The downstream consumer of the
|
||
|
divider output can only handle rates =< 400 MHz, but the divider
|
||
|
can only choose between divisors of 1 and 4.
|
||
|
|
||
|
+-----+ +----------------+
|
||
|
| PLL |-->| div 1 or div 4 |---> consumer device
|
||
|
+-----+ +----------------+
|
||
|
|
||
|
To achieve a rate of 400MHz on the output of the divider, we
|
||
|
would have to set the rate of the PLL to 1.6 GHz and then divide
|
||
|
it by 4. The current code would set the PLL to 1.6GHz first while
|
||
|
the divider is still set to 1, thus causing the downstream
|
||
|
consumer of the clock to receive a few clock cycles of 1.6GHz
|
||
|
clock (far beyond it's maximum acceptable rate). We should be
|
||
|
changing the divider first before increasing the PLL rate to
|
||
|
avoid this problem.
|
||
|
|
||
|
Therefore, set the rate of any child clocks that are increasing
|
||
|
in rate from their current rate so that they can increase their
|
||
|
dividers if necessary. We assume that there isn't such a thing as
|
||
|
minimum rate requirements.
|
||
|
|
||
|
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||
|
|
||
|
---
|
||
|
drivers/clk/clk.c | 34 ++++++++++++++++++++++------------
|
||
|
1 file changed, 22 insertions(+), 12 deletions(-)
|
||
|
|
||
|
--- a/drivers/clk/clk.c
|
||
|
+++ b/drivers/clk/clk.c
|
||
|
@@ -1688,21 +1688,24 @@ static struct clk_core *clk_propagate_ra
|
||
|
* walk down a subtree and set the new rates notifying the rate
|
||
|
* change on the way
|
||
|
*/
|
||
|
-static void clk_change_rate(struct clk_core *clk)
|
||
|
+static void
|
||
|
+clk_change_rate(struct clk_core *clk, unsigned long best_parent_rate)
|
||
|
{
|
||
|
struct clk_core *child;
|
||
|
struct hlist_node *tmp;
|
||
|
unsigned long old_rate;
|
||
|
- unsigned long best_parent_rate = 0;
|
||
|
bool skip_set_rate = false;
|
||
|
struct clk_core *old_parent;
|
||
|
|
||
|
- old_rate = clk->rate;
|
||
|
+ hlist_for_each_entry(child, &clk->children, child_node) {
|
||
|
+ /* Skip children who will be reparented to another clock */
|
||
|
+ if (child->new_parent && child->new_parent != clk)
|
||
|
+ continue;
|
||
|
+ if (child->new_rate > child->rate)
|
||
|
+ clk_change_rate(child, clk->new_rate);
|
||
|
+ }
|
||
|
|
||
|
- if (clk->new_parent)
|
||
|
- best_parent_rate = clk->new_parent->rate;
|
||
|
- else if (clk->parent)
|
||
|
- best_parent_rate = clk->parent->rate;
|
||
|
+ old_rate = clk->rate;
|
||
|
|
||
|
if (clk->new_parent && clk->new_parent != clk->parent) {
|
||
|
old_parent = __clk_set_parent_before(clk, clk->new_parent);
|
||
|
@@ -1722,7 +1725,7 @@ static void clk_change_rate(struct clk_c
|
||
|
if (!skip_set_rate && clk->ops->set_rate)
|
||
|
clk->ops->set_rate(clk->hw, clk->new_rate, best_parent_rate);
|
||
|
|
||
|
- clk->rate = clk_recalc(clk, best_parent_rate);
|
||
|
+ clk->rate = clk->new_rate;
|
||
|
|
||
|
if (clk->notifier_count && old_rate != clk->rate)
|
||
|
__clk_notify(clk, POST_RATE_CHANGE, old_rate, clk->rate);
|
||
|
@@ -1735,12 +1738,13 @@ static void clk_change_rate(struct clk_c
|
||
|
/* Skip children who will be reparented to another clock */
|
||
|
if (child->new_parent && child->new_parent != clk)
|
||
|
continue;
|
||
|
- clk_change_rate(child);
|
||
|
+ if (child->new_rate != child->rate)
|
||
|
+ clk_change_rate(child, clk->new_rate);
|
||
|
}
|
||
|
|
||
|
/* handle the new child who might not be in clk->children yet */
|
||
|
- if (clk->new_child)
|
||
|
- clk_change_rate(clk->new_child);
|
||
|
+ if (clk->new_child && clk->new_child->new_rate != clk->new_child->rate)
|
||
|
+ clk_change_rate(clk->new_child, clk->new_rate);
|
||
|
}
|
||
|
|
||
|
static int clk_core_set_rate_nolock(struct clk_core *clk,
|
||
|
@@ -1749,6 +1753,7 @@ static int clk_core_set_rate_nolock(stru
|
||
|
struct clk_core *top, *fail_clk;
|
||
|
unsigned long rate = req_rate;
|
||
|
int ret = 0;
|
||
|
+ unsigned long parent_rate;
|
||
|
|
||
|
if (!clk)
|
||
|
return 0;
|
||
|
@@ -1774,8 +1779,13 @@ static int clk_core_set_rate_nolock(stru
|
||
|
return -EBUSY;
|
||
|
}
|
||
|
|
||
|
+ if (top->parent)
|
||
|
+ parent_rate = top->parent->rate;
|
||
|
+ else
|
||
|
+ parent_rate = 0;
|
||
|
+
|
||
|
/* change the rates */
|
||
|
- clk_change_rate(top);
|
||
|
+ clk_change_rate(top, parent_rate);
|
||
|
|
||
|
clk->req_rate = req_rate;
|
||
|
|