Path: blob/master/drivers/media/video/cx18/cx18-alsa-pcm.c
17728 views
/*1* ALSA PCM device for the2* ALSA interface to cx18 PCM capture streams3*4* Copyright (C) 2009 Andy Walls <[email protected]>5* Copyright (C) 2009 Devin Heitmueller <[email protected]>6*7* Portions of this work were sponsored by ONELAN Limited.8*9* This program is free software; you can redistribute it and/or modify10* it under the terms of the GNU General Public License as published by11* the Free Software Foundation; either version 2 of the License, or12* (at your option) any later version.13*14* This program is distributed in the hope that it will be useful,15* but WITHOUT ANY WARRANTY; without even the implied warranty of16* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the17* GNU General Public License for more details.18*19* You should have received a copy of the GNU General Public License20* along with this program; if not, write to the Free Software21* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA22* 02111-1307 USA23*/2425#include <linux/init.h>26#include <linux/kernel.h>27#include <linux/vmalloc.h>2829#include <media/v4l2-device.h>3031#include <sound/core.h>32#include <sound/pcm.h>3334#include "cx18-driver.h"35#include "cx18-queue.h"36#include "cx18-streams.h"37#include "cx18-fileops.h"38#include "cx18-alsa.h"3940static unsigned int pcm_debug;41module_param(pcm_debug, int, 0644);42MODULE_PARM_DESC(pcm_debug, "enable debug messages for pcm");4344#define dprintk(fmt, arg...) do { \45if (pcm_debug) \46printk(KERN_INFO "cx18-alsa-pcm %s: " fmt, \47__func__, ##arg); \48} while (0)4950static struct snd_pcm_hardware snd_cx18_hw_capture = {51.info = SNDRV_PCM_INFO_BLOCK_TRANSFER |52SNDRV_PCM_INFO_MMAP |53SNDRV_PCM_INFO_INTERLEAVED |54SNDRV_PCM_INFO_MMAP_VALID,5556.formats = SNDRV_PCM_FMTBIT_S16_LE,5758.rates = SNDRV_PCM_RATE_48000,5960.rate_min = 48000,61.rate_max = 48000,62.channels_min = 2,63.channels_max = 2,64.buffer_bytes_max = 62720 * 8, /* just about the value in usbaudio.c */65.period_bytes_min = 64, /* 12544/2, */66.period_bytes_max = 12544,67.periods_min = 2,68.periods_max = 98, /* 12544, */69};7071void cx18_alsa_announce_pcm_data(struct snd_cx18_card *cxsc, u8 *pcm_data,72size_t num_bytes)73{74struct snd_pcm_substream *substream;75struct snd_pcm_runtime *runtime;76unsigned int oldptr;77unsigned int stride;78int period_elapsed = 0;79int length;8081dprintk("cx18 alsa announce ptr=%p data=%p num_bytes=%zd\n", cxsc,82pcm_data, num_bytes);8384substream = cxsc->capture_pcm_substream;85if (substream == NULL) {86dprintk("substream was NULL\n");87return;88}8990runtime = substream->runtime;91if (runtime == NULL) {92dprintk("runtime was NULL\n");93return;94}9596stride = runtime->frame_bits >> 3;97if (stride == 0) {98dprintk("stride is zero\n");99return;100}101102length = num_bytes / stride;103if (length == 0) {104dprintk("%s: length was zero\n", __func__);105return;106}107108if (runtime->dma_area == NULL) {109dprintk("dma area was NULL - ignoring\n");110return;111}112113oldptr = cxsc->hwptr_done_capture;114if (oldptr + length >= runtime->buffer_size) {115unsigned int cnt =116runtime->buffer_size - oldptr;117memcpy(runtime->dma_area + oldptr * stride, pcm_data,118cnt * stride);119memcpy(runtime->dma_area, pcm_data + cnt * stride,120length * stride - cnt * stride);121} else {122memcpy(runtime->dma_area + oldptr * stride, pcm_data,123length * stride);124}125snd_pcm_stream_lock(substream);126127cxsc->hwptr_done_capture += length;128if (cxsc->hwptr_done_capture >=129runtime->buffer_size)130cxsc->hwptr_done_capture -=131runtime->buffer_size;132133cxsc->capture_transfer_done += length;134if (cxsc->capture_transfer_done >=135runtime->period_size) {136cxsc->capture_transfer_done -=137runtime->period_size;138period_elapsed = 1;139}140141snd_pcm_stream_unlock(substream);142143if (period_elapsed)144snd_pcm_period_elapsed(substream);145}146147static int snd_cx18_pcm_capture_open(struct snd_pcm_substream *substream)148{149struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);150struct snd_pcm_runtime *runtime = substream->runtime;151struct v4l2_device *v4l2_dev = cxsc->v4l2_dev;152struct cx18 *cx = to_cx18(v4l2_dev);153struct cx18_stream *s;154struct cx18_open_id item;155int ret;156157/* Instruct the cx18 to start sending packets */158snd_cx18_lock(cxsc);159s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM];160161item.cx = cx;162item.type = s->type;163item.open_id = cx->open_id++;164165/* See if the stream is available */166if (cx18_claim_stream(&item, item.type)) {167/* No, it's already in use */168snd_cx18_unlock(cxsc);169return -EBUSY;170}171172if (test_bit(CX18_F_S_STREAMOFF, &s->s_flags) ||173test_and_set_bit(CX18_F_S_STREAMING, &s->s_flags)) {174/* We're already streaming. No additional action required */175snd_cx18_unlock(cxsc);176return 0;177}178179180runtime->hw = snd_cx18_hw_capture;181snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);182cxsc->capture_pcm_substream = substream;183runtime->private_data = cx;184185cx->pcm_announce_callback = cx18_alsa_announce_pcm_data;186187/* Not currently streaming, so start it up */188set_bit(CX18_F_S_STREAMING, &s->s_flags);189ret = cx18_start_v4l2_encode_stream(s);190snd_cx18_unlock(cxsc);191192return 0;193}194195static int snd_cx18_pcm_capture_close(struct snd_pcm_substream *substream)196{197struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);198struct v4l2_device *v4l2_dev = cxsc->v4l2_dev;199struct cx18 *cx = to_cx18(v4l2_dev);200struct cx18_stream *s;201int ret;202203/* Instruct the cx18 to stop sending packets */204snd_cx18_lock(cxsc);205s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM];206ret = cx18_stop_v4l2_encode_stream(s, 0);207clear_bit(CX18_F_S_STREAMING, &s->s_flags);208209cx18_release_stream(s);210211cx->pcm_announce_callback = NULL;212snd_cx18_unlock(cxsc);213214return 0;215}216217static int snd_cx18_pcm_ioctl(struct snd_pcm_substream *substream,218unsigned int cmd, void *arg)219{220struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);221int ret;222223snd_cx18_lock(cxsc);224ret = snd_pcm_lib_ioctl(substream, cmd, arg);225snd_cx18_unlock(cxsc);226return ret;227}228229230static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs,231size_t size)232{233struct snd_pcm_runtime *runtime = subs->runtime;234235dprintk("Allocating vbuffer\n");236if (runtime->dma_area) {237if (runtime->dma_bytes > size)238return 0;239240vfree(runtime->dma_area);241}242runtime->dma_area = vmalloc(size);243if (!runtime->dma_area)244return -ENOMEM;245246runtime->dma_bytes = size;247248return 0;249}250251static int snd_cx18_pcm_hw_params(struct snd_pcm_substream *substream,252struct snd_pcm_hw_params *params)253{254int ret;255256dprintk("%s called\n", __func__);257258ret = snd_pcm_alloc_vmalloc_buffer(substream,259params_buffer_bytes(params));260return 0;261}262263static int snd_cx18_pcm_hw_free(struct snd_pcm_substream *substream)264{265struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);266unsigned long flags;267268spin_lock_irqsave(&cxsc->slock, flags);269if (substream->runtime->dma_area) {270dprintk("freeing pcm capture region\n");271vfree(substream->runtime->dma_area);272substream->runtime->dma_area = NULL;273}274spin_unlock_irqrestore(&cxsc->slock, flags);275276return 0;277}278279static int snd_cx18_pcm_prepare(struct snd_pcm_substream *substream)280{281struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);282283cxsc->hwptr_done_capture = 0;284cxsc->capture_transfer_done = 0;285286return 0;287}288289static int snd_cx18_pcm_trigger(struct snd_pcm_substream *substream, int cmd)290{291return 0;292}293294static295snd_pcm_uframes_t snd_cx18_pcm_pointer(struct snd_pcm_substream *substream)296{297unsigned long flags;298snd_pcm_uframes_t hwptr_done;299struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);300301spin_lock_irqsave(&cxsc->slock, flags);302hwptr_done = cxsc->hwptr_done_capture;303spin_unlock_irqrestore(&cxsc->slock, flags);304305return hwptr_done;306}307308static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs,309unsigned long offset)310{311void *pageptr = subs->runtime->dma_area + offset;312313return vmalloc_to_page(pageptr);314}315316static struct snd_pcm_ops snd_cx18_pcm_capture_ops = {317.open = snd_cx18_pcm_capture_open,318.close = snd_cx18_pcm_capture_close,319.ioctl = snd_cx18_pcm_ioctl,320.hw_params = snd_cx18_pcm_hw_params,321.hw_free = snd_cx18_pcm_hw_free,322.prepare = snd_cx18_pcm_prepare,323.trigger = snd_cx18_pcm_trigger,324.pointer = snd_cx18_pcm_pointer,325.page = snd_pcm_get_vmalloc_page,326};327328int snd_cx18_pcm_create(struct snd_cx18_card *cxsc)329{330struct snd_pcm *sp;331struct snd_card *sc = cxsc->sc;332struct v4l2_device *v4l2_dev = cxsc->v4l2_dev;333struct cx18 *cx = to_cx18(v4l2_dev);334int ret;335336ret = snd_pcm_new(sc, "CX23418 PCM",3370, /* PCM device 0, the only one for this card */3380, /* 0 playback substreams */3391, /* 1 capture substream */340&sp);341if (ret) {342CX18_ALSA_ERR("%s: snd_cx18_pcm_create() failed with err %d\n",343__func__, ret);344goto err_exit;345}346347spin_lock_init(&cxsc->slock);348349snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_CAPTURE,350&snd_cx18_pcm_capture_ops);351sp->info_flags = 0;352sp->private_data = cxsc;353strlcpy(sp->name, cx->card_name, sizeof(sp->name));354355return 0;356357err_exit:358return ret;359}360361362