I stumpled upon this, when familiarizing myself with clk drivers.
Unfortunately, I have no boards to test this patch. It seems the only
user of this flag in mainline is drivers/clk/imx/clk-composite-7ulp.c,
therefore I'm cc-ing
get_maintainers.pl --git-blame -f drivers/clk/imx/clk-composite-7ulp.c
in the hopes of a wider audience.
V2: https://lore.kernel.org/lkml/[email protected]/
V1: https://lore.kernel.org/lkml/[email protected]/
Changes since V2:
- Completely reworked the test cases
- Moved tests to separate file as per Stephen's request
- Move each edge case into their individual test case as per
Stephen's request
- Test clk_fd_round_rate instead of
clk_fractional_divider_general_approximation as testing the latter
broke builds
Changes since V1:
- Added test case as requested by Stephen Boyd
- Fixed commit message as the Cc: was missing a closing bracket, so that the
original mail unfortunately did not go out to A. s. Dong.
Thank you for considering this contribution,
Frank
Frank Oltmanns (2):
clk: fractional-divider: Improve approximation when zero based
clk: fractional-divider: tests: Add test suite for edge cases
drivers/clk/.kunitconfig | 1 +
drivers/clk/Kconfig | 7 +
drivers/clk/Makefile | 1 +
drivers/clk/clk-fractional-divider.c | 26 ++-
drivers/clk/clk-fractional-divider_test.c | 196 ++++++++++++++++++++++
5 files changed, 224 insertions(+), 7 deletions(-)
create mode 100644 drivers/clk/clk-fractional-divider_test.c
--
2.41.0
Consider the CLK_FRAC_DIVIDER_ZERO_BASED flag when finding the best
approximation for m and n. By doing so, increase the range of valid
values for the numerator and denominator by 1.
Cc: A.s. Dong <[email protected]>
Signed-off-by: Frank Oltmanns <[email protected]>
---
drivers/clk/clk-fractional-divider.c | 26 +++++++++++++++++++-------
1 file changed, 19 insertions(+), 7 deletions(-)
diff --git a/drivers/clk/clk-fractional-divider.c b/drivers/clk/clk-fractional-divider.c
index 479297763e70..7da21cd2bdb1 100644
--- a/drivers/clk/clk-fractional-divider.c
+++ b/drivers/clk/clk-fractional-divider.c
@@ -123,6 +123,7 @@ void clk_fractional_divider_general_approximation(struct clk_hw *hw,
unsigned long *m, unsigned long *n)
{
struct clk_fractional_divider *fd = to_clk_fd(hw);
+ unsigned long max_m, max_n;
/*
* Get rate closer to *parent_rate to guarantee there is no overflow
@@ -138,9 +139,15 @@ void clk_fractional_divider_general_approximation(struct clk_hw *hw,
rate <<= scale - fd->nwidth;
}
- rational_best_approximation(rate, *parent_rate,
- GENMASK(fd->mwidth - 1, 0), GENMASK(fd->nwidth - 1, 0),
- m, n);
+ if (fd->flags & CLK_FRAC_DIVIDER_ZERO_BASED) {
+ max_m = 1 << fd->mwidth;
+ max_n = 1 << fd->nwidth;
+ } else {
+ max_m = GENMASK(fd->mwidth - 1, 0);
+ max_n = GENMASK(fd->nwidth - 1, 0);
+ }
+
+ rational_best_approximation(rate, *parent_rate, max_m, max_n, m, n);
}
static long clk_fd_round_rate(struct clk_hw *hw, unsigned long rate,
@@ -169,13 +176,18 @@ static int clk_fd_set_rate(struct clk_hw *hw, unsigned long rate,
{
struct clk_fractional_divider *fd = to_clk_fd(hw);
unsigned long flags = 0;
- unsigned long m, n;
+ unsigned long m, n, max_m, max_n;
u32 mmask, nmask;
u32 val;
- rational_best_approximation(rate, parent_rate,
- GENMASK(fd->mwidth - 1, 0), GENMASK(fd->nwidth - 1, 0),
- &m, &n);
+ if (fd->flags & CLK_FRAC_DIVIDER_ZERO_BASED) {
+ max_m = 1 << fd->mwidth;
+ max_n = 1 << fd->nwidth;
+ } else {
+ max_m = GENMASK(fd->mwidth - 1, 0);
+ max_n = GENMASK(fd->nwidth - 1, 0);
+ }
+ rational_best_approximation(rate, parent_rate, max_m, max_n, &m, &n);
if (fd->flags & CLK_FRAC_DIVIDER_ZERO_BASED) {
m--;
--
2.41.0
In light of the recent discovery that the fractional divisor
approximation does not utilize the full available range for clocks that
are flagged CLK_FRAC_DIVIDER_ZERO_BASED, implement tests for the edge
cases of this clock type.
Signed-off-by: Frank Oltmanns <[email protected]>
---
Please note: I get two checkpatch warnings for this patch:
- Concerning the help text in Kconfig.
- Regarding the file being added, asking if MAINTAINERS needs updating.
Both the help text as well as the MAINTAINERS file seem fine to me.
As expected, when the tests are run *without* PATCH 1, the two "zero
based" test cases fail:
================= clk-fd-test (4 subtests) =================
[PASSED] clk_fd_test_round_rate_max_div
[PASSED] clk_fd_test_round_rate_max_mul
# clk_fd_test_round_rate_max_div_zero_based: EXPECTATION FAILED at drivers/clk/clk-fractional-divider_test.c:139
Expected rounded_rate == parent_rate / 8, but
rounded_rate == 342857142 (0x146f95b6)
parent_rate / 8 == 300000000 (0x11e1a300)
[FAILED] clk_fd_test_round_rate_max_div_zero_based
# clk_fd_test_round_rate_max_mul_zero_based: EXPECTATION FAILED at drivers/clk/clk-fractional-divider_test.c:172
Expected rounded_rate == rate, but
rounded_rate == 252000000 (0xf053700)
rate == 240000000 (0xe4e1c00)
[FAILED] clk_fd_test_round_rate_max_mul_zero_based
# clk-fd-test: pass:2 fail:2 skip:0 total:4
# Totals: pass:2 fail:2 skip:0 total:4
=================== [FAILED] clk-fd-test ===================
Best regards,
Frank
drivers/clk/.kunitconfig | 1 +
drivers/clk/Kconfig | 7 +
drivers/clk/Makefile | 1 +
drivers/clk/clk-fractional-divider_test.c | 196 ++++++++++++++++++++++
4 files changed, 205 insertions(+)
create mode 100644 drivers/clk/clk-fractional-divider_test.c
diff --git a/drivers/clk/.kunitconfig b/drivers/clk/.kunitconfig
index 2fbeb71316f8..efa12ac2b3f2 100644
*** a/drivers/clk/.kunitconfig
--- b/drivers/clk/.kunitconfig
***************
*** 2,5 ****
--- 2,6 ----
CONFIG_COMMON_CLK=y
CONFIG_CLK_KUNIT_TEST=y
CONFIG_CLK_GATE_KUNIT_TEST=y
+ CONFIG_CLK_FD_KUNIT_TEST=y
CONFIG_UML_PCI_OVER_VIRTIO=n
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 016814e15536..3fbb40cb5551 100644
*** a/drivers/clk/Kconfig
--- b/drivers/clk/Kconfig
***************
*** 513,516 ****
--- 513,523 ----
help
Kunit test for the basic clk gate type.
+ config CLK_FD_KUNIT_TEST
+ tristate "Basic fractional divider type Kunit test" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ Kunit test for the clk-fractional-divider type.
+
endif
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 0aebef17edc6..9d2337c12dd1 100644
*** a/drivers/clk/Makefile
--- b/drivers/clk/Makefile
@@ -12,6 +12,7 @@
obj-$(CONFIG_COMMON_CLK) += clk-mux.o
obj-$(CONFIG_COMMON_CLK) += clk-composite.o
obj-$(CONFIG_COMMON_CLK) += clk-fractional-divider.o
+obj-$(CONFIG_CLK_FD_KUNIT_TEST) += clk-fractional-divider_test.o
obj-$(CONFIG_COMMON_CLK) += clk-gpio.o
ifeq ($(CONFIG_OF), y)
obj-$(CONFIG_COMMON_CLK) += clk-conf.o
diff --git a/drivers/clk/clk-fractional-divider_test.c b/drivers/clk/clk-fractional-divider_test.c
new file mode 100644
index 000000000000..0ea6b1ae85a8
--- /dev/null
+++ b/drivers/clk/clk-fractional-divider_test.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Kunit test for clock fractional divider
+ */
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+
+/* Needed for clk_hw_get_clk() */
+#include "clk.h"
+
+/* Needed for clk_fractional_divider_ops */
+#include "clk-fractional-divider.h"
+
+#include <kunit/test.h>
+
+u32 fdregsim;
+
+static int clk_fd_test_init(struct kunit *test)
+{
+ struct clk_hw *hw;
+
+ hw = clk_hw_register_fractional_divider(NULL, "test_fd", NULL,
+ 0, /* flags */
+ &fdregsim,
+ 0, 2, /* mshift, mwidth */
+ 8, 3, /* nshift, nwidth */
+ 0, /* clk_divider_flags */
+ NULL); /* spinlock */
+ if (!hw)
+ return -ENOMEM;
+ else if (IS_ERR_VALUE(hw))
+ return PTR_ERR(hw);
+
+ test->priv = hw;
+ return 0;
+}
+
+static void clk_fd_test_exit(struct kunit *test)
+{
+ struct clk_hw *hw = test->priv;
+
+ clk_hw_unregister(hw);
+}
+
+/*
+ * Test the maximum divisor case for fd clock without flags.
+ *
+ * Prerequisites:
+ * - Requested rate is 240MHz.
+ * - Parent runs at 10 times the rate of the requested rate (2.4GHz).
+ * - m and n are 2 and 3 bits wide respectively.
+ * - The clock has no flags set, hence
+ * - the maximum value for the numerator is 3
+ * - the maximum value for the denominator is 7
+ *
+ * Expected result:
+ * Expect the highest possible divisor to be used in order to get as close as possible to the
+ * requested rate, resulting in the following rounded rate:
+ * rounded_rate = parent_rate / 7.
+ */
+static void clk_fd_test_round_rate_max_div(struct kunit *test)
+{
+ struct clk_hw *hw = test->priv;
+ struct clk *clk = clk_hw_get_clk(hw, NULL);
+ unsigned long rate, parent_rate, rounded_rate;
+
+ rate = 240000000;
+ parent_rate = 10 * rate;
+
+ rounded_rate = clk_fractional_divider_ops.round_rate(hw, rate, &parent_rate);
+ /* This test case assumes that the round_rate operation does not change the parent */
+ KUNIT_ASSERT_EQ(test, parent_rate, 10 * rate);
+ /* Make sure the highest possible divisor was used */
+ KUNIT_EXPECT_EQ(test, rounded_rate, parent_rate / 7);
+
+ clk_put(clk);
+}
+
+/*
+ * Test the maximum multiplier case for fd clock without flags.
+ *
+ * Prerequisites:
+ * - Requested rate is 240MHz.
+ * - Parent runs at 7/4th the rate of the requested rate (420MHz).
+ * - m and n are 2 and 3 bits wide respectively.
+ * - The clock has no flags set, hence
+ * - the maximum value for the numerator is 3
+ * - the maximum value for the denominator is 7
+ *
+ * Expected result:
+ * Ideally the parent rate must be multiplied by 4 and divided by 7. But the highest possible
+ * multiplier is 3. So, the closest rate is not 4/7th of the parent rate, but 3/5th (252 MHz).
+ */
+static void clk_fd_test_round_rate_max_mul(struct kunit *test)
+{
+ struct clk_hw *hw = test->priv;
+ unsigned long rate, parent_rate, rounded_rate;
+
+ rate = 240000000;
+ parent_rate = 240000000 * 7 / 4;
+
+ rounded_rate = clk_fractional_divider_ops.round_rate(hw, rate, &parent_rate);
+ /* This test case assumes that the round_rate operation does not change the parent */
+ KUNIT_ASSERT_EQ(test, parent_rate, rate * 7 / 4);
+ /* Make sure the highest possible multiplier was used */
+ KUNIT_EXPECT_EQ(test, rounded_rate, parent_rate * 3 / 5);
+}
+
+/*
+ * Test the maximum divisor case for zero based fd clock.
+ *
+ * Prerequisites:
+ * - Requested rate is 240MHz.
+ * - Parent runs at 10 times the rate of the requested rate (2.4 GHz).
+ * - m and n are 2 and 3 bits wide respectively.
+ * - The clock is zero based, hence
+ * - the maximum value for the numerator is 4
+ * - the maximum value for the denominator is 8
+ *
+ * Expected result:
+ * Expect the highest possible divisor to be used resulting in the following rounded rate:
+ * rounded_rate = parent_rate / 8.
+ */
+static void clk_fd_test_round_rate_max_div_zero_based(struct kunit *test)
+{
+ struct clk_hw *hw = test->priv;
+ unsigned long rate, parent_rate, rounded_rate;
+ struct clk_fractional_divider *fd = to_clk_fd(hw);
+
+ fd->flags = CLK_FRAC_DIVIDER_ZERO_BASED;
+
+ rate = 240000000;
+ parent_rate = 10 * rate;
+
+ rounded_rate = clk_fractional_divider_ops.round_rate(hw, rate, &parent_rate);
+ /* This test case assumes that the round_rate operation does not change the parent */
+ KUNIT_ASSERT_EQ(test, parent_rate, 10 * rate);
+ /* Make sure the highest possible divisor was used */
+ KUNIT_EXPECT_EQ(test, rounded_rate, parent_rate / 8);
+}
+
+/*
+ * Test the maximum multiplier case for fd clock without flags.
+ *
+ * Prerequisites:
+ * - Requested rate is 240MHz.
+ * - Parent runs at 7/4th the rate of the requested rate (420MHz).
+ * - m and n are 2 and 3 bits wide respectively.
+ * - The clock is zero based, hence
+ * - the maximum value for the numerator is 4
+ * - the maximum value for the denominator is 8
+ *
+ * Expected result:
+ * Ideally the parent rate must be multiplied by 4 and divided by seven. And since for a zero based
+ * clock 4 is actually the highest possible multiplier the exact rate can be reached.
+ */
+static void clk_fd_test_round_rate_max_mul_zero_based(struct kunit *test)
+{
+ struct clk_hw *hw = test->priv;
+ unsigned long rate, parent_rate, rounded_rate;
+ struct clk_fractional_divider *fd = to_clk_fd(hw);
+
+ fd->flags = CLK_FRAC_DIVIDER_ZERO_BASED;
+
+ rate = 240000000;
+ parent_rate = 240000000 * 7 / 4;
+
+ rounded_rate = clk_fractional_divider_ops.round_rate(hw, rate, &parent_rate);
+ /* This test case assumes that the round_rate operation does not change the parent */
+ KUNIT_ASSERT_EQ(test, parent_rate, rate * 7 / 4);
+ /* Make sure the highest possible multiplier was used */
+ KUNIT_EXPECT_EQ(test, rounded_rate, rate);
+}
+
+static struct kunit_case clk_fd_test_cases[] = {
+ KUNIT_CASE(clk_fd_test_round_rate_max_div),
+ KUNIT_CASE(clk_fd_test_round_rate_max_mul),
+ KUNIT_CASE(clk_fd_test_round_rate_max_div_zero_based),
+ KUNIT_CASE(clk_fd_test_round_rate_max_mul_zero_based),
+ {}
+};
+
+/*
+ * Test suite for a fractional divider clock.
+ */
+static struct kunit_suite clk_fd_test_suite = {
+ .name = "clk-fd-test",
+ .init = clk_fd_test_init,
+ .exit = clk_fd_test_exit,
+ .test_cases = clk_fd_test_cases,
+};
+
+kunit_test_suites(
+ &clk_fd_test_suite
+);
+MODULE_LICENSE("GPL");
--
2.41.0