root/drivers/firmware/arm_scmi/voltage.c
// SPDX-License-Identifier: GPL-2.0
/*
 * System Control and Management Interface (SCMI) Voltage Protocol
 *
 * Copyright (C) 2020-2022 ARM Ltd.
 */

#include <linux/module.h>
#include <linux/scmi_protocol.h>

#include "protocols.h"

/* Updated only after ALL the mandatory features for that version are merged */
#define SCMI_PROTOCOL_SUPPORTED_VERSION         0x20001

#define VOLTAGE_DOMS_NUM_MASK           GENMASK(15, 0)
#define REMAINING_LEVELS_MASK           GENMASK(31, 16)
#define RETURNED_LEVELS_MASK            GENMASK(11, 0)

enum scmi_voltage_protocol_cmd {
        VOLTAGE_DOMAIN_ATTRIBUTES = 0x3,
        VOLTAGE_DESCRIBE_LEVELS = 0x4,
        VOLTAGE_CONFIG_SET = 0x5,
        VOLTAGE_CONFIG_GET = 0x6,
        VOLTAGE_LEVEL_SET = 0x7,
        VOLTAGE_LEVEL_GET = 0x8,
        VOLTAGE_DOMAIN_NAME_GET = 0x09,
};

#define NUM_VOLTAGE_DOMAINS(x)  ((u16)(FIELD_GET(VOLTAGE_DOMS_NUM_MASK, (x))))

struct scmi_msg_resp_domain_attributes {
        __le32 attr;
#define SUPPORTS_ASYNC_LEVEL_SET(x)     ((x) & BIT(31))
#define SUPPORTS_EXTENDED_NAMES(x)      ((x) & BIT(30))
        u8 name[SCMI_SHORT_NAME_MAX_SIZE];
};

struct scmi_msg_cmd_describe_levels {
        __le32 domain_id;
        __le32 level_index;
};

struct scmi_msg_resp_describe_levels {
        __le32 flags;
#define NUM_REMAINING_LEVELS(f) ((u16)(FIELD_GET(REMAINING_LEVELS_MASK, (f))))
#define NUM_RETURNED_LEVELS(f)  ((u16)(FIELD_GET(RETURNED_LEVELS_MASK, (f))))
#define SUPPORTS_SEGMENTED_LEVELS(f)    ((f) & BIT(12))
        __le32 voltage[];
};

struct scmi_msg_cmd_config_set {
        __le32 domain_id;
        __le32 config;
};

struct scmi_msg_cmd_level_set {
        __le32 domain_id;
        __le32 flags;
        __le32 voltage_level;
};

struct scmi_resp_voltage_level_set_complete {
        __le32 domain_id;
        __le32 voltage_level;
};

struct voltage_info {
        unsigned int num_domains;
        struct scmi_voltage_info *domains;
};

static int scmi_protocol_attributes_get(const struct scmi_protocol_handle *ph,
                                        struct voltage_info *vinfo)
{
        int ret;
        struct scmi_xfer *t;

        ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0,
                                      sizeof(__le32), &t);
        if (ret)
                return ret;

        ret = ph->xops->do_xfer(ph, t);
        if (!ret)
                vinfo->num_domains =
                        NUM_VOLTAGE_DOMAINS(get_unaligned_le32(t->rx.buf));

        ph->xops->xfer_put(ph, t);
        return ret;
}

static int scmi_init_voltage_levels(struct device *dev,
                                    struct scmi_voltage_info *v,
                                    u32 num_returned, u32 num_remaining,
                                    bool segmented)
{
        u32 num_levels;

        num_levels = num_returned + num_remaining;
        /*
         * segmented levels entries are represented by a single triplet
         * returned all in one go.
         */
        if (!num_levels ||
            (segmented && (num_remaining || num_returned != 3))) {
                dev_err(dev,
                        "Invalid level descriptor(%d/%d/%d) for voltage dom %d\n",
                        num_levels, num_returned, num_remaining, v->id);
                return -EINVAL;
        }

        v->levels_uv = devm_kcalloc(dev, num_levels, sizeof(u32), GFP_KERNEL);
        if (!v->levels_uv)
                return -ENOMEM;

        v->num_levels = num_levels;
        v->segmented = segmented;

        return 0;
}

struct scmi_volt_ipriv {
        struct device *dev;
        struct scmi_voltage_info *v;
};

static void iter_volt_levels_prepare_message(void *message,
                                             unsigned int desc_index,
                                             const void *priv)
{
        struct scmi_msg_cmd_describe_levels *msg = message;
        const struct scmi_volt_ipriv *p = priv;

        msg->domain_id = cpu_to_le32(p->v->id);
        msg->level_index = cpu_to_le32(desc_index);
}

static int iter_volt_levels_update_state(struct scmi_iterator_state *st,
                                         const void *response, void *priv)
{
        int ret = 0;
        u32 flags;
        const struct scmi_msg_resp_describe_levels *r = response;
        struct scmi_volt_ipriv *p = priv;

        flags = le32_to_cpu(r->flags);
        st->num_returned = NUM_RETURNED_LEVELS(flags);
        st->num_remaining = NUM_REMAINING_LEVELS(flags);

        /* Allocate space for num_levels if not already done */
        if (!p->v->num_levels) {
                ret = scmi_init_voltage_levels(p->dev, p->v, st->num_returned,
                                               st->num_remaining,
                                              SUPPORTS_SEGMENTED_LEVELS(flags));
                if (!ret)
                        st->max_resources = p->v->num_levels;
        }

        return ret;
}

static int
iter_volt_levels_process_response(const struct scmi_protocol_handle *ph,
                                  const void *response,
                                  struct scmi_iterator_state *st, void *priv)
{
        s32 val;
        const struct scmi_msg_resp_describe_levels *r = response;
        struct scmi_volt_ipriv *p = priv;

        val = (s32)le32_to_cpu(r->voltage[st->loop_idx]);
        p->v->levels_uv[st->desc_index + st->loop_idx] = val;
        if (val < 0)
                p->v->negative_volts_allowed = true;

        return 0;
}

static int scmi_voltage_levels_get(const struct scmi_protocol_handle *ph,
                                   struct scmi_voltage_info *v)
{
        int ret;
        void *iter;
        struct scmi_iterator_ops ops = {
                .prepare_message = iter_volt_levels_prepare_message,
                .update_state = iter_volt_levels_update_state,
                .process_response = iter_volt_levels_process_response,
        };
        struct scmi_volt_ipriv vpriv = {
                .dev = ph->dev,
                .v = v,
        };

        iter = ph->hops->iter_response_init(ph, &ops, v->num_levels,
                                            VOLTAGE_DESCRIBE_LEVELS,
                                            sizeof(struct scmi_msg_cmd_describe_levels),
                                            &vpriv);
        if (IS_ERR(iter))
                return PTR_ERR(iter);

        ret = ph->hops->iter_response_run(iter);
        if (ret) {
                v->num_levels = 0;
                devm_kfree(ph->dev, v->levels_uv);
        }

        return ret;
}

static int scmi_voltage_descriptors_get(const struct scmi_protocol_handle *ph,
                                        struct voltage_info *vinfo)
{
        int ret, dom;
        struct scmi_xfer *td;
        struct scmi_msg_resp_domain_attributes *resp_dom;

        ret = ph->xops->xfer_get_init(ph, VOLTAGE_DOMAIN_ATTRIBUTES,
                                      sizeof(__le32), sizeof(*resp_dom), &td);
        if (ret)
                return ret;
        resp_dom = td->rx.buf;

        for (dom = 0; dom < vinfo->num_domains; dom++) {
                u32 attributes;
                struct scmi_voltage_info *v;

                /* Retrieve domain attributes at first ... */
                put_unaligned_le32(dom, td->tx.buf);
                /* Skip domain on comms error */
                if (ph->xops->do_xfer(ph, td)) {
                        ph->xops->reset_rx_to_maxsz(ph, td);
                        continue;
                }

                v = vinfo->domains + dom;
                v->id = dom;
                attributes = le32_to_cpu(resp_dom->attr);
                strscpy(v->name, resp_dom->name, SCMI_SHORT_NAME_MAX_SIZE);

                /*
                 * If supported overwrite short name with the extended one;
                 * on error just carry on and use already provided short name.
                 */
                if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2) {
                        if (SUPPORTS_EXTENDED_NAMES(attributes))
                                ph->hops->extended_name_get(ph,
                                                        VOLTAGE_DOMAIN_NAME_GET,
                                                        v->id, NULL, v->name,
                                                        SCMI_MAX_STR_SIZE);
                        if (SUPPORTS_ASYNC_LEVEL_SET(attributes))
                                v->async_level_set = true;
                }

                /* Skip invalid voltage descriptors */
                scmi_voltage_levels_get(ph, v);
        }

        ph->xops->xfer_put(ph, td);

        return ret;
}

static int __scmi_voltage_get_u32(const struct scmi_protocol_handle *ph,
                                  u8 cmd_id, u32 domain_id, u32 *value)
{
        int ret;
        struct scmi_xfer *t;
        struct voltage_info *vinfo = ph->get_priv(ph);

        if (domain_id >= vinfo->num_domains)
                return -EINVAL;

        ret = ph->xops->xfer_get_init(ph, cmd_id, sizeof(__le32), 0, &t);
        if (ret)
                return ret;

        put_unaligned_le32(domain_id, t->tx.buf);
        ret = ph->xops->do_xfer(ph, t);
        if (!ret)
                *value = get_unaligned_le32(t->rx.buf);

        ph->xops->xfer_put(ph, t);
        return ret;
}

static int scmi_voltage_config_set(const struct scmi_protocol_handle *ph,
                                   u32 domain_id, u32 config)
{
        int ret;
        struct scmi_xfer *t;
        struct voltage_info *vinfo = ph->get_priv(ph);
        struct scmi_msg_cmd_config_set *cmd;

        if (domain_id >= vinfo->num_domains)
                return -EINVAL;

        ret = ph->xops->xfer_get_init(ph, VOLTAGE_CONFIG_SET,
                                     sizeof(*cmd), 0, &t);
        if (ret)
                return ret;

        cmd = t->tx.buf;
        cmd->domain_id = cpu_to_le32(domain_id);
        cmd->config = cpu_to_le32(config & GENMASK(3, 0));

        ret = ph->xops->do_xfer(ph, t);

        ph->xops->xfer_put(ph, t);
        return ret;
}

static int scmi_voltage_config_get(const struct scmi_protocol_handle *ph,
                                   u32 domain_id, u32 *config)
{
        return __scmi_voltage_get_u32(ph, VOLTAGE_CONFIG_GET,
                                      domain_id, config);
}

static int scmi_voltage_level_set(const struct scmi_protocol_handle *ph,
                                  u32 domain_id,
                                  enum scmi_voltage_level_mode mode,
                                  s32 volt_uV)
{
        int ret;
        struct scmi_xfer *t;
        struct voltage_info *vinfo = ph->get_priv(ph);
        struct scmi_msg_cmd_level_set *cmd;
        struct scmi_voltage_info *v;

        if (domain_id >= vinfo->num_domains)
                return -EINVAL;

        ret = ph->xops->xfer_get_init(ph, VOLTAGE_LEVEL_SET,
                                      sizeof(*cmd), 0, &t);
        if (ret)
                return ret;

        v = vinfo->domains + domain_id;

        cmd = t->tx.buf;
        cmd->domain_id = cpu_to_le32(domain_id);
        cmd->voltage_level = cpu_to_le32(volt_uV);

        if (!v->async_level_set || mode != SCMI_VOLTAGE_LEVEL_SET_AUTO) {
                cmd->flags = cpu_to_le32(0x0);
                ret = ph->xops->do_xfer(ph, t);
        } else {
                cmd->flags = cpu_to_le32(0x1);
                ret = ph->xops->do_xfer_with_response(ph, t);
                if (!ret) {
                        struct scmi_resp_voltage_level_set_complete *resp;

                        resp = t->rx.buf;
                        if (le32_to_cpu(resp->domain_id) == domain_id)
                                dev_dbg(ph->dev,
                                        "Voltage domain %d set async to %d\n",
                                        v->id,
                                        le32_to_cpu(resp->voltage_level));
                        else
                                ret = -EPROTO;
                }
        }

        ph->xops->xfer_put(ph, t);
        return ret;
}

static int scmi_voltage_level_get(const struct scmi_protocol_handle *ph,
                                  u32 domain_id, s32 *volt_uV)
{
        return __scmi_voltage_get_u32(ph, VOLTAGE_LEVEL_GET,
                                      domain_id, (u32 *)volt_uV);
}

static const struct scmi_voltage_info * __must_check
scmi_voltage_info_get(const struct scmi_protocol_handle *ph, u32 domain_id)
{
        struct voltage_info *vinfo = ph->get_priv(ph);

        if (domain_id >= vinfo->num_domains ||
            !vinfo->domains[domain_id].num_levels)
                return NULL;

        return vinfo->domains + domain_id;
}

static int scmi_voltage_domains_num_get(const struct scmi_protocol_handle *ph)
{
        struct voltage_info *vinfo = ph->get_priv(ph);

        return vinfo->num_domains;
}

static const struct scmi_voltage_proto_ops voltage_proto_ops = {
        .num_domains_get = scmi_voltage_domains_num_get,
        .info_get = scmi_voltage_info_get,
        .config_set = scmi_voltage_config_set,
        .config_get = scmi_voltage_config_get,
        .level_set = scmi_voltage_level_set,
        .level_get = scmi_voltage_level_get,
};

static int scmi_voltage_protocol_init(const struct scmi_protocol_handle *ph)
{
        int ret;
        struct voltage_info *vinfo;

        dev_dbg(ph->dev, "Voltage Version %d.%d\n",
                PROTOCOL_REV_MAJOR(ph->version), PROTOCOL_REV_MINOR(ph->version));

        vinfo = devm_kzalloc(ph->dev, sizeof(*vinfo), GFP_KERNEL);
        if (!vinfo)
                return -ENOMEM;

        ret = scmi_protocol_attributes_get(ph, vinfo);
        if (ret)
                return ret;

        if (vinfo->num_domains) {
                vinfo->domains = devm_kcalloc(ph->dev, vinfo->num_domains,
                                              sizeof(*vinfo->domains),
                                              GFP_KERNEL);
                if (!vinfo->domains)
                        return -ENOMEM;
                ret = scmi_voltage_descriptors_get(ph, vinfo);
                if (ret)
                        return ret;
        } else {
                dev_warn(ph->dev, "No Voltage domains found.\n");
        }

        return ph->set_priv(ph, vinfo);
}

static const struct scmi_protocol scmi_voltage = {
        .id = SCMI_PROTOCOL_VOLTAGE,
        .owner = THIS_MODULE,
        .instance_init = &scmi_voltage_protocol_init,
        .ops = &voltage_proto_ops,
        .supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
};

DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(voltage, scmi_voltage)