2005-04-16 15:20:36 -07:00
/*
* Sound driver for Silicon Graphics 320 and 540 Visual Workstations '
* onboard audio . See notes in Documentation / sound / oss / vwsnd .
*
* Copyright 1999 Silicon Graphics , Inc . All rights reserved .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# undef VWSND_DEBUG /* define for debugging */
/*
* XXX to do -
*
* External sync .
* Rename swbuf , hwbuf , u & i , hwptr & swptr to something rational .
* Bug - if select ( ) called before read ( ) , pcm_setup ( ) not called .
* Bug - output doesn ' t stop soon enough if process killed .
*/
/*
* Things to test -
*
* Will readv / writev work ? Write a test .
*
* insmod / rmmod 100 million times .
*
* Run I / O until int ptrs wrap around ( roughly 6.2 hours @ DAT
* rate ) .
*
* Concurrent threads banging on mixer simultaneously , both UP
* and SMP kernels . Especially , watch for thread A changing
* OUTSRC while thread B changes gain - - both write to the same
* ad1843 register .
*
* What happens if a client opens / dev / audio then forks ?
* Do two procs have / dev / audio open ? Test .
*
* Pump audio through the CD , MIC and line inputs and verify that
* they mix / mute into the output .
*
* Apps :
* amp
* mpg123
* x11amp
* mxv
* kmedia
* esound
* need more input apps
*
* Run tests while bombarding with signals . setitimer ( 2 ) will do it . . . */
/*
* This driver is organized in nine sections .
* The nine sections are :
*
* debug stuff
* low level lithium access
* high level lithium access
* AD1843 access
* PCM I / O
* audio driver
* mixer driver
* probe / attach / unload
* initialization and loadable kernel module interface
*
* That is roughly the order of increasing abstraction , so forward
* dependencies are minimal .
*/
/*
* Locking Notes
*
* INC_USE_COUNT and DEC_USE_COUNT keep track of the number of
* open descriptors to this driver . They store it in vwsnd_use_count .
* The global device list , vwsnd_dev_list , is immutable when the IN_USE
* is true .
*
* devc - > open_lock is a semaphore that is used to enforce the
* single reader / single writer rule for / dev / audio . The rule is
* that each device may have at most one reader and one writer .
* Open will block until the previous client has closed the
* device , unless O_NONBLOCK is specified .
*
2006-03-23 03:00:39 -08:00
* The semaphore devc - > io_mutex serializes PCM I / O syscalls . This
2005-04-16 15:20:36 -07:00
* is unnecessary in Linux 2.2 , because the kernel lock
* serializes read , write , and ioctl globally , but it ' s there ,
* ready for the brave , new post - kernel - lock world .
*
* Locking between interrupt and baselevel is handled by the
* " lock " spinlock in vwsnd_port ( one lock each for read and
* write ) . Each half holds the lock just long enough to see what
* area it owns and update its pointers . See pcm_output ( ) and
* pcm_input ( ) for most of the gory stuff .
*
2006-03-23 03:00:39 -08:00
* devc - > mix_mutex serializes all mixer ioctls . This is also
2005-04-16 15:20:36 -07:00
* redundant because of the kernel lock .
*
* The lowest level lock is lith - > lithium_lock . It is a
* spinlock which is held during the two - register tango of
* reading / writing an AD1843 register . See
* li_ { read , write } _ad1843_reg ( ) .
*/
/*
* Sample Format Notes
*
* Lithium ' s DMA engine has two formats : 16 - bit 2 ' s complement
* and 8 - bit unsigned . 16 - bit transfers the data unmodified , 2
* bytes per sample . 8 - bit unsigned transfers 1 byte per sample
* and XORs each byte with 0x80 . Lithium can input or output
* either mono or stereo in either format .
*
* The AD1843 has four formats : 16 - bit 2 ' s complement , 8 - bit
* unsigned , 8 - bit mu - Law and 8 - bit A - Law .
*
* This driver supports five formats : AFMT_S8 , AFMT_U8 ,
* AFMT_MU_LAW , AFMT_A_LAW , and AFMT_S16_LE .
*
* For AFMT_U8 output , we keep the AD1843 in 16 - bit mode , and
* rely on Lithium ' s XOR to translate between U8 and S8 .
*
* For AFMT_S8 , AFMT_MU_LAW and AFMT_A_LAW output , we have to XOR
* the 0x80 bit in software to compensate for Lithium ' s XOR .
* This happens in pcm_copy_ { in , out } ( ) .
*
* Changes :
* 11 - 10 - 2000 Bartlomiej Zolnierkiewicz < bkz @ linux - ide . org >
* Added some __init / __exit
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/spinlock.h>
# include <linux/smp_lock.h>
# include <linux/wait.h>
# include <linux/interrupt.h>
2006-03-23 03:00:39 -08:00
# include <linux/mutex.h>
2005-04-16 15:20:36 -07:00
# include <asm/mach-visws/cobalt.h>
# include "sound_config.h"
/*****************************************************************************/
/* debug stuff */
# ifdef VWSND_DEBUG
static int shut_up = 1 ;
/*
* dbgassert - called when an assertion fails .
*/
static void dbgassert ( const char * fcn , int line , const char * expr )
{
if ( in_interrupt ( ) )
panic ( " ASSERTION FAILED IN INTERRUPT, %s:%s:%d %s \n " ,
__FILE__ , fcn , line , expr ) ;
else {
int x ;
printk ( KERN_ERR " ASSERTION FAILED, %s:%s:%d %s \n " ,
__FILE__ , fcn , line , expr ) ;
x = * ( volatile int * ) 0 ; /* force proc to exit */
}
}
/*
* Bunch of useful debug macros :
*
* ASSERT - print unless e nonzero ( panic if in interrupt )
* DBGDO - include arbitrary code if debugging
* DBGX - debug print raw ( w / o function name )
* DBGP - debug print w / function name
* DBGE - debug print function entry
* DBGC - debug print function call
* DBGR - debug print function return
* DBGXV - debug print raw when verbose
* DBGPV - debug print when verbose
* DBGEV - debug print function entry when verbose
* DBGRV - debug print function return when verbose
*/
# define ASSERT(e) ((e) ? (void) 0 : dbgassert(__FUNCTION__, __LINE__, #e))
# define DBGDO(x) x
# define DBGX(fmt, args...) (in_interrupt() ? 0 : printk(KERN_ERR fmt, ##args))
# define DBGP(fmt, args...) (DBGX("%s: " fmt, __FUNCTION__ , ##args))
# define DBGE(fmt, args...) (DBGX("%s" fmt, __FUNCTION__ , ##args))
# define DBGC(rtn) (DBGP("calling %s\n", rtn))
# define DBGR() (DBGP("returning\n"))
# define DBGXV(fmt, args...) (shut_up ? 0 : DBGX(fmt, ##args))
# define DBGPV(fmt, args...) (shut_up ? 0 : DBGP(fmt, ##args))
# define DBGEV(fmt, args...) (shut_up ? 0 : DBGE(fmt, ##args))
# define DBGCV(rtn) (shut_up ? 0 : DBGC(rtn))
# define DBGRV() (shut_up ? 0 : DBGR())
# else /* !VWSND_DEBUG */
# define ASSERT(e) ((void) 0)
# define DBGDO(x) /* don't */
# define DBGX(fmt, args...) ((void) 0)
# define DBGP(fmt, args...) ((void) 0)
# define DBGE(fmt, args...) ((void) 0)
# define DBGC(rtn) ((void) 0)
# define DBGR() ((void) 0)
# define DBGPV(fmt, args...) ((void) 0)
# define DBGXV(fmt, args...) ((void) 0)
# define DBGEV(fmt, args...) ((void) 0)
# define DBGCV(rtn) ((void) 0)
# define DBGRV() ((void) 0)
# endif /* !VWSND_DEBUG */
/*****************************************************************************/
/* low level lithium access */
/*
* We need to talk to Lithium registers on three pages . Here are
* the pages ' offsets from the base address ( 0xFF001000 ) .
*/
enum {
LI_PAGE0_OFFSET = 0x01000 - 0x1000 , /* FF001000 */
LI_PAGE1_OFFSET = 0x0F000 - 0x1000 , /* FF00F000 */
LI_PAGE2_OFFSET = 0x10000 - 0x1000 , /* FF010000 */
} ;
/* low-level lithium data */
typedef struct lithium {
void * page0 ; /* virtual addresses */
void * page1 ;
void * page2 ;
spinlock_t lock ; /* protects codec and UST/MSC access */
} lithium_t ;
/*
* li_create initializes the lithium_t structure and sets up vm mappings
* to access the registers .
* Returns 0 on success , - errno on failure .
*/
static int __init li_create ( lithium_t * lith , unsigned long baseaddr )
{
static void li_destroy ( lithium_t * ) ;
spin_lock_init ( & lith - > lock ) ;
lith - > page0 = ioremap_nocache ( baseaddr + LI_PAGE0_OFFSET , PAGE_SIZE ) ;
lith - > page1 = ioremap_nocache ( baseaddr + LI_PAGE1_OFFSET , PAGE_SIZE ) ;
lith - > page2 = ioremap_nocache ( baseaddr + LI_PAGE2_OFFSET , PAGE_SIZE ) ;
if ( ! lith - > page0 | | ! lith - > page1 | | ! lith - > page2 ) {
li_destroy ( lith ) ;
return - ENOMEM ;
}
return 0 ;
}
/*
* li_destroy destroys the lithium_t structure and vm mappings .
*/
static void li_destroy ( lithium_t * lith )
{
if ( lith - > page0 ) {
iounmap ( lith - > page0 ) ;
lith - > page0 = NULL ;
}
if ( lith - > page1 ) {
iounmap ( lith - > page1 ) ;
lith - > page1 = NULL ;
}
if ( lith - > page2 ) {
iounmap ( lith - > page2 ) ;
lith - > page2 = NULL ;
}
}
/*
* basic register accessors - read / write long / byte
*/
static __inline__ unsigned long li_readl ( lithium_t * lith , int off )
{
return * ( volatile unsigned long * ) ( lith - > page0 + off ) ;
}
static __inline__ unsigned char li_readb ( lithium_t * lith , int off )
{
return * ( volatile unsigned char * ) ( lith - > page0 + off ) ;
}
static __inline__ void li_writel ( lithium_t * lith , int off , unsigned long val )
{
* ( volatile unsigned long * ) ( lith - > page0 + off ) = val ;
}
static __inline__ void li_writeb ( lithium_t * lith , int off , unsigned char val )
{
* ( volatile unsigned char * ) ( lith - > page0 + off ) = val ;
}
/*****************************************************************************/
/* High Level Lithium Access */
/*
* Lithium DMA Notes
*
* Lithium has two dedicated DMA channels for audio . They are known
* as comm1 and comm2 ( communication areas 1 and 2 ) . Comm1 is for
* input , and comm2 is for output . Each is controlled by three
* registers : BASE ( base address ) , CFG ( config ) and CCTL
* ( config / control ) .
*
* Each DMA channel points to a physically contiguous ring buffer in
* main memory of up to 8 Kbytes . ( This driver always uses 8 Kb . )
* There are three pointers into the ring buffer : read , write , and
* trigger . The pointers are 8 bits each . Each pointer points to
* 32 - byte " chunks " of data . The DMA engine moves 32 bytes at a time ,
* so there is no finer - granularity control .
*
* In comm1 , the hardware updates the write ptr , and software updates
* the read ptr . In comm2 , it ' s the opposite : hardware updates the
* read ptr , and software updates the write ptr . I designate the
* hardware - updated ptr as the hwptr , and the software - updated ptr as
* the swptr .
*
* The trigger ptr and trigger mask are used to trigger interrupts .
* From the Lithium spec , section 5.6 .8 , revision of 12 / 15 / 1998 :
*
* Trigger Mask Value
*
* A three bit wide field that represents a power of two mask
* that is used whenever the trigger pointer is compared to its
* respective read or write pointer . A value of zero here
* implies a mask of 0xFF and a value of seven implies a mask
* 0x01 . This value can be used to sub - divide the ring buffer
* into pie sections so that interrupts monitor the progress of
* hardware from section to section .
*
* My interpretation of that is , whenever the hw ptr is updated , it is
* compared with the trigger ptr , and the result is masked by the
* trigger mask . ( Actually , by the complement of the trigger mask . )
* If the result is zero , an interrupt is triggered . I . e . , interrupt
* if ( ( hwptr & ~ mask ) = = ( trptr & ~ mask ) ) . The mask is formed from
* the trigger register value as mask = ( 1 < < ( 8 - tmreg ) ) - 1.
*
* In yet different words , setting tmreg to 0 causes an interrupt after
* every 256 DMA chunks ( 8192 bytes ) or once per traversal of the
* ring buffer . Setting it to 7 caues an interrupt every 2 DMA chunks
* ( 64 bytes ) or 128 times per traversal of the ring buffer .
*/
/* Lithium register offsets and bit definitions */
# define LI_HOST_CONTROLLER 0x000
# define LI_HC_RESET 0x00008000
# define LI_HC_LINK_ENABLE 0x00004000
# define LI_HC_LINK_FAILURE 0x00000004
# define LI_HC_LINK_CODEC 0x00000002
# define LI_HC_LINK_READY 0x00000001
# define LI_INTR_STATUS 0x010
# define LI_INTR_MASK 0x014
# define LI_INTR_LINK_ERR 0x00008000
# define LI_INTR_COMM2_TRIG 0x00000008
# define LI_INTR_COMM2_UNDERFLOW 0x00000004
# define LI_INTR_COMM1_TRIG 0x00000002
# define LI_INTR_COMM1_OVERFLOW 0x00000001
# define LI_CODEC_COMMAND 0x018
# define LI_CC_BUSY 0x00008000
# define LI_CC_DIR 0x00000080
# define LI_CC_DIR_RD LI_CC_DIR
# define LI_CC_DIR_WR (!LI_CC_DIR)
# define LI_CC_ADDR_MASK 0x0000007F
# define LI_CODEC_DATA 0x01C
# define LI_COMM1_BASE 0x100
# define LI_COMM1_CTL 0x104
# define LI_CCTL_RESET 0x80000000
# define LI_CCTL_SIZE 0x70000000
# define LI_CCTL_DMA_ENABLE 0x08000000
# define LI_CCTL_TMASK 0x07000000 /* trigger mask */
# define LI_CCTL_TPTR 0x00FF0000 /* trigger pointer */
# define LI_CCTL_RPTR 0x0000FF00
# define LI_CCTL_WPTR 0x000000FF
# define LI_COMM1_CFG 0x108
# define LI_CCFG_LOCK 0x00008000
# define LI_CCFG_SLOT 0x00000070
# define LI_CCFG_DIRECTION 0x00000008
# define LI_CCFG_DIR_IN (!LI_CCFG_DIRECTION)
# define LI_CCFG_DIR_OUT LI_CCFG_DIRECTION
# define LI_CCFG_MODE 0x00000004
# define LI_CCFG_MODE_MONO (!LI_CCFG_MODE)
# define LI_CCFG_MODE_STEREO LI_CCFG_MODE
# define LI_CCFG_FORMAT 0x00000003
# define LI_CCFG_FMT_8BIT 0x00000000
# define LI_CCFG_FMT_16BIT 0x00000001
# define LI_COMM2_BASE 0x10C
# define LI_COMM2_CTL 0x110
/* bit definitions are the same as LI_COMM1_CTL */
# define LI_COMM2_CFG 0x114
/* bit definitions are the same as LI_COMM1_CFG */
# define LI_UST_LOW 0x200 /* 64-bit Unadjusted System Time is */
# define LI_UST_HIGH 0x204 /* microseconds since boot */
# define LI_AUDIO1_UST 0x300 /* UST-MSC pairs */
# define LI_AUDIO1_MSC 0x304 /* MSC (Media Stream Counter) */
# define LI_AUDIO2_UST 0x308 /* counts samples actually */
# define LI_AUDIO2_MSC 0x30C /* processed as of time UST */
/*
* Lithium ' s DMA engine operates on chunks of 32 bytes . We call that
* a DMACHUNK .
*/
# define DMACHUNK_SHIFT 5
# define DMACHUNK_SIZE (1 << DMACHUNK_SHIFT)
# define BYTES_TO_CHUNKS(bytes) ((bytes) >> DMACHUNK_SHIFT)
# define CHUNKS_TO_BYTES(chunks) ((chunks) << DMACHUNK_SHIFT)
/*
* Two convenient macros to shift bitfields into / out of position .
*
* Observe that ( mask & - mask ) is ( 1 < < low_set_bit_of ( mask ) ) .
* As long as mask is constant , we trust the compiler will change the
* multipy and divide into shifts .
*/
# define SHIFT_FIELD(val, mask) (((val) * ((mask) & -(mask))) & (mask))
# define UNSHIFT_FIELD(val, mask) (((val) & (mask)) / ((mask) & -(mask)))
/*
* dma_chan_desc is invariant information about a Lithium
* DMA channel . There are two instances , li_comm1 and li_comm2 .
*
* Note that the CCTL register fields are write ptr and read ptr , but what
* we care about are which pointer is updated by software and which by
* hardware .
*/
typedef struct dma_chan_desc {
int basereg ;
int cfgreg ;
int ctlreg ;
int hwptrreg ;
int swptrreg ;
int ustreg ;
int mscreg ;
unsigned long swptrmask ;
int ad1843_slot ;
int direction ; /* LI_CCTL_DIR_IN/OUT */
} dma_chan_desc_t ;
static const dma_chan_desc_t li_comm1 = {
LI_COMM1_BASE , /* base register offset */
LI_COMM1_CFG , /* config register offset */
LI_COMM1_CTL , /* control register offset */
LI_COMM1_CTL + 0 , /* hw ptr reg offset (write ptr) */
LI_COMM1_CTL + 1 , /* sw ptr reg offset (read ptr) */
LI_AUDIO1_UST , /* ust reg offset */
LI_AUDIO1_MSC , /* msc reg offset */
LI_CCTL_RPTR , /* sw ptr bitmask in ctlval */
2 , /* ad1843 serial slot */
LI_CCFG_DIR_IN /* direction */
} ;
static const dma_chan_desc_t li_comm2 = {
LI_COMM2_BASE , /* base register offset */
LI_COMM2_CFG , /* config register offset */
LI_COMM2_CTL , /* control register offset */
LI_COMM2_CTL + 1 , /* hw ptr reg offset (read ptr) */
LI_COMM2_CTL + 0 , /* sw ptr reg offset (writr ptr) */
LI_AUDIO2_UST , /* ust reg offset */
LI_AUDIO2_MSC , /* msc reg offset */
LI_CCTL_WPTR , /* sw ptr bitmask in ctlval */
2 , /* ad1843 serial slot */
LI_CCFG_DIR_OUT /* direction */
} ;
/*
* dma_chan is variable information about a Lithium DMA channel .
*
* The desc field points to invariant information .
* The lith field points to a lithium_t which is passed
* to li_read * and li_write * to access the registers .
* The * val fields shadow the lithium registers ' contents .
*/
typedef struct dma_chan {
const dma_chan_desc_t * desc ;
lithium_t * lith ;
unsigned long baseval ;
unsigned long cfgval ;
unsigned long ctlval ;
} dma_chan_t ;
/*
* ustmsc is a UST / MSC pair ( Unadjusted System Time / Media Stream Counter ) .
* UST is time in microseconds since the system booted , and MSC is a
* counter that increments with every audio sample .
*/
typedef struct ustmsc {
unsigned long long ust ;
unsigned long msc ;
} ustmsc_t ;
/*
* li_ad1843_wait waits until lithium says the AD1843 register
* exchange is not busy . Returns 0 on success , - EBUSY on timeout .
*
* Locking : must be called with lithium_lock held .
*/
static int li_ad1843_wait ( lithium_t * lith )
{
unsigned long later = jiffies + 2 ;
while ( li_readl ( lith , LI_CODEC_COMMAND ) & LI_CC_BUSY )
if ( time_after_eq ( jiffies , later ) )
return - EBUSY ;
return 0 ;
}
/*
* li_read_ad1843_reg returns the current contents of a 16 bit AD1843 register .
*
* Returns unsigned register value on success , - errno on failure .
*/
static int li_read_ad1843_reg ( lithium_t * lith , int reg )
{
int val ;
ASSERT ( ! in_interrupt ( ) ) ;
spin_lock ( & lith - > lock ) ;
{
val = li_ad1843_wait ( lith ) ;
if ( val = = 0 ) {
li_writel ( lith , LI_CODEC_COMMAND , LI_CC_DIR_RD | reg ) ;
val = li_ad1843_wait ( lith ) ;
}
if ( val = = 0 )
val = li_readl ( lith , LI_CODEC_DATA ) ;
}
spin_unlock ( & lith - > lock ) ;
DBGXV ( " li_read_ad1843_reg(lith=0x%p, reg=%d) returns 0x%04x \n " ,
lith , reg , val ) ;
return val ;
}
/*
* li_write_ad1843_reg writes the specified value to a 16 bit AD1843 register .
*/
static void li_write_ad1843_reg ( lithium_t * lith , int reg , int newval )
{
spin_lock ( & lith - > lock ) ;
{
if ( li_ad1843_wait ( lith ) = = 0 ) {
li_writel ( lith , LI_CODEC_DATA , newval ) ;
li_writel ( lith , LI_CODEC_COMMAND , LI_CC_DIR_WR | reg ) ;
}
}
spin_unlock ( & lith - > lock ) ;
}
/*
* li_setup_dma calculates all the register settings for DMA in a particular
* mode . It takes too many arguments .
*/
static void li_setup_dma ( dma_chan_t * chan ,
const dma_chan_desc_t * desc ,
lithium_t * lith ,
unsigned long buffer_paddr ,
int bufshift ,
int fragshift ,
int channels ,
int sampsize )
{
unsigned long mode , format ;
unsigned long size , tmask ;
DBGEV ( " (chan=0x%p, desc=0x%p, lith=0x%p, buffer_paddr=0x%lx, "
" bufshift=%d, fragshift=%d, channels=%d, sampsize=%d) \n " ,
chan , desc , lith , buffer_paddr ,
bufshift , fragshift , channels , sampsize ) ;
/* Reset the channel first. */
li_writel ( lith , desc - > ctlreg , LI_CCTL_RESET ) ;
ASSERT ( channels = = 1 | | channels = = 2 ) ;
if ( channels = = 2 )
mode = LI_CCFG_MODE_STEREO ;
else
mode = LI_CCFG_MODE_MONO ;
ASSERT ( sampsize = = 1 | | sampsize = = 2 ) ;
if ( sampsize = = 2 )
format = LI_CCFG_FMT_16BIT ;
else
format = LI_CCFG_FMT_8BIT ;
chan - > desc = desc ;
chan - > lith = lith ;
/*
* Lithium DMA address register takes a 40 - bit physical
* address , right - shifted by 8 so it fits in 32 bits . Bit 37
* must be set - - it enables cache coherence .
*/
ASSERT ( ! ( buffer_paddr & 0xFF ) ) ;
chan - > baseval = ( buffer_paddr > > 8 ) | 1 < < ( 37 - 8 ) ;
chan - > cfgval = ( ! LI_CCFG_LOCK |
SHIFT_FIELD ( desc - > ad1843_slot , LI_CCFG_SLOT ) |
desc - > direction |
mode |
format ) ;
size = bufshift - 6 ;
tmask = 13 - fragshift ; /* See Lithium DMA Notes above. */
ASSERT ( size > = 2 & & size < = 7 ) ;
ASSERT ( tmask > = 1 & & tmask < = 7 ) ;
chan - > ctlval = ( ! LI_CCTL_RESET |
SHIFT_FIELD ( size , LI_CCTL_SIZE ) |
! LI_CCTL_DMA_ENABLE |
SHIFT_FIELD ( tmask , LI_CCTL_TMASK ) |
SHIFT_FIELD ( 0 , LI_CCTL_TPTR ) ) ;
DBGPV ( " basereg 0x%x = 0x%lx \n " , desc - > basereg , chan - > baseval ) ;
DBGPV ( " cfgreg 0x%x = 0x%lx \n " , desc - > cfgreg , chan - > cfgval ) ;
DBGPV ( " ctlreg 0x%x = 0x%lx \n " , desc - > ctlreg , chan - > ctlval ) ;
li_writel ( lith , desc - > basereg , chan - > baseval ) ;
li_writel ( lith , desc - > cfgreg , chan - > cfgval ) ;
li_writel ( lith , desc - > ctlreg , chan - > ctlval ) ;
DBGRV ( ) ;
}
static void li_shutdown_dma ( dma_chan_t * chan )
{
lithium_t * lith = chan - > lith ;
void * lith1 = lith - > page1 ;
DBGEV ( " (chan=0x%p) \n " , chan ) ;
chan - > ctlval & = ~ LI_CCTL_DMA_ENABLE ;
DBGPV ( " ctlreg 0x%x = 0x%lx \n " , chan - > desc - > ctlreg , chan - > ctlval ) ;
li_writel ( lith , chan - > desc - > ctlreg , chan - > ctlval ) ;
/*
* Offset 0x500 on Lithium page 1 is an undocumented ,
* unsupported register that holds the zero sample value .
* Lithium is supposed to output zero samples when DMA is
* inactive , and repeat the last sample when DMA underflows .
* But it has a bug , where , after underflow occurs , the zero
* sample is not reset .
*
* I expect this to break in a future rev of Lithium .
*/
if ( lith1 & & chan - > desc - > direction = = LI_CCFG_DIR_OUT )
* ( volatile unsigned long * ) ( lith1 + 0x500 ) = 0 ;
}
/*
* li_activate_dma always starts dma at the beginning of the buffer .
*
* N . B . , these may be called from interrupt .
*/
static __inline__ void li_activate_dma ( dma_chan_t * chan )
{
chan - > ctlval | = LI_CCTL_DMA_ENABLE ;
DBGPV ( " ctlval = 0x%lx \n " , chan - > ctlval ) ;
li_writel ( chan - > lith , chan - > desc - > ctlreg , chan - > ctlval ) ;
}
static void li_deactivate_dma ( dma_chan_t * chan )
{
lithium_t * lith = chan - > lith ;
void * lith2 = lith - > page2 ;
chan - > ctlval & = ~ ( LI_CCTL_DMA_ENABLE | LI_CCTL_RPTR | LI_CCTL_WPTR ) ;
DBGPV ( " ctlval = 0x%lx \n " , chan - > ctlval ) ;
DBGPV ( " ctlreg 0x%x = 0x%lx \n " , chan - > desc - > ctlreg , chan - > ctlval ) ;
li_writel ( lith , chan - > desc - > ctlreg , chan - > ctlval ) ;
/*
* Offsets 0x98 and 0x9C on Lithium page 2 are undocumented ,
* unsupported registers that are internal copies of the DMA
* read and write pointers . Because of a Lithium bug , these
* registers aren ' t zeroed correctly when DMA is shut off . So
* we whack them directly .
*
* I expect this to break in a future rev of Lithium .
*/
if ( lith2 & & chan - > desc - > direction = = LI_CCFG_DIR_OUT ) {
* ( volatile unsigned long * ) ( lith2 + 0x98 ) = 0 ;
* ( volatile unsigned long * ) ( lith2 + 0x9C ) = 0 ;
}
}
/*
* read / write the ring buffer pointers . These routines ' arguments and results
* are byte offsets from the beginning of the ring buffer .
*/
static __inline__ int li_read_swptr ( dma_chan_t * chan )
{
const unsigned long mask = chan - > desc - > swptrmask ;
return CHUNKS_TO_BYTES ( UNSHIFT_FIELD ( chan - > ctlval , mask ) ) ;
}
static __inline__ int li_read_hwptr ( dma_chan_t * chan )
{
return CHUNKS_TO_BYTES ( li_readb ( chan - > lith , chan - > desc - > hwptrreg ) ) ;
}
static __inline__ void li_write_swptr ( dma_chan_t * chan , int val )
{
const unsigned long mask = chan - > desc - > swptrmask ;
ASSERT ( ! ( val & ~ CHUNKS_TO_BYTES ( 0xFF ) ) ) ;
val = BYTES_TO_CHUNKS ( val ) ;
chan - > ctlval = ( chan - > ctlval & ~ mask ) | SHIFT_FIELD ( val , mask ) ;
li_writeb ( chan - > lith , chan - > desc - > swptrreg , val ) ;
}
/* li_read_USTMSC() returns a UST/MSC pair for the given channel. */
static void li_read_USTMSC ( dma_chan_t * chan , ustmsc_t * ustmsc )
{
lithium_t * lith = chan - > lith ;
const dma_chan_desc_t * desc = chan - > desc ;
unsigned long now_low , now_high0 , now_high1 , chan_ust ;
spin_lock ( & lith - > lock ) ;
{
/*
* retry until we do all five reads without the
* high word changing . ( High word increments
* every 2 ^ 32 microseconds , i . e . , not often )
*/
do {
now_high0 = li_readl ( lith , LI_UST_HIGH ) ;
now_low = li_readl ( lith , LI_UST_LOW ) ;
/*
* Lithium guarantees these two reads will be
* atomic - - ust will not increment after msc
* is read .
*/
ustmsc - > msc = li_readl ( lith , desc - > mscreg ) ;
chan_ust = li_readl ( lith , desc - > ustreg ) ;
now_high1 = li_readl ( lith , LI_UST_HIGH ) ;
} while ( now_high0 ! = now_high1 ) ;
}
spin_unlock ( & lith - > lock ) ;
ustmsc - > ust = ( ( unsigned long long ) now_high0 < < 32 | chan_ust ) ;
}
static void li_enable_interrupts ( lithium_t * lith , unsigned int mask )
{
DBGEV ( " (lith=0x%p, mask=0x%x) \n " , lith , mask ) ;
/* clear any already-pending interrupts. */
li_writel ( lith , LI_INTR_STATUS , mask ) ;
/* enable the interrupts. */
mask | = li_readl ( lith , LI_INTR_MASK ) ;
li_writel ( lith , LI_INTR_MASK , mask ) ;
}
static void li_disable_interrupts ( lithium_t * lith , unsigned int mask )
{
unsigned int keepmask ;
DBGEV ( " (lith=0x%p, mask=0x%x) \n " , lith , mask ) ;
/* disable the interrupts */
keepmask = li_readl ( lith , LI_INTR_MASK ) & ~ mask ;
li_writel ( lith , LI_INTR_MASK , keepmask ) ;
/* clear any pending interrupts. */
li_writel ( lith , LI_INTR_STATUS , mask ) ;
}
/* Get the interrupt status and clear all pending interrupts. */
static unsigned int li_get_clear_intr_status ( lithium_t * lith )
{
unsigned int status ;
status = li_readl ( lith , LI_INTR_STATUS ) ;
li_writel ( lith , LI_INTR_STATUS , ~ 0 ) ;
return status & li_readl ( lith , LI_INTR_MASK ) ;
}
static int li_init ( lithium_t * lith )
{
/* 1. System power supplies stabilize. */
/* 2. Assert the ~RESET signal. */
li_writel ( lith , LI_HOST_CONTROLLER , LI_HC_RESET ) ;
udelay ( 1 ) ;
/* 3. Deassert the ~RESET signal and enter a wait period to allow
the AD1843 internal clocks and the external crystal oscillator
to stabilize . */
li_writel ( lith , LI_HOST_CONTROLLER , LI_HC_LINK_ENABLE ) ;
udelay ( 1 ) ;
return 0 ;
}
/*****************************************************************************/
/* AD1843 access */
/*
* AD1843 bitfield definitions . All are named as in the AD1843 data
* sheet , with ad1843_ prepended and individual bit numbers removed .
*
* E . g . , bits LSS0 through LSS2 become ad1843_LSS .
*
* Only the bitfields we need are defined .
*/
typedef struct ad1843_bitfield {
char reg ;
char lo_bit ;
char nbits ;
} ad1843_bitfield_t ;
static const ad1843_bitfield_t
ad1843_PDNO = { 0 , 14 , 1 } , /* Converter Power-Down Flag */
ad1843_INIT = { 0 , 15 , 1 } , /* Clock Initialization Flag */
ad1843_RIG = { 2 , 0 , 4 } , /* Right ADC Input Gain */
ad1843_RMGE = { 2 , 4 , 1 } , /* Right ADC Mic Gain Enable */
ad1843_RSS = { 2 , 5 , 3 } , /* Right ADC Source Select */
ad1843_LIG = { 2 , 8 , 4 } , /* Left ADC Input Gain */
ad1843_LMGE = { 2 , 12 , 1 } , /* Left ADC Mic Gain Enable */
ad1843_LSS = { 2 , 13 , 3 } , /* Left ADC Source Select */
ad1843_RX1M = { 4 , 0 , 5 } , /* Right Aux 1 Mix Gain/Atten */
ad1843_RX1MM = { 4 , 7 , 1 } , /* Right Aux 1 Mix Mute */
ad1843_LX1M = { 4 , 8 , 5 } , /* Left Aux 1 Mix Gain/Atten */
ad1843_LX1MM = { 4 , 15 , 1 } , /* Left Aux 1 Mix Mute */
ad1843_RX2M = { 5 , 0 , 5 } , /* Right Aux 2 Mix Gain/Atten */
ad1843_RX2MM = { 5 , 7 , 1 } , /* Right Aux 2 Mix Mute */
ad1843_LX2M = { 5 , 8 , 5 } , /* Left Aux 2 Mix Gain/Atten */
ad1843_LX2MM = { 5 , 15 , 1 } , /* Left Aux 2 Mix Mute */
ad1843_RMCM = { 7 , 0 , 5 } , /* Right Mic Mix Gain/Atten */
ad1843_RMCMM = { 7 , 7 , 1 } , /* Right Mic Mix Mute */
ad1843_LMCM = { 7 , 8 , 5 } , /* Left Mic Mix Gain/Atten */
ad1843_LMCMM = { 7 , 15 , 1 } , /* Left Mic Mix Mute */
ad1843_HPOS = { 8 , 4 , 1 } , /* Headphone Output Voltage Swing */
ad1843_HPOM = { 8 , 5 , 1 } , /* Headphone Output Mute */
ad1843_RDA1G = { 9 , 0 , 6 } , /* Right DAC1 Analog/Digital Gain */
ad1843_RDA1GM = { 9 , 7 , 1 } , /* Right DAC1 Analog Mute */
ad1843_LDA1G = { 9 , 8 , 6 } , /* Left DAC1 Analog/Digital Gain */
ad1843_LDA1GM = { 9 , 15 , 1 } , /* Left DAC1 Analog Mute */
ad1843_RDA1AM = { 11 , 7 , 1 } , /* Right DAC1 Digital Mute */
ad1843_LDA1AM = { 11 , 15 , 1 } , /* Left DAC1 Digital Mute */
ad1843_ADLC = { 15 , 0 , 2 } , /* ADC Left Sample Rate Source */
ad1843_ADRC = { 15 , 2 , 2 } , /* ADC Right Sample Rate Source */
ad1843_DA1C = { 15 , 8 , 2 } , /* DAC1 Sample Rate Source */
ad1843_C1C = { 17 , 0 , 16 } , /* Clock 1 Sample Rate Select */
ad1843_C2C = { 20 , 0 , 16 } , /* Clock 1 Sample Rate Select */
ad1843_DAADL = { 25 , 4 , 2 } , /* Digital ADC Left Source Select */
ad1843_DAADR = { 25 , 6 , 2 } , /* Digital ADC Right Source Select */
ad1843_DRSFLT = { 25 , 15 , 1 } , /* Digital Reampler Filter Mode */
ad1843_ADLF = { 26 , 0 , 2 } , /* ADC Left Channel Data Format */
ad1843_ADRF = { 26 , 2 , 2 } , /* ADC Right Channel Data Format */
ad1843_ADTLK = { 26 , 4 , 1 } , /* ADC Transmit Lock Mode Select */
ad1843_SCF = { 26 , 7 , 1 } , /* SCLK Frequency Select */
ad1843_DA1F = { 26 , 8 , 2 } , /* DAC1 Data Format Select */
ad1843_DA1SM = { 26 , 14 , 1 } , /* DAC1 Stereo/Mono Mode Select */
ad1843_ADLEN = { 27 , 0 , 1 } , /* ADC Left Channel Enable */
ad1843_ADREN = { 27 , 1 , 1 } , /* ADC Right Channel Enable */
ad1843_AAMEN = { 27 , 4 , 1 } , /* Analog to Analog Mix Enable */
ad1843_ANAEN = { 27 , 7 , 1 } , /* Analog Channel Enable */
ad1843_DA1EN = { 27 , 8 , 1 } , /* DAC1 Enable */
ad1843_DA2EN = { 27 , 9 , 1 } , /* DAC2 Enable */
ad1843_C1EN = { 28 , 11 , 1 } , /* Clock Generator 1 Enable */
ad1843_C2EN = { 28 , 12 , 1 } , /* Clock Generator 2 Enable */
ad1843_PDNI = { 28 , 15 , 1 } ; /* Converter Power Down */
/*
* The various registers of the AD1843 use three different formats for
* specifying gain . The ad1843_gain structure parameterizes the
* formats .
*/
typedef struct ad1843_gain {
int negative ; /* nonzero if gain is negative. */
const ad1843_bitfield_t * lfield ;
const ad1843_bitfield_t * rfield ;
} ad1843_gain_t ;
static const ad1843_gain_t ad1843_gain_RECLEV
= { 0 , & ad1843_LIG , & ad1843_RIG } ;
static const ad1843_gain_t ad1843_gain_LINE
= { 1 , & ad1843_LX1M , & ad1843_RX1M } ;
static const ad1843_gain_t ad1843_gain_CD
= { 1 , & ad1843_LX2M , & ad1843_RX2M } ;
static const ad1843_gain_t ad1843_gain_MIC
= { 1 , & ad1843_LMCM , & ad1843_RMCM } ;
static const ad1843_gain_t ad1843_gain_PCM
= { 1 , & ad1843_LDA1G , & ad1843_RDA1G } ;
/* read the current value of an AD1843 bitfield. */
static int ad1843_read_bits ( lithium_t * lith , const ad1843_bitfield_t * field )
{
int w = li_read_ad1843_reg ( lith , field - > reg ) ;
int val = w > > field - > lo_bit & ( ( 1 < < field - > nbits ) - 1 ) ;
DBGXV ( " ad1843_read_bits(lith=0x%p, field->{%d %d %d}) returns 0x%x \n " ,
lith , field - > reg , field - > lo_bit , field - > nbits , val ) ;
return val ;
}
/*
* write a new value to an AD1843 bitfield and return the old value .
*/
static int ad1843_write_bits ( lithium_t * lith ,
const ad1843_bitfield_t * field ,
int newval )
{
int w = li_read_ad1843_reg ( lith , field - > reg ) ;
int mask = ( ( 1 < < field - > nbits ) - 1 ) < < field - > lo_bit ;
int oldval = ( w & mask ) > > field - > lo_bit ;
int newbits = ( newval < < field - > lo_bit ) & mask ;
w = ( w & ~ mask ) | newbits ;
( void ) li_write_ad1843_reg ( lith , field - > reg , w ) ;
DBGXV ( " ad1843_write_bits(lith=0x%p, field->{%d %d %d}, val=0x%x) "
" returns 0x%x \n " ,
lith , field - > reg , field - > lo_bit , field - > nbits , newval ,
oldval ) ;
return oldval ;
}
/*
* ad1843_read_multi reads multiple bitfields from the same AD1843
* register . It uses a single read cycle to do it . ( Reading the
* ad1843 requires 256 bit times at 12.288 MHz , or nearly 20
* microseconds . )
*
* Called ike this .
*
* ad1843_read_multi ( lith , nfields ,
* & ad1843_FIELD1 , & val1 ,
* & ad1843_FIELD2 , & val2 , . . . ) ;
*/
static void ad1843_read_multi ( lithium_t * lith , int argcount , . . . )
{
va_list ap ;
const ad1843_bitfield_t * fp ;
int w = 0 , mask , * value , reg = - 1 ;
va_start ( ap , argcount ) ;
while ( - - argcount > = 0 ) {
fp = va_arg ( ap , const ad1843_bitfield_t * ) ;
value = va_arg ( ap , int * ) ;
if ( reg = = - 1 ) {
reg = fp - > reg ;
w = li_read_ad1843_reg ( lith , reg ) ;
}
ASSERT ( reg = = fp - > reg ) ;
mask = ( 1 < < fp - > nbits ) - 1 ;
* value = w > > fp - > lo_bit & mask ;
}
va_end ( ap ) ;
}
/*
* ad1843_write_multi stores multiple bitfields into the same AD1843
* register . It uses one read and one write cycle to do it .
*
* Called like this .
*
* ad1843_write_multi ( lith , nfields ,
* & ad1843_FIELD1 , val1 ,
* & ad1843_FIELF2 , val2 , . . . ) ;
*/
static void ad1843_write_multi ( lithium_t * lith , int argcount , . . . )
{
va_list ap ;
int reg ;
const ad1843_bitfield_t * fp ;
int value ;
int w , m , mask , bits ;
mask = 0 ;
bits = 0 ;
reg = - 1 ;
va_start ( ap , argcount ) ;
while ( - - argcount > = 0 ) {
fp = va_arg ( ap , const ad1843_bitfield_t * ) ;
value = va_arg ( ap , int ) ;
if ( reg = = - 1 )
reg = fp - > reg ;
ASSERT ( fp - > reg = = reg ) ;
m = ( ( 1 < < fp - > nbits ) - 1 ) < < fp - > lo_bit ;
mask | = m ;
bits | = ( value < < fp - > lo_bit ) & m ;
}
va_end ( ap ) ;
ASSERT ( ! ( bits & ~ mask ) ) ;
if ( ~ mask & 0xFFFF )
w = li_read_ad1843_reg ( lith , reg ) ;
else
w = 0 ;
w = ( w & ~ mask ) | bits ;
( void ) li_write_ad1843_reg ( lith , reg , w ) ;
}
/*
* ad1843_get_gain reads the specified register and extracts the gain value
* using the supplied gain type . It returns the gain in OSS format .
*/
static int ad1843_get_gain ( lithium_t * lith , const ad1843_gain_t * gp )
{
int lg , rg ;
unsigned short mask = ( 1 < < gp - > lfield - > nbits ) - 1 ;
ad1843_read_multi ( lith , 2 , gp - > lfield , & lg , gp - > rfield , & rg ) ;
if ( gp - > negative ) {
lg = mask - lg ;
rg = mask - rg ;
}
lg = ( lg * 100 + ( mask > > 1 ) ) / mask ;
rg = ( rg * 100 + ( mask > > 1 ) ) / mask ;
return lg < < 0 | rg < < 8 ;
}
/*
* Set an audio channel ' s gain . Converts from OSS format to AD1843 ' s
* format .
*
* Returns the new gain , which may be lower than the old gain .
*/
static int ad1843_set_gain ( lithium_t * lith ,
const ad1843_gain_t * gp ,
int newval )
{
unsigned short mask = ( 1 < < gp - > lfield - > nbits ) - 1 ;
int lg = newval > > 0 & 0xFF ;
int rg = newval > > 8 ;
if ( lg < 0 | | lg > 100 | | rg < 0 | | rg > 100 )
return - EINVAL ;
lg = ( lg * mask + ( mask > > 1 ) ) / 100 ;
rg = ( rg * mask + ( mask > > 1 ) ) / 100 ;
if ( gp - > negative ) {
lg = mask - lg ;
rg = mask - rg ;
}
ad1843_write_multi ( lith , 2 , gp - > lfield , lg , gp - > rfield , rg ) ;
return ad1843_get_gain ( lith , gp ) ;
}
/* Returns the current recording source, in OSS format. */
static int ad1843_get_recsrc ( lithium_t * lith )
{
int ls = ad1843_read_bits ( lith , & ad1843_LSS ) ;
switch ( ls ) {
case 1 :
return SOUND_MASK_MIC ;
case 2 :
return SOUND_MASK_LINE ;
case 3 :
return SOUND_MASK_CD ;
case 6 :
return SOUND_MASK_PCM ;
default :
ASSERT ( 0 ) ;
return - 1 ;
}
}
/*
* Enable / disable digital resample mode in the AD1843 .
*
* The AD1843 requires that ADL , ADR , DA1 and DA2 be powered down
* while switching modes . So we save DA1 ' s state ( DA2 ' s state is not
* interesting ) , power them down , switch into / out of resample mode ,
* power them up , and restore state .
*
* This will cause audible glitches if D / A or A / D is going on , so the
* driver disallows that ( in mixer_write_ioctl ( ) ) .
*
* The open question is , is this worth doing ? I ' m leaving it in ,
* because it ' s written , but . . .
*/
static void ad1843_set_resample_mode ( lithium_t * lith , int onoff )
{
/* Save DA1 mute and gain (addr 9 is DA1 analog gain/attenuation) */
int save_da1 = li_read_ad1843_reg ( lith , 9 ) ;
/* Power down A/D and D/A. */
ad1843_write_multi ( lith , 4 ,
& ad1843_DA1EN , 0 ,
& ad1843_DA2EN , 0 ,
& ad1843_ADLEN , 0 ,
& ad1843_ADREN , 0 ) ;
/* Switch mode */
ASSERT ( onoff = = 0 | | onoff = = 1 ) ;
ad1843_write_bits ( lith , & ad1843_DRSFLT , onoff ) ;
/* Power up A/D and D/A. */
ad1843_write_multi ( lith , 3 ,
& ad1843_DA1EN , 1 ,
& ad1843_ADLEN , 1 ,
& ad1843_ADREN , 1 ) ;
/* Restore DA1 mute and gain. */
li_write_ad1843_reg ( lith , 9 , save_da1 ) ;
}
/*
* Set recording source . Arg newsrc specifies an OSS channel mask .
*
* The complication is that when we switch into / out of loopback mode
* ( i . e . , src = SOUND_MASK_PCM ) , we change the AD1843 into / out of
* digital resampling mode .
*
* Returns newsrc on success , - errno on failure .
*/
static int ad1843_set_recsrc ( lithium_t * lith , int newsrc )
{
int bits ;
int oldbits ;
switch ( newsrc ) {
case SOUND_MASK_PCM :
bits = 6 ;
break ;
case SOUND_MASK_MIC :
bits = 1 ;
break ;
case SOUND_MASK_LINE :
bits = 2 ;
break ;
case SOUND_MASK_CD :
bits = 3 ;
break ;
default :
return - EINVAL ;
}
oldbits = ad1843_read_bits ( lith , & ad1843_LSS ) ;
if ( newsrc = = SOUND_MASK_PCM & & oldbits ! = 6 ) {
DBGP ( " enabling digital resample mode \n " ) ;
ad1843_set_resample_mode ( lith , 1 ) ;
ad1843_write_multi ( lith , 2 ,
& ad1843_DAADL , 2 ,
& ad1843_DAADR , 2 ) ;
} else if ( newsrc ! = SOUND_MASK_PCM & & oldbits = = 6 ) {
DBGP ( " disabling digital resample mode \n " ) ;
ad1843_set_resample_mode ( lith , 0 ) ;
ad1843_write_multi ( lith , 2 ,
& ad1843_DAADL , 0 ,
& ad1843_DAADR , 0 ) ;
}
ad1843_write_multi ( lith , 2 , & ad1843_LSS , bits , & ad1843_RSS , bits ) ;
return newsrc ;
}
/*
* Return current output sources , in OSS format .
*/
static int ad1843_get_outsrc ( lithium_t * lith )
{
int pcm , line , mic , cd ;
pcm = ad1843_read_bits ( lith , & ad1843_LDA1GM ) ? 0 : SOUND_MASK_PCM ;
line = ad1843_read_bits ( lith , & ad1843_LX1MM ) ? 0 : SOUND_MASK_LINE ;
cd = ad1843_read_bits ( lith , & ad1843_LX2MM ) ? 0 : SOUND_MASK_CD ;
mic = ad1843_read_bits ( lith , & ad1843_LMCMM ) ? 0 : SOUND_MASK_MIC ;
return pcm | line | cd | mic ;
}
/*
* Set output sources . Arg is a mask of active sources in OSS format .
*
* Returns source mask on success , - errno on failure .
*/
static int ad1843_set_outsrc ( lithium_t * lith , int mask )
{
int pcm , line , mic , cd ;
if ( mask & ~ ( SOUND_MASK_PCM | SOUND_MASK_LINE |
SOUND_MASK_CD | SOUND_MASK_MIC ) )
return - EINVAL ;
pcm = ( mask & SOUND_MASK_PCM ) ? 0 : 1 ;
line = ( mask & SOUND_MASK_LINE ) ? 0 : 1 ;
mic = ( mask & SOUND_MASK_MIC ) ? 0 : 1 ;
cd = ( mask & SOUND_MASK_CD ) ? 0 : 1 ;
ad1843_write_multi ( lith , 2 , & ad1843_LDA1GM , pcm , & ad1843_RDA1GM , pcm ) ;
ad1843_write_multi ( lith , 2 , & ad1843_LX1MM , line , & ad1843_RX1MM , line ) ;
ad1843_write_multi ( lith , 2 , & ad1843_LX2MM , cd , & ad1843_RX2MM , cd ) ;
ad1843_write_multi ( lith , 2 , & ad1843_LMCMM , mic , & ad1843_RMCMM , mic ) ;
return mask ;
}
/* Setup ad1843 for D/A conversion. */
static void ad1843_setup_dac ( lithium_t * lith ,
int framerate ,
int fmt ,
int channels )
{
int ad_fmt = 0 , ad_mode = 0 ;
DBGEV ( " (lith=0x%p, framerate=%d, fmt=%d, channels=%d) \n " ,
lith , framerate , fmt , channels ) ;
switch ( fmt ) {
case AFMT_S8 : ad_fmt = 1 ; break ;
case AFMT_U8 : ad_fmt = 1 ; break ;
case AFMT_S16_LE : ad_fmt = 1 ; break ;
case AFMT_MU_LAW : ad_fmt = 2 ; break ;
case AFMT_A_LAW : ad_fmt = 3 ; break ;
default : ASSERT ( 0 ) ;
}
switch ( channels ) {
case 2 : ad_mode = 0 ; break ;
case 1 : ad_mode = 1 ; break ;
default : ASSERT ( 0 ) ;
}
DBGPV ( " ad_mode = %d, ad_fmt = %d \n " , ad_mode , ad_fmt ) ;
ASSERT ( framerate > = 4000 & & framerate < = 49000 ) ;
ad1843_write_bits ( lith , & ad1843_C1C , framerate ) ;
ad1843_write_multi ( lith , 2 ,
& ad1843_DA1SM , ad_mode , & ad1843_DA1F , ad_fmt ) ;
}
static void ad1843_shutdown_dac ( lithium_t * lith )
{
ad1843_write_bits ( lith , & ad1843_DA1F , 1 ) ;
}
static void ad1843_setup_adc ( lithium_t * lith , int framerate , int fmt , int channels )
{
int da_fmt = 0 ;
DBGEV ( " (lith=0x%p, framerate=%d, fmt=%d, channels=%d) \n " ,
lith , framerate , fmt , channels ) ;
switch ( fmt ) {
case AFMT_S8 : da_fmt = 1 ; break ;
case AFMT_U8 : da_fmt = 1 ; break ;
case AFMT_S16_LE : da_fmt = 1 ; break ;
case AFMT_MU_LAW : da_fmt = 2 ; break ;
case AFMT_A_LAW : da_fmt = 3 ; break ;
default : ASSERT ( 0 ) ;
}
DBGPV ( " da_fmt = %d \n " , da_fmt ) ;
ASSERT ( framerate > = 4000 & & framerate < = 49000 ) ;
ad1843_write_bits ( lith , & ad1843_C2C , framerate ) ;
ad1843_write_multi ( lith , 2 ,
& ad1843_ADLF , da_fmt , & ad1843_ADRF , da_fmt ) ;
}
static void ad1843_shutdown_adc ( lithium_t * lith )
{
/* nothing to do */
}
/*
* Fully initialize the ad1843 . As described in the AD1843 data
* sheet , section " START-UP SEQUENCE " . The numbered comments are
* subsection headings from the data sheet . See the data sheet , pages
* 52 - 54 , for more info .
*
* return 0 on success , - errno on failure . */
static int __init ad1843_init ( lithium_t * lith )
{
unsigned long later ;
int err ;
err = li_init ( lith ) ;
if ( err )
return err ;
if ( ad1843_read_bits ( lith , & ad1843_INIT ) ! = 0 ) {
printk ( KERN_ERR " vwsnd sound: AD1843 won't initialize \n " ) ;
return - EIO ;
}
ad1843_write_bits ( lith , & ad1843_SCF , 1 ) ;
/* 4. Put the conversion resources into standby. */
ad1843_write_bits ( lith , & ad1843_PDNI , 0 ) ;
later = jiffies + HZ / 2 ; /* roughly half a second */
DBGDO ( shut_up + + ) ;
while ( ad1843_read_bits ( lith , & ad1843_PDNO ) ) {
if ( time_after ( jiffies , later ) ) {
printk ( KERN_ERR
" vwsnd audio: AD1843 won't power up \n " ) ;
return - EIO ;
}
schedule ( ) ;
}
DBGDO ( shut_up - - ) ;
/* 5. Power up the clock generators and enable clock output pins. */
ad1843_write_multi ( lith , 2 , & ad1843_C1EN , 1 , & ad1843_C2EN , 1 ) ;
/* 6. Configure conversion resources while they are in standby. */
/* DAC1 uses clock 1 as source, ADC uses clock 2. Always. */
ad1843_write_multi ( lith , 3 ,
& ad1843_DA1C , 1 ,
& ad1843_ADLC , 2 ,
& ad1843_ADRC , 2 ) ;
/* 7. Enable conversion resources. */
ad1843_write_bits ( lith , & ad1843_ADTLK , 1 ) ;
ad1843_write_multi ( lith , 5 ,
& ad1843_ANAEN , 1 ,
& ad1843_AAMEN , 1 ,
& ad1843_DA1EN , 1 ,
& ad1843_ADLEN , 1 ,
& ad1843_ADREN , 1 ) ;
/* 8. Configure conversion resources while they are enabled. */
ad1843_write_bits ( lith , & ad1843_DA1C , 1 ) ;
/* Unmute all channels. */
ad1843_set_outsrc ( lith ,
( SOUND_MASK_PCM | SOUND_MASK_LINE |
SOUND_MASK_MIC | SOUND_MASK_CD ) ) ;
ad1843_write_multi ( lith , 2 , & ad1843_LDA1AM , 0 , & ad1843_RDA1AM , 0 ) ;
/* Set default recording source to Line In and set
* mic gain to + 20 dB .
*/
ad1843_set_recsrc ( lith , SOUND_MASK_LINE ) ;
ad1843_write_multi ( lith , 2 , & ad1843_LMGE , 1 , & ad1843_RMGE , 1 ) ;
/* Set Speaker Out level to +/- 4V and unmute it. */
ad1843_write_multi ( lith , 2 , & ad1843_HPOS , 1 , & ad1843_HPOM , 0 ) ;
return 0 ;
}
/*****************************************************************************/
/* PCM I/O */
# define READ_INTR_MASK (LI_INTR_COMM1_TRIG | LI_INTR_COMM1_OVERFLOW)
# define WRITE_INTR_MASK (LI_INTR_COMM2_TRIG | LI_INTR_COMM2_UNDERFLOW)
typedef enum vwsnd_port_swstate { /* software state */
SW_OFF ,
SW_INITIAL ,
SW_RUN ,
SW_DRAIN ,
} vwsnd_port_swstate_t ;
typedef enum vwsnd_port_hwstate { /* hardware state */
HW_STOPPED ,
HW_RUNNING ,
} vwsnd_port_hwstate_t ;
/*
* These flags are read by ISR , but only written at baseline .
*/
typedef enum vwsnd_port_flags {
DISABLED = 1 < < 0 ,
ERFLOWN = 1 < < 1 , /* overflown or underflown */
HW_BUSY = 1 < < 2 ,
} vwsnd_port_flags_t ;
/*
* vwsnd_port is the per - port data structure . Each device has two
* ports , one for input and one for output .
*
* Locking :
*
* port - > lock protects : hwstate , flags , swb_ [ iu ] _avail .
*
2006-03-23 03:00:39 -08:00
* devc - > io_mutex protects : swstate , sw_ * , swb_ [ iu ] _idx .
2005-04-16 15:20:36 -07:00
*
* everything else is only written by open / release or
* pcm_ { setup , shutdown } ( ) , which are serialized by a
2006-03-23 03:00:39 -08:00
* combination of devc - > open_mutex and devc - > io_mutex .
2005-04-16 15:20:36 -07:00
*/
typedef struct vwsnd_port {
spinlock_t lock ;
wait_queue_head_t queue ;
vwsnd_port_swstate_t swstate ;
vwsnd_port_hwstate_t hwstate ;
vwsnd_port_flags_t flags ;
int sw_channels ;
int sw_samplefmt ;
int sw_framerate ;
int sample_size ;
int frame_size ;
unsigned int zero_word ; /* zero for the sample format */
int sw_fragshift ;
int sw_fragcount ;
int sw_subdivshift ;
unsigned int hw_fragshift ;
unsigned int hw_fragsize ;
unsigned int hw_fragcount ;
int hwbuf_size ;
unsigned long hwbuf_paddr ;
unsigned long hwbuf_vaddr ;
void * hwbuf ; /* hwbuf == hwbuf_vaddr */
int hwbuf_max ; /* max bytes to preload */
void * swbuf ;
unsigned int swbuf_size ; /* size in bytes */
unsigned int swb_u_idx ; /* index of next user byte */
unsigned int swb_i_idx ; /* index of next intr byte */
unsigned int swb_u_avail ; /* # bytes avail to user */
unsigned int swb_i_avail ; /* # bytes avail to intr */
dma_chan_t chan ;
/* Accounting */
int byte_count ;
int frag_count ;
int MSC_offset ;
} vwsnd_port_t ;
/* vwsnd_dev is the per-device data structure. */
typedef struct vwsnd_dev {
struct vwsnd_dev * next_dev ;
int audio_minor ; /* minor number of audio device */
int mixer_minor ; /* minor number of mixer device */
2006-03-23 03:00:39 -08:00
struct mutex open_mutex ;
struct mutex io_mutex ;
struct mutex mix_mutex ;
2005-04-16 15:20:36 -07:00
mode_t open_mode ;
wait_queue_head_t open_wait ;
lithium_t lith ;
vwsnd_port_t rport ;
vwsnd_port_t wport ;
} vwsnd_dev_t ;
static vwsnd_dev_t * vwsnd_dev_list ; /* linked list of all devices */
static atomic_t vwsnd_use_count = ATOMIC_INIT ( 0 ) ;
# define INC_USE_COUNT (atomic_inc(&vwsnd_use_count))
# define DEC_USE_COUNT (atomic_dec(&vwsnd_use_count))
# define IN_USE (atomic_read(&vwsnd_use_count) != 0)
/*
* Lithium can only DMA multiples of 32 bytes . Its DMA buffer may
* be up to 8 Kb . This driver always uses 8 Kb .
*
* Memory bug workaround - - I ' m not sure what ' s going on here , but
* somehow pcm_copy_out ( ) was triggering segv ' s going on to the next
* page of the hw buffer . So , I make the hw buffer one size bigger
* than we actually use . That way , the following page is allocated
* and mapped , and no error . I suspect that something is broken
* in Cobalt , but haven ' t really investigated . HBO is the actual
* size of the buffer , and HWBUF_ORDER is what we allocate .
*/
# define HWBUF_SHIFT 13
# define HWBUF_SIZE (1 << HWBUF_SHIFT)
# define HBO (HWBUF_SHIFT > PAGE_SHIFT ? HWBUF_SHIFT - PAGE_SHIFT : 0)
# define HWBUF_ORDER (HBO + 1) /* next size bigger */
# define MIN_SPEED 4000
# define MAX_SPEED 49000
# define MIN_FRAGSHIFT (DMACHUNK_SHIFT + 1)
# define MAX_FRAGSHIFT (PAGE_SHIFT)
# define MIN_FRAGSIZE (1 << MIN_FRAGSHIFT)
# define MAX_FRAGSIZE (1 << MAX_FRAGSHIFT)
# define MIN_FRAGCOUNT(fragsize) 3
# define MAX_FRAGCOUNT(fragsize) (32 * PAGE_SIZE / (fragsize))
# define DEFAULT_FRAGSHIFT 12
# define DEFAULT_FRAGCOUNT 16
# define DEFAULT_SUBDIVSHIFT 0
/*
* The software buffer ( swbuf ) is a ring buffer shared between user
* level and interrupt level . Each level owns some of the bytes in
* the buffer , and may give bytes away by calling swb_inc_ { u , i } ( ) .
* User level calls _u for user , and interrupt level calls _i for
* interrupt .
*
* port - > swb_ { u , i } _avail is the number of bytes available to that level .
*
* port - > swb_ { u , i } _idx is the index of the first available byte in the
* buffer .
*
* Each level calls swb_inc_ { u , i } ( ) to atomically increment its index ,
* recalculate the number of bytes available for both sides , and
* return the number of bytes available . Since each side can only
* give away bytes , the other side can only increase the number of
* bytes available to this side . Each side updates its own index
* variable , swb_ { u , i } _idx , so no lock is needed to read it .
*
* To query the number of bytes available , call swb_inc_ { u , i } with an
* increment of zero .
*/
static __inline__ unsigned int __swb_inc_u ( vwsnd_port_t * port , int inc )
{
if ( inc ) {
port - > swb_u_idx + = inc ;
port - > swb_u_idx % = port - > swbuf_size ;
port - > swb_u_avail - = inc ;
port - > swb_i_avail + = inc ;
}
return port - > swb_u_avail ;
}
static __inline__ unsigned int swb_inc_u ( vwsnd_port_t * port , int inc )
{
unsigned long flags ;
unsigned int ret ;
spin_lock_irqsave ( & port - > lock , flags ) ;
{
ret = __swb_inc_u ( port , inc ) ;
}
spin_unlock_irqrestore ( & port - > lock , flags ) ;
return ret ;
}
static __inline__ unsigned int __swb_inc_i ( vwsnd_port_t * port , int inc )
{
if ( inc ) {
port - > swb_i_idx + = inc ;
port - > swb_i_idx % = port - > swbuf_size ;
port - > swb_i_avail - = inc ;
port - > swb_u_avail + = inc ;
}
return port - > swb_i_avail ;
}
static __inline__ unsigned int swb_inc_i ( vwsnd_port_t * port , int inc )
{
unsigned long flags ;
unsigned int ret ;
spin_lock_irqsave ( & port - > lock , flags ) ;
{
ret = __swb_inc_i ( port , inc ) ;
}
spin_unlock_irqrestore ( & port - > lock , flags ) ;
return ret ;
}
/*
* pcm_setup - this routine initializes all port state after
* mode - setting ioctls have been done , but before the first I / O is
* done .
*
2006-03-23 03:00:39 -08:00
* Locking : called with devc - > io_mutex held .
2005-04-16 15:20:36 -07:00
*
* Returns 0 on success , - errno on failure .
*/
static int pcm_setup ( vwsnd_dev_t * devc ,
vwsnd_port_t * rport ,
vwsnd_port_t * wport )
{
vwsnd_port_t * aport = rport ? rport : wport ;
int sample_size ;
unsigned int zero_word ;
DBGEV ( " (devc=0x%p, rport=0x%p, wport=0x%p) \n " , devc , rport , wport ) ;
ASSERT ( aport ! = NULL ) ;
if ( aport - > swbuf ! = NULL )
return 0 ;
switch ( aport - > sw_samplefmt ) {
case AFMT_MU_LAW :
sample_size = 1 ;
zero_word = 0xFFFFFFFF ^ 0x80808080 ;
break ;
case AFMT_A_LAW :
sample_size = 1 ;
zero_word = 0xD5D5D5D5 ^ 0x80808080 ;
break ;
case AFMT_U8 :
sample_size = 1 ;
zero_word = 0x80808080 ;
break ;
case AFMT_S8 :
sample_size = 1 ;
zero_word = 0x00000000 ;
break ;
case AFMT_S16_LE :
sample_size = 2 ;
zero_word = 0x00000000 ;
break ;
default :
sample_size = 0 ; /* prevent compiler warning */
zero_word = 0 ;
ASSERT ( 0 ) ;
}
aport - > sample_size = sample_size ;
aport - > zero_word = zero_word ;
aport - > frame_size = aport - > sw_channels * aport - > sample_size ;
aport - > hw_fragshift = aport - > sw_fragshift - aport - > sw_subdivshift ;
aport - > hw_fragsize = 1 < < aport - > hw_fragshift ;
aport - > hw_fragcount = aport - > sw_fragcount < < aport - > sw_subdivshift ;
ASSERT ( aport - > hw_fragsize > = MIN_FRAGSIZE ) ;
ASSERT ( aport - > hw_fragsize < = MAX_FRAGSIZE ) ;
ASSERT ( aport - > hw_fragcount > = MIN_FRAGCOUNT ( aport - > hw_fragsize ) ) ;
ASSERT ( aport - > hw_fragcount < = MAX_FRAGCOUNT ( aport - > hw_fragsize ) ) ;
if ( rport ) {
int hwfrags , swfrags ;
rport - > hwbuf_max = aport - > hwbuf_size - DMACHUNK_SIZE ;
hwfrags = rport - > hwbuf_max > > aport - > hw_fragshift ;
swfrags = aport - > hw_fragcount - hwfrags ;
if ( swfrags < 2 )
swfrags = 2 ;
rport - > swbuf_size = swfrags * aport - > hw_fragsize ;
DBGPV ( " hwfrags = %d, swfrags = %d \n " , hwfrags , swfrags ) ;
DBGPV ( " read hwbuf_max = %d, swbuf_size = %d \n " ,
rport - > hwbuf_max , rport - > swbuf_size ) ;
}
if ( wport ) {
int hwfrags , swfrags ;
int total_bytes = aport - > hw_fragcount * aport - > hw_fragsize ;
wport - > hwbuf_max = aport - > hwbuf_size - DMACHUNK_SIZE ;
if ( wport - > hwbuf_max > total_bytes )
wport - > hwbuf_max = total_bytes ;
hwfrags = wport - > hwbuf_max > > aport - > hw_fragshift ;
DBGPV ( " hwfrags = %d \n " , hwfrags ) ;
swfrags = aport - > hw_fragcount - hwfrags ;
if ( swfrags < 2 )
swfrags = 2 ;
wport - > swbuf_size = swfrags * aport - > hw_fragsize ;
DBGPV ( " hwfrags = %d, swfrags = %d \n " , hwfrags , swfrags ) ;
DBGPV ( " write hwbuf_max = %d, swbuf_size = %d \n " ,
wport - > hwbuf_max , wport - > swbuf_size ) ;
}
aport - > swb_u_idx = 0 ;
aport - > swb_i_idx = 0 ;
aport - > byte_count = 0 ;
/*
* Is this a Cobalt bug ? We need to make this buffer extend
* one page further than we actually use - - somehow memcpy
* causes an exceptoin otherwise . I suspect there ' s a bug in
* Cobalt ( or somewhere ) where it ' s generating a fault on a
* speculative load or something . Obviously , I haven ' t taken
* the time to track it down .
*/
aport - > swbuf = vmalloc ( aport - > swbuf_size + PAGE_SIZE ) ;
if ( ! aport - > swbuf )
return - ENOMEM ;
if ( rport & & wport ) {
ASSERT ( aport = = rport ) ;
ASSERT ( wport - > swbuf = = NULL ) ;
/* One extra page - see comment above. */
wport - > swbuf = vmalloc ( aport - > swbuf_size + PAGE_SIZE ) ;
if ( ! wport - > swbuf ) {
vfree ( aport - > swbuf ) ;
aport - > swbuf = NULL ;
return - ENOMEM ;
}
wport - > sample_size = rport - > sample_size ;
wport - > zero_word = rport - > zero_word ;
wport - > frame_size = rport - > frame_size ;
wport - > hw_fragshift = rport - > hw_fragshift ;
wport - > hw_fragsize = rport - > hw_fragsize ;
wport - > hw_fragcount = rport - > hw_fragcount ;
wport - > swbuf_size = rport - > swbuf_size ;
wport - > hwbuf_max = rport - > hwbuf_max ;
wport - > swb_u_idx = rport - > swb_u_idx ;
wport - > swb_i_idx = rport - > swb_i_idx ;
wport - > byte_count = rport - > byte_count ;
}
if ( rport ) {
rport - > swb_u_avail = 0 ;
rport - > swb_i_avail = rport - > swbuf_size ;
rport - > swstate = SW_RUN ;
li_setup_dma ( & rport - > chan ,
& li_comm1 ,
& devc - > lith ,
rport - > hwbuf_paddr ,
HWBUF_SHIFT ,
rport - > hw_fragshift ,
rport - > sw_channels ,
rport - > sample_size ) ;
ad1843_setup_adc ( & devc - > lith ,
rport - > sw_framerate ,
rport - > sw_samplefmt ,
rport - > sw_channels ) ;
li_enable_interrupts ( & devc - > lith , READ_INTR_MASK ) ;
if ( ! ( rport - > flags & DISABLED ) ) {
ustmsc_t ustmsc ;
rport - > hwstate = HW_RUNNING ;
li_activate_dma ( & rport - > chan ) ;
li_read_USTMSC ( & rport - > chan , & ustmsc ) ;
rport - > MSC_offset = ustmsc . msc ;
}
}
if ( wport ) {
if ( wport - > hwbuf_max > wport - > swbuf_size )
wport - > hwbuf_max = wport - > swbuf_size ;
wport - > flags & = ~ ERFLOWN ;
wport - > swb_u_avail = wport - > swbuf_size ;
wport - > swb_i_avail = 0 ;
wport - > swstate = SW_RUN ;
li_setup_dma ( & wport - > chan ,
& li_comm2 ,
& devc - > lith ,
wport - > hwbuf_paddr ,
HWBUF_SHIFT ,
wport - > hw_fragshift ,
wport - > sw_channels ,
wport - > sample_size ) ;
ad1843_setup_dac ( & devc - > lith ,
wport - > sw_framerate ,
wport - > sw_samplefmt ,
wport - > sw_channels ) ;
li_enable_interrupts ( & devc - > lith , WRITE_INTR_MASK ) ;
}
DBGRV ( ) ;
return 0 ;
}
/*
* pcm_shutdown_port - shut down one port ( direction ) for PCM I / O .
* Only called from pcm_shutdown .
*/
static void pcm_shutdown_port ( vwsnd_dev_t * devc ,
vwsnd_port_t * aport ,
unsigned int mask )
{
unsigned long flags ;
vwsnd_port_hwstate_t hwstate ;
DECLARE_WAITQUEUE ( wait , current ) ;
aport - > swstate = SW_INITIAL ;
add_wait_queue ( & aport - > queue , & wait ) ;
while ( 1 ) {
set_current_state ( TASK_UNINTERRUPTIBLE ) ;
spin_lock_irqsave ( & aport - > lock , flags ) ;
{
hwstate = aport - > hwstate ;
}
spin_unlock_irqrestore ( & aport - > lock , flags ) ;
if ( hwstate = = HW_STOPPED )
break ;
schedule ( ) ;
}
current - > state = TASK_RUNNING ;
remove_wait_queue ( & aport - > queue , & wait ) ;
li_disable_interrupts ( & devc - > lith , mask ) ;
if ( aport = = & devc - > rport )
ad1843_shutdown_adc ( & devc - > lith ) ;
else /* aport == &devc->wport) */
ad1843_shutdown_dac ( & devc - > lith ) ;
li_shutdown_dma ( & aport - > chan ) ;
vfree ( aport - > swbuf ) ;
aport - > swbuf = NULL ;
aport - > byte_count = 0 ;
}
/*
* pcm_shutdown undoes what pcm_setup did .
* Also sets the ports ' swstate to newstate .
*/
static void pcm_shutdown ( vwsnd_dev_t * devc ,
vwsnd_port_t * rport ,
vwsnd_port_t * wport )
{
DBGEV ( " (devc=0x%p, rport=0x%p, wport=0x%p) \n " , devc , rport , wport ) ;
if ( rport & & rport - > swbuf ) {
DBGPV ( " shutting down rport \n " ) ;
pcm_shutdown_port ( devc , rport , READ_INTR_MASK ) ;
}
if ( wport & & wport - > swbuf ) {
DBGPV ( " shutting down wport \n " ) ;
pcm_shutdown_port ( devc , wport , WRITE_INTR_MASK ) ;
}
DBGRV ( ) ;
}
static void pcm_copy_in ( vwsnd_port_t * rport , int swidx , int hwidx , int nb )
{
char * src = rport - > hwbuf + hwidx ;
char * dst = rport - > swbuf + swidx ;
int fmt = rport - > sw_samplefmt ;
DBGPV ( " swidx = %d, hwidx = %d \n " , swidx , hwidx ) ;
ASSERT ( rport - > hwbuf ! = NULL ) ;
ASSERT ( rport - > swbuf ! = NULL ) ;
ASSERT ( nb > 0 & & ( nb % 32 ) = = 0 ) ;
ASSERT ( swidx % 32 = = 0 & & hwidx % 32 = = 0 ) ;
ASSERT ( swidx > = 0 & & swidx + nb < = rport - > swbuf_size ) ;
ASSERT ( hwidx > = 0 & & hwidx + nb < = rport - > hwbuf_size ) ;
if ( fmt = = AFMT_MU_LAW | | fmt = = AFMT_A_LAW | | fmt = = AFMT_S8 ) {
/* See Sample Format Notes above. */
char * end = src + nb ;
while ( src < end )
* dst + + = * src + + ^ 0x80 ;
} else
memcpy ( dst , src , nb ) ;
}
static void pcm_copy_out ( vwsnd_port_t * wport , int swidx , int hwidx , int nb )
{
char * src = wport - > swbuf + swidx ;
char * dst = wport - > hwbuf + hwidx ;
int fmt = wport - > sw_samplefmt ;
ASSERT ( nb > 0 & & ( nb % 32 ) = = 0 ) ;
ASSERT ( wport - > hwbuf ! = NULL ) ;
ASSERT ( wport - > swbuf ! = NULL ) ;
ASSERT ( swidx % 32 = = 0 & & hwidx % 32 = = 0 ) ;
ASSERT ( swidx > = 0 & & swidx + nb < = wport - > swbuf_size ) ;
ASSERT ( hwidx > = 0 & & hwidx + nb < = wport - > hwbuf_size ) ;
if ( fmt = = AFMT_MU_LAW | | fmt = = AFMT_A_LAW | | fmt = = AFMT_S8 ) {
/* See Sample Format Notes above. */
char * end = src + nb ;
while ( src < end )
* dst + + = * src + + ^ 0x80 ;
} else
memcpy ( dst , src , nb ) ;
}
/*
* pcm_output ( ) is called both from baselevel and from interrupt level .
* This is where audio frames are copied into the hardware - accessible
* ring buffer .
*
* Locking note : The part of this routine that figures out what to do
* holds wport - > lock . The longer part releases wport - > lock , but sets
* wport - > flags & HW_BUSY . Afterward , it reacquires wport - > lock , and
* checks for more work to do .
*
* If another thread calls pcm_output ( ) while HW_BUSY is set , it
* returns immediately , knowing that the thread that set HW_BUSY will
* look for more work to do before returning .
*
* This has the advantage that port - > lock is held for several short
* periods instead of one long period . Also , when pcm_output is
* called from base level , it reenables interrupts .
*/
static void pcm_output ( vwsnd_dev_t * devc , int erflown , int nb )
{
vwsnd_port_t * wport = & devc - > wport ;
const int hwmax = wport - > hwbuf_max ;
const int hwsize = wport - > hwbuf_size ;
const int swsize = wport - > swbuf_size ;
const int fragsize = wport - > hw_fragsize ;
unsigned long iflags ;
DBGEV ( " (devc=0x%p, erflown=%d, nb=%d) \n " , devc , erflown , nb ) ;
spin_lock_irqsave ( & wport - > lock , iflags ) ;
if ( erflown )
wport - > flags | = ERFLOWN ;
( void ) __swb_inc_u ( wport , nb ) ;
if ( wport - > flags & HW_BUSY ) {
spin_unlock_irqrestore ( & wport - > lock , iflags ) ;
DBGPV ( " returning: HW BUSY \n " ) ;
return ;
}
if ( wport - > flags & DISABLED ) {
spin_unlock_irqrestore ( & wport - > lock , iflags ) ;
DBGPV ( " returning: DISABLED \n " ) ;
return ;
}
wport - > flags | = HW_BUSY ;
while ( 1 ) {
int swptr , hwptr , hw_avail , sw_avail , swidx ;
vwsnd_port_hwstate_t hwstate = wport - > hwstate ;
vwsnd_port_swstate_t swstate = wport - > swstate ;
int hw_unavail ;
ustmsc_t ustmsc ;
hwptr = li_read_hwptr ( & wport - > chan ) ;
swptr = li_read_swptr ( & wport - > chan ) ;
hw_unavail = ( swptr - hwptr + hwsize ) % hwsize ;
hw_avail = ( hwmax - hw_unavail ) & - fragsize ;
sw_avail = wport - > swb_i_avail & - fragsize ;
if ( sw_avail & & swstate = = SW_RUN ) {
if ( wport - > flags & ERFLOWN ) {
wport - > flags & = ~ ERFLOWN ;
}
} else if ( swstate = = SW_INITIAL | |
swstate = = SW_OFF | |
( swstate = = SW_DRAIN & &
! sw_avail & &
( wport - > flags & ERFLOWN ) ) ) {
DBGP ( " stopping. hwstate = %d \n " , hwstate ) ;
if ( hwstate ! = HW_STOPPED ) {
li_deactivate_dma ( & wport - > chan ) ;
wport - > hwstate = HW_STOPPED ;
}
wake_up ( & wport - > queue ) ;
break ;
}
if ( ! sw_avail | | ! hw_avail )
break ;
spin_unlock_irqrestore ( & wport - > lock , iflags ) ;
/*
* We gave up the port lock , but we have the HW_BUSY flag .
* Proceed without accessing any nonlocal state .
* Do not exit the loop - - must check for more work .
*/
swidx = wport - > swb_i_idx ;
nb = hw_avail ;
if ( nb > sw_avail )
nb = sw_avail ;
if ( nb > hwsize - swptr )
nb = hwsize - swptr ; /* don't overflow hwbuf */
if ( nb > swsize - swidx )
nb = swsize - swidx ; /* don't overflow swbuf */
ASSERT ( nb > 0 ) ;
if ( nb % fragsize ) {
DBGP ( " nb = %d, fragsize = %d \n " , nb , fragsize ) ;
DBGP ( " hw_avail = %d \n " , hw_avail ) ;
DBGP ( " sw_avail = %d \n " , sw_avail ) ;
DBGP ( " hwsize = %d, swptr = %d \n " , hwsize , swptr ) ;
DBGP ( " swsize = %d, swidx = %d \n " , swsize , swidx ) ;
}
ASSERT ( ! ( nb % fragsize ) ) ;
DBGPV ( " copying swb[%d..%d] to hwb[%d..%d] \n " ,
swidx , swidx + nb , swptr , swptr + nb ) ;
pcm_copy_out ( wport , swidx , swptr , nb ) ;
li_write_swptr ( & wport - > chan , ( swptr + nb ) % hwsize ) ;
spin_lock_irqsave ( & wport - > lock , iflags ) ;
if ( hwstate = = HW_STOPPED ) {
DBGPV ( " starting \n " ) ;
li_activate_dma ( & wport - > chan ) ;
wport - > hwstate = HW_RUNNING ;
li_read_USTMSC ( & wport - > chan , & ustmsc ) ;
ASSERT ( wport - > byte_count % wport - > frame_size = = 0 ) ;
wport - > MSC_offset = ustmsc . msc - wport - > byte_count / wport - > frame_size ;
}
__swb_inc_i ( wport , nb ) ;
wport - > byte_count + = nb ;
wport - > frag_count + = nb / fragsize ;
ASSERT ( nb % fragsize = = 0 ) ;
wake_up ( & wport - > queue ) ;
}
wport - > flags & = ~ HW_BUSY ;
spin_unlock_irqrestore ( & wport - > lock , iflags ) ;
DBGRV ( ) ;
}
/*
* pcm_input ( ) is called both from baselevel and from interrupt level .
* This is where audio frames are copied out of the hardware - accessible
* ring buffer .
*
* Locking note : The part of this routine that figures out what to do
* holds rport - > lock . The longer part releases rport - > lock , but sets
* rport - > flags & HW_BUSY . Afterward , it reacquires rport - > lock , and
* checks for more work to do .
*
* If another thread calls pcm_input ( ) while HW_BUSY is set , it
* returns immediately , knowing that the thread that set HW_BUSY will
* look for more work to do before returning .
*
* This has the advantage that port - > lock is held for several short
* periods instead of one long period . Also , when pcm_input is
* called from base level , it reenables interrupts .
*/
static void pcm_input ( vwsnd_dev_t * devc , int erflown , int nb )
{
vwsnd_port_t * rport = & devc - > rport ;
const int hwmax = rport - > hwbuf_max ;
const int hwsize = rport - > hwbuf_size ;
const int swsize = rport - > swbuf_size ;
const int fragsize = rport - > hw_fragsize ;
unsigned long iflags ;
DBGEV ( " (devc=0x%p, erflown=%d, nb=%d) \n " , devc , erflown , nb ) ;
spin_lock_irqsave ( & rport - > lock , iflags ) ;
if ( erflown )
rport - > flags | = ERFLOWN ;
( void ) __swb_inc_u ( rport , nb ) ;
if ( rport - > flags & HW_BUSY | | ! rport - > swbuf ) {
spin_unlock_irqrestore ( & rport - > lock , iflags ) ;
DBGPV ( " returning: HW BUSY or !swbuf \n " ) ;
return ;
}
if ( rport - > flags & DISABLED ) {
spin_unlock_irqrestore ( & rport - > lock , iflags ) ;
DBGPV ( " returning: DISABLED \n " ) ;
return ;
}
rport - > flags | = HW_BUSY ;
while ( 1 ) {
int swptr , hwptr , hw_avail , sw_avail , swidx ;
vwsnd_port_hwstate_t hwstate = rport - > hwstate ;
vwsnd_port_swstate_t swstate = rport - > swstate ;
hwptr = li_read_hwptr ( & rport - > chan ) ;
swptr = li_read_swptr ( & rport - > chan ) ;
hw_avail = ( hwptr - swptr + hwsize ) % hwsize & - fragsize ;
if ( hw_avail > hwmax )
hw_avail = hwmax ;
sw_avail = rport - > swb_i_avail & - fragsize ;
if ( swstate ! = SW_RUN ) {
DBGP ( " stopping. hwstate = %d \n " , hwstate ) ;
if ( hwstate ! = HW_STOPPED ) {
li_deactivate_dma ( & rport - > chan ) ;
rport - > hwstate = HW_STOPPED ;
}
wake_up ( & rport - > queue ) ;
break ;
}
if ( ! sw_avail | | ! hw_avail )
break ;
spin_unlock_irqrestore ( & rport - > lock , iflags ) ;
/*
* We gave up the port lock , but we have the HW_BUSY flag .
* Proceed without accessing any nonlocal state .
* Do not exit the loop - - must check for more work .
*/
swidx = rport - > swb_i_idx ;
nb = hw_avail ;
if ( nb > sw_avail )
nb = sw_avail ;
if ( nb > hwsize - swptr )
nb = hwsize - swptr ; /* don't overflow hwbuf */
if ( nb > swsize - swidx )
nb = swsize - swidx ; /* don't overflow swbuf */
ASSERT ( nb > 0 ) ;
if ( nb % fragsize ) {
DBGP ( " nb = %d, fragsize = %d \n " , nb , fragsize ) ;
DBGP ( " hw_avail = %d \n " , hw_avail ) ;
DBGP ( " sw_avail = %d \n " , sw_avail ) ;
DBGP ( " hwsize = %d, swptr = %d \n " , hwsize , swptr ) ;
DBGP ( " swsize = %d, swidx = %d \n " , swsize , swidx ) ;
}
ASSERT ( ! ( nb % fragsize ) ) ;
DBGPV ( " copying hwb[%d..%d] to swb[%d..%d] \n " ,
swptr , swptr + nb , swidx , swidx + nb ) ;
pcm_copy_in ( rport , swidx , swptr , nb ) ;
li_write_swptr ( & rport - > chan , ( swptr + nb ) % hwsize ) ;
spin_lock_irqsave ( & rport - > lock , iflags ) ;
__swb_inc_i ( rport , nb ) ;
rport - > byte_count + = nb ;
rport - > frag_count + = nb / fragsize ;
ASSERT ( nb % fragsize = = 0 ) ;
wake_up ( & rport - > queue ) ;
}
rport - > flags & = ~ HW_BUSY ;
spin_unlock_irqrestore ( & rport - > lock , iflags ) ;
DBGRV ( ) ;
}
/*
* pcm_flush_frag ( ) writes zero samples to fill the current fragment ,
* then flushes it to the hardware .
*
* It is only meaningful to flush output , not input .
*/
static void pcm_flush_frag ( vwsnd_dev_t * devc )
{
vwsnd_port_t * wport = & devc - > wport ;
DBGPV ( " swstate = %d \n " , wport - > swstate ) ;
if ( wport - > swstate = = SW_RUN ) {
int idx = wport - > swb_u_idx ;
int end = ( idx + wport - > hw_fragsize - 1 )
> > wport - > hw_fragshift
< < wport - > hw_fragshift ;
int nb = end - idx ;
DBGPV ( " clearing %d bytes \n " , nb ) ;
if ( nb )
memset ( wport - > swbuf + idx ,
( char ) wport - > zero_word ,
nb ) ;
wport - > swstate = SW_DRAIN ;
pcm_output ( devc , 0 , nb ) ;
}
DBGRV ( ) ;
}
/*
* Wait for output to drain . This sleeps uninterruptibly because
* there is nothing intelligent we can do if interrupted . This
* means the process will be delayed in responding to the signal .
*/
static void pcm_write_sync ( vwsnd_dev_t * devc )
{
vwsnd_port_t * wport = & devc - > wport ;
DECLARE_WAITQUEUE ( wait , current ) ;
unsigned long flags ;
vwsnd_port_hwstate_t hwstate ;
DBGEV ( " (devc=0x%p) \n " , devc ) ;
add_wait_queue ( & wport - > queue , & wait ) ;
while ( 1 ) {
set_current_state ( TASK_UNINTERRUPTIBLE ) ;
spin_lock_irqsave ( & wport - > lock , flags ) ;
{
hwstate = wport - > hwstate ;
}
spin_unlock_irqrestore ( & wport - > lock , flags ) ;
if ( hwstate = = HW_STOPPED )
break ;
schedule ( ) ;
}
current - > state = TASK_RUNNING ;
remove_wait_queue ( & wport - > queue , & wait ) ;
DBGPV ( " swstate = %d, hwstate = %d \n " , wport - > swstate , wport - > hwstate ) ;
DBGRV ( ) ;
}
/*****************************************************************************/
/* audio driver */
/*
* seek on an audio device always fails .
*/
static void vwsnd_audio_read_intr ( vwsnd_dev_t * devc , unsigned int status )
{
int overflown = status & LI_INTR_COMM1_OVERFLOW ;
if ( status & READ_INTR_MASK )
pcm_input ( devc , overflown , 0 ) ;
}
static void vwsnd_audio_write_intr ( vwsnd_dev_t * devc , unsigned int status )
{
int underflown = status & LI_INTR_COMM2_UNDERFLOW ;
if ( status & WRITE_INTR_MASK )
pcm_output ( devc , underflown , 0 ) ;
}
static irqreturn_t vwsnd_audio_intr ( int irq , void * dev_id , struct pt_regs * regs )
{
vwsnd_dev_t * devc = ( vwsnd_dev_t * ) dev_id ;
unsigned int status ;
DBGEV ( " (irq=%d, dev_id=0x%p, regs=0x%p) \n " , irq , dev_id , regs ) ;
status = li_get_clear_intr_status ( & devc - > lith ) ;
vwsnd_audio_read_intr ( devc , status ) ;
vwsnd_audio_write_intr ( devc , status ) ;
return IRQ_HANDLED ;
}
static ssize_t vwsnd_audio_do_read ( struct file * file ,
char * buffer ,
size_t count ,
loff_t * ppos )
{
vwsnd_dev_t * devc = file - > private_data ;
vwsnd_port_t * rport = ( ( file - > f_mode & FMODE_READ ) ?
& devc - > rport : NULL ) ;
int ret , nb ;
DBGEV ( " (file=0x%p, buffer=0x%p, count=%d, ppos=0x%p) \n " ,
file , buffer , count , ppos ) ;
if ( ! rport )
return - EINVAL ;
if ( rport - > swbuf = = NULL ) {
vwsnd_port_t * wport = ( file - > f_mode & FMODE_WRITE ) ?
& devc - > wport : NULL ;
ret = pcm_setup ( devc , rport , wport ) ;
if ( ret < 0 )
return ret ;
}
if ( ! access_ok ( VERIFY_READ , buffer , count ) )
return - EFAULT ;
ret = 0 ;
while ( count ) {
DECLARE_WAITQUEUE ( wait , current ) ;
add_wait_queue ( & rport - > queue , & wait ) ;
while ( ( nb = swb_inc_u ( rport , 0 ) ) = = 0 ) {
DBGPV ( " blocking \n " ) ;
set_current_state ( TASK_INTERRUPTIBLE ) ;
if ( rport - > flags & DISABLED | |
file - > f_flags & O_NONBLOCK ) {
current - > state = TASK_RUNNING ;
remove_wait_queue ( & rport - > queue , & wait ) ;
return ret ? ret : - EAGAIN ;
}
schedule ( ) ;
if ( signal_pending ( current ) ) {
current - > state = TASK_RUNNING ;
remove_wait_queue ( & rport - > queue , & wait ) ;
return ret ? ret : - ERESTARTSYS ;
}
}
current - > state = TASK_RUNNING ;
remove_wait_queue ( & rport - > queue , & wait ) ;
pcm_input ( devc , 0 , 0 ) ;
/* nb bytes are available in userbuf. */
if ( nb > count )
nb = count ;
DBGPV ( " nb = %d \n " , nb ) ;
if ( copy_to_user ( buffer , rport - > swbuf + rport - > swb_u_idx , nb ) )
return - EFAULT ;
( void ) swb_inc_u ( rport , nb ) ;
buffer + = nb ;
count - = nb ;
ret + = nb ;
}
DBGPV ( " returning %d \n " , ret ) ;
return ret ;
}
static ssize_t vwsnd_audio_read ( struct file * file ,
char * buffer ,
size_t count ,
loff_t * ppos )
{
vwsnd_dev_t * devc = file - > private_data ;
ssize_t ret ;
2006-03-23 03:00:39 -08:00
mutex_lock ( & devc - > io_mutex ) ;
2005-04-16 15:20:36 -07:00
ret = vwsnd_audio_do_read ( file , buffer , count , ppos ) ;
2006-03-23 03:00:39 -08:00
mutex_unlock ( & devc - > io_mutex ) ;
2005-04-16 15:20:36 -07:00
return ret ;
}
static ssize_t vwsnd_audio_do_write ( struct file * file ,
const char * buffer ,
size_t count ,
loff_t * ppos )
{
vwsnd_dev_t * devc = file - > private_data ;
vwsnd_port_t * wport = ( ( file - > f_mode & FMODE_WRITE ) ?
& devc - > wport : NULL ) ;
int ret , nb ;
DBGEV ( " (file=0x%p, buffer=0x%p, count=%d, ppos=0x%p) \n " ,
file , buffer , count , ppos ) ;
if ( ! wport )
return - EINVAL ;
if ( wport - > swbuf = = NULL ) {
vwsnd_port_t * rport = ( file - > f_mode & FMODE_READ ) ?
& devc - > rport : NULL ;
ret = pcm_setup ( devc , rport , wport ) ;
if ( ret < 0 )
return ret ;
}
if ( ! access_ok ( VERIFY_WRITE , buffer , count ) )
return - EFAULT ;
ret = 0 ;
while ( count ) {
DECLARE_WAITQUEUE ( wait , current ) ;
add_wait_queue ( & wport - > queue , & wait ) ;
while ( ( nb = swb_inc_u ( wport , 0 ) ) = = 0 ) {
set_current_state ( TASK_INTERRUPTIBLE ) ;
if ( wport - > flags & DISABLED | |
file - > f_flags & O_NONBLOCK ) {
current - > state = TASK_RUNNING ;
remove_wait_queue ( & wport - > queue , & wait ) ;
return ret ? ret : - EAGAIN ;
}
schedule ( ) ;
if ( signal_pending ( current ) ) {
current - > state = TASK_RUNNING ;
remove_wait_queue ( & wport - > queue , & wait ) ;
return ret ? ret : - ERESTARTSYS ;
}
}
current - > state = TASK_RUNNING ;
remove_wait_queue ( & wport - > queue , & wait ) ;
/* nb bytes are available in userbuf. */
if ( nb > count )
nb = count ;
DBGPV ( " nb = %d \n " , nb ) ;
if ( copy_from_user ( wport - > swbuf + wport - > swb_u_idx , buffer , nb ) )
return - EFAULT ;
pcm_output ( devc , 0 , nb ) ;
buffer + = nb ;
count - = nb ;
ret + = nb ;
}
DBGPV ( " returning %d \n " , ret ) ;
return ret ;
}
static ssize_t vwsnd_audio_write ( struct file * file ,
const char * buffer ,
size_t count ,
loff_t * ppos )
{
vwsnd_dev_t * devc = file - > private_data ;
ssize_t ret ;
2006-03-23 03:00:39 -08:00
mutex_lock ( & devc - > io_mutex ) ;
2005-04-16 15:20:36 -07:00
ret = vwsnd_audio_do_write ( file , buffer , count , ppos ) ;
2006-03-23 03:00:39 -08:00
mutex_unlock ( & devc - > io_mutex ) ;
2005-04-16 15:20:36 -07:00
return ret ;
}
/* No kernel lock - fine */
static unsigned int vwsnd_audio_poll ( struct file * file ,
struct poll_table_struct * wait )
{
vwsnd_dev_t * devc = ( vwsnd_dev_t * ) file - > private_data ;
vwsnd_port_t * rport = ( file - > f_mode & FMODE_READ ) ?
& devc - > rport : NULL ;
vwsnd_port_t * wport = ( file - > f_mode & FMODE_WRITE ) ?
& devc - > wport : NULL ;
unsigned int mask = 0 ;
DBGEV ( " (file=0x%p, wait=0x%p) \n " , file , wait ) ;
ASSERT ( rport | | wport ) ;
if ( rport ) {
poll_wait ( file , & rport - > queue , wait ) ;
if ( swb_inc_u ( rport , 0 ) )
mask | = ( POLLIN | POLLRDNORM ) ;
}
if ( wport ) {
poll_wait ( file , & wport - > queue , wait ) ;
if ( wport - > swbuf = = NULL | | swb_inc_u ( wport , 0 ) )
mask | = ( POLLOUT | POLLWRNORM ) ;
}
DBGPV ( " returning 0x%x \n " , mask ) ;
return mask ;
}
static int vwsnd_audio_do_ioctl ( struct inode * inode ,
struct file * file ,
unsigned int cmd ,
unsigned long arg )
{
vwsnd_dev_t * devc = ( vwsnd_dev_t * ) file - > private_data ;
vwsnd_port_t * rport = ( file - > f_mode & FMODE_READ ) ?
& devc - > rport : NULL ;
vwsnd_port_t * wport = ( file - > f_mode & FMODE_WRITE ) ?
& devc - > wport : NULL ;
vwsnd_port_t * aport = rport ? rport : wport ;
struct audio_buf_info buf_info ;
struct count_info info ;
unsigned long flags ;
int ival ;
DBGEV ( " (inode=0x%p, file=0x%p, cmd=0x%x, arg=0x%lx) \n " ,
inode , file , cmd , arg ) ;
switch ( cmd ) {
case OSS_GETVERSION : /* _SIOR ('M', 118, int) */
DBGX ( " OSS_GETVERSION \n " ) ;
ival = SOUND_VERSION ;
return put_user ( ival , ( int * ) arg ) ;
case SNDCTL_DSP_GETCAPS : /* _SIOR ('P',15, int) */
DBGX ( " SNDCTL_DSP_GETCAPS \n " ) ;
ival = DSP_CAP_DUPLEX | DSP_CAP_REALTIME | DSP_CAP_TRIGGER ;
return put_user ( ival , ( int * ) arg ) ;
case SNDCTL_DSP_GETFMTS : /* _SIOR ('P',11, int) */
DBGX ( " SNDCTL_DSP_GETFMTS \n " ) ;
ival = ( AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW |
AFMT_U8 | AFMT_S8 ) ;
return put_user ( ival , ( int * ) arg ) ;
break ;
case SOUND_PCM_READ_RATE : /* _SIOR ('P', 2, int) */
DBGX ( " SOUND_PCM_READ_RATE \n " ) ;
ival = aport - > sw_framerate ;
return put_user ( ival , ( int * ) arg ) ;
case SOUND_PCM_READ_CHANNELS : /* _SIOR ('P', 6, int) */
DBGX ( " SOUND_PCM_READ_CHANNELS \n " ) ;
ival = aport - > sw_channels ;
return put_user ( ival , ( int * ) arg ) ;
case SNDCTL_DSP_SPEED : /* _SIOWR('P', 2, int) */
if ( get_user ( ival , ( int * ) arg ) )
return - EFAULT ;
DBGX ( " SNDCTL_DSP_SPEED %d \n " , ival ) ;
if ( ival ) {
if ( aport - > swstate ! = SW_INITIAL ) {
DBGX ( " SNDCTL_DSP_SPEED failed: swstate = %d \n " ,
aport - > swstate ) ;
return - EINVAL ;
}
if ( ival < MIN_SPEED )
ival = MIN_SPEED ;
if ( ival > MAX_SPEED )
ival = MAX_SPEED ;
if ( rport )
rport - > sw_framerate = ival ;
if ( wport )
wport - > sw_framerate = ival ;
} else
ival = aport - > sw_framerate ;
return put_user ( ival , ( int * ) arg ) ;
case SNDCTL_DSP_STEREO : /* _SIOWR('P', 3, int) */
if ( get_user ( ival , ( int * ) arg ) )
return - EFAULT ;
DBGX ( " SNDCTL_DSP_STEREO %d \n " , ival ) ;
if ( ival ! = 0 & & ival ! = 1 )
return - EINVAL ;
if ( aport - > swstate ! = SW_INITIAL )
return - EINVAL ;
if ( rport )
rport - > sw_channels = ival + 1 ;
if ( wport )
wport - > sw_channels = ival + 1 ;
return put_user ( ival , ( int * ) arg ) ;
case SNDCTL_DSP_CHANNELS : /* _SIOWR('P', 6, int) */
if ( get_user ( ival , ( int * ) arg ) )
return - EFAULT ;
DBGX ( " SNDCTL_DSP_CHANNELS %d \n " , ival ) ;
if ( ival ! = 1 & & ival ! = 2 )
return - EINVAL ;
if ( aport - > swstate ! = SW_INITIAL )
return - EINVAL ;
if ( rport )
rport - > sw_channels = ival ;
if ( wport )
wport - > sw_channels = ival ;
return put_user ( ival , ( int * ) arg ) ;
case SNDCTL_DSP_GETBLKSIZE : /* _SIOWR('P', 4, int) */
ival = pcm_setup ( devc , rport , wport ) ;
if ( ival < 0 ) {
DBGX ( " SNDCTL_DSP_GETBLKSIZE failed, errno %d \n " , ival ) ;
return ival ;
}
ival = 1 < < aport - > sw_fragshift ;
DBGX ( " SNDCTL_DSP_GETBLKSIZE returning %d \n " , ival ) ;
return put_user ( ival , ( int * ) arg ) ;
case SNDCTL_DSP_SETFRAGMENT : /* _SIOWR('P',10, int) */
if ( get_user ( ival , ( int * ) arg ) )
return - EFAULT ;
DBGX ( " SNDCTL_DSP_SETFRAGMENT %d:%d \n " ,
ival > > 16 , ival & 0xFFFF ) ;
if ( aport - > swstate ! = SW_INITIAL )
return - EINVAL ;
{
int sw_fragshift = ival & 0xFFFF ;
int sw_subdivshift = aport - > sw_subdivshift ;
int hw_fragshift = sw_fragshift - sw_subdivshift ;
int sw_fragcount = ( ival > > 16 ) & 0xFFFF ;
int hw_fragsize ;
if ( hw_fragshift < MIN_FRAGSHIFT )
hw_fragshift = MIN_FRAGSHIFT ;
if ( hw_fragshift > MAX_FRAGSHIFT )
hw_fragshift = MAX_FRAGSHIFT ;
sw_fragshift = hw_fragshift + aport - > sw_subdivshift ;
hw_fragsize = 1 < < hw_fragshift ;
if ( sw_fragcount < MIN_FRAGCOUNT ( hw_fragsize ) )
sw_fragcount = MIN_FRAGCOUNT ( hw_fragsize ) ;
if ( sw_fragcount > MAX_FRAGCOUNT ( hw_fragsize ) )
sw_fragcount = MAX_FRAGCOUNT ( hw_fragsize ) ;
DBGPV ( " sw_fragshift = %d \n " , sw_fragshift ) ;
DBGPV ( " rport = 0x%p, wport = 0x%p \n " , rport , wport ) ;
if ( rport ) {
rport - > sw_fragshift = sw_fragshift ;
rport - > sw_fragcount = sw_fragcount ;
}
if ( wport ) {
wport - > sw_fragshift = sw_fragshift ;
wport - > sw_fragcount = sw_fragcount ;
}
ival = sw_fragcount < < 16 | sw_fragshift ;
}
DBGX ( " SNDCTL_DSP_SETFRAGMENT returns %d:%d \n " ,
ival > > 16 , ival & 0xFFFF ) ;
return put_user ( ival , ( int * ) arg ) ;
case SNDCTL_DSP_SUBDIVIDE : /* _SIOWR('P', 9, int) */
if ( get_user ( ival , ( int * ) arg ) )
return - EFAULT ;
DBGX ( " SNDCTL_DSP_SUBDIVIDE %d \n " , ival ) ;
if ( aport - > swstate ! = SW_INITIAL )
return - EINVAL ;
{
int subdivshift ;
int hw_fragshift , hw_fragsize , hw_fragcount ;
switch ( ival ) {
case 1 : subdivshift = 0 ; break ;
case 2 : subdivshift = 1 ; break ;
case 4 : subdivshift = 2 ; break ;
default : return - EINVAL ;
}
hw_fragshift = aport - > sw_fragshift - subdivshift ;
if ( hw_fragshift < MIN_FRAGSHIFT | |
hw_fragshift > MAX_FRAGSHIFT )
return - EINVAL ;
hw_fragsize = 1 < < hw_fragshift ;
hw_fragcount = aport - > sw_fragcount > > subdivshift ;
if ( hw_fragcount < MIN_FRAGCOUNT ( hw_fragsize ) | |
hw_fragcount > MAX_FRAGCOUNT ( hw_fragsize ) )
return - EINVAL ;
if ( rport )
rport - > sw_subdivshift = subdivshift ;
if ( wport )
wport - > sw_subdivshift = subdivshift ;
}
return 0 ;
case SNDCTL_DSP_SETFMT : /* _SIOWR('P',5, int) */
if ( get_user ( ival , ( int * ) arg ) )
return - EFAULT ;
DBGX ( " SNDCTL_DSP_SETFMT %d \n " , ival ) ;
if ( ival ! = AFMT_QUERY ) {
if ( aport - > swstate ! = SW_INITIAL ) {
DBGP ( " SETFMT failed, swstate = %d \n " ,
aport - > swstate ) ;
return - EINVAL ;
}
switch ( ival ) {
case AFMT_MU_LAW :
case AFMT_A_LAW :
case AFMT_U8 :
case AFMT_S8 :
case AFMT_S16_LE :
if ( rport )
rport - > sw_samplefmt = ival ;
if ( wport )
wport - > sw_samplefmt = ival ;
break ;
default :
return - EINVAL ;
}
}
ival = aport - > sw_samplefmt ;
return put_user ( ival , ( int * ) arg ) ;
case SNDCTL_DSP_GETOSPACE : /* _SIOR ('P',12, audio_buf_info) */
DBGXV ( " SNDCTL_DSP_GETOSPACE \n " ) ;
if ( ! wport )
return - EINVAL ;
ival = pcm_setup ( devc , rport , wport ) ;
if ( ival < 0 )
return ival ;
ival = swb_inc_u ( wport , 0 ) ;
buf_info . fragments = ival > > wport - > sw_fragshift ;
buf_info . fragstotal = wport - > sw_fragcount ;
buf_info . fragsize = 1 < < wport - > sw_fragshift ;
buf_info . bytes = ival ;
DBGXV ( " SNDCTL_DSP_GETOSPACE returns { %d %d %d %d } \n " ,
buf_info . fragments , buf_info . fragstotal ,
buf_info . fragsize , buf_info . bytes ) ;
if ( copy_to_user ( ( void * ) arg , & buf_info , sizeof buf_info ) )
return - EFAULT ;
return 0 ;
case SNDCTL_DSP_GETISPACE : /* _SIOR ('P',13, audio_buf_info) */
DBGX ( " SNDCTL_DSP_GETISPACE \n " ) ;
if ( ! rport )
return - EINVAL ;
ival = pcm_setup ( devc , rport , wport ) ;
if ( ival < 0 )
return ival ;
ival = swb_inc_u ( rport , 0 ) ;
buf_info . fragments = ival > > rport - > sw_fragshift ;
buf_info . fragstotal = rport - > sw_fragcount ;
buf_info . fragsize = 1 < < rport - > sw_fragshift ;
buf_info . bytes = ival ;
DBGX ( " SNDCTL_DSP_GETISPACE returns { %d %d %d %d } \n " ,
buf_info . fragments , buf_info . fragstotal ,
buf_info . fragsize , buf_info . bytes ) ;
if ( copy_to_user ( ( void * ) arg , & buf_info , sizeof buf_info ) )
return - EFAULT ;
return 0 ;
case SNDCTL_DSP_NONBLOCK : /* _SIO ('P',14) */
DBGX ( " SNDCTL_DSP_NONBLOCK \n " ) ;
file - > f_flags | = O_NONBLOCK ;
return 0 ;
case SNDCTL_DSP_RESET : /* _SIO ('P', 0) */
DBGX ( " SNDCTL_DSP_RESET \n " ) ;
/*
* Nothing special needs to be done for input . Input
* samples sit in swbuf , but it will be reinitialized
* to empty when pcm_setup ( ) is called .
*/
if ( wport & & wport - > swbuf ) {
wport - > swstate = SW_INITIAL ;
pcm_output ( devc , 0 , 0 ) ;
pcm_write_sync ( devc ) ;
}
pcm_shutdown ( devc , rport , wport ) ;
return 0 ;
case SNDCTL_DSP_SYNC : /* _SIO ('P', 1) */
DBGX ( " SNDCTL_DSP_SYNC \n " ) ;
if ( wport ) {
pcm_flush_frag ( devc ) ;
pcm_write_sync ( devc ) ;
}
pcm_shutdown ( devc , rport , wport ) ;
return 0 ;
case SNDCTL_DSP_POST : /* _SIO ('P', 8) */
DBGX ( " SNDCTL_DSP_POST \n " ) ;
if ( ! wport )
return - EINVAL ;
pcm_flush_frag ( devc ) ;
return 0 ;
case SNDCTL_DSP_GETIPTR : /* _SIOR ('P', 17, count_info) */
DBGX ( " SNDCTL_DSP_GETIPTR \n " ) ;
if ( ! rport )
return - EINVAL ;
spin_lock_irqsave ( & rport - > lock , flags ) ;
{
ustmsc_t ustmsc ;
if ( rport - > hwstate = = HW_RUNNING ) {
ASSERT ( rport - > swstate = = SW_RUN ) ;
li_read_USTMSC ( & rport - > chan , & ustmsc ) ;
info . bytes = ustmsc . msc - rport - > MSC_offset ;
info . bytes * = rport - > frame_size ;
} else {
info . bytes = rport - > byte_count ;
}
info . blocks = rport - > frag_count ;
info . ptr = 0 ; /* not implemented */
rport - > frag_count = 0 ;
}
spin_unlock_irqrestore ( & rport - > lock , flags ) ;
if ( copy_to_user ( ( void * ) arg , & info , sizeof info ) )
return - EFAULT ;
return 0 ;
case SNDCTL_DSP_GETOPTR : /* _SIOR ('P',18, count_info) */
DBGX ( " SNDCTL_DSP_GETOPTR \n " ) ;
if ( ! wport )
return - EINVAL ;
spin_lock_irqsave ( & wport - > lock , flags ) ;
{
ustmsc_t ustmsc ;
if ( wport - > hwstate = = HW_RUNNING ) {
ASSERT ( wport - > swstate = = SW_RUN ) ;
li_read_USTMSC ( & wport - > chan , & ustmsc ) ;
info . bytes = ustmsc . msc - wport - > MSC_offset ;
info . bytes * = wport - > frame_size ;
} else {
info . bytes = wport - > byte_count ;
}
info . blocks = wport - > frag_count ;
info . ptr = 0 ; /* not implemented */
wport - > frag_count = 0 ;
}
spin_unlock_irqrestore ( & wport - > lock , flags ) ;
if ( copy_to_user ( ( void * ) arg , & info , sizeof info ) )
return - EFAULT ;
return 0 ;
case SNDCTL_DSP_GETODELAY : /* _SIOR ('P', 23, int) */
DBGX ( " SNDCTL_DSP_GETODELAY \n " ) ;
if ( ! wport )
return - EINVAL ;
spin_lock_irqsave ( & wport - > lock , flags ) ;
{
int fsize = wport - > frame_size ;
ival = wport - > swb_i_avail / fsize ;
if ( wport - > hwstate = = HW_RUNNING ) {
int swptr , hwptr , hwframes , hwbytes , hwsize ;
int totalhwbytes ;
ustmsc_t ustmsc ;
hwsize = wport - > hwbuf_size ;
swptr = li_read_swptr ( & wport - > chan ) ;
li_read_USTMSC ( & wport - > chan , & ustmsc ) ;
hwframes = ustmsc . msc - wport - > MSC_offset ;
totalhwbytes = hwframes * fsize ;
hwptr = totalhwbytes % hwsize ;
hwbytes = ( swptr - hwptr + hwsize ) % hwsize ;
ival + = hwbytes / fsize ;
}
}
spin_unlock_irqrestore ( & wport - > lock , flags ) ;
return put_user ( ival , ( int * ) arg ) ;
case SNDCTL_DSP_PROFILE : /* _SIOW ('P', 23, int) */
DBGX ( " SNDCTL_DSP_PROFILE \n " ) ;
/*
* Thomas Sailer explains SNDCTL_DSP_PROFILE
* ( private email , March 24 , 1999 ) :
*
* This gives the sound driver a hint on what it
* should do with partial fragments
* ( i . e . fragments partially filled with write ) .
* This can direct the driver to zero them or
* leave them alone . But don ' t ask me what this
* is good for , my driver just zeroes the last
* fragment before the receiver stops , no idea
* what good for any other behaviour could
* be . Implementing it as NOP seems safe .
*/
break ;
case SNDCTL_DSP_GETTRIGGER : /* _SIOR ('P',16, int) */
DBGX ( " SNDCTL_DSP_GETTRIGGER \n " ) ;
ival = 0 ;
if ( rport ) {
spin_lock_irqsave ( & rport - > lock , flags ) ;
{
if ( ! ( rport - > flags & DISABLED ) )
ival | = PCM_ENABLE_INPUT ;
}
spin_unlock_irqrestore ( & rport - > lock , flags ) ;
}
if ( wport ) {
spin_lock_irqsave ( & wport - > lock , flags ) ;
{
if ( ! ( wport - > flags & DISABLED ) )
ival | = PCM_ENABLE_OUTPUT ;
}
spin_unlock_irqrestore ( & wport - > lock , flags ) ;
}
return put_user ( ival , ( int * ) arg ) ;
case SNDCTL_DSP_SETTRIGGER : /* _SIOW ('P',16, int) */
if ( get_user ( ival , ( int * ) arg ) )
return - EFAULT ;
DBGX ( " SNDCTL_DSP_SETTRIGGER %d \n " , ival ) ;
/*
* If user is disabling I / O and port is not in initial
* state , fail with EINVAL .
*/
if ( ( ( rport & & ! ( ival & PCM_ENABLE_INPUT ) ) | |
( wport & & ! ( ival & PCM_ENABLE_OUTPUT ) ) ) & &
aport - > swstate ! = SW_INITIAL )
return - EINVAL ;
if ( rport ) {
vwsnd_port_hwstate_t hwstate ;
spin_lock_irqsave ( & rport - > lock , flags ) ;
{
hwstate = rport - > hwstate ;
if ( ival & PCM_ENABLE_INPUT )
rport - > flags & = ~ DISABLED ;
else
rport - > flags | = DISABLED ;
}
spin_unlock_irqrestore ( & rport - > lock , flags ) ;
if ( hwstate ! = HW_RUNNING & & ival & PCM_ENABLE_INPUT ) {
if ( rport - > swstate = = SW_INITIAL )
pcm_setup ( devc , rport , wport ) ;
else
li_activate_dma ( & rport - > chan ) ;
}
}
if ( wport ) {
vwsnd_port_flags_t pflags ;
spin_lock_irqsave ( & wport - > lock , flags ) ;
{
pflags = wport - > flags ;
if ( ival & PCM_ENABLE_OUTPUT )
wport - > flags & = ~ DISABLED ;
else
wport - > flags | = DISABLED ;
}
spin_unlock_irqrestore ( & wport - > lock , flags ) ;
if ( pflags & DISABLED & & ival & PCM_ENABLE_OUTPUT ) {
if ( wport - > swstate = = SW_RUN )
pcm_output ( devc , 0 , 0 ) ;
}
}
return 0 ;
default :
DBGP ( " unknown ioctl 0x%x \n " , cmd ) ;
return - EINVAL ;
}
DBGP ( " unimplemented ioctl 0x%x \n " , cmd ) ;
return - EINVAL ;
}
static int vwsnd_audio_ioctl ( struct inode * inode ,
struct file * file ,
unsigned int cmd ,
unsigned long arg )
{
vwsnd_dev_t * devc = ( vwsnd_dev_t * ) file - > private_data ;
int ret ;
2006-03-23 03:00:39 -08:00
mutex_lock ( & devc - > io_mutex ) ;
2005-04-16 15:20:36 -07:00
ret = vwsnd_audio_do_ioctl ( inode , file , cmd , arg ) ;
2006-03-23 03:00:39 -08:00
mutex_unlock ( & devc - > io_mutex ) ;
2005-04-16 15:20:36 -07:00
return ret ;
}
/* No mmap. */
static int vwsnd_audio_mmap ( struct file * file , struct vm_area_struct * vma )
{
DBGE ( " (file=0x%p, vma=0x%p) \n " , file , vma ) ;
return - ENODEV ;
}
/*
* Open the audio device for read and / or write .
*
* Returns 0 on success , - errno on failure .
*/
static int vwsnd_audio_open ( struct inode * inode , struct file * file )
{
vwsnd_dev_t * devc ;
int minor = iminor ( inode ) ;
int sw_samplefmt ;
DBGE ( " (inode=0x%p, file=0x%p) \n " , inode , file ) ;
INC_USE_COUNT ;
for ( devc = vwsnd_dev_list ; devc ; devc = devc - > next_dev )
if ( ( devc - > audio_minor & ~ 0x0F ) = = ( minor & ~ 0x0F ) )
break ;
if ( devc = = NULL ) {
DEC_USE_COUNT ;
return - ENODEV ;
}
2006-03-23 03:00:39 -08:00
mutex_lock ( & devc - > open_mutex ) ;
2005-04-16 15:20:36 -07:00
while ( devc - > open_mode & file - > f_mode ) {
2006-03-23 03:00:39 -08:00
mutex_unlock ( & devc - > open_mutex ) ;
2005-04-16 15:20:36 -07:00
if ( file - > f_flags & O_NONBLOCK ) {
DEC_USE_COUNT ;
return - EBUSY ;
}
interruptible_sleep_on ( & devc - > open_wait ) ;
if ( signal_pending ( current ) ) {
DEC_USE_COUNT ;
return - ERESTARTSYS ;
}
2006-03-23 03:00:39 -08:00
mutex_lock ( & devc - > open_mutex ) ;
2005-04-16 15:20:36 -07:00
}
devc - > open_mode | = file - > f_mode & ( FMODE_READ | FMODE_WRITE ) ;
2006-03-23 03:00:39 -08:00
mutex_unlock ( & devc - > open_mutex ) ;
2005-04-16 15:20:36 -07:00
/* get default sample format from minor number. */
sw_samplefmt = 0 ;
if ( ( minor & 0xF ) = = SND_DEV_DSP )
sw_samplefmt = AFMT_U8 ;
else if ( ( minor & 0xF ) = = SND_DEV_AUDIO )
sw_samplefmt = AFMT_MU_LAW ;
else if ( ( minor & 0xF ) = = SND_DEV_DSP16 )
sw_samplefmt = AFMT_S16_LE ;
else
ASSERT ( 0 ) ;
/* Initialize vwsnd_ports. */
2006-03-23 03:00:39 -08:00
mutex_lock ( & devc - > io_mutex ) ;
2005-04-16 15:20:36 -07:00
{
if ( file - > f_mode & FMODE_READ ) {
devc - > rport . swstate = SW_INITIAL ;
devc - > rport . flags = 0 ;
devc - > rport . sw_channels = 1 ;
devc - > rport . sw_samplefmt = sw_samplefmt ;
devc - > rport . sw_framerate = 8000 ;
devc - > rport . sw_fragshift = DEFAULT_FRAGSHIFT ;
devc - > rport . sw_fragcount = DEFAULT_FRAGCOUNT ;
devc - > rport . sw_subdivshift = DEFAULT_SUBDIVSHIFT ;
devc - > rport . byte_count = 0 ;
devc - > rport . frag_count = 0 ;
}
if ( file - > f_mode & FMODE_WRITE ) {
devc - > wport . swstate = SW_INITIAL ;
devc - > wport . flags = 0 ;
devc - > wport . sw_channels = 1 ;
devc - > wport . sw_samplefmt = sw_samplefmt ;
devc - > wport . sw_framerate = 8000 ;
devc - > wport . sw_fragshift = DEFAULT_FRAGSHIFT ;
devc - > wport . sw_fragcount = DEFAULT_FRAGCOUNT ;
devc - > wport . sw_subdivshift = DEFAULT_SUBDIVSHIFT ;
devc - > wport . byte_count = 0 ;
devc - > wport . frag_count = 0 ;
}
}
2006-03-23 03:00:39 -08:00
mutex_unlock ( & devc - > io_mutex ) ;
2005-04-16 15:20:36 -07:00
file - > private_data = devc ;
DBGRV ( ) ;
return 0 ;
}
/*
* Release ( close ) the audio device .
*/
static int vwsnd_audio_release ( struct inode * inode , struct file * file )
{
vwsnd_dev_t * devc = ( vwsnd_dev_t * ) file - > private_data ;
vwsnd_port_t * wport = NULL , * rport = NULL ;
int err = 0 ;
lock_kernel ( ) ;
2006-03-23 03:00:39 -08:00
mutex_lock ( & devc - > io_mutex ) ;
2005-04-16 15:20:36 -07:00
{
DBGEV ( " (inode=0x%p, file=0x%p) \n " , inode , file ) ;
if ( file - > f_mode & FMODE_READ )
rport = & devc - > rport ;
if ( file - > f_mode & FMODE_WRITE ) {
wport = & devc - > wport ;
pcm_flush_frag ( devc ) ;
pcm_write_sync ( devc ) ;
}
pcm_shutdown ( devc , rport , wport ) ;
if ( rport )
rport - > swstate = SW_OFF ;
if ( wport )
wport - > swstate = SW_OFF ;
}
2006-03-23 03:00:39 -08:00
mutex_unlock ( & devc - > io_mutex ) ;
2005-04-16 15:20:36 -07:00
2006-03-23 03:00:39 -08:00
mutex_lock ( & devc - > open_mutex ) ;
2005-04-16 15:20:36 -07:00
{
devc - > open_mode & = ~ file - > f_mode ;
}
2006-03-23 03:00:39 -08:00
mutex_unlock ( & devc - > open_mutex ) ;
2005-04-16 15:20:36 -07:00
wake_up ( & devc - > open_wait ) ;
DEC_USE_COUNT ;
DBGR ( ) ;
unlock_kernel ( ) ;
return err ;
}
static struct file_operations vwsnd_audio_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. read = vwsnd_audio_read ,
. write = vwsnd_audio_write ,
. poll = vwsnd_audio_poll ,
. ioctl = vwsnd_audio_ioctl ,
. mmap = vwsnd_audio_mmap ,
. open = vwsnd_audio_open ,
. release = vwsnd_audio_release ,
} ;
/*****************************************************************************/
/* mixer driver */
/* open the mixer device. */
static int vwsnd_mixer_open ( struct inode * inode , struct file * file )
{
vwsnd_dev_t * devc ;
DBGEV ( " (inode=0x%p, file=0x%p) \n " , inode , file ) ;
INC_USE_COUNT ;
for ( devc = vwsnd_dev_list ; devc ; devc = devc - > next_dev )
if ( devc - > mixer_minor = = iminor ( inode ) )
break ;
if ( devc = = NULL ) {
DEC_USE_COUNT ;
return - ENODEV ;
}
file - > private_data = devc ;
return 0 ;
}
/* release (close) the mixer device. */
static int vwsnd_mixer_release ( struct inode * inode , struct file * file )
{
DBGEV ( " (inode=0x%p, file=0x%p) \n " , inode , file ) ;
DEC_USE_COUNT ;
return 0 ;
}
/* mixer_read_ioctl handles all read ioctls on the mixer device. */
static int mixer_read_ioctl ( vwsnd_dev_t * devc , unsigned int nr , void __user * arg )
{
int val = - 1 ;
DBGEV ( " (devc=0x%p, nr=0x%x, arg=0x%p) \n " , devc , nr , arg ) ;
switch ( nr ) {
case SOUND_MIXER_CAPS :
val = SOUND_CAP_EXCL_INPUT ;
break ;
case SOUND_MIXER_DEVMASK :
val = ( SOUND_MASK_PCM | SOUND_MASK_LINE |
SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_RECLEV ) ;
break ;
case SOUND_MIXER_STEREODEVS :
val = ( SOUND_MASK_PCM | SOUND_MASK_LINE |
SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_RECLEV ) ;
break ;
case SOUND_MIXER_OUTMASK :
val = ( SOUND_MASK_PCM | SOUND_MASK_LINE |
SOUND_MASK_MIC | SOUND_MASK_CD ) ;
break ;
case SOUND_MIXER_RECMASK :
val = ( SOUND_MASK_PCM | SOUND_MASK_LINE |
SOUND_MASK_MIC | SOUND_MASK_CD ) ;
break ;
case SOUND_MIXER_PCM :
val = ad1843_get_gain ( & devc - > lith , & ad1843_gain_PCM ) ;
break ;
case SOUND_MIXER_LINE :
val = ad1843_get_gain ( & devc - > lith , & ad1843_gain_LINE ) ;
break ;
case SOUND_MIXER_MIC :
val = ad1843_get_gain ( & devc - > lith , & ad1843_gain_MIC ) ;
break ;
case SOUND_MIXER_CD :
val = ad1843_get_gain ( & devc - > lith , & ad1843_gain_CD ) ;
break ;
case SOUND_MIXER_RECLEV :
val = ad1843_get_gain ( & devc - > lith , & ad1843_gain_RECLEV ) ;
break ;
case SOUND_MIXER_RECSRC :
val = ad1843_get_recsrc ( & devc - > lith ) ;
break ;
case SOUND_MIXER_OUTSRC :
val = ad1843_get_outsrc ( & devc - > lith ) ;
break ;
default :
return - EINVAL ;
}
return put_user ( val , ( int __user * ) arg ) ;
}
/* mixer_write_ioctl handles all write ioctls on the mixer device. */
static int mixer_write_ioctl ( vwsnd_dev_t * devc , unsigned int nr , void __user * arg )
{
int val ;
int err ;
DBGEV ( " (devc=0x%p, nr=0x%x, arg=0x%p) \n " , devc , nr , arg ) ;
err = get_user ( val , ( int __user * ) arg ) ;
if ( err )
return - EFAULT ;
switch ( nr ) {
case SOUND_MIXER_PCM :
val = ad1843_set_gain ( & devc - > lith , & ad1843_gain_PCM , val ) ;
break ;
case SOUND_MIXER_LINE :
val = ad1843_set_gain ( & devc - > lith , & ad1843_gain_LINE , val ) ;
break ;
case SOUND_MIXER_MIC :
val = ad1843_set_gain ( & devc - > lith , & ad1843_gain_MIC , val ) ;
break ;
case SOUND_MIXER_CD :
val = ad1843_set_gain ( & devc - > lith , & ad1843_gain_CD , val ) ;
break ;
case SOUND_MIXER_RECLEV :
val = ad1843_set_gain ( & devc - > lith , & ad1843_gain_RECLEV , val ) ;
break ;
case SOUND_MIXER_RECSRC :
if ( devc - > rport . swbuf | | devc - > wport . swbuf )
return - EBUSY ; /* can't change recsrc while running */
val = ad1843_set_recsrc ( & devc - > lith , val ) ;
break ;
case SOUND_MIXER_OUTSRC :
val = ad1843_set_outsrc ( & devc - > lith , val ) ;
break ;
default :
return - EINVAL ;
}
if ( val < 0 )
return val ;
return put_user ( val , ( int __user * ) arg ) ;
}
/* This is the ioctl entry to the mixer driver. */
static int vwsnd_mixer_ioctl ( struct inode * ioctl ,
struct file * file ,
unsigned int cmd ,
unsigned long arg )
{
vwsnd_dev_t * devc = ( vwsnd_dev_t * ) file - > private_data ;
const unsigned int nrmask = _IOC_NRMASK < < _IOC_NRSHIFT ;
const unsigned int nr = ( cmd & nrmask ) > > _IOC_NRSHIFT ;
int retval ;
DBGEV ( " (devc=0x%p, cmd=0x%x, arg=0x%lx) \n " , devc , cmd , arg ) ;
2006-03-23 03:00:39 -08:00
mutex_lock ( & devc - > mix_mutex ) ;
2005-04-16 15:20:36 -07:00
{
if ( ( cmd & ~ nrmask ) = = MIXER_READ ( 0 ) )
retval = mixer_read_ioctl ( devc , nr , ( void __user * ) arg ) ;
else if ( ( cmd & ~ nrmask ) = = MIXER_WRITE ( 0 ) )
retval = mixer_write_ioctl ( devc , nr , ( void __user * ) arg ) ;
else
retval = - EINVAL ;
}
2006-03-23 03:00:39 -08:00
mutex_unlock ( & devc - > mix_mutex ) ;
2005-04-16 15:20:36 -07:00
return retval ;
}
static struct file_operations vwsnd_mixer_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. ioctl = vwsnd_mixer_ioctl ,
. open = vwsnd_mixer_open ,
. release = vwsnd_mixer_release ,
} ;
/*****************************************************************************/
/* probe/attach/unload */
/* driver probe routine. Return nonzero if hardware is found. */
static int __init probe_vwsnd ( struct address_info * hw_config )
{
lithium_t lith ;
int w ;
unsigned long later ;
DBGEV ( " (hw_config=0x%p) \n " , hw_config ) ;
/* XXX verify lithium present (to prevent crash on non-vw) */
if ( li_create ( & lith , hw_config - > io_base ) ! = 0 ) {
printk ( KERN_WARNING " probe_vwsnd: can't map lithium \n " ) ;
return 0 ;
}
later = jiffies + 2 ;
li_writel ( & lith , LI_HOST_CONTROLLER , LI_HC_LINK_ENABLE ) ;
do {
w = li_readl ( & lith , LI_HOST_CONTROLLER ) ;
} while ( w = = LI_HC_LINK_ENABLE & & time_before ( jiffies , later ) ) ;
li_destroy ( & lith ) ;
DBGPV ( " HC = 0x%04x \n " , w ) ;
if ( ( w = = LI_HC_LINK_ENABLE ) | | ( w & LI_HC_LINK_CODEC ) ) {
/* This may indicate a beta machine with no audio,
* or a future machine with different audio .
* On beta - release 320 w / no audio , HC = = 0x4000 */
printk ( KERN_WARNING " probe_vwsnd: audio codec not found \n " ) ;
return 0 ;
}
if ( w & LI_HC_LINK_FAILURE ) {
printk ( KERN_WARNING " probe_vwsnd: can't init audio codec \n " ) ;
return 0 ;
}
printk ( KERN_INFO " vwsnd: lithium audio at mmio %#x irq %d \n " ,
hw_config - > io_base , hw_config - > irq ) ;
return 1 ;
}
/*
* driver attach routine . Initialize driver data structures and
* initialize hardware . A new vwsnd_dev_t is allocated and put
* onto the global list , vwsnd_dev_list .
*
* Return + minor_dev on success , - errno on failure .
*/
static int __init attach_vwsnd ( struct address_info * hw_config )
{
vwsnd_dev_t * devc = NULL ;
int err = - ENOMEM ;
DBGEV ( " (hw_config=0x%p) \n " , hw_config ) ;
devc = kmalloc ( sizeof ( vwsnd_dev_t ) , GFP_KERNEL ) ;
if ( devc = = NULL )
goto fail0 ;
err = li_create ( & devc - > lith , hw_config - > io_base ) ;
if ( err )
goto fail1 ;
init_waitqueue_head ( & devc - > open_wait ) ;
devc - > rport . hwbuf_size = HWBUF_SIZE ;
devc - > rport . hwbuf_vaddr = __get_free_pages ( GFP_KERNEL , HWBUF_ORDER ) ;
if ( ! devc - > rport . hwbuf_vaddr )
goto fail2 ;
devc - > rport . hwbuf = ( void * ) devc - > rport . hwbuf_vaddr ;
devc - > rport . hwbuf_paddr = virt_to_phys ( devc - > rport . hwbuf ) ;
/*
* Quote from the NT driver :
*
* // WARNING!!! HACK to setup output dma!!!
* // This is required because even on output there is some data
* // trickling into the input DMA channel. This is a bug in the
* // Lithium microcode.
* // --sde
*
* We set the input side ' s DMA base address here . It will remain
* valid until the driver is unloaded .
*/
li_writel ( & devc - > lith , LI_COMM1_BASE ,
devc - > rport . hwbuf_paddr > > 8 | 1 < < ( 37 - 8 ) ) ;
devc - > wport . hwbuf_size = HWBUF_SIZE ;
devc - > wport . hwbuf_vaddr = __get_free_pages ( GFP_KERNEL , HWBUF_ORDER ) ;
if ( ! devc - > wport . hwbuf_vaddr )
goto fail3 ;
devc - > wport . hwbuf = ( void * ) devc - > wport . hwbuf_vaddr ;
devc - > wport . hwbuf_paddr = virt_to_phys ( devc - > wport . hwbuf ) ;
DBGP ( " wport hwbuf = 0x%p \n " , devc - > wport . hwbuf ) ;
DBGDO ( shut_up + + ) ;
err = ad1843_init ( & devc - > lith ) ;
DBGDO ( shut_up - - ) ;
if ( err )
goto fail4 ;
/* install interrupt handler */
err = request_irq ( hw_config - > irq , vwsnd_audio_intr , 0 , " vwsnd " , devc ) ;
if ( err )
goto fail5 ;
/* register this device's drivers. */
devc - > audio_minor = register_sound_dsp ( & vwsnd_audio_fops , - 1 ) ;
if ( ( err = devc - > audio_minor ) < 0 ) {
DBGDO ( printk ( KERN_WARNING
" attach_vwsnd: register_sound_dsp error %d \n " ,
err ) ) ;
goto fail6 ;
}
devc - > mixer_minor = register_sound_mixer ( & vwsnd_mixer_fops ,
devc - > audio_minor > > 4 ) ;
if ( ( err = devc - > mixer_minor ) < 0 ) {
DBGDO ( printk ( KERN_WARNING
" attach_vwsnd: register_sound_mixer error %d \n " ,
err ) ) ;
goto fail7 ;
}
/* Squirrel away device indices for unload routine. */
hw_config - > slots [ 0 ] = devc - > audio_minor ;
/* Initialize as much of *devc as possible */
2006-03-23 03:00:39 -08:00
mutex_init ( & devc - > open_mutex ) ;
mutex_init ( & devc - > io_mutex ) ;
mutex_init ( & devc - > mix_mutex ) ;
2005-04-16 15:20:36 -07:00
devc - > open_mode = 0 ;
spin_lock_init ( & devc - > rport . lock ) ;
init_waitqueue_head ( & devc - > rport . queue ) ;
devc - > rport . swstate = SW_OFF ;
devc - > rport . hwstate = HW_STOPPED ;
devc - > rport . flags = 0 ;
devc - > rport . swbuf = NULL ;
spin_lock_init ( & devc - > wport . lock ) ;
init_waitqueue_head ( & devc - > wport . queue ) ;
devc - > wport . swstate = SW_OFF ;
devc - > wport . hwstate = HW_STOPPED ;
devc - > wport . flags = 0 ;
devc - > wport . swbuf = NULL ;
/* Success. Link us onto the local device list. */
devc - > next_dev = vwsnd_dev_list ;
vwsnd_dev_list = devc ;
return devc - > audio_minor ;
/* So many ways to fail. Undo what we did. */
fail7 :
unregister_sound_dsp ( devc - > audio_minor ) ;
fail6 :
free_irq ( hw_config - > irq , devc ) ;
fail5 :
fail4 :
free_pages ( devc - > wport . hwbuf_vaddr , HWBUF_ORDER ) ;
fail3 :
free_pages ( devc - > rport . hwbuf_vaddr , HWBUF_ORDER ) ;
fail2 :
li_destroy ( & devc - > lith ) ;
fail1 :
kfree ( devc ) ;
fail0 :
return err ;
}
static int __exit unload_vwsnd ( struct address_info * hw_config )
{
vwsnd_dev_t * devc , * * devcp ;
DBGE ( " () \n " ) ;
devcp = & vwsnd_dev_list ;
while ( ( devc = * devcp ) ) {
if ( devc - > audio_minor = = hw_config - > slots [ 0 ] ) {
* devcp = devc - > next_dev ;
break ;
}
devcp = & devc - > next_dev ;
}
if ( ! devc )
return - ENODEV ;
unregister_sound_mixer ( devc - > mixer_minor ) ;
unregister_sound_dsp ( devc - > audio_minor ) ;
free_irq ( hw_config - > irq , devc ) ;
free_pages ( devc - > wport . hwbuf_vaddr , HWBUF_ORDER ) ;
free_pages ( devc - > rport . hwbuf_vaddr , HWBUF_ORDER ) ;
li_destroy ( & devc - > lith ) ;
kfree ( devc ) ;
return 0 ;
}
/*****************************************************************************/
/* initialization and loadable kernel module interface */
static struct address_info the_hw_config = {
0xFF001000 , /* lithium phys addr */
CO_IRQ ( CO_APIC_LI_AUDIO ) /* irq */
} ;
MODULE_DESCRIPTION ( " SGI Visual Workstation sound module " ) ;
MODULE_AUTHOR ( " Bob Miller <kbob@sgi.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;
static int __init init_vwsnd ( void )
{
int err ;
DBGXV ( " \n " ) ;
DBGXV ( " sound::vwsnd::init_module() \n " ) ;
if ( ! probe_vwsnd ( & the_hw_config ) )
return - ENODEV ;
err = attach_vwsnd ( & the_hw_config ) ;
if ( err < 0 )
return err ;
return 0 ;
}
static void __exit cleanup_vwsnd ( void )
{
DBGX ( " sound::vwsnd::cleanup_module() \n " ) ;
unload_vwsnd ( & the_hw_config ) ;
}
module_init ( init_vwsnd ) ;
module_exit ( cleanup_vwsnd ) ;