Commit | Line | Data |
---|---|---|
76a65509 SH |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * System Control and Management Interface (SCMI) Power Protocol | |
4 | * | |
23136bff | 5 | * Copyright (C) 2018-2022 ARM Ltd. |
76a65509 SH |
6 | */ |
7 | ||
e27077bc CM |
8 | #define pr_fmt(fmt) "SCMI Notifications POWER - " fmt |
9 | ||
f5800e0b | 10 | #include <linux/module.h> |
e27077bc CM |
11 | #include <linux/scmi_protocol.h> |
12 | ||
23136bff | 13 | #include "protocols.h" |
e27077bc | 14 | #include "notify.h" |
76a65509 SH |
15 | |
16 | enum scmi_power_protocol_cmd { | |
17 | POWER_DOMAIN_ATTRIBUTES = 0x3, | |
18 | POWER_STATE_SET = 0x4, | |
19 | POWER_STATE_GET = 0x5, | |
20 | POWER_STATE_NOTIFY = 0x6, | |
b260fcca | 21 | POWER_DOMAIN_NAME_GET = 0x8, |
76a65509 SH |
22 | }; |
23 | ||
24 | struct 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 | ||
32 | struct 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)) | |
b260fcca CM |
37 | #define SUPPORTS_EXTENDED_NAMES(x) ((x) & BIT(27)) |
38 | u8 name[SCMI_SHORT_NAME_MAX_SIZE]; | |
76a65509 SH |
39 | }; |
40 | ||
41 | struct scmi_power_set_state { | |
42 | __le32 flags; | |
43 | #define STATE_SET_ASYNC BIT(0) | |
44 | __le32 domain; | |
45 | __le32 state; | |
46 | }; | |
47 | ||
48 | struct scmi_power_state_notify { | |
49 | __le32 domain; | |
50 | __le32 notify_enable; | |
51 | }; | |
52 | ||
e27077bc CM |
53 | struct scmi_power_state_notify_payld { |
54 | __le32 agent_id; | |
55 | __le32 domain_id; | |
56 | __le32 power_state; | |
57 | }; | |
58 | ||
76a65509 SH |
59 | struct 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 | ||
66 | struct scmi_power_info { | |
b55b06b7 | 67 | u32 version; |
76a65509 SH |
68 | int num_domains; |
69 | u64 stats_addr; | |
70 | u32 stats_size; | |
71 | struct power_dom_info *dom_info; | |
72 | }; | |
73 | ||
9bc8069c | 74 | static int scmi_power_attributes_get(const struct scmi_protocol_handle *ph, |
76a65509 SH |
75 | struct scmi_power_info *pi) |
76 | { | |
77 | int ret; | |
78 | struct scmi_xfer *t; | |
79 | struct scmi_msg_resp_power_attributes *attr; | |
80 | ||
9bc8069c CM |
81 | ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, |
82 | 0, sizeof(*attr), &t); | |
76a65509 SH |
83 | if (ret) |
84 | return ret; | |
85 | ||
86 | attr = t->rx.buf; | |
87 | ||
9bc8069c | 88 | ret = ph->xops->do_xfer(ph, t); |
76a65509 SH |
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 | ||
9bc8069c | 96 | ph->xops->xfer_put(ph, t); |
76a65509 SH |
97 | return ret; |
98 | } | |
99 | ||
100 | static int | |
9bc8069c | 101 | scmi_power_domain_attributes_get(const struct scmi_protocol_handle *ph, |
b260fcca CM |
102 | u32 domain, struct power_dom_info *dom_info, |
103 | u32 version) | |
76a65509 SH |
104 | { |
105 | int ret; | |
b260fcca | 106 | u32 flags; |
76a65509 SH |
107 | struct scmi_xfer *t; |
108 | struct scmi_msg_resp_power_domain_attributes *attr; | |
109 | ||
9bc8069c CM |
110 | ret = ph->xops->xfer_get_init(ph, POWER_DOMAIN_ATTRIBUTES, |
111 | sizeof(domain), sizeof(*attr), &t); | |
76a65509 SH |
112 | if (ret) |
113 | return ret; | |
114 | ||
aa90ac45 | 115 | put_unaligned_le32(domain, t->tx.buf); |
76a65509 SH |
116 | attr = t->rx.buf; |
117 | ||
9bc8069c | 118 | ret = ph->xops->do_xfer(ph, t); |
76a65509 | 119 | if (!ret) { |
b260fcca | 120 | flags = le32_to_cpu(attr->flags); |
76a65509 SH |
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); | |
4314f9f4 | 125 | strscpy(dom_info->name, attr->name, SCMI_SHORT_NAME_MAX_SIZE); |
76a65509 | 126 | } |
9bc8069c | 127 | ph->xops->xfer_put(ph, t); |
b260fcca CM |
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 | ||
76a65509 SH |
140 | return ret; |
141 | } | |
142 | ||
9bc8069c CM |
143 | static int scmi_power_state_set(const struct scmi_protocol_handle *ph, |
144 | u32 domain, u32 state) | |
76a65509 SH |
145 | { |
146 | int ret; | |
147 | struct scmi_xfer *t; | |
148 | struct scmi_power_set_state *st; | |
149 | ||
9bc8069c | 150 | ret = ph->xops->xfer_get_init(ph, POWER_STATE_SET, sizeof(*st), 0, &t); |
76a65509 SH |
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 | ||
9bc8069c | 159 | ret = ph->xops->do_xfer(ph, t); |
76a65509 | 160 | |
9bc8069c | 161 | ph->xops->xfer_put(ph, t); |
76a65509 SH |
162 | return ret; |
163 | } | |
164 | ||
9bc8069c CM |
165 | static int scmi_power_state_get(const struct scmi_protocol_handle *ph, |
166 | u32 domain, u32 *state) | |
76a65509 SH |
167 | { |
168 | int ret; | |
169 | struct scmi_xfer *t; | |
170 | ||
9bc8069c | 171 | ret = ph->xops->xfer_get_init(ph, POWER_STATE_GET, sizeof(u32), sizeof(u32), &t); |
76a65509 SH |
172 | if (ret) |
173 | return ret; | |
174 | ||
aa90ac45 | 175 | put_unaligned_le32(domain, t->tx.buf); |
76a65509 | 176 | |
9bc8069c | 177 | ret = ph->xops->do_xfer(ph, t); |
76a65509 | 178 | if (!ret) |
aa90ac45 | 179 | *state = get_unaligned_le32(t->rx.buf); |
76a65509 | 180 | |
9bc8069c | 181 | ph->xops->xfer_put(ph, t); |
76a65509 SH |
182 | return ret; |
183 | } | |
184 | ||
9bc8069c CM |
185 | static int scmi_power_num_domains_get(const struct scmi_protocol_handle *ph) |
186 | { | |
187 | struct scmi_power_info *pi = ph->get_priv(ph); | |
76a65509 SH |
188 | |
189 | return pi->num_domains; | |
190 | } | |
191 | ||
992be5d3 CM |
192 | static const char * |
193 | scmi_power_name_get(const struct scmi_protocol_handle *ph, | |
194 | u32 domain) | |
9bc8069c CM |
195 | { |
196 | struct scmi_power_info *pi = ph->get_priv(ph); | |
76a65509 SH |
197 | struct power_dom_info *dom = pi->dom_info + domain; |
198 | ||
199 | return dom->name; | |
200 | } | |
201 | ||
9bc8069c | 202 | static const struct scmi_power_proto_ops power_proto_ops = { |
76a65509 SH |
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 | ||
9bc8069c | 209 | static int scmi_power_request_notify(const struct scmi_protocol_handle *ph, |
e27077bc CM |
210 | u32 domain, bool enable) |
211 | { | |
212 | int ret; | |
213 | struct scmi_xfer *t; | |
214 | struct scmi_power_state_notify *notify; | |
215 | ||
9bc8069c CM |
216 | ret = ph->xops->xfer_get_init(ph, POWER_STATE_NOTIFY, |
217 | sizeof(*notify), 0, &t); | |
e27077bc CM |
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 | ||
9bc8069c | 225 | ret = ph->xops->do_xfer(ph, t); |
e27077bc | 226 | |
9bc8069c | 227 | ph->xops->xfer_put(ph, t); |
e27077bc CM |
228 | return ret; |
229 | } | |
230 | ||
3cb8c95f | 231 | static int scmi_power_set_notify_enabled(const struct scmi_protocol_handle *ph, |
e27077bc CM |
232 | u8 evt_id, u32 src_id, bool enable) |
233 | { | |
234 | int ret; | |
235 | ||
9bc8069c | 236 | ret = scmi_power_request_notify(ph, src_id, enable); |
e27077bc CM |
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 | ||
3cb8c95f CM |
244 | static void * |
245 | scmi_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) | |
e27077bc CM |
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 | ||
3cb8c95f | 265 | static int scmi_power_get_num_sources(const struct scmi_protocol_handle *ph) |
533c7095 | 266 | { |
3cb8c95f | 267 | struct scmi_power_info *pinfo = ph->get_priv(ph); |
533c7095 CM |
268 | |
269 | if (!pinfo) | |
270 | return -EINVAL; | |
271 | ||
272 | return pinfo->num_domains; | |
273 | } | |
274 | ||
e27077bc CM |
275 | static 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 | ||
284 | static const struct scmi_event_ops power_event_ops = { | |
533c7095 | 285 | .get_num_sources = scmi_power_get_num_sources, |
e27077bc CM |
286 | .set_notify_enabled = scmi_power_set_notify_enabled, |
287 | .fill_custom_report = scmi_power_fill_custom_report, | |
288 | }; | |
289 | ||
533c7095 CM |
290 | static 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 | ||
9bc8069c | 297 | static int scmi_power_protocol_init(const struct scmi_protocol_handle *ph) |
76a65509 | 298 | { |
4de1b36f | 299 | int domain, ret; |
76a65509 SH |
300 | u32 version; |
301 | struct scmi_power_info *pinfo; | |
302 | ||
4de1b36f CM |
303 | ret = ph->xops->version_get(ph, &version); |
304 | if (ret) | |
305 | return ret; | |
76a65509 | 306 | |
9bc8069c | 307 | dev_dbg(ph->dev, "Power Version %d.%d\n", |
76a65509 SH |
308 | PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version)); |
309 | ||
9bc8069c | 310 | pinfo = devm_kzalloc(ph->dev, sizeof(*pinfo), GFP_KERNEL); |
76a65509 SH |
311 | if (!pinfo) |
312 | return -ENOMEM; | |
313 | ||
4de1b36f CM |
314 | ret = scmi_power_attributes_get(ph, pinfo); |
315 | if (ret) | |
316 | return ret; | |
76a65509 | 317 | |
9bc8069c | 318 | pinfo->dom_info = devm_kcalloc(ph->dev, pinfo->num_domains, |
76a65509 SH |
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 | ||
b260fcca | 326 | scmi_power_domain_attributes_get(ph, domain, dom, version); |
76a65509 SH |
327 | } |
328 | ||
b55b06b7 | 329 | pinfo->version = version; |
9bc8069c | 330 | |
9bc8069c | 331 | return ph->set_priv(ph, pinfo); |
76a65509 SH |
332 | } |
333 | ||
48dc16e2 CM |
334 | static const struct scmi_protocol scmi_power = { |
335 | .id = SCMI_PROTOCOL_POWER, | |
f5800e0b | 336 | .owner = THIS_MODULE, |
9bc8069c CM |
337 | .instance_init = &scmi_power_protocol_init, |
338 | .ops = &power_proto_ops, | |
533c7095 | 339 | .events = &power_protocol_events, |
48dc16e2 CM |
340 | }; |
341 | ||
342 | DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(power, scmi_power) |