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