371 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * System Control and Management Interface (SCMI) Power Protocol4 *5 * Copyright (C) 2018-2022 ARM Ltd.6 */7 8#define pr_fmt(fmt) "SCMI Notifications POWER - " fmt9 10#include <linux/module.h>11#include <linux/scmi_protocol.h>12 13#include "protocols.h"14#include "notify.h"15 16/* Updated only after ALL the mandatory features for that version are merged */17#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x3000118 19enum scmi_power_protocol_cmd {20 POWER_DOMAIN_ATTRIBUTES = 0x3,21 POWER_STATE_SET = 0x4,22 POWER_STATE_GET = 0x5,23 POWER_STATE_NOTIFY = 0x6,24 POWER_DOMAIN_NAME_GET = 0x8,25};26 27struct scmi_msg_resp_power_attributes {28 __le16 num_domains;29 __le16 reserved;30 __le32 stats_addr_low;31 __le32 stats_addr_high;32 __le32 stats_size;33};34 35struct scmi_msg_resp_power_domain_attributes {36 __le32 flags;37#define SUPPORTS_STATE_SET_NOTIFY(x) ((x) & BIT(31))38#define SUPPORTS_STATE_SET_ASYNC(x) ((x) & BIT(30))39#define SUPPORTS_STATE_SET_SYNC(x) ((x) & BIT(29))40#define SUPPORTS_EXTENDED_NAMES(x) ((x) & BIT(27))41 u8 name[SCMI_SHORT_NAME_MAX_SIZE];42};43 44struct scmi_power_set_state {45 __le32 flags;46#define STATE_SET_ASYNC BIT(0)47 __le32 domain;48 __le32 state;49};50 51struct scmi_power_state_notify {52 __le32 domain;53 __le32 notify_enable;54};55 56struct scmi_power_state_notify_payld {57 __le32 agent_id;58 __le32 domain_id;59 __le32 power_state;60};61 62struct power_dom_info {63 bool state_set_sync;64 bool state_set_async;65 bool state_set_notify;66 char name[SCMI_MAX_STR_SIZE];67};68 69struct scmi_power_info {70 u32 version;71 bool notify_state_change_cmd;72 int num_domains;73 u64 stats_addr;74 u32 stats_size;75 struct power_dom_info *dom_info;76};77 78static int scmi_power_attributes_get(const struct scmi_protocol_handle *ph,79 struct scmi_power_info *pi)80{81 int ret;82 struct scmi_xfer *t;83 struct scmi_msg_resp_power_attributes *attr;84 85 ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES,86 0, sizeof(*attr), &t);87 if (ret)88 return ret;89 90 attr = t->rx.buf;91 92 ret = ph->xops->do_xfer(ph, t);93 if (!ret) {94 pi->num_domains = le16_to_cpu(attr->num_domains);95 pi->stats_addr = le32_to_cpu(attr->stats_addr_low) |96 (u64)le32_to_cpu(attr->stats_addr_high) << 32;97 pi->stats_size = le32_to_cpu(attr->stats_size);98 }99 100 ph->xops->xfer_put(ph, t);101 102 if (!ret)103 if (!ph->hops->protocol_msg_check(ph, POWER_STATE_NOTIFY, NULL))104 pi->notify_state_change_cmd = true;105 106 return ret;107}108 109static int110scmi_power_domain_attributes_get(const struct scmi_protocol_handle *ph,111 u32 domain, struct power_dom_info *dom_info,112 u32 version, bool notify_state_change_cmd)113{114 int ret;115 u32 flags;116 struct scmi_xfer *t;117 struct scmi_msg_resp_power_domain_attributes *attr;118 119 ret = ph->xops->xfer_get_init(ph, POWER_DOMAIN_ATTRIBUTES,120 sizeof(domain), sizeof(*attr), &t);121 if (ret)122 return ret;123 124 put_unaligned_le32(domain, t->tx.buf);125 attr = t->rx.buf;126 127 ret = ph->xops->do_xfer(ph, t);128 if (!ret) {129 flags = le32_to_cpu(attr->flags);130 131 if (notify_state_change_cmd)132 dom_info->state_set_notify =133 SUPPORTS_STATE_SET_NOTIFY(flags);134 dom_info->state_set_async = SUPPORTS_STATE_SET_ASYNC(flags);135 dom_info->state_set_sync = SUPPORTS_STATE_SET_SYNC(flags);136 strscpy(dom_info->name, attr->name, SCMI_SHORT_NAME_MAX_SIZE);137 }138 ph->xops->xfer_put(ph, t);139 140 /*141 * If supported overwrite short name with the extended one;142 * on error just carry on and use already provided short name.143 */144 if (!ret && PROTOCOL_REV_MAJOR(version) >= 0x3 &&145 SUPPORTS_EXTENDED_NAMES(flags)) {146 ph->hops->extended_name_get(ph, POWER_DOMAIN_NAME_GET,147 domain, NULL, dom_info->name,148 SCMI_MAX_STR_SIZE);149 }150 151 return ret;152}153 154static int scmi_power_state_set(const struct scmi_protocol_handle *ph,155 u32 domain, u32 state)156{157 int ret;158 struct scmi_xfer *t;159 struct scmi_power_set_state *st;160 161 ret = ph->xops->xfer_get_init(ph, POWER_STATE_SET, sizeof(*st), 0, &t);162 if (ret)163 return ret;164 165 st = t->tx.buf;166 st->flags = cpu_to_le32(0);167 st->domain = cpu_to_le32(domain);168 st->state = cpu_to_le32(state);169 170 ret = ph->xops->do_xfer(ph, t);171 172 ph->xops->xfer_put(ph, t);173 return ret;174}175 176static int scmi_power_state_get(const struct scmi_protocol_handle *ph,177 u32 domain, u32 *state)178{179 int ret;180 struct scmi_xfer *t;181 182 ret = ph->xops->xfer_get_init(ph, POWER_STATE_GET, sizeof(u32), sizeof(u32), &t);183 if (ret)184 return ret;185 186 put_unaligned_le32(domain, t->tx.buf);187 188 ret = ph->xops->do_xfer(ph, t);189 if (!ret)190 *state = get_unaligned_le32(t->rx.buf);191 192 ph->xops->xfer_put(ph, t);193 return ret;194}195 196static int scmi_power_num_domains_get(const struct scmi_protocol_handle *ph)197{198 struct scmi_power_info *pi = ph->get_priv(ph);199 200 return pi->num_domains;201}202 203static const char *204scmi_power_name_get(const struct scmi_protocol_handle *ph,205 u32 domain)206{207 struct scmi_power_info *pi = ph->get_priv(ph);208 struct power_dom_info *dom = pi->dom_info + domain;209 210 return dom->name;211}212 213static const struct scmi_power_proto_ops power_proto_ops = {214 .num_domains_get = scmi_power_num_domains_get,215 .name_get = scmi_power_name_get,216 .state_set = scmi_power_state_set,217 .state_get = scmi_power_state_get,218};219 220static int scmi_power_request_notify(const struct scmi_protocol_handle *ph,221 u32 domain, bool enable)222{223 int ret;224 struct scmi_xfer *t;225 struct scmi_power_state_notify *notify;226 227 ret = ph->xops->xfer_get_init(ph, POWER_STATE_NOTIFY,228 sizeof(*notify), 0, &t);229 if (ret)230 return ret;231 232 notify = t->tx.buf;233 notify->domain = cpu_to_le32(domain);234 notify->notify_enable = enable ? cpu_to_le32(BIT(0)) : 0;235 236 ret = ph->xops->do_xfer(ph, t);237 238 ph->xops->xfer_put(ph, t);239 return ret;240}241 242static bool scmi_power_notify_supported(const struct scmi_protocol_handle *ph,243 u8 evt_id, u32 src_id)244{245 struct power_dom_info *dom;246 struct scmi_power_info *pinfo = ph->get_priv(ph);247 248 if (evt_id != SCMI_EVENT_POWER_STATE_CHANGED ||249 src_id >= pinfo->num_domains)250 return false;251 252 dom = pinfo->dom_info + src_id;253 return dom->state_set_notify;254}255 256static int scmi_power_set_notify_enabled(const struct scmi_protocol_handle *ph,257 u8 evt_id, u32 src_id, bool enable)258{259 int ret;260 261 ret = scmi_power_request_notify(ph, src_id, enable);262 if (ret)263 pr_debug("FAIL_ENABLE - evt[%X] dom[%d] - ret:%d\n",264 evt_id, src_id, ret);265 266 return ret;267}268 269static void *270scmi_power_fill_custom_report(const struct scmi_protocol_handle *ph,271 u8 evt_id, ktime_t timestamp,272 const void *payld, size_t payld_sz,273 void *report, u32 *src_id)274{275 const struct scmi_power_state_notify_payld *p = payld;276 struct scmi_power_state_changed_report *r = report;277 278 if (evt_id != SCMI_EVENT_POWER_STATE_CHANGED || sizeof(*p) != payld_sz)279 return NULL;280 281 r->timestamp = timestamp;282 r->agent_id = le32_to_cpu(p->agent_id);283 r->domain_id = le32_to_cpu(p->domain_id);284 r->power_state = le32_to_cpu(p->power_state);285 *src_id = r->domain_id;286 287 return r;288}289 290static int scmi_power_get_num_sources(const struct scmi_protocol_handle *ph)291{292 struct scmi_power_info *pinfo = ph->get_priv(ph);293 294 if (!pinfo)295 return -EINVAL;296 297 return pinfo->num_domains;298}299 300static const struct scmi_event power_events[] = {301 {302 .id = SCMI_EVENT_POWER_STATE_CHANGED,303 .max_payld_sz = sizeof(struct scmi_power_state_notify_payld),304 .max_report_sz =305 sizeof(struct scmi_power_state_changed_report),306 },307};308 309static const struct scmi_event_ops power_event_ops = {310 .is_notify_supported = scmi_power_notify_supported,311 .get_num_sources = scmi_power_get_num_sources,312 .set_notify_enabled = scmi_power_set_notify_enabled,313 .fill_custom_report = scmi_power_fill_custom_report,314};315 316static const struct scmi_protocol_events power_protocol_events = {317 .queue_sz = SCMI_PROTO_QUEUE_SZ,318 .ops = &power_event_ops,319 .evts = power_events,320 .num_events = ARRAY_SIZE(power_events),321};322 323static int scmi_power_protocol_init(const struct scmi_protocol_handle *ph)324{325 int domain, ret;326 u32 version;327 struct scmi_power_info *pinfo;328 329 ret = ph->xops->version_get(ph, &version);330 if (ret)331 return ret;332 333 dev_dbg(ph->dev, "Power Version %d.%d\n",334 PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));335 336 pinfo = devm_kzalloc(ph->dev, sizeof(*pinfo), GFP_KERNEL);337 if (!pinfo)338 return -ENOMEM;339 340 ret = scmi_power_attributes_get(ph, pinfo);341 if (ret)342 return ret;343 344 pinfo->dom_info = devm_kcalloc(ph->dev, pinfo->num_domains,345 sizeof(*pinfo->dom_info), GFP_KERNEL);346 if (!pinfo->dom_info)347 return -ENOMEM;348 349 for (domain = 0; domain < pinfo->num_domains; domain++) {350 struct power_dom_info *dom = pinfo->dom_info + domain;351 352 scmi_power_domain_attributes_get(ph, domain, dom, version,353 pinfo->notify_state_change_cmd);354 }355 356 pinfo->version = version;357 358 return ph->set_priv(ph, pinfo, version);359}360 361static const struct scmi_protocol scmi_power = {362 .id = SCMI_PROTOCOL_POWER,363 .owner = THIS_MODULE,364 .instance_init = &scmi_power_protocol_init,365 .ops = &power_proto_ops,366 .events = &power_protocol_events,367 .supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,368};369 370DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(power, scmi_power)371