1 // SPDX-License-Identifier: GPL-2.0
3 * System Control and Management Interface (SCMI) Power Protocol
5 * Copyright (C) 2018-2022 ARM Ltd.
8 #define pr_fmt(fmt) "SCMI Notifications POWER - " fmt
10 #include <linux/module.h>
11 #include <linux/scmi_protocol.h>
13 #include "protocols.h"
16 /* Updated only after ALL the mandatory features for that version are merged */
17 #define SCMI_PROTOCOL_SUPPORTED_VERSION 0x30001
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,
27 struct scmi_msg_resp_power_attributes
{
30 __le32 stats_addr_low
;
31 __le32 stats_addr_high
;
35 struct scmi_msg_resp_power_domain_attributes
{
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
];
44 struct scmi_power_set_state
{
46 #define STATE_SET_ASYNC BIT(0)
51 struct scmi_power_state_notify
{
56 struct scmi_power_state_notify_payld
{
62 struct power_dom_info
{
65 bool state_set_notify
;
66 char name
[SCMI_MAX_STR_SIZE
];
69 struct scmi_power_info
{
71 bool notify_state_change_cmd
;
75 struct power_dom_info
*dom_info
;
78 static int scmi_power_attributes_get(const struct scmi_protocol_handle
*ph
,
79 struct scmi_power_info
*pi
)
83 struct scmi_msg_resp_power_attributes
*attr
;
85 ret
= ph
->xops
->xfer_get_init(ph
, PROTOCOL_ATTRIBUTES
,
86 0, sizeof(*attr
), &t
);
92 ret
= ph
->xops
->do_xfer(ph
, t
);
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
);
100 ph
->xops
->xfer_put(ph
, t
);
103 if (!ph
->hops
->protocol_msg_check(ph
, POWER_STATE_NOTIFY
, NULL
))
104 pi
->notify_state_change_cmd
= true;
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
)
117 struct scmi_msg_resp_power_domain_attributes
*attr
;
119 ret
= ph
->xops
->xfer_get_init(ph
, POWER_DOMAIN_ATTRIBUTES
,
120 sizeof(domain
), sizeof(*attr
), &t
);
124 put_unaligned_le32(domain
, t
->tx
.buf
);
127 ret
= ph
->xops
->do_xfer(ph
, t
);
129 flags
= le32_to_cpu(attr
->flags
);
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
);
138 ph
->xops
->xfer_put(ph
, t
);
141 * If supported overwrite short name with the extended one;
142 * on error just carry on and use already provided short name.
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
,
154 static int scmi_power_state_set(const struct scmi_protocol_handle
*ph
,
155 u32 domain
, u32 state
)
159 struct scmi_power_set_state
*st
;
161 ret
= ph
->xops
->xfer_get_init(ph
, POWER_STATE_SET
, sizeof(*st
), 0, &t
);
166 st
->flags
= cpu_to_le32(0);
167 st
->domain
= cpu_to_le32(domain
);
168 st
->state
= cpu_to_le32(state
);
170 ret
= ph
->xops
->do_xfer(ph
, t
);
172 ph
->xops
->xfer_put(ph
, t
);
176 static int scmi_power_state_get(const struct scmi_protocol_handle
*ph
,
177 u32 domain
, u32
*state
)
182 ret
= ph
->xops
->xfer_get_init(ph
, POWER_STATE_GET
, sizeof(u32
), sizeof(u32
), &t
);
186 put_unaligned_le32(domain
, t
->tx
.buf
);
188 ret
= ph
->xops
->do_xfer(ph
, t
);
190 *state
= get_unaligned_le32(t
->rx
.buf
);
192 ph
->xops
->xfer_put(ph
, t
);
196 static int scmi_power_num_domains_get(const struct scmi_protocol_handle
*ph
)
198 struct scmi_power_info
*pi
= ph
->get_priv(ph
);
200 return pi
->num_domains
;
204 scmi_power_name_get(const struct scmi_protocol_handle
*ph
,
207 struct scmi_power_info
*pi
= ph
->get_priv(ph
);
208 struct power_dom_info
*dom
= pi
->dom_info
+ domain
;
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
,
220 static int scmi_power_request_notify(const struct scmi_protocol_handle
*ph
,
221 u32 domain
, bool enable
)
225 struct scmi_power_state_notify
*notify
;
227 ret
= ph
->xops
->xfer_get_init(ph
, POWER_STATE_NOTIFY
,
228 sizeof(*notify
), 0, &t
);
233 notify
->domain
= cpu_to_le32(domain
);
234 notify
->notify_enable
= enable
? cpu_to_le32(BIT(0)) : 0;
236 ret
= ph
->xops
->do_xfer(ph
, t
);
238 ph
->xops
->xfer_put(ph
, t
);
242 static bool scmi_power_notify_supported(const struct scmi_protocol_handle
*ph
,
243 u8 evt_id
, u32 src_id
)
245 struct power_dom_info
*dom
;
246 struct scmi_power_info
*pinfo
= ph
->get_priv(ph
);
248 if (evt_id
!= SCMI_EVENT_POWER_STATE_CHANGED
||
249 src_id
>= pinfo
->num_domains
)
252 dom
= pinfo
->dom_info
+ src_id
;
253 return dom
->state_set_notify
;
256 static int scmi_power_set_notify_enabled(const struct scmi_protocol_handle
*ph
,
257 u8 evt_id
, u32 src_id
, bool enable
)
261 ret
= scmi_power_request_notify(ph
, src_id
, enable
);
263 pr_debug("FAIL_ENABLE - evt[%X] dom[%d] - ret:%d\n",
264 evt_id
, src_id
, ret
);
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
)
275 const struct scmi_power_state_notify_payld
*p
= payld
;
276 struct scmi_power_state_changed_report
*r
= report
;
278 if (evt_id
!= SCMI_EVENT_POWER_STATE_CHANGED
|| sizeof(*p
) != payld_sz
)
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
;
290 static int scmi_power_get_num_sources(const struct scmi_protocol_handle
*ph
)
292 struct scmi_power_info
*pinfo
= ph
->get_priv(ph
);
297 return pinfo
->num_domains
;
300 static const struct scmi_event power_events
[] = {
302 .id
= SCMI_EVENT_POWER_STATE_CHANGED
,
303 .max_payld_sz
= sizeof(struct scmi_power_state_notify_payld
),
305 sizeof(struct scmi_power_state_changed_report
),
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
,
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
),
323 static int scmi_power_protocol_init(const struct scmi_protocol_handle
*ph
)
327 struct scmi_power_info
*pinfo
;
329 ret
= ph
->xops
->version_get(ph
, &version
);
333 dev_dbg(ph
->dev
, "Power Version %d.%d\n",
334 PROTOCOL_REV_MAJOR(version
), PROTOCOL_REV_MINOR(version
));
336 pinfo
= devm_kzalloc(ph
->dev
, sizeof(*pinfo
), GFP_KERNEL
);
340 ret
= scmi_power_attributes_get(ph
, pinfo
);
344 pinfo
->dom_info
= devm_kcalloc(ph
->dev
, pinfo
->num_domains
,
345 sizeof(*pinfo
->dom_info
), GFP_KERNEL
);
346 if (!pinfo
->dom_info
)
349 for (domain
= 0; domain
< pinfo
->num_domains
; domain
++) {
350 struct power_dom_info
*dom
= pinfo
->dom_info
+ domain
;
352 scmi_power_domain_attributes_get(ph
, domain
, dom
, version
,
353 pinfo
->notify_state_change_cmd
);
356 pinfo
->version
= version
;
358 return ph
->set_priv(ph
, pinfo
, version
);
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
,
370 DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(power
, scmi_power
)