2005-04-16 15:20:36 -07:00
/*
* linux / sound / oss / dmasound / dmasound_paula . c
*
* Amiga ` Paula ' DMA Sound Driver
*
* See linux / sound / oss / dmasound / dmasound_core . c for copyright and credits
* prior to 28 / 01 / 2001
*
* 28 / 01 / 2001 [ 0.1 ] Iain Sandoe
* - added versioning
* - put in and populated the hardware_afmts field .
* [ 0.2 ] - put in SNDCTL_DSP_GETCAPS value .
* [ 0.3 ] - put in constraint on state buffer usage .
* [ 0.4 ] - put in default hard / soft settings
*/
# include <linux/module.h>
# include <linux/config.h>
# include <linux/mm.h>
# include <linux/init.h>
# include <linux/ioport.h>
# include <linux/soundcard.h>
# include <linux/interrupt.h>
# include <asm/uaccess.h>
# include <asm/setup.h>
# include <asm/amigahw.h>
# include <asm/amigaints.h>
# include <asm/machdep.h>
# include "dmasound.h"
# define DMASOUND_PAULA_REVISION 0
# define DMASOUND_PAULA_EDITION 4
2006-01-12 01:06:12 -08:00
# define custom amiga_custom
2005-04-16 15:20:36 -07:00
/*
* The minimum period for audio depends on htotal ( for OCS / ECS / AGA )
* ( Imported from arch / m68k / amiga / amisound . c )
*/
extern volatile u_short amiga_audio_min_period ;
/*
* amiga_mksound ( ) should be able to restore the period after beeping
* ( Imported from arch / m68k / amiga / amisound . c )
*/
extern u_short amiga_audio_period ;
/*
* Audio DMA masks
*/
# define AMI_AUDIO_OFF (DMAF_AUD0 | DMAF_AUD1 | DMAF_AUD2 | DMAF_AUD3)
# define AMI_AUDIO_8 (DMAF_SETCLR | DMAF_MASTER | DMAF_AUD0 | DMAF_AUD1)
# define AMI_AUDIO_14 (AMI_AUDIO_8 | DMAF_AUD2 | DMAF_AUD3)
/*
* Helper pointers for 16 ( 14 ) - bit sound
*/
static int write_sq_block_size_half , write_sq_block_size_quarter ;
/*** Low level stuff *********************************************************/
2005-10-21 03:22:18 -04:00
static void * AmiAlloc ( unsigned int size , gfp_t flags ) ;
2005-04-16 15:20:36 -07:00
static void AmiFree ( void * obj , unsigned int size ) ;
static int AmiIrqInit ( void ) ;
# ifdef MODULE
static void AmiIrqCleanUp ( void ) ;
# endif
static void AmiSilence ( void ) ;
static void AmiInit ( void ) ;
static int AmiSetFormat ( int format ) ;
static int AmiSetVolume ( int volume ) ;
static int AmiSetTreble ( int treble ) ;
static void AmiPlayNextFrame ( int index ) ;
static void AmiPlay ( void ) ;
static irqreturn_t AmiInterrupt ( int irq , void * dummy , struct pt_regs * fp ) ;
# ifdef CONFIG_HEARTBEAT
/*
* Heartbeat interferes with sound since the 7 kHz low - pass filter and the
* power LED are controlled by the same line .
*/
# ifdef CONFIG_APUS
# define mach_heartbeat ppc_md.heartbeat
# endif
static void ( * saved_heartbeat ) ( int ) = NULL ;
static inline void disable_heartbeat ( void )
{
if ( mach_heartbeat ) {
saved_heartbeat = mach_heartbeat ;
mach_heartbeat = NULL ;
}
AmiSetTreble ( dmasound . treble ) ;
}
static inline void enable_heartbeat ( void )
{
if ( saved_heartbeat )
mach_heartbeat = saved_heartbeat ;
}
# else /* !CONFIG_HEARTBEAT */
# define disable_heartbeat() do { } while (0)
# define enable_heartbeat() do { } while (0)
# endif /* !CONFIG_HEARTBEAT */
/*** Mid level stuff *********************************************************/
static void AmiMixerInit ( void ) ;
static int AmiMixerIoctl ( u_int cmd , u_long arg ) ;
static int AmiWriteSqSetup ( void ) ;
static int AmiStateInfo ( char * buffer , size_t space ) ;
/*** Translations ************************************************************/
/* ++TeSche: radically changed for new expanding purposes...
*
* These two routines now deal with copying / expanding / translating the samples
* from user space into our buffer at the right frequency . They take care about
* how much data there ' s actually to read , how much buffer space there is and
* to convert samples into the right frequency / encoding . They will only work on
* complete samples so it may happen they leave some bytes in the input stream
* if the user didn ' t write a multiple of the current sample size . They both
* return the number of bytes they ' ve used from both streams so you may detect
* such a situation . Luckily all programs should be able to cope with that .
*
* I think I ' ve optimized anything as far as one can do in plain C , all
* variables should fit in registers and the loops are really short . There ' s
* one loop for every possible situation . Writing a more generalized and thus
* parameterized loop would only produce slower code . Feel free to optimize
* this in assembler if you like . : )
*
* I think these routines belong here because they ' re not yet really hardware
* independent , especially the fact that the Falcon can play 16 bit samples
* only in stereo is hardcoded in both of them !
*
* + + geert : split in even more functions ( one per format )
*/
/*
* Native format
*/
static ssize_t ami_ct_s8 ( const u_char * userPtr , size_t userCount ,
u_char frame [ ] , ssize_t * frameUsed , ssize_t frameLeft )
{
ssize_t count , used ;
if ( ! dmasound . soft . stereo ) {
void * p = & frame [ * frameUsed ] ;
count = min_t ( unsigned long , userCount , frameLeft ) & ~ 1 ;
used = count ;
if ( copy_from_user ( p , userPtr , count ) )
return - EFAULT ;
} else {
u_char * left = & frame [ * frameUsed > > 1 ] ;
u_char * right = left + write_sq_block_size_half ;
count = min_t ( unsigned long , userCount , frameLeft ) > > 1 & ~ 1 ;
used = count * 2 ;
while ( count > 0 ) {
if ( get_user ( * left + + , userPtr + + )
| | get_user ( * right + + , userPtr + + ) )
return - EFAULT ;
count - - ;
}
}
* frameUsed + = used ;
return used ;
}
/*
* Copy and convert 8 bit data
*/
# define GENERATE_AMI_CT8(funcname, convsample) \
static ssize_t funcname ( const u_char * userPtr , size_t userCount , \
u_char frame [ ] , ssize_t * frameUsed , \
ssize_t frameLeft ) \
{ \
ssize_t count , used ; \
\
if ( ! dmasound . soft . stereo ) { \
u_char * p = & frame [ * frameUsed ] ; \
count = min_t ( size_t , userCount , frameLeft ) & ~ 1 ; \
used = count ; \
while ( count > 0 ) { \
u_char data ; \
if ( get_user ( data , userPtr + + ) ) \
return - EFAULT ; \
* p + + = convsample ( data ) ; \
count - - ; \
} \
} else { \
u_char * left = & frame [ * frameUsed > > 1 ] ; \
u_char * right = left + write_sq_block_size_half ; \
count = min_t ( size_t , userCount , frameLeft ) > > 1 & ~ 1 ; \
used = count * 2 ; \
while ( count > 0 ) { \
u_char data ; \
if ( get_user ( data , userPtr + + ) ) \
return - EFAULT ; \
* left + + = convsample ( data ) ; \
if ( get_user ( data , userPtr + + ) ) \
return - EFAULT ; \
* right + + = convsample ( data ) ; \
count - - ; \
} \
} \
* frameUsed + = used ; \
return used ; \
}
# define AMI_CT_ULAW(x) (dmasound_ulaw2dma8[(x)])
# define AMI_CT_ALAW(x) (dmasound_alaw2dma8[(x)])
# define AMI_CT_U8(x) ((x) ^ 0x80)
GENERATE_AMI_CT8 ( ami_ct_ulaw , AMI_CT_ULAW )
GENERATE_AMI_CT8 ( ami_ct_alaw , AMI_CT_ALAW )
GENERATE_AMI_CT8 ( ami_ct_u8 , AMI_CT_U8 )
/*
* Copy and convert 16 bit data
*/
# define GENERATE_AMI_CT_16(funcname, convsample) \
static ssize_t funcname ( const u_char * userPtr , size_t userCount , \
u_char frame [ ] , ssize_t * frameUsed , \
ssize_t frameLeft ) \
{ \
ssize_t count , used ; \
u_short data ; \
\
if ( ! dmasound . soft . stereo ) { \
u_char * high = & frame [ * frameUsed > > 1 ] ; \
u_char * low = high + write_sq_block_size_half ; \
count = min_t ( size_t , userCount , frameLeft ) > > 1 & ~ 1 ; \
used = count * 2 ; \
while ( count > 0 ) { \
if ( get_user ( data , ( ( u_short * ) userPtr ) + + ) ) \
return - EFAULT ; \
data = convsample ( data ) ; \
* high + + = data > > 8 ; \
* low + + = ( data > > 2 ) & 0x3f ; \
count - - ; \
} \
} else { \
u_char * lefth = & frame [ * frameUsed > > 2 ] ; \
u_char * leftl = lefth + write_sq_block_size_quarter ; \
u_char * righth = lefth + write_sq_block_size_half ; \
u_char * rightl = righth + write_sq_block_size_quarter ; \
count = min_t ( size_t , userCount , frameLeft ) > > 2 & ~ 1 ; \
used = count * 4 ; \
while ( count > 0 ) { \
if ( get_user ( data , ( ( u_short * ) userPtr ) + + ) ) \
return - EFAULT ; \
data = convsample ( data ) ; \
* lefth + + = data > > 8 ; \
* leftl + + = ( data > > 2 ) & 0x3f ; \
if ( get_user ( data , ( ( u_short * ) userPtr ) + + ) ) \
return - EFAULT ; \
data = convsample ( data ) ; \
* righth + + = data > > 8 ; \
* rightl + + = ( data > > 2 ) & 0x3f ; \
count - - ; \
} \
} \
* frameUsed + = used ; \
return used ; \
}
# define AMI_CT_S16BE(x) (x)
# define AMI_CT_U16BE(x) ((x) ^ 0x8000)
# define AMI_CT_S16LE(x) (le2be16((x)))
# define AMI_CT_U16LE(x) (le2be16((x)) ^ 0x8000)
GENERATE_AMI_CT_16 ( ami_ct_s16be , AMI_CT_S16BE )
GENERATE_AMI_CT_16 ( ami_ct_u16be , AMI_CT_U16BE )
GENERATE_AMI_CT_16 ( ami_ct_s16le , AMI_CT_S16LE )
GENERATE_AMI_CT_16 ( ami_ct_u16le , AMI_CT_U16LE )
static TRANS transAmiga = {
. ct_ulaw = ami_ct_ulaw ,
. ct_alaw = ami_ct_alaw ,
. ct_s8 = ami_ct_s8 ,
. ct_u8 = ami_ct_u8 ,
. ct_s16be = ami_ct_s16be ,
. ct_u16be = ami_ct_u16be ,
. ct_s16le = ami_ct_s16le ,
. ct_u16le = ami_ct_u16le ,
} ;
/*** Low level stuff *********************************************************/
static inline void StopDMA ( void )
{
custom . aud [ 0 ] . audvol = custom . aud [ 1 ] . audvol = 0 ;
custom . aud [ 2 ] . audvol = custom . aud [ 3 ] . audvol = 0 ;
custom . dmacon = AMI_AUDIO_OFF ;
enable_heartbeat ( ) ;
}
2005-10-21 03:22:18 -04:00
static void * AmiAlloc ( unsigned int size , gfp_t flags )
2005-04-16 15:20:36 -07:00
{
return amiga_chip_alloc ( ( long ) size , " dmasound [Paula] " ) ;
}
static void AmiFree ( void * obj , unsigned int size )
{
amiga_chip_free ( obj ) ;
}
static int __init AmiIrqInit ( void )
{
/* turn off DMA for audio channels */
StopDMA ( ) ;
/* Register interrupt handler. */
if ( request_irq ( IRQ_AMIGA_AUD0 , AmiInterrupt , 0 , " DMA sound " ,
AmiInterrupt ) )
return 0 ;
return 1 ;
}
# ifdef MODULE
static void AmiIrqCleanUp ( void )
{
/* turn off DMA for audio channels */
StopDMA ( ) ;
/* release the interrupt */
free_irq ( IRQ_AMIGA_AUD0 , AmiInterrupt ) ;
}
# endif /* MODULE */
static void AmiSilence ( void )
{
/* turn off DMA for audio channels */
StopDMA ( ) ;
}
static void AmiInit ( void )
{
int period , i ;
AmiSilence ( ) ;
if ( dmasound . soft . speed )
period = amiga_colorclock / dmasound . soft . speed - 1 ;
else
period = amiga_audio_min_period ;
dmasound . hard = dmasound . soft ;
dmasound . trans_write = & transAmiga ;
if ( period < amiga_audio_min_period ) {
/* we would need to squeeze the sound, but we won't do that */
period = amiga_audio_min_period ;
} else if ( period > 65535 ) {
period = 65535 ;
}
dmasound . hard . speed = amiga_colorclock / ( period + 1 ) ;
for ( i = 0 ; i < 4 ; i + + )
custom . aud [ i ] . audper = period ;
amiga_audio_period = period ;
}
static int AmiSetFormat ( int format )
{
int size ;
/* Amiga sound DMA supports 8bit and 16bit (pseudo 14 bit) modes */
switch ( format ) {
case AFMT_QUERY :
return dmasound . soft . format ;
case AFMT_MU_LAW :
case AFMT_A_LAW :
case AFMT_U8 :
case AFMT_S8 :
size = 8 ;
break ;
case AFMT_S16_BE :
case AFMT_U16_BE :
case AFMT_S16_LE :
case AFMT_U16_LE :
size = 16 ;
break ;
default : /* :-) */
size = 8 ;
format = AFMT_S8 ;
}
dmasound . soft . format = format ;
dmasound . soft . size = size ;
if ( dmasound . minDev = = SND_DEV_DSP ) {
dmasound . dsp . format = format ;
dmasound . dsp . size = dmasound . soft . size ;
}
AmiInit ( ) ;
return format ;
}
# define VOLUME_VOXWARE_TO_AMI(v) \
( ( ( v ) < 0 ) ? 0 : ( ( v ) > 100 ) ? 64 : ( ( v ) * 64 ) / 100 )
# define VOLUME_AMI_TO_VOXWARE(v) ((v)*100 / 64)
static int AmiSetVolume ( int volume )
{
dmasound . volume_left = VOLUME_VOXWARE_TO_AMI ( volume & 0xff ) ;
custom . aud [ 0 ] . audvol = dmasound . volume_left ;
dmasound . volume_right = VOLUME_VOXWARE_TO_AMI ( ( volume & 0xff00 ) > > 8 ) ;
custom . aud [ 1 ] . audvol = dmasound . volume_right ;
if ( dmasound . hard . size = = 16 ) {
if ( dmasound . volume_left = = 64 & & dmasound . volume_right = = 64 ) {
custom . aud [ 2 ] . audvol = 1 ;
custom . aud [ 3 ] . audvol = 1 ;
} else {
custom . aud [ 2 ] . audvol = 0 ;
custom . aud [ 3 ] . audvol = 0 ;
}
}
return VOLUME_AMI_TO_VOXWARE ( dmasound . volume_left ) |
( VOLUME_AMI_TO_VOXWARE ( dmasound . volume_right ) < < 8 ) ;
}
static int AmiSetTreble ( int treble )
{
dmasound . treble = treble ;
if ( treble < 50 )
ciaa . pra & = ~ 0x02 ;
else
ciaa . pra | = 0x02 ;
return treble ;
}
# define AMI_PLAY_LOADED 1
# define AMI_PLAY_PLAYING 2
# define AMI_PLAY_MASK 3
static void AmiPlayNextFrame ( int index )
{
u_char * start , * ch0 , * ch1 , * ch2 , * ch3 ;
u_long size ;
/* used by AmiPlay() if all doubts whether there really is something
* to be played are already wiped out .
*/
start = write_sq . buffers [ write_sq . front ] ;
size = ( write_sq . count = = index ? write_sq . rear_size
: write_sq . block_size ) > > 1 ;
if ( dmasound . hard . stereo ) {
ch0 = start ;
ch1 = start + write_sq_block_size_half ;
size > > = 1 ;
} else {
ch0 = start ;
ch1 = start ;
}
disable_heartbeat ( ) ;
custom . aud [ 0 ] . audvol = dmasound . volume_left ;
custom . aud [ 1 ] . audvol = dmasound . volume_right ;
if ( dmasound . hard . size = = 8 ) {
custom . aud [ 0 ] . audlc = ( u_short * ) ZTWO_PADDR ( ch0 ) ;
custom . aud [ 0 ] . audlen = size ;
custom . aud [ 1 ] . audlc = ( u_short * ) ZTWO_PADDR ( ch1 ) ;
custom . aud [ 1 ] . audlen = size ;
custom . dmacon = AMI_AUDIO_8 ;
} else {
size > > = 1 ;
custom . aud [ 0 ] . audlc = ( u_short * ) ZTWO_PADDR ( ch0 ) ;
custom . aud [ 0 ] . audlen = size ;
custom . aud [ 1 ] . audlc = ( u_short * ) ZTWO_PADDR ( ch1 ) ;
custom . aud [ 1 ] . audlen = size ;
if ( dmasound . volume_left = = 64 & & dmasound . volume_right = = 64 ) {
/* We can play pseudo 14-bit only with the maximum volume */
ch3 = ch0 + write_sq_block_size_quarter ;
ch2 = ch1 + write_sq_block_size_quarter ;
custom . aud [ 2 ] . audvol = 1 ; /* we are being affected by the beeps */
custom . aud [ 3 ] . audvol = 1 ; /* restoring volume here helps a bit */
custom . aud [ 2 ] . audlc = ( u_short * ) ZTWO_PADDR ( ch2 ) ;
custom . aud [ 2 ] . audlen = size ;
custom . aud [ 3 ] . audlc = ( u_short * ) ZTWO_PADDR ( ch3 ) ;
custom . aud [ 3 ] . audlen = size ;
custom . dmacon = AMI_AUDIO_14 ;
} else {
custom . aud [ 2 ] . audvol = 0 ;
custom . aud [ 3 ] . audvol = 0 ;
custom . dmacon = AMI_AUDIO_8 ;
}
}
write_sq . front = ( write_sq . front + 1 ) % write_sq . max_count ;
write_sq . active | = AMI_PLAY_LOADED ;
}
static void AmiPlay ( void )
{
int minframes = 1 ;
custom . intena = IF_AUD0 ;
if ( write_sq . active & AMI_PLAY_LOADED ) {
/* There's already a frame loaded */
custom . intena = IF_SETCLR | IF_AUD0 ;
return ;
}
if ( write_sq . active & AMI_PLAY_PLAYING )
/* Increase threshold: frame 1 is already being played */
minframes = 2 ;
if ( write_sq . count < minframes ) {
/* Nothing to do */
custom . intena = IF_SETCLR | IF_AUD0 ;
return ;
}
if ( write_sq . count < = minframes & &
write_sq . rear_size < write_sq . block_size & & ! write_sq . syncing ) {
/* hmmm, the only existing frame is not
* yet filled and we ' re not syncing ?
*/
custom . intena = IF_SETCLR | IF_AUD0 ;
return ;
}
AmiPlayNextFrame ( minframes ) ;
custom . intena = IF_SETCLR | IF_AUD0 ;
}
static irqreturn_t AmiInterrupt ( int irq , void * dummy , struct pt_regs * fp )
{
int minframes = 1 ;
custom . intena = IF_AUD0 ;
if ( ! write_sq . active ) {
/* Playing was interrupted and sq_reset() has already cleared
* the sq variables , so better don ' t do anything here .
*/
WAKE_UP ( write_sq . sync_queue ) ;
return IRQ_HANDLED ;
}
if ( write_sq . active & AMI_PLAY_PLAYING ) {
/* We've just finished a frame */
write_sq . count - - ;
WAKE_UP ( write_sq . action_queue ) ;
}
if ( write_sq . active & AMI_PLAY_LOADED )
/* Increase threshold: frame 1 is already being played */
minframes = 2 ;
/* Shift the flags */
write_sq . active = ( write_sq . active < < 1 ) & AMI_PLAY_MASK ;
if ( ! write_sq . active )
/* No frame is playing, disable audio DMA */
StopDMA ( ) ;
custom . intena = IF_SETCLR | IF_AUD0 ;
if ( write_sq . count > = minframes )
/* Try to play the next frame */
AmiPlay ( ) ;
if ( ! write_sq . active )
/* Nothing to play anymore.
Wake up a process waiting for audio output to drain . */
WAKE_UP ( write_sq . sync_queue ) ;
return IRQ_HANDLED ;
}
/*** Mid level stuff *********************************************************/
/*
* / dev / mixer abstraction
*/
static void __init AmiMixerInit ( void )
{
dmasound . volume_left = 64 ;
dmasound . volume_right = 64 ;
custom . aud [ 0 ] . audvol = dmasound . volume_left ;
custom . aud [ 3 ] . audvol = 1 ; /* For pseudo 14bit */
custom . aud [ 1 ] . audvol = dmasound . volume_right ;
custom . aud [ 2 ] . audvol = 1 ; /* For pseudo 14bit */
dmasound . treble = 50 ;
}
static int AmiMixerIoctl ( u_int cmd , u_long arg )
{
int data ;
switch ( cmd ) {
case SOUND_MIXER_READ_DEVMASK :
return IOCTL_OUT ( arg , SOUND_MASK_VOLUME | SOUND_MASK_TREBLE ) ;
case SOUND_MIXER_READ_RECMASK :
return IOCTL_OUT ( arg , 0 ) ;
case SOUND_MIXER_READ_STEREODEVS :
return IOCTL_OUT ( arg , SOUND_MASK_VOLUME ) ;
case SOUND_MIXER_READ_VOLUME :
return IOCTL_OUT ( arg ,
VOLUME_AMI_TO_VOXWARE ( dmasound . volume_left ) |
VOLUME_AMI_TO_VOXWARE ( dmasound . volume_right ) < < 8 ) ;
case SOUND_MIXER_WRITE_VOLUME :
IOCTL_IN ( arg , data ) ;
return IOCTL_OUT ( arg , dmasound_set_volume ( data ) ) ;
case SOUND_MIXER_READ_TREBLE :
return IOCTL_OUT ( arg , dmasound . treble ) ;
case SOUND_MIXER_WRITE_TREBLE :
IOCTL_IN ( arg , data ) ;
return IOCTL_OUT ( arg , dmasound_set_treble ( data ) ) ;
}
return - EINVAL ;
}
static int AmiWriteSqSetup ( void )
{
write_sq_block_size_half = write_sq . block_size > > 1 ;
write_sq_block_size_quarter = write_sq_block_size_half > > 1 ;
return 0 ;
}
static int AmiStateInfo ( char * buffer , size_t space )
{
int len = 0 ;
len + = sprintf ( buffer + len , " \t sound.volume_left = %d [0...64] \n " ,
dmasound . volume_left ) ;
len + = sprintf ( buffer + len , " \t sound.volume_right = %d [0...64] \n " ,
dmasound . volume_right ) ;
if ( len > = space ) {
printk ( KERN_ERR " dmasound_paula: overlowed state buffer alloc. \n " ) ;
len = space ;
}
return len ;
}
/*** Machine definitions *****************************************************/
static SETTINGS def_hard = {
. format = AFMT_S8 ,
. stereo = 0 ,
. size = 8 ,
. speed = 8000
} ;
static SETTINGS def_soft = {
. format = AFMT_U8 ,
. stereo = 0 ,
. size = 8 ,
. speed = 8000
} ;
static MACHINE machAmiga = {
. name = " Amiga " ,
. name2 = " AMIGA " ,
. owner = THIS_MODULE ,
. dma_alloc = AmiAlloc ,
. dma_free = AmiFree ,
. irqinit = AmiIrqInit ,
# ifdef MODULE
. irqcleanup = AmiIrqCleanUp ,
# endif /* MODULE */
. init = AmiInit ,
. silence = AmiSilence ,
. setFormat = AmiSetFormat ,
. setVolume = AmiSetVolume ,
. setTreble = AmiSetTreble ,
. play = AmiPlay ,
. mixer_init = AmiMixerInit ,
. mixer_ioctl = AmiMixerIoctl ,
. write_sq_setup = AmiWriteSqSetup ,
. state_info = AmiStateInfo ,
. min_dsp_speed = 8000 ,
. version = ( ( DMASOUND_PAULA_REVISION < < 8 ) | DMASOUND_PAULA_EDITION ) ,
. hardware_afmts = ( AFMT_S8 | AFMT_S16_BE ) , /* h'ware-supported formats *only* here */
. capabilities = DSP_CAP_BATCH /* As per SNDCTL_DSP_GETCAPS */
} ;
/*** Config & Setup **********************************************************/
int __init dmasound_paula_init ( void )
{
int err ;
if ( MACH_IS_AMIGA & & AMIGAHW_PRESENT ( AMI_AUDIO ) ) {
if ( ! request_mem_region ( CUSTOM_PHYSADDR + 0xa0 , 0x40 ,
" dmasound [Paula] " ) )
return - EBUSY ;
dmasound . mach = machAmiga ;
dmasound . mach . default_hard = def_hard ;
dmasound . mach . default_soft = def_soft ;
err = dmasound_init ( ) ;
if ( err )
release_mem_region ( CUSTOM_PHYSADDR + 0xa0 , 0x40 ) ;
return err ;
} else
return - ENODEV ;
}
static void __exit dmasound_paula_cleanup ( void )
{
dmasound_deinit ( ) ;
release_mem_region ( CUSTOM_PHYSADDR + 0xa0 , 0x40 ) ;
}
module_init ( dmasound_paula_init ) ;
module_exit ( dmasound_paula_cleanup ) ;
MODULE_LICENSE ( " GPL " ) ;