Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/firmware/arm_scmi/power.c
51301 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
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
77
static 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
108
static int
109
scmi_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
153
static 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
175
static 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
195
static 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
202
static const char *
203
scmi_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
212
static 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
219
static 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
241
static 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
255
static 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
268
static void *
269
scmi_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
289
static 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
299
static 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
308
static 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
315
static 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
322
static 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
353
static 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
362
DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(power, scmi_power)
363
364