2010-06-17 21:27:26 +04:00
/*
2013-03-25 12:35:17 +04:00
* Copyright ( C ) 2010 - 2013 Bluecherry , LLC < http : //www.bluecherrydvr.com>
*
* Original author :
* Ben Collins < bcollins @ ubuntu . com >
*
* Additional work by :
* John Brooks < john . brooks @ bluecherry . net >
2010-06-17 21:27:26 +04: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 .
*/
# include <linux/kernel.h>
# include <linux/mempool.h>
# include <linux/poll.h>
# include <linux/kthread.h>
# include <linux/freezer.h>
2013-03-25 12:35:17 +04:00
# include <linux/module.h>
# include <linux/slab.h>
2010-06-17 21:27:26 +04:00
# include <sound/core.h>
# include <sound/initval.h>
# include <sound/pcm.h>
# include <sound/control.h>
2013-03-25 12:35:17 +04:00
2011-02-11 15:36:27 +03:00
# include "solo6x10.h"
2013-03-25 12:42:46 +04:00
# include "solo6x10-tw28.h"
2010-06-17 21:27:26 +04:00
# define G723_FDMA_PAGES 32
# define G723_PERIOD_BYTES 48
# define G723_PERIOD_BLOCK 1024
# define G723_FRAMES_PER_PAGE 48
/* Sets up channels 16-19 for decoding and 0-15 for encoding */
# define OUTMODE_MASK 0x300
# define SAMPLERATE 8000
# define BITRATE 25
/* The solo writes to 1k byte pages, 32 pages, in the dma. Each 1k page
* is broken down to 20 * 48 byte regions ( one for each channel possible )
* with the rest of the page being dummy data . */
2015-06-08 16:35:05 +03:00
# define PERIODS G723_FDMA_PAGES
2013-03-25 12:35:17 +04:00
# define G723_INTR_ORDER 4 /* 0 - 4 */
2010-06-17 21:27:26 +04:00
struct solo_snd_pcm {
2013-03-25 12:35:17 +04:00
int on ;
spinlock_t lock ;
2014-12-24 14:35:59 +03:00
struct solo_dev * solo_dev ;
u8 * g723_buf ;
2013-03-25 12:35:17 +04:00
dma_addr_t g723_dma ;
2010-06-17 21:27:26 +04:00
} ;
2011-02-11 15:38:20 +03:00
static void solo_g723_config ( struct solo_dev * solo_dev )
2010-06-17 21:27:26 +04:00
{
int clk_div ;
2013-03-25 12:35:17 +04:00
clk_div = ( solo_dev - > clock_mhz * 1000000 )
/ ( SAMPLERATE * ( BITRATE * 2 ) * 2 ) ;
2010-06-17 21:27:26 +04:00
solo_reg_write ( solo_dev , SOLO_AUDIO_SAMPLE ,
2013-03-25 12:35:17 +04:00
SOLO_AUDIO_BITRATE ( BITRATE )
| SOLO_AUDIO_CLK_DIV ( clk_div ) ) ;
2010-06-17 21:27:26 +04:00
solo_reg_write ( solo_dev , SOLO_AUDIO_FDMA_INTR ,
2013-03-25 12:35:17 +04:00
SOLO_AUDIO_FDMA_INTERVAL ( 1 )
| SOLO_AUDIO_INTR_ORDER ( G723_INTR_ORDER )
| SOLO_AUDIO_FDMA_BASE ( SOLO_G723_EXT_ADDR ( solo_dev ) > > 16 ) ) ;
2010-06-17 21:27:26 +04:00
solo_reg_write ( solo_dev , SOLO_AUDIO_CONTROL ,
2013-03-25 12:35:17 +04:00
SOLO_AUDIO_ENABLE
| SOLO_AUDIO_I2S_MODE
| SOLO_AUDIO_I2S_MULTI ( 3 )
| SOLO_AUDIO_MODE ( OUTMODE_MASK ) ) ;
2010-06-17 21:27:26 +04:00
}
2011-02-11 15:38:20 +03:00
void solo_g723_isr ( struct solo_dev * solo_dev )
2010-06-17 21:27:26 +04:00
{
struct snd_pcm_str * pstr =
& solo_dev - > snd_pcm - > streams [ SNDRV_PCM_STREAM_CAPTURE ] ;
struct snd_pcm_substream * ss ;
struct solo_snd_pcm * solo_pcm ;
for ( ss = pstr - > substream ; ss ! = NULL ; ss = ss - > next ) {
if ( snd_pcm_substream_chip ( ss ) = = NULL )
continue ;
/* This means open() hasn't been called on this one */
if ( snd_pcm_substream_chip ( ss ) = = solo_dev )
continue ;
/* Haven't triggered a start yet */
solo_pcm = snd_pcm_substream_chip ( ss ) ;
if ( ! solo_pcm - > on )
continue ;
snd_pcm_period_elapsed ( ss ) ;
}
}
static int snd_solo_hw_params ( struct snd_pcm_substream * ss ,
struct snd_pcm_hw_params * hw_params )
{
return snd_pcm_lib_malloc_pages ( ss , params_buffer_bytes ( hw_params ) ) ;
}
static int snd_solo_hw_free ( struct snd_pcm_substream * ss )
{
return snd_pcm_lib_free_pages ( ss ) ;
}
2013-03-25 12:35:17 +04:00
static const struct snd_pcm_hardware snd_solo_pcm_hw = {
2010-06-17 21:27:26 +04:00
. info = ( SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID ) ,
. formats = SNDRV_PCM_FMTBIT_U8 ,
. rates = SNDRV_PCM_RATE_8000 ,
2013-03-25 12:35:17 +04:00
. rate_min = SAMPLERATE ,
. rate_max = SAMPLERATE ,
2010-06-17 21:27:26 +04:00
. channels_min = 1 ,
. channels_max = 1 ,
2015-06-08 16:35:05 +03:00
. buffer_bytes_max = G723_PERIOD_BYTES * PERIODS ,
2010-06-17 21:27:26 +04:00
. period_bytes_min = G723_PERIOD_BYTES ,
. period_bytes_max = G723_PERIOD_BYTES ,
2015-06-08 16:35:05 +03:00
. periods_min = PERIODS ,
. periods_max = PERIODS ,
2010-06-17 21:27:26 +04:00
} ;
static int snd_solo_pcm_open ( struct snd_pcm_substream * ss )
{
2011-02-11 15:38:20 +03:00
struct solo_dev * solo_dev = snd_pcm_substream_chip ( ss ) ;
2010-06-17 21:27:26 +04:00
struct solo_snd_pcm * solo_pcm ;
solo_pcm = kzalloc ( sizeof ( * solo_pcm ) , GFP_KERNEL ) ;
if ( solo_pcm = = NULL )
2013-03-25 12:35:17 +04:00
goto oom ;
solo_pcm - > g723_buf = pci_alloc_consistent ( solo_dev - > pdev ,
G723_PERIOD_BYTES ,
& solo_pcm - > g723_dma ) ;
if ( solo_pcm - > g723_buf = = NULL )
goto oom ;
2010-06-17 21:27:26 +04:00
spin_lock_init ( & solo_pcm - > lock ) ;
solo_pcm - > solo_dev = solo_dev ;
ss - > runtime - > hw = snd_solo_pcm_hw ;
snd_pcm_substream_chip ( ss ) = solo_pcm ;
return 0 ;
2013-03-25 12:35:17 +04:00
oom :
kfree ( solo_pcm ) ;
return - ENOMEM ;
2010-06-17 21:27:26 +04:00
}
static int snd_solo_pcm_close ( struct snd_pcm_substream * ss )
{
struct solo_snd_pcm * solo_pcm = snd_pcm_substream_chip ( ss ) ;
snd_pcm_substream_chip ( ss ) = solo_pcm - > solo_dev ;
2013-03-25 12:35:17 +04:00
pci_free_consistent ( solo_pcm - > solo_dev - > pdev , G723_PERIOD_BYTES ,
solo_pcm - > g723_buf , solo_pcm - > g723_dma ) ;
2010-06-17 21:27:26 +04:00
kfree ( solo_pcm ) ;
2010-11-05 05:37:15 +03:00
return 0 ;
2010-06-17 21:27:26 +04:00
}
static int snd_solo_pcm_trigger ( struct snd_pcm_substream * ss , int cmd )
{
struct solo_snd_pcm * solo_pcm = snd_pcm_substream_chip ( ss ) ;
2011-02-11 15:38:20 +03:00
struct solo_dev * solo_dev = solo_pcm - > solo_dev ;
2010-06-17 21:27:26 +04:00
int ret = 0 ;
spin_lock ( & solo_pcm - > lock ) ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
if ( solo_pcm - > on = = 0 ) {
/* If this is the first user, switch on interrupts */
if ( atomic_inc_return ( & solo_dev - > snd_users ) = = 1 )
2011-02-11 15:38:20 +03:00
solo_irq_on ( solo_dev , SOLO_IRQ_G723 ) ;
2010-06-17 21:27:26 +04:00
solo_pcm - > on = 1 ;
}
break ;
case SNDRV_PCM_TRIGGER_STOP :
if ( solo_pcm - > on ) {
/* If this was our last user, switch them off */
if ( atomic_dec_return ( & solo_dev - > snd_users ) = = 0 )
2011-02-11 15:38:20 +03:00
solo_irq_off ( solo_dev , SOLO_IRQ_G723 ) ;
2010-06-17 21:27:26 +04:00
solo_pcm - > on = 0 ;
}
break ;
default :
ret = - EINVAL ;
}
spin_unlock ( & solo_pcm - > lock ) ;
return ret ;
}
static int snd_solo_pcm_prepare ( struct snd_pcm_substream * ss )
{
2010-11-05 05:37:15 +03:00
return 0 ;
2010-06-17 21:27:26 +04:00
}
static snd_pcm_uframes_t snd_solo_pcm_pointer ( struct snd_pcm_substream * ss )
{
struct solo_snd_pcm * solo_pcm = snd_pcm_substream_chip ( ss ) ;
2011-02-11 15:38:20 +03:00
struct solo_dev * solo_dev = solo_pcm - > solo_dev ;
2010-06-17 21:27:26 +04:00
snd_pcm_uframes_t idx = solo_reg_read ( solo_dev , SOLO_AUDIO_STA ) & 0x1f ;
return idx * G723_FRAMES_PER_PAGE ;
}
static int snd_solo_pcm_copy ( struct snd_pcm_substream * ss , int channel ,
snd_pcm_uframes_t pos , void __user * dst ,
snd_pcm_uframes_t count )
{
struct solo_snd_pcm * solo_pcm = snd_pcm_substream_chip ( ss ) ;
2011-02-11 15:38:20 +03:00
struct solo_dev * solo_dev = solo_pcm - > solo_dev ;
2010-06-17 21:27:26 +04:00
int err , i ;
for ( i = 0 ; i < ( count / G723_FRAMES_PER_PAGE ) ; i + + ) {
int page = ( pos / G723_FRAMES_PER_PAGE ) + i ;
2013-03-25 12:35:17 +04:00
err = solo_p2m_dma_t ( solo_dev , 0 , solo_pcm - > g723_dma ,
SOLO_G723_EXT_ADDR ( solo_dev ) +
( page * G723_PERIOD_BLOCK ) +
( ss - > number * G723_PERIOD_BYTES ) ,
G723_PERIOD_BYTES , 0 , 0 ) ;
2010-06-17 21:27:26 +04:00
if ( err )
return err ;
err = copy_to_user ( dst + ( i * G723_PERIOD_BYTES ) ,
solo_pcm - > g723_buf , G723_PERIOD_BYTES ) ;
if ( err )
2010-08-10 10:09:05 +04:00
return - EFAULT ;
2010-06-17 21:27:26 +04:00
}
return 0 ;
}
2016-09-08 03:44:39 +03:00
static const struct snd_pcm_ops snd_solo_pcm_ops = {
2010-06-17 21:27:26 +04:00
. open = snd_solo_pcm_open ,
. close = snd_solo_pcm_close ,
. ioctl = snd_pcm_lib_ioctl ,
. hw_params = snd_solo_hw_params ,
. hw_free = snd_solo_hw_free ,
. prepare = snd_solo_pcm_prepare ,
. trigger = snd_solo_pcm_trigger ,
. pointer = snd_solo_pcm_pointer ,
. copy = snd_solo_pcm_copy ,
} ;
static int snd_solo_capture_volume_info ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_info * info )
{
info - > type = SNDRV_CTL_ELEM_TYPE_INTEGER ;
info - > count = 1 ;
info - > value . integer . min = 0 ;
info - > value . integer . max = 15 ;
info - > value . integer . step = 1 ;
return 0 ;
}
static int snd_solo_capture_volume_get ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * value )
{
2011-02-11 15:38:20 +03:00
struct solo_dev * solo_dev = snd_kcontrol_chip ( kcontrol ) ;
2010-06-17 21:27:26 +04:00
u8 ch = value - > id . numid - 1 ;
value - > value . integer . value [ 0 ] = tw28_get_audio_gain ( solo_dev , ch ) ;
2010-11-05 05:37:15 +03:00
return 0 ;
2010-06-17 21:27:26 +04:00
}
static int snd_solo_capture_volume_put ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * value )
{
2011-02-11 15:38:20 +03:00
struct solo_dev * solo_dev = snd_kcontrol_chip ( kcontrol ) ;
2010-06-17 21:27:26 +04:00
u8 ch = value - > id . numid - 1 ;
2010-11-05 05:37:15 +03:00
u8 old_val ;
2010-06-17 21:27:26 +04:00
2010-11-05 05:37:15 +03:00
old_val = tw28_get_audio_gain ( solo_dev , ch ) ;
2010-06-17 21:27:26 +04:00
if ( old_val = = value - > value . integer . value [ 0 ] )
return 0 ;
tw28_set_audio_gain ( solo_dev , ch , value - > value . integer . value [ 0 ] ) ;
2010-11-05 05:37:15 +03:00
return 1 ;
2010-06-17 21:27:26 +04:00
}
static struct snd_kcontrol_new snd_solo_capture_volume = {
. iface = SNDRV_CTL_ELEM_IFACE_MIXER ,
. name = " Capture Volume " ,
. info = snd_solo_capture_volume_info ,
. get = snd_solo_capture_volume_get ,
. put = snd_solo_capture_volume_put ,
} ;
2011-02-11 15:38:20 +03:00
static int solo_snd_pcm_init ( struct solo_dev * solo_dev )
2010-06-17 21:27:26 +04:00
{
struct snd_card * card = solo_dev - > snd_card ;
struct snd_pcm * pcm ;
struct snd_pcm_substream * ss ;
int ret ;
int i ;
ret = snd_pcm_new ( card , card - > driver , 0 , 0 , solo_dev - > nr_chans ,
& pcm ) ;
if ( ret < 0 )
return ret ;
snd_pcm_set_ops ( pcm , SNDRV_PCM_STREAM_CAPTURE ,
& snd_solo_pcm_ops ) ;
snd_pcm_chip ( pcm ) = solo_dev ;
pcm - > info_flags = 0 ;
strcpy ( pcm - > name , card - > shortname ) ;
for ( i = 0 , ss = pcm - > streams [ SNDRV_PCM_STREAM_CAPTURE ] . substream ;
ss ; ss = ss - > next , i + + )
sprintf ( ss - > name , " Camera #%d Audio " , i ) ;
ret = snd_pcm_lib_preallocate_pages_for_all ( pcm ,
SNDRV_DMA_TYPE_CONTINUOUS ,
snd_dma_continuous_data ( GFP_KERNEL ) ,
2015-06-08 16:35:05 +03:00
G723_PERIOD_BYTES * PERIODS ,
G723_PERIOD_BYTES * PERIODS ) ;
2010-06-17 21:27:26 +04:00
if ( ret < 0 )
return ret ;
solo_dev - > snd_pcm = pcm ;
return 0 ;
}
2011-02-11 15:38:20 +03:00
int solo_g723_init ( struct solo_dev * solo_dev )
2010-06-17 21:27:26 +04:00
{
2016-12-17 04:05:36 +03:00
static struct snd_device_ops ops = { } ;
2010-06-17 21:27:26 +04:00
struct snd_card * card ;
struct snd_kcontrol_new kctl ;
char name [ 32 ] ;
int ret ;
atomic_set ( & solo_dev - > snd_users , 0 ) ;
/* Allows for easier mapping between video and audio */
sprintf ( name , " Softlogic%d " , solo_dev - > vfd - > num ) ;
2014-01-29 18:03:37 +04:00
ret = snd_card_new ( & solo_dev - > pdev - > dev ,
SNDRV_DEFAULT_IDX1 , name , THIS_MODULE , 0 ,
& solo_dev - > snd_card ) ;
2010-06-17 21:27:26 +04:00
if ( ret < 0 )
return ret ;
card = solo_dev - > snd_card ;
2011-02-11 15:38:20 +03:00
strcpy ( card - > driver , SOLO6X10_NAME ) ;
strcpy ( card - > shortname , " SOLO-6x10 Audio " ) ;
2010-06-17 21:27:26 +04:00
sprintf ( card - > longname , " %s on %s IRQ %d " , card - > shortname ,
pci_name ( solo_dev - > pdev ) , solo_dev - > pdev - > irq ) ;
ret = snd_device_new ( card , SNDRV_DEV_LOWLEVEL , solo_dev , & ops ) ;
if ( ret < 0 )
goto snd_error ;
/* Mixer controls */
2011-02-11 15:38:20 +03:00
strcpy ( card - > mixername , " SOLO-6x10 " ) ;
2010-06-17 21:27:26 +04:00
kctl = snd_solo_capture_volume ;
kctl . count = solo_dev - > nr_chans ;
2013-03-25 12:35:17 +04:00
2010-11-05 05:37:15 +03:00
ret = snd_ctl_add ( card , snd_ctl_new1 ( & kctl , solo_dev ) ) ;
2010-06-17 21:27:26 +04:00
if ( ret < 0 )
return ret ;
2010-11-10 16:39:33 +03:00
ret = solo_snd_pcm_init ( solo_dev ) ;
if ( ret < 0 )
2010-06-17 21:27:26 +04:00
goto snd_error ;
2010-11-10 16:39:33 +03:00
ret = snd_card_register ( card ) ;
if ( ret < 0 )
2010-06-17 21:27:26 +04:00
goto snd_error ;
solo_g723_config ( solo_dev ) ;
dev_info ( & solo_dev - > pdev - > dev , " Alsa sound card as %s \n " , name ) ;
return 0 ;
snd_error :
snd_card_free ( card ) ;
return ret ;
}
2011-02-11 15:38:20 +03:00
void solo_g723_exit ( struct solo_dev * solo_dev )
2010-06-17 21:27:26 +04:00
{
2013-03-25 12:35:17 +04:00
if ( ! solo_dev - > snd_card )
return ;
2010-06-17 21:27:26 +04:00
solo_reg_write ( solo_dev , SOLO_AUDIO_CONTROL , 0 ) ;
2011-02-11 15:38:20 +03:00
solo_irq_off ( solo_dev , SOLO_IRQ_G723 ) ;
2010-06-17 21:27:26 +04:00
snd_card_free ( solo_dev - > snd_card ) ;
2013-03-25 12:35:17 +04:00
solo_dev - > snd_card = NULL ;
2010-06-17 21:27:26 +04:00
}