/* kw audio driver */
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#include "io.h"
enum {
Bufc = 4,
Bufsz = 16 * 1024,
Bufsize = Bufc * Bufsz,
};
typedef struct Audiokw Audiokw;
typedef struct Kwaudio Kwaudio;
struct Audiokw {
Rendez vous;
Kwaudio *reg;
struct {
QLock;
long fill, off;
ulong intr, flow;
char *bp;
} play, rec;
};
struct Kwaudio {
char _unkn0[0x0a00];
struct { /* 0a00 */
#define WINATTR(v) (((v) & MASK(8)) << 8)
#define WINSIZE(v) (((v)/(64*1024) - 1) << 16)
ulong base;
ulong size;
} recwin,playwin;
char _unkn1[0x1000-0x0a10];
struct { /* 1000 and 1100 */
ulong control;
ulong buf, bufsz, bufoff;
long pad[60];
} recc, playc;
struct { /* 1200 */
long _unkn0;
ulong DCO;
long _unkn2;
ulong SPCR;
long _unkn3[4];
ulong sample_control, play_sample, record_sample;
long _unkn4;
ulong clocks;
long _unkn5[3];
long pad[48];
} clock;
struct { /* 1300 */
ulong err_cause, err_mask;
ulong int_cause, int_mask;
ulong rec_count, play_count;
ulong play_cc, rec_cc;
long pad[56];
} interr;
uchar _unkn2[0xc00];
struct { /* 2000 and 2200 */
long unkn0;
uint control;
long unkn1[30];
uint csl[6], csr[6], ubl[6], ubr[6];
long pad[72];
} spdif[2];
struct { /* 2400 and 2500 */
long unkn0;
ulong control;
long pad[62];
} reci2s, playi2s;
};
enum {
int_rec = 1<<13,
int_play = 1<<14,
SZ_16c = 3<<0, /* normal 16bit little endian i/o */
CPause = 1<<9,
Rec_DMA_128 = 2<<5,
Rec_I2Sen = 1<<10,
Play_I2Sen = 1<<3,
Play_DMA_128 = 2<<11,
I2S_ss_24 = 1<<30, /* to codec */
I2S_just_left = 0<<26,
};
static Audiokw audio = {
.reg (Kwaudio *)Addraudio,
};
static int
recfill(void *)
{
return audio.rec.fill;
}
static uint
recbuf(char *ib, uint sz)
{
char *ie;
uint len;
ie = ib + sz;
qlock(&audio.play);
audio.reg->recc.control |= Rec_I2Sen;
if(audio.rec.fill==Bufc/2) audio.reg->recc.control &= ~CPause;
while(ib < ie){
if(audio.rec.fill==0) sleep(&audio.vous, recfill, 0);
len = MIN(-audio.rec.off % Bufsz + Bufsz, ie-ib);
memmove(ib, audio.rec.bp + audio.rec.off, len);
audio.rec.off = (audio.rec.off + len) % Bufsize;
ib += len;
if(audio.rec.off%Bufsz==0)
audio.rec.fill--;
if(audio.rec.fill==Bufc/2) audio.reg->recc.control &= ~CPause;
}
qunlock(&audio.rec);
return sz;
}
static void
recopen(void)
{
Kwaudio *reg = audio.reg;
if(audio.rec.bp==nil)
audio.rec.bp = mallocalign(Bufsize, 128, 0, 0);
reg->recc.control = Rec_DMA_128 | CPause | SZ_16c;
reg->reci2s.control = I2S_ss_24 | I2S_just_left;
reg->recc.buf = PADDR(audio.rec.bp);
reg->recc.bufsz = Bufsize/4-1;
reg->recc.bufoff = audio.rec.off = 0;
reg->interr.int_mask |= int_rec;
reg->interr.rec_count = Bufsz;
audio.rec.fill = 0;
}
static void
recint(void)
{
audio.rec.fill++;
audio.rec.intr++;
if(audio.rec.fill>Bufc){
audio.reg->recc.control |= CPause;
audio.rec.flow++;
}
}
static void
recclose(void)
{
Kwaudio *reg = audio.reg;
reg->recc.control |= CPause;
reg->recc.control &= ~Rec_I2Sen;
if(audio.rec.bp){
free(audio.rec.bp);
audio.rec.bp = nil;
reg->recc.buf = 0;
reg->recc.bufsz = 0;
}
if(audio.rec.locked)
qunlock(&audio.rec);
}
static int
playfill(void *)
{
return audio.play.fill;
}
static uint
playbuf(char *ib, uint sz)
{
char *ie;
uint len;
ie = ib + sz;
qlock(&audio.play);
audio.reg->playc.control |= Play_I2Sen;
while(ib < ie){
if(audio.play.fill==0) sleep(&audio.vous, playfill, 0);
len = MIN(-audio.play.off % Bufsz + Bufsz, ie-ib);
memmove(audio.play.bp + audio.play.off, ib, len);
audio.play.off = (audio.play.off + len) % Bufsize;
ib += len;
if(audio.play.off%Bufsz==0)
audio.play.fill--;
if(audio.play.fill==Bufc/2) audio.reg->playc.control &= ~CPause;
}
qunlock(&audio.play);
return sz;
}
static void
playopen(void)
{
Kwaudio *reg = audio.reg;
if(audio.play.bp==nil)
audio.play.bp = mallocalign(Bufsize, 128, 0, 0);
reg->playc.control = Play_DMA_128 | CPause | SZ_16c;
reg->playi2s.control = I2S_ss_24 | I2S_just_left;
reg->playc.buf = PADDR(audio.play.bp);
reg->playc.bufsz = Bufsize/4-1;
reg->playc.bufoff = audio.play.off = 0;
reg->interr.int_mask |= int_play;
reg->interr.play_count = Bufsz;
audio.play.fill = Bufc;
}
static void
playint(void)
{
audio.play.fill++;
audio.play.intr++;
if(audio.play.fill>Bufc){
audio.reg->playc.control |= CPause;
audio.play.flow++;
}
}
static void
playclose(void)
{
Kwaudio *reg = audio.reg;
reg->playc.control |= CPause;
reg->playc.control &= ~Play_I2Sen;
if(audio.play.bp){
free(audio.play.bp);
audio.play.bp = nil;
reg->playc.buf = 0;
reg->playc.bufsz = 0;
}
if(audio.play.locked)
qunlock(&audio.play);
}
static void
interrupt(Ureg *, void *)
{
Kwaudio *reg = audio.reg;
if(reg->interr.int_cause & int_play)
playint();
if(reg->interr.int_cause & int_rec)
recint();
wakeup(&audio.vous);
reg->interr.int_cause = reg->interr.int_cause;
intrclear(Irqlo, IRQ0audio);
}
static void
audioinit(void)
{
Kwaudio *reg = audio.reg;
reg->recwin.base = reg->playwin.base = PHYSDRAM;
reg->recwin.size = reg->playwin.size = WINSIZE(256*MB) | WINATTR(Attrcs0) | Targdram<<4 | 1;
reg->interr.int_cause = ~0;
reg->interr.int_mask = 0;
intrenable(Irqlo, IRQ0audio, interrupt, &audio, "audiokw");
}
static void
audioshutdown(void)
{
recclose();
playclose();
intrdisable(Irqlo, IRQ0audio, interrupt, &audio, "audiokw");
}
enum {
Qdir,
Qin,
Qout,
Qstat,
};
Dirtab
audiodir[] =
{
".", {Qdir, 0, QTDIR}, 0, DMDIR|0555,
"audioin", {Qin}, 0, 0444,
"audio", {Qout}, 0, 0222,
"audiostat", {Qstat}, 0, 0444,
};
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)
{
audiodir[Qout].length = 0;
return devstat(c, db, n, audiodir, nelem(audiodir), devgen);
}
static Chan*
audioopen(Chan *c, int omode)
{
switch((ulong)c->qid.path){
default:
error(Eperm);
break;
case Qin:
recopen();
break;
case Qout:
playopen();
break;
case Qdir:
case Qstat:
break;
}
c = devopen(c, omode, audiodir, nelem(audiodir), devgen);
c->mode = openmode(omode);
c->flag |= COPEN;
c->offset = 0;
return c;
}
static void
audioclose(Chan *c)
{
switch((ulong)c->qid.path){
default:
error(Eperm);
break;
case Qin:
recclose();
case Qout:
playclose();
break;
case Qdir:
case Qstat:
break;
}
}
static long
audioread(Chan *c, void *v, long n, vlong off)
{
char buf[256];
switch((ulong)c->qid.path){
default:
error(Eperm);
break;
case Qdir:
return devdirread(c, v, n, audiodir, nelem(audiodir), devgen);
break;
case Qin:
return recbuf(v, n);
break;
case Qstat:
snprint(buf, sizeof(buf),
" fill off flow int\n"
"rec %ld %ld %uld %uld\n"
"play %ld %ld %uld %uld\n",
audio.rec.fill, audio.rec.off, audio.rec.flow, audio.rec.intr,
audio.play.fill, audio.play.off, audio.play.flow, audio.play.intr);
return readstr(off, v, n, buf);
}
return 0;
}
static long
audiowrite(Chan *c, void *v, long n, vlong)
{
switch((ulong)c->qid.path){
default:
error(Eperm);
break;
case Qout:
return playbuf(v, n);
break;
}
return 0;
}
Dev audiokwdevtab = {
'A',
"audio",
devreset,
audioinit,
audioshutdown,
audioattach,
audiowalk,
audiostat,
audioopen,
devcreate,
audioclose,
audioread,
devbread,
audiowrite,
devbwrite,
devremove,
devwstat,
};
|