2009-05-14 08:05:58 +02:00
/**
* Copyright ( C ) 2008 , Creative Technology Ltd . All Rights Reserved .
*
* This source file is released under GPL v2 license ( no other versions ) .
* See the COPYING file included in the main directory of this source
* distribution for the license terms and conditions .
*
* @ File ctpcm . c
*
* @ Brief
* This file contains the definition of the pcm device functions .
*
* @ Author Liu Chun
* @ Date Apr 2 2008
*
*/
# include "ctpcm.h"
# include <sound/pcm.h>
/* Hardware descriptions for playback */
static struct snd_pcm_hardware ct_pcm_playback_hw = {
. info = ( SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE ) ,
. formats = ( SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_U16_LE |
SNDRV_PCM_FMTBIT_S24_3LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE ) ,
. rates = ( SNDRV_PCM_RATE_CONTINUOUS |
SNDRV_PCM_RATE_8000_192000 ) ,
. rate_min = 8000 ,
. rate_max = 192000 ,
. channels_min = 1 ,
. channels_max = 2 ,
. buffer_bytes_max = ( 128 * 1024 ) ,
. period_bytes_min = ( 64 ) ,
. period_bytes_max = ( 128 * 1024 ) ,
. periods_min = 1 ,
. periods_max = 1024 ,
. fifo_size = 0 ,
} ;
static struct snd_pcm_hardware ct_spdif_passthru_playback_hw = {
. info = ( SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE ) ,
. formats = ( SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_U16_LE ) ,
. rates = ( SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_32000 ) ,
. rate_min = 32000 ,
. rate_max = 48000 ,
. channels_min = 2 ,
. channels_max = 2 ,
. buffer_bytes_max = ( 128 * 1024 ) ,
. period_bytes_min = ( 64 ) ,
. period_bytes_max = ( 128 * 1024 ) ,
. periods_min = 1 ,
. periods_max = 1024 ,
. fifo_size = 0 ,
} ;
/* Hardware descriptions for capture */
static struct snd_pcm_hardware ct_pcm_capture_hw = {
. info = ( SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_MMAP_VALID ) ,
. formats = ( SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_U16_LE |
SNDRV_PCM_FMTBIT_S24_3LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE ) ,
. rates = ( SNDRV_PCM_RATE_CONTINUOUS |
SNDRV_PCM_RATE_8000_96000 ) ,
. rate_min = 8000 ,
. rate_max = 96000 ,
. channels_min = 1 ,
. channels_max = 2 ,
. buffer_bytes_max = ( 128 * 1024 ) ,
. period_bytes_min = ( 384 ) ,
. period_bytes_max = ( 64 * 1024 ) ,
. periods_min = 2 ,
. periods_max = 1024 ,
. fifo_size = 0 ,
} ;
static void ct_atc_pcm_interrupt ( struct ct_atc_pcm * atc_pcm )
{
struct ct_atc_pcm * apcm = atc_pcm ;
if ( NULL = = apcm - > substream )
return ;
snd_pcm_period_elapsed ( apcm - > substream ) ;
}
static void ct_atc_pcm_free_substream ( struct snd_pcm_runtime * runtime )
{
struct ct_atc_pcm * apcm = runtime - > private_data ;
struct ct_atc * atc = snd_pcm_substream_chip ( apcm - > substream ) ;
atc - > pcm_release_resources ( atc , apcm ) ;
kfree ( apcm ) ;
runtime - > private_data = NULL ;
}
/* pcm playback operations */
static int ct_pcm_playback_open ( struct snd_pcm_substream * substream )
{
struct ct_atc * atc = snd_pcm_substream_chip ( substream ) ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct ct_atc_pcm * apcm ;
int err ;
apcm = kzalloc ( sizeof ( * apcm ) , GFP_KERNEL ) ;
if ( NULL = = apcm )
return - ENOMEM ;
spin_lock_init ( & apcm - > timer_lock ) ;
apcm - > stop_timer = 0 ;
apcm - > substream = substream ;
apcm - > interrupt = ct_atc_pcm_interrupt ;
runtime - > private_data = apcm ;
runtime - > private_free = ct_atc_pcm_free_substream ;
if ( IEC958 = = substream - > pcm - > device ) {
runtime - > hw = ct_spdif_passthru_playback_hw ;
atc - > spdif_out_passthru ( atc , 1 ) ;
} else {
runtime - > hw = ct_pcm_playback_hw ;
if ( FRONT = = substream - > pcm - > device )
runtime - > hw . channels_max = 8 ;
}
err = snd_pcm_hw_constraint_integer ( runtime ,
SNDRV_PCM_HW_PARAM_PERIODS ) ;
if ( err < 0 ) {
kfree ( apcm ) ;
return err ;
}
err = snd_pcm_hw_constraint_minmax ( runtime ,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES ,
1024 , UINT_MAX ) ;
if ( err < 0 ) {
kfree ( apcm ) ;
return err ;
}
return 0 ;
}
static int ct_pcm_playback_close ( struct snd_pcm_substream * substream )
{
struct ct_atc * atc = snd_pcm_substream_chip ( substream ) ;
/* TODO: Notify mixer inactive. */
if ( IEC958 = = substream - > pcm - > device )
atc - > spdif_out_passthru ( atc , 0 ) ;
/* The ct_atc_pcm object will be freed by runtime->private_free */
return 0 ;
}
static int ct_pcm_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * hw_params )
{
return snd_pcm_lib_malloc_pages ( substream ,
params_buffer_bytes ( hw_params ) ) ;
}
static int ct_pcm_hw_free ( struct snd_pcm_substream * substream )
{
/* Free snd-allocated pages */
return snd_pcm_lib_free_pages ( substream ) ;
}
static void ct_pcm_timer_callback ( unsigned long data )
{
struct ct_atc_pcm * apcm = ( struct ct_atc_pcm * ) data ;
struct snd_pcm_substream * substream = apcm - > substream ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
unsigned int period_size = runtime - > period_size ;
unsigned int buffer_size = runtime - > buffer_size ;
unsigned long flags ;
unsigned int position = 0 , dist = 0 , interval = 0 ;
position = substream - > ops - > pointer ( substream ) ;
dist = ( position + buffer_size - apcm - > position ) % buffer_size ;
if ( ( dist > = period_size ) | |
( position / period_size ! = apcm - > position / period_size ) ) {
apcm - > interrupt ( apcm ) ;
apcm - > position = position ;
}
/* Add extra HZ*5/1000 to avoid overrun issue when recording
* at 8 kHz in 8 - bit format or at 88 kHz in 24 - bit format . */
interval = ( ( period_size - ( position % period_size ) )
* HZ + ( runtime - > rate - 1 ) ) / runtime - > rate + HZ * 5 / 1000 ;
spin_lock_irqsave ( & apcm - > timer_lock , flags ) ;
apcm - > timer . expires = jiffies + interval ;
if ( ! apcm - > stop_timer )
add_timer ( & apcm - > timer ) ;
spin_unlock_irqrestore ( & apcm - > timer_lock , flags ) ;
}
static int ct_pcm_timer_prepare ( struct ct_atc_pcm * apcm )
{
unsigned long flags ;
spin_lock_irqsave ( & apcm - > timer_lock , flags ) ;
if ( timer_pending ( & apcm - > timer ) ) {
/* The timer has already been started. */
spin_unlock_irqrestore ( & apcm - > timer_lock , flags ) ;
return 0 ;
}
init_timer ( & apcm - > timer ) ;
apcm - > timer . data = ( unsigned long ) apcm ;
apcm - > timer . function = ct_pcm_timer_callback ;
spin_unlock_irqrestore ( & apcm - > timer_lock , flags ) ;
apcm - > position = 0 ;
return 0 ;
}
static int ct_pcm_timer_start ( struct ct_atc_pcm * apcm )
{
struct snd_pcm_runtime * runtime = apcm - > substream - > runtime ;
unsigned long flags ;
spin_lock_irqsave ( & apcm - > timer_lock , flags ) ;
if ( timer_pending ( & apcm - > timer ) ) {
/* The timer has already been started. */
spin_unlock_irqrestore ( & apcm - > timer_lock , flags ) ;
return 0 ;
}
apcm - > timer . expires = jiffies + ( runtime - > period_size * HZ +
( runtime - > rate - 1 ) ) / runtime - > rate ;
apcm - > stop_timer = 0 ;
add_timer ( & apcm - > timer ) ;
spin_unlock_irqrestore ( & apcm - > timer_lock , flags ) ;
return 0 ;
}
static int ct_pcm_timer_stop ( struct ct_atc_pcm * apcm )
{
unsigned long flags ;
spin_lock_irqsave ( & apcm - > timer_lock , flags ) ;
apcm - > stop_timer = 1 ;
del_timer ( & apcm - > timer ) ;
spin_unlock_irqrestore ( & apcm - > timer_lock , flags ) ;
try_to_del_timer_sync ( & apcm - > timer ) ;
return 0 ;
}
static int ct_pcm_playback_prepare ( struct snd_pcm_substream * substream )
{
int err ;
struct ct_atc * atc = snd_pcm_substream_chip ( substream ) ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct ct_atc_pcm * apcm = runtime - > private_data ;
if ( IEC958 = = substream - > pcm - > device )
err = atc - > spdif_passthru_playback_prepare ( atc , apcm ) ;
else
err = atc - > pcm_playback_prepare ( atc , apcm ) ;
if ( err < 0 ) {
2009-05-14 15:19:30 +02:00
printk ( KERN_ERR " ctxfi: Preparing pcm playback failed!!! \n " ) ;
2009-05-14 08:05:58 +02:00
return err ;
}
ct_pcm_timer_prepare ( apcm ) ;
return 0 ;
}
static int
ct_pcm_playback_trigger ( struct snd_pcm_substream * substream , int cmd )
{
struct ct_atc * atc = snd_pcm_substream_chip ( substream ) ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct ct_atc_pcm * apcm = runtime - > private_data ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
case SNDRV_PCM_TRIGGER_RESUME :
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
atc - > pcm_playback_start ( atc , apcm ) ;
ct_pcm_timer_start ( apcm ) ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
case SNDRV_PCM_TRIGGER_SUSPEND :
case SNDRV_PCM_TRIGGER_PAUSE_PUSH :
ct_pcm_timer_stop ( apcm ) ;
atc - > pcm_playback_stop ( atc , apcm ) ;
break ;
default :
break ;
}
return 0 ;
}
static snd_pcm_uframes_t
ct_pcm_playback_pointer ( struct snd_pcm_substream * substream )
{
unsigned long position ;
struct ct_atc * atc = snd_pcm_substream_chip ( substream ) ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct ct_atc_pcm * apcm = runtime - > private_data ;
/* Read out playback position */
position = atc - > pcm_playback_position ( atc , apcm ) ;
position = bytes_to_frames ( runtime , position ) ;
return position ;
}
/* pcm capture operations */
static int ct_pcm_capture_open ( struct snd_pcm_substream * substream )
{
struct ct_atc * atc = snd_pcm_substream_chip ( substream ) ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct ct_atc_pcm * apcm ;
int err ;
apcm = kzalloc ( sizeof ( * apcm ) , GFP_KERNEL ) ;
if ( NULL = = apcm )
return - ENOMEM ;
spin_lock_init ( & apcm - > timer_lock ) ;
apcm - > started = 0 ;
apcm - > stop_timer = 0 ;
apcm - > substream = substream ;
apcm - > interrupt = ct_atc_pcm_interrupt ;
runtime - > private_data = apcm ;
runtime - > private_free = ct_atc_pcm_free_substream ;
runtime - > hw = ct_pcm_capture_hw ;
runtime - > hw . rate_max = atc - > rsr * atc - > msr ;
err = snd_pcm_hw_constraint_integer ( runtime ,
SNDRV_PCM_HW_PARAM_PERIODS ) ;
if ( err < 0 ) {
kfree ( apcm ) ;
return err ;
}
err = snd_pcm_hw_constraint_minmax ( runtime ,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES ,
1024 , UINT_MAX ) ;
if ( err < 0 ) {
kfree ( apcm ) ;
return err ;
}
return 0 ;
}
static int ct_pcm_capture_close ( struct snd_pcm_substream * substream )
{
/* The ct_atc_pcm object will be freed by runtime->private_free */
/* TODO: Notify mixer inactive. */
return 0 ;
}
static int ct_pcm_capture_prepare ( struct snd_pcm_substream * substream )
{
int err ;
struct ct_atc * atc = snd_pcm_substream_chip ( substream ) ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct ct_atc_pcm * apcm = runtime - > private_data ;
err = atc - > pcm_capture_prepare ( atc , apcm ) ;
if ( err < 0 ) {
2009-05-14 15:19:30 +02:00
printk ( KERN_ERR " ctxfi: Preparing pcm capture failed!!! \n " ) ;
2009-05-14 08:05:58 +02:00
return err ;
}
ct_pcm_timer_prepare ( apcm ) ;
return 0 ;
}
static int
ct_pcm_capture_trigger ( struct snd_pcm_substream * substream , int cmd )
{
struct ct_atc * atc = snd_pcm_substream_chip ( substream ) ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct ct_atc_pcm * apcm = runtime - > private_data ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
atc - > pcm_capture_start ( atc , apcm ) ;
ct_pcm_timer_start ( apcm ) ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
ct_pcm_timer_stop ( apcm ) ;
atc - > pcm_capture_stop ( atc , apcm ) ;
break ;
default :
ct_pcm_timer_stop ( apcm ) ;
atc - > pcm_capture_stop ( atc , apcm ) ;
break ;
}
return 0 ;
}
static snd_pcm_uframes_t
ct_pcm_capture_pointer ( struct snd_pcm_substream * substream )
{
unsigned long position ;
struct ct_atc * atc = snd_pcm_substream_chip ( substream ) ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct ct_atc_pcm * apcm = runtime - > private_data ;
/* Read out playback position */
position = atc - > pcm_capture_position ( atc , apcm ) ;
position = bytes_to_frames ( runtime , position ) ;
return position ;
}
/* PCM operators for playback */
static struct snd_pcm_ops ct_pcm_playback_ops = {
. open = ct_pcm_playback_open ,
. close = ct_pcm_playback_close ,
. ioctl = snd_pcm_lib_ioctl ,
. hw_params = ct_pcm_hw_params ,
. hw_free = ct_pcm_hw_free ,
. prepare = ct_pcm_playback_prepare ,
. trigger = ct_pcm_playback_trigger ,
. pointer = ct_pcm_playback_pointer ,
} ;
/* PCM operators for capture */
static struct snd_pcm_ops ct_pcm_capture_ops = {
. open = ct_pcm_capture_open ,
. close = ct_pcm_capture_close ,
. ioctl = snd_pcm_lib_ioctl ,
. hw_params = ct_pcm_hw_params ,
. hw_free = ct_pcm_hw_free ,
. prepare = ct_pcm_capture_prepare ,
. trigger = ct_pcm_capture_trigger ,
. pointer = ct_pcm_capture_pointer ,
} ;
/* Create ALSA pcm device */
int ct_alsa_pcm_create ( struct ct_atc * atc ,
enum CTALSADEVS device ,
const char * device_name )
{
struct snd_pcm * pcm ;
int err ;
int playback_count , capture_count ;
char name [ 128 ] ;
strncpy ( name , device_name , sizeof ( name ) ) ;
playback_count = ( IEC958 = = device ) ? 1 : 8 ;
capture_count = ( FRONT = = device ) ? 1 : 0 ;
err = snd_pcm_new ( atc - > card , name , device ,
playback_count , capture_count , & pcm ) ;
if ( err < 0 ) {
2009-05-14 15:19:30 +02:00
printk ( KERN_ERR " ctxfi: snd_pcm_new failed!! Err=%d \n " , err ) ;
2009-05-14 08:05:58 +02:00
return err ;
}
pcm - > private_data = atc ;
pcm - > info_flags = 0 ;
pcm - > dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX ;
strcpy ( pcm - > name , device_name ) ;
snd_pcm_set_ops ( pcm , SNDRV_PCM_STREAM_PLAYBACK , & ct_pcm_playback_ops ) ;
if ( FRONT = = device )
snd_pcm_set_ops ( pcm ,
SNDRV_PCM_STREAM_CAPTURE , & ct_pcm_capture_ops ) ;
snd_pcm_lib_preallocate_pages_for_all ( pcm , SNDRV_DMA_TYPE_DEV ,
snd_dma_pci_data ( atc - > pci ) , 128 * 1024 , 128 * 1024 ) ;
return 0 ;
}