root/drivers/firmware/arm_scmi/powercap.c
// 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 &&
            !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 (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;

        /*
         * 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)