Parse the Spread Spectrum Configuration(SSC) from device tree and configure
them before using the clock.
Each SSC is three u32 elements which means '<modfreq spreaddepth
modmethod>', so assigned-clock-sscs is an array of multiple three u32
elements.
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
drivers/clk/clk-conf.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 69 insertions(+)
diff --git a/drivers/clk/clk-conf.c b/drivers/clk/clk-conf.c
index 303a0bb26e54a95655ce094a35b989c97ebc6fd8..dd6083597db3f8f27d86abf5640dfc3fb39a9b88 100644
--- a/drivers/clk/clk-conf.c
+++ b/drivers/clk/clk-conf.c
@@ -155,6 +155,71 @@ static int __set_clk_rates(struct device_node *node, bool clk_supplier)
return 0;
}
+static int __set_clk_spread_spectrum(struct device_node *node, bool clk_supplier)
+{
+ struct clk_spread_spectrum *sscs __free(kfree) = NULL;
+ u32 elem_size = sizeof(struct clk_spread_spectrum);
+ struct of_phandle_args clkspec;
+ int rc, count, index;
+ struct clk *clk;
+
+ /* modfreq, spreadPercent, modmethod */
+ count = of_property_count_elems_of_size(node, "assigned-clock-sscs", elem_size);
+ if (count <= 0)
+ return 0;
+
+ sscs = kcalloc(count, elem_size, GFP_KERNEL);
+ if (!sscs)
+ return -ENOMEM;
+
+ rc = of_property_read_u32_array(node, "assigned-clock-sscs", (u32 *)sscs,
+ count * 3);
+ if (rc)
+ return rc;
+
+ for (index = 0; index < count; index++) {
+ struct clk_spread_spectrum *conf = &sscs[index];
+ struct clk_hw *hw;
+
+ if (!conf->modfreq_hz && !conf->spread_bp && !conf->method)
+ continue;
+
+ rc = of_parse_phandle_with_args(node, "assigned-clocks", "#clock-cells",
+ index, &clkspec);
+ if (rc < 0) {
+ /* skip empty (null) phandles */
+ if (rc == -ENOENT)
+ continue;
+ else
+ return rc;
+ }
+
+ if (clkspec.np == node && !clk_supplier) {
+ of_node_put(clkspec.np);
+ return 0;
+ }
+
+ clk = of_clk_get_from_provider(&clkspec);
+ of_node_put(clkspec.np);
+ if (IS_ERR(clk)) {
+ if (PTR_ERR(clk) != -EPROBE_DEFER)
+ pr_warn("clk: couldn't get clock %d for %pOF\n",
+ index, node);
+ return PTR_ERR(clk);
+ }
+
+ hw = __clk_get_hw(clk);
+ rc = clk_hw_set_spread_spectrum(hw, conf);
+ if (rc < 0)
+ pr_err("clk: couldn't set %s clk spread spectrum %u %u %u: %d\n",
+ __clk_get_name(clk), conf->modfreq_hz, conf->spread_bp,
+ conf->method, rc);
+ clk_put(clk);
+ }
+
+ return 0;
+}
+
/**
* of_clk_set_defaults() - parse and set assigned clocks configuration
* @node: device node to apply clock settings for
@@ -174,6 +239,10 @@ int of_clk_set_defaults(struct device_node *node, bool clk_supplier)
if (!node)
return 0;
+ rc = __set_clk_spread_spectrum(node, clk_supplier);
+ if (rc < 0)
+ return rc;
+
rc = __set_clk_parents(node, clk_supplier);
if (rc < 0)
return rc;
--
2.37.1
Hi Peng,
On Mon, Sep 01, 2025 at 11:51:46AM +0800, Peng Fan wrote:
> Parse the Spread Spectrum Configuration(SSC) from device tree and configure
> them before using the clock.
>
> Each SSC is three u32 elements which means '<modfreq spreaddepth
> modmethod>', so assigned-clock-sscs is an array of multiple three u32
> elements.
>
> Signed-off-by: Peng Fan <peng.fan@nxp.com>
Stephen has also been asking for kunit tests to be added for new
functionality in the clk core. There's already one kunit test that
calls of_clk_set_defaults(). I attached a very rough draft of a patch
showing that it'd be possible to mock this up in a test with what's
already there. I set a log statement with the configuration from
device tree:
test_assigned_rate0: Spread Sprectrum Configuration: modfreq_hz=10000 spread_bp=3 method=1
You can run the kunit tests with:
./tools/testing/kunit/kunit.py run \
--kunitconfig drivers/clk/.kunitconfig \
--raw_output=all
Additionally, what do you think about making a dt-bindings include file
for CLK_SSC_CENTER_SPREAD + friends? Right now, the test illustrates
that we need to hardcode the number from the clk-provider.h file inside
the DTS.
Here's the patch and feel free to make it your own as you see fit.
Brian
diff --git a/drivers/clk/clk_test.c b/drivers/clk/clk_test.c
index a268d7b5d4cb..6cc3ad883b35 100644
--- a/drivers/clk/clk_test.c
+++ b/drivers/clk/clk_test.c
@@ -28,6 +28,7 @@ static const struct clk_ops empty_clk_ops = { };
struct clk_dummy_context {
struct clk_hw hw;
unsigned long rate;
+ struct clk_spread_spectrum sscs;
};
static unsigned long clk_dummy_recalc_rate(struct clk_hw *hw,
@@ -83,6 +84,17 @@ static int clk_dummy_set_rate(struct clk_hw *hw,
return 0;
}
+static int clk_dummy_set_spread_spectrum(struct clk_hw *hw,
+ struct clk_spread_spectrum *conf)
+{
+ struct clk_dummy_context *ctx =
+ container_of(hw, struct clk_dummy_context, hw);
+
+ ctx->sscs = *conf;
+
+ return 0;
+}
+
static int clk_dummy_single_set_parent(struct clk_hw *hw, u8 index)
{
if (index >= clk_hw_get_num_parents(hw))
@@ -100,18 +112,21 @@ static const struct clk_ops clk_dummy_rate_ops = {
.recalc_rate = clk_dummy_recalc_rate,
.determine_rate = clk_dummy_determine_rate,
.set_rate = clk_dummy_set_rate,
+ .set_spread_spectrum = clk_dummy_set_spread_spectrum,
};
static const struct clk_ops clk_dummy_maximize_rate_ops = {
.recalc_rate = clk_dummy_recalc_rate,
.determine_rate = clk_dummy_maximize_rate,
.set_rate = clk_dummy_set_rate,
+ .set_spread_spectrum = clk_dummy_set_spread_spectrum,
};
static const struct clk_ops clk_dummy_minimize_rate_ops = {
.recalc_rate = clk_dummy_recalc_rate,
.determine_rate = clk_dummy_minimize_rate,
.set_rate = clk_dummy_set_rate,
+ .set_spread_spectrum = clk_dummy_set_spread_spectrum,
};
static const struct clk_ops clk_dummy_single_parent_ops = {
@@ -3192,7 +3207,13 @@ static int clk_assigned_rates_test_init(struct kunit *test)
consumer = of_find_compatible_node(NULL, NULL, "test,clk-consumer"));
of_node_put_kunit(test, consumer);
+ // Here's an example of a test that shows where
+ // of_clk_set_defaults() is called for the consumer.
KUNIT_ASSERT_EQ(test, 0, of_clk_set_defaults(consumer, false));
+ pr_crit("%s: Spread Sprectrum Configuration: modfreq_hz=%u spread_bp=%u method=%u\n",
+ clk_hw_get_name(&ctx->clk0.hw), ctx->clk0.sscs.modfreq_hz,
+ ctx->clk0.sscs.spread_bp, ctx->clk0.sscs.method);
+
}
return 0;
diff --git a/drivers/clk/kunit_clk_assigned_rates_one_consumer.dtso b/drivers/clk/kunit_clk_assigned_rates_one_consumer.dtso
index a41dca806318..a157a316a10d 100644
--- a/drivers/clk/kunit_clk_assigned_rates_one_consumer.dtso
+++ b/drivers/clk/kunit_clk_assigned_rates_one_consumer.dtso
@@ -14,5 +14,6 @@ kunit-clock-consumer {
compatible = "test,clk-consumer";
assigned-clocks = <&clk>;
assigned-clock-rates = <ASSIGNED_RATES_0_RATE>;
+ assigned-clock-sscs = <10000 3 1>;
};
};
Hi Brian, On Tue, Sep 02, 2025 at 07:56:07PM -0400, Brian Masney wrote: >Hi Peng, > >On Mon, Sep 01, 2025 at 11:51:46AM +0800, Peng Fan wrote: >> Parse the Spread Spectrum Configuration(SSC) from device tree and configure >> them before using the clock. >> >> Each SSC is three u32 elements which means '<modfreq spreaddepth >> modmethod>', so assigned-clock-sscs is an array of multiple three u32 >> elements. >> >> Signed-off-by: Peng Fan <peng.fan@nxp.com> > >Stephen has also been asking for kunit tests to be added for new >functionality in the clk core. There's already one kunit test that >calls of_clk_set_defaults(). I attached a very rough draft of a patch >showing that it'd be possible to mock this up in a test with what's >already there. I set a log statement with the configuration from >device tree: Appreciate your help on this. I was not aware of kunit tests is required. I will add one in V3. > >test_assigned_rate0: Spread Sprectrum Configuration: modfreq_hz=10000 spread_bp=3 method=1 > >You can run the kunit tests with: > >./tools/testing/kunit/kunit.py run \ > --kunitconfig drivers/clk/.kunitconfig \ > --raw_output=all > >Additionally, what do you think about making a dt-bindings include file >for CLK_SSC_CENTER_SPREAD + friends? Right now, the test illustrates >that we need to hardcode the number from the clk-provider.h file inside >the DTS. ok to add a generic dt-bindings file for the the SSC Spread method. I will add one in V3. > >Here's the patch and feel free to make it your own as you see fit. > Thanks a lot for sharing this. Thanks, Peng
On Mon, Sep 01, 2025 at 11:51:46AM +0800, Peng Fan wrote:
> Parse the Spread Spectrum Configuration(SSC) from device tree and configure
> them before using the clock.
>
> Each SSC is three u32 elements which means '<modfreq spreaddepth
> modmethod>', so assigned-clock-sscs is an array of multiple three u32
> elements.
>
> Signed-off-by: Peng Fan <peng.fan@nxp.com>
> ---
> drivers/clk/clk-conf.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 69 insertions(+)
>
> diff --git a/drivers/clk/clk-conf.c b/drivers/clk/clk-conf.c
> index 303a0bb26e54a95655ce094a35b989c97ebc6fd8..dd6083597db3f8f27d86abf5640dfc3fb39a9b88 100644
> --- a/drivers/clk/clk-conf.c
> +++ b/drivers/clk/clk-conf.c
> @@ -155,6 +155,71 @@ static int __set_clk_rates(struct device_node *node, bool clk_supplier)
> return 0;
> }
>
> +static int __set_clk_spread_spectrum(struct device_node *node, bool clk_supplier)
> +{
> + struct clk_spread_spectrum *sscs __free(kfree) = NULL;
> + u32 elem_size = sizeof(struct clk_spread_spectrum);
> + struct of_phandle_args clkspec;
> + int rc, count, index;
> + struct clk *clk;
> +
> + /* modfreq, spreadPercent, modmethod */
> + count = of_property_count_elems_of_size(node, "assigned-clock-sscs", elem_size);
> + if (count <= 0)
> + return 0;
> +
> + sscs = kcalloc(count, elem_size, GFP_KERNEL);
> + if (!sscs)
> + return -ENOMEM;
> +
> + rc = of_property_read_u32_array(node, "assigned-clock-sscs", (u32 *)sscs,
> + count * 3);
> + if (rc)
> + return rc;
> +
> + for (index = 0; index < count; index++) {
> + struct clk_spread_spectrum *conf = &sscs[index];
> + struct clk_hw *hw;
> +
> + if (!conf->modfreq_hz && !conf->spread_bp && !conf->method)
> + continue;
> +
> + rc = of_parse_phandle_with_args(node, "assigned-clocks", "#clock-cells",
> + index, &clkspec);
> + if (rc < 0) {
> + /* skip empty (null) phandles */
> + if (rc == -ENOENT)
> + continue;
> + else
> + return rc;
> + }
> +
> + if (clkspec.np == node && !clk_supplier) {
> + of_node_put(clkspec.np);
> + return 0;
> + }
> +
> + clk = of_clk_get_from_provider(&clkspec);
> + of_node_put(clkspec.np);
> + if (IS_ERR(clk)) {
> + if (PTR_ERR(clk) != -EPROBE_DEFER)
> + pr_warn("clk: couldn't get clock %d for %pOF\n",
> + index, node);
> + return PTR_ERR(clk);
This chunk can be replaced with dev_warn_probe(). Sorry I missed that in
v1. Otherwise the rest looks good to me. With that fixed:
Reviewed-by: Brian Masney <bmasney@redhat.com>
Hi Brian,
On Tue, Sep 02, 2025 at 05:49:17PM -0400, Brian Masney wrote:
[...]
>> + if (IS_ERR(clk)) {
>> + if (PTR_ERR(clk) != -EPROBE_DEFER)
>> + pr_warn("clk: couldn't get clock %d for %pOF\n",
>> + index, node);
>> + return PTR_ERR(clk);
>
>This chunk can be replaced with dev_warn_probe(). Sorry I missed that in
>v1. Otherwise the rest looks good to me. With that fixed:
dev_warn_probe() could not be used here. There is no 'device' pointer
here.
I take __set_clk_parents() as example here, so use pr_warn().
>
>Reviewed-by: Brian Masney <bmasney@redhat.com>
Could I still keep this tag with keeping pr_warn()?
Thanks,
Peng
>
On Tue, Sep 09, 2025 at 04:50:13PM +0800, Peng Fan wrote:
> Hi Brian,
> On Tue, Sep 02, 2025 at 05:49:17PM -0400, Brian Masney wrote:
> [...]
> >> + if (IS_ERR(clk)) {
> >> + if (PTR_ERR(clk) != -EPROBE_DEFER)
> >> + pr_warn("clk: couldn't get clock %d for %pOF\n",
> >> + index, node);
> >> + return PTR_ERR(clk);
> >
> >This chunk can be replaced with dev_warn_probe(). Sorry I missed that in
> >v1. Otherwise the rest looks good to me. With that fixed:
>
> dev_warn_probe() could not be used here. There is no 'device' pointer
> here.
Ahh ok, gotcha.
> I take __set_clk_parents() as example here, so use pr_warn().
>
> >
> >Reviewed-by: Brian Masney <bmasney@redhat.com>
>
> Could I still keep this tag with keeping pr_warn()?
Yes. Thank you!
Brian
© 2016 - 2026 Red Hat, Inc.