1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * System Control and Management Interface (SCMI) Power Protocol |
4 | * |
5 | * Copyright (C) 2018-2022 ARM Ltd. |
6 | */ |
7 | |
8 | #define pr_fmt(fmt) "SCMI Notifications POWER - " fmt |
9 | |
10 | #include <linux/module.h> |
11 | #include <linux/scmi_protocol.h> |
12 | |
13 | #include "protocols.h" |
14 | #include "notify.h" |
15 | |
16 | /* Updated only after ALL the mandatory features for that version are merged */ |
17 | #define SCMI_PROTOCOL_SUPPORTED_VERSION 0x30000 |
18 | |
19 | enum scmi_power_protocol_cmd { |
20 | POWER_DOMAIN_ATTRIBUTES = 0x3, |
21 | POWER_STATE_SET = 0x4, |
22 | POWER_STATE_GET = 0x5, |
23 | POWER_STATE_NOTIFY = 0x6, |
24 | POWER_DOMAIN_NAME_GET = 0x8, |
25 | }; |
26 | |
27 | struct scmi_msg_resp_power_attributes { |
28 | __le16 num_domains; |
29 | __le16 reserved; |
30 | __le32 stats_addr_low; |
31 | __le32 stats_addr_high; |
32 | __le32 stats_size; |
33 | }; |
34 | |
35 | struct scmi_msg_resp_power_domain_attributes { |
36 | __le32 flags; |
37 | #define SUPPORTS_STATE_SET_NOTIFY(x) ((x) & BIT(31)) |
38 | #define SUPPORTS_STATE_SET_ASYNC(x) ((x) & BIT(30)) |
39 | #define SUPPORTS_STATE_SET_SYNC(x) ((x) & BIT(29)) |
40 | #define SUPPORTS_EXTENDED_NAMES(x) ((x) & BIT(27)) |
41 | u8 name[SCMI_SHORT_NAME_MAX_SIZE]; |
42 | }; |
43 | |
44 | struct scmi_power_set_state { |
45 | __le32 flags; |
46 | #define STATE_SET_ASYNC BIT(0) |
47 | __le32 domain; |
48 | __le32 state; |
49 | }; |
50 | |
51 | struct scmi_power_state_notify { |
52 | __le32 domain; |
53 | __le32 notify_enable; |
54 | }; |
55 | |
56 | struct scmi_power_state_notify_payld { |
57 | __le32 agent_id; |
58 | __le32 domain_id; |
59 | __le32 power_state; |
60 | }; |
61 | |
62 | struct power_dom_info { |
63 | bool state_set_sync; |
64 | bool state_set_async; |
65 | bool state_set_notify; |
66 | char name[SCMI_MAX_STR_SIZE]; |
67 | }; |
68 | |
69 | struct scmi_power_info { |
70 | u32 version; |
71 | bool notify_state_change_cmd; |
72 | int num_domains; |
73 | u64 stats_addr; |
74 | u32 stats_size; |
75 | struct power_dom_info *dom_info; |
76 | }; |
77 | |
78 | static int scmi_power_attributes_get(const struct scmi_protocol_handle *ph, |
79 | struct scmi_power_info *pi) |
80 | { |
81 | int ret; |
82 | struct scmi_xfer *t; |
83 | struct scmi_msg_resp_power_attributes *attr; |
84 | |
85 | ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, |
86 | 0, sizeof(*attr), &t); |
87 | if (ret) |
88 | return ret; |
89 | |
90 | attr = t->rx.buf; |
91 | |
92 | ret = ph->xops->do_xfer(ph, t); |
93 | if (!ret) { |
94 | pi->num_domains = le16_to_cpu(attr->num_domains); |
95 | pi->stats_addr = le32_to_cpu(attr->stats_addr_low) | |
96 | (u64)le32_to_cpu(attr->stats_addr_high) << 32; |
97 | pi->stats_size = le32_to_cpu(attr->stats_size); |
98 | } |
99 | |
100 | ph->xops->xfer_put(ph, t); |
101 | |
102 | if (!ret) |
103 | if (!ph->hops->protocol_msg_check(ph, POWER_STATE_NOTIFY, NULL)) |
104 | pi->notify_state_change_cmd = true; |
105 | |
106 | return ret; |
107 | } |
108 | |
109 | static int |
110 | scmi_power_domain_attributes_get(const struct scmi_protocol_handle *ph, |
111 | u32 domain, struct power_dom_info *dom_info, |
112 | u32 version, bool notify_state_change_cmd) |
113 | { |
114 | int ret; |
115 | u32 flags; |
116 | struct scmi_xfer *t; |
117 | struct scmi_msg_resp_power_domain_attributes *attr; |
118 | |
119 | ret = ph->xops->xfer_get_init(ph, POWER_DOMAIN_ATTRIBUTES, |
120 | sizeof(domain), sizeof(*attr), &t); |
121 | if (ret) |
122 | return ret; |
123 | |
124 | put_unaligned_le32(val: domain, p: t->tx.buf); |
125 | attr = t->rx.buf; |
126 | |
127 | ret = ph->xops->do_xfer(ph, t); |
128 | if (!ret) { |
129 | flags = le32_to_cpu(attr->flags); |
130 | |
131 | if (notify_state_change_cmd) |
132 | dom_info->state_set_notify = |
133 | SUPPORTS_STATE_SET_NOTIFY(flags); |
134 | dom_info->state_set_async = SUPPORTS_STATE_SET_ASYNC(flags); |
135 | dom_info->state_set_sync = SUPPORTS_STATE_SET_SYNC(flags); |
136 | strscpy(dom_info->name, attr->name, SCMI_SHORT_NAME_MAX_SIZE); |
137 | } |
138 | ph->xops->xfer_put(ph, t); |
139 | |
140 | /* |
141 | * If supported overwrite short name with the extended one; |
142 | * on error just carry on and use already provided short name. |
143 | */ |
144 | if (!ret && PROTOCOL_REV_MAJOR(version) >= 0x3 && |
145 | SUPPORTS_EXTENDED_NAMES(flags)) { |
146 | ph->hops->extended_name_get(ph, POWER_DOMAIN_NAME_GET, |
147 | domain, NULL, dom_info->name, |
148 | SCMI_MAX_STR_SIZE); |
149 | } |
150 | |
151 | return ret; |
152 | } |
153 | |
154 | static int scmi_power_state_set(const struct scmi_protocol_handle *ph, |
155 | u32 domain, u32 state) |
156 | { |
157 | int ret; |
158 | struct scmi_xfer *t; |
159 | struct scmi_power_set_state *st; |
160 | |
161 | ret = ph->xops->xfer_get_init(ph, POWER_STATE_SET, sizeof(*st), 0, &t); |
162 | if (ret) |
163 | return ret; |
164 | |
165 | st = t->tx.buf; |
166 | st->flags = cpu_to_le32(0); |
167 | st->domain = cpu_to_le32(domain); |
168 | st->state = cpu_to_le32(state); |
169 | |
170 | ret = ph->xops->do_xfer(ph, t); |
171 | |
172 | ph->xops->xfer_put(ph, t); |
173 | return ret; |
174 | } |
175 | |
176 | static int scmi_power_state_get(const struct scmi_protocol_handle *ph, |
177 | u32 domain, u32 *state) |
178 | { |
179 | int ret; |
180 | struct scmi_xfer *t; |
181 | |
182 | ret = ph->xops->xfer_get_init(ph, POWER_STATE_GET, sizeof(u32), sizeof(u32), &t); |
183 | if (ret) |
184 | return ret; |
185 | |
186 | put_unaligned_le32(val: domain, p: t->tx.buf); |
187 | |
188 | ret = ph->xops->do_xfer(ph, t); |
189 | if (!ret) |
190 | *state = get_unaligned_le32(p: t->rx.buf); |
191 | |
192 | ph->xops->xfer_put(ph, t); |
193 | return ret; |
194 | } |
195 | |
196 | static int scmi_power_num_domains_get(const struct scmi_protocol_handle *ph) |
197 | { |
198 | struct scmi_power_info *pi = ph->get_priv(ph); |
199 | |
200 | return pi->num_domains; |
201 | } |
202 | |
203 | static const char * |
204 | scmi_power_name_get(const struct scmi_protocol_handle *ph, |
205 | u32 domain) |
206 | { |
207 | struct scmi_power_info *pi = ph->get_priv(ph); |
208 | struct power_dom_info *dom = pi->dom_info + domain; |
209 | |
210 | return dom->name; |
211 | } |
212 | |
213 | static const struct scmi_power_proto_ops power_proto_ops = { |
214 | .num_domains_get = scmi_power_num_domains_get, |
215 | .name_get = scmi_power_name_get, |
216 | .state_set = scmi_power_state_set, |
217 | .state_get = scmi_power_state_get, |
218 | }; |
219 | |
220 | static int scmi_power_request_notify(const struct scmi_protocol_handle *ph, |
221 | u32 domain, bool enable) |
222 | { |
223 | int ret; |
224 | struct scmi_xfer *t; |
225 | struct scmi_power_state_notify *notify; |
226 | |
227 | ret = ph->xops->xfer_get_init(ph, POWER_STATE_NOTIFY, |
228 | sizeof(*notify), 0, &t); |
229 | if (ret) |
230 | return ret; |
231 | |
232 | notify = t->tx.buf; |
233 | notify->domain = cpu_to_le32(domain); |
234 | notify->notify_enable = enable ? cpu_to_le32(BIT(0)) : 0; |
235 | |
236 | ret = ph->xops->do_xfer(ph, t); |
237 | |
238 | ph->xops->xfer_put(ph, t); |
239 | return ret; |
240 | } |
241 | |
242 | static bool scmi_power_notify_supported(const struct scmi_protocol_handle *ph, |
243 | u8 evt_id, u32 src_id) |
244 | { |
245 | struct power_dom_info *dom; |
246 | struct scmi_power_info *pinfo = ph->get_priv(ph); |
247 | |
248 | if (evt_id != SCMI_EVENT_POWER_STATE_CHANGED || |
249 | src_id >= pinfo->num_domains) |
250 | return false; |
251 | |
252 | dom = pinfo->dom_info + src_id; |
253 | return dom->state_set_notify; |
254 | } |
255 | |
256 | static int scmi_power_set_notify_enabled(const struct scmi_protocol_handle *ph, |
257 | u8 evt_id, u32 src_id, bool enable) |
258 | { |
259 | int ret; |
260 | |
261 | ret = scmi_power_request_notify(ph, domain: src_id, enable); |
262 | if (ret) |
263 | pr_debug("FAIL_ENABLE - evt[%X] dom[%d] - ret:%d\n" , |
264 | evt_id, src_id, ret); |
265 | |
266 | return ret; |
267 | } |
268 | |
269 | static void * |
270 | scmi_power_fill_custom_report(const struct scmi_protocol_handle *ph, |
271 | u8 evt_id, ktime_t timestamp, |
272 | const void *payld, size_t payld_sz, |
273 | void *report, u32 *src_id) |
274 | { |
275 | const struct scmi_power_state_notify_payld *p = payld; |
276 | struct scmi_power_state_changed_report *r = report; |
277 | |
278 | if (evt_id != SCMI_EVENT_POWER_STATE_CHANGED || sizeof(*p) != payld_sz) |
279 | return NULL; |
280 | |
281 | r->timestamp = timestamp; |
282 | r->agent_id = le32_to_cpu(p->agent_id); |
283 | r->domain_id = le32_to_cpu(p->domain_id); |
284 | r->power_state = le32_to_cpu(p->power_state); |
285 | *src_id = r->domain_id; |
286 | |
287 | return r; |
288 | } |
289 | |
290 | static int scmi_power_get_num_sources(const struct scmi_protocol_handle *ph) |
291 | { |
292 | struct scmi_power_info *pinfo = ph->get_priv(ph); |
293 | |
294 | if (!pinfo) |
295 | return -EINVAL; |
296 | |
297 | return pinfo->num_domains; |
298 | } |
299 | |
300 | static const struct scmi_event power_events[] = { |
301 | { |
302 | .id = SCMI_EVENT_POWER_STATE_CHANGED, |
303 | .max_payld_sz = sizeof(struct scmi_power_state_notify_payld), |
304 | .max_report_sz = |
305 | sizeof(struct scmi_power_state_changed_report), |
306 | }, |
307 | }; |
308 | |
309 | static const struct scmi_event_ops power_event_ops = { |
310 | .is_notify_supported = scmi_power_notify_supported, |
311 | .get_num_sources = scmi_power_get_num_sources, |
312 | .set_notify_enabled = scmi_power_set_notify_enabled, |
313 | .fill_custom_report = scmi_power_fill_custom_report, |
314 | }; |
315 | |
316 | static const struct scmi_protocol_events power_protocol_events = { |
317 | .queue_sz = SCMI_PROTO_QUEUE_SZ, |
318 | .ops = &power_event_ops, |
319 | .evts = power_events, |
320 | .num_events = ARRAY_SIZE(power_events), |
321 | }; |
322 | |
323 | static int scmi_power_protocol_init(const struct scmi_protocol_handle *ph) |
324 | { |
325 | int domain, ret; |
326 | u32 version; |
327 | struct scmi_power_info *pinfo; |
328 | |
329 | ret = ph->xops->version_get(ph, &version); |
330 | if (ret) |
331 | return ret; |
332 | |
333 | dev_dbg(ph->dev, "Power Version %d.%d\n" , |
334 | PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version)); |
335 | |
336 | pinfo = devm_kzalloc(dev: ph->dev, size: sizeof(*pinfo), GFP_KERNEL); |
337 | if (!pinfo) |
338 | return -ENOMEM; |
339 | |
340 | ret = scmi_power_attributes_get(ph, pi: pinfo); |
341 | if (ret) |
342 | return ret; |
343 | |
344 | pinfo->dom_info = devm_kcalloc(dev: ph->dev, n: pinfo->num_domains, |
345 | size: sizeof(*pinfo->dom_info), GFP_KERNEL); |
346 | if (!pinfo->dom_info) |
347 | return -ENOMEM; |
348 | |
349 | for (domain = 0; domain < pinfo->num_domains; domain++) { |
350 | struct power_dom_info *dom = pinfo->dom_info + domain; |
351 | |
352 | scmi_power_domain_attributes_get(ph, domain, dom_info: dom, version, |
353 | notify_state_change_cmd: pinfo->notify_state_change_cmd); |
354 | } |
355 | |
356 | pinfo->version = version; |
357 | |
358 | return ph->set_priv(ph, pinfo, version); |
359 | } |
360 | |
361 | static const struct scmi_protocol scmi_power = { |
362 | .id = SCMI_PROTOCOL_POWER, |
363 | .owner = THIS_MODULE, |
364 | .instance_init = &scmi_power_protocol_init, |
365 | .ops = &power_proto_ops, |
366 | .events = &power_protocol_events, |
367 | .supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION, |
368 | }; |
369 | |
370 | DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(power, scmi_power) |
371 | |