1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * System Control and Management Interface (SCMI) Powercap Protocol |
4 | * |
5 | * Copyright (C) 2022 ARM Ltd. |
6 | */ |
7 | |
8 | #define pr_fmt(fmt) "SCMI Notifications POWERCAP - " fmt |
9 | |
10 | #include <linux/bitfield.h> |
11 | #include <linux/io.h> |
12 | #include <linux/module.h> |
13 | #include <linux/scmi_protocol.h> |
14 | |
15 | #include <trace/events/scmi.h> |
16 | |
17 | #include "protocols.h" |
18 | #include "notify.h" |
19 | |
20 | /* Updated only after ALL the mandatory features for that version are merged */ |
21 | #define SCMI_PROTOCOL_SUPPORTED_VERSION 0x20000 |
22 | |
23 | enum scmi_powercap_protocol_cmd { |
24 | POWERCAP_DOMAIN_ATTRIBUTES = 0x3, |
25 | POWERCAP_CAP_GET = 0x4, |
26 | POWERCAP_CAP_SET = 0x5, |
27 | POWERCAP_PAI_GET = 0x6, |
28 | POWERCAP_PAI_SET = 0x7, |
29 | POWERCAP_DOMAIN_NAME_GET = 0x8, |
30 | POWERCAP_MEASUREMENTS_GET = 0x9, |
31 | POWERCAP_CAP_NOTIFY = 0xa, |
32 | POWERCAP_MEASUREMENTS_NOTIFY = 0xb, |
33 | POWERCAP_DESCRIBE_FASTCHANNEL = 0xc, |
34 | }; |
35 | |
36 | enum { |
37 | POWERCAP_FC_CAP, |
38 | POWERCAP_FC_PAI, |
39 | POWERCAP_FC_MAX, |
40 | }; |
41 | |
42 | struct scmi_msg_resp_powercap_domain_attributes { |
43 | __le32 attributes; |
44 | #define SUPPORTS_POWERCAP_CAP_CHANGE_NOTIFY(x) ((x) & BIT(31)) |
45 | #define SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(x) ((x) & BIT(30)) |
46 | #define SUPPORTS_ASYNC_POWERCAP_CAP_SET(x) ((x) & BIT(29)) |
47 | #define SUPPORTS_EXTENDED_NAMES(x) ((x) & BIT(28)) |
48 | #define SUPPORTS_POWERCAP_CAP_CONFIGURATION(x) ((x) & BIT(27)) |
49 | #define SUPPORTS_POWERCAP_MONITORING(x) ((x) & BIT(26)) |
50 | #define SUPPORTS_POWERCAP_PAI_CONFIGURATION(x) ((x) & BIT(25)) |
51 | #define SUPPORTS_POWERCAP_FASTCHANNELS(x) ((x) & BIT(22)) |
52 | #define POWERCAP_POWER_UNIT(x) \ |
53 | (FIELD_GET(GENMASK(24, 23), (x))) |
54 | #define SUPPORTS_POWER_UNITS_MW(x) \ |
55 | (POWERCAP_POWER_UNIT(x) == 0x2) |
56 | #define SUPPORTS_POWER_UNITS_UW(x) \ |
57 | (POWERCAP_POWER_UNIT(x) == 0x1) |
58 | u8 name[SCMI_SHORT_NAME_MAX_SIZE]; |
59 | __le32 min_pai; |
60 | __le32 max_pai; |
61 | __le32 pai_step; |
62 | __le32 min_power_cap; |
63 | __le32 max_power_cap; |
64 | __le32 power_cap_step; |
65 | __le32 sustainable_power; |
66 | __le32 accuracy; |
67 | __le32 parent_id; |
68 | }; |
69 | |
70 | struct scmi_msg_powercap_set_cap_or_pai { |
71 | __le32 domain; |
72 | __le32 flags; |
73 | #define CAP_SET_ASYNC BIT(1) |
74 | #define CAP_SET_IGNORE_DRESP BIT(0) |
75 | __le32 value; |
76 | }; |
77 | |
78 | struct scmi_msg_resp_powercap_cap_set_complete { |
79 | __le32 domain; |
80 | __le32 power_cap; |
81 | }; |
82 | |
83 | struct scmi_msg_resp_powercap_meas_get { |
84 | __le32 power; |
85 | __le32 pai; |
86 | }; |
87 | |
88 | struct scmi_msg_powercap_notify_cap { |
89 | __le32 domain; |
90 | __le32 notify_enable; |
91 | }; |
92 | |
93 | struct scmi_msg_powercap_notify_thresh { |
94 | __le32 domain; |
95 | __le32 notify_enable; |
96 | __le32 power_thresh_low; |
97 | __le32 power_thresh_high; |
98 | }; |
99 | |
100 | struct scmi_powercap_cap_changed_notify_payld { |
101 | __le32 agent_id; |
102 | __le32 domain_id; |
103 | __le32 power_cap; |
104 | __le32 pai; |
105 | }; |
106 | |
107 | struct scmi_powercap_meas_changed_notify_payld { |
108 | __le32 agent_id; |
109 | __le32 domain_id; |
110 | __le32 power; |
111 | }; |
112 | |
113 | struct scmi_powercap_state { |
114 | bool enabled; |
115 | u32 last_pcap; |
116 | bool meas_notif_enabled; |
117 | u64 thresholds; |
118 | #define THRESH_LOW(p, id) \ |
119 | (lower_32_bits((p)->states[(id)].thresholds)) |
120 | #define THRESH_HIGH(p, id) \ |
121 | (upper_32_bits((p)->states[(id)].thresholds)) |
122 | }; |
123 | |
124 | struct powercap_info { |
125 | u32 version; |
126 | int num_domains; |
127 | bool notify_cap_cmd; |
128 | bool notify_measurements_cmd; |
129 | struct scmi_powercap_state *states; |
130 | struct scmi_powercap_info *powercaps; |
131 | }; |
132 | |
133 | static enum scmi_powercap_protocol_cmd evt_2_cmd[] = { |
134 | POWERCAP_CAP_NOTIFY, |
135 | POWERCAP_MEASUREMENTS_NOTIFY, |
136 | }; |
137 | |
138 | static int scmi_powercap_notify(const struct scmi_protocol_handle *ph, |
139 | u32 domain, int message_id, bool enable); |
140 | |
141 | static int |
142 | scmi_powercap_attributes_get(const struct scmi_protocol_handle *ph, |
143 | struct powercap_info *pi) |
144 | { |
145 | int ret; |
146 | struct scmi_xfer *t; |
147 | |
148 | ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0, |
149 | sizeof(u32), &t); |
150 | if (ret) |
151 | return ret; |
152 | |
153 | ret = ph->xops->do_xfer(ph, t); |
154 | if (!ret) { |
155 | u32 attributes; |
156 | |
157 | attributes = get_unaligned_le32(p: t->rx.buf); |
158 | pi->num_domains = FIELD_GET(GENMASK(15, 0), attributes); |
159 | } |
160 | |
161 | ph->xops->xfer_put(ph, t); |
162 | |
163 | if (!ret) { |
164 | if (!ph->hops->protocol_msg_check(ph, |
165 | POWERCAP_CAP_NOTIFY, NULL)) |
166 | pi->notify_cap_cmd = true; |
167 | |
168 | if (!ph->hops->protocol_msg_check(ph, |
169 | POWERCAP_MEASUREMENTS_NOTIFY, |
170 | NULL)) |
171 | pi->notify_measurements_cmd = true; |
172 | } |
173 | |
174 | return ret; |
175 | } |
176 | |
177 | static inline int |
178 | scmi_powercap_validate(unsigned int min_val, unsigned int max_val, |
179 | unsigned int step_val, bool configurable) |
180 | { |
181 | if (!min_val || !max_val) |
182 | return -EPROTO; |
183 | |
184 | if ((configurable && min_val == max_val) || |
185 | (!configurable && min_val != max_val)) |
186 | return -EPROTO; |
187 | |
188 | if (min_val != max_val && !step_val) |
189 | return -EPROTO; |
190 | |
191 | return 0; |
192 | } |
193 | |
194 | static int |
195 | scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph, |
196 | struct powercap_info *pinfo, u32 domain) |
197 | { |
198 | int ret; |
199 | u32 flags; |
200 | struct scmi_xfer *t; |
201 | struct scmi_powercap_info *dom_info = pinfo->powercaps + domain; |
202 | struct scmi_msg_resp_powercap_domain_attributes *resp; |
203 | |
204 | ret = ph->xops->xfer_get_init(ph, POWERCAP_DOMAIN_ATTRIBUTES, |
205 | sizeof(domain), sizeof(*resp), &t); |
206 | if (ret) |
207 | return ret; |
208 | |
209 | put_unaligned_le32(val: domain, p: t->tx.buf); |
210 | resp = t->rx.buf; |
211 | |
212 | ret = ph->xops->do_xfer(ph, t); |
213 | if (!ret) { |
214 | flags = le32_to_cpu(resp->attributes); |
215 | |
216 | dom_info->id = domain; |
217 | if (pinfo->notify_cap_cmd) |
218 | dom_info->notify_powercap_cap_change = |
219 | SUPPORTS_POWERCAP_CAP_CHANGE_NOTIFY(flags); |
220 | if (pinfo->notify_measurements_cmd) |
221 | dom_info->notify_powercap_measurement_change = |
222 | SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(flags); |
223 | dom_info->async_powercap_cap_set = |
224 | SUPPORTS_ASYNC_POWERCAP_CAP_SET(flags); |
225 | dom_info->powercap_cap_config = |
226 | SUPPORTS_POWERCAP_CAP_CONFIGURATION(flags); |
227 | dom_info->powercap_monitoring = |
228 | SUPPORTS_POWERCAP_MONITORING(flags); |
229 | dom_info->powercap_pai_config = |
230 | SUPPORTS_POWERCAP_PAI_CONFIGURATION(flags); |
231 | dom_info->powercap_scale_mw = |
232 | SUPPORTS_POWER_UNITS_MW(flags); |
233 | dom_info->powercap_scale_uw = |
234 | SUPPORTS_POWER_UNITS_UW(flags); |
235 | dom_info->fastchannels = |
236 | SUPPORTS_POWERCAP_FASTCHANNELS(flags); |
237 | |
238 | strscpy(dom_info->name, resp->name, SCMI_SHORT_NAME_MAX_SIZE); |
239 | |
240 | dom_info->min_pai = le32_to_cpu(resp->min_pai); |
241 | dom_info->max_pai = le32_to_cpu(resp->max_pai); |
242 | dom_info->pai_step = le32_to_cpu(resp->pai_step); |
243 | ret = scmi_powercap_validate(min_val: dom_info->min_pai, |
244 | max_val: dom_info->max_pai, |
245 | step_val: dom_info->pai_step, |
246 | configurable: dom_info->powercap_pai_config); |
247 | if (ret) { |
248 | dev_err(ph->dev, |
249 | "Platform reported inconsistent PAI config for domain %d - %s\n" , |
250 | dom_info->id, dom_info->name); |
251 | goto clean; |
252 | } |
253 | |
254 | dom_info->min_power_cap = le32_to_cpu(resp->min_power_cap); |
255 | dom_info->max_power_cap = le32_to_cpu(resp->max_power_cap); |
256 | dom_info->power_cap_step = le32_to_cpu(resp->power_cap_step); |
257 | ret = scmi_powercap_validate(min_val: dom_info->min_power_cap, |
258 | max_val: dom_info->max_power_cap, |
259 | step_val: dom_info->power_cap_step, |
260 | configurable: dom_info->powercap_cap_config); |
261 | if (ret) { |
262 | dev_err(ph->dev, |
263 | "Platform reported inconsistent CAP config for domain %d - %s\n" , |
264 | dom_info->id, dom_info->name); |
265 | goto clean; |
266 | } |
267 | |
268 | dom_info->sustainable_power = |
269 | le32_to_cpu(resp->sustainable_power); |
270 | dom_info->accuracy = le32_to_cpu(resp->accuracy); |
271 | |
272 | dom_info->parent_id = le32_to_cpu(resp->parent_id); |
273 | if (dom_info->parent_id != SCMI_POWERCAP_ROOT_ZONE_ID && |
274 | (dom_info->parent_id >= pinfo->num_domains || |
275 | dom_info->parent_id == dom_info->id)) { |
276 | dev_err(ph->dev, |
277 | "Platform reported inconsistent parent ID for domain %d - %s\n" , |
278 | dom_info->id, dom_info->name); |
279 | ret = -ENODEV; |
280 | } |
281 | } |
282 | |
283 | clean: |
284 | ph->xops->xfer_put(ph, t); |
285 | |
286 | /* |
287 | * If supported overwrite short name with the extended one; |
288 | * on error just carry on and use already provided short name. |
289 | */ |
290 | if (!ret && SUPPORTS_EXTENDED_NAMES(flags)) |
291 | ph->hops->extended_name_get(ph, POWERCAP_DOMAIN_NAME_GET, |
292 | domain, NULL, dom_info->name, |
293 | SCMI_MAX_STR_SIZE); |
294 | |
295 | return ret; |
296 | } |
297 | |
298 | static int scmi_powercap_num_domains_get(const struct scmi_protocol_handle *ph) |
299 | { |
300 | struct powercap_info *pi = ph->get_priv(ph); |
301 | |
302 | return pi->num_domains; |
303 | } |
304 | |
305 | static const struct scmi_powercap_info * |
306 | scmi_powercap_dom_info_get(const struct scmi_protocol_handle *ph, u32 domain_id) |
307 | { |
308 | struct powercap_info *pi = ph->get_priv(ph); |
309 | |
310 | if (domain_id >= pi->num_domains) |
311 | return NULL; |
312 | |
313 | return pi->powercaps + domain_id; |
314 | } |
315 | |
316 | static int scmi_powercap_xfer_cap_get(const struct scmi_protocol_handle *ph, |
317 | u32 domain_id, u32 *power_cap) |
318 | { |
319 | int ret; |
320 | struct scmi_xfer *t; |
321 | |
322 | ret = ph->xops->xfer_get_init(ph, POWERCAP_CAP_GET, sizeof(u32), |
323 | sizeof(u32), &t); |
324 | if (ret) |
325 | return ret; |
326 | |
327 | put_unaligned_le32(val: domain_id, p: t->tx.buf); |
328 | ret = ph->xops->do_xfer(ph, t); |
329 | if (!ret) |
330 | *power_cap = get_unaligned_le32(p: t->rx.buf); |
331 | |
332 | ph->xops->xfer_put(ph, t); |
333 | |
334 | return ret; |
335 | } |
336 | |
337 | static int __scmi_powercap_cap_get(const struct scmi_protocol_handle *ph, |
338 | const struct scmi_powercap_info *dom, |
339 | u32 *power_cap) |
340 | { |
341 | if (dom->fc_info && dom->fc_info[POWERCAP_FC_CAP].get_addr) { |
342 | *power_cap = ioread32(dom->fc_info[POWERCAP_FC_CAP].get_addr); |
343 | trace_scmi_fc_call(protocol_id: SCMI_PROTOCOL_POWERCAP, msg_id: POWERCAP_CAP_GET, |
344 | res_id: dom->id, val1: *power_cap, val2: 0); |
345 | return 0; |
346 | } |
347 | |
348 | return scmi_powercap_xfer_cap_get(ph, domain_id: dom->id, power_cap); |
349 | } |
350 | |
351 | static int scmi_powercap_cap_get(const struct scmi_protocol_handle *ph, |
352 | u32 domain_id, u32 *power_cap) |
353 | { |
354 | const struct scmi_powercap_info *dom; |
355 | |
356 | if (!power_cap) |
357 | return -EINVAL; |
358 | |
359 | dom = scmi_powercap_dom_info_get(ph, domain_id); |
360 | if (!dom) |
361 | return -EINVAL; |
362 | |
363 | return __scmi_powercap_cap_get(ph, dom, power_cap); |
364 | } |
365 | |
366 | static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph, |
367 | const struct scmi_powercap_info *pc, |
368 | u32 power_cap, bool ignore_dresp) |
369 | { |
370 | int ret; |
371 | struct scmi_xfer *t; |
372 | struct scmi_msg_powercap_set_cap_or_pai *msg; |
373 | |
374 | ret = ph->xops->xfer_get_init(ph, POWERCAP_CAP_SET, |
375 | sizeof(*msg), 0, &t); |
376 | if (ret) |
377 | return ret; |
378 | |
379 | msg = t->tx.buf; |
380 | msg->domain = cpu_to_le32(pc->id); |
381 | msg->flags = |
382 | cpu_to_le32(FIELD_PREP(CAP_SET_ASYNC, pc->async_powercap_cap_set) | |
383 | FIELD_PREP(CAP_SET_IGNORE_DRESP, ignore_dresp)); |
384 | msg->value = cpu_to_le32(power_cap); |
385 | |
386 | if (!pc->async_powercap_cap_set || ignore_dresp) { |
387 | ret = ph->xops->do_xfer(ph, t); |
388 | } else { |
389 | ret = ph->xops->do_xfer_with_response(ph, t); |
390 | if (!ret) { |
391 | struct scmi_msg_resp_powercap_cap_set_complete *resp; |
392 | |
393 | resp = t->rx.buf; |
394 | if (le32_to_cpu(resp->domain) == pc->id) |
395 | dev_dbg(ph->dev, |
396 | "Powercap ID %d CAP set async to %u\n" , |
397 | pc->id, |
398 | get_unaligned_le32(&resp->power_cap)); |
399 | else |
400 | ret = -EPROTO; |
401 | } |
402 | } |
403 | |
404 | ph->xops->xfer_put(ph, t); |
405 | return ret; |
406 | } |
407 | |
408 | static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph, |
409 | struct powercap_info *pi, u32 domain_id, |
410 | u32 power_cap, bool ignore_dresp) |
411 | { |
412 | int ret = -EINVAL; |
413 | const struct scmi_powercap_info *pc; |
414 | |
415 | pc = scmi_powercap_dom_info_get(ph, domain_id); |
416 | if (!pc || !pc->powercap_cap_config) |
417 | return ret; |
418 | |
419 | if (power_cap && |
420 | (power_cap < pc->min_power_cap || power_cap > pc->max_power_cap)) |
421 | return ret; |
422 | |
423 | if (pc->fc_info && pc->fc_info[POWERCAP_FC_CAP].set_addr) { |
424 | struct scmi_fc_info *fci = &pc->fc_info[POWERCAP_FC_CAP]; |
425 | |
426 | iowrite32(power_cap, fci->set_addr); |
427 | ph->hops->fastchannel_db_ring(fci->set_db); |
428 | trace_scmi_fc_call(protocol_id: SCMI_PROTOCOL_POWERCAP, msg_id: POWERCAP_CAP_SET, |
429 | res_id: domain_id, val1: power_cap, val2: 0); |
430 | ret = 0; |
431 | } else { |
432 | ret = scmi_powercap_xfer_cap_set(ph, pc, power_cap, |
433 | ignore_dresp); |
434 | } |
435 | |
436 | /* Save the last explicitly set non-zero powercap value */ |
437 | if (PROTOCOL_REV_MAJOR(pi->version) >= 0x2 && !ret && power_cap) |
438 | pi->states[domain_id].last_pcap = power_cap; |
439 | |
440 | return ret; |
441 | } |
442 | |
443 | static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph, |
444 | u32 domain_id, u32 power_cap, |
445 | bool ignore_dresp) |
446 | { |
447 | struct powercap_info *pi = ph->get_priv(ph); |
448 | |
449 | /* |
450 | * Disallow zero as a possible explicitly requested powercap: |
451 | * there are enable/disable operations for this. |
452 | */ |
453 | if (!power_cap) |
454 | return -EINVAL; |
455 | |
456 | /* Just log the last set request if acting on a disabled domain */ |
457 | if (PROTOCOL_REV_MAJOR(pi->version) >= 0x2 && |
458 | !pi->states[domain_id].enabled) { |
459 | pi->states[domain_id].last_pcap = power_cap; |
460 | return 0; |
461 | } |
462 | |
463 | return __scmi_powercap_cap_set(ph, pi, domain_id, |
464 | power_cap, ignore_dresp); |
465 | } |
466 | |
467 | static int scmi_powercap_xfer_pai_get(const struct scmi_protocol_handle *ph, |
468 | u32 domain_id, u32 *pai) |
469 | { |
470 | int ret; |
471 | struct scmi_xfer *t; |
472 | |
473 | ret = ph->xops->xfer_get_init(ph, POWERCAP_PAI_GET, sizeof(u32), |
474 | sizeof(u32), &t); |
475 | if (ret) |
476 | return ret; |
477 | |
478 | put_unaligned_le32(val: domain_id, p: t->tx.buf); |
479 | ret = ph->xops->do_xfer(ph, t); |
480 | if (!ret) |
481 | *pai = get_unaligned_le32(p: t->rx.buf); |
482 | |
483 | ph->xops->xfer_put(ph, t); |
484 | |
485 | return ret; |
486 | } |
487 | |
488 | static int scmi_powercap_pai_get(const struct scmi_protocol_handle *ph, |
489 | u32 domain_id, u32 *pai) |
490 | { |
491 | struct scmi_powercap_info *dom; |
492 | struct powercap_info *pi = ph->get_priv(ph); |
493 | |
494 | if (!pai || domain_id >= pi->num_domains) |
495 | return -EINVAL; |
496 | |
497 | dom = pi->powercaps + domain_id; |
498 | if (dom->fc_info && dom->fc_info[POWERCAP_FC_PAI].get_addr) { |
499 | *pai = ioread32(dom->fc_info[POWERCAP_FC_PAI].get_addr); |
500 | trace_scmi_fc_call(protocol_id: SCMI_PROTOCOL_POWERCAP, msg_id: POWERCAP_PAI_GET, |
501 | res_id: domain_id, val1: *pai, val2: 0); |
502 | return 0; |
503 | } |
504 | |
505 | return scmi_powercap_xfer_pai_get(ph, domain_id, pai); |
506 | } |
507 | |
508 | static int scmi_powercap_xfer_pai_set(const struct scmi_protocol_handle *ph, |
509 | u32 domain_id, u32 pai) |
510 | { |
511 | int ret; |
512 | struct scmi_xfer *t; |
513 | struct scmi_msg_powercap_set_cap_or_pai *msg; |
514 | |
515 | ret = ph->xops->xfer_get_init(ph, POWERCAP_PAI_SET, |
516 | sizeof(*msg), 0, &t); |
517 | if (ret) |
518 | return ret; |
519 | |
520 | msg = t->tx.buf; |
521 | msg->domain = cpu_to_le32(domain_id); |
522 | msg->flags = cpu_to_le32(0); |
523 | msg->value = cpu_to_le32(pai); |
524 | |
525 | ret = ph->xops->do_xfer(ph, t); |
526 | |
527 | ph->xops->xfer_put(ph, t); |
528 | return ret; |
529 | } |
530 | |
531 | static int scmi_powercap_pai_set(const struct scmi_protocol_handle *ph, |
532 | u32 domain_id, u32 pai) |
533 | { |
534 | const struct scmi_powercap_info *pc; |
535 | |
536 | pc = scmi_powercap_dom_info_get(ph, domain_id); |
537 | if (!pc || !pc->powercap_pai_config || !pai || |
538 | pai < pc->min_pai || pai > pc->max_pai) |
539 | return -EINVAL; |
540 | |
541 | if (pc->fc_info && pc->fc_info[POWERCAP_FC_PAI].set_addr) { |
542 | struct scmi_fc_info *fci = &pc->fc_info[POWERCAP_FC_PAI]; |
543 | |
544 | trace_scmi_fc_call(protocol_id: SCMI_PROTOCOL_POWERCAP, msg_id: POWERCAP_PAI_SET, |
545 | res_id: domain_id, val1: pai, val2: 0); |
546 | iowrite32(pai, fci->set_addr); |
547 | ph->hops->fastchannel_db_ring(fci->set_db); |
548 | return 0; |
549 | } |
550 | |
551 | return scmi_powercap_xfer_pai_set(ph, domain_id, pai); |
552 | } |
553 | |
554 | static int scmi_powercap_measurements_get(const struct scmi_protocol_handle *ph, |
555 | u32 domain_id, u32 *average_power, |
556 | u32 *pai) |
557 | { |
558 | int ret; |
559 | struct scmi_xfer *t; |
560 | struct scmi_msg_resp_powercap_meas_get *resp; |
561 | const struct scmi_powercap_info *pc; |
562 | |
563 | pc = scmi_powercap_dom_info_get(ph, domain_id); |
564 | if (!pc || !pc->powercap_monitoring || !pai || !average_power) |
565 | return -EINVAL; |
566 | |
567 | ret = ph->xops->xfer_get_init(ph, POWERCAP_MEASUREMENTS_GET, |
568 | sizeof(u32), sizeof(*resp), &t); |
569 | if (ret) |
570 | return ret; |
571 | |
572 | resp = t->rx.buf; |
573 | put_unaligned_le32(val: domain_id, p: t->tx.buf); |
574 | ret = ph->xops->do_xfer(ph, t); |
575 | if (!ret) { |
576 | *average_power = le32_to_cpu(resp->power); |
577 | *pai = le32_to_cpu(resp->pai); |
578 | } |
579 | |
580 | ph->xops->xfer_put(ph, t); |
581 | return ret; |
582 | } |
583 | |
584 | static int |
585 | scmi_powercap_measurements_threshold_get(const struct scmi_protocol_handle *ph, |
586 | u32 domain_id, u32 *power_thresh_low, |
587 | u32 *power_thresh_high) |
588 | { |
589 | struct powercap_info *pi = ph->get_priv(ph); |
590 | |
591 | if (!power_thresh_low || !power_thresh_high || |
592 | domain_id >= pi->num_domains) |
593 | return -EINVAL; |
594 | |
595 | *power_thresh_low = THRESH_LOW(pi, domain_id); |
596 | *power_thresh_high = THRESH_HIGH(pi, domain_id); |
597 | |
598 | return 0; |
599 | } |
600 | |
601 | static int |
602 | scmi_powercap_measurements_threshold_set(const struct scmi_protocol_handle *ph, |
603 | u32 domain_id, u32 power_thresh_low, |
604 | u32 power_thresh_high) |
605 | { |
606 | int ret = 0; |
607 | struct powercap_info *pi = ph->get_priv(ph); |
608 | |
609 | if (domain_id >= pi->num_domains || |
610 | power_thresh_low > power_thresh_high) |
611 | return -EINVAL; |
612 | |
613 | /* Anything to do ? */ |
614 | if (THRESH_LOW(pi, domain_id) == power_thresh_low && |
615 | THRESH_HIGH(pi, domain_id) == power_thresh_high) |
616 | return ret; |
617 | |
618 | pi->states[domain_id].thresholds = |
619 | (FIELD_PREP(GENMASK_ULL(31, 0), power_thresh_low) | |
620 | FIELD_PREP(GENMASK_ULL(63, 32), power_thresh_high)); |
621 | |
622 | /* Update thresholds if notification already enabled */ |
623 | if (pi->states[domain_id].meas_notif_enabled) |
624 | ret = scmi_powercap_notify(ph, domain: domain_id, |
625 | message_id: POWERCAP_MEASUREMENTS_NOTIFY, |
626 | enable: true); |
627 | |
628 | return ret; |
629 | } |
630 | |
631 | static int scmi_powercap_cap_enable_set(const struct scmi_protocol_handle *ph, |
632 | u32 domain_id, bool enable) |
633 | { |
634 | int ret; |
635 | u32 power_cap; |
636 | struct powercap_info *pi = ph->get_priv(ph); |
637 | |
638 | if (PROTOCOL_REV_MAJOR(pi->version) < 0x2) |
639 | return -EINVAL; |
640 | |
641 | if (enable == pi->states[domain_id].enabled) |
642 | return 0; |
643 | |
644 | if (enable) { |
645 | /* Cannot enable with a zero powercap. */ |
646 | if (!pi->states[domain_id].last_pcap) |
647 | return -EINVAL; |
648 | |
649 | ret = __scmi_powercap_cap_set(ph, pi, domain_id, |
650 | power_cap: pi->states[domain_id].last_pcap, |
651 | ignore_dresp: true); |
652 | } else { |
653 | ret = __scmi_powercap_cap_set(ph, pi, domain_id, power_cap: 0, ignore_dresp: true); |
654 | } |
655 | |
656 | if (ret) |
657 | return ret; |
658 | |
659 | /* |
660 | * Update our internal state to reflect final platform state: the SCMI |
661 | * server could have ignored a disable request and kept enforcing some |
662 | * powercap limit requested by other agents. |
663 | */ |
664 | ret = scmi_powercap_cap_get(ph, domain_id, power_cap: &power_cap); |
665 | if (!ret) |
666 | pi->states[domain_id].enabled = !!power_cap; |
667 | |
668 | return ret; |
669 | } |
670 | |
671 | static int scmi_powercap_cap_enable_get(const struct scmi_protocol_handle *ph, |
672 | u32 domain_id, bool *enable) |
673 | { |
674 | int ret; |
675 | u32 power_cap; |
676 | struct powercap_info *pi = ph->get_priv(ph); |
677 | |
678 | *enable = true; |
679 | if (PROTOCOL_REV_MAJOR(pi->version) < 0x2) |
680 | return 0; |
681 | |
682 | /* |
683 | * Report always real platform state; platform could have ignored |
684 | * a previous disable request. Default true on any error. |
685 | */ |
686 | ret = scmi_powercap_cap_get(ph, domain_id, power_cap: &power_cap); |
687 | if (!ret) |
688 | *enable = !!power_cap; |
689 | |
690 | /* Update internal state with current real platform state */ |
691 | pi->states[domain_id].enabled = *enable; |
692 | |
693 | return 0; |
694 | } |
695 | |
696 | static const struct scmi_powercap_proto_ops powercap_proto_ops = { |
697 | .num_domains_get = scmi_powercap_num_domains_get, |
698 | .info_get = scmi_powercap_dom_info_get, |
699 | .cap_get = scmi_powercap_cap_get, |
700 | .cap_set = scmi_powercap_cap_set, |
701 | .cap_enable_set = scmi_powercap_cap_enable_set, |
702 | .cap_enable_get = scmi_powercap_cap_enable_get, |
703 | .pai_get = scmi_powercap_pai_get, |
704 | .pai_set = scmi_powercap_pai_set, |
705 | .measurements_get = scmi_powercap_measurements_get, |
706 | .measurements_threshold_set = scmi_powercap_measurements_threshold_set, |
707 | .measurements_threshold_get = scmi_powercap_measurements_threshold_get, |
708 | }; |
709 | |
710 | static void scmi_powercap_domain_init_fc(const struct scmi_protocol_handle *ph, |
711 | u32 domain, struct scmi_fc_info **p_fc) |
712 | { |
713 | struct scmi_fc_info *fc; |
714 | |
715 | fc = devm_kcalloc(dev: ph->dev, n: POWERCAP_FC_MAX, size: sizeof(*fc), GFP_KERNEL); |
716 | if (!fc) |
717 | return; |
718 | |
719 | ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL, |
720 | POWERCAP_CAP_SET, 4, domain, |
721 | &fc[POWERCAP_FC_CAP].set_addr, |
722 | &fc[POWERCAP_FC_CAP].set_db, |
723 | &fc[POWERCAP_FC_CAP].rate_limit); |
724 | |
725 | ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL, |
726 | POWERCAP_CAP_GET, 4, domain, |
727 | &fc[POWERCAP_FC_CAP].get_addr, NULL, |
728 | &fc[POWERCAP_FC_CAP].rate_limit); |
729 | |
730 | ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL, |
731 | POWERCAP_PAI_SET, 4, domain, |
732 | &fc[POWERCAP_FC_PAI].set_addr, |
733 | &fc[POWERCAP_FC_PAI].set_db, |
734 | &fc[POWERCAP_FC_PAI].rate_limit); |
735 | |
736 | ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL, |
737 | POWERCAP_PAI_GET, 4, domain, |
738 | &fc[POWERCAP_FC_PAI].get_addr, NULL, |
739 | &fc[POWERCAP_FC_PAI].rate_limit); |
740 | |
741 | *p_fc = fc; |
742 | } |
743 | |
744 | static int scmi_powercap_notify(const struct scmi_protocol_handle *ph, |
745 | u32 domain, int message_id, bool enable) |
746 | { |
747 | int ret; |
748 | struct scmi_xfer *t; |
749 | |
750 | switch (message_id) { |
751 | case POWERCAP_CAP_NOTIFY: |
752 | { |
753 | struct scmi_msg_powercap_notify_cap *notify; |
754 | |
755 | ret = ph->xops->xfer_get_init(ph, message_id, |
756 | sizeof(*notify), 0, &t); |
757 | if (ret) |
758 | return ret; |
759 | |
760 | notify = t->tx.buf; |
761 | notify->domain = cpu_to_le32(domain); |
762 | notify->notify_enable = cpu_to_le32(enable ? BIT(0) : 0); |
763 | break; |
764 | } |
765 | case POWERCAP_MEASUREMENTS_NOTIFY: |
766 | { |
767 | u32 low, high; |
768 | struct scmi_msg_powercap_notify_thresh *notify; |
769 | |
770 | /* |
771 | * Note that we have to pick the most recently configured |
772 | * thresholds to build a proper POWERCAP_MEASUREMENTS_NOTIFY |
773 | * enable request and we fail, complaining, if no thresholds |
774 | * were ever set, since this is an indication the API has been |
775 | * used wrongly. |
776 | */ |
777 | ret = scmi_powercap_measurements_threshold_get(ph, domain_id: domain, |
778 | power_thresh_low: &low, power_thresh_high: &high); |
779 | if (ret) |
780 | return ret; |
781 | |
782 | if (enable && !low && !high) { |
783 | dev_err(ph->dev, |
784 | "Invalid Measurements Notify thresholds: %u/%u\n" , |
785 | low, high); |
786 | return -EINVAL; |
787 | } |
788 | |
789 | ret = ph->xops->xfer_get_init(ph, message_id, |
790 | sizeof(*notify), 0, &t); |
791 | if (ret) |
792 | return ret; |
793 | |
794 | notify = t->tx.buf; |
795 | notify->domain = cpu_to_le32(domain); |
796 | notify->notify_enable = cpu_to_le32(enable ? BIT(0) : 0); |
797 | notify->power_thresh_low = cpu_to_le32(low); |
798 | notify->power_thresh_high = cpu_to_le32(high); |
799 | break; |
800 | } |
801 | default: |
802 | return -EINVAL; |
803 | } |
804 | |
805 | ret = ph->xops->do_xfer(ph, t); |
806 | |
807 | ph->xops->xfer_put(ph, t); |
808 | return ret; |
809 | } |
810 | |
811 | static bool |
812 | scmi_powercap_notify_supported(const struct scmi_protocol_handle *ph, |
813 | u8 evt_id, u32 src_id) |
814 | { |
815 | bool supported = false; |
816 | const struct scmi_powercap_info *dom_info; |
817 | struct powercap_info *pi = ph->get_priv(ph); |
818 | |
819 | if (evt_id >= ARRAY_SIZE(evt_2_cmd) || src_id >= pi->num_domains) |
820 | return false; |
821 | |
822 | dom_info = pi->powercaps + src_id; |
823 | if (evt_id == SCMI_EVENT_POWERCAP_CAP_CHANGED) |
824 | supported = dom_info->notify_powercap_cap_change; |
825 | else if (evt_id == SCMI_EVENT_POWERCAP_MEASUREMENTS_CHANGED) |
826 | supported = dom_info->notify_powercap_measurement_change; |
827 | |
828 | return supported; |
829 | } |
830 | |
831 | static int |
832 | scmi_powercap_set_notify_enabled(const struct scmi_protocol_handle *ph, |
833 | u8 evt_id, u32 src_id, bool enable) |
834 | { |
835 | int ret, cmd_id; |
836 | struct powercap_info *pi = ph->get_priv(ph); |
837 | |
838 | if (evt_id >= ARRAY_SIZE(evt_2_cmd) || src_id >= pi->num_domains) |
839 | return -EINVAL; |
840 | |
841 | cmd_id = evt_2_cmd[evt_id]; |
842 | ret = scmi_powercap_notify(ph, domain: src_id, message_id: cmd_id, enable); |
843 | if (ret) |
844 | pr_debug("FAIL_ENABLED - evt[%X] dom[%d] - ret:%d\n" , |
845 | evt_id, src_id, ret); |
846 | else if (cmd_id == POWERCAP_MEASUREMENTS_NOTIFY) |
847 | /* |
848 | * On success save the current notification enabled state, so |
849 | * as to be able to properly update the notification thresholds |
850 | * when they are modified on a domain for which measurement |
851 | * notifications were currently enabled. |
852 | * |
853 | * This is needed because the SCMI Notification core machinery |
854 | * and API does not support passing per-notification custom |
855 | * arguments at callback registration time. |
856 | * |
857 | * Note that this can be done here with a simple flag since the |
858 | * SCMI core Notifications code takes care of keeping proper |
859 | * per-domain enables refcounting, so that this helper function |
860 | * will be called only once (for enables) when the first user |
861 | * registers a callback on this domain and once more (disable) |
862 | * when the last user de-registers its callback. |
863 | */ |
864 | pi->states[src_id].meas_notif_enabled = enable; |
865 | |
866 | return ret; |
867 | } |
868 | |
869 | static void * |
870 | scmi_powercap_fill_custom_report(const struct scmi_protocol_handle *ph, |
871 | u8 evt_id, ktime_t timestamp, |
872 | const void *payld, size_t payld_sz, |
873 | void *report, u32 *src_id) |
874 | { |
875 | void *rep = NULL; |
876 | |
877 | switch (evt_id) { |
878 | case SCMI_EVENT_POWERCAP_CAP_CHANGED: |
879 | { |
880 | const struct scmi_powercap_cap_changed_notify_payld *p = payld; |
881 | struct scmi_powercap_cap_changed_report *r = report; |
882 | |
883 | if (sizeof(*p) != payld_sz) |
884 | break; |
885 | |
886 | r->timestamp = timestamp; |
887 | r->agent_id = le32_to_cpu(p->agent_id); |
888 | r->domain_id = le32_to_cpu(p->domain_id); |
889 | r->power_cap = le32_to_cpu(p->power_cap); |
890 | r->pai = le32_to_cpu(p->pai); |
891 | *src_id = r->domain_id; |
892 | rep = r; |
893 | break; |
894 | } |
895 | case SCMI_EVENT_POWERCAP_MEASUREMENTS_CHANGED: |
896 | { |
897 | const struct scmi_powercap_meas_changed_notify_payld *p = payld; |
898 | struct scmi_powercap_meas_changed_report *r = report; |
899 | |
900 | if (sizeof(*p) != payld_sz) |
901 | break; |
902 | |
903 | r->timestamp = timestamp; |
904 | r->agent_id = le32_to_cpu(p->agent_id); |
905 | r->domain_id = le32_to_cpu(p->domain_id); |
906 | r->power = le32_to_cpu(p->power); |
907 | *src_id = r->domain_id; |
908 | rep = r; |
909 | break; |
910 | } |
911 | default: |
912 | break; |
913 | } |
914 | |
915 | return rep; |
916 | } |
917 | |
918 | static int |
919 | scmi_powercap_get_num_sources(const struct scmi_protocol_handle *ph) |
920 | { |
921 | struct powercap_info *pi = ph->get_priv(ph); |
922 | |
923 | if (!pi) |
924 | return -EINVAL; |
925 | |
926 | return pi->num_domains; |
927 | } |
928 | |
929 | static const struct scmi_event powercap_events[] = { |
930 | { |
931 | .id = SCMI_EVENT_POWERCAP_CAP_CHANGED, |
932 | .max_payld_sz = |
933 | sizeof(struct scmi_powercap_cap_changed_notify_payld), |
934 | .max_report_sz = |
935 | sizeof(struct scmi_powercap_cap_changed_report), |
936 | }, |
937 | { |
938 | .id = SCMI_EVENT_POWERCAP_MEASUREMENTS_CHANGED, |
939 | .max_payld_sz = |
940 | sizeof(struct scmi_powercap_meas_changed_notify_payld), |
941 | .max_report_sz = |
942 | sizeof(struct scmi_powercap_meas_changed_report), |
943 | }, |
944 | }; |
945 | |
946 | static const struct scmi_event_ops powercap_event_ops = { |
947 | .is_notify_supported = scmi_powercap_notify_supported, |
948 | .get_num_sources = scmi_powercap_get_num_sources, |
949 | .set_notify_enabled = scmi_powercap_set_notify_enabled, |
950 | .fill_custom_report = scmi_powercap_fill_custom_report, |
951 | }; |
952 | |
953 | static const struct scmi_protocol_events powercap_protocol_events = { |
954 | .queue_sz = SCMI_PROTO_QUEUE_SZ, |
955 | .ops = &powercap_event_ops, |
956 | .evts = powercap_events, |
957 | .num_events = ARRAY_SIZE(powercap_events), |
958 | }; |
959 | |
960 | static int |
961 | scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph) |
962 | { |
963 | int domain, ret; |
964 | u32 version; |
965 | struct powercap_info *pinfo; |
966 | |
967 | ret = ph->xops->version_get(ph, &version); |
968 | if (ret) |
969 | return ret; |
970 | |
971 | dev_dbg(ph->dev, "Powercap Version %d.%d\n" , |
972 | PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version)); |
973 | |
974 | pinfo = devm_kzalloc(dev: ph->dev, size: sizeof(*pinfo), GFP_KERNEL); |
975 | if (!pinfo) |
976 | return -ENOMEM; |
977 | |
978 | ret = scmi_powercap_attributes_get(ph, pi: pinfo); |
979 | if (ret) |
980 | return ret; |
981 | |
982 | pinfo->powercaps = devm_kcalloc(dev: ph->dev, n: pinfo->num_domains, |
983 | size: sizeof(*pinfo->powercaps), |
984 | GFP_KERNEL); |
985 | if (!pinfo->powercaps) |
986 | return -ENOMEM; |
987 | |
988 | pinfo->states = devm_kcalloc(dev: ph->dev, n: pinfo->num_domains, |
989 | size: sizeof(*pinfo->states), GFP_KERNEL); |
990 | if (!pinfo->states) |
991 | return -ENOMEM; |
992 | |
993 | /* |
994 | * Note that any failure in retrieving any domain attribute leads to |
995 | * the whole Powercap protocol initialization failure: this way the |
996 | * reported Powercap domains are all assured, when accessed, to be well |
997 | * formed and correlated by sane parent-child relationship (if any). |
998 | */ |
999 | for (domain = 0; domain < pinfo->num_domains; domain++) { |
1000 | ret = scmi_powercap_domain_attributes_get(ph, pinfo, domain); |
1001 | if (ret) |
1002 | return ret; |
1003 | |
1004 | if (pinfo->powercaps[domain].fastchannels) |
1005 | scmi_powercap_domain_init_fc(ph, domain, |
1006 | p_fc: &pinfo->powercaps[domain].fc_info); |
1007 | |
1008 | /* Grab initial state when disable is supported. */ |
1009 | if (PROTOCOL_REV_MAJOR(version) >= 0x2) { |
1010 | ret = __scmi_powercap_cap_get(ph, |
1011 | dom: &pinfo->powercaps[domain], |
1012 | power_cap: &pinfo->states[domain].last_pcap); |
1013 | if (ret) |
1014 | return ret; |
1015 | |
1016 | pinfo->states[domain].enabled = |
1017 | !!pinfo->states[domain].last_pcap; |
1018 | } |
1019 | } |
1020 | |
1021 | pinfo->version = version; |
1022 | return ph->set_priv(ph, pinfo, version); |
1023 | } |
1024 | |
1025 | static const struct scmi_protocol scmi_powercap = { |
1026 | .id = SCMI_PROTOCOL_POWERCAP, |
1027 | .owner = THIS_MODULE, |
1028 | .instance_init = &scmi_powercap_protocol_init, |
1029 | .ops = &powercap_proto_ops, |
1030 | .events = &powercap_protocol_events, |
1031 | .supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION, |
1032 | }; |
1033 | |
1034 | DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(powercap, scmi_powercap) |
1035 | |