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