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