add mikmod source
[dosdemo] / libs / mikmod / drivers / dos / doswss.c
diff --git a/libs/mikmod/drivers/dos/doswss.c b/libs/mikmod/drivers/dos/doswss.c
new file mode 100644 (file)
index 0000000..41cdf38
--- /dev/null
@@ -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 <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: */