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