From 70674a17fa248d4abd6b07e41879311c30d7b5d8 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Wed, 25 Feb 2026 14:41:18 -0600 Subject: [PATCH 1/4] FROMLIST: dt-bindings: clock: Add spread spectrum definition Per dt-schema, the modulation methods are: down-spread(3), up-spread(2), center-spread(1), no-spread(0). So define them in dt-bindings to avoid write the magic number in device tree. Reviewed-by: Brian Masney Acked-by: Rob Herring (Arm) Reviewed-by: Sebin Francis Signed-off-by: Peng Fan Link: https://lore.kernel.org/r/20251231-clk-ssc-v7-1-v7-1-380e8b58f9e3@nxp.com Signed-off-by: Kendall Willis Signed-off-by: Jay Goyal --- include/dt-bindings/clock/clock.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 include/dt-bindings/clock/clock.h diff --git a/include/dt-bindings/clock/clock.h b/include/dt-bindings/clock/clock.h new file mode 100644 index 000000000000..155e2653a120 --- /dev/null +++ b/include/dt-bindings/clock/clock.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* + * Copyright 2025 NXP + */ + +#ifndef __DT_BINDINGS_CLOCK_H +#define __DT_BINDINGS_CLOCK_H + +#define CLK_SSC_NO_SPREAD 0 +#define CLK_SSC_CENTER_SPREAD 1 +#define CLK_SSC_UP_SPREAD 2 +#define CLK_SSC_DOWN_SPREAD 3 + +#endif /* __DT_BINDINGS_CLOCK_H */ -- 2.54.0 From f7d4df84b89ecdfd3cb7922cc38550a0df070f79 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Wed, 25 Feb 2026 14:41:19 -0600 Subject: [PATCH 2/4] FROMLIST: clk: Introduce clk_hw_set_spread_spectrum Add clk_hw_set_spread_spectrum to configure a clock to enable spread spectrum feature. set_spread_spectrum ops is added for clk drivers to have their own hardware specific implementation. Reviewed-by: Brian Masney Reviewed-by: Sebin Francis Signed-off-by: Peng Fan Link: https://lore.kernel.org/r/20251231-clk-ssc-v7-1-v7-2-380e8b58f9e3@nxp.com Signed-off-by: Kendall Willis Signed-off-by: Jay Goyal --- drivers/clk/clk.c | 27 +++++++++++++++++++++++++++ include/linux/clk-provider.h | 31 +++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 7de3dfdae4b5..8f59b779258e 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -2796,6 +2796,33 @@ int clk_set_max_rate(struct clk *clk, unsigned long rate) } EXPORT_SYMBOL_GPL(clk_set_max_rate); +int clk_hw_set_spread_spectrum(struct clk_hw *hw, const struct clk_spread_spectrum *ss_conf) +{ + struct clk_core *core; + int ret; + + if (!hw) + return 0; + + core = hw->core; + + clk_prepare_lock(); + + ret = clk_pm_runtime_get(core); + if (ret) + goto fail; + + if (core->ops->set_spread_spectrum) + ret = core->ops->set_spread_spectrum(hw, ss_conf); + + clk_pm_runtime_put(core); + +fail: + clk_prepare_unlock(); + return ret; +} +EXPORT_SYMBOL_GPL(clk_hw_set_spread_spectrum); + /** * clk_get_parent - return the parent of a clk * @clk: the clk whose parent gets returned diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 7e43caabb54b..7721b3345b13 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -6,6 +6,7 @@ #ifndef __LINUX_CLK_PROVIDER_H #define __LINUX_CLK_PROVIDER_H +#include #include #include @@ -84,6 +85,26 @@ struct clk_duty { unsigned int den; }; +enum clk_ssc_method { + CLK_SPREAD_NO = CLK_SSC_NO_SPREAD, + CLK_SPREAD_CENTER = CLK_SSC_CENTER_SPREAD, + CLK_SPREAD_UP = CLK_SSC_UP_SPREAD, + CLK_SPREAD_DOWN = CLK_SSC_DOWN_SPREAD, +}; + +/** + * struct clk_spread_spectrum - Structure encoding spread spectrum of a clock + * + * @modfreq_hz: Modulation frequency + * @spread_bp: Modulation percent in permyriad + * @method: Modulation method + */ +struct clk_spread_spectrum { + u32 modfreq_hz; + u32 spread_bp; + enum clk_ssc_method method; +}; + /** * struct clk_ops - Callback operations for hardware clocks; these are to * be provided by the clock implementation, and will be called by drivers @@ -178,6 +199,12 @@ struct clk_duty { * separately via calls to .set_parent and .set_rate. * Returns 0 on success, -EERROR otherwise. * + * @set_spread_spectrum: Optional callback used to configure the spread + * spectrum modulation frequency, percentage, and method + * to reduce EMI by spreading the clock frequency over a + * wider range. + * Returns 0 on success, -EERROR otherwise. + * * @recalc_accuracy: Recalculate the accuracy of this clock. The clock accuracy * is expressed in ppb (parts per billion). The parent accuracy is * an input parameter. @@ -255,6 +282,8 @@ struct clk_ops { int (*set_rate_and_parent)(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate, u8 index); + int (*set_spread_spectrum)(struct clk_hw *hw, + const struct clk_spread_spectrum *ss_conf); unsigned long (*recalc_accuracy)(struct clk_hw *hw, unsigned long parent_accuracy); int (*get_phase)(struct clk_hw *hw); @@ -1379,6 +1408,8 @@ void clk_hw_get_rate_range(struct clk_hw *hw, unsigned long *min_rate, unsigned long *max_rate); void clk_hw_set_rate_range(struct clk_hw *hw, unsigned long min_rate, unsigned long max_rate); +int clk_hw_set_spread_spectrum(struct clk_hw *hw, + const struct clk_spread_spectrum *ss_conf); static inline void __clk_hw_set_clk(struct clk_hw *dst, struct clk_hw *src) { -- 2.54.0 From 7ee5726759e8576b05daae2a4934af6a24f5aa61 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Wed, 25 Feb 2026 14:41:20 -0600 Subject: [PATCH 3/4] FROMLIST: clk: conf: Support assigned-clock-sscs Parse the Spread Spectrum Configuration(SSC) from device tree and configure them before using the clock. Each SSC is three u32 elements which means '', so assigned-clock-sscs is an array of multiple three u32 elements. Reviewed-by: Brian Masney Reviewed-by: Sebin Francis Signed-off-by: Peng Fan Link: https://lore.kernel.org/r/20251231-clk-ssc-v7-1-v7-3-380e8b58f9e3@nxp.com Signed-off-by: Kendall Willis Signed-off-by: Jay Goyal --- drivers/clk/clk-conf.c | 75 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/drivers/clk/clk-conf.c b/drivers/clk/clk-conf.c index 303a0bb26e54..cdc7631f2e15 100644 --- a/drivers/clk/clk-conf.c +++ b/drivers/clk/clk-conf.c @@ -155,6 +155,77 @@ 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) +{ + u32 elem_size = sizeof(struct clk_spread_spectrum); + struct clk_spread_spectrum *sscs; + 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) + goto free_sscs; + + 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 + goto free_sscs; + } + + if (clkspec.np == node && !clk_supplier) { + of_node_put(clkspec.np); + goto free_sscs; + } + + 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); + rc = PTR_ERR(clk); + goto free_sscs; + } + + 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); + /* Do not fail */ + rc = 0; + } + clk_put(clk); + } + +free_sscs: + kfree(sscs); + return rc; +} + /** * of_clk_set_defaults() - parse and set assigned clocks configuration * @node: device node to apply clock settings for @@ -174,6 +245,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.54.0 From ca1624cb39eefbfde6105866b0cef0fc0069273a Mon Sep 17 00:00:00 2001 From: Sebin Francis Date: Wed, 25 Feb 2026 14:41:21 -0600 Subject: [PATCH 4/4] PENDING: clk: keystone: sci-clk: Add support for clock ssc configuration Add support for configuring a clock's spread spectrum modulation. Signed-off-by: Sebin Francis Signed-off-by: Kendall Willis Signed-off-by: Jay Goyal --- drivers/clk/keystone/sci-clk.c | 22 ++++++++ drivers/firmware/ti_sci.c | 76 ++++++++++++++++++++++++++ drivers/firmware/ti_sci.h | 30 ++++++++++ include/linux/soc/ti/ti_sci_protocol.h | 3 + 4 files changed, 131 insertions(+) diff --git a/drivers/clk/keystone/sci-clk.c b/drivers/clk/keystone/sci-clk.c index c5894fc9395e..8dd484e5e34e 100644 --- a/drivers/clk/keystone/sci-clk.c +++ b/drivers/clk/keystone/sci-clk.c @@ -260,6 +260,27 @@ static int sci_clk_set_parent(struct clk_hw *hw, u8 index) index + 1 + clk->clk_id); } +/** + * sci_clk_set_spread_spectrum - Set spread spectrum for a TI SCI clock + * @hw: clock to set parent for + * @ss_conf: spread spectrum configuration + * + * Sets the spread spectrum of a TI SCI clock. Return TI SCI protocol status. + */ +static int sci_clk_set_spread_spectrum(struct clk_hw *hw, const struct clk_spread_spectrum *ss_conf) +{ + struct sci_clk *clk = to_sci_clk(hw); + + clk->cached_req = 0; + + if (clk->provider->ops->set_spread_spectrum) + return clk->provider->ops->set_spread_spectrum(clk->provider->sci, clk->dev_id, + clk->clk_id, ss_conf->modfreq_hz, ss_conf->spread_bp, + (u8)ss_conf->method); + + return 0; +} + static const struct clk_ops sci_clk_ops = { .prepare = sci_clk_prepare, .unprepare = sci_clk_unprepare, @@ -269,6 +290,7 @@ static const struct clk_ops sci_clk_ops = { .set_rate = sci_clk_set_rate, .get_parent = sci_clk_get_parent, .set_parent = sci_clk_set_parent, + .set_spread_spectrum = sci_clk_set_spread_spectrum, }; /** diff --git a/drivers/firmware/ti_sci.c b/drivers/firmware/ti_sci.c index 41534daf5371..64b7000ebfd3 100644 --- a/drivers/firmware/ti_sci.c +++ b/drivers/firmware/ti_sci.c @@ -9,6 +9,7 @@ #define pr_fmt(fmt) "%s: " fmt, __func__ #include +#include #include #include #include @@ -1664,6 +1665,79 @@ static int ti_sci_cmd_clk_get_freq(const struct ti_sci_handle *handle, return ret; } +/** + * ti_sci_cmd_clk_set_ssc() - Set SSC configurations + * @handle: pointer to TI SCI handle + * @dev_id: Device identifier this request is for + * @clk_id: Clock identifier for the device for this request. + * Each device has it's own set of clock inputs. This indexes + * which clock input to modify. + * @modfreq_hz: The modulation frequency in Hz + * @mod_depth: The modulation depth in "permyriad". + * @spread_type: Type of spread sprectum modulation. + * + * Return: 0 if all went well, else returns appropriate error value. + */ +static int ti_sci_cmd_clk_set_ssc(const struct ti_sci_handle *handle, + u32 dev_id, u32 clk_id, u32 modfreq_hz, + u32 mod_depth, u8 spread_type) +{ + struct ti_sci_info *info; + struct ti_sci_msg_req_set_clock_ssc *req; + struct ti_sci_msg_hdr *resp; + struct ti_sci_xfer *xfer; + struct device *dev; + int ret = 0; + + if (IS_ERR(handle)) + return PTR_ERR(handle); + if (!handle) + return -EINVAL; + + /* mod_depth must be between 10 and 310 */ + if (mod_depth > 310 || mod_depth < 10) + return -EINVAL; + + info = handle_to_ti_sci_info(handle); + dev = info->dev; + + xfer = ti_sci_get_one_xfer(info, TI_SCI_MSG_SET_CLOCK_SSC, + TI_SCI_FLAG_REQ_ACK_ON_PROCESSED, + sizeof(*req), sizeof(*resp)); + if (IS_ERR(xfer)) { + ret = PTR_ERR(xfer); + dev_err(dev, "Message alloc failed(%d)\n", ret); + return ret; + } + + req = (struct ti_sci_msg_req_set_clock_ssc *)xfer->xfer_buf; + req->dev_id = dev_id; + req->clk_id = clk_id; + req->modfreq_hz = modfreq_hz; + req->mod_depth = mod_depth; + req->spread_type = spread_type; + + if (spread_type == CLK_SPREAD_NO) + req->enable = false; + else + req->enable = true; + + ret = ti_sci_do_xfer(info, xfer); + if (ret) { + dev_err(dev, "Mbox send fail %d\n", ret); + goto fail; + } + + resp = (struct ti_sci_msg_hdr *)xfer->xfer_buf; + + ret = ti_sci_is_response_ack(resp) ? 0 : -ENODEV; + +fail: + ti_sci_put_one_xfer(&info->minfo, xfer); + + return ret; +} + /** * ti_sci_cmd_prepare_sleep() - Prepare system for system suspend * @handle: pointer to TI SCI handle @@ -3255,6 +3329,8 @@ static void ti_sci_setup_ops(struct ti_sci_info *info) cops->get_best_match_freq = ti_sci_cmd_clk_get_match_freq; cops->set_freq = ti_sci_cmd_clk_set_freq; cops->get_freq = ti_sci_cmd_clk_get_freq; + if (info->fw_caps & MSG_FLAG_CAPS_CLOCK_SSC) + cops->set_spread_spectrum = ti_sci_cmd_clk_set_ssc; if (info->fw_caps & MSG_FLAG_CAPS_LPM_DM_MANAGED) { pr_debug("detected DM managed LPM in fw_caps\n"); diff --git a/drivers/firmware/ti_sci.h b/drivers/firmware/ti_sci.h index 6da13df05320..07e798874858 100644 --- a/drivers/firmware/ti_sci.h +++ b/drivers/firmware/ti_sci.h @@ -32,6 +32,7 @@ #define TI_SCI_MSG_SET_CLOCK_PARENT 0x0102 #define TI_SCI_MSG_GET_CLOCK_PARENT 0x0103 #define TI_SCI_MSG_GET_NUM_CLOCK_PARENTS 0x0104 +#define TI_SCI_MSG_SET_CLOCK_SSC 0x010a #define TI_SCI_MSG_SET_CLOCK_FREQ 0x010c #define TI_SCI_MSG_QUERY_CLOCK_FREQ 0x010d #define TI_SCI_MSG_GET_CLOCK_FREQ 0x010e @@ -158,6 +159,9 @@ struct ti_sci_msg_resp_query_fw_caps { #define MSG_FLAG_CAPS_GENERIC TI_SCI_MSG_FLAG(0) #define MSG_FLAG_CAPS_LPM_PARTIAL_IO TI_SCI_MSG_FLAG(4) #define MSG_FLAG_CAPS_LPM_DM_MANAGED TI_SCI_MSG_FLAG(5) +#define MSG_FLAG_CAPS_LPM_ABORT TI_SCI_MSG_FLAG(9) +#define MSG_FLAG_CAPS_IO_ISOLATION TI_SCI_MSG_FLAG(7) +#define MSG_FLAG_CAPS_CLOCK_SSC TI_SCI_MSG_FLAG(10) #define MSG_MASK_CAPS_LPM GENMASK_ULL(4, 1) u64 fw_caps; } __packed; @@ -575,6 +579,32 @@ struct ti_sci_msg_resp_get_clock_freq { u64 freq_hz; } __packed; +/** + * struct ti_sci_msg_req_set_clock_ssc - Request to setup a clock spread spectrum + * @hdr: Generic Header + * @dev_id: Device identifier this request is for + * @clk_id: Clock identifier for the device for this request. + * @modfreq_hz: The desired modulation frequency in Hz. + * @mod_depth: The target modulation depth in "permyriad". The modulation depth + * refers to the maximum variation in frequency as a percentage of + * the clock center frequency. The minimum modulation depth is 0.1% + * and the maximum is 3.1%. Modulation depth can be adjusted in 0.1% + * increments. + * @spread_type: The target spread type. + * @enable: Enable or disable SSC. + * + * This message is used to enable/disable a clock's spread spectrum configurations + */ +struct ti_sci_msg_req_set_clock_ssc { + struct ti_sci_msg_hdr hdr; + u32 dev_id; + u32 clk_id; + u32 modfreq_hz; + u32 mod_depth; + u8 spread_type; + u8 enable; +} __packed; + /** * struct tisci_msg_req_prepare_sleep - Request for TISCI_MSG_PREPARE_SLEEP. * diff --git a/include/linux/soc/ti/ti_sci_protocol.h b/include/linux/soc/ti/ti_sci_protocol.h index d5c00b2661a4..73b74f878b87 100644 --- a/include/linux/soc/ti/ti_sci_protocol.h +++ b/include/linux/soc/ti/ti_sci_protocol.h @@ -193,6 +193,9 @@ struct ti_sci_clk_ops { u64 min_freq, u64 target_freq, u64 max_freq); int (*get_freq)(const struct ti_sci_handle *handle, u32 did, u32 cid, u64 *current_freq); + int (*set_spread_spectrum)(const struct ti_sci_handle *handle, + u32 dev_id, u32 clk_id, u32 modfreq_hz, + u32 mod_depth, u8 spread_type); }; /* TISCI LPM IO isolation control values */ -- 2.54.0