clk: scmi: Add i.MX95 OEM extension support for SCMI clock driver

- Introduce 'clk-scmi-oem.c' to support vendor-specific OEM extensions
   for the SCMI clock driver, allows clean integration of vendor-specific
   features without impacting the core SCMI clock driver logic.
 - Extend 'clk-scmi.h' with 'scmi_clk_oem' structure and related
   declarations.
 - Initialize OEM extensions via 'scmi_clk_oem_init()'.
 - Support querying OEM-specific features and setting spread spectrum.
 - Pass 'scmi_device' to 'scmi_clk_ops_select()' for OEM data access.

Reviewed-by: Sebin Francis <sebin.francis@ti.com>
Reviewed-by: Cristian Marussi <cristian.marussi@arm.com>
Signed-off-by: Peng Fan <peng.fan@nxp.com>
Signed-off-by: Brian Masney <bmasney@redhat.com>
This commit is contained in:
Peng Fan 2026-06-12 16:46:28 +08:00 committed by Brian Masney
parent a73edd3ed8
commit 77369b1e6a
4 changed files with 136 additions and 4 deletions

View File

@ -98,7 +98,7 @@ obj-$(CONFIG_COMMON_CLK_RP1) += clk-rp1.o
obj-$(CONFIG_COMMON_CLK_RPMI) += clk-rpmi.o
obj-$(CONFIG_COMMON_CLK_HI655X) += clk-hi655x.o
obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o
obj-$(CONFIG_COMMON_CLK_SCMI) += clk-scmi.o
obj-$(CONFIG_COMMON_CLK_SCMI) += clk-scmi.o clk-scmi-oem.o
obj-$(CONFIG_COMMON_CLK_SCPI) += clk-scpi.o
obj-$(CONFIG_COMMON_CLK_SI5341) += clk-si5341.o
obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o

108
drivers/clk/clk-scmi-oem.c Normal file
View File

@ -0,0 +1,108 @@
// SPDX-License-Identifier: GPL-2.0
/*
* The Vendor OEM extension for System Control and Power Interface (SCMI)
* Protocol based clock driver
*
* Copyright 2025 NXP
*/
#include <linux/clk-provider.h>
#include <linux/of.h>
#include <linux/scmi_imx_protocol.h>
#include <linux/scmi_protocol.h>
#include "clk-scmi.h"
#define SCMI_CLOCK_CFG_IMX_SSC 0x80
#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)
/*
* Selection is based on SCMI vendor_id/sub_vendor_id and optional machine
* compatible string, without involving impl_ver. impl_verspecific behavior
* should be considered a bug and handled via SCMI Quirk framework.
*/
struct scmi_clk_oem_info {
char *vendor_id;
char *sub_vendor_id;
char *compatible;
const void *data;
};
static int
scmi_clk_imx_set_spread_spectrum(struct clk_hw *hw,
const struct clk_spread_spectrum *ss_conf)
{
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, ss_conf->spread_bp / 10000);
val |= FIELD_PREP(SCMI_CLOCK_IMX_SS_MOD_FREQ_MASK, ss_conf->modfreq_hz);
if (ss_conf->method != CLK_SPREAD_NO)
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",
ss_conf->modfreq_hz, ss_conf->spread_bp, ss_conf->method,
clk->id);
return ret;
}
static int
scmi_clk_imx_query_oem_feats(const struct scmi_protocol_handle *ph, u32 id,
unsigned int *feats_key)
{
int ret;
u32 val;
ret = scmi_proto_clk_ops->config_oem_get(ph, id,
SCMI_CLOCK_CFG_IMX_SSC,
&val, NULL, false);
if (!ret)
*feats_key |= BIT(SCMI_CLK_EXT_OEM_SSC_SUPPORTED);
return 0;
}
static const struct scmi_clk_oem scmi_clk_oem_imx = {
.query_ext_oem_feats = scmi_clk_imx_query_oem_feats,
.set_spread_spectrum = scmi_clk_imx_set_spread_spectrum,
};
static const struct scmi_clk_oem_info info[] = {
{ SCMI_IMX_VENDOR, SCMI_IMX_SUBVENDOR, NULL, &scmi_clk_oem_imx },
};
int scmi_clk_oem_init(struct scmi_device *sdev)
{
const struct scmi_handle *handle = sdev->handle;
int i, size = ARRAY_SIZE(info);
for (i = 0; i < size; i++) {
if (strcmp(handle->version->vendor_id, info[i].vendor_id) ||
strcmp(handle->version->sub_vendor_id, info[i].sub_vendor_id))
continue;
if (info[i].compatible &&
!of_machine_is_compatible(info[i].compatible))
continue;
break;
}
if (i < size)
dev_set_drvdata(&sdev->dev, (void *)info[i].data);
return 0;
}

View File

@ -13,6 +13,8 @@
#include <linux/module.h>
#include <linux/scmi_protocol.h>
#include "clk-scmi.h"
const struct scmi_clk_proto_ops *scmi_proto_clk_ops;
static unsigned long scmi_clk_recalc_rate(struct clk_hw *hw,
@ -210,6 +212,7 @@ static int scmi_clk_ops_init(struct device *dev, struct scmi_clk *sclk,
static const struct clk_ops *
scmi_clk_ops_alloc(struct device *dev, unsigned long feats_key)
{
struct scmi_clk_oem *oem_data = dev_get_drvdata(dev);
struct clk_ops *ops;
ops = devm_kzalloc(dev, sizeof(*ops), GFP_KERNEL);
@ -256,11 +259,15 @@ scmi_clk_ops_alloc(struct device *dev, unsigned long feats_key)
ops->set_duty_cycle = scmi_clk_set_duty_cycle;
}
if (oem_data && (feats_key & BIT(SCMI_CLK_EXT_OEM_SSC_SUPPORTED)))
ops->set_spread_spectrum = oem_data->set_spread_spectrum;
return ops;
}
/**
* scmi_clk_ops_select() - Select a proper set of clock operations
* @sdev: pointer to the SCMI device
* @sclk: A reference to an SCMI clock descriptor
* @atomic_capable: A flag to indicate if atomic mode is supported by the
* transport
@ -285,8 +292,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(struct scmi_device *sdev, struct scmi_clk *sclk,
bool atomic_capable, unsigned int atomic_threshold_us,
const struct clk_ops **clk_ops_db, size_t db_size)
{
int ret;
@ -294,6 +301,7 @@ scmi_clk_ops_select(struct scmi_clk *sclk, bool atomic_capable,
const struct scmi_clock_info *ci = sclk->info;
unsigned int feats_key = 0;
const struct clk_ops *ops;
struct scmi_clk_oem *oem_data = dev_get_drvdata(&sdev->dev);
/*
* Note that when transport is atomic but SCMI protocol did not
@ -318,6 +326,9 @@ scmi_clk_ops_select(struct scmi_clk *sclk, bool atomic_capable,
&val, NULL, false);
if (!ret)
feats_key |= BIT(SCMI_CLK_DUTY_CYCLE_SUPPORTED);
if (oem_data && oem_data->query_ext_oem_feats)
oem_data->query_ext_oem_feats(sclk->ph, sclk->id, &feats_key);
}
if (WARN_ON(feats_key >= db_size))
@ -375,6 +386,8 @@ static int scmi_clocks_probe(struct scmi_device *sdev)
clk_data->num = count;
hws = clk_data->hws;
scmi_clk_oem_init(sdev);
transport_is_atomic = handle->is_transport_atomic(handle,
&atomic_threshold_us);
@ -406,7 +419,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(sdev, sclk, transport_is_atomic,
atomic_threshold_us,
scmi_clk_ops_db,
ARRAY_SIZE(scmi_clk_ops_db));

View File

@ -7,6 +7,7 @@
#define __SCMI_CLK_H
#include <linux/bits.h>
#include <linux/clk-provider.h>
#include <linux/scmi_protocol.h>
#include <linux/types.h>
@ -19,6 +20,7 @@ enum scmi_clk_feats {
SCMI_CLK_RATE_CTRL_SUPPORTED,
SCMI_CLK_PARENT_CTRL_SUPPORTED,
SCMI_CLK_DUTY_CYCLE_SUPPORTED,
SCMI_CLK_EXT_OEM_SSC_SUPPORTED,
SCMI_CLK_FEATS_COUNT
};
@ -37,4 +39,13 @@ struct scmi_clk {
extern const struct scmi_clk_proto_ops *scmi_proto_clk_ops;
struct scmi_clk_oem {
int (*query_ext_oem_feats)(const struct scmi_protocol_handle *ph,
u32 id, unsigned int *feats_key);
int (*set_spread_spectrum)(struct clk_hw *hw,
const struct clk_spread_spectrum *ss_conf);
};
int scmi_clk_oem_init(struct scmi_device *dev);
#endif