shithub: riscv

Download patch

ref: d642d726babfc8e6a04a9ea2280655b0c5ff8f98
parent: 1a81fb86eb713f3e98a71522259d843e8cc14995
author: cinap_lenrek <cinap_lenrek@localhost>
date: Mon May 16 08:16:43 EDT 2011

add ac97 driver

--- a/sys/src/9/pc/audio.h
+++ /dev/null
@@ -1,15 +1,0 @@
-enum
-{
-	Bufsize	= 1024,	/* 5.8 ms each, must be power of two */
-	Nbuf		= 128,	/* .74 seconds total */
-	Dma		= 6,
-	IrqAUDIO	= 7,
-	SBswab	= 0,
-};
-
-#define seteisadma(a, b)	dmainit(a, Bufsize);
-#define CACHELINESZ		8
-#define UNCACHED(type, v)	(type*)((ulong)(v))
-
-#define Int0vec
-#define setvec(v, f, a)		intrenable(v, f, a, BUSUNKNOWN, "audio")
--- /dev/null
+++ b/sys/src/9/pc/audioac97.c
@@ -1,0 +1,609 @@
+#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 Hwdesc Hwdesc;
+typedef struct Ctlr Ctlr;
+static uint sis7012 = 0;
+
+enum {
+	Ioc	=	1<<31,
+	Bup	=	1<<30,
+};
+
+struct Hwdesc {
+	ulong addr;
+	ulong size;
+};
+
+enum {
+	Ndesc = 32,
+	Nts = 33,
+	Bufsize = 32768,	/* bytes, must be divisible by ndesc */
+	Maxbusywait = 500000, /* microseconds, roughly */
+	BytesPerSample = 4,
+};
+
+struct Ctlr {
+	/* keep these first, they want to be 8-aligned */
+	Hwdesc indesc[Ndesc];
+	Hwdesc outdesc[Ndesc];
+	Hwdesc micdesc[Ndesc];
+
+	Lock;
+	Rendez outr;
+
+	ulong port;
+	ulong mixport;
+
+	char *out;
+	char *in;
+	char *mic;
+
+	char *outp;
+	char *inp;
+	char *micp;
+
+	/* shared variables, ilock to access */
+	int outavail;
+	int inavail;
+	int micavail;
+
+	/* interrupt handler alone */
+	int outciv;
+	int inciv;
+	int micciv;
+
+	int tsouti;
+	uvlong tsoutp;
+	ulong tsout[Nts];
+	int tsoutb[Nts];
+
+	ulong civstat[Ndesc];
+	ulong lvistat[Ndesc];
+
+	int targetrate;
+	int hardrate;
+
+	int attachok;
+};
+
+#define iorl(c, r)	(inl((c)->port+(r)))
+#define iowl(c, r, l)	(outl((c)->port+(r), (ulong)(l)))
+
+enum {
+	In = 0x00,
+	Out = 0x10,
+	Mic = 0x20,
+		Bar = 0x00,	/* Base address register, 8-byte aligned */
+		/* a 32-bit read at 0x04 can be used to get civ:lvi:sr in one step */
+		Civ = 0x04,	/* current index value (desc being processed) */
+		Lvi = 0x05,	/* Last valid index (index of first unused entry!) */
+		Sr = 0x06,	/* status register */
+			Fifoe = 1<<4,	/* fifo error (r/wc) */
+			Bcis = 1<<3,	/* buffer completion interrupt status (r/wc) */
+			Lvbci = 1<<2,	/* last valid buffer completion(in)/fetched(out) interrupt (r/wc) */
+			Celv = 1<<1,	/* current equals last valid (ro) */
+			Dch = 1<<0,	/* dma controller halted (ro) */
+		Picb = 0x08,	/* position in current buffer */
+		Piv = 0x0a,	/* prefetched index value */
+		Cr = 0x0b,	/* control register */
+			Ioce = 1<<4,	/* interrupt on buffer completion (if bit set in hwdesc.size) (rw) */
+			Feie = 1<<3,	/* fifo error interrupt enable (rw) */
+			Lvbie = 1<<2,	/* last valid buffer interrupt enable (rw) */
+			RR = 1<<1,	/* reset busmaster related regs, excl. ioce,feie,lvbie (rw) */
+			Rpbm = 1<<0,	/* run/pause busmaster. 0 stops, 1 starts (rw) */
+	Cnt = 0x2c,	/* global control */
+		Ena16bit = 0x0<<22,
+		Ena20bit = 0x1<<22,
+		Ena2chan = 0x0<<20,
+		Ena4chan = 0x1<<20,
+		Enam6chan = 0x2<<20,
+		EnaRESER = 0x3<<20,
+		Sr2ie = 1<<6,	/* sdin2 interrupt enable (rw) */
+		Srie = 1<<5,	/* sdin1 interrupt enable (rw) */
+		Prie = 1<<4,	/* sdin0 interrupt enable (rw) */
+		Aclso = 1<<3,	/* ac link shut-off (rw) */
+		Acwr = 1<<2,	/* ac 97 warm reset (rw) */
+		Accr = 1<<1,	/* ac 97 cold reset (rw) */
+		GPIie = 1<<0,	/* GPI interrupt enable (rw) */
+	Sta = 0x30,			/* global status */
+		Cap6chan = 1<<21,
+		Cap4chan = 1<<20,
+		Md3 = 1<<17,	/* modem powerdown semaphore */
+		Ad3 = 1<<16,	/* audio powerdown semaphore */
+		Rcs = 1<<15,	/* read completion status (r/wc) */
+		S2ri = 1<<29,	/* sdin2 resume interrupt (r/wc) */
+		Sri = 1<<11,	/* sdin1 resume interrupt (r/wc) */
+		Pri = 1<<10,	/* sdin0 resume interrupt (r/wc) */
+		S2cr = 1<<28,	/* sdin2 codec ready (ro) */
+		Scr = 1<<9,	/* sdin1 codec ready (ro) */
+		Pcr = 1<<8,	/* sdin0 codec ready (ro) */
+		Mint = 1<<7,	/* microphone in inetrrupt (ro) */
+		Point = 1<<6,	/* pcm out interrupt (ro) */
+		Piint = 1<<5,	/* pcm in interrupt (ro) */
+		Moint = 1<<2,	/* modem out interrupt (ro) */
+		Miint = 1<<1,	/* modem in interrupt (ro) */
+		Gsci = 1<<0,	/* GPI status change interrupt */
+	Cas = 0x34,	/* codec access semaphore */
+		Casp = 1<<0,	/* set to 1 on read if zero, cleared by hardware */
+};
+
+#define csr8r(c, r)	(inb((c)->port+(r)))
+#define csr16r(c, r)	(ins((c)->port+(r)))
+#define csr32r(c, r)	(inl((c)->port+(r)))
+#define csr8w(c, r, b)	(outb((c)->port+(r), (int)(b)))
+#define csr16w(c, r, w)	(outs((c)->port+(r), (ushort)(w)))
+#define csr32w(c, r, w)	(outl((c)->port+(r), (ulong)(w)))
+
+static void
+ac97waitcodec(Audio *adev)
+{
+	Ctlr *ctlr;
+	int i;
+	ctlr = adev->ctlr;
+	for(i = 0; i < Maxbusywait/10; i++){
+		if((csr8r(ctlr, Cas) & Casp) == 0)
+			break;
+		microdelay(10);
+	}
+	if(i == Maxbusywait)
+		print("#A%d: ac97 exhausted waiting codec access\n", adev->ctlrno);
+}
+
+static void
+ac97mixw(Audio *adev, int port, ushort val)
+{
+	Ctlr *ctlr;
+	ac97waitcodec(adev);
+	ctlr = adev->ctlr;
+	outs(ctlr->mixport+port, val);
+}
+
+static ushort
+ac97mixr(Audio *adev, int port)
+{
+	Ctlr *ctlr;
+	ac97waitcodec(adev);
+	ctlr = adev->ctlr;
+	return ins(ctlr->mixport+port);
+}
+
+static int
+outavail(void *arg)
+{
+	Ctlr *ctlr;
+	ctlr = arg;
+	return ctlr->outavail > 0;
+}
+
+static void
+ac97interrupt(Ureg *, void *arg)
+{
+	Audio *adev;
+	Ctlr *ctlr;
+	int civ, n, i;
+	ulong stat;
+	uvlong now;
+
+	adev = arg;
+	ctlr = adev->ctlr;
+	stat = csr32r(ctlr, Sta);
+
+	stat &= S2ri | Sri | Pri | Mint | Point | Piint | Moint | Miint | Gsci;
+
+	ilock(ctlr);
+	if(stat & Point){
+		if(sis7012)
+			csr16w(ctlr, Out + Picb, csr16r(ctlr, Out + Picb) & ~Dch);
+		else
+			csr16w(ctlr, Out + Sr, csr16r(ctlr, Out + Sr) & ~Dch);
+		
+		civ = csr8r(ctlr, Out + Civ);
+		n = 0;
+		while(ctlr->outciv != civ){
+			ctlr->civstat[ctlr->outciv++]++;
+			if(ctlr->outciv == Ndesc)
+				ctlr->outciv = 0;
+			n += Bufsize/Ndesc;
+		}
+
+		now = fastticks(0);
+		i = ctlr->tsouti;
+		ctlr->tsoutb[i] = n;
+		ctlr->tsout[i] = now - ctlr->tsoutp;
+		ctlr->tsouti = (i + 1) % Nts;
+		ctlr->tsoutp = now;
+		ctlr->outavail += n;
+		
+		if(ctlr->outavail > Bufsize/2)
+			wakeup(&ctlr->outr);
+		stat &= ~Point;	
+	}
+	iunlock(ctlr);
+	if(stat) /* have seen 0x400, which is sdin0 resume */
+		print("#A%d: ac97 unhandled interrupt(s): stat 0x%lux\n", adev->ctlrno, stat);
+}
+
+static int
+off2lvi(char *base, char *p)
+{
+	int lvi;
+	lvi = p - base;
+	return lvi / (Bufsize/Ndesc);
+}
+
+static long
+ac97medianoutrate(Audio *adev)
+{
+	ulong ts[Nts], t;
+	uvlong hz;
+	int i, j;
+	Ctlr *ctlr;
+	ctlr = adev->ctlr;
+	fastticks(&hz);
+	for(i = 0; i < Nts; i++)
+		if(ctlr->tsout[i] > 0)
+			ts[i] = (ctlr->tsoutb[i] * hz) / ctlr->tsout[i];
+		else
+			ts[i] = 0;
+	for(i = 1; i < Nts; i++){
+		t = ts[i];
+		j = i;
+		while(j > 0 && ts[j-1] > t){
+			ts[j] = ts[j-1];
+			j--;
+		}
+		ts[j] = t;
+	}
+	return ts[Nts/2] / BytesPerSample;
+}
+
+static void
+ac97volume(Audio *adev, char *msg)
+{
+	adev->volwrite(adev, msg, strlen(msg), 0);
+}
+
+static void
+ac97attach(Audio *adev)
+{
+	Ctlr *ctlr;
+	ctlr = adev->ctlr;
+	if(!ctlr->attachok){
+		ac97hardrate(adev, ctlr->hardrate);
+		ac97volume(adev, "audio 75");
+		ac97volume(adev, "head 100");
+		ac97volume(adev, "master 100");
+		ctlr->attachok = 1;
+	}
+}
+
+static long
+ac97status(Audio *adev, void *a, long n, vlong off)
+{
+	Ctlr *ctlr;
+	char *buf;
+	long i, l;
+	ctlr = adev->ctlr;
+	l = 0;
+	buf = malloc(READSTR);
+	l += snprint(buf + l, READSTR - l, "rate %d\n", ctlr->targetrate);
+	l += snprint(buf + l, READSTR - l, "median rate %lud\n", ac97medianoutrate(adev));
+	l += snprint(buf + l, READSTR - l, "hard rate %d\n", ac97hardrate(adev, -1));
+
+	l += snprint(buf + l, READSTR - l, "civ stats");
+	for(i = 0; i < Ndesc; i++)
+		l += snprint(buf + l, READSTR - l, " %lud", ctlr->civstat[i]);
+	l += snprint(buf + l, READSTR - l, "\n");
+
+	l += snprint(buf + l, READSTR - l, "lvi stats");
+	for(i = 0; i < Ndesc; i++)
+		l += snprint(buf + l, READSTR - l, " %lud", ctlr->lvistat[i]);
+	snprint(buf + l, READSTR - l, "\n");
+
+	n = readstr(off, a, n, buf);
+	free(buf);
+	return n;
+}
+
+static long
+ac97buffered(Audio *adev)
+{
+	Ctlr *ctlr;
+	ctlr = adev->ctlr;
+	return Bufsize - Bufsize/Ndesc - ctlr->outavail;
+}
+
+static long
+ac97ctl(Audio *adev, void *a, long n, vlong)
+{
+	Ctlr *ctlr;
+	char *tok[2], *p;
+	int ntok;
+	long t;
+
+	ctlr = adev->ctlr;
+	if(n > READSTR)
+		n = READSTR - 1;
+	p = malloc(READSTR);
+
+	if(waserror()){
+		free(p);
+		nexterror();
+	}
+	memmove(p, a, n);
+	p[n] = 0;
+	ntok = tokenize(p, tok, nelem(tok));
+	if(ntok > 1 && !strcmp(tok[0], "rate")){
+		t = strtol(tok[1], 0, 10);
+		if(t < 8000 || t > 48000)
+			error("rate must be between 8000 and 48000");
+		ctlr->targetrate = t;
+		ctlr->hardrate = t;
+		ac97hardrate(adev, ctlr->hardrate);
+		poperror();
+		free(p);
+		return n;
+	}
+	error("invalid ctl");
+	return n; /* shut up, you compiler you */
+}
+
+static void
+ac97kick(Ctlr *ctlr, long reg)
+{
+	csr8w(ctlr, reg+Cr, Ioce | Rpbm);
+}
+
+static long
+ac97write(Audio *adev, void *a, long nwr, vlong)
+{
+	Ctlr *ctlr;
+	char *p, *sp, *ep;
+	int len, lvi, olvi;
+	int t;
+	long n;
+
+	ctlr = adev->ctlr;
+	ilock(ctlr);
+	p = ctlr->outp;
+	sp = a;
+	ep = ctlr->out + Bufsize;
+	olvi = off2lvi(ctlr->out, p);
+	n = nwr;
+	while(n > 0){
+		len = ep - p;
+		if(ctlr->outavail < len)
+			len = ctlr->outavail;
+		if(n < len)
+			len = n;
+		ctlr->outavail -= len;
+		iunlock(ctlr);
+		memmove(p, sp, len);
+		ilock(ctlr);
+		p += len;
+		sp += len;
+		n -= len;
+		if(p == ep)
+			p = ctlr->out;
+		lvi = off2lvi(ctlr->out, p);
+		if(olvi != lvi){
+			t = olvi;
+			while(t != lvi){
+				t = (t + 1) % Ndesc;
+				ctlr->lvistat[t]++;
+				csr8w(ctlr, Out+Lvi, t);
+				ac97kick(ctlr, Out);
+			}
+			olvi = lvi;
+		}
+		if(ctlr->outavail == 0){
+			ctlr->outp = p;
+			iunlock(ctlr);
+			sleep(&ctlr->outr, outavail, ctlr);
+			ilock(ctlr);
+		}
+	}
+	ctlr->outp = p;
+	iunlock(ctlr);
+	return nwr;
+}
+
+static Pcidev*
+ac97match(Pcidev *p)
+{
+	/* not all of the matched devices have been tested */
+	while(p = pcimatch(p, 0, 0))
+		switch(p->vid){
+		default:
+			break;
+		case 0x1039:
+			switch(p->did){
+			default:
+				break;
+			case 0x7012:
+				sis7012 = 1;
+				return p;
+			}
+		case 0x1022:
+			switch(p->did){
+			default:
+				break;
+			case 0x746d:
+			case 0x7445:
+				return p;
+			}
+		case 0x10de:
+			switch(p->did){
+			default:
+				break;
+			case 0x01b1:
+			case 0x006a:
+			case 0x00da:
+			case 0x00ea:
+				return p;
+			}
+		case 0x8086:
+			switch(p->did){
+			default:
+				break;
+			case 0x2415:
+			case 0x2425:
+			case 0x2445:
+			case 0x2485:
+			case 0x24c5:
+			case 0x24d5:
+			case 0x25a6:
+			case 0x266e:
+			case 0x7195:
+				return p;
+			}
+		}
+	return nil;
+}
+
+static void
+sethwp(Ctlr *ctlr, long off, void *ptr)
+{
+	csr8w(ctlr, off+Cr, RR);
+	csr32w(ctlr, off+Bar, PCIWADDR(ptr));
+	csr8w(ctlr, off+Lvi, 0);
+}
+
+static int
+ac97reset(Audio *adev)
+{
+	static int ncards = 1;
+	int i, irq, tbdf;
+	Pcidev *p;
+	Ctlr *ctlr;
+	ulong ctl, stat = 0;
+
+	p = nil;
+	for(i = 0; i < ncards; i++)
+		if((p = ac97match(p)) == nil)
+			return -1;
+	ncards++;
+
+	ctlr = xspanalloc(sizeof(Ctlr), 8, 0);
+	memset(ctlr, 0, sizeof(Ctlr));
+	adev->ctlr = ctlr;
+	ctlr->targetrate = 44100;
+	ctlr->hardrate = 44100;
+
+	if(p->mem[0].size == 64){
+		ctlr->port = p->mem[0].bar & ~3;
+		ctlr->mixport = p->mem[1].bar & ~3;
+	} else if(p->mem[1].size == 64){
+		ctlr->port = p->mem[1].bar & ~3;
+		ctlr->mixport = p->mem[0].bar & ~3;
+	} else if(p->mem[0].size == 256){			/* sis7012 */
+		ctlr->port = p->mem[1].bar & ~3;
+		ctlr->mixport = p->mem[0].bar & ~3;
+	} else if(p->mem[1].size == 256){
+		ctlr->port = p->mem[0].bar & ~3;
+		ctlr->mixport = p->mem[1].bar & ~3;
+	}
+
+	irq = p->intl;
+	tbdf = p->tbdf;
+
+	print("#A%d: ac97 port 0x%04lux mixport 0x%04lux irq %d\n",
+		adev->ctlrno, ctlr->port, ctlr->mixport, irq);
+
+	pcisetbme(p);
+	pcisetioe(p);
+
+	ctlr->mic = xspanalloc(Bufsize, 8, 0);
+	ctlr->in = xspanalloc(Bufsize, 8, 0);
+	ctlr->out = xspanalloc(Bufsize, 8, 0);
+
+	for(i = 0; i < Ndesc; i++){
+		int size, off = i * (Bufsize/Ndesc);
+		
+		if(sis7012)
+			size = (Bufsize/Ndesc);
+		else
+			size = (Bufsize/Ndesc) / 2;
+		
+		ctlr->micdesc[i].addr = PCIWADDR(ctlr->mic + off);
+		ctlr->micdesc[i].size = Ioc | size;
+		ctlr->indesc[i].addr = PCIWADDR(ctlr->in + off);
+		ctlr->indesc[i].size = Ioc | size;
+		ctlr->outdesc[i].addr = PCIWADDR(ctlr->out + off);
+		ctlr->outdesc[i].size = Ioc | size;
+	}
+
+	ctlr->outavail = Bufsize - Bufsize/Ndesc;
+	ctlr->outp = ctlr->out;
+
+	ctl = csr32r(ctlr, Cnt);
+	ctl &= ~(EnaRESER | Aclso);
+
+	if((ctl & Accr) == 0){
+		print("#A%d: ac97 cold reset\n", adev->ctlrno);
+		ctl |= Accr;
+	}else{
+		print("#A%d: ac97 warm reset\n", adev->ctlrno);
+		ctl |= Acwr;
+	}
+
+	csr32w(ctlr, Cnt, ctl);
+	for(i = 0; i < Maxbusywait; i++){
+		if((csr32r(ctlr, Cnt) & Acwr) == 0)
+			break;
+		microdelay(1);
+	}
+	if(i == Maxbusywait)
+		print("#A%d: ac97 gave up waiting Acwr to go down\n", adev->ctlrno);
+
+	for(i = 0; i < Maxbusywait; i++){
+		if((stat = csr32r(ctlr, Sta)) & (Pcr | Scr | S2cr))
+			break;
+		microdelay(1);
+	}
+	if(i == Maxbusywait)
+		print("#A%d: ac97 gave up waiting codecs become ready\n", adev->ctlrno);
+
+	print("#A%d: ac97 codecs ready:%s%s%s\n", adev->ctlrno,
+		(stat & Pcr) ? " sdin0" : "",
+		(stat & Scr) ? " sdin1" : "",
+		(stat & S2cr) ? " sdin2" : "");
+
+	print("#A%d: ac97 codecs resumed:%s%s%s\n", adev->ctlrno,
+		(stat & Pri) ? " sdin0" : "",
+		(stat & Sri) ? " sdin1" : "",
+		(stat & S2ri) ? " sdin2" : "");
+
+	sethwp(ctlr, In, ctlr->indesc);
+	sethwp(ctlr, Out, ctlr->outdesc);
+	sethwp(ctlr, Mic, ctlr->micdesc);
+
+	csr8w(ctlr, In+Cr, Ioce);	/*  | Lvbie | Feie */
+	csr8w(ctlr, Out+Cr, Ioce);	/*  | Lvbie | Feie */
+	csr8w(ctlr, Mic+Cr, Ioce);	/*  | Lvbie | Feie */
+
+	adev->attach = ac97attach;
+	adev->write = ac97write;
+	adev->status = ac97status;
+	adev->ctl = ac97ctl;
+	adev->buffered = ac97buffered;
+	
+	ac97mixreset(adev, ac97mixw, ac97mixr);
+
+	intrenable(irq, ac97interrupt, adev, tbdf, adev->name);
+
+	return 0;
+}
+
+void
+audioac97link(void)
+{
+	addaudiocard("ac97audio", ac97reset);
+}
--- /dev/null
+++ b/sys/src/9/pc/audioac97mix.c
@@ -1,0 +1,365 @@
+#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 ushort (*ac97rdfn)(Audio *, int);
+typedef void (*ac97wrfn)(Audio *, int, ushort);
+
+typedef struct Mixer Mixer;
+typedef struct Volume Volume;
+
+struct Mixer {
+	QLock;
+	ac97wrfn wr;
+	ac97rdfn rr;
+	int vra;
+};
+
+enum { Maxbusywait = 500000 };
+
+enum {
+	Reset = 0x0,
+		Capmic = 0x1,
+		Captonectl = 0x4,
+		Capsimstereo = 0x8,
+		Capheadphones = 0x10,
+		Caploudness = 0x20,
+		Capdac18 = 0x40,
+		Capdac20 = 0x80,
+		Capadc18 = 0x100,
+		Capadc20 = 0x200,
+		Capenh = 0xfc00,
+	Master = 0x02,
+	Headphone = 0x04,
+	Monomaster = 0x06,
+	Mastertone = 0x08,
+	Pcbeep = 0x0A,
+	Phone = 0x0C,
+	Mic = 0x0E,
+	Line = 0x10,
+	Cd = 0x12,
+	Video = 0x14,
+	Aux = 0x16,
+	Pcmout = 0x18,
+		Mute = 0x8000,
+	Recsel = 0x1A,
+	Recgain = 0x1C,
+	Micgain = 0x1E,
+	General = 0x20,
+	ThreeDctl = 0x22,
+	Ac97RESER = 0x24,
+	Powerdowncsr = 0x26,
+		Adcpower = 0x1,
+		Dacpower = 0x2,
+		Anlpower = 0x4,
+		Refpower = 0x8,
+		Inpower = 0x100,
+		Outpower = 0x200,
+		Mixpower = 0x400,
+		Mixvrefpower = 0x800,
+		Aclinkpower = 0x1000,
+		Clkpower = 0x2000,
+		Auxpower = 0x4000,
+		Eamppower = 0x8000,
+	Extid = 0x28,
+	Extcsr = 0x2A,
+		Extvra = 1<<0,
+		Extdra = 1<<1,
+		Extspdif = 1<<2,
+		Extvrm = 1<<3,
+		Extiddsa0 = 0<<4,	/* extid only */
+		Extiddsa1 = 1<<4,	/* extid only */
+		Extiddsa2 = 2<<4,	/* extid only */
+		Extiddsa3 = 3<<4,	/* extid only */
+		Extcsrspsa34 = 0<<4,	/* extcsr only */
+		Extcsrspsa78 = 1<<4,	/* extcsr only */
+		Extcsrspsa69 = 2<<4,	/* extcsr only */
+		ExtcsrspsaAB = 3<<4,	/* extcsr only */
+		Extcdac = 1<<6,
+		Extsdac = 1<<7,
+		Extldac = 1<<8,
+		Extidamap = 1<<9,	/* extid only */
+		Extidrev11 = 0<<10,	/* extid only */
+		Extidrev22 = 1<<10,	/* extid only */
+		Extidrev23 = 2<<10,	/* extid only */
+		Extidprim = 0<<14,	/* extid only */
+		Extidsec0 = 1<<14,	/* extid only */
+		Extidsec1 = 2<<14,	/* extid only */
+		Extidsec2 = 3<<14,	/* extid only */
+		Extcsrmadc = 1<<9,	/* extcsr only */
+		Extcsrspcv = 1<<10,	/* extcsr only */
+		Extcsrpri = 1<<11,	/* extcsr only */
+		Extcsrprj = 1<<12,	/* extcsr only */
+		Extcsrprk = 1<<13,	/* extcsr only */
+		Extcsrprl = 1<<14,	/* extcsr only */
+		Extcsrvcfg = 1<<15,	/* extcsr only */
+	Pcmfrontdacrate = 0x2C,
+	Pcmsurrounddacrate = 0x2E,
+	Pcmlfedacrate = 0x30,
+	Pcmadcrate = 0x32,
+	Pcmmicadcrate = 0x34,
+	CenterLfe = 0x36,
+	LrSurround = 0x38,
+	Spdifcsr = 0x3a,
+		Spdifpro = 1<<0,
+		Spdifnonaudio = 1<<1,
+		Spdifcopy = 1<<2,
+		Spdifpre = 1<<3,
+		SpdifCC = 0x7f<<4,
+		Spdifl = 1<<11,
+		Spdif44k1 = 0<<12,
+		Spdif32k = 1<<12,
+		Spdif48k = 2<<12,
+		Spdifdsr = 1<<14,
+		Spdifv = 1<<15,
+	VID1 = 0x7c,
+	VID2 = 0x7e,
+	Speed = 0x1234567,
+};
+
+enum {
+	Left,
+	Right,
+	Stereo,
+	Absolute,
+};
+
+enum {
+	Vmaster,
+	Vhead,
+	Vaudio,
+	Vcd,
+	Vbass,
+	Vtreb,
+	Vbeep,
+	Vphone,
+	Vmic,
+	Vline,
+	Vvideo,
+	Vaux,
+	Vrecgain,
+	Vmicgain,
+};
+
+struct Volume {
+	int reg;
+	int range;
+	int type;
+	int cap;
+	char *name;
+};
+
+struct Topology {
+	Volume *this;
+	Volume *next[2];
+};
+
+Volume vol[] = {
+[Vmaster]	{Master, 63, Stereo, 0, "master"},
+[Vaudio]	{Pcmout, 31, Stereo,	0, "audio"},
+[Vhead]	{Headphone, 31, Stereo, Capheadphones, "head"},
+[Vbass]	{Mastertone, 15, Left, Captonectl, "bass"},
+[Vtreb]	{Mastertone, 15, Right, Captonectl, "treb"},
+[Vbeep]	{Pcbeep, 31, Right, 0, "beep"},
+[Vphone]	{Phone, 31, Right, 0, "phone"},
+[Vmic]	{Mic, 31, Right, Capmic, "mic"},
+[Vline]	{Line, 31, Stereo, 0, "line"},
+[Vcd]	{Cd, 31, Stereo,	0, "cd"},
+[Vvideo]	{Video, 31, Stereo, 0, "video"},
+[Vaux]	{Aux, 63, Stereo, 0, "aux"},
+[Vrecgain]	{Recgain, 15, Stereo, 0, "recgain"},
+[Vmicgain]	{Micgain, 15, Right, Capmic, "micgain"},
+	{0, 0, 0, 0, 0},
+};
+
+long
+ac97mixtopology(Audio *adev, void *a, long n, vlong off)
+{
+	Mixer *m;
+	char *buf;
+	long l;
+	ulong caps;
+	m = adev->mixer;
+	qlock(m);
+	caps = m->rr(adev, Reset);
+	caps |= m->rr(adev, Extid) << 16;
+	l = 0;
+	buf = malloc(READSTR);
+	l += snprint(buf+l, READSTR-l, "not implemented. have fun.\n");
+	USED(caps);
+	USED(l);
+	qunlock(m);
+	n = readstr(off, a, n, buf);
+	free(buf);
+	return n;
+}
+
+long
+ac97mixread(Audio *adev, void *a, long n, vlong off)
+{
+	Mixer *m;
+	char *nam, *buf;
+	long l;
+	ushort v;
+	ulong caps;
+	int i, rang, le, ri;
+	buf = malloc(READSTR);
+	m = adev->mixer;
+	qlock(m);
+	l = 0;
+	caps = m->rr(adev, Reset);
+	caps |= m->rr(adev, Extid) << 16;
+	for(i = 0; vol[i].name != 0; ++i){
+		if(vol[i].cap && ((vol[i].cap & caps) == 0))
+			continue;
+		v = m->rr(adev, vol[i].reg);
+		nam = vol[i].name;
+		rang = vol[i].range;
+		if(vol[i].type == Absolute){
+			l += snprint(buf+l, READSTR-l, "%s %d", nam, v);
+		} else {
+			ri = ((rang-(v&rang)) * 100) / rang;
+			le = ((rang-((v>>8)&rang)) * 100) / rang;
+			if(vol[i].type == Stereo)
+				l += snprint(buf+l, READSTR-l, "%s %d %d", nam, le, ri);
+			if(vol[i].type == Left)
+				l += snprint(buf+l, READSTR-l, "%s %d", nam, le);
+			if(vol[i].type == Right)
+				l += snprint(buf+l, READSTR-l, "%s %d", nam, ri);
+			if(v&Mute)
+				l += snprint(buf+l, READSTR-l, " mute");
+		}
+		l += snprint(buf+l, READSTR-l, "\n");
+	}
+	qunlock(m);
+	n = readstr(off, a, n, buf);
+	free(buf);
+	return n;
+}
+
+long
+ac97mixwrite(Audio *adev, void *a, long n, vlong)
+{
+	Mixer *m;
+	char *tok[4];
+	int ntok, i, left, right, rang, reg;
+	ushort v;
+	m = adev->mixer;
+	qlock(m);
+	ntok = tokenize(a, tok, 4);
+	for(i = 0; vol[i].name != 0; ++i){
+		if(!strcmp(vol[i].name, tok[0])){
+			rang = vol[i].range;
+			reg = vol[i].reg;
+			left = right = 0;
+			if(ntok > 1)
+				left = right = atoi(tok[1]);
+			if(ntok > 2)
+				right = atoi(tok[2]);
+
+			if(vol[i].type == Absolute){
+				m->wr(adev, reg, left);
+			} else {
+				left = rang - ((left*rang)) / 100;
+				right = rang - ((right*rang)) / 100;
+				switch(vol[i].type){
+				default:
+					break;
+				case Left:
+					v = m->rr(adev, reg);
+					v = (v & 0x007f) | (left << 8);
+					m->wr(adev, reg, v);
+					break;
+				case Right:
+					v = m->rr(adev, reg);
+					v = (v & 0x7f00) | right;
+					m->wr(adev, reg, v);
+					break;
+				case Stereo:
+					v = (left<<8) | right;
+					m->wr(adev, reg, v);
+					break;
+				}
+			}
+			qunlock(m);
+			return n;
+		}
+	}
+	if(vol[i].name == nil){
+		char *p;
+		for(p = tok[0]; *p; ++p)
+			if(*p < '0' || *p > '9') {
+				qunlock(m);
+				error("no such volume setting");
+			}
+		rang = vol[0].range;
+		reg = vol[0].reg;
+		left = right = rang - ((atoi(tok[0])*rang)) / 100;
+		v = (left<<8) | right;
+		m->wr(adev, reg, v);
+	}
+	qunlock(m);
+
+	return n;
+}
+
+int
+ac97hardrate(Audio *adev, int rate)
+{
+	Mixer *m;
+	int oldrate;
+	m = adev->mixer;
+	oldrate = m->rr(adev, Pcmfrontdacrate);
+	if(rate > 0)
+		m->wr(adev, Pcmfrontdacrate, rate);
+	return oldrate;
+}
+
+void
+ac97mixreset(Audio *adev, ac97wrfn wr, ac97rdfn rr)
+{
+	Mixer *m;
+	int i;
+	ushort t;
+	if(adev->mixer == nil)
+		adev->mixer = malloc(sizeof(Mixer));
+	m = adev->mixer;
+	m->wr = wr;
+	m->rr = rr;
+	adev->volread = ac97mixread;
+	adev->volwrite = ac97mixwrite;
+	m->wr(adev, Reset, 0);
+	m->wr(adev, Powerdowncsr, 0);
+
+	t = (Adcpower | Dacpower | Anlpower | Refpower);
+	for(i = 0; i < Maxbusywait; i++){
+		if((m->rr(adev, Powerdowncsr) & t) == t)
+			break;
+		microdelay(1);
+	}
+	if(i == Maxbusywait)
+		print("#A%d: ac97 exhausted waiting powerup\n", adev->ctlrno);
+
+	t = m->rr(adev, Extid);
+	print("#A%d: ac97 codec ext:%s%s%s%s%s%s%s\n", adev->ctlrno,
+		(t & Extvra) ? " vra" : "",
+		(t & Extdra) ? " dra" : "",
+		(t & Extspdif) ? " spdif" : "",
+		(t & Extvrm) ? " vrm" : "",
+		(t & Extcdac) ? " cdac" : "",
+		(t & Extsdac) ? " sdac" : "",
+		(t & Extldac) ? " ldac" : "");
+
+	if(t & Extvra){
+		m->wr(adev, Extcsr, Extvra);
+		m->vra = 1;
+	} else {
+		print("#A%d: ac97 vra extension not supported\n", adev->ctlrno);
+		m->vra = 0;
+	}
+}
--- a/sys/src/9/pc/pcf
+++ b/sys/src/9/pc/pcf
@@ -73,6 +73,8 @@
 	usbohci
 	usbehci		usbehcipc
 
+	audioac97	audioac97mix
+
 misc
 	archmp		mp apic
 	mtrr
--- a/sys/src/9/port/devaudio.c
+++ b/sys/src/9/port/devaudio.c
@@ -1,875 +1,165 @@
-/*
- *	SB 16 driver
- */
 #include	"u.h"
 #include	"../port/lib.h"
 #include	"mem.h"
 #include	"dat.h"
 #include	"fns.h"
-#include	"../port/error.h"
 #include	"io.h"
-#include	"audio.h"
+#include	"../port/error.h"
+#include	"../port/audio.h"
 
-typedef struct	AQueue	AQueue;
-typedef struct	Buf	Buf;
+typedef struct Audioprobe Audioprobe;
+struct Audioprobe {
+	char *name;
+	int (*probe)(Audio*);
+};
 
-enum
-{
-	Qdir		= 0,
+enum {
+	Qdir = 0,
 	Qaudio,
+	Qaudioctl,
+	Qaudiostatus,
 	Qvolume,
-	Qstatus,
-
-	Fmono		= 1,
-	Fin		= 2,
-	Fout		= 4,
-
-	Aclosed		= 0,
-	Aread,
-	Awrite,
-
-	Vaudio		= 0,
-	Vsynth,
-	Vcd,
-	Vline,
-	Vmic,
-	Vspeaker,
-	Vtreb,
-	Vbass,
-	Vspeed,
-	Nvol,
-
-	Speed		= 44100,
-	Ncmd		= 50,		/* max volume command words */
+	Maxaudioprobes = 8,
 };
 
-Dirtab
-audiodir[] =
-{
-	".",	{Qdir, 0, QTDIR},		0,	DMDIR|0555,
-	"audio",	{Qaudio},		0,	0666,
-	"volume",	{Qvolume},		0,	0666,
-	"audiostat",{Qstatus},		0,	0444,
-};
+static int naudioprobes;
+static Audioprobe audioprobes[Maxaudioprobes];
+static Audio *audiodevs;
 
-struct	Buf
-{
-	uchar*	virt;
-	ulong	phys;
-	Buf*	next;
+static Dirtab audiodir[] = {
+	".",	{Qdir, 0, QTDIR},	0,	DMDIR|0555,
+	"audio",	{Qaudio},			0,	0666,
+	"audioctl",	{Qaudioctl},			0,	0666,
+	"audiostat",	{Qaudiostatus},			0,	0666,
+	"volume",	{Qvolume},			0,	0666,
 };
-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	amode;		/* Aclosed/Aread/Awrite for /audio */
-	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;
-} audio;
-
-static	struct
+void
+addaudiocard(char *name, int (*probefn)(Audio *))
 {
-	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: sbcmd (%#.2x) timeout\n", 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: sbread did not respond\n");	/**/
-	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(audio.amode == Aread){
-		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(audio.amode == Aread)
-		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(audio.amode == Aread) {
-		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, audio.amode == Aread) >= 0)
-		return;
-	print("#A: dmasetup fail\n");
-	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(audio.amode == Aread) {
-		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(audio.amode == Aread)
-		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);
+	Audioprobe *probe;
+	probe = &audioprobes[naudioprobes++];
+	probe->name = name;
+	probe->probe = probefn;
 }
 
-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: no response %#.2x\n", i);
-		return 1;
-	}
-
-	if(sbcmd(0xC6)){		/* extended mode */
-		print("#A: barf 3\n");
-		return 1;
-	}
-
-	return 0;
-}
-
-static	void
-ess1688startdma(void)
-{
-	ulong count;
-	int speed, x;
-
-	ilock(&blaster);
-	dmaend(blaster.dma);
-
-	if(audio.amode == Awrite)
-		ess1688reset();
-	if(audio.amode == Aread)
-		sbcmd(0xD3);			/* speaker off */
-
-	/*
-	 * Set the speed.
-	 */
-	if(audio.amode == Aread)
-		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(audio.amode == Aread)
-		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(audio.amode == Awrite)
-		ess1688w(0xB6, 0);
-	ess1688w(0xB7, 0x71);
-	ess1688w(0xB7, 0xBC);
-
-	x = ess1688r(0xB1) & 0x0F;
-	ess1688w(0xB1, x|0x50);
-	x = ess1688r(0xB2) & 0x0F;
-	ess1688w(0xB2, x|0x50);
-	if(audio.amode == Awrite)
-		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)
+audioreset(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);
+	int i, ctlrno = 0;
+	Audio **pp;
+	Audioprobe *probe;
+	pp = &audiodevs;
+	*pp = malloc(sizeof(Audio));
+	(*pp)->ctlrno = ctlrno++;
+	for(i = 0; i < naudioprobes; i++){
+		probe = &audioprobes[i];
+		(*pp)->name = probe->name;
+		while(!probe->probe(*pp)){
+			pp = &(*pp)->next;
+			*pp = malloc(sizeof(Audio));
+			(*pp)->ctlrno = ctlrno++;
+			(*pp)->name = probe->name;
 		}
-		if(stat & 1) {
-			dummy = inb(blaster.clri8);
-		}
-		if(stat & 4) {
-			dummy = inb(blaster.clri401);
-		}
-		USED(dummy);
 	}
+	free(*pp);
+	*pp = nil;
 }
 
-static void
-ess1688intr(void)
+static Chan*
+audioattach(char *spec)
 {
-	int dummy;
-
-	if(audio.active){
-		ilock(&blaster);
-		contindma();
-		dummy = inb(blaster.clri8);
-		iunlock(&blaster);
-		audio.intr = 1;
-		wakeup(&audio.vous);
-		USED(dummy);
-	}
+	Chan *c;
+	Audio *p;
+	int i;
+	if(spec != nil && *spec != '\0')
+		i = strtol(spec, 0, 10);
 	else
-		print("#A: unexpected ess1688 interrupt\n");
+		i = 0;
+	for(p = audiodevs; p; p = p->next)
+		if(i-- == 0)
+			break;
+	if(p == nil)
+		error(Enodev);
+	c = devattach('A', spec);
+	c->qid.path = Qdir;
+	c->aux = p;
+	if(p->attach)
+		p->attach(p);
+	return c;
 }
 
-void
-audiosbintr(void)
+static long
+audioread(Chan *c, void *a, long n, vlong off)
 {
-	/*
-	 * Carrera interrupt interface.
-	 */
-	blaster.intr();
-}
-
-static void
-pcaudiosbintr(Ureg*, void*)
-{
-	/*
-	 * x86 interrupt interface.
-	 */
-	blaster.intr();
-}
-
-void
-audiodmaintr(void)
-{
-/*	print("#A: dma interrupt\n");	/**/
-}
-
-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: audio timeout\n");	/**/
-		audio.active = 0;
-		pokeaudio();
-	}
-}
-
-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 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: model %#.2x %#.2x; not ESS1688 compatible\n", major, minor);
-		return 1;
-	}
-
-	ess1688reset();
-
-	switch(sbconf->irq){
-	case 2:
-	case 9:
-		i = 0x50|(0<<2);
+	Audio *adev;
+	long (*fn)(Audio *, void *, long, vlong);
+	adev = c->aux;
+	switch((ulong)c->qid.path){
+	default:
+		error("audio bugger (rd)");
+	case Qaudioctl:
+		fn = adev->ctl;
 		break;
-	case 5:
-		i = 0x50|(1<<2);
+	case Qdir:
+		if(adev->buffered)
+			audiodir[Qaudio].length = adev->buffered(adev);
+		return devdirread(c, a, n, audiodir, nelem(audiodir), devgen);
+	case Qaudio:
+		fn = adev->read;
 		break;
-	case 7:
-		i = 0x50|(2<<2);
+	case Qaudiostatus:
+		fn = adev->status;
 		break;
-	case 10:
-		i = 0x50|(3<<2);
+	case Qvolume:
+		fn = adev->volread;
 		break;
-	default:
-		print("#A: bad ESS1688 irq %d\n", sbconf->irq);
-		return 1;
 	}
-	ess1688w(0xB1, i);
+	if(fn == nil)
+		error("not implemented");
+	return fn(adev, a, n, off);
+}
 
-	switch(sbconf->dma){
-	case 0:
-		i = 0x50|(1<<2);
+static long
+audiowrite(Chan *c, void *a, long n, vlong off)
+{
+	Audio *adev;
+	long (*fn)(Audio *, void *, long, vlong);
+	adev = c->aux;
+	switch((ulong)c->qid.path){
+	default:
+		error("audio bugger (wr)");
+	case Qaudio:
+		fn = adev->write;
 		break;
-	case 1:
-		i = 0xF0|(2<<2);
+	case Qaudioctl:
+		fn = adev->ctl;
 		break;
-	case 3:
-		i = 0x50|(3<<2);
+	case Qvolume:
+		fn = adev->volwrite;
 		break;
-	default:
-		print("#A: bad ESS1688 dma %lud\n", sbconf->dma);
-		return 1;
 	}
-	ess1688w(0xB2, i);
-
-	ess1688reset();
-
-	blaster.startdma = ess1688startdma;
-	blaster.intr = ess1688intr;
-
-	return 0;
+	if(fn == nil)
+		error("not implemented");
+	return fn(adev, a, n, off);
 }
 
 static void
-audioinit(void)
+audioclose(Chan *c)
 {
-	ISAConf sbconf;
-	int i, x;
-	static int irq[] = {2,5,7,10};
-
-	sbconf.port = 0x220;
-	sbconf.dma = Dma;
-	sbconf.irq = IrqAUDIO;
-	if(isaconfig("audio", 0, &sbconf) == 0)
-		return;
-	if(sbconf.type == nil ||
-		(cistrcmp(sbconf.type, "sb16") != 0 && 
-		 cistrcmp(sbconf.type, "ess1688") != 0))
-		return;
-	switch(sbconf.port){
-	case 0x220:
-	case 0x240:
-	case 0x260:
-	case 0x280:
-		break;
+	Audio *adev;
+	adev = c->aux;
+	switch((ulong)c->qid.path){
 	default:
-		print("#A: bad port %#lux\n", sbconf.port);
 		return;
-	}
-
-	if(ioalloc(sbconf.port, 0x10, 0, "audio") < 0){
-		print("#A: cannot ioalloc range %lux+0x10\n", sbconf.port);
+	case Qaudio:
+		if(adev->close == nil)
+			return;
+		adev->close(adev);
 		return;
 	}
-	if(ioalloc(sbconf.port+0x100, 1, 0, "audio.mpu401") < 0){
-		iofree(sbconf.port);
-		print("#A: cannot ioalloc range %lux+0x01\n", sbconf.port+0x100);
-		return;
-	}
-
-	switch(sbconf.irq){
-	case 2:
-	case 5:
-	case 7:
-	case 9:
-	case 10:
-		break;
-	default:
-		print("#A: bad irq %d\n", sbconf.irq);
-		iofree(sbconf.port);
-		iofree(sbconf.port+0x100);
-		return;
-	}
-
-	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;
-
-	audio.amode = Aclosed;
-	resetlevel();
-
-	outb(blaster.reset, 1);
-	delay(1);			/* >3 υs */
-	outb(blaster.reset, 0);
-	delay(1);
-
-	i = sbread();
-	if(i != 0xaa) {
-		print("#A: no response #%.2x\n", i);
-		iofree(sbconf.port);
-		iofree(sbconf.port+0x100);
-		return;
-	}
-
-	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: model %#.2x %#.2x; not SB 16 compatible\n",
-				audio.major, audio.minor);
-			iofree(sbconf.port);
-			iofree(sbconf.port+0x100);
-			return;
-		}
-		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;
-		}
-
-	seteisadma(blaster.dma, audiodmaintr);
-	setvec(Int0vec+sbconf.irq, pcaudiosbintr, 0);
 }
 
-static Chan*
-audioattach(char *param)
-{
-	return devattach('A', param);
-}
-
 static Walkqid*
 audiowalk(Chan *c, Chan *nc, char **name, int nname)
 {
@@ -877,374 +167,26 @@
 }
 
 static int
-audiostat(Chan *c, uchar *db, int n)
+audiostat(Chan *c, uchar *dp, int n)
 {
-	audiodir[Qaudio].length = audio.buffered;
-	return devstat(c, db, n, audiodir, nelem(audiodir), devgen);
+	Audio *adev;
+	adev = c->aux;
+	if(adev->buffered && (ulong)c->qid.path == Qaudio)
+		audiodir[Qaudio].length = adev->buffered(adev);
+	return devstat(c, dp, n, audiodir, nelem(audiodir), devgen);
 }
 
 static Chan*
 audioopen(Chan *c, int omode)
 {
-	int amode;
-
-	if(audio.major != 4)
-		error(Emajor);
-
-	switch((ulong)c->qid.path) {
-	default:
-		error(Eperm);
-		break;
-
-	case Qstatus:
-		if((omode&7) != OREAD)
-			error(Eperm);
-	case Qvolume:
-	case Qdir:
-		break;
-
-	case Qaudio:
-		amode = Awrite;
-		if((omode&7) == OREAD)
-			amode = Aread;
-		qlock(&audio);
-		if(audio.amode != Aclosed){
-			qunlock(&audio);
-			error(Einuse);
-		}
-		if(audio.bufinit == 0) {
-			audio.bufinit = 1;
-			sbbufinit();
-		}
-		audio.amode = amode;
-		setempty();
-		audio.curcount = 0;
-		qunlock(&audio);
-		mxvolume();
-		break;
-	}
-	c = devopen(c, omode, audiodir, nelem(audiodir), devgen);
-	c->mode = openmode(omode);
-	c->flag |= COPEN;
-	c->offset = 0;
-
-	return c;
+	return devopen(c, omode, audiodir, nelem(audiodir), devgen);
 }
 
-static void
-audioclose(Chan *c)
-{
-	Buf *b;
-
-	switch((ulong)c->qid.path) {
-	default:
-		error(Eperm);
-		break;
-
-	case Qdir:
-	case Qvolume:
-	case Qstatus:
-		break;
-
-	case Qaudio:
-		if(c->flag & COPEN) {
-			qlock(&audio);
-			if(audio.amode == Awrite) {
-				/* 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();
-			}
-			audio.amode = Aclosed;
-			if(waserror()){
-				qunlock(&audio);
-				nexterror();
-			}
-			while(audio.active)
-				waitaudio();
-			setempty();
-			poperror();
-			qunlock(&audio);
-		}
-		break;
-	}
-}
-
-static long
-audioread(Chan *c, void *v, long n, vlong off)
-{
-	int liv, riv, lov, rov;
-	long m, n0;
-	char buf[300];
-	Buf *b;
-	int j;
-	ulong offset = off;
-	char *a;
-
-	n0 = n;
-	a = v;
-	switch((ulong)c->qid.path) {
-	default:
-		error(Eperm);
-		break;
-
-	case Qdir:
-		return devdirread(c, a, n, audiodir, nelem(audiodir), devgen);
-
-	case Qaudio:
-		if(audio.amode != Aread)
-			error(Emode);
-		qlock(&audio);
-		if(waserror()){
-			qunlock(&audio);
-			nexterror();
-		}
-		while(n > 0) {
-			b = audio.filling;
-			if(b == 0) {
-				b = getbuf(&audio.full);
-				if(b == 0) {
-					waitaudio();
-					continue;
-				}
-				audio.filling = b;
-				swab(b->virt);
-				audio.curcount = 0;
-			}
-			m = Bufsize-audio.curcount;
-			if(m > n)
-				m = n;
-			memmove(a, b->virt+audio.curcount, m);
-
-			audio.curcount += m;
-			n -= m;
-			a += m;
-			audio.buffered -= m;
-			if(audio.curcount >= Bufsize) {
-				audio.filling = 0;
-				putbuf(&audio.empty, b);
-			}
-		}
-		poperror();
-		qunlock(&audio);
-		break;
-
-	case Qstatus:
-		buf[0] = 0;
-		snprint(buf, sizeof(buf), "bufsize %6d buffered %6d offset  %10lud time %19lld\n",
-			Bufsize, audio.buffered, audio.totcount, audio.tottime);
-		return readstr(offset, a, n, buf);
-
-	case Qvolume:
-		j = 0;
-		buf[0] = 0;
-		for(m=0; volumes[m].name; m++){
-			liv = audio.livol[m];
-			riv = audio.rivol[m];
-			lov = audio.lovol[m];
-			rov = audio.rovol[m];
-			j += snprint(buf+j, sizeof(buf)-j, "%s", volumes[m].name);
-			if((volumes[m].flag & Fmono) || liv==riv && lov==rov){
-				if((volumes[m].flag&(Fin|Fout))==(Fin|Fout) && liv==lov)
-					j += snprint(buf+j, sizeof(buf)-j, " %d", liv);
-				else{
-					if(volumes[m].flag & Fin)
-						j += snprint(buf+j, sizeof(buf)-j,
-							" in %d", liv);
-					if(volumes[m].flag & Fout)
-						j += snprint(buf+j, sizeof(buf)-j,
-							" out %d", lov);
-				}
-			}else{
-				if((volumes[m].flag&(Fin|Fout))==(Fin|Fout) &&
-				    liv==lov && riv==rov)
-					j += snprint(buf+j, sizeof(buf)-j,
-						" left %d right %d",
-						liv, riv);
-				else{
-					if(volumes[m].flag & Fin)
-						j += snprint(buf+j, sizeof(buf)-j,
-							" in left %d right %d",
-							liv, riv);
-					if(volumes[m].flag & Fout)
-						j += snprint(buf+j, sizeof(buf)-j,
-							" out left %d right %d",
-							lov, rov);
-				}
-			}
-			j += snprint(buf+j, sizeof(buf)-j, "\n");
-		}
-		return readstr(offset, a, n, buf);
-	}
-	return n0-n;
-}
-
-static long
-audiowrite(Chan *c, void *vp, long n, vlong)
-{
-	long m, n0;
-	int i, v, left, right, in, out;
-	Cmdbuf *cb;
-	Buf *b;
-	char *a;
-
-	a = vp;
-	n0 = n;
-	switch((ulong)c->qid.path) {
-	default:
-		error(Eperm);
-		break;
-
-	case Qvolume:
-		v = Vaudio;
-		left = 1;
-		right = 1;
-		in = 1;
-		out = 1;
-		cb = parsecmd(vp, n);
-		if(waserror()){
-			free(cb);
-			nexterror();
-		}
-
-		for(i = 0; i < cb->nf; i++){
-			/*
-			 * a number is volume
-			 */
-			if(cb->f[i][0] >= '0' && cb->f[i][0] <= '9') {
-				m = strtoul(cb->f[i], 0, 10);
-				if(left && out)
-					audio.lovol[v] = m;
-				if(left && in)
-					audio.livol[v] = m;
-				if(right && out)
-					audio.rovol[v] = m;
-				if(right && in)
-					audio.rivol[v] = m;
-				mxvolume();
-				goto cont0;
-			}
-
-			for(m=0; volumes[m].name; m++) {
-				if(strcmp(cb->f[i], volumes[m].name) == 0) {
-					v = m;
-					in = 1;
-					out = 1;
-					left = 1;
-					right = 1;
-					goto cont0;
-				}
-			}
-
-			if(strcmp(cb->f[i], "reset") == 0) {
-				resetlevel();
-				mxvolume();
-				goto cont0;
-			}
-			if(strcmp(cb->f[i], "in") == 0) {
-				in = 1;
-				out = 0;
-				goto cont0;
-			}
-			if(strcmp(cb->f[i], "out") == 0) {
-				in = 0;
-				out = 1;
-				goto cont0;
-			}
-			if(strcmp(cb->f[i], "left") == 0) {
-				left = 1;
-				right = 0;
-				goto cont0;
-			}
-			if(strcmp(cb->f[i], "right") == 0) {
-				left = 0;
-				right = 1;
-				goto cont0;
-			}
-			error(Evolume);
-			break;
-		cont0:;
-		}
-		free(cb);
-		poperror();
-		break;
-
-	case Qaudio:
-		if(audio.amode != Awrite)
-			error(Emode);
-		qlock(&audio);
-		if(waserror()){
-			qunlock(&audio);
-			nexterror();
-		}
-		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);
-		break;
-	}
-	return n0 - n;
-}
-
-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;
-	}
-}
-
 Dev audiodevtab = {
 	'A',
 	"audio",
-
-	devreset,
-	audioinit,
+	audioreset,
+	devinit,
 	devshutdown,
 	audioattach,
 	audiowalk,