1 // SPDX-License-Identifier: GPL-2.0-only
3 * ALSA PCM device for the
4 * ALSA interface to cobalt PCM capture streams
6 * Copyright 2014-2015 Cisco Systems, Inc. and/or its affiliates.
10 #include <linux/init.h>
11 #include <linux/kernel.h>
12 #include <linux/vmalloc.h>
13 #include <linux/delay.h>
15 #include <media/v4l2-device.h>
17 #include <sound/core.h>
18 #include <sound/pcm.h>
20 #include "cobalt-driver.h"
21 #include "cobalt-alsa.h"
22 #include "cobalt-alsa-pcm.h"
24 static unsigned int pcm_debug
;
25 module_param(pcm_debug
, int, 0644);
26 MODULE_PARM_DESC(pcm_debug
, "enable debug messages for pcm");
28 #define dprintk(fmt, arg...) \
31 pr_info("cobalt-alsa-pcm %s: " fmt, __func__, ##arg); \
34 static const struct snd_pcm_hardware snd_cobalt_hdmi_capture
= {
35 .info
= SNDRV_PCM_INFO_BLOCK_TRANSFER
|
37 SNDRV_PCM_INFO_INTERLEAVED
|
38 SNDRV_PCM_INFO_MMAP_VALID
,
40 .formats
= SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S32_LE
,
42 .rates
= SNDRV_PCM_RATE_48000
,
48 .buffer_bytes_max
= 4 * 240 * 8 * 4, /* 5 ms of data */
49 .period_bytes_min
= 1920, /* 1 sample = 8 * 4 bytes */
50 .period_bytes_max
= 240 * 8 * 4, /* 5 ms of 8 channel data */
55 static const struct snd_pcm_hardware snd_cobalt_playback
= {
56 .info
= SNDRV_PCM_INFO_BLOCK_TRANSFER
|
58 SNDRV_PCM_INFO_INTERLEAVED
|
59 SNDRV_PCM_INFO_MMAP_VALID
,
61 .formats
= SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S32_LE
,
63 .rates
= SNDRV_PCM_RATE_48000
,
69 .buffer_bytes_max
= 4 * 240 * 8 * 4, /* 5 ms of data */
70 .period_bytes_min
= 1920, /* 1 sample = 8 * 4 bytes */
71 .period_bytes_max
= 240 * 8 * 4, /* 5 ms of 8 channel data */
76 static void sample_cpy(u8
*dst
, const u8
*src
, u32 len
, bool is_s32
)
78 static const unsigned map
[8] = { 0, 1, 5, 4, 2, 3, 6, 7 };
81 while (len
>= (is_s32
? 4 : 2)) {
82 unsigned offset
= map
[idx
] * 4;
83 u32 val
= src
[offset
+ 1] + (src
[offset
+ 2] << 8) +
84 (src
[offset
+ 3] << 16);
90 *dst
++ = (val
>> 8) & 0xff;
91 *dst
++ = (val
>> 16) & 0xff;
92 len
-= is_s32
? 4 : 2;
97 static void cobalt_alsa_announce_pcm_data(struct snd_cobalt_card
*cobsc
,
102 struct snd_pcm_substream
*substream
;
103 struct snd_pcm_runtime
*runtime
;
107 int length
= samples
;
108 int period_elapsed
= 0;
111 dprintk("cobalt alsa announce ptr=%p data=%p num_bytes=%zd\n", cobsc
,
114 substream
= cobsc
->capture_pcm_substream
;
115 if (substream
== NULL
) {
116 dprintk("substream was NULL\n");
120 runtime
= substream
->runtime
;
121 if (runtime
== NULL
) {
122 dprintk("runtime was NULL\n");
125 is_s32
= runtime
->format
== SNDRV_PCM_FORMAT_S32_LE
;
127 stride
= runtime
->frame_bits
>> 3;
129 dprintk("stride is zero\n");
134 dprintk("%s: length was zero\n", __func__
);
138 if (runtime
->dma_area
== NULL
) {
139 dprintk("dma area was NULL - ignoring\n");
143 oldptr
= cobsc
->hwptr_done_capture
;
144 if (oldptr
+ length
>= runtime
->buffer_size
) {
145 unsigned int cnt
= runtime
->buffer_size
- oldptr
;
148 for (i
= 0; i
< cnt
; i
++)
149 sample_cpy(runtime
->dma_area
+ (oldptr
+ i
) * stride
,
152 for (i
= cnt
; i
< length
; i
++)
153 sample_cpy(runtime
->dma_area
+ (i
- cnt
) * stride
,
154 pcm_data
+ i
* skip
, stride
, is_s32
);
158 for (i
= 0; i
< length
; i
++)
159 sample_cpy(runtime
->dma_area
+ (oldptr
+ i
) * stride
,
163 snd_pcm_stream_lock_irqsave(substream
, flags
);
165 cobsc
->hwptr_done_capture
+= length
;
166 if (cobsc
->hwptr_done_capture
>=
167 runtime
->buffer_size
)
168 cobsc
->hwptr_done_capture
-=
169 runtime
->buffer_size
;
171 cobsc
->capture_transfer_done
+= length
;
172 if (cobsc
->capture_transfer_done
>=
173 runtime
->period_size
) {
174 cobsc
->capture_transfer_done
-=
175 runtime
->period_size
;
179 snd_pcm_stream_unlock_irqrestore(substream
, flags
);
182 snd_pcm_period_elapsed(substream
);
185 static int alsa_fnc(struct vb2_buffer
*vb
, void *priv
)
187 struct cobalt_stream
*s
= priv
;
188 unsigned char *p
= vb2_plane_vaddr(vb
, 0);
193 for (i
= 0; i
< 8 * 4; i
++) {
196 pr_cont("%02x", p
[i
]);
200 cobalt_alsa_announce_pcm_data(s
->alsa
,
201 vb2_plane_vaddr(vb
, 0),
203 vb2_get_plane_payload(vb
, 0) / (8 * 4));
207 static int snd_cobalt_pcm_capture_open(struct snd_pcm_substream
*substream
)
209 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
210 struct snd_cobalt_card
*cobsc
= snd_pcm_substream_chip(substream
);
211 struct cobalt_stream
*s
= cobsc
->s
;
213 runtime
->hw
= snd_cobalt_hdmi_capture
;
214 snd_pcm_hw_constraint_integer(runtime
, SNDRV_PCM_HW_PARAM_PERIODS
);
215 cobsc
->capture_pcm_substream
= substream
;
216 runtime
->private_data
= s
;
217 cobsc
->alsa_record_cnt
++;
218 if (cobsc
->alsa_record_cnt
== 1) {
221 rc
= vb2_thread_start(&s
->q
, alsa_fnc
, s
, s
->vdev
.name
);
223 cobsc
->alsa_record_cnt
--;
230 static int snd_cobalt_pcm_capture_close(struct snd_pcm_substream
*substream
)
232 struct snd_cobalt_card
*cobsc
= snd_pcm_substream_chip(substream
);
233 struct cobalt_stream
*s
= cobsc
->s
;
235 cobsc
->alsa_record_cnt
--;
236 if (cobsc
->alsa_record_cnt
== 0)
237 vb2_thread_stop(&s
->q
);
241 static int snd_cobalt_pcm_ioctl(struct snd_pcm_substream
*substream
,
242 unsigned int cmd
, void *arg
)
244 return snd_pcm_lib_ioctl(substream
, cmd
, arg
);
248 static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream
*subs
,
251 struct snd_pcm_runtime
*runtime
= subs
->runtime
;
253 dprintk("Allocating vbuffer\n");
254 if (runtime
->dma_area
) {
255 if (runtime
->dma_bytes
> size
)
258 vfree(runtime
->dma_area
);
260 runtime
->dma_area
= vmalloc(size
);
261 if (!runtime
->dma_area
)
264 runtime
->dma_bytes
= size
;
269 static int snd_cobalt_pcm_hw_params(struct snd_pcm_substream
*substream
,
270 struct snd_pcm_hw_params
*params
)
272 dprintk("%s called\n", __func__
);
274 return snd_pcm_alloc_vmalloc_buffer(substream
,
275 params_buffer_bytes(params
));
278 static int snd_cobalt_pcm_hw_free(struct snd_pcm_substream
*substream
)
280 if (substream
->runtime
->dma_area
) {
281 dprintk("freeing pcm capture region\n");
282 vfree(substream
->runtime
->dma_area
);
283 substream
->runtime
->dma_area
= NULL
;
289 static int snd_cobalt_pcm_prepare(struct snd_pcm_substream
*substream
)
291 struct snd_cobalt_card
*cobsc
= snd_pcm_substream_chip(substream
);
293 cobsc
->hwptr_done_capture
= 0;
294 cobsc
->capture_transfer_done
= 0;
299 static int snd_cobalt_pcm_trigger(struct snd_pcm_substream
*substream
, int cmd
)
302 case SNDRV_PCM_TRIGGER_START
:
303 case SNDRV_PCM_TRIGGER_STOP
:
312 snd_pcm_uframes_t
snd_cobalt_pcm_pointer(struct snd_pcm_substream
*substream
)
314 snd_pcm_uframes_t hwptr_done
;
315 struct snd_cobalt_card
*cobsc
= snd_pcm_substream_chip(substream
);
317 hwptr_done
= cobsc
->hwptr_done_capture
;
322 static void pb_sample_cpy(u8
*dst
, const u8
*src
, u32 len
, bool is_s32
)
324 static const unsigned map
[8] = { 0, 1, 5, 4, 2, 3, 6, 7 };
327 while (len
>= (is_s32
? 4 : 2)) {
328 unsigned offset
= map
[idx
] * 4;
329 u8
*out
= dst
+ offset
;
340 len
-= is_s32
? 4 : 2;
345 static void cobalt_alsa_pb_pcm_data(struct snd_cobalt_card
*cobsc
,
350 struct snd_pcm_substream
*substream
;
351 struct snd_pcm_runtime
*runtime
;
358 dprintk("cobalt alsa pb ptr=%p data=%p samples=%zd\n", cobsc
,
361 substream
= cobsc
->playback_pcm_substream
;
362 if (substream
== NULL
) {
363 dprintk("substream was NULL\n");
367 runtime
= substream
->runtime
;
368 if (runtime
== NULL
) {
369 dprintk("runtime was NULL\n");
373 is_s32
= runtime
->format
== SNDRV_PCM_FORMAT_S32_LE
;
374 stride
= runtime
->frame_bits
>> 3;
376 dprintk("stride is zero\n");
381 dprintk("%s: samples was zero\n", __func__
);
385 if (runtime
->dma_area
== NULL
) {
386 dprintk("dma area was NULL - ignoring\n");
390 pos
= cobsc
->pb_pos
% cobsc
->pb_size
;
391 for (i
= 0; i
< cobsc
->pb_count
/ (8 * 4); i
++)
392 pb_sample_cpy(pcm_data
+ i
* skip
,
393 runtime
->dma_area
+ pos
+ i
* stride
,
395 snd_pcm_stream_lock_irqsave(substream
, flags
);
397 cobsc
->pb_pos
+= i
* stride
;
399 snd_pcm_stream_unlock_irqrestore(substream
, flags
);
400 if (cobsc
->pb_pos
% cobsc
->pb_count
== 0)
401 snd_pcm_period_elapsed(substream
);
404 static int alsa_pb_fnc(struct vb2_buffer
*vb
, void *priv
)
406 struct cobalt_stream
*s
= priv
;
408 if (s
->alsa
->alsa_pb_channel
)
409 cobalt_alsa_pb_pcm_data(s
->alsa
,
410 vb2_plane_vaddr(vb
, 0),
412 vb2_get_plane_payload(vb
, 0) / (8 * 4));
416 static int snd_cobalt_pcm_playback_open(struct snd_pcm_substream
*substream
)
418 struct snd_cobalt_card
*cobsc
= snd_pcm_substream_chip(substream
);
419 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
420 struct cobalt_stream
*s
= cobsc
->s
;
422 runtime
->hw
= snd_cobalt_playback
;
423 snd_pcm_hw_constraint_integer(runtime
, SNDRV_PCM_HW_PARAM_PERIODS
);
424 cobsc
->playback_pcm_substream
= substream
;
425 runtime
->private_data
= s
;
426 cobsc
->alsa_playback_cnt
++;
427 if (cobsc
->alsa_playback_cnt
== 1) {
430 rc
= vb2_thread_start(&s
->q
, alsa_pb_fnc
, s
, s
->vdev
.name
);
432 cobsc
->alsa_playback_cnt
--;
440 static int snd_cobalt_pcm_playback_close(struct snd_pcm_substream
*substream
)
442 struct snd_cobalt_card
*cobsc
= snd_pcm_substream_chip(substream
);
443 struct cobalt_stream
*s
= cobsc
->s
;
445 cobsc
->alsa_playback_cnt
--;
446 if (cobsc
->alsa_playback_cnt
== 0)
447 vb2_thread_stop(&s
->q
);
451 static int snd_cobalt_pcm_pb_prepare(struct snd_pcm_substream
*substream
)
453 struct snd_cobalt_card
*cobsc
= snd_pcm_substream_chip(substream
);
455 cobsc
->pb_size
= snd_pcm_lib_buffer_bytes(substream
);
456 cobsc
->pb_count
= snd_pcm_lib_period_bytes(substream
);
462 static int snd_cobalt_pcm_pb_trigger(struct snd_pcm_substream
*substream
,
465 struct snd_cobalt_card
*cobsc
= snd_pcm_substream_chip(substream
);
468 case SNDRV_PCM_TRIGGER_START
:
469 if (cobsc
->alsa_pb_channel
)
471 cobsc
->alsa_pb_channel
= true;
473 case SNDRV_PCM_TRIGGER_STOP
:
474 cobsc
->alsa_pb_channel
= false;
482 snd_pcm_uframes_t
snd_cobalt_pcm_pb_pointer(struct snd_pcm_substream
*substream
)
484 struct snd_cobalt_card
*cobsc
= snd_pcm_substream_chip(substream
);
489 return bytes_to_frames(substream
->runtime
, ptr
) %
490 substream
->runtime
->buffer_size
;
493 static struct page
*snd_pcm_get_vmalloc_page(struct snd_pcm_substream
*subs
,
494 unsigned long offset
)
496 void *pageptr
= subs
->runtime
->dma_area
+ offset
;
498 return vmalloc_to_page(pageptr
);
501 static const struct snd_pcm_ops snd_cobalt_pcm_capture_ops
= {
502 .open
= snd_cobalt_pcm_capture_open
,
503 .close
= snd_cobalt_pcm_capture_close
,
504 .ioctl
= snd_cobalt_pcm_ioctl
,
505 .hw_params
= snd_cobalt_pcm_hw_params
,
506 .hw_free
= snd_cobalt_pcm_hw_free
,
507 .prepare
= snd_cobalt_pcm_prepare
,
508 .trigger
= snd_cobalt_pcm_trigger
,
509 .pointer
= snd_cobalt_pcm_pointer
,
510 .page
= snd_pcm_get_vmalloc_page
,
513 static const struct snd_pcm_ops snd_cobalt_pcm_playback_ops
= {
514 .open
= snd_cobalt_pcm_playback_open
,
515 .close
= snd_cobalt_pcm_playback_close
,
516 .ioctl
= snd_cobalt_pcm_ioctl
,
517 .hw_params
= snd_cobalt_pcm_hw_params
,
518 .hw_free
= snd_cobalt_pcm_hw_free
,
519 .prepare
= snd_cobalt_pcm_pb_prepare
,
520 .trigger
= snd_cobalt_pcm_pb_trigger
,
521 .pointer
= snd_cobalt_pcm_pb_pointer
,
522 .page
= snd_pcm_get_vmalloc_page
,
525 int snd_cobalt_pcm_create(struct snd_cobalt_card
*cobsc
)
528 struct snd_card
*sc
= cobsc
->sc
;
529 struct cobalt_stream
*s
= cobsc
->s
;
530 struct cobalt
*cobalt
= s
->cobalt
;
533 s
->q
.gfp_flags
|= __GFP_ZERO
;
536 cobalt_s_bit_sysctrl(cobalt
,
537 COBALT_SYS_CTRL_AUDIO_IPP_RESETN_BIT(s
->video_channel
),
540 cobalt_s_bit_sysctrl(cobalt
,
541 COBALT_SYS_CTRL_AUDIO_IPP_RESETN_BIT(s
->video_channel
),
545 ret
= snd_pcm_new(sc
, "Cobalt PCM-In HDMI",
546 0, /* PCM device 0, the only one for this card */
547 0, /* 0 playback substreams */
548 1, /* 1 capture substream */
551 cobalt_err("snd_cobalt_pcm_create() failed for input with err %d\n",
556 snd_pcm_set_ops(sp
, SNDRV_PCM_STREAM_CAPTURE
,
557 &snd_cobalt_pcm_capture_ops
);
559 sp
->private_data
= cobsc
;
560 strlcpy(sp
->name
, "cobalt", sizeof(sp
->name
));
562 cobalt_s_bit_sysctrl(cobalt
,
563 COBALT_SYS_CTRL_AUDIO_OPP_RESETN_BIT
, 0);
565 cobalt_s_bit_sysctrl(cobalt
,
566 COBALT_SYS_CTRL_AUDIO_OPP_RESETN_BIT
, 1);
569 ret
= snd_pcm_new(sc
, "Cobalt PCM-Out HDMI",
570 0, /* PCM device 0, the only one for this card */
571 1, /* 0 playback substreams */
572 0, /* 1 capture substream */
575 cobalt_err("snd_cobalt_pcm_create() failed for output with err %d\n",
580 snd_pcm_set_ops(sp
, SNDRV_PCM_STREAM_PLAYBACK
,
581 &snd_cobalt_pcm_playback_ops
);
583 sp
->private_data
= cobsc
;
584 strlcpy(sp
->name
, "cobalt", sizeof(sp
->name
));