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