diff options
author | cinap_lenrek <cinap_lenrek@localhost> | 2011-05-16 12:16:43 +0000 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@localhost> | 2011-05-16 12:16:43 +0000 |
commit | d642d726babfc8e6a04a9ea2280655b0c5ff8f98 (patch) | |
tree | 7fe39211e5f6d17622c9f6f0c9eb53aa8351576a /sys/src/9/pc/audioac97.c | |
parent | 1a81fb86eb713f3e98a71522259d843e8cc14995 (diff) |
add ac97 driver
Diffstat (limited to 'sys/src/9/pc/audioac97.c')
-rw-r--r-- | sys/src/9/pc/audioac97.c | 609 |
1 files changed, 609 insertions, 0 deletions
diff --git a/sys/src/9/pc/audioac97.c b/sys/src/9/pc/audioac97.c new file mode 100644 index 000000000..0dc38f6cb --- /dev/null +++ b/sys/src/9/pc/audioac97.c @@ -0,0 +1,609 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "../port/error.h" +#include "../port/audio.h" + +typedef struct Hwdesc Hwdesc; +typedef struct Ctlr Ctlr; +static uint sis7012 = 0; + +enum { + Ioc = 1<<31, + Bup = 1<<30, +}; + +struct Hwdesc { + ulong addr; + ulong size; +}; + +enum { + Ndesc = 32, + Nts = 33, + Bufsize = 32768, /* bytes, must be divisible by ndesc */ + Maxbusywait = 500000, /* microseconds, roughly */ + BytesPerSample = 4, +}; + +struct Ctlr { + /* keep these first, they want to be 8-aligned */ + Hwdesc indesc[Ndesc]; + Hwdesc outdesc[Ndesc]; + Hwdesc micdesc[Ndesc]; + + Lock; + Rendez outr; + + ulong port; + ulong mixport; + + char *out; + char *in; + char *mic; + + char *outp; + char *inp; + char *micp; + + /* shared variables, ilock to access */ + int outavail; + int inavail; + int micavail; + + /* interrupt handler alone */ + int outciv; + int inciv; + int micciv; + + int tsouti; + uvlong tsoutp; + ulong tsout[Nts]; + int tsoutb[Nts]; + + ulong civstat[Ndesc]; + ulong lvistat[Ndesc]; + + int targetrate; + int hardrate; + + int attachok; +}; + +#define iorl(c, r) (inl((c)->port+(r))) +#define iowl(c, r, l) (outl((c)->port+(r), (ulong)(l))) + +enum { + In = 0x00, + Out = 0x10, + Mic = 0x20, + Bar = 0x00, /* Base address register, 8-byte aligned */ + /* a 32-bit read at 0x04 can be used to get civ:lvi:sr in one step */ + Civ = 0x04, /* current index value (desc being processed) */ + Lvi = 0x05, /* Last valid index (index of first unused entry!) */ + Sr = 0x06, /* status register */ + Fifoe = 1<<4, /* fifo error (r/wc) */ + Bcis = 1<<3, /* buffer completion interrupt status (r/wc) */ + Lvbci = 1<<2, /* last valid buffer completion(in)/fetched(out) interrupt (r/wc) */ + Celv = 1<<1, /* current equals last valid (ro) */ + Dch = 1<<0, /* dma controller halted (ro) */ + Picb = 0x08, /* position in current buffer */ + Piv = 0x0a, /* prefetched index value */ + Cr = 0x0b, /* control register */ + Ioce = 1<<4, /* interrupt on buffer completion (if bit set in hwdesc.size) (rw) */ + Feie = 1<<3, /* fifo error interrupt enable (rw) */ + Lvbie = 1<<2, /* last valid buffer interrupt enable (rw) */ + RR = 1<<1, /* reset busmaster related regs, excl. ioce,feie,lvbie (rw) */ + Rpbm = 1<<0, /* run/pause busmaster. 0 stops, 1 starts (rw) */ + Cnt = 0x2c, /* global control */ + Ena16bit = 0x0<<22, + Ena20bit = 0x1<<22, + Ena2chan = 0x0<<20, + Ena4chan = 0x1<<20, + Enam6chan = 0x2<<20, + EnaRESER = 0x3<<20, + Sr2ie = 1<<6, /* sdin2 interrupt enable (rw) */ + Srie = 1<<5, /* sdin1 interrupt enable (rw) */ + Prie = 1<<4, /* sdin0 interrupt enable (rw) */ + Aclso = 1<<3, /* ac link shut-off (rw) */ + Acwr = 1<<2, /* ac 97 warm reset (rw) */ + Accr = 1<<1, /* ac 97 cold reset (rw) */ + GPIie = 1<<0, /* GPI interrupt enable (rw) */ + Sta = 0x30, /* global status */ + Cap6chan = 1<<21, + Cap4chan = 1<<20, + Md3 = 1<<17, /* modem powerdown semaphore */ + Ad3 = 1<<16, /* audio powerdown semaphore */ + Rcs = 1<<15, /* read completion status (r/wc) */ + S2ri = 1<<29, /* sdin2 resume interrupt (r/wc) */ + Sri = 1<<11, /* sdin1 resume interrupt (r/wc) */ + Pri = 1<<10, /* sdin0 resume interrupt (r/wc) */ + S2cr = 1<<28, /* sdin2 codec ready (ro) */ + Scr = 1<<9, /* sdin1 codec ready (ro) */ + Pcr = 1<<8, /* sdin0 codec ready (ro) */ + Mint = 1<<7, /* microphone in inetrrupt (ro) */ + Point = 1<<6, /* pcm out interrupt (ro) */ + Piint = 1<<5, /* pcm in interrupt (ro) */ + Moint = 1<<2, /* modem out interrupt (ro) */ + Miint = 1<<1, /* modem in interrupt (ro) */ + Gsci = 1<<0, /* GPI status change interrupt */ + Cas = 0x34, /* codec access semaphore */ + Casp = 1<<0, /* set to 1 on read if zero, cleared by hardware */ +}; + +#define csr8r(c, r) (inb((c)->port+(r))) +#define csr16r(c, r) (ins((c)->port+(r))) +#define csr32r(c, r) (inl((c)->port+(r))) +#define csr8w(c, r, b) (outb((c)->port+(r), (int)(b))) +#define csr16w(c, r, w) (outs((c)->port+(r), (ushort)(w))) +#define csr32w(c, r, w) (outl((c)->port+(r), (ulong)(w))) + +static void +ac97waitcodec(Audio *adev) +{ + Ctlr *ctlr; + int i; + ctlr = adev->ctlr; + for(i = 0; i < Maxbusywait/10; i++){ + if((csr8r(ctlr, Cas) & Casp) == 0) + break; + microdelay(10); + } + if(i == Maxbusywait) + print("#A%d: ac97 exhausted waiting codec access\n", adev->ctlrno); +} + +static void +ac97mixw(Audio *adev, int port, ushort val) +{ + Ctlr *ctlr; + ac97waitcodec(adev); + ctlr = adev->ctlr; + outs(ctlr->mixport+port, val); +} + +static ushort +ac97mixr(Audio *adev, int port) +{ + Ctlr *ctlr; + ac97waitcodec(adev); + ctlr = adev->ctlr; + return ins(ctlr->mixport+port); +} + +static int +outavail(void *arg) +{ + Ctlr *ctlr; + ctlr = arg; + return ctlr->outavail > 0; +} + +static void +ac97interrupt(Ureg *, void *arg) +{ + Audio *adev; + Ctlr *ctlr; + int civ, n, i; + ulong stat; + uvlong now; + + adev = arg; + ctlr = adev->ctlr; + stat = csr32r(ctlr, Sta); + + stat &= S2ri | Sri | Pri | Mint | Point | Piint | Moint | Miint | Gsci; + + ilock(ctlr); + if(stat & Point){ + if(sis7012) + csr16w(ctlr, Out + Picb, csr16r(ctlr, Out + Picb) & ~Dch); + else + csr16w(ctlr, Out + Sr, csr16r(ctlr, Out + Sr) & ~Dch); + + civ = csr8r(ctlr, Out + Civ); + n = 0; + while(ctlr->outciv != civ){ + ctlr->civstat[ctlr->outciv++]++; + if(ctlr->outciv == Ndesc) + ctlr->outciv = 0; + n += Bufsize/Ndesc; + } + + now = fastticks(0); + i = ctlr->tsouti; + ctlr->tsoutb[i] = n; + ctlr->tsout[i] = now - ctlr->tsoutp; + ctlr->tsouti = (i + 1) % Nts; + ctlr->tsoutp = now; + ctlr->outavail += n; + + if(ctlr->outavail > Bufsize/2) + wakeup(&ctlr->outr); + stat &= ~Point; + } + iunlock(ctlr); + if(stat) /* have seen 0x400, which is sdin0 resume */ + print("#A%d: ac97 unhandled interrupt(s): stat 0x%lux\n", adev->ctlrno, stat); +} + +static int +off2lvi(char *base, char *p) +{ + int lvi; + lvi = p - base; + return lvi / (Bufsize/Ndesc); +} + +static long +ac97medianoutrate(Audio *adev) +{ + ulong ts[Nts], t; + uvlong hz; + int i, j; + Ctlr *ctlr; + ctlr = adev->ctlr; + fastticks(&hz); + for(i = 0; i < Nts; i++) + if(ctlr->tsout[i] > 0) + ts[i] = (ctlr->tsoutb[i] * hz) / ctlr->tsout[i]; + else + ts[i] = 0; + for(i = 1; i < Nts; i++){ + t = ts[i]; + j = i; + while(j > 0 && ts[j-1] > t){ + ts[j] = ts[j-1]; + j--; + } + ts[j] = t; + } + return ts[Nts/2] / BytesPerSample; +} + +static void +ac97volume(Audio *adev, char *msg) +{ + adev->volwrite(adev, msg, strlen(msg), 0); +} + +static void +ac97attach(Audio *adev) +{ + Ctlr *ctlr; + ctlr = adev->ctlr; + if(!ctlr->attachok){ + ac97hardrate(adev, ctlr->hardrate); + ac97volume(adev, "audio 75"); + ac97volume(adev, "head 100"); + ac97volume(adev, "master 100"); + ctlr->attachok = 1; + } +} + +static long +ac97status(Audio *adev, void *a, long n, vlong off) +{ + Ctlr *ctlr; + char *buf; + long i, l; + ctlr = adev->ctlr; + l = 0; + buf = malloc(READSTR); + l += snprint(buf + l, READSTR - l, "rate %d\n", ctlr->targetrate); + l += snprint(buf + l, READSTR - l, "median rate %lud\n", ac97medianoutrate(adev)); + l += snprint(buf + l, READSTR - l, "hard rate %d\n", ac97hardrate(adev, -1)); + + l += snprint(buf + l, READSTR - l, "civ stats"); + for(i = 0; i < Ndesc; i++) + l += snprint(buf + l, READSTR - l, " %lud", ctlr->civstat[i]); + l += snprint(buf + l, READSTR - l, "\n"); + + l += snprint(buf + l, READSTR - l, "lvi stats"); + for(i = 0; i < Ndesc; i++) + l += snprint(buf + l, READSTR - l, " %lud", ctlr->lvistat[i]); + snprint(buf + l, READSTR - l, "\n"); + + n = readstr(off, a, n, buf); + free(buf); + return n; +} + +static long +ac97buffered(Audio *adev) +{ + Ctlr *ctlr; + ctlr = adev->ctlr; + return Bufsize - Bufsize/Ndesc - ctlr->outavail; +} + +static long +ac97ctl(Audio *adev, void *a, long n, vlong) +{ + Ctlr *ctlr; + char *tok[2], *p; + int ntok; + long t; + + ctlr = adev->ctlr; + if(n > READSTR) + n = READSTR - 1; + p = malloc(READSTR); + + if(waserror()){ + free(p); + nexterror(); + } + memmove(p, a, n); + p[n] = 0; + ntok = tokenize(p, tok, nelem(tok)); + if(ntok > 1 && !strcmp(tok[0], "rate")){ + t = strtol(tok[1], 0, 10); + if(t < 8000 || t > 48000) + error("rate must be between 8000 and 48000"); + ctlr->targetrate = t; + ctlr->hardrate = t; + ac97hardrate(adev, ctlr->hardrate); + poperror(); + free(p); + return n; + } + error("invalid ctl"); + return n; /* shut up, you compiler you */ +} + +static void +ac97kick(Ctlr *ctlr, long reg) +{ + csr8w(ctlr, reg+Cr, Ioce | Rpbm); +} + +static long +ac97write(Audio *adev, void *a, long nwr, vlong) +{ + Ctlr *ctlr; + char *p, *sp, *ep; + int len, lvi, olvi; + int t; + long n; + + ctlr = adev->ctlr; + ilock(ctlr); + p = ctlr->outp; + sp = a; + ep = ctlr->out + Bufsize; + olvi = off2lvi(ctlr->out, p); + n = nwr; + while(n > 0){ + len = ep - p; + if(ctlr->outavail < len) + len = ctlr->outavail; + if(n < len) + len = n; + ctlr->outavail -= len; + iunlock(ctlr); + memmove(p, sp, len); + ilock(ctlr); + p += len; + sp += len; + n -= len; + if(p == ep) + p = ctlr->out; + lvi = off2lvi(ctlr->out, p); + if(olvi != lvi){ + t = olvi; + while(t != lvi){ + t = (t + 1) % Ndesc; + ctlr->lvistat[t]++; + csr8w(ctlr, Out+Lvi, t); + ac97kick(ctlr, Out); + } + olvi = lvi; + } + if(ctlr->outavail == 0){ + ctlr->outp = p; + iunlock(ctlr); + sleep(&ctlr->outr, outavail, ctlr); + ilock(ctlr); + } + } + ctlr->outp = p; + iunlock(ctlr); + return nwr; +} + +static Pcidev* +ac97match(Pcidev *p) +{ + /* not all of the matched devices have been tested */ + while(p = pcimatch(p, 0, 0)) + switch(p->vid){ + default: + break; + case 0x1039: + switch(p->did){ + default: + break; + case 0x7012: + sis7012 = 1; + return p; + } + case 0x1022: + switch(p->did){ + default: + break; + case 0x746d: + case 0x7445: + return p; + } + case 0x10de: + switch(p->did){ + default: + break; + case 0x01b1: + case 0x006a: + case 0x00da: + case 0x00ea: + return p; + } + case 0x8086: + switch(p->did){ + default: + break; + case 0x2415: + case 0x2425: + case 0x2445: + case 0x2485: + case 0x24c5: + case 0x24d5: + case 0x25a6: + case 0x266e: + case 0x7195: + return p; + } + } + return nil; +} + +static void +sethwp(Ctlr *ctlr, long off, void *ptr) +{ + csr8w(ctlr, off+Cr, RR); + csr32w(ctlr, off+Bar, PCIWADDR(ptr)); + csr8w(ctlr, off+Lvi, 0); +} + +static int +ac97reset(Audio *adev) +{ + static int ncards = 1; + int i, irq, tbdf; + Pcidev *p; + Ctlr *ctlr; + ulong ctl, stat = 0; + + p = nil; + for(i = 0; i < ncards; i++) + if((p = ac97match(p)) == nil) + return -1; + ncards++; + + ctlr = xspanalloc(sizeof(Ctlr), 8, 0); + memset(ctlr, 0, sizeof(Ctlr)); + adev->ctlr = ctlr; + ctlr->targetrate = 44100; + ctlr->hardrate = 44100; + + if(p->mem[0].size == 64){ + ctlr->port = p->mem[0].bar & ~3; + ctlr->mixport = p->mem[1].bar & ~3; + } else if(p->mem[1].size == 64){ + ctlr->port = p->mem[1].bar & ~3; + ctlr->mixport = p->mem[0].bar & ~3; + } else if(p->mem[0].size == 256){ /* sis7012 */ + ctlr->port = p->mem[1].bar & ~3; + ctlr->mixport = p->mem[0].bar & ~3; + } else if(p->mem[1].size == 256){ + ctlr->port = p->mem[0].bar & ~3; + ctlr->mixport = p->mem[1].bar & ~3; + } + + irq = p->intl; + tbdf = p->tbdf; + + print("#A%d: ac97 port 0x%04lux mixport 0x%04lux irq %d\n", + adev->ctlrno, ctlr->port, ctlr->mixport, irq); + + pcisetbme(p); + pcisetioe(p); + + ctlr->mic = xspanalloc(Bufsize, 8, 0); + ctlr->in = xspanalloc(Bufsize, 8, 0); + ctlr->out = xspanalloc(Bufsize, 8, 0); + + for(i = 0; i < Ndesc; i++){ + int size, off = i * (Bufsize/Ndesc); + + if(sis7012) + size = (Bufsize/Ndesc); + else + size = (Bufsize/Ndesc) / 2; + + ctlr->micdesc[i].addr = PCIWADDR(ctlr->mic + off); + ctlr->micdesc[i].size = Ioc | size; + ctlr->indesc[i].addr = PCIWADDR(ctlr->in + off); + ctlr->indesc[i].size = Ioc | size; + ctlr->outdesc[i].addr = PCIWADDR(ctlr->out + off); + ctlr->outdesc[i].size = Ioc | size; + } + + ctlr->outavail = Bufsize - Bufsize/Ndesc; + ctlr->outp = ctlr->out; + + ctl = csr32r(ctlr, Cnt); + ctl &= ~(EnaRESER | Aclso); + + if((ctl & Accr) == 0){ + print("#A%d: ac97 cold reset\n", adev->ctlrno); + ctl |= Accr; + }else{ + print("#A%d: ac97 warm reset\n", adev->ctlrno); + ctl |= Acwr; + } + + csr32w(ctlr, Cnt, ctl); + for(i = 0; i < Maxbusywait; i++){ + if((csr32r(ctlr, Cnt) & Acwr) == 0) + break; + microdelay(1); + } + if(i == Maxbusywait) + print("#A%d: ac97 gave up waiting Acwr to go down\n", adev->ctlrno); + + for(i = 0; i < Maxbusywait; i++){ + if((stat = csr32r(ctlr, Sta)) & (Pcr | Scr | S2cr)) + break; + microdelay(1); + } + if(i == Maxbusywait) + print("#A%d: ac97 gave up waiting codecs become ready\n", adev->ctlrno); + + print("#A%d: ac97 codecs ready:%s%s%s\n", adev->ctlrno, + (stat & Pcr) ? " sdin0" : "", + (stat & Scr) ? " sdin1" : "", + (stat & S2cr) ? " sdin2" : ""); + + print("#A%d: ac97 codecs resumed:%s%s%s\n", adev->ctlrno, + (stat & Pri) ? " sdin0" : "", + (stat & Sri) ? " sdin1" : "", + (stat & S2ri) ? " sdin2" : ""); + + sethwp(ctlr, In, ctlr->indesc); + sethwp(ctlr, Out, ctlr->outdesc); + sethwp(ctlr, Mic, ctlr->micdesc); + + csr8w(ctlr, In+Cr, Ioce); /* | Lvbie | Feie */ + csr8w(ctlr, Out+Cr, Ioce); /* | Lvbie | Feie */ + csr8w(ctlr, Mic+Cr, Ioce); /* | Lvbie | Feie */ + + adev->attach = ac97attach; + adev->write = ac97write; + adev->status = ac97status; + adev->ctl = ac97ctl; + adev->buffered = ac97buffered; + + ac97mixreset(adev, ac97mixw, ac97mixr); + + intrenable(irq, ac97interrupt, adev, tbdf, adev->name); + + return 0; +} + +void +audioac97link(void) +{ + addaudiocard("ac97audio", ac97reset); +} |