ref: 254b07675e1a5348f2a8723f55e6b5b1b18a4721
dir: /os/ipaq1110/devaudio.c/
/*
* 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,
};