--- /dev/null
+/* 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: */