[PATCH 3/3] clk: scmi: Support Spread Spectrum for NXP i.MX95
From: Peng Fan <peng.fan@nxp.com>
Date: 2025-08-12 12:18:49
Also in:
arm-scmi, linux-clk, lkml
Subsystem:
common clk framework, system control & power/management interface (scpi/scmi) message protocol drivers, the rest · Maintainers:
Michael Turquette, Stephen Boyd, Sudeep Holla, Linus Torvalds
The PLL clocks on NXP i.MX95 SoCs support Spread Spectrum (SS). This patch introduces scmi_clk_imx_set_spread_spectrum to pass SS configuration to the SCMI firmware, which handles the actual implementation. To ensure this feature is only enabled on NXP platforms, scmi_clk_imx_extended_config_oem is added. Since SS is only applicable to PLL clocks, config_oem_get is used to verify SS support for a given clock. i.MX95 SCMI firmware Spread Spectrum extConfigValue definition is as below, no modulation method because firmware forces to use down spread. extConfigValue[7:0] - spread percentage (%) extConfigValue[23:8] - Modulation Frequency (KHz) extConfigValue[24] - Enable/Disable extConfigValue[31:25] - Reserved Signed-off-by: Peng Fan <peng.fan@nxp.com> --- drivers/clk/clk-scmi.c | 64 ++++++++++++++++++++++++++++++++++++++++--- include/linux/scmi_protocol.h | 5 ++++ 2 files changed, 65 insertions(+), 4 deletions(-)
diff --git a/drivers/clk/clk-scmi.c b/drivers/clk/clk-scmi.c
index d2408403283fc72f0cf902e65f4c08bcbc7b4b0b..bb5e20dab18e92932ab4b99192b496e0c4d96417 100644
--- a/drivers/clk/clk-scmi.c
+++ b/drivers/clk/clk-scmi.c@@ -12,6 +12,7 @@ #include <linux/of.h> #include <linux/module.h> #include <linux/scmi_protocol.h> +#include <linux/scmi_imx_protocol.h> #include <asm/div64.h> #define NOT_ATOMIC false
@@ -23,6 +24,7 @@ enum scmi_clk_feats { SCMI_CLK_RATE_CTRL_SUPPORTED, SCMI_CLK_PARENT_CTRL_SUPPORTED, SCMI_CLK_DUTY_CYCLE_SUPPORTED, + SCMI_CLK_IMX_SSC_SUPPORTED, SCMI_CLK_FEATS_COUNT };
@@ -98,6 +100,35 @@ static int scmi_clk_set_parent(struct clk_hw *hw, u8 parent_index) return scmi_proto_clk_ops->parent_set(clk->ph, clk->id, parent_index); } +static int scmi_clk_imx_set_spread_spectrum(struct clk_hw *hw, + struct clk_spread_spectrum *clk_ss) +{ + struct scmi_clk *clk = to_scmi_clk(hw); + int ret; + u32 val; + + /* + * extConfigValue[7:0] - spread percentage (%) + * extConfigValue[23:8] - Modulation Frequency + * extConfigValue[24] - Enable/Disable + * extConfigValue[31:25] - Reserved + */ + val = FIELD_PREP(SCMI_CLOCK_IMX_SS_PERCENTAGE_MASK, clk_ss->spread_bp / 10000); + val |= FIELD_PREP(SCMI_CLOCK_IMX_SS_MOD_FREQ_MASK, clk_ss->modfreq_hz); + if (clk_ss->method != CLK_SSC_NO_SPREAD) + val |= SCMI_CLOCK_IMX_SS_ENABLE_MASK; + ret = scmi_proto_clk_ops->config_oem_set(clk->ph, clk->id, + SCMI_CLOCK_CFG_IMX_SSC, + val, false); + if (ret) + dev_warn(clk->dev, + "Failed to set spread spectrum(%u,%u,%u) for clock ID %d\n", + clk_ss->modfreq_hz, clk_ss->spread_bp, clk_ss->method, + clk->id); + + return ret; +} + static u8 scmi_clk_get_parent(struct clk_hw *hw) { struct scmi_clk *clk = to_scmi_clk(hw);
@@ -316,11 +347,33 @@ scmi_clk_ops_alloc(struct device *dev, unsigned long feats_key) ops->set_duty_cycle = scmi_clk_set_duty_cycle; } + if (feats_key & BIT(SCMI_CLK_IMX_SSC_SUPPORTED)) + ops->set_spread_spectrum = scmi_clk_imx_set_spread_spectrum; + return ops; } +static void scmi_clk_imx_extended_config_oem(const struct scmi_handle *handle, + struct scmi_clk *sclk, + unsigned int *feats_key) +{ + int ret; + u32 val; + + if (strcmp(handle->version->vendor_id, SCMI_IMX_VENDOR) || + strcmp(handle->version->sub_vendor_id, SCMI_IMX_SUBVENDOR)) + return; + + ret = scmi_proto_clk_ops->config_oem_get(sclk->ph, sclk->id, + SCMI_CLOCK_CFG_IMX_SSC, + &val, NULL, false); + if (!ret) + *feats_key |= BIT(SCMI_CLK_IMX_SSC_SUPPORTED); +} + /** * scmi_clk_ops_select() - Select a proper set of clock operations + * @handle: A reference to an SCMI entity * @sclk: A reference to an SCMI clock descriptor * @atomic_capable: A flag to indicate if atomic mode is supported by the * transport
@@ -345,8 +398,8 @@ scmi_clk_ops_alloc(struct device *dev, unsigned long feats_key) * NULL otherwise. */ static const struct clk_ops * -scmi_clk_ops_select(struct scmi_clk *sclk, bool atomic_capable, - unsigned int atomic_threshold_us, +scmi_clk_ops_select(const struct scmi_handle *handle, struct scmi_clk *sclk, + bool atomic_capable, unsigned int atomic_threshold_us, const struct clk_ops **clk_ops_db, size_t db_size) { const struct scmi_clock_info *ci = sclk->info;
@@ -370,9 +423,12 @@ scmi_clk_ops_select(struct scmi_clk *sclk, bool atomic_capable, if (!ci->parent_ctrl_forbidden) feats_key |= BIT(SCMI_CLK_PARENT_CTRL_SUPPORTED); - if (ci->extended_config) + if (ci->extended_config) { feats_key |= BIT(SCMI_CLK_DUTY_CYCLE_SUPPORTED); + scmi_clk_imx_extended_config_oem(handle, sclk, &feats_key); + } + if (WARN_ON(feats_key >= db_size)) return NULL;
@@ -459,7 +515,7 @@ static int scmi_clocks_probe(struct scmi_device *sdev) * to avoid sharing the devm_ allocated clk_ops between multiple * SCMI clk driver instances. */ - scmi_ops = scmi_clk_ops_select(sclk, transport_is_atomic, + scmi_ops = scmi_clk_ops_select(handle, sclk, transport_is_atomic, atomic_threshold_us, scmi_clk_ops_db, ARRAY_SIZE(scmi_clk_ops_db));
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 688466a0e816247d24704f7ba109667a14226b67..6f9f197ee671540e38470a5666eb5ba8ec9de439 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h@@ -80,9 +80,14 @@ enum scmi_clock_oem_config { SCMI_CLOCK_CFG_DUTY_CYCLE = 0x1, SCMI_CLOCK_CFG_PHASE, SCMI_CLOCK_CFG_OEM_START = 0x80, + SCMI_CLOCK_CFG_IMX_SSC = 0x80, SCMI_CLOCK_CFG_OEM_END = 0xFF, }; +#define SCMI_CLOCK_IMX_SS_PERCENTAGE_MASK GENMASK(7, 0) +#define SCMI_CLOCK_IMX_SS_MOD_FREQ_MASK GENMASK(23, 8) +#define SCMI_CLOCK_IMX_SS_ENABLE_MASK BIT(24) + /** * struct scmi_clk_proto_ops - represents the various operations provided * by SCMI Clock Protocol
--
2.37.1