1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * ALSA PCM device for the |
4 | * ALSA interface to cobalt PCM capture streams |
5 | * |
6 | * Copyright 2014-2015 Cisco Systems, Inc. and/or its affiliates. |
7 | * All rights reserved. |
8 | */ |
9 | |
10 | #include <linux/init.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/delay.h> |
13 | |
14 | #include <media/v4l2-device.h> |
15 | |
16 | #include <sound/core.h> |
17 | #include <sound/pcm.h> |
18 | |
19 | #include "cobalt-driver.h" |
20 | #include "cobalt-alsa.h" |
21 | #include "cobalt-alsa-pcm.h" |
22 | |
23 | static unsigned int pcm_debug; |
24 | module_param(pcm_debug, int, 0644); |
25 | MODULE_PARM_DESC(pcm_debug, "enable debug messages for pcm" ); |
26 | |
27 | #define dprintk(fmt, arg...) \ |
28 | do { \ |
29 | if (pcm_debug) \ |
30 | pr_info("cobalt-alsa-pcm %s: " fmt, __func__, ##arg); \ |
31 | } while (0) |
32 | |
33 | static const struct snd_pcm_hardware snd_cobalt_hdmi_capture = { |
34 | .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | |
35 | SNDRV_PCM_INFO_MMAP | |
36 | SNDRV_PCM_INFO_INTERLEAVED | |
37 | SNDRV_PCM_INFO_MMAP_VALID, |
38 | |
39 | .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, |
40 | |
41 | .rates = SNDRV_PCM_RATE_48000, |
42 | |
43 | .rate_min = 48000, |
44 | .rate_max = 48000, |
45 | .channels_min = 1, |
46 | .channels_max = 8, |
47 | .buffer_bytes_max = 4 * 240 * 8 * 4, /* 5 ms of data */ |
48 | .period_bytes_min = 1920, /* 1 sample = 8 * 4 bytes */ |
49 | .period_bytes_max = 240 * 8 * 4, /* 5 ms of 8 channel data */ |
50 | .periods_min = 1, |
51 | .periods_max = 4, |
52 | }; |
53 | |
54 | static const struct snd_pcm_hardware snd_cobalt_playback = { |
55 | .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | |
56 | SNDRV_PCM_INFO_MMAP | |
57 | SNDRV_PCM_INFO_INTERLEAVED | |
58 | SNDRV_PCM_INFO_MMAP_VALID, |
59 | |
60 | .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, |
61 | |
62 | .rates = SNDRV_PCM_RATE_48000, |
63 | |
64 | .rate_min = 48000, |
65 | .rate_max = 48000, |
66 | .channels_min = 1, |
67 | .channels_max = 8, |
68 | .buffer_bytes_max = 4 * 240 * 8 * 4, /* 5 ms of data */ |
69 | .period_bytes_min = 1920, /* 1 sample = 8 * 4 bytes */ |
70 | .period_bytes_max = 240 * 8 * 4, /* 5 ms of 8 channel data */ |
71 | .periods_min = 1, |
72 | .periods_max = 4, |
73 | }; |
74 | |
75 | static void sample_cpy(u8 *dst, const u8 *src, u32 len, bool is_s32) |
76 | { |
77 | static const unsigned map[8] = { 0, 1, 5, 4, 2, 3, 6, 7 }; |
78 | unsigned idx = 0; |
79 | |
80 | while (len >= (is_s32 ? 4 : 2)) { |
81 | unsigned offset = map[idx] * 4; |
82 | u32 val = src[offset + 1] + (src[offset + 2] << 8) + |
83 | (src[offset + 3] << 16); |
84 | |
85 | if (is_s32) { |
86 | *dst++ = 0; |
87 | *dst++ = val & 0xff; |
88 | } |
89 | *dst++ = (val >> 8) & 0xff; |
90 | *dst++ = (val >> 16) & 0xff; |
91 | len -= is_s32 ? 4 : 2; |
92 | idx++; |
93 | } |
94 | } |
95 | |
96 | static void cobalt_alsa_announce_pcm_data(struct snd_cobalt_card *cobsc, |
97 | u8 *pcm_data, |
98 | size_t skip, |
99 | size_t samples) |
100 | { |
101 | struct snd_pcm_substream *substream; |
102 | struct snd_pcm_runtime *runtime; |
103 | unsigned long flags; |
104 | unsigned int oldptr; |
105 | unsigned int stride; |
106 | int length = samples; |
107 | int period_elapsed = 0; |
108 | bool is_s32; |
109 | |
110 | dprintk("cobalt alsa announce ptr=%p data=%p num_bytes=%zd\n" , cobsc, |
111 | pcm_data, samples); |
112 | |
113 | substream = cobsc->capture_pcm_substream; |
114 | if (substream == NULL) { |
115 | dprintk("substream was NULL\n" ); |
116 | return; |
117 | } |
118 | |
119 | runtime = substream->runtime; |
120 | if (runtime == NULL) { |
121 | dprintk("runtime was NULL\n" ); |
122 | return; |
123 | } |
124 | is_s32 = runtime->format == SNDRV_PCM_FORMAT_S32_LE; |
125 | |
126 | stride = runtime->frame_bits >> 3; |
127 | if (stride == 0) { |
128 | dprintk("stride is zero\n" ); |
129 | return; |
130 | } |
131 | |
132 | if (length == 0) { |
133 | dprintk("%s: length was zero\n" , __func__); |
134 | return; |
135 | } |
136 | |
137 | if (runtime->dma_area == NULL) { |
138 | dprintk("dma area was NULL - ignoring\n" ); |
139 | return; |
140 | } |
141 | |
142 | oldptr = cobsc->hwptr_done_capture; |
143 | if (oldptr + length >= runtime->buffer_size) { |
144 | unsigned int cnt = runtime->buffer_size - oldptr; |
145 | unsigned i; |
146 | |
147 | for (i = 0; i < cnt; i++) |
148 | sample_cpy(dst: runtime->dma_area + (oldptr + i) * stride, |
149 | src: pcm_data + i * skip, |
150 | len: stride, is_s32); |
151 | for (i = cnt; i < length; i++) |
152 | sample_cpy(dst: runtime->dma_area + (i - cnt) * stride, |
153 | src: pcm_data + i * skip, len: stride, is_s32); |
154 | } else { |
155 | unsigned i; |
156 | |
157 | for (i = 0; i < length; i++) |
158 | sample_cpy(dst: runtime->dma_area + (oldptr + i) * stride, |
159 | src: pcm_data + i * skip, |
160 | len: stride, is_s32); |
161 | } |
162 | snd_pcm_stream_lock_irqsave(substream, flags); |
163 | |
164 | cobsc->hwptr_done_capture += length; |
165 | if (cobsc->hwptr_done_capture >= |
166 | runtime->buffer_size) |
167 | cobsc->hwptr_done_capture -= |
168 | runtime->buffer_size; |
169 | |
170 | cobsc->capture_transfer_done += length; |
171 | if (cobsc->capture_transfer_done >= |
172 | runtime->period_size) { |
173 | cobsc->capture_transfer_done -= |
174 | runtime->period_size; |
175 | period_elapsed = 1; |
176 | } |
177 | |
178 | snd_pcm_stream_unlock_irqrestore(substream, flags); |
179 | |
180 | if (period_elapsed) |
181 | snd_pcm_period_elapsed(substream); |
182 | } |
183 | |
184 | static int alsa_fnc(struct vb2_buffer *vb, void *priv) |
185 | { |
186 | struct cobalt_stream *s = priv; |
187 | unsigned char *p = vb2_plane_vaddr(vb, plane_no: 0); |
188 | int i; |
189 | |
190 | if (pcm_debug) { |
191 | pr_info("alsa: " ); |
192 | for (i = 0; i < 8 * 4; i++) { |
193 | if (!(i & 3)) |
194 | pr_cont(" " ); |
195 | pr_cont("%02x" , p[i]); |
196 | } |
197 | pr_cont("\n" ); |
198 | } |
199 | cobalt_alsa_announce_pcm_data(cobsc: s->alsa, |
200 | pcm_data: vb2_plane_vaddr(vb, plane_no: 0), |
201 | skip: 8 * 4, |
202 | samples: vb2_get_plane_payload(vb, plane_no: 0) / (8 * 4)); |
203 | return 0; |
204 | } |
205 | |
206 | static int snd_cobalt_pcm_capture_open(struct snd_pcm_substream *substream) |
207 | { |
208 | struct snd_pcm_runtime *runtime = substream->runtime; |
209 | struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); |
210 | struct cobalt_stream *s = cobsc->s; |
211 | |
212 | runtime->hw = snd_cobalt_hdmi_capture; |
213 | snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); |
214 | cobsc->capture_pcm_substream = substream; |
215 | runtime->private_data = s; |
216 | cobsc->alsa_record_cnt++; |
217 | if (cobsc->alsa_record_cnt == 1) { |
218 | int rc; |
219 | |
220 | rc = vb2_thread_start(q: &s->q, fnc: alsa_fnc, priv: s, thread_name: s->vdev.name); |
221 | if (rc) { |
222 | cobsc->alsa_record_cnt--; |
223 | return rc; |
224 | } |
225 | } |
226 | return 0; |
227 | } |
228 | |
229 | static int snd_cobalt_pcm_capture_close(struct snd_pcm_substream *substream) |
230 | { |
231 | struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); |
232 | struct cobalt_stream *s = cobsc->s; |
233 | |
234 | cobsc->alsa_record_cnt--; |
235 | if (cobsc->alsa_record_cnt == 0) |
236 | vb2_thread_stop(q: &s->q); |
237 | return 0; |
238 | } |
239 | |
240 | static int snd_cobalt_pcm_prepare(struct snd_pcm_substream *substream) |
241 | { |
242 | struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); |
243 | |
244 | cobsc->hwptr_done_capture = 0; |
245 | cobsc->capture_transfer_done = 0; |
246 | |
247 | return 0; |
248 | } |
249 | |
250 | static int snd_cobalt_pcm_trigger(struct snd_pcm_substream *substream, int cmd) |
251 | { |
252 | switch (cmd) { |
253 | case SNDRV_PCM_TRIGGER_START: |
254 | case SNDRV_PCM_TRIGGER_STOP: |
255 | return 0; |
256 | default: |
257 | return -EINVAL; |
258 | } |
259 | return 0; |
260 | } |
261 | |
262 | static |
263 | snd_pcm_uframes_t snd_cobalt_pcm_pointer(struct snd_pcm_substream *substream) |
264 | { |
265 | snd_pcm_uframes_t hwptr_done; |
266 | struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); |
267 | |
268 | hwptr_done = cobsc->hwptr_done_capture; |
269 | |
270 | return hwptr_done; |
271 | } |
272 | |
273 | static void pb_sample_cpy(u8 *dst, const u8 *src, u32 len, bool is_s32) |
274 | { |
275 | static const unsigned map[8] = { 0, 1, 5, 4, 2, 3, 6, 7 }; |
276 | unsigned idx = 0; |
277 | |
278 | while (len >= (is_s32 ? 4 : 2)) { |
279 | unsigned offset = map[idx] * 4; |
280 | u8 *out = dst + offset; |
281 | |
282 | *out++ = 0; |
283 | if (is_s32) { |
284 | src++; |
285 | *out++ = *src++; |
286 | } else { |
287 | *out++ = 0; |
288 | } |
289 | *out++ = *src++; |
290 | *out = *src++; |
291 | len -= is_s32 ? 4 : 2; |
292 | idx++; |
293 | } |
294 | } |
295 | |
296 | static void cobalt_alsa_pb_pcm_data(struct snd_cobalt_card *cobsc, |
297 | u8 *pcm_data, |
298 | size_t skip, |
299 | size_t samples) |
300 | { |
301 | struct snd_pcm_substream *substream; |
302 | struct snd_pcm_runtime *runtime; |
303 | unsigned long flags; |
304 | unsigned int pos; |
305 | unsigned int stride; |
306 | bool is_s32; |
307 | unsigned i; |
308 | |
309 | dprintk("cobalt alsa pb ptr=%p data=%p samples=%zd\n" , cobsc, |
310 | pcm_data, samples); |
311 | |
312 | substream = cobsc->playback_pcm_substream; |
313 | if (substream == NULL) { |
314 | dprintk("substream was NULL\n" ); |
315 | return; |
316 | } |
317 | |
318 | runtime = substream->runtime; |
319 | if (runtime == NULL) { |
320 | dprintk("runtime was NULL\n" ); |
321 | return; |
322 | } |
323 | |
324 | is_s32 = runtime->format == SNDRV_PCM_FORMAT_S32_LE; |
325 | stride = runtime->frame_bits >> 3; |
326 | if (stride == 0) { |
327 | dprintk("stride is zero\n" ); |
328 | return; |
329 | } |
330 | |
331 | if (samples == 0) { |
332 | dprintk("%s: samples was zero\n" , __func__); |
333 | return; |
334 | } |
335 | |
336 | if (runtime->dma_area == NULL) { |
337 | dprintk("dma area was NULL - ignoring\n" ); |
338 | return; |
339 | } |
340 | |
341 | pos = cobsc->pb_pos % cobsc->pb_size; |
342 | for (i = 0; i < cobsc->pb_count / (8 * 4); i++) |
343 | pb_sample_cpy(dst: pcm_data + i * skip, |
344 | src: runtime->dma_area + pos + i * stride, |
345 | len: stride, is_s32); |
346 | snd_pcm_stream_lock_irqsave(substream, flags); |
347 | |
348 | cobsc->pb_pos += i * stride; |
349 | |
350 | snd_pcm_stream_unlock_irqrestore(substream, flags); |
351 | if (cobsc->pb_pos % cobsc->pb_count == 0) |
352 | snd_pcm_period_elapsed(substream); |
353 | } |
354 | |
355 | static int alsa_pb_fnc(struct vb2_buffer *vb, void *priv) |
356 | { |
357 | struct cobalt_stream *s = priv; |
358 | |
359 | if (s->alsa->alsa_pb_channel) |
360 | cobalt_alsa_pb_pcm_data(cobsc: s->alsa, |
361 | pcm_data: vb2_plane_vaddr(vb, plane_no: 0), |
362 | skip: 8 * 4, |
363 | samples: vb2_get_plane_payload(vb, plane_no: 0) / (8 * 4)); |
364 | return 0; |
365 | } |
366 | |
367 | static int snd_cobalt_pcm_playback_open(struct snd_pcm_substream *substream) |
368 | { |
369 | struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); |
370 | struct snd_pcm_runtime *runtime = substream->runtime; |
371 | struct cobalt_stream *s = cobsc->s; |
372 | |
373 | runtime->hw = snd_cobalt_playback; |
374 | snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); |
375 | cobsc->playback_pcm_substream = substream; |
376 | runtime->private_data = s; |
377 | cobsc->alsa_playback_cnt++; |
378 | if (cobsc->alsa_playback_cnt == 1) { |
379 | int rc; |
380 | |
381 | rc = vb2_thread_start(q: &s->q, fnc: alsa_pb_fnc, priv: s, thread_name: s->vdev.name); |
382 | if (rc) { |
383 | cobsc->alsa_playback_cnt--; |
384 | return rc; |
385 | } |
386 | } |
387 | |
388 | return 0; |
389 | } |
390 | |
391 | static int snd_cobalt_pcm_playback_close(struct snd_pcm_substream *substream) |
392 | { |
393 | struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); |
394 | struct cobalt_stream *s = cobsc->s; |
395 | |
396 | cobsc->alsa_playback_cnt--; |
397 | if (cobsc->alsa_playback_cnt == 0) |
398 | vb2_thread_stop(q: &s->q); |
399 | return 0; |
400 | } |
401 | |
402 | static int snd_cobalt_pcm_pb_prepare(struct snd_pcm_substream *substream) |
403 | { |
404 | struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); |
405 | |
406 | cobsc->pb_size = snd_pcm_lib_buffer_bytes(substream); |
407 | cobsc->pb_count = snd_pcm_lib_period_bytes(substream); |
408 | cobsc->pb_pos = 0; |
409 | |
410 | return 0; |
411 | } |
412 | |
413 | static int snd_cobalt_pcm_pb_trigger(struct snd_pcm_substream *substream, |
414 | int cmd) |
415 | { |
416 | struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); |
417 | |
418 | switch (cmd) { |
419 | case SNDRV_PCM_TRIGGER_START: |
420 | if (cobsc->alsa_pb_channel) |
421 | return -EBUSY; |
422 | cobsc->alsa_pb_channel = true; |
423 | return 0; |
424 | case SNDRV_PCM_TRIGGER_STOP: |
425 | cobsc->alsa_pb_channel = false; |
426 | return 0; |
427 | default: |
428 | return -EINVAL; |
429 | } |
430 | } |
431 | |
432 | static |
433 | snd_pcm_uframes_t snd_cobalt_pcm_pb_pointer(struct snd_pcm_substream *substream) |
434 | { |
435 | struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); |
436 | size_t ptr; |
437 | |
438 | ptr = cobsc->pb_pos; |
439 | |
440 | return bytes_to_frames(runtime: substream->runtime, size: ptr) % |
441 | substream->runtime->buffer_size; |
442 | } |
443 | |
444 | static const struct snd_pcm_ops snd_cobalt_pcm_capture_ops = { |
445 | .open = snd_cobalt_pcm_capture_open, |
446 | .close = snd_cobalt_pcm_capture_close, |
447 | .prepare = snd_cobalt_pcm_prepare, |
448 | .trigger = snd_cobalt_pcm_trigger, |
449 | .pointer = snd_cobalt_pcm_pointer, |
450 | }; |
451 | |
452 | static const struct snd_pcm_ops snd_cobalt_pcm_playback_ops = { |
453 | .open = snd_cobalt_pcm_playback_open, |
454 | .close = snd_cobalt_pcm_playback_close, |
455 | .prepare = snd_cobalt_pcm_pb_prepare, |
456 | .trigger = snd_cobalt_pcm_pb_trigger, |
457 | .pointer = snd_cobalt_pcm_pb_pointer, |
458 | }; |
459 | |
460 | int snd_cobalt_pcm_create(struct snd_cobalt_card *cobsc) |
461 | { |
462 | struct snd_pcm *sp; |
463 | struct snd_card *sc = cobsc->sc; |
464 | struct cobalt_stream *s = cobsc->s; |
465 | struct cobalt *cobalt = s->cobalt; |
466 | int ret; |
467 | |
468 | s->q.gfp_flags |= __GFP_ZERO; |
469 | |
470 | if (!s->is_output) { |
471 | cobalt_s_bit_sysctrl(cobalt, |
472 | COBALT_SYS_CTRL_AUDIO_IPP_RESETN_BIT(s->video_channel), |
473 | val: 0); |
474 | mdelay(2); |
475 | cobalt_s_bit_sysctrl(cobalt, |
476 | COBALT_SYS_CTRL_AUDIO_IPP_RESETN_BIT(s->video_channel), |
477 | val: 1); |
478 | mdelay(1); |
479 | |
480 | ret = snd_pcm_new(card: sc, id: "Cobalt PCM-In HDMI" , |
481 | device: 0, /* PCM device 0, the only one for this card */ |
482 | playback_count: 0, /* 0 playback substreams */ |
483 | capture_count: 1, /* 1 capture substream */ |
484 | rpcm: &sp); |
485 | if (ret) { |
486 | cobalt_err("snd_cobalt_pcm_create() failed for input with err %d\n" , |
487 | ret); |
488 | goto err_exit; |
489 | } |
490 | |
491 | snd_pcm_set_ops(pcm: sp, direction: SNDRV_PCM_STREAM_CAPTURE, |
492 | ops: &snd_cobalt_pcm_capture_ops); |
493 | snd_pcm_set_managed_buffer_all(pcm: sp, SNDRV_DMA_TYPE_VMALLOC, |
494 | NULL, size: 0, max: 0); |
495 | sp->info_flags = 0; |
496 | sp->private_data = cobsc; |
497 | strscpy(sp->name, "cobalt" , sizeof(sp->name)); |
498 | } else { |
499 | cobalt_s_bit_sysctrl(cobalt, |
500 | COBALT_SYS_CTRL_AUDIO_OPP_RESETN_BIT, val: 0); |
501 | mdelay(2); |
502 | cobalt_s_bit_sysctrl(cobalt, |
503 | COBALT_SYS_CTRL_AUDIO_OPP_RESETN_BIT, val: 1); |
504 | mdelay(1); |
505 | |
506 | ret = snd_pcm_new(card: sc, id: "Cobalt PCM-Out HDMI" , |
507 | device: 0, /* PCM device 0, the only one for this card */ |
508 | playback_count: 1, /* 0 playback substreams */ |
509 | capture_count: 0, /* 1 capture substream */ |
510 | rpcm: &sp); |
511 | if (ret) { |
512 | cobalt_err("snd_cobalt_pcm_create() failed for output with err %d\n" , |
513 | ret); |
514 | goto err_exit; |
515 | } |
516 | |
517 | snd_pcm_set_ops(pcm: sp, direction: SNDRV_PCM_STREAM_PLAYBACK, |
518 | ops: &snd_cobalt_pcm_playback_ops); |
519 | snd_pcm_set_managed_buffer_all(pcm: sp, SNDRV_DMA_TYPE_VMALLOC, |
520 | NULL, size: 0, max: 0); |
521 | sp->info_flags = 0; |
522 | sp->private_data = cobsc; |
523 | strscpy(sp->name, "cobalt" , sizeof(sp->name)); |
524 | } |
525 | |
526 | return 0; |
527 | |
528 | err_exit: |
529 | return ret; |
530 | } |
531 | |