1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright (C) 2019-2022 Linaro Limited
4 */
5
6#define LOG_CATEGORY UCLASS_CLK
7
8#include <common.h>
9#include <clk-uclass.h>
10#include <dm.h>
11#include <scmi_agent.h>
12#include <scmi_protocols.h>
13#include <asm/types.h>
14#include <linux/clk-provider.h>
15
16static int scmi_clk_get_num_clock(struct udevice *dev, size_t *num_clocks)
17{
18	struct scmi_clk_protocol_attr_out out;
19	struct scmi_msg msg = {
20		.protocol_id = SCMI_PROTOCOL_ID_CLOCK,
21		.message_id = SCMI_PROTOCOL_ATTRIBUTES,
22		.out_msg = (u8 *)&out,
23		.out_msg_sz = sizeof(out),
24	};
25	int ret;
26
27	ret = devm_scmi_process_msg(dev, &msg);
28	if (ret)
29		return ret;
30
31	*num_clocks = out.attributes & SCMI_CLK_PROTO_ATTR_COUNT_MASK;
32
33	return 0;
34}
35
36static int scmi_clk_get_attibute(struct udevice *dev, int clkid, char **name)
37{
38	struct scmi_clk_attribute_in in = {
39		.clock_id = clkid,
40	};
41	struct scmi_clk_attribute_out out;
42	struct scmi_msg msg = {
43		.protocol_id = SCMI_PROTOCOL_ID_CLOCK,
44		.message_id = SCMI_CLOCK_ATTRIBUTES,
45		.in_msg = (u8 *)&in,
46		.in_msg_sz = sizeof(in),
47		.out_msg = (u8 *)&out,
48		.out_msg_sz = sizeof(out),
49	};
50	int ret;
51
52	ret = devm_scmi_process_msg(dev, &msg);
53	if (ret)
54		return ret;
55
56	*name = strdup(out.clock_name);
57
58	return 0;
59}
60
61static int scmi_clk_gate(struct clk *clk, int enable)
62{
63	struct scmi_clk_state_in in = {
64		.clock_id = clk->id,
65		.attributes = enable,
66	};
67	struct scmi_clk_state_out out;
68	struct scmi_msg msg = SCMI_MSG_IN(SCMI_PROTOCOL_ID_CLOCK,
69					  SCMI_CLOCK_CONFIG_SET,
70					  in, out);
71	int ret;
72
73	ret = devm_scmi_process_msg(clk->dev, &msg);
74	if (ret)
75		return ret;
76
77	return scmi_to_linux_errno(out.status);
78}
79
80static int scmi_clk_enable(struct clk *clk)
81{
82	return scmi_clk_gate(clk, 1);
83}
84
85static int scmi_clk_disable(struct clk *clk)
86{
87	return scmi_clk_gate(clk, 0);
88}
89
90static ulong scmi_clk_get_rate(struct clk *clk)
91{
92	struct scmi_clk_rate_get_in in = {
93		.clock_id = clk->id,
94	};
95	struct scmi_clk_rate_get_out out;
96	struct scmi_msg msg = SCMI_MSG_IN(SCMI_PROTOCOL_ID_CLOCK,
97					  SCMI_CLOCK_RATE_GET,
98					  in, out);
99	int ret;
100
101	ret = devm_scmi_process_msg(clk->dev, &msg);
102	if (ret < 0)
103		return ret;
104
105	ret = scmi_to_linux_errno(out.status);
106	if (ret < 0)
107		return ret;
108
109	return (ulong)(((u64)out.rate_msb << 32) | out.rate_lsb);
110}
111
112static ulong scmi_clk_set_rate(struct clk *clk, ulong rate)
113{
114	struct scmi_clk_rate_set_in in = {
115		.clock_id = clk->id,
116		.flags = SCMI_CLK_RATE_ROUND_CLOSEST,
117		.rate_lsb = (u32)rate,
118		.rate_msb = (u32)((u64)rate >> 32),
119	};
120	struct scmi_clk_rate_set_out out;
121	struct scmi_msg msg = SCMI_MSG_IN(SCMI_PROTOCOL_ID_CLOCK,
122					  SCMI_CLOCK_RATE_SET,
123					  in, out);
124	int ret;
125
126	ret = devm_scmi_process_msg(clk->dev, &msg);
127	if (ret < 0)
128		return ret;
129
130	ret = scmi_to_linux_errno(out.status);
131	if (ret < 0)
132		return ret;
133
134	return scmi_clk_get_rate(clk);
135}
136
137static int scmi_clk_probe(struct udevice *dev)
138{
139	struct clk *clk;
140	size_t num_clocks, i;
141	int ret;
142
143	ret = devm_scmi_of_get_channel(dev);
144	if (ret)
145		return ret;
146
147	if (!CONFIG_IS_ENABLED(CLK_CCF))
148		return 0;
149
150	/* register CCF children: CLK UCLASS, no probed again */
151	if (device_get_uclass_id(dev->parent) == UCLASS_CLK)
152		return 0;
153
154	ret = scmi_clk_get_num_clock(dev, &num_clocks);
155	if (ret)
156		return ret;
157
158	for (i = 0; i < num_clocks; i++) {
159		char *clock_name;
160
161		if (!scmi_clk_get_attibute(dev, i, &clock_name)) {
162			clk = kzalloc(sizeof(*clk), GFP_KERNEL);
163			if (!clk || !clock_name)
164				ret = -ENOMEM;
165			else
166				ret = clk_register(clk, dev->driver->name,
167						   clock_name, dev->name);
168
169			if (ret) {
170				free(clk);
171				free(clock_name);
172				return ret;
173			}
174
175			clk_dm(i, clk);
176		}
177	}
178
179	return 0;
180}
181
182static const struct clk_ops scmi_clk_ops = {
183	.enable = scmi_clk_enable,
184	.disable = scmi_clk_disable,
185	.get_rate = scmi_clk_get_rate,
186	.set_rate = scmi_clk_set_rate,
187};
188
189U_BOOT_DRIVER(scmi_clock) = {
190	.name = "scmi_clk",
191	.id = UCLASS_CLK,
192	.ops = &scmi_clk_ops,
193	.probe = scmi_clk_probe,
194};
195