mirror of
https://github.com/kbeckmann/game-and-watch-retro-go.git
synced 2025-12-17 19:16:02 +01:00
578 lines
13 KiB
C
578 lines
13 KiB
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.
|
|
*/
|
|
|
|
/*==============================================================================
|
|
|
|
Windows Sound System I/O routines (CS42XX, ESS18XX, GUS+DaughterBoard etc)
|
|
Written by Andrew Zabolotny <bit@eltech.ru>
|
|
|
|
==============================================================================*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#ifdef DRV_WSS
|
|
|
|
#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);
|
|
}
|
|
|
|
#endif /* DRV_WSS */
|
|
|
|
/* ex:set ts=4: */
|