2005-04-17 02:20:36 +04:00
/*
2006-10-04 01:01:26 +04:00
* sound / oss / pas2_mixer . c
2005-04-17 02:20:36 +04:00
*
* Mixer routines for the Pro Audio Spectrum cards .
*/
/*
* Copyright ( C ) by Hannu Savolainen 1993 - 1997
*
* OSS / Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE ( GPL )
* Version 2 ( June 1991 ) . See the " COPYING " file distributed with this software
* for more info .
*/
/*
* Thomas Sailer : ioctl code reworked ( vmalloc / vfree removed )
* Bartlomiej Zolnierkiewicz : added __init to pas_init_mixer ( )
*/
# include <linux/init.h>
# include "sound_config.h"
# include "pas2.h"
extern int pas_translate_code ;
extern char pas_model ;
extern int * pas_osp ;
extern int pas_audiodev ;
static int rec_devices = ( SOUND_MASK_MIC ) ; /* Default recording source */
static int mode_control ;
# define POSSIBLE_RECORDING_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \
SOUND_MASK_CD | SOUND_MASK_ALTPCM )
# define SUPPORTED_MIXER_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \
SOUND_MASK_CD | SOUND_MASK_ALTPCM | SOUND_MASK_IMIX | \
SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE | SOUND_MASK_RECLEV )
static int * levels ;
static int default_levels [ 32 ] =
{
0x3232 , /* Master Volume */
0x3232 , /* Bass */
0x3232 , /* Treble */
0x5050 , /* FM */
0x4b4b , /* PCM */
0x3232 , /* PC Speaker */
0x4b4b , /* Ext Line */
0x4b4b , /* Mic */
0x4b4b , /* CD */
0x6464 , /* Recording monitor */
0x4b4b , /* SB PCM */
0x6464 /* Recording level */
} ;
void
mix_write ( unsigned char data , int ioaddr )
{
/*
* The Revision D cards have a problem with their MVA508 interface . The
* kludge - o - rama fix is to make a 16 - bit quantity with identical LSB and
* MSBs out of the output byte and to do a 16 - bit out to the mixer port -
* 1. We need to do this because it isn ' t timing problem but chip access
* sequence problem .
*/
if ( pas_model = = 4 )
{
outw ( data | ( data < < 8 ) , ( ioaddr + pas_translate_code ) - 1 ) ;
outb ( ( 0x80 ) , 0 ) ;
} else
pas_write ( data , ioaddr ) ;
}
static int
mixer_output ( int right_vol , int left_vol , int div , int bits ,
int mixer ) /* Input or output mixer */
{
int left = left_vol * div / 100 ;
int right = right_vol * div / 100 ;
if ( bits & 0x10 )
{
left | = mixer ;
right | = mixer ;
}
if ( bits = = 0x03 | | bits = = 0x04 )
{
mix_write ( 0x80 | bits , 0x078B ) ;
mix_write ( left , 0x078B ) ;
right_vol = left_vol ;
} else
{
mix_write ( 0x80 | 0x20 | bits , 0x078B ) ;
mix_write ( left , 0x078B ) ;
mix_write ( 0x80 | 0x40 | bits , 0x078B ) ;
mix_write ( right , 0x078B ) ;
}
return ( left_vol | ( right_vol < < 8 ) ) ;
}
static void
set_mode ( int new_mode )
{
mix_write ( 0x80 | 0x05 , 0x078B ) ;
mix_write ( new_mode , 0x078B ) ;
mode_control = new_mode ;
}
static int
pas_mixer_set ( int whichDev , unsigned int level )
{
int left , right , devmask , changed , i , mixer = 0 ;
left = level & 0x7f ;
right = ( level & 0x7f00 ) > > 8 ;
if ( whichDev < SOUND_MIXER_NRDEVICES ) {
if ( ( 1 < < whichDev ) & rec_devices )
mixer = 0x20 ;
else
mixer = 0x00 ;
}
switch ( whichDev )
{
case SOUND_MIXER_VOLUME : /* Master volume (0-63) */
levels [ whichDev ] = mixer_output ( right , left , 63 , 0x01 , 0 ) ;
break ;
/*
* Note ! Bass and Treble are mono devices . Will use just the left
* channel .
*/
case SOUND_MIXER_BASS : /* Bass (0-12) */
levels [ whichDev ] = mixer_output ( right , left , 12 , 0x03 , 0 ) ;
break ;
case SOUND_MIXER_TREBLE : /* Treble (0-12) */
levels [ whichDev ] = mixer_output ( right , left , 12 , 0x04 , 0 ) ;
break ;
case SOUND_MIXER_SYNTH : /* Internal synthesizer (0-31) */
levels [ whichDev ] = mixer_output ( right , left , 31 , 0x10 | 0x00 , mixer ) ;
break ;
case SOUND_MIXER_PCM : /* PAS PCM (0-31) */
levels [ whichDev ] = mixer_output ( right , left , 31 , 0x10 | 0x05 , mixer ) ;
break ;
case SOUND_MIXER_ALTPCM : /* SB PCM (0-31) */
levels [ whichDev ] = mixer_output ( right , left , 31 , 0x10 | 0x07 , mixer ) ;
break ;
case SOUND_MIXER_SPEAKER : /* PC speaker (0-31) */
levels [ whichDev ] = mixer_output ( right , left , 31 , 0x10 | 0x06 , mixer ) ;
break ;
case SOUND_MIXER_LINE : /* External line (0-31) */
levels [ whichDev ] = mixer_output ( right , left , 31 , 0x10 | 0x02 , mixer ) ;
break ;
case SOUND_MIXER_CD : /* CD (0-31) */
levels [ whichDev ] = mixer_output ( right , left , 31 , 0x10 | 0x03 , mixer ) ;
break ;
case SOUND_MIXER_MIC : /* External microphone (0-31) */
levels [ whichDev ] = mixer_output ( right , left , 31 , 0x10 | 0x04 , mixer ) ;
break ;
case SOUND_MIXER_IMIX : /* Recording monitor (0-31) (Output mixer only) */
levels [ whichDev ] = mixer_output ( right , left , 31 , 0x10 | 0x01 ,
0x00 ) ;
break ;
case SOUND_MIXER_RECLEV : /* Recording level (0-15) */
levels [ whichDev ] = mixer_output ( right , left , 15 , 0x02 , 0 ) ;
break ;
case SOUND_MIXER_RECSRC :
devmask = level & POSSIBLE_RECORDING_DEVICES ;
changed = devmask ^ rec_devices ;
rec_devices = devmask ;
for ( i = 0 ; i < SOUND_MIXER_NRDEVICES ; i + + )
if ( changed & ( 1 < < i ) )
{
pas_mixer_set ( i , levels [ i ] ) ;
}
return rec_devices ;
break ;
default :
return - EINVAL ;
}
return ( levels [ whichDev ] ) ;
}
/*****/
static void
pas_mixer_reset ( void )
{
int foo ;
for ( foo = 0 ; foo < SOUND_MIXER_NRDEVICES ; foo + + )
pas_mixer_set ( foo , levels [ foo ] ) ;
set_mode ( 0x04 | 0x01 ) ;
}
static int pas_mixer_ioctl ( int dev , unsigned int cmd , void __user * arg )
{
int level , v ;
int __user * p = ( int __user * ) arg ;
if ( cmd = = SOUND_MIXER_PRIVATE1 ) { /* Set loudness bit */
if ( get_user ( level , p ) )
return - EFAULT ;
if ( level = = - 1 ) /* Return current settings */
level = ( mode_control & 0x04 ) ;
else {
mode_control & = ~ 0x04 ;
if ( level )
mode_control | = 0x04 ;
set_mode ( mode_control ) ;
}
level = ! ! level ;
return put_user ( level , p ) ;
}
if ( cmd = = SOUND_MIXER_PRIVATE2 ) { /* Set enhance bit */
if ( get_user ( level , p ) )
return - EFAULT ;
if ( level = = - 1 ) { /* Return current settings */
if ( ! ( mode_control & 0x03 ) )
level = 0 ;
else
level = ( ( mode_control & 0x03 ) + 1 ) * 20 ;
} else {
int i = 0 ;
level & = 0x7f ;
if ( level )
i = ( level / 20 ) - 1 ;
mode_control & = ~ 0x03 ;
mode_control | = i & 0x03 ;
set_mode ( mode_control ) ;
if ( i )
i = ( i + 1 ) * 20 ;
level = i ;
}
return put_user ( level , p ) ;
}
if ( cmd = = SOUND_MIXER_PRIVATE3 ) { /* Set mute bit */
if ( get_user ( level , p ) )
return - EFAULT ;
if ( level = = - 1 ) /* Return current settings */
level = ! ( pas_read ( 0x0B8A ) & 0x20 ) ;
else {
if ( level )
pas_write ( pas_read ( 0x0B8A ) & ( ~ 0x20 ) , 0x0B8A ) ;
else
pas_write ( pas_read ( 0x0B8A ) | 0x20 , 0x0B8A ) ;
level = ! ( pas_read ( 0x0B8A ) & 0x20 ) ;
}
return put_user ( level , p ) ;
}
if ( ( ( cmd > > 8 ) & 0xff ) = = ' M ' ) {
if ( get_user ( v , p ) )
return - EFAULT ;
if ( _SIOC_DIR ( cmd ) & _SIOC_WRITE ) {
v = pas_mixer_set ( cmd & 0xff , v ) ;
} else {
switch ( cmd & 0xff ) {
case SOUND_MIXER_RECSRC :
v = rec_devices ;
break ;
case SOUND_MIXER_STEREODEVS :
v = SUPPORTED_MIXER_DEVICES & ~ ( SOUND_MASK_BASS | SOUND_MASK_TREBLE ) ;
break ;
case SOUND_MIXER_DEVMASK :
v = SUPPORTED_MIXER_DEVICES ;
break ;
case SOUND_MIXER_RECMASK :
v = POSSIBLE_RECORDING_DEVICES & SUPPORTED_MIXER_DEVICES ;
break ;
case SOUND_MIXER_CAPS :
v = 0 ; /* No special capabilities */
break ;
default :
v = levels [ cmd & 0xff ] ;
break ;
}
}
return put_user ( v , p ) ;
}
return - EINVAL ;
}
static struct mixer_operations pas_mixer_operations =
{
. owner = THIS_MODULE ,
. id = " PAS16 " ,
. name = " Pro Audio Spectrum 16 " ,
. ioctl = pas_mixer_ioctl
} ;
int __init
pas_init_mixer ( void )
{
int d ;
levels = load_mixer_volumes ( " PAS16_1 " , default_levels , 1 ) ;
pas_mixer_reset ( ) ;
if ( ( d = sound_alloc_mixerdev ( ) ) ! = - 1 )
{
audio_devs [ pas_audiodev ] - > mixer_dev = d ;
mixer_devs [ d ] = & pas_mixer_operations ;
}
return 1 ;
}