diff options
Diffstat (limited to 'sound/soc/intel/atom/sst/sst_ipc.c')
-rw-r--r-- | sound/soc/intel/atom/sst/sst_ipc.c | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/sound/soc/intel/atom/sst/sst_ipc.c b/sound/soc/intel/atom/sst/sst_ipc.c new file mode 100644 index 000000000000..5a278618466c --- /dev/null +++ b/sound/soc/intel/atom/sst/sst_ipc.c | |||
@@ -0,0 +1,373 @@ | |||
1 | /* | ||
2 | * sst_ipc.c - Intel SST Driver for audio engine | ||
3 | * | ||
4 | * Copyright (C) 2008-14 Intel Corporation | ||
5 | * Authors: Vinod Koul <vinod.koul@intel.com> | ||
6 | * Harsha Priya <priya.harsha@intel.com> | ||
7 | * Dharageswari R <dharageswari.r@intel.com> | ||
8 | * KP Jeeja <jeeja.kp@intel.com> | ||
9 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify | ||
12 | * it under the terms of the GNU General Public License as published by | ||
13 | * the Free Software Foundation; version 2 of the License. | ||
14 | * | ||
15 | * This program is distributed in the hope that it will be useful, but | ||
16 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
18 | * General Public License for more details. | ||
19 | * | ||
20 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
21 | */ | ||
22 | #include <linux/pci.h> | ||
23 | #include <linux/firmware.h> | ||
24 | #include <linux/sched.h> | ||
25 | #include <linux/delay.h> | ||
26 | #include <linux/pm_runtime.h> | ||
27 | #include <sound/core.h> | ||
28 | #include <sound/pcm.h> | ||
29 | #include <sound/soc.h> | ||
30 | #include <sound/compress_driver.h> | ||
31 | #include <asm/intel-mid.h> | ||
32 | #include <asm/platform_sst_audio.h> | ||
33 | #include "../sst-mfld-platform.h" | ||
34 | #include "sst.h" | ||
35 | #include "../../common/sst-dsp.h" | ||
36 | |||
37 | struct sst_block *sst_create_block(struct intel_sst_drv *ctx, | ||
38 | u32 msg_id, u32 drv_id) | ||
39 | { | ||
40 | struct sst_block *msg = NULL; | ||
41 | |||
42 | dev_dbg(ctx->dev, "Enter\n"); | ||
43 | msg = kzalloc(sizeof(*msg), GFP_KERNEL); | ||
44 | if (!msg) | ||
45 | return NULL; | ||
46 | msg->condition = false; | ||
47 | msg->on = true; | ||
48 | msg->msg_id = msg_id; | ||
49 | msg->drv_id = drv_id; | ||
50 | spin_lock_bh(&ctx->block_lock); | ||
51 | list_add_tail(&msg->node, &ctx->block_list); | ||
52 | spin_unlock_bh(&ctx->block_lock); | ||
53 | |||
54 | return msg; | ||
55 | } | ||
56 | |||
57 | /* | ||
58 | * while handling the interrupts, we need to check for message status and | ||
59 | * then if we are blocking for a message | ||
60 | * | ||
61 | * here we are unblocking the blocked ones, this is based on id we have | ||
62 | * passed and search that for block threads. | ||
63 | * We will not find block in two cases | ||
64 | * a) when its small message and block in not there, so silently ignore | ||
65 | * them | ||
66 | * b) when we are actually not able to find the block (bug perhaps) | ||
67 | * | ||
68 | * Since we have bit of small messages we can spam kernel log with err | ||
69 | * print on above so need to keep as debug prints which should be enabled | ||
70 | * via dynamic debug while debugging IPC issues | ||
71 | */ | ||
72 | int sst_wake_up_block(struct intel_sst_drv *ctx, int result, | ||
73 | u32 drv_id, u32 ipc, void *data, u32 size) | ||
74 | { | ||
75 | struct sst_block *block = NULL; | ||
76 | |||
77 | dev_dbg(ctx->dev, "Enter\n"); | ||
78 | |||
79 | spin_lock_bh(&ctx->block_lock); | ||
80 | list_for_each_entry(block, &ctx->block_list, node) { | ||
81 | dev_dbg(ctx->dev, "Block ipc %d, drv_id %d\n", block->msg_id, | ||
82 | block->drv_id); | ||
83 | if (block->msg_id == ipc && block->drv_id == drv_id) { | ||
84 | dev_dbg(ctx->dev, "free up the block\n"); | ||
85 | block->ret_code = result; | ||
86 | block->data = data; | ||
87 | block->size = size; | ||
88 | block->condition = true; | ||
89 | spin_unlock_bh(&ctx->block_lock); | ||
90 | wake_up(&ctx->wait_queue); | ||
91 | return 0; | ||
92 | } | ||
93 | } | ||
94 | spin_unlock_bh(&ctx->block_lock); | ||
95 | dev_dbg(ctx->dev, | ||
96 | "Block not found or a response received for a short msg for ipc %d, drv_id %d\n", | ||
97 | ipc, drv_id); | ||
98 | return -EINVAL; | ||
99 | } | ||
100 | |||
101 | int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed) | ||
102 | { | ||
103 | struct sst_block *block = NULL, *__block; | ||
104 | |||
105 | dev_dbg(ctx->dev, "Enter\n"); | ||
106 | spin_lock_bh(&ctx->block_lock); | ||
107 | list_for_each_entry_safe(block, __block, &ctx->block_list, node) { | ||
108 | if (block == freed) { | ||
109 | pr_debug("pvt_id freed --> %d\n", freed->drv_id); | ||
110 | /* toggle the index position of pvt_id */ | ||
111 | list_del(&freed->node); | ||
112 | spin_unlock_bh(&ctx->block_lock); | ||
113 | kfree(freed->data); | ||
114 | freed->data = NULL; | ||
115 | kfree(freed); | ||
116 | return 0; | ||
117 | } | ||
118 | } | ||
119 | spin_unlock_bh(&ctx->block_lock); | ||
120 | dev_err(ctx->dev, "block is already freed!!!\n"); | ||
121 | return -EINVAL; | ||
122 | } | ||
123 | |||
124 | int sst_post_message_mrfld(struct intel_sst_drv *sst_drv_ctx, | ||
125 | struct ipc_post *ipc_msg, bool sync) | ||
126 | { | ||
127 | struct ipc_post *msg = ipc_msg; | ||
128 | union ipc_header_mrfld header; | ||
129 | unsigned int loop_count = 0; | ||
130 | int retval = 0; | ||
131 | unsigned long irq_flags; | ||
132 | |||
133 | dev_dbg(sst_drv_ctx->dev, "Enter: sync: %d\n", sync); | ||
134 | spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); | ||
135 | header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX); | ||
136 | if (sync) { | ||
137 | while (header.p.header_high.part.busy) { | ||
138 | if (loop_count > 25) { | ||
139 | dev_err(sst_drv_ctx->dev, | ||
140 | "sst: Busy wait failed, cant send this msg\n"); | ||
141 | retval = -EBUSY; | ||
142 | goto out; | ||
143 | } | ||
144 | cpu_relax(); | ||
145 | loop_count++; | ||
146 | header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX); | ||
147 | } | ||
148 | } else { | ||
149 | if (list_empty(&sst_drv_ctx->ipc_dispatch_list)) { | ||
150 | /* queue is empty, nothing to send */ | ||
151 | spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); | ||
152 | dev_dbg(sst_drv_ctx->dev, | ||
153 | "Empty msg queue... NO Action\n"); | ||
154 | return 0; | ||
155 | } | ||
156 | |||
157 | if (header.p.header_high.part.busy) { | ||
158 | spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); | ||
159 | dev_dbg(sst_drv_ctx->dev, "Busy not free... post later\n"); | ||
160 | return 0; | ||
161 | } | ||
162 | |||
163 | /* copy msg from list */ | ||
164 | msg = list_entry(sst_drv_ctx->ipc_dispatch_list.next, | ||
165 | struct ipc_post, node); | ||
166 | list_del(&msg->node); | ||
167 | } | ||
168 | dev_dbg(sst_drv_ctx->dev, "sst: Post message: header = %x\n", | ||
169 | msg->mrfld_header.p.header_high.full); | ||
170 | dev_dbg(sst_drv_ctx->dev, "sst: size = 0x%x\n", | ||
171 | msg->mrfld_header.p.header_low_payload); | ||
172 | |||
173 | if (msg->mrfld_header.p.header_high.part.large) | ||
174 | memcpy_toio(sst_drv_ctx->mailbox + SST_MAILBOX_SEND, | ||
175 | msg->mailbox_data, | ||
176 | msg->mrfld_header.p.header_low_payload); | ||
177 | |||
178 | sst_shim_write64(sst_drv_ctx->shim, SST_IPCX, msg->mrfld_header.full); | ||
179 | |||
180 | out: | ||
181 | spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); | ||
182 | kfree(msg->mailbox_data); | ||
183 | kfree(msg); | ||
184 | return retval; | ||
185 | } | ||
186 | |||
187 | void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx) | ||
188 | { | ||
189 | union interrupt_reg_mrfld isr; | ||
190 | union interrupt_reg_mrfld imr; | ||
191 | union ipc_header_mrfld clear_ipc; | ||
192 | unsigned long irq_flags; | ||
193 | |||
194 | spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); | ||
195 | imr.full = sst_shim_read64(sst_drv_ctx->shim, SST_IMRX); | ||
196 | isr.full = sst_shim_read64(sst_drv_ctx->shim, SST_ISRX); | ||
197 | |||
198 | /* write 1 to clear*/ | ||
199 | isr.part.busy_interrupt = 1; | ||
200 | sst_shim_write64(sst_drv_ctx->shim, SST_ISRX, isr.full); | ||
201 | |||
202 | /* Set IA done bit */ | ||
203 | clear_ipc.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCD); | ||
204 | |||
205 | clear_ipc.p.header_high.part.busy = 0; | ||
206 | clear_ipc.p.header_high.part.done = 1; | ||
207 | clear_ipc.p.header_low_payload = IPC_ACK_SUCCESS; | ||
208 | sst_shim_write64(sst_drv_ctx->shim, SST_IPCD, clear_ipc.full); | ||
209 | /* un mask busy interrupt */ | ||
210 | imr.part.busy_interrupt = 0; | ||
211 | sst_shim_write64(sst_drv_ctx->shim, SST_IMRX, imr.full); | ||
212 | spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); | ||
213 | } | ||
214 | |||
215 | |||
216 | /* | ||
217 | * process_fw_init - process the FW init msg | ||
218 | * | ||
219 | * @msg: IPC message mailbox data from FW | ||
220 | * | ||
221 | * This function processes the FW init msg from FW | ||
222 | * marks FW state and prints debug info of loaded FW | ||
223 | */ | ||
224 | static void process_fw_init(struct intel_sst_drv *sst_drv_ctx, | ||
225 | void *msg) | ||
226 | { | ||
227 | struct ipc_header_fw_init *init = | ||
228 | (struct ipc_header_fw_init *)msg; | ||
229 | int retval = 0; | ||
230 | |||
231 | dev_dbg(sst_drv_ctx->dev, "*** FW Init msg came***\n"); | ||
232 | if (init->result) { | ||
233 | sst_set_fw_state_locked(sst_drv_ctx, SST_RESET); | ||
234 | dev_err(sst_drv_ctx->dev, "FW Init failed, Error %x\n", | ||
235 | init->result); | ||
236 | retval = init->result; | ||
237 | goto ret; | ||
238 | } | ||
239 | |||
240 | ret: | ||
241 | sst_wake_up_block(sst_drv_ctx, retval, FW_DWNL_ID, 0 , NULL, 0); | ||
242 | } | ||
243 | |||
244 | static void process_fw_async_msg(struct intel_sst_drv *sst_drv_ctx, | ||
245 | struct ipc_post *msg) | ||
246 | { | ||
247 | u32 msg_id; | ||
248 | int str_id; | ||
249 | u32 data_size, i; | ||
250 | void *data_offset; | ||
251 | struct stream_info *stream; | ||
252 | union ipc_header_high msg_high; | ||
253 | u32 msg_low, pipe_id; | ||
254 | |||
255 | msg_high = msg->mrfld_header.p.header_high; | ||
256 | msg_low = msg->mrfld_header.p.header_low_payload; | ||
257 | msg_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->cmd_id; | ||
258 | data_offset = (msg->mailbox_data + sizeof(struct ipc_dsp_hdr)); | ||
259 | data_size = msg_low - (sizeof(struct ipc_dsp_hdr)); | ||
260 | |||
261 | switch (msg_id) { | ||
262 | case IPC_SST_PERIOD_ELAPSED_MRFLD: | ||
263 | pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; | ||
264 | str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id); | ||
265 | if (str_id > 0) { | ||
266 | dev_dbg(sst_drv_ctx->dev, | ||
267 | "Period elapsed rcvd for pipe id 0x%x\n", | ||
268 | pipe_id); | ||
269 | stream = &sst_drv_ctx->streams[str_id]; | ||
270 | if (stream->period_elapsed) | ||
271 | stream->period_elapsed(stream->pcm_substream); | ||
272 | if (stream->compr_cb) | ||
273 | stream->compr_cb(stream->compr_cb_param); | ||
274 | } | ||
275 | break; | ||
276 | |||
277 | case IPC_IA_DRAIN_STREAM_MRFLD: | ||
278 | pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; | ||
279 | str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id); | ||
280 | if (str_id > 0) { | ||
281 | stream = &sst_drv_ctx->streams[str_id]; | ||
282 | if (stream->drain_notify) | ||
283 | stream->drain_notify(stream->drain_cb_param); | ||
284 | } | ||
285 | break; | ||
286 | |||
287 | case IPC_IA_FW_ASYNC_ERR_MRFLD: | ||
288 | dev_err(sst_drv_ctx->dev, "FW sent async error msg:\n"); | ||
289 | for (i = 0; i < (data_size/4); i++) | ||
290 | print_hex_dump(KERN_DEBUG, NULL, DUMP_PREFIX_NONE, | ||
291 | 16, 4, data_offset, data_size, false); | ||
292 | break; | ||
293 | |||
294 | case IPC_IA_FW_INIT_CMPLT_MRFLD: | ||
295 | process_fw_init(sst_drv_ctx, data_offset); | ||
296 | break; | ||
297 | |||
298 | case IPC_IA_BUF_UNDER_RUN_MRFLD: | ||
299 | pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; | ||
300 | str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id); | ||
301 | if (str_id > 0) | ||
302 | dev_err(sst_drv_ctx->dev, | ||
303 | "Buffer under-run for pipe:%#x str_id:%d\n", | ||
304 | pipe_id, str_id); | ||
305 | break; | ||
306 | |||
307 | default: | ||
308 | dev_err(sst_drv_ctx->dev, | ||
309 | "Unrecognized async msg from FW msg_id %#x\n", msg_id); | ||
310 | } | ||
311 | } | ||
312 | |||
313 | void sst_process_reply_mrfld(struct intel_sst_drv *sst_drv_ctx, | ||
314 | struct ipc_post *msg) | ||
315 | { | ||
316 | unsigned int drv_id; | ||
317 | void *data; | ||
318 | union ipc_header_high msg_high; | ||
319 | u32 msg_low; | ||
320 | struct ipc_dsp_hdr *dsp_hdr; | ||
321 | unsigned int cmd_id; | ||
322 | |||
323 | msg_high = msg->mrfld_header.p.header_high; | ||
324 | msg_low = msg->mrfld_header.p.header_low_payload; | ||
325 | |||
326 | dev_dbg(sst_drv_ctx->dev, "IPC process message header %x payload %x\n", | ||
327 | msg->mrfld_header.p.header_high.full, | ||
328 | msg->mrfld_header.p.header_low_payload); | ||
329 | |||
330 | drv_id = msg_high.part.drv_id; | ||
331 | |||
332 | /* Check for async messages first */ | ||
333 | if (drv_id == SST_ASYNC_DRV_ID) { | ||
334 | /*FW sent async large message*/ | ||
335 | process_fw_async_msg(sst_drv_ctx, msg); | ||
336 | return; | ||
337 | } | ||
338 | |||
339 | /* FW sent short error response for an IPC */ | ||
340 | if (msg_high.part.result && drv_id && !msg_high.part.large) { | ||
341 | /* 32-bit FW error code in msg_low */ | ||
342 | dev_err(sst_drv_ctx->dev, "FW sent error response 0x%x", msg_low); | ||
343 | sst_wake_up_block(sst_drv_ctx, msg_high.part.result, | ||
344 | msg_high.part.drv_id, | ||
345 | msg_high.part.msg_id, NULL, 0); | ||
346 | return; | ||
347 | } | ||
348 | |||
349 | /* | ||
350 | * Process all valid responses | ||
351 | * if it is a large message, the payload contains the size to | ||
352 | * copy from mailbox | ||
353 | **/ | ||
354 | if (msg_high.part.large) { | ||
355 | data = kzalloc(msg_low, GFP_KERNEL); | ||
356 | if (!data) | ||
357 | return; | ||
358 | memcpy(data, (void *) msg->mailbox_data, msg_low); | ||
359 | /* Copy command id so that we can use to put sst to reset */ | ||
360 | dsp_hdr = (struct ipc_dsp_hdr *)data; | ||
361 | cmd_id = dsp_hdr->cmd_id; | ||
362 | dev_dbg(sst_drv_ctx->dev, "cmd_id %d\n", dsp_hdr->cmd_id); | ||
363 | if (sst_wake_up_block(sst_drv_ctx, msg_high.part.result, | ||
364 | msg_high.part.drv_id, | ||
365 | msg_high.part.msg_id, data, msg_low)) | ||
366 | kfree(data); | ||
367 | } else { | ||
368 | sst_wake_up_block(sst_drv_ctx, msg_high.part.result, | ||
369 | msg_high.part.drv_id, | ||
370 | msg_high.part.msg_id, NULL, 0); | ||
371 | } | ||
372 | |||
373 | } | ||