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