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