1 // SPDX-License-Identifier: BSD-3-Clause
2 //
3 // Copyright(c) 2018 Intel Corporation. All rights reserved.
4 //
5 // Author: Tomasz Lauda <tomasz.lauda@linux.intel.com>
6
7 #include <rtos/panic.h>
8 #include <rtos/idc.h>
9 #include <rtos/interrupt.h>
10 #include <sof/lib/cpu.h>
11 #include <sof/lib/mailbox.h>
12 #include <sof/lib/shim.h>
13 #include <sof/platform.h>
14 #include <sof/schedule/schedule.h>
15 #include <rtos/task.h>
16 #include <rtos/string.h>
17 #include <sof/trace/trace.h>
18 #include <errno.h>
19 #include <stdbool.h>
20 #include <stdint.h>
21
22 /**
23 * \brief Enables IDC interrupts.
24 * \param[in] target_core Target core id.
25 * \param[in] source_core Source core id.
26 */
idc_enable_interrupts(int target_core,int source_core)27 void idc_enable_interrupts(int target_core, int source_core)
28 {
29 struct idc *idc = *idc_get();
30
31 idc_write(IPC_IDCCTL, target_core,
32 IPC_IDCCTL_IDCTBIE(source_core));
33 interrupt_unmask(idc->irq, target_core);
34 }
35
36 /**
37 * \brief IDC interrupt handler.
38 * \param[in,out] arg Pointer to IDC data.
39 */
idc_irq_handler(void * arg)40 static void idc_irq_handler(void *arg)
41 {
42 struct idc *idc = arg;
43 int core = cpu_get_id();
44 uint32_t idctfc;
45 uint32_t idctefc;
46 uint32_t i;
47
48 tr_dbg(&idc_tr, "idc_irq_handler()");
49
50 for (i = 0; i < CONFIG_CORE_COUNT; i++) {
51 /* skip current core */
52 if (core == i)
53 continue;
54
55 idctfc = idc_read(IPC_IDCTFC(i), core);
56
57 if (idctfc & IPC_IDCTFC_BUSY) {
58 tr_info(&idc_tr, "idc_irq_handler(), IPC_IDCTFC_BUSY");
59
60 /* disable BUSY interrupt */
61 idc_write(IPC_IDCCTL, core, 0);
62
63 idc->received_msg.core = i;
64 idc->received_msg.header =
65 idctfc & IPC_IDCTFC_MSG_MASK;
66
67 idctefc = idc_read(IPC_IDCTEFC(i), core);
68 idc->received_msg.extension =
69 idctefc & IPC_IDCTEFC_MSG_MASK;
70
71 schedule_task(&idc->idc_task, 0, IDC_DEADLINE);
72 }
73 }
74 }
75
76 /**
77 * \brief Checks IDC registers whether message has been received.
78 * \param[in] target_core Id of the core receiving the message.
79 * \return True if message received, false otherwise.
80 */
idc_is_received(int target_core)81 static bool idc_is_received(int target_core)
82 {
83 return idc_read(IPC_IDCIETC(target_core), cpu_get_id()) &
84 IPC_IDCIETC_DONE;
85 }
86
87 /**
88 * \brief Checks core status register.
89 * \param[in] target_core Id of the core powering up.
90 * \return True if core powered up, false otherwise.
91 */
idc_is_powered_up(int target_core)92 static bool idc_is_powered_up(int target_core)
93 {
94 #if CONFIG_IPC_MAJOR_4
95 return cpu_is_core_enabled(target_core);
96 #else
97 return mailbox_sw_reg_read(PLATFORM_TRACEP_SECONDARY_CORE(target_core)) ==
98 TRACE_BOOT_PLATFORM;
99 #endif
100 }
101
102 /**
103 * \brief Checks core status register.
104 * \param[in] target_core Id of the core powering up.
105 * \return True if core powered up, false otherwise.
106 */
idc_is_powered_down(int target_core)107 static bool idc_is_powered_down(int target_core)
108 {
109 #if CONFIG_IPC_MAJOR_4
110 return !cpu_is_core_enabled(target_core);
111 #else
112 return mailbox_sw_reg_read(PLATFORM_TRACEP_SECONDARY_CORE(target_core)) == 0;
113 #endif
114 }
115
116 /**
117 * \brief Sends IDC message.
118 * \param[in,out] msg Pointer to IDC message.
119 * \param[in] mode Is message blocking or not.
120 * \return Error code.
121 */
idc_send_msg(struct idc_msg * msg,uint32_t mode)122 int idc_send_msg(struct idc_msg *msg, uint32_t mode)
123 {
124 struct idc *idc = *idc_get();
125 struct idc_payload *payload = idc_payload_get(idc, msg->core);
126 int core = cpu_get_id();
127 uint32_t idcietc;
128 int ret = 0;
129
130 tr_dbg(&idc_tr, "arch_idc_send_msg()");
131
132 /* clear any previous messages */
133 idcietc = idc_read(IPC_IDCIETC(msg->core), core);
134 if (idcietc & IPC_IDCIETC_DONE)
135 idc_write(IPC_IDCIETC(msg->core), core, idcietc);
136
137 /* copy payload if available */
138 if (msg->payload) {
139 ret = memcpy_s(payload->data, IDC_MAX_PAYLOAD_SIZE,
140 msg->payload, msg->size);
141 assert(!ret);
142 }
143
144 idc_write(IPC_IDCIETC(msg->core), core, msg->extension);
145 idc_write(IPC_IDCITC(msg->core), core, msg->header | IPC_IDCITC_BUSY);
146
147 switch (mode) {
148 case IDC_BLOCKING:
149 ret = idc_wait_in_blocking_mode(msg->core, idc_is_received);
150 if (ret < 0) {
151 tr_err(&idc_tr, "idc_send_msg(), blocking msg 0x%x failed for core %d",
152 msg->header, msg->core);
153 return ret;
154 }
155
156 idc_write(IPC_IDCIETC(msg->core), core,
157 idc_read(IPC_IDCIETC(msg->core), core) |
158 IPC_IDCIETC_DONE);
159
160 ret = idc_msg_status_get(msg->core);
161 break;
162
163 case IDC_POWER_UP:
164 ret = idc_wait_in_blocking_mode(msg->core, idc_is_powered_up);
165 if (ret < 0) {
166 #if CONFIG_IPC_MAJOR_4
167 tr_err(&idc_tr, "idc_send_msg(), power up core %d failed",
168 msg->core);
169 #else
170 tr_err(&idc_tr, "idc_send_msg(), power up core %d failed, reason 0x%x",
171 msg->core,
172 mailbox_sw_reg_read(PLATFORM_TRACEP_SECONDARY_CORE(msg->core)));
173 #endif
174 }
175 break;
176
177 case IDC_POWER_DOWN:
178 ret = idc_wait_in_blocking_mode(msg->core, idc_is_powered_down);
179 if (ret < 0) {
180 #if CONFIG_IPC_MAJOR_4
181 tr_err(&idc_tr, "idc_send_msg(), power down core %d failed",
182 msg->core);
183 #else
184 tr_err(&idc_tr, "idc_send_msg(), power down core %d failed, reason 0x%x",
185 msg->core,
186 mailbox_sw_reg_read(PLATFORM_TRACEP_SECONDARY_CORE(msg->core)));
187 #endif
188 }
189 break;
190 }
191
192 return ret;
193 }
194
195 /**
196 * \brief Handles received IDC message.
197 * \param[in,out] data Pointer to IDC data.
198 */
idc_do_cmd(void * data)199 enum task_state idc_do_cmd(void *data)
200 {
201 struct idc *idc = data;
202 int core = cpu_get_id();
203 int initiator = idc->received_msg.core;
204
205 tr_info(&idc_tr, "idc_do_cmd()");
206
207 idc_cmd(&idc->received_msg);
208
209 /* clear BUSY bit */
210 idc_write(IPC_IDCTFC(initiator), core,
211 idc_read(IPC_IDCTFC(initiator), core) | IPC_IDCTFC_BUSY);
212
213 /* enable BUSY interrupt */
214 idc_write(IPC_IDCCTL, core, idc->busy_bit_mask);
215
216 return SOF_TASK_STATE_COMPLETED;
217 }
218
219 /**
220 * \brief Returns BUSY interrupt mask based on core id.
221 * \param[in] core Core id.
222 * \return BUSY interrupt mask.
223 */
idc_get_busy_bit_mask(int core)224 static uint32_t idc_get_busy_bit_mask(int core)
225 {
226 uint32_t busy_mask = 0;
227 int i;
228
229 for (i = 0; i < CONFIG_CORE_COUNT; i++) {
230 if (i != core)
231 busy_mask |= IPC_IDCCTL_IDCTBIE(i);
232 }
233
234 return busy_mask;
235 }
236
237 /**
238 * \brief Initializes IDC data and registers for interrupt.
239 */
platform_idc_init(void)240 int platform_idc_init(void)
241 {
242 struct idc *idc = *idc_get();
243 int core = cpu_get_id();
244 int ret;
245
246 /* initialize idc data */
247 idc->busy_bit_mask = idc_get_busy_bit_mask(core);
248
249 /* configure interrupt */
250 idc->irq = interrupt_get_irq(PLATFORM_IDC_INTERRUPT,
251 PLATFORM_IDC_INTERRUPT_NAME);
252 if (idc->irq < 0)
253 return idc->irq;
254 ret = interrupt_register(idc->irq, idc_irq_handler, idc);
255 if (ret < 0)
256 return ret;
257 interrupt_enable(idc->irq, idc);
258
259 /* enable BUSY interrupt */
260 idc_write(IPC_IDCCTL, core, idc->busy_bit_mask);
261
262 return 0;
263 }
264
265 /**
266 * \brief Restores IDC interrupt. During D0->D0ix/D0ix->D0 flow primary core
267 * disables all secondary cores - this is not cold boot process, because
268 * memory has not been powered off. In that case, we should only enable
269 * idc interrupts, because all required structures alreade exist.
270 */
platform_idc_restore(void)271 int platform_idc_restore(void)
272 {
273 struct idc *idc = *idc_get();
274 int core = cpu_get_id();
275 int ret;
276
277 idc->irq = interrupt_get_irq(PLATFORM_IDC_INTERRUPT,
278 PLATFORM_IDC_INTERRUPT_NAME);
279 if (idc->irq < 0) {
280 tr_err(&idc_tr, "platform_idc_restore(): getting irq failed.");
281 return idc->irq;
282 }
283
284 ret = interrupt_register(idc->irq, idc_irq_handler, idc);
285 if (ret < 0) {
286 tr_err(&idc_tr, "platform_idc_restore(): registering irq failed.");
287 return ret;
288 }
289
290 interrupt_enable(idc->irq, idc);
291
292 /* enable BUSY interrupt */
293 idc_write(IPC_IDCCTL, core, idc->busy_bit_mask);
294
295 return 0;
296 }
297
298 /**
299 * \brief Frees IDC data and unregisters interrupt.
300 */
idc_free(uint32_t flags)301 void idc_free(uint32_t flags)
302 {
303 struct idc *idc = *idc_get();
304 int core = cpu_get_id();
305 int i = 0;
306 uint32_t idctfc;
307
308 tr_info(&idc_tr, "idc_free()");
309
310 /* disable and unregister interrupt */
311 interrupt_disable(idc->irq, idc);
312 interrupt_unregister(idc->irq, idc);
313
314 /* clear BUSY bits */
315 for (i = 0; i < CONFIG_CORE_COUNT; i++) {
316 idctfc = idc_read(IPC_IDCTFC(i), core);
317 if (idctfc & IPC_IDCTFC_BUSY)
318 idc_write(IPC_IDCTFC(i), core, idctfc);
319 }
320
321 if (flags & IDC_FREE_IRQ_ONLY)
322 return;
323
324 schedule_task_free(&idc->idc_task);
325 }
326