2005-04-17 02:20:36 +04:00
/*
*
* AD1816 lowlevel sound driver for Linux 2.6 .0 and above
*
* Copyright ( C ) 1998 - 2003 by Thorsten Knabe < linux @ thorsten - knabe . de >
*
* Based on the CS4232 / AD1848 driver Copyright ( C ) by Hannu Savolainen 1993 - 1996
*
*
* version : 1.5
* status : beta
* date : 2003 / 07 / 15
*
* Changes :
* Oleg Drokin : Some cleanup of load / unload functions . 1998 / 11 / 24
*
* Thorsten Knabe : attach and unload rewritten ,
* some argument checks added 1998 / 11 / 30
*
* Thorsten Knabe : Buggy isa bridge workaround added 1999 / 01 / 16
*
* David Moews / Thorsten Knabe : Introduced options
* parameter . Added slightly modified patch from
* David Moews to disable dsp audio sources by setting
* bit 0 of options parameter . This seems to be
* required by some Aztech / Newcom SC - 16 cards . 1999 / 04 / 18
*
* Christoph Hellwig : Adapted to module_init / module_exit . 2000 / 03 / 03
*
* Christoph Hellwig : Added isapnp support 2000 / 03 / 15
*
* Arnaldo Carvalho de Melo : get rid of check_region 2001 / 10 / 07
*
* Thorsten Knabe : Compiling with CONFIG_PNP enabled
* works again . It is now possible to use more than one
* AD1816 sound card . Sample rate now may be changed during
* playback / capture . printk ( ) uses log levels everywhere .
* SMP fixes . DMA handling fixes .
* Other minor code cleanup . 2003 / 07 / 15
*
*/
# include <linux/config.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/isapnp.h>
# include <linux/stddef.h>
# include <linux/spinlock.h>
# include "sound_config.h"
# define DEBUGNOISE(x)
# define CHECK_FOR_POWER { int timeout=100; \
while ( timeout > 0 & & ( inb ( devc - > base ) & 0x80 ) ! = 0x80 ) { \
timeout - - ; \
} \
if ( timeout = = 0 ) { \
printk ( KERN_WARNING " ad1816: Check for power failed in %s line: %d \n " , __FILE__ , __LINE__ ) ; \
} \
}
/* structure to hold device specific information */
typedef struct
{
int base ; /* set in attach */
int irq ;
int dma_playback ;
int dma_capture ;
int opened ; /* open */
int speed ;
int channels ;
int audio_format ;
int audio_mode ;
int recmask ; /* setup */
unsigned char format_bits ;
int supported_devices ;
int supported_rec_devices ;
unsigned short levels [ SOUND_MIXER_NRDEVICES ] ;
/* misc */
struct pnp_dev * pnpdev ; /* configured via pnp */
int dev_no ; /* this is the # in audio_devs and NOT
in ad1816_info */
spinlock_t lock ;
} ad1816_info ;
static int nr_ad1816_devs ;
static int ad1816_clockfreq = 33000 ;
static int options ;
/* supported audio formats */
static int ad_format_mask =
AFMT_U8 | AFMT_S16_LE | AFMT_S16_BE | AFMT_MU_LAW | AFMT_A_LAW ;
/* array of device info structures */
static ad1816_info dev_info [ MAX_AUDIO_DEV ] ;
/* ------------------------------------------------------------------- */
/* functions for easier access to inderect registers */
static int ad_read ( ad1816_info * devc , int reg )
{
int result ;
CHECK_FOR_POWER ;
outb ( ( unsigned char ) ( reg & 0x3f ) , devc - > base + 0 ) ;
result = inb ( devc - > base + 2 ) ;
result + = inb ( devc - > base + 3 ) < < 8 ;
return ( result ) ;
}
static void ad_write ( ad1816_info * devc , int reg , int data )
{
CHECK_FOR_POWER ;
outb ( ( unsigned char ) ( reg & 0xff ) , devc - > base + 0 ) ;
outb ( ( unsigned char ) ( data & 0xff ) , devc - > base + 2 ) ;
outb ( ( unsigned char ) ( ( data > > 8 ) & 0xff ) , devc - > base + 3 ) ;
}
/* ------------------------------------------------------------------- */
/* function interface required by struct audio_driver */
static void ad1816_halt_input ( int dev )
{
unsigned long flags ;
ad1816_info * devc = ( ad1816_info * ) audio_devs [ dev ] - > devc ;
unsigned char buffer ;
DEBUGNOISE ( printk ( KERN_DEBUG " ad1816: halt_input called \n " ) ) ;
spin_lock_irqsave ( & devc - > lock , flags ) ;
if ( ! isa_dma_bridge_buggy ) {
disable_dma ( audio_devs [ dev ] - > dmap_in - > dma ) ;
}
buffer = inb ( devc - > base + 9 ) ;
if ( buffer & 0x01 ) {
/* disable capture */
outb ( buffer & ~ 0x01 , devc - > base + 9 ) ;
}
if ( ! isa_dma_bridge_buggy ) {
enable_dma ( audio_devs [ dev ] - > dmap_in - > dma ) ;
}
/* Clear interrupt status */
outb ( ~ 0x40 , devc - > base + 1 ) ;
devc - > audio_mode & = ~ PCM_ENABLE_INPUT ;
spin_unlock_irqrestore ( & devc - > lock , flags ) ;
}
static void ad1816_halt_output ( int dev )
{
unsigned long flags ;
ad1816_info * devc = ( ad1816_info * ) audio_devs [ dev ] - > devc ;
unsigned char buffer ;
DEBUGNOISE ( printk ( KERN_DEBUG " ad1816: halt_output called! \n " ) ) ;
spin_lock_irqsave ( & devc - > lock , flags ) ;
/* Mute pcm output */
ad_write ( devc , 4 , ad_read ( devc , 4 ) | 0x8080 ) ;
if ( ! isa_dma_bridge_buggy ) {
disable_dma ( audio_devs [ dev ] - > dmap_out - > dma ) ;
}
buffer = inb ( devc - > base + 8 ) ;
if ( buffer & 0x01 ) {
/* disable capture */
outb ( buffer & ~ 0x01 , devc - > base + 8 ) ;
}
if ( ! isa_dma_bridge_buggy ) {
enable_dma ( audio_devs [ dev ] - > dmap_out - > dma ) ;
}
/* Clear interrupt status */
outb ( ( unsigned char ) ~ 0x80 , devc - > base + 1 ) ;
devc - > audio_mode & = ~ PCM_ENABLE_OUTPUT ;
spin_unlock_irqrestore ( & devc - > lock , flags ) ;
}
static void ad1816_output_block ( int dev , unsigned long buf ,
int count , int intrflag )
{
unsigned long flags ;
unsigned long cnt ;
ad1816_info * devc = ( ad1816_info * ) audio_devs [ dev ] - > devc ;
DEBUGNOISE ( printk ( KERN_DEBUG " ad1816: output_block called buf=%ld count=%d flags=%d \n " , buf , count , intrflag ) ) ;
cnt = count / 4 - 1 ;
spin_lock_irqsave ( & devc - > lock , flags ) ;
/* set transfer count */
ad_write ( devc , 8 , cnt & 0xffff ) ;
devc - > audio_mode | = PCM_ENABLE_OUTPUT ;
spin_unlock_irqrestore ( & devc - > lock , flags ) ;
}
static void ad1816_start_input ( int dev , unsigned long buf , int count ,
int intrflag )
{
unsigned long flags ;
unsigned long cnt ;
ad1816_info * devc = ( ad1816_info * ) audio_devs [ dev ] - > devc ;
DEBUGNOISE ( printk ( KERN_DEBUG " ad1816: start_input called buf=%ld count=%d flags=%d \n " , buf , count , intrflag ) ) ;
cnt = count / 4 - 1 ;
spin_lock_irqsave ( & devc - > lock , flags ) ;
/* set transfer count */
ad_write ( devc , 10 , cnt & 0xffff ) ;
devc - > audio_mode | = PCM_ENABLE_INPUT ;
spin_unlock_irqrestore ( & devc - > lock , flags ) ;
}
static int ad1816_prepare_for_input ( int dev , int bsize , int bcount )
{
unsigned long flags ;
unsigned int freq ;
ad1816_info * devc = ( ad1816_info * ) audio_devs [ dev ] - > devc ;
unsigned char fmt_bits ;
DEBUGNOISE ( printk ( KERN_DEBUG " ad1816: prepare_for_input called: bsize=%d bcount=%d \n " , bsize , bcount ) ) ;
spin_lock_irqsave ( & devc - > lock , flags ) ;
fmt_bits = ( devc - > format_bits & 0x7 ) < < 3 ;
/* set mono/stereo mode */
if ( devc - > channels > 1 ) {
fmt_bits | = 0x4 ;
}
/* set Mono/Stereo in playback/capture register */
outb ( ( inb ( devc - > base + 8 ) & ~ 0x3C ) | fmt_bits , devc - > base + 8 ) ;
outb ( ( inb ( devc - > base + 9 ) & ~ 0x3C ) | fmt_bits , devc - > base + 9 ) ;
freq = ( ( unsigned int ) devc - > speed * 33000 ) / ad1816_clockfreq ;
/* write playback/capture speeds */
ad_write ( devc , 2 , freq & 0xffff ) ;
ad_write ( devc , 3 , freq & 0xffff ) ;
spin_unlock_irqrestore ( & devc - > lock , flags ) ;
ad1816_halt_input ( dev ) ;
return 0 ;
}
static int ad1816_prepare_for_output ( int dev , int bsize , int bcount )
{
unsigned long flags ;
unsigned int freq ;
ad1816_info * devc = ( ad1816_info * ) audio_devs [ dev ] - > devc ;
unsigned char fmt_bits ;
DEBUGNOISE ( printk ( KERN_DEBUG " ad1816: prepare_for_output called: bsize=%d bcount=%d \n " , bsize , bcount ) ) ;
spin_lock_irqsave ( & devc - > lock , flags ) ;
fmt_bits = ( devc - > format_bits & 0x7 ) < < 3 ;
/* set mono/stereo mode */
if ( devc - > channels > 1 ) {
fmt_bits | = 0x4 ;
}
/* write format bits to playback/capture registers */
outb ( ( inb ( devc - > base + 8 ) & ~ 0x3C ) | fmt_bits , devc - > base + 8 ) ;
outb ( ( inb ( devc - > base + 9 ) & ~ 0x3C ) | fmt_bits , devc - > base + 9 ) ;
freq = ( ( unsigned int ) devc - > speed * 33000 ) / ad1816_clockfreq ;
/* write playback/capture speeds */
ad_write ( devc , 2 , freq & 0xffff ) ;
ad_write ( devc , 3 , freq & 0xffff ) ;
spin_unlock_irqrestore ( & devc - > lock , flags ) ;
ad1816_halt_output ( dev ) ;
return 0 ;
}
static void ad1816_trigger ( int dev , int state )
{
unsigned long flags ;
ad1816_info * devc = ( ad1816_info * ) audio_devs [ dev ] - > devc ;
DEBUGNOISE ( printk ( KERN_DEBUG " ad1816: trigger called! (devc=%d,devc->base=%d \n " , devc , devc - > base ) ) ;
/* mode may have changed */
spin_lock_irqsave ( & devc - > lock , flags ) ;
/* mask out modes not specified on open call */
state & = devc - > audio_mode ;
/* setup soundchip to new io-mode */
if ( state & PCM_ENABLE_INPUT ) {
/* enable capture */
outb ( inb ( devc - > base + 9 ) | 0x01 , devc - > base + 9 ) ;
} else {
/* disable capture */
outb ( inb ( devc - > base + 9 ) & ~ 0x01 , devc - > base + 9 ) ;
}
if ( state & PCM_ENABLE_OUTPUT ) {
/* enable playback */
outb ( inb ( devc - > base + 8 ) | 0x01 , devc - > base + 8 ) ;
/* unmute pcm output */
ad_write ( devc , 4 , ad_read ( devc , 4 ) & ~ 0x8080 ) ;
} else {
/* mute pcm output */
ad_write ( devc , 4 , ad_read ( devc , 4 ) | 0x8080 ) ;
/* disable capture */
outb ( inb ( devc - > base + 8 ) & ~ 0x01 , devc - > base + 8 ) ;
}
spin_unlock_irqrestore ( & devc - > lock , flags ) ;
}
/* halt input & output */
static void ad1816_halt ( int dev )
{
ad1816_halt_input ( dev ) ;
ad1816_halt_output ( dev ) ;
}
static void ad1816_reset ( int dev )
{
ad1816_halt ( dev ) ;
}
/* set playback speed */
static int ad1816_set_speed ( int dev , int arg )
{
unsigned long flags ;
unsigned int freq ;
int ret ;
ad1816_info * devc = ( ad1816_info * ) audio_devs [ dev ] - > devc ;
spin_lock_irqsave ( & devc - > lock , flags ) ;
if ( arg = = 0 ) {
ret = devc - > speed ;
spin_unlock_irqrestore ( & devc - > lock , flags ) ;
return ret ;
}
/* range checking */
if ( arg < 4000 ) {
arg = 4000 ;
}
if ( arg > 55000 ) {
arg = 55000 ;
}
devc - > speed = arg ;
/* change speed during playback */
freq = ( ( unsigned int ) devc - > speed * 33000 ) / ad1816_clockfreq ;
/* write playback/capture speeds */
ad_write ( devc , 2 , freq & 0xffff ) ;
ad_write ( devc , 3 , freq & 0xffff ) ;
ret = devc - > speed ;
spin_unlock_irqrestore ( & devc - > lock , flags ) ;
return ret ;
}
static unsigned int ad1816_set_bits ( int dev , unsigned int arg )
{
unsigned long flags ;
ad1816_info * devc = ( ad1816_info * ) audio_devs [ dev ] - > devc ;
static struct format_tbl {
int format ;
unsigned char bits ;
} format2bits [ ] = {
{ 0 , 0 } ,
{ AFMT_MU_LAW , 1 } ,
{ AFMT_A_LAW , 3 } ,
{ AFMT_IMA_ADPCM , 0 } ,
{ AFMT_U8 , 0 } ,
{ AFMT_S16_LE , 2 } ,
{ AFMT_S16_BE , 6 } ,
{ AFMT_S8 , 0 } ,
{ AFMT_U16_LE , 0 } ,
{ AFMT_U16_BE , 0 }
} ;
int i , n = sizeof ( format2bits ) / sizeof ( struct format_tbl ) ;
spin_lock_irqsave ( & devc - > lock , flags ) ;
/* return current format */
if ( arg = = 0 ) {
arg = devc - > audio_format ;
spin_unlock_irqrestore ( & devc - > lock , flags ) ;
return arg ;
}
devc - > audio_format = arg ;
/* search matching format bits */
for ( i = 0 ; i < n ; i + + )
if ( format2bits [ i ] . format = = arg ) {
devc - > format_bits = format2bits [ i ] . bits ;
devc - > audio_format = arg ;
spin_unlock_irqrestore ( & devc - > lock , flags ) ;
return arg ;
}
/* Still hanging here. Something must be terribly wrong */
devc - > format_bits = 0 ;
devc - > audio_format = AFMT_U8 ;
spin_unlock_irqrestore ( & devc - > lock , flags ) ;
return ( AFMT_U8 ) ;
}
static short ad1816_set_channels ( int dev , short arg )
{
ad1816_info * devc = ( ad1816_info * ) audio_devs [ dev ] - > devc ;
if ( arg ! = 1 & & arg ! = 2 )
return devc - > channels ;
devc - > channels = arg ;
return arg ;
}
/* open device */
static int ad1816_open ( int dev , int mode )
{
ad1816_info * devc = NULL ;
unsigned long flags ;
/* is device number valid ? */
if ( dev < 0 | | dev > = num_audiodevs )
return - ( ENXIO ) ;
/* get device info of this dev */
devc = ( ad1816_info * ) audio_devs [ dev ] - > devc ;
/* make check if device already open atomic */
spin_lock_irqsave ( & devc - > lock , flags ) ;
if ( devc - > opened ) {
spin_unlock_irqrestore ( & devc - > lock , flags ) ;
return - ( EBUSY ) ;
}
/* mark device as open */
devc - > opened = 1 ;
devc - > audio_mode = 0 ;
devc - > speed = 8000 ;
devc - > audio_format = AFMT_U8 ;
devc - > channels = 1 ;
spin_unlock_irqrestore ( & devc - > lock , flags ) ;
ad1816_reset ( devc - > dev_no ) ; /* halt all pending output */
return 0 ;
}
static void ad1816_close ( int dev ) /* close device */
{
unsigned long flags ;
ad1816_info * devc = ( ad1816_info * ) audio_devs [ dev ] - > devc ;
/* halt all pending output */
ad1816_reset ( devc - > dev_no ) ;
spin_lock_irqsave ( & devc - > lock , flags ) ;
devc - > opened = 0 ;
devc - > audio_mode = 0 ;
devc - > speed = 8000 ;
devc - > audio_format = AFMT_U8 ;
devc - > format_bits = 0 ;
spin_unlock_irqrestore ( & devc - > lock , flags ) ;
}
/* ------------------------------------------------------------------- */
/* Audio driver structure */
static struct audio_driver ad1816_audio_driver =
{
. owner = THIS_MODULE ,
. open = ad1816_open ,
. close = ad1816_close ,
. output_block = ad1816_output_block ,
. start_input = ad1816_start_input ,
. prepare_for_input = ad1816_prepare_for_input ,
. prepare_for_output = ad1816_prepare_for_output ,
. halt_io = ad1816_halt ,
. halt_input = ad1816_halt_input ,
. halt_output = ad1816_halt_output ,
. trigger = ad1816_trigger ,
. set_speed = ad1816_set_speed ,
. set_bits = ad1816_set_bits ,
. set_channels = ad1816_set_channels ,
} ;
/* ------------------------------------------------------------------- */
/* Interrupt handler */
static irqreturn_t ad1816_interrupt ( int irq , void * dev_id , struct pt_regs * dummy )
{
unsigned char status ;
ad1816_info * devc = ( ad1816_info * ) dev_id ;
if ( irq < 0 | | irq > 15 ) {
printk ( KERN_WARNING " ad1816: Got bogus interrupt %d \n " , irq ) ;
return IRQ_NONE ;
}
spin_lock ( & devc - > lock ) ;
/* read interrupt register */
status = inb ( devc - > base + 1 ) ;
/* Clear all interrupt */
outb ( ~ status , devc - > base + 1 ) ;
DEBUGNOISE ( printk ( KERN_DEBUG " ad1816: Got interrupt subclass %d \n " , status ) ) ;
if ( status = = 0 ) {
DEBUGNOISE ( printk ( KERN_DEBUG " ad1816: interrupt: Got interrupt, but no source. \n " ) ) ;
spin_unlock ( & devc - > lock ) ;
return IRQ_NONE ;
}
if ( devc - > opened & & ( devc - > audio_mode & PCM_ENABLE_INPUT ) & & ( status & 64 ) )
DMAbuf_inputintr ( devc - > dev_no ) ;
if ( devc - > opened & & ( devc - > audio_mode & PCM_ENABLE_OUTPUT ) & & ( status & 128 ) )
DMAbuf_outputintr ( devc - > dev_no , 1 ) ;
spin_unlock ( & devc - > lock ) ;
return IRQ_HANDLED ;
}
/* ------------------------------------------------------------------- */
/* Mixer stuff */
struct mixer_def {
unsigned int regno : 7 ;
unsigned int polarity : 1 ; /* 0=normal, 1=reversed */
unsigned int bitpos : 4 ;
unsigned int nbits : 4 ;
} ;
static char mix_cvt [ 101 ] = {
0 , 0 , 3 , 7 , 10 , 13 , 16 , 19 , 21 , 23 , 26 , 28 , 30 , 32 , 34 , 35 , 37 , 39 , 40 , 42 ,
43 , 45 , 46 , 47 , 49 , 50 , 51 , 52 , 53 , 55 , 56 , 57 , 58 , 59 , 60 , 61 , 62 , 63 , 64 , 65 ,
65 , 66 , 67 , 68 , 69 , 70 , 70 , 71 , 72 , 73 , 73 , 74 , 75 , 75 , 76 , 77 , 77 , 78 , 79 , 79 ,
80 , 81 , 81 , 82 , 82 , 83 , 84 , 84 , 85 , 85 , 86 , 86 , 87 , 87 , 88 , 88 , 89 , 89 , 90 , 90 ,
91 , 91 , 92 , 92 , 93 , 93 , 94 , 94 , 95 , 95 , 96 , 96 , 96 , 97 , 97 , 98 , 98 , 98 , 99 , 99 ,
100
} ;
typedef struct mixer_def mixer_ent ;
/*
* Most of the mixer entries work in backwards . Setting the polarity field
* makes them to work correctly .
*
* The channel numbering used by individual soundcards is not fixed . Some
* cards have assigned different meanings for the AUX1 , AUX2 and LINE inputs .
* The current version doesn ' t try to compensate this .
*/
# define MIX_ENT(name, reg_l, pola_l, pos_l, len_l, reg_r, pola_r, pos_r, len_r) \
{ { reg_l , pola_l , pos_l , len_l } , { reg_r , pola_r , pos_r , len_r } }
2005-06-26 01:58:53 +04:00
static mixer_ent mix_devices [ SOUND_MIXER_NRDEVICES ] [ 2 ] = {
2005-04-17 02:20:36 +04:00
MIX_ENT ( SOUND_MIXER_VOLUME , 14 , 1 , 8 , 5 , 14 , 1 , 0 , 5 ) ,
MIX_ENT ( SOUND_MIXER_BASS , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ) ,
MIX_ENT ( SOUND_MIXER_TREBLE , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ) ,
MIX_ENT ( SOUND_MIXER_SYNTH , 5 , 1 , 8 , 6 , 5 , 1 , 0 , 6 ) ,
MIX_ENT ( SOUND_MIXER_PCM , 4 , 1 , 8 , 6 , 4 , 1 , 0 , 6 ) ,
MIX_ENT ( SOUND_MIXER_SPEAKER , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ) ,
MIX_ENT ( SOUND_MIXER_LINE , 18 , 1 , 8 , 5 , 18 , 1 , 0 , 5 ) ,
MIX_ENT ( SOUND_MIXER_MIC , 19 , 1 , 8 , 5 , 19 , 1 , 0 , 5 ) ,
MIX_ENT ( SOUND_MIXER_CD , 15 , 1 , 8 , 5 , 15 , 1 , 0 , 5 ) ,
MIX_ENT ( SOUND_MIXER_IMIX , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ) ,
MIX_ENT ( SOUND_MIXER_ALTPCM , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ) ,
MIX_ENT ( SOUND_MIXER_RECLEV , 20 , 0 , 8 , 4 , 20 , 0 , 0 , 4 ) ,
MIX_ENT ( SOUND_MIXER_IGAIN , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ) ,
MIX_ENT ( SOUND_MIXER_OGAIN , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ) ,
MIX_ENT ( SOUND_MIXER_LINE1 , 17 , 1 , 8 , 5 , 17 , 1 , 0 , 5 ) ,
MIX_ENT ( SOUND_MIXER_LINE2 , 16 , 1 , 8 , 5 , 16 , 1 , 0 , 5 ) ,
MIX_ENT ( SOUND_MIXER_LINE3 , 39 , 0 , 9 , 4 , 39 , 1 , 0 , 5 )
} ;
static unsigned short default_mixer_levels [ SOUND_MIXER_NRDEVICES ] =
{
0x4343 , /* Master Volume */
0x3232 , /* Bass */
0x3232 , /* Treble */
0x0000 , /* FM */
0x4343 , /* PCM */
0x0000 , /* PC Speaker */
0x0000 , /* Ext Line */
0x0000 , /* Mic */
0x0000 , /* CD */
0x0000 , /* Recording monitor */
0x0000 , /* SB PCM */
0x0000 , /* Recording level */
0x0000 , /* Input gain */
0x0000 , /* Output gain */
0x0000 , /* Line1 */
0x0000 , /* Line2 */
0x0000 /* Line3 (usually line in)*/
} ;
# define LEFT_CHN 0
# define RIGHT_CHN 1
static int
ad1816_set_recmask ( ad1816_info * devc , int mask )
{
unsigned long flags ;
unsigned char recdev ;
int i , n ;
spin_lock_irqsave ( & devc - > lock , flags ) ;
mask & = devc - > supported_rec_devices ;
n = 0 ;
/* Count selected device bits */
for ( i = 0 ; i < 32 ; i + + )
if ( mask & ( 1 < < i ) )
n + + ;
if ( n = = 0 )
mask = SOUND_MASK_MIC ;
else if ( n ! = 1 ) { /* Too many devices selected */
/* Filter out active settings */
mask & = ~ devc - > recmask ;
n = 0 ;
/* Count selected device bits */
for ( i = 0 ; i < 32 ; i + + )
if ( mask & ( 1 < < i ) )
n + + ;
if ( n ! = 1 )
mask = SOUND_MASK_MIC ;
}
switch ( mask ) {
case SOUND_MASK_MIC :
recdev = 5 ;
break ;
case SOUND_MASK_LINE :
recdev = 0 ;
break ;
case SOUND_MASK_CD :
recdev = 2 ;
break ;
case SOUND_MASK_LINE1 :
recdev = 4 ;
break ;
case SOUND_MASK_LINE2 :
recdev = 3 ;
break ;
case SOUND_MASK_VOLUME :
recdev = 1 ;
break ;
default :
mask = SOUND_MASK_MIC ;
recdev = 5 ;
}
recdev < < = 4 ;
ad_write ( devc , 20 ,
( ad_read ( devc , 20 ) & 0x8f8f ) | recdev | ( recdev < < 8 ) ) ;
devc - > recmask = mask ;
spin_unlock_irqrestore ( & devc - > lock , flags ) ;
return mask ;
}
static void
change_bits ( int * regval , int dev , int chn , int newval )
{
unsigned char mask ;
int shift ;
/* Reverse polarity*/
if ( mix_devices [ dev ] [ chn ] . polarity = = 1 )
newval = 100 - newval ;
mask = ( 1 < < mix_devices [ dev ] [ chn ] . nbits ) - 1 ;
shift = mix_devices [ dev ] [ chn ] . bitpos ;
/* Scale it */
newval = ( int ) ( ( newval * mask ) + 50 ) / 100 ;
/* Clear bits */
* regval & = ~ ( mask < < shift ) ;
/* Set new value */
* regval | = ( newval & mask ) < < shift ;
}
static int
ad1816_mixer_get ( ad1816_info * devc , int dev )
{
DEBUGNOISE ( printk ( KERN_DEBUG " ad1816: mixer_get called! \n " ) ) ;
/* range check + supported mixer check */
if ( dev < 0 | | dev > = SOUND_MIXER_NRDEVICES )
return ( - ( EINVAL ) ) ;
if ( ! ( ( 1 < < dev ) & devc - > supported_devices ) )
return - ( EINVAL ) ;
return devc - > levels [ dev ] ;
}
static int
ad1816_mixer_set ( ad1816_info * devc , int dev , int value )
{
int left = value & 0x000000ff ;
int right = ( value & 0x0000ff00 ) > > 8 ;
int retvol ;
int regoffs ;
int val ;
int valmute ;
unsigned long flags ;
DEBUGNOISE ( printk ( KERN_DEBUG " ad1816: mixer_set called! \n " ) ) ;
if ( dev < 0 | | dev > = SOUND_MIXER_NRDEVICES )
return - ( EINVAL ) ;
if ( left > 100 )
left = 100 ;
if ( left < 0 )
left = 0 ;
if ( right > 100 )
right = 100 ;
if ( right < 0 )
right = 0 ;
/* Mono control */
if ( mix_devices [ dev ] [ RIGHT_CHN ] . nbits = = 0 )
right = left ;
retvol = left | ( right < < 8 ) ;
/* Scale it */
left = mix_cvt [ left ] ;
right = mix_cvt [ right ] ;
/* reject all mixers that are not supported */
if ( ! ( devc - > supported_devices & ( 1 < < dev ) ) )
return - ( EINVAL ) ;
/* sanity check */
if ( mix_devices [ dev ] [ LEFT_CHN ] . nbits = = 0 )
return - ( EINVAL ) ;
spin_lock_irqsave ( & devc - > lock , flags ) ;
/* keep precise volume internal */
devc - > levels [ dev ] = retvol ;
/* Set the left channel */
regoffs = mix_devices [ dev ] [ LEFT_CHN ] . regno ;
val = ad_read ( devc , regoffs ) ;
change_bits ( & val , dev , LEFT_CHN , left ) ;
valmute = val ;
/* Mute bit masking on some registers */
if ( regoffs = = 5 | | regoffs = = 14 | | regoffs = = 15 | |
regoffs = = 16 | | regoffs = = 17 | | regoffs = = 18 | |
regoffs = = 19 | | regoffs = = 39 ) {
if ( left = = 0 )
valmute | = 0x8000 ;
else
valmute & = ~ 0x8000 ;
}
ad_write ( devc , regoffs , valmute ) ; /* mute */
/*
* Set the right channel
*/
/* Was just a mono channel */
if ( mix_devices [ dev ] [ RIGHT_CHN ] . nbits = = 0 ) {
spin_unlock_irqrestore ( & devc - > lock , flags ) ;
return retvol ;
}
regoffs = mix_devices [ dev ] [ RIGHT_CHN ] . regno ;
val = ad_read ( devc , regoffs ) ;
change_bits ( & val , dev , RIGHT_CHN , right ) ;
valmute = val ;
if ( regoffs = = 5 | | regoffs = = 14 | | regoffs = = 15 | |
regoffs = = 16 | | regoffs = = 17 | | regoffs = = 18 | |
regoffs = = 19 | | regoffs = = 39 ) {
if ( right = = 0 )
valmute | = 0x80 ;
else
valmute & = ~ 0x80 ;
}
ad_write ( devc , regoffs , valmute ) ; /* mute */
spin_unlock_irqrestore ( & devc - > lock , flags ) ;
return retvol ;
}
# define MIXER_DEVICES ( SOUND_MASK_VOLUME | \
SOUND_MASK_SYNTH | \
SOUND_MASK_PCM | \
SOUND_MASK_LINE | \
SOUND_MASK_LINE1 | \
SOUND_MASK_LINE2 | \
SOUND_MASK_LINE3 | \
SOUND_MASK_MIC | \
SOUND_MASK_CD | \
SOUND_MASK_RECLEV \
)
# define REC_DEVICES ( SOUND_MASK_LINE2 |\
SOUND_MASK_LINE | \
SOUND_MASK_LINE1 | \
SOUND_MASK_MIC | \
SOUND_MASK_CD | \
SOUND_MASK_VOLUME \
)
static void
ad1816_mixer_reset ( ad1816_info * devc )
{
int i ;
devc - > supported_devices = MIXER_DEVICES ;
devc - > supported_rec_devices = REC_DEVICES ;
for ( i = 0 ; i < SOUND_MIXER_NRDEVICES ; i + + )
if ( devc - > supported_devices & ( 1 < < i ) )
ad1816_mixer_set ( devc , i , default_mixer_levels [ i ] ) ;
ad1816_set_recmask ( devc , SOUND_MASK_MIC ) ;
}
static int
ad1816_mixer_ioctl ( int dev , unsigned int cmd , void __user * arg )
{
ad1816_info * devc = mixer_devs [ dev ] - > devc ;
int val ;
int __user * p = arg ;
DEBUGNOISE ( printk ( KERN_DEBUG " ad1816: mixer_ioctl called! \n " ) ) ;
/* Mixer ioctl */
if ( ( ( cmd > > 8 ) & 0xff ) = = ' M ' ) {
/* set ioctl */
if ( _SIOC_DIR ( cmd ) & _SIOC_WRITE ) {
switch ( cmd & 0xff ) {
case SOUND_MIXER_RECSRC :
if ( get_user ( val , p ) )
return - EFAULT ;
val = ad1816_set_recmask ( devc , val ) ;
return put_user ( val , p ) ;
break ;
default :
if ( get_user ( val , p ) )
return - EFAULT ;
if ( ( val = ad1816_mixer_set ( devc , cmd & 0xff , val ) ) < 0 )
return val ;
else
return put_user ( val , p ) ;
}
} else {
/* read ioctl */
switch ( cmd & 0xff ) {
case SOUND_MIXER_RECSRC :
val = devc - > recmask ;
return put_user ( val , p ) ;
break ;
case SOUND_MIXER_DEVMASK :
val = devc - > supported_devices ;
return put_user ( val , p ) ;
break ;
case SOUND_MIXER_STEREODEVS :
val = devc - > supported_devices & ~ ( SOUND_MASK_SPEAKER | SOUND_MASK_IMIX ) ;
return put_user ( val , p ) ;
break ;
case SOUND_MIXER_RECMASK :
val = devc - > supported_rec_devices ;
return put_user ( val , p ) ;
break ;
case SOUND_MIXER_CAPS :
val = SOUND_CAP_EXCL_INPUT ;
return put_user ( val , p ) ;
break ;
default :
if ( ( val = ad1816_mixer_get ( devc , cmd & 0xff ) ) < 0 )
return val ;
else
return put_user ( val , p ) ;
}
}
} else
/* not for mixer */
return - ( EINVAL ) ;
}
/* ------------------------------------------------------------------- */
/* Mixer structure */
static struct mixer_operations ad1816_mixer_operations = {
. owner = THIS_MODULE ,
. id = " AD1816 " ,
. name = " AD1816 Mixer " ,
. ioctl = ad1816_mixer_ioctl
} ;
/* ------------------------------------------------------------------- */
/* stuff for card recognition, init and unloading PNP ...*/
/* check if AD1816 present at specified hw_config and register device with OS
* return 1 if initialization was successful , 0 otherwise
*/
static int __init ad1816_init_card ( struct address_info * hw_config ,
struct pnp_dev * pnp )
{
ad1816_info * devc = NULL ;
int tmp ;
int oss_devno = - 1 ;
printk ( KERN_INFO " ad1816: initializing card: io=0x%x, irq=%d, dma=%d, "
" dma2=%d, clockfreq=%d, options=%d isadmabug=%d "
" %s \n " ,
hw_config - > io_base ,
hw_config - > irq ,
hw_config - > dma ,
hw_config - > dma2 ,
ad1816_clockfreq ,
options ,
isa_dma_bridge_buggy ,
pnp ? " (PNP) " : " " ) ;
/* ad1816_info structure remaining ? */
if ( nr_ad1816_devs > = MAX_AUDIO_DEV ) {
printk ( KERN_WARNING " ad1816: no more ad1816_info structures "
" left \n " ) ;
goto out ;
}
devc = & dev_info [ nr_ad1816_devs ] ;
devc - > base = hw_config - > io_base ;
devc - > irq = hw_config - > irq ;
devc - > dma_playback = hw_config - > dma ;
devc - > dma_capture = hw_config - > dma2 ;
devc - > opened = 0 ;
devc - > pnpdev = pnp ;
spin_lock_init ( & devc - > lock ) ;
if ( ! request_region ( devc - > base , 16 , " AD1816 Sound " ) ) {
printk ( KERN_WARNING " ad1816: I/O port 0x%03x not free \n " ,
devc - > base ) ;
goto out ;
}
printk ( KERN_INFO " ad1816: Examining AD1816 at address 0x%03x. \n " ,
devc - > base ) ;
/* tests for ad1816 */
/* base+0: bit 1 must be set but not 255 */
tmp = inb ( devc - > base ) ;
if ( ( tmp & 0x80 ) = = 0 | | tmp = = 255 ) {
printk ( KERN_INFO " ad1816: Chip is not an AD1816 or chip "
" is not active (Test 0) \n " ) ;
goto out_release_region ;
}
/* writes to ireg 8 are copied to ireg 9 */
ad_write ( devc , 8 , 12345 ) ;
if ( ad_read ( devc , 9 ) ! = 12345 ) {
printk ( KERN_INFO " ad1816: Chip is not an AD1816 (Test 1) \n " ) ;
goto out_release_region ;
}
/* writes to ireg 8 are copied to ireg 9 */
ad_write ( devc , 8 , 54321 ) ;
if ( ad_read ( devc , 9 ) ! = 54321 ) {
printk ( KERN_INFO " ad1816: Chip is not an AD1816 (Test 2) \n " ) ;
goto out_release_region ;
}
/* writes to ireg 10 are copied to ireg 11 */
ad_write ( devc , 10 , 54321 ) ;
if ( ad_read ( devc , 11 ) ! = 54321 ) {
printk ( KERN_INFO " ad1816: Chip is not an AD1816 (Test 3) \n " ) ;
goto out_release_region ;
}
/* writes to ireg 10 are copied to ireg 11 */
ad_write ( devc , 10 , 12345 ) ;
if ( ad_read ( devc , 11 ) ! = 12345 ) {
printk ( KERN_INFO " ad1816: Chip is not an AD1816 (Test 4) \n " ) ;
goto out_release_region ;
}
/* bit in base +1 cannot be set to 1 */
tmp = inb ( devc - > base + 1 ) ;
outb ( 0xff , devc - > base + 1 ) ;
if ( inb ( devc - > base + 1 ) ! = tmp ) {
printk ( KERN_INFO " ad1816: Chip is not an AD1816 (Test 5) \n " ) ;
goto out_release_region ;
}
printk ( KERN_INFO " ad1816: AD1816 (version %d) successfully detected! \n " ,
ad_read ( devc , 45 ) ) ;
/* disable all interrupts */
ad_write ( devc , 1 , 0 ) ;
/* Clear pending interrupts */
outb ( 0 , devc - > base + 1 ) ;
/* allocate irq */
if ( devc - > irq < 0 | | devc - > irq > 15 )
goto out_release_region ;
if ( request_irq ( devc - > irq , ad1816_interrupt , 0 ,
" SoundPort " , devc ) < 0 ) {
printk ( KERN_WARNING " ad1816: IRQ in use \n " ) ;
goto out_release_region ;
}
/* DMA stuff */
if ( sound_alloc_dma ( devc - > dma_playback , " Sound System " ) ) {
printk ( KERN_WARNING " ad1816: Can't allocate DMA%d \n " ,
devc - > dma_playback ) ;
goto out_free_irq ;
}
if ( devc - > dma_capture > = 0 & &
devc - > dma_capture ! = devc - > dma_playback ) {
if ( sound_alloc_dma ( devc - > dma_capture ,
" Sound System (capture) " ) ) {
printk ( KERN_WARNING " ad1816: Can't allocate DMA%d \n " ,
devc - > dma_capture ) ;
goto out_free_dma ;
}
devc - > audio_mode = DMA_AUTOMODE | DMA_DUPLEX ;
} else {
printk ( KERN_WARNING " ad1816: Only one DMA channel "
" available/configured. No duplex operation possible \n " ) ;
devc - > audio_mode = DMA_AUTOMODE ;
}
conf_printf2 ( " AD1816 audio driver " ,
devc - > base , devc - > irq , devc - > dma_playback ,
devc - > dma_capture ) ;
/* register device */
if ( ( oss_devno = sound_install_audiodrv ( AUDIO_DRIVER_VERSION ,
" AD1816 audio driver " ,
& ad1816_audio_driver ,
sizeof ( struct audio_driver ) ,
devc - > audio_mode ,
ad_format_mask ,
devc ,
devc - > dma_playback ,
devc - > dma_capture ) ) < 0 ) {
printk ( KERN_WARNING " ad1816: Can't install sound driver \n " ) ;
goto out_free_dma_2 ;
}
ad_write ( devc , 32 , 0x80f0 ) ; /* sound system mode */
if ( options & 1 ) {
ad_write ( devc , 33 , 0 ) ; /* disable all audiosources for dsp */
} else {
ad_write ( devc , 33 , 0x03f8 ) ; /* enable all audiosources for dsp */
}
ad_write ( devc , 4 , 0x8080 ) ; /* default values for volumes (muted)*/
ad_write ( devc , 5 , 0x8080 ) ;
ad_write ( devc , 6 , 0x8080 ) ;
ad_write ( devc , 7 , 0x8080 ) ;
ad_write ( devc , 15 , 0x8888 ) ;
ad_write ( devc , 16 , 0x8888 ) ;
ad_write ( devc , 17 , 0x8888 ) ;
ad_write ( devc , 18 , 0x8888 ) ;
ad_write ( devc , 19 , 0xc888 ) ; /* +20db mic active */
ad_write ( devc , 14 , 0x0000 ) ; /* Master volume unmuted */
ad_write ( devc , 39 , 0x009f ) ; /* 3D effect on 0% phone out muted */
ad_write ( devc , 44 , 0x0080 ) ; /* everything on power, 3d enabled for d/a */
outb ( 0x10 , devc - > base + 8 ) ; /* set dma mode */
outb ( 0x10 , devc - > base + 9 ) ;
/* enable capture + playback interrupt */
ad_write ( devc , 1 , 0xc000 ) ;
/* set mixer defaults */
ad1816_mixer_reset ( devc ) ;
/* register mixer */
if ( ( audio_devs [ oss_devno ] - > mixer_dev = sound_install_mixer (
MIXER_DRIVER_VERSION ,
" AD1816 audio driver " ,
& ad1816_mixer_operations ,
sizeof ( struct mixer_operations ) ,
devc ) ) < 0 ) {
printk ( KERN_WARNING " Can't install mixer \n " ) ;
}
/* make ad1816_info active */
nr_ad1816_devs + + ;
printk ( KERN_INFO " ad1816: card successfully installed! \n " ) ;
return 1 ;
/* error handling */
out_free_dma_2 :
if ( devc - > dma_capture > = 0 & & devc - > dma_capture ! = devc - > dma_playback )
sound_free_dma ( devc - > dma_capture ) ;
out_free_dma :
sound_free_dma ( devc - > dma_playback ) ;
out_free_irq :
free_irq ( devc - > irq , devc ) ;
out_release_region :
release_region ( devc - > base , 16 ) ;
out :
return 0 ;
}
static void __exit unload_card ( ad1816_info * devc )
{
int mixer , dev = 0 ;
if ( devc ! = NULL ) {
printk ( " ad1816: Unloading card at address 0x%03x \n " , devc - > base ) ;
dev = devc - > dev_no ;
mixer = audio_devs [ dev ] - > mixer_dev ;
/* unreg mixer*/
if ( mixer > = 0 ) {
sound_unload_mixerdev ( mixer ) ;
}
/* unreg audiodev */
sound_unload_audiodev ( dev ) ;
/* free dma channels */
if ( devc - > dma_capture > = 0 & &
devc - > dma_capture ! = devc - > dma_playback ) {
sound_free_dma ( devc - > dma_capture ) ;
}
sound_free_dma ( devc - > dma_playback ) ;
/* free irq */
free_irq ( devc - > irq , devc ) ;
/* free io */
release_region ( devc - > base , 16 ) ;
# ifdef __ISAPNP__
if ( devc - > pnpdev ) {
pnp_disable_dev ( devc - > pnpdev ) ;
pnp_device_detach ( devc - > pnpdev ) ;
}
# endif
} else
printk ( KERN_WARNING " ad1816: no device/card specified \n " ) ;
}
static int __initdata io = - 1 ;
static int __initdata irq = - 1 ;
static int __initdata dma = - 1 ;
static int __initdata dma2 = - 1 ;
# ifdef __ISAPNP__
/* use isapnp for configuration */
static int isapnp = 1 ;
static int isapnpjump ;
module_param ( isapnp , bool , 0 ) ;
module_param ( isapnpjump , int , 0 ) ;
# endif
module_param ( io , int , 0 ) ;
module_param ( irq , int , 0 ) ;
module_param ( dma , int , 0 ) ;
module_param ( dma2 , int , 0 ) ;
module_param ( ad1816_clockfreq , int , 0 ) ;
module_param ( options , int , 0 ) ;
# ifdef __ISAPNP__
static struct {
unsigned short card_vendor , card_device ;
unsigned short vendor ;
unsigned short function ;
struct ad1816_data * data ;
} isapnp_ad1816_list [ ] __initdata = {
{ ISAPNP_ANY_ID , ISAPNP_ANY_ID ,
ISAPNP_VENDOR ( ' A ' , ' D ' , ' S ' ) , ISAPNP_FUNCTION ( 0x7150 ) ,
NULL } ,
{ ISAPNP_ANY_ID , ISAPNP_ANY_ID ,
ISAPNP_VENDOR ( ' A ' , ' D ' , ' S ' ) , ISAPNP_FUNCTION ( 0x7180 ) ,
NULL } ,
{ 0 }
} ;
MODULE_DEVICE_TABLE ( isapnp , isapnp_ad1816_list ) ;
static void __init ad1816_config_pnp_card ( struct pnp_card * card ,
unsigned short vendor ,
unsigned short function )
{
struct address_info cfg ;
struct pnp_dev * card_dev = pnp_find_dev ( card , vendor , function , NULL ) ;
if ( ! card_dev ) return ;
if ( pnp_device_attach ( card_dev ) < 0 ) {
printk ( KERN_WARNING " ad1816: Failed to attach PnP device \n " ) ;
return ;
}
if ( pnp_activate_dev ( card_dev ) < 0 ) {
printk ( KERN_WARNING " ad1816: Failed to activate PnP device \n " ) ;
pnp_device_detach ( card_dev ) ;
return ;
}
cfg . io_base = pnp_port_start ( card_dev , 2 ) ;
cfg . irq = pnp_irq ( card_dev , 0 ) ;
cfg . dma = pnp_irq ( card_dev , 0 ) ;
cfg . dma2 = pnp_irq ( card_dev , 1 ) ;
if ( ! ad1816_init_card ( & cfg , card_dev ) ) {
pnp_disable_dev ( card_dev ) ;
pnp_device_detach ( card_dev ) ;
}
}
static void __init ad1816_config_pnp_cards ( void )
{
int nr_pnp_cfg ;
int i ;
/* Count entries in isapnp_ad1816_list */
for ( nr_pnp_cfg = 0 ; isapnp_ad1816_list [ nr_pnp_cfg ] . card_vendor ! = 0 ;
nr_pnp_cfg + + ) ;
/* Check and adjust isapnpjump */
if ( isapnpjump < 0 | | isapnpjump > = nr_pnp_cfg ) {
printk ( KERN_WARNING
" ad1816: Valid range for isapnpjump is 0-%d. "
" Adjusted to 0. \n " , nr_pnp_cfg - 1 ) ;
isapnpjump = 0 ;
}
for ( i = isapnpjump ; isapnp_ad1816_list [ i ] . card_vendor ! = 0 ; i + + ) {
struct pnp_card * card = NULL ;
/* iterate over all pnp cards */
while ( ( card = pnp_find_card ( isapnp_ad1816_list [ i ] . card_vendor ,
isapnp_ad1816_list [ i ] . card_device , card ) ) )
ad1816_config_pnp_card ( card ,
isapnp_ad1816_list [ i ] . vendor ,
isapnp_ad1816_list [ i ] . function ) ;
}
}
# endif
/* module initialization */
static int __init init_ad1816 ( void )
{
printk ( KERN_INFO " ad1816: AD1816 sounddriver "
" Copyright (C) 1998-2003 by Thorsten Knabe and "
" others \n " ) ;
# ifdef AD1816_CLOCK
/* set ad1816_clockfreq if set during compilation */
ad1816_clockfreq = AD1816_CLOCK ;
# endif
if ( ad1816_clockfreq < 5000 | | ad1816_clockfreq > 100000 ) {
ad1816_clockfreq = 33000 ;
}
# ifdef __ISAPNP__
/* configure PnP cards */
if ( isapnp ) ad1816_config_pnp_cards ( ) ;
# endif
/* configure card by module params */
if ( io ! = - 1 & & irq ! = - 1 & & dma ! = - 1 ) {
struct address_info cfg ;
cfg . io_base = io ;
cfg . irq = irq ;
cfg . dma = dma ;
cfg . dma2 = dma2 ;
ad1816_init_card ( & cfg , NULL ) ;
}
if ( nr_ad1816_devs < = 0 )
return - ENODEV ;
return 0 ;
}
/* module cleanup */
static void __exit cleanup_ad1816 ( void )
{
int i ;
ad1816_info * devc = NULL ;
/* remove any soundcard */
for ( i = 0 ; i < nr_ad1816_devs ; i + + ) {
devc = & dev_info [ i ] ;
unload_card ( devc ) ;
}
nr_ad1816_devs = 0 ;
printk ( KERN_INFO " ad1816: driver unloaded! \n " ) ;
}
module_init ( init_ad1816 ) ;
module_exit ( cleanup_ad1816 ) ;
# ifndef MODULE
/* kernel command line parameter evaluation */
static int __init setup_ad1816 ( char * str )
{
/* io, irq, dma, dma2 */
int ints [ 5 ] ;
str = get_options ( str , ARRAY_SIZE ( ints ) , ints ) ;
io = ints [ 1 ] ;
irq = ints [ 2 ] ;
dma = ints [ 3 ] ;
dma2 = ints [ 4 ] ;
return 1 ;
}
__setup ( " ad1816= " , setup_ad1816 ) ;
# endif
MODULE_LICENSE ( " GPL " ) ;