Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/firmware/arm_scmi/power.c
26428 views
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
19
enum 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
27
struct 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
35
struct 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
44
struct scmi_power_set_state {
45
__le32 flags;
46
#define STATE_SET_ASYNC BIT(0)
47
__le32 domain;
48
__le32 state;
49
};
50
51
struct scmi_power_state_notify {
52
__le32 domain;
53
__le32 notify_enable;
54
};
55
56
struct scmi_power_state_notify_payld {
57
__le32 agent_id;
58
__le32 domain_id;
59
__le32 power_state;
60
};
61
62
struct 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
69
struct 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
78
static 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
109
static int
110
scmi_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
154
static 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
176
static 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
196
static 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
203
static const char *
204
scmi_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
213
static 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
220
static 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
242
static 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
256
static 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
269
static void *
270
scmi_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
290
static 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
300
static 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
309
static 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
316
static 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
323
static 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
361
static 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
370
DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(power, scmi_power)
371
372