xref: /linux/drivers/clk/clk-scmi-oem.c (revision 502d45774af09f1c681c754c4b7cdfb5d7f72fd9)
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
scmi_clk_imx_set_spread_spectrum(struct clk_hw * hw,const struct clk_spread_spectrum * ss_conf)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
scmi_clk_imx_query_oem_feats(const struct scmi_protocol_handle * ph,u32 id,unsigned int * feats_key)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 
scmi_clk_oem_init(struct scmi_device * sdev)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