@@ 0,0 1,841 @@
+/*
+ * SB 16 driver
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "devtab.h"
+#include "io.h"
+#include "audio.h"
+
+
+#define nelem(x) (sizeof (x)/sizeof(x[0]))
+#define NPORT (sizeof audiodir/sizeof(Dirtab))
+
+typedef struct Buf Buf;
+typedef struct Level Level;
+
+enum
+{
+ Qdir = 0,
+ Qaudio,
+ Qvolume,
+
+ Fmono = 1,
+
+ Aread = 0,
+ Awrite,
+
+ Speed = 44100,
+ Ncmd = 50, /* max volume command words */
+};
+
+Dirtab
+audiodir[] =
+{
+ "audio", {Qaudio}, 0, 0666,
+ "volume", {Qvolume}, 0, 0666,
+};
+
+struct Level
+{
+ int master; /* mixer output volume control */
+ int ogain; /* extra gain after master */
+ int pcm; /* mixer volume on D/A (voice) */
+ int synth; /* mixer volume on synthesizer (MIDI) */
+ int cd; /* mixer volume on cd */
+ int line; /* mixer volume on line */
+ int igain; /* mixer volume to A/D */
+ int treb; /* treb control */
+ int bass; /* bass control */
+ int iswitch; /* input on/off switches */
+};
+struct Buf
+{
+ uchar* virt;
+ ulong phys;
+ Buf* next;
+};
+struct Queue
+{
+ Lock;
+ Buf* first;
+ Buf* last;
+};
+static struct
+{
+ Rendez vous;
+ 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; /* Aread/Awrite for /audio */
+ Level left; /* all of left volumes */
+ Level right; /* all of right volumes */
+ int mic; /* mono level */
+ int speaker; /* mono level */
+ int oswitch; /* output on/off switches */
+ int speed; /* pcm sample rate, doesnt change w stereo */
+ int major; /* SB16 major version number (sb 4) */
+ int minor; /* SB16 minor version number */
+ char place[20]; /* static place to decode words */
+
+ Buf buf[Nbuf]; /* buffers and queues */
+ Queue empty;
+ Queue full;
+ Buf* current;
+ Buf* filling;
+} audio;
+
+static struct
+{
+ char* name;
+ int* ptr1;
+ int* ptr2;
+ int flag;
+ int ilval;
+ int irval;
+} volumes[] =
+{
+ "master", &audio.left.master, &audio.right.master, 0, 50, 50,
+ "ogain", &audio.left.ogain, &audio.right.ogain, 0, 0, 0,
+ "igain", &audio.left.igain, &audio.right.igain, 0, 0, 0,
+
+ "treb", &audio.left.treb, &audio.right.treb, 0, 50, 50,
+ "bass", &audio.left.bass, &audio.right.bass, 0, 50, 50,
+
+ "pcm", &audio.left.pcm, &audio.right.pcm, 0, 90, 90,
+ "synth", &audio.left.synth, &audio.right.synth, 0, 90, 90,
+ "cd", &audio.left.cd, &audio.right.cd, 0, 81, 81,
+ "line", &audio.left.line, &audio.right.line, 0, 65, 65,
+
+ "mic", &audio.mic, &audio.mic, Fmono, 0, 0,
+ "speaker", &audio.speaker, &audio.speaker, Fmono, 0, 0,
+ "oswitch", &audio.oswitch, &audio.oswitch, Fmono, 31, 31,
+ "iswitch", &audio.left.iswitch, &audio.right.iswitch, 0, 85, 43,
+
+ "speed", &audio.speed, &audio.speed, Fmono, Speed, Speed,
+ 0
+};
+
+static void swab(uchar*);
+
+static char Emajor[] = "SB16 version too old";
+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(PORT_WSTATUS);
+ if((s & 0x80) == 0) {
+ outb(PORT_WRITE, val);
+ return 0;
+ }
+ }
+/* print("SB16 sbcmd (#%.2x) timeout\n", val); /**/
+ return 1;
+}
+
+static int
+sbread(void)
+{
+ int i, s;
+
+ for(i=1<<16; i!=0; i--) {
+ s = inb(PORT_RSTATUS);
+ if((s & 0x80) != 0) {
+ return inb(PORT_READ);
+ }
+ }
+/* print("SB16 sbread did not respond\n"); /**/
+ return 0xbb;
+}
+
+static int
+mxcmd(int addr, int val)
+{
+
+ outb(PORT_MIXER_ADDR, addr);
+ outb(PORT_MIXER_DATA, val);
+ return 1;
+}
+
+static int
+mxread(int addr)
+{
+ int s;
+
+ outb(PORT_MIXER_ADDR, addr);
+ s = inb(PORT_MIXER_DATA);
+ 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)
+{
+ ulong sp;
+
+ sp = splhi();
+ mxcmds(0x30, audio.left.master);
+ mxcmds(0x31, audio.right.master);
+
+ mxcmdt(0x32, audio.left.pcm);
+ mxcmdt(0x33, audio.right.pcm);
+
+ mxcmdt(0x34, audio.left.synth);
+ mxcmdt(0x35, audio.right.synth);
+
+ mxcmdt(0x36, audio.left.cd);
+ mxcmdt(0x37, audio.right.cd);
+
+ mxcmdt(0x38, audio.left.line);
+ mxcmdt(0x39, audio.right.line);
+
+ mxcmdt(0x3a, audio.mic);
+ mxcmdt(0x3b, audio.speaker);
+
+ mxcmds(0x3f, audio.left.igain);
+ mxcmds(0x40, audio.right.igain);
+ mxcmds(0x41, audio.left.ogain);
+ mxcmds(0x42, audio.right.ogain);
+
+ mxcmdu(0x44, audio.left.treb);
+ mxcmdu(0x45, audio.right.treb);
+
+ mxcmdu(0x46, audio.left.bass);
+ mxcmdu(0x47, audio.right.bass);
+
+ mxcmd(0x3c, audio.oswitch);
+ mxcmd(0x3d, audio.left.iswitch);
+ mxcmd(0x3e, audio.right.iswitch);
+ splx(sp);
+}
+
+static Buf*
+getbuf(Queue *q)
+{
+ Buf *b;
+
+ ilock(q);
+ b = q->first;
+ if(b)
+ q->first = b->next;
+ iunlock(q);
+
+ return b;
+}
+
+static void
+putbuf(Queue *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;
+ ulong count, addr;
+
+ if(!audio.active)
+ goto shutdown;
+
+ b = audio.current;
+ if(audio.amode == Aread) {
+ if(b) /* shouldnt happen */
+ putbuf(&audio.full, b);
+ b = getbuf(&audio.empty);
+ } else {
+ if(b) /* shouldnt happen */
+ putbuf(&audio.empty, b);
+ b = getbuf(&audio.full);
+ }
+ audio.current = b;
+ if(b == 0)
+ goto shutdown;
+
+ addr = b->phys;
+ count = ((Bufsize) >> 1) - 1;
+
+/* outb(DMA2_WRMASK, 4|(Dma&3)); /* disable dma */
+ outb(DMA6_LPAGE, (addr>>16) & 0xfe);
+ outb(DMA2_CLRBP, 0); /* clear ff */
+ outb(DMA6_ADDRESS, addr>>1);
+ outb(DMA6_ADDRESS, addr>>9);
+
+ outb(DMA2_CLRBP, 0); /* clear ff */
+ outb(DMA6_COUNT, count);
+ outb(DMA6_COUNT, count>>8);
+ outb(DMA2_WRMASK, (Dma&3)); /* enable dma */
+
+ return;
+
+shutdown:
+ outb(DMA2_WRMASK, 4|(Dma&3)); /* disable 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
+startdma(void)
+{
+ ulong count;
+
+ outb(DMA2_WRMASK, 4|(Dma&3)); /* disable dma */
+ if(audio.amode == Aread) {
+ outb(DMA2_WRMODE, 0x44 | (Dma&3)); /* set mode */
+ sbcmd(0x42); /* input sampling rate */
+ } else {
+ outb(DMA2_WRMODE, 0x48 | (Dma&3)); /* set mode */
+ sbcmd(0x41); /* output sampling rate */
+ }
+ sbcmd(audio.speed>>8);
+ sbcmd(audio.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();
+}
+
+/*
+ * if audio is stopped,
+ * start it up again.
+ */
+static void
+pokeaudio(void)
+{
+ ulong sp;
+
+ sp = splhi();
+ if(!audio.active)
+ startdma();
+ splx(sp);
+}
+
+void
+audiosbintr(void)
+{
+ int stat, dummy;
+
+ stat = mxread(0x82) & 7; /* get irq status */
+ if(stat) {
+ dummy = 0;
+ if(stat & 2) {
+ dummy = inb(PORT_CLRI16);
+ contindma();
+ audio.intr = 1;
+ wakeup(&audio.vous);
+ }
+ if(stat & 1) {
+ dummy = inb(PORT_CLRI8);
+ }
+ if(stat & 4) {
+ dummy = inb(PORT_CLRI401);
+ }
+ USED(dummy);
+ }
+}
+
+void
+audiodmaintr(void)
+{
+/* print("sb16 dma interrupt\n"); /**/
+}
+
+static int
+anybuf(void *p)
+{
+ USED(p);
+ 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, 10*1000);
+ if(audio.intr == 0) {
+/* print("audio timeout\n"); /**/
+ audio.active = 0;
+ pokeaudio();
+ }
+}
+
+static void
+sbbufinit(void)
+{
+ int i;
+ void *p;
+
+ for(i=0; i<Nbuf; i++) {
+ p = xspanalloc(Bufsize, CACHELINESZ, 64*1024);
+ dcflush(p, Bufsize);
+ audio.buf[i].virt = UNCACHED(uchar, p);
+ audio.buf[i].phys = (ulong)PADDR(p);
+ }
+}
+
+static void
+setempty(void)
+{
+ int i;
+
+ audio.empty.first = 0;
+ audio.empty.last = 0;
+ audio.full.first = 0;
+ audio.full.last = 0;
+ audio.current = 0;
+ audio.filling = 0;
+ for(i=0; i<Nbuf; i++)
+ putbuf(&audio.empty, &audio.buf[i]);
+}
+
+void
+audioreset(void)
+{
+}
+
+static void
+resetlevel(void)
+{
+ int i;
+
+ for(i=0; volumes[i].name; i++) {
+ *volumes[i].ptr1 = volumes[i].ilval;
+ *volumes[i].ptr2 = volumes[i].irval;
+ }
+}
+
+void
+audioinit(void)
+{
+ int i;
+
+ seteisadma(Dma, audiodmaintr);
+
+ resetlevel();
+
+ outb(PORT_RESET, 1);
+ delay(1); /* >3 Ï…s */
+ outb(PORT_RESET, 0);
+ delay(1);
+
+ i = sbread();
+ if(i != 0xaa) {
+ print("sound blaster didnt respond #%.2x\n", i);
+ return;
+ }
+
+ sbcmd(0xe1); /* get version */
+ audio.major = sbread();
+ audio.minor = sbread();
+
+ if(audio.major != 4) {
+ print("bad soundblaster model #%.2x #.2x\n", audio.major, audio.minor);
+ return;
+ }
+ /*
+ * initialize the mixer
+ */
+ mxcmd(0x00, 0); /* Reset mixer */
+ mxvolume();
+
+ /*
+ * set up irq/dma chans
+ */
+ mxcmd(0x80, /* irq */
+ (Irq==2)? 1:
+ (Irq==5)? 2:
+ (Irq==7)? 4:
+ (Irq==10)? 8:
+ 0);
+ mxcmd(0x81, 1<<Dma); /* dma */
+}
+
+Chan*
+audioattach(char *param)
+{
+ return devattach('A', param);
+}
+
+Chan*
+audioclone(Chan *c, Chan *nc)
+{
+ return devclone(c, nc);
+}
+
+int
+audiowalk(Chan *c, char *name)
+{
+ return devwalk(c, name, audiodir, NPORT, devgen);
+}
+
+void
+audiostat(Chan *c, char *db)
+{
+ devstat(c, db, audiodir, NPORT, devgen);
+}
+
+Chan*
+audioopen(Chan *c, int omode)
+{
+
+ if(audio.major != 4)
+ error(Emajor);
+
+ switch(c->qid.path & ~CHDIR) {
+ default:
+ error(Eperm);
+ break;
+
+ case Qvolume:
+ case Qdir:
+ break;
+
+ case Qaudio:
+ if(audio.bufinit == 0) {
+ audio.bufinit = 1;
+ sbbufinit();
+ }
+
+ audio.amode = Awrite;
+ if((omode&7) == OREAD)
+ audio.amode = Aread;
+
+ setempty();
+ audio.curcount = 0;
+ break;
+ }
+ c = devopen(c, omode, audiodir, NPORT, devgen);
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+
+ return c;
+}
+
+void
+audiocreate(Chan *c, char *name, int omode, ulong perm)
+{
+ USED(c);
+ USED(name);
+ USED(omode);
+ USED(perm);
+
+ error(Eperm);
+}
+
+void
+audioclose(Chan *c)
+{
+
+ switch(c->qid.path & ~CHDIR) {
+ default:
+ error(Eperm);
+ break;
+
+ case Qdir:
+ case Qvolume:
+ break;
+
+ case Qaudio:
+ if(c->flag & COPEN) {
+ while(audio.active)
+ waitaudio();
+ setempty();
+ }
+ break;
+ }
+}
+
+long
+audioread(Chan *c, char *a, long n, ulong offset)
+{
+ long m, o, n0, bn;
+ char buf[256];
+ Buf *b;
+ int j;
+
+ n0 = n;
+ switch(c->qid.path & ~CHDIR) {
+ default:
+ error(Eperm);
+ break;
+
+ case Qdir:
+ return devdirread(c, a, n, audiodir, NPORT, devgen);
+
+ case Qaudio:
+ if(audio.amode != Aread)
+ error(Emode);
+ 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;
+ if(audio.curcount >= Bufsize) {
+ audio.filling = 0;
+ putbuf(&audio.empty, b);
+ }
+ }
+ break;
+
+ case Qvolume:
+ j = 0;
+ for(m=0; volumes[m].name; m++) {
+ o = *volumes[m].ptr1;
+ if(volumes[m].flag & Fmono)
+ j += snprint(buf+j, sizeof(buf)-j, "%s %d\n", volumes[m].name, o);
+ else {
+ bn = *volumes[m].ptr2;
+ if(o == bn)
+ j += snprint(buf+j, sizeof(buf)-j, "%s both %d\n", volumes[m].name, o);
+ else
+ j += snprint(buf+j, sizeof(buf)-j, "%s left %d right %d\n",
+ volumes[m].name, o, bn);
+ }
+ }
+
+ return readstr(offset, a, n, buf);
+ }
+ return n0-n;
+}
+
+Block*
+audiobread(Chan *c, long n, ulong offset)
+{
+ return devbread(c, n, offset);
+}
+
+long
+audiowrite(Chan *c, char *a, long n, ulong offset)
+{
+ long m, n0;
+ int i, nf, v, left, right;
+ char buf[255], *field[Ncmd];
+ Buf *b;
+
+ USED(offset);
+
+ n0 = n;
+ switch(c->qid.path & ~CHDIR) {
+ default:
+ error(Eperm);
+ break;
+
+ case Qvolume:
+ v = 0;
+ left = 1;
+ right = 1;
+ if(n > sizeof(buf)-1)
+ n = sizeof(buf)-1;
+ memmove(buf, a, n);
+ buf[n] = '\0';
+
+ nf = getfields(buf, field, Ncmd, " \t\n");
+ for(i = 0; i < nf; i++){
+ /*
+ * a number is volume
+ */
+ if(field[i][0] >= '0' && field[i][0] <= '9') {
+ m = strtoul(field[i], 0, 10);
+ if(left)
+ *volumes[v].ptr1 = m;
+ if(right)
+ *volumes[v].ptr2 = m;
+ mxvolume();
+ goto cont0;
+ }
+
+ for(m=0; volumes[m].name; m++) {
+ if(strcmp(field[i], volumes[m].name) == 0) {
+ v = m;
+ goto cont0;
+ }
+ }
+
+ if(strcmp(field[i], "reset") == 0) {
+ resetlevel();
+ mxvolume();
+ goto cont0;
+ }
+ if(strcmp(field[i], "left") == 0) {
+ left = 1;
+ right = 0;
+ goto cont0;
+ }
+ if(strcmp(field[i], "right") == 0) {
+ left = 0;
+ right = 1;
+ goto cont0;
+ }
+ if(strcmp(field[i], "both") == 0) {
+ left = 1;
+ right = 1;
+ goto cont0;
+ }
+ error(Evolume);
+ break;
+ cont0:;
+ }
+ break;
+
+ case Qaudio:
+ if(audio.amode != Awrite)
+ error(Emode);
+ 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;
+ if(audio.curcount >= Bufsize) {
+ audio.filling = 0;
+ swab(b->virt);
+ putbuf(&audio.full, b);
+ }
+ }
+ break;
+ }
+ return n0 - n;
+}
+
+long
+audiobwrite(Chan *c, Block *bp, ulong offset)
+{
+ return devbwrite(c, bp, offset);
+}
+
+void
+audioremove(Chan *c)
+{
+ USED(c);
+
+ error(Eperm);
+}
+
+void
+audiowstat(Chan *c, char *dp)
+{
+ USED(c);
+ USED(dp);
+
+ error(Eperm);
+}
+
+static void
+swab(uchar *a)
+{
+ ulong *p, *ep, b;
+
+ 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;
+ }
+}