Commit | Line | Data |
---|---|---|
3d9ff346 VK |
1 | /* |
2 | * sst_stream.c - Intel SST Driver for audio engine | |
3 | * | |
4 | * Copyright (C) 2008-14 Intel Corp | |
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/platform_sst_audio.h> | |
32 | #include "../sst-mfld-platform.h" | |
33 | #include "sst.h" | |
b97169da | 34 | #include "../../common/sst-dsp.h" |
3d9ff346 VK |
35 | |
36 | int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params) | |
37 | { | |
473858ca | 38 | struct snd_pcm_params *pcm_params; |
3d9ff346 VK |
39 | struct snd_sst_params *str_params; |
40 | struct snd_sst_tstamp fw_tstamp; | |
41 | struct stream_info *str_info; | |
473858ca | 42 | int i, num_ch, str_id; |
3d9ff346 VK |
43 | |
44 | dev_dbg(sst_drv_ctx->dev, "Enter\n"); | |
3d9ff346 VK |
45 | |
46 | str_params = (struct snd_sst_params *)params; | |
473858ca HG |
47 | str_id = str_params->stream_id; |
48 | str_info = get_stream_info(sst_drv_ctx, str_id); | |
49 | if (!str_info) | |
50 | return -EINVAL; | |
51 | ||
52 | memset(&str_info->alloc_param, 0, sizeof(str_info->alloc_param)); | |
53 | str_info->alloc_param.operation = str_params->ops; | |
54 | str_info->alloc_param.codec_type = str_params->codec; | |
55 | str_info->alloc_param.sg_count = str_params->aparams.sg_count; | |
56 | str_info->alloc_param.ring_buf_info[0].addr = | |
3d9ff346 | 57 | str_params->aparams.ring_buf_info[0].addr; |
473858ca | 58 | str_info->alloc_param.ring_buf_info[0].size = |
3d9ff346 | 59 | str_params->aparams.ring_buf_info[0].size; |
473858ca | 60 | str_info->alloc_param.frag_size = str_params->aparams.frag_size; |
3d9ff346 | 61 | |
473858ca | 62 | memcpy(&str_info->alloc_param.codec_params, &str_params->sparams, |
3d9ff346 VK |
63 | sizeof(struct snd_sst_stream_params)); |
64 | ||
65 | /* | |
66 | * fill channel map params for multichannel support. | |
67 | * Ideally channel map should be received from upper layers | |
68 | * for multichannel support. | |
69 | * Currently hardcoding as per FW reqm. | |
70 | */ | |
71 | num_ch = sst_get_num_channel(str_params); | |
473858ca | 72 | pcm_params = &str_info->alloc_param.codec_params.uc.pcm_params; |
3d9ff346 VK |
73 | for (i = 0; i < 8; i++) { |
74 | if (i < num_ch) | |
473858ca | 75 | pcm_params->channel_map[i] = i; |
3d9ff346 | 76 | else |
473858ca | 77 | pcm_params->channel_map[i] = 0xff; |
3d9ff346 VK |
78 | } |
79 | ||
8cf732bb HG |
80 | sst_drv_ctx->streams[str_id].status = STREAM_INIT; |
81 | sst_drv_ctx->streams[str_id].prev = STREAM_UN_INIT; | |
473858ca HG |
82 | sst_drv_ctx->streams[str_id].pipe_id = str_params->device_type; |
83 | sst_drv_ctx->streams[str_id].task_id = str_params->task; | |
3d9ff346 VK |
84 | sst_drv_ctx->streams[str_id].num_ch = num_ch; |
85 | ||
86 | if (sst_drv_ctx->info.lpe_viewpt_rqd) | |
473858ca | 87 | str_info->alloc_param.ts = sst_drv_ctx->info.mailbox_start + |
3d9ff346 VK |
88 | sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp)); |
89 | else | |
473858ca | 90 | str_info->alloc_param.ts = sst_drv_ctx->mailbox_add + |
3d9ff346 VK |
91 | sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp)); |
92 | ||
93 | dev_dbg(sst_drv_ctx->dev, "alloc tstamp location = 0x%x\n", | |
473858ca | 94 | str_info->alloc_param.ts); |
3d9ff346 | 95 | dev_dbg(sst_drv_ctx->dev, "assigned pipe id 0x%x to task %d\n", |
473858ca HG |
96 | str_info->pipe_id, str_info->task_id); |
97 | ||
98 | return sst_realloc_stream(sst_drv_ctx, str_id); | |
99 | } | |
100 | ||
101 | /** | |
102 | * sst_realloc_stream - Send msg for (re-)allocating a stream using the | |
103 | * @sst_drv_ctx intel_sst_drv context pointer | |
104 | * @str_id: stream ID | |
105 | * | |
106 | * Send a msg for (re-)allocating a stream using the parameters previously | |
107 | * passed to sst_alloc_stream_mrfld() for the same stream ID. | |
108 | * Return: 0 or negative errno value. | |
109 | */ | |
110 | int sst_realloc_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) | |
111 | { | |
112 | struct snd_sst_alloc_response *response; | |
113 | struct stream_info *str_info; | |
114 | void *data = NULL; | |
115 | int ret; | |
116 | ||
117 | str_info = get_stream_info(sst_drv_ctx, str_id); | |
118 | if (!str_info) | |
119 | return -EINVAL; | |
3d9ff346 | 120 | |
91197a92 | 121 | dev_dbg(sst_drv_ctx->dev, "Alloc for str %d pipe %#x\n", |
473858ca HG |
122 | str_id, str_info->pipe_id); |
123 | ||
124 | ret = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD, | |
125 | IPC_IA_ALLOC_STREAM_MRFLD, str_info->pipe_id, | |
126 | sizeof(str_info->alloc_param), &str_info->alloc_param, | |
127 | &data, true, true, false, true); | |
3d9ff346 VK |
128 | |
129 | if (ret < 0) { | |
130 | dev_err(sst_drv_ctx->dev, "FW alloc failed ret %d\n", ret); | |
131 | /* alloc failed, so reset the state to uninit */ | |
132 | str_info->status = STREAM_UN_INIT; | |
133 | str_id = ret; | |
134 | } else if (data) { | |
135 | response = (struct snd_sst_alloc_response *)data; | |
136 | ret = response->str_type.result; | |
137 | if (!ret) | |
138 | goto out; | |
139 | dev_err(sst_drv_ctx->dev, "FW alloc failed ret %d\n", ret); | |
140 | if (ret == SST_ERR_STREAM_IN_USE) { | |
141 | dev_err(sst_drv_ctx->dev, | |
142 | "FW not in clean state, send free for:%d\n", str_id); | |
143 | sst_free_stream(sst_drv_ctx, str_id); | |
144 | } | |
145 | str_id = -ret; | |
146 | } | |
147 | out: | |
148 | kfree(data); | |
149 | return str_id; | |
150 | } | |
151 | ||
152 | /** | |
153 | * sst_start_stream - Send msg for a starting stream | |
154 | * @str_id: stream ID | |
155 | * | |
156 | * This function is called by any function which wants to start | |
157 | * a stream. | |
158 | */ | |
159 | int sst_start_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) | |
160 | { | |
161 | int retval = 0; | |
162 | struct stream_info *str_info; | |
163 | u16 data = 0; | |
164 | ||
165 | dev_dbg(sst_drv_ctx->dev, "sst_start_stream for %d\n", str_id); | |
166 | str_info = get_stream_info(sst_drv_ctx, str_id); | |
167 | if (!str_info) | |
168 | return -EINVAL; | |
169 | if (str_info->status != STREAM_RUNNING) | |
170 | return -EBADRQC; | |
171 | ||
172 | retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, | |
173 | IPC_CMD, IPC_IA_START_STREAM_MRFLD, str_info->pipe_id, | |
174 | sizeof(u16), &data, NULL, true, true, true, false); | |
175 | ||
176 | return retval; | |
177 | } | |
178 | ||
179 | int sst_send_byte_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, | |
180 | struct snd_sst_bytes_v2 *bytes) | |
181 | { struct ipc_post *msg = NULL; | |
182 | u32 length; | |
183 | int pvt_id, ret = 0; | |
184 | struct sst_block *block = NULL; | |
185 | ||
186 | dev_dbg(sst_drv_ctx->dev, | |
187 | "type:%u ipc_msg:%u block:%u task_id:%u pipe: %#x length:%#x\n", | |
188 | bytes->type, bytes->ipc_msg, bytes->block, bytes->task_id, | |
189 | bytes->pipe_id, bytes->len); | |
190 | ||
191 | if (sst_create_ipc_msg(&msg, true)) | |
192 | return -ENOMEM; | |
193 | ||
194 | pvt_id = sst_assign_pvt_id(sst_drv_ctx); | |
195 | sst_fill_header_mrfld(&msg->mrfld_header, bytes->ipc_msg, | |
196 | bytes->task_id, 1, pvt_id); | |
197 | msg->mrfld_header.p.header_high.part.res_rqd = bytes->block; | |
198 | length = bytes->len; | |
199 | msg->mrfld_header.p.header_low_payload = length; | |
200 | dev_dbg(sst_drv_ctx->dev, "length is %d\n", length); | |
201 | memcpy(msg->mailbox_data, &bytes->bytes, bytes->len); | |
202 | if (bytes->block) { | |
203 | block = sst_create_block(sst_drv_ctx, bytes->ipc_msg, pvt_id); | |
204 | if (block == NULL) { | |
205 | kfree(msg); | |
206 | ret = -ENOMEM; | |
207 | goto out; | |
208 | } | |
209 | } | |
210 | ||
211 | sst_add_to_dispatch_list_and_post(sst_drv_ctx, msg); | |
212 | dev_dbg(sst_drv_ctx->dev, "msg->mrfld_header.p.header_low_payload:%d", | |
213 | msg->mrfld_header.p.header_low_payload); | |
214 | ||
215 | if (bytes->block) { | |
216 | ret = sst_wait_timeout(sst_drv_ctx, block); | |
217 | if (ret) { | |
218 | dev_err(sst_drv_ctx->dev, "fw returned err %d\n", ret); | |
219 | sst_free_block(sst_drv_ctx, block); | |
220 | goto out; | |
221 | } | |
222 | } | |
223 | if (bytes->type == SND_SST_BYTES_GET) { | |
224 | /* | |
225 | * copy the reply and send back | |
226 | * we need to update only sz and payload | |
227 | */ | |
228 | if (bytes->block) { | |
229 | unsigned char *r = block->data; | |
230 | ||
231 | dev_dbg(sst_drv_ctx->dev, "read back %d bytes", | |
232 | bytes->len); | |
233 | memcpy(bytes->bytes, r, bytes->len); | |
234 | } | |
235 | } | |
236 | if (bytes->block) | |
237 | sst_free_block(sst_drv_ctx, block); | |
238 | out: | |
239 | test_and_clear_bit(pvt_id, &sst_drv_ctx->pvt_id); | |
eaadb1ca | 240 | return ret; |
3d9ff346 VK |
241 | } |
242 | ||
25f3fd04 | 243 | /** |
3d9ff346 VK |
244 | * sst_pause_stream - Send msg for a pausing stream |
245 | * @str_id: stream ID | |
246 | * | |
247 | * This function is called by any function which wants to pause | |
248 | * an already running stream. | |
249 | */ | |
250 | int sst_pause_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) | |
251 | { | |
252 | int retval = 0; | |
253 | struct stream_info *str_info; | |
254 | ||
255 | dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_pause_stream for %d\n", str_id); | |
256 | str_info = get_stream_info(sst_drv_ctx, str_id); | |
257 | if (!str_info) | |
258 | return -EINVAL; | |
259 | if (str_info->status == STREAM_PAUSED) | |
260 | return 0; | |
261 | if (str_info->status == STREAM_RUNNING || | |
262 | str_info->status == STREAM_INIT) { | |
263 | if (str_info->prev == STREAM_UN_INIT) | |
264 | return -EBADRQC; | |
265 | ||
266 | retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD, | |
267 | IPC_IA_PAUSE_STREAM_MRFLD, str_info->pipe_id, | |
268 | 0, NULL, NULL, true, true, false, true); | |
269 | ||
270 | if (retval == 0) { | |
271 | str_info->prev = str_info->status; | |
272 | str_info->status = STREAM_PAUSED; | |
6f506277 | 273 | } else if (retval == -SST_ERR_INVALID_STREAM_ID) { |
3d9ff346 VK |
274 | retval = -EINVAL; |
275 | mutex_lock(&sst_drv_ctx->sst_lock); | |
276 | sst_clean_stream(str_info); | |
277 | mutex_unlock(&sst_drv_ctx->sst_lock); | |
278 | } | |
279 | } else { | |
280 | retval = -EBADRQC; | |
25f3fd04 | 281 | dev_dbg(sst_drv_ctx->dev, "SST DBG:BADRQC for stream\n"); |
3d9ff346 VK |
282 | } |
283 | ||
284 | return retval; | |
285 | } | |
286 | ||
287 | /** | |
288 | * sst_resume_stream - Send msg for resuming stream | |
289 | * @str_id: stream ID | |
290 | * | |
291 | * This function is called by any function which wants to resume | |
292 | * an already paused stream. | |
293 | */ | |
294 | int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) | |
295 | { | |
296 | int retval = 0; | |
297 | struct stream_info *str_info; | |
298 | ||
299 | dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_resume_stream for %d\n", str_id); | |
300 | str_info = get_stream_info(sst_drv_ctx, str_id); | |
301 | if (!str_info) | |
302 | return -EINVAL; | |
303 | if (str_info->status == STREAM_RUNNING) | |
25f3fd04 | 304 | return 0; |
bf642bf5 HG |
305 | |
306 | if (str_info->resume_status == STREAM_PAUSED && | |
307 | str_info->resume_prev == STREAM_RUNNING) { | |
308 | /* | |
309 | * Stream was running before suspend and re-created on resume, | |
310 | * start it to get back to running state. | |
311 | */ | |
312 | dev_dbg(sst_drv_ctx->dev, "restart recreated stream after resume\n"); | |
313 | str_info->status = STREAM_RUNNING; | |
314 | str_info->prev = STREAM_PAUSED; | |
315 | retval = sst_start_stream(sst_drv_ctx, str_id); | |
316 | str_info->resume_status = STREAM_UN_INIT; | |
317 | } else if (str_info->resume_status == STREAM_PAUSED && | |
318 | str_info->resume_prev == STREAM_INIT) { | |
319 | /* | |
320 | * Stream was idle before suspend and re-created on resume, | |
321 | * keep it as is. | |
322 | */ | |
323 | dev_dbg(sst_drv_ctx->dev, "leaving recreated stream idle after resume\n"); | |
324 | str_info->status = STREAM_INIT; | |
325 | str_info->prev = STREAM_PAUSED; | |
326 | str_info->resume_status = STREAM_UN_INIT; | |
327 | } else if (str_info->status == STREAM_PAUSED) { | |
3d9ff346 VK |
328 | retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, |
329 | IPC_CMD, IPC_IA_RESUME_STREAM_MRFLD, | |
330 | str_info->pipe_id, 0, NULL, NULL, | |
331 | true, true, false, true); | |
332 | ||
333 | if (!retval) { | |
334 | if (str_info->prev == STREAM_RUNNING) | |
335 | str_info->status = STREAM_RUNNING; | |
336 | else | |
337 | str_info->status = STREAM_INIT; | |
338 | str_info->prev = STREAM_PAUSED; | |
339 | } else if (retval == -SST_ERR_INVALID_STREAM_ID) { | |
340 | retval = -EINVAL; | |
341 | mutex_lock(&sst_drv_ctx->sst_lock); | |
342 | sst_clean_stream(str_info); | |
343 | mutex_unlock(&sst_drv_ctx->sst_lock); | |
344 | } | |
345 | } else { | |
346 | retval = -EBADRQC; | |
347 | dev_err(sst_drv_ctx->dev, "SST ERR: BADQRC for stream\n"); | |
348 | } | |
349 | ||
350 | return retval; | |
351 | } | |
352 | ||
353 | ||
354 | /** | |
355 | * sst_drop_stream - Send msg for stopping stream | |
356 | * @str_id: stream ID | |
357 | * | |
358 | * This function is called by any function which wants to stop | |
359 | * a stream. | |
360 | */ | |
361 | int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) | |
362 | { | |
363 | int retval = 0; | |
364 | struct stream_info *str_info; | |
365 | ||
366 | dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_drop_stream for %d\n", str_id); | |
367 | str_info = get_stream_info(sst_drv_ctx, str_id); | |
368 | if (!str_info) | |
369 | return -EINVAL; | |
370 | ||
371 | if (str_info->status != STREAM_UN_INIT) { | |
372 | str_info->prev = STREAM_UN_INIT; | |
373 | str_info->status = STREAM_INIT; | |
374 | str_info->cumm_bytes = 0; | |
375 | retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, | |
376 | IPC_CMD, IPC_IA_DROP_STREAM_MRFLD, | |
377 | str_info->pipe_id, 0, NULL, NULL, | |
378 | true, true, true, false); | |
379 | } else { | |
380 | retval = -EBADRQC; | |
381 | dev_dbg(sst_drv_ctx->dev, "BADQRC for stream, state %x\n", | |
382 | str_info->status); | |
383 | } | |
384 | return retval; | |
385 | } | |
386 | ||
387 | /** | |
388 | * sst_drain_stream - Send msg for draining stream | |
389 | * @str_id: stream ID | |
390 | * | |
391 | * This function is called by any function which wants to drain | |
392 | * a stream. | |
393 | */ | |
394 | int sst_drain_stream(struct intel_sst_drv *sst_drv_ctx, | |
395 | int str_id, bool partial_drain) | |
396 | { | |
397 | int retval = 0; | |
398 | struct stream_info *str_info; | |
399 | ||
400 | dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_drain_stream for %d\n", str_id); | |
401 | str_info = get_stream_info(sst_drv_ctx, str_id); | |
402 | if (!str_info) | |
403 | return -EINVAL; | |
404 | if (str_info->status != STREAM_RUNNING && | |
405 | str_info->status != STREAM_INIT && | |
406 | str_info->status != STREAM_PAUSED) { | |
407 | dev_err(sst_drv_ctx->dev, "SST ERR: BADQRC for stream = %d\n", | |
408 | str_info->status); | |
409 | return -EBADRQC; | |
410 | } | |
411 | ||
412 | retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD, | |
413 | IPC_IA_DRAIN_STREAM_MRFLD, str_info->pipe_id, | |
414 | sizeof(u8), &partial_drain, NULL, true, true, false, false); | |
415 | /* | |
416 | * with new non blocked drain implementation in core we dont need to | |
417 | * wait for respsonse, and need to only invoke callback for drain | |
418 | * complete | |
419 | */ | |
420 | ||
421 | return retval; | |
422 | } | |
423 | ||
424 | /** | |
425 | * sst_free_stream - Frees a stream | |
426 | * @str_id: stream ID | |
427 | * | |
428 | * This function is called by any function which wants to free | |
429 | * a stream. | |
430 | */ | |
431 | int sst_free_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) | |
432 | { | |
433 | int retval = 0; | |
434 | struct stream_info *str_info; | |
3d9ff346 VK |
435 | |
436 | dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_free_stream for %d\n", str_id); | |
437 | ||
438 | mutex_lock(&sst_drv_ctx->sst_lock); | |
439 | if (sst_drv_ctx->sst_state == SST_RESET) { | |
440 | mutex_unlock(&sst_drv_ctx->sst_lock); | |
441 | return -ENODEV; | |
442 | } | |
443 | mutex_unlock(&sst_drv_ctx->sst_lock); | |
444 | str_info = get_stream_info(sst_drv_ctx, str_id); | |
445 | if (!str_info) | |
446 | return -EINVAL; | |
3d9ff346 VK |
447 | |
448 | mutex_lock(&str_info->lock); | |
449 | if (str_info->status != STREAM_UN_INIT) { | |
450 | str_info->prev = str_info->status; | |
451 | str_info->status = STREAM_UN_INIT; | |
452 | mutex_unlock(&str_info->lock); | |
453 | ||
91197a92 | 454 | dev_dbg(sst_drv_ctx->dev, "Free for str %d pipe %#x\n", |
3d9ff346 VK |
455 | str_id, str_info->pipe_id); |
456 | retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD, | |
457 | IPC_IA_FREE_STREAM_MRFLD, str_info->pipe_id, 0, | |
458 | NULL, NULL, true, true, false, true); | |
459 | ||
460 | dev_dbg(sst_drv_ctx->dev, "sst: wait for free returned %d\n", | |
461 | retval); | |
462 | mutex_lock(&sst_drv_ctx->sst_lock); | |
463 | sst_clean_stream(str_info); | |
464 | mutex_unlock(&sst_drv_ctx->sst_lock); | |
465 | dev_dbg(sst_drv_ctx->dev, "SST DBG:Stream freed\n"); | |
466 | } else { | |
467 | mutex_unlock(&str_info->lock); | |
468 | retval = -EBADRQC; | |
469 | dev_dbg(sst_drv_ctx->dev, "SST DBG:BADQRC for stream\n"); | |
470 | } | |
471 | ||
472 | return retval; | |
473 | } |