18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * ALSA PCM device for the 48c2ecf20Sopenharmony_ci * ALSA interface to cx18 PCM capture streams 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Copyright (C) 2009 Andy Walls <awalls@md.metrocast.net> 78c2ecf20Sopenharmony_ci * Copyright (C) 2009 Devin Heitmueller <dheitmueller@kernellabs.com> 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * Portions of this work were sponsored by ONELAN Limited. 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/init.h> 138c2ecf20Sopenharmony_ci#include <linux/kernel.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include <media/v4l2-device.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#include <sound/core.h> 188c2ecf20Sopenharmony_ci#include <sound/pcm.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include "cx18-driver.h" 218c2ecf20Sopenharmony_ci#include "cx18-queue.h" 228c2ecf20Sopenharmony_ci#include "cx18-streams.h" 238c2ecf20Sopenharmony_ci#include "cx18-fileops.h" 248c2ecf20Sopenharmony_ci#include "cx18-alsa.h" 258c2ecf20Sopenharmony_ci#include "cx18-alsa-pcm.h" 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistatic unsigned int pcm_debug; 288c2ecf20Sopenharmony_cimodule_param(pcm_debug, int, 0644); 298c2ecf20Sopenharmony_ciMODULE_PARM_DESC(pcm_debug, "enable debug messages for pcm"); 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#define dprintk(fmt, arg...) do { \ 328c2ecf20Sopenharmony_ci if (pcm_debug) \ 338c2ecf20Sopenharmony_ci printk(KERN_INFO "cx18-alsa-pcm %s: " fmt, \ 348c2ecf20Sopenharmony_ci __func__, ##arg); \ 358c2ecf20Sopenharmony_ci } while (0) 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistatic const struct snd_pcm_hardware snd_cx18_hw_capture = { 388c2ecf20Sopenharmony_ci .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | 398c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_MMAP | 408c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_INTERLEAVED | 418c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_MMAP_VALID, 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S16_LE, 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_48000, 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci .rate_min = 48000, 488c2ecf20Sopenharmony_ci .rate_max = 48000, 498c2ecf20Sopenharmony_ci .channels_min = 2, 508c2ecf20Sopenharmony_ci .channels_max = 2, 518c2ecf20Sopenharmony_ci .buffer_bytes_max = 62720 * 8, /* just about the value in usbaudio.c */ 528c2ecf20Sopenharmony_ci .period_bytes_min = 64, /* 12544/2, */ 538c2ecf20Sopenharmony_ci .period_bytes_max = 12544, 548c2ecf20Sopenharmony_ci .periods_min = 2, 558c2ecf20Sopenharmony_ci .periods_max = 98, /* 12544, */ 568c2ecf20Sopenharmony_ci}; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_civoid cx18_alsa_announce_pcm_data(struct snd_cx18_card *cxsc, u8 *pcm_data, 598c2ecf20Sopenharmony_ci size_t num_bytes) 608c2ecf20Sopenharmony_ci{ 618c2ecf20Sopenharmony_ci struct snd_pcm_substream *substream; 628c2ecf20Sopenharmony_ci struct snd_pcm_runtime *runtime; 638c2ecf20Sopenharmony_ci unsigned int oldptr; 648c2ecf20Sopenharmony_ci unsigned int stride; 658c2ecf20Sopenharmony_ci int period_elapsed = 0; 668c2ecf20Sopenharmony_ci int length; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci dprintk("cx18 alsa announce ptr=%p data=%p num_bytes=%zu\n", cxsc, 698c2ecf20Sopenharmony_ci pcm_data, num_bytes); 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci substream = cxsc->capture_pcm_substream; 728c2ecf20Sopenharmony_ci if (substream == NULL) { 738c2ecf20Sopenharmony_ci dprintk("substream was NULL\n"); 748c2ecf20Sopenharmony_ci return; 758c2ecf20Sopenharmony_ci } 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci runtime = substream->runtime; 788c2ecf20Sopenharmony_ci if (runtime == NULL) { 798c2ecf20Sopenharmony_ci dprintk("runtime was NULL\n"); 808c2ecf20Sopenharmony_ci return; 818c2ecf20Sopenharmony_ci } 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci stride = runtime->frame_bits >> 3; 848c2ecf20Sopenharmony_ci if (stride == 0) { 858c2ecf20Sopenharmony_ci dprintk("stride is zero\n"); 868c2ecf20Sopenharmony_ci return; 878c2ecf20Sopenharmony_ci } 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci length = num_bytes / stride; 908c2ecf20Sopenharmony_ci if (length == 0) { 918c2ecf20Sopenharmony_ci dprintk("%s: length was zero\n", __func__); 928c2ecf20Sopenharmony_ci return; 938c2ecf20Sopenharmony_ci } 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci if (runtime->dma_area == NULL) { 968c2ecf20Sopenharmony_ci dprintk("dma area was NULL - ignoring\n"); 978c2ecf20Sopenharmony_ci return; 988c2ecf20Sopenharmony_ci } 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci oldptr = cxsc->hwptr_done_capture; 1018c2ecf20Sopenharmony_ci if (oldptr + length >= runtime->buffer_size) { 1028c2ecf20Sopenharmony_ci unsigned int cnt = 1038c2ecf20Sopenharmony_ci runtime->buffer_size - oldptr; 1048c2ecf20Sopenharmony_ci memcpy(runtime->dma_area + oldptr * stride, pcm_data, 1058c2ecf20Sopenharmony_ci cnt * stride); 1068c2ecf20Sopenharmony_ci memcpy(runtime->dma_area, pcm_data + cnt * stride, 1078c2ecf20Sopenharmony_ci length * stride - cnt * stride); 1088c2ecf20Sopenharmony_ci } else { 1098c2ecf20Sopenharmony_ci memcpy(runtime->dma_area + oldptr * stride, pcm_data, 1108c2ecf20Sopenharmony_ci length * stride); 1118c2ecf20Sopenharmony_ci } 1128c2ecf20Sopenharmony_ci snd_pcm_stream_lock(substream); 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci cxsc->hwptr_done_capture += length; 1158c2ecf20Sopenharmony_ci if (cxsc->hwptr_done_capture >= 1168c2ecf20Sopenharmony_ci runtime->buffer_size) 1178c2ecf20Sopenharmony_ci cxsc->hwptr_done_capture -= 1188c2ecf20Sopenharmony_ci runtime->buffer_size; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci cxsc->capture_transfer_done += length; 1218c2ecf20Sopenharmony_ci if (cxsc->capture_transfer_done >= 1228c2ecf20Sopenharmony_ci runtime->period_size) { 1238c2ecf20Sopenharmony_ci cxsc->capture_transfer_done -= 1248c2ecf20Sopenharmony_ci runtime->period_size; 1258c2ecf20Sopenharmony_ci period_elapsed = 1; 1268c2ecf20Sopenharmony_ci } 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci snd_pcm_stream_unlock(substream); 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci if (period_elapsed) 1318c2ecf20Sopenharmony_ci snd_pcm_period_elapsed(substream); 1328c2ecf20Sopenharmony_ci} 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_cistatic int snd_cx18_pcm_capture_open(struct snd_pcm_substream *substream) 1358c2ecf20Sopenharmony_ci{ 1368c2ecf20Sopenharmony_ci struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); 1378c2ecf20Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 1388c2ecf20Sopenharmony_ci struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; 1398c2ecf20Sopenharmony_ci struct cx18 *cx = to_cx18(v4l2_dev); 1408c2ecf20Sopenharmony_ci struct cx18_stream *s; 1418c2ecf20Sopenharmony_ci struct cx18_open_id item; 1428c2ecf20Sopenharmony_ci int ret; 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci /* Instruct the cx18 to start sending packets */ 1458c2ecf20Sopenharmony_ci snd_cx18_lock(cxsc); 1468c2ecf20Sopenharmony_ci s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM]; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci item.cx = cx; 1498c2ecf20Sopenharmony_ci item.type = s->type; 1508c2ecf20Sopenharmony_ci item.open_id = cx->open_id++; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci /* See if the stream is available */ 1538c2ecf20Sopenharmony_ci if (cx18_claim_stream(&item, item.type)) { 1548c2ecf20Sopenharmony_ci /* No, it's already in use */ 1558c2ecf20Sopenharmony_ci snd_cx18_unlock(cxsc); 1568c2ecf20Sopenharmony_ci return -EBUSY; 1578c2ecf20Sopenharmony_ci } 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci if (test_bit(CX18_F_S_STREAMOFF, &s->s_flags) || 1608c2ecf20Sopenharmony_ci test_and_set_bit(CX18_F_S_STREAMING, &s->s_flags)) { 1618c2ecf20Sopenharmony_ci /* We're already streaming. No additional action required */ 1628c2ecf20Sopenharmony_ci snd_cx18_unlock(cxsc); 1638c2ecf20Sopenharmony_ci return 0; 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci runtime->hw = snd_cx18_hw_capture; 1688c2ecf20Sopenharmony_ci snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); 1698c2ecf20Sopenharmony_ci cxsc->capture_pcm_substream = substream; 1708c2ecf20Sopenharmony_ci runtime->private_data = cx; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci cx->pcm_announce_callback = cx18_alsa_announce_pcm_data; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci /* Not currently streaming, so start it up */ 1758c2ecf20Sopenharmony_ci set_bit(CX18_F_S_STREAMING, &s->s_flags); 1768c2ecf20Sopenharmony_ci ret = cx18_start_v4l2_encode_stream(s); 1778c2ecf20Sopenharmony_ci snd_cx18_unlock(cxsc); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci return ret; 1808c2ecf20Sopenharmony_ci} 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_cistatic int snd_cx18_pcm_capture_close(struct snd_pcm_substream *substream) 1838c2ecf20Sopenharmony_ci{ 1848c2ecf20Sopenharmony_ci struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); 1858c2ecf20Sopenharmony_ci struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; 1868c2ecf20Sopenharmony_ci struct cx18 *cx = to_cx18(v4l2_dev); 1878c2ecf20Sopenharmony_ci struct cx18_stream *s; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci /* Instruct the cx18 to stop sending packets */ 1908c2ecf20Sopenharmony_ci snd_cx18_lock(cxsc); 1918c2ecf20Sopenharmony_ci s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM]; 1928c2ecf20Sopenharmony_ci cx18_stop_v4l2_encode_stream(s, 0); 1938c2ecf20Sopenharmony_ci clear_bit(CX18_F_S_STREAMING, &s->s_flags); 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci cx18_release_stream(s); 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci cx->pcm_announce_callback = NULL; 1988c2ecf20Sopenharmony_ci snd_cx18_unlock(cxsc); 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci return 0; 2018c2ecf20Sopenharmony_ci} 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_cistatic int snd_cx18_pcm_prepare(struct snd_pcm_substream *substream) 2048c2ecf20Sopenharmony_ci{ 2058c2ecf20Sopenharmony_ci struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci cxsc->hwptr_done_capture = 0; 2088c2ecf20Sopenharmony_ci cxsc->capture_transfer_done = 0; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci return 0; 2118c2ecf20Sopenharmony_ci} 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_cistatic int snd_cx18_pcm_trigger(struct snd_pcm_substream *substream, int cmd) 2148c2ecf20Sopenharmony_ci{ 2158c2ecf20Sopenharmony_ci return 0; 2168c2ecf20Sopenharmony_ci} 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_cistatic 2198c2ecf20Sopenharmony_cisnd_pcm_uframes_t snd_cx18_pcm_pointer(struct snd_pcm_substream *substream) 2208c2ecf20Sopenharmony_ci{ 2218c2ecf20Sopenharmony_ci unsigned long flags; 2228c2ecf20Sopenharmony_ci snd_pcm_uframes_t hwptr_done; 2238c2ecf20Sopenharmony_ci struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci spin_lock_irqsave(&cxsc->slock, flags); 2268c2ecf20Sopenharmony_ci hwptr_done = cxsc->hwptr_done_capture; 2278c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&cxsc->slock, flags); 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci return hwptr_done; 2308c2ecf20Sopenharmony_ci} 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_cistatic const struct snd_pcm_ops snd_cx18_pcm_capture_ops = { 2338c2ecf20Sopenharmony_ci .open = snd_cx18_pcm_capture_open, 2348c2ecf20Sopenharmony_ci .close = snd_cx18_pcm_capture_close, 2358c2ecf20Sopenharmony_ci .prepare = snd_cx18_pcm_prepare, 2368c2ecf20Sopenharmony_ci .trigger = snd_cx18_pcm_trigger, 2378c2ecf20Sopenharmony_ci .pointer = snd_cx18_pcm_pointer, 2388c2ecf20Sopenharmony_ci}; 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ciint snd_cx18_pcm_create(struct snd_cx18_card *cxsc) 2418c2ecf20Sopenharmony_ci{ 2428c2ecf20Sopenharmony_ci struct snd_pcm *sp; 2438c2ecf20Sopenharmony_ci struct snd_card *sc = cxsc->sc; 2448c2ecf20Sopenharmony_ci struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; 2458c2ecf20Sopenharmony_ci struct cx18 *cx = to_cx18(v4l2_dev); 2468c2ecf20Sopenharmony_ci int ret; 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci ret = snd_pcm_new(sc, "CX23418 PCM", 2498c2ecf20Sopenharmony_ci 0, /* PCM device 0, the only one for this card */ 2508c2ecf20Sopenharmony_ci 0, /* 0 playback substreams */ 2518c2ecf20Sopenharmony_ci 1, /* 1 capture substream */ 2528c2ecf20Sopenharmony_ci &sp); 2538c2ecf20Sopenharmony_ci if (ret) { 2548c2ecf20Sopenharmony_ci CX18_ALSA_ERR("%s: snd_cx18_pcm_create() failed with err %d\n", 2558c2ecf20Sopenharmony_ci __func__, ret); 2568c2ecf20Sopenharmony_ci goto err_exit; 2578c2ecf20Sopenharmony_ci } 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci spin_lock_init(&cxsc->slock); 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_CAPTURE, 2628c2ecf20Sopenharmony_ci &snd_cx18_pcm_capture_ops); 2638c2ecf20Sopenharmony_ci snd_pcm_set_managed_buffer_all(sp, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); 2648c2ecf20Sopenharmony_ci sp->info_flags = 0; 2658c2ecf20Sopenharmony_ci sp->private_data = cxsc; 2668c2ecf20Sopenharmony_ci strscpy(sp->name, cx->card_name, sizeof(sp->name)); 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci return 0; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_cierr_exit: 2718c2ecf20Sopenharmony_ci return ret; 2728c2ecf20Sopenharmony_ci} 273