1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * System Control and Management Interface (SCMI) System Power Protocol |
4 | * |
5 | * Copyright (C) 2020-2022 ARM Ltd. |
6 | */ |
7 | |
8 | #define pr_fmt(fmt) "SCMI Notifications SYSTEM - " 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 0x20000 |
18 | |
19 | #define SCMI_SYSTEM_NUM_SOURCES 1 |
20 | |
21 | enum scmi_system_protocol_cmd { |
22 | SYSTEM_POWER_STATE_NOTIFY = 0x5, |
23 | }; |
24 | |
25 | struct scmi_system_power_state_notify { |
26 | __le32 notify_enable; |
27 | }; |
28 | |
29 | struct scmi_system_power_state_notifier_payld { |
30 | __le32 agent_id; |
31 | __le32 flags; |
32 | __le32 system_state; |
33 | __le32 timeout; |
34 | }; |
35 | |
36 | struct scmi_system_info { |
37 | u32 version; |
38 | bool graceful_timeout_supported; |
39 | bool power_state_notify_cmd; |
40 | }; |
41 | |
42 | static bool scmi_system_notify_supported(const struct scmi_protocol_handle *ph, |
43 | u8 evt_id, u32 src_id) |
44 | { |
45 | struct scmi_system_info *pinfo = ph->get_priv(ph); |
46 | |
47 | if (evt_id != SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER) |
48 | return false; |
49 | |
50 | return pinfo->power_state_notify_cmd; |
51 | } |
52 | |
53 | static int scmi_system_request_notify(const struct scmi_protocol_handle *ph, |
54 | bool enable) |
55 | { |
56 | int ret; |
57 | struct scmi_xfer *t; |
58 | struct scmi_system_power_state_notify *notify; |
59 | |
60 | ret = ph->xops->xfer_get_init(ph, SYSTEM_POWER_STATE_NOTIFY, |
61 | sizeof(*notify), 0, &t); |
62 | if (ret) |
63 | return ret; |
64 | |
65 | notify = t->tx.buf; |
66 | notify->notify_enable = enable ? cpu_to_le32(BIT(0)) : 0; |
67 | |
68 | ret = ph->xops->do_xfer(ph, t); |
69 | |
70 | ph->xops->xfer_put(ph, t); |
71 | return ret; |
72 | } |
73 | |
74 | static int scmi_system_set_notify_enabled(const struct scmi_protocol_handle *ph, |
75 | u8 evt_id, u32 src_id, bool enable) |
76 | { |
77 | int ret; |
78 | |
79 | ret = scmi_system_request_notify(ph, enable); |
80 | if (ret) |
81 | pr_debug("FAIL_ENABLE - evt[%X] - ret:%d\n" , evt_id, ret); |
82 | |
83 | return ret; |
84 | } |
85 | |
86 | static void * |
87 | scmi_system_fill_custom_report(const struct scmi_protocol_handle *ph, |
88 | u8 evt_id, ktime_t timestamp, |
89 | const void *payld, size_t payld_sz, |
90 | void *report, u32 *src_id) |
91 | { |
92 | size_t expected_sz; |
93 | const struct scmi_system_power_state_notifier_payld *p = payld; |
94 | struct scmi_system_power_state_notifier_report *r = report; |
95 | struct scmi_system_info *pinfo = ph->get_priv(ph); |
96 | |
97 | expected_sz = pinfo->graceful_timeout_supported ? |
98 | sizeof(*p) : sizeof(*p) - sizeof(__le32); |
99 | if (evt_id != SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER || |
100 | payld_sz != expected_sz) |
101 | return NULL; |
102 | |
103 | r->timestamp = timestamp; |
104 | r->agent_id = le32_to_cpu(p->agent_id); |
105 | r->flags = le32_to_cpu(p->flags); |
106 | r->system_state = le32_to_cpu(p->system_state); |
107 | if (pinfo->graceful_timeout_supported && |
108 | r->system_state == SCMI_SYSTEM_SHUTDOWN && |
109 | SCMI_SYSPOWER_IS_REQUEST_GRACEFUL(r->flags)) |
110 | r->timeout = le32_to_cpu(p->timeout); |
111 | else |
112 | r->timeout = 0x00; |
113 | *src_id = 0; |
114 | |
115 | return r; |
116 | } |
117 | |
118 | static const struct scmi_event system_events[] = { |
119 | { |
120 | .id = SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER, |
121 | .max_payld_sz = |
122 | sizeof(struct scmi_system_power_state_notifier_payld), |
123 | .max_report_sz = |
124 | sizeof(struct scmi_system_power_state_notifier_report), |
125 | }, |
126 | }; |
127 | |
128 | static const struct scmi_event_ops system_event_ops = { |
129 | .is_notify_supported = scmi_system_notify_supported, |
130 | .set_notify_enabled = scmi_system_set_notify_enabled, |
131 | .fill_custom_report = scmi_system_fill_custom_report, |
132 | }; |
133 | |
134 | static const struct scmi_protocol_events system_protocol_events = { |
135 | .queue_sz = SCMI_PROTO_QUEUE_SZ, |
136 | .ops = &system_event_ops, |
137 | .evts = system_events, |
138 | .num_events = ARRAY_SIZE(system_events), |
139 | .num_sources = SCMI_SYSTEM_NUM_SOURCES, |
140 | }; |
141 | |
142 | static int scmi_system_protocol_init(const struct scmi_protocol_handle *ph) |
143 | { |
144 | int ret; |
145 | u32 version; |
146 | struct scmi_system_info *pinfo; |
147 | |
148 | ret = ph->xops->version_get(ph, &version); |
149 | if (ret) |
150 | return ret; |
151 | |
152 | dev_dbg(ph->dev, "System Power Version %d.%d\n" , |
153 | PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version)); |
154 | |
155 | pinfo = devm_kzalloc(dev: ph->dev, size: sizeof(*pinfo), GFP_KERNEL); |
156 | if (!pinfo) |
157 | return -ENOMEM; |
158 | |
159 | pinfo->version = version; |
160 | if (PROTOCOL_REV_MAJOR(pinfo->version) >= 0x2) |
161 | pinfo->graceful_timeout_supported = true; |
162 | |
163 | if (!ph->hops->protocol_msg_check(ph, SYSTEM_POWER_STATE_NOTIFY, NULL)) |
164 | pinfo->power_state_notify_cmd = true; |
165 | |
166 | return ph->set_priv(ph, pinfo, version); |
167 | } |
168 | |
169 | static const struct scmi_protocol scmi_system = { |
170 | .id = SCMI_PROTOCOL_SYSTEM, |
171 | .owner = THIS_MODULE, |
172 | .instance_init = &scmi_system_protocol_init, |
173 | .ops = NULL, |
174 | .events = &system_protocol_events, |
175 | .supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION, |
176 | }; |
177 | |
178 | DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(system, scmi_system) |
179 | |