]> git.lizzy.rs Git - plan9front.git/blobdiff - sys/src/9/pc/audiosb16.c
pc/ether*: use 64-bit physical addresses and check pci membar types and sizes
[plan9front.git] / sys / src / 9 / pc / audiosb16.c
index 1d042fe8602618c0bec39e6c4ce01e2da69b4d7b..a38483aed93bdf4a9b6451adca96a8bd6c0236b3 100644 (file)
@@ -8,18 +8,16 @@
 #include "fns.h"
 #include "io.h"
 #include "../port/error.h"
-#include "../port/audio.h"
+#include "../port/audioif.h"
 
-typedef struct AQueue  AQueue;
-typedef struct Buf     Buf;
+typedef struct Ring    Ring;
+typedef struct Blaster Blaster;
+typedef struct Ctlr    Ctlr;
 
 enum
 {
-       Fmono           = 1,
-       Fin             = 2,
-       Fout            = 4,
-
-       Vaudio          = 0,
+       Vmaster,
+       Vaudio,
        Vsynth,
        Vcd,
        Vline,
@@ -27,87 +25,26 @@ enum
        Vspeaker,
        Vtreb,
        Vbass,
+       Vigain,
+       Vogain,
        Vspeed,
+       Vdelay,
        Nvol,
 
-       Speed           = 44100,
-       Ncmd            = 50,           /* max volume command words */
+       Blocksize       = 4096,
+       Blocks          = 65536/Blocksize,
 };
 
-enum
+struct Ring
 {
-       Bufsize = 1024, /* 5.8 ms each, must be power of two */
-       Nbuf    = 128,  /* .74 seconds total */
-       SBswab  = 0,
-};
+       uchar   *buf;
+       ulong   nbuf;
 
-#define CACHELINESZ            8
-#define UNCACHED(type, v)      (type*)((ulong)(v))
-
-struct Buf
-{
-       uchar*  virt;
-       ulong   phys;
-       Buf*    next;
+       ulong   ri;
+       ulong   wi;
 };
 
-struct AQueue
-{
-       Lock;
-       Buf*    first;
-       Buf*    last;
-};
-
-static struct
-{
-       QLock;
-       Rendez  vous;
-       int     buffered;               /* number of bytes en route */
-       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
+struct Blaster
 {
        Lock;
        int     reset;          /* io ports to the sound blaster */
@@ -120,259 +57,244 @@ static struct
        int     clri8;
        int     clri16;
        int     clri401;
-       int     dma;
 
-       void    (*startdma)(void);
-       void    (*intr)(void);
-} blaster;
+       void    (*startdma)(Ctlr*);
+       void    (*intr)(Ctlr*);
+};
 
-static void    swab(uchar*);
+struct Ctlr
+{
+       Rendez  vous;
+       int     active;         /* boolean dma running */
+       int     major;          /* SB16 major version number (sb 4) */
+       int     minor;          /* SB16 minor version number */
+       Ring    ring;           /* dma ring buffer */
+       Blaster blaster;
+
+       int     lvol[Nvol];
+       int     rvol[Nvol];
+
+       /* for probe */
+       Audio   *adev;
+       ISAConf conf;
+       Ctlr *next;
+};
+
+static Volume voltab[] = {
+       [Vmaster] "master", 0x30, 0xff, Stereo, 0,
+       [Vaudio] "audio", 0x32, 0xff, Stereo, 0,
+       [Vsynth] "synth", 0x34, 0xff, Stereo, 0,
+       [Vcd] "cd", 0x36, 0xff, Stereo, 0,
+       [Vline] "line", 0x38, 0xff, Stereo, 0,
+       [Vmic] "mic", 0x3a, 0xff, Mono, 0,
+       [Vspeaker] "speaker", 0x3b, 0xff, Mono, 0,
+       [Vtreb] "treb", 0x44, 0xff, Stereo, 0,
+       [Vbass] "bass", 0x46, 0xff, Stereo, 0,
+       [Vigain] "recgain", 0x3f, 0xff, Stereo, 0,
+       [Vogain] "outgain", 0x41, 0xff, Stereo, 0,
+       [Vspeed] "speed", 0, 0, Absolute, 0,
+       [Vdelay] "delay", 0, 0, Absolute, 0,
+       0,
+};
 
 static char    Emajor[]        = "soundblaster not responding/wrong version";
-static char    Emode[]         = "illegal open mode";
-static char    Evolume[]       = "illegal volume specifier";
+
+static long
+buffered(Ring *r)
+{
+       ulong ri, wi;
+
+       ri = r->ri;
+       wi = r->wi;
+       if(wi >= ri)
+               return wi - ri;
+       else
+               return r->nbuf - (ri - wi);
+}
+
+static long
+available(Ring *r)
+{
+       long m;
+
+       m = (r->nbuf - 1) - buffered(r);
+       if(m < 0)
+               m = 0;
+       return m;
+}
+
+static long
+readring(Ring *r, uchar *p, long n)
+{
+       long n0, m;
+
+       n0 = n;
+       while(n > 0){
+               if((m = buffered(r)) <= 0)
+                       break;
+               if(m > n)
+                       m = n;
+               if(p){
+                       if(r->ri + m > r->nbuf)
+                               m = r->nbuf - r->ri;
+                       memmove(p, r->buf + r->ri, m);
+                       p += m;
+               }
+               r->ri = (r->ri + m) % r->nbuf;
+               n -= m;
+       }
+       return n0 - n;
+}
+
+static long
+writering(Ring *r, uchar *p, long n)
+{
+       long n0, m;
+
+       n0 = n;
+       while(n > 0){
+               if((m = available(r)) <= 0)
+                       break;
+               if(m > n)
+                       m = n;
+               if(p){
+                       if(r->wi + m > r->nbuf)
+                               m = r->nbuf - r->wi;
+                       memmove(r->buf + r->wi, p, m);
+                       p += m;
+               }
+               r->wi = (r->wi + m) % r->nbuf;
+               n -= m;
+       }
+       return n0 - n;
+}
 
 static int
-sbcmd(int val)
+sbcmd(Blaster *blaster, int val)
 {
        int i, s;
 
        for(i=1<<16; i!=0; i--) {
-               s = inb(blaster.wstatus);
+               s = inb(blaster->wstatus);
                if((s & 0x80) == 0) {
-                       outb(blaster.write, val);
+                       outb(blaster->write, val);
                        return 0;
                }
        }
-       print("#A%d: sbcmd (%#.2x) timeout\n", audio.ctlrno, val);      /**/
        return 1;
 }
 
 static int
-sbread(void)
+sbread(Blaster *blaster)
 {
        int i, s;
 
        for(i=1<<16; i!=0; i--) {
-               s = inb(blaster.rstatus);
+               s = inb(blaster->rstatus);
                if((s & 0x80) != 0) {
-                       return inb(blaster.read);
+                       return inb(blaster->read);
                }
        }
-       print("#A%d: sbread did not respond\n", audio.ctlrno);  /**/
        return -1;
 }
 
 static int
-ess1688w(int reg, int val)
+ess1688w(Blaster *blaster, int reg, int val)
 {
-       if(sbcmd(reg) || sbcmd(val))
+       if(sbcmd(blaster, reg) || sbcmd(blaster, val))
                return 1;
-
        return 0;
 }
 
 static int
-ess1688r(int reg)
+ess1688r(Blaster *blaster, int reg)
 {
-       if(sbcmd(0xC0) || sbcmd(reg))
+       if(sbcmd(blaster, 0xC0) || sbcmd(blaster, reg))
                return -1;
-
-       return sbread();
+       return sbread(blaster);
 }
 
 static int
-mxcmd(int addr, int val)
+mxcmd(Blaster *blaster, int addr, int val)
 {
-
-       outb(blaster.mixaddr, addr);
-       outb(blaster.mixdata, val);
+       outb(blaster->mixaddr, addr);
+       outb(blaster->mixdata, val);
        return 1;
 }
 
 static int
-mxread(int addr)
+mxread(Blaster *blaster, int addr)
 {
        int s;
 
-       outb(blaster.mixaddr, addr);
-       s = inb(blaster.mixdata);
+       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;
+static int
+mxsetvol(Audio *adev, int x, int a[2])
+{
+       Blaster *blaster;
+       Ctlr *ctlr = adev->ctlr;
+       Volume *vol;
+
+       vol = voltab+x;
+       blaster = &ctlr->blaster;
+       ilock(blaster);
+       switch(vol->type){
+       case Absolute:
+               switch(x){
+               case Vdelay:
+                       adev->delay = a[0];
+                       break;
+               case Vspeed:
+                       adev->speed = a[0];
+                       break;
+               }
+               ctlr->lvol[x] = ctlr->rvol[x] = a[0];
+               break;
+       case Stereo:
+               ctlr->rvol[x] = a[1];
+               mxcmd(blaster, vol->reg+1, a[1]);
+               /* no break */
+       case Mono:
+               ctlr->lvol[x] = a[0];
+               mxcmd(blaster, vol->reg, a[0]);
        }
+       iunlock(blaster);
 
-       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);
+       return 0;
 }
 
-static Buf*
-getbuf(AQueue *q)
+static int
+mxgetvol(Audio *adev, int x, int a[2])
 {
-       Buf *b;
+       Ctlr *ctlr = adev->ctlr;
 
-       ilock(q);
-       b = q->first;
-       if(b)
-               q->first = b->next;
-       iunlock(q);
+       a[0] = ctlr->lvol[x];
+       a[1] = ctlr->rvol[x];
 
-       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);
+       return 0;
 }
 
-/*
- * 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);
+contindma(Ctlr *ctlr)
+{
+       Blaster *blaster;
+       Ring *ring;
+
+       blaster = &ctlr->blaster;
+       ring = &ctlr->ring;
+       if(buffered(ring) >= Blocksize)
+               ring->ri = ring->nbuf - dmacount(ctlr->conf.dma);
+       else{
+               dmaend(ctlr->conf.dma);
+               sbcmd(blaster, 0xd9);   /* exit at end of count */
+               sbcmd(blaster, 0xd5);   /* pause */
+               ctlr->active = 0;
        }
-       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;
+       wakeup(&ctlr->vous);
 }
 
 /*
@@ -380,60 +302,62 @@ shutdown:
  * start first dma
  */
 static void
-sb16startdma(void)
+sb16startdma(Ctlr *ctlr)
 {
-       ulong count;
+       Blaster *blaster;
+       Ring *ring;
+       long 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);
+       blaster = &ctlr->blaster;
+       ring = &ctlr->ring;
+       ilock(blaster);
+       dmaend(ctlr->conf.dma);
+       if(0)
+               sbcmd(blaster, 0x42);   /* input sampling rate */
+       else
+               sbcmd(blaster, 0x41);   /* output sampling rate */
+       speed = ctlr->adev->speed;
+       sbcmd(blaster, speed>>8);
+       sbcmd(blaster, speed);
 
        if(0)
-               sbcmd(0xbe);                    /* A/D, autoinit */
+               sbcmd(blaster, 0xbe);   /* A/D, autoinit */
        else
-               sbcmd(0xb6);                    /* D/A, autoinit */
-       sbcmd(0x30);                            /* stereo, signed 16 bit */
+               sbcmd(blaster, 0xb6);   /* D/A, autoinit */
 
-       /*
-        * not really sure if this is correct, but
-        * it works in bochs
-        */
-       count = (Bufsize >> 2) - 1;
-       sbcmd(count);
-       sbcmd(count>>8);
+       sbcmd(blaster, 0x30);   /* stereo, signed 16 bit */
 
-       audio.active = 1;
-       contindma();
-       iunlock(&blaster);
+       count = (Blocksize>>1) - 1;
+       sbcmd(blaster, count);
+       sbcmd(blaster, count>>8);
+
+       ctlr->active = 1;
+       if(dmasetup(ctlr->conf.dma, ring->buf, ring->nbuf, DMAWRITE|DMALOOP) < 0){
+               ctlr->active = 0;
+               print("#A%d: dmasetup fail\n", ctlr->adev->ctlrno);
+       }
+       iunlock(blaster);
 }
 
 static int
-ess1688reset(void)
+ess1688reset(Blaster *blaster, int ctlrno)
 {
        int i;
 
-       outb(blaster.reset, 3);
-       delay(1);                       /* >3 υs */
-       outb(blaster.reset, 0);
+       outb(blaster->reset, 3);
+       delay(1);       /* >3 υs */
+       outb(blaster->reset, 0);
        delay(1);
 
-       i = sbread();
+       i = sbread(blaster);
        if(i != 0xAA) {
-               print("#A%d: no response %#.2x\n", audio.ctlrno, i);
+               print("#A%d: no response %#.2x\n", ctlrno, i);
                return 1;
        }
 
-       if(sbcmd(0xC6)){                /* extended mode */
-               print("#A%d: barf 3\n", audio.ctlrno);
+       if(sbcmd(blaster, 0xC6)){       /* extended mode */
+               print("#A%d: barf 3\n", ctlrno);
                return 1;
        }
 
@@ -441,349 +365,268 @@ ess1688reset(void)
 }
 
 static void
-ess1688startdma(void)
+ess1688startdma(Ctlr *ctlr)
 {
+       Blaster *blaster;
+       Ring *ring;
        ulong count;
        int speed, x;
 
-       ilock(&blaster);
-       dmaend(blaster.dma);
+       blaster = &ctlr->blaster;
+       ring = &ctlr->ring;
+
+       ilock(blaster);
+       dmaend(ctlr->conf.dma);
 
-       ess1688reset();
+       ess1688reset(blaster, ctlr->adev->ctlrno);
 
        /*
         * Set the speed.
         */
-       if(0)
-               speed = audio.livol[Vspeed];
-       else
-               speed = audio.lovol[Vspeed];
+       speed = ctlr->adev->speed;
        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);
+       ess1688w(blaster, 0xA1, x & 0xFF);
 
        speed = (speed * 9) / 20;
        x = 256 - 7160000 / (speed * 82);
-       ess1688w(0xA2, x & 0xFF);
+       ess1688w(blaster, 0xA2, x & 0xFF);
 
        if(0)
-               ess1688w(0xB8, 0x0E);           /* A/D, autoinit */
+               ess1688w(blaster, 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 */
+               ess1688w(blaster, 0xB8, 0x04);  /* D/A, autoinit */
+       x = ess1688r(blaster, 0xA8) & ~0x03;
+       ess1688w(blaster, 0xA8, x|0x01);        /* 2 channels */
+       ess1688w(blaster, 0xB9, 2);     /* demand mode, 4 bytes per request */
 
        if(1)
-               ess1688w(0xB6, 0);              /* for output */
+               ess1688w(blaster, 0xB6, 0);     /* for output */
 
-       ess1688w(0xB7, 0x71);
-       ess1688w(0xB7, 0xBC);
+       ess1688w(blaster, 0xB7, 0x71);
+       ess1688w(blaster, 0xB7, 0xBC);
 
-       x = ess1688r(0xB1) & 0x0F;
-       ess1688w(0xB1, x|0x50);
-       x = ess1688r(0xB2) & 0x0F;
-       ess1688w(0xB2, x|0x50);
+       x = ess1688r(blaster, 0xB1) & 0x0F;
+       ess1688w(blaster, 0xB1, x|0x50);
+       x = ess1688r(blaster, 0xB2) & 0x0F;
+       ess1688w(blaster, 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();
+               sbcmd(blaster, 0xD1);   /* speaker on */
+
+       count = -Blocksize;
+       ess1688w(blaster, 0xA4, count & 0xFF);
+       ess1688w(blaster, 0xA5, (count>>8) & 0xFF);
+       x = ess1688r(blaster, 0xB8);
+       ess1688w(blaster, 0xB8, x|0x05);
+
+       ctlr->active = 1;
+       if(dmasetup(ctlr->conf.dma, ring->buf, ring->nbuf, DMAWRITE|DMALOOP) < 0){
+               ctlr->active = 0;
+               print("#A%d: dmasetup fail\n", ctlr->adev->ctlrno);
+       }
+       iunlock(blaster);
 }
 
 static void
-sb16intr(void)
+sb16intr(Ctlr *ctlr)
 {
+       Blaster *blaster;
        int stat;
 
-       stat = mxread(0x82);            /* get irq status */
-       if(stat & 7) {
-               ilock(&blaster);
+       blaster = &ctlr->blaster;
+       ilock(blaster);
+       stat = mxread(blaster, 0x82);           /* get irq status */
+       if(stat & 3){
+               contindma(ctlr);
                if(stat & 2)
-                       inb(blaster.clri16);
+                       inb(blaster->clri16);
                else if(stat & 1)
-                       inb(blaster.clri8);
-               if(stat & 3){
-                       contindma();
-                       iunlock(&blaster);
-
-                       audio.intr = 1;
-                       wakeup(&audio.vous);
-               }
-               if(stat & 4)
-                       inb(blaster.clri401);
-       }
+                       inb(blaster->clri8);
+       } else if(stat & 4)
+               inb(blaster->clri401);
+       iunlock(blaster);
 }
 
 static void
-ess1688intr(void)
-{
-       if(audio.active){
-               ilock(&blaster);
-               contindma();
-               inb(blaster.clri8);
-               iunlock(&blaster);
-               audio.intr = 1;
-               wakeup(&audio.vous);
-               return;
-       }
-       print("#A%d: unexpected ess1688 interrupt\n", audio.ctlrno);
-}
-
-void
-audiosbintr(void)
+ess1688intr(Ctlr *ctlr)
 {
-       /*
-        * Carrera interrupt interface.
-        */
-       blaster.intr();
+       Blaster *blaster;
+
+       blaster = &ctlr->blaster;
+       ilock(blaster);
+       contindma(ctlr);
+       inb(blaster->clri8);
+       iunlock(blaster);
 }
 
 static void
-pcaudiosbintr(Ureg*, void*)
+audiointr(Ureg *, void *arg)
 {
-       /*
-        * x86 interrupt interface.
-        */
-       blaster.intr();
+       Audio *adev;
+       Ctlr *ctlr;
+
+       adev = arg;
+       ctlr = adev->ctlr;
+       if(!ctlr->active){
+               iprint("#A%d: unexpected %s interrupt\n",
+                       ctlr->adev->ctlrno, ctlr->adev->name);
+               return;
+       }
+       ctlr->blaster.intr(ctlr);
 }
 
-static int
-anybuf(void*)
+static void
+setempty(Ctlr *ctlr)
 {
-       return audio.intr;
+       ilock(&ctlr->blaster);
+       ctlr->ring.ri = 0;
+       ctlr->ring.wi = 0;
+       iunlock(&ctlr->blaster);
 }
 
-/*
- * wait for some output to get
- * empty buffers back.
- */
-static int
-waitaudio(void)
+static long
+audiobuffered(Audio *adev)
 {
-
-       audio.intr = 0;
-       pokeaudio();
-       tsleep(&audio.vous, anybuf, 0, 10000);
-       if(audio.intr == 0) {
-               audio.active = 0;
-               print("#A%d: audio timeout\n", audio.ctlrno);   /**/
-               return -1;
-       }
-       return 0;
+       return buffered(&((Ctlr*)adev->ctlr)->ring);
 }
 
-static void
-swab(uchar *a)
+static long
+audiostatus(Audio *adev, void *a, long n, vlong)
 {
-       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;
-       }
+       Ctlr *ctlr = adev->ctlr;
+       return snprint((char*)a, n, "bufsize %6d buffered %6ld\n",
+               Blocksize, buffered(&ctlr->ring));
 }
 
-static void
-sbbufinit(void)
+static int
+inactive(void *arg)
 {
-       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;
-       }
+       Ctlr *ctlr = arg;
+       return !ctlr->active;
 }
 
-static void
-setempty(void)
+static int
+anybuf(void *arg)
 {
-       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);
+       Ctlr *ctlr = arg;
+       return available(&ctlr->ring) || inactive(ctlr);
 }
 
-static void
-resetlevel(void)
+static int
+ratebuf(void *arg)
 {
-       int i;
+       Ctlr *ctlr = arg;
+       int delay = ctlr->adev->delay*4;
+       return (delay <= 0) || (buffered(&ctlr->ring) <= delay) || inactive(ctlr);
+}
 
-       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
+audiowrite(Audio *adev, void *vp, long n, vlong)
+{
+       uchar *p, *e;
+       Ctlr *ctlr;
+       Ring *ring;
+
+       p = vp;
+       e = p + n;
+       ctlr = adev->ctlr;
+       ring = &ctlr->ring;
+       while(p < e) {
+               if((n = writering(ring, p, e - p)) <= 0){
+                       if(!ctlr->active && ring->ri == 0)
+                               ctlr->blaster.startdma(ctlr);
+                       if(!ctlr->active)
+                               setempty(ctlr);
+                       else
+                               sleep(&ctlr->vous, anybuf, ctlr);
+               }
+               p += n;
        }
+       while(ratebuf(ctlr) == 0)
+               sleep(&ctlr->vous, ratebuf, ctlr);
+       return p - (uchar*)vp;
 }
 
-static long
-audiobuffered(Audio *)
+static void
+audioclose(Audio *adev, int mode)
 {
-       return audio.buffered;
+       Ctlr *ctlr;
+
+       if(mode == OREAD)
+               return;
+       ctlr = adev->ctlr;
+       sleep(&ctlr->vous, inactive, ctlr);
+       setempty(ctlr);
 }
 
 static long
-audiostatus(Audio *, void *a, long n, vlong off)
+audiovolread(Audio *adev, void *a, long n, vlong)
 {
-       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);
+       return genaudiovolread(adev, a, n, 0, voltab, mxgetvol, 0);
 }
 
 static long
-audiowrite(Audio *, void *vp, long n, vlong)
+audiovolwrite(Audio *adev, void *a, long n, vlong)
 {
-       long m, n0;
-       Buf *b;
-       char *a;
-
-       a = vp;
-       n0 = n;
-       qlock(&audio);
-       if(waserror()){
-               qunlock(&audio);
-               nexterror();
-       }
+       Blaster *blaster;
+       Ctlr *ctlr;
+       int source;
 
-       while(n > 0) {
-               b = audio.filling;
-               if(b == 0) {
-                       b = getbuf(&audio.empty);
-                       if(b == 0) {
-                               if(waitaudio())
-                                       pokeaudio();
-                               continue;
-                       }
-                       audio.filling = b;
-                       audio.curcount = 0;
-               }
+       ctlr = adev->ctlr;
+       blaster = &ctlr->blaster;
 
-               m = Bufsize-audio.curcount;
-               if(m > n)
-                       m = n;
-               memmove(b->virt+audio.curcount, a, m);
+       n = genaudiovolwrite(adev, a, n, 0, voltab, mxsetvol, 0);
 
-               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);
+       source = 0;
+       if(ctlr->lvol[Vsynth])
+               source |= 1<<6;
+       if(ctlr->rvol[Vsynth])
+               source |= 1<<5;
+       if(ctlr->lvol[Vaudio])
+               source |= 1<<4;
+       if(ctlr->rvol[Vaudio])
+               source |= 1<<3;
+       if(ctlr->lvol[Vcd])
+               source |= 1<<2;
+       if(ctlr->rvol[Vcd])
+               source |= 1<<1;
+       if(ctlr->lvol[Vmic])
+               source |= 1<<0;
 
-       return n0 - n;
-}
+       ilock(blaster);
+       mxcmd(blaster, 0x3c, source);   /* output switch */
+       mxcmd(blaster, 0x3d, source);   /* input left switch */
+       mxcmd(blaster, 0x3e, source);   /* input right switch */
+       iunlock(blaster);
 
-static void
-audioclose(Audio *)
-{
-       qlock(&audio);
-       if(waserror()){
-               qunlock(&audio);
-               nexterror();
-       }
-       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();
-       }
-       while(audio.active && waitaudio() == 0)
-               ;
-       setempty();
-       audio.curcount = 0;
-       poperror();
-       qunlock(&audio);
+       return n;
 }
 
 static int
-ess1688(ISAConf* sbconf)
+ess1688(ISAConf* sbconf, Blaster *blaster, int ctlrno)
 {
        int i, major, minor;
 
        /*
         * Try for ESS1688.
         */
-       sbcmd(0xE7);                    /* get version */
-       major = sbread();
-       minor = sbread();
+       sbcmd(blaster, 0xE7);                   /* get version */
+       major = sbread(blaster);
+       minor = sbread(blaster);
        if(major != 0x68 || minor != 0x8B){
-               print("#A%d: model %#.2x %#.2x; not ESS1688 compatible\n", audio.ctlrno, major, minor);
-               return 1;
+               print("#A%d: model %#.2x %#.2x; not ESS1688 compatible\n",
+                       ctlrno, major, minor);
+               return -1;
        }
 
-       ess1688reset();
+       ess1688reset(blaster, ctlrno);
 
        switch(sbconf->irq){
        case 2:
@@ -800,10 +643,10 @@ ess1688(ISAConf* sbconf)
                i = 0x50|(3<<2);
                break;
        default:
-               print("#A%d: bad ESS1688 irq %d\n", audio.ctlrno, sbconf->irq);
+               print("#A%d: bad ESS1688 irq %d\n", ctlrno, sbconf->irq);
                return 1;
        }
-       ess1688w(0xB1, i);
+       ess1688w(blaster, 0xB1, i);
 
        switch(sbconf->dma){
        case 0:
@@ -816,15 +659,15 @@ ess1688(ISAConf* sbconf)
                i = 0x50|(3<<2);
                break;
        default:
-               print("#A%d: bad ESS1688 dma %lud\n", audio.ctlrno, sbconf->dma);
+               print("#A%d: bad ESS1688 dma %lud\n", ctlrno, sbconf->dma);
                return 1;
        }
-       ess1688w(0xB2, i);
+       ess1688w(blaster, 0xB2, i);
 
-       ess1688reset();
+       ess1688reset(blaster, ctlrno);
 
-       blaster.startdma = ess1688startdma;
-       blaster.intr = ess1688intr;
+       blaster->startdma = ess1688startdma;
+       blaster->intr = ess1688intr;
 
        return 0;
 }
@@ -832,157 +675,179 @@ ess1688(ISAConf* sbconf)
 static int
 audioprobe(Audio *adev)
 {
-       ISAConf sbconf;
+       static int irq[] = {9,5,7,10};
+       static Ctlr *cards = nil;
+
+       Ctlr *ctlr;
+       Blaster *blaster;
        int i, x;
-       static int irq[] = {2,5,7,10};
-       static int dma16[] = {0,5,6,7};
 
-       if(audio.probed)
-               return -1;
+       /* make a list of audio isa cards if not already done */
+       if(cards == nil){
+               for(i=0; i<nelem(irq); i++){
+                       ctlr = mallocz(sizeof(Ctlr), 1);
+                       if(ctlr == nil){
+                               print("sb16: can't allocate memory\n");
+                               break;
+                       }
+                       ctlr->conf.port = 0x220 + i*0x10;
+                       ctlr->conf.irq = irq[i];
+                       ctlr->conf.dma = 0;
+                       if(isaconfig("audio", i, &ctlr->conf) == 0){
+                               free(ctlr);
+                               break;
+                       }
+                       ctlr->next = cards;
+                       cards = ctlr;
+               }
+       }
 
-       sbconf.port = 0x220;
-       sbconf.dma = 1;
-       sbconf.irq = 5;
-       if(isaconfig("audio", adev->ctlrno, &sbconf) == 0)
-               return -1;
+       /* pick a card */
+       for(ctlr = cards; ctlr; ctlr = ctlr->next){
+               if(ctlr->conf.type && strcmp(adev->name, ctlr->conf.type) == 0){
+                       ctlr->conf.type = nil;
+                       goto Found;
+               }
+       }
+       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){
+Found:
+       switch(ctlr->conf.port){
        case 0x220:
        case 0x240:
        case 0x260:
        case 0x280:
                break;
        default:
-               print("#A%d: bad port %#lux\n", audio.ctlrno, sbconf.port);
+               print("#A%d: bad port %#lux\n", adev->ctlrno, ctlr->conf.port);
                return -1;
        }
 
-       if(ioalloc(sbconf.port, 0x10, 0, "audio") < 0){
-               print("#A%d: cannot ioalloc range %lux+0x10\n", audio.ctlrno, sbconf.port);
+       if(ioalloc(ctlr->conf.port, 0x10, 0, "audio") < 0){
+               print("#A%d: cannot ioalloc range %lux+0x10\n",
+                       adev->ctlrno, ctlr->conf.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);
+       if(ioalloc(ctlr->conf.port+0x100, 1, 0, "audio.mpu401") < 0){
+               iofree(ctlr->conf.port);
+               print("#A%d: cannot ioalloc range %lux+0x01\n",
+                       adev->ctlrno, ctlr->conf.port+0x100);
                return -1;
        }
 
-       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(3);                       /* >3 υs */
-       outb(blaster.reset, 0);
+       ctlr->adev = adev;
+       adev->ctlr = ctlr;
+
+       blaster = &ctlr->blaster;
+       blaster->reset = ctlr->conf.port + 0x6;
+       blaster->read = ctlr->conf.port + 0xa;
+       blaster->write = ctlr->conf.port + 0xc;
+       blaster->wstatus = ctlr->conf.port + 0xc;
+       blaster->rstatus = ctlr->conf.port + 0xe;
+       blaster->mixaddr = ctlr->conf.port + 0x4;
+       blaster->mixdata = ctlr->conf.port + 0x5;
+       blaster->clri8 = ctlr->conf.port + 0xe;
+       blaster->clri16 = ctlr->conf.port + 0xf;
+       blaster->clri401 = ctlr->conf.port + 0x100;
+
+       blaster->startdma = sb16startdma;
+       blaster->intr = sb16intr;
+
+       outb(blaster->reset, 1);
+       delay(1);                       /* >3 υs */
+       outb(blaster->reset, 0);
        delay(1);
 
-       i = sbread();
+       i = sbread(blaster);
        if(i != 0xaa) {
-               print("#A%d: no response #%.2x\n", audio.ctlrno, i);
-               iofree(sbconf.port);
-               iofree(sbconf.port+0x100);
+               print("#A%d: no response #%.2x\n", adev->ctlrno, i);
+Errout:
+               iofree(ctlr->conf.port);
+               iofree(ctlr->conf.port+0x100);
                return -1;
        }
 
-       sbcmd(0xe1);                    /* get version */
-       audio.major = sbread();
-       audio.minor = sbread();
+       sbcmd(blaster, 0xe1);                   /* get version */
+       ctlr->major = sbread(blaster);
+       ctlr->minor = sbread(blaster);
 
-       if(audio.major != 4) {
-               if(audio.major != 3 || audio.minor != 1 || ess1688(&sbconf)){
+       if(ctlr->major != 4) {
+               if(ctlr->major != 3 || ctlr->minor != 1 ||
+                       ess1688(&ctlr->conf, blaster, adev->ctlrno)){
                        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;
+                               adev->ctlrno, ctlr->major, ctlr->minor);
+                       goto Errout;
                }
-               audio.major = 4;
+               ctlr->major = 4;
        }
 
        /*
         * initialize the mixer
         */
-       mxcmd(0x00, 0);                 /* Reset mixer */
-       mxvolume();
+       mxcmd(blaster, 0x00, 0);                        /* Reset mixer */
 
-       /*
-        * 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.
-        */
+       for(i=0; i<Nvol; i++){
+               int a[2];
+
+               a[0] = 0;
+               a[1] = 0;
+               mxsetvol(adev, i, a);
+       }
+
+       /* set irq */
        for(i=0; i<nelem(irq); i++){
-               if(irq[i] == sbconf.irq){
-                       mxcmd(0x80, 1<<i);
+               if(ctlr->conf.irq == irq[i]){
+                       mxcmd(blaster, 0x80, 1<<i);
                        break;
                }
        }
-       x = mxread(0x80);
-       for(i=0; i<nelem(irq); i++)
+       x = mxread(blaster, 0x80);
+       for(i=0; i<nelem(irq); i++){
                if(x & (1<<i)){
-                       sbconf.irq = irq[i];
+                       ctlr->conf.irq = irq[i];
                        break;
                }
+       }
 
-       if(blaster.dma < 4)
-               mxcmd(0x81, 1<<blaster.dma);
-       else {
-               for(i=0; i<nelem(dma16); i++){
-                       if(dma16[i] == blaster.dma){
-                               x = mxread(0x81);
-                               mxcmd(0x81, 0x10<<i | (x & 0xf));
+       for(;;){
+               /* set 16bit dma */
+               if(ctlr->conf.dma>=5 && ctlr->conf.dma<=7){
+                       x = mxread(blaster, 0x81);
+                       mxcmd(blaster, 0x81, (1<<ctlr->conf.dma) & 0xF0 | (x & 0x0F));
+               }
+               x = mxread(blaster, 0x81);
+               for(i=5; i<=7; i++){
+                       if(x & (1<<i)){
+                               ctlr->conf.dma = i;
                                break;
                        }
                }
-       }
-       x = mxread(0x81);
-       for(i=0; i<4; i++)
-               if(x & (1<<i))
-                       blaster.dma = i;
-       for(i=0; i<nelem(dma16); i++)
-               if(x & (0x10<<i)){
-                       blaster.dma = dma16[i];
+               if(ctlr->conf.dma>=5)
                        break;
-               }
+               ctlr->conf.dma = 7;
+       }
 
-       print("#A%d: %s port 0x%04lux irq %d dma %d\n", audio.ctlrno, sbconf.type,
-               sbconf.port, sbconf.irq, blaster.dma);
+       print("#A%d: %s port 0x%04lux irq %d dma %lud\n", adev->ctlrno, adev->name,
+               ctlr->conf.port, ctlr->conf.irq, ctlr->conf.dma);
 
-       if(dmainit(blaster.dma, Bufsize))
-               return -1;
-       intrenable(sbconf.irq, pcaudiosbintr, 0, BUSUNKNOWN, "sb16");
+       ctlr->ring.nbuf = Blocks*Blocksize;
+       if(dmainit(ctlr->conf.dma, ctlr->ring.nbuf))
+               goto Errout;
+       ctlr->ring.buf = dmabva(ctlr->conf.dma);
+       print("#A%d: %s dma buffer %p-%p\n", adev->ctlrno, adev->name,
+               ctlr->ring.buf, ctlr->ring.buf+ctlr->ring.nbuf);
 
-       sbbufinit();
-       setempty();
-       mxvolume();
+       setempty(ctlr);
 
        adev->write = audiowrite;
        adev->close = audioclose;
+       adev->volread = audiovolread;
+       adev->volwrite = audiovolwrite;
        adev->status = audiostatus;
        adev->buffered = audiobuffered;
 
+       intrenable(ctlr->conf.irq, audiointr, adev, BUSUNKNOWN, adev->name);
+
        return 0;
 }
 
@@ -990,4 +855,5 @@ void
 audiosb16link(void)
 {
        addaudiocard("sb16", audioprobe);
+       addaudiocard("ess1688", audioprobe);
 }