/* 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, };