📄 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 + -