shithub: riscv

Download patch

ref: 7aee021b1327cd31279c0215475fa9eccb15fddc
parent: 258a072d95cc8324dfeeb8e9d9a347a92422a844
author: cinap_lenrek <cinap_lenrek@localhost>
date: Mon May 16 10:28:00 EDT 2011

first attempt to port old sb16/ess driver to new audio layer

--- /dev/null
+++ b/sys/src/9/pc/audiosb16.c
@@ -1,0 +1,1001 @@
+/*
+ *	SB 16 driver
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+#include "../port/audio.h"
+
+typedef struct	AQueue	AQueue;
+typedef struct	Buf	Buf;
+
+enum
+{
+	Fmono		= 1,
+	Fin		= 2,
+	Fout		= 4,
+
+	Vaudio		= 0,
+	Vsynth,
+	Vcd,
+	Vline,
+	Vmic,
+	Vspeaker,
+	Vtreb,
+	Vbass,
+	Vspeed,
+	Nvol,
+
+	Speed		= 44100,
+	Ncmd		= 50,		/* max volume command words */
+};
+
+enum
+{
+	Bufsize	= 1024,	/* 5.8 ms each, must be power of two */
+	Nbuf		= 128,	/* .74 seconds total */
+	Dma		= 6,
+	IrqAUDIO	= 7,
+	SBswab	= 0,
+};
+
+#define CACHELINESZ		8
+#define UNCACHED(type, v)	(type*)((ulong)(v))
+
+struct	Buf
+{
+	uchar*	virt;
+	ulong	phys;
+	Buf*	next;
+};
+
+struct	AQueue
+{
+	Lock;
+	Buf*	first;
+	Buf*	last;
+};
+
+static	struct
+{
+	QLock;
+	Rendez	vous;
+	int	buffered;		/* number of bytes en route */
+	int	bufinit;		/* boolean if buffers allocated */
+	int	curcount;		/* how much data in current buffer */
+	int	active;		/* boolean dma running */
+	int	intr;			/* boolean an interrupt has happened */
+	int	rivol[Nvol];	/* right/left input/output volumes */
+	int	livol[Nvol];
+	int	rovol[Nvol];
+	int	lovol[Nvol];
+	int	major;		/* SB16 major version number (sb 4) */
+	int	minor;		/* SB16 minor version number */
+	ulong	totcount;	/* how many bytes processed since open */
+	vlong	tottime;	/* time at which totcount bytes were processed */
+
+	Buf	buf[Nbuf];		/* buffers and queues */
+	AQueue	empty;
+	AQueue	full;
+	Buf*	current;
+	Buf*	filling;
+
+	int	probed;
+	int	ctlrno;
+} audio;
+
+static	struct
+{
+	char*	name;
+	int	flag;
+	int	ilval;		/* initial values */
+	int	irval;
+} volumes[] =
+{
+[Vaudio]		"audio",	Fout, 		50,	50,
+[Vsynth]		"synth",	Fin|Fout,	0,	0,
+[Vcd]		"cd",		Fin|Fout,	0,	0,
+[Vline]		"line",	Fin|Fout,	0,	0,
+[Vmic]		"mic",	Fin|Fout|Fmono,	0,	0,
+[Vspeaker]	"speaker",	Fout|Fmono,	0,	0,
+
+[Vtreb]		"treb",		Fout, 		50,	50,
+[Vbass]		"bass",		Fout, 		50,	50,
+
+[Vspeed]	"speed",	Fin|Fout|Fmono,	Speed,	Speed,
+		0
+};
+
+static struct
+{
+	Lock;
+	int	reset;		/* io ports to the sound blaster */
+	int	read;
+	int	write;
+	int	wstatus;
+	int	rstatus;
+	int	mixaddr;
+	int	mixdata;
+	int	clri8;
+	int	clri16;
+	int	clri401;
+	int	dma;
+
+	void	(*startdma)(void);
+	void	(*intr)(void);
+} blaster;
+
+static	void	swab(uchar*);
+
+static	char	Emajor[]	= "soundblaster not responding/wrong version";
+static	char	Emode[]		= "illegal open mode";
+static	char	Evolume[]	= "illegal volume specifier";
+
+static	int
+sbcmd(int val)
+{
+	int i, s;
+
+	for(i=1<<16; i!=0; i--) {
+		s = inb(blaster.wstatus);
+		if((s & 0x80) == 0) {
+			outb(blaster.write, val);
+			return 0;
+		}
+	}
+/*	print("#A%d: sbcmd (%#.2x) timeout\n", audio.ctlrno, val);	/**/
+	return 1;
+}
+
+static	int
+sbread(void)
+{
+	int i, s;
+
+	for(i=1<<16; i!=0; i--) {
+		s = inb(blaster.rstatus);
+		if((s & 0x80) != 0) {
+			return inb(blaster.read);
+		}
+	}
+/*	print("#A%d: sbread did not respond\n", audio.ctlrno);	/**/
+	return -1;
+}
+
+static int
+ess1688w(int reg, int val)
+{
+	if(sbcmd(reg) || sbcmd(val))
+		return 1;
+
+	return 0;
+}
+
+static int
+ess1688r(int reg)
+{
+	if(sbcmd(0xC0) || sbcmd(reg))
+		return -1;
+
+	return sbread();
+}
+
+static	int
+mxcmd(int addr, int val)
+{
+
+	outb(blaster.mixaddr, addr);
+	outb(blaster.mixdata, val);
+	return 1;
+}
+
+static	int
+mxread(int addr)
+{
+	int s;
+
+	outb(blaster.mixaddr, addr);
+	s = inb(blaster.mixdata);
+	return s;
+}
+
+static	void
+mxcmds(int s, int v)
+{
+
+	if(v > 100)
+		v = 100;
+	if(v < 0)
+		v = 0;
+	mxcmd(s, (v*255)/100);
+}
+
+static	void
+mxcmdt(int s, int v)
+{
+
+	if(v > 100)
+		v = 100;
+	if(v <= 0)
+		mxcmd(s, 0);
+	else
+		mxcmd(s, 255-100+v);
+}
+
+static	void
+mxcmdu(int s, int v)
+{
+
+	if(v > 100)
+		v = 100;
+	if(v <= 0)
+		v = 0;
+	mxcmd(s, 128-50+v);
+}
+
+static	void
+mxvolume(void)
+{
+	int *left, *right;
+	int source;
+
+	if(0){
+		left = audio.livol;
+		right = audio.rivol;
+	}else{
+		left = audio.lovol;
+		right = audio.rovol;
+	}
+
+	ilock(&blaster);
+
+	mxcmd(0x30, 255);		/* left master */
+	mxcmd(0x31, 255);		/* right master */
+	mxcmd(0x3f, 0);		/* left igain */
+	mxcmd(0x40, 0);		/* right igain */
+	mxcmd(0x41, 0);		/* left ogain */
+	mxcmd(0x42, 0);		/* right ogain */
+
+	mxcmds(0x32, left[Vaudio]);
+	mxcmds(0x33, right[Vaudio]);
+
+	mxcmds(0x34, left[Vsynth]);
+	mxcmds(0x35, right[Vsynth]);
+
+	mxcmds(0x36, left[Vcd]);
+	mxcmds(0x37, right[Vcd]);
+
+	mxcmds(0x38, left[Vline]);
+	mxcmds(0x39, right[Vline]);
+
+	mxcmds(0x3a, left[Vmic]);
+	mxcmds(0x3b, left[Vspeaker]);
+
+	mxcmdu(0x44, left[Vtreb]);
+	mxcmdu(0x45, right[Vtreb]);
+
+	mxcmdu(0x46, left[Vbass]);
+	mxcmdu(0x47, right[Vbass]);
+
+	source = 0;
+	if(left[Vsynth])
+		source |= 1<<6;
+	if(right[Vsynth])
+		source |= 1<<5;
+	if(left[Vaudio])
+		source |= 1<<4;
+	if(right[Vaudio])
+		source |= 1<<3;
+	if(left[Vcd])
+		source |= 1<<2;
+	if(right[Vcd])
+		source |= 1<<1;
+	if(left[Vmic])
+		source |= 1<<0;
+	if(0)
+		mxcmd(0x3c, 0);		/* output switch */
+	else
+		mxcmd(0x3c, source);
+	mxcmd(0x3d, source);		/* input left switch */
+	mxcmd(0x3e, source);		/* input right switch */
+	iunlock(&blaster);
+}
+
+static	Buf*
+getbuf(AQueue *q)
+{
+	Buf *b;
+
+	ilock(q);
+	b = q->first;
+	if(b)
+		q->first = b->next;
+	iunlock(q);
+
+	return b;
+}
+
+static	void
+putbuf(AQueue *q, Buf *b)
+{
+
+	ilock(q);
+	b->next = 0;
+	if(q->first)
+		q->last->next = b;
+	else
+		q->first = b;
+	q->last = b;
+	iunlock(q);
+}
+
+/*
+ * move the dma to the next buffer
+ */
+static	void
+contindma(void)
+{
+	Buf *b;
+
+	if(!audio.active)
+		goto shutdown;
+
+	b = audio.current;
+	if(b){
+		audio.totcount += Bufsize;
+		audio.tottime = todget(nil);
+	}
+	if(0) {
+		if(b){
+			putbuf(&audio.full, b);
+			audio.buffered += Bufsize;
+		}
+		b = getbuf(&audio.empty);
+	} else {
+		if(b){
+			putbuf(&audio.empty, b);
+			audio.buffered -= Bufsize;
+		}
+		b = getbuf(&audio.full);
+	}
+	audio.current = b;
+	if(b == 0)
+		goto shutdown;
+
+	if(dmasetup(blaster.dma, b->virt, Bufsize, 0) >= 0)
+		return;
+	print("#A%d: dmasetup fail\n", audio.ctlrno);
+	putbuf(&audio.empty, b);
+
+shutdown:
+	dmaend(blaster.dma);
+	sbcmd(0xd9);				/* exit at end of count */
+	sbcmd(0xd5);				/* pause */
+	audio.curcount = 0;
+	audio.active = 0;
+}
+
+/*
+ * cause sb to get an interrupt per buffer.
+ * start first dma
+ */
+static	void
+sb16startdma(void)
+{
+	ulong count;
+	int speed;
+
+	ilock(&blaster);
+	dmaend(blaster.dma);
+	if(0) {
+		sbcmd(0x42);			/* input sampling rate */
+		speed = audio.livol[Vspeed];
+	} else {
+		sbcmd(0x41);			/* output sampling rate */
+		speed = audio.lovol[Vspeed];
+	}
+	sbcmd(speed>>8);
+	sbcmd(speed);
+
+	count = (Bufsize >> 1) - 1;
+	if(0)
+		sbcmd(0xbe);			/* A/D, autoinit */
+	else
+		sbcmd(0xb6);			/* D/A, autoinit */
+	sbcmd(0x30);				/* stereo, 16 bit */
+	sbcmd(count);
+	sbcmd(count>>8);
+
+	audio.active = 1;
+	contindma();
+	iunlock(&blaster);
+}
+
+static int
+ess1688reset(void)
+{
+	int i;
+
+	outb(blaster.reset, 3);
+	delay(1);			/* >3 υs */
+	outb(blaster.reset, 0);
+	delay(1);
+
+	i = sbread();
+	if(i != 0xAA) {
+		print("#A%d: no response %#.2x\n", audio.ctlrno, i);
+		return 1;
+	}
+
+	if(sbcmd(0xC6)){		/* extended mode */
+		print("#A%d: barf 3\n", audio.ctlrno);
+		return 1;
+	}
+
+	return 0;
+}
+
+static	void
+ess1688startdma(void)
+{
+	ulong count;
+	int speed, x;
+
+	ilock(&blaster);
+	dmaend(blaster.dma);
+
+	ess1688reset();
+
+	/*
+	 * Set the speed.
+	 */
+	if(0)
+		speed = audio.livol[Vspeed];
+	else
+		speed = audio.lovol[Vspeed];
+	if(speed < 4000)
+		speed = 4000;
+	else if(speed > 48000)
+		speed = 48000;
+
+	if(speed > 22000)
+		  x = 0x80|(256-(795500+speed/2)/speed);
+	else
+		  x = 128-(397700+speed/2)/speed;
+	ess1688w(0xA1, x & 0xFF);
+
+	speed = (speed * 9) / 20;
+	x = 256 - 7160000 / (speed * 82);
+	ess1688w(0xA2, x & 0xFF);
+
+	if(0)
+		ess1688w(0xB8, 0x0E);		/* A/D, autoinit */
+	else
+		ess1688w(0xB8, 0x04);		/* D/A, autoinit */
+	x = ess1688r(0xA8) & ~0x03;
+	ess1688w(0xA8, x|0x01);			/* 2 channels */
+	ess1688w(0xB9, 2);			/* demand mode, 4 bytes per request */
+
+	if(1)
+		ess1688w(0xB6, 0);		/* for output */
+
+	ess1688w(0xB7, 0x71);
+	ess1688w(0xB7, 0xBC);
+
+	x = ess1688r(0xB1) & 0x0F;
+	ess1688w(0xB1, x|0x50);
+	x = ess1688r(0xB2) & 0x0F;
+	ess1688w(0xB2, x|0x50);
+
+	if(1)
+		sbcmd(0xD1);			/* speaker on */
+
+	count = -Bufsize;
+	ess1688w(0xA4, count & 0xFF);
+	ess1688w(0xA5, (count>>8) & 0xFF);
+	x = ess1688r(0xB8);
+	ess1688w(0xB8, x|0x05);
+
+	audio.active = 1;
+	contindma();
+	iunlock(&blaster);
+}
+
+/*
+ * if audio is stopped,
+ * start it up again.
+ */
+static	void
+pokeaudio(void)
+{
+	if(!audio.active)
+		blaster.startdma();
+}
+
+static void
+sb16intr(void)
+{
+	int stat, dummy;
+
+	stat = mxread(0x82) & 7;		/* get irq status */
+	if(stat) {
+		dummy = 0;
+		if(stat & 2) {
+			ilock(&blaster);
+			dummy = inb(blaster.clri16);
+			contindma();
+			iunlock(&blaster);
+			audio.intr = 1;
+			wakeup(&audio.vous);
+		}
+		if(stat & 1) {
+			dummy = inb(blaster.clri8);
+		}
+		if(stat & 4) {
+			dummy = inb(blaster.clri401);
+		}
+		USED(dummy);
+	}
+}
+
+static void
+ess1688intr(void)
+{
+	int dummy;
+
+	if(audio.active){
+		ilock(&blaster);
+		contindma();
+		dummy = inb(blaster.clri8);
+		iunlock(&blaster);
+		audio.intr = 1;
+		wakeup(&audio.vous);
+		USED(dummy);
+	}
+	else
+		print("#A%d: unexpected ess1688 interrupt\n", audio.ctlrno);
+}
+
+void
+audiosbintr(void)
+{
+	/*
+	 * Carrera interrupt interface.
+	 */
+	blaster.intr();
+}
+
+static void
+pcaudiosbintr(Ureg*, void*)
+{
+	/*
+	 * x86 interrupt interface.
+	 */
+	blaster.intr();
+}
+
+static int
+anybuf(void*)
+{
+	return audio.intr;
+}
+
+/*
+ * wait for some output to get
+ * empty buffers back.
+ */
+static void
+waitaudio(void)
+{
+
+	audio.intr = 0;
+	pokeaudio();
+	tsleep(&audio.vous, anybuf, 0, 10000);
+	if(audio.intr == 0) {
+/*		print("#A%d: audio timeout\n", audio.ctlrno);	/**/
+		audio.active = 0;
+		pokeaudio();
+	}
+}
+
+static	void
+swab(uchar *a)
+{
+	ulong *p, *ep, b;
+
+	if(!SBswab){
+		USED(a);
+		return;
+	}
+	p = (ulong*)a;
+	ep = p + (Bufsize>>2);
+	while(p < ep) {
+		b = *p;
+		b = (b>>24) | (b<<24) |
+			((b&0xff0000) >> 8) |
+			((b&0x00ff00) << 8);
+		*p++ = b;
+	}
+}
+
+static void
+sbbufinit(void)
+{
+	int i;
+	uchar *p;
+
+	p = (uchar*)(((ulong)xalloc((Nbuf+1) * Bufsize) + Bufsize-1) &
+		~(Bufsize-1));
+	if (p == nil)
+		panic("sbbufinit: no memory");
+	for(i=0; i<Nbuf; i++) {
+		dcflush(p, Bufsize);
+		audio.buf[i].virt = UNCACHED(uchar, p);
+		audio.buf[i].phys = (ulong)PADDR(p);
+		p += Bufsize;
+	}
+}
+
+static	void
+setempty(void)
+{
+	int i;
+
+	ilock(&blaster);
+	audio.empty.first = 0;
+	audio.empty.last = 0;
+	audio.full.first = 0;
+	audio.full.last = 0;
+	audio.current = 0;
+	audio.filling = 0;
+	audio.buffered = 0;
+	for(i=0; i<Nbuf; i++)
+		putbuf(&audio.empty, &audio.buf[i]);
+	audio.totcount = 0;
+	audio.tottime = 0LL;
+	iunlock(&blaster);
+}
+
+static	void
+resetlevel(void)
+{
+	int i;
+
+	for(i=0; volumes[i].name; i++) {
+		audio.lovol[i] = volumes[i].ilval;
+		audio.rovol[i] = volumes[i].irval;
+		audio.livol[i] = volumes[i].ilval;
+		audio.rivol[i] = volumes[i].irval;
+	}
+}
+
+static long
+audiobuffered(Audio *)
+{
+	return audio.buffered;
+}
+
+static long
+audiostatus(Audio *, void *a, long n, vlong off)
+{
+	char buf[300];
+
+	snprint(buf, sizeof(buf), "bufsize %6d buffered %6d offset  %10lud time %19lld\n",
+		Bufsize, audio.buffered, audio.totcount, audio.tottime);
+	return readstr(off, a, n, buf);
+}
+
+static long
+audiowrite(Audio *, void *vp, long n, vlong off)
+{
+	long m, n0;
+	Buf *b;
+	char *a;
+
+	a = vp;
+	n0 = n;
+	qlock(&audio);
+	if(waserror()){
+		qunlock(&audio);
+		nexterror();
+	}
+
+	if(off == 0){
+		if(audio.bufinit == 0) {
+			audio.bufinit = 1;
+			sbbufinit();
+		}
+		setempty();
+		audio.curcount = 0;
+		mxvolume();
+	}
+
+	while(n > 0) {
+		b = audio.filling;
+		if(b == 0) {
+			b = getbuf(&audio.empty);
+			if(b == 0) {
+				waitaudio();
+				continue;
+			}
+			audio.filling = b;
+			audio.curcount = 0;
+		}
+
+		m = Bufsize-audio.curcount;
+		if(m > n)
+			m = n;
+		memmove(b->virt+audio.curcount, a, m);
+
+		audio.curcount += m;
+		n -= m;
+		a += m;
+		audio.buffered += m;
+		if(audio.curcount >= Bufsize) {
+			audio.filling = 0;
+			swab(b->virt);
+			putbuf(&audio.full, b);
+			pokeaudio();
+		}
+	}
+	poperror();
+	qunlock(&audio);
+
+	return n0 - n;
+}
+
+static void
+audioclose(Audio *)
+{
+	qlock(&audio);
+	if(1) {
+		Buf *b;
+
+		/* flush out last partial buffer */
+		b = audio.filling;
+		if(b) {
+			audio.filling = 0;
+			memset(b->virt+audio.curcount, 0, Bufsize-audio.curcount);
+			audio.buffered += Bufsize-audio.curcount;
+			swab(b->virt);
+			putbuf(&audio.full, b);
+		}
+		if(!audio.active && audio.full.first)
+			pokeaudio();
+	}
+	if(waserror()){
+		qunlock(&audio);
+		nexterror();
+	}
+	while(audio.active)
+		waitaudio();
+	setempty();
+	poperror();
+	qunlock(&audio);
+}
+
+static int
+ess1688(ISAConf* sbconf)
+{
+	int i, major, minor;
+
+	/*
+	 * Try for ESS1688.
+	 */
+	sbcmd(0xE7);			/* get version */
+	major = sbread();
+	minor = sbread();
+	if(major != 0x68 || minor != 0x8B){
+		print("#A%d: model %#.2x %#.2x; not ESS1688 compatible\n", audio.ctlrno, major, minor);
+		return 1;
+	}
+
+	ess1688reset();
+
+	switch(sbconf->irq){
+	case 2:
+	case 9:
+		i = 0x50|(0<<2);
+		break;
+	case 5:
+		i = 0x50|(1<<2);
+		break;
+	case 7:
+		i = 0x50|(2<<2);
+		break;
+	case 10:
+		i = 0x50|(3<<2);
+		break;
+	default:
+		print("#A%d: bad ESS1688 irq %d\n", audio.ctlrno, sbconf->irq);
+		return 1;
+	}
+	ess1688w(0xB1, i);
+
+	switch(sbconf->dma){
+	case 0:
+		i = 0x50|(1<<2);
+		break;
+	case 1:
+		i = 0xF0|(2<<2);
+		break;
+	case 3:
+		i = 0x50|(3<<2);
+		break;
+	default:
+		print("#A%d: bad ESS1688 dma %lud\n", audio.ctlrno, sbconf->dma);
+		return 1;
+	}
+	ess1688w(0xB2, i);
+
+	ess1688reset();
+
+	blaster.startdma = ess1688startdma;
+	blaster.intr = ess1688intr;
+
+	return 0;
+}
+
+static int
+audioprobe(Audio *adev)
+{
+	ISAConf sbconf;
+	int i, x;
+	static int irq[] = {2,5,7,10};
+
+	if(audio.probed)
+		return -1;
+
+	sbconf.port = 0x220;
+	sbconf.dma = Dma;
+	sbconf.irq = IrqAUDIO;
+	if(isaconfig("audio", adev->ctlrno, &sbconf) == 0)
+		return -1;
+
+	audio.probed = 1;
+	audio.ctlrno = adev->ctlrno;
+	if(sbconf.type == nil ||
+		(cistrcmp(sbconf.type, "sb16") != 0 && 
+		 cistrcmp(sbconf.type, "ess1688") != 0))
+		return -1;
+	switch(sbconf.port){
+	case 0x220:
+	case 0x240:
+	case 0x260:
+	case 0x280:
+		break;
+	default:
+		print("#A%d: bad port %#lux\n", audio.ctlrno, sbconf.port);
+		return -1;
+	}
+
+	if(ioalloc(sbconf.port, 0x10, 0, "audio") < 0){
+		print("#A%d: cannot ioalloc range %lux+0x10\n", audio.ctlrno, sbconf.port);
+		return -1;
+	}
+	if(ioalloc(sbconf.port+0x100, 1, 0, "audio.mpu401") < 0){
+		iofree(sbconf.port);
+		print("#A%d: cannot ioalloc range %lux+0x01\n", audio.ctlrno, sbconf.port+0x100);
+		return -1;
+	}
+
+	switch(sbconf.irq){
+	case 2:
+	case 5:
+	case 7:
+	case 9:
+	case 10:
+		break;
+	default:
+		print("#A%d: bad irq %d\n", audio.ctlrno, sbconf.irq);
+		iofree(sbconf.port);
+		iofree(sbconf.port+0x100);
+		return -1;
+	}
+
+	print("#A%d: %s port 0x%04lux irq %d\n", audio.ctlrno, sbconf.type,
+		sbconf.port, sbconf.irq);
+
+	blaster.reset = sbconf.port + 0x6;
+	blaster.read = sbconf.port + 0xa;
+	blaster.write = sbconf.port + 0xc;
+	blaster.wstatus = sbconf.port + 0xc;
+	blaster.rstatus = sbconf.port + 0xe;
+	blaster.mixaddr = sbconf.port + 0x4;
+	blaster.mixdata = sbconf.port + 0x5;
+	blaster.clri8 = sbconf.port + 0xe;
+	blaster.clri16 = sbconf.port + 0xf;
+	blaster.clri401 = sbconf.port + 0x100;
+	blaster.dma = sbconf.dma;
+
+	blaster.startdma = sb16startdma;
+	blaster.intr = sb16intr;
+
+	resetlevel();
+
+	outb(blaster.reset, 1);
+	delay(1);			/* >3 υs */
+	outb(blaster.reset, 0);
+	delay(1);
+
+	i = sbread();
+	if(i != 0xaa) {
+		print("#A%d: no response #%.2x\n", audio.ctlrno, i);
+		iofree(sbconf.port);
+		iofree(sbconf.port+0x100);
+		return -1;
+	}
+
+	sbcmd(0xe1);			/* get version */
+	audio.major = sbread();
+	audio.minor = sbread();
+
+	if(audio.major != 4) {
+		if(audio.major != 3 || audio.minor != 1 || ess1688(&sbconf)){
+			print("#A%d: model %#.2x %#.2x; not SB 16 compatible\n",
+				audio.ctlrno, audio.major, audio.minor);
+			iofree(sbconf.port);
+			iofree(sbconf.port+0x100);
+			return -1;
+		}
+		audio.major = 4;
+	}
+
+	/*
+	 * initialize the mixer
+	 */
+	mxcmd(0x00, 0);			/* Reset mixer */
+	mxvolume();
+
+	/*
+	 * Attempt to set IRQ/DMA channels.
+	 * On old ISA boards, these registers are writable.
+	 * On Plug-n-Play boards, these are read-only.
+	 *
+	 * To accomodate both, we write to the registers,
+	 * but then use the contents in case the write is
+	 * disallowed.
+	 */
+	mxcmd(0x80,			/* irq */
+		(sbconf.irq==2)? 1:
+		(sbconf.irq==5)? 2:
+		(sbconf.irq==7)? 4:
+		(sbconf.irq==9)? 1:
+		(sbconf.irq==10)? 8:
+		0);
+
+	mxcmd(0x81, 1<<blaster.dma);	/* dma */
+
+	x = mxread(0x81);
+	for(i=5; i<=7; i++)
+		if(x & (1<<i)){
+			blaster.dma = i;
+			break;
+		}
+
+	x = mxread(0x80);
+	for(i=0; i<=3; i++)
+		if(x & (1<<i)){
+			sbconf.irq = irq[i];
+			break;
+		}
+
+	adev->write = audiowrite;
+	adev->close = audioclose;
+	adev->status = audiostatus;
+	adev->buffered = audiobuffered;
+
+	dmainit(blaster.dma, Bufsize);
+	intrenable(sbconf.irq, pcaudiosbintr, 0, BUSUNKNOWN, "sb16");
+	return 0;
+}
+
+void
+audiosb16link(void)
+{
+	addaudiocard("sb16", audioprobe);
+}
--- a/sys/src/9/pc/pcf
+++ b/sys/src/9/pc/pcf
@@ -30,7 +30,7 @@
 	aoe
 	lpt
 
-	audio		dma
+	audio
 	pccard
 	i82365		cis
 	uart
@@ -73,6 +73,7 @@
 	usbohci
 	usbehci		usbehcipc
 
+	audiosb16	dma
 	audioac97	audioac97mix
 
 misc