As demonstrated by the kunit tests, clk_calc_subtree() in the clk core
can overwrite a sibling clk with the parent rate. Clocks that are used
for some subsystems like DRM and sound are particularly sensitive to
this issue.
I consider this to be a logic bug in the clk subsystem, however this
functionality has existed since the clk core was introduced with
commit b2476490ef11 ("clk: introduce the common clock framework"),
and some boards are unknowingly dependent on this behavior.
Let's add support for a v2 rate negotiation logic that addresses the
logic error. Clks can opt into this new behavior by adding the flag
CLK_V2_RATE_NEGOTIATION.
Link: https://lore.kernel.org/linux-clk/[email protected]/
Link: https://lpc.events/event/19/contributions/2152/
Signed-off-by: Brian Masney <[email protected]>
---
drivers/clk/clk.c | 58 ++++++++++++++++++++++++++++++++++----------
include/linux/clk-provider.h | 3 +++
2 files changed, 48 insertions(+), 13 deletions(-)
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index
f1afd6c93eba49b9fc6c5c0e1db11d46c79069e9..514f2e2eb62a09df4f797b0207aa9b1448ddaf3d
100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -887,6 +887,31 @@ unsigned long clk_hw_get_children_lcm(struct clk_hw *hw,
struct clk_hw *requesti
}
EXPORT_SYMBOL_GPL(clk_hw_get_children_lcm);
+/**
+ * clk_has_v2_rate_negotiation - Check if a clk should use v2 rate negotiation
+ * @core: The clock core to check
+ *
+ * This function recursively checks if the clk or any of its descendants have
+ * the CLK_V2_RATE_NEGOTIATION flag set.
+ *
+ * Returns: true if the v2 logic should be used; false otherwise
+ */
+bool clk_has_v2_rate_negotiation(const struct clk_core *core)
+{
+ struct clk_core *child;
+
+ if (core->flags & CLK_V2_RATE_NEGOTIATION)
+ return true;
+
+ hlist_for_each_entry(child, &core->children, child_node) {
+ if (clk_has_v2_rate_negotiation(child))
+ return true;
+ }
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(clk_has_v2_rate_negotiation);
+
/*
* __clk_mux_determine_rate - clk_ops::determine_rate implementation for a mux
type clk
* @hw: mux type clk to determine rate on
@@ -2294,7 +2319,8 @@ static int __clk_speculate_rates(struct clk_core *core,
}
static void clk_calc_subtree(struct clk_core *core, unsigned long new_rate,
- struct clk_core *new_parent, u8 p_index)
+ struct clk_core *new_parent, u8 p_index,
+ struct clk_core *initiating_clk)
{
struct clk_core *child;
@@ -2307,8 +2333,12 @@ static void clk_calc_subtree(struct clk_core *core,
unsigned long new_rate,
new_parent->new_child = core;
hlist_for_each_entry(child, &core->children, child_node) {
- child->new_rate = clk_recalc(child, new_rate);
- clk_calc_subtree(child, child->new_rate, NULL, 0);
+ if (child != initiating_clk &&
clk_has_v2_rate_negotiation(child))
+ child->new_rate = child->rate;
+ else
+ child->new_rate = clk_recalc(child, new_rate);
+
+ clk_calc_subtree(child, child->new_rate, NULL, 0,
initiating_clk);
}
}
@@ -2317,7 +2347,8 @@ static void clk_calc_subtree(struct clk_core *core,
unsigned long new_rate,
* changed.
*/
static struct clk_core *clk_calc_new_rates(struct clk_core *core,
- unsigned long rate)
+ unsigned long rate,
+ struct clk_core *initiating_clk)
{
struct clk_core *top = core;
struct clk_core *old_parent, *parent;
@@ -2365,7 +2396,7 @@ static struct clk_core *clk_calc_new_rates(struct
clk_core *core,
return NULL;
} else {
/* pass-through clock with adjustable parent */
- top = clk_calc_new_rates(parent, rate);
+ top = clk_calc_new_rates(parent, rate, initiating_clk);
new_rate = parent->new_rate;
goto out;
}
@@ -2390,10 +2421,10 @@ static struct clk_core *clk_calc_new_rates(struct
clk_core *core,
if ((core->flags & CLK_SET_RATE_PARENT) && parent &&
best_parent_rate != parent->rate)
- top = clk_calc_new_rates(parent, best_parent_rate);
+ top = clk_calc_new_rates(parent, best_parent_rate,
initiating_clk);
out:
- clk_calc_subtree(core, new_rate, parent, p_index);
+ clk_calc_subtree(core, new_rate, parent, p_index, initiating_clk);
return top;
}
@@ -2441,7 +2472,7 @@ static struct clk_core *clk_propagate_rate_change(struct
clk_core *core,
* walk down a subtree and set the new rates notifying the rate
* change on the way
*/
-static void clk_change_rate(struct clk_core *core)
+static void clk_change_rate(struct clk_core *core, struct clk_core
*initiating_clk)
{
struct clk_core *child;
struct hlist_node *tmp;
@@ -2510,7 +2541,7 @@ static void clk_change_rate(struct clk_core *core)
__clk_notify(core, POST_RATE_CHANGE, old_rate, core->rate);
if (core->flags & CLK_RECALC_NEW_RATES)
- (void)clk_calc_new_rates(core, core->new_rate);
+ (void)clk_calc_new_rates(core, core->new_rate, initiating_clk);
/*
* Use safe iteration, as change_rate can actually swap parents
@@ -2520,12 +2551,12 @@ static void clk_change_rate(struct clk_core *core)
/* Skip children who will be reparented to another clock */
if (child->new_parent && child->new_parent != core)
continue;
- clk_change_rate(child);
+ clk_change_rate(child, initiating_clk);
}
/* handle the new child who might not be in core->children yet */
if (core->new_child)
- clk_change_rate(core->new_child);
+ clk_change_rate(core->new_child, initiating_clk);
clk_pm_runtime_put(core);
}
@@ -2581,7 +2612,7 @@ static int clk_core_set_rate_nolock(struct clk_core *core,
return -EBUSY;
/* calculate new rates and get the topmost changed clock */
- top = clk_calc_new_rates(core, req_rate);
+ top = clk_calc_new_rates(core, req_rate, core);
if (!top)
return -EINVAL;
@@ -2600,7 +2631,7 @@ static int clk_core_set_rate_nolock(struct clk_core *core,
}
/* change the rates */
- clk_change_rate(top);
+ clk_change_rate(top, core);
core->req_rate = req_rate;
err:
@@ -3587,6 +3618,7 @@ static const struct {
ENTRY(CLK_IS_CRITICAL),
ENTRY(CLK_OPS_PARENT_ENABLE),
ENTRY(CLK_DUTY_CYCLE_PARENT),
+ ENTRY(CLK_V2_RATE_NEGOTIATION),
#undef ENTRY
};
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index
2699b9759e13d0c1f0b54f4fa4f7f7bdd42e8dde..e0fc0bd347e5920e999ac96dbed9fc247f9443fa
100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -32,6 +32,8 @@
#define CLK_OPS_PARENT_ENABLE BIT(12)
/* duty cycle call may be forwarded to the parent clock */
#define CLK_DUTY_CYCLE_PARENT BIT(13)
+/* clock participates in v2 rate negotiation */
+#define CLK_V2_RATE_NEGOTIATION BIT(14)
struct clk;
struct clk_hw;
@@ -1432,6 +1434,7 @@ void clk_hw_set_rate_range(struct clk_hw *hw, unsigned
long min_rate,
unsigned long max_rate);
unsigned long clk_hw_get_children_lcm(struct clk_hw *hw, struct clk_hw
*requesting_hw,
unsigned long requesting_rate);
+bool clk_has_v2_rate_negotiation(const struct clk_core *core);
static inline void __clk_hw_set_clk(struct clk_hw *dst, struct clk_hw *src)
{
--
2.53.0