1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * The Vendor OEM extension for System Control and Power Interface (SCMI) 4 * Protocol based clock driver 5 * 6 * Copyright 2025 NXP 7 */ 8 9 #include <linux/clk-provider.h> 10 #include <linux/of.h> 11 #include <linux/scmi_imx_protocol.h> 12 #include <linux/scmi_protocol.h> 13 14 #include "clk-scmi.h" 15 16 #define SCMI_CLOCK_CFG_IMX_SSC 0x80 17 #define SCMI_CLOCK_IMX_SS_PERCENTAGE_MASK GENMASK(7, 0) 18 #define SCMI_CLOCK_IMX_SS_MOD_FREQ_MASK GENMASK(23, 8) 19 #define SCMI_CLOCK_IMX_SS_ENABLE_MASK BIT(24) 20 21 /* 22 * Selection is based on SCMI vendor_id/sub_vendor_id and optional machine 23 * compatible string, without involving impl_ver. impl_ver‑specific behavior 24 * should be considered a bug and handled via SCMI Quirk framework. 25 */ 26 struct scmi_clk_oem_info { 27 char *vendor_id; 28 char *sub_vendor_id; 29 char *compatible; 30 const void *data; 31 }; 32 33 static int 34 scmi_clk_imx_set_spread_spectrum(struct clk_hw *hw, 35 const struct clk_spread_spectrum *ss_conf) 36 { 37 struct scmi_clk *clk = to_scmi_clk(hw); 38 int ret; 39 u32 val; 40 41 /* 42 * extConfigValue[7:0] - spread percentage (%) 43 * extConfigValue[23:8] - Modulation Frequency 44 * extConfigValue[24] - Enable/Disable 45 * extConfigValue[31:25] - Reserved 46 */ 47 val = FIELD_PREP(SCMI_CLOCK_IMX_SS_PERCENTAGE_MASK, ss_conf->spread_bp / 10000); 48 val |= FIELD_PREP(SCMI_CLOCK_IMX_SS_MOD_FREQ_MASK, ss_conf->modfreq_hz); 49 if (ss_conf->method != CLK_SPREAD_NO) 50 val |= SCMI_CLOCK_IMX_SS_ENABLE_MASK; 51 ret = scmi_proto_clk_ops->config_oem_set(clk->ph, clk->id, 52 SCMI_CLOCK_CFG_IMX_SSC, 53 val, false); 54 if (ret) 55 dev_warn(clk->dev, 56 "Failed to set spread spectrum(%u,%u,%u) for clock ID %d\n", 57 ss_conf->modfreq_hz, ss_conf->spread_bp, ss_conf->method, 58 clk->id); 59 60 return ret; 61 } 62 63 static int 64 scmi_clk_imx_query_oem_feats(const struct scmi_protocol_handle *ph, u32 id, 65 unsigned int *feats_key) 66 { 67 int ret; 68 u32 val; 69 70 ret = scmi_proto_clk_ops->config_oem_get(ph, id, 71 SCMI_CLOCK_CFG_IMX_SSC, 72 &val, NULL, false); 73 if (!ret) 74 *feats_key |= BIT(SCMI_CLK_EXT_OEM_SSC_SUPPORTED); 75 76 return 0; 77 } 78 79 static const struct scmi_clk_oem scmi_clk_oem_imx = { 80 .query_ext_oem_feats = scmi_clk_imx_query_oem_feats, 81 .set_spread_spectrum = scmi_clk_imx_set_spread_spectrum, 82 }; 83 84 static const struct scmi_clk_oem_info info[] = { 85 { SCMI_IMX_VENDOR, SCMI_IMX_SUBVENDOR, NULL, &scmi_clk_oem_imx }, 86 }; 87 88 int scmi_clk_oem_init(struct scmi_device *sdev) 89 { 90 const struct scmi_handle *handle = sdev->handle; 91 int i, size = ARRAY_SIZE(info); 92 93 for (i = 0; i < size; i++) { 94 if (strcmp(handle->version->vendor_id, info[i].vendor_id) || 95 strcmp(handle->version->sub_vendor_id, info[i].sub_vendor_id)) 96 continue; 97 if (info[i].compatible && 98 !of_machine_is_compatible(info[i].compatible)) 99 continue; 100 101 break; 102 } 103 104 if (i < size) 105 dev_set_drvdata(&sdev->dev, (void *)info[i].data); 106 107 return 0; 108 } 109