From: Brian Masney <bmasney@redhat.com>
To: Michael Turquette <mturquette@baylibre.com>,
Stephen Boyd <sboyd@kernel.org>,
Maxime Ripard <mripard@kernel.org>,
Alberto Ruiz <aruiz@redhat.com>,
Brendan Higgins <brendan.higgins@linux.dev>,
David Gow <david@davidgow.net>, Rae Moar <raemoar63@gmail.com>
Cc: linux-clk@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-kselftest@vger.kernel.org, kunit-dev@googlegroups.com,
Brian Masney <bmasney@redhat.com>
Subject: [PATCH v8 2/8] clk: divider: introduce divider kunit tests
Date: Fri, 27 Mar 2026 15:31:57 -0400 [thread overview]
Message-ID: <20260327-clk-scaling-v8-2-86cd0aba3c5f@redhat.com> (raw)
In-Reply-To: <20260327-clk-scaling-v8-0-86cd0aba3c5f@redhat.com>
Introduce some kunit tests for the generic clk-divider.c implementation.
This test suite 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.
For consistency with clk-fixed-rate_test.c and
drivers/clk/clk-gate_test.c, the divider tests are setup as it's own
separate kernel module.
Link: https://lore.kernel.org/linux-clk/aUSWU7UymULCXOeF@redhat.com/
Link: https://lpc.events/event/19/contributions/2152/
Signed-off-by: Brian Masney <bmasney@redhat.com>
---
drivers/clk/.kunitconfig | 1 +
drivers/clk/Kconfig | 7 ++
drivers/clk/Makefile | 1 +
drivers/clk/clk-divider_test.c | 212 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 221 insertions(+)
diff --git a/drivers/clk/.kunitconfig b/drivers/clk/.kunitconfig
index 8a0ea41934a2100e1bb1521ce3ad490baec76ec2..ea05b9a28c8000c647c76c4adf813da97c500ab6 100644
--- a/drivers/clk/.kunitconfig
+++ b/drivers/clk/.kunitconfig
@@ -4,6 +4,7 @@ CONFIG_OF=y
CONFIG_OF_OVERLAY=y
CONFIG_COMMON_CLK=y
CONFIG_CLK_KUNIT_TEST=y
+CONFIG_CLK_DIVIDER_KUNIT_TEST=y
CONFIG_CLK_FIXED_RATE_KUNIT_TEST=y
CONFIG_CLK_GATE_KUNIT_TEST=y
CONFIG_CLK_FD_KUNIT_TEST=y
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 8cc300b90b5fd9fb38ce94fcb1098810c3f52c36..ba4af56949e39249652fc414eb23b44aee1d37f5 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -550,6 +550,13 @@ config CLK_KUNIT_TEST
help
Kunit tests for the common clock framework.
+config CLK_DIVIDER_KUNIT_TEST
+ tristate "Basic divider clk type KUnit test" if !KUNIT_ALL_TESTS
+ depends on KUNIT && CLK_KUNIT_TEST
+ default KUNIT_ALL_TESTS
+ help
+ KUnit tests for the basic divider clk type.
+
config CLK_FIXED_RATE_KUNIT_TEST
tristate "Basic fixed rate clk type KUnit test" if !KUNIT_ALL_TESTS
depends on KUNIT
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index f52cf3ac64fcce7e20f3fd91f837c5096375521a..f11d37cb09423b914d7653fa4c3fa17370430aa7 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -21,6 +21,7 @@ clk-test-y := clk_test.o \
kunit_clk_hw_get_dev_of_node.dtbo.o \
kunit_clk_parent_data_test.dtbo.o
obj-$(CONFIG_COMMON_CLK) += clk-divider.o
+obj-$(CONFIG_CLK_DIVIDER_KUNIT_TEST) += clk-divider_test.o
obj-$(CONFIG_COMMON_CLK) += clk-fixed-factor.o
obj-$(CONFIG_COMMON_CLK) += clk-fixed-rate.o
obj-$(CONFIG_CLK_FIXED_RATE_KUNIT_TEST) += clk-fixed-rate-test.o
diff --git a/drivers/clk/clk-divider_test.c b/drivers/clk/clk-divider_test.c
new file mode 100644
index 0000000000000000000000000000000000000000..0991bd865da03c1e42f154b25a7f1b5760dff3c5
--- /dev/null
+++ b/drivers/clk/clk-divider_test.c
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Kunit tests for clk divider
+ */
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/platform_device.h>
+#include <linux/units.h>
+
+#include <kunit/clk.h>
+#include <kunit/test.h>
+
+/* 4 ought to be enough for anybody */
+#define CLK_DUMMY_DIV_WIDTH 4
+#define CLK_DUMMY_DIV_FLAGS (CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ROUND_CLOSEST)
+
+struct clk_dummy_div {
+ struct clk_hw hw;
+ unsigned int div;
+};
+
+static unsigned long clk_dummy_div_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_dummy_div *div = container_of(hw, struct clk_dummy_div, hw);
+
+ return divider_recalc_rate(hw, parent_rate, div->div, NULL,
+ CLK_DUMMY_DIV_FLAGS, CLK_DUMMY_DIV_WIDTH);
+}
+
+static int clk_dummy_div_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) && req->best_parent_rate < req->rate)
+ return -EINVAL;
+
+ return divider_determine_rate(hw, req, NULL, CLK_DUMMY_DIV_WIDTH, CLK_DUMMY_DIV_FLAGS);
+}
+
+static int clk_dummy_div_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_dummy_div *div = container_of(hw, struct clk_dummy_div, hw);
+ int ret;
+
+ ret = divider_get_val(rate, parent_rate, NULL, CLK_DUMMY_DIV_WIDTH,
+ CLK_DUMMY_DIV_FLAGS);
+ if (ret < 0)
+ return ret;
+
+ div->div = ret;
+
+ return 0;
+}
+
+static const struct clk_ops clk_dummy_div_ops = {
+ .recalc_rate = clk_dummy_div_recalc_rate,
+ .determine_rate = clk_dummy_div_determine_rate,
+ .set_rate = clk_dummy_div_set_rate,
+};
+
+struct clk_rate_change_divider_context {
+ struct clk_dummy_context parent;
+ struct clk_dummy_div child1, child2;
+ struct clk *parent_clk, *child1_clk, *child2_clk;
+};
+
+struct clk_rate_change_divider_test_param {
+ const char *desc;
+ const struct clk_ops *ops;
+ unsigned int extra_child_flags;
+};
+
+static const struct clk_rate_change_divider_test_param
+clk_rate_change_divider_test_regular_ops_params[] = {
+ {
+ .desc = "regular_ops",
+ .ops = &clk_dummy_div_ops,
+ .extra_child_flags = 0,
+ },
+};
+
+KUNIT_ARRAY_PARAM_DESC(clk_rate_change_divider_test_regular_ops,
+ clk_rate_change_divider_test_regular_ops_params, desc)
+
+static int clk_rate_change_divider_test_init(struct kunit *test)
+{
+ const struct clk_rate_change_divider_test_param *param = test->param_value;
+ struct clk_rate_change_divider_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);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ 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);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ 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);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ ctx->parent_clk = clk_hw_get_clk_prepared_enabled_kunit(test, &ctx->parent.hw, __func__);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->parent_clk);
+
+ ctx->child1_clk = clk_hw_get_clk_prepared_enabled_kunit(test, &ctx->child1.hw, __func__);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->child1_clk);
+
+ ctx->child2_clk = clk_hw_get_clk_prepared_enabled_kunit(test, &ctx->child2.hw, __func__);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->child2_clk);
+
+ return 0;
+}
+
+/*
+ * 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_divider_1(struct kunit *test)
+{
+ struct clk_rate_change_divider_context *ctx = test->priv;
+ int ret;
+
+ KUNIT_ASSERT_EQ(test, clk_get_rate(ctx->parent_clk), 24 * HZ_PER_MHZ);
+ KUNIT_ASSERT_EQ(test, clk_get_rate(ctx->child1_clk), 24 * HZ_PER_MHZ);
+ KUNIT_EXPECT_EQ(test, ctx->child1.div, 1);
+ KUNIT_ASSERT_EQ(test, clk_get_rate(ctx->child2_clk), 24 * HZ_PER_MHZ);
+ KUNIT_EXPECT_EQ(test, ctx->child2.div, 1);
+
+ 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_divider_2_v1(struct kunit *test)
+{
+ struct clk_rate_change_divider_context *ctx = test->priv;
+ int ret;
+
+ KUNIT_ASSERT_EQ(test, clk_get_rate(ctx->parent_clk), 24 * HZ_PER_MHZ);
+ KUNIT_ASSERT_EQ(test, clk_get_rate(ctx->child1_clk), 24 * HZ_PER_MHZ);
+ KUNIT_EXPECT_EQ(test, ctx->child1.div, 1);
+ KUNIT_ASSERT_EQ(test, clk_get_rate(ctx->child2_clk), 24 * HZ_PER_MHZ);
+ KUNIT_EXPECT_EQ(test, ctx->child2.div, 1);
+
+ 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_divider_cases[] = {
+ KUNIT_CASE_PARAM(clk_test_rate_change_divider_1,
+ clk_rate_change_divider_test_regular_ops_gen_params),
+ KUNIT_CASE_PARAM(clk_test_rate_change_divider_2_v1,
+ clk_rate_change_divider_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_divider_test_suite = {
+ .name = "clk-rate-change-divider",
+ .init = clk_rate_change_divider_test_init,
+ .test_cases = clk_rate_change_divider_cases,
+};
+
+kunit_test_suites(
+ &clk_rate_change_divider_test_suite,
+);
+
+MODULE_DESCRIPTION("Kunit tests for clk divider");
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
+MODULE_LICENSE("GPL");
--
2.53.0
next prev parent reply other threads:[~2026-03-27 19:32 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-27 19:31 [PATCH v8 0/8] clk: add support for v1 / v2 clock rate negotiation and " Brian Masney
2026-03-27 19:31 ` [PATCH v8 1/8] clk: test: export clk_dummy_rate_ops and clk_dummy_context() for other tests Brian Masney
2026-03-27 19:31 ` Brian Masney [this message]
2026-03-27 19:31 ` [PATCH v8 3/8] clk: introduce new helper clk_hw_get_children_lcm() to calculate LCM of all child rates Brian Masney
2026-10-20 9:57 ` Miquel Raynal
2026-03-27 19:31 ` [PATCH v8 4/8] clk: divider: test: introduce additional test case for parent rate change Brian Masney
2026-03-27 19:32 ` [PATCH v8 5/8] clk: introduce new flag CLK_V2_RATE_NEGOTIATION for sensitive clocks Brian Masney
2026-03-27 19:32 ` [PATCH v8 6/8] clk: divider: enable optional support for v2 rate negotiation Brian Masney
2026-03-27 19:32 ` [PATCH v8 7/8] clk: divider: test: introduce additional test case showing v2 rate change + LCM parent Brian Masney
2026-03-27 19:32 ` [PATCH v8 8/8] clk: divider: test: mark some tests as supporting only v1 negotiation Brian Masney
2026-10-20 10:11 ` [PATCH v8 0/8] clk: add support for v1 / v2 clock rate negotiation and kunit tests Miquel Raynal
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260327-clk-scaling-v8-2-86cd0aba3c5f@redhat.com \
--to=bmasney@redhat.com \
--cc=aruiz@redhat.com \
--cc=brendan.higgins@linux.dev \
--cc=david@davidgow.net \
--cc=kunit-dev@googlegroups.com \
--cc=linux-clk@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=mripard@kernel.org \
--cc=mturquette@baylibre.com \
--cc=raemoar63@gmail.com \
--cc=sboyd@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®