2011-10-10 18:09:52 +04:00
/*
*
* Support for CX23885 analog audio capture
*
* ( c ) 2008 Mijhail Moreyra < mijhail . moreyra @ gmail . com >
* Adapted from cx88 - alsa . c
* ( c ) 2009 Steven Toth < stoth @ kernellabs . com >
*
* 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 . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/device.h>
# include <linux/interrupt.h>
# include <linux/vmalloc.h>
# include <linux/dma-mapping.h>
# include <linux/pci.h>
# include <asm/delay.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include <sound/control.h>
# include <sound/initval.h>
# include <sound/tlv.h>
# include "cx23885.h"
# include "cx23885-reg.h"
# define AUDIO_SRAM_CHANNEL SRAM_CH07
2012-10-27 19:34:10 +04:00
# define dprintk(level, fmt, arg...) do { \
if ( audio_debug + 1 > level ) \
printk ( KERN_INFO " %s: " fmt , chip - > dev - > name , # # arg ) ; \
} while ( 0 )
2011-10-10 18:09:52 +04:00
# define dprintk_core(level, fmt, arg...) if (audio_debug >= level) \
2011-10-10 18:09:54 +04:00
printk ( KERN_DEBUG " %s: " fmt , chip - > dev - > name , # # arg )
2011-10-10 18:09:52 +04:00
/****************************************************************************
Module global static vars
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static unsigned int disable_analog_audio ;
module_param ( disable_analog_audio , int , 0644 ) ;
MODULE_PARM_DESC ( disable_analog_audio , " disable analog audio ALSA driver " ) ;
static unsigned int audio_debug ;
module_param ( audio_debug , int , 0644 ) ;
MODULE_PARM_DESC ( audio_debug , " enable debug messages [analog audio] " ) ;
/****************************************************************************
Board specific funtions
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Constants taken from cx88-reg.h */
# define AUD_INT_DN_RISCI1 (1 << 0)
# define AUD_INT_UP_RISCI1 (1 << 1)
# define AUD_INT_RDS_DN_RISCI1 (1 << 2)
# define AUD_INT_DN_RISCI2 (1 << 4) /* yes, 3 is skipped */
# define AUD_INT_UP_RISCI2 (1 << 5)
# define AUD_INT_RDS_DN_RISCI2 (1 << 6)
# define AUD_INT_DN_SYNC (1 << 12)
# define AUD_INT_UP_SYNC (1 << 13)
# define AUD_INT_RDS_DN_SYNC (1 << 14)
# define AUD_INT_OPC_ERR (1 << 16)
# define AUD_INT_BER_IRQ (1 << 20)
# define AUD_INT_MCHG_IRQ (1 << 21)
# define GP_COUNT_CONTROL_RESET 0x3
/*
* BOARD Specific : Sets audio DMA
*/
static int cx23885_start_audio_dma ( struct cx23885_audio_dev * chip )
{
struct cx23885_audio_buffer * buf = chip - > buf ;
struct cx23885_dev * dev = chip - > dev ;
struct sram_channel * audio_ch =
& dev - > sram_channels [ AUDIO_SRAM_CHANNEL ] ;
dprintk ( 1 , " %s() \n " , __func__ ) ;
/* Make sure RISC/FIFO are off before changing FIFO/RISC settings */
cx_clear ( AUD_INT_DMA_CTL , 0x11 ) ;
/* setup fifo + format - out channel */
cx23885_sram_channel_setup ( chip - > dev , audio_ch , buf - > bpl ,
buf - > risc . dma ) ;
/* sets bpl size */
cx_write ( AUD_INT_A_LNGTH , buf - > bpl ) ;
/* This is required to get good audio (1 seems to be ok) */
cx_write ( AUD_INT_A_MODE , 1 ) ;
/* reset counter */
cx_write ( AUD_INT_A_GPCNT_CTL , GP_COUNT_CONTROL_RESET ) ;
atomic_set ( & chip - > count , 0 ) ;
dprintk ( 1 , " Start audio DMA, %d B/line, %d lines/FIFO, %d periods, %d "
" byte buffer \n " , buf - > bpl , cx_read ( audio_ch - > cmds_start + 12 ) > > 1 ,
chip - > num_periods , buf - > bpl * chip - > num_periods ) ;
/* Enables corresponding bits at AUD_INT_STAT */
cx_write ( AUDIO_INT_INT_MSK , AUD_INT_OPC_ERR | AUD_INT_DN_SYNC |
AUD_INT_DN_RISCI1 ) ;
/* Clean any pending interrupt bits already set */
cx_write ( AUDIO_INT_INT_STAT , ~ 0 ) ;
/* enable audio irqs */
cx_set ( PCI_INT_MSK , chip - > dev - > pci_irqmask | PCI_MSK_AUD_INT ) ;
/* start dma */
cx_set ( DEV_CNTRL2 , ( 1 < < 5 ) ) ; /* Enables Risc Processor */
cx_set ( AUD_INT_DMA_CTL , 0x11 ) ; /* audio downstream FIFO and
RISC enable */
if ( audio_debug )
cx23885_sram_channel_dump ( chip - > dev , audio_ch ) ;
return 0 ;
}
/*
* BOARD Specific : Resets audio DMA
*/
static int cx23885_stop_audio_dma ( struct cx23885_audio_dev * chip )
{
struct cx23885_dev * dev = chip - > dev ;
dprintk ( 1 , " Stopping audio DMA \n " ) ;
/* stop dma */
cx_clear ( AUD_INT_DMA_CTL , 0x11 ) ;
/* disable irqs */
cx_clear ( PCI_INT_MSK , PCI_MSK_AUD_INT ) ;
cx_clear ( AUDIO_INT_INT_MSK , AUD_INT_OPC_ERR | AUD_INT_DN_SYNC |
AUD_INT_DN_RISCI1 ) ;
if ( audio_debug )
cx23885_sram_channel_dump ( chip - > dev ,
& dev - > sram_channels [ AUDIO_SRAM_CHANNEL ] ) ;
return 0 ;
}
/*
* BOARD Specific : Handles audio IRQ
*/
int cx23885_audio_irq ( struct cx23885_dev * dev , u32 status , u32 mask )
{
struct cx23885_audio_dev * chip = dev - > audio_dev ;
if ( 0 = = ( status & mask ) )
return 0 ;
cx_write ( AUDIO_INT_INT_STAT , status ) ;
/* risc op code error */
if ( status & AUD_INT_OPC_ERR ) {
printk ( KERN_WARNING " %s/1: Audio risc op code error \n " ,
dev - > name ) ;
cx_clear ( AUD_INT_DMA_CTL , 0x11 ) ;
cx23885_sram_channel_dump ( dev ,
& dev - > sram_channels [ AUDIO_SRAM_CHANNEL ] ) ;
}
if ( status & AUD_INT_DN_SYNC ) {
dprintk ( 1 , " Downstream sync error \n " ) ;
cx_write ( AUD_INT_A_GPCNT_CTL , GP_COUNT_CONTROL_RESET ) ;
return 1 ;
}
/* risc1 downstream */
if ( status & AUD_INT_DN_RISCI1 ) {
atomic_set ( & chip - > count , cx_read ( AUD_INT_A_GPCNT ) ) ;
snd_pcm_period_elapsed ( chip - > substream ) ;
}
/* FIXME: Any other status should deserve a special handling? */
return 1 ;
}
static int dsp_buffer_free ( struct cx23885_audio_dev * chip )
{
BUG_ON ( ! chip - > dma_size ) ;
dprintk ( 2 , " Freeing buffer \n " ) ;
videobuf_dma_unmap ( & chip - > pci - > dev , chip - > dma_risc ) ;
videobuf_dma_free ( chip - > dma_risc ) ;
btcx_riscmem_free ( chip - > pci , & chip - > buf - > risc ) ;
kfree ( chip - > buf ) ;
chip - > dma_risc = NULL ;
chip - > dma_size = 0 ;
return 0 ;
}
/****************************************************************************
ALSA PCM Interface
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
* Digital hardware definition
*/
# define DEFAULT_FIFO_SIZE 4096
static struct snd_pcm_hardware snd_cx23885_digital_hw = {
. info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID ,
. formats = SNDRV_PCM_FMTBIT_S16_LE ,
. rates = SNDRV_PCM_RATE_48000 ,
. rate_min = 48000 ,
. rate_max = 48000 ,
. channels_min = 2 ,
. channels_max = 2 ,
/* Analog audio output will be full of clicks and pops if there
are not exactly four lines in the SRAM FIFO buffer . */
. period_bytes_min = DEFAULT_FIFO_SIZE / 4 ,
. period_bytes_max = DEFAULT_FIFO_SIZE / 4 ,
. periods_min = 1 ,
. periods_max = 1024 ,
. buffer_bytes_max = ( 1024 * 1024 ) ,
} ;
/*
* audio pcm capture open callback
*/
static int snd_cx23885_pcm_open ( struct snd_pcm_substream * substream )
{
struct cx23885_audio_dev * chip = snd_pcm_substream_chip ( substream ) ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
int err ;
if ( ! chip ) {
printk ( KERN_ERR " BUG: cx23885 can't find device struct. "
" Can't proceed with open \n " ) ;
return - ENODEV ;
}
err = snd_pcm_hw_constraint_pow2 ( runtime , 0 ,
SNDRV_PCM_HW_PARAM_PERIODS ) ;
if ( err < 0 )
goto _error ;
chip - > substream = substream ;
runtime - > hw = snd_cx23885_digital_hw ;
if ( chip - > dev - > sram_channels [ AUDIO_SRAM_CHANNEL ] . fifo_size ! =
DEFAULT_FIFO_SIZE ) {
unsigned int bpl = chip - > dev - >
sram_channels [ AUDIO_SRAM_CHANNEL ] . fifo_size / 4 ;
bpl & = ~ 7 ; /* must be multiple of 8 */
runtime - > hw . period_bytes_min = bpl ;
runtime - > hw . period_bytes_max = bpl ;
}
return 0 ;
_error :
dprintk ( 1 , " Error opening PCM! \n " ) ;
return err ;
}
/*
* audio close callback
*/
static int snd_cx23885_close ( struct snd_pcm_substream * substream )
{
return 0 ;
}
/*
* hw_params callback
*/
static int snd_cx23885_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * hw_params )
{
struct cx23885_audio_dev * chip = snd_pcm_substream_chip ( substream ) ;
struct videobuf_dmabuf * dma ;
struct cx23885_audio_buffer * buf ;
int ret ;
if ( substream - > runtime - > dma_area ) {
dsp_buffer_free ( chip ) ;
substream - > runtime - > dma_area = NULL ;
}
chip - > period_size = params_period_bytes ( hw_params ) ;
chip - > num_periods = params_periods ( hw_params ) ;
chip - > dma_size = chip - > period_size * params_periods ( hw_params ) ;
BUG_ON ( ! chip - > dma_size ) ;
BUG_ON ( chip - > num_periods & ( chip - > num_periods - 1 ) ) ;
buf = kzalloc ( sizeof ( * buf ) , GFP_KERNEL ) ;
if ( NULL = = buf )
return - ENOMEM ;
buf - > bpl = chip - > period_size ;
dma = & buf - > dma ;
videobuf_dma_init ( dma ) ;
ret = videobuf_dma_init_kernel ( dma , PCI_DMA_FROMDEVICE ,
( PAGE_ALIGN ( chip - > dma_size ) > > PAGE_SHIFT ) ) ;
if ( ret < 0 )
goto error ;
ret = videobuf_dma_map ( & chip - > pci - > dev , dma ) ;
if ( ret < 0 )
goto error ;
ret = cx23885_risc_databuffer ( chip - > pci , & buf - > risc , dma - > sglist ,
chip - > period_size , chip - > num_periods , 1 ) ;
if ( ret < 0 )
goto error ;
/* Loop back to start of program */
buf - > risc . jmp [ 0 ] = cpu_to_le32 ( RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC ) ;
buf - > risc . jmp [ 1 ] = cpu_to_le32 ( buf - > risc . dma ) ;
buf - > risc . jmp [ 2 ] = cpu_to_le32 ( 0 ) ; /* bits 63-32 */
chip - > buf = buf ;
chip - > dma_risc = dma ;
substream - > runtime - > dma_area = chip - > dma_risc - > vaddr ;
substream - > runtime - > dma_bytes = chip - > dma_size ;
substream - > runtime - > dma_addr = 0 ;
return 0 ;
error :
kfree ( buf ) ;
return ret ;
}
/*
* hw free callback
*/
static int snd_cx23885_hw_free ( struct snd_pcm_substream * substream )
{
struct cx23885_audio_dev * chip = snd_pcm_substream_chip ( substream ) ;
if ( substream - > runtime - > dma_area ) {
dsp_buffer_free ( chip ) ;
substream - > runtime - > dma_area = NULL ;
}
return 0 ;
}
/*
* prepare callback
*/
static int snd_cx23885_prepare ( struct snd_pcm_substream * substream )
{
return 0 ;
}
/*
* trigger callback
*/
static int snd_cx23885_card_trigger ( struct snd_pcm_substream * substream ,
int cmd )
{
struct cx23885_audio_dev * chip = snd_pcm_substream_chip ( substream ) ;
int err ;
/* Local interrupts are already disabled by ALSA */
spin_lock ( & chip - > lock ) ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
err = cx23885_start_audio_dma ( chip ) ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
err = cx23885_stop_audio_dma ( chip ) ;
break ;
default :
err = - EINVAL ;
break ;
}
spin_unlock ( & chip - > lock ) ;
return err ;
}
/*
* pointer callback
*/
static snd_pcm_uframes_t snd_cx23885_pointer (
struct snd_pcm_substream * substream )
{
struct cx23885_audio_dev * chip = snd_pcm_substream_chip ( substream ) ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
u16 count ;
count = atomic_read ( & chip - > count ) ;
return runtime - > period_size * ( count & ( runtime - > periods - 1 ) ) ;
}
/*
* page callback ( needed for mmap )
*/
static struct page * snd_cx23885_page ( struct snd_pcm_substream * substream ,
unsigned long offset )
{
void * pageptr = substream - > runtime - > dma_area + offset ;
return vmalloc_to_page ( pageptr ) ;
}
/*
* operators
*/
static struct snd_pcm_ops snd_cx23885_pcm_ops = {
. open = snd_cx23885_pcm_open ,
. close = snd_cx23885_close ,
. ioctl = snd_pcm_lib_ioctl ,
. hw_params = snd_cx23885_hw_params ,
. hw_free = snd_cx23885_hw_free ,
. prepare = snd_cx23885_prepare ,
. trigger = snd_cx23885_card_trigger ,
. pointer = snd_cx23885_pointer ,
. page = snd_cx23885_page ,
} ;
/*
* create a PCM device
*/
static int snd_cx23885_pcm ( struct cx23885_audio_dev * chip , int device ,
char * name )
{
int err ;
struct snd_pcm * pcm ;
err = snd_pcm_new ( chip - > card , name , device , 0 , 1 , & pcm ) ;
if ( err < 0 )
return err ;
pcm - > private_data = chip ;
strcpy ( pcm - > name , name ) ;
snd_pcm_set_ops ( pcm , SNDRV_PCM_STREAM_CAPTURE , & snd_cx23885_pcm_ops ) ;
return 0 ;
}
/****************************************************************************
Basic Flow for Sound Devices
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
* Alsa Constructor - Component probe
*/
2011-10-10 18:09:53 +04:00
struct cx23885_audio_dev * cx23885_audio_register ( struct cx23885_dev * dev )
2011-10-10 18:09:52 +04:00
{
struct snd_card * card ;
struct cx23885_audio_dev * chip ;
int err ;
if ( disable_analog_audio )
return NULL ;
if ( dev - > sram_channels [ AUDIO_SRAM_CHANNEL ] . cmds_start = = 0 ) {
printk ( KERN_WARNING " %s(): Missing SRAM channel configuration "
" for analog TV Audio \n " , __func__ ) ;
return NULL ;
}
err = snd_card_create ( SNDRV_DEFAULT_IDX1 , SNDRV_DEFAULT_STR1 ,
THIS_MODULE , sizeof ( struct cx23885_audio_dev ) , & card ) ;
if ( err < 0 )
goto error ;
chip = ( struct cx23885_audio_dev * ) card - > private_data ;
chip - > dev = dev ;
chip - > pci = dev - > pci ;
chip - > card = card ;
spin_lock_init ( & chip - > lock ) ;
snd_card_set_dev ( card , & dev - > pci - > dev ) ;
err = snd_cx23885_pcm ( chip , 0 , " CX23885 Digital " ) ;
if ( err < 0 )
goto error ;
strcpy ( card - > driver , " CX23885 " ) ;
sprintf ( card - > shortname , " Conexant CX23885 " ) ;
sprintf ( card - > longname , " %s at %s " , card - > shortname , dev - > name ) ;
err = snd_card_register ( card ) ;
if ( err < 0 )
goto error ;
dprintk ( 0 , " registered ALSA audio device \n " ) ;
return chip ;
error :
snd_card_free ( card ) ;
printk ( KERN_ERR " %s(): Failed to register analog "
" audio adapter \n " , __func__ ) ;
return NULL ;
}
/*
* ALSA destructor
*/
2011-10-10 18:09:53 +04:00
void cx23885_audio_unregister ( struct cx23885_dev * dev )
2011-10-10 18:09:52 +04:00
{
struct cx23885_audio_dev * chip = dev - > audio_dev ;
snd_card_free ( chip - > card ) ;
}