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> | |
b2765275 | 20 | #include <linux/most.h> |
54b4856f CG |
21 | |
22 | #define DRIVER_NAME "sound" | |
acdbb897 | 23 | #define STRING_SIZE 80 |
54b4856f | 24 | |
45917e79 | 25 | static struct most_component comp; |
54b4856f CG |
26 | |
27 | /** | |
28 | * struct channel - private structure to keep channel specific data | |
29 | * @substream: stores the substream structure | |
fba3993e | 30 | * @pcm_hardware: low-level hardware description |
54b4856f CG |
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 | |
fba3993e | 42 | * @copy_fn: copy function for PCM-specific format and width |
54b4856f CG |
43 | */ |
44 | struct channel { | |
45 | struct snd_pcm_substream *substream; | |
d8018872 | 46 | struct snd_pcm_hardware pcm_hardware; |
54b4856f CG |
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; | |
54b4856f CG |
55 | struct task_struct *playback_task; |
56 | wait_queue_head_t playback_waitq; | |
54b4856f CG |
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 | static void swap_copy16(u16 *dest, const u16 *source, unsigned int bytes) |
78 | { | |
79 | unsigned int i = 0; | |
80 | ||
81 | while (i < (bytes / 2)) { | |
eb27cf08 | 82 | dest[i] = swab16(source[i]); |
54b4856f CG |
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 | ||
45b754ae CG |
91 | if (bytes < 2) |
92 | return; | |
54b4856f CG |
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) { | |
eb27cf08 | 106 | dest[i] = swab32(source[i]); |
54b4856f CG |
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(most, alsa, bytes); | |
119 | } | |
120 | ||
121 | static void alsa_to_most_copy24(void *alsa, void *most, unsigned int bytes) | |
122 | { | |
123 | swap_copy24(most, alsa, bytes); | |
124 | } | |
125 | ||
126 | static void alsa_to_most_copy32(void *alsa, void *most, unsigned int bytes) | |
127 | { | |
128 | swap_copy32(most, 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(alsa, most, bytes); | |
139 | } | |
140 | ||
141 | static void most_to_alsa_copy24(void *alsa, void *most, unsigned int bytes) | |
142 | { | |
143 | swap_copy24(alsa, most, bytes); | |
144 | } | |
145 | ||
146 | static void most_to_alsa_copy32(void *alsa, void *most, unsigned int bytes) | |
147 | { | |
148 | swap_copy32(alsa, 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 | { | |
15600aea | 164 | struct sound_adapter *adpt = iface->priv; |
9810cad7 | 165 | struct channel *channel; |
54b4856f | 166 | |
9810cad7 | 167 | list_for_each_entry(channel, &adpt->dev_list, list) { |
54b4856f CG |
168 | if ((channel->iface == iface) && (channel->id == channel_id)) |
169 | return channel; | |
170 | } | |
54b4856f CG |
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 | } | |
54b4856f CG |
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 | ||
54b4856f CG |
231 | while (!kthread_should_stop()) { |
232 | struct mbo *mbo = NULL; | |
233 | bool period_elapsed = false; | |
54b4856f CG |
234 | |
235 | wait_event_interruptible( | |
236 | channel->playback_waitq, | |
237 | kthread_should_stop() || | |
5e8aa94e CG |
238 | (channel->is_stream_running && |
239 | (mbo = most_get_mbo(channel->iface, channel->id, | |
1f95cf02 | 240 | &comp)))); |
54b4856f CG |
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 | ||
a6f9d846 | 249 | most_submit_mbo(mbo); |
54b4856f CG |
250 | if (period_elapsed) |
251 | snd_pcm_period_elapsed(channel->substream); | |
252 | } | |
54b4856f CG |
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; | |
b1abd84d | 270 | int ret; |
54b4856f | 271 | |
54b4856f CG |
272 | channel->substream = substream; |
273 | ||
274 | if (cfg->direction == MOST_CH_TX) { | |
246ed517 | 275 | channel->playback_task = kthread_run(playback_thread, channel, |
54b4856f | 276 | "most_audio_playback"); |
5e8aa94e CG |
277 | if (IS_ERR(channel->playback_task)) { |
278 | pr_err("Couldn't start thread\n"); | |
54b4856f | 279 | return PTR_ERR(channel->playback_task); |
5e8aa94e | 280 | } |
54b4856f CG |
281 | } |
282 | ||
b1abd84d CG |
283 | ret = most_start_channel(channel->iface, channel->id, &comp); |
284 | if (ret) { | |
54b4856f CG |
285 | pr_err("most_start_channel() failed!\n"); |
286 | if (cfg->direction == MOST_CH_TX) | |
287 | kthread_stop(channel->playback_task); | |
b1abd84d | 288 | return ret; |
54b4856f CG |
289 | } |
290 | ||
d8018872 | 291 | runtime->hw = channel->pcm_hardware; |
54b4856f CG |
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 | ||
54b4856f CG |
309 | if (channel->cfg->direction == MOST_CH_TX) |
310 | kthread_stop(channel->playback_task); | |
1f95cf02 | 311 | most_stop_channel(channel->iface, channel->id, &comp); |
54b4856f CG |
312 | return 0; |
313 | } | |
314 | ||
54b4856f CG |
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(runtime->format); | |
330 | ||
331 | channel->copy_fn = NULL; | |
332 | ||
333 | if (cfg->direction == MOST_CH_TX) { | |
334 | if (snd_pcm_format_big_endian(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(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 | ||
c0b122a0 | 353 | if (!channel->copy_fn) |
54b4856f | 354 | return -EINVAL; |
54b4856f CG |
355 | channel->period_pos = 0; |
356 | channel->buffer_pos = 0; | |
54b4856f CG |
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; | |
5e8aa94e | 377 | wake_up_interruptible(&channel->playback_waitq); |
54b4856f CG |
378 | return 0; |
379 | ||
380 | case SNDRV_PCM_TRIGGER_STOP: | |
381 | channel->is_stream_running = false; | |
382 | return 0; | |
383 | ||
384 | default: | |
54b4856f CG |
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 | ||
fba3993e | 405 | /* |
54b4856f CG |
406 | * Initialization of struct snd_pcm_ops |
407 | */ | |
cfb459ed | 408 | static const struct snd_pcm_ops pcm_ops = { |
54b4856f CG |
409 | .open = pcm_open, |
410 | .close = pcm_close, | |
54b4856f CG |
411 | .prepare = pcm_prepare, |
412 | .trigger = pcm_trigger, | |
413 | .pointer = pcm_pointer, | |
54b4856f CG |
414 | }; |
415 | ||
9640baca | 416 | static int split_arg_list(char *buf, u16 *ch_num, char **sample_res) |
54b4856f | 417 | { |
ed856eb5 CG |
418 | char *num; |
419 | int ret; | |
420 | ||
ed856eb5 CG |
421 | num = strsep(&buf, "x"); |
422 | if (!num) | |
423 | goto err; | |
424 | ret = kstrtou16(num, 0, ch_num); | |
425 | if (ret) | |
426 | goto err; | |
427 | *sample_res = strsep(&buf, ".\n"); | |
428 | if (!*sample_res) | |
429 | goto err; | |
54b4856f | 430 | return 0; |
ed856eb5 CG |
431 | |
432 | err: | |
433 | pr_err("Bad PCM format\n"); | |
b1abd84d | 434 | return -EINVAL; |
54b4856f CG |
435 | } |
436 | ||
ed856eb5 CG |
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 | ||
fb0056a8 | 448 | static int audio_set_hw_params(struct snd_pcm_hardware *pcm_hw, |
ed856eb5 | 449 | u16 ch_num, char *sample_res, |
35c33721 | 450 | struct most_channel_config *cfg) |
54b4856f | 451 | { |
ed856eb5 CG |
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"); | |
b1abd84d | 459 | return -EINVAL; |
ed856eb5 CG |
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 | ||
e569da26 CG |
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; | |
ed856eb5 CG |
481 | pcm_hw->channels_min = ch_num; |
482 | pcm_hw->channels_max = ch_num; | |
483 | pcm_hw->formats = sinfo[i].formats; | |
54b4856f | 484 | return 0; |
54b4856f CG |
485 | } |
486 | ||
15600aea CG |
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(&channel->list); | |
493 | kfree(channel); | |
494 | } | |
495 | if (adpt->card) | |
496 | snd_card_free(adpt->card); | |
497 | list_del(&adpt->list); | |
498 | kfree(adpt); | |
499 | } | |
500 | ||
54b4856f CG |
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 | |
fba3993e RD |
506 | * @device_name: name of the device to be created in /dev |
507 | * @arg_list: string that provides the desired audio resolution | |
54b4856f CG |
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, | |
dfee92dd | 515 | char *device_name, char *arg_list) |
54b4856f CG |
516 | { |
517 | struct channel *channel; | |
15600aea | 518 | struct sound_adapter *adpt; |
54b4856f CG |
519 | struct snd_pcm *pcm; |
520 | int playback_count = 0; | |
521 | int capture_count = 0; | |
522 | int ret; | |
523 | int direction; | |
ed856eb5 CG |
524 | u16 ch_num; |
525 | char *sample_res; | |
acdbb897 | 526 | char arg_list_cpy[STRING_SIZE]; |
54b4856f | 527 | |
54b4856f CG |
528 | if (cfg->data_type != MOST_CH_SYNC) { |
529 | pr_err("Incompatible channel type\n"); | |
530 | return -EINVAL; | |
531 | } | |
6367dee9 | 532 | strscpy(arg_list_cpy, arg_list, STRING_SIZE); |
acdbb897 | 533 | ret = split_arg_list(arg_list_cpy, &ch_num, &sample_res); |
15600aea CG |
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(sizeof(*adpt), GFP_KERNEL); | |
546 | if (!adpt) | |
547 | return -ENOMEM; | |
548 | ||
549 | adpt->iface = iface; | |
550 | INIT_LIST_HEAD(&adpt->dev_list); | |
551 | iface->priv = adpt; | |
552 | list_add_tail(&adpt->list, &adpt_list); | |
98592c1f | 553 | ret = snd_card_new(iface->driver_dev, -1, "INIC", THIS_MODULE, |
15600aea CG |
554 | sizeof(*channel), &adpt->card); |
555 | if (ret < 0) | |
ba99c631 | 556 | goto err_free_adpt; |
15600aea CG |
557 | snprintf(adpt->card->driver, sizeof(adpt->card->driver), |
558 | "%s", DRIVER_NAME); | |
559 | snprintf(adpt->card->shortname, sizeof(adpt->card->shortname), | |
6cebb201 | 560 | "Microchip INIC"); |
15600aea | 561 | snprintf(adpt->card->longname, sizeof(adpt->card->longname), |
e6861b91 | 562 | "%s at %s", adpt->card->shortname, iface->description); |
15600aea | 563 | skip_adpt_alloc: |
54b4856f CG |
564 | if (get_channel(iface, channel_id)) { |
565 | pr_err("channel (%s:%d) is already linked\n", | |
566 | iface->description, channel_id); | |
b1abd84d | 567 | return -EEXIST; |
54b4856f CG |
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 | } | |
15600aea CG |
577 | channel = kzalloc(sizeof(*channel), GFP_KERNEL); |
578 | if (!channel) { | |
579 | ret = -ENOMEM; | |
ba99c631 | 580 | goto err_free_adpt; |
15600aea CG |
581 | } |
582 | channel->card = adpt->card; | |
54b4856f CG |
583 | channel->cfg = cfg; |
584 | channel->iface = iface; | |
585 | channel->id = channel_id; | |
5e8aa94e | 586 | init_waitqueue_head(&channel->playback_waitq); |
15600aea | 587 | list_add_tail(&channel->list, &adpt->dev_list); |
d8018872 | 588 | |
ed856eb5 CG |
589 | ret = audio_set_hw_params(&channel->pcm_hardware, ch_num, sample_res, |
590 | cfg); | |
ca8b3fa7 | 591 | if (ret) |
ba99c631 | 592 | goto err_free_adpt; |
54b4856f | 593 | |
4c6375dc | 594 | ret = snd_pcm_new(adpt->card, device_name, adpt->pcm_dev_idx, |
15600aea | 595 | playback_count, capture_count, &pcm); |
54b4856f | 596 | |
54b4856f | 597 | if (ret < 0) |
ba99c631 | 598 | goto err_free_adpt; |
54b4856f CG |
599 | |
600 | pcm->private_data = channel; | |
a86028f8 | 601 | strscpy(pcm->name, device_name, sizeof(pcm->name)); |
54b4856f | 602 | snd_pcm_set_ops(pcm, direction, &pcm_ops); |
7e6d24d9 | 603 | snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); |
54b4856f CG |
604 | return 0; |
605 | ||
ba99c631 | 606 | err_free_adpt: |
15600aea | 607 | release_adapter(adpt); |
54b4856f CG |
608 | return ret; |
609 | } | |
610 | ||
9640baca CG |
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(adpt->card); | |
623 | if (ret < 0) { | |
624 | release_adapter(adpt); | |
625 | return ret; | |
626 | } | |
627 | adpt->registered = true; | |
628 | return 0; | |
629 | } | |
630 | ||
54b4856f CG |
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; | |
15600aea | 644 | struct sound_adapter *adpt = iface->priv; |
54b4856f | 645 | |
54b4856f | 646 | channel = get_channel(iface, channel_id); |
c0b122a0 | 647 | if (!channel) |
54b4856f | 648 | return -EINVAL; |
54b4856f CG |
649 | |
650 | list_del(&channel->list); | |
54b4856f | 651 | |
15600aea CG |
652 | kfree(channel); |
653 | if (list_empty(&adpt->dev_list)) | |
654 | release_adapter(adpt); | |
54b4856f CG |
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(mbo->ifp, mbo->hdm_channel_id); | |
670 | bool period_elapsed = false; | |
671 | ||
c0b122a0 | 672 | if (!channel) |
54b4856f | 673 | return -EINVAL; |
54b4856f CG |
674 | if (channel->is_stream_running) |
675 | period_elapsed = copy_data(channel, mbo); | |
54b4856f | 676 | most_put_mbo(mbo); |
54b4856f CG |
677 | if (period_elapsed) |
678 | snd_pcm_period_elapsed(channel->substream); | |
54b4856f CG |
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 | ||
c0b122a0 | 697 | if (!channel) |
54b4856f | 698 | return -EINVAL; |
54b4856f CG |
699 | |
700 | wake_up_interruptible(&channel->playback_waitq); | |
54b4856f CG |
701 | return 0; |
702 | } | |
703 | ||
fba3993e | 704 | /* |
45917e79 | 705 | * Initialization of the struct most_component |
54b4856f | 706 | */ |
45917e79 | 707 | static struct most_component comp = { |
08283d30 | 708 | .mod = THIS_MODULE, |
54b4856f CG |
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, | |
9640baca | 714 | .cfg_complete = audio_create_sound_card, |
54b4856f CG |
715 | }; |
716 | ||
717 | static int __init audio_init(void) | |
718 | { | |
919c03ae CG |
719 | int ret; |
720 | ||
15600aea | 721 | INIT_LIST_HEAD(&adpt_list); |
54b4856f | 722 | |
919c03ae | 723 | ret = most_register_component(&comp); |
36b67efe | 724 | if (ret) { |
919c03ae | 725 | pr_err("Failed to register %s\n", comp.name); |
36b67efe CG |
726 | return ret; |
727 | } | |
919c03ae | 728 | ret = most_register_configfs_subsys(&comp); |
3982f1df | 729 | if (ret) { |
919c03ae | 730 | pr_err("Failed to register %s configfs subsys\n", comp.name); |
3982f1df Y |
731 | most_deregister_component(&comp); |
732 | } | |
919c03ae | 733 | return ret; |
54b4856f CG |
734 | } |
735 | ||
736 | static void __exit audio_exit(void) | |
737 | { | |
919c03ae | 738 | most_deregister_configfs_subsys(&comp); |
1f95cf02 | 739 | most_deregister_component(&comp); |
54b4856f CG |
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>"); | |
b7937dc4 | 747 | MODULE_DESCRIPTION("Sound Component Module for Mostcore"); |