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