2005-04-16 15:20:36 -07:00
/*
* linux / drivers / sound / waveartist . c
*
* The low level driver for the RWA010 Rockwell Wave Artist
* codec chip used in the Rebel . com NetWinder .
*
* Cleaned up and integrated into 2.1 by Russell King ( rmk @ arm . linux . org . uk )
* and Pat Beirne ( patb @ corel . ca )
*
*
* Copyright ( C ) by Rebel . com 1998 - 1999
*
* RWA010 specs received under NDA from Rockwell
*
* 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 .
*
* Changes :
* 11 - 10 - 2000 Bartlomiej Zolnierkiewicz < bkz @ linux - ide . org >
* Added __init to waveartist_init ( )
*/
/* Debugging */
# define DEBUG_CMD 1
# define DEBUG_OUT 2
# define DEBUG_IN 4
# define DEBUG_INTR 8
# define DEBUG_MIXER 16
# define DEBUG_TRIGGER 32
# define debug_flg (0)
# include <linux/module.h>
# include <linux/init.h>
# include <linux/config.h>
# include <linux/sched.h>
# include <linux/interrupt.h>
# include <linux/delay.h>
# include <linux/spinlock.h>
# include <linux/bitops.h>
# include <asm/system.h>
# include "sound_config.h"
# include "waveartist.h"
# ifdef CONFIG_ARM
# include <asm/hardware.h>
# include <asm/mach-types.h>
# endif
# ifndef NO_DMA
# define NO_DMA 255
# endif
# define SUPPORTED_MIXER_DEVICES (SOUND_MASK_SYNTH |\
SOUND_MASK_PCM | \
SOUND_MASK_LINE | \
SOUND_MASK_MIC | \
SOUND_MASK_LINE1 | \
SOUND_MASK_RECLEV | \
SOUND_MASK_VOLUME | \
SOUND_MASK_IMIX )
static unsigned short levels [ SOUND_MIXER_NRDEVICES ] = {
0x5555 , /* Master Volume */
0x0000 , /* Bass */
0x0000 , /* Treble */
0x2323 , /* Synth (FM) */
0x4b4b , /* PCM */
0x6464 , /* PC Speaker */
0x0000 , /* Ext Line */
0x0000 , /* Mic */
0x0000 , /* CD */
0x6464 , /* Recording monitor */
0x0000 , /* SB PCM (ALT PCM) */
0x0000 , /* Recording level */
0x6464 , /* Input gain */
0x6464 , /* Output gain */
0x0000 , /* Line1 (Aux1) */
0x0000 , /* Line2 (Aux2) */
0x0000 , /* Line3 (Aux3) */
0x0000 , /* Digital1 */
0x0000 , /* Digital2 */
0x0000 , /* Digital3 */
0x0000 , /* Phone In */
0x6464 , /* Phone Out */
0x0000 , /* Video */
0x0000 , /* Radio */
0x0000 /* Monitor */
} ;
typedef struct {
struct address_info hw ; /* hardware */
char * chip_name ;
int xfer_count ;
int audio_mode ;
int open_mode ;
int audio_flags ;
int record_dev ;
int playback_dev ;
int dev_no ;
/* Mixer parameters */
const struct waveartist_mixer_info * mix ;
unsigned short * levels ; /* cache of volume settings */
int recmask ; /* currently enabled recording device! */
# ifdef CONFIG_ARCH_NETWINDER
signed int slider_vol ; /* hardware slider volume */
unsigned int handset_detect : 1 ;
unsigned int telephone_detect : 1 ;
unsigned int no_autoselect : 1 ; /* handset/telephone autoselects a path */
unsigned int spkr_mute_state : 1 ; /* set by ioctl or autoselect */
unsigned int line_mute_state : 1 ; /* set by ioctl or autoselect */
unsigned int use_slider : 1 ; /* use slider setting for o/p vol */
# endif
} wavnc_info ;
/*
* This is the implementation specific mixer information .
*/
struct waveartist_mixer_info {
unsigned int supported_devs ; /* Supported devices */
unsigned int recording_devs ; /* Recordable devies */
unsigned int stereo_devs ; /* Stereo devices */
unsigned int ( * select_input ) ( wavnc_info * , unsigned int ,
unsigned char * , unsigned char * ) ;
int ( * decode_mixer ) ( wavnc_info * , int ,
unsigned char , unsigned char ) ;
int ( * get_mixer ) ( wavnc_info * , int ) ;
} ;
typedef struct wavnc_port_info {
int open_mode ;
int speed ;
int channels ;
int audio_format ;
} wavnc_port_info ;
static int nr_waveartist_devs ;
static wavnc_info adev_info [ MAX_AUDIO_DEV ] ;
static DEFINE_SPINLOCK ( waveartist_lock ) ;
# ifndef CONFIG_ARCH_NETWINDER
# define machine_is_netwinder() 0
# else
static struct timer_list vnc_timer ;
static void vnc_configure_mixer ( wavnc_info * devc , unsigned int input_mask ) ;
static int vnc_private_ioctl ( int dev , unsigned int cmd , int __user * arg ) ;
static void vnc_slider_tick ( unsigned long data ) ;
# endif
static inline void
waveartist_set_ctlr ( struct address_info * hw , unsigned char clear , unsigned char set )
{
unsigned int ctlr_port = hw - > io_base + CTLR ;
clear = ~ clear & inb ( ctlr_port ) ;
outb ( clear | set , ctlr_port ) ;
}
/* Toggle IRQ acknowledge line
*/
static inline void
waveartist_iack ( wavnc_info * devc )
{
unsigned int ctlr_port = devc - > hw . io_base + CTLR ;
int old_ctlr ;
old_ctlr = inb ( ctlr_port ) & ~ IRQ_ACK ;
outb ( old_ctlr | IRQ_ACK , ctlr_port ) ;
outb ( old_ctlr , ctlr_port ) ;
}
static inline int
waveartist_sleep ( int timeout_ms )
{
unsigned int timeout = timeout_ms * 10 * HZ / 100 ;
do {
set_current_state ( TASK_INTERRUPTIBLE ) ;
timeout = schedule_timeout ( timeout ) ;
} while ( timeout ) ;
return 0 ;
}
static int
waveartist_reset ( wavnc_info * devc )
{
struct address_info * hw = & devc - > hw ;
unsigned int timeout , res = - 1 ;
waveartist_set_ctlr ( hw , - 1 , RESET ) ;
waveartist_sleep ( 2 ) ;
waveartist_set_ctlr ( hw , RESET , 0 ) ;
timeout = 500 ;
do {
mdelay ( 2 ) ;
if ( inb ( hw - > io_base + STATR ) & CMD_RF ) {
res = inw ( hw - > io_base + CMDR ) ;
if ( res = = 0x55aa )
break ;
}
} while ( - - timeout ) ;
if ( timeout = = 0 ) {
printk ( KERN_WARNING " WaveArtist: reset timeout " ) ;
if ( res ! = ( unsigned int ) - 1 )
printk ( " (res=%04X) " , res ) ;
printk ( " \n " ) ;
return 1 ;
}
return 0 ;
}
/* Helper function to send and receive words
* from WaveArtist . It handles all the handshaking
* and can send or receive multiple words .
*/
static int
waveartist_cmd ( wavnc_info * devc ,
int nr_cmd , unsigned int * cmd ,
int nr_resp , unsigned int * resp )
{
unsigned int io_base = devc - > hw . io_base ;
unsigned int timed_out = 0 ;
unsigned int i ;
if ( debug_flg & DEBUG_CMD ) {
printk ( " waveartist_cmd: cmd= " ) ;
for ( i = 0 ; i < nr_cmd ; i + + )
printk ( " %04X " , cmd [ i ] ) ;
printk ( " \n " ) ;
}
if ( inb ( io_base + STATR ) & CMD_RF ) {
int old_data ;
/* flush the port
*/
old_data = inw ( io_base + CMDR ) ;
if ( debug_flg & DEBUG_CMD )
printk ( " flushed %04X... " , old_data ) ;
udelay ( 10 ) ;
}
for ( i = 0 ; ! timed_out & & i < nr_cmd ; i + + ) {
int count ;
for ( count = 5000 ; count ; count - - )
if ( inb ( io_base + STATR ) & CMD_WE )
break ;
if ( ! count )
timed_out = 1 ;
else
outw ( cmd [ i ] , io_base + CMDR ) ;
}
for ( i = 0 ; ! timed_out & & i < nr_resp ; i + + ) {
int count ;
for ( count = 5000 ; count ; count - - )
if ( inb ( io_base + STATR ) & CMD_RF )
break ;
if ( ! count )
timed_out = 1 ;
else
resp [ i ] = inw ( io_base + CMDR ) ;
}
if ( debug_flg & DEBUG_CMD ) {
if ( ! timed_out ) {
printk ( " waveartist_cmd: resp= " ) ;
for ( i = 0 ; i < nr_resp ; i + + )
printk ( " %04X " , resp [ i ] ) ;
printk ( " \n " ) ;
} else
printk ( " waveartist_cmd: timed out \n " ) ;
}
return timed_out ? 1 : 0 ;
}
/*
* Send one command word
*/
static inline int
waveartist_cmd1 ( wavnc_info * devc , unsigned int cmd )
{
return waveartist_cmd ( devc , 1 , & cmd , 0 , NULL ) ;
}
/*
* Send one command , receive one word
*/
static inline unsigned int
waveartist_cmd1_r ( wavnc_info * devc , unsigned int cmd )
{
unsigned int ret ;
waveartist_cmd ( devc , 1 , & cmd , 1 , & ret ) ;
return ret ;
}
/*
* Send a double command , receive one
* word ( and throw it away )
*/
static inline int
waveartist_cmd2 ( wavnc_info * devc , unsigned int cmd , unsigned int arg )
{
unsigned int vals [ 2 ] ;
vals [ 0 ] = cmd ;
vals [ 1 ] = arg ;
return waveartist_cmd ( devc , 2 , vals , 1 , vals ) ;
}
/*
* Send a triple command
*/
static inline int
waveartist_cmd3 ( wavnc_info * devc , unsigned int cmd ,
unsigned int arg1 , unsigned int arg2 )
{
unsigned int vals [ 3 ] ;
vals [ 0 ] = cmd ;
vals [ 1 ] = arg1 ;
vals [ 2 ] = arg2 ;
return waveartist_cmd ( devc , 3 , vals , 0 , NULL ) ;
}
static int
waveartist_getrev ( wavnc_info * devc , char * rev )
{
unsigned int temp [ 2 ] ;
unsigned int cmd = WACMD_GETREV ;
waveartist_cmd ( devc , 1 , & cmd , 2 , temp ) ;
rev [ 0 ] = temp [ 0 ] > > 8 ;
rev [ 1 ] = temp [ 0 ] & 255 ;
rev [ 2 ] = ' \0 ' ;
return temp [ 0 ] ;
}
static void waveartist_halt_output ( int dev ) ;
static void waveartist_halt_input ( int dev ) ;
static void waveartist_halt ( int dev ) ;
static void waveartist_trigger ( int dev , int state ) ;
static int
waveartist_open ( int dev , int mode )
{
wavnc_info * devc ;
wavnc_port_info * portc ;
unsigned long flags ;
if ( dev < 0 | | dev > = num_audiodevs )
return - ENXIO ;
devc = ( wavnc_info * ) audio_devs [ dev ] - > devc ;
portc = ( wavnc_port_info * ) audio_devs [ dev ] - > portc ;
spin_lock_irqsave ( & waveartist_lock , flags ) ;
if ( portc - > open_mode | | ( devc - > open_mode & mode ) ) {
spin_unlock_irqrestore ( & waveartist_lock , flags ) ;
return - EBUSY ;
}
devc - > audio_mode = 0 ;
devc - > open_mode | = mode ;
portc - > open_mode = mode ;
waveartist_trigger ( dev , 0 ) ;
if ( mode & OPEN_READ )
devc - > record_dev = dev ;
if ( mode & OPEN_WRITE )
devc - > playback_dev = dev ;
spin_unlock_irqrestore ( & waveartist_lock , flags ) ;
return 0 ;
}
static void
waveartist_close ( int dev )
{
wavnc_info * devc = ( wavnc_info * ) audio_devs [ dev ] - > devc ;
wavnc_port_info * portc = ( wavnc_port_info * ) audio_devs [ dev ] - > portc ;
unsigned long flags ;
spin_lock_irqsave ( & waveartist_lock , flags ) ;
waveartist_halt ( dev ) ;
devc - > audio_mode = 0 ;
devc - > open_mode & = ~ portc - > open_mode ;
portc - > open_mode = 0 ;
spin_unlock_irqrestore ( & waveartist_lock , flags ) ;
}
static void
waveartist_output_block ( int dev , unsigned long buf , int __count , int intrflag )
{
wavnc_port_info * portc = ( wavnc_port_info * ) audio_devs [ dev ] - > portc ;
wavnc_info * devc = ( wavnc_info * ) audio_devs [ dev ] - > devc ;
unsigned long flags ;
unsigned int count = __count ;
if ( debug_flg & DEBUG_OUT )
printk ( " waveartist: output block, buf=0x%lx, count=0x%x... \n " ,
buf , count ) ;
/*
* 16 bit data
*/
if ( portc - > audio_format & ( AFMT_S16_LE | AFMT_S16_BE ) )
count > > = 1 ;
if ( portc - > channels > 1 )
count > > = 1 ;
count - = 1 ;
if ( devc - > audio_mode & PCM_ENABLE_OUTPUT & &
audio_devs [ dev ] - > flags & DMA_AUTOMODE & &
intrflag & &
count = = devc - > xfer_count ) {
devc - > audio_mode | = PCM_ENABLE_OUTPUT ;
return ; /*
* Auto DMA mode on . No need to react
*/
}
spin_lock_irqsave ( & waveartist_lock , flags ) ;
/*
* set sample count
*/
waveartist_cmd2 ( devc , WACMD_OUTPUTSIZE , count ) ;
devc - > xfer_count = count ;
devc - > audio_mode | = PCM_ENABLE_OUTPUT ;
spin_unlock_irqrestore ( & waveartist_lock , flags ) ;
}
static void
waveartist_start_input ( int dev , unsigned long buf , int __count , int intrflag )
{
wavnc_port_info * portc = ( wavnc_port_info * ) audio_devs [ dev ] - > portc ;
wavnc_info * devc = ( wavnc_info * ) audio_devs [ dev ] - > devc ;
unsigned long flags ;
unsigned int count = __count ;
if ( debug_flg & DEBUG_IN )
printk ( " waveartist: start input, buf=0x%lx, count=0x%x... \n " ,
buf , count ) ;
if ( portc - > audio_format & ( AFMT_S16_LE | AFMT_S16_BE ) ) /* 16 bit data */
count > > = 1 ;
if ( portc - > channels > 1 )
count > > = 1 ;
count - = 1 ;
if ( devc - > audio_mode & PCM_ENABLE_INPUT & &
audio_devs [ dev ] - > flags & DMA_AUTOMODE & &
intrflag & &
count = = devc - > xfer_count ) {
devc - > audio_mode | = PCM_ENABLE_INPUT ;
return ; /*
* Auto DMA mode on . No need to react
*/
}
spin_lock_irqsave ( & waveartist_lock , flags ) ;
/*
* set sample count
*/
waveartist_cmd2 ( devc , WACMD_INPUTSIZE , count ) ;
devc - > xfer_count = count ;
devc - > audio_mode | = PCM_ENABLE_INPUT ;
spin_unlock_irqrestore ( & waveartist_lock , flags ) ;
}
static int
waveartist_ioctl ( int dev , unsigned int cmd , void __user * arg )
{
return - EINVAL ;
}
static unsigned int
waveartist_get_speed ( wavnc_port_info * portc )
{
unsigned int speed ;
/*
* program the speed , channels , bits
*/
if ( portc - > speed = = 8000 )
speed = 0x2E71 ;
else if ( portc - > speed = = 11025 )
speed = 0x4000 ;
else if ( portc - > speed = = 22050 )
speed = 0x8000 ;
else if ( portc - > speed = = 44100 )
speed = 0x0 ;
else {
/*
* non - standard - just calculate
*/
speed = portc - > speed < < 16 ;
speed = ( speed / 44100 ) & 65535 ;
}
return speed ;
}
static unsigned int
waveartist_get_bits ( wavnc_port_info * portc )
{
unsigned int bits ;
if ( portc - > audio_format = = AFMT_S16_LE )
bits = 1 ;
else if ( portc - > audio_format = = AFMT_S8 )
bits = 0 ;
else
bits = 2 ; //default AFMT_U8
return bits ;
}
static int
waveartist_prepare_for_input ( int dev , int bsize , int bcount )
{
unsigned long flags ;
wavnc_info * devc = ( wavnc_info * ) audio_devs [ dev ] - > devc ;
wavnc_port_info * portc = ( wavnc_port_info * ) audio_devs [ dev ] - > portc ;
unsigned int speed , bits ;
if ( devc - > audio_mode )
return 0 ;
speed = waveartist_get_speed ( portc ) ;
bits = waveartist_get_bits ( portc ) ;
spin_lock_irqsave ( & waveartist_lock , flags ) ;
if ( waveartist_cmd2 ( devc , WACMD_INPUTFORMAT , bits ) )
printk ( KERN_WARNING " waveartist: error setting the "
" record format to %d \n " , portc - > audio_format ) ;
if ( waveartist_cmd2 ( devc , WACMD_INPUTCHANNELS , portc - > channels ) )
printk ( KERN_WARNING " waveartist: error setting record "
" to %d channels \n " , portc - > channels ) ;
/*
* write cmd SetSampleSpeedTimeConstant
*/
if ( waveartist_cmd2 ( devc , WACMD_INPUTSPEED , speed ) )
printk ( KERN_WARNING " waveartist: error setting the record "
" speed to %dHz. \n " , portc - > speed ) ;
if ( waveartist_cmd2 ( devc , WACMD_INPUTDMA , 1 ) )
printk ( KERN_WARNING " waveartist: error setting the record "
" data path to 0x%X \n " , 1 ) ;
if ( waveartist_cmd2 ( devc , WACMD_INPUTFORMAT , bits ) )
printk ( KERN_WARNING " waveartist: error setting the record "
" format to %d \n " , portc - > audio_format ) ;
devc - > xfer_count = 0 ;
spin_unlock_irqrestore ( & waveartist_lock , flags ) ;
waveartist_halt_input ( dev ) ;
if ( debug_flg & DEBUG_INTR ) {
printk ( " WA CTLR reg: 0x%02X. \n " ,
inb ( devc - > hw . io_base + CTLR ) ) ;
printk ( " WA STAT reg: 0x%02X. \n " ,
inb ( devc - > hw . io_base + STATR ) ) ;
printk ( " WA IRQS reg: 0x%02X. \n " ,
inb ( devc - > hw . io_base + IRQSTAT ) ) ;
}
return 0 ;
}
static int
waveartist_prepare_for_output ( int dev , int bsize , int bcount )
{
unsigned long flags ;
wavnc_info * devc = ( wavnc_info * ) audio_devs [ dev ] - > devc ;
wavnc_port_info * portc = ( wavnc_port_info * ) audio_devs [ dev ] - > portc ;
unsigned int speed , bits ;
/*
* program the speed , channels , bits
*/
speed = waveartist_get_speed ( portc ) ;
bits = waveartist_get_bits ( portc ) ;
spin_lock_irqsave ( & waveartist_lock , flags ) ;
if ( waveartist_cmd2 ( devc , WACMD_OUTPUTSPEED , speed ) & &
waveartist_cmd2 ( devc , WACMD_OUTPUTSPEED , speed ) )
printk ( KERN_WARNING " waveartist: error setting the playback "
" speed to %dHz. \n " , portc - > speed ) ;
if ( waveartist_cmd2 ( devc , WACMD_OUTPUTCHANNELS , portc - > channels ) )
printk ( KERN_WARNING " waveartist: error setting the playback "
" to %d channels \n " , portc - > channels ) ;
if ( waveartist_cmd2 ( devc , WACMD_OUTPUTDMA , 0 ) )
printk ( KERN_WARNING " waveartist: error setting the playback "
" data path to 0x%X \n " , 0 ) ;
if ( waveartist_cmd2 ( devc , WACMD_OUTPUTFORMAT , bits ) )
printk ( KERN_WARNING " waveartist: error setting the playback "
" format to %d \n " , portc - > audio_format ) ;
devc - > xfer_count = 0 ;
spin_unlock_irqrestore ( & waveartist_lock , flags ) ;
waveartist_halt_output ( dev ) ;
if ( debug_flg & DEBUG_INTR ) {
printk ( " WA CTLR reg: 0x%02X. \n " , inb ( devc - > hw . io_base + CTLR ) ) ;
printk ( " WA STAT reg: 0x%02X. \n " , inb ( devc - > hw . io_base + STATR ) ) ;
printk ( " WA IRQS reg: 0x%02X. \n " , inb ( devc - > hw . io_base + IRQSTAT ) ) ;
}
return 0 ;
}
static void
waveartist_halt ( int dev )
{
wavnc_port_info * portc = ( wavnc_port_info * ) audio_devs [ dev ] - > portc ;
wavnc_info * devc ;
if ( portc - > open_mode & OPEN_WRITE )
waveartist_halt_output ( dev ) ;
if ( portc - > open_mode & OPEN_READ )
waveartist_halt_input ( dev ) ;
devc = ( wavnc_info * ) audio_devs [ dev ] - > devc ;
devc - > audio_mode = 0 ;
}
static void
waveartist_halt_input ( int dev )
{
wavnc_info * devc = ( wavnc_info * ) audio_devs [ dev ] - > devc ;
unsigned long flags ;
spin_lock_irqsave ( & waveartist_lock , flags ) ;
/*
* Stop capture
*/
waveartist_cmd1 ( devc , WACMD_INPUTSTOP ) ;
devc - > audio_mode & = ~ PCM_ENABLE_INPUT ;
/*
* Clear interrupt by toggling
* the IRQ_ACK bit in CTRL
*/
if ( inb ( devc - > hw . io_base + STATR ) & IRQ_REQ )
waveartist_iack ( devc ) ;
// devc->audio_mode &= ~PCM_ENABLE_INPUT;
spin_unlock_irqrestore ( & waveartist_lock , flags ) ;
}
static void
waveartist_halt_output ( int dev )
{
wavnc_info * devc = ( wavnc_info * ) audio_devs [ dev ] - > devc ;
unsigned long flags ;
spin_lock_irqsave ( & waveartist_lock , flags ) ;
waveartist_cmd1 ( devc , WACMD_OUTPUTSTOP ) ;
devc - > audio_mode & = ~ PCM_ENABLE_OUTPUT ;
/*
* Clear interrupt by toggling
* the IRQ_ACK bit in CTRL
*/
if ( inb ( devc - > hw . io_base + STATR ) & IRQ_REQ )
waveartist_iack ( devc ) ;
// devc->audio_mode &= ~PCM_ENABLE_OUTPUT;
spin_unlock_irqrestore ( & waveartist_lock , flags ) ;
}
static void
waveartist_trigger ( int dev , int state )
{
wavnc_info * devc = ( wavnc_info * ) audio_devs [ dev ] - > devc ;
wavnc_port_info * portc = ( wavnc_port_info * ) audio_devs [ dev ] - > portc ;
unsigned long flags ;
if ( debug_flg & DEBUG_TRIGGER ) {
printk ( " wavnc: audio trigger " ) ;
if ( state & PCM_ENABLE_INPUT )
printk ( " in " ) ;
if ( state & PCM_ENABLE_OUTPUT )
printk ( " out " ) ;
printk ( " \n " ) ;
}
spin_lock_irqsave ( & waveartist_lock , flags ) ;
state & = devc - > audio_mode ;
if ( portc - > open_mode & OPEN_READ & &
state & PCM_ENABLE_INPUT )
/*
* enable ADC Data Transfer to PC
*/
waveartist_cmd1 ( devc , WACMD_INPUTSTART ) ;
if ( portc - > open_mode & OPEN_WRITE & &
state & PCM_ENABLE_OUTPUT )
/*
* enable DAC data transfer from PC
*/
waveartist_cmd1 ( devc , WACMD_OUTPUTSTART ) ;
spin_unlock_irqrestore ( & waveartist_lock , flags ) ;
}
static int
waveartist_set_speed ( int dev , int arg )
{
wavnc_port_info * portc = ( wavnc_port_info * ) audio_devs [ dev ] - > portc ;
if ( arg < = 0 )
return portc - > speed ;
if ( arg < 5000 )
arg = 5000 ;
if ( arg > 44100 )
arg = 44100 ;
portc - > speed = arg ;
return portc - > speed ;
}
static short
waveartist_set_channels ( int dev , short arg )
{
wavnc_port_info * portc = ( wavnc_port_info * ) audio_devs [ dev ] - > portc ;
if ( arg ! = 1 & & arg ! = 2 )
return portc - > channels ;
portc - > channels = arg ;
return arg ;
}
static unsigned int
waveartist_set_bits ( int dev , unsigned int arg )
{
wavnc_port_info * portc = ( wavnc_port_info * ) audio_devs [ dev ] - > portc ;
if ( arg = = 0 )
return portc - > audio_format ;
if ( ( arg ! = AFMT_U8 ) & & ( arg ! = AFMT_S16_LE ) & & ( arg ! = AFMT_S8 ) )
arg = AFMT_U8 ;
portc - > audio_format = arg ;
return arg ;
}
static struct audio_driver waveartist_audio_driver = {
. owner = THIS_MODULE ,
. open = waveartist_open ,
. close = waveartist_close ,
. output_block = waveartist_output_block ,
. start_input = waveartist_start_input ,
. ioctl = waveartist_ioctl ,
. prepare_for_input = waveartist_prepare_for_input ,
. prepare_for_output = waveartist_prepare_for_output ,
. halt_io = waveartist_halt ,
. halt_input = waveartist_halt_input ,
. halt_output = waveartist_halt_output ,
. trigger = waveartist_trigger ,
. set_speed = waveartist_set_speed ,
. set_bits = waveartist_set_bits ,
. set_channels = waveartist_set_channels
} ;
static irqreturn_t
waveartist_intr ( int irq , void * dev_id , struct pt_regs * regs )
{
wavnc_info * devc = ( wavnc_info * ) dev_id ;
int irqstatus , status ;
spin_lock ( & waveartist_lock ) ;
irqstatus = inb ( devc - > hw . io_base + IRQSTAT ) ;
status = inb ( devc - > hw . io_base + STATR ) ;
if ( debug_flg & DEBUG_INTR )
printk ( " waveartist_intr: stat=%02x, irqstat=%02x \n " ,
status , irqstatus ) ;
if ( status & IRQ_REQ ) /* Clear interrupt */
waveartist_iack ( devc ) ;
else
printk ( KERN_WARNING " waveartist: unexpected interrupt \n " ) ;
if ( irqstatus & 0x01 ) {
int temp = 1 ;
/* PCM buffer done
*/
if ( ( status & DMA0 ) & & ( devc - > audio_mode & PCM_ENABLE_OUTPUT ) ) {
DMAbuf_outputintr ( devc - > playback_dev , 1 ) ;
temp = 0 ;
}
if ( ( status & DMA1 ) & & ( devc - > audio_mode & PCM_ENABLE_INPUT ) ) {
DMAbuf_inputintr ( devc - > record_dev ) ;
temp = 0 ;
}
if ( temp ) //default:
printk ( KERN_WARNING " waveartist: Unknown interrupt \n " ) ;
}
if ( irqstatus & 0x2 )
// We do not use SB mode natively...
printk ( KERN_WARNING " waveartist: Unexpected SB interrupt... \n " ) ;
spin_unlock ( & waveartist_lock ) ;
return IRQ_HANDLED ;
}
/* -------------------------------------------------------------------------
* Mixer stuff
*/
struct mix_ent {
unsigned char reg_l ;
unsigned char reg_r ;
unsigned char shift ;
unsigned char max ;
} ;
static const struct mix_ent mix_devs [ SOUND_MIXER_NRDEVICES ] = {
{ 2 , 6 , 1 , 7 } , /* SOUND_MIXER_VOLUME */
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_BASS */
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_TREBLE */
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_SYNTH */
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_PCM */
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_SPEAKER */
{ 0 , 4 , 6 , 31 } , /* SOUND_MIXER_LINE */
{ 2 , 6 , 4 , 3 } , /* SOUND_MIXER_MIC */
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_CD */
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_IMIX */
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_ALTPCM */
#if 0
{ 3 , 7 , 0 , 10 } , /* SOUND_MIXER_RECLEV */
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_IGAIN */
# else
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_RECLEV */
{ 3 , 7 , 0 , 7 } , /* SOUND_MIXER_IGAIN */
# endif
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_OGAIN */
{ 0 , 4 , 1 , 31 } , /* SOUND_MIXER_LINE1 */
{ 1 , 5 , 6 , 31 } , /* SOUND_MIXER_LINE2 */
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_LINE3 */
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_DIGITAL1 */
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_DIGITAL2 */
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_DIGITAL3 */
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_PHONEIN */
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_PHONEOUT */
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_VIDEO */
{ 0 , 0 , 0 , 0 } , /* SOUND_MIXER_RADIO */
{ 0 , 0 , 0 , 0 } /* SOUND_MIXER_MONITOR */
} ;
static void
waveartist_mixer_update ( wavnc_info * devc , int whichDev )
{
unsigned int lev_left , lev_right ;
lev_left = devc - > levels [ whichDev ] & 0xff ;
lev_right = devc - > levels [ whichDev ] > > 8 ;
if ( lev_left > 100 )
lev_left = 100 ;
if ( lev_right > 100 )
lev_right = 100 ;
# define SCALE(lev,max) ((lev) * (max) / 100)
if ( machine_is_netwinder ( ) & & whichDev = = SOUND_MIXER_PHONEOUT )
whichDev = SOUND_MIXER_VOLUME ;
if ( mix_devs [ whichDev ] . reg_l | | mix_devs [ whichDev ] . reg_r ) {
const struct mix_ent * mix = mix_devs + whichDev ;
unsigned int mask , left , right ;
mask = mix - > max < < mix - > shift ;
lev_left = SCALE ( lev_left , mix - > max ) < < mix - > shift ;
lev_right = SCALE ( lev_right , mix - > max ) < < mix - > shift ;
/* read left setting */
left = waveartist_cmd1_r ( devc , WACMD_GET_LEVEL |
mix - > reg_l < < 8 ) ;
/* read right setting */
right = waveartist_cmd1_r ( devc , WACMD_GET_LEVEL |
mix - > reg_r < < 8 ) ;
left = ( left & ~ mask ) | ( lev_left & mask ) ;
right = ( right & ~ mask ) | ( lev_right & mask ) ;
/* write left,right back */
waveartist_cmd3 ( devc , WACMD_SET_MIXER , left , right ) ;
} else {
switch ( whichDev ) {
case SOUND_MIXER_PCM :
waveartist_cmd3 ( devc , WACMD_SET_LEVEL ,
SCALE ( lev_left , 32767 ) ,
SCALE ( lev_right , 32767 ) ) ;
break ;
case SOUND_MIXER_SYNTH :
waveartist_cmd3 ( devc , 0x0100 | WACMD_SET_LEVEL ,
SCALE ( lev_left , 32767 ) ,
SCALE ( lev_right , 32767 ) ) ;
break ;
}
}
}
/*
* Set the ADC MUX to the specified values . We do NOT do any
* checking of the values passed , since we assume that the
* relevant * _select_input function has done that for us .
*/
static void
waveartist_set_adc_mux ( wavnc_info * devc , char left_dev , char right_dev )
{
unsigned int reg_08 , reg_09 ;
reg_08 = waveartist_cmd1_r ( devc , WACMD_GET_LEVEL | 0x0800 ) ;
reg_09 = waveartist_cmd1_r ( devc , WACMD_GET_LEVEL | 0x0900 ) ;
reg_08 = ( reg_08 & ~ 0x3f ) | right_dev < < 3 | left_dev ;
waveartist_cmd3 ( devc , WACMD_SET_MIXER , reg_08 , reg_09 ) ;
}
/*
* Decode a recording mask into a mixer selection as follows :
*
* OSS Source WA Source Actual source
* SOUND_MASK_IMIX Mixer Mixer output ( same as AD1848 )
* SOUND_MASK_LINE Line Line in
* SOUND_MASK_LINE1 Aux 1 Aux 1 in
* SOUND_MASK_LINE2 Aux 2 Aux 2 in
* SOUND_MASK_MIC Mic Microphone
*/
static unsigned int
waveartist_select_input ( wavnc_info * devc , unsigned int recmask ,
unsigned char * dev_l , unsigned char * dev_r )
{
unsigned int recdev = ADC_MUX_NONE ;
if ( recmask & SOUND_MASK_IMIX ) {
recmask = SOUND_MASK_IMIX ;
recdev = ADC_MUX_MIXER ;
} else if ( recmask & SOUND_MASK_LINE2 ) {
recmask = SOUND_MASK_LINE2 ;
recdev = ADC_MUX_AUX2 ;
} else if ( recmask & SOUND_MASK_LINE1 ) {
recmask = SOUND_MASK_LINE1 ;
recdev = ADC_MUX_AUX1 ;
} else if ( recmask & SOUND_MASK_LINE ) {
recmask = SOUND_MASK_LINE ;
recdev = ADC_MUX_LINE ;
} else if ( recmask & SOUND_MASK_MIC ) {
recmask = SOUND_MASK_MIC ;
recdev = ADC_MUX_MIC ;
}
* dev_l = * dev_r = recdev ;
return recmask ;
}
static int
waveartist_decode_mixer ( wavnc_info * devc , int dev , unsigned char lev_l ,
unsigned char lev_r )
{
switch ( dev ) {
case SOUND_MIXER_VOLUME :
case SOUND_MIXER_SYNTH :
case SOUND_MIXER_PCM :
case SOUND_MIXER_LINE :
case SOUND_MIXER_MIC :
case SOUND_MIXER_IGAIN :
case SOUND_MIXER_LINE1 :
case SOUND_MIXER_LINE2 :
devc - > levels [ dev ] = lev_l | lev_r < < 8 ;
break ;
case SOUND_MIXER_IMIX :
break ;
default :
dev = - EINVAL ;
break ;
}
return dev ;
}
static int waveartist_get_mixer ( wavnc_info * devc , int dev )
{
return devc - > levels [ dev ] ;
}
static const struct waveartist_mixer_info waveartist_mixer = {
. supported_devs = SUPPORTED_MIXER_DEVICES | SOUND_MASK_IGAIN ,
. recording_devs = SOUND_MASK_LINE | SOUND_MASK_MIC |
SOUND_MASK_LINE1 | SOUND_MASK_LINE2 |
SOUND_MASK_IMIX ,
. stereo_devs = ( SUPPORTED_MIXER_DEVICES | SOUND_MASK_IGAIN ) & ~
( SOUND_MASK_SPEAKER | SOUND_MASK_IMIX ) ,
. select_input = waveartist_select_input ,
. decode_mixer = waveartist_decode_mixer ,
. get_mixer = waveartist_get_mixer ,
} ;
static void
waveartist_set_recmask ( wavnc_info * devc , unsigned int recmask )
{
unsigned char dev_l , dev_r ;
recmask & = devc - > mix - > recording_devs ;
/*
* If more than one recording device selected ,
* disable the device that is currently in use .
*/
if ( hweight32 ( recmask ) > 1 )
recmask & = ~ devc - > recmask ;
/*
* Translate the recording device mask into
* the ADC multiplexer settings .
*/
devc - > recmask = devc - > mix - > select_input ( devc , recmask ,
& dev_l , & dev_r ) ;
waveartist_set_adc_mux ( devc , dev_l , dev_r ) ;
}
static int
waveartist_set_mixer ( wavnc_info * devc , int dev , unsigned int level )
{
unsigned int lev_left = level & 0x00ff ;
unsigned int lev_right = ( level & 0xff00 ) > > 8 ;
if ( lev_left > 100 )
lev_left = 100 ;
if ( lev_right > 100 )
lev_right = 100 ;
/*
* Mono devices have their right volume forced to their
* left volume . ( from ALSA driver OSS emulation ) .
*/
if ( ! ( devc - > mix - > stereo_devs & ( 1 < < dev ) ) )
lev_right = lev_left ;
dev = devc - > mix - > decode_mixer ( devc , dev , lev_left , lev_right ) ;
if ( dev > = 0 )
waveartist_mixer_update ( devc , dev ) ;
return dev < 0 ? dev : 0 ;
}
static int
waveartist_mixer_ioctl ( int dev , unsigned int cmd , void __user * arg )
{
wavnc_info * devc = ( wavnc_info * ) audio_devs [ dev ] - > devc ;
int ret = 0 , val , nr ;
/*
* All SOUND_MIXER_ * ioctls use type ' M '
*/
if ( ( ( cmd > > 8 ) & 255 ) ! = ' M ' )
return - ENOIOCTLCMD ;
# ifdef CONFIG_ARCH_NETWINDER
if ( machine_is_netwinder ( ) ) {
ret = vnc_private_ioctl ( dev , cmd , arg ) ;
if ( ret ! = - ENOIOCTLCMD )
return ret ;
else
ret = 0 ;
}
# endif
nr = cmd & 0xff ;
if ( _SIOC_DIR ( cmd ) & _SIOC_WRITE ) {
if ( get_user ( val , ( int __user * ) arg ) )
return - EFAULT ;
switch ( nr ) {
case SOUND_MIXER_RECSRC :
waveartist_set_recmask ( devc , val ) ;
break ;
default :
ret = - EINVAL ;
if ( nr < SOUND_MIXER_NRDEVICES & &
devc - > mix - > supported_devs & ( 1 < < nr ) )
ret = waveartist_set_mixer ( devc , nr , val ) ;
}
}
if ( ret = = 0 & & _SIOC_DIR ( cmd ) & _SIOC_READ ) {
ret = - EINVAL ;
switch ( nr ) {
case SOUND_MIXER_RECSRC :
ret = devc - > recmask ;
break ;
case SOUND_MIXER_DEVMASK :
ret = devc - > mix - > supported_devs ;
break ;
case SOUND_MIXER_STEREODEVS :
ret = devc - > mix - > stereo_devs ;
break ;
case SOUND_MIXER_RECMASK :
ret = devc - > mix - > recording_devs ;
break ;
case SOUND_MIXER_CAPS :
ret = SOUND_CAP_EXCL_INPUT ;
break ;
default :
if ( nr < SOUND_MIXER_NRDEVICES )
ret = devc - > mix - > get_mixer ( devc , nr ) ;
break ;
}
if ( ret > = 0 )
ret = put_user ( ret , ( int __user * ) arg ) ? - EFAULT : 0 ;
}
return ret ;
}
static struct mixer_operations waveartist_mixer_operations =
{
. owner = THIS_MODULE ,
. id = " WaveArtist " ,
. name = " WaveArtist " ,
. ioctl = waveartist_mixer_ioctl
} ;
static void
waveartist_mixer_reset ( wavnc_info * devc )
{
int i ;
if ( debug_flg & DEBUG_MIXER )
printk ( " %s: mixer_reset \n " , devc - > hw . name ) ;
/*
* reset mixer cmd
*/
waveartist_cmd1 ( devc , WACMD_RST_MIXER ) ;
/*
* set input for ADC to come from ' quiet '
* turn on default modes
*/
waveartist_cmd3 ( devc , WACMD_SET_MIXER , 0x9800 , 0xa836 ) ;
/*
* set mixer input select to none , RX filter gains 0 dB
*/
waveartist_cmd3 ( devc , WACMD_SET_MIXER , 0x4c00 , 0x8c00 ) ;
/*
* set bit 0 reg 2 to 1 - unmute MonoOut
*/
waveartist_cmd3 ( devc , WACMD_SET_MIXER , 0x2801 , 0x6800 ) ;
/* set default input device = internal mic
* current recording device = none
*/
waveartist_set_recmask ( devc , 0 ) ;
for ( i = 0 ; i < SOUND_MIXER_NRDEVICES ; i + + )
waveartist_mixer_update ( devc , i ) ;
}
static int __init waveartist_init ( wavnc_info * devc )
{
wavnc_port_info * portc ;
char rev [ 3 ] , dev_name [ 64 ] ;
int my_dev ;
if ( waveartist_reset ( devc ) )
return - ENODEV ;
sprintf ( dev_name , " %s (%s " , devc - > hw . name , devc - > chip_name ) ;
if ( waveartist_getrev ( devc , rev ) ) {
strcat ( dev_name , " rev. " ) ;
strcat ( dev_name , rev ) ;
}
strcat ( dev_name , " ) " ) ;
conf_printf2 ( dev_name , devc - > hw . io_base , devc - > hw . irq ,
devc - > hw . dma , devc - > hw . dma2 ) ;
portc = ( wavnc_port_info * ) kmalloc ( sizeof ( wavnc_port_info ) , GFP_KERNEL ) ;
if ( portc = = NULL )
goto nomem ;
memset ( portc , 0 , sizeof ( wavnc_port_info ) ) ;
my_dev = sound_install_audiodrv ( AUDIO_DRIVER_VERSION , dev_name ,
& waveartist_audio_driver , sizeof ( struct audio_driver ) ,
devc - > audio_flags , AFMT_U8 | AFMT_S16_LE | AFMT_S8 ,
devc , devc - > hw . dma , devc - > hw . dma2 ) ;
if ( my_dev < 0 )
goto free ;
audio_devs [ my_dev ] - > portc = portc ;
waveartist_mixer_reset ( devc ) ;
/*
* clear any pending interrupt
*/
waveartist_iack ( devc ) ;
if ( request_irq ( devc - > hw . irq , waveartist_intr , 0 , devc - > hw . name , devc ) < 0 ) {
printk ( KERN_ERR " %s: IRQ %d in use \n " ,
devc - > hw . name , devc - > hw . irq ) ;
goto uninstall ;
}
if ( sound_alloc_dma ( devc - > hw . dma , devc - > hw . name ) ) {
printk ( KERN_ERR " %s: Can't allocate DMA%d \n " ,
devc - > hw . name , devc - > hw . dma ) ;
goto uninstall_irq ;
}
if ( devc - > hw . dma ! = devc - > hw . dma2 & & devc - > hw . dma2 ! = NO_DMA )
if ( sound_alloc_dma ( devc - > hw . dma2 , devc - > hw . name ) ) {
printk ( KERN_ERR " %s: can't allocate DMA%d \n " ,
devc - > hw . name , devc - > hw . dma2 ) ;
goto uninstall_dma ;
}
waveartist_set_ctlr ( & devc - > hw , 0 , DMA1_IE | DMA0_IE ) ;
audio_devs [ my_dev ] - > mixer_dev =
sound_install_mixer ( MIXER_DRIVER_VERSION ,
dev_name ,
& waveartist_mixer_operations ,
sizeof ( struct mixer_operations ) ,
devc ) ;
return my_dev ;
uninstall_dma :
sound_free_dma ( devc - > hw . dma ) ;
uninstall_irq :
free_irq ( devc - > hw . irq , devc ) ;
uninstall :
sound_unload_audiodev ( my_dev ) ;
free :
kfree ( portc ) ;
nomem :
return - 1 ;
}
static int __init probe_waveartist ( struct address_info * hw_config )
{
wavnc_info * devc = & adev_info [ nr_waveartist_devs ] ;
if ( nr_waveartist_devs > = MAX_AUDIO_DEV ) {
printk ( KERN_WARNING " waveartist: too many audio devices \n " ) ;
return 0 ;
}
if ( ! request_region ( hw_config - > io_base , 15 , hw_config - > name ) ) {
printk ( KERN_WARNING " WaveArtist: I/O port conflict \n " ) ;
return 0 ;
}
if ( hw_config - > irq > 15 | | hw_config - > irq < 0 ) {
release_region ( hw_config - > io_base , 15 ) ;
printk ( KERN_WARNING " WaveArtist: Bad IRQ %d \n " ,
hw_config - > irq ) ;
return 0 ;
}
if ( hw_config - > dma ! = 3 ) {
release_region ( hw_config - > io_base , 15 ) ;
printk ( KERN_WARNING " WaveArtist: Bad DMA %d \n " ,
hw_config - > dma ) ;
return 0 ;
}
hw_config - > name = " WaveArtist " ;
devc - > hw = * hw_config ;
devc - > open_mode = 0 ;
devc - > chip_name = " RWA-010 " ;
return 1 ;
}
static void __init
attach_waveartist ( struct address_info * hw , const struct waveartist_mixer_info * mix )
{
wavnc_info * devc = & adev_info [ nr_waveartist_devs ] ;
/*
* NOTE ! If irq < 0 , there is another driver which has allocated the
* IRQ so that this driver doesn ' t need to allocate / deallocate it .
* The actually used IRQ is ABS ( irq ) .
*/
devc - > hw = * hw ;
devc - > hw . irq = ( hw - > irq > 0 ) ? hw - > irq : 0 ;
devc - > open_mode = 0 ;
devc - > playback_dev = 0 ;
devc - > record_dev = 0 ;
devc - > audio_flags = DMA_AUTOMODE ;
devc - > levels = levels ;
if ( hw - > dma ! = hw - > dma2 & & hw - > dma2 ! = NO_DMA )
devc - > audio_flags | = DMA_DUPLEX ;
devc - > mix = mix ;
devc - > dev_no = waveartist_init ( devc ) ;
if ( devc - > dev_no < 0 )
release_region ( hw - > io_base , 15 ) ;
else {
# ifdef CONFIG_ARCH_NETWINDER
if ( machine_is_netwinder ( ) ) {
init_timer ( & vnc_timer ) ;
vnc_timer . function = vnc_slider_tick ;
vnc_timer . expires = jiffies ;
vnc_timer . data = nr_waveartist_devs ;
add_timer ( & vnc_timer ) ;
vnc_configure_mixer ( devc , 0 ) ;
devc - > no_autoselect = 1 ;
}
# endif
nr_waveartist_devs + = 1 ;
}
}
static void __exit unload_waveartist ( struct address_info * hw )
{
wavnc_info * devc = NULL ;
int i ;
for ( i = 0 ; i < nr_waveartist_devs ; i + + )
if ( hw - > io_base = = adev_info [ i ] . hw . io_base ) {
devc = adev_info + i ;
break ;
}
if ( devc ! = NULL ) {
int mixer ;
# ifdef CONFIG_ARCH_NETWINDER
if ( machine_is_netwinder ( ) )
del_timer ( & vnc_timer ) ;
# endif
release_region ( devc - > hw . io_base , 15 ) ;
waveartist_set_ctlr ( & devc - > hw , DMA1_IE | DMA0_IE , 0 ) ;
if ( devc - > hw . irq > = 0 )
free_irq ( devc - > hw . irq , devc ) ;
sound_free_dma ( devc - > hw . dma ) ;
if ( devc - > hw . dma ! = devc - > hw . dma2 & &
devc - > hw . dma2 ! = NO_DMA )
sound_free_dma ( devc - > hw . dma2 ) ;
mixer = audio_devs [ devc - > dev_no ] - > mixer_dev ;
if ( mixer > = 0 )
sound_unload_mixerdev ( mixer ) ;
if ( devc - > dev_no > = 0 )
sound_unload_audiodev ( devc - > dev_no ) ;
nr_waveartist_devs - = 1 ;
for ( ; i < nr_waveartist_devs ; i + + )
adev_info [ i ] = adev_info [ i + 1 ] ;
} else
printk ( KERN_WARNING " waveartist: can't find device "
" to unload \n " ) ;
}
# ifdef CONFIG_ARCH_NETWINDER
/*
* Rebel . com Netwinder specifics . . .
*/
# include <asm/hardware/dec21285.h>
# define VNC_TIMER_PERIOD (HZ / 4) //check slider 4 times/sec
# define MIXER_PRIVATE3_RESET 0x53570000
# define MIXER_PRIVATE3_READ 0x53570001
# define MIXER_PRIVATE3_WRITE 0x53570002
# define VNC_MUTE_INTERNAL_SPKR 0x01 //the sw mute on/off control bit
# define VNC_MUTE_LINE_OUT 0x10
# define VNC_PHONE_DETECT 0x20
# define VNC_HANDSET_DETECT 0x40
# define VNC_DISABLE_AUTOSWITCH 0x80
extern spinlock_t gpio_lock ;
static inline void
vnc_mute_spkr ( wavnc_info * devc )
{
unsigned long flags ;
spin_lock_irqsave ( & gpio_lock , flags ) ;
cpld_modify ( CPLD_UNMUTE , devc - > spkr_mute_state ? 0 : CPLD_UNMUTE ) ;
spin_unlock_irqrestore ( & gpio_lock , flags ) ;
}
static void
vnc_mute_lout ( wavnc_info * devc )
{
unsigned int left , right ;
left = waveartist_cmd1_r ( devc , WACMD_GET_LEVEL ) ;
right = waveartist_cmd1_r ( devc , WACMD_GET_LEVEL | 0x400 ) ;
if ( devc - > line_mute_state ) {
left & = ~ 1 ;
right & = ~ 1 ;
} else {
left | = 1 ;
right | = 1 ;
}
waveartist_cmd3 ( devc , WACMD_SET_MIXER , left , right ) ;
}
static int
vnc_volume_slider ( wavnc_info * devc )
{
static signed int old_slider_volume ;
unsigned long flags ;
signed int volume = 255 ;
* CSR_TIMER1_LOAD = 0x00ffffff ;
spin_lock_irqsave ( & waveartist_lock , flags ) ;
outb ( 0xFF , 0x201 ) ;
* CSR_TIMER1_CNTL = TIMER_CNTL_ENABLE | TIMER_CNTL_DIV1 ;
while ( volume & & ( inb ( 0x201 ) & 0x01 ) )
volume - - ;
* CSR_TIMER1_CNTL = 0 ;
spin_unlock_irqrestore ( & waveartist_lock , flags ) ;
volume = 0x00ffffff - * CSR_TIMER1_VALUE ;
# ifndef REVERSE
volume = 150 - ( volume > > 5 ) ;
# else
volume = ( volume > > 6 ) - 25 ;
# endif
if ( volume < 0 )
volume = 0 ;
if ( volume > 100 )
volume = 100 ;
/*
* slider quite often reads + - 8 , so debounce this random noise
*/
if ( abs ( volume - old_slider_volume ) > 7 ) {
old_slider_volume = volume ;
if ( debug_flg & DEBUG_MIXER )
printk ( KERN_DEBUG " Slider volume: %d. \n " , volume ) ;
}
return old_slider_volume ;
}
/*
* Decode a recording mask into a mixer selection on the NetWinder
* as follows :
*
* OSS Source WA Source Actual source
* SOUND_MASK_IMIX Mixer Mixer output ( same as AD1848 )
* SOUND_MASK_LINE Line Line in
* SOUND_MASK_LINE1 Left Mic Handset
* SOUND_MASK_PHONEIN Left Aux Telephone microphone
* SOUND_MASK_MIC Right Mic Builtin microphone
*/
static unsigned int
netwinder_select_input ( wavnc_info * devc , unsigned int recmask ,
unsigned char * dev_l , unsigned char * dev_r )
{
unsigned int recdev_l = ADC_MUX_NONE , recdev_r = ADC_MUX_NONE ;
if ( recmask & SOUND_MASK_IMIX ) {
recmask = SOUND_MASK_IMIX ;
recdev_l = ADC_MUX_MIXER ;
recdev_r = ADC_MUX_MIXER ;
} else if ( recmask & SOUND_MASK_LINE ) {
recmask = SOUND_MASK_LINE ;
recdev_l = ADC_MUX_LINE ;
recdev_r = ADC_MUX_LINE ;
} else if ( recmask & SOUND_MASK_LINE1 ) {
recmask = SOUND_MASK_LINE1 ;
waveartist_cmd1 ( devc , WACMD_SET_MONO ) ; /* left */
recdev_l = ADC_MUX_MIC ;
recdev_r = ADC_MUX_NONE ;
} else if ( recmask & SOUND_MASK_PHONEIN ) {
recmask = SOUND_MASK_PHONEIN ;
waveartist_cmd1 ( devc , WACMD_SET_MONO ) ; /* left */
recdev_l = ADC_MUX_AUX1 ;
recdev_r = ADC_MUX_NONE ;
} else if ( recmask & SOUND_MASK_MIC ) {
recmask = SOUND_MASK_MIC ;
waveartist_cmd1 ( devc , WACMD_SET_MONO | 0x100 ) ; /* right */
recdev_l = ADC_MUX_NONE ;
recdev_r = ADC_MUX_MIC ;
}
* dev_l = recdev_l ;
* dev_r = recdev_r ;
return recmask ;
}
static int
netwinder_decode_mixer ( wavnc_info * devc , int dev , unsigned char lev_l ,
unsigned char lev_r )
{
switch ( dev ) {
case SOUND_MIXER_VOLUME :
case SOUND_MIXER_SYNTH :
case SOUND_MIXER_PCM :
case SOUND_MIXER_LINE :
case SOUND_MIXER_IGAIN :
devc - > levels [ dev ] = lev_l | lev_r < < 8 ;
break ;
case SOUND_MIXER_MIC : /* right mic only */
devc - > levels [ SOUND_MIXER_MIC ] & = 0xff ;
devc - > levels [ SOUND_MIXER_MIC ] | = lev_l < < 8 ;
break ;
case SOUND_MIXER_LINE1 : /* left mic only */
devc - > levels [ SOUND_MIXER_MIC ] & = 0xff00 ;
devc - > levels [ SOUND_MIXER_MIC ] | = lev_l ;
dev = SOUND_MIXER_MIC ;
break ;
case SOUND_MIXER_PHONEIN : /* left aux only */
devc - > levels [ SOUND_MIXER_LINE1 ] = lev_l ;
dev = SOUND_MIXER_LINE1 ;
break ;
case SOUND_MIXER_IMIX :
case SOUND_MIXER_PHONEOUT :
break ;
default :
dev = - EINVAL ;
break ;
}
return dev ;
}
static int netwinder_get_mixer ( wavnc_info * devc , int dev )
{
int levels ;
switch ( dev ) {
case SOUND_MIXER_VOLUME :
case SOUND_MIXER_SYNTH :
case SOUND_MIXER_PCM :
case SOUND_MIXER_LINE :
case SOUND_MIXER_IGAIN :
levels = devc - > levels [ dev ] ;
break ;
case SOUND_MIXER_MIC : /* builtin mic: right mic only */
levels = devc - > levels [ SOUND_MIXER_MIC ] > > 8 ;
levels | = levels < < 8 ;
break ;
case SOUND_MIXER_LINE1 : /* handset mic: left mic only */
levels = devc - > levels [ SOUND_MIXER_MIC ] & 0xff ;
levels | = levels < < 8 ;
break ;
case SOUND_MIXER_PHONEIN : /* phone mic: left aux1 only */
levels = devc - > levels [ SOUND_MIXER_LINE1 ] & 0xff ;
levels | = levels < < 8 ;
break ;
default :
levels = 0 ;
}
return levels ;
}
/*
* Waveartist specific mixer information .
*/
static const struct waveartist_mixer_info netwinder_mixer = {
. supported_devs = SOUND_MASK_VOLUME | SOUND_MASK_SYNTH |
SOUND_MASK_PCM | SOUND_MASK_SPEAKER |
SOUND_MASK_LINE | SOUND_MASK_MIC |
SOUND_MASK_IMIX | SOUND_MASK_LINE1 |
SOUND_MASK_PHONEIN | SOUND_MASK_PHONEOUT |
SOUND_MASK_IGAIN ,
. recording_devs = SOUND_MASK_LINE | SOUND_MASK_MIC |
SOUND_MASK_IMIX | SOUND_MASK_LINE1 |
SOUND_MASK_PHONEIN ,
. stereo_devs = SOUND_MASK_VOLUME | SOUND_MASK_SYNTH |
SOUND_MASK_PCM | SOUND_MASK_LINE |
SOUND_MASK_IMIX | SOUND_MASK_IGAIN ,
. select_input = netwinder_select_input ,
. decode_mixer = netwinder_decode_mixer ,
. get_mixer = netwinder_get_mixer ,
} ;
static void
vnc_configure_mixer ( wavnc_info * devc , unsigned int recmask )
{
if ( ! devc - > no_autoselect ) {
if ( devc - > handset_detect ) {
recmask = SOUND_MASK_LINE1 ;
devc - > spkr_mute_state = devc - > line_mute_state = 1 ;
} else if ( devc - > telephone_detect ) {
recmask = SOUND_MASK_PHONEIN ;
devc - > spkr_mute_state = devc - > line_mute_state = 1 ;
} else {
/* unless someone has asked for LINE-IN,
* we default to MIC
*/
if ( ( devc - > recmask & SOUND_MASK_LINE ) = = 0 )
devc - > recmask = SOUND_MASK_MIC ;
devc - > spkr_mute_state = devc - > line_mute_state = 0 ;
}
vnc_mute_spkr ( devc ) ;
vnc_mute_lout ( devc ) ;
if ( recmask ! = devc - > recmask )
waveartist_set_recmask ( devc , recmask ) ;
}
}
static int
vnc_slider ( wavnc_info * devc )
{
signed int slider_volume ;
unsigned int temp , old_hs , old_td ;
/*
* read the " buttons " state .
* Bit 4 = 0 means handset present
* Bit 5 = 1 means phone offhook
*/
temp = inb ( 0x201 ) ;
old_hs = devc - > handset_detect ;
old_td = devc - > telephone_detect ;
devc - > handset_detect = ! ( temp & 0x10 ) ;
devc - > telephone_detect = ! ! ( temp & 0x20 ) ;
if ( ! devc - > no_autoselect & &
( old_hs ! = devc - > handset_detect | |
old_td ! = devc - > telephone_detect ) )
vnc_configure_mixer ( devc , devc - > recmask ) ;
slider_volume = vnc_volume_slider ( devc ) ;
/*
* If we ' re using software controlled volume , and
* the slider moves by more than 20 % , then we
* switch back to slider controlled volume .
*/
if ( abs ( devc - > slider_vol - slider_volume ) > 20 )
devc - > use_slider = 1 ;
/*
* use only left channel
*/
temp = levels [ SOUND_MIXER_VOLUME ] & 0xFF ;
if ( slider_volume ! = temp & & devc - > use_slider ) {
devc - > slider_vol = slider_volume ;
waveartist_set_mixer ( devc , SOUND_MIXER_VOLUME ,
slider_volume | slider_volume < < 8 ) ;
return 1 ;
}
return 0 ;
}
static void
vnc_slider_tick ( unsigned long data )
{
int next_timeout ;
if ( vnc_slider ( adev_info + data ) )
next_timeout = 5 ; // mixer reported change
else
next_timeout = VNC_TIMER_PERIOD ;
mod_timer ( & vnc_timer , jiffies + next_timeout ) ;
}
static int
vnc_private_ioctl ( int dev , unsigned int cmd , int __user * arg )
{
wavnc_info * devc = ( wavnc_info * ) audio_devs [ dev ] - > devc ;
int val ;
switch ( cmd ) {
case SOUND_MIXER_PRIVATE1 :
{
u_int prev_spkr_mute , prev_line_mute , prev_auto_state ;
int val ;
if ( get_user ( val , arg ) )
return - EFAULT ;
/* check if parameter is logical */
if ( val & ~ ( VNC_MUTE_INTERNAL_SPKR |
VNC_MUTE_LINE_OUT |
VNC_DISABLE_AUTOSWITCH ) )
return - EINVAL ;
prev_auto_state = devc - > no_autoselect ;
prev_spkr_mute = devc - > spkr_mute_state ;
prev_line_mute = devc - > line_mute_state ;
devc - > no_autoselect = ( val & VNC_DISABLE_AUTOSWITCH ) ? 1 : 0 ;
devc - > spkr_mute_state = ( val & VNC_MUTE_INTERNAL_SPKR ) ? 1 : 0 ;
devc - > line_mute_state = ( val & VNC_MUTE_LINE_OUT ) ? 1 : 0 ;
if ( prev_spkr_mute ! = devc - > spkr_mute_state )
vnc_mute_spkr ( devc ) ;
if ( prev_line_mute ! = devc - > line_mute_state )
vnc_mute_lout ( devc ) ;
if ( prev_auto_state ! = devc - > no_autoselect )
vnc_configure_mixer ( devc , devc - > recmask ) ;
return 0 ;
}
case SOUND_MIXER_PRIVATE2 :
if ( get_user ( val , arg ) )
return - EFAULT ;
switch ( val ) {
# define VNC_SOUND_PAUSE 0x53 //to pause the DSP
# define VNC_SOUND_RESUME 0x57 //to unpause the DSP
case VNC_SOUND_PAUSE :
waveartist_cmd1 ( devc , 0x16 ) ;
break ;
case VNC_SOUND_RESUME :
waveartist_cmd1 ( devc , 0x18 ) ;
break ;
default :
return - EINVAL ;
}
return 0 ;
/* private ioctl to allow bulk access to waveartist */
case SOUND_MIXER_PRIVATE3 :
{
unsigned long flags ;
int mixer_reg [ 15 ] , i , val ;
if ( get_user ( val , arg ) )
return - EFAULT ;
if ( copy_from_user ( mixer_reg , ( void * ) val , sizeof ( mixer_reg ) ) )
return - EFAULT ;
switch ( mixer_reg [ 14 ] ) {
case MIXER_PRIVATE3_RESET :
waveartist_mixer_reset ( devc ) ;
break ;
case MIXER_PRIVATE3_WRITE :
waveartist_cmd3 ( devc , WACMD_SET_MIXER , mixer_reg [ 0 ] , mixer_reg [ 4 ] ) ;
waveartist_cmd3 ( devc , WACMD_SET_MIXER , mixer_reg [ 1 ] , mixer_reg [ 5 ] ) ;
waveartist_cmd3 ( devc , WACMD_SET_MIXER , mixer_reg [ 2 ] , mixer_reg [ 6 ] ) ;
waveartist_cmd3 ( devc , WACMD_SET_MIXER , mixer_reg [ 3 ] , mixer_reg [ 7 ] ) ;
waveartist_cmd3 ( devc , WACMD_SET_MIXER , mixer_reg [ 8 ] , mixer_reg [ 9 ] ) ;
waveartist_cmd3 ( devc , WACMD_SET_LEVEL , mixer_reg [ 10 ] , mixer_reg [ 11 ] ) ;
waveartist_cmd3 ( devc , WACMD_SET_LEVEL , mixer_reg [ 12 ] , mixer_reg [ 13 ] ) ;
break ;
case MIXER_PRIVATE3_READ :
spin_lock_irqsave ( & waveartist_lock , flags ) ;
for ( i = 0x30 ; i < 14 < < 8 ; i + = 1 < < 8 )
waveartist_cmd ( devc , 1 , & i , 1 , mixer_reg + ( i > > 8 ) ) ;
spin_unlock_irqrestore ( & waveartist_lock , flags ) ;
if ( copy_to_user ( ( void * ) val , mixer_reg , sizeof ( mixer_reg ) ) )
return - EFAULT ;
break ;
default :
return - EINVAL ;
}
return 0 ;
}
/* read back the state from PRIVATE1 */
case SOUND_MIXER_PRIVATE4 :
val = ( devc - > spkr_mute_state ? VNC_MUTE_INTERNAL_SPKR : 0 ) |
( devc - > line_mute_state ? VNC_MUTE_LINE_OUT : 0 ) |
( devc - > handset_detect ? VNC_HANDSET_DETECT : 0 ) |
( devc - > telephone_detect ? VNC_PHONE_DETECT : 0 ) |
( devc - > no_autoselect ? VNC_DISABLE_AUTOSWITCH : 0 ) ;
return put_user ( val , arg ) ? - EFAULT : 0 ;
}
if ( _SIOC_DIR ( cmd ) & _SIOC_WRITE ) {
/*
* special case for master volume : if we
* received this call - switch from hw
* volume control to a software volume
* control , till the hw volume is modified
* to signal that user wants to be back in
* hardware . . .
*/
if ( ( cmd & 0xff ) = = SOUND_MIXER_VOLUME )
devc - > use_slider = 0 ;
/* speaker output */
if ( ( cmd & 0xff ) = = SOUND_MIXER_SPEAKER ) {
unsigned int val , l , r ;
if ( get_user ( val , arg ) )
return - EFAULT ;
l = val & 0x7f ;
r = ( val & 0x7f00 ) > > 8 ;
val = ( l + r ) / 2 ;
devc - > levels [ SOUND_MIXER_SPEAKER ] = val | ( val < < 8 ) ;
devc - > spkr_mute_state = ( val < = 50 ) ;
vnc_mute_spkr ( devc ) ;
return 0 ;
}
}
return - ENOIOCTLCMD ;
}
# endif
static struct address_info cfg ;
static int attached ;
static int __initdata io = 0 ;
static int __initdata irq = 0 ;
static int __initdata dma = 0 ;
static int __initdata dma2 = 0 ;
static int __init init_waveartist ( void )
{
const struct waveartist_mixer_info * mix ;
if ( ! io & & machine_is_netwinder ( ) ) {
/*
* The NetWinder WaveArtist is at a fixed address .
* If the user does not supply an address , use the
* well - known parameters .
*/
io = 0x250 ;
irq = 12 ;
dma = 3 ;
dma2 = 7 ;
}
mix = & waveartist_mixer ;
# ifdef CONFIG_ARCH_NETWINDER
if ( machine_is_netwinder ( ) )
mix = & netwinder_mixer ;
# endif
cfg . io_base = io ;
cfg . irq = irq ;
cfg . dma = dma ;
cfg . dma2 = dma2 ;
if ( ! probe_waveartist ( & cfg ) )
return - ENODEV ;
attach_waveartist ( & cfg , mix ) ;
attached = 1 ;
return 0 ;
}
static void __exit cleanup_waveartist ( void )
{
if ( attached )
unload_waveartist ( & cfg ) ;
}
module_init ( init_waveartist ) ;
module_exit ( cleanup_waveartist ) ;
# ifndef MODULE
static int __init setup_waveartist ( 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 ( " waveartist= " , setup_waveartist ) ;
# endif
MODULE_DESCRIPTION ( " Rockwell WaveArtist RWA-010 sound driver " ) ;
2006-03-25 03:07:05 -08:00
module_param ( io , int , 0 ) ; /* IO base */
module_param ( irq , int , 0 ) ; /* IRQ */
module_param ( dma , int , 0 ) ; /* DMA */
module_param ( dma2 , int , 0 ) ; /* DMA2 */
2005-04-16 15:20:36 -07:00
MODULE_LICENSE ( " GPL " ) ;