| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * System Control and Management Interface (SCMI) Powercap Protocol | |
| * | |
| * Copyright (C) 2022 ARM Ltd. | |
| */ | |
| #define pr_fmt(fmt) "SCMI Notifications POWERCAP - " fmt | |
| #include <linux/bitfield.h> | |
| #include <linux/io.h> | |
| #include <linux/module.h> | |
| #include <linux/scmi_protocol.h> | |
| #include <trace/events/scmi.h> | |
| #include "protocols.h" | |
| #include "notify.h" | |
| /* Updated only after ALL the mandatory features for that version are merged */ | |
| #define SCMI_PROTOCOL_SUPPORTED_VERSION 0x20000 | |
| enum scmi_powercap_protocol_cmd { | |
| POWERCAP_DOMAIN_ATTRIBUTES = 0x3, | |
| POWERCAP_CAP_GET = 0x4, | |
| POWERCAP_CAP_SET = 0x5, | |
| POWERCAP_PAI_GET = 0x6, | |
| POWERCAP_PAI_SET = 0x7, | |
| POWERCAP_DOMAIN_NAME_GET = 0x8, | |
| POWERCAP_MEASUREMENTS_GET = 0x9, | |
| POWERCAP_CAP_NOTIFY = 0xa, | |
| POWERCAP_MEASUREMENTS_NOTIFY = 0xb, | |
| POWERCAP_DESCRIBE_FASTCHANNEL = 0xc, | |
| }; | |
| enum { | |
| POWERCAP_FC_CAP, | |
| POWERCAP_FC_PAI, | |
| POWERCAP_FC_MAX, | |
| }; | |
| struct scmi_msg_resp_powercap_domain_attributes { | |
| __le32 attributes; | |
| #define SUPPORTS_POWERCAP_CAP_CHANGE_NOTIFY(x) ((x) & BIT(31)) | |
| #define SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(x) ((x) & BIT(30)) | |
| #define SUPPORTS_ASYNC_POWERCAP_CAP_SET(x) ((x) & BIT(29)) | |
| #define SUPPORTS_EXTENDED_NAMES(x) ((x) & BIT(28)) | |
| #define SUPPORTS_POWERCAP_CAP_CONFIGURATION(x) ((x) & BIT(27)) | |
| #define SUPPORTS_POWERCAP_MONITORING(x) ((x) & BIT(26)) | |
| #define SUPPORTS_POWERCAP_PAI_CONFIGURATION(x) ((x) & BIT(25)) | |
| #define SUPPORTS_POWERCAP_FASTCHANNELS(x) ((x) & BIT(22)) | |
| #define POWERCAP_POWER_UNIT(x) \ | |
| (FIELD_GET(GENMASK(24, 23), (x))) | |
| #define SUPPORTS_POWER_UNITS_MW(x) \ | |
| (POWERCAP_POWER_UNIT(x) == 0x2) | |
| #define SUPPORTS_POWER_UNITS_UW(x) \ | |
| (POWERCAP_POWER_UNIT(x) == 0x1) | |
| u8 name[SCMI_SHORT_NAME_MAX_SIZE]; | |
| __le32 min_pai; | |
| __le32 max_pai; | |
| __le32 pai_step; | |
| __le32 min_power_cap; | |
| __le32 max_power_cap; | |
| __le32 power_cap_step; | |
| __le32 sustainable_power; | |
| __le32 accuracy; | |
| __le32 parent_id; | |
| }; | |
| struct scmi_msg_powercap_set_cap_or_pai { | |
| __le32 domain; | |
| __le32 flags; | |
| #define CAP_SET_ASYNC BIT(1) | |
| #define CAP_SET_IGNORE_DRESP BIT(0) | |
| __le32 value; | |
| }; | |
| struct scmi_msg_resp_powercap_cap_set_complete { | |
| __le32 domain; | |
| __le32 power_cap; | |
| }; | |
| struct scmi_msg_resp_powercap_meas_get { | |
| __le32 power; | |
| __le32 pai; | |
| }; | |
| struct scmi_msg_powercap_notify_cap { | |
| __le32 domain; | |
| __le32 notify_enable; | |
| }; | |
| struct scmi_msg_powercap_notify_thresh { | |
| __le32 domain; | |
| __le32 notify_enable; | |
| __le32 power_thresh_low; | |
| __le32 power_thresh_high; | |
| }; | |
| struct scmi_powercap_cap_changed_notify_payld { | |
| __le32 agent_id; | |
| __le32 domain_id; | |
| __le32 power_cap; | |
| __le32 pai; | |
| }; | |
| struct scmi_powercap_meas_changed_notify_payld { | |
| __le32 agent_id; | |
| __le32 domain_id; | |
| __le32 power; | |
| }; | |
| struct scmi_powercap_state { | |
| bool enabled; | |
| u32 last_pcap; | |
| bool meas_notif_enabled; | |
| u64 thresholds; | |
| #define THRESH_LOW(p, id) \ | |
| (lower_32_bits((p)->states[(id)].thresholds)) | |
| #define THRESH_HIGH(p, id) \ | |
| (upper_32_bits((p)->states[(id)].thresholds)) | |
| }; | |
| struct powercap_info { | |
| int num_domains; | |
| bool notify_cap_cmd; | |
| bool notify_measurements_cmd; | |
| struct scmi_powercap_state *states; | |
| struct scmi_powercap_info *powercaps; | |
| }; | |
| static enum scmi_powercap_protocol_cmd evt_2_cmd[] = { | |
| POWERCAP_CAP_NOTIFY, | |
| POWERCAP_MEASUREMENTS_NOTIFY, | |
| }; | |
| static int scmi_powercap_notify(const struct scmi_protocol_handle *ph, | |
| u32 domain, int message_id, bool enable); | |
| static int | |
| scmi_powercap_attributes_get(const struct scmi_protocol_handle *ph, | |
| struct powercap_info *pi) | |
| { | |
| int ret; | |
| struct scmi_xfer *t; | |
| ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0, | |
| sizeof(u32), &t); | |
| if (ret) | |
| return ret; | |
| ret = ph->xops->do_xfer(ph, t); | |
| if (!ret) { | |
| u32 attributes; | |
| attributes = get_unaligned_le32(t->rx.buf); | |
| pi->num_domains = FIELD_GET(GENMASK(15, 0), attributes); | |
| } | |
| ph->xops->xfer_put(ph, t); | |
| if (!ret) { | |
| if (!ph->hops->protocol_msg_check(ph, | |
| POWERCAP_CAP_NOTIFY, NULL)) | |
| pi->notify_cap_cmd = true; | |
| if (!ph->hops->protocol_msg_check(ph, | |
| POWERCAP_MEASUREMENTS_NOTIFY, | |
| NULL)) | |
| pi->notify_measurements_cmd = true; | |
| } | |
| return ret; | |
| } | |
| static inline int | |
| scmi_powercap_validate(unsigned int min_val, unsigned int max_val, | |
| unsigned int step_val, bool configurable) | |
| { | |
| if (!min_val || !max_val) | |
| return -EPROTO; | |
| if ((configurable && min_val == max_val) || | |
| (!configurable && min_val != max_val)) | |
| return -EPROTO; | |
| if (min_val != max_val && !step_val) | |
| return -EPROTO; | |
| return 0; | |
| } | |
| static int | |
| scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph, | |
| struct powercap_info *pinfo, u32 domain) | |
| { | |
| int ret; | |
| u32 flags; | |
| struct scmi_xfer *t; | |
| struct scmi_powercap_info *dom_info = pinfo->powercaps + domain; | |
| struct scmi_msg_resp_powercap_domain_attributes *resp; | |
| ret = ph->xops->xfer_get_init(ph, POWERCAP_DOMAIN_ATTRIBUTES, | |
| sizeof(domain), sizeof(*resp), &t); | |
| if (ret) | |
| return ret; | |
| put_unaligned_le32(domain, t->tx.buf); | |
| resp = t->rx.buf; | |
| ret = ph->xops->do_xfer(ph, t); | |
| if (!ret) { | |
| flags = le32_to_cpu(resp->attributes); | |
| dom_info->id = domain; | |
| if (pinfo->notify_cap_cmd) | |
| dom_info->notify_powercap_cap_change = | |
| SUPPORTS_POWERCAP_CAP_CHANGE_NOTIFY(flags); | |
| if (pinfo->notify_measurements_cmd) | |
| dom_info->notify_powercap_measurement_change = | |
| SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(flags); | |
| dom_info->async_powercap_cap_set = | |
| SUPPORTS_ASYNC_POWERCAP_CAP_SET(flags); | |
| dom_info->powercap_cap_config = | |
| SUPPORTS_POWERCAP_CAP_CONFIGURATION(flags); | |
| dom_info->powercap_monitoring = | |
| SUPPORTS_POWERCAP_MONITORING(flags); | |
| dom_info->powercap_pai_config = | |
| SUPPORTS_POWERCAP_PAI_CONFIGURATION(flags); | |
| dom_info->powercap_scale_mw = | |
| SUPPORTS_POWER_UNITS_MW(flags); | |
| dom_info->powercap_scale_uw = | |
| SUPPORTS_POWER_UNITS_UW(flags); | |
| dom_info->fastchannels = | |
| SUPPORTS_POWERCAP_FASTCHANNELS(flags); | |
| strscpy(dom_info->name, resp->name, SCMI_SHORT_NAME_MAX_SIZE); | |
| dom_info->min_pai = le32_to_cpu(resp->min_pai); | |
| dom_info->max_pai = le32_to_cpu(resp->max_pai); | |
| dom_info->pai_step = le32_to_cpu(resp->pai_step); | |
| ret = scmi_powercap_validate(dom_info->min_pai, | |
| dom_info->max_pai, | |
| dom_info->pai_step, | |
| dom_info->powercap_pai_config); | |
| if (ret) { | |
| dev_err(ph->dev, | |
| "Platform reported inconsistent PAI config for domain %d - %s\n", | |
| dom_info->id, dom_info->name); | |
| goto clean; | |
| } | |
| dom_info->min_power_cap = le32_to_cpu(resp->min_power_cap); | |
| dom_info->max_power_cap = le32_to_cpu(resp->max_power_cap); | |
| dom_info->power_cap_step = le32_to_cpu(resp->power_cap_step); | |
| ret = scmi_powercap_validate(dom_info->min_power_cap, | |
| dom_info->max_power_cap, | |
| dom_info->power_cap_step, | |
| dom_info->powercap_cap_config); | |
| if (ret) { | |
| dev_err(ph->dev, | |
| "Platform reported inconsistent CAP config for domain %d - %s\n", | |
| dom_info->id, dom_info->name); | |
| goto clean; | |
| } | |
| dom_info->sustainable_power = | |
| le32_to_cpu(resp->sustainable_power); | |
| dom_info->accuracy = le32_to_cpu(resp->accuracy); | |
| dom_info->parent_id = le32_to_cpu(resp->parent_id); | |
| if (dom_info->parent_id != SCMI_POWERCAP_ROOT_ZONE_ID && | |
| (dom_info->parent_id >= pinfo->num_domains || | |
| dom_info->parent_id == dom_info->id)) { | |
| dev_err(ph->dev, | |
| "Platform reported inconsistent parent ID for domain %d - %s\n", | |
| dom_info->id, dom_info->name); | |
| ret = -ENODEV; | |
| } | |
| } | |
| clean: | |
| ph->xops->xfer_put(ph, t); | |
| /* | |
| * If supported overwrite short name with the extended one; | |
| * on error just carry on and use already provided short name. | |
| */ | |
| if (!ret && SUPPORTS_EXTENDED_NAMES(flags)) | |
| ph->hops->extended_name_get(ph, POWERCAP_DOMAIN_NAME_GET, | |
| domain, NULL, dom_info->name, | |
| SCMI_MAX_STR_SIZE); | |
| return ret; | |
| } | |
| static int scmi_powercap_num_domains_get(const struct scmi_protocol_handle *ph) | |
| { | |
| struct powercap_info *pi = ph->get_priv(ph); | |
| return pi->num_domains; | |
| } | |
| static const struct scmi_powercap_info * | |
| scmi_powercap_dom_info_get(const struct scmi_protocol_handle *ph, u32 domain_id) | |
| { | |
| struct powercap_info *pi = ph->get_priv(ph); | |
| if (domain_id >= pi->num_domains) | |
| return NULL; | |
| return pi->powercaps + domain_id; | |
| } | |
| static int scmi_powercap_xfer_cap_get(const struct scmi_protocol_handle *ph, | |
| u32 domain_id, u32 *power_cap) | |
| { | |
| int ret; | |
| struct scmi_xfer *t; | |
| ret = ph->xops->xfer_get_init(ph, POWERCAP_CAP_GET, sizeof(u32), | |
| sizeof(u32), &t); | |
| if (ret) | |
| return ret; | |
| put_unaligned_le32(domain_id, t->tx.buf); | |
| ret = ph->xops->do_xfer(ph, t); | |
| if (!ret) | |
| *power_cap = get_unaligned_le32(t->rx.buf); | |
| ph->xops->xfer_put(ph, t); | |
| return ret; | |
| } | |
| static int __scmi_powercap_cap_get(const struct scmi_protocol_handle *ph, | |
| const struct scmi_powercap_info *dom, | |
| u32 *power_cap) | |
| { | |
| if (dom->fc_info && dom->fc_info[POWERCAP_FC_CAP].get_addr) { | |
| *power_cap = ioread32(dom->fc_info[POWERCAP_FC_CAP].get_addr); | |
| trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_CAP_GET, | |
| dom->id, *power_cap, 0); | |
| return 0; | |
| } | |
| return scmi_powercap_xfer_cap_get(ph, dom->id, power_cap); | |
| } | |
| static int scmi_powercap_cap_get(const struct scmi_protocol_handle *ph, | |
| u32 domain_id, u32 *power_cap) | |
| { | |
| const struct scmi_powercap_info *dom; | |
| if (!power_cap) | |
| return -EINVAL; | |
| dom = scmi_powercap_dom_info_get(ph, domain_id); | |
| if (!dom) | |
| return -EINVAL; | |
| return __scmi_powercap_cap_get(ph, dom, power_cap); | |
| } | |
| static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph, | |
| const struct scmi_powercap_info *pc, | |
| u32 power_cap, bool ignore_dresp) | |
| { | |
| int ret; | |
| struct scmi_xfer *t; | |
| struct scmi_msg_powercap_set_cap_or_pai *msg; | |
| ret = ph->xops->xfer_get_init(ph, POWERCAP_CAP_SET, | |
| sizeof(*msg), 0, &t); | |
| if (ret) | |
| return ret; | |
| msg = t->tx.buf; | |
| msg->domain = cpu_to_le32(pc->id); | |
| msg->flags = | |
| cpu_to_le32(FIELD_PREP(CAP_SET_ASYNC, pc->async_powercap_cap_set) | | |
| FIELD_PREP(CAP_SET_IGNORE_DRESP, ignore_dresp)); | |
| msg->value = cpu_to_le32(power_cap); | |
| if (!pc->async_powercap_cap_set || ignore_dresp) { | |
| ret = ph->xops->do_xfer(ph, t); | |
| } else { | |
| ret = ph->xops->do_xfer_with_response(ph, t); | |
| if (!ret) { | |
| struct scmi_msg_resp_powercap_cap_set_complete *resp; | |
| resp = t->rx.buf; | |
| if (le32_to_cpu(resp->domain) == pc->id) | |
| dev_dbg(ph->dev, | |
| "Powercap ID %d CAP set async to %u\n", | |
| pc->id, | |
| get_unaligned_le32(&resp->power_cap)); | |
| else | |
| ret = -EPROTO; | |
| } | |
| } | |
| ph->xops->xfer_put(ph, t); | |
| return ret; | |
| } | |
| static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph, | |
| struct powercap_info *pi, u32 domain_id, | |
| u32 power_cap, bool ignore_dresp) | |
| { | |
| int ret = -EINVAL; | |
| const struct scmi_powercap_info *pc; | |
| pc = scmi_powercap_dom_info_get(ph, domain_id); | |
| if (!pc || !pc->powercap_cap_config) | |
| return ret; | |
| if (power_cap && | |
| (power_cap < pc->min_power_cap || power_cap > pc->max_power_cap)) | |
| return ret; | |
| if (pc->fc_info && pc->fc_info[POWERCAP_FC_CAP].set_addr) { | |
| struct scmi_fc_info *fci = &pc->fc_info[POWERCAP_FC_CAP]; | |
| iowrite32(power_cap, fci->set_addr); | |
| ph->hops->fastchannel_db_ring(fci->set_db); | |
| trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_CAP_SET, | |
| domain_id, power_cap, 0); | |
| ret = 0; | |
| } else { | |
| ret = scmi_powercap_xfer_cap_set(ph, pc, power_cap, | |
| ignore_dresp); | |
| } | |
| /* Save the last explicitly set non-zero powercap value */ | |
| if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2 && !ret && power_cap) | |
| pi->states[domain_id].last_pcap = power_cap; | |
| return ret; | |
| } | |
| static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph, | |
| u32 domain_id, u32 power_cap, | |
| bool ignore_dresp) | |
| { | |
| struct powercap_info *pi = ph->get_priv(ph); | |
| /* | |
| * Disallow zero as a possible explicitly requested powercap: | |
| * there are enable/disable operations for this. | |
| */ | |
| if (!power_cap) | |
| return -EINVAL; | |
| /* Just log the last set request if acting on a disabled domain */ | |
| if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2) { | |
| if (!scmi_powercap_dom_info_get(ph, domain_id)) | |
| return -EINVAL; | |
| if (!pi->states[domain_id].enabled) { | |
| pi->states[domain_id].last_pcap = power_cap; | |
| return 0; | |
| } | |
| } | |
| return __scmi_powercap_cap_set(ph, pi, domain_id, | |
| power_cap, ignore_dresp); | |
| } | |
| static int scmi_powercap_xfer_pai_get(const struct scmi_protocol_handle *ph, | |
| u32 domain_id, u32 *pai) | |
| { | |
| int ret; | |
| struct scmi_xfer *t; | |
| ret = ph->xops->xfer_get_init(ph, POWERCAP_PAI_GET, sizeof(u32), | |
| sizeof(u32), &t); | |
| if (ret) | |
| return ret; | |
| put_unaligned_le32(domain_id, t->tx.buf); | |
| ret = ph->xops->do_xfer(ph, t); | |
| if (!ret) | |
| *pai = get_unaligned_le32(t->rx.buf); | |
| ph->xops->xfer_put(ph, t); | |
| return ret; | |
| } | |
| static int scmi_powercap_pai_get(const struct scmi_protocol_handle *ph, | |
| u32 domain_id, u32 *pai) | |
| { | |
| struct scmi_powercap_info *dom; | |
| struct powercap_info *pi = ph->get_priv(ph); | |
| if (!pai || domain_id >= pi->num_domains) | |
| return -EINVAL; | |
| dom = pi->powercaps + domain_id; | |
| if (dom->fc_info && dom->fc_info[POWERCAP_FC_PAI].get_addr) { | |
| *pai = ioread32(dom->fc_info[POWERCAP_FC_PAI].get_addr); | |
| trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_PAI_GET, | |
| domain_id, *pai, 0); | |
| return 0; | |
| } | |
| return scmi_powercap_xfer_pai_get(ph, domain_id, pai); | |
| } | |
| static int scmi_powercap_xfer_pai_set(const struct scmi_protocol_handle *ph, | |
| u32 domain_id, u32 pai) | |
| { | |
| int ret; | |
| struct scmi_xfer *t; | |
| struct scmi_msg_powercap_set_cap_or_pai *msg; | |
| ret = ph->xops->xfer_get_init(ph, POWERCAP_PAI_SET, | |
| sizeof(*msg), 0, &t); | |
| if (ret) | |
| return ret; | |
| msg = t->tx.buf; | |
| msg->domain = cpu_to_le32(domain_id); | |
| msg->flags = cpu_to_le32(0); | |
| msg->value = cpu_to_le32(pai); | |
| ret = ph->xops->do_xfer(ph, t); | |
| ph->xops->xfer_put(ph, t); | |
| return ret; | |
| } | |
| static int scmi_powercap_pai_set(const struct scmi_protocol_handle *ph, | |
| u32 domain_id, u32 pai) | |
| { | |
| const struct scmi_powercap_info *pc; | |
| pc = scmi_powercap_dom_info_get(ph, domain_id); | |
| if (!pc || !pc->powercap_pai_config || !pai || | |
| pai < pc->min_pai || pai > pc->max_pai) | |
| return -EINVAL; | |
| if (pc->fc_info && pc->fc_info[POWERCAP_FC_PAI].set_addr) { | |
| struct scmi_fc_info *fci = &pc->fc_info[POWERCAP_FC_PAI]; | |
| trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_PAI_SET, | |
| domain_id, pai, 0); | |
| iowrite32(pai, fci->set_addr); | |
| ph->hops->fastchannel_db_ring(fci->set_db); | |
| return 0; | |
| } | |
| return scmi_powercap_xfer_pai_set(ph, domain_id, pai); | |
| } | |
| static int scmi_powercap_measurements_get(const struct scmi_protocol_handle *ph, | |
| u32 domain_id, u32 *average_power, | |
| u32 *pai) | |
| { | |
| int ret; | |
| struct scmi_xfer *t; | |
| struct scmi_msg_resp_powercap_meas_get *resp; | |
| const struct scmi_powercap_info *pc; | |
| pc = scmi_powercap_dom_info_get(ph, domain_id); | |
| if (!pc || !pc->powercap_monitoring || !pai || !average_power) | |
| return -EINVAL; | |
| ret = ph->xops->xfer_get_init(ph, POWERCAP_MEASUREMENTS_GET, | |
| sizeof(u32), sizeof(*resp), &t); | |
| if (ret) | |
| return ret; | |
| resp = t->rx.buf; | |
| put_unaligned_le32(domain_id, t->tx.buf); | |
| ret = ph->xops->do_xfer(ph, t); | |
| if (!ret) { | |
| *average_power = le32_to_cpu(resp->power); | |
| *pai = le32_to_cpu(resp->pai); | |
| } | |
| ph->xops->xfer_put(ph, t); | |
| return ret; | |
| } | |
| static int | |
| scmi_powercap_measurements_threshold_get(const struct scmi_protocol_handle *ph, | |
| u32 domain_id, u32 *power_thresh_low, | |
| u32 *power_thresh_high) | |
| { | |
| struct powercap_info *pi = ph->get_priv(ph); | |
| if (!power_thresh_low || !power_thresh_high || | |
| domain_id >= pi->num_domains) | |
| return -EINVAL; | |
| *power_thresh_low = THRESH_LOW(pi, domain_id); | |
| *power_thresh_high = THRESH_HIGH(pi, domain_id); | |
| return 0; | |
| } | |
| static int | |
| scmi_powercap_measurements_threshold_set(const struct scmi_protocol_handle *ph, | |
| u32 domain_id, u32 power_thresh_low, | |
| u32 power_thresh_high) | |
| { | |
| int ret = 0; | |
| struct powercap_info *pi = ph->get_priv(ph); | |
| if (domain_id >= pi->num_domains || | |
| power_thresh_low > power_thresh_high) | |
| return -EINVAL; | |
| /* Anything to do ? */ | |
| if (THRESH_LOW(pi, domain_id) == power_thresh_low && | |
| THRESH_HIGH(pi, domain_id) == power_thresh_high) | |
| return ret; | |
| pi->states[domain_id].thresholds = | |
| (FIELD_PREP(GENMASK_ULL(31, 0), power_thresh_low) | | |
| FIELD_PREP(GENMASK_ULL(63, 32), power_thresh_high)); | |
| /* Update thresholds if notification already enabled */ | |
| if (pi->states[domain_id].meas_notif_enabled) | |
| ret = scmi_powercap_notify(ph, domain_id, | |
| POWERCAP_MEASUREMENTS_NOTIFY, | |
| true); | |
| return ret; | |
| } | |
| static int scmi_powercap_cap_enable_set(const struct scmi_protocol_handle *ph, | |
| u32 domain_id, bool enable) | |
| { | |
| int ret; | |
| u32 power_cap; | |
| struct powercap_info *pi = ph->get_priv(ph); | |
| if (PROTOCOL_REV_MAJOR(ph->version) < 0x2) | |
| return -EINVAL; | |
| if (!scmi_powercap_dom_info_get(ph, domain_id)) | |
| return -EINVAL; | |
| if (enable == pi->states[domain_id].enabled) | |
| return 0; | |
| if (enable) { | |
| /* Cannot enable with a zero powercap. */ | |
| if (!pi->states[domain_id].last_pcap) | |
| return -EINVAL; | |
| ret = __scmi_powercap_cap_set(ph, pi, domain_id, | |
| pi->states[domain_id].last_pcap, | |
| true); | |
| } else { | |
| ret = __scmi_powercap_cap_set(ph, pi, domain_id, 0, true); | |
| } | |
| if (ret) | |
| return ret; | |
| /* | |
| * Update our internal state to reflect final platform state: the SCMI | |
| * server could have ignored a disable request and kept enforcing some | |
| * powercap limit requested by other agents. | |
| */ | |
| ret = scmi_powercap_cap_get(ph, domain_id, &power_cap); | |
| if (!ret) | |
| pi->states[domain_id].enabled = !!power_cap; | |
| return ret; | |
| } | |
| static int scmi_powercap_cap_enable_get(const struct scmi_protocol_handle *ph, | |
| u32 domain_id, bool *enable) | |
| { | |
| int ret; | |
| u32 power_cap; | |
| struct powercap_info *pi = ph->get_priv(ph); | |
| *enable = true; | |
| if (PROTOCOL_REV_MAJOR(ph->version) < 0x2) | |
| return 0; | |
| if (!scmi_powercap_dom_info_get(ph, domain_id)) | |
| return -EINVAL; | |
| /* | |
| * Report always real platform state; platform could have ignored | |
| * a previous disable request. Default true on any error. | |
| */ | |
| ret = scmi_powercap_cap_get(ph, domain_id, &power_cap); | |
| if (!ret) | |
| *enable = !!power_cap; | |
| /* Update internal state with current real platform state */ | |
| pi->states[domain_id].enabled = *enable; | |
| return 0; | |
| } | |
| static const struct scmi_powercap_proto_ops powercap_proto_ops = { | |
| .num_domains_get = scmi_powercap_num_domains_get, | |
| .info_get = scmi_powercap_dom_info_get, | |
| .cap_get = scmi_powercap_cap_get, | |
| .cap_set = scmi_powercap_cap_set, | |
| .cap_enable_set = scmi_powercap_cap_enable_set, | |
| .cap_enable_get = scmi_powercap_cap_enable_get, | |
| .pai_get = scmi_powercap_pai_get, | |
| .pai_set = scmi_powercap_pai_set, | |
| .measurements_get = scmi_powercap_measurements_get, | |
| .measurements_threshold_set = scmi_powercap_measurements_threshold_set, | |
| .measurements_threshold_get = scmi_powercap_measurements_threshold_get, | |
| }; | |
| static void scmi_powercap_domain_init_fc(const struct scmi_protocol_handle *ph, | |
| u32 domain, struct scmi_fc_info **p_fc) | |
| { | |
| struct scmi_fc_info *fc; | |
| fc = devm_kcalloc(ph->dev, POWERCAP_FC_MAX, sizeof(*fc), GFP_KERNEL); | |
| if (!fc) | |
| return; | |
| ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL, | |
| POWERCAP_CAP_SET, 4, domain, | |
| &fc[POWERCAP_FC_CAP].set_addr, | |
| &fc[POWERCAP_FC_CAP].set_db, | |
| &fc[POWERCAP_FC_CAP].rate_limit); | |
| ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL, | |
| POWERCAP_CAP_GET, 4, domain, | |
| &fc[POWERCAP_FC_CAP].get_addr, NULL, | |
| &fc[POWERCAP_FC_CAP].rate_limit); | |
| ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL, | |
| POWERCAP_PAI_SET, 4, domain, | |
| &fc[POWERCAP_FC_PAI].set_addr, | |
| &fc[POWERCAP_FC_PAI].set_db, | |
| &fc[POWERCAP_FC_PAI].rate_limit); | |
| ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL, | |
| POWERCAP_PAI_GET, 4, domain, | |
| &fc[POWERCAP_FC_PAI].get_addr, NULL, | |
| &fc[POWERCAP_FC_PAI].rate_limit); | |
| *p_fc = fc; | |
| } | |
| static int scmi_powercap_notify(const struct scmi_protocol_handle *ph, | |
| u32 domain, int message_id, bool enable) | |
| { | |
| int ret; | |
| struct scmi_xfer *t; | |
| switch (message_id) { | |
| case POWERCAP_CAP_NOTIFY: | |
| { | |
| struct scmi_msg_powercap_notify_cap *notify; | |
| ret = ph->xops->xfer_get_init(ph, message_id, | |
| sizeof(*notify), 0, &t); | |
| if (ret) | |
| return ret; | |
| notify = t->tx.buf; | |
| notify->domain = cpu_to_le32(domain); | |
| notify->notify_enable = cpu_to_le32(enable ? BIT(0) : 0); | |
| break; | |
| } | |
| case POWERCAP_MEASUREMENTS_NOTIFY: | |
| { | |
| u32 low, high; | |
| struct scmi_msg_powercap_notify_thresh *notify; | |
| /* | |
| * Note that we have to pick the most recently configured | |
| * thresholds to build a proper POWERCAP_MEASUREMENTS_NOTIFY | |
| * enable request and we fail, complaining, if no thresholds | |
| * were ever set, since this is an indication the API has been | |
| * used wrongly. | |
| */ | |
| ret = scmi_powercap_measurements_threshold_get(ph, domain, | |
| &low, &high); | |
| if (ret) | |
| return ret; | |
| if (enable && !low && !high) { | |
| dev_err(ph->dev, | |
| "Invalid Measurements Notify thresholds: %u/%u\n", | |
| low, high); | |
| return -EINVAL; | |
| } | |
| ret = ph->xops->xfer_get_init(ph, message_id, | |
| sizeof(*notify), 0, &t); | |
| if (ret) | |
| return ret; | |
| notify = t->tx.buf; | |
| notify->domain = cpu_to_le32(domain); | |
| notify->notify_enable = cpu_to_le32(enable ? BIT(0) : 0); | |
| notify->power_thresh_low = cpu_to_le32(low); | |
| notify->power_thresh_high = cpu_to_le32(high); | |
| break; | |
| } | |
| default: | |
| return -EINVAL; | |
| } | |
| ret = ph->xops->do_xfer(ph, t); | |
| ph->xops->xfer_put(ph, t); | |
| return ret; | |
| } | |
| static bool | |
| scmi_powercap_notify_supported(const struct scmi_protocol_handle *ph, | |
| u8 evt_id, u32 src_id) | |
| { | |
| bool supported = false; | |
| const struct scmi_powercap_info *dom_info; | |
| struct powercap_info *pi = ph->get_priv(ph); | |
| if (evt_id >= ARRAY_SIZE(evt_2_cmd) || src_id >= pi->num_domains) | |
| return false; | |
| dom_info = pi->powercaps + src_id; | |
| if (evt_id == SCMI_EVENT_POWERCAP_CAP_CHANGED) | |
| supported = dom_info->notify_powercap_cap_change; | |
| else if (evt_id == SCMI_EVENT_POWERCAP_MEASUREMENTS_CHANGED) | |
| supported = dom_info->notify_powercap_measurement_change; | |
| return supported; | |
| } | |
| static int | |
| scmi_powercap_set_notify_enabled(const struct scmi_protocol_handle *ph, | |
| u8 evt_id, u32 src_id, bool enable) | |
| { | |
| int ret, cmd_id; | |
| struct powercap_info *pi = ph->get_priv(ph); | |
| if (evt_id >= ARRAY_SIZE(evt_2_cmd) || src_id >= pi->num_domains) | |
| return -EINVAL; | |
| cmd_id = evt_2_cmd[evt_id]; | |
| ret = scmi_powercap_notify(ph, src_id, cmd_id, enable); | |
| if (ret) | |
| pr_debug("FAIL_ENABLED - evt[%X] dom[%d] - ret:%d\n", | |
| evt_id, src_id, ret); | |
| else if (cmd_id == POWERCAP_MEASUREMENTS_NOTIFY) | |
| /* | |
| * On success save the current notification enabled state, so | |
| * as to be able to properly update the notification thresholds | |
| * when they are modified on a domain for which measurement | |
| * notifications were currently enabled. | |
| * | |
| * This is needed because the SCMI Notification core machinery | |
| * and API does not support passing per-notification custom | |
| * arguments at callback registration time. | |
| * | |
| * Note that this can be done here with a simple flag since the | |
| * SCMI core Notifications code takes care of keeping proper | |
| * per-domain enables refcounting, so that this helper function | |
| * will be called only once (for enables) when the first user | |
| * registers a callback on this domain and once more (disable) | |
| * when the last user de-registers its callback. | |
| */ | |
| pi->states[src_id].meas_notif_enabled = enable; | |
| return ret; | |
| } | |
| static void * | |
| scmi_powercap_fill_custom_report(const struct scmi_protocol_handle *ph, | |
| u8 evt_id, ktime_t timestamp, | |
| const void *payld, size_t payld_sz, | |
| void *report, u32 *src_id) | |
| { | |
| void *rep = NULL; | |
| switch (evt_id) { | |
| case SCMI_EVENT_POWERCAP_CAP_CHANGED: | |
| { | |
| const struct scmi_powercap_cap_changed_notify_payld *p = payld; | |
| struct scmi_powercap_cap_changed_report *r = report; | |
| if (sizeof(*p) != payld_sz) | |
| break; | |
| r->timestamp = timestamp; | |
| r->agent_id = le32_to_cpu(p->agent_id); | |
| r->domain_id = le32_to_cpu(p->domain_id); | |
| r->power_cap = le32_to_cpu(p->power_cap); | |
| r->pai = le32_to_cpu(p->pai); | |
| *src_id = r->domain_id; | |
| rep = r; | |
| break; | |
| } | |
| case SCMI_EVENT_POWERCAP_MEASUREMENTS_CHANGED: | |
| { | |
| const struct scmi_powercap_meas_changed_notify_payld *p = payld; | |
| struct scmi_powercap_meas_changed_report *r = report; | |
| if (sizeof(*p) != payld_sz) | |
| break; | |
| r->timestamp = timestamp; | |
| r->agent_id = le32_to_cpu(p->agent_id); | |
| r->domain_id = le32_to_cpu(p->domain_id); | |
| r->power = le32_to_cpu(p->power); | |
| *src_id = r->domain_id; | |
| rep = r; | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| return rep; | |
| } | |
| static int | |
| scmi_powercap_get_num_sources(const struct scmi_protocol_handle *ph) | |
| { | |
| struct powercap_info *pi = ph->get_priv(ph); | |
| if (!pi) | |
| return -EINVAL; | |
| return pi->num_domains; | |
| } | |
| static const struct scmi_event powercap_events[] = { | |
| { | |
| .id = SCMI_EVENT_POWERCAP_CAP_CHANGED, | |
| .max_payld_sz = | |
| sizeof(struct scmi_powercap_cap_changed_notify_payld), | |
| .max_report_sz = | |
| sizeof(struct scmi_powercap_cap_changed_report), | |
| }, | |
| { | |
| .id = SCMI_EVENT_POWERCAP_MEASUREMENTS_CHANGED, | |
| .max_payld_sz = | |
| sizeof(struct scmi_powercap_meas_changed_notify_payld), | |
| .max_report_sz = | |
| sizeof(struct scmi_powercap_meas_changed_report), | |
| }, | |
| }; | |
| static const struct scmi_event_ops powercap_event_ops = { | |
| .is_notify_supported = scmi_powercap_notify_supported, | |
| .get_num_sources = scmi_powercap_get_num_sources, | |
| .set_notify_enabled = scmi_powercap_set_notify_enabled, | |
| .fill_custom_report = scmi_powercap_fill_custom_report, | |
| }; | |
| static const struct scmi_protocol_events powercap_protocol_events = { | |
| .queue_sz = SCMI_PROTO_QUEUE_SZ, | |
| .ops = &powercap_event_ops, | |
| .evts = powercap_events, | |
| .num_events = ARRAY_SIZE(powercap_events), | |
| }; | |
| static int | |
| scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph) | |
| { | |
| int domain, ret; | |
| struct powercap_info *pinfo; | |
| dev_dbg(ph->dev, "Powercap Version %d.%d\n", | |
| PROTOCOL_REV_MAJOR(ph->version), PROTOCOL_REV_MINOR(ph->version)); | |
| pinfo = devm_kzalloc(ph->dev, sizeof(*pinfo), GFP_KERNEL); | |
| if (!pinfo) | |
| return -ENOMEM; | |
| ret = scmi_powercap_attributes_get(ph, pinfo); | |
| if (ret) | |
| return ret; | |
| pinfo->powercaps = devm_kcalloc(ph->dev, pinfo->num_domains, | |
| sizeof(*pinfo->powercaps), | |
| GFP_KERNEL); | |
| if (!pinfo->powercaps) | |
| return -ENOMEM; | |
| pinfo->states = devm_kcalloc(ph->dev, pinfo->num_domains, | |
| sizeof(*pinfo->states), GFP_KERNEL); | |
| if (!pinfo->states) | |
| return -ENOMEM; | |
| /* | |
| * Note that any failure in retrieving any domain attribute leads to | |
| * the whole Powercap protocol initialization failure: this way the | |
| * reported Powercap domains are all assured, when accessed, to be well | |
| * formed and correlated by sane parent-child relationship (if any). | |
| */ | |
| for (domain = 0; domain < pinfo->num_domains; domain++) { | |
| ret = scmi_powercap_domain_attributes_get(ph, pinfo, domain); | |
| if (ret) | |
| return ret; | |
| if (pinfo->powercaps[domain].fastchannels) | |
| scmi_powercap_domain_init_fc(ph, domain, | |
| &pinfo->powercaps[domain].fc_info); | |
| /* Grab initial state when disable is supported. */ | |
| if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2) { | |
| ret = __scmi_powercap_cap_get(ph, | |
| &pinfo->powercaps[domain], | |
| &pinfo->states[domain].last_pcap); | |
| if (ret) | |
| return ret; | |
| pinfo->states[domain].enabled = | |
| !!pinfo->states[domain].last_pcap; | |
| } | |
| } | |
| return ph->set_priv(ph, pinfo); | |
| } | |
| static const struct scmi_protocol scmi_powercap = { | |
| .id = SCMI_PROTOCOL_POWERCAP, | |
| .owner = THIS_MODULE, | |
| .instance_init = &scmi_powercap_protocol_init, | |
| .ops = &powercap_proto_ops, | |
| .events = &powercap_protocol_events, | |
| .supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION, | |
| }; | |
| DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(powercap, scmi_powercap) |