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