1035 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * System Control and Management Interface (SCMI) Powercap Protocol4 *5 * Copyright (C) 2022 ARM Ltd.6 */7 8#define pr_fmt(fmt) "SCMI Notifications POWERCAP - " fmt9 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 0x2000022 23enum 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 36enum {37 POWERCAP_FC_CAP,38 POWERCAP_FC_PAI,39 POWERCAP_FC_MAX,40};41 42struct 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 70struct 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 78struct scmi_msg_resp_powercap_cap_set_complete {79 __le32 domain;80 __le32 power_cap;81};82 83struct scmi_msg_resp_powercap_meas_get {84 __le32 power;85 __le32 pai;86};87 88struct scmi_msg_powercap_notify_cap {89 __le32 domain;90 __le32 notify_enable;91};92 93struct scmi_msg_powercap_notify_thresh {94 __le32 domain;95 __le32 notify_enable;96 __le32 power_thresh_low;97 __le32 power_thresh_high;98};99 100struct scmi_powercap_cap_changed_notify_payld {101 __le32 agent_id;102 __le32 domain_id;103 __le32 power_cap;104 __le32 pai;105};106 107struct scmi_powercap_meas_changed_notify_payld {108 __le32 agent_id;109 __le32 domain_id;110 __le32 power;111};112 113struct 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 124struct 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 133static enum scmi_powercap_protocol_cmd evt_2_cmd[] = {134 POWERCAP_CAP_NOTIFY,135 POWERCAP_MEASUREMENTS_NOTIFY,136};137 138static int scmi_powercap_notify(const struct scmi_protocol_handle *ph,139 u32 domain, int message_id, bool enable);140 141static int142scmi_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(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 177static inline int178scmi_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 194static int195scmi_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(domain, 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(dom_info->min_pai,244 dom_info->max_pai,245 dom_info->pai_step,246 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(dom_info->min_power_cap,258 dom_info->max_power_cap,259 dom_info->power_cap_step,260 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 283clean: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 298static 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 305static const struct scmi_powercap_info *306scmi_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 316static 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(domain_id, t->tx.buf);328 ret = ph->xops->do_xfer(ph, t);329 if (!ret)330 *power_cap = get_unaligned_le32(t->rx.buf);331 332 ph->xops->xfer_put(ph, t);333 334 return ret;335}336 337static 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(SCMI_PROTOCOL_POWERCAP, POWERCAP_CAP_GET,344 dom->id, *power_cap, 0);345 return 0;346 }347 348 return scmi_powercap_xfer_cap_get(ph, dom->id, power_cap);349}350 351static 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 366static 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 else400 ret = -EPROTO;401 }402 }403 404 ph->xops->xfer_put(ph, t);405 return ret;406}407 408static 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(SCMI_PROTOCOL_POWERCAP, POWERCAP_CAP_SET,429 domain_id, power_cap, 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 443static 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 467static 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(domain_id, t->tx.buf);479 ret = ph->xops->do_xfer(ph, t);480 if (!ret)481 *pai = get_unaligned_le32(t->rx.buf);482 483 ph->xops->xfer_put(ph, t);484 485 return ret;486}487 488static 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(SCMI_PROTOCOL_POWERCAP, POWERCAP_PAI_GET,501 domain_id, *pai, 0);502 return 0;503 }504 505 return scmi_powercap_xfer_pai_get(ph, domain_id, pai);506}507 508static 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 531static 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(SCMI_PROTOCOL_POWERCAP, POWERCAP_PAI_SET,545 domain_id, pai, 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 554static 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(domain_id, 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 584static int585scmi_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 601static int602scmi_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_id,625 POWERCAP_MEASUREMENTS_NOTIFY,626 true);627 628 return ret;629}630 631static 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 pi->states[domain_id].last_pcap,651 true);652 } else {653 ret = __scmi_powercap_cap_set(ph, pi, domain_id, 0, true);654 }655 656 if (ret)657 return ret;658 659 /*660 * Update our internal state to reflect final platform state: the SCMI661 * server could have ignored a disable request and kept enforcing some662 * powercap limit requested by other agents.663 */664 ret = scmi_powercap_cap_get(ph, domain_id, &power_cap);665 if (!ret)666 pi->states[domain_id].enabled = !!power_cap;667 668 return ret;669}670 671static 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 ignored684 * a previous disable request. Default true on any error.685 */686 ret = scmi_powercap_cap_get(ph, domain_id, &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 696static 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 710static 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(ph->dev, POWERCAP_FC_MAX, 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 744static 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 configured772 * thresholds to build a proper POWERCAP_MEASUREMENTS_NOTIFY773 * enable request and we fail, complaining, if no thresholds774 * were ever set, since this is an indication the API has been775 * used wrongly.776 */777 ret = scmi_powercap_measurements_threshold_get(ph, domain,778 &low, &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 811static bool812scmi_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 831static int832scmi_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, src_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, so849 * as to be able to properly update the notification thresholds850 * when they are modified on a domain for which measurement851 * notifications were currently enabled.852 *853 * This is needed because the SCMI Notification core machinery854 * and API does not support passing per-notification custom855 * arguments at callback registration time.856 *857 * Note that this can be done here with a simple flag since the858 * SCMI core Notifications code takes care of keeping proper859 * per-domain enables refcounting, so that this helper function860 * will be called only once (for enables) when the first user861 * 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 869static void *870scmi_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 918static int919scmi_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 929static 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 946static 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 953static 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 960static int961scmi_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(ph->dev, sizeof(*pinfo), GFP_KERNEL);975 if (!pinfo)976 return -ENOMEM;977 978 ret = scmi_powercap_attributes_get(ph, pinfo);979 if (ret)980 return ret;981 982 pinfo->powercaps = devm_kcalloc(ph->dev, pinfo->num_domains,983 sizeof(*pinfo->powercaps),984 GFP_KERNEL);985 if (!pinfo->powercaps)986 return -ENOMEM;987 988 pinfo->states = devm_kcalloc(ph->dev, pinfo->num_domains,989 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 to995 * the whole Powercap protocol initialization failure: this way the996 * reported Powercap domains are all assured, when accessed, to be well997 * 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 &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 &pinfo->powercaps[domain],1012 &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 1025static 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 1034DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(powercap, scmi_powercap)1035