// SPDX-License-Identifier: GPL-2.0
/*
* System Control and Management Interface (SCMI) Performance Protocol
*
* Copyright (C) 2018-2023 ARM Ltd.
*/
#define pr_fmt(fmt) "SCMI Notifications PERF - " fmt
#include <linux/bits.h>
#include <linux/hashtable.h>
#include <linux/io.h>
#include <linux/log2.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
#include <linux/scmi_protocol.h>
#include <linux/sort.h>
#include <linux/xarray.h>
#include <trace/events/scmi.h>
#include "protocols.h"
#include "notify.h"
#define MAX_OPPS 16
enum scmi_performance_protocol_cmd {
PERF_DOMAIN_ATTRIBUTES = 0x3,
PERF_DESCRIBE_LEVELS = 0x4,
PERF_LIMITS_SET = 0x5,
PERF_LIMITS_GET = 0x6,
PERF_LEVEL_SET = 0x7,
PERF_LEVEL_GET = 0x8,
PERF_NOTIFY_LIMITS = 0x9,
PERF_NOTIFY_LEVEL = 0xa,
PERF_DESCRIBE_FASTCHANNEL = 0xb,
PERF_DOMAIN_NAME_GET = 0xc,
};
enum {
PERF_FC_LEVEL,
PERF_FC_LIMIT,
PERF_FC_MAX,
};
struct scmi_opp {
u32 perf;
u32 power;
u32 trans_latency_us;
u32 indicative_freq;
u32 level_index;
struct hlist_node hash;
};
struct scmi_msg_resp_perf_attributes {
__le16 num_domains;
__le16 flags;
#define POWER_SCALE_IN_MILLIWATT(x) ((x) & BIT(0))
#define POWER_SCALE_IN_MICROWATT(x) ((x) & BIT(1))
__le32 stats_addr_low;
__le32 stats_addr_high;
__le32 stats_size;
};
struct scmi_msg_resp_perf_domain_attributes {
__le32 flags;
#define SUPPORTS_SET_LIMITS(x) ((x) & BIT(31))
#define SUPPORTS_SET_PERF_LVL(x) ((x) & BIT(30))
#define SUPPORTS_PERF_LIMIT_NOTIFY(x) ((x) & BIT(29))
#define SUPPORTS_PERF_LEVEL_NOTIFY(x) ((x) & BIT(28))
#define SUPPORTS_PERF_FASTCHANNELS(x) ((x) & BIT(27))
#define SUPPORTS_EXTENDED_NAMES(x) ((x) & BIT(26))
#define SUPPORTS_LEVEL_INDEXING(x) ((x) & BIT(25))
__le32 rate_limit_us;
__le32 sustained_freq_khz;
__le32 sustained_perf_level;
u8 name[SCMI_SHORT_NAME_MAX_SIZE];
};
struct scmi_msg_perf_describe_levels {
__le32 domain;
__le32 level_index;
};
struct scmi_perf_set_limits {
__le32 domain;
__le32 max_level;
__le32 min_level;
};
struct scmi_perf_get_limits {
__le32 max_level;
__le32 min_level;
};
struct scmi_perf_set_level {
__le32 domain;
__le32 level;
};
struct scmi_perf_notify_level_or_limits {
__le32 domain;
__le32 notify_enable;
};
struct scmi_perf_limits_notify_payld {
__le32 agent_id;
__le32 domain_id;
__le32 range_max;
__le32 range_min;
};
struct scmi_perf_level_notify_payld {
__le32 agent_id;
__le32 domain_id;
__le32 performance_level;
};
struct scmi_msg_resp_perf_describe_levels {
__le16 num_returned;
__le16 num_remaining;
struct {
__le32 perf_val;
__le32 power;
__le16 transition_latency_us;
__le16 reserved;
} opp[];
};
struct scmi_msg_resp_perf_describe_levels_v4 {
__le16 num_returned;
__le16 num_remaining;
struct {
__le32 perf_val;
__le32 power;
__le16 transition_latency_us;
__le16 reserved;
__le32 indicative_freq;
__le32 level_index;
} opp[];
};
struct perf_dom_info {
u32 id;
bool set_limits;
bool perf_limit_notify;
bool perf_level_notify;
bool perf_fastchannels;
bool level_indexing_mode;
u32 opp_count;
u32 sustained_freq_khz;
u32 sustained_perf_level;
u32 mult_factor;
struct scmi_perf_domain_info info;
struct scmi_opp opp[MAX_OPPS];
struct scmi_fc_info *fc_info;
struct xarray opps_by_idx;
struct xarray opps_by_lvl;
DECLARE_HASHTABLE(opps_by_freq, ilog2(MAX_OPPS));
};
#define LOOKUP_BY_FREQ(__htp, __freq) \
({ \
/* u32 cast is needed to pick right hash func */ \
u32 f_ = (u32)(__freq); \
struct scmi_opp *_opp; \
\
hash_for_each_possible((__htp), _opp, hash, f_) \
if (_opp->indicative_freq == f_) \
break; \
_opp; \
})
struct scmi_perf_info {
u32 version;
u16 num_domains;
enum scmi_power_scale power_scale;
u64 stats_addr;
u32 stats_size;
struct perf_dom_info *dom_info;
};
static enum scmi_performance_protocol_cmd evt_2_cmd[] = {
PERF_NOTIFY_LIMITS,
PERF_NOTIFY_LEVEL,
};
static int scmi_perf_attributes_get(const struct scmi_protocol_handle *ph,
struct scmi_perf_info *pi)
{
int ret;
struct scmi_xfer *t;
struct scmi_msg_resp_perf_attributes *attr;
ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0,
sizeof(*attr), &t);
if (ret)
return ret;
attr = t->rx.buf;
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
u16 flags = le16_to_cpu(attr->flags);
pi->num_domains = le16_to_cpu(attr->num_domains);
if (POWER_SCALE_IN_MILLIWATT(flags))
pi->power_scale = SCMI_POWER_MILLIWATTS;
if (PROTOCOL_REV_MAJOR(pi->version) >= 0x3)
if (POWER_SCALE_IN_MICROWATT(flags))
pi->power_scale = SCMI_POWER_MICROWATTS;
pi->stats_addr = le32_to_cpu(attr->stats_addr_low)