diff options
Diffstat (limited to 'target/linux/ipq806x/patches-4.0/135-clk-Avoid-sending-high-rates-to-downstream-clocks-during-set_rate.patch')
-rw-r--r-- | target/linux/ipq806x/patches-4.0/135-clk-Avoid-sending-high-rates-to-downstream-clocks-during-set_rate.patch | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/target/linux/ipq806x/patches-4.0/135-clk-Avoid-sending-high-rates-to-downstream-clocks-during-set_rate.patch b/target/linux/ipq806x/patches-4.0/135-clk-Avoid-sending-high-rates-to-downstream-clocks-during-set_rate.patch new file mode 100644 index 0000000000..f699521dd1 --- /dev/null +++ b/target/linux/ipq806x/patches-4.0/135-clk-Avoid-sending-high-rates-to-downstream-clocks-during-set_rate.patch @@ -0,0 +1,130 @@ +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; + |