Commit | Line | Data |
---|---|---|
54b4856f CG |
1 | /* |
2 | * sound.c - Audio Application Interface Module for Mostcore | |
3 | * | |
4 | * Copyright (C) 2015 Microchip Technology Germany II GmbH & Co. KG | |
5 | * | |
6 | * This program is distributed in the hope that it will be useful, | |
7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
9 | * GNU General Public License for more details. | |
10 | * | |
11 | * This file is licensed under GPLv2. | |
12 | */ | |
13 | ||
14 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
15 | ||
16 | #include <linux/module.h> | |
17 | #include <linux/printk.h> | |
18 | #include <linux/kernel.h> | |
19 | #include <linux/init.h> | |
20 | #include <sound/core.h> | |
21 | #include <sound/pcm.h> | |
8e4a0ef1 | 22 | #include <sound/pcm_params.h> |
54b4856f CG |
23 | #include <linux/sched.h> |
24 | #include <linux/kthread.h> | |
25 | #include <mostcore.h> | |
26 | ||
27 | #define DRIVER_NAME "sound" | |
28 | ||
29 | static struct list_head dev_list; | |
f13f6981 | 30 | static struct most_aim audio_aim; |
54b4856f CG |
31 | |
32 | /** | |
33 | * struct channel - private structure to keep channel specific data | |
34 | * @substream: stores the substream structure | |
35 | * @iface: interface for which the channel belongs to | |
36 | * @cfg: channel configuration | |
37 | * @card: registered sound card | |
38 | * @list: list for private use | |
39 | * @id: channel index | |
40 | * @period_pos: current period position (ring buffer) | |
41 | * @buffer_pos: current buffer position (ring buffer) | |
42 | * @is_stream_running: identifies whether a stream is running or not | |
43 | * @opened: set when the stream is opened | |
44 | * @playback_task: playback thread | |
45 | * @playback_waitq: waitq used by playback thread | |
46 | */ | |
47 | struct channel { | |
48 | struct snd_pcm_substream *substream; | |
d8018872 | 49 | struct snd_pcm_hardware pcm_hardware; |
54b4856f CG |
50 | struct most_interface *iface; |
51 | struct most_channel_config *cfg; | |
52 | struct snd_card *card; | |
53 | struct list_head list; | |
54 | int id; | |
55 | unsigned int period_pos; | |
56 | unsigned int buffer_pos; | |
57 | bool is_stream_running; | |
58 | ||
59 | struct task_struct *playback_task; | |
60 | wait_queue_head_t playback_waitq; | |
61 | ||
62 | void (*copy_fn)(void *alsa, void *most, unsigned int bytes); | |
63 | }; | |
64 | ||
65 | #define MOST_PCM_INFO (SNDRV_PCM_INFO_MMAP | \ | |
66 | SNDRV_PCM_INFO_MMAP_VALID | \ | |
67 | SNDRV_PCM_INFO_BATCH | \ | |
68 | SNDRV_PCM_INFO_INTERLEAVED | \ | |
69 | SNDRV_PCM_INFO_BLOCK_TRANSFER) | |
70 | ||
71 | /** | |
72 | * Initialization of struct snd_pcm_hardware | |
73 | */ | |
d8018872 CG |
74 | static void init_pcm_hardware(struct snd_pcm_hardware *pcm_hw) |
75 | { | |
76 | pcm_hw->info = MOST_PCM_INFO; | |
77 | pcm_hw->rates = SNDRV_PCM_RATE_48000; | |
78 | pcm_hw->rate_min = 48000; | |
79 | pcm_hw->rate_max = 48000; | |
54b4856f CG |
80 | }; |
81 | ||
82 | #define swap16(val) ( \ | |
83 | (((u16)(val) << 8) & (u16)0xFF00) | \ | |
84 | (((u16)(val) >> 8) & (u16)0x00FF)) | |
85 | ||
86 | #define swap32(val) ( \ | |
87 | (((u32)(val) << 24) & (u32)0xFF000000) | \ | |
88 | (((u32)(val) << 8) & (u32)0x00FF0000) | \ | |
89 | (((u32)(val) >> 8) & (u32)0x0000FF00) | \ | |
90 | (((u32)(val) >> 24) & (u32)0x000000FF)) | |
91 | ||
92 | static void swap_copy16(u16 *dest, const u16 *source, unsigned int bytes) | |
93 | { | |
94 | unsigned int i = 0; | |
95 | ||
96 | while (i < (bytes / 2)) { | |
97 | dest[i] = swap16(source[i]); | |
98 | i++; | |
99 | } | |
100 | } | |
101 | ||
102 | static void swap_copy24(u8 *dest, const u8 *source, unsigned int bytes) | |
103 | { | |
104 | unsigned int i = 0; | |
105 | ||
106 | while (i < bytes - 2) { | |
107 | dest[i] = source[i + 2]; | |
108 | dest[i + 1] = source[i + 1]; | |
109 | dest[i + 2] = source[i]; | |
110 | i += 3; | |
111 | } | |
112 | } | |
113 | ||
114 | static void swap_copy32(u32 *dest, const u32 *source, unsigned int bytes) | |
115 | { | |
116 | unsigned int i = 0; | |
117 | ||
118 | while (i < bytes / 4) { | |
119 | dest[i] = swap32(source[i]); | |
120 | i++; | |
121 | } | |
122 | } | |
123 | ||
124 | static void alsa_to_most_memcpy(void *alsa, void *most, unsigned int bytes) | |
125 | { | |
126 | memcpy(most, alsa, bytes); | |
127 | } | |
128 | ||
129 | static void alsa_to_most_copy16(void *alsa, void *most, unsigned int bytes) | |
130 | { | |
131 | swap_copy16(most, alsa, bytes); | |
132 | } | |
133 | ||
134 | static void alsa_to_most_copy24(void *alsa, void *most, unsigned int bytes) | |
135 | { | |
136 | swap_copy24(most, alsa, bytes); | |
137 | } | |
138 | ||
139 | static void alsa_to_most_copy32(void *alsa, void *most, unsigned int bytes) | |
140 | { | |
141 | swap_copy32(most, alsa, bytes); | |
142 | } | |
143 | ||
144 | static void most_to_alsa_memcpy(void *alsa, void *most, unsigned int bytes) | |
145 | { | |
146 | memcpy(alsa, most, bytes); | |
147 | } | |
148 | ||
149 | static void most_to_alsa_copy16(void *alsa, void *most, unsigned int bytes) | |
150 | { | |
151 | swap_copy16(alsa, most, bytes); | |
152 | } | |
153 | ||
154 | static void most_to_alsa_copy24(void *alsa, void *most, unsigned int bytes) | |
155 | { | |
156 | swap_copy24(alsa, most, bytes); | |
157 | } | |
158 | ||
159 | static void most_to_alsa_copy32(void *alsa, void *most, unsigned int bytes) | |
160 | { | |
161 | swap_copy32(alsa, most, bytes); | |
162 | } | |
163 | ||
164 | /** | |
165 | * get_channel - get pointer to channel | |
166 | * @iface: interface structure | |
167 | * @channel_id: channel ID | |
168 | * | |
169 | * This traverses the channel list and returns the channel matching the | |
170 | * ID and interface. | |
171 | * | |
172 | * Returns pointer to channel on success or NULL otherwise. | |
173 | */ | |
174 | static struct channel *get_channel(struct most_interface *iface, | |
175 | int channel_id) | |
176 | { | |
177 | struct channel *channel, *tmp; | |
178 | ||
179 | list_for_each_entry_safe(channel, tmp, &dev_list, list) { | |
180 | if ((channel->iface == iface) && (channel->id == channel_id)) | |
181 | return channel; | |
182 | } | |
183 | ||
184 | return NULL; | |
185 | } | |
186 | ||
187 | /** | |
188 | * copy_data - implements data copying function | |
189 | * @channel: channel | |
190 | * @mbo: MBO from core | |
191 | * | |
192 | * Copy data from/to ring buffer to/from MBO and update the buffer position | |
193 | */ | |
194 | static bool copy_data(struct channel *channel, struct mbo *mbo) | |
195 | { | |
196 | struct snd_pcm_runtime *const runtime = channel->substream->runtime; | |
197 | unsigned int const frame_bytes = channel->cfg->subbuffer_size; | |
198 | unsigned int const buffer_size = runtime->buffer_size; | |
199 | unsigned int frames; | |
200 | unsigned int fr0; | |
201 | ||
202 | if (channel->cfg->direction & MOST_CH_RX) | |
203 | frames = mbo->processed_length / frame_bytes; | |
204 | else | |
205 | frames = mbo->buffer_length / frame_bytes; | |
206 | fr0 = min(buffer_size - channel->buffer_pos, frames); | |
207 | ||
208 | channel->copy_fn(runtime->dma_area + channel->buffer_pos * frame_bytes, | |
209 | mbo->virt_address, | |
210 | fr0 * frame_bytes); | |
211 | ||
212 | if (frames > fr0) { | |
213 | /* wrap around at end of ring buffer */ | |
214 | channel->copy_fn(runtime->dma_area, | |
215 | mbo->virt_address + fr0 * frame_bytes, | |
216 | (frames - fr0) * frame_bytes); | |
217 | } | |
218 | ||
219 | channel->buffer_pos += frames; | |
220 | if (channel->buffer_pos >= buffer_size) | |
221 | channel->buffer_pos -= buffer_size; | |
222 | channel->period_pos += frames; | |
223 | if (channel->period_pos >= runtime->period_size) { | |
224 | channel->period_pos -= runtime->period_size; | |
225 | return true; | |
226 | } | |
227 | ||
228 | return false; | |
229 | } | |
230 | ||
231 | /** | |
232 | * playback_thread - function implements the playback thread | |
233 | * @data: private data | |
234 | * | |
235 | * Thread which does the playback functionality in a loop. It waits for a free | |
236 | * MBO from mostcore for a particular channel and copy the data from ring buffer | |
237 | * to MBO. Submit the MBO back to mostcore, after copying the data. | |
238 | * | |
239 | * Returns 0 on success or error code otherwise. | |
240 | */ | |
241 | static int playback_thread(void *data) | |
242 | { | |
243 | struct channel *const channel = data; | |
244 | ||
54b4856f CG |
245 | while (!kthread_should_stop()) { |
246 | struct mbo *mbo = NULL; | |
247 | bool period_elapsed = false; | |
248 | int ret; | |
249 | ||
250 | wait_event_interruptible( | |
251 | channel->playback_waitq, | |
252 | kthread_should_stop() || | |
71457d48 CG |
253 | (mbo = most_get_mbo(channel->iface, channel->id, |
254 | &audio_aim))); | |
54b4856f CG |
255 | |
256 | if (!mbo) | |
257 | continue; | |
258 | ||
259 | if (channel->is_stream_running) | |
260 | period_elapsed = copy_data(channel, mbo); | |
261 | else | |
262 | memset(mbo->virt_address, 0, mbo->buffer_length); | |
263 | ||
264 | ret = most_submit_mbo(mbo); | |
265 | if (ret) | |
266 | channel->is_stream_running = false; | |
267 | ||
268 | if (period_elapsed) | |
269 | snd_pcm_period_elapsed(channel->substream); | |
270 | } | |
271 | ||
272 | return 0; | |
273 | } | |
274 | ||
275 | /** | |
276 | * pcm_open - implements open callback function for PCM middle layer | |
277 | * @substream: pointer to ALSA PCM substream | |
278 | * | |
279 | * This is called when a PCM substream is opened. At least, the function should | |
280 | * initialize the runtime->hw record. | |
281 | * | |
282 | * Returns 0 on success or error code otherwise. | |
283 | */ | |
284 | static int pcm_open(struct snd_pcm_substream *substream) | |
285 | { | |
286 | struct channel *channel = substream->private_data; | |
287 | struct snd_pcm_runtime *runtime = substream->runtime; | |
288 | struct most_channel_config *cfg = channel->cfg; | |
289 | ||
54b4856f CG |
290 | channel->substream = substream; |
291 | ||
292 | if (cfg->direction == MOST_CH_TX) { | |
293 | init_waitqueue_head(&channel->playback_waitq); | |
294 | channel->playback_task = kthread_run(&playback_thread, channel, | |
295 | "most_audio_playback"); | |
296 | if (IS_ERR(channel->playback_task)) | |
297 | return PTR_ERR(channel->playback_task); | |
298 | } | |
299 | ||
f13f6981 | 300 | if (most_start_channel(channel->iface, channel->id, &audio_aim)) { |
54b4856f CG |
301 | pr_err("most_start_channel() failed!\n"); |
302 | if (cfg->direction == MOST_CH_TX) | |
303 | kthread_stop(channel->playback_task); | |
304 | return -EBUSY; | |
305 | } | |
306 | ||
d8018872 | 307 | runtime->hw = channel->pcm_hardware; |
54b4856f CG |
308 | runtime->hw.buffer_bytes_max = cfg->num_buffers * cfg->buffer_size; |
309 | runtime->hw.period_bytes_min = cfg->buffer_size; | |
310 | runtime->hw.period_bytes_max = cfg->buffer_size; | |
311 | runtime->hw.periods_min = 1; | |
312 | runtime->hw.periods_max = cfg->num_buffers; | |
313 | ||
314 | return 0; | |
315 | } | |
316 | ||
317 | /** | |
318 | * pcm_close - implements close callback function for PCM middle layer | |
319 | * @substream: sub-stream pointer | |
320 | * | |
321 | * Obviously, this is called when a PCM substream is closed. Any private | |
322 | * instance for a PCM substream allocated in the open callback will be | |
323 | * released here. | |
324 | * | |
325 | * Returns 0 on success or error code otherwise. | |
326 | */ | |
327 | static int pcm_close(struct snd_pcm_substream *substream) | |
328 | { | |
329 | struct channel *channel = substream->private_data; | |
330 | ||
54b4856f CG |
331 | if (channel->cfg->direction == MOST_CH_TX) |
332 | kthread_stop(channel->playback_task); | |
f13f6981 | 333 | most_stop_channel(channel->iface, channel->id, &audio_aim); |
54b4856f CG |
334 | |
335 | return 0; | |
336 | } | |
337 | ||
338 | /** | |
339 | * pcm_hw_params - implements hw_params callback function for PCM middle layer | |
340 | * @substream: sub-stream pointer | |
341 | * @hw_params: contains the hardware parameters set by the application | |
342 | * | |
343 | * This is called when the hardware parameters is set by the application, that | |
344 | * is, once when the buffer size, the period size, the format, etc. are defined | |
345 | * for the PCM substream. Many hardware setups should be done is this callback, | |
346 | * including the allocation of buffers. | |
347 | * | |
348 | * Returns 0 on success or error code otherwise. | |
349 | */ | |
350 | static int pcm_hw_params(struct snd_pcm_substream *substream, | |
351 | struct snd_pcm_hw_params *hw_params) | |
352 | { | |
8e4a0ef1 | 353 | int ret; |
d8018872 | 354 | struct channel *channel = substream->private_data; |
8e4a0ef1 | 355 | |
d8018872 CG |
356 | if ((params_channels(hw_params) > channel->pcm_hardware.channels_max) || |
357 | (params_channels(hw_params) < channel->pcm_hardware.channels_min) || | |
358 | !(params_format(hw_params) != channel->pcm_hardware.formats)) | |
8e4a0ef1 CG |
359 | return -EINVAL; |
360 | ret = snd_pcm_lib_alloc_vmalloc_buffer(substream, | |
d8018872 | 361 | params_buffer_bytes(hw_params)); |
8e4a0ef1 CG |
362 | |
363 | return ret; | |
54b4856f CG |
364 | } |
365 | ||
366 | /** | |
367 | * pcm_hw_free - implements hw_free callback function for PCM middle layer | |
368 | * @substream: substream pointer | |
369 | * | |
370 | * This is called to release the resources allocated via hw_params. | |
371 | * This function will be always called before the close callback is called. | |
372 | * | |
373 | * Returns 0 on success or error code otherwise. | |
374 | */ | |
375 | static int pcm_hw_free(struct snd_pcm_substream *substream) | |
376 | { | |
54b4856f CG |
377 | return snd_pcm_lib_free_vmalloc_buffer(substream); |
378 | } | |
379 | ||
380 | /** | |
381 | * pcm_prepare - implements prepare callback function for PCM middle layer | |
382 | * @substream: substream pointer | |
383 | * | |
384 | * This callback is called when the PCM is "prepared". Format rate, sample rate, | |
385 | * etc., can be set here. This callback can be called many times at each setup. | |
386 | * | |
387 | * Returns 0 on success or error code otherwise. | |
388 | */ | |
389 | static int pcm_prepare(struct snd_pcm_substream *substream) | |
390 | { | |
391 | struct channel *channel = substream->private_data; | |
392 | struct snd_pcm_runtime *runtime = substream->runtime; | |
393 | struct most_channel_config *cfg = channel->cfg; | |
394 | int width = snd_pcm_format_physical_width(runtime->format); | |
395 | ||
396 | channel->copy_fn = NULL; | |
397 | ||
398 | if (cfg->direction == MOST_CH_TX) { | |
399 | if (snd_pcm_format_big_endian(runtime->format) || width == 8) | |
400 | channel->copy_fn = alsa_to_most_memcpy; | |
401 | else if (width == 16) | |
402 | channel->copy_fn = alsa_to_most_copy16; | |
403 | else if (width == 24) | |
404 | channel->copy_fn = alsa_to_most_copy24; | |
405 | else if (width == 32) | |
406 | channel->copy_fn = alsa_to_most_copy32; | |
407 | } else { | |
408 | if (snd_pcm_format_big_endian(runtime->format) || width == 8) | |
409 | channel->copy_fn = most_to_alsa_memcpy; | |
410 | else if (width == 16) | |
411 | channel->copy_fn = most_to_alsa_copy16; | |
412 | else if (width == 24) | |
413 | channel->copy_fn = most_to_alsa_copy24; | |
414 | else if (width == 32) | |
415 | channel->copy_fn = most_to_alsa_copy32; | |
416 | } | |
417 | ||
418 | if (!channel->copy_fn) { | |
419 | pr_err("unsupported format\n"); | |
420 | return -EINVAL; | |
421 | } | |
422 | ||
423 | channel->period_pos = 0; | |
424 | channel->buffer_pos = 0; | |
425 | ||
426 | return 0; | |
427 | } | |
428 | ||
429 | /** | |
430 | * pcm_trigger - implements trigger callback function for PCM middle layer | |
431 | * @substream: substream pointer | |
432 | * @cmd: action to perform | |
433 | * | |
434 | * This is called when the PCM is started, stopped or paused. The action will be | |
435 | * specified in the second argument, SNDRV_PCM_TRIGGER_XXX | |
436 | * | |
437 | * Returns 0 on success or error code otherwise. | |
438 | */ | |
439 | static int pcm_trigger(struct snd_pcm_substream *substream, int cmd) | |
440 | { | |
441 | struct channel *channel = substream->private_data; | |
442 | ||
443 | switch (cmd) { | |
444 | case SNDRV_PCM_TRIGGER_START: | |
445 | channel->is_stream_running = true; | |
446 | return 0; | |
447 | ||
448 | case SNDRV_PCM_TRIGGER_STOP: | |
449 | channel->is_stream_running = false; | |
450 | return 0; | |
451 | ||
452 | default: | |
453 | pr_info("pcm_trigger(), invalid\n"); | |
454 | return -EINVAL; | |
455 | } | |
456 | return 0; | |
457 | } | |
458 | ||
459 | /** | |
460 | * pcm_pointer - implements pointer callback function for PCM middle layer | |
461 | * @substream: substream pointer | |
462 | * | |
463 | * This callback is called when the PCM middle layer inquires the current | |
464 | * hardware position on the buffer. The position must be returned in frames, | |
465 | * ranging from 0 to buffer_size-1. | |
466 | */ | |
467 | static snd_pcm_uframes_t pcm_pointer(struct snd_pcm_substream *substream) | |
468 | { | |
469 | struct channel *channel = substream->private_data; | |
470 | ||
471 | return channel->buffer_pos; | |
472 | } | |
473 | ||
474 | /** | |
475 | * Initialization of struct snd_pcm_ops | |
476 | */ | |
477 | static struct snd_pcm_ops pcm_ops = { | |
478 | .open = pcm_open, | |
479 | .close = pcm_close, | |
480 | .ioctl = snd_pcm_lib_ioctl, | |
481 | .hw_params = pcm_hw_params, | |
482 | .hw_free = pcm_hw_free, | |
483 | .prepare = pcm_prepare, | |
484 | .trigger = pcm_trigger, | |
485 | .pointer = pcm_pointer, | |
486 | .page = snd_pcm_lib_get_vmalloc_page, | |
487 | .mmap = snd_pcm_lib_mmap_vmalloc, | |
488 | }; | |
489 | ||
490 | ||
809da8a5 | 491 | static int split_arg_list(char *buf, char **card_name, char **pcm_format) |
54b4856f CG |
492 | { |
493 | *card_name = strsep(&buf, "."); | |
494 | if (!*card_name) | |
495 | return -EIO; | |
496 | *pcm_format = strsep(&buf, ".\n"); | |
497 | if (!*pcm_format) | |
498 | return -EIO; | |
499 | return 0; | |
500 | } | |
501 | ||
d8018872 CG |
502 | static int audio_set_pcm_format(struct snd_pcm_hardware *pcm_hw, |
503 | char *pcm_format, | |
809da8a5 | 504 | struct most_channel_config *cfg) |
54b4856f CG |
505 | { |
506 | if (!strcmp(pcm_format, "1x8")) { | |
507 | if (cfg->subbuffer_size != 1) | |
508 | goto error; | |
509 | pr_info("PCM format is 8-bit mono\n"); | |
d8018872 | 510 | pcm_hw->formats = SNDRV_PCM_FMTBIT_S8; |
54b4856f CG |
511 | } else if (!strcmp(pcm_format, "2x16")) { |
512 | if (cfg->subbuffer_size != 4) | |
513 | goto error; | |
514 | pr_info("PCM format is 16-bit stereo\n"); | |
d8018872 CG |
515 | pcm_hw->channels_min = 2; |
516 | pcm_hw->channels_max = 2; | |
517 | pcm_hw->formats = SNDRV_PCM_FMTBIT_S16_LE | | |
518 | SNDRV_PCM_FMTBIT_S16_BE; | |
54b4856f CG |
519 | } else if (!strcmp(pcm_format, "2x24")) { |
520 | if (cfg->subbuffer_size != 6) | |
521 | goto error; | |
522 | pr_info("PCM format is 24-bit stereo\n"); | |
d8018872 CG |
523 | pcm_hw->channels_min = 2; |
524 | pcm_hw->channels_max = 2; | |
525 | pcm_hw->formats = SNDRV_PCM_FMTBIT_S24_3LE | | |
526 | SNDRV_PCM_FMTBIT_S24_3BE; | |
54b4856f CG |
527 | } else if (!strcmp(pcm_format, "2x32")) { |
528 | if (cfg->subbuffer_size != 8) | |
529 | goto error; | |
530 | pr_info("PCM format is 32-bit stereo\n"); | |
d8018872 CG |
531 | pcm_hw->channels_min = 2; |
532 | pcm_hw->channels_max = 2; | |
533 | pcm_hw->formats = SNDRV_PCM_FMTBIT_S32_LE | | |
534 | SNDRV_PCM_FMTBIT_S32_BE; | |
8e4a0ef1 CG |
535 | } else if (!strcmp(pcm_format, "6x16")) { |
536 | if (cfg->subbuffer_size != 12) | |
537 | goto error; | |
538 | pr_info("PCM format is 16-bit 5.1 multi channel\n"); | |
d8018872 CG |
539 | pcm_hw->channels_min = 6; |
540 | pcm_hw->channels_max = 6; | |
541 | pcm_hw->formats = SNDRV_PCM_FMTBIT_S16_LE | | |
542 | SNDRV_PCM_FMTBIT_S16_BE; | |
54b4856f CG |
543 | } else { |
544 | pr_err("PCM format %s not supported\n", pcm_format); | |
545 | return -EIO; | |
546 | } | |
547 | return 0; | |
548 | error: | |
549 | pr_err("Audio resolution doesn't fit subbuffer size\n"); | |
550 | return -EINVAL; | |
551 | } | |
552 | ||
553 | /** | |
554 | * audio_probe_channel - probe function of the driver module | |
555 | * @iface: pointer to interface instance | |
556 | * @channel_id: channel index/ID | |
557 | * @cfg: pointer to actual channel configuration | |
558 | * @parent: pointer to kobject (needed for sysfs hook-up) | |
559 | * @arg_list: string that provides the name of the device to be created in /dev | |
560 | * plus the desired audio resolution | |
561 | * | |
562 | * Creates sound card, pcm device, sets pcm ops and registers sound card. | |
563 | * | |
564 | * Returns 0 on success or error code otherwise. | |
565 | */ | |
566 | static int audio_probe_channel(struct most_interface *iface, int channel_id, | |
567 | struct most_channel_config *cfg, | |
568 | struct kobject *parent, char *arg_list) | |
569 | { | |
570 | struct channel *channel; | |
571 | struct snd_card *card; | |
572 | struct snd_pcm *pcm; | |
573 | int playback_count = 0; | |
574 | int capture_count = 0; | |
575 | int ret; | |
576 | int direction; | |
577 | char *card_name; | |
578 | char *pcm_format; | |
579 | ||
54b4856f CG |
580 | if (!iface) |
581 | return -EINVAL; | |
582 | ||
583 | if (cfg->data_type != MOST_CH_SYNC) { | |
584 | pr_err("Incompatible channel type\n"); | |
585 | return -EINVAL; | |
586 | } | |
587 | ||
588 | if (get_channel(iface, channel_id)) { | |
589 | pr_err("channel (%s:%d) is already linked\n", | |
590 | iface->description, channel_id); | |
591 | return -EINVAL; | |
592 | } | |
593 | ||
594 | if (cfg->direction == MOST_CH_TX) { | |
595 | playback_count = 1; | |
596 | direction = SNDRV_PCM_STREAM_PLAYBACK; | |
597 | } else { | |
598 | capture_count = 1; | |
599 | direction = SNDRV_PCM_STREAM_CAPTURE; | |
600 | } | |
601 | ||
602 | ret = split_arg_list(arg_list, &card_name, &pcm_format); | |
603 | if (ret < 0) { | |
604 | pr_info("PCM format missing\n"); | |
605 | return ret; | |
606 | } | |
54b4856f CG |
607 | |
608 | ret = snd_card_new(NULL, -1, card_name, THIS_MODULE, | |
609 | sizeof(*channel), &card); | |
610 | if (ret < 0) | |
611 | return ret; | |
612 | ||
613 | channel = card->private_data; | |
614 | channel->card = card; | |
615 | channel->cfg = cfg; | |
616 | channel->iface = iface; | |
617 | channel->id = channel_id; | |
d8018872 CG |
618 | init_pcm_hardware(&channel->pcm_hardware); |
619 | ||
620 | if (audio_set_pcm_format(&channel->pcm_hardware, pcm_format, cfg)) | |
621 | goto err_free_card; | |
54b4856f CG |
622 | |
623 | snprintf(card->driver, sizeof(card->driver), "%s", DRIVER_NAME); | |
9801345c | 624 | snprintf(card->shortname, sizeof(card->shortname), "Microchip MOST:%d", |
54b4856f CG |
625 | card->number); |
626 | snprintf(card->longname, sizeof(card->longname), "%s at %s, ch %d", | |
627 | card->shortname, iface->description, channel_id); | |
628 | ||
629 | ret = snd_pcm_new(card, card_name, 0, playback_count, | |
630 | capture_count, &pcm); | |
631 | if (ret < 0) | |
632 | goto err_free_card; | |
633 | ||
634 | pcm->private_data = channel; | |
635 | ||
636 | snd_pcm_set_ops(pcm, direction, &pcm_ops); | |
637 | ||
638 | ret = snd_card_register(card); | |
639 | if (ret < 0) | |
640 | goto err_free_card; | |
641 | ||
642 | list_add_tail(&channel->list, &dev_list); | |
643 | ||
644 | return 0; | |
645 | ||
646 | err_free_card: | |
647 | snd_card_free(card); | |
648 | return ret; | |
649 | } | |
650 | ||
651 | /** | |
652 | * audio_disconnect_channel - function to disconnect a channel | |
653 | * @iface: pointer to interface instance | |
654 | * @channel_id: channel index | |
655 | * | |
656 | * This frees allocated memory and removes the sound card from ALSA | |
657 | * | |
658 | * Returns 0 on success or error code otherwise. | |
659 | */ | |
660 | static int audio_disconnect_channel(struct most_interface *iface, | |
661 | int channel_id) | |
662 | { | |
663 | struct channel *channel; | |
664 | ||
54b4856f CG |
665 | channel = get_channel(iface, channel_id); |
666 | if (!channel) { | |
667 | pr_err("sound_disconnect_channel(), invalid channel %d\n", | |
668 | channel_id); | |
669 | return -EINVAL; | |
670 | } | |
671 | ||
672 | list_del(&channel->list); | |
673 | snd_card_free(channel->card); | |
674 | ||
675 | return 0; | |
676 | } | |
677 | ||
678 | /** | |
679 | * audio_rx_completion - completion handler for rx channels | |
680 | * @mbo: pointer to buffer object that has completed | |
681 | * | |
682 | * This searches for the channel this MBO belongs to and copy the data from MBO | |
683 | * to ring buffer | |
684 | * | |
685 | * Returns 0 on success or error code otherwise. | |
686 | */ | |
687 | static int audio_rx_completion(struct mbo *mbo) | |
688 | { | |
689 | struct channel *channel = get_channel(mbo->ifp, mbo->hdm_channel_id); | |
690 | bool period_elapsed = false; | |
691 | ||
692 | if (!channel) { | |
693 | pr_err("sound_rx_completion(), invalid channel %d\n", | |
694 | mbo->hdm_channel_id); | |
695 | return -EINVAL; | |
696 | } | |
697 | ||
698 | if (channel->is_stream_running) | |
699 | period_elapsed = copy_data(channel, mbo); | |
700 | ||
701 | most_put_mbo(mbo); | |
702 | ||
703 | if (period_elapsed) | |
704 | snd_pcm_period_elapsed(channel->substream); | |
705 | ||
706 | return 0; | |
707 | } | |
708 | ||
709 | /** | |
710 | * audio_tx_completion - completion handler for tx channels | |
711 | * @iface: pointer to interface instance | |
712 | * @channel_id: channel index/ID | |
713 | * | |
714 | * This searches the channel that belongs to this combination of interface | |
715 | * pointer and channel ID and wakes a process sitting in the wait queue of | |
716 | * this channel. | |
717 | * | |
718 | * Returns 0 on success or error code otherwise. | |
719 | */ | |
720 | static int audio_tx_completion(struct most_interface *iface, int channel_id) | |
721 | { | |
722 | struct channel *channel = get_channel(iface, channel_id); | |
723 | ||
724 | if (!channel) { | |
725 | pr_err("sound_tx_completion(), invalid channel %d\n", | |
726 | channel_id); | |
727 | return -EINVAL; | |
728 | } | |
729 | ||
730 | wake_up_interruptible(&channel->playback_waitq); | |
731 | ||
732 | return 0; | |
733 | } | |
734 | ||
735 | /** | |
736 | * Initialization of the struct most_aim | |
737 | */ | |
738 | static struct most_aim audio_aim = { | |
739 | .name = DRIVER_NAME, | |
740 | .probe_channel = audio_probe_channel, | |
741 | .disconnect_channel = audio_disconnect_channel, | |
742 | .rx_completion = audio_rx_completion, | |
743 | .tx_completion = audio_tx_completion, | |
744 | }; | |
745 | ||
746 | static int __init audio_init(void) | |
747 | { | |
748 | pr_info("init()\n"); | |
749 | ||
750 | INIT_LIST_HEAD(&dev_list); | |
751 | ||
752 | return most_register_aim(&audio_aim); | |
753 | } | |
754 | ||
755 | static void __exit audio_exit(void) | |
756 | { | |
757 | struct channel *channel, *tmp; | |
758 | ||
759 | pr_info("exit()\n"); | |
760 | ||
761 | list_for_each_entry_safe(channel, tmp, &dev_list, list) { | |
762 | list_del(&channel->list); | |
763 | snd_card_free(channel->card); | |
764 | } | |
765 | ||
766 | most_deregister_aim(&audio_aim); | |
767 | } | |
768 | ||
769 | module_init(audio_init); | |
770 | module_exit(audio_exit); | |
771 | ||
772 | MODULE_LICENSE("GPL"); | |
773 | MODULE_AUTHOR("Christian Gromm <christian.gromm@microchip.com>"); | |
774 | MODULE_DESCRIPTION("Audio Application Interface Module for MostCore"); |