Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
at v6.13 370 lines 9.0 kB view raw
1// SPDX-License-Identifier: GPL-2.0 2/* 3 * System Control and Management Interface (SCMI) Power Protocol 4 * 5 * Copyright (C) 2018-2022 ARM Ltd. 6 */ 7 8#define pr_fmt(fmt) "SCMI Notifications POWER - " fmt 9 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 0x30001 18 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 int 110scmi_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)