Sound Blaster Unix driver 1/2

Brian Smith brians at eecs.cs.pdx.edu
Fri Jun 7 01:46:28 AEST 1991


Sound Blaster driver for System V 3.2, for the 386.  The Sound Blaster
is an audio and music card for the PC.

Brian

/---------------------------------------|------------------------------------\
| #include <std_disclaim.h>             | Inet: brians at cs.pdx.edu            |
| #include <human_errors.h>             | UUCP: tektronix!pdxgate!brians     |
|---------------------------------------|------------------------------------|
| Behold the warranty.. the bold print giveth and the fine print taketh away.|
\----------------------------------------------------------------------------/

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	INSTALL
#	LICENSE
#	Makefile
#	Master
#	Name
#	Node
#	README
#	Space.c
#	System
#	sb.c
#	sb.h
# This archive created: Thu Jun  6 08:33:21 1991
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'INSTALL'
then
	echo shar: "will not over-write existing file 'INSTALL'"
else
cat << \SHAR_EOF > 'INSTALL'

                         Sound Blaster(tm) Driver
                       (Copyright 1991, Brian Smith)


INSTALLING THE DRIVER

1. Run make in the directory with the driver code (sb.c, sb.h, etc...).
2. su to root, if not already so, and run /etc/insdriver.
    a. Answer . for query asking for the directory of the driver
        files.
    b. Answer sb for the query asking for the driver name.
    c. Accept the default of /etc/conf for the configuration directory
        by hitting return.
    d. Enter any description you like.  Maybe: "Sound Blaster Driver".
    e. Answer n to the configuration question.
3. As root still, run /etc/kconfig.
4. Make sure the driver is configured into the kernel in the
    configuration menu.
5. Build a new kernel.
6. Install the new kernel.  This reboots the machine.


INSTALLING THE USER PROGRAMS

1. Edit the Makefile in the apps directory.
2. Run make.
3. Run make install.
4. Manually install the man files however you like.  The *.1 files are
nroff source, while the *.doc files are nroff'ed for those of use
without.
SHAR_EOF
fi
if test -f 'LICENSE'
then
	echo shar: "will not over-write existing file 'LICENSE'"
else
cat << \SHAR_EOF > 'LICENSE'

                 SoundBlaster(tm) Driver General Public License
                       (Copyright 1991, Brian Smith)

  Most licenses are restrictive in one way or another, the GNU license
as well.  The whole purpose of this excercise was to benefit the computer
community as a whole, including those of us unprincipled enough to want
to make money on software. :-)

  For my protection, it must be stated at the outset that there is NO
stated or implied warranty for the driver nor is there any stated or
implied warranty for any of the associated user programs nor for the
documentation.  The mistakes are mine, the responsibility is yours (for
using the package).

COPYING ANDUSE POLICIES

1. You may use this unmodified package on any and all computers which
you wish to install this package.

2. You may copy and distribute verbatim copies of this SoundBlaster driver
and associated user software as you recieved them.  Distribute them however
you like, as long as they are distributed with the original copyright
and (lack of) warranty messages intact.

3. You may distribute modified copies of this driver in source forms if
and only if my copyright and (lack of) is intact in addition to an
appended statement of your modification.  I must not be held responsible
for any modifications made by other persons.

4. Any persons or companies or persons who wish to make a commercial
distribution (binaries only) using this package or any portion of this
package must contact me first.  I will most likely license the person or
company for little or no charge.  Mostly, I'm a snoop: if you are using
my software let me know. :-)

As I am not a lawyer, so, there are likely legal holes the size of
small continents, but please try to keep the spirit of this license.  The
intent of this was for betterment of the community.  Any changes should
be sent to me for integration (either e-mail or snail-mail).

CURRENT ADDRESSES
    e-mail:     brians at cs.pdx.edu
                ...!uunet!tektronix!pdxgate!eecs!brians
    US Mail:    Brian Smith
                18700 SW Cascadia Court
                Aloha, OR 97007
SHAR_EOF
fi
if test -f 'Makefile'
then
	echo shar: "will not over-write existing file 'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
# 
#                       Sound Blaster(tm) Driver
#                     (Copyright 1991, Brian Smith)
#

SHELL=/bin/sh
INCLSYS = /usr/include/sys
LKDRVRDIR = /etc/conf/pack.d/sb
LKSCONFDIR = /etc/conf/sdevice.d
LKNCONFDIR = /etc/conf/node.d
LKICONFDIR = /etc/conf/init.d
LKKCONFDIR = /etc/conf/kconfig.d
DRVRNAME = Driver.o
CONFNAME = sb

CC=cc
CFLAGS = -O -DINKERNEL

OBJS = Driver.o

all: Driver.o $(INCLSYS)/sb.h

Driver.o: sb.c $(INCLSYS)/sb.h Makefile
	$(CC) $(CFLAGS) -c sb.c
	mv sb.o Driver.o

install: $(LKDRVRDIR) $(LKDRVRDIR)/$(DRVRNAME) $(LKDRVRDIR)/space.c \
	$(INCLSYS)/sb.h

# OLD install, now done manually
# install: Driver.o $(INCLSYS)/sb.h Master System Space.c Node
# 	/etc/insdriver
# 	/etc/kconfig

$(LKDRVRDIR):
	-mkdir $(LKDRVRDIR) 2> /dev/null
	chmod 755 $(LKDRVRDIR)

$(INCLSYS)/sb.h: sb.h
	@echo /bin/cp sb.h $(INCLSYS)/sb.h
	/bin/cp sb.h $(INCLSYS)/sb.h

$(LKDRVRDIR)/Driver.o: Driver.o
	cp Driver.o $(LKDRVRDIR)/$(DRVRNAME)
	chmod 644 $(LKDRVRDIR)/$(DRVRNAME)

$(LKDRVRDIR)/space.c: Space.c
	cp Space.c $(LKDRVRDIR)/space.c
	chmod 644 $(LKDRVRDIR)/space.c

clean:
	rm -f Driver.o
SHAR_EOF
fi
if test -f 'Master'
then
	echo shar: "will not over-write existing file 'Master'"
else
cat << \SHAR_EOF > 'Master'
sb ocrwiI iHc sb 0 0 1 3 1
SHAR_EOF
fi
if test -f 'Name'
then
	echo shar: "will not over-write existing file 'Name'"
else
cat << \SHAR_EOF > 'Name'
SoundBlaster Driver
SHAR_EOF
fi
if test -f 'Node'
then
	echo shar: "will not over-write existing file 'Node'"
else
cat << \SHAR_EOF > 'Node'
sb  sbcms  c  0
sb  sbfm   c  1
sb  sbdsp  c  2
SHAR_EOF
fi
if test -f 'README'
then
	echo shar: "will not over-write existing file 'README'"
else
cat << \SHAR_EOF > 'README'

                         Sound Blaster(tm) Driver
                       (Copyright 1991, Brian Smith)


REQUIREMENTS

This driver and the associated user programs require a 386/486 system
running System V 3.2 Unix.  It has been tested on ISC Unix versions
2.0.2 through 2.2.1.  I believe it will work correctly on Esix, Intel,
and AT&T Unix.  SCO is a mystery to me (as well as many others).  I do
not believe it will work in SysV 4.0 and above, but the modifications
should be fairly minimal.  Remember, though, THERE IS NO WARRANTY.
 

GENERAL INFORMATION

This driver is for the Sound Blaster sound card designed by Creative
Labs, Incorporated.  The Sound Blaster is a sound card for the
IBM-PC(tm) type I/O bus.  The card is capable of sampling sound from a
microphone jack from 4KHz to 23KHz (or 24KHz, depending upon which
portion of the manual you believe) at 8 raw bits of resolution.  It is
also capable of playing samples at speeds from 4KHz up to 12KHz.  The
board can play raw 8 bit samples, or 2 bit, 2.6 bit, or 4 bit ADPCM
packed samples.  ADCPM is a lossy method of data compression.  The
sampling and output may be directly driven by the CPU, or it may be
driven by DMA.  The card is also capable of playing music through an
on-board FM synthesizer chip.  The chip may play 9 simultaneous
instruments, or it can play 6 simultaneous voices with 5 additional
rhythm instruments.  In addition, the board (but NOT my driver) also
supports the addition of 2 C/MS chips. For more information, read the
advertising blurb, errr...  manual which comes with the Sound Blaster.

The driver is capable of utilizing only a subset of the capabilities
of the Sound Blaster card.  The driver is accessed through three
device nodes.

The /dev/sbdsp node accesses the DSP chip which controls sampling and
sample playback.  Only 8-bit sound is supported thus far.  The driver
utilizes DMA, and therefore is very unobtrusive to the system load.
The ls utility is harder upon the system.  Read(2) and write(2) upon
/dev/sbdsp sample sound and play back sound samples, respectively.
Several ioctl(2)'s control sampling/playback speed, resets, etc... 
The play_snd utility demonstrates the use of the /dev/sbdsp node.

The /dev/sbfm node accesses the FM chips.  The only access to the FM
chips are through ioctl(2)'s.  The play_cmf utility demonstrates the
use of the FM chips by (mostly) interpreting and playing CMF files.
The various options controlling the voices can be dumped from a CMF
file by the get_instruments utility.  Those familiar with synthesizers
can then learn how to create their own instruments.  I can't provide
direct documentation, as I believe that would be a violation of
Creative Lab's copyrights.  Those having questions, send me e-mail.  I
think it would be OK to answer questions.

The /dev/sbcms node accesses absolutely nothing.  I do not have the
C/MS chips with which to experiment.

This driver is merely the rough outlines of what it could be.  Support
could be added for oodles of features, like efficiency improvements, and
ADCPM interpretion for the /dev/sbdsp node.  If you wish to add to this
driver, please be my guest (after reading the license file).  When you
have patches, send those to me.  I will then integrate, do rudimentary
testing, and send the patches to Usenet.  That way, there won't be
billions of versions of this driver to contend with.

I am not requiring donations to my, no doubt worthy cause, however,
they are willingly accepted. :-)  They will help me in future
projects, and if there are enough requests (loudness does not count),
I will continue work on this driver and write more software to use
it.
SHAR_EOF
fi
if test -f 'Space.c'
then
	echo shar: "will not over-write existing file 'Space.c'"
else
cat << \SHAR_EOF > 'Space.c'
/*
 * Copyrighted as an unpublished work.
 * (c) Copyright 1991 Brian Smith
 * All rights reserved.
 *
 * Read the LICENSE file for details on distribution and use.
 *
 */

#include <sys/types.h>
#include <sys/iobuf.h>
#include <config.h>

#include <sys/sb.h>

ushort sb_configured = SB;
ushort sb_dma_chan = SB_CHAN;
ushort sb_interrupt = SB_0_VECT;
struct iobuf sbtab[3];
SHAR_EOF
fi
if test -f 'System'
then
	echo shar: "will not over-write existing file 'System'"
else
cat << \SHAR_EOF > 'System'
sb  Y  3  6  3  5  220  22F  0  0
SHAR_EOF
fi
if test -f 'sb.c'
then
	echo shar: "will not over-write existing file 'sb.c'"
else
cat << \SHAR_EOF > 'sb.c'
/*
 * Copyrighted as an unpublished work.
 * (c) Copyright 1991 Brian Smith
 * All rights reserved.
 *
 * Read the LICENSE file for details on distribution and use.
 *
 */


#include <sys/types.h>
#include <sys/param.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/dir.h>
#include <sys/buf.h>
#include <sys/iobuf.h>
#include <sys/signal.h>
#include <sys/user.h>
#include <sys/sysmacros.h>
#include <sys/dma.h>
#include <sys/cmn_err.h>
#include <sys/errno.h>
#include <sys/inline.h>
#include "sb.h"

/* defs needed because of ommisions ISC! */
#ifndef DMA_Rdmode
#define DMA_Rdmode 0x44
#endif
#ifndef DMA_Wrmode
#define DMA_Wrmode 0x48
#endif
#ifndef DMA_BLOCK
#define DMA_BLOCK 0
#endif
#ifndef DMA_NBLOCK
#define DMA_NBLOCK 1
#endif

extern ushort sb_configured;
extern ushort sb_dma_chan;
extern ushort sb_interrupt;

/* GLOBALS */
struct sb_stat_type sb_status;          /* Soundblaster Status */
extern time_t lbolt;


/*
 * reset DSP chip, and return TRUE if successful
 */
static int dsp_reset()
{
    int i;
    register unsigned char rc;

    /* reset dsp */
    outb(DSP_RESET, 0x01);
    tenmicrosec();
    outb(DSP_RESET, 0x00);
    for (i=0; i<200; i++)
    {
        rc = (unsigned char)inb(DSP_RDAVAIL);
        if (rc & 128)
        {
            rc = inb(DSP_RDDATA);
            if (rc == 0xAA)
                break;
        }

        i--;
        continue;
    }
    if (i>=200)
        cmn_err(CE_WARN, "SoundBlaster(tm) DSP failed initialization\n");

    /* reset sampling speed */
    dsp_speed();

    return(TRUE);
}


/*
 * program the DSP's time constant: the sampling/output rate
 */
int dsp_speed()
{
    unchar time_constant;
    unchar dsp_return;

    time_constant = (char)(256 - (1000000/sb_status.dsp_speed));

    /* send command SET_TIME_CONSTANT (0x40) */
    do
    {
        dsp_return = inb(DSP_STATUS);
    } while (dsp_return & (1<<7));
    outb(DSP_COMMAND, 0x40);

    /* send one byte time_constant */
    do
    {
        dsp_return = inb(DSP_STATUS);
    } while (dsp_return & (1<<7));
    outb(DSP_COMMAND, time_constant);

    return(TRUE);
}


/*
 * called at OS startup time to initialize the SoundBlaster
 */
int sbinit()
{
    int failure = FALSE;

    if (!sb_configured)
        cmn_err(CE_WARN, "sbinit(): SoundBlaster(tm) not configured\n");

    sbtab[0].b_actf = NULL;
    sbtab[0].b_actl = NULL;
    sbtab[0].b_active = FALSE;

    /* init cms chips' state */
    sb_status.cms_open = NOT_OPEN;
    sb_status.cms_waiting = 0;

    /* init fm chips' state */
    sb_status.fm_open = NOT_OPEN;
    sb_status.fm_waiting = 0;

    /* init dsp chip(s)' state */
    sb_status.dsp_open = NOT_OPEN;
    sb_status.dsp_speed = 11000;
    sb_status.dsp_compression = ADCPM_8;
    if (!dsp_reset())
        failure = TRUE;

    /* all OK, send notification to console */
    if (!failure)
    {
        cmn_err(CE_CONT, "SoundBlaster(tm) is recognized and initialized\n");
#ifdef DEBUG
        cmn_err(CE_CONT, "operating upon DMA channel %d\n", sb_dma_chan);
        cmn_err(CE_CONT, "operating upon interrupt vector %d\n", sb_interrupt);
#endif
    }
    else
    {
        cmn_err(CE_WARN, "SoundBlaster(tm) initialization failed\n");
        sb_status.dsp_open = OPEN_READ | OPEN_WRITE;
        sb_status.fm_open  = OPEN_READ | OPEN_WRITE;
        sb_status.cms_open = OPEN_READ | OPEN_WRITE;
    }

    return(0);
}


/*
 * turn the dsp voice on if param is true
 */
void dsp_voice(on)
int on;
{
    unchar dsp_return;

    do
    {
        dsp_return = inb(DSP_STATUS);
    } while (dsp_return & (1<<7));

    if (on)
    {
        outb(DSP_COMMAND, 0xD1);
#ifdef DEBUG
        cmn_err(CE_CONT, "dsp_voice(): voice on\n");
#endif
    }
    else
    {
        outb(DSP_COMMAND, 0xD3);
#ifdef DEBUG
        cmn_err(CE_CONT, "dsp_voice(): voice off\n");
#endif
    }

    return;
}


/*
 * grabs the DSP chip for a process.
 * sets u.u_error to EBUSY if already opened by other device
 */
static void dsp_open(flag)
int flag;
{
    int old_pri;
    int rc;


    /* check if already open */
    old_pri = spl6();
    if (sb_status.dsp_open)
    {
        u.u_error = EBUSY;
        splx(old_pri);
        return;
    }

    /* grab dma and device */
    dma_alloc(SB_DMA_CHAN, DMA_BLOCK);
    dsp_reset();
    dsp_voice(FALSE);
    if (flag & FWRITE)
    {
        if (flag & FREAD)
        {
            sb_status.dsp_open = OPEN_WRITE | OPEN_READ;
#ifdef DEBUG
            cmn_err(CE_CONT, "open read/write %d\n", flag);
#endif
        }
        else
        {
            sb_status.dsp_open = OPEN_WRITE;
#ifdef DEBUG
            cmn_err(CE_CONT, "open write %d\n", flag);
#endif
            dsp_voice(TRUE);
        }
    }
    else if (flag & FREAD)
    {
        sb_status.dsp_open = OPEN_READ;
#ifdef DEBUG
        cmn_err(CE_CONT, "open read/write %d\n", flag);
#endif
    }
    else
    {
#ifdef DEBUG
        cmn_err(CE_CONT, "unknown flags on open %d\n", flag);
#endif
        u.u_error = ENXIO;
    }

    /* END CRITICAL */
    splx(old_pri);

    return;
}


/*
 * grabs the FM chips for a process.
 * sets u.u_error to EBUSY if already opened by other device
 */
static void fm_open(flag)
int flag;
{
    int old_pri;
    int rc;


    /* check if already open */
    old_pri = spl6();
    if (sb_status.fm_open != NOT_OPEN)
    {
        u.u_error = EBUSY;
        splx(old_pri);
        return;
    }

    /* grab device and affirm that only openable with write permission */
    fm_reset();
    if (flag & FWRITE)
        sb_status.fm_open = OPEN_WRITE;
    else
        u.u_error = ENXIO;

    /* END CRITICAL */
    splx(old_pri);

    return;
}



/*
 * multiplexes opens to dsp_open(), fm_open(), and cms_open()
 * depending upon which minor dev was used
 */
int sbopen(dev, flag)
int dev;
int flag;
{
    int minor_num;

#ifdef DEBUG
    cmn_err(CE_CONT, "opened with flag %d\n", flag);
#endif

    minor_num = minor(dev);
    switch (minor_num)
    {
        case SB_CMS_NUM:
            u.u_error = ENXIO;
            break;
        case SB_FM_NUM:
            fm_open(flag);
            break;
        case SB_DSP_NUM:
            dsp_open(flag);
            break;
        default:
            u.u_error = ENXIO;
    }

    return(0);
}


/*
 * Release and reset the dsp chip
 */
void dsp_close()
{
    int old_pri;

    old_pri = spl6();
    sb_status.dsp_open = NOT_OPEN;
    dsp_reset();
    dsp_voice(FALSE);
    dma_relse(SB_DMA_CHAN);
    splx(old_pri);

    return;
}


/*
 * Release and reset the fm chip
 */
void fm_close()
{
    int old_pri;

    old_pri = spl6();
    sb_status.fm_open = NOT_OPEN;
    fm_reset();
    splx(old_pri);

    return;
}


/*
 * Multiplexes between the closes for dsp, fm, and cms chips
 */
int sbclose(dev)
int dev;
{
    int minor_num;

    minor_num = minor(dev);
    switch (minor_num)
    {
        case SB_CMS_NUM:
            break;
        case SB_FM_NUM:
            fm_close();
            break;
        case SB_DSP_NUM:
            dsp_close();
            break;
        default:
            u.u_error = ENXIO;
    }

    return(0);
}


/*
 * start DMA rolling for DSP
 * only used by DSP chips, so no need for multiplexing on minor num
 */
int sb_iostart()
{
    int old_pri;
    paddr_t phys_address;
    unsigned char tmp_byte;
    unsigned int length;

    /* begin critical */
    old_pri = spl6();

    /* check for invalid length of buf */
    if ((sbtab[0].b_actf->b_bcount < 1) || (sbtab[0].b_actf->b_bcount > 0xFFFF))
    {
        cmn_err(CE_WARN, "sb_iostart(): invalid length of buf, %d\n",
            sbtab[0].b_actf->b_bcount);

        /* artificially terminate */
        sbtab[0].b_actf->b_resid = 0;
        sbtab[0].b_actf = sbtab[0].b_actf->av_forw;
        iodone(sbtab[0].b_actf);
        splx(old_pri);
        return(0);
    }

    /* mark device in sbtab as active */
    sbtab[0].b_active = TRUE;

    /* prep DMA channel */
    phys_address = vtop(paddr(sbtab[0].b_actf), sbtab[0].b_actf->b_proc);
    if (sbtab[0].b_actf->b_flags & B_READ)
        dma_param(SB_DMA_CHAN, DMA_Rdmode, phys_address,
            sbtab[0].b_actf->b_bcount);
    else
        dma_param(SB_DMA_CHAN, DMA_Wrmode, phys_address,
            sbtab[0].b_actf->b_bcount);
    dma_enable(SB_DMA_CHAN);

    /* prep SoundBlaster for 8-bit DMA */
    do {
        tmp_byte = inb(DSP_STATUS);
    } while (tmp_byte & (1<<7));
    if (sbtab[0].b_actf->b_flags & B_READ)
        outb(DSP_COMMAND, 0x24);
    else
        outb(DSP_COMMAND, 0x14);

    /* prep SoundBlaster for length */
    length = sbtab[0].b_actf->b_bcount-1;
    do {
        tmp_byte = inb(DSP_STATUS);
    } while (tmp_byte & (1<<7));
    tmp_byte = length & 0xFF;
    outb(DSP_COMMAND, tmp_byte);
    do {
        tmp_byte = inb(DSP_STATUS);
    } while (tmp_byte & (1<<7));
    tmp_byte = (length & 0xFF00) >> 8;
    outb(DSP_COMMAND, tmp_byte);

    /* end critical */
    splx(old_pri);

    return(0);
}


/*
 * write sound samples to dsp
 */
int sb_strategy(bufhead)
register struct buf *bufhead;
{
    int old_pri;

    /* start critical section */
    old_pri = spl6();

    /* prep buffer for addition to queue */
    bufhead->b_start = lbolt;
    bufhead->av_forw = NULL;

    /* add buffer to queue */
    if (sbtab[0].b_actf == NULL)
    {
        /* add to empty queue */
        sbtab[0].b_actf = bufhead;
        sbtab[0].b_actl = bufhead;

        /* start io */
        sb_iostart();
    }
    else
    {
        /* add to already started queue */
        sbtab[0].b_actl->av_forw = bufhead;
        sbtab[0].b_actl = bufhead;
    }

    /* end critical section */
    splx(old_pri);

    return(0);
}


/*
 * breaks up io requests so that DMA can be done
 */
static int sb_breakup(bp)
register struct buf *bp;
{
    dma_breakup(sb_strategy, bp);
    return(0);
}


/*
 * Starts the DMA write to the  Soundblaster
 */
int dsp_write(dev)
int dev;
{
    physio(sb_breakup, 0, dev, B_WRITE);
    return(0);
}


/*
 * multiplexes writes to dsp, cm/s and fm chips
 */
int sbwrite(dev)
int dev;
{
    int minor_num;

    minor_num = minor(dev);
    switch (minor_num)
    {
        case SB_CMS_NUM:
            cmn_err(CE_CONT, "sbwrite(): error, cms device accessed\n");
            u.u_error = ENXIO;
            break;
        case SB_FM_NUM:
            u.u_error = ENXIO;
            break;
        case SB_DSP_NUM:
            dsp_write(dev);
            break;
        default:
            cmn_err(CE_CONT, "sbwrite(): unknown minor device %d\n", minor_num);
            u.u_error = ENXIO;
    }
    return(0);
}


/*
 * Starts the DMA read from the Soundblaster
 */
int dsp_read(dev)
int dev;
{
    physio(sb_breakup, 0, dev, B_READ);
    return(0);
}


/*
 * multiplexes read/writes to different functions
 */
int sbread(dev)
int dev;
{
    int minor_num;

    minor_num = minor(dev);
    switch (minor_num)
    {
        case SB_CMS_NUM:
            cmn_err(CE_CONT, "sbread(): error, cms device accessed\n");
            u.u_error = ENXIO;
            break;
        case SB_FM_NUM:
            u.u_error = ENXIO;
            break;
        case SB_DSP_NUM:
            dsp_read(dev);
            break;
        default:
            cmn_err(CE_CONT, "sbread(): unknown minor device %d\n", minor_num);
            u.u_error = ENXIO;
    }
    return(0);
}


/*
 * minor control function for the dsp
 */
void dsp_ioctl(cmd, arg1, arg2)
int cmd;
caddr_t arg1, arg2;
{
    switch(cmd)
    {
        case DSP_IOCTL_RESET:
            dsp_reset();
            break;
        case DSP_IOCTL_SPEED:
            sb_status.dsp_speed = (int)arg1;
            dsp_speed();
            break;
        case DSP_IOCTL_VOICE:
            dsp_voice((int)arg1);
            break;
        default:
            break;
    }

    return;
}


/*
 * turns a note/key off
 */
void fm_key_off(voice_num)
int voice_num;
{
    unsigned char reg_num;
    
    /* error checking to avoid munching kernel */
    if ((voice_num < 0) || (voice_num >= MAX_FM_NOTES))
    {
        u.u_error = EFAULT;
        return;
    }
    
    /* turn voice off */
    reg_num = (unsigned char)0xB0 + (unsigned char)voice_num;
#ifdef DEBUG
    cmn_err(CE_CONT, "turning off voice for voice %d\n", voice_num);
    cmn_err(CE_CONT, "reg_num is %x\n", reg_num);
#endif
    outb(FM_SELECT, reg_num);
    tenmicrosec();
    outb(FM_REG, 0);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();

    return;
}

/*
 * turns a key on, with the frequency and octave so indicated in the
 * low 2 bytes of the data integer
 */
void fm_key_on(usr_note)
int usr_note;
{
    register unsigned char reg_num;
    int tmp_int;

#ifdef DEBUG
    cmn_err(CE_CONT, "turning on voice for voice %d\n", note_num(usr_note));
    cmn_err(CE_CONT, "fnum_low (dec): %d\n", fnum_low(usr_note));
    cmn_err(CE_CONT, "fnum_low (hex): %x\n", fnum_low(usr_note));
    cmn_err(CE_CONT, "keyon_blk_fnum (dec): %d\n", keyon_blk_fnum(usr_note));
    cmn_err(CE_CONT, "keyon_blk_fnum (hex): %x\n", keyon_blk_fnum(usr_note));
#endif

    /* put out first byte */
    reg_num = (unsigned char)0xA0 + note_num(usr_note);
#ifdef DEBUG
    cmn_err(CE_CONT, "reg_num is %x\n", reg_num);
#endif
    outb(FM_SELECT, reg_num);
    tenmicrosec();
    outb(FM_REG, fnum_low(usr_note));
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();

    /* put out second byte */
    reg_num = (unsigned char)0xB0 + note_num(usr_note);
#ifdef DEBUG
    cmn_err(CE_CONT, "reg_num is %x\n", reg_num);
#endif
    outb(FM_SELECT, reg_num);
    tenmicrosec();
    outb(FM_REG, keyon_blk_fnum(usr_note));
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();
    
    return;
}

/* at this point, it just turns all notes to off */
int fm_reset()
{
    int i;

    /* must be initialized? */
    outb(FM_SELECT, 1);
    tenmicrosec();
    outb(FM_REG, 0);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();

    /* dispense for time being */
    for (i=0; i<MAX_FM_NOTES; i++)
        fm_key_off(i);

    return(0);
}


/*
 * set characteristics on a voice
 */
void fm_set_voice(usr_character)
sb_fm_character *usr_character;
{
    register unsigned char op_cell_num;
    int cell_offset;
    sb_fm_character voice_data;
    int i;

    /* copy in characteristics */
    if (copyin(usr_character, &voice_data, sizeof(sb_fm_character)) == -1)
    {
#ifdef DEBUG
        cmn_err(CE_CONT, "fm_set_voice(): bad address\n");
#endif
        u.u_error = EFAULT;
        return;
    }

    /* echo voice characteristics */
#ifdef DEBUG
    cmn_err(CE_CONT, "setting voice number %d\n", voice_data.voice_num);
    cmn_err(CE_CONT, "setting voice number(hex) %x\n", voice_data.voice_num);
    cmn_err(CE_CONT, "data: ");
    for (i=0; i<16; i++)
        cmn_err(CE_CONT, "%x ", (unsigned int)voice_data.data[i]);
    cmn_err(CE_CONT, "\n");
#endif

    /* check on voice_num range */
    if ((voice_data.voice_num >= MAX_FM_NOTES) || (voice_data.voice_num < 0))
    {
        cmn_err(CE_CONT, "fm_set_voice(): voice number out of range\n");
        u.u_error = EFAULT;
    }
    cell_offset = voice_data.voice_num%3 + ((voice_data.voice_num / 3) << 3);

    /* set sound characteristic */
    op_cell_num = 0x20 + (char)cell_offset;
#ifdef DEBUG
    cmn_err(CE_CONT, "op_cell for 20-35 = %x\n", op_cell_num);
#endif
    outb(FM_SELECT, op_cell_num);
    tenmicrosec();
    outb(FM_REG, voice_data.data[0]);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();
    op_cell_num += 3;
#ifdef DEBUG
    cmn_err(CE_CONT, "op_cell for 20-35 = %x\n", op_cell_num);
#endif
    outb(FM_SELECT, op_cell_num);
    tenmicrosec();
    outb(FM_REG, voice_data.data[1]);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();

    /* set level/output */
    op_cell_num = 0x40 + (char)cell_offset;
#ifdef DEBUG
    cmn_err(CE_CONT, "op_cell for 40-55 = %x\n", op_cell_num);
#endif
    outb(FM_SELECT, op_cell_num);
    tenmicrosec();
    outb(FM_REG, voice_data.data[2]);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();
    op_cell_num += 3;
#ifdef DEBUG
    cmn_err(CE_CONT, "op_cell for 40-55 = %x\n", op_cell_num);
#endif
    outb(FM_SELECT, op_cell_num);
    tenmicrosec();
    outb(FM_REG, voice_data.data[3]);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();

    /* set Attack/Decay */
    op_cell_num = 0x60 + (char)cell_offset;
#ifdef DEBUG
    cmn_err(CE_CONT, "op_cell for 60-75 = %x\n", op_cell_num);
#endif
    outb(FM_SELECT, op_cell_num);
    tenmicrosec();
    outb(FM_REG, voice_data.data[4]);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();
    op_cell_num += 3;
#ifdef DEBUG
    cmn_err(CE_CONT, "op_cell for 60-75 = %x\n", op_cell_num);
#endif
    outb(FM_SELECT, op_cell_num);
    tenmicrosec();
    outb(FM_REG, voice_data.data[5]);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();

    /* set Sustain/Release */
    op_cell_num = 0x80 + (char)cell_offset;
#ifdef DEBUG
    cmn_err(CE_CONT, "op_cell for 80-95 = %x\n", op_cell_num);
#endif
    outb(FM_SELECT, op_cell_num);
    tenmicrosec();
    outb(FM_REG, voice_data.data[6]);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();
    op_cell_num += 3;
#ifdef DEBUG
    cmn_err(CE_CONT, "op_cell for 80-95 = %x\n", op_cell_num);
#endif
    outb(FM_SELECT, op_cell_num);
    tenmicrosec();
    outb(FM_REG, voice_data.data[7]);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();

    /* set Wave Select */
    op_cell_num = 0xE0 + (char)cell_offset;
#ifdef DEBUG
    cmn_err(CE_CONT, "op_cell for E0-F5 = %x\n", op_cell_num);
#endif
    outb(FM_SELECT, op_cell_num);
    tenmicrosec();
    outb(FM_REG, voice_data.data[8]);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();
    op_cell_num += 3;
#ifdef DEBUG
    cmn_err(CE_CONT, "op_cell for E0-F5 = %x\n", op_cell_num);
#endif
    outb(FM_SELECT, op_cell_num);
    tenmicrosec();
    outb(FM_REG, voice_data.data[9]);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();

    /* set Feedback/Selectivity */
    op_cell_num = (unsigned char)0xC0 + (unsigned char)voice_data.voice_num;
#ifdef DEBUG
    cmn_err(CE_CONT, "op_cell for C0-C8 = %x\n", op_cell_num);
#endif
    outb(FM_SELECT, op_cell_num);
    tenmicrosec();
    outb(FM_REG, voice_data.data[10]);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();

    return;
}


/*
 * set characteristics on an opcell
 */
void fm_set_opcell(usr_character)
sb_fm_character *usr_character;
{
    register unsigned char op_cell_num;
    int cell_offset;
    sb_fm_character voice_data;
    int i;

    /* copy in characteristics */
    if (copyin(usr_character, &voice_data, sizeof(sb_fm_character)) == -1)
    {
#ifdef DEBUG
        cmn_err(CE_CONT, "bad address\n");
#endif
        u.u_error = EFAULT;
        return;
    }

    /* echo voice characteristics */
#ifdef DEBUG
    cmn_err(CE_CONT, "setting opcell number %d\n", voice_data.voice_num);
    cmn_err(CE_CONT, "setting opcell number(hex) %x\n", voice_data.voice_num);
    cmn_err(CE_CONT, "data: ");
    for (i=0; i<8; i++)
        cmn_err(CE_CONT, "%x ", (unsigned int)voice_data.data[i]);
    cmn_err(CE_CONT, "\n");
#endif

    /* check on opcell range */
    if ((voice_data.voice_num >= 2*MAX_FM_NOTES) || (voice_data.voice_num < 0))
    {
        cmn_err(CE_CONT, "opcell number out of range\n");
        u.u_error = EFAULT;
    }

    /* set sound characteristic */
    op_cell_num = 0x20 + (char)voice_data.voice_num;
#ifdef DEBUG
    cmn_err(CE_CONT, "op_cell for 20-35 = %x\n", op_cell_num);
#endif
    outb(FM_SELECT, op_cell_num);
    tenmicrosec();
    outb(FM_REG, voice_data.data[0]);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();

    /* set level/output */
    op_cell_num = 0x40 + (char)voice_data.voice_num;
#ifdef DEBUG
    cmn_err(CE_CONT, "op_cell for 40-55 = %x\n", op_cell_num);
#endif
    outb(FM_SELECT, op_cell_num);
    tenmicrosec();
    outb(FM_REG, voice_data.data[1]);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();

    /* set Attack/Decay */
    op_cell_num = 0x60 + (char)voice_data.voice_num;
#ifdef DEBUG
    cmn_err(CE_CONT, "op_cell for 60-75 = %x\n", op_cell_num);
#endif
    outb(FM_SELECT, op_cell_num);
    tenmicrosec();
    outb(FM_REG, voice_data.data[2]);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();

    /* set Sustain/Release */
    op_cell_num = 0x80 + (char)voice_data.voice_num;
#ifdef DEBUG
    cmn_err(CE_CONT, "op_cell for 80-95 = %x\n", op_cell_num);
#endif
    outb(FM_SELECT, op_cell_num);
    tenmicrosec();
    outb(FM_REG, voice_data.data[3]);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();

    /* set Wave Select */
    op_cell_num = 0xE0 + (char)voice_data.voice_num;
#ifdef DEBUG
    cmn_err(CE_CONT, "op_cell for E0-F5 = %x\n", op_cell_num);
#endif
    outb(FM_SELECT, op_cell_num);
    tenmicrosec();
    outb(FM_REG, voice_data.data[4]);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();

    /* set Feedback/Selectivity */
    op_cell_num = (unsigned char)0xC0 + (unsigned char)voice_data.voice_num;
#ifdef DEBUG
    cmn_err(CE_CONT, "op_cell for C0-C8 = %x\n", op_cell_num);
#endif
    outb(FM_SELECT, op_cell_num);
    tenmicrosec();
    outb(FM_REG, voice_data.data[5]);
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();

    return;
}

/*
 * set the register which contains the keyon/off for rhythm, and depth flags
 */
void fm_set_rhythm(new_rhythm)
int new_rhythm;
{
    /* herf it in */
    outb(FM_SELECT, 0xBD);
    tenmicrosec();
    outb(FM_REG, lobyte(new_rhythm));
    tenmicrosec();
    tenmicrosec();
    tenmicrosec();

    return;
}


/*
 * The only control for the FM chips.  The rest belongs in user code.
 */
void fm_ioctl(cmd, arg1, arg2)
int cmd;
caddr_t arg1, arg2;
{
    switch(cmd)
    {
        case FM_IOCTL_RESET:
            fm_reset();
            break;
        case FM_IOCTL_NOTE_ON:
            fm_key_on((int)arg1);
            break;
        case FM_IOCTL_NOTE_OFF:
            fm_key_off((int)arg1);
            break;
        case FM_IOCTL_SET_VOICE:
            fm_set_voice((sb_fm_character *)arg1);
            break;
        case FM_IOCTL_SET_OPCELL:
            fm_set_opcell((sb_fm_character *)arg1);
            break;
        case FM_IOCTL_SET_RHYTHM:
            fm_set_rhythm((int)arg1);
            break;
        default:
            break;
    }

    return;
}

 
/*
 * multiplex ioctl to different sub-devices (minor numbers)
 */
int sbioctl(dev, cmd, arg1, arg2)
int dev;
int cmd;
caddr_t arg1, arg2;
{
    int minor_num;

    minor_num = minor(dev);
    switch (minor_num)
    {
        case SB_CMS_NUM:
            cmn_err(CE_CONT, "sbioctl cms\n");
            break;
        case SB_FM_NUM:
            fm_ioctl(cmd, arg1, arg2);
            break;
        case SB_DSP_NUM:
            dsp_ioctl(cmd, arg1, arg2);
            break;
        default:
            cmn_err(CE_CONT, "sbioctl unknown minor %d\n", minor_num);
            u.u_error = ENXIO;
    }
    return(0);
}


/*
 * responds to interrupt sent by DSP chips
 * and starts the next block of DMA (if one is queued)
 */
int sbintr(vect)
int vect;
{
    int old_pri;
    unsigned char tmp_byte;

    /* ASSUME CRITICAL PRIORITY */
    old_pri = spl6();

    /* check for validity of interrupt */
    if (! sbtab[0].b_active)
    {   
        cmn_err(CE_CONT, "sbintr(): spurious interrupt\n");
        splx(old_pri);
        return(0);
    }

    /* aknowledge interrupt */
    tmp_byte = inb(DSP_RDAVAIL);

    /* acknowledge as done to waiting thread and remove buf from queue */ 
    sbtab[0].b_actf->b_resid = 0;
    iodone(sbtab[0].b_actf);
    sbtab[0].b_actf = sbtab[0].b_actf->av_forw;

    /* mark device as inactive and service rest of queue (if not empty) */
    sbtab[0].b_active = FALSE;
    if (sbtab[0].b_actf != NULL)
        sb_iostart();

    /* end of critical section */
    splx(old_pri);

    return(0);
}
SHAR_EOF
fi
if test -f 'sb.h'
then
	echo shar: "will not over-write existing file 'sb.h'"
else
cat << \SHAR_EOF > 'sb.h'
/*
 * Copyrighted as an unpublished work.
 * (c) Copyright 1991 Brian Smith
 * All rights reserved.
 *
 * Read the LICENSE file for details on distribution and use.
 *
 */

#ifndef NBPP
#include <sys/immu.h>
#endif

#if !defined(TRUE) || !defined(FALSE)
#define FALSE 0
#define TRUE  1
#endif

/* minor numbers */
#define SB_CMS_NUM  0
#define SB_FM_NUM   1
#define SB_DSP_NUM  2

/* These are hard wired for speed */
#define SB_DMA_CHAN 1
#define SB_IO_PORT 0x220

/* C/MS (not supported) */
#define CMS_DATA1   (SB_IO_PORT + 0x00)
#define CMS_REG1    (SB_IO_PORT + 0x01)
#define CMS_DATA2   (SB_IO_PORT + 0x02)
#define CMS_REG2    (SB_IO_PORT + 0x03)

/* FM Chips */
#define FM_SELECT   (SB_IO_PORT + 0x08)
#define FM_REG      (SB_IO_PORT + 0x09)
#define MAX_FM_NOTES 9

/* DSP (DAC and ADC) Chip(s) */
#define DSP_RESET   (SB_IO_PORT + 0x06)
#define DSP_RDDATA  (SB_IO_PORT + 0x0A)
#define DSP_WRDATA  (SB_IO_PORT + 0x0C)
#define DSP_COMMAND (SB_IO_PORT + 0x0C)
#define DSP_STATUS  (SB_IO_PORT + 0x0C)
#define DSP_RDAVAIL (SB_IO_PORT + 0x0E)

/* status bytes (can be OR'ed) */
#define NOT_OPEN    0
#define OPEN_READ   1
#define OPEN_WRITE  2

/* compression types */
#define ADCPM_8     0
#define ADCPM_4     1
#define ADCPM_2_6   2
#define ADCPM_2     3

/* ioctl numbers for DSP */
#define DSP_IOCTL_RESET 00
#define DSP_IOCTL_SPEED 01
#define DSP_IOCTL_VOICE 02

/* ioctl numbers for FM */
#define FM_IOCTL_RESET      00
#define FM_IOCTL_NOTE_ON    01
#define FM_IOCTL_NOTE_OFF   02
#define FM_IOCTL_SET_VOICE  03
#define FM_IOCTL_SET_OPCELL 04
#define FM_IOCTL_SET_RHYTHM 05

/* struct for setting a note/voice/key on */
typedef int sb_fm_note;
#define note_num(X) (((unsigned char *)&X)[0])
#define fnum_low(X) (((unsigned char *)&X)[1])
#define keyon_blk_fnum(X) (((unsigned char *)&X)[2])

typedef struct {
    unsigned char   voice_num;
    unsigned char   data[16];
} sb_fm_character;

#ifdef INKERNEL
struct sb_stat_type {
    char unsigned   cms_open;           /* whether in read/write */
    char unsigned   cms_waiting;        /* number of procs waiting to open */
    char unsigned   fm_open;            /* whether in read/write */
    char unsigned   fm_waiting;         /* number of procs waiting to open */
    char unsigned   dsp_open;           /* whether in read/write */
    unsigned int    dsp_speed;          /* sample read/write HZ */
    char unsigned   dsp_compression;    /* compression protocol */
};

extern struct sb_stat_type sb_status;
extern struct iobuf sbtab[3];

#endif
SHAR_EOF
fi
exit 0
#	End of shell archive



More information about the Alt.sources mailing list