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 <sof/debug/panic.h>
8 #include <sof/drivers/idc.h>
9 #include <sof/drivers/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 <sof/schedule/task.h>
16 #include <sof/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 	return mailbox_sw_reg_read(PLATFORM_TRACEP_SECONDARY_CORE(target_core)) ==
95 		TRACE_BOOT_PLATFORM;
96 }
97 
98 /**
99  * \brief Checks core status register.
100  * \param[in] target_core Id of the core powering up.
101  * \return True if core powered up, false otherwise.
102  */
idc_is_powered_down(int target_core)103 static bool idc_is_powered_down(int target_core)
104 {
105 	return mailbox_sw_reg_read(PLATFORM_TRACEP_SECONDARY_CORE(target_core)) == 0;
106 }
107 
108 /**
109  * \brief Sends IDC message.
110  * \param[in,out] msg Pointer to IDC message.
111  * \param[in] mode Is message blocking or not.
112  * \return Error code.
113  */
idc_send_msg(struct idc_msg * msg,uint32_t mode)114 int idc_send_msg(struct idc_msg *msg, uint32_t mode)
115 {
116 	struct idc *idc = *idc_get();
117 	struct idc_payload *payload = idc_payload_get(idc, msg->core);
118 	int core = cpu_get_id();
119 	uint32_t idcietc;
120 	int ret = 0;
121 
122 	tr_dbg(&idc_tr, "arch_idc_send_msg()");
123 
124 	/* clear any previous messages */
125 	idcietc = idc_read(IPC_IDCIETC(msg->core), core);
126 	if (idcietc & IPC_IDCIETC_DONE)
127 		idc_write(IPC_IDCIETC(msg->core), core, idcietc);
128 
129 	/* copy payload if available */
130 	if (msg->payload) {
131 		ret = memcpy_s(payload->data, IDC_MAX_PAYLOAD_SIZE,
132 			       msg->payload, msg->size);
133 		assert(!ret);
134 	}
135 
136 	idc_write(IPC_IDCIETC(msg->core), core, msg->extension);
137 	idc_write(IPC_IDCITC(msg->core), core, msg->header | IPC_IDCITC_BUSY);
138 
139 	switch (mode) {
140 	case IDC_BLOCKING:
141 		ret = idc_wait_in_blocking_mode(msg->core, idc_is_received);
142 		if (ret < 0) {
143 			tr_err(&idc_tr, "idc_send_msg(), blocking msg 0x%x failed for core %d",
144 			       msg->header, msg->core);
145 			return ret;
146 		}
147 
148 		idc_write(IPC_IDCIETC(msg->core), core,
149 			  idc_read(IPC_IDCIETC(msg->core), core) |
150 			  IPC_IDCIETC_DONE);
151 
152 		ret = idc_msg_status_get(msg->core);
153 		break;
154 
155 	case IDC_POWER_UP:
156 		ret = idc_wait_in_blocking_mode(msg->core, idc_is_powered_up);
157 		if (ret < 0) {
158 			tr_err(&idc_tr, "idc_send_msg(), power up core %d failed, reason 0x%x",
159 			       msg->core,
160 			       mailbox_sw_reg_read(PLATFORM_TRACEP_SECONDARY_CORE(msg->core)));
161 		}
162 		break;
163 	case IDC_POWER_DOWN:
164 		ret = idc_wait_in_blocking_mode(msg->core, idc_is_powered_down);
165 		if (ret < 0) {
166 			tr_err(&idc_tr, "idc_send_msg(), power down core %d failed, reason 0x%x",
167 			       msg->core,
168 			       mailbox_sw_reg_read(PLATFORM_TRACEP_SECONDARY_CORE(msg->core)));
169 		}
170 		break;
171 	}
172 
173 	return ret;
174 }
175 
176 /**
177  * \brief Handles received IDC message.
178  * \param[in,out] data Pointer to IDC data.
179  */
idc_do_cmd(void * data)180 enum task_state idc_do_cmd(void *data)
181 {
182 	struct idc *idc = data;
183 	int core = cpu_get_id();
184 	int initiator = idc->received_msg.core;
185 
186 	tr_info(&idc_tr, "idc_do_cmd()");
187 
188 	idc_cmd(&idc->received_msg);
189 
190 	/* clear BUSY bit */
191 	idc_write(IPC_IDCTFC(initiator), core,
192 		  idc_read(IPC_IDCTFC(initiator), core) | IPC_IDCTFC_BUSY);
193 
194 	/* enable BUSY interrupt */
195 	idc_write(IPC_IDCCTL, core, idc->busy_bit_mask);
196 
197 	return SOF_TASK_STATE_COMPLETED;
198 }
199 
200 /**
201  * \brief Returns BUSY interrupt mask based on core id.
202  * \param[in] core Core id.
203  * \return BUSY interrupt mask.
204  */
idc_get_busy_bit_mask(int core)205 static uint32_t idc_get_busy_bit_mask(int core)
206 {
207 	uint32_t busy_mask = 0;
208 	int i;
209 
210 	for (i = 0; i < CONFIG_CORE_COUNT; i++) {
211 		if (i != core)
212 			busy_mask |= IPC_IDCCTL_IDCTBIE(i);
213 	}
214 
215 	return busy_mask;
216 }
217 
218 /**
219  * \brief Initializes IDC data and registers for interrupt.
220  */
platform_idc_init(void)221 int platform_idc_init(void)
222 {
223 	struct idc *idc = *idc_get();
224 	int core = cpu_get_id();
225 	int ret;
226 
227 	/* initialize idc data */
228 	idc->busy_bit_mask = idc_get_busy_bit_mask(core);
229 
230 	/* configure interrupt */
231 	idc->irq = interrupt_get_irq(PLATFORM_IDC_INTERRUPT,
232 				     PLATFORM_IDC_INTERRUPT_NAME);
233 	if (idc->irq < 0)
234 		return idc->irq;
235 	ret = interrupt_register(idc->irq, idc_irq_handler, idc);
236 	if (ret < 0)
237 		return ret;
238 	interrupt_enable(idc->irq, idc);
239 
240 	/* enable BUSY interrupt */
241 	idc_write(IPC_IDCCTL, core, idc->busy_bit_mask);
242 
243 	return 0;
244 }
245 
246 /**
247  * \brief Frees IDC data and unregisters interrupt.
248  */
idc_free(void)249 void idc_free(void)
250 {
251 	struct idc *idc = *idc_get();
252 	int core = cpu_get_id();
253 	int i = 0;
254 	uint32_t idctfc;
255 
256 	tr_info(&idc_tr, "idc_free()");
257 
258 	/* disable and unregister interrupt */
259 	interrupt_disable(idc->irq, idc);
260 	interrupt_unregister(idc->irq, idc);
261 
262 	/* clear BUSY bits */
263 	for (i = 0; i < CONFIG_CORE_COUNT; i++) {
264 		idctfc = idc_read(IPC_IDCTFC(i), core);
265 		if (idctfc & IPC_IDCTFC_BUSY)
266 			idc_write(IPC_IDCTFC(i), core, idctfc);
267 	}
268 
269 	schedule_task_free(&idc->idc_task);
270 }
271