/* ______ ___ ___ * /\ _ \ /\_ \ /\_ \ * \ \ \L\ \\//\ \ \//\ \ __ __ _ __ ___ * \ \ __ \ \ \ \ \ \ \ /'__`\ /'_ `\/\`'__\/ __`\ * \ \ \/\ \ \_\ \_ \_\ \_/\ __//\ \L\ \ \ \//\ \L\ \ * \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/ * \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/ * /\____/ * \_/__/ * * ESS AudioDrive driver. * * By Carsten Sorensen. * * See readme.txt for copyright information. */ #include "allegro.h" #include "allegro/internal/aintern.h" #include "allegro/platform/aintdos.h" #ifndef ALLEGRO_DOS #error something is wrong with the makefile #endif /* external interface to the AudioDrive driver */ static int ess_detect(int input); static int ess_init(int input, int voices); static void ess_exit(int input); static int ess_mixer_volume(int volume); static int ess_buffer_size(void); static char ess_desc[256] = EMPTY_STRING; DIGI_DRIVER digi_audiodrive = { DIGI_AUDIODRIVE, empty_string, empty_string, "ESS AudioDrive", 0, 0, MIXER_MAX_SFX, MIXER_DEF_SFX, ess_detect, ess_init, ess_exit, ess_mixer_volume, NULL, NULL, ess_buffer_size, _mixer_init_voice, _mixer_release_voice, _mixer_start_voice, _mixer_stop_voice, _mixer_loop_voice, _mixer_get_position, _mixer_set_position, _mixer_get_volume, _mixer_set_volume, _mixer_ramp_volume, _mixer_stop_volume_ramp, _mixer_get_frequency, _mixer_set_frequency, _mixer_sweep_frequency, _mixer_stop_frequency_sweep, _mixer_get_pan, _mixer_set_pan, _mixer_sweep_pan, _mixer_stop_pan_sweep, _mixer_set_echo, _mixer_set_tremolo, _mixer_set_vibrato, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL }; static int ess_int = -1; /* interrupt vector */ static int ess_hw_ver = -1; /* as reported by autodetect */ static int ess_dma_size = -1; /* size of dma transfer */ static int ess_dma_count = 0; /* need to resync with dma? */ static volatile int ess_semaphore = FALSE; /* reentrant interrupt? */ static int ess_sel; /* selector for the DMA buffer */ static unsigned long ess_buf[2]; /* pointers to the two buffers */ static int ess_bufnum = 0; /* the one currently in use */ static void ess_lock_mem(void); /* ess_buffer_size: * Returns the current DMA buffer size, for use by the audiostream code. */ static int ess_buffer_size(void) { return ess_dma_size/4; /* convert bytes to stereo 16 bit samples */ } /* is_dsp_ready_for_read: * Determines if DSP is ready to be read from. */ static INLINE RET_VOLATILE int is_dsp_ready_for_read(void) { return (inportb(0x0E + _sound_port) & 0x80); } /* ess_read_dsp: * Reads a byte from the DSP chip. Returns -1 if it times out. */ static INLINE RET_VOLATILE int ess_read_dsp(void) { int x; for (x=0; x<0xffff; x++) if (inportb(0x0E + _sound_port) & 0x80) return inportb(0x0A+_sound_port); return -1; } /* ess_write_dsp: * Writes a byte to the DSP chip. Returns -1 if it times out. */ static INLINE RET_VOLATILE int ess_write_dsp(unsigned char byte) { int x; for (x=0; x<0xffff; x++) { if (!(inportb(0x0C+_sound_port) & 0x80)) { outportb(0x0C+_sound_port, byte); return 0; } } return -1; } /* ess_mixer_volume: * Sets the AudioDrive mixer volume for playing digital samples. */ static int ess_mixer_volume(int volume) { return _sb_set_mixer(volume, -1); } /* ess_set_sample_rate: * The parameter is the rate to set in Hz (samples per second). */ static void ess_set_sample_rate(unsigned int rate) { int tc; int divider; if (rate > 22094) tc = 256 - 795500/rate; else tc = 128 - 397700/rate; rate = (rate*9)/20; divider = 256 - 7160000/(rate*82); ess_write_dsp(0xA1); ess_write_dsp(tc); ess_write_dsp(0xA2); ess_write_dsp(divider); } /* ess_read_dsp_version: * Reads the version number of the AudioDrive DSP chip, returning -1 on error. */ static int ess_read_dsp_version(void) { if (ess_hw_ver > 0) return ess_hw_ver; if (_sound_port <= 0) _sound_port = 0x220; if (_sb_reset_dsp(1) != 0) { ess_hw_ver = -1; } else { int major=0, minor=0; int i; ess_write_dsp(0xE7); for (i=0; i<240; i++) { if (is_dsp_ready_for_read()) { if (!major) major = ess_read_dsp(); else minor = ess_read_dsp(); } } if ((major==0x68) && ((minor&0xF0)==0x80)) { if ((minor&0x0F) >= 0xB) ess_hw_ver = 0x1868; else if ((minor&0x0F) >= 8) ess_hw_ver = 0x1688; else ess_hw_ver = 0x0688; return ess_hw_ver; } } return -1; } /* ess_play_buffer: * Starts a dma transfer of size bytes. */ static void ess_play_buffer(int size) { int value; ess_write_dsp(0xA4); ess_write_dsp((-size)&0xFF); ess_write_dsp(0xA5); ess_write_dsp((-size)>>8); ess_write_dsp(0xC0); ess_write_dsp(0xB8); value = ess_read_dsp() | 0x05; ess_write_dsp(0xB8); ess_write_dsp(value); } END_OF_STATIC_FUNCTION(ess_play_buffer); /* ess_interrupt: * The AudioDrive end-of-buffer interrupt handler. */ static int ess_interrupt(void) { int value; ess_dma_count++; if (ess_dma_count > 16) { ess_bufnum = (_dma_todo(_sound_dma) > (unsigned)ess_dma_size) ? 1 : 0; ess_dma_count = 0; } if (!ess_semaphore) { ess_semaphore = TRUE; ENABLE(); /* mix some more samples */ _mix_some_samples(ess_buf[ess_bufnum], _dos_ds, FALSE); DISABLE(); ess_semaphore = FALSE; } ess_bufnum = 1 - ess_bufnum; /* acknowlege AudioDrive */ ess_write_dsp(0xA4); ess_write_dsp((-ess_dma_size)&0xFF); ess_write_dsp(0xA5); ess_write_dsp((-ess_dma_size)>>8); ess_write_dsp(0xC0); ess_write_dsp(0xB8); value = ess_read_dsp() | 0x05; ess_write_dsp(0xB8); ess_write_dsp(value); /* acknowledge interrupt */ _eoi(_sound_irq); return 0; } END_OF_STATIC_FUNCTION(ess_interrupt); /* ess_detect: * AudioDrive detection routine. Uses the BLASTER environment variable, * or 'sensible' gueses if that doesn't exist. */ static int ess_detect(int input) { int orig_port = _sound_port; int orig_irq = _sound_irq; int orig_dma = _sound_dma; char *blaster = getenv("BLASTER"); if (input) return FALSE; /* parse BLASTER env */ if (blaster) { while (*blaster) { while ((*blaster == ' ') || (*blaster == '\t')) blaster++; if (*blaster) { switch (*blaster) { case 'a': case 'A': if (_sound_port < 0) _sound_port = strtol(blaster+1, NULL, 16); break; case 'i': case 'I': if (_sound_irq < 0) _sound_irq = strtol(blaster+1, NULL, 10); break; case 'd': case 'D': if (_sound_dma < 0) _sound_dma = strtol(blaster+1, NULL, 10); break; } while ((*blaster) && (*blaster != ' ') && (*blaster != '\t')) blaster++; } } } if (_sound_port < 0) _sound_port = 0x220; if (_sound_irq < 0) _sound_irq = 7; if (_sound_dma < 0) _sound_dma = 1; /* make sure we got a good port addres */ if (_sb_reset_dsp(1) != 0) { static int bases[] = { 0x210, 0x220, 0x230, 0x240, 0x250, 0x260, 0 }; int i; for (i=0; bases[i]; i++) { _sound_port = bases[i]; if (_sb_reset_dsp(1) == 0) break; } } /* check if the card really exists */ ess_read_dsp_version(); if (ess_hw_ver < 0) { ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("ESS AudioDrive not found")); _sound_port = orig_port; _sound_irq = orig_irq; _sound_dma = orig_dma; return FALSE; } /* figure out the hardware interrupt number */ ess_int = _map_irq(_sound_irq); /* set up the playback frequency */ if (_sound_freq <= 0) { _sound_freq = 22729; ess_dma_size = 1024; } else if (_sound_freq < 15000) { _sound_freq = 11363; ess_dma_size = 512; } else if (_sound_freq < 20000) { _sound_freq = 17046; ess_dma_size = 512; } else if (_sound_freq < 40000) { _sound_freq = 22729; ess_dma_size = 1024; } else { _sound_freq = 44194; ess_dma_size = 2048; } /* set up the card description */ uszprintf(ess_desc, sizeof(ess_desc), get_config_text("ES%X (%d hz) on port %X, using IRQ %d and DMA channel %d"), ess_hw_ver, _sound_freq, _sound_port, _sound_irq, _sound_dma); digi_audiodrive.desc = ess_desc; return TRUE; } /* ess_init: * AudioDrive init routine: returns zero on succes, -1 on failure. */ static int ess_init(int input, int voices) { int value; if (_dma_allocate_mem(ess_dma_size*2, &ess_sel, &ess_buf[0]) != 0) return -1; ess_buf[1] = ess_buf[0] + ess_dma_size; ess_lock_mem(); digi_audiodrive.voices = voices; if (_mixer_init(ess_dma_size/2, _sound_freq, TRUE, TRUE, &digi_audiodrive.voices) != 0) return -1; _mix_some_samples(ess_buf[0], _dos_ds, FALSE); _mix_some_samples(ess_buf[1], _dos_ds, FALSE); ess_bufnum = 0; _enable_irq(_sound_irq); _install_irq(ess_int, ess_interrupt); /* switch to AudioDrive extended mode */ _sb_reset_dsp(0x03); ess_write_dsp(0xC6); ess_set_sample_rate(_sound_freq); ess_write_dsp(0xB8); ess_write_dsp(0x04); ess_write_dsp(0xC0); ess_write_dsp(0xA8); value = ess_read_dsp() & ~3; /* 16 bit stereo */ value |= 0x01; ess_write_dsp(0xA8); ess_write_dsp(value); ess_write_dsp(0xB6); ess_write_dsp(0x00); ess_write_dsp(0xB7); ess_write_dsp(0x71); ess_write_dsp(0xB7); ess_write_dsp(0x9C); /* demand mode (4 bytes/request) */ ess_write_dsp(0xB9); ess_write_dsp(2); ess_write_dsp(0xC0); ess_write_dsp(0xB1); value = (ess_read_dsp() & 0x0F) | 0x50; ess_write_dsp(0xB1); ess_write_dsp(value); ess_write_dsp(0xC0); ess_write_dsp(0xB2); value = (ess_read_dsp() & 0x0F) | 0x50; ess_write_dsp(0xB2); ess_write_dsp(value); _dma_start(_sound_dma, ess_buf[0], ess_dma_size*2, TRUE, FALSE); ess_play_buffer(ess_dma_size); rest(100); _sb_voice(1); return 0; } /* ess_exit: * AudioDrive driver cleanup routine, removes ints, stops dma, * frees buffers, etc. */ static void ess_exit(int input) { /* halt sound output */ _sb_voice(0); /* stop dma transfer */ _dma_stop(_sound_dma); _sb_reset_dsp(1); /* restore interrupts */ _remove_irq(ess_int); /* reset PIC channels */ _restore_irq(_sound_irq); /* free conventional memory buffer */ __dpmi_free_dos_memory(ess_sel); _mixer_exit(); ess_hw_ver = -1; } /* ess_lock_mem: * Locks all the memory touched by parts of the AudiDrive code that are * executed in an interrupt context. */ static void ess_lock_mem(void) { LOCK_VARIABLE(digi_audiodrive); LOCK_VARIABLE(_sound_freq); LOCK_VARIABLE(_sound_port); LOCK_VARIABLE(_sound_dma); LOCK_VARIABLE(_sound_irq); LOCK_VARIABLE(ess_int); LOCK_VARIABLE(ess_hw_ver); LOCK_VARIABLE(ess_dma_size); LOCK_VARIABLE(ess_sel); LOCK_VARIABLE(ess_buf); LOCK_VARIABLE(ess_bufnum); LOCK_VARIABLE(ess_dma_count); LOCK_VARIABLE(ess_semaphore); LOCK_FUNCTION(ess_play_buffer); LOCK_FUNCTION(ess_interrupt); _dma_lock_mem(); }