[PATCH v6 2/7] clk: test: introduce test suite for sibling rate changes on a divider

From: Brian Masney

Date: Fri Mar 13 2026 - 12:44:21 EST


Introduce a kunit test suite that demonstrates the current behavior
of how a clock can unknowingly change the rate of it's siblings. Some
boards are unknowingly dependent on this behavior, and per discussions
at the 2025 Linux Plumbers Conference in Tokyo, we can't break the
existing behavior. So let's add kunit tests with the current behavior
so that we can be made aware if that functionality changes in the
future.

The tests in this commit use the following simplified clk tree with
the initial state:

parent
24 MHz
/ \
child1 child2
24 MHz 24 MHz

child1 and child2 both divider-only clocks that have CLK_SET_RATE_PARENT
set, and the parent is capable of achieving any rate.

Link: https://lore.kernel.org/linux-clk/aUSWU7UymULCXOeF@xxxxxxxxxx/
Link: https://lpc.events/event/19/contributions/2152/
Signed-off-by: Brian Masney <bmasney@xxxxxxxxxx>
---
drivers/clk/clk_test.c | 146 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 146 insertions(+)

diff --git a/drivers/clk/clk_test.c b/drivers/clk/clk_test.c
index 88e35f4419c958983578750356a97c0a45effb55..325da7c84ab2ecdcf6b7a023ce4c2c4ef2d49862 100644
--- a/drivers/clk/clk_test.c
+++ b/drivers/clk/clk_test.c
@@ -7,6 +7,7 @@
#include <linux/clk/clk-conf.h>
#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/units.h>

/* Needed for clk_hw_get_clk() */
#include "clk.h"
@@ -652,6 +653,150 @@ clk_multiple_parents_mux_test_suite = {
.test_cases = clk_multiple_parents_mux_test_cases,
};

+struct clk_rate_change_sibling_div_div_context {
+ struct clk_dummy_context parent;
+ struct clk_dummy_div child1, child2;
+ struct clk *parent_clk, *child1_clk, *child2_clk;
+};
+
+struct clk_rate_change_sibling_div_div_test_param {
+ const char *desc;
+ const struct clk_ops *ops;
+ unsigned int extra_child_flags;
+};
+
+static const struct clk_rate_change_sibling_div_div_test_param
+clk_rate_change_sibling_div_div_test_regular_ops_params[] = {
+ {
+ .desc = "regular_ops",
+ .ops = &clk_dummy_div_ops,
+ .extra_child_flags = 0,
+ },
+};
+
+KUNIT_ARRAY_PARAM_DESC(clk_rate_change_sibling_div_div_test_regular_ops,
+ clk_rate_change_sibling_div_div_test_regular_ops_params, desc)
+
+static int clk_rate_change_sibling_div_div_test_init(struct kunit *test)
+{
+ const struct clk_rate_change_sibling_div_div_test_param *param = test->param_value;
+ struct clk_rate_change_sibling_div_div_context *ctx;
+ int ret;
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+ test->priv = ctx;
+
+ ctx->parent.hw.init = CLK_HW_INIT_NO_PARENT("parent", &clk_dummy_rate_ops, 0);
+ ctx->parent.rate = 24 * HZ_PER_MHZ;
+ ret = clk_hw_register_kunit(test, NULL, &ctx->parent.hw);
+ if (ret)
+ return ret;
+
+ ctx->child1.hw.init = CLK_HW_INIT_HW("child1", &ctx->parent.hw,
+ param->ops,
+ CLK_SET_RATE_PARENT | param->extra_child_flags);
+ ctx->child1.div = 1;
+ ret = clk_hw_register_kunit(test, NULL, &ctx->child1.hw);
+ if (ret)
+ return ret;
+
+ ctx->child2.hw.init = CLK_HW_INIT_HW("child2", &ctx->parent.hw,
+ param->ops,
+ CLK_SET_RATE_PARENT | param->extra_child_flags);
+ ctx->child2.div = 1;
+ ret = clk_hw_register_kunit(test, NULL, &ctx->child2.hw);
+ if (ret)
+ return ret;
+
+ ctx->parent_clk = clk_hw_get_clk(&ctx->parent.hw, NULL);
+ ctx->child1_clk = clk_hw_get_clk(&ctx->child1.hw, NULL);
+ ctx->child2_clk = clk_hw_get_clk(&ctx->child2.hw, NULL);
+
+ KUNIT_EXPECT_EQ(test, clk_get_rate(ctx->parent_clk), 24 * HZ_PER_MHZ);
+ KUNIT_EXPECT_EQ(test, clk_get_rate(ctx->child1_clk), 24 * HZ_PER_MHZ);
+ KUNIT_EXPECT_EQ(test, clk_get_rate(ctx->child2_clk), 24 * HZ_PER_MHZ);
+
+ return 0;
+}
+
+static void clk_rate_change_sibling_div_div_test_exit(struct kunit *test)
+{
+ struct clk_rate_change_sibling_div_div_context *ctx = test->priv;
+
+ clk_put(ctx->parent_clk);
+ clk_put(ctx->child1_clk);
+ clk_put(ctx->child2_clk);
+}
+
+/*
+ * Test that, for a parent with two divider-only children with CLK_SET_RATE_PARENT set
+ * and one requests a rate compatible with the existing parent rate, the parent and
+ * sibling rates are not affected.
+ */
+static void clk_test_rate_change_sibling_div_div_1(struct kunit *test)
+{
+ struct clk_rate_change_sibling_div_div_context *ctx = test->priv;
+ int ret;
+
+ ret = clk_set_rate(ctx->child1_clk, 6 * HZ_PER_MHZ);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ KUNIT_EXPECT_EQ(test, clk_get_rate(ctx->parent_clk), 24 * HZ_PER_MHZ);
+ KUNIT_EXPECT_EQ(test, clk_get_rate(ctx->child1_clk), 6 * HZ_PER_MHZ);
+ KUNIT_EXPECT_EQ(test, ctx->child1.div, 4);
+ KUNIT_EXPECT_EQ(test, clk_get_rate(ctx->child2_clk), 24 * HZ_PER_MHZ);
+ KUNIT_EXPECT_EQ(test, ctx->child2.div, 1);
+}
+
+/*
+ * Test that, for a parent with two divider-only children with CLK_SET_RATE_PARENT
+ * set and one requests a rate incompatible with the existing parent rate, the
+ * sibling rate is also affected. This preserves existing behavior in the clk
+ * core that some drivers may be unknowingly dependent on.
+ */
+static void clk_test_rate_change_sibling_div_div_2_v1(struct kunit *test)
+{
+ struct clk_rate_change_sibling_div_div_context *ctx = test->priv;
+ int ret;
+
+ ret = clk_set_rate(ctx->child1_clk, 32 * HZ_PER_MHZ);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ /*
+ * The last sibling rate change is the one that was successful, and
+ * wins. The parent, and two children are all changed to 32 MHz. This
+ * keeps the long-standing behavior of the clk core that some drivers
+ * may be unknowingly dependent on.
+ */
+ KUNIT_EXPECT_EQ(test, clk_get_rate(ctx->parent_clk), 32 * HZ_PER_MHZ);
+ KUNIT_EXPECT_EQ(test, clk_get_rate(ctx->child1_clk), 32 * HZ_PER_MHZ);
+ KUNIT_EXPECT_EQ(test, ctx->child1.div, 1);
+ KUNIT_EXPECT_EQ(test, clk_get_rate(ctx->child2_clk), 32 * HZ_PER_MHZ);
+ KUNIT_EXPECT_EQ(test, ctx->child2.div, 1);
+}
+
+static struct kunit_case clk_rate_change_sibling_div_div_cases[] = {
+ KUNIT_CASE_PARAM(clk_test_rate_change_sibling_div_div_1,
+ clk_rate_change_sibling_div_div_test_regular_ops_gen_params),
+ KUNIT_CASE_PARAM(clk_test_rate_change_sibling_div_div_2_v1,
+ clk_rate_change_sibling_div_div_test_regular_ops_gen_params),
+ {}
+};
+
+/*
+ * Test suite that creates a parent with two divider-only children, and
+ * documents the behavior of what happens to the sibling clock when one child
+ * changes its rate.
+ */
+static struct kunit_suite clk_rate_change_sibling_div_div_test_suite = {
+ .name = "clk-rate-change-sibling-div-div",
+ .init = clk_rate_change_sibling_div_div_test_init,
+ .exit = clk_rate_change_sibling_div_div_test_exit,
+ .test_cases = clk_rate_change_sibling_div_div_cases,
+};
+
static int
clk_orphan_transparent_multiple_parent_mux_test_init(struct kunit *test)
{
@@ -3592,6 +3737,7 @@ kunit_test_suites(
&clk_leaf_mux_set_rate_parent_test_suite,
&clk_test_suite,
&clk_multiple_parents_mux_test_suite,
+ &clk_rate_change_sibling_div_div_test_suite,
&clk_mux_no_reparent_test_suite,
&clk_mux_notifier_test_suite,
&clk_orphan_transparent_multiple_parent_mux_test_suite,

--
2.53.0