summaryrefslogtreecommitdiff
path: root/sys/src/9/pc/audioac97.c
diff options
context:
space:
mode:
authorcinap_lenrek <cinap_lenrek@localhost>2011-05-16 12:16:43 +0000
committercinap_lenrek <cinap_lenrek@localhost>2011-05-16 12:16:43 +0000
commitd642d726babfc8e6a04a9ea2280655b0c5ff8f98 (patch)
tree7fe39211e5f6d17622c9f6f0c9eb53aa8351576a /sys/src/9/pc/audioac97.c
parent1a81fb86eb713f3e98a71522259d843e8cc14995 (diff)
add ac97 driver
Diffstat (limited to 'sys/src/9/pc/audioac97.c')
-rw-r--r--sys/src/9/pc/audioac97.c609
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);
+}