summaryrefslogtreecommitdiff
path: root/os/ipaq1110/devaudio.c
diff options
context:
space:
mode:
Diffstat (limited to 'os/ipaq1110/devaudio.c')
-rw-r--r--os/ipaq1110/devaudio.c1056
1 files changed, 1056 insertions, 0 deletions
diff --git a/os/ipaq1110/devaudio.c b/os/ipaq1110/devaudio.c
new file mode 100644
index 00000000..66fb5cdc
--- /dev/null
+++ b/os/ipaq1110/devaudio.c
@@ -0,0 +1,1056 @@
+/*
+ * SAC/UDA 1341 Audio driver for the Bitsy
+ *
+ * This code is covered by the Lucent Public Licence 1.02 (http://plan9.bell-labs.com/plan9dist/license.html);
+ * see the file NOTICE in the current directory. Modifications for the Inferno environment by Vita Nuova.
+ *
+ * The Philips UDA 1341 sound chip is accessed through the Serial Audio
+ * Controller (SAC) of the StrongARM SA-1110.
+ *
+ * The code morphs Nicolas Pitre's <nico@cam.org> Linux controller
+ * and Ken's Soundblaster controller.
+ *
+ * The interface should be identical to that of devaudio.c
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "io.h"
+
+static int debug = 0;
+
+/* UDA 1341 Registers */
+enum {
+ /* Status0 register */
+ UdaStatusDC = 0, /* 1 bit */
+ UdaStatusIF = 1, /* 3 bits */
+ UdaStatusSC = 4, /* 2 bits */
+ UdaStatusRST = 6, /* 1 bit */
+};
+
+enum {
+ /* Status1 register */
+ UdaStatusPC = 0, /* 2 bits */
+ UdaStatusDS = 2, /* 1 bit */
+ UdaStatusPDA = 3, /* 1 bit */
+ UdaStatusPAD = 4, /* 1 bit */
+ UdaStatusIGS = 5, /* 1 bit */
+ UdaStatusOGS = 6, /* 1 bit */
+};
+
+/*
+ * UDA1341 L3 address and command types
+ */
+
+enum {
+ UDA1341_DATA0 = 0,
+ UDA1341_DATA1,
+ UDA1341_STATUS,
+ UDA1341_L3Addr = 0x14,
+};
+
+typedef struct AQueue AQueue;
+typedef struct Buf Buf;
+typedef struct IOstate IOstate;
+
+enum
+{
+ Qdir = 0,
+ Qaudio,
+ Qvolume,
+ Qstatus,
+ Qaudioctl,
+
+ Fmono = 1,
+ Fin = 2,
+ Fout = 4,
+
+ Aclosed = 0,
+ Aread,
+ Awrite,
+
+ Vaudio = 0,
+ Vmic,
+ Vtreb,
+ Vbass,
+ Vspeed,
+ Vfilter,
+ Vinvert,
+ Nvol,
+
+ Bufsize = 4*1024, /* 46 ms each */
+ Nbuf = 32, /* 1.5 seconds total */
+
+ Speed = 44100,
+ Ncmd = 50, /* max volume command words */
+};
+
+Dirtab
+audiodir[] =
+{
+ ".", {Qdir, 0, QTDIR}, 0, 0555,
+ "audio", {Qaudio}, 0, 0666,
+ "volume", {Qvolume}, 0, 0666,
+ "audioctl", {Qaudioctl}, 0, 0666,
+ "audiostat",{Qstatus}, 0, 0444,
+};
+
+struct Buf
+{
+ uchar* virt;
+ ulong phys;
+ uint nbytes;
+};
+
+struct IOstate
+{
+ QLock;
+ Lock ilock;
+ Rendez vous;
+ Chan *chan; /* chan of open */
+ Dma* dma; /* dma chan, alloc on open, free on close */
+ int bufinit; /* boolean, if buffers allocated */
+ Buf buf[Nbuf]; /* buffers and queues */
+ volatile Buf *current; /* next dma to finish */
+ volatile Buf *next; /* next candidate for dma */
+ volatile Buf *filling; /* buffer being filled */
+/* just be be cute (and to have defines like linux, a real operating system) */
+#define emptying filling
+};
+
+static struct
+{
+ QLock;
+ int amode; /* Aclosed/Aread/Awrite for /audio */
+ int intr; /* boolean an interrupt has happened */
+ int rivol[Nvol]; /* right/left input/output volumes */
+ int livol[Nvol];
+ int rovol[Nvol];
+ int lovol[Nvol];
+ uvlong totcount; /* how many bytes processed since open */
+ vlong tottime; /* time at which totcount bytes were processed */
+ int clockout; /* need steady output to provide input clock */
+ IOstate i;
+ IOstate o;
+} audio;
+
+static struct
+{
+ ulong bytes;
+ ulong totaldma;
+ ulong idledma;
+ ulong faildma;
+ ulong samedma;
+} iostats;
+
+static struct
+{
+ char* name;
+ int flag;
+ int ilval; /* initial values */
+ int irval;
+} volumes[] =
+{
+[Vaudio] {"audio", Fout|Fmono, 80, 80},
+[Vmic] {"mic", Fin|Fmono, 0, 0},
+[Vtreb] {"treb", Fout|Fmono, 50, 50},
+[Vbass] {"bass", Fout|Fmono, 50, 50},
+[Vspeed] {"speed", Fin|Fout|Fmono, Speed, Speed},
+[Vfilter] {"filter", Fout|Fmono, 0, 0},
+[Vinvert] {"invert", Fin|Fout|Fmono, 0, 0},
+[Nvol] {0}
+};
+
+static void setreg(char *name, int val, int n);
+
+static char Emode[] = "illegal open mode";
+static char Evolume[] = "illegal volume specifier";
+
+static void
+bufinit(IOstate *b)
+{
+ int i;
+
+ if (debug) print("bufinit\n");
+ for (i = 0; i < Nbuf; i++) {
+ b->buf[i].virt = xspanalloc(Bufsize, CACHELINESZ, 0);
+ b->buf[i].phys = PADDR(b->buf[i].virt);
+ }
+ b->bufinit = 1;
+};
+
+static void
+setempty(IOstate *b)
+{
+ int i;
+
+ if (debug) print("setempty\n");
+ for (i = 0; i < Nbuf; i++) {
+ b->buf[i].nbytes = 0;
+ }
+ b->filling = b->buf;
+ b->current = b->buf;
+ b->next = b->buf;
+}
+
+static int
+audioqnotempty(void *x)
+{
+ IOstate *s = x;
+
+ return dmaidle(s->dma) || s->emptying != s->current;
+}
+
+static int
+audioqnotfull(void *x)
+{
+ IOstate *s = x;
+
+ return dmaidle(s->dma) || s->filling != s->current;
+}
+
+static void
+audioreset(void)
+{
+ /* Turn MCP operations off */
+ MCPREG->mccr = 0;
+}
+
+uchar status0[1] = {0x22};
+uchar status1[1] = {0x80};
+uchar data00[1] = {0x00}; /* volume control, bits 0 – 5 */
+uchar data01[1] = {0x40};
+uchar data02[1] = {0x80};
+uchar data0e0[2] = {0xc0, 0xe0};
+uchar data0e1[2] = {0xc1, 0xe0};
+uchar data0e2[2] = {0xc2, 0xf2};
+/* there is no data0e3 */
+uchar data0e4[2] = {0xc4, 0xe0};
+uchar data0e5[2] = {0xc5, 0xe0};
+uchar data0e6[2] = {0xc6, 0xe3};
+
+static void
+enable(void)
+{
+ uchar data[1];
+ int cs;
+
+ L3init();
+
+ PPCREG->ppar &= ~PPAR_SPR;
+
+ /* external clock and ssp configured for current samples/sec */
+ cs = archaudiospeed(audio.livol[Vspeed], 1);
+ status0[0] = (status0[0] & ~(3<<4)) | (cs<<4);
+
+ /* Enable the audio power */
+ archaudiopower(1);
+// egpiobits(EGPIO_audio_ic_power | EGPIO_codec_reset, 1);
+
+ /* Wait for the UDA1341 to wake up */
+ delay(100);
+
+ /* Reset the chip */
+ data[0] = status0[0] | 1<<UdaStatusRST;
+ L3write(UDA1341_L3Addr | UDA1341_STATUS, data, 1 );
+ archcodecreset();
+
+ /* write uda 1341 status[0] */
+ L3write(UDA1341_L3Addr | UDA1341_STATUS, status0, 1 );
+ L3write(UDA1341_L3Addr | UDA1341_STATUS, status1, 1);
+ L3write(UDA1341_L3Addr | UDA1341_DATA0, data02, 1);
+ L3write(UDA1341_L3Addr | UDA1341_DATA0, data0e2, 2);
+ L3write(UDA1341_L3Addr | UDA1341_DATA0, data0e6, 2 );
+
+ if (debug) {
+ print("enable: status0 = 0x%2.2ux\n", status0[0]);
+ print("enable: status1 = 0x%2.2ux\n", status1[0]);
+ print("enable: data02 = 0x%2.2ux\n", data02[0]);
+ print("enable: data0e2 = 0x%4.4ux\n", data0e2[0] | data0e2[1]<<8);
+ print("enable: data0e4 = 0x%4.4ux\n", data0e4[0] | data0e4[1]<<8);
+ print("enable: data0e6 = 0x%4.4ux\n", data0e6[0] | data0e6[1]<<8);
+ }
+}
+
+static void
+disable(void)
+{
+ SSPREG->sscr0 = 0x031f; /* disable */
+}
+
+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 void
+mxvolume(void) {
+ int *left, *right;
+ int cs;
+
+ cs = archaudiospeed(audio.livol[Vspeed], 1);
+ status0[0] = (status0[0] & ~(3<<4)) | (cs<<4);
+ L3write(UDA1341_L3Addr | UDA1341_STATUS, status0, 1);
+ if(debug)
+ print("mxvolume: status0 = %2.2ux\n", status0[0]);
+ if(audio.amode & Aread){
+ left = audio.livol;
+ right = audio.rivol;
+ if (left[Vmic]+right[Vmic] == 0) {
+ /* Turn on automatic gain control (AGC) */
+ data0e4[1] |= 0x10;
+ L3write(UDA1341_L3Addr | UDA1341_DATA0, data0e4, 2 );
+ } else {
+ int v;
+ /* Turn on manual gain control */
+ v = ((left[Vmic]+right[Vmic])*0x7f/200)&0x7f;
+ data0e4[1] &= ~0x13;
+ data0e5[1] &= ~0x1f;
+ data0e4[1] |= v & 0x3;
+ data0e5[0] |= (v & 0x7c)<<6;
+ data0e5[1] |= (v & 0x7c)>>2;
+ L3write(UDA1341_L3Addr | UDA1341_DATA0, data0e4, 2 );
+ L3write(UDA1341_L3Addr | UDA1341_DATA0, data0e5, 2 );
+ }
+ if (left[Vinvert]+right[Vinvert] == 0)
+ status1[0] &= ~0x10;
+ else
+ status1[0] |= 0x10;
+ L3write(UDA1341_L3Addr | UDA1341_STATUS, status1, 1);
+ if (debug) {
+ print("mxvolume: status1 = 0x%2.2ux\n", status1[0]);
+ print("mxvolume: data0e4 = 0x%4.4ux\n", data0e4[0]|data0e4[0]<<8);
+ print("mxvolume: data0e5 = 0x%4.4ux\n", data0e5[0]|data0e5[0]<<8);
+ }
+ }
+ if(audio.amode & Awrite){
+ left = audio.lovol;
+ right = audio.rovol;
+ data00[0] &= ~0x3f;
+ data00[0] |= ((200-left[Vaudio]-right[Vaudio])*0x3f/200)&0x3f;
+ if (left[Vtreb]+right[Vtreb] <= 100
+ && left[Vbass]+right[Vbass] <= 100)
+ /* settings neutral */
+ data02[0] &= ~0x03;
+ else {
+ data02[0] |= 0x03;
+ data01[0] &= ~0x3f;
+ data01[0] |= ((left[Vtreb]+right[Vtreb]-100)*0x3/100)&0x03;
+ data01[0] |= (((left[Vbass]+right[Vbass]-100)*0xf/100)&0xf)<<2;
+ }
+ if (left[Vfilter]+right[Vfilter] == 0)
+ data02[0] &= ~0x10;
+ else
+ data02[0]|= 0x10;
+ if (left[Vinvert]+right[Vinvert] == 0)
+ status1[0] &= ~0x8;
+ else
+ status1[0] |= 0x8;
+ L3write(UDA1341_L3Addr | UDA1341_STATUS, status1, 1);
+ L3write(UDA1341_L3Addr | UDA1341_DATA0, data00, 1);
+ L3write(UDA1341_L3Addr | UDA1341_DATA0, data01, 1);
+ L3write(UDA1341_L3Addr | UDA1341_DATA0, data02, 1);
+ if (debug) {
+ print("mxvolume: status1 = 0x%2.2ux\n", status1[0]);
+ print("mxvolume: data00 = 0x%2.2ux\n", data00[0]);
+ print("mxvolume: data01 = 0x%2.2ux\n", data01[0]);
+ print("mxvolume: data02 = 0x%2.2ux\n", data02[0]);
+ }
+ }
+}
+
+static void
+setreg(char *name, int val, int n)
+{
+ uchar x[2];
+ int i;
+
+ if(strcmp(name, "pause") == 0){
+ for(i = 0; i < n; i++)
+ microdelay(val);
+ return;
+ }
+
+ x[0] = val;
+ x[1] = val>>8;
+
+ switch(n){
+ case 1:
+ case 2:
+ break;
+ default:
+ error("setreg");
+ }
+
+ if(strcmp(name, "status") == 0){
+ L3write(UDA1341_L3Addr | UDA1341_STATUS, x, n);
+ } else if(strcmp(name, "data0") == 0){
+ L3write(UDA1341_L3Addr | UDA1341_DATA0, x, n);
+ } else if(strcmp(name, "data1") == 0){
+ L3write(UDA1341_L3Addr | UDA1341_DATA1, x, n);
+ } else
+ error("setreg");
+}
+
+static void
+outenable(void) {
+ /* turn on DAC, set output gain switch */
+ archaudioamp(1);
+ archaudiomute(0);
+ status1[0] |= 0x41;
+ L3write(UDA1341_L3Addr | UDA1341_STATUS, status1, 1);
+ /* set volume */
+ data00[0] |= 0xf;
+ L3write(UDA1341_L3Addr | UDA1341_DATA0, data00, 1);
+ if (debug) {
+ print("outenable: status1 = 0x%2.2ux\n", status1[0]);
+ print("outenable: data00 = 0x%2.2ux\n", data00[0]);
+ }
+}
+
+static void
+outdisable(void) {
+ archaudiomute(1);
+ dmastop(audio.o.dma);
+ /* turn off DAC, clear output gain switch */
+ archaudioamp(0);
+ status1[0] &= ~0x41;
+ L3write(UDA1341_L3Addr | UDA1341_STATUS, status1, 1);
+ if (debug) {
+ print("outdisable: status1 = 0x%2.2ux\n", status1[0]);
+ }
+// egpiobits(EGPIO_audio_power, 0);
+}
+
+static void
+inenable(void) {
+ /* turn on ADC, set input gain switch */
+ status1[0] |= 0x22;
+ L3write(UDA1341_L3Addr | UDA1341_STATUS, status1, 1);
+ if (debug) {
+ print("inenable: status1 = 0x%2.2ux\n", status1[0]);
+ }
+}
+
+static void
+indisable(void) {
+ dmastop(audio.i.dma);
+ /* turn off ADC, clear input gain switch */
+ status1[0] &= ~0x22;
+ L3write(UDA1341_L3Addr | UDA1341_STATUS, status1, 1);
+ if (debug) {
+ print("indisable: status1 = 0x%2.2ux\n", status1[0]);
+ }
+}
+
+static void
+sendaudio(IOstate *s) {
+ /* interrupt routine calls this too */
+ int n;
+
+ if (debug > 1) print("#A: sendaudio\n");
+ ilock(&s->ilock);
+ while (s->next != s->filling) {
+ assert(s->next->nbytes);
+ if ((n = dmastart(s->dma, (void*)s->next->phys, s->next->nbytes)) == 0) {
+ iostats.faildma++;
+ break;
+ }
+ iostats.totaldma++;
+ switch (n) {
+ case 1:
+ iostats.idledma++;
+ break;
+ case 3:
+ iostats.faildma++;
+ break;
+ }
+ if (debug) {
+ if (debug > 1)
+ print("dmastart @%p\n", s->next);
+ else
+ iprint("+");
+ }
+ s->next->nbytes = 0;
+ s->next++;
+ if (s->next == &s->buf[Nbuf])
+ s->next = &s->buf[0];
+ }
+ iunlock(&s->ilock);
+}
+
+static void
+recvaudio(IOstate *s) {
+ /* interrupt routine calls this too */
+ int n;
+
+ if (debug > 1) print("#A: recvaudio\n");
+ ilock(&s->ilock);
+ while (s->next != s->emptying) {
+ assert(s->next->nbytes == 0);
+ if ((n = dmastart(s->dma, (void*)s->next->phys, Bufsize)) == 0) {
+ iostats.faildma++;
+ break;
+ }
+ iostats.totaldma++;
+ switch (n) {
+ case 1:
+ iostats.idledma++;
+ break;
+ case 3:
+ iostats.faildma++;
+ break;
+ }
+ if (debug) {
+ if (debug > 1)
+ print("dmastart @%p\n", s->next);
+ else
+ iprint("+");
+ }
+ s->next++;
+ if (s->next == &s->buf[Nbuf])
+ s->next = &s->buf[0];
+ }
+ iunlock(&s->ilock);
+}
+
+static void
+audiopower(int flag) {
+ IOstate *s;
+
+ if (debug) {
+ iprint("audiopower %d\n", flag);
+ }
+ if (flag) {
+ /* power on only when necessary */
+ if (audio.amode) {
+ archaudiopower(1);
+ enable();
+ if (audio.amode & Aread) {
+ inenable();
+ s = &audio.i;
+ dmastop(s->dma);
+ recvaudio(s);
+ }
+ if (audio.amode & Awrite) {
+ outenable();
+ s = &audio.o;
+ dmastop(s->dma);
+ sendaudio(s);
+ }
+ mxvolume();
+ }
+ } else {
+ /* power off */
+ if (audio.amode & Aread)
+ indisable();
+ if (audio.amode & Awrite)
+ outdisable();
+ disable();
+ archaudiopower(0);
+ }
+}
+
+static void
+audiointr(void *x, ulong ndma) {
+ IOstate *s = x;
+
+ if (debug) {
+ if (debug > 1)
+ iprint("#A: audio interrupt @%p\n", s->current);
+ else
+ iprint("-");
+ }
+ /* Only interrupt routine touches s->current */
+ s->current++;
+ if (s->current == &s->buf[Nbuf])
+ s->current = &s->buf[0];
+ if (ndma > 0) {
+ if (s == &audio.o)
+ sendaudio(s);
+ else if (s == &audio.i)
+ recvaudio(s);
+ }
+ wakeup(&s->vous);
+}
+
+static void
+audioinit(void)
+{
+ audio.amode = Aclosed;
+ resetlevel();
+// powerenable(audiopower);
+}
+
+static Chan*
+audioattach(char *param)
+{
+ return devattach('A', param);
+}
+
+static Walkqid*
+audiowalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, audiodir, nelem(audiodir), devgen);
+}
+
+static int
+audiostat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, audiodir, nelem(audiodir), devgen);
+}
+
+static Chan*
+audioopen(Chan *c, int mode)
+{
+ IOstate *s;
+ int omode = mode;
+
+ switch((ulong)c->qid.path) {
+ default:
+ error(Eperm);
+ break;
+
+ case Qstatus:
+ if((omode&7) != OREAD)
+ error(Eperm);
+ case Qvolume:
+ case Qaudioctl:
+ case Qdir:
+ break;
+
+ case Qaudio:
+ omode = (omode & 0x7) + 1;
+ if (omode & ~(Aread | Awrite))
+ error(Ebadarg);
+ qlock(&audio);
+ if(audio.amode & omode){
+ qunlock(&audio);
+ error(Einuse);
+ }
+ enable();
+ memset(&iostats, 0, sizeof(iostats));
+ if (omode & Aread) {
+ inenable();
+ s = &audio.i;
+ if(s->bufinit == 0)
+ bufinit(s);
+ setempty(s);
+ s->emptying = &s->buf[Nbuf-1];
+ s->chan = c;
+ s->dma = dmasetup(DmaSSP, 1, 0, audiointr, (void*)s);
+ audio.amode |= Aread;
+ audio.clockout = 1;
+ }
+ if (omode & Awrite) {
+ outenable();
+ s = &audio.o;
+ audio.amode |= Awrite;
+ if(s->bufinit == 0)
+ bufinit(s);
+ setempty(s);
+ s->chan = c;
+ s->dma = dmasetup(DmaSSP, 0, 0, audiointr, (void*)s);
+ audio.amode |= Awrite;
+ }
+ mxvolume();
+ qunlock(&audio);
+ if (debug) print("open done\n");
+ break;
+ }
+ c = devopen(c, mode, audiodir, nelem(audiodir), devgen);
+ c->mode = openmode(mode);
+ c->flag |= COPEN;
+ c->offset = 0;
+
+ return c;
+}
+
+static void
+audioclose(Chan *c)
+{
+ IOstate *s;
+
+ switch((ulong)c->qid.path) {
+ default:
+ error(Eperm);
+ break;
+
+ case Qdir:
+ case Qvolume:
+ case Qaudioctl:
+ case Qstatus:
+ break;
+
+ case Qaudio:
+ if (debug > 1) print("#A: close\n");
+ if(c->flag & COPEN) {
+ qlock(&audio);
+ if(waserror()){
+ qunlock(&audio);
+ nexterror();
+ }
+ if (audio.o.chan == c) {
+ /* closing the write end */
+ audio.amode &= ~Awrite;
+ s = &audio.o;
+ qlock(s);
+ if(waserror()){
+ qunlock(s);
+ nexterror();
+ }
+ if (s->filling->nbytes) {
+ /* send remaining partial buffer */
+ s->filling++;
+ if (s->filling == &s->buf[Nbuf])
+ s->filling = &s->buf[0];
+ sendaudio(s);
+ }
+ dmawait(s->dma);
+ outdisable();
+ setempty(s);
+ dmafree(s->dma);
+ qunlock(s);
+ poperror();
+ }
+ if (audio.i.chan == c) {
+ /* closing the read end */
+ audio.amode &= ~Aread;
+ s = &audio.i;
+ qlock(s);
+ if(waserror()){
+ qunlock(s);
+ nexterror();
+ }
+ indisable();
+ setempty(s);
+ dmafree(s->dma);
+ qunlock(s);
+ poperror();
+ }
+ if (audio.amode == 0) {
+ /* turn audio off */
+ archaudiopower(0);
+ }
+ qunlock(&audio);
+ poperror();
+ if (debug) {
+ print("total dmas: %lud\n", iostats.totaldma);
+ print("dmas while idle: %lud\n", iostats.idledma);
+ print("dmas while busy: %lud\n", iostats.faildma);
+ print("out of order dma: %lud\n", iostats.samedma);
+ }
+ }
+ break;
+ }
+}
+
+static long
+audioread(Chan *c, void *v, long n, vlong off)
+{
+ int liv, riv, lov, rov;
+ long m, n0;
+ char buf[300];
+ int j;
+ ulong offset = off;
+ char *p;
+ IOstate *s;
+
+ n0 = n;
+ p = v;
+ switch((ulong)c->qid.path) {
+ default:
+ error(Eperm);
+ break;
+
+ case Qdir:
+ return devdirread(c, p, n, audiodir, nelem(audiodir), devgen);
+
+ case Qaudio:
+ if (debug > 1) print("#A: read %ld\n", n);
+ if((audio.amode & Aread) == 0)
+ error(Emode);
+ s = &audio.i;
+ qlock(s);
+ if(waserror()){
+ qunlock(s);
+ nexterror();
+ }
+ while(n > 0) {
+ if(s->emptying->nbytes == 0) {
+ if (debug > 1) print("#A: emptied @%p\n", s->emptying);
+ recvaudio(s);
+ s->emptying++;
+ if (s->emptying == &s->buf[Nbuf])
+ s->emptying = s->buf;
+ }
+ /* wait if dma in progress */
+ while (!dmaidle(s->dma) && s->emptying == s->current) {
+ if (debug > 1) print("#A: sleep\n");
+ sleep(&s->vous, audioqnotempty, s);
+ }
+
+ m = Bufsize - s->emptying->nbytes;
+ if(m > n)
+ m = n;
+ memmove(p, s->emptying->virt + s->emptying->nbytes, m);
+
+ s->emptying->nbytes -= m;
+ n -= m;
+ p += m;
+ }
+ poperror();
+ qunlock(s);
+ break;
+ break;
+
+ case Qstatus:
+ buf[0] = 0;
+ snprint(buf, sizeof(buf), "bytes %llud\ntime %lld\n",
+ audio.totcount, audio.tottime);
+ return readstr(offset, p, n, buf);
+
+ case Qvolume:
+ case Qaudioctl:
+ 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, p, n, buf);
+ }
+ return n0-n;
+}
+
+static long
+audiowrite(Chan *c, void *vp, long n, vlong)
+{
+ long m, n0;
+ int i, nf, v, left, right, in, out;
+ char buf[255], *field[Ncmd];
+ char *p;
+ IOstate *a;
+
+ p = vp;
+ n0 = n;
+ switch((ulong)c->qid.path) {
+ default:
+ error(Eperm);
+ break;
+
+ case Qvolume:
+ case Qaudioctl:
+ v = Vaudio;
+ left = 1;
+ right = 1;
+ in = 1;
+ out = 1;
+ if(n > sizeof(buf)-1)
+ n = sizeof(buf)-1;
+ memmove(buf, p, n);
+ buf[n] = '\0';
+ n = 0;
+
+ nf = getfields(buf, field, Ncmd, 1, " \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(v == Vspeed){
+ if(archaudiospeed(m, 0) < 0)
+ error(Evolume);
+ }else
+ if(m < 0 || m > 100)
+ error(Evolume);
+ 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;
+ goto cont0;
+ }
+ if(strcmp(field[i], "rate") == 0)
+ field[i] = "speed"; /* honestly ... */
+
+ for(m=0; volumes[m].name; m++) {
+ if(strcmp(field[i], volumes[m].name) == 0) {
+ v = m;
+ in = 1;
+ out = 1;
+ left = 1;
+ right = 1;
+ goto cont0;
+ }
+ }
+ if(strcmp(field[i], "enc") == 0) {
+ if(++i >= nf)
+ error(Evolume);
+ if(strcmp(field[i], "pcm") != 0)
+ error(Evolume);
+ goto cont0;
+ }
+ if(strcmp(field[i], "bits") == 0) {
+ if(++i >= nf)
+ error(Evolume);
+ if(strtol(field[i], 0, 0) != 16)
+ error(Evolume);
+ goto cont0;
+ }
+ if(strcmp(field[i], "chans") == 0) {
+ if(++i >= nf)
+ error(Evolume);
+ if(strtol(field[i], 0, 0) != 2)
+ error(Evolume);
+ goto cont0;
+ }
+ if(strcmp(field[i], "reset") == 0) {
+ resetlevel();
+ goto cont0;
+ }
+ if(strcmp(field[i], "debug") == 0) {
+ debug = debug?0:1;
+ goto cont0;
+ }
+ if(strcmp(field[i], "in") == 0) {
+ in = 1;
+ out = 0;
+ goto cont0;
+ }
+ if(strcmp(field[i], "out") == 0) {
+ in = 0;
+ out = 1;
+ 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], "reg") == 0) {
+ if(nf < 3)
+ error(Evolume);
+ setreg(field[1], atoi(field[2]), nf == 4 ? atoi(field[3]):1);
+ return n0;
+ }
+ error(Evolume);
+ break;
+ cont0:;
+ }
+ mxvolume();
+ break;
+
+ case Qaudio:
+ if (debug > 1) print("#A: write %ld\n", n);
+ if((audio.amode & Awrite) == 0)
+ error(Emode);
+ a = &audio.o;
+ qlock(a);
+ if(waserror()){
+ qunlock(a);
+ nexterror();
+ }
+ while(n > 0) {
+ /* wait if dma in progress */
+ while (!dmaidle(a->dma) && a->filling == a->current) {
+ if (debug > 1) print("#A: sleep\n");
+ sleep(&a->vous, audioqnotfull, a);
+ }
+
+ m = Bufsize - a->filling->nbytes;
+ if(m > n)
+ m = n;
+ memmove(a->filling->virt + a->filling->nbytes, p, m);
+
+ a->filling->nbytes += m;
+ n -= m;
+ p += m;
+ if(a->filling->nbytes >= Bufsize) {
+ if (debug > 1) print("#A: filled @%p\n", a->filling);
+ a->filling++;
+ if (a->filling == &a->buf[Nbuf])
+ a->filling = a->buf;
+ sendaudio(a);
+ }
+ }
+ poperror();
+ qunlock(a);
+ break;
+ }
+ return n0 - n;
+}
+
+Dev audiodevtab = {
+ 'A',
+ "audio",
+
+ audioreset,
+ audioinit,
+ devshutdown,
+ audioattach,
+ audiowalk,
+ audiostat,
+ audioopen,
+ devcreate,
+ audioclose,
+ audioread,
+ devbread,
+ audiowrite,
+ devbwrite,
+ devremove,
+ devwstat,
+ audiopower,
+};