2007-12-14 16:40:23 +03:00
/*
* Driver for SiS7019 Audio Accelerator
*
* Copyright ( C ) 2004 - 2007 , David Dillow
* Written by David Dillow < dave @ thedillows . org >
* Inspired by the Trident 4 D - WaveDX / NX driver .
*
* All rights reserved .
*
* 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 , version 2.
*
* 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/pci.h>
# include <linux/time.h>
# include <linux/moduleparam.h>
# include <linux/interrupt.h>
# include <linux/delay.h>
# include <sound/core.h>
# include <sound/ac97_codec.h>
# include <sound/initval.h>
# include "sis7019.h"
MODULE_AUTHOR ( " David Dillow <dave@thedillows.org> " ) ;
MODULE_DESCRIPTION ( " SiS7019 " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_SUPPORTED_DEVICE ( " {{SiS,SiS7019 Audio Accelerator}} " ) ;
static int index = SNDRV_DEFAULT_IDX1 ; /* Index 0-MAX */
static char * id = SNDRV_DEFAULT_STR1 ; /* ID for this card */
static int enable = 1 ;
module_param ( index , int , 0444 ) ;
MODULE_PARM_DESC ( index , " Index value for SiS7019 Audio Accelerator. " ) ;
module_param ( id , charp , 0444 ) ;
MODULE_PARM_DESC ( id , " ID string for SiS7019 Audio Accelerator. " ) ;
module_param ( enable , bool , 0444 ) ;
MODULE_PARM_DESC ( enable , " Enable SiS7019 Audio Accelerator. " ) ;
2010-02-06 01:21:03 +03:00
static DEFINE_PCI_DEVICE_TABLE ( snd_sis7019_ids ) = {
2007-12-14 16:40:23 +03:00
{ PCI_DEVICE ( PCI_VENDOR_ID_SI , 0x7019 ) } ,
{ 0 , }
} ;
MODULE_DEVICE_TABLE ( pci , snd_sis7019_ids ) ;
/* There are three timing modes for the voices.
*
* For both playback and capture , when the buffer is one or two periods long ,
* we use the hardware ' s built - in Mid - Loop Interrupt and End - Loop Interrupt
* to let us know when the periods have ended .
*
* When performing playback with more than two periods per buffer , we set
* the " Stop Sample Offset " and tell the hardware to interrupt us when we
* reach it . We then update the offset and continue on until we are
* interrupted for the next period .
*
* Capture channels do not have a SSO , so we allocate a playback channel to
* use as a timer for the capture periods . We use the SSO on the playback
* channel to clock out virtual periods , and adjust the virtual period length
* to maintain synchronization . This algorithm came from the Trident driver .
*
* FIXME : It ' d be nice to make use of some of the synth features in the
* hardware , but a woeful lack of documentation is a significant roadblock .
*/
struct voice {
u16 flags ;
# define VOICE_IN_USE 1
# define VOICE_CAPTURE 2
# define VOICE_SSO_TIMING 4
# define VOICE_SYNC_TIMING 8
u16 sync_cso ;
u16 period_size ;
u16 buffer_size ;
u16 sync_period_size ;
u16 sync_buffer_size ;
u32 sso ;
u32 vperiod ;
struct snd_pcm_substream * substream ;
struct voice * timing ;
void __iomem * ctrl_base ;
void __iomem * wave_base ;
void __iomem * sync_base ;
int num ;
} ;
/* We need four pages to store our wave parameters during a suspend. If
* we ' re not doing power management , we still need to allocate a page
* for the silence buffer .
*/
# ifdef CONFIG_PM
# define SIS_SUSPEND_PAGES 4
# else
# define SIS_SUSPEND_PAGES 1
# endif
struct sis7019 {
unsigned long ioport ;
void __iomem * ioaddr ;
int irq ;
int codecs_present ;
struct pci_dev * pci ;
struct snd_pcm * pcm ;
struct snd_card * card ;
struct snd_ac97 * ac97 [ 3 ] ;
/* Protect against more than one thread hitting the AC97
* registers ( in a more polite manner than pounding the hardware
* semaphore )
*/
struct mutex ac97_mutex ;
/* voice_lock protects allocation/freeing of the voice descriptions
*/
spinlock_t voice_lock ;
struct voice voices [ 64 ] ;
struct voice capture_voice ;
/* Allocate pages to store the internal wave state during
* suspends . When we ' re operating , this can be used as a silence
* buffer for a timing channel .
*/
void * suspend_state [ SIS_SUSPEND_PAGES ] ;
int silence_users ;
dma_addr_t silence_dma_addr ;
} ;
# define SIS_PRIMARY_CODEC_PRESENT 0x0001
# define SIS_SECONDARY_CODEC_PRESENT 0x0002
# define SIS_TERTIARY_CODEC_PRESENT 0x0004
/* The HW offset parameters (Loop End, Stop Sample, End Sample) have a
* documented range of 8 - 0xfff8 samples . Given that they are 0 - based ,
* that places our period / buffer range at 9 - 0xfff9 samples . That makes the
* max buffer size 0xfff9 samples * 2 channels * 2 bytes per sample , and
* max samples / min samples gives us the max periods in a buffer .
*
* We ' ll add a constraint upon open that limits the period and buffer sample
* size to values that are legal for the hardware .
*/
static struct snd_pcm_hardware sis_playback_hw_info = {
. info = ( SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_SYNC_START |
SNDRV_PCM_INFO_RESUME ) ,
. formats = ( SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE ) ,
. rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_CONTINUOUS ,
. rate_min = 4000 ,
. rate_max = 48000 ,
. channels_min = 1 ,
. channels_max = 2 ,
. buffer_bytes_max = ( 0xfff9 * 4 ) ,
. period_bytes_min = 9 ,
. period_bytes_max = ( 0xfff9 * 4 ) ,
. periods_min = 1 ,
. periods_max = ( 0xfff9 / 9 ) ,
} ;
static struct snd_pcm_hardware sis_capture_hw_info = {
. info = ( SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_SYNC_START |
SNDRV_PCM_INFO_RESUME ) ,
. formats = ( SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE ) ,
. rates = SNDRV_PCM_RATE_48000 ,
. rate_min = 4000 ,
. rate_max = 48000 ,
. channels_min = 1 ,
. channels_max = 2 ,
. buffer_bytes_max = ( 0xfff9 * 4 ) ,
. period_bytes_min = 9 ,
. period_bytes_max = ( 0xfff9 * 4 ) ,
. periods_min = 1 ,
. periods_max = ( 0xfff9 / 9 ) ,
} ;
static void sis_update_sso ( struct voice * voice , u16 period )
{
void __iomem * base = voice - > ctrl_base ;
voice - > sso + = period ;
if ( voice - > sso > = voice - > buffer_size )
voice - > sso - = voice - > buffer_size ;
/* Enforce the documented hardware minimum offset */
if ( voice - > sso < 8 )
voice - > sso = 8 ;
/* The SSO is in the upper 16 bits of the register. */
writew ( voice - > sso & 0xffff , base + SIS_PLAY_DMA_SSO_ESO + 2 ) ;
}
static void sis_update_voice ( struct voice * voice )
{
if ( voice - > flags & VOICE_SSO_TIMING ) {
sis_update_sso ( voice , voice - > period_size ) ;
} else if ( voice - > flags & VOICE_SYNC_TIMING ) {
int sync ;
/* If we've not hit the end of the virtual period, update
* our records and keep going .
*/
if ( voice - > vperiod > voice - > period_size ) {
voice - > vperiod - = voice - > period_size ;
if ( voice - > vperiod < voice - > period_size )
sis_update_sso ( voice , voice - > vperiod ) ;
else
sis_update_sso ( voice , voice - > period_size ) ;
return ;
}
/* Calculate our relative offset between the target and
* the actual CSO value . Since we ' re operating in a loop ,
* if the value is more than half way around , we can
* consider ourselves wrapped .
*/
sync = voice - > sync_cso ;
sync - = readw ( voice - > sync_base + SIS_CAPTURE_DMA_FORMAT_CSO ) ;
if ( sync > ( voice - > sync_buffer_size / 2 ) )
sync - = voice - > sync_buffer_size ;
/* If sync is positive, then we interrupted too early, and
* we ' ll need to come back in a few samples and try again .
* There ' s a minimum wait , as it takes some time for the DMA
* engine to startup , etc . . .
*/
if ( sync > 0 ) {
if ( sync < 16 )
sync = 16 ;
sis_update_sso ( voice , sync ) ;
return ;
}
/* Ok, we interrupted right on time, or (hopefully) just
* a bit late . We ' ll adjst our next waiting period based
* on how close we got .
*
* We need to stay just behind the actual channel to ensure
* it really is past a period when we get our interrupt - -
* otherwise we ' ll fall into the early code above and have
* a minimum wait time , which makes us quite late here ,
* eating into the user ' s time to refresh the buffer , esp .
* if using small periods .
*
* If we ' re less than 9 samples behind , we ' re on target .
*/
if ( sync > - 9 )
voice - > vperiod = voice - > sync_period_size + 1 ;
else
voice - > vperiod = voice - > sync_period_size - 4 ;
if ( voice - > vperiod < voice - > buffer_size ) {
sis_update_sso ( voice , voice - > vperiod ) ;
voice - > vperiod = 0 ;
} else
sis_update_sso ( voice , voice - > period_size ) ;
sync = voice - > sync_cso + voice - > sync_period_size ;
if ( sync > = voice - > sync_buffer_size )
sync - = voice - > sync_buffer_size ;
voice - > sync_cso = sync ;
}
snd_pcm_period_elapsed ( voice - > substream ) ;
}
static void sis_voice_irq ( u32 status , struct voice * voice )
{
int bit ;
while ( status ) {
bit = __ffs ( status ) ;
status > > = bit + 1 ;
voice + = bit ;
sis_update_voice ( voice ) ;
voice + + ;
}
}
static irqreturn_t sis_interrupt ( int irq , void * dev )
{
struct sis7019 * sis = dev ;
unsigned long io = sis - > ioport ;
struct voice * voice ;
u32 intr , status ;
/* We only use the DMA interrupts, and we don't enable any other
* source of interrupts . But , it is possible to see an interupt
* status that didn ' t actually interrupt us , so eliminate anything
* we ' re not expecting to avoid falsely claiming an IRQ , and an
* ensuing endless loop .
*/
intr = inl ( io + SIS_GISR ) ;
intr & = SIS_GISR_AUDIO_PLAY_DMA_IRQ_STATUS |
SIS_GISR_AUDIO_RECORD_DMA_IRQ_STATUS ;
if ( ! intr )
return IRQ_NONE ;
do {
status = inl ( io + SIS_PISR_A ) ;
if ( status ) {
sis_voice_irq ( status , sis - > voices ) ;
outl ( status , io + SIS_PISR_A ) ;
}
status = inl ( io + SIS_PISR_B ) ;
if ( status ) {
sis_voice_irq ( status , & sis - > voices [ 32 ] ) ;
outl ( status , io + SIS_PISR_B ) ;
}
status = inl ( io + SIS_RISR ) ;
if ( status ) {
voice = & sis - > capture_voice ;
if ( ! voice - > timing )
snd_pcm_period_elapsed ( voice - > substream ) ;
outl ( status , io + SIS_RISR ) ;
}
outl ( intr , io + SIS_GISR ) ;
intr = inl ( io + SIS_GISR ) ;
intr & = SIS_GISR_AUDIO_PLAY_DMA_IRQ_STATUS |
SIS_GISR_AUDIO_RECORD_DMA_IRQ_STATUS ;
} while ( intr ) ;
return IRQ_HANDLED ;
}
static u32 sis_rate_to_delta ( unsigned int rate )
{
u32 delta ;
/* This was copied from the trident driver, but it seems its gotten
* around a bit . . . nevertheless , it works well .
*
* We special case 44100 and 8000 since rounding with the equation
* does not give us an accurate enough value . For 11025 and 22050
* the equation gives us the best answer . All other frequencies will
* also use the equation . JDW
*/
if ( rate = = 44100 )
delta = 0xeb3 ;
else if ( rate = = 8000 )
delta = 0x2ab ;
else if ( rate = = 48000 )
delta = 0x1000 ;
else
delta = ( ( ( rate < < 12 ) + 24000 ) / 48000 ) & 0x0000ffff ;
return delta ;
}
static void __sis_map_silence ( struct sis7019 * sis )
{
/* Helper function: must hold sis->voice_lock on entry */
if ( ! sis - > silence_users )
sis - > silence_dma_addr = pci_map_single ( sis - > pci ,
sis - > suspend_state [ 0 ] ,
4096 , PCI_DMA_TODEVICE ) ;
sis - > silence_users + + ;
}
static void __sis_unmap_silence ( struct sis7019 * sis )
{
/* Helper function: must hold sis->voice_lock on entry */
sis - > silence_users - - ;
if ( ! sis - > silence_users )
pci_unmap_single ( sis - > pci , sis - > silence_dma_addr , 4096 ,
PCI_DMA_TODEVICE ) ;
}
static void sis_free_voice ( struct sis7019 * sis , struct voice * voice )
{
unsigned long flags ;
spin_lock_irqsave ( & sis - > voice_lock , flags ) ;
if ( voice - > timing ) {
__sis_unmap_silence ( sis ) ;
voice - > timing - > flags & = ~ ( VOICE_IN_USE | VOICE_SSO_TIMING |
VOICE_SYNC_TIMING ) ;
voice - > timing = NULL ;
}
voice - > flags & = ~ ( VOICE_IN_USE | VOICE_SSO_TIMING | VOICE_SYNC_TIMING ) ;
spin_unlock_irqrestore ( & sis - > voice_lock , flags ) ;
}
static struct voice * __sis_alloc_playback_voice ( struct sis7019 * sis )
{
/* Must hold the voice_lock on entry */
struct voice * voice ;
int i ;
for ( i = 0 ; i < 64 ; i + + ) {
voice = & sis - > voices [ i ] ;
if ( voice - > flags & VOICE_IN_USE )
continue ;
voice - > flags | = VOICE_IN_USE ;
goto found_one ;
}
voice = NULL ;
found_one :
return voice ;
}
static struct voice * sis_alloc_playback_voice ( struct sis7019 * sis )
{
struct voice * voice ;
unsigned long flags ;
spin_lock_irqsave ( & sis - > voice_lock , flags ) ;
voice = __sis_alloc_playback_voice ( sis ) ;
spin_unlock_irqrestore ( & sis - > voice_lock , flags ) ;
return voice ;
}
static int sis_alloc_timing_voice ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * hw_params )
{
struct sis7019 * sis = snd_pcm_substream_chip ( substream ) ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct voice * voice = runtime - > private_data ;
unsigned int period_size , buffer_size ;
unsigned long flags ;
int needed ;
/* If there are one or two periods per buffer, we don't need a
* timing voice , as we can use the capture channel ' s interrupts
* to clock out the periods .
*/
period_size = params_period_size ( hw_params ) ;
buffer_size = params_buffer_size ( hw_params ) ;
needed = ( period_size ! = buffer_size & &
period_size ! = ( buffer_size / 2 ) ) ;
if ( needed & & ! voice - > timing ) {
spin_lock_irqsave ( & sis - > voice_lock , flags ) ;
voice - > timing = __sis_alloc_playback_voice ( sis ) ;
if ( voice - > timing )
__sis_map_silence ( sis ) ;
spin_unlock_irqrestore ( & sis - > voice_lock , flags ) ;
if ( ! voice - > timing )
return - ENOMEM ;
voice - > timing - > substream = substream ;
} else if ( ! needed & & voice - > timing ) {
sis_free_voice ( sis , voice ) ;
voice - > timing = NULL ;
}
return 0 ;
}
static int sis_playback_open ( struct snd_pcm_substream * substream )
{
struct sis7019 * sis = snd_pcm_substream_chip ( substream ) ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct voice * voice ;
voice = sis_alloc_playback_voice ( sis ) ;
if ( ! voice )
return - EAGAIN ;
voice - > substream = substream ;
runtime - > private_data = voice ;
runtime - > hw = sis_playback_hw_info ;
snd_pcm_hw_constraint_minmax ( runtime , SNDRV_PCM_HW_PARAM_PERIOD_SIZE ,
9 , 0xfff9 ) ;
snd_pcm_hw_constraint_minmax ( runtime , SNDRV_PCM_HW_PARAM_BUFFER_SIZE ,
9 , 0xfff9 ) ;
snd_pcm_set_sync ( substream ) ;
return 0 ;
}
static int sis_substream_close ( struct snd_pcm_substream * substream )
{
struct sis7019 * sis = snd_pcm_substream_chip ( substream ) ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct voice * voice = runtime - > private_data ;
sis_free_voice ( sis , voice ) ;
return 0 ;
}
static int sis_playback_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 sis_hw_free ( struct snd_pcm_substream * substream )
{
return snd_pcm_lib_free_pages ( substream ) ;
}
static int sis_pcm_playback_prepare ( struct snd_pcm_substream * substream )
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct voice * voice = runtime - > private_data ;
void __iomem * ctrl_base = voice - > ctrl_base ;
void __iomem * wave_base = voice - > wave_base ;
u32 format , dma_addr , control , sso_eso , delta , reg ;
u16 leo ;
/* We rely on the PCM core to ensure that the parameters for this
* substream do not change on us while we ' re programming the HW .
*/
format = 0 ;
if ( snd_pcm_format_width ( runtime - > format ) = = 8 )
format | = SIS_PLAY_DMA_FORMAT_8BIT ;
if ( ! snd_pcm_format_signed ( runtime - > format ) )
format | = SIS_PLAY_DMA_FORMAT_UNSIGNED ;
if ( runtime - > channels = = 1 )
format | = SIS_PLAY_DMA_FORMAT_MONO ;
/* The baseline setup is for a single period per buffer, and
* we add bells and whistles as needed from there .
*/
dma_addr = runtime - > dma_addr ;
leo = runtime - > buffer_size - 1 ;
control = leo | SIS_PLAY_DMA_LOOP | SIS_PLAY_DMA_INTR_AT_LEO ;
sso_eso = leo ;
if ( runtime - > period_size = = ( runtime - > buffer_size / 2 ) ) {
control | = SIS_PLAY_DMA_INTR_AT_MLP ;
} else if ( runtime - > period_size ! = runtime - > buffer_size ) {
voice - > flags | = VOICE_SSO_TIMING ;
voice - > sso = runtime - > period_size - 1 ;
voice - > period_size = runtime - > period_size ;
voice - > buffer_size = runtime - > buffer_size ;
control & = ~ SIS_PLAY_DMA_INTR_AT_LEO ;
control | = SIS_PLAY_DMA_INTR_AT_SSO ;
sso_eso | = ( runtime - > period_size - 1 ) < < 16 ;
}
delta = sis_rate_to_delta ( runtime - > rate ) ;
/* Ok, we're ready to go, set up the channel.
*/
writel ( format , ctrl_base + SIS_PLAY_DMA_FORMAT_CSO ) ;
writel ( dma_addr , ctrl_base + SIS_PLAY_DMA_BASE ) ;
writel ( control , ctrl_base + SIS_PLAY_DMA_CONTROL ) ;
writel ( sso_eso , ctrl_base + SIS_PLAY_DMA_SSO_ESO ) ;
for ( reg = 0 ; reg < SIS_WAVE_SIZE ; reg + = 4 )
writel ( 0 , wave_base + reg ) ;
writel ( SIS_WAVE_GENERAL_WAVE_VOLUME , wave_base + SIS_WAVE_GENERAL ) ;
writel ( delta < < 16 , wave_base + SIS_WAVE_GENERAL_ARTICULATION ) ;
writel ( SIS_WAVE_CHANNEL_CONTROL_FIRST_SAMPLE |
SIS_WAVE_CHANNEL_CONTROL_AMP_ENABLE |
SIS_WAVE_CHANNEL_CONTROL_INTERPOLATE_ENABLE ,
wave_base + SIS_WAVE_CHANNEL_CONTROL ) ;
/* Force PCI writes to post. */
readl ( ctrl_base ) ;
return 0 ;
}
static int sis_pcm_trigger ( struct snd_pcm_substream * substream , int cmd )
{
struct sis7019 * sis = snd_pcm_substream_chip ( substream ) ;
unsigned long io = sis - > ioport ;
struct snd_pcm_substream * s ;
struct voice * voice ;
void * chip ;
int starting ;
u32 record = 0 ;
u32 play [ 2 ] = { 0 , 0 } ;
/* No locks needed, as the PCM core will hold the locks on the
* substreams , and the HW will only start / stop the indicated voices
* without changing the state of the others .
*/
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
case SNDRV_PCM_TRIGGER_RESUME :
starting = 1 ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
case SNDRV_PCM_TRIGGER_PAUSE_PUSH :
case SNDRV_PCM_TRIGGER_SUSPEND :
starting = 0 ;
break ;
default :
return - EINVAL ;
}
snd_pcm_group_for_each_entry ( s , substream ) {
/* Make sure it is for us... */
chip = snd_pcm_substream_chip ( s ) ;
if ( chip ! = sis )
continue ;
voice = s - > runtime - > private_data ;
if ( voice - > flags & VOICE_CAPTURE ) {
record | = 1 < < voice - > num ;
voice = voice - > timing ;
}
/* voice could be NULL if this a recording stream, and it
* doesn ' t have an external timing channel .
*/
if ( voice )
play [ voice - > num / 32 ] | = 1 < < ( voice - > num & 0x1f ) ;
snd_pcm_trigger_done ( s , substream ) ;
}
if ( starting ) {
if ( record )
outl ( record , io + SIS_RECORD_START_REG ) ;
if ( play [ 0 ] )
outl ( play [ 0 ] , io + SIS_PLAY_START_A_REG ) ;
if ( play [ 1 ] )
outl ( play [ 1 ] , io + SIS_PLAY_START_B_REG ) ;
} else {
if ( record )
outl ( record , io + SIS_RECORD_STOP_REG ) ;
if ( play [ 0 ] )
outl ( play [ 0 ] , io + SIS_PLAY_STOP_A_REG ) ;
if ( play [ 1 ] )
outl ( play [ 1 ] , io + SIS_PLAY_STOP_B_REG ) ;
}
return 0 ;
}
static snd_pcm_uframes_t sis_pcm_pointer ( struct snd_pcm_substream * substream )
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct voice * voice = runtime - > private_data ;
u32 cso ;
cso = readl ( voice - > ctrl_base + SIS_PLAY_DMA_FORMAT_CSO ) ;
cso & = 0xffff ;
return cso ;
}
static int sis_capture_open ( struct snd_pcm_substream * substream )
{
struct sis7019 * sis = snd_pcm_substream_chip ( substream ) ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct voice * voice = & sis - > capture_voice ;
unsigned long flags ;
/* FIXME: The driver only supports recording from one channel
* at the moment , but it could support more .
*/
spin_lock_irqsave ( & sis - > voice_lock , flags ) ;
if ( voice - > flags & VOICE_IN_USE )
voice = NULL ;
else
voice - > flags | = VOICE_IN_USE ;
spin_unlock_irqrestore ( & sis - > voice_lock , flags ) ;
if ( ! voice )
return - EAGAIN ;
voice - > substream = substream ;
runtime - > private_data = voice ;
runtime - > hw = sis_capture_hw_info ;
runtime - > hw . rates = sis - > ac97 [ 0 ] - > rates [ AC97_RATES_ADC ] ;
snd_pcm_limit_hw_rates ( runtime ) ;
snd_pcm_hw_constraint_minmax ( runtime , SNDRV_PCM_HW_PARAM_PERIOD_SIZE ,
9 , 0xfff9 ) ;
snd_pcm_hw_constraint_minmax ( runtime , SNDRV_PCM_HW_PARAM_BUFFER_SIZE ,
9 , 0xfff9 ) ;
snd_pcm_set_sync ( substream ) ;
return 0 ;
}
static int sis_capture_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * hw_params )
{
struct sis7019 * sis = snd_pcm_substream_chip ( substream ) ;
int rc ;
rc = snd_ac97_set_rate ( sis - > ac97 [ 0 ] , AC97_PCM_LR_ADC_RATE ,
params_rate ( hw_params ) ) ;
if ( rc )
goto out ;
rc = snd_pcm_lib_malloc_pages ( substream ,
params_buffer_bytes ( hw_params ) ) ;
if ( rc < 0 )
goto out ;
rc = sis_alloc_timing_voice ( substream , hw_params ) ;
out :
return rc ;
}
static void sis_prepare_timing_voice ( struct voice * voice ,
struct snd_pcm_substream * substream )
{
struct sis7019 * sis = snd_pcm_substream_chip ( substream ) ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct voice * timing = voice - > timing ;
void __iomem * play_base = timing - > ctrl_base ;
void __iomem * wave_base = timing - > wave_base ;
u16 buffer_size , period_size ;
u32 format , control , sso_eso , delta ;
u32 vperiod , sso , reg ;
/* Set our initial buffer and period as large as we can given a
* single page of silence .
*/
buffer_size = 4096 / runtime - > channels ;
buffer_size / = snd_pcm_format_size ( runtime - > format , 1 ) ;
period_size = buffer_size ;
/* Initially, we want to interrupt just a bit behind the end of
* the period we ' re clocking out . 10 samples seems to give a good
* delay .
*
* We want to spread our interrupts throughout the virtual period ,
* so that we don ' t end up with two interrupts back to back at the
* end - - this helps minimize the effects of any jitter . Adjust our
* clocking period size so that the last period is at least a fourth
* of a full period .
*
* This is all moot if we don ' t need to use virtual periods .
*/
vperiod = runtime - > period_size + 10 ;
if ( vperiod > period_size ) {
u16 tail = vperiod % period_size ;
u16 quarter_period = period_size / 4 ;
if ( tail & & tail < quarter_period ) {
u16 loops = vperiod / period_size ;
tail = quarter_period - tail ;
tail + = loops - 1 ;
tail / = loops ;
period_size - = tail ;
}
sso = period_size - 1 ;
} else {
/* The initial period will fit inside the buffer, so we
* don ' t need to use virtual periods - - disable them .
*/
period_size = runtime - > period_size ;
sso = vperiod - 1 ;
vperiod = 0 ;
}
/* The interrupt handler implements the timing syncronization, so
* setup its state .
*/
timing - > flags | = VOICE_SYNC_TIMING ;
timing - > sync_base = voice - > ctrl_base ;
timing - > sync_cso = runtime - > period_size - 1 ;
timing - > sync_period_size = runtime - > period_size ;
timing - > sync_buffer_size = runtime - > buffer_size ;
timing - > period_size = period_size ;
timing - > buffer_size = buffer_size ;
timing - > sso = sso ;
timing - > vperiod = vperiod ;
/* Using unsigned samples with the all-zero silence buffer
* forces the output to the lower rail , killing playback .
* So ignore unsigned vs signed - - it doesn ' t change the timing .
*/
format = 0 ;
if ( snd_pcm_format_width ( runtime - > format ) = = 8 )
format = SIS_CAPTURE_DMA_FORMAT_8BIT ;
if ( runtime - > channels = = 1 )
format | = SIS_CAPTURE_DMA_FORMAT_MONO ;
control = timing - > buffer_size - 1 ;
control | = SIS_PLAY_DMA_LOOP | SIS_PLAY_DMA_INTR_AT_SSO ;
sso_eso = timing - > buffer_size - 1 ;
sso_eso | = timing - > sso < < 16 ;
delta = sis_rate_to_delta ( runtime - > rate ) ;
/* We've done the math, now configure the channel.
*/
writel ( format , play_base + SIS_PLAY_DMA_FORMAT_CSO ) ;
writel ( sis - > silence_dma_addr , play_base + SIS_PLAY_DMA_BASE ) ;
writel ( control , play_base + SIS_PLAY_DMA_CONTROL ) ;
writel ( sso_eso , play_base + SIS_PLAY_DMA_SSO_ESO ) ;
for ( reg = 0 ; reg < SIS_WAVE_SIZE ; reg + = 4 )
writel ( 0 , wave_base + reg ) ;
writel ( SIS_WAVE_GENERAL_WAVE_VOLUME , wave_base + SIS_WAVE_GENERAL ) ;
writel ( delta < < 16 , wave_base + SIS_WAVE_GENERAL_ARTICULATION ) ;
writel ( SIS_WAVE_CHANNEL_CONTROL_FIRST_SAMPLE |
SIS_WAVE_CHANNEL_CONTROL_AMP_ENABLE |
SIS_WAVE_CHANNEL_CONTROL_INTERPOLATE_ENABLE ,
wave_base + SIS_WAVE_CHANNEL_CONTROL ) ;
}
static int sis_pcm_capture_prepare ( struct snd_pcm_substream * substream )
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct voice * voice = runtime - > private_data ;
void __iomem * rec_base = voice - > ctrl_base ;
u32 format , dma_addr , control ;
u16 leo ;
/* We rely on the PCM core to ensure that the parameters for this
* substream do not change on us while we ' re programming the HW .
*/
format = 0 ;
if ( snd_pcm_format_width ( runtime - > format ) = = 8 )
format = SIS_CAPTURE_DMA_FORMAT_8BIT ;
if ( ! snd_pcm_format_signed ( runtime - > format ) )
format | = SIS_CAPTURE_DMA_FORMAT_UNSIGNED ;
if ( runtime - > channels = = 1 )
format | = SIS_CAPTURE_DMA_FORMAT_MONO ;
dma_addr = runtime - > dma_addr ;
leo = runtime - > buffer_size - 1 ;
control = leo | SIS_CAPTURE_DMA_LOOP ;
/* If we've got more than two periods per buffer, then we have
* use a timing voice to clock out the periods . Otherwise , we can
* use the capture channel ' s interrupts .
*/
if ( voice - > timing ) {
sis_prepare_timing_voice ( voice , substream ) ;
} else {
control | = SIS_CAPTURE_DMA_INTR_AT_LEO ;
if ( runtime - > period_size ! = runtime - > buffer_size )
control | = SIS_CAPTURE_DMA_INTR_AT_MLP ;
}
writel ( format , rec_base + SIS_CAPTURE_DMA_FORMAT_CSO ) ;
writel ( dma_addr , rec_base + SIS_CAPTURE_DMA_BASE ) ;
writel ( control , rec_base + SIS_CAPTURE_DMA_CONTROL ) ;
/* Force the writes to post. */
readl ( rec_base ) ;
return 0 ;
}
static struct snd_pcm_ops sis_playback_ops = {
. open = sis_playback_open ,
. close = sis_substream_close ,
. ioctl = snd_pcm_lib_ioctl ,
. hw_params = sis_playback_hw_params ,
. hw_free = sis_hw_free ,
. prepare = sis_pcm_playback_prepare ,
. trigger = sis_pcm_trigger ,
. pointer = sis_pcm_pointer ,
} ;
static struct snd_pcm_ops sis_capture_ops = {
. open = sis_capture_open ,
. close = sis_substream_close ,
. ioctl = snd_pcm_lib_ioctl ,
. hw_params = sis_capture_hw_params ,
. hw_free = sis_hw_free ,
. prepare = sis_pcm_capture_prepare ,
. trigger = sis_pcm_trigger ,
. pointer = sis_pcm_pointer ,
} ;
static int __devinit sis_pcm_create ( struct sis7019 * sis )
{
struct snd_pcm * pcm ;
int rc ;
/* We have 64 voices, and the driver currently records from
* only one channel , though that could change in the future .
*/
rc = snd_pcm_new ( sis - > card , " SiS7019 " , 0 , 64 , 1 , & pcm ) ;
if ( rc )
return rc ;
pcm - > private_data = sis ;
strcpy ( pcm - > name , " SiS7019 " ) ;
sis - > pcm = pcm ;
snd_pcm_set_ops ( pcm , SNDRV_PCM_STREAM_PLAYBACK , & sis_playback_ops ) ;
snd_pcm_set_ops ( pcm , SNDRV_PCM_STREAM_CAPTURE , & sis_capture_ops ) ;
/* Try to preallocate some memory, but it's not the end of the
* world if this fails .
*/
snd_pcm_lib_preallocate_pages_for_all ( pcm , SNDRV_DMA_TYPE_DEV ,
snd_dma_pci_data ( sis - > pci ) , 64 * 1024 , 128 * 1024 ) ;
return 0 ;
}
static unsigned short sis_ac97_rw ( struct sis7019 * sis , int codec , u32 cmd )
{
unsigned long io = sis - > ioport ;
unsigned short val = 0xffff ;
u16 status ;
u16 rdy ;
int count ;
2008-04-22 02:25:51 +04:00
static const u16 codec_ready [ 3 ] = {
2007-12-14 16:40:23 +03:00
SIS_AC97_STATUS_CODEC_READY ,
SIS_AC97_STATUS_CODEC2_READY ,
SIS_AC97_STATUS_CODEC3_READY ,
} ;
rdy = codec_ready [ codec ] ;
/* Get the AC97 semaphore -- software first, so we don't spin
* pounding out IO reads on the hardware semaphore . . .
*/
mutex_lock ( & sis - > ac97_mutex ) ;
count = 0xffff ;
while ( ( inw ( io + SIS_AC97_SEMA ) & SIS_AC97_SEMA_BUSY ) & & - - count )
udelay ( 1 ) ;
if ( ! count )
goto timeout ;
/* ... and wait for any outstanding commands to complete ...
*/
count = 0xffff ;
do {
status = inw ( io + SIS_AC97_STATUS ) ;
if ( ( status & rdy ) & & ! ( status & SIS_AC97_STATUS_BUSY ) )
break ;
udelay ( 1 ) ;
} while ( - - count ) ;
if ( ! count )
goto timeout_sema ;
/* ... before sending our command and waiting for it to finish ...
*/
outl ( cmd , io + SIS_AC97_CMD ) ;
udelay ( 10 ) ;
count = 0xffff ;
while ( ( inw ( io + SIS_AC97_STATUS ) & SIS_AC97_STATUS_BUSY ) & & - - count )
udelay ( 1 ) ;
/* ... and reading the results (if any).
*/
val = inl ( io + SIS_AC97_CMD ) > > 16 ;
timeout_sema :
outl ( SIS_AC97_SEMA_RELEASE , io + SIS_AC97_SEMA ) ;
timeout :
mutex_unlock ( & sis - > ac97_mutex ) ;
if ( ! count ) {
printk ( KERN_ERR " sis7019: ac97 codec %d timeout cmd 0x%08x \n " ,
codec , cmd ) ;
}
return val ;
}
static void sis_ac97_write ( struct snd_ac97 * ac97 , unsigned short reg ,
unsigned short val )
{
2008-04-22 02:25:51 +04:00
static const u32 cmd [ 3 ] = {
2007-12-14 16:40:23 +03:00
SIS_AC97_CMD_CODEC_WRITE ,
SIS_AC97_CMD_CODEC2_WRITE ,
SIS_AC97_CMD_CODEC3_WRITE ,
} ;
sis_ac97_rw ( ac97 - > private_data , ac97 - > num ,
( val < < 16 ) | ( reg < < 8 ) | cmd [ ac97 - > num ] ) ;
}
static unsigned short sis_ac97_read ( struct snd_ac97 * ac97 , unsigned short reg )
{
2008-04-22 02:25:51 +04:00
static const u32 cmd [ 3 ] = {
2007-12-14 16:40:23 +03:00
SIS_AC97_CMD_CODEC_READ ,
SIS_AC97_CMD_CODEC2_READ ,
SIS_AC97_CMD_CODEC3_READ ,
} ;
return sis_ac97_rw ( ac97 - > private_data , ac97 - > num ,
( reg < < 8 ) | cmd [ ac97 - > num ] ) ;
}
static int __devinit sis_mixer_create ( struct sis7019 * sis )
{
struct snd_ac97_bus * bus ;
struct snd_ac97_template ac97 ;
static struct snd_ac97_bus_ops ops = {
. write = sis_ac97_write ,
. read = sis_ac97_read ,
} ;
int rc ;
memset ( & ac97 , 0 , sizeof ( ac97 ) ) ;
ac97 . private_data = sis ;
rc = snd_ac97_bus ( sis - > card , 0 , & ops , NULL , & bus ) ;
if ( ! rc & & sis - > codecs_present & SIS_PRIMARY_CODEC_PRESENT )
rc = snd_ac97_mixer ( bus , & ac97 , & sis - > ac97 [ 0 ] ) ;
ac97 . num = 1 ;
if ( ! rc & & ( sis - > codecs_present & SIS_SECONDARY_CODEC_PRESENT ) )
rc = snd_ac97_mixer ( bus , & ac97 , & sis - > ac97 [ 1 ] ) ;
ac97 . num = 2 ;
if ( ! rc & & ( sis - > codecs_present & SIS_TERTIARY_CODEC_PRESENT ) )
rc = snd_ac97_mixer ( bus , & ac97 , & sis - > ac97 [ 2 ] ) ;
/* If we return an error here, then snd_card_free() should
* free up any ac97 codecs that got created , as well as the bus .
*/
return rc ;
}
static void sis_free_suspend ( struct sis7019 * sis )
{
int i ;
for ( i = 0 ; i < SIS_SUSPEND_PAGES ; i + + )
kfree ( sis - > suspend_state [ i ] ) ;
}
static int sis_chip_free ( struct sis7019 * sis )
{
/* Reset the chip, and disable all interrputs.
*/
outl ( SIS_GCR_SOFTWARE_RESET , sis - > ioport + SIS_GCR ) ;
udelay ( 10 ) ;
outl ( 0 , sis - > ioport + SIS_GCR ) ;
outl ( 0 , sis - > ioport + SIS_GIER ) ;
/* Now, free everything we allocated.
*/
if ( sis - > irq > = 0 )
free_irq ( sis - > irq , sis ) ;
if ( sis - > ioaddr )
iounmap ( sis - > ioaddr ) ;
pci_release_regions ( sis - > pci ) ;
pci_disable_device ( sis - > pci ) ;
sis_free_suspend ( sis ) ;
return 0 ;
}
static int sis_dev_free ( struct snd_device * dev )
{
struct sis7019 * sis = dev - > device_data ;
return sis_chip_free ( sis ) ;
}
static int sis_chip_init ( struct sis7019 * sis )
{
unsigned long io = sis - > ioport ;
void __iomem * ioaddr = sis - > ioaddr ;
u16 status ;
int count ;
int i ;
/* Reset the audio controller
*/
outl ( SIS_GCR_SOFTWARE_RESET , io + SIS_GCR ) ;
udelay ( 10 ) ;
outl ( 0 , io + SIS_GCR ) ;
/* Get the AC-link semaphore, and reset the codecs
*/
count = 0xffff ;
while ( ( inw ( io + SIS_AC97_SEMA ) & SIS_AC97_SEMA_BUSY ) & & - - count )
udelay ( 1 ) ;
if ( ! count )
return - EIO ;
outl ( SIS_AC97_CMD_CODEC_COLD_RESET , io + SIS_AC97_CMD ) ;
udelay ( 10 ) ;
count = 0xffff ;
while ( ( inw ( io + SIS_AC97_STATUS ) & SIS_AC97_STATUS_BUSY ) & & - - count )
udelay ( 1 ) ;
/* Now that we've finished the reset, find out what's attached.
*/
status = inl ( io + SIS_AC97_STATUS ) ;
if ( status & SIS_AC97_STATUS_CODEC_READY )
sis - > codecs_present | = SIS_PRIMARY_CODEC_PRESENT ;
if ( status & SIS_AC97_STATUS_CODEC2_READY )
sis - > codecs_present | = SIS_SECONDARY_CODEC_PRESENT ;
if ( status & SIS_AC97_STATUS_CODEC3_READY )
sis - > codecs_present | = SIS_TERTIARY_CODEC_PRESENT ;
/* All done, let go of the semaphore, and check for errors
*/
outl ( SIS_AC97_SEMA_RELEASE , io + SIS_AC97_SEMA ) ;
if ( ! sis - > codecs_present | | ! count )
return - EIO ;
/* Let the hardware know that the audio driver is alive,
* and enable PCM slots on the AC - link for L / R playback ( 3 & 4 ) and
* record channels . We ' re going to want to use Variable Rate Audio
* for recording , to avoid needlessly resampling from 48 kHZ .
*/
outl ( SIS_AC97_CONF_AUDIO_ALIVE , io + SIS_AC97_CONF ) ;
outl ( SIS_AC97_CONF_AUDIO_ALIVE | SIS_AC97_CONF_PCM_LR_ENABLE |
SIS_AC97_CONF_PCM_CAP_MIC_ENABLE |
SIS_AC97_CONF_PCM_CAP_LR_ENABLE |
SIS_AC97_CONF_CODEC_VRA_ENABLE , io + SIS_AC97_CONF ) ;
/* All AC97 PCM slots should be sourced from sub-mixer 0.
*/
outl ( 0 , io + SIS_AC97_PSR ) ;
/* There is only one valid DMA setup for a PCI environment.
*/
outl ( SIS_DMA_CSR_PCI_SETTINGS , io + SIS_DMA_CSR ) ;
/* Reset the syncronization groups for all of the channels
* to be asyncronous . If we start doing SPDIF or 5.1 sound , etc .
* we ' ll need to change how we handle these . Until then , we just
* assign sub - mixer 0 to all playback channels , and avoid any
* attenuation on the audio .
*/
outl ( 0 , io + SIS_PLAY_SYNC_GROUP_A ) ;
outl ( 0 , io + SIS_PLAY_SYNC_GROUP_B ) ;
outl ( 0 , io + SIS_PLAY_SYNC_GROUP_C ) ;
outl ( 0 , io + SIS_PLAY_SYNC_GROUP_D ) ;
outl ( 0 , io + SIS_MIXER_SYNC_GROUP ) ;
for ( i = 0 ; i < 64 ; i + + ) {
writel ( i , SIS_MIXER_START_ADDR ( ioaddr , i ) ) ;
writel ( SIS_MIXER_RIGHT_NO_ATTEN | SIS_MIXER_LEFT_NO_ATTEN |
SIS_MIXER_DEST_0 , SIS_MIXER_ADDR ( ioaddr , i ) ) ;
}
/* Don't attenuate any audio set for the wave amplifier.
*
* FIXME : Maximum attenuation is set for the music amp , which will
* need to change if we start using the synth engine .
*/
outl ( 0xffff0000 , io + SIS_WEVCR ) ;
/* Ensure that the wave engine is in normal operating mode.
*/
outl ( 0 , io + SIS_WECCR ) ;
/* Go ahead and enable the DMA interrupts. They won't go live
* until we start a channel .
*/
outl ( SIS_GIER_AUDIO_PLAY_DMA_IRQ_ENABLE |
SIS_GIER_AUDIO_RECORD_DMA_IRQ_ENABLE , io + SIS_GIER ) ;
return 0 ;
}
# ifdef CONFIG_PM
static int sis_suspend ( struct pci_dev * pci , pm_message_t state )
{
struct snd_card * card = pci_get_drvdata ( pci ) ;
struct sis7019 * sis = card - > private_data ;
void __iomem * ioaddr = sis - > ioaddr ;
int i ;
snd_power_change_state ( card , SNDRV_CTL_POWER_D3hot ) ;
snd_pcm_suspend_all ( sis - > pcm ) ;
if ( sis - > codecs_present & SIS_PRIMARY_CODEC_PRESENT )
snd_ac97_suspend ( sis - > ac97 [ 0 ] ) ;
if ( sis - > codecs_present & SIS_SECONDARY_CODEC_PRESENT )
snd_ac97_suspend ( sis - > ac97 [ 1 ] ) ;
if ( sis - > codecs_present & SIS_TERTIARY_CODEC_PRESENT )
snd_ac97_suspend ( sis - > ac97 [ 2 ] ) ;
/* snd_pcm_suspend_all() stopped all channels, so we're quiescent.
*/
if ( sis - > irq > = 0 ) {
free_irq ( sis - > irq , sis ) ;
sis - > irq = - 1 ;
}
/* Save the internal state away
*/
for ( i = 0 ; i < 4 ; i + + ) {
memcpy_fromio ( sis - > suspend_state [ i ] , ioaddr , 4096 ) ;
ioaddr + = 4096 ;
}
pci_disable_device ( pci ) ;
pci_save_state ( pci ) ;
pci_set_power_state ( pci , pci_choose_state ( pci , state ) ) ;
return 0 ;
}
static int sis_resume ( struct pci_dev * pci )
{
struct snd_card * card = pci_get_drvdata ( pci ) ;
struct sis7019 * sis = card - > private_data ;
void __iomem * ioaddr = sis - > ioaddr ;
int i ;
pci_set_power_state ( pci , PCI_D0 ) ;
pci_restore_state ( pci ) ;
if ( pci_enable_device ( pci ) < 0 ) {
printk ( KERN_ERR " sis7019: unable to re-enable device \n " ) ;
goto error ;
}
if ( sis_chip_init ( sis ) ) {
printk ( KERN_ERR " sis7019: unable to re-init controller \n " ) ;
goto error ;
}
if ( request_irq ( pci - > irq , sis_interrupt , IRQF_DISABLED | IRQF_SHARED ,
card - > shortname , sis ) ) {
printk ( KERN_ERR " sis7019: unable to regain IRQ %d \n " , pci - > irq ) ;
goto error ;
}
/* Restore saved state, then clear out the page we use for the
* silence buffer .
*/
for ( i = 0 ; i < 4 ; i + + ) {
memcpy_toio ( ioaddr , sis - > suspend_state [ i ] , 4096 ) ;
ioaddr + = 4096 ;
}
memset ( sis - > suspend_state [ 0 ] , 0 , 4096 ) ;
sis - > irq = pci - > irq ;
pci_set_master ( pci ) ;
if ( sis - > codecs_present & SIS_PRIMARY_CODEC_PRESENT )
snd_ac97_resume ( sis - > ac97 [ 0 ] ) ;
if ( sis - > codecs_present & SIS_SECONDARY_CODEC_PRESENT )
snd_ac97_resume ( sis - > ac97 [ 1 ] ) ;
if ( sis - > codecs_present & SIS_TERTIARY_CODEC_PRESENT )
snd_ac97_resume ( sis - > ac97 [ 2 ] ) ;
snd_power_change_state ( card , SNDRV_CTL_POWER_D0 ) ;
return 0 ;
error :
snd_card_disconnect ( card ) ;
return - EIO ;
}
# endif /* CONFIG_PM */
static int sis_alloc_suspend ( struct sis7019 * sis )
{
int i ;
/* We need 16K to store the internal wave engine state during a
* suspend , but we don ' t need it to be contiguous , so play nice
* with the memory system . We ' ll also use this area for a silence
* buffer .
*/
for ( i = 0 ; i < SIS_SUSPEND_PAGES ; i + + ) {
sis - > suspend_state [ i ] = kmalloc ( 4096 , GFP_KERNEL ) ;
if ( ! sis - > suspend_state [ i ] )
return - ENOMEM ;
}
memset ( sis - > suspend_state [ 0 ] , 0 , 4096 ) ;
return 0 ;
}
static int __devinit sis_chip_create ( struct snd_card * card ,
struct pci_dev * pci )
{
struct sis7019 * sis = card - > private_data ;
struct voice * voice ;
static struct snd_device_ops ops = {
. dev_free = sis_dev_free ,
} ;
int rc ;
int i ;
rc = pci_enable_device ( pci ) ;
if ( rc )
goto error_out ;
2009-04-07 06:01:17 +04:00
if ( pci_set_dma_mask ( pci , DMA_BIT_MASK ( 30 ) ) < 0 ) {
2007-12-14 16:40:23 +03:00
printk ( KERN_ERR " sis7019: architecture does not support "
" 30-bit PCI busmaster DMA " ) ;
goto error_out_enabled ;
}
memset ( sis , 0 , sizeof ( * sis ) ) ;
mutex_init ( & sis - > ac97_mutex ) ;
spin_lock_init ( & sis - > voice_lock ) ;
sis - > card = card ;
sis - > pci = pci ;
sis - > irq = - 1 ;
sis - > ioport = pci_resource_start ( pci , 0 ) ;
rc = pci_request_regions ( pci , " SiS7019 " ) ;
if ( rc ) {
printk ( KERN_ERR " sis7019: unable request regions \n " ) ;
goto error_out_enabled ;
}
rc = - EIO ;
sis - > ioaddr = ioremap_nocache ( pci_resource_start ( pci , 1 ) , 0x4000 ) ;
if ( ! sis - > ioaddr ) {
printk ( KERN_ERR " sis7019: unable to remap MMIO, aborting \n " ) ;
goto error_out_cleanup ;
}
rc = sis_alloc_suspend ( sis ) ;
if ( rc < 0 ) {
printk ( KERN_ERR " sis7019: unable to allocate state storage \n " ) ;
goto error_out_cleanup ;
}
rc = sis_chip_init ( sis ) ;
if ( rc )
goto error_out_cleanup ;
if ( request_irq ( pci - > irq , sis_interrupt , IRQF_DISABLED | IRQF_SHARED ,
card - > shortname , sis ) ) {
printk ( KERN_ERR " unable to allocate irq %d \n " , sis - > irq ) ;
goto error_out_cleanup ;
}
sis - > irq = pci - > irq ;
pci_set_master ( pci ) ;
for ( i = 0 ; i < 64 ; i + + ) {
voice = & sis - > voices [ i ] ;
voice - > num = i ;
voice - > ctrl_base = SIS_PLAY_DMA_ADDR ( sis - > ioaddr , i ) ;
voice - > wave_base = SIS_WAVE_ADDR ( sis - > ioaddr , i ) ;
}
voice = & sis - > capture_voice ;
voice - > flags = VOICE_CAPTURE ;
voice - > num = SIS_CAPTURE_CHAN_AC97_PCM_IN ;
voice - > ctrl_base = SIS_CAPTURE_DMA_ADDR ( sis - > ioaddr , voice - > num ) ;
rc = snd_device_new ( card , SNDRV_DEV_LOWLEVEL , sis , & ops ) ;
if ( rc )
goto error_out_cleanup ;
snd_card_set_dev ( card , & pci - > dev ) ;
return 0 ;
error_out_cleanup :
sis_chip_free ( sis ) ;
error_out_enabled :
pci_disable_device ( pci ) ;
error_out :
return rc ;
}
static int __devinit snd_sis7019_probe ( struct pci_dev * pci ,
const struct pci_device_id * pci_id )
{
struct snd_card * card ;
struct sis7019 * sis ;
int rc ;
rc = - ENOENT ;
if ( ! enable )
goto error_out ;
2008-12-28 18:44:30 +03:00
rc = snd_card_create ( index , id , THIS_MODULE , sizeof ( * sis ) , & card ) ;
if ( rc < 0 )
2007-12-14 16:40:23 +03:00
goto error_out ;
strcpy ( card - > driver , " SiS7019 " ) ;
strcpy ( card - > shortname , " SiS7019 " ) ;
rc = sis_chip_create ( card , pci ) ;
if ( rc )
goto card_error_out ;
sis = card - > private_data ;
rc = sis_mixer_create ( sis ) ;
if ( rc )
goto card_error_out ;
rc = sis_pcm_create ( sis ) ;
if ( rc )
goto card_error_out ;
snprintf ( card - > longname , sizeof ( card - > longname ) ,
" %s Audio Accelerator with %s at 0x%lx, irq %d " ,
card - > shortname , snd_ac97_get_short_name ( sis - > ac97 [ 0 ] ) ,
sis - > ioport , sis - > irq ) ;
rc = snd_card_register ( card ) ;
if ( rc )
goto card_error_out ;
pci_set_drvdata ( pci , card ) ;
return 0 ;
card_error_out :
snd_card_free ( card ) ;
error_out :
return rc ;
}
static void __devexit snd_sis7019_remove ( struct pci_dev * pci )
{
snd_card_free ( pci_get_drvdata ( pci ) ) ;
pci_set_drvdata ( pci , NULL ) ;
}
static struct pci_driver sis7019_driver = {
. name = " SiS7019 " ,
. id_table = snd_sis7019_ids ,
. probe = snd_sis7019_probe ,
. remove = __devexit_p ( snd_sis7019_remove ) ,
# ifdef CONFIG_PM
. suspend = sis_suspend ,
. resume = sis_resume ,
# endif
} ;
static int __init sis7019_init ( void )
{
return pci_register_driver ( & sis7019_driver ) ;
}
static void __exit sis7019_exit ( void )
{
pci_unregister_driver ( & sis7019_driver ) ;
}
module_init ( sis7019_init ) ;
module_exit ( sis7019_exit ) ;