2005-04-16 15:20:36 -07:00
/*
drivers / sound / harmony . c
This is a sound driver for ASP ' s and Lasi ' s Harmony sound chip
and is unlikely to be used for anything other than on a HP PA - RISC .
Harmony is found in HP 712 s , 715 / new and many other GSC based machines .
On older 715 machines you ' ll find the technically identical chip
called ' Vivace ' . Both Harmony and Vicace are supported by this driver .
Copyright 2000 ( c ) Linuxcare Canada , Alex deVries < alex @ onefishtwo . ca >
Copyright 2000 - 2003 ( c ) Helge Deller < deller @ gmx . de >
Copyright 2001 ( c ) Matthieu Delahaye < delahaym @ esiee . fr >
Copyright 2001 ( c ) Jean - Christophe Vaugeois < vaugeoij @ esiee . fr >
Copyright 2004 ( c ) Stuart Brady < sdbrady @ ntlworld . com >
TODO :
- fix SNDCTL_DSP_GETOSPACE and SNDCTL_DSP_GETISPACE ioctls to
return the real values
- add private ioctl for selecting line - or microphone input
( only one of them is available at the same time )
- add module parameters
- implement mmap functionality
- implement gain meter ?
- . . .
*/
# include <linux/delay.h>
# include <linux/errno.h>
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/ioport.h>
# include <linux/types.h>
# include <linux/mm.h>
# include <linux/pci.h>
# include <asm/parisc-device.h>
# include <asm/io.h>
# include "sound_config.h"
# define PFX "harmony: "
# define HARMONY_VERSION "V0.9a"
# undef DEBUG
# ifdef DEBUG
# define DPRINTK printk
# else
# define DPRINTK(x,...)
# endif
# define MAX_BUFS 10 /* maximum number of rotating buffers */
# define HARMONY_BUF_SIZE 4096 /* needs to be a multiple of PAGE_SIZE (4096)! */
# define CNTL_C 0x80000000
# define CNTL_ST 0x00000020
# define CNTL_44100 0x00000015 /* HARMONY_SR_44KHZ */
# define CNTL_8000 0x00000008 /* HARMONY_SR_8KHZ */
# define GAINCTL_HE 0x08000000
# define GAINCTL_LE 0x04000000
# define GAINCTL_SE 0x02000000
# define DSTATUS_PN 0x00000200
# define DSTATUS_RN 0x00000002
# define DSTATUS_IE 0x80000000
# define HARMONY_DF_16BIT_LINEAR 0
# define HARMONY_DF_8BIT_ULAW 1
# define HARMONY_DF_8BIT_ALAW 2
# define HARMONY_SS_MONO 0
# define HARMONY_SS_STEREO 1
# define HARMONY_SR_8KHZ 0x08
# define HARMONY_SR_16KHZ 0x09
# define HARMONY_SR_27KHZ 0x0A
# define HARMONY_SR_32KHZ 0x0B
# define HARMONY_SR_48KHZ 0x0E
# define HARMONY_SR_9KHZ 0x0F
# define HARMONY_SR_5KHZ 0x10
# define HARMONY_SR_11KHZ 0x11
# define HARMONY_SR_18KHZ 0x12
# define HARMONY_SR_22KHZ 0x13
# define HARMONY_SR_37KHZ 0x14
# define HARMONY_SR_44KHZ 0x15
# define HARMONY_SR_33KHZ 0x16
# define HARMONY_SR_6KHZ 0x17
/*
* Some magics numbers used to auto - detect file formats
*/
# define HARMONY_MAGIC_8B_ULAW 1
# define HARMONY_MAGIC_8B_ALAW 27
# define HARMONY_MAGIC_16B_LINEAR 3
# define HARMONY_MAGIC_MONO 1
# define HARMONY_MAGIC_STEREO 2
/*
* Channels Positions in mixer register
*/
# define GAIN_HE_SHIFT 27
# define GAIN_HE_MASK ( 1 << GAIN_HE_SHIFT)
# define GAIN_LE_SHIFT 26
# define GAIN_LE_MASK ( 1 << GAIN_LE_SHIFT)
# define GAIN_SE_SHIFT 25
# define GAIN_SE_MASK ( 1 << GAIN_SE_SHIFT)
# define GAIN_IS_SHIFT 24
# define GAIN_IS_MASK ( 1 << GAIN_IS_SHIFT)
# define GAIN_MA_SHIFT 20
# define GAIN_MA_MASK ( 0x0f << GAIN_MA_SHIFT)
# define GAIN_LI_SHIFT 16
# define GAIN_LI_MASK ( 0x0f << GAIN_LI_SHIFT)
# define GAIN_RI_SHIFT 12
# define GAIN_RI_MASK ( 0x0f << GAIN_RI_SHIFT)
# define GAIN_LO_SHIFT 6
# define GAIN_LO_MASK ( 0x3f << GAIN_LO_SHIFT)
# define GAIN_RO_SHIFT 0
# define GAIN_RO_MASK ( 0x3f << GAIN_RO_SHIFT)
# define MAX_OUTPUT_LEVEL (GAIN_RO_MASK >> GAIN_RO_SHIFT)
# define MAX_INPUT_LEVEL (GAIN_RI_MASK >> GAIN_RI_SHIFT)
# define MAX_MONITOR_LEVEL (GAIN_MA_MASK >> GAIN_MA_SHIFT)
# define MIXER_INTERNAL SOUND_MIXER_LINE1
# define MIXER_LINEOUT SOUND_MIXER_LINE2
# define MIXER_HEADPHONES SOUND_MIXER_LINE3
# define MASK_INTERNAL SOUND_MASK_LINE1
# define MASK_LINEOUT SOUND_MASK_LINE2
# define MASK_HEADPHONES SOUND_MASK_LINE3
/*
* Channels Mask in mixer register
*/
# define GAIN_TOTAL_SILENCE 0x00F00FFF
# define GAIN_DEFAULT 0x0FF00000
struct harmony_hpa {
u8 unused000 ;
u8 id ;
u8 teleshare_id ;
u8 unused003 ;
u32 reset ;
u32 cntl ;
u32 gainctl ;
u32 pnxtadd ;
u32 pcuradd ;
u32 rnxtadd ;
u32 rcuradd ;
u32 dstatus ;
u32 ov ;
u32 pio ;
u32 unused02c ;
u32 unused030 [ 3 ] ;
u32 diag ;
} ;
struct harmony_dev {
struct harmony_hpa * hpa ;
struct parisc_device * dev ;
u32 current_gain ;
u32 dac_rate ; /* 8000 ... 48000 (Hz) */
u8 data_format ; /* HARMONY_DF_xx_BIT_xxx */
u8 sample_rate ; /* HARMONY_SR_xx_KHZ */
u8 stereo_select ; /* HARMONY_SS_MONO or HARMONY_SS_STEREO */
int format_initialized : 1 ;
int suspended_playing : 1 ;
int suspended_recording : 1 ;
int blocked_playing : 1 ;
int blocked_recording : 1 ;
int audio_open : 1 ;
int mixer_open : 1 ;
wait_queue_head_t wq_play , wq_record ;
int first_filled_play ; /* first buffer containing data (next to play) */
int nb_filled_play ;
int play_offset ;
int first_filled_record ;
int nb_filled_record ;
int dsp_unit , mixer_unit ;
} ;
static struct harmony_dev harmony ;
/*
* Dynamic sound buffer allocation and DMA memory
*/
struct harmony_buffer {
unsigned char * addr ;
dma_addr_t dma_handle ;
int dma_coherent ; /* Zero if dma_alloc_coherent() fails */
unsigned int len ;
} ;
/*
* Harmony memory buffers
*/
static struct harmony_buffer played_buf , recorded_buf , silent , graveyard ;
# define CHECK_WBACK_INV_OFFSET(b,offset,len) \
do { if ( ! b . dma_coherent ) \
dma_cache_wback_inv ( ( unsigned long ) b . addr + offset , len ) ; \
} while ( 0 )
static int __init harmony_alloc_buffer ( struct harmony_buffer * b ,
unsigned int buffer_count )
{
b - > len = buffer_count * HARMONY_BUF_SIZE ;
b - > addr = dma_alloc_coherent ( & harmony . dev - > dev ,
b - > len , & b - > dma_handle , GFP_KERNEL | GFP_DMA ) ;
if ( b - > addr & & b - > dma_handle ) {
b - > dma_coherent = 1 ;
DPRINTK ( KERN_INFO PFX " coherent memory: 0x%lx, played_buf: 0x%lx \n " ,
( unsigned long ) b - > dma_handle , ( unsigned long ) b - > addr ) ;
} else {
b - > dma_coherent = 0 ;
/* kmalloc()ed memory will HPMC on ccio machines ! */
b - > addr = kmalloc ( b - > len , GFP_KERNEL ) ;
if ( ! b - > addr ) {
printk ( KERN_ERR PFX " couldn't allocate memory \n " ) ;
return - EBUSY ;
}
b - > dma_handle = __pa ( b - > addr ) ;
}
return 0 ;
}
static void __exit harmony_free_buffer ( struct harmony_buffer * b )
{
if ( ! b - > addr )
return ;
if ( b - > dma_coherent )
dma_free_coherent ( & harmony . dev - > dev ,
b - > len , b - > addr , b - > dma_handle ) ;
else
kfree ( b - > addr ) ;
memset ( b , 0 , sizeof ( * b ) ) ;
}
/*
* Low - Level sound - chip programming
*/
static void __inline__ harmony_wait_CNTL ( void )
{
/* Wait until we're out of control mode */
while ( gsc_readl ( & harmony . hpa - > cntl ) & CNTL_C )
/* wait */ ;
}
static void harmony_update_control ( void )
{
u32 default_cntl ;
/* Set CNTL */
default_cntl = ( CNTL_C | /* The C bit */
( harmony . data_format < < 6 ) | /* Set the data format */
( harmony . stereo_select < < 5 ) | /* Stereo select */
( harmony . sample_rate ) ) ; /* Set sample rate */
harmony . format_initialized = 1 ;
/* initialize CNTL */
gsc_writel ( default_cntl , & harmony . hpa - > cntl ) ;
}
static void harmony_set_control ( u8 data_format , u8 sample_rate , u8 stereo_select )
{
harmony . sample_rate = sample_rate ;
harmony . data_format = data_format ;
harmony . stereo_select = stereo_select ;
harmony_update_control ( ) ;
}
static void harmony_set_rate ( u8 data_rate )
{
harmony . sample_rate = data_rate ;
harmony_update_control ( ) ;
}
static int harmony_detect_rate ( int * freq )
{
int newrate ;
switch ( * freq ) {
case 8000 : newrate = HARMONY_SR_8KHZ ; break ;
case 16000 : newrate = HARMONY_SR_16KHZ ; break ;
case 27428 : newrate = HARMONY_SR_27KHZ ; break ;
case 32000 : newrate = HARMONY_SR_32KHZ ; break ;
case 48000 : newrate = HARMONY_SR_48KHZ ; break ;
case 9600 : newrate = HARMONY_SR_9KHZ ; break ;
case 5512 : newrate = HARMONY_SR_5KHZ ; break ;
case 11025 : newrate = HARMONY_SR_11KHZ ; break ;
case 18900 : newrate = HARMONY_SR_18KHZ ; break ;
case 22050 : newrate = HARMONY_SR_22KHZ ; break ;
case 37800 : newrate = HARMONY_SR_37KHZ ; break ;
case 44100 : newrate = HARMONY_SR_44KHZ ; break ;
case 33075 : newrate = HARMONY_SR_33KHZ ; break ;
case 6615 : newrate = HARMONY_SR_6KHZ ; break ;
default : newrate = HARMONY_SR_8KHZ ;
* freq = 8000 ; break ;
}
return newrate ;
}
static void harmony_set_format ( u8 data_format )
{
harmony . data_format = data_format ;
harmony_update_control ( ) ;
}
static void harmony_set_stereo ( u8 stereo_select )
{
harmony . stereo_select = stereo_select ;
harmony_update_control ( ) ;
}
static void harmony_disable_interrupts ( void )
{
harmony_wait_CNTL ( ) ;
gsc_writel ( 0 , & harmony . hpa - > dstatus ) ;
}
static void harmony_enable_interrupts ( void )
{
harmony_wait_CNTL ( ) ;
gsc_writel ( DSTATUS_IE , & harmony . hpa - > dstatus ) ;
}
/*
* harmony_silence ( )
*
* This subroutine fills in a buffer starting at location start and
* silences for length bytes . This references the current
* configuration of the audio format .
*
*/
static void harmony_silence ( struct harmony_buffer * buffer , int start , int length )
{
u8 silence_char ;
/* Despite what you hear, silence is different in
different audio formats . */
switch ( harmony . data_format ) {
case HARMONY_DF_8BIT_ULAW : silence_char = 0x55 ; break ;
case HARMONY_DF_8BIT_ALAW : silence_char = 0xff ; break ;
case HARMONY_DF_16BIT_LINEAR : /* fall through */
default : silence_char = 0 ;
}
memset ( buffer - > addr + start , silence_char , length ) ;
}
static int harmony_audio_open ( struct inode * inode , struct file * file )
{
if ( harmony . audio_open )
return - EBUSY ;
harmony . audio_open = 1 ;
harmony . suspended_playing = harmony . suspended_recording = 1 ;
harmony . blocked_playing = harmony . blocked_recording = 0 ;
harmony . first_filled_play = harmony . first_filled_record = 0 ;
harmony . nb_filled_play = harmony . nb_filled_record = 0 ;
harmony . play_offset = 0 ;
init_waitqueue_head ( & harmony . wq_play ) ;
init_waitqueue_head ( & harmony . wq_record ) ;
/* Start off in a balanced mode. */
harmony_set_control ( HARMONY_DF_8BIT_ULAW , HARMONY_SR_8KHZ , HARMONY_SS_MONO ) ;
harmony_update_control ( ) ;
harmony . format_initialized = 0 ;
/* Clear out all the buffers and flush to cache */
harmony_silence ( & played_buf , 0 , HARMONY_BUF_SIZE * MAX_BUFS ) ;
CHECK_WBACK_INV_OFFSET ( played_buf , 0 , HARMONY_BUF_SIZE * MAX_BUFS ) ;
return 0 ;
}
/*
* Release ( close ) the audio device .
*/
static int harmony_audio_release ( struct inode * inode , struct file * file )
{
if ( ! harmony . audio_open )
return - EBUSY ;
harmony . audio_open = 0 ;
return 0 ;
}
/*
* Read recorded data off the audio device .
*/
static ssize_t harmony_audio_read ( struct file * file ,
char * buffer ,
size_t size_count ,
loff_t * ppos )
{
int total_count = ( int ) size_count ;
int count = 0 ;
int buf_to_read ;
while ( count < total_count ) {
/* Wait until we're out of control mode */
harmony_wait_CNTL ( ) ;
/* Figure out which buffer to fill in */
if ( harmony . nb_filled_record < = 2 ) {
harmony . blocked_recording = 1 ;
if ( harmony . suspended_recording ) {
harmony . suspended_recording = 0 ;
harmony_enable_interrupts ( ) ;
}
interruptible_sleep_on ( & harmony . wq_record ) ;
harmony . blocked_recording = 0 ;
}
if ( harmony . nb_filled_record < 2 )
return - EBUSY ;
buf_to_read = harmony . first_filled_record ;
/* Copy the page to an aligned buffer */
if ( copy_to_user ( buffer + count , recorded_buf . addr +
( HARMONY_BUF_SIZE * buf_to_read ) ,
HARMONY_BUF_SIZE ) ) {
count = - EFAULT ;
break ;
}
harmony . nb_filled_record - - ;
harmony . first_filled_record + + ;
harmony . first_filled_record % = MAX_BUFS ;
count + = HARMONY_BUF_SIZE ;
}
return count ;
}
/*
* Here is the place where we try to recognize file format .
* Sun / NeXT . au files begin with the string . snd
* At offset 12 is specified the encoding .
* At offset 16 is specified speed rate
* At Offset 20 is specified the numbers of voices
*/
# define four_bytes_to_u32(start) (file_header[start] << 24)|\
( file_header [ start + 1 ] < < 16 ) | \
( file_header [ start + 2 ] < < 8 ) | \
( file_header [ start + 3 ] ) ;
# define test_rate(tested,real_value,harmony_value) if ((tested)<=(real_value))\
static int harmony_format_auto_detect ( const char * buffer , int block_size )
{
u8 file_header [ 24 ] ;
u32 start_string ;
int ret = 0 ;
if ( block_size > 24 ) {
if ( copy_from_user ( file_header , buffer , sizeof ( file_header ) ) )
ret = - EFAULT ;
start_string = four_bytes_to_u32 ( 0 ) ;
if ( ( file_header [ 4 ] = = 0 ) & & ( start_string = = 0x2E736E64 ) ) {
u32 format ;
u32 nb_voices ;
u32 speed ;
format = four_bytes_to_u32 ( 12 ) ;
nb_voices = four_bytes_to_u32 ( 20 ) ;
speed = four_bytes_to_u32 ( 16 ) ;
switch ( format ) {
case HARMONY_MAGIC_8B_ULAW :
harmony . data_format = HARMONY_DF_8BIT_ULAW ;
break ;
case HARMONY_MAGIC_8B_ALAW :
harmony . data_format = HARMONY_DF_8BIT_ALAW ;
break ;
case HARMONY_MAGIC_16B_LINEAR :
harmony . data_format = HARMONY_DF_16BIT_LINEAR ;
break ;
default :
harmony_set_control ( HARMONY_DF_16BIT_LINEAR ,
HARMONY_SR_44KHZ , HARMONY_SS_STEREO ) ;
goto out ;
}
switch ( nb_voices ) {
case HARMONY_MAGIC_MONO :
harmony . stereo_select = HARMONY_SS_MONO ;
break ;
case HARMONY_MAGIC_STEREO :
harmony . stereo_select = HARMONY_SS_STEREO ;
break ;
default :
harmony . stereo_select = HARMONY_SS_MONO ;
break ;
}
harmony_set_rate ( harmony_detect_rate ( & speed ) ) ;
harmony . dac_rate = speed ;
goto out ;
}
}
harmony_set_control ( HARMONY_DF_8BIT_ULAW , HARMONY_SR_8KHZ , HARMONY_SS_MONO ) ;
out :
return ret ;
}
# undef four_bytes_to_u32
static ssize_t harmony_audio_write ( struct file * file ,
const char * buffer ,
size_t size_count ,
loff_t * ppos )
{
int total_count = ( int ) size_count ;
int count = 0 ;
int frame_size ;
int buf_to_fill ;
int fresh_buffer ;
if ( ! harmony . format_initialized ) {
if ( harmony_format_auto_detect ( buffer , total_count ) )
return - EFAULT ;
}
while ( count < total_count ) {
/* Wait until we're out of control mode */
harmony_wait_CNTL ( ) ;
/* Figure out which buffer to fill in */
if ( harmony . nb_filled_play + 2 > = MAX_BUFS & & ! harmony . play_offset ) {
harmony . blocked_playing = 1 ;
interruptible_sleep_on ( & harmony . wq_play ) ;
harmony . blocked_playing = 0 ;
}
if ( harmony . nb_filled_play + 2 > = MAX_BUFS & & ! harmony . play_offset )
return - EBUSY ;
buf_to_fill = ( harmony . first_filled_play + harmony . nb_filled_play ) ;
if ( harmony . play_offset ) {
buf_to_fill - - ;
buf_to_fill + = MAX_BUFS ;
}
buf_to_fill % = MAX_BUFS ;
fresh_buffer = ( harmony . play_offset = = 0 ) ;
/* Figure out the size of the frame */
if ( ( total_count - count ) > = HARMONY_BUF_SIZE - harmony . play_offset ) {
frame_size = HARMONY_BUF_SIZE - harmony . play_offset ;
} else {
frame_size = total_count - count ;
/* Clear out the buffer, since there we'll only be
overlaying part of the old buffer with the new one */
harmony_silence ( & played_buf ,
HARMONY_BUF_SIZE * buf_to_fill + frame_size + harmony . play_offset ,
HARMONY_BUF_SIZE - frame_size - harmony . play_offset ) ;
}
/* Copy the page to an aligned buffer */
if ( copy_from_user ( played_buf . addr + ( HARMONY_BUF_SIZE * buf_to_fill ) + harmony . play_offset ,
buffer + count , frame_size ) )
return - EFAULT ;
CHECK_WBACK_INV_OFFSET ( played_buf , ( HARMONY_BUF_SIZE * buf_to_fill + harmony . play_offset ) ,
frame_size ) ;
if ( fresh_buffer )
harmony . nb_filled_play + + ;
count + = frame_size ;
harmony . play_offset + = frame_size ;
harmony . play_offset % = HARMONY_BUF_SIZE ;
if ( harmony . suspended_playing & & ( harmony . nb_filled_play > = 4 ) )
harmony_enable_interrupts ( ) ;
}
return count ;
}
static unsigned int harmony_audio_poll ( struct file * file ,
struct poll_table_struct * wait )
{
unsigned int mask = 0 ;
if ( file - > f_mode & FMODE_READ ) {
if ( ! harmony . suspended_recording )
poll_wait ( file , & harmony . wq_record , wait ) ;
if ( harmony . nb_filled_record )
mask | = POLLIN | POLLRDNORM ;
}
if ( file - > f_mode & FMODE_WRITE ) {
if ( ! harmony . suspended_playing )
poll_wait ( file , & harmony . wq_play , wait ) ;
if ( harmony . nb_filled_play )
mask | = POLLOUT | POLLWRNORM ;
}
return mask ;
}
static int harmony_audio_ioctl ( struct inode * inode ,
struct file * file ,
unsigned int cmd ,
unsigned long arg )
{
int ival , new_format ;
int frag_size , frag_buf ;
struct audio_buf_info info ;
switch ( cmd ) {
case OSS_GETVERSION :
return put_user ( SOUND_VERSION , ( int * ) arg ) ;
case SNDCTL_DSP_GETCAPS :
ival = DSP_CAP_DUPLEX ;
return put_user ( ival , ( int * ) arg ) ;
case SNDCTL_DSP_GETFMTS :
ival = ( AFMT_S16_BE | AFMT_MU_LAW | AFMT_A_LAW ) ;
return put_user ( ival , ( int * ) arg ) ;
case SNDCTL_DSP_SETFMT :
if ( get_user ( ival , ( int * ) arg ) )
return - EFAULT ;
if ( ival ! = AFMT_QUERY ) {
switch ( ival ) {
case AFMT_MU_LAW : new_format = HARMONY_DF_8BIT_ULAW ; break ;
case AFMT_A_LAW : new_format = HARMONY_DF_8BIT_ALAW ; break ;
case AFMT_S16_BE : new_format = HARMONY_DF_16BIT_LINEAR ; break ;
default : {
DPRINTK ( KERN_WARNING PFX
" unsupported sound format 0x%04x requested. \n " ,
ival ) ;
ival = AFMT_S16_BE ;
return put_user ( ival , ( int * ) arg ) ;
}
}
harmony_set_format ( new_format ) ;
return 0 ;
} else {
switch ( harmony . data_format ) {
case HARMONY_DF_8BIT_ULAW : ival = AFMT_MU_LAW ; break ;
case HARMONY_DF_8BIT_ALAW : ival = AFMT_A_LAW ; break ;
case HARMONY_DF_16BIT_LINEAR : ival = AFMT_U16_BE ; break ;
default : ival = 0 ;
}
return put_user ( ival , ( int * ) arg ) ;
}
case SOUND_PCM_READ_RATE :
ival = harmony . dac_rate ;
return put_user ( ival , ( int * ) arg ) ;
case SNDCTL_DSP_SPEED :
if ( get_user ( ival , ( int * ) arg ) )
return - EFAULT ;
harmony_set_rate ( harmony_detect_rate ( & ival ) ) ;
harmony . dac_rate = ival ;
return put_user ( ival , ( int * ) arg ) ;
case SNDCTL_DSP_STEREO :
if ( get_user ( ival , ( int * ) arg ) )
return - EFAULT ;
if ( ival ! = 0 & & ival ! = 1 )
return - EINVAL ;
harmony_set_stereo ( ival ) ;
return 0 ;
case SNDCTL_DSP_CHANNELS :
if ( get_user ( ival , ( int * ) arg ) )
return - EFAULT ;
if ( ival ! = 1 & & ival ! = 2 ) {
ival = harmony . stereo_select = = HARMONY_SS_MONO ? 1 : 2 ;
return put_user ( ival , ( int * ) arg ) ;
}
harmony_set_stereo ( ival - 1 ) ;
return 0 ;
case SNDCTL_DSP_GETBLKSIZE :
ival = HARMONY_BUF_SIZE ;
return put_user ( ival , ( int * ) arg ) ;
case SNDCTL_DSP_NONBLOCK :
file - > f_flags | = O_NONBLOCK ;
return 0 ;
case SNDCTL_DSP_RESET :
if ( ! harmony . suspended_recording ) {
/* TODO: stop_recording() */
}
return 0 ;
case SNDCTL_DSP_SETFRAGMENT :
if ( get_user ( ival , ( int * ) arg ) )
return - EFAULT ;
frag_size = ival & 0xffff ;
frag_buf = ( ival > > 16 ) & 0xffff ;
/* TODO: We use hardcoded fragment sizes and numbers for now */
frag_size = 12 ; /* 4096 == 2^12 */
frag_buf = MAX_BUFS ;
ival = ( frag_buf < < 16 ) + frag_size ;
return put_user ( ival , ( int * ) arg ) ;
case SNDCTL_DSP_GETOSPACE :
if ( ! ( file - > f_mode & FMODE_WRITE ) )
return - EINVAL ;
info . fragstotal = MAX_BUFS ;
info . fragments = MAX_BUFS - harmony . nb_filled_play ;
info . fragsize = HARMONY_BUF_SIZE ;
info . bytes = info . fragments * info . fragsize ;
return copy_to_user ( ( void * ) arg , & info , sizeof ( info ) ) ? - EFAULT : 0 ;
case SNDCTL_DSP_GETISPACE :
if ( ! ( file - > f_mode & FMODE_READ ) )
return - EINVAL ;
info . fragstotal = MAX_BUFS ;
info . fragments = /*MAX_BUFS-*/ harmony . nb_filled_record ;
info . fragsize = HARMONY_BUF_SIZE ;
info . bytes = info . fragments * info . fragsize ;
return copy_to_user ( ( void * ) arg , & info , sizeof ( info ) ) ? - EFAULT : 0 ;
case SNDCTL_DSP_SYNC :
return 0 ;
}
return - EINVAL ;
}
/*
* harmony_interrupt ( )
*
* harmony interruption service routine
*
*/
static irqreturn_t harmony_interrupt ( int irq , void * dev , struct pt_regs * regs )
{
u32 dstatus ;
struct harmony_hpa * hpa ;
/* Setup the hpa */
hpa = ( ( struct harmony_dev * ) dev ) - > hpa ;
harmony_wait_CNTL ( ) ;
/* Read dstatus and pcuradd (the current address) */
dstatus = gsc_readl ( & hpa - > dstatus ) ;
/* Turn off interrupts */
harmony_disable_interrupts ( ) ;
/* Check if this is a request to get the next play buffer */
if ( dstatus & DSTATUS_PN ) {
if ( ! harmony . nb_filled_play ) {
harmony . suspended_playing = 1 ;
gsc_writel ( ( unsigned long ) silent . dma_handle , & hpa - > pnxtadd ) ;
if ( ! harmony . suspended_recording )
harmony_enable_interrupts ( ) ;
} else {
harmony . suspended_playing = 0 ;
gsc_writel ( ( unsigned long ) played_buf . dma_handle +
( HARMONY_BUF_SIZE * harmony . first_filled_play ) ,
& hpa - > pnxtadd ) ;
harmony . first_filled_play + + ;
harmony . first_filled_play % = MAX_BUFS ;
harmony . nb_filled_play - - ;
harmony_enable_interrupts ( ) ;
}
if ( harmony . blocked_playing )
wake_up_interruptible ( & harmony . wq_play ) ;
}
/* Check if we're being asked to fill in a recording buffer */
if ( dstatus & DSTATUS_RN ) {
if ( ( harmony . nb_filled_record + 2 > = MAX_BUFS ) | | harmony . suspended_recording )
{
harmony . nb_filled_record = 0 ;
harmony . first_filled_record = 0 ;
harmony . suspended_recording = 1 ;
gsc_writel ( ( unsigned long ) graveyard . dma_handle , & hpa - > rnxtadd ) ;
if ( ! harmony . suspended_playing )
harmony_enable_interrupts ( ) ;
} else {
int buf_to_fill ;
buf_to_fill = ( harmony . first_filled_record + harmony . nb_filled_record ) % MAX_BUFS ;
CHECK_WBACK_INV_OFFSET ( recorded_buf , HARMONY_BUF_SIZE * buf_to_fill , HARMONY_BUF_SIZE ) ;
gsc_writel ( ( unsigned long ) recorded_buf . dma_handle +
HARMONY_BUF_SIZE * buf_to_fill ,
& hpa - > rnxtadd ) ;
harmony . nb_filled_record + + ;
harmony_enable_interrupts ( ) ;
}
if ( harmony . blocked_recording & & harmony . nb_filled_record > 3 )
wake_up_interruptible ( & harmony . wq_record ) ;
}
return IRQ_HANDLED ;
}
/*
* Sound playing functions
*/
static struct file_operations harmony_audio_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. read = harmony_audio_read ,
. write = harmony_audio_write ,
. poll = harmony_audio_poll ,
. ioctl = harmony_audio_ioctl ,
. open = harmony_audio_open ,
. release = harmony_audio_release ,
} ;
static int harmony_audio_init ( void )
{
/* Request that IRQ */
if ( request_irq ( harmony . dev - > irq , harmony_interrupt , 0 , " harmony " , & harmony ) ) {
printk ( KERN_ERR PFX " Error requesting irq %d. \n " , harmony . dev - > irq ) ;
return - EFAULT ;
}
harmony . dsp_unit = register_sound_dsp ( & harmony_audio_fops , - 1 ) ;
if ( harmony . dsp_unit < 0 ) {
printk ( KERN_ERR PFX " Error registering dsp \n " ) ;
free_irq ( harmony . dev - > irq , & harmony ) ;
return - EFAULT ;
}
/* Clear the buffers so you don't end up with crap in the buffers. */
harmony_silence ( & played_buf , 0 , HARMONY_BUF_SIZE * MAX_BUFS ) ;
/* Make sure this makes it to cache */
CHECK_WBACK_INV_OFFSET ( played_buf , 0 , HARMONY_BUF_SIZE * MAX_BUFS ) ;
/* Clear out the silent buffer and flush to cache */
harmony_silence ( & silent , 0 , HARMONY_BUF_SIZE ) ;
CHECK_WBACK_INV_OFFSET ( silent , 0 , HARMONY_BUF_SIZE ) ;
harmony . audio_open = 0 ;
return 0 ;
}
/*
* mixer functions
*/
static void harmony_mixer_set_gain ( void )
{
harmony_wait_CNTL ( ) ;
gsc_writel ( harmony . current_gain , & harmony . hpa - > gainctl ) ;
}
/*
* Read gain of selected channel .
* The OSS rate is from 0 ( silent ) to 100 - > need some conversions
*
* The harmony gain are attenuation for output and monitor gain .
* is amplifaction for input gain
*/
# define to_harmony_level(level,max) ((level)*max / 100)
# define to_oss_level(level,max) ((level)*100 / max)
static int harmony_mixer_get_level ( int channel )
{
int left_level ;
int right_level ;
switch ( channel ) {
case SOUND_MIXER_VOLUME :
left_level = ( harmony . current_gain & GAIN_LO_MASK ) > > GAIN_LO_SHIFT ;
right_level = ( harmony . current_gain & GAIN_RO_MASK ) > > GAIN_RO_SHIFT ;
left_level = to_oss_level ( MAX_OUTPUT_LEVEL - left_level , MAX_OUTPUT_LEVEL ) ;
right_level = to_oss_level ( MAX_OUTPUT_LEVEL - right_level , MAX_OUTPUT_LEVEL ) ;
return ( right_level < < 8 ) + left_level ;
case SOUND_MIXER_IGAIN :
left_level = ( harmony . current_gain & GAIN_LI_MASK ) > > GAIN_LI_SHIFT ;
right_level = ( harmony . current_gain & GAIN_RI_MASK ) > > GAIN_RI_SHIFT ;
left_level = to_oss_level ( left_level , MAX_INPUT_LEVEL ) ;
right_level = to_oss_level ( right_level , MAX_INPUT_LEVEL ) ;
return ( right_level < < 8 ) + left_level ;
case SOUND_MIXER_MONITOR :
left_level = ( harmony . current_gain & GAIN_MA_MASK ) > > GAIN_MA_SHIFT ;
left_level = to_oss_level ( MAX_MONITOR_LEVEL - left_level , MAX_MONITOR_LEVEL ) ;
return ( left_level < < 8 ) + left_level ;
}
return - EINVAL ;
}
/*
* Some conversions for the same reasons .
* We give back the new real value ( s ) due to
* the rescale .
*/
static int harmony_mixer_set_level ( int channel , int value )
{
int left_level ;
int right_level ;
int new_left_level ;
int new_right_level ;
right_level = ( value & 0x0000ff00 ) > > 8 ;
left_level = value & 0x000000ff ;
if ( right_level > 100 ) right_level = 100 ;
if ( left_level > 100 ) left_level = 100 ;
switch ( channel ) {
case SOUND_MIXER_VOLUME :
right_level = to_harmony_level ( 100 - right_level , MAX_OUTPUT_LEVEL ) ;
left_level = to_harmony_level ( 100 - left_level , MAX_OUTPUT_LEVEL ) ;
new_right_level = to_oss_level ( MAX_OUTPUT_LEVEL - right_level , MAX_OUTPUT_LEVEL ) ;
new_left_level = to_oss_level ( MAX_OUTPUT_LEVEL - left_level , MAX_OUTPUT_LEVEL ) ;
harmony . current_gain = ( harmony . current_gain & ~ ( GAIN_LO_MASK | GAIN_RO_MASK ) )
| ( left_level < < GAIN_LO_SHIFT ) | ( right_level < < GAIN_RO_SHIFT ) ;
harmony_mixer_set_gain ( ) ;
return ( new_right_level < < 8 ) + new_left_level ;
case SOUND_MIXER_IGAIN :
right_level = to_harmony_level ( right_level , MAX_INPUT_LEVEL ) ;
left_level = to_harmony_level ( left_level , MAX_INPUT_LEVEL ) ;
new_right_level = to_oss_level ( right_level , MAX_INPUT_LEVEL ) ;
new_left_level = to_oss_level ( left_level , MAX_INPUT_LEVEL ) ;
harmony . current_gain = ( harmony . current_gain & ~ ( GAIN_LI_MASK | GAIN_RI_MASK ) )
| ( left_level < < GAIN_LI_SHIFT ) | ( right_level < < GAIN_RI_SHIFT ) ;
harmony_mixer_set_gain ( ) ;
return ( new_right_level < < 8 ) + new_left_level ;
case SOUND_MIXER_MONITOR :
left_level = to_harmony_level ( 100 - left_level , MAX_MONITOR_LEVEL ) ;
new_left_level = to_oss_level ( MAX_MONITOR_LEVEL - left_level , MAX_MONITOR_LEVEL ) ;
harmony . current_gain = ( harmony . current_gain & ~ GAIN_MA_MASK ) | ( left_level < < GAIN_MA_SHIFT ) ;
harmony_mixer_set_gain ( ) ;
return ( new_left_level < < 8 ) + new_left_level ;
}
return - EINVAL ;
}
# undef to_harmony_level
# undef to_oss_level
/*
* Return the selected input device ( mic or line )
*/
static int harmony_mixer_get_recmask ( void )
{
int current_input_line ;
current_input_line = ( harmony . current_gain & GAIN_IS_MASK )
> > GAIN_IS_SHIFT ;
if ( current_input_line )
return SOUND_MASK_MIC ;
return SOUND_MASK_LINE ;
}
/*
* Set the input ( only one at time , arbitrary priority to line in )
*/
static int harmony_mixer_set_recmask ( int recmask )
{
int new_input_line ;
int new_input_mask ;
int current_input_line ;
current_input_line = ( harmony . current_gain & GAIN_IS_MASK )
> > GAIN_IS_SHIFT ;
if ( ( current_input_line & & ( ( recmask & SOUND_MASK_LINE ) | | ! ( recmask & SOUND_MASK_MIC ) ) ) | |
( ! current_input_line & & ( ( recmask & SOUND_MASK_LINE ) & & ! ( recmask & SOUND_MASK_MIC ) ) ) ) {
new_input_line = 0 ;
new_input_mask = SOUND_MASK_LINE ;
} else {
new_input_line = 1 ;
new_input_mask = SOUND_MASK_MIC ;
}
harmony . current_gain = ( ( harmony . current_gain & ~ GAIN_IS_MASK ) |
( new_input_line < < GAIN_IS_SHIFT ) ) ;
harmony_mixer_set_gain ( ) ;
return new_input_mask ;
}
/*
* give the active outlines
*/
static int harmony_mixer_get_outmask ( void )
{
int outmask = 0 ;
if ( harmony . current_gain & GAIN_SE_MASK ) outmask | = MASK_INTERNAL ;
if ( harmony . current_gain & GAIN_LE_MASK ) outmask | = MASK_LINEOUT ;
if ( harmony . current_gain & GAIN_HE_MASK ) outmask | = MASK_HEADPHONES ;
return outmask ;
}
static int harmony_mixer_set_outmask ( int outmask )
{
if ( outmask & MASK_INTERNAL )
harmony . current_gain | = GAIN_SE_MASK ;
else
harmony . current_gain & = ~ GAIN_SE_MASK ;
if ( outmask & MASK_LINEOUT )
harmony . current_gain | = GAIN_LE_MASK ;
else
harmony . current_gain & = ~ GAIN_LE_MASK ;
if ( outmask & MASK_HEADPHONES )
harmony . current_gain | = GAIN_HE_MASK ;
else
harmony . current_gain & = ~ GAIN_HE_MASK ;
harmony_mixer_set_gain ( ) ;
return ( outmask & ( MASK_INTERNAL | MASK_LINEOUT | MASK_HEADPHONES ) ) ;
}
/*
* This code is inspired from sb_mixer . c
*/
static int harmony_mixer_ioctl ( struct inode * inode , struct file * file ,
unsigned int cmd , unsigned long arg )
{
int val ;
int ret ;
if ( cmd = = SOUND_MIXER_INFO ) {
mixer_info info ;
memset ( & info , 0 , sizeof ( info ) ) ;
strncpy ( info . id , " harmony " , sizeof ( info . id ) - 1 ) ;
strncpy ( info . name , " Harmony audio " , sizeof ( info . name ) - 1 ) ;
info . modify_counter = 1 ; /* ? */
if ( copy_to_user ( ( void * ) arg , & info , sizeof ( info ) ) )
return - EFAULT ;
return 0 ;
}
if ( cmd = = OSS_GETVERSION )
return put_user ( SOUND_VERSION , ( int * ) arg ) ;
/* read */
val = 0 ;
if ( _SIOC_DIR ( cmd ) & _SIOC_WRITE )
if ( get_user ( val , ( int * ) arg ) )
return - EFAULT ;
switch ( cmd ) {
case MIXER_READ ( SOUND_MIXER_CAPS ) :
ret = SOUND_CAP_EXCL_INPUT ;
break ;
case MIXER_READ ( SOUND_MIXER_STEREODEVS ) :
ret = SOUND_MASK_VOLUME | SOUND_MASK_IGAIN ;
break ;
case MIXER_READ ( SOUND_MIXER_RECMASK ) :
ret = SOUND_MASK_MIC | SOUND_MASK_LINE ;
break ;
case MIXER_READ ( SOUND_MIXER_DEVMASK ) :
ret = SOUND_MASK_VOLUME | SOUND_MASK_IGAIN |
SOUND_MASK_MONITOR ;
break ;
case MIXER_READ ( SOUND_MIXER_OUTMASK ) :
ret = MASK_INTERNAL | MASK_LINEOUT |
MASK_HEADPHONES ;
break ;
case MIXER_WRITE ( SOUND_MIXER_RECSRC ) :
ret = harmony_mixer_set_recmask ( val ) ;
break ;
case MIXER_READ ( SOUND_MIXER_RECSRC ) :
ret = harmony_mixer_get_recmask ( ) ;
break ;
case MIXER_WRITE ( SOUND_MIXER_OUTSRC ) :
ret = harmony_mixer_set_outmask ( val ) ;
break ;
case MIXER_READ ( SOUND_MIXER_OUTSRC ) :
ret = harmony_mixer_get_outmask ( ) ;
break ;
case MIXER_WRITE ( SOUND_MIXER_VOLUME ) :
case MIXER_WRITE ( SOUND_MIXER_IGAIN ) :
case MIXER_WRITE ( SOUND_MIXER_MONITOR ) :
ret = harmony_mixer_set_level ( cmd & 0xff , val ) ;
break ;
case MIXER_READ ( SOUND_MIXER_VOLUME ) :
case MIXER_READ ( SOUND_MIXER_IGAIN ) :
case MIXER_READ ( SOUND_MIXER_MONITOR ) :
ret = harmony_mixer_get_level ( cmd & 0xff ) ;
break ;
default :
return - EINVAL ;
}
if ( put_user ( ret , ( int * ) arg ) )
return - EFAULT ;
return 0 ;
}
static int harmony_mixer_open ( struct inode * inode , struct file * file )
{
if ( harmony . mixer_open )
return - EBUSY ;
harmony . mixer_open = 1 ;
return 0 ;
}
static int harmony_mixer_release ( struct inode * inode , struct file * file )
{
if ( ! harmony . mixer_open )
return - EBUSY ;
harmony . mixer_open = 0 ;
return 0 ;
}
static struct file_operations harmony_mixer_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. open = harmony_mixer_open ,
. release = harmony_mixer_release ,
. ioctl = harmony_mixer_ioctl ,
} ;
/*
* Mute all the output and reset Harmony .
*/
static void __init harmony_mixer_reset ( void )
{
harmony . current_gain = GAIN_TOTAL_SILENCE ;
harmony_mixer_set_gain ( ) ;
harmony_wait_CNTL ( ) ;
gsc_writel ( 1 , & harmony . hpa - > reset ) ;
mdelay ( 50 ) ; /* wait 50 ms */
gsc_writel ( 0 , & harmony . hpa - > reset ) ;
harmony . current_gain = GAIN_DEFAULT ;
harmony_mixer_set_gain ( ) ;
}
static int __init harmony_mixer_init ( void )
{
/* Register the device file operations */
harmony . mixer_unit = register_sound_mixer ( & harmony_mixer_fops , - 1 ) ;
if ( harmony . mixer_unit < 0 ) {
printk ( KERN_WARNING PFX " Error Registering Mixer Driver \n " ) ;
return - EFAULT ;
}
harmony_mixer_reset ( ) ;
harmony . mixer_open = 0 ;
return 0 ;
}
/*
* This is the callback that ' s called by the inventory hardware code
* if it finds a match to the registered driver .
*/
static int __devinit
harmony_driver_probe ( struct parisc_device * dev )
{
u8 id ;
u8 rev ;
u32 cntl ;
int ret ;
if ( harmony . hpa ) {
/* We only support one Harmony at this time */
printk ( KERN_ERR PFX " driver already registered \n " ) ;
return - EBUSY ;
}
if ( ! dev - > irq ) {
printk ( KERN_ERR PFX " no irq found \n " ) ;
return - ENODEV ;
}
/* Set the HPA of harmony */
2006-01-10 20:47:58 -05:00
harmony . hpa = ( struct harmony_hpa * ) dev - > hpa . start ;
2005-04-16 15:20:36 -07:00
harmony . dev = dev ;
/* Grab the ID and revision from the device */
id = gsc_readb ( & harmony . hpa - > id ) ;
if ( ( id | 1 ) ! = 0x15 ) {
printk ( KERN_WARNING PFX " wrong harmony id 0x%02x \n " , id ) ;
return - EBUSY ;
}
cntl = gsc_readl ( & harmony . hpa - > cntl ) ;
rev = ( cntl > > 20 ) & 0xff ;
printk ( KERN_INFO " Lasi Harmony Audio driver " HARMONY_VERSION " , "
" h/w id %i, rev. %i at 0x%lx, IRQ %i \n " ,
2006-01-10 20:47:58 -05:00
id , rev , dev - > hpa . start , harmony . dev - > irq ) ;
2005-04-16 15:20:36 -07:00
/* Make sure the control bit isn't set, although I don't think it
ever is . */
if ( cntl & CNTL_C ) {
printk ( KERN_WARNING PFX " CNTL busy \n " ) ;
harmony . hpa = 0 ;
return - EBUSY ;
}
/* Initialize the memory buffers */
if ( harmony_alloc_buffer ( & played_buf , MAX_BUFS ) | |
harmony_alloc_buffer ( & recorded_buf , MAX_BUFS ) | |
harmony_alloc_buffer ( & graveyard , 1 ) | |
harmony_alloc_buffer ( & silent , 1 ) ) {
ret = - EBUSY ;
goto out_err ;
}
/* Initialize /dev/mixer and /dev/audio */
if ( ( ret = harmony_mixer_init ( ) ) )
goto out_err ;
if ( ( ret = harmony_audio_init ( ) ) )
goto out_err ;
return 0 ;
out_err :
harmony . hpa = 0 ;
harmony_free_buffer ( & played_buf ) ;
harmony_free_buffer ( & recorded_buf ) ;
harmony_free_buffer ( & graveyard ) ;
harmony_free_buffer ( & silent ) ;
return ret ;
}
static struct parisc_device_id harmony_tbl [ ] = {
/* { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007A }, Bushmaster/Flounder */
{ HPHW_FIO , HVERSION_REV_ANY_ID , HVERSION_ANY_ID , 0x0007B } , /* 712/715 Audio */
{ HPHW_FIO , HVERSION_REV_ANY_ID , HVERSION_ANY_ID , 0x0007E } , /* Pace Audio */
{ HPHW_FIO , HVERSION_REV_ANY_ID , HVERSION_ANY_ID , 0x0007F } , /* Outfield / Coral II */
{ 0 , }
} ;
MODULE_DEVICE_TABLE ( parisc , harmony_tbl ) ;
static struct parisc_driver harmony_driver = {
. name = " Lasi Harmony " ,
. id_table = harmony_tbl ,
. probe = harmony_driver_probe ,
} ;
static int __init init_harmony ( void )
{
return register_parisc_driver ( & harmony_driver ) ;
}
static void __exit cleanup_harmony ( void )
{
free_irq ( harmony . dev - > irq , & harmony ) ;
unregister_sound_mixer ( harmony . mixer_unit ) ;
unregister_sound_dsp ( harmony . dsp_unit ) ;
harmony_free_buffer ( & played_buf ) ;
harmony_free_buffer ( & recorded_buf ) ;
harmony_free_buffer ( & graveyard ) ;
harmony_free_buffer ( & silent ) ;
unregister_parisc_driver ( & harmony_driver ) ;
}
MODULE_AUTHOR ( " Alex DeVries <alex@onefishtwo.ca> " ) ;
MODULE_DESCRIPTION ( " Harmony sound driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_init ( init_harmony ) ;
module_exit ( cleanup_harmony ) ;