/*	$NetBSD: sbdsp.c,v 1.5 1995/03/25 00:01:00 mycroft Exp $	*/

/*
 * Copyright (c) 1991-1993 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the Computer Systems
 *	Engineering Group at Lawrence Berkeley Laboratory.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	$Id: sbdsp.c,v 1.5 1995/03/25 00:01:00 mycroft Exp $
 */
/*
 * SoundBlaster Pro code provided by John Kohl, based on lots of
 * information he gleaned from Steve Haehnichen <steve@vigra.com>'s
 * SBlast driver for 386BSD and DOS driver code from Daniel Sachs
 * <sachs@meibm15.cen.uiuc.edu>.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/syslog.h>
#include <sys/device.h>
#include <sys/proc.h>
#include <sys/buf.h>
#include <vm/vm.h>

#include <machine/cpu.h>
#include <machine/pio.h>

#include <sys/audioio.h>
#include <dev/audio_if.h>

#include <i386/isa/isavar.h>
#include <i386/isa/dmavar.h>
#include <i386/isa/icu.h>

#include <i386/isa/sbreg.h>
#include <i386/isa/sbdspvar.h>

#ifdef DEBUG
extern void Dprintf __P((const char *, ...));

#define DPRINTF(x)	if (sbdspdebug) printf x
int	sbdspdebug = 0;
#else
#define DPRINTF(x)
#endif

#ifndef NEWCONFIG
#define at_dma(flags, ptr, cc, chan)	isa_dmastart(flags, ptr, cc, chan)
#endif

#ifndef SBDSP_NPOLL
#define SBDSP_NPOLL 3000
#endif

struct {
	int wdsp;
	int rdsp;
	int wmidi;
} sberr;

#ifdef DEBUG
void
sb_printsc(struct sbdsp_softc *sc)
{
	int i;
    
	printf("open %d dmachan %d iobase %x locked %d\n", sc->sc_open, sc->sc_drq,
		sc->sc_iobase, sc->sc_locked);
	printf("hispeed %d irate %d orate %d encoding %x\n",
		sc->sc_adacmode, sc->sc_irate, sc->sc_orate, sc->encoding);
	printf("outport %d inport %d spkron %d nintr %d\n",
		sc->out_port, sc->in_port, sc->spkr_state, sc->sc_interrupts);
	printf("tc %x chans %x scintr %x arg %x\n", sc->sc_adactc, sc->sc_chans,
		sc->sc_intr, sc->sc_arg);
	printf("gain: ");
	for (i = 0; i < SB_NDEVS; i++)
	    printf("%d ", sc->gain[i]);
	printf("\n");
}
#endif

/*
 * Probe / attach routines.
 */

/*
 * Probe for the soundblaster hardware.
 */
int
sbdsp_probe(sc)
	struct sbdsp_softc *sc;
{
	register u_short iobase = sc->sc_iobase;

	if (sbdsp_reset(sc) < 0) {
		DPRINTF(("sbdsp: couldn't reset card\n"));
		return 0;
	}
	sc->sc_model = sbversion(sc);

	return 1;
}

/*
 * Attach hardware to driver, attach hardware driver to audio
 * pseudo-device driver .
 */
void
sbdsp_attach(sc)
	struct sbdsp_softc *sc;
{
	register u_short iobase = sc->sc_iobase;

	sc->sc_locked = 0;

#ifdef NEWCONFIG
	/*
	 * We limit DMA transfers to a page, and use the generic DMA handling
	 * code in isa.c.  This code can end up copying a buffer, but since
	 * the audio driver uses relative small buffers this isn't likely.
	 *
	 * This allocation scheme means that the maximum transfer is limited
	 * by the page size (rather than 64k).  This is reasonable.  For 4K
	 * pages, the transfer time at 48KHz is 4096 / 48000 = 85ms.  This
	 * is plenty long enough to amortize any fixed time overhead.
	 */
	at_setup_dmachan(sc->sc_drq, NBPG);
#endif

	/* Set defaults */
	if (ISSBPROCLASS(sc))
	    sc->sc_irate = sc->sc_orate = 45454;
  	else
	    sc->sc_irate = sc->sc_orate = 14925;
	sc->sc_chans = 1;
	sc->encoding = AUDIO_ENCODING_LINEAR;

	(void) sbdsp_set_in_sr_real(sc, sc->sc_irate);
	(void) sbdsp_set_out_sr_real(sc, sc->sc_orate);

	(void) sbdsp_set_in_port(sc, SB_MIC_PORT);
	(void) sbdsp_set_out_port(sc, SB_SPEAKER);

	if (ISSBPROCLASS(sc)) {
		int i;
	    
		/* set mixer to default levels, by sending a mixer
                   reset command. */
		sbdsp_mix_write(sc, SBP_MIX_RESET, SBP_MIX_RESET);
		/* then some adjustments :) */
		sbdsp_mix_write(sc, SBP_CD_VOL,
				sbdsp_stereo_vol(SBP_MAXVOL, SBP_MAXVOL));
		sbdsp_mix_write(sc, SBP_DAC_VOL,
				sbdsp_stereo_vol(SBP_MAXVOL, SBP_MAXVOL));
		sbdsp_mix_write(sc, SBP_MASTER_VOL,
				sbdsp_stereo_vol(SBP_MAXVOL, SBP_MAXVOL));
		sbdsp_mix_write(sc, SBP_LINE_VOL,
				sbdsp_stereo_vol(SBP_MAXVOL, SBP_MAXVOL));
		for (i = 0; i < SB_NDEVS; i++)
		    sc->gain[i] = sbdsp_stereo_vol(SBP_MAXVOL, SBP_MAXVOL);
	}
	printf(": dsp v%d.%d\n",
	       SBVER_MAJOR(sc->sc_model), SBVER_MINOR(sc->sc_model));
}

/*
 * Various routines to interface to higher level audio driver
 */

void
sbdsp_mix_write(sc, mixerport, val)
	struct sbdsp_softc *sc;
	int mixerport;
	int val;
{
	int iobase = sc->sc_iobase;
	outb(iobase + SBP_MIXER_ADDR, mixerport);
	delay(10);
	outb(iobase + SBP_MIXER_DATA, val);
	delay(30);
}

int
sbdsp_mix_read(sc, mixerport)
	struct sbdsp_softc *sc;
	int mixerport;
{
	int iobase = sc->sc_iobase;
	outb(iobase + SBP_MIXER_ADDR, mixerport);
	delay(10);
	return inb(iobase + SBP_MIXER_DATA);
}

int
sbdsp_set_in_sr(addr, sr)
	void *addr;
	u_long sr;
{
	register struct sbdsp_softc *sc = addr;

	sc->sc_irate = sr;

	return 0;
}

int
sbdsp_set_in_sr_real(addr, sr)
	void *addr;
	u_long sr;
{
	register struct sbdsp_softc *sc = addr;
	int rval;

	if (rval = sbdsp_set_sr(sc, &sr, SB_INPUT_RATE))
		return rval;
	sc->sc_irate = sr;
	sc->sc_dmain_inprogress = 0;		/* do it again on next DMA out */
	sc->sc_dmaout_inprogress = 0;
	return(0);
}

u_long
sbdsp_get_in_sr(addr)
	void *addr;
{
	register struct sbdsp_softc *sc = addr;

	return(sc->sc_irate);
}

int
sbdsp_set_out_sr(addr, sr)
	void *addr;
	u_long sr;
{
	register struct sbdsp_softc *sc = addr;

	sc->sc_orate = sr;
	return(0);
}

int
sbdsp_set_out_sr_real(addr, sr)
	void *addr;
	u_long sr;
{
	register struct sbdsp_softc *sc = addr;
	int rval;

	if (rval = sbdsp_set_sr(sc, &sr, SB_OUTPUT_RATE))
		return rval;
	sc->sc_orate = sr;
	sc->sc_dmain_inprogress = 0;		/* do it again on next DMA out */
	return(0);
}

u_long
sbdsp_get_out_sr(addr)
	void *addr;
{
	register struct sbdsp_softc *sc = addr;

	return(sc->sc_orate);
}

int
sbdsp_query_encoding(addr, fp)
    void *addr;
    struct audio_encoding *fp;
{
    register struct sbdsp_softc *sc = addr;

    switch (fp->index) {
    case 0:
	strcpy(fp->name, "MU-Law");
	fp->format_id = AUDIO_ENCODING_ULAW;
	break;
    case 2:
	strcpy(fp->name, "pcm16");
	fp->format_id = AUDIO_ENCODING_PCM16;
	break;
    default:
	return(EINVAL);
	/*NOTREACHED*/
    }
    return (0);
}

int
sbdsp_set_encoding(addr, enc)
	void *addr;
	u_int enc;
{
	register struct sbdsp_softc *sc = addr;
	
	switch(enc){
	case AUDIO_ENCODING_ULAW:
		sc->encoding = AUDIO_ENCODING_ULAW;
		break;
	case AUDIO_ENCODING_LINEAR:
		sc->encoding = AUDIO_ENCODING_LINEAR;
		break;
	default:
		return (EINVAL);
	}
	return (0);
}

int
sbdsp_get_encoding(addr)
	void *addr;
{
	register struct sbdsp_softc *sc = addr;

	return(sc->encoding);
}

int
sbdsp_set_precision(addr, prec)
	void *addr;
	u_int prec;
{

	if (prec != 8)
		return(EINVAL);
	return(0);
}

int
sbdsp_get_precision(addr)
	void *addr;
{
	return(8);
}

int
sbdsp_set_channels(addr, chans)
	void *addr;
	int chans;
{
	register struct sbdsp_softc *sc = addr;
	int rval;

	if (ISSBPROCLASS(sc)) {
		if (chans != 1 && chans != 2)
			return(EINVAL);

		sc->sc_chans = chans;
		if (rval = sbdsp_set_in_sr_real(addr, sc->sc_irate))
		    return rval;
		sbdsp_mix_write(sc, SBP_STEREO,
				(sbdsp_mix_read(sc, SBP_STEREO) & ~SBP_PLAYMODE_MASK) |
				(chans == 2 ? SBP_PLAYMODE_STEREO : SBP_PLAYMODE_MONO));
		/* recording channels needs to be done right when we start
		   DMA recording.  Just record number of channels for now
		   and set stereo when ready. */
	}
	else {
		if (chans != 1)
			return(EINVAL);
		sc->sc_chans = 1;
	}
	
	return(0);
}

int
sbdsp_get_channels(addr)
	void *addr;
{
	register struct sbdsp_softc *sc = addr;
	
#if 0
	/* recording stereo may frob the mixer output */
	if (ISSBPROCLASS(sc)) {
		if ((sbdsp_mix_read(sc, SBP_STEREO) & SBP_PLAYMODE_MASK) == SBP_PLAYMODE_STEREO) {
			sc->sc_chans = 2;
		}
		else {
			sc->sc_chans = 1;
		}
	}
	else {
		sc->sc_chans = 1;
	}
#endif

	return(sc->sc_chans);
}

int
sbdsp_set_out_port(addr, port)
	void *addr;
	int port;
{
	register struct sbdsp_softc *sc = addr;
	
	sc->out_port = port; /* Just record it */

	return(0);
}

int
sbdsp_get_out_port(addr)
	void *addr;
{
	register struct sbdsp_softc *sc = addr;

	return(sc->out_port);
}


int
sbdsp_set_in_port(addr, port)
	void *addr;
	int port;
{
	register struct sbdsp_softc *sc = addr;
	int mixport, sbport;
	
	switch (port) {
	case SB_MIC_PORT:
	    sbport = SBP_FROM_MIC;
	    mixport = SBP_MIC_VOL;
	    break;
	}
	
	if (ISSBPROCLASS(sc)) {
	    switch (port) {
	    case SB_LINE_IN_PORT:
		sbport = SBP_FROM_LINE;
		mixport = SBP_LINE_VOL;
		break;
	    case SB_CD_PORT:
		sbport = SBP_FROM_CD;
		mixport = SBP_CD_VOL;
		break;
	    case SB_DAC_PORT:
	    case SB_FM_PORT:
	    default:
		return(EINVAL);
		/*NOTREACHED*/
	    }
	}
	else {
	    return(EINVAL);
	    /*NOTREACHED*/
	}	    

	sc->in_port = port;	/* Just record it */

	if (ISSBPROCLASS(sc)) {
		/* record from that port */
		sbdsp_mix_write(sc, SBP_RECORD_SOURCE,
				SBP_RECORD_FROM(sbport, SBP_FILTER_OFF,
						SBP_FILTER_HIGH));
		/* fetch gain from that port */
		sc->gain[port] = sbdsp_mix_read(sc, mixport);
	}

	return(0);
}

int
sbdsp_get_in_port(addr)
	void *addr;
{
	register struct sbdsp_softc *sc = addr;

	return(sc->in_port);
}


int
sbdsp_speaker_ctl(addr, newstate)
	void *addr;
	int newstate;
{
	register struct sbdsp_softc *sc = addr;

	if ((newstate == SPKR_ON) &&
	    (sc->spkr_state == SPKR_OFF)) {
		sbdsp_spkron(sc);
		sc->spkr_state = SPKR_ON;
	}
	if ((newstate == SPKR_OFF) &&
	    (sc->spkr_state == SPKR_ON)) {
		sbdsp_spkroff(sc);
		sc->spkr_state = SPKR_OFF;
	}
	return(0);
}

int
sbdsp_round_blocksize(addr, blk)
	void *addr;
	int blk;
{
	register struct sbdsp_softc *sc = addr;

	sc->sc_last_hsr_size = sc->sc_last_hsw_size = 0;

	/* Higher speeds need bigger blocks to avoid popping and silence gaps. */
	if ((sc->sc_orate > 8000 || sc->sc_irate > 8000) &&
	    (blk > NBPG/2 || blk < NBPG/4))
		blk = NBPG/2;
	/* don't try to DMA too much at once, though. */
	if (blk > NBPG) blk = NBPG;
	if (sc->sc_chans == 2)
		return (blk & ~1); /* must be even to preserve stereo separation */
	else
		return(blk);	/* Anything goes :-) */
}

int
sbdsp_commit_settings(addr)
	void *addr;
{
	/* due to potentially unfortunate ordering in the above layers,
	   re-do a few sets which may be important--input gains
	   (adjust the proper channels), number of input channels (hit the
	   record rate and set mode) */

	register struct sbdsp_softc *sc = addr;

	sbdsp_set_out_sr_real(addr, sc->sc_orate);
	sbdsp_set_in_sr_real(addr, sc->sc_irate);

	sc->sc_last_hsw_size = sc->sc_last_hsr_size = 0;
	return(0);
}


int
sbdsp_open(sc, dev, flags)
	register struct sbdsp_softc *sc;
	dev_t dev;
	int flags;
{
        DPRINTF(("sbdsp_open: sc=0x%x\n", sc));

	if (sc->sc_open != 0 || sbdsp_reset(sc) != 0)
		return ENXIO;

	sc->sc_open = 1;
	sc->sc_mintr = 0;
	sc->sc_intr = 0;
	sc->sc_arg = 0;
	sc->sc_locked = 0;
	if (ISSBPROCLASS(sc) &&
	    sbdsp_wdsp(sc->sc_iobase, SB_DSP_RECORD_MONO) < 0) {
		DPRINTF(("sbdsp_open: can't set mono mode\n"));
		/* we'll readjust when it's time for DMA. */
	}
	sc->sc_dmain_inprogress = 0;
	sc->sc_dmaout_inprogress = 0;

	/*
	 * Leave most things as they were; users must change things if
	 * the previous process didn't leave it they way they wanted.
	 * Looked at another way, it's easy to set up a configuration
	 * in one program and leave it for another to inherit.
	 */
	DPRINTF(("sbdsp_open: opened\n"));

	return 0;
}

void
sbdsp_close(addr)
	void *addr;
{
	struct sbdsp_softc *sc = addr;

        DPRINTF(("sbdsp_close: sc=0x%x\n", sc));

	sc->sc_open = 0;
	sbdsp_spkroff(sc);
	sc->spkr_state = SPKR_OFF;
	sc->sc_intr = 0;
	sc->sc_mintr = 0;
	/* XXX this will turn off any dma */
	sbdsp_reset(sc);

	DPRINTF(("sbdsp_close: closed\n"));
}

/*
 * Lower-level routines
 */

/*
 * Reset the card.
 * Return non-zero if the card isn't detected.
 */
int
sbdsp_reset(sc)
	register struct sbdsp_softc *sc;
{
	register u_short iobase = sc->sc_iobase;

	/*
	 * erase any memory of last transfer size.
	 */
	sc->sc_last_hsr_size = sc->sc_last_hsw_size = 0;
	/*
	 * See SBK, section 11.3.
	 * We pulse a reset signal into the card.
	 * Gee, what a brilliant hardware design.
	 */
	outb(iobase + SBP_DSP_RESET, 1);
	delay(3);
	outb(iobase + SBP_DSP_RESET, 0);
	if (sbdsp_rdsp(iobase) != SB_MAGIC)
		return -1;
	return 0;
}

/*
 * Write a byte to the dsp.
 * XXX We are at the mercy of the card as we use a
 * polling loop and wait until it can take the byte.
 */
int
sbdsp_wdsp(u_short iobase, int v)
{
	register int i;

	for (i = SBDSP_NPOLL; --i >= 0; ) {
		if ((inb(iobase + SBP_DSP_WSTAT) & SB_DSP_BUSY) != 0) {
			delay(10); continue;
		}
		outb(iobase + SBP_DSP_WRITE, v);
		return 0;
	}
	++sberr.wdsp;
	return -1;
}

/*
 * Read a byte from the DSP, using polling.
 */
int
sbdsp_rdsp(u_short iobase)
{
	register int i;

	for (i = SBDSP_NPOLL; --i >= 0; ) {
		if ((inb(iobase + SBP_DSP_RSTAT) & SB_DSP_READY) == 0)
			continue;
		return inb(iobase + SBP_DSP_READ);
	}
	++sberr.rdsp;
	return -1;
}

/*
 * Doing certain things (like toggling the speaker) make
 * the SB hardware go away for a while, so pause a little.
 */
void
sbdsp_to(arg)
	void *arg;
{
	wakeup(arg);
}

void
sbdsp_pause(sc)
	struct sbdsp_softc *sc;
{
	extern int hz;

	timeout(sbdsp_to, sbdsp_to, hz/8);
	(void)tsleep(sbdsp_to, PWAIT, "sbpause", 0);
}

/*
 * Turn on the speaker.  The SBK documention says this operation
 * can take up to 1/10 of a second.  Higher level layers should
 * probably let the task sleep for this amount of time after
 * calling here.  Otherwise, things might not work (because
 * sbdsp_wdsp() and sbdsp_rdsp() will probably timeout.)
 *
 * These engineers had their heads up their ass when
 * they designed this card.
 */
void
sbdsp_spkron(sc)
	struct sbdsp_softc *sc;
{
	(void)sbdsp_wdsp(sc->sc_iobase, SB_DSP_SPKR_ON);
	sbdsp_pause(sc);
}

/*
 * Turn off the speaker; see comment above.
 */
void
sbdsp_spkroff(sc)
	struct sbdsp_softc *sc;
{
	(void)sbdsp_wdsp(sc->sc_iobase, SB_DSP_SPKR_OFF);
	sbdsp_pause(sc);
}

/*
 * Read the version number out of the card.  Return major code
 * in high byte, and minor code in low byte.
 */
short
sbversion(sc)
	struct sbdsp_softc *sc;
{
	register u_short iobase = sc->sc_iobase;
	short v;

	if (sbdsp_wdsp(iobase, SB_DSP_VERSION) < 0)
		return 0;
	v = sbdsp_rdsp(iobase) << 8;
	v |= sbdsp_rdsp(iobase);
	return ((v >= 0) ? v : 0);
}

/*
 * Halt a DMA in progress.  A low-speed transfer can be
 * resumed with sbdsp_contdma().
 */
int
sbdsp_haltdma(addr)
	void *addr;
{
	register struct sbdsp_softc *sc = addr;

	DPRINTF(("sbdsp_haltdma: sc=0x%x\n", sc));

	if (sc->sc_locked)
		sbdsp_reset(sc);
	else
		(void)sbdsp_wdsp(sc->sc_iobase, SB_DSP_HALT);

	isa_dmaabort(sc->sc_drq);
	sc->dmaaddr = 0;
	sc->dmacnt = 0;
	sc->sc_locked = 0;
	sc->dmaflags = 0;
	sc->sc_dmain_inprogress = sc->sc_dmaout_inprogress = 0;
	return(0);
}

int
sbdsp_contdma(addr)
	void *addr;
{
	register struct sbdsp_softc *sc = addr;

	DPRINTF(("sbdsp_contdma: sc=0x%x\n", sc));

	/* XXX how do we reinitialize the DMA controller state?  do we care? */
	(void)sbdsp_wdsp(sc->sc_iobase, SB_DSP_CONT);
	return(0);
}

/*
 * Time constant routines follow.  See SBK, section 12.
 * Although they don't come out and say it (in the docs),
 * the card clearly uses a 1MHz countdown timer, as the
 * low-speed formula (p. 12-4) is:
 *	tc = 256 - 10^6 / sr
 * In high-speed mode, the constant is the upper byte of a 16-bit counter,
 * and a 256MHz clock is used:
 *	tc = 65536 - 256 * 10^ 6 / sr
 * Since we can only use the upper byte of the HS TC, the two formulae
 * are equivalent.  (Why didn't they say so?)  E.g.,
 * 	(65536 - 256 * 10 ^ 6 / x) >> 8 = 256 - 10^6 / x
 *
 * The crossover point (from low- to high-speed modes) is different
 * for the SBPRO and SB20.  The table on p. 12-5 gives the following data:
 *
 *				SBPRO			SB20
 *				-----			--------
 * input ls min			4	KHz		4	KHz
 * input ls max			23	KHz		13	KHz
 * input hs max			44.1	KHz		15	KHz
 * output ls min		4	KHz		4	KHz
 * output ls max		23	KHz		23	KHz
 * output hs max		44.1	KHz		44.1	KHz
 */
#define SB_LS_MIN	0x06	/* 4000 Hz */
#define SBPRO_ADC_LS_MAX	0xd4	/* 22727 Hz */
#define SBPRO_ADC_HS_MAX	0xea	/* 45454 Hz */
#define SBCLA_ADC_LS_MAX	0xb3	/* 12987 Hz */
#define SBCLA_ADC_HS_MAX	0xbd	/* 14925 Hz */
#define SB_DAC_LS_MAX	0xd4	/* 22727 Hz */
#define SB_DAC_HS_MAX	0xea	/* 45454 Hz */

/*
 * Convert a linear sampling rate into the DAC time constant.
 * Set *mode to indicate the high/low-speed DMA operation.
 * Because of limitations of the card, not all rates are possible.
 * We return the time constant of the closest possible rate.
 * The sampling rate limits are different for the DAC and ADC,
 * so isdac indicates output, and !isdac indicates input.
 */
int
sbdsp_srtotc(sc, sr, mode, isdac)
	register struct sbdsp_softc *sc;
	int sr;
	int *mode;
	int isdac;
{
	int adc_ls_max, adc_hs_max;
	register int tc;

	if (sr == 0) {
		*mode = SB_ADAC_LS;
		return SB_LS_MIN;
	}
	tc = 256 - 1000000 / sr;
	
	/* XXX use better rounding--compare distance to nearest tc on both
	   sides of requested speed */
	if (ISSBPROCLASS(sc)) {
		adc_ls_max = SBPRO_ADC_LS_MAX;
		adc_hs_max = SBPRO_ADC_HS_MAX;
	}
	else {
		adc_ls_max = SBCLA_ADC_LS_MAX;
		adc_hs_max = SBCLA_ADC_HS_MAX;
	}
	    
	if (tc < SB_LS_MIN) {
		tc = SB_LS_MIN;
		*mode = SB_ADAC_LS;
	} else if (isdac) {
		if (tc <= SB_DAC_LS_MAX)
			*mode = SB_ADAC_LS;
		else {
			*mode = SB_ADAC_HS;
			if (tc > SB_DAC_HS_MAX)
				tc = SB_DAC_HS_MAX;
		}
	} else {
		if (tc <= adc_ls_max)
			*mode = SB_ADAC_LS;
		else {
			*mode = SB_ADAC_HS;
			if (tc > adc_hs_max)
				tc = adc_hs_max;
		}
	}
	return tc;
}

/*
 * Convert a DAC time constant to a sampling rate.
 * See SBK, section 12.
 */
int
sbdsp_tctosr(sc, tc)
	register struct sbdsp_softc *sc;
	int tc;
{
	int adc;

	if (ISSBPROCLASS(sc))
		adc = SBPRO_ADC_HS_MAX;
	else
		adc = SBCLA_ADC_HS_MAX;
	
	if (tc > adc)
		tc = adc;
	
	return (1000000 / (256 - tc));
}

int
sbdsp_set_sr(sc, srp, isdac)
	register struct sbdsp_softc *sc;
	u_long *srp;
	int isdac;
{
	register int tc;
	int mode;
	int sr = *srp;
	register u_short iobase;

	/*
	 * A SBPro in stereo mode uses time constants at double the
	 * actual rate.
	 */
	if (ISSBPRO(sc) && sc->sc_chans == 2) {
		if (sr > 22727)
			sr = 22727;	/* Can't bounce it...order of
					   operations may yield bogus
					   sr here. */
		sr *= 2;
	}
	else if (!ISSBPROCLASS(sc) && sc->sc_chans != 1)
		return EINVAL;

	tc = sbdsp_srtotc(sc, sr, &mode, isdac);
	DPRINTF(("sbdsp_set_sr: sc=0x%x sr=%d mode=0x%x\n", sc, sr, mode));

	iobase = sc->sc_iobase;
	if (sbdsp_wdsp(iobase, SB_DSP_TIMECONST) < 0 ||
	    sbdsp_wdsp(iobase, tc) < 0)
		return EIO;
	    
	sr = sbdsp_tctosr(sc, tc);
	if (ISSBPRO(sc) && sc->sc_chans == 2)
		*srp = sr / 2;
	else
		*srp = sr;

	sc->sc_adacmode = mode;
	sc->sc_adactc = tc;
	return 0;
}

int
sbdsp_dma_input(addr, p, cc, intr, arg)
	void *addr;
	void *p;
	int cc;
	void (*intr)();
	void *arg;
{
	register struct sbdsp_softc *sc = addr;
	register u_short iobase;
	u_int phys;
	
#ifdef DEBUG
	if (sbdspdebug > 1)
		Dprintf("sbdsp_dma_input: cc=%d 0x%x (0x%x)\n", cc, intr, arg);
#endif
	if (sc->sc_chans == 2 && (cc & 1)) {
		DPRINTF(("sbdsp_dma_input: stereo input, odd bytecnt\n"));
		return EIO;
	}
	iobase = sc->sc_iobase;
	if (ISSBPROCLASS(sc) && !sc->sc_dmain_inprogress) {
		if (sc->sc_chans == 2) {
			if (sbdsp_wdsp(iobase, SB_DSP_RECORD_STEREO) < 0)
				goto badmode;
			sbdsp_mix_write(sc, SBP_STEREO,
					sbdsp_mix_read(sc, SBP_STEREO) & ~SBP_PLAYMODE_MASK);
			sbdsp_mix_write(sc, SBP_INFILTER,
					sbdsp_mix_read(sc, SBP_INFILTER) | SBP_FILTER_OFF);
		}
		else {
			if (sbdsp_wdsp(iobase, SB_DSP_RECORD_MONO) < 0)
				goto badmode;
			sbdsp_mix_write(sc, SBP_STEREO,
					sbdsp_mix_read(sc, SBP_STEREO) & ~SBP_PLAYMODE_MASK);
			sbdsp_mix_write(sc, SBP_INFILTER,
					sc->sc_irate <= 8000 ? 
					sbdsp_mix_read(sc, SBP_INFILTER) & ~SBP_FILTER_MASK :
					sbdsp_mix_read(sc, SBP_INFILTER) | SBP_FILTER_OFF);
		}
		sc->sc_dmain_inprogress = 1;
		sc->sc_last_hsr_size = 0;	/* restarting */
	}
	sc->sc_dmaout_inprogress = 0;

	at_dma(B_READ, p, cc, sc->sc_drq);
	sc->sc_intr = intr;
	sc->sc_arg = arg;
	sc->dmaflags = B_READ;
	sc->dmaaddr = p;
	sc->dmacnt = --cc;		/* DMA controller is strange...? */
	if (sc->sc_adacmode == SB_ADAC_LS) {
		if (sbdsp_wdsp(iobase, SB_DSP_RDMA) < 0 ||
		    sbdsp_wdsp(iobase, cc) < 0 ||
		    sbdsp_wdsp(iobase, cc >> 8) < 0) {
			goto giveup;
		}
	}
	else {
		if (cc != sc->sc_last_hsr_size) {
			if (sbdsp_wdsp(iobase, SB_DSP_BLOCKSIZE) < 0 ||
			    sbdsp_wdsp(iobase, cc) < 0 ||
			    sbdsp_wdsp(iobase, cc >> 8) < 0)
				goto giveup;
		}
		if (sbdsp_wdsp(iobase, SB_DSP_HS_INPUT) < 0)
			goto giveup;
		sc->sc_last_hsr_size = cc;
		sc->sc_locked = 1;
	}
	return 0;

giveup:
	isa_dmaabort(sc->sc_drq);
	sbdsp_reset(sc);
	sc->sc_intr = 0;
	sc->sc_arg = 0;
	return EIO;
badmode:
	DPRINTF(("sbdsp_dma_input: can't set %s mode\n",
		 sc->sc_chans == 2 ? "stereo" : "mono"));
	return EIO;
}

int
sbdsp_dma_output(addr, p, cc, intr, arg)
	void *addr;
	void *p;
	int cc;
	void (*intr)();
	void *arg;
{
	register struct sbdsp_softc *sc = addr;
	register u_short iobase;
	
#ifdef DEBUG
	if (sbdspdebug > 1)
		Dprintf("sbdsp_dma_output: cc=%d 0x%x (0x%x)\n", cc, intr, arg);
#endif
	if (sc->sc_chans == 2 && cc & 1) {
		DPRINTF(("stereo playback odd bytes (%d)\n", cc));
		return EIO;
	}

	if (ISSBPROCLASS(sc) && !sc->sc_dmaout_inprogress) {
		/* make sure we re-set stereo mixer bit when we start
		   output. */
		sbdsp_mix_write(sc, SBP_STEREO,
				(sbdsp_mix_read(sc, SBP_STEREO) & ~SBP_PLAYMODE_MASK) |
				(sc->sc_chans == 2 ?
				 SBP_PLAYMODE_STEREO : SBP_PLAYMODE_MONO));
		sc->sc_dmaout_inprogress = 1;
		sc->sc_last_hsw_size = 0;	/* restarting */
	}
	sc->sc_dmain_inprogress = 0;
	at_dma(B_WRITE, p, cc, sc->sc_drq);
	sc->sc_intr = intr;
	sc->sc_arg = arg;
	sc->dmaflags = B_WRITE;
	sc->dmaaddr = p;
	sc->dmacnt = --cc;	/* a vagary of how DMA works, apparently. */

	iobase = sc->sc_iobase;
	if (sc->sc_adacmode == SB_ADAC_LS) {
		if (sbdsp_wdsp(iobase, SB_DSP_WDMA) < 0 ||
		    sbdsp_wdsp(iobase, cc) < 0 ||
		    sbdsp_wdsp(iobase, cc >> 8) < 0) {
		        DPRINTF(("sbdsp_dma_output: LS DMA start failed\n"));
			goto giveup;
		}
	}
	else {
		if (cc != sc->sc_last_hsw_size) {
			if (sbdsp_wdsp(iobase, SB_DSP_BLOCKSIZE) < 0) {
				/* sometimes fails initial startup?? */
				delay(100);
				if (sbdsp_wdsp(iobase, SB_DSP_BLOCKSIZE) < 0) {
					DPRINTF(("sbdsp_dma_output: BLOCKSIZE failed\n"));
					goto giveup;
				}
			}
			if (sbdsp_wdsp(iobase, cc) < 0 ||
			    sbdsp_wdsp(iobase, cc >> 8) < 0) {
				DPRINTF(("sbdsp_dma_output: HS DMA start failed\n"));
				goto giveup;
			}
			sc->sc_last_hsw_size = cc;
		}
		if (sbdsp_wdsp(iobase, SB_DSP_HS_OUTPUT) < 0) {
			delay(100);
			if (sbdsp_wdsp(iobase, SB_DSP_HS_OUTPUT) < 0) {
				DPRINTF(("sbdsp_dma_output: HS DMA restart failed\n"));
				goto giveup;
			}
		}
		sc->sc_locked = 1;
	}

	return 0;

 giveup:
	isa_dmaabort(sc->sc_drq);
	sbdsp_reset(sc);
	sc->sc_intr = 0;
	sc->sc_arg = 0;
	return EIO;
}

/*
 * Only the DSP unit on the sound blaster generates interrupts.
 * There are three cases of interrupt: reception of a midi byte
 * (when mode is enabled), completion of dma transmission, or 
 * completion of a dma reception.  The three modes are mutually
 * exclusive so we know a priori which event has occurred.
 */
int
sbdsp_intr(sc)
	register struct sbdsp_softc *sc;
{

#ifdef DEBUG
	if (sbdspdebug > 1)
		Dprintf("sbdsp_intr: intr=0x%x\n", sc->sc_intr);
#endif
	sc->sc_interrupts++;
	sc->sc_locked = 0;
	/* clear interrupt */
	inb(sc->sc_iobase + SBP_DSP_RSTAT);
#if 0
	if (sc->sc_mintr != 0) {
		int c = sbdsp_rdsp(sc->sc_iobase);
		(*sc->sc_mintr)(sc->sc_arg, c);
	} else
#endif
	if (sc->sc_intr != 0) {
	    /*
	     * The SBPro used to develop and test this driver often
	     * generated dma underruns--it interrupted to signal
	     * completion of the DMA input recording block, but the
	     * ISA DMA controller didn't think the channel was
	     * finished.  Maybe this is just a bus speed issue, I dunno,
	     * but it seems strange and leads to channel-flipping with stereo
	     * recording.  Sigh.
	     */
		isa_dmadone(sc->dmaflags, sc->dmaaddr, sc->dmacnt,
			    sc->sc_drq);
		sc->dmaflags = 0;
		sc->dmaaddr = 0;
		sc->dmacnt = 0;
		(*sc->sc_intr)(sc->sc_arg);
	}
	else
		return 0;
	return 1;
}

#if 0
/*
 * Enter midi uart mode and arrange for read interrupts
 * to vector to `intr'.  This puts the card in a mode
 * which allows only midi I/O; the card must be reset
 * to leave this mode.  Unfortunately, the card does not
 * use transmit interrupts, so bytes must be output
 * using polling.  To keep the polling overhead to a
 * minimum, output should be driven off a timer.
 * This is a little tricky since only 320us separate
 * consecutive midi bytes.
 */
void
sbdsp_set_midi_mode(sc, intr, arg)
	struct sbdsp_softc *sc;
	void (*intr)();
	void *arg;
{

	sbdsp_wdsp(sc->sc_iobase, SB_MIDI_UART_INTR);
	sc->sc_mintr = intr;
	sc->sc_intr = 0;
	sc->sc_arg = arg;
}

/*
 * Write a byte to the midi port, when in midi uart mode.
 */
void
sbdsp_midi_output(sc, v)
	struct sbdsp_softc *sc;
	int v;
{

	if (sbdsp_wdsp(sc->sc_iobase, v) < 0)
		++sberr.wmidi;
}
#endif

u_int
sbdsp_get_silence(enc)
    int enc;
{
#define ULAW_SILENCE	0x7f
#define LINEAR_SILENCE	0
    u_int auzero;
    
    switch (enc) {
    case AUDIO_ENCODING_ULAW:
	auzero = ULAW_SILENCE; 
	break;
    case AUDIO_ENCODING_PCM16:
    default:
	auzero = LINEAR_SILENCE;
	break;
    }

    return(auzero);
}

static u_char mulawtolin[256] = {
	128, 4, 8, 12, 16, 20, 24, 28, 
	32, 36, 40, 44, 48, 52, 56, 60, 
	64, 66, 68, 70, 72, 74, 76, 78, 
	80, 82, 84, 86, 88, 90, 92, 94, 
	96, 97, 98, 99, 100, 101, 102, 103, 
	104, 105, 106, 107, 108, 109, 110, 111, 
	112, 112, 113, 113, 114, 114, 115, 115, 
	116, 116, 117, 117, 118, 118, 119, 119, 
	120, 120, 120, 121, 121, 121, 121, 122, 
	122, 122, 122, 123, 123, 123, 123, 124, 
	124, 124, 124, 124, 125, 125, 125, 125, 
	125, 125, 125, 125, 126, 126, 126, 126, 
	126, 126, 126, 126, 126, 126, 126, 126, 
	127, 127, 127, 127, 127, 127, 127, 127, 
	127, 127, 127, 127, 127, 127, 127, 127, 
	127, 127, 127, 127, 127, 127, 127, 127, 
	255, 251, 247, 243, 239, 235, 231, 227, 
	223, 219, 215, 211, 207, 203, 199, 195, 
	191, 189, 187, 185, 183, 181, 179, 177, 
	175, 173, 171, 169, 167, 165, 163, 161, 
	159, 158, 157, 156, 155, 154, 153, 152, 
	151, 150, 149, 148, 147, 146, 145, 144, 
	143, 143, 142, 142, 141, 141, 140, 140, 
	139, 139, 138, 138, 137, 137, 136, 136, 
	135, 135, 135, 134, 134, 134, 134, 133, 
	133, 133, 133, 132, 132, 132, 132, 131, 
	131, 131, 131, 131, 130, 130, 130, 130, 
	130, 130, 130, 130, 129, 129, 129, 129, 
	129, 129, 129, 129, 129, 129, 129, 129, 
	128, 128, 128, 128, 128, 128, 128, 128, 
	128, 128, 128, 128, 128, 128, 128, 128, 
	128, 128, 128, 128, 128, 128, 128, 128, 
};

static u_char lintomulaw[256] = {
	0, 0, 0, 0, 0, 1, 1, 1, 
	1, 2, 2, 2, 2, 3, 3, 3, 
	3, 4, 4, 4, 4, 5, 5, 5, 
	5, 6, 6, 6, 6, 7, 7, 7, 
	7, 8, 8, 8, 8, 9, 9, 9, 
	9, 10, 10, 10, 10, 11, 11, 11, 
	11, 12, 12, 12, 12, 13, 13, 13, 
	13, 14, 14, 14, 14, 15, 15, 15, 
	15, 16, 16, 17, 17, 18, 18, 19, 
	19, 20, 20, 21, 21, 22, 22, 23, 
	23, 24, 24, 25, 25, 26, 26, 27, 
	27, 28, 28, 29, 29, 30, 30, 31, 
	31, 32, 33, 34, 35, 36, 37, 38, 
	39, 40, 41, 42, 43, 44, 45, 46, 
	47, 48, 50, 52, 54, 56, 58, 60, 
	62, 65, 69, 73, 77, 83, 91, 103, 
	255, 231, 219, 211, 205, 201, 197, 193, 
	190, 188, 186, 184, 182, 180, 178, 176, 
	175, 174, 173, 172, 171, 170, 169, 168, 
	167, 166, 165, 164, 163, 162, 161, 160, 
	159, 159, 158, 158, 157, 157, 156, 156, 
	155, 155, 154, 154, 153, 153, 152, 152, 
	151, 151, 150, 150, 149, 149, 148, 148, 
	147, 147, 146, 146, 145, 145, 144, 144, 
	143, 143, 143, 143, 142, 142, 142, 142, 
	141, 141, 141, 141, 140, 140, 140, 140, 
	139, 139, 139, 139, 138, 138, 138, 138, 
	137, 137, 137, 137, 136, 136, 136, 136, 
	135, 135, 135, 135, 134, 134, 134, 134, 
	133, 133, 133, 133, 132, 132, 132, 132, 
	131, 131, 131, 131, 130, 130, 130, 130, 
	129, 129, 129, 129, 128, 128, 128, 128, 
};

void
sbdsp_compress(e, p, cc)
	int e;
	u_char *p;
	int cc;
{
	u_char *tab;

	switch (e) {
	case AUDIO_ENCODING_ULAW:
		tab = lintomulaw;
		break;
	default:
		return;
	}

	while (--cc >= 0) {
		*p = tab[*p];
		++p;
	}
}

void
sbdsp_expand(e, p, cc)
	int e;
	u_char *p;
	int cc;
{
	u_char *tab;

	switch (e) {
	case AUDIO_ENCODING_ULAW:
		tab = mulawtolin;
		break;
	default:
		return;
	}
	
	while (--cc >= 0) {
		*p = tab[*p];
		++p;
	}
}

int
sbdsp_setfd(addr, flag)
	void *addr;
	int flag;
{
	/* Can't do full-duplex */
	return(ENOTTY);
}

int
sbdsp_mixer_set_port(addr, cp)
    void *addr;
    mixer_ctrl_t *cp;
{
    register struct sbdsp_softc *sc = addr;
    int error = 0;
    int src, gain;
    int left, right;
    
    DPRINTF(("sbdsp_mixer_set_port: port=%d num_channels=%d\n", cp->dev, cp->un.value.num_channels));

    /*
     * Everything is a value except for SBPro special OUTPUT_MODE and
     * RECORD_SOURCE
     */
    if (cp->type != AUDIO_MIXER_VALUE) {
	if (!ISSBPROCLASS(sc) || (cp->dev != SB_OUTPUT_MODE &&
				  cp->dev != SB_RECORD_SOURCE))
	    return EINVAL;
    }
    else {
	/*
	 * All the mixer ports are stereo except for the microphone.
	 * If we get a single-channel gain value passed in, then we
	 * duplicate it to both left and right channels.
	 */
    if (cp->un.value.num_channels == 2) {
	left  = cp->un.value.level[AUDIO_MIXER_LEVEL_LEFT];
	right = cp->un.value.level[AUDIO_MIXER_LEVEL_RIGHT];
    }
    else
	    left = right = cp->un.value.level[AUDIO_MIXER_LEVEL_MONO];
    }    
    
    if (ISSBPROCLASS(sc)) {
	/* The _PORT things are all signal inputs to the mixer.
	 * Here we are tweaking their mixing level.
	 *
	 * We can also tweak the output stage volume (MASTER_VOL)
	 */
	gain = sbdsp_stereo_vol(SBP_AGAIN_TO_SBGAIN(left),
				SBP_AGAIN_TO_SBGAIN(right));
	switch(cp->dev) {
        case SB_MIC_PORT:
	    src = SBP_MIC_VOL;
	    if (cp->un.value.num_channels != 1)
		error = EINVAL;
	    else
		/* handle funny microphone gain */
		gain = SBP_AGAIN_TO_MICGAIN(left);
	    break;
        case SB_LINE_IN_PORT:
	    src = SBP_LINE_VOL;
	    break;
        case SB_DAC_PORT:
	    src = SBP_DAC_VOL;
	    break;
        case SB_FM_PORT:
	    src = SBP_FM_VOL;
	    break;
        case SB_CD_PORT:
	    src = SBP_CD_VOL;
	    break;
	case SB_SPEAKER:
	    cp->dev = SB_MASTER_VOL;
        case SB_MASTER_VOL:
	    src = SBP_MASTER_VOL;
	    break;
#if 0
	case SB_OUTPUT_MODE:
	    if (cp->type == AUDIO_MIXER_ENUM)
		return sbdsp_set_channels(addr, cp->un.ord);
	    /* fall through...carefully! */
#endif
	case SB_RECORD_SOURCE:
	    if (cp->type == AUDIO_MIXER_ENUM)
		return sbdsp_set_in_port(addr, cp->un.ord);
	    /* else fall through: bad input */
        case SB_TREBLE:
        case SB_BASS:
        default:
	    error =  EINVAL;
	    break;
	}
	if (!error)
	sbdsp_mix_write(sc, src, gain);
    }    
    else if (cp->dev != SB_MIC_PORT &&
	     cp->dev != SB_SPEAKER)
	error = EINVAL;

    if (!error)
	sc->gain[cp->dev] = gain;

    return(error);
}

int
sbdsp_mixer_get_port(addr, cp)
    void *addr;
    mixer_ctrl_t *cp;
{
    register struct sbdsp_softc *sc = addr;
    int error = 0;
    int done = 0;
    
    DPRINTF(("sbdsp_mixer_get_port: port=%d", cp->dev));

    if (ISSBPROCLASS(sc))
    switch(cp->dev) {
    case SB_MIC_PORT:
	    if (cp->un.value.num_channels == 1) {
		cp->un.value.level[AUDIO_MIXER_LEVEL_MONO] =
		    SBP_MICGAIN_TO_AGAIN(sc->gain[cp->dev]);
		return 0;
    }
	    else
		return EINVAL;
	    break;
	case SB_LINE_IN_PORT:
        case SB_DAC_PORT:
        case SB_FM_PORT:
        case SB_CD_PORT:
        case SB_MASTER_VOL:
	    break;
	case SB_SPEAKER:
	    cp->dev = SB_MASTER_VOL;
	    break;
        default:
	    error =  EINVAL;
	    break;
	}
    else {
	if (cp->un.value.num_channels != 1) /* no stereo on SB classic */
	    error = EINVAL;
    else
	    switch(cp->dev) {
	    case SB_MIC_PORT:
		break;
	    case SB_SPEAKER:
		break;
	    default:
	error = EINVAL;
		break;
	    }
    }
    if (error == 0) {
	if (cp->un.value.num_channels == 1) {
	    cp->un.value.level[AUDIO_MIXER_LEVEL_MONO] =
		SBP_SBGAIN_TO_AGAIN(sc->gain[cp->dev]);
	}
	else if (cp->un.value.num_channels == 2) {
	    cp->un.value.level[AUDIO_MIXER_LEVEL_LEFT] =
		SBP_LEFTGAIN(sc->gain[cp->dev]);
	    cp->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] =
		SBP_RIGHTGAIN(sc->gain[cp->dev]);
	} else
	    return EINVAL;
    }
    return(error);
}

int
sbdsp_mixer_query_devinfo(addr, dip)
    void *addr;
    register mixer_devinfo_t *dip;
{
    register struct sbdsp_softc *sc = addr;
    int done = 0;

    DPRINTF(("sbdsp_mixer_query_devinfo: index=%d\n", dip->index));

    switch (dip->index) {
    case SB_MIC_PORT:
	dip->type = AUDIO_MIXER_VALUE;
	dip->mixer_class = SB_INPUT_CLASS;
	dip->prev = AUDIO_MIXER_LAST;
	dip->next = AUDIO_MIXER_LAST;
	strcpy(dip->label.name, AudioNmicrophone);
	dip->un.v.num_channels = 1;
	strcpy(dip->un.v.units.name, AudioNvolume);
	done = 1;
	break;
    case SB_SPEAKER:
	dip->type = AUDIO_MIXER_VALUE;
	dip->mixer_class = SB_OUTPUT_CLASS;
	dip->prev = AUDIO_MIXER_LAST;
	dip->next = AUDIO_MIXER_LAST;
	strcpy(dip->label.name, AudioNspeaker);
	dip->un.v.num_channels = 1;
	strcpy(dip->un.v.units.name, AudioNvolume);
	done = 1;
	break;
    case SB_INPUT_CLASS:
	dip->type = AUDIO_MIXER_CLASS;
	dip->mixer_class = SB_INPUT_CLASS;
	dip->next = dip->prev = AUDIO_MIXER_LAST;
	strcpy(dip->label.name, AudioCInputs);
	done = 1;
	break;
    case SB_OUTPUT_CLASS:
	dip->type = AUDIO_MIXER_CLASS;
	dip->mixer_class = SB_OUTPUT_CLASS;
	dip->next = dip->prev = AUDIO_MIXER_LAST;
	strcpy(dip->label.name, AudioCOutputs);
	done = 1;
	break;
    }

    if (!done) {
    if (ISSBPROCLASS(sc))
	switch(dip->index) {
	case SB_LINE_IN_PORT:
	    dip->type = AUDIO_MIXER_VALUE;
	    dip->mixer_class = SB_INPUT_CLASS;
	    dip->prev = AUDIO_MIXER_LAST;
	    dip->next = AUDIO_MIXER_LAST;
	    strcpy(dip->label.name, AudioNline);
	    dip->un.v.num_channels = 2;
	    strcpy(dip->un.v.units.name, AudioNvolume);
	    break;
	case SB_DAC_PORT:
	    dip->type = AUDIO_MIXER_VALUE;
	    dip->mixer_class = SB_INPUT_CLASS;
	    dip->prev = AUDIO_MIXER_LAST;
	    dip->next = AUDIO_MIXER_LAST;
	    strcpy(dip->label.name, AudioNdac);
	    dip->un.v.num_channels = 2;
	    strcpy(dip->un.v.units.name, AudioNvolume);
	    break;
	case SB_CD_PORT:
	    dip->type = AUDIO_MIXER_VALUE;
	    dip->mixer_class = SB_INPUT_CLASS;
	    dip->prev = AUDIO_MIXER_LAST;
	    dip->next = AUDIO_MIXER_LAST;
	    strcpy(dip->label.name, AudioNcd);
	    dip->un.v.num_channels = 2;
	    strcpy(dip->un.v.units.name, AudioNvolume);
	    break;
	case SB_FM_PORT:
	    dip->type = AUDIO_MIXER_VALUE;
	    dip->mixer_class = SB_INPUT_CLASS;
	    dip->prev = AUDIO_MIXER_LAST;
	    dip->next = AUDIO_MIXER_LAST;
	    strcpy(dip->label.name, "fmsynth");	/* XXX move to audioio.h */
	    dip->un.v.num_channels = 2;
	    strcpy(dip->un.v.units.name, AudioNvolume);
	    break;
	case SB_MASTER_VOL:
	    dip->type = AUDIO_MIXER_VALUE;
	    dip->mixer_class = SB_OUTPUT_CLASS;
	    dip->prev = AUDIO_MIXER_LAST;
	    dip->next = SB_OUTPUT_MODE;
	    strcpy(dip->label.name, AudioNvolume);
	    dip->un.v.num_channels = 2;
	    strcpy(dip->un.v.units.name, AudioNvolume);
	    break;
#if 0
	case SB_OUTPUT_MODE:
	    dip->mixer_class = SB_OUTPUT_CLASS;
	    dip->type = AUDIO_MIXER_ENUM;
	    dip->prev = SB_MASTER_VOL;
	    dip->next = AUDIO_MIXER_LAST;
	    strcpy(dip->label.name, AudioNmode);
	    dip->un.e.num_mem = 2;
	    strcpy(dip->un.e.member[0].label.name, AudioNmono);
	    dip->un.e.member[0].ord = 1; /* nchans */
	    strcpy(dip->un.e.member[1].label.name, AudioNstereo);
	    dip->un.e.member[1].ord = 2; /* nchans */
	    break;
#endif
	case SB_RECORD_SOURCE:
	    dip->mixer_class = SB_RECORD_CLASS;
	    dip->type = AUDIO_MIXER_ENUM;
	    dip->prev = AUDIO_MIXER_LAST;
	    dip->next = AUDIO_MIXER_LAST;
	    strcpy(dip->label.name, AudioNsource);
	    dip->un.e.num_mem = 3;
	    strcpy(dip->un.e.member[0].label.name, AudioNmicrophone);
	    dip->un.e.member[0].ord = SB_MIC_PORT;
	    strcpy(dip->un.e.member[1].label.name, AudioNcd);
	    dip->un.e.member[1].ord = SB_CD_PORT;
	    strcpy(dip->un.e.member[2].label.name, AudioNline);
	    dip->un.e.member[2].ord = SB_LINE_IN_PORT;
	    break;
	case SB_BASS:
	case SB_TREBLE:
	default:
	    return ENXIO;
	    /*NOTREACHED*/
	} 
    else
	return ENXIO;
    }

    DPRINTF(("AUDIO_MIXER_DEVINFO: name=%s\n", dip->label.name));

    return 0;
}
