📄 doswss.c
字号:
/* 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.*//*============================================================================== $Id: doswss.c,v 1.1 2004/02/01 02:01:17 raph Exp $ Windows Sound System I/O routines (CS42XX, ESS18XX, GUS+DaughterBoard etc)==============================================================================*//* Written by Andrew Zabolotny <bit@eltech.ru>*/#include <stdlib.h>#include <dpmi.h>#include <go32.h>#include <dos.h>#include <sys/nearptr.h>#include <sys/farptr.h>#include <string.h>#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);}/* ex:set ts=4: */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -