/* SBIO.C */ /* Soundblaster 16 basic audio I/O functions */ /* Copyright 1995 by Ethan Brodsky. All rights reserved */ /* Modified extensively by Philip VanBaren to suit my purposes */ /* Modified by Emil Laurentiu Wednesday, 06 August 1997 */ /* Interface variables that can be changed in the background */ volatile int sb16dmarunning; volatile char curblock; #include #include #include #include #include #include "sbio.h" #include "freq.h" /* Needed for DOUT() definition only */ #define lo(value) (unsigned char)((value) & 0x00FF) #define hi(value) (unsigned char)((value) >> 8) int mixerport; int mixdataport; int resetport; int readport; int writeport; int pollport; int poll16port; int pic_rotateport; int pic_maskport; int dma_maskport; int dma_clrptrport; int dma_modeport; int dma_baseaddrport; int dma_countport; int dma_pageport; char irq_startmask; char irq_stopmask; char irq_intvector; char int_controller; char dma_startmask; char dma_stopmask; char dma_mode; /* This function is defined in sc_sb16.c */ extern void interrupt sb16_callback( void ); void interrupt( *oldintvector ) ( ) = NULL; int handlerinstalled; void far *dmabuffer = NULL; /* Twice the size of the output buffer */ int far *dmaptr = NULL; /* Pointer to the used portion */ unsigned long buf_addr; /* 16-bit addressing */ unsigned char buf_page; unsigned int buf_ofs; mode iomode; /* Flags input or output mode */ /* Low level sound card I/O */ void write_dsp( unsigned char value ) { while ( inp( writeport ) & 0x80 ); /* Wait for bit 7 to be cleared */ outp( writeport, value ); } unsigned char read_dsp( void ) { long timeout = 1000000L; /* Wait for bit 7 to be set, or for a timeout */ while ( ( !( inp( pollport ) & 0x80 ) ) && ( --timeout ) ); return inp( readport ); } int reset_dsp( void ) { int i; sb16dmarunning = 0; outp( resetport, 1 ); outp( resetport, 0 ); i = 100; while ( ( read_dsp( ) != 0xAA ) && i-- ); return i; } /* Convert a far pointer to a linear address, for DMA addressing */ #define getlinearaddr(p) ((unsigned long)FP_SEG(p)*16 + (unsigned long)FP_OFF(p)) /* Initialization and shutdown */ void installhandler( void ) { /* Install the interrupt handler */ disable( ); /* Disable interrupts */ outp( pic_maskport, ( inp( pic_maskport ) | irq_stopmask ) ); /* Mask IRQ */ oldintvector = getvect( irq_intvector ); /* Save old vector */ setvect( irq_intvector, sb16_callback ); /* Install new handler */ outp( pic_maskport, ( inp( pic_maskport ) & irq_startmask ) ); /* Unmask IRQ */ enable( ); /* Reenable interupts */ handlerinstalled = 1; } void uninstallhandler( void ) { sb16dmarunning = 0; disable( ); /* Disable interrupts */ outp( pic_maskport, ( inp( pic_maskport ) | irq_stopmask ) ); /* Mask IRQ */ setvect( irq_intvector, oldintvector ); /* Restore old vector */ enable( ); /* Enable interrupts */ handlerinstalled = 0; } /* This function is run at program exit to ensure cleanup */ void sb_exitproc( void ) { sb16dmarunning = 0; outp( 0x20, 0x20 ); outp( 0xA0, 0x20 ); /* Acknowledge any hanging ints */ write_dsp( 0xD5 ); /* Stop digitized sound xfer */ outp( dma_maskport, dma_stopmask ); /* Mask DMA channel */ if ( handlerinstalled ) uninstallhandler( ); /* Uninstall int handler */ reset_dsp( ); /* Reset SB DSP */ } /* * Initialize the Soundblaster-16 card with the settings: * baseio: base IO address for the card * irq: IRQ channel used by the card * dma16: 16-bit DMA channel used by the card * io: sampling mode, either "input" or "output" * length: maximum block size to be used, in samples * (this is used for allocating the DMA buffer) */ int init_sb( int baseio, char irq, char dma16, mode io, unsigned int length ) { int val, onboard; /* Sound card IO ports */ mixerport = baseio + 0x004; mixdataport = baseio + 0x005; resetport = baseio + 0x006; readport = baseio + 0x00A; writeport = baseio + 0x00C; pollport = baseio + 0x00E; poll16port = baseio + 0x00F; /* Reset DSP */ sb16dmarunning = 0; if ( !reset_dsp( ) ) return 0; /* Verify that we have a SB-16 at this address */ write_dsp( 0xE1 ); val = read_dsp( ); /* Get the major version number */ read_dsp( ); /* Grab the minor version number also */ if ( val < 4 ) return 0; /* * Check the current IRQ and DMA settings, and compare with the values * passed to this routine */ outportb( mixerport, 0x80 ); /* IRQ settings */ val = inportb( mixdataport ); onboard = -1; if ( val & 0x01 ) { onboard = 2; DOUT( "SB16: IRQ2 enabled" ); } if ( val & 0x02 ) { onboard = 5; DOUT( "SB16: IRQ5 enabled" ); } if ( val & 0x04 ) { onboard = 7; DOUT( "SB16: IRQ7 enabled" ); } if ( val & 0x08 ) { onboard = 10; DOUT( "SB16: IRQ10 enabled" ); } if ( onboard == -1 ) { puts( "Error: Soundblaster has no IRQ enabled, aborting ..." ); return ( 0 ); } switch ( irq ) { case 2: if ( !( val & 0x01 ) ) { irq = -1; } break; case 5: if ( !( val & 0x02 ) ) { irq = -1; } break; case 7: if ( !( val & 0x04 ) ) { irq = -1; } break; case 10: if ( !( val & 0x08 ) ) { irq = -1; } break; default: irq = -1; } if ( irq == -1 ) { printf( "Warning: Soundblaster has IRQ%d selected, using that value instead\n", onboard ); irq = onboard; } outportb( mixerport, 0x81 ); /* DMA settings */ val = inportb( mixdataport ); onboard = -1; if ( val & 0x01 ) { /* onboard=0; */ DOUT( "SB16: DMA0 enabled" ); } if ( val & 0x02 ) { /* onboard=1; */ DOUT( "SB16: DMA1 enabled" ); } if ( val & 0x08 ) { /* onboard=3; */ DOUT( "SB16: DMA3 enabled" ); } if ( val & 0x20 ) { onboard = 5; DOUT( "SB16: DMA5 enabled" ); } if ( val & 0x40 ) { onboard = 6; DOUT( "SB16: DMA6 enabled" ); } if ( val & 0x80 ) { onboard = 7; DOUT( "SB16: DMA7 enabled" ); } if ( onboard == -1 ) { puts( "Error: Soundblaster has no 16-bit DMA enabled, aborting ..." ); return ( 0 ); } switch ( dma16 ) { // case 0: if(!(val&0x01)) { dma16=-1; } break; // case 1: if(!(val&0x02)) { dma16=-1; } break; // case 3: if(!(val&0x08)) { dma16=-1; } break; case 5: if ( !( val & 0x20 ) ) { dma16 = -1; } break; case 6: if ( !( val & 0x40 ) ) { dma16 = -1; } break; case 7: if ( !( val & 0x80 ) ) { dma16 = -1; } break; default: dma16 = -1; } if ( dma16 == -1 ) { printf( "Warning: Soundblaster has DMA%d selected, using that value instead\n", onboard ); dma16 = onboard; } #ifdef DEBUG_OUTPUT /* Check if the IRQs are enabled */ outportb( mixerport, 0x82 ); /* IRQ status */ val = inportb( mixdataport ); if ( val & 0x01 ) { DOUT( "SB16: 8-bit IRQ active" ); } if ( val & 0x02 ) { DOUT( "SB16: 16-bit IRQ active" ); } if ( val & 0x04 ) { DOUT( "SB16: MPU-401 IRQ active" ); } #endif /* These two lines are strictly a kludge for freq.exe */ outportb( mixerport, 0x3d ); /* Input control */ outportb( mixdataport, 0x7f );/* Use all channels for input */ /* Compute interrupt ports and parameters */ if ( irq < 8 ) { int_controller = 1; pic_rotateport = 0x20; pic_maskport = 0x21; irq_intvector = 0x08 + irq; } else { int_controller = 2; pic_rotateport = 0xA0; pic_maskport = 0x21; irq_intvector = 0x70 + irq - 8; } irq_stopmask = 1 << ( irq % 8 ); irq_startmask = ~irq_stopmask; /* Compute DMA ports and parameters */ dma_maskport = 0xD4; dma_clrptrport = 0xD8; dma_modeport = 0xD6; dma_baseaddrport = 0xC0 + 4 * ( dma16 - 4 ); dma_countport = 0xC2 + 4 * ( dma16 - 4 ); switch ( dma16 ) { case 5: dma_pageport = 0x8B; break; case 6: dma_pageport = 0x89; break; case 7: dma_pageport = 0x8A; break; } dma_stopmask = dma16 - 4 + 0x04; /* 000001xx */ dma_startmask = dma16 - 4 + 0x00; /* 000000xx */ /* Allocate a buffer for DMA transfer */ /* (need a block of memory that does not cross a page boundary) */ if ( ( dmabuffer = farmalloc( 8 * length ) ) == NULL ) { puts( "Unable to allocate DMA buffer." ); exit( 1 ); } dmaptr = ( int far * ) dmabuffer; if ( ( ( getlinearaddr( dmabuffer ) >> 1 ) % 65536L ) + length * 2 > 65536L ) dmaptr += 2 * length; /* Pick second half to avoid crossing * boundary */ /* Compute DMA parameters */ buf_addr = getlinearaddr( dmaptr ); buf_page = ( unsigned int ) ( buf_addr >> 16 ); buf_ofs = ( unsigned int ) ( ( buf_addr >> 1 ) % 65536L ); /* Other initialization */ iomode = io; switch ( iomode ) { case input: dma_mode = dma16 - 4 + 0x54; break; /* 010101xx */ case output: dma_mode = dma16 - 4 + 0x58; break; /* 010110xx */ } installhandler( ); /* Install interrupt handler */ atexit( sb_exitproc ); /* Install exit procedure */ return 1; } /* * Shut down the sampling process, clean up the interrupts, * and free up the memory allocation */ void shutdown_sb( void ) { sb16dmarunning = 0; reset_dsp( ); if ( handlerinstalled ) uninstallhandler( ); farfree( dmabuffer ); } /* * Start continuous I/O at a rate of {rate} Hz, with a call to the * callback_sb16 procedure after every block of length {length} has * been finished. The sampling input or output is done on alternating * blocks pointed to by (dmaptr+curblock*length). * i.e. When curblock is 0, the current block is (dmaptr). * When curblock is 1, the current block is (dmaptr+length) * The variable curblock is maintained in the callback_sb16 routine. */ void startio( unsigned int rate, unsigned long length ) { sb16dmarunning = 1; curblock = 0; /* Program DMA controller */ outp( dma_maskport, dma_stopmask ); outp( dma_clrptrport, 0x00 ); outp( dma_modeport, dma_mode ); outp( dma_baseaddrport, lo( buf_ofs ) ); /* Low byte of offset */ outp( dma_baseaddrport, hi( buf_ofs ) ); /* High word of offset */ outp( dma_countport, lo( length * 2 - 1 ) ); /* Low byte of count */ outp( dma_countport, hi( length * 2 - 1 ) ); /* High byte of count */ outp( dma_pageport, buf_page ); outp( dma_maskport, dma_startmask ); /* Program sound card */ switch ( iomode ) { case input: write_dsp( 0x42 ); break; /* Set input sampling rate */ case output: write_dsp( 0x41 ); break; /* Set output sampling rate */ } write_dsp( hi( rate ) ); /* High byte of sampling rate */ write_dsp( lo( rate ) ); /* Low byte of sampling rate */ switch ( iomode ) { case output: write_dsp( 0xB6 ); break; /* 16-bit D->A, A/I, FIFO */ case input: write_dsp( 0xBE ); break; /* 16-bit A->D, A/I, FIFO */ } write_dsp( 0x10 ); /* DMA Mode: 16-bit signed mono */ write_dsp( lo( length - 1 ) );/* Low byte of block length */ write_dsp( hi( length - 1 ) );/* High byte of block length */ } /* * Stops the current sampling process. */ void stopio( void ) { outp( 0x20, 0x20 ); outp( 0xA0, 0x20 ); /* Acknowledge any hanging ints */ write_dsp( 0xD9 ); /* Stop digitized sound xfer */ outp( dma_maskport, dma_stopmask ); /* Mask DMA channel */ sb16dmarunning = 0; reset_dsp( ); /* Reset SB DSP */ } /* Mixer setting and reading functions */ void set_cd_level( unsigned int level ) { level = level * 256 / 100; if ( level > 255 ) level = 255; outportb( mixerport, 0x36 ); /* CD Audio left */ outportb( mixdataport, level ); outportb( mixerport, 0x37 ); /* CD Audio right */ outportb( mixdataport, level ); } void set_mic_level( unsigned int level ) { level = level * 256 / 100; if ( level > 255 ) level = 255; outportb( mixerport, 0x3A ); /* Microphone */ outportb( mixdataport, level ); } void set_line_level( unsigned int level ) { level = level * 256 / 100; if ( level > 255 ) level = 255; outportb( mixerport, 0x38 ); /* Line In left */ outportb( mixdataport, level ); outportb( mixerport, 0x39 ); /* Line In right */ outportb( mixdataport, level ); } unsigned int get_cd_level( void ) { unsigned int level; outportb( mixerport, 0x36 ); /* CD Audio left */ level = inportb( mixdataport ); outportb( mixerport, 0x37 ); /* CD Audio right */ level += inportb( mixdataport ); level = level * 50 / 256; if ( level > 100 ) level = 100; return ( level ); } unsigned int get_mic_level( void ) { unsigned int level; outportb( mixerport, 0x3A ); /* Microphone */ level = inportb( mixdataport ); level = level * 100 / 256; if ( level > 100 ) level = 100; return ( level ); } unsigned int get_line_level( void ) { unsigned int level; outportb( mixerport, 0x38 ); /* Line In left */ level = inportb( mixdataport ); outportb( mixerport, 0x39 ); /* Line In right */ level += inportb( mixdataport ); level = level * 50 / 256; if ( level > 100 ) level = 100; return ( level ); } void set_master_level( int level ) { level &= 0xf; level |= level << 4; outportb( sb_addr + 0x4, 0x22 ); /* Master volume */ outportb( sb_addr + 0x5, level ); return; } void set_fm_level( int level ) { level &= 0xf; level |= level << 4; outportb( sb_addr + 0x4, 0x26 ); /* FM volume */ outportb( sb_addr + 0x5, level ); return; }