X-Git-Url: http://git.mutantstargoat.com/user/nuclear/?p=dosdemo;a=blobdiff_plain;f=libs%2Fmikmod%2Fdrivers%2Fdos%2Fdoswss.c;fp=libs%2Fmikmod%2Fdrivers%2Fdos%2Fdoswss.c;h=41cdf38948339ece22ba0204843191315fd1ef52;hp=0000000000000000000000000000000000000000;hb=cf94899f4f4d8535074db6421245b973d2fcde8c;hpb=8024ae981f39d370af5cceb3cb97f62820b0a120 diff --git a/libs/mikmod/drivers/dos/doswss.c b/libs/mikmod/drivers/dos/doswss.c new file mode 100644 index 0000000..41cdf38 --- /dev/null +++ b/libs/mikmod/drivers/dos/doswss.c @@ -0,0 +1,577 @@ +/* MikMod sound library + (c) 1998, 1999 Miodrag Vallat and others - see file AUTHORS for + complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library 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 Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + Windows Sound System I/O routines (CS42XX, ESS18XX, GUS+DaughterBoard etc) + Written by Andrew Zabolotny + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef DRV_WSS + +#include +#include +#include +#include +#include +#include +#include + +#include "doswss.h" + +/********************************************* Private variables/routines *****/ + +__wss_state wss; + +/* WSS frequency rates... lower bit selects one of two frequency generators */ +static unsigned int wss_rates[14][2] = { + {5510, 0x00 | WSSM_XTAL2}, + {6620, 0x0E | WSSM_XTAL2}, + {8000, 0x00 | WSSM_XTAL1}, + {9600, 0x0E | WSSM_XTAL1}, + {11025, 0x02 | WSSM_XTAL2}, + {16000, 0x02 | WSSM_XTAL1}, + {18900, 0x04 | WSSM_XTAL2}, + {22050, 0x06 | WSSM_XTAL2}, + {27420, 0x04 | WSSM_XTAL1}, + {32000, 0x06 | WSSM_XTAL1}, + {33075, 0x0C | WSSM_XTAL2}, + {37800, 0x08 | WSSM_XTAL2}, + {44100, 0x0A | WSSM_XTAL2}, + {48000, 0x0C | WSSM_XTAL1} +}; + +static void wss_irq() +{ + /* Make sure its not a spurious IRQ */ + if (!irq_check(wss.irq_handle)) + return; + + wss.irqcount++; + + /* Clear IRQ status */ + outportb(WSS_STATUS, 0); + + /* Write transfer count again */ + __wss_outreg(WSSR_COUNT_LOW, wss.samples & 0xff); + __wss_outreg(WSSR_COUNT_HIGH, wss.samples >> 8); + irq_ack(wss.irq_handle); + + enable(); + if (wss.timer_callback) + wss.timer_callback(); +} + +static void wss_irq_end() +{ +} + +/* WSS accepts some conventional values instead of frequency in Hz... */ +static unsigned char __wss_getrate(unsigned int *freq) +{ + int i, best = -1, delta = 0xffff; + + for (i = 0; i < 14; i++) { + int newdelta = abs(wss_rates[i][0] - *freq); + if (newdelta < delta) + best = i, delta = newdelta; + } + + *freq = wss_rates[best][0]; + return wss_rates[best][1]; +} + +/* Check if we really have a WSS compatible card on given address */ +static boolean __wss_ping() +{ + /* Disable CODEC operations first */ + __wss_regbit_reset(WSSR_IFACE_CTRL, WSSM_PLAYBACK_ENABLE); + /* Now put some harmless values in registers and check them */ + __wss_outreg(WSSR_COUNT_LOW, 0xaa); + __wss_outreg(WSSR_COUNT_HIGH, 0x55); + return (__wss_inreg(WSSR_COUNT_LOW) == 0xaa) + && (__wss_inreg(WSSR_COUNT_HIGH) == 0x55); +} + +static boolean __wss_reset() +{ + int count; + + /* Disable output */ + wss_output(FALSE); + + /* Now select the test/initialization register */ + count = 10000; + while (inportb(WSS_ADDR) != WSSR_TEST_INIT) { + outportb(WSS_ADDR, WSSR_TEST_INIT); + if (!--count) + return FALSE; + } + + count = 10000; + while (inportb(WSS_DATA) & WSSM_CALIB_IN_PROGRESS) { + outportb(WSS_ADDR, WSSR_TEST_INIT); + if (!--count) + return FALSE; + } + + /* Enable playback IRQ */ + __wss_regbit_set(WSSR_PIN_CTRL, WSSM_IRQ_ENABLE); + __wss_outreg(WSSR_IRQ_STATUS, WSSM_PLAYBACK_IRQ); + + /* Clear IRQ status */ + outportb(WSS_STATUS, 0); + + return TRUE; +} + +static boolean __wss_setformat(unsigned char format) +{ + int count; + + outportb(WSS_ADDR, WSSM_MCE | WSSR_PLAY_FORMAT); + outportb(WSS_DATA, format); + inportb(WSS_DATA); /* ERRATA SHEETS ... */ + inportb(WSS_DATA); /* ERRATA SHEETS ... */ + + /* Wait end of syncronization ... */ + if (!__wss_wait()) + return FALSE; + + /* Turn off the ModeChangeEnable bit: do it until it works */ + count = 10000; + while (inportb(WSS_ADDR) != WSSR_PLAY_FORMAT) { + outportb(WSS_ADDR, WSSR_PLAY_FORMAT); + if (!--count) + return FALSE; + } + + return __wss_reset(); +} + +/**************************************************** WSS detection stuff *****/ + +static int __wss_irq_irqdetect(int irqno) +{ + unsigned char status = inportb(WSS_STATUS); + /* Clear IRQ status */ + outportb(WSS_STATUS, 0); + /* Reset transfer counter */ + __wss_outreg(WSSR_COUNT_LOW, 0); + __wss_outreg(WSSR_COUNT_HIGH, 0); + return (status & WSSM_INT); +} + +static boolean __wss_detect() +{ + /* First find the port number */ + if (!wss.port) { + static unsigned int wss_ports[] = + { 0x32c, 0x530, 0x604, 0xE80, 0xF40 }; + int i; + for (i = 0; i < 5; i++) { + wss.port = wss_ports[i]; + if (__wss_ping()) + break; + } + if (i < 0) { + wss.port = 0; + return FALSE; + } + } + + /* Now disable output */ + wss_output(FALSE); + + /* Detect the DMA channel */ + if (!wss.dma) { + static int __dma[] = { 0, 1, 3 }; + int i; + + /* Enable playback IRQ */ + __wss_regbit_set(WSSR_PIN_CTRL, WSSM_IRQ_ENABLE); + __wss_outreg(WSSR_IRQ_STATUS, WSSM_PLAYBACK_IRQ); + + /* Start a short DMA transfer and check if DMA count is zero */ + for (i = 0; i < 3; i++) { + unsigned int timer, status, freq = 44100; + + wss.dma = __dma[i]; + + dma_disable(wss.dma); + dma_set_mode(wss.dma, DMA_MODE_WRITE); + dma_clear_ff(wss.dma); + dma_set_count(wss.dma, 10); + dma_enable(wss.dma); + + /* Clear IRQ status */ + outportb(WSS_STATUS, 0); + + __wss_setformat(__wss_getrate(&freq)); + __wss_outreg(WSSR_COUNT_LOW, 1); + __wss_outreg(WSSR_COUNT_HIGH, 0); + /* Tell codec to start transfer */ + __wss_regbit_set(WSSR_IFACE_CTRL, WSSM_PLAYBACK_ENABLE); + + _farsetsel(_dos_ds); + timer = _farnspeekl(0x46c); + + while (_farnspeekl(0x46c) - timer <= 2) + if (dma_get_count(wss.dma) == 0) + break; + __wss_regbit_reset(WSSR_IFACE_CTRL, WSSM_PLAYBACK_ENABLE); + dma_disable(wss.dma); + + /* Now check if DMA transfer count is zero and an IRQ is pending */ + status = inportb(WSS_STATUS); + outportb(WSS_STATUS, 0); + if ((dma_get_count(wss.dma) == 0) && (status & WSSM_INT)) + break; + + wss.dma = 0; + } + + if (!wss.dma) + return FALSE; + } + + /* Now detect the IRQ number */ + if (!wss.irq) { + unsigned int i, irqmask, freq = 5510; + unsigned long timer, delta = 0x7fffffff; + + /* IRQ can be one of 2,3,5,7,10 */ + irq_detect_start(0x04ac, __wss_irq_irqdetect); + + dma_disable(wss.dma); + dma_set_mode(wss.dma, DMA_MODE_WRITE | DMA_MODE_AUTOINIT); + dma_clear_ff(wss.dma); + dma_set_count(wss.dma, 1); + dma_enable(wss.dma); + + __wss_setformat(__wss_getrate(&freq)); + + /* Clear IRQ status */ + outportb(WSS_STATUS, 0); + + __wss_outreg(WSSR_COUNT_LOW, 0); + __wss_outreg(WSSR_COUNT_HIGH, 0); + + /* Prepare timeout counter */ + _farsetsel(_dos_ds); + timer = _farnspeekl(0x46c); + while (timer == _farnspeekl(0x46c)); + timer = _farnspeekl(0x46c); + + /* Reset all IRQ counters */ + irq_detect_clear(); + + /* Tell codec to start transfer */ + __wss_regbit_set(WSSR_IFACE_CTRL, WSSM_PLAYBACK_ENABLE); + + /* Now wait 1/18 seconds */ + while (timer == _farnspeekl(0x46c)); + __wss_regbit_reset(WSSR_IFACE_CTRL, WSSM_PLAYBACK_ENABLE); + dma_disable(wss.dma); + + /* Given frequency 5510Hz, a buffer size of 1 byte and a time interval + of 1/18.2 second, we should have received about 302 interrupts */ + for (i = 2; i <= 10; i++) { + int count = abs(302 - irq_detect_get(i, &irqmask)); + if (count < delta) + wss.irq = i, delta = count; + } + if (delta > 150) + wss.irq = 0; + + irq_detect_end(); + if (!wss.irq) + return FALSE; + } + + return TRUE; +} + +/*************************************************** High-level interface *****/ + +/* Detect whenever WSS is present and fill "wss" structure */ +boolean wss_detect() +{ + char *env; + + /* Try to find the port and DMA from environment */ + env = getenv("WSS"); + + while (env && *env) { + /* Skip whitespace */ + while ((*env == ' ') || (*env == '\t')) + env++; + if (!*env) + break; + + switch (*env++) { + case 'A': + case 'a': + if (!wss.port) + wss.port = strtol(env, &env, 16); + break; + case 'I': + case 'i': + if (!wss.irq) + wss.irq = strtol(env, &env, 10); + break; + case 'D': + case 'd': + if (!wss.dma) + wss.dma = strtol(env, &env, 10); + break; + default: + /* Skip other values */ + while (*env && (*env != ' ') && (*env != '\t')) + env++; + break; + } + } + + /* Try to fill the gaps in wss hardware parameters */ + __wss_detect(); + + if (!wss.port || !wss.irq || !wss.dma) + return FALSE; + + if (!__wss_ping()) + return FALSE; + + if (!__wss_reset()) + return FALSE; + + wss.ok = 1; + return TRUE; +} + +/* Reset WSS */ +void wss_reset() +{ + wss_stop_dma(); + __wss_reset(); +} + +/* Open WSS for usage */ +boolean wss_open() +{ + __dpmi_meminfo struct_info; + + if (!wss.ok) + if (!wss_detect()) + return FALSE; + + if (wss.open) + return FALSE; + + /* Now lock the wss structure in memory */ + struct_info.address = __djgpp_base_address + (unsigned long)&wss; + struct_info.size = sizeof(wss); + if (__dpmi_lock_linear_region(&struct_info)) + return FALSE; + + /* Hook the WSS IRQ */ + wss.irq_handle = + irq_hook(wss.irq, wss_irq, (long)wss_irq_end - (long)wss_irq); + if (!wss.irq_handle) { + __dpmi_unlock_linear_region(&struct_info); + return FALSE; + } + + /* Enable the interrupt */ + irq_enable(wss.irq_handle); + if (wss.irq > 7) + _irq_enable(2); + + wss.open++; + + return TRUE; +} + +/* Finish working with WSS */ +boolean wss_close() +{ + __dpmi_meminfo struct_info; + if (!wss.open) + return FALSE; + + wss.open--; + + /* Stop/free DMA buffer */ + wss_stop_dma(); + + /* Unhook IRQ */ + irq_unhook(wss.irq_handle); + wss.irq_handle = NULL; + + /* Unlock the wss structure */ + struct_info.address = __djgpp_base_address + (unsigned long)&wss; + struct_info.size = sizeof(wss); + __dpmi_unlock_linear_region(&struct_info); + + return TRUE; +} + +/* Adjust frequency rate to nearest WSS available */ +unsigned int wss_adjust_freq(unsigned int freq) +{ + __wss_getrate(&freq); + return freq; +} + +/* Enable/disable speaker output */ +/* Start playing from DMA buffer in either 8/16 bit mono/stereo */ +boolean wss_start_dma(unsigned char mode, unsigned int freq) +{ + int dmabuffsize; + unsigned char format; + + /* Stop DMA transfer if it is enabled */ + wss_stop_dma(); + + /* Sanity check: we support only 8-bit unsigned and 16-bit signed formats */ + if (((mode & WSSMODE_16BITS) && !(mode & WSSMODE_SIGNED)) + || (!(mode & WSSMODE_16BITS) && (mode & WSSMODE_SIGNED))) + return FALSE; + + /* Find the nearest frequency divisor (rate) */ + format = __wss_getrate(&freq); + wss.mode = mode; + + /* Get a DMA buffer enough for a 1sec interval... 4K <= dmasize <= 32K */ + dmabuffsize = freq; + if (mode & WSSMODE_STEREO) + dmabuffsize *= 2; + if (mode & WSSMODE_16BITS) + dmabuffsize *= 2; + dmabuffsize >>= 2; + if (dmabuffsize < 4096) + dmabuffsize = 4096; + if (dmabuffsize > 32768) + dmabuffsize = 32768; + dmabuffsize = (dmabuffsize + 255) & 0xffffff00; + + wss.dma_buff = dma_allocate(wss.dma, dmabuffsize); + if (!wss.dma_buff) + return FALSE; + + /* Fill DMA buffer with silence */ + dmabuffsize = wss.dma_buff->size; + if (mode & WSSMODE_SIGNED) + memset(wss.dma_buff->linear, 0, dmabuffsize); + else + memset(wss.dma_buff->linear, 0x80, dmabuffsize); + + /* Check data size and build a WSSR_PLAY_FORMAT value accordingly */ + wss.samples = dmabuffsize; + if (mode & WSSMODE_16BITS) { + wss.samples >>= 1; + format |= WSSM_16BITS; + } + + if (mode & WSSMODE_STEREO) { + wss.samples >>= 1; + format |= WSSM_STEREO; + } + + if (!__wss_setformat(format)) { + wss_stop_dma(); + return FALSE; + } + + /* Prime DMA for transfer */ + dma_start(wss.dma_buff, dmabuffsize, DMA_MODE_WRITE | DMA_MODE_AUTOINIT); + + /* Tell codec how many samples to transfer */ + wss.samples = (wss.samples >> 1) - 1; + __wss_outreg(WSSR_COUNT_LOW, wss.samples & 0xff); + __wss_outreg(WSSR_COUNT_HIGH, wss.samples >> 8); + + /* Tell codec to start transfer */ + __wss_regbit_set(WSSR_IFACE_CTRL, WSSM_PLAYBACK_ENABLE); + + return TRUE; +} + +/* Stop playing from DMA buffer */ +void wss_stop_dma() +{ + if (!wss.dma_buff) + return; + + __wss_regbit_reset(WSSR_IFACE_CTRL, WSSM_PLAYBACK_ENABLE); + dma_disable(wss.dma); + dma_free(wss.dma_buff); + wss.dma_buff = NULL; +} + +/* Query current position/total size of the DMA buffer */ +void wss_query_dma(unsigned int *dma_size, unsigned int *dma_pos) +{ + unsigned int dma_left; + *dma_size = wss.dma_buff->size; + /* It can happen we try to read DMA count when HI/LO bytes will be + inconsistent */ + for (;;) { + unsigned int dma_left_test; + dma_clear_ff(wss.dma); + dma_left_test = dma_get_count(wss.dma); + dma_left = dma_get_count(wss.dma); + if ((dma_left >= dma_left_test) && (dma_left - dma_left_test < 10)) + break; + } + *dma_pos = *dma_size - dma_left; +} + +void wss_output(boolean enable) +{ + if (enable) + wss.curlevel = wss.level; + else + wss.curlevel = 0x3f; + + __wss_outreg(WSSR_MASTER_L, wss.curlevel); + __wss_outreg(WSSR_MASTER_R, wss.curlevel); +} + +void wss_level(int level) +{ + if (level < 0) + level = 0; + if (level > 63) + level = 63; + wss.curlevel = wss.level = level ^ 63; + + __wss_outreg(WSSR_MASTER_L, wss.curlevel); + __wss_outreg(WSSR_MASTER_R, wss.curlevel); +} + +#endif /* DRV_WSS */ + +/* ex:set ts=4: */