2009-06-24 07:26:45 -03:00
/*
* ALSA PCM device for the
* ALSA interface to cx18 PCM capture streams
*
2010-05-23 18:53:35 -03:00
* Copyright ( C ) 2009 Andy Walls < awalls @ md . metrocast . net >
2010-01-18 21:29:51 -03:00
* Copyright ( C ) 2009 Devin Heitmueller < dheitmueller @ kernellabs . com >
*
* Portions of this work were sponsored by ONELAN Limited .
2009-06-24 07:26:45 -03:00
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA
* 02111 - 1307 USA
*/
# include <linux/init.h>
# include <linux/kernel.h>
2010-01-27 03:53:39 -03:00
# include <linux/vmalloc.h>
2009-06-24 07:26:45 -03:00
# include <media/v4l2-device.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include "cx18-driver.h"
2010-01-18 21:29:51 -03:00
# include "cx18-queue.h"
# include "cx18-streams.h"
# include "cx18-fileops.h"
2009-06-24 07:26:45 -03:00
# include "cx18-alsa.h"
2012-10-27 11:28:50 -03:00
# include "cx18-alsa-pcm.h"
2009-06-24 07:26:45 -03:00
2009-12-20 23:50:02 -03:00
static unsigned int pcm_debug ;
module_param ( pcm_debug , int , 0644 ) ;
MODULE_PARM_DESC ( pcm_debug , " enable debug messages for pcm " ) ;
2010-01-18 21:29:51 -03:00
# define dprintk(fmt, arg...) do { \
2009-12-20 23:50:02 -03:00
if ( pcm_debug ) \
printk ( KERN_INFO " cx18-alsa-pcm %s: " fmt , \
2010-01-18 21:29:51 -03:00
__func__ , # # arg ) ; \
} while ( 0 )
static struct snd_pcm_hardware snd_cx18_hw_capture = {
. info = SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID ,
. formats = SNDRV_PCM_FMTBIT_S16_LE ,
2010-01-07 00:56:14 -03:00
. rates = SNDRV_PCM_RATE_48000 ,
2010-01-18 21:29:51 -03:00
. rate_min = 48000 ,
. rate_max = 48000 ,
. channels_min = 2 ,
. channels_max = 2 ,
. buffer_bytes_max = 62720 * 8 , /* just about the value in usbaudio.c */
. period_bytes_min = 64 , /* 12544/2, */
. period_bytes_max = 12544 ,
. periods_min = 2 ,
. periods_max = 98 , /* 12544, */
} ;
2009-12-20 23:53:46 -03:00
void cx18_alsa_announce_pcm_data ( struct snd_cx18_card * cxsc , u8 * pcm_data ,
size_t num_bytes )
{
struct snd_pcm_substream * substream ;
struct snd_pcm_runtime * runtime ;
unsigned int oldptr ;
unsigned int stride ;
int period_elapsed = 0 ;
int length ;
2010-02-02 19:40:49 -03:00
dprintk ( " cx18 alsa announce ptr=%p data=%p num_bytes=%zd \n " , cxsc ,
2009-12-20 23:53:46 -03:00
pcm_data , num_bytes ) ;
substream = cxsc - > capture_pcm_substream ;
if ( substream = = NULL ) {
dprintk ( " substream was NULL \n " ) ;
return ;
}
runtime = substream - > runtime ;
if ( runtime = = NULL ) {
dprintk ( " runtime was NULL \n " ) ;
return ;
}
stride = runtime - > frame_bits > > 3 ;
if ( stride = = 0 ) {
dprintk ( " stride is zero \n " ) ;
return ;
}
length = num_bytes / stride ;
if ( length = = 0 ) {
dprintk ( " %s: length was zero \n " , __func__ ) ;
return ;
}
if ( runtime - > dma_area = = NULL ) {
dprintk ( " dma area was NULL - ignoring \n " ) ;
return ;
}
oldptr = cxsc - > hwptr_done_capture ;
if ( oldptr + length > = runtime - > buffer_size ) {
unsigned int cnt =
runtime - > buffer_size - oldptr ;
memcpy ( runtime - > dma_area + oldptr * stride , pcm_data ,
cnt * stride ) ;
memcpy ( runtime - > dma_area , pcm_data + cnt * stride ,
length * stride - cnt * stride ) ;
} else {
memcpy ( runtime - > dma_area + oldptr * stride , pcm_data ,
length * stride ) ;
}
snd_pcm_stream_lock ( substream ) ;
cxsc - > hwptr_done_capture + = length ;
if ( cxsc - > hwptr_done_capture > =
runtime - > buffer_size )
cxsc - > hwptr_done_capture - =
runtime - > buffer_size ;
cxsc - > capture_transfer_done + = length ;
if ( cxsc - > capture_transfer_done > =
runtime - > period_size ) {
cxsc - > capture_transfer_done - =
runtime - > period_size ;
period_elapsed = 1 ;
}
snd_pcm_stream_unlock ( substream ) ;
if ( period_elapsed )
snd_pcm_period_elapsed ( substream ) ;
}
2009-06-24 07:26:45 -03:00
static int snd_cx18_pcm_capture_open ( struct snd_pcm_substream * substream )
{
struct snd_cx18_card * cxsc = snd_pcm_substream_chip ( substream ) ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct v4l2_device * v4l2_dev = cxsc - > v4l2_dev ;
struct cx18 * cx = to_cx18 ( v4l2_dev ) ;
2010-01-18 21:29:51 -03:00
struct cx18_stream * s ;
2010-01-30 15:50:51 -03:00
struct cx18_open_id item ;
2010-01-18 21:29:51 -03:00
int ret ;
/* Instruct the cx18 to start sending packets */
2010-01-30 15:28:22 -03:00
snd_cx18_lock ( cxsc ) ;
2010-01-18 21:29:51 -03:00
s = & cx - > streams [ CX18_ENC_STREAM_TYPE_PCM ] ;
2010-01-30 15:50:51 -03:00
item . cx = cx ;
item . type = s - > type ;
item . open_id = cx - > open_id + + ;
2010-01-18 21:29:51 -03:00
/* See if the stream is available */
2010-01-30 15:50:51 -03:00
if ( cx18_claim_stream ( & item , item . type ) ) {
2010-01-18 21:29:51 -03:00
/* No, it's already in use */
2010-01-30 15:28:22 -03:00
snd_cx18_unlock ( cxsc ) ;
2010-01-18 21:29:51 -03:00
return - EBUSY ;
}
if ( test_bit ( CX18_F_S_STREAMOFF , & s - > s_flags ) | |
test_and_set_bit ( CX18_F_S_STREAMING , & s - > s_flags ) ) {
/* We're already streaming. No additional action required */
2010-01-30 15:28:22 -03:00
snd_cx18_unlock ( cxsc ) ;
2010-01-18 21:29:51 -03:00
return 0 ;
}
runtime - > hw = snd_cx18_hw_capture ;
snd_pcm_hw_constraint_integer ( runtime , SNDRV_PCM_HW_PARAM_PERIODS ) ;
cxsc - > capture_pcm_substream = substream ;
runtime - > private_data = cx ;
cx - > pcm_announce_callback = cx18_alsa_announce_pcm_data ;
/* Not currently streaming, so start it up */
set_bit ( CX18_F_S_STREAMING , & s - > s_flags ) ;
ret = cx18_start_v4l2_encode_stream ( s ) ;
2010-01-30 15:28:22 -03:00
snd_cx18_unlock ( cxsc ) ;
2010-01-18 21:29:51 -03:00
2012-04-23 08:25:20 -03:00
return ret ;
2009-06-24 07:26:45 -03:00
}
static int snd_cx18_pcm_capture_close ( struct snd_pcm_substream * substream )
{
2010-01-18 21:29:51 -03:00
struct snd_cx18_card * cxsc = snd_pcm_substream_chip ( substream ) ;
struct v4l2_device * v4l2_dev = cxsc - > v4l2_dev ;
struct cx18 * cx = to_cx18 ( v4l2_dev ) ;
struct cx18_stream * s ;
/* Instruct the cx18 to stop sending packets */
2010-01-30 15:28:22 -03:00
snd_cx18_lock ( cxsc ) ;
2010-01-18 21:29:51 -03:00
s = & cx - > streams [ CX18_ENC_STREAM_TYPE_PCM ] ;
2012-04-23 08:25:20 -03:00
cx18_stop_v4l2_encode_stream ( s , 0 ) ;
2010-01-18 21:29:51 -03:00
clear_bit ( CX18_F_S_STREAMING , & s - > s_flags ) ;
cx18_release_stream ( s ) ;
cx - > pcm_announce_callback = NULL ;
2010-01-30 15:28:22 -03:00
snd_cx18_unlock ( cxsc ) ;
2010-01-18 21:29:51 -03:00
2009-06-24 07:26:45 -03:00
return 0 ;
}
static int snd_cx18_pcm_ioctl ( struct snd_pcm_substream * substream ,
unsigned int cmd , void * arg )
{
2010-11-19 17:04:31 -03:00
struct snd_cx18_card * cxsc = snd_pcm_substream_chip ( substream ) ;
int ret ;
snd_cx18_lock ( cxsc ) ;
ret = snd_pcm_lib_ioctl ( substream , cmd , arg ) ;
snd_cx18_unlock ( cxsc ) ;
return ret ;
2009-06-24 07:26:45 -03:00
}
2010-01-18 21:29:51 -03:00
static int snd_pcm_alloc_vmalloc_buffer ( struct snd_pcm_substream * subs ,
size_t size )
{
struct snd_pcm_runtime * runtime = subs - > runtime ;
dprintk ( " Allocating vbuffer \n " ) ;
if ( runtime - > dma_area ) {
if ( runtime - > dma_bytes > size )
return 0 ;
vfree ( runtime - > dma_area ) ;
}
runtime - > dma_area = vmalloc ( size ) ;
if ( ! runtime - > dma_area )
return - ENOMEM ;
runtime - > dma_bytes = size ;
return 0 ;
}
2009-06-24 07:26:45 -03:00
static int snd_cx18_pcm_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params )
{
2010-01-18 21:29:51 -03:00
dprintk ( " %s called \n " , __func__ ) ;
2012-04-23 08:25:20 -03:00
return snd_pcm_alloc_vmalloc_buffer ( substream ,
2010-01-18 21:29:51 -03:00
params_buffer_bytes ( params ) ) ;
2009-06-24 07:26:45 -03:00
}
static int snd_cx18_pcm_hw_free ( struct snd_pcm_substream * substream )
{
2010-01-07 00:56:14 -03:00
struct snd_cx18_card * cxsc = snd_pcm_substream_chip ( substream ) ;
unsigned long flags ;
spin_lock_irqsave ( & cxsc - > slock , flags ) ;
if ( substream - > runtime - > dma_area ) {
dprintk ( " freeing pcm capture region \n " ) ;
vfree ( substream - > runtime - > dma_area ) ;
substream - > runtime - > dma_area = NULL ;
}
spin_unlock_irqrestore ( & cxsc - > slock , flags ) ;
2009-06-24 07:26:45 -03:00
return 0 ;
}
static int snd_cx18_pcm_prepare ( struct snd_pcm_substream * substream )
{
2010-01-18 21:29:51 -03:00
struct snd_cx18_card * cxsc = snd_pcm_substream_chip ( substream ) ;
cxsc - > hwptr_done_capture = 0 ;
cxsc - > capture_transfer_done = 0 ;
2009-06-24 07:26:45 -03:00
return 0 ;
}
static int snd_cx18_pcm_trigger ( struct snd_pcm_substream * substream , int cmd )
{
return 0 ;
}
static
snd_pcm_uframes_t snd_cx18_pcm_pointer ( struct snd_pcm_substream * substream )
{
2010-01-18 21:29:51 -03:00
unsigned long flags ;
snd_pcm_uframes_t hwptr_done ;
struct snd_cx18_card * cxsc = snd_pcm_substream_chip ( substream ) ;
spin_lock_irqsave ( & cxsc - > slock , flags ) ;
hwptr_done = cxsc - > hwptr_done_capture ;
spin_unlock_irqrestore ( & cxsc - > slock , flags ) ;
return hwptr_done ;
}
static struct page * snd_pcm_get_vmalloc_page ( struct snd_pcm_substream * subs ,
unsigned long offset )
{
void * pageptr = subs - > runtime - > dma_area + offset ;
return vmalloc_to_page ( pageptr ) ;
2009-06-24 07:26:45 -03:00
}
static struct snd_pcm_ops snd_cx18_pcm_capture_ops = {
. open = snd_cx18_pcm_capture_open ,
. close = snd_cx18_pcm_capture_close ,
. ioctl = snd_cx18_pcm_ioctl ,
. hw_params = snd_cx18_pcm_hw_params ,
. hw_free = snd_cx18_pcm_hw_free ,
. prepare = snd_cx18_pcm_prepare ,
. trigger = snd_cx18_pcm_trigger ,
. pointer = snd_cx18_pcm_pointer ,
2010-01-18 21:29:51 -03:00
. page = snd_pcm_get_vmalloc_page ,
2009-06-24 07:26:45 -03:00
} ;
2010-01-18 21:29:51 -03:00
int snd_cx18_pcm_create ( struct snd_cx18_card * cxsc )
2009-06-24 07:26:45 -03:00
{
struct snd_pcm * sp ;
struct snd_card * sc = cxsc - > sc ;
struct v4l2_device * v4l2_dev = cxsc - > v4l2_dev ;
struct cx18 * cx = to_cx18 ( v4l2_dev ) ;
int ret ;
ret = snd_pcm_new ( sc , " CX23418 PCM " ,
0 , /* PCM device 0, the only one for this card */
0 , /* 0 playback substreams */
1 , /* 1 capture substream */
& sp ) ;
if ( ret ) {
CX18_ALSA_ERR ( " %s: snd_cx18_pcm_create() failed with err %d \n " ,
__func__ , ret ) ;
goto err_exit ;
}
2010-01-18 21:29:51 -03:00
spin_lock_init ( & cxsc - > slock ) ;
2009-12-20 23:15:58 -03:00
snd_pcm_set_ops ( sp , SNDRV_PCM_STREAM_CAPTURE ,
& snd_cx18_pcm_capture_ops ) ;
2010-01-18 21:29:51 -03:00
sp - > info_flags = 0 ;
sp - > private_data = cxsc ;
2009-11-20 02:15:20 -03:00
strlcpy ( sp - > name , cx - > card_name , sizeof ( sp - > name ) ) ;
2010-01-18 21:29:51 -03:00
2009-06-24 07:26:45 -03:00
return 0 ;
err_exit :
return ret ;
}