/*
 * 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;

void dsp_voice ();


/*
 * 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;
    }

    dsp_voice (TRUE);

    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);
    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
        }
    }
    else if (flag & FREAD)
    {
        sb_status.dsp_open = OPEN_READ;
#ifdef DEBUG
        cmn_err(CE_CONT, "open read/write %d\n", flag);
#endif
	dsp_voice (FALSE);
    }
    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();
    if (sb_status.dsp_open & OPEN_READ)
      dsp_voice (TRUE);

    sb_status.dsp_open = NOT_OPEN;
    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();
	    dsp_voice (TRUE);
            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);
}
