ftp.nice.ch/pub/next/tools/emulators/vice.0.15.0.NeXT.sd.tgz#/vice-0.15.0/src/arch/msdos/vicesb.c

This is vicesb.c in view mode; [Download] [Up]

/*         ______   ___    ___
 *        /\  _  \ /\_ \  /\_ \
 *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
 *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
 *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
 *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
 *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
 *                                           /\____/
 *                                           \_/__/
 *      By Shawn Hargreaves,
 *      1 Salisbury Road,
 *      Market Drayton,
 *      Shropshire,
 *      England, TF9 1AJ.
 *
 *      Soundblaster driver: supports DMA driven sample playback.
 *
 *      Adapted for use with VICE by Ettore Perazzoli (ettore@comm2000.it).
 *
 *      This file is now part of VICE, the Versatile Commodore Emulator.
 *      See README for copyright notice.
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU 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 General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 *      02111-1307  USA.
 *
 */

/* Warning: This might not work with versions of Allegro other than 3.0.  */

#ifndef DJGPP
#error This file should only be used by the djgpp version of Allegro
#endif

#include <stdlib.h>
#include <stdio.h>
#include <dos.h>
#include <go32.h>
#include <dpmi.h>
#include <limits.h>
#include <sys/farptr.h>

#include <allegro.h>

/* Extern declarations for internal Allegro stuff.  */
extern int _sb_port;
extern int _sb_dma;
extern int _sb_irq;
extern int _dma_allocate_mem(int bytes, int *sel, unsigned long *phys);
extern void _dma_start(int channel, unsigned long addr, int size, int auto_init);
extern void _dma_stop(int channel);
extern unsigned long _dma_todo(int channel);
extern int _install_irq(int num, int (*handler)());
extern void _remove_irq(int num);

static char vicesb_desc[80] = "not initialised";

static int vicesb_in_use = FALSE;	/* is SB being used? */
static int vicesb_stereo = FALSE;	/* in stereo mode? */
static int vicesb_16bit = FALSE;	/* in 16 bit mode? */
static int vicesb_int = -1;		/* interrupt vector */
static int vicesb_dsp_ver = -1;	/* SB DSP version */
static int vicesb_hw_dsp_ver = -1;	/* as reported by autodetect */
static int vicesb_dma_size = -1;	/* size of dma transfer in bytes */
static int vicesb_dma_count = 0;	/* need to resync with dma? */
static int vicesb_max_freq = 0;         /* maximum frequency */
static int vicesb_freq = 0;             /* sample rate */
static volatile int vicesb_semaphore = FALSE;	/* reentrant interrupt? */

static void (*vicesb_interrupt_function)(unsigned long buf);

static int vicesb_sel[2];		/* selectors for the buffers */
static unsigned long vicesb_buf[2];	/* pointers to the two buffers */
static int vicesb_bufnum = 0;	/* the one currently in use */

static unsigned char vicesb_default_pic1;	/* PIC mask flags to restore */
static unsigned char vicesb_default_pic2;

static int vicesb_master_vol;	/* stored mixer settings */
static int vicesb_digi_vol;

static void vicesb_lock_mem();



/* vicesb_read_dsp:
 *  Reads a byte from the VICESB DSP chip. Returns -1 if it times out.
 */
static inline volatile int vicesb_read_dsp()
{
    int x;

    for (x = 0; x < 0xffff; x++)
	if (inportb(0x0E + _sb_port) & 0x80)
	    return inportb(0x0A + _sb_port);

    return -1;
}



/* vicesb_write_dsp:
 *  Writes a byte to the SB DSP chip. Returns -1 if it times out.
 */
static inline volatile int vicesb_write_dsp(unsigned char byte)
{
    int x;

    for (x = 0; x < 0xffff; x++) {
	if (!(inportb(0x0C + _sb_port) & 0x80)) {
	    outportb(0x0C + _sb_port, byte);
	    return 0;
	}
    }
    return -1;
}



/* vicesb_voice:
 *  Turns the SB speaker on or off.
 */
static void vicesb_voice(int state)
{
    if (state) {
	vicesb_write_dsp(0xD1);

	if (vicesb_hw_dsp_ver >= 0x300) {	/* set up the mixer */

	    outportb(_sb_port + 4, 0x22);	/* store master volume */
	    vicesb_master_vol = inportb(_sb_port + 5);

	    outportb(_sb_port + 4, 4);	/* store DAC level */
	    vicesb_digi_vol = inportb(_sb_port + 5);
	}
    } else {
	vicesb_write_dsp(0xD3);

	if (vicesb_hw_dsp_ver >= 0x300) {	/* reset previous mixer settings */

	    outportb(_sb_port + 4, 0x22);	/* restore master volume */
	    outportb(_sb_port + 5, vicesb_master_vol);

	    outportb(_sb_port + 4, 4);	/* restore DAC level */
	    outportb(_sb_port + 5, vicesb_digi_vol);
	}
    }
}



/* _vicesb_set_mixer:
 *  Alters the SB-Pro hardware mixer.
 */
int vicesb_set_volume(int volume)
{
    if (vicesb_hw_dsp_ver < 0x300)
	return -1;

    if (volume >= 0) {	/* set DAC level */
	outportb(_sb_port + 4, 4);
	outportb(_sb_port + 5, (volume & 0xF0) | (volume >> 4));
    }
    return 0;
}


/* vicesb_stereo_mode:
 *  Enables or disables stereo output for SB-Pro.
 */
static void vicesb_stereo_mode(int enable)
{
    outportb(_sb_port + 0x04, 0x0E);
    outportb(_sb_port + 0x05, (enable ? 2 : 0));
}


/* vicesb_set_sample_rate:
 *  The parameter is the rate to set in Hz (samples per second).
 */
static void vicesb_set_sample_rate(unsigned int rate)
{
    if (vicesb_16bit) {
	vicesb_write_dsp(0x41);
	vicesb_write_dsp(rate >> 8);
	vicesb_write_dsp(rate & 0xff);
    } else {
	if (vicesb_stereo)
	    rate *= 2;

	vicesb_write_dsp(0x40);
	vicesb_write_dsp((unsigned char) (256 - 1000000 / rate));
    }
}



/* vicesb_reset_dsp:
 *  Resets the SB DSP chip, returning -1 on error.
 */
static int vicesb_reset_dsp()
{
    int x;

    outportb(0x06 + _sb_port, 1);

    for (x = 0; x < 8; x++)
	inportb(0x06 + _sb_port);

    outportb(0x06 + _sb_port, 0);

    if (vicesb_read_dsp() != 0xAA)
	return -1;

    return 0;
}



/* vicesb_read_dsp_version:
 *  Reads the version number of the SB DSP chip, returning -1 on error.
 */
static int vicesb_read_dsp_version(void)
{
    int x, y;

    if (vicesb_hw_dsp_ver > 0)
	return vicesb_hw_dsp_ver;

    if (_sb_port <= 0)
	_sb_port = 0x220;

    if (vicesb_reset_dsp() != 0)
	vicesb_hw_dsp_ver = -1;
    else {
	vicesb_write_dsp(0xE1);
	x = vicesb_read_dsp();
	y = vicesb_read_dsp();
	vicesb_hw_dsp_ver = ((x << 8) | y);
    }

    return vicesb_hw_dsp_ver;
}



/* vicesb_play_buffer:
 *  Starts a dma transfer of size bytes. On cards capable of it, the
 *  transfer will use auto-initialised dma, so there is no need to call
 *  this routine more than once. On older cards it must be called from
 *  the end-of-buffer handler to switch to the new buffer.
 */
static void vicesb_play_buffer(int size)
{
    if (vicesb_dsp_ver <= 0x200) {	/* 8 bit single-shot */
	vicesb_write_dsp(0x14);
	vicesb_write_dsp((size - 1) & 0xFF);
	vicesb_write_dsp((size - 1) >> 8);
    } else if (vicesb_dsp_ver < 0x400) {	/* 8 bit auto-initialised */
	vicesb_write_dsp(0x48);
	vicesb_write_dsp((size - 1) & 0xff);
	vicesb_write_dsp((size - 1) >> 8);
	vicesb_write_dsp(0x90);
    } else {			/* 16 bit */
	size /= 2;
	vicesb_write_dsp(0xB6);
        vicesb_write_dsp(0x10);
	/* vicesb_write_dsp(0x20); */
	vicesb_write_dsp((size - 1) & 0xFF);
	vicesb_write_dsp((size - 1) >> 8);
    }
}

static END_OF_FUNCTION(vicesb_play_buffer);



/* vicesb_interrupt:
 *  The SB end-of-buffer interrupt handler. Swaps to the other buffer
 *  if the card doesn't have auto-initialised dma, and then refills the
 *  buffer that just finished playing.
 */
static int vicesb_interrupt(void)
{
    if (vicesb_dsp_ver <= 0x200) {	/* not auto-initialised */
	_dma_start(_sb_dma, vicesb_buf[1 - vicesb_bufnum],
                   vicesb_dma_size, FALSE);
	vicesb_play_buffer(vicesb_dma_size);
    } else {			/* poll dma position */
	vicesb_dma_count++;
	if (vicesb_dma_count > 16) {
	    vicesb_bufnum = ((_dma_todo(_sb_dma) > (unsigned) vicesb_dma_size)
                             ? 1 : 0);
	    vicesb_dma_count = 0;
	}
    }

    if (!vicesb_semaphore) {
	vicesb_semaphore = TRUE;

        asm volatile ("sti");
        vicesb_interrupt_function((unsigned long) vicesb_buf[vicesb_bufnum]);
        asm volatile ("cli");

	vicesb_semaphore = FALSE;
    }
    vicesb_bufnum = 1 - vicesb_bufnum;

    if (vicesb_16bit)		/* acknowlege SB */
	inportb(_sb_port + 0x0F);
    else
	inportb(_sb_port + 0x0E);

    outportb(0x20, 0x20);	/* acknowledge interrupt */
    outportb(0xA0, 0x20);

    return 0;
}

static END_OF_FUNCTION(vicesb_interrupt);



/* vicesb_detect:
 *  SB detection routine. Uses the BLASTER environment variable,
 *  or 'sensible' guesses if that doesn't exist.
 */
int vicesb_detect(int *is_16bit)
{
    char *blaster = getenv("BLASTER");
    char *msg;
    int dma8 = 1;
    int dma16 = 5;
    int default_freq;

    /* what breed of SB are we looking for? */
    switch (digi_card) {

      case DIGI_SB10:
        vicesb_dsp_ver = 0x100;
        break;

      case DIGI_SB15:
        vicesb_dsp_ver = 0x200;
        break;

      case DIGI_SB20:
        vicesb_dsp_ver = 0x201;
        break;

      case DIGI_SBPRO:
        vicesb_dsp_ver = 0x300;
        break;

      case DIGI_SB16:
        vicesb_dsp_ver = 0x400;
        break;

      default:
        vicesb_dsp_ver = -1;
        break;
    }

    /* parse BLASTER env */
    if (blaster) {
	while (*blaster) {
	    while ((*blaster == ' ') || (*blaster == '\t'))
		blaster++;

	    if (*blaster) {
		switch (*blaster) {
		  case 'a':
		  case 'A':
                    if (_sb_port < 0)
                        _sb_port = strtol(blaster + 1, NULL, 16);
                    break;

		  case 'i':
		  case 'I':
                    if (_sb_irq < 0)
                        _sb_irq = strtol(blaster + 1, NULL, 10);
                    break;

		  case 'd':
		  case 'D':
                    dma8 = strtol(blaster + 1, NULL, 10);
                    break;

		  case 'h':
		  case 'H':
                    dma16 = strtol(blaster + 1, NULL, 10);
                    break;
		}

		while ((*blaster) && (*blaster != ' ') && (*blaster != '\t'))
		    blaster++;
	    }
	}
    }
    if (_sb_port < 0)
	_sb_port = 0x220;

    if (_sb_irq < 0)
	/* _sb_irq = 7; */
	_sb_irq = 5;

    /* make sure we got a good port address */
    if (vicesb_reset_dsp() != 0) {
	static int bases[] = {0x210, 0x220, 0x230, 0x240, 0x250, 0x260, 0};
	int i;

	for (i = 0; bases[i]; i++) {
	    _sb_port = bases[i];
	    if (vicesb_reset_dsp() == 0)
		break;
	}
    }
    /* check if the card really exists */
    vicesb_read_dsp_version();
    if (vicesb_hw_dsp_ver < 0) {
	strcpy(allegro_error, "Sound Blaster not found");
	return FALSE;
    }
    if (vicesb_dsp_ver < 0)
	vicesb_dsp_ver = vicesb_hw_dsp_ver;
    else {
	if (vicesb_dsp_ver > vicesb_hw_dsp_ver) {
	    vicesb_hw_dsp_ver = vicesb_dsp_ver = -1;
	    strcpy(allegro_error, "Older SB version detected");
	    return FALSE;
	}
    }

    /* figure out the hardware interrupt number */
    if (_sb_irq > 7)
	vicesb_int = _sb_irq + 104;
    else
	vicesb_int = _sb_irq + 8;

    /* what breed of SB? */
    if (vicesb_dsp_ver >= 0x400) {
	msg = "SB 16";
	vicesb_max_freq = 45454;
    } else if (vicesb_dsp_ver >= 0x300) {
	msg = "SB Pro";
	vicesb_max_freq = 22727;
    } else if (vicesb_dsp_ver >= 0x201) {
	msg = "SB 2.0";
	vicesb_max_freq = 45454;
    } else if (vicesb_dsp_ver >= 0x200) {
	msg = "SB 1.5";
	vicesb_max_freq = 16129;
    } else {
	msg = "SB 1.0";
	vicesb_max_freq = 16129;
    }

    /* can we handle 16 bit playback? */
    if (vicesb_dsp_ver >= 0x400) {
	if (_sb_dma < 0)
	    _sb_dma = dma16;
	vicesb_16bit = TRUE;
    } else {
	if (_sb_dma < 0)
	    _sb_dma = dma8;
	vicesb_16bit = FALSE;
    }

#if 0
    /* can we handle stereo? */
    if (vicesb_dsp_ver >= 0x300) {
	vicesb_stereo = TRUE;
	vicesb_dma_size <<= 1;
    } else
	vicesb_stereo = FALSE;
#else
    vicesb_stereo = FALSE;
#endif

    /* set up the card description */
    sprintf(vicesb_desc, "%s on port %X, using IRQ %d and DMA channel %d",
	    msg, _sb_port, _sb_irq, _sb_dma);
    puts(vicesb_desc);

    *is_16bit = vicesb_16bit;
    return TRUE;
}



/* vicesb_init:
 *  SB init routine: returns zero on success, -1 on failure.
 */
int vicesb_init(int *frequency, int *dma_size,
                void (*interrupt_func)(unsigned long))
{
    /* XXX: Do we really need this?  */
    if ((digi_card == DIGI_SB) || (digi_card == DIGI_AUTODETECT)) {
	if (vicesb_dsp_ver <= 0x100)
	    digi_card = DIGI_SB10;
	else if (vicesb_dsp_ver <= 0x200)
	    digi_card = DIGI_SB15;
	else if (vicesb_dsp_ver < 0x300)
	    digi_card = DIGI_SB20;
	else if (vicesb_dsp_ver < 0x400)
	    digi_card = DIGI_SBPRO;
	else
	    digi_card = DIGI_SB16;
    }

    if (*frequency > vicesb_max_freq)
        *frequency = vicesb_max_freq;
    vicesb_freq = *frequency;

    /* Check DMA size.  */
    {
        int min_factor;

        printf("%s(): Fixing DMA size; requested %d\n",
               __FUNCTION__, *dma_size);
        if (vicesb_dsp_ver <= 0x200)
            min_factor = 4;
        else
            min_factor = 1;
        if (vicesb_freq < 20000) {
            if (*dma_size < 128 * min_factor)
                *dma_size = 128 * min_factor;
        } else if (vicesb_freq < 40000) {
            if (*dma_size < 256 * min_factor)
                *dma_size = 256 * min_factor;
        } else {
            if (*dma_size < 512 * min_factor)
                *dma_size = 512 * min_factor;
        }
        vicesb_dma_size = *dma_size;
        printf("%s(): Fixing DMA size; given %d\n", __FUNCTION__, *dma_size);
    }

    if (vicesb_dsp_ver <= 0x200) {	/* two conventional mem buffers */
	if ((_dma_allocate_mem(vicesb_dma_size, &vicesb_sel[0], &vicesb_buf[0]) != 0) ||
	    (_dma_allocate_mem(vicesb_dma_size, &vicesb_sel[1], &vicesb_buf[1]) != 0))
	    return -1;
    } else {			/* auto-init dma, one big buffer */
	if (_dma_allocate_mem(vicesb_dma_size * 2, &vicesb_sel[0], &vicesb_buf[0]) != 0)
	    return -1;

	vicesb_sel[1] = vicesb_sel[0];
	vicesb_buf[1] = vicesb_buf[0] + vicesb_dma_size;
    }

    {
        /* Clear buffers.  */
        int i;

        _farsetsel(_dos_ds);

        if (vicesb_16bit) {
            for (i = 0; i < vicesb_dma_size; i += 2) {
                _farnspokew(vicesb_buf[0] + i, 0x0);
                _farnspokew(vicesb_buf[1] + i, 0x0);
            }
        } else {
            for (i = 0; i < vicesb_dma_size; i++) {
                _farnspokeb(vicesb_buf[0] + i, 0x80);
                _farnspokeb(vicesb_buf[1] + i, 0x80);
            }
        }
    }

    vicesb_lock_mem();
    vicesb_bufnum = 0;

    vicesb_default_pic1 = inportb(0x21);
    vicesb_default_pic2 = inportb(0xA1);

    if (_sb_irq > 7) {		/* enable irq2 and PIC-2 irq */
	outportb(0x21, vicesb_default_pic1 & 0xFB);
	outportb(0xA1, vicesb_default_pic2 & (~(1 << (_sb_irq - 8))));
    } else			/* enable PIC-1 irq */
	outportb(0x21, vicesb_default_pic1 & (~(1 << _sb_irq)));

    if (_install_irq(vicesb_int, vicesb_interrupt) < 0) {
        fprintf(stderr, "Cannot install IRQ %d handler for SB!\n", vicesb_int);
        return FALSE;
    }

    vicesb_voice(1);
    vicesb_set_sample_rate(*frequency);

#if 0
    if ((vicesb_hw_dsp_ver >= 0x300) && (vicesb_dsp_ver < 0x400))
	vicesb_stereo_mode(vicesb_stereo);
#else
    vicesb_stereo_mode(0);
#endif

    if (vicesb_dsp_ver <= 0x200)
	_dma_start(_sb_dma, vicesb_buf[0], vicesb_dma_size, FALSE);
    else
	_dma_start(_sb_dma, vicesb_buf[0], vicesb_dma_size * 2, TRUE);

    vicesb_play_buffer(vicesb_dma_size);

    vicesb_in_use = TRUE;

    vicesb_interrupt_function = interrupt_func;

    return TRUE;
}



/* vicesb_close:
 *  SB driver cleanup routine, removes ints, stops dma, frees buffers, etc.
 */
void vicesb_close(void)
{
    /* halt sound output */
    vicesb_voice(0);

    /* stop dma transfer */
    _dma_stop(_sb_dma);

    if (vicesb_dsp_ver <= 0x0200)
	vicesb_write_dsp(0xD0);

    vicesb_reset_dsp();

    /* restore interrupts */
    _remove_irq(vicesb_int);

    /* reset PIC channels */
    outportb(0x21, vicesb_default_pic1);
    outportb(0xA1, vicesb_default_pic2);

    /* free conventional memory buffer */
    __dpmi_free_dos_memory(vicesb_sel[0]);
    if (vicesb_sel[1] != vicesb_sel[0])
	__dpmi_free_dos_memory(vicesb_sel[1]);

    vicesb_in_use = FALSE;
}


/* vicesb_lock_mem:
 *  Locks all the memory touched by parts of the SB code that are executed
 *  in an interrupt context.
 */
static void vicesb_lock_mem()
{
    LOCK_VARIABLE(digi_sb);
    LOCK_VARIABLE(midi_sb_out);
    LOCK_VARIABLE(_sb_port);
    LOCK_VARIABLE(_sb_dma);
    LOCK_VARIABLE(_sb_irq);
    LOCK_VARIABLE(vicesb_int);
    LOCK_VARIABLE(vicesb_in_use);
    LOCK_VARIABLE(vicesb_dsp_ver);
    LOCK_VARIABLE(vicesb_hw_dsp_ver);
    LOCK_VARIABLE(vicesb_dma_size);
    LOCK_VARIABLE(vicesb_freq);
    LOCK_VARIABLE(vicesb_sel);
    LOCK_VARIABLE(vicesb_buf);
    LOCK_VARIABLE(vicesb_bufnum);
    LOCK_VARIABLE(vicesb_default_pic1);
    LOCK_VARIABLE(vicesb_default_pic2);
    LOCK_VARIABLE(vicesb_dma_count);
    LOCK_VARIABLE(vicesb_semaphore);
    LOCK_FUNCTION(vicesb_play_buffer);
    LOCK_FUNCTION(vicesb_interrupt);
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.