| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * KUnit tests for clk_divider_bestdiv() |
| */ |
| #include <kunit/test.h> |
| #include <linux/clk.h> |
| #include <linux/clk-provider.h> |
| #include <linux/limits.h> |
| #include <linux/units.h> |
| |
| #define PARENT_RATE_1GHZ GIGA |
| #define PARENT_RATE_2GHZ (2 * GIGA) |
| #define PARENT_RATE_4GHZ (4 * GIGA) |
| |
| KUNIT_DEFINE_ACTION_WRAPPER(clk_hw_unregister_fixed_rate_wrapper, |
| clk_hw_unregister_fixed_rate, struct clk_hw *); |
| KUNIT_DEFINE_ACTION_WRAPPER(clk_hw_unregister_divider_wrapper, |
| clk_hw_unregister_divider, struct clk_hw *); |
| KUNIT_DEFINE_ACTION_WRAPPER(clk_hw_unregister_mux_wrapper, |
| clk_hw_unregister_mux, struct clk_hw *); |
| |
| static const struct clk_div_table bestdiv_table[] = { |
| { .val = 0, .div = 2 }, |
| { .val = 1, .div = 4 }, |
| { .val = 2, .div = 8 }, |
| { /* sentinel */ } |
| }; |
| |
| /* |
| * Test that clk_round_rate(clk, ULONG_MAX) returns the maximum achievable |
| * rate for a divider clock. |
| */ |
| static void clk_divider_bestdiv_ulong_max_returns_max_rate(struct kunit *test) |
| { |
| struct clk_hw *parent_hw, *div_hw; |
| unsigned long rate; |
| u32 *fake_reg; |
| |
| fake_reg = kunit_kzalloc(test, sizeof(*fake_reg), GFP_KERNEL); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fake_reg); |
| |
| parent_hw = clk_hw_register_fixed_rate(NULL, "bestdiv-parent", |
| NULL, 0, PARENT_RATE_1GHZ); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent_hw); |
| KUNIT_ASSERT_EQ(test, 0, |
| kunit_add_action_or_reset(test, clk_hw_unregister_fixed_rate_wrapper, |
| parent_hw)); |
| |
| div_hw = clk_hw_register_divider_table(NULL, "bestdiv-div", |
| "bestdiv-parent", |
| CLK_SET_RATE_PARENT, |
| (void __iomem __force *)fake_reg, |
| 0, 2, 0, bestdiv_table, NULL); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, div_hw); |
| KUNIT_ASSERT_EQ(test, 0, |
| kunit_add_action_or_reset(test, clk_hw_unregister_divider_wrapper, |
| div_hw)); |
| |
| /* |
| * ULONG_MAX is the canonical way to probe the maximum rate a clock |
| * can produce. |
| */ |
| rate = clk_hw_round_rate(div_hw, ULONG_MAX); |
| KUNIT_EXPECT_EQ(test, rate, PARENT_RATE_1GHZ / 2); |
| } |
| |
| /* |
| * Test that clk_round_rate(clk, ULONG_MAX) returns the correct maximum rate |
| * when a mux clock sits between a divider and its parent candidates. |
| * |
| * Topology: |
| * |
| * [fixed 4 GHz] --\ |
| * +--> [mux CLK_SET_RATE_PARENT] --> [div {2,4,8} CLK_SET_RATE_PARENT] |
| * [fixed 2 GHz] --/ |
| * |
| */ |
| static void clk_divider_bestdiv_mux_ulong_max_returns_max_rate(struct kunit *test) |
| { |
| static const char * const mux_parents[] = { |
| "bestdiv-mux-parent-a", |
| "bestdiv-mux-parent-b", |
| }; |
| struct clk_hw *parent_a_hw, *parent_b_hw, *mux_hw, *div_hw; |
| u32 *fake_reg_mux, *fake_reg_div; |
| unsigned long rate; |
| |
| fake_reg_mux = kunit_kzalloc(test, sizeof(*fake_reg_mux), GFP_KERNEL); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fake_reg_mux); |
| |
| fake_reg_div = kunit_kzalloc(test, sizeof(*fake_reg_div), GFP_KERNEL); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fake_reg_div); |
| |
| /* Higher-rate parent: the mux should select this for ULONG_MAX. */ |
| parent_a_hw = clk_hw_register_fixed_rate(NULL, "bestdiv-mux-parent-a", |
| NULL, 0, PARENT_RATE_4GHZ); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent_a_hw); |
| KUNIT_ASSERT_EQ(test, 0, |
| kunit_add_action_or_reset(test, clk_hw_unregister_fixed_rate_wrapper, |
| parent_a_hw)); |
| |
| /* Lower-rate parent: should not be selected. */ |
| parent_b_hw = clk_hw_register_fixed_rate(NULL, "bestdiv-mux-parent-b", |
| NULL, 0, PARENT_RATE_2GHZ); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent_b_hw); |
| KUNIT_ASSERT_EQ(test, 0, |
| kunit_add_action_or_reset(test, clk_hw_unregister_fixed_rate_wrapper, |
| parent_b_hw)); |
| |
| /* |
| * 1-bit mux register selects between the two parents. |
| * CLK_SET_RATE_PARENT allows the divider's rate request to |
| * propagate into clk_mux_determine_rate(). |
| */ |
| mux_hw = clk_hw_register_mux(NULL, "bestdiv-mux", |
| mux_parents, ARRAY_SIZE(mux_parents), |
| CLK_SET_RATE_PARENT, |
| (void __iomem __force *)fake_reg_mux, |
| 0, 1, 0, NULL); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mux_hw); |
| KUNIT_ASSERT_EQ(test, 0, |
| kunit_add_action_or_reset(test, clk_hw_unregister_mux_wrapper, |
| mux_hw)); |
| |
| div_hw = clk_hw_register_divider_table(NULL, "bestdiv-mux-div", |
| "bestdiv-mux", |
| CLK_SET_RATE_PARENT, |
| (void __iomem __force *)fake_reg_div, |
| 0, 2, 0, bestdiv_table, NULL); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, div_hw); |
| KUNIT_ASSERT_EQ(test, 0, |
| kunit_add_action_or_reset(test, clk_hw_unregister_divider_wrapper, |
| div_hw)); |
| |
| rate = clk_hw_round_rate(div_hw, ULONG_MAX); |
| KUNIT_EXPECT_EQ(test, rate, PARENT_RATE_4GHZ / 2); |
| } |
| |
| static struct kunit_case clk_divider_bestdiv_test_cases[] = { |
| KUNIT_CASE(clk_divider_bestdiv_ulong_max_returns_max_rate), |
| KUNIT_CASE(clk_divider_bestdiv_mux_ulong_max_returns_max_rate), |
| {} |
| }; |
| |
| static struct kunit_suite clk_divider_bestdiv_test_suite = { |
| .name = "clk_divider_bestdiv", |
| .test_cases = clk_divider_bestdiv_test_cases, |
| }; |
| |
| kunit_test_suite(clk_divider_bestdiv_test_suite); |
| |
| MODULE_DESCRIPTION("KUnit tests for clk divider"); |
| MODULE_LICENSE("GPL"); |