--- /sys/src/9/pc/audioac97.c Fri Feb 19 00:00:00 2016 +++ /sys/src/9/pc/audioac97.c Fri Feb 19 00:00:00 2016 @@ -0,0 +1,599 @@ +#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/audioif.h" + +typedef struct Ring Ring; +typedef struct Hwdesc Hwdesc; +typedef struct Ctlr Ctlr; + +enum { + Ioc = 1<<31, + Bup = 1<<30, +}; + +struct Hwdesc { + ulong addr; + ulong size; +}; + +enum { + Ndesc = 32, + Bufsize = 32768, /* bytes, must be divisible by ndesc */ + Blocksize = Bufsize/Ndesc, + Maxbusywait = 500000, /* microseconds, roughly */ + BytesPerSample = 4, +}; + +struct Ring +{ + Rendez r; + + uchar *buf; + ulong nbuf; + + ulong ri; + ulong wi; +}; + +struct Ctlr { + /* keep these first, they want to be 8-aligned */ + Hwdesc indesc[Ndesc]; + Hwdesc outdesc[Ndesc]; + Hwdesc micdesc[Ndesc]; + + Lock; + + ulong port; + ulong mixport; + + Ring inring, micring, outring; + + int sis7012; + + /* for probe */ + Audio *adev; + Pcidev *pcidev; + Ctlr *next; +}; + +#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 */ +}; + +static long +buffered(Ring *r) +{ + ulong ri, wi; + + ri = r->ri; + wi = r->wi; + if(wi >= ri) + return wi - ri; + else + return r->nbuf - (ri - wi); +} + +static long +available(Ring *r) +{ + long m; + + m = (r->nbuf - BytesPerSample) - buffered(r); + if(m < 0) + m = 0; + return m; +} + +static long +readring(Ring *r, uchar *p, long n) +{ + long n0, m; + + n0 = n; + while(n > 0){ + if((m = buffered(r)) <= 0) + break; + if(m > n) + m = n; + if(p){ + if(r->ri + m > r->nbuf) + m = r->nbuf - r->ri; + memmove(p, r->buf + r->ri, m); + p += m; + } + r->ri = (r->ri + m) % r->nbuf; + n -= m; + } + return n0 - n; +} + +static long +writering(Ring *r, uchar *p, long n) +{ + long n0, m; + + n0 = n; + while(n > 0){ + if((m = available(r)) <= 0) + break; + if(m > n) + m = n; + if(p){ + if(r->wi + m > r->nbuf) + m = r->nbuf - r->wi; + memmove(r->buf + r->wi, p, m); + p += m; + } + r->wi = (r->wi + m) % r->nbuf; + n -= m; + } + return n0 - n; +} + +#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))) + +/* audioac97mix */ +extern void ac97mixreset(Audio *, + void (*wr)(Audio*,int,ushort), + ushort (*rr)(Audio*,int)); + +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 void +ac97interrupt(Ureg *, void *arg) +{ + Audio *adev; + Ctlr *ctlr; + ulong stat; + + adev = arg; + ctlr = adev->ctlr; + stat = csr32r(ctlr, Sta); + stat &= S2ri | Sri | Pri | Mint | Point | Piint | Moint | Miint | Gsci; + if(stat & (Point|Piint|Mint)){ + ilock(ctlr); + if(stat & Point){ + ctlr->outring.ri = csr8r(ctlr, Out + Civ) * Blocksize; + wakeup(&ctlr->outring.r); + + if(ctlr->sis7012) + csr16w(ctlr, Out + Picb, csr16r(ctlr, Out + Picb) & ~Dch); + else + csr16w(ctlr, Out + Sr, csr16r(ctlr, Out + Sr) & ~Dch); + stat &= ~Point; + } + if(stat & Piint){ + ctlr->inring.wi = csr8r(ctlr, In + Civ) * Blocksize; + wakeup(&ctlr->inring.r); + + if(ctlr->sis7012) + csr16w(ctlr, In + Picb, csr16r(ctlr, In + Picb) & ~Dch); + else + csr16w(ctlr, In + Sr, csr16r(ctlr, In + Sr) & ~Dch); + stat &= ~Piint; + } + if(stat & Mint){ + ctlr->micring.wi = csr8r(ctlr, Mic + Civ) * Blocksize; + wakeup(&ctlr->micring.r); + + if(ctlr->sis7012) + csr16w(ctlr, Mic + Picb, csr16r(ctlr, Mic + Picb) & ~Dch); + else + csr16w(ctlr, Mic + Sr, csr16r(ctlr, Mic + Sr) & ~Dch); + stat &= ~Mint; + } + iunlock(ctlr); + } + if(stat) /* have seen 0x400, which is sdin0 resume */ + iprint("#A%d: ac97 unhandled interrupt(s): stat 0x%lux\n", + adev->ctlrno, stat); +} + +static long +ac97buffered(Audio *adev) +{ + Ctlr *ctlr = adev->ctlr; + return buffered(&ctlr->outring); +} + +static long +ac97status(Audio *adev, void *a, long n, vlong) +{ + Ctlr *ctlr = adev->ctlr; + return snprint((char*)a, n, "bufsize %6d buffered %6ld\n", + Blocksize, buffered(&ctlr->outring)); +} + +static int +inavail(void *arg) +{ + Ring *r = arg; + return buffered(r) > 0; +} + +static int +outavail(void *arg) +{ + Ring *r = arg; + return available(r) > 0; +} + +static int +outrate(void *arg) +{ + Ctlr *ctlr = arg; + int delay = ctlr->adev->delay*BytesPerSample; + return (delay <= 0) || (buffered(&ctlr->outring) <= delay); +} + +static long +ac97read(Audio *adev, void *vp, long n, vlong) +{ + uchar *p, *e; + Ctlr *ctlr; + Ring *ring; + ulong oi, ni; + + p = vp; + e = p + n; + ctlr = adev->ctlr; + ring = &ctlr->inring; + while(p < e) { + oi = ring->ri / Blocksize; + if((n = readring(ring, p, e - p)) <= 0){ + csr8w(ctlr, In + Lvi, (oi - 1) % Ndesc); + csr8w(ctlr, In + Cr, Ioce | Rpbm); + sleep(&ring->r, inavail, ring); + continue; + } + ni = ring->ri / Blocksize; + while(oi != ni){ + csr8w(ctlr, In + Lvi, (oi - 1) % Ndesc); + csr8w(ctlr, In + Cr, Ioce | Rpbm); + oi = (oi + 1) % Ndesc; + } + p += n; + } + return p - (uchar*)vp; +} + +static long +ac97write(Audio *adev, void *vp, long n, vlong) +{ + uchar *p, *e; + Ctlr *ctlr; + Ring *ring; + ulong oi, ni; + + p = vp; + e = p + n; + ctlr = adev->ctlr; + ring = &ctlr->outring; + while(p < e) { + oi = ring->wi / Blocksize; + if((n = writering(ring, p, e - p)) <= 0){ + sleep(&ring->r, outavail, ring); + continue; + } + ni = ring->wi / Blocksize; + while(oi != ni){ + csr8w(ctlr, Out+Lvi, oi); + csr8w(ctlr, Out+Cr, Ioce | Rpbm); + oi = (oi + 1) % Ndesc; + } + p += n; + } + while(outrate(ctlr) == 0) + sleep(&ring->r, outrate, ctlr); + return p - (uchar*)vp; +} + +static void +ac97close(Audio *adev, int mode) +{ + Ctlr *ctlr; + Ring *ring; + + if(mode == OREAD) + return; + + ctlr = adev->ctlr; + ring = &ctlr->outring; + while(ring->wi % Blocksize) + if(writering(ring, (uchar*)"", 1) <= 0) + break; +} + +static Pcidev* +ac97match(Pcidev *p) +{ + /* not all of the matched devices have been tested */ + while(p = pcimatch(p, 0, 0)) + switch((p->vid<<16)|p->did){ + case (0x1039<<16)|0x7012: + case (0x1022<<16)|0x746d: + case (0x1022<<16)|0x7445: + case (0x10de<<16)|0x01b1: + case (0x10de<<16)|0x006a: + case (0x10de<<16)|0x00da: + case (0x10de<<16)|0x00ea: + case (0x8086<<16)|0x2415: + case (0x8086<<16)|0x2425: + case (0x8086<<16)|0x2445: + case (0x8086<<16)|0x2485: + case (0x8086<<16)|0x24c5: + case (0x8086<<16)|0x24d5: + case (0x8086<<16)|0x25a6: + case (0x8086<<16)|0x266e: + case (0x8086<<16)|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 Ctlr *cards = nil; + Pcidev *p; + int i, irq, tbdf; + Ctlr *ctlr; + ulong ctl, stat = 0; + + /* make a list of all ac97 cards if not already done */ + if(cards == nil){ + p = nil; + while(p = ac97match(p)){ + ctlr = xspanalloc(sizeof(Ctlr), 8, 0); + memset(ctlr, 0, sizeof(Ctlr)); + ctlr->pcidev = p; + ctlr->next = cards; + cards = ctlr; + } + } + + /* pick a card from the list */ + for(ctlr = cards; ctlr; ctlr = ctlr->next){ + if(p = ctlr->pcidev){ + ctlr->pcidev = nil; + goto Found; + } + } + return -1; + +Found: + adev->ctlr = ctlr; + ctlr->adev = adev; + + if((p->mem[0].bar & 1) == 0 || (p->mem[1].bar & 1) == 0){ + print("ac97: not i/o regions 0x%04lux 0x%04lux\n", p->mem[0].bar, p->mem[1].bar); + return -1; + } + + i = 1; + if(p->vid == 0x1039 && p->did == 0x7012){ + ctlr->sis7012 = 1; + //i = 0; i/o bars swaped? + } + ctlr->port = p->mem[i].bar & ~3; + if(ioalloc(ctlr->port, p->mem[i].size, 0, "ac97") < 0){ + print("ac97: ioalloc failed for port 0x%04lux\n", ctlr->port); + return -1; + } + i = (i+1) & 1; + ctlr->mixport = p->mem[i].bar & ~3; + if(ioalloc(ctlr->mixport, p->mem[i].size, 0, "ac97mix") < 0){ + print("ac97: ioalloc failed for mixport 0x%04lux\n", ctlr->mixport); + iofree(ctlr->port); + return -1; + } + + 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->micring.buf = xspanalloc(Bufsize, 8, 0); + ctlr->micring.nbuf = Bufsize; + ctlr->micring.ri = 0; + ctlr->micring.wi = 0; + + ctlr->inring.buf = xspanalloc(Bufsize, 8, 0); + ctlr->inring.nbuf = Bufsize; + ctlr->inring.ri = 0; + ctlr->inring.wi = 0; + + ctlr->outring.buf = xspanalloc(Bufsize, 8, 0); + ctlr->outring.nbuf = Bufsize; + ctlr->outring.ri = 0; + ctlr->outring.wi = 0; + + for(i = 0; i < Ndesc; i++){ + int size, off = i * Blocksize; + + if(ctlr->sis7012) + size = Blocksize; + else + size = Blocksize / 2; + ctlr->micdesc[i].addr = PCIWADDR(ctlr->micring.buf + off); + ctlr->micdesc[i].size = Ioc | size; + ctlr->indesc[i].addr = PCIWADDR(ctlr->inring.buf + off); + ctlr->indesc[i].size = Ioc | size; + ctlr->outdesc[i].addr = PCIWADDR(ctlr->outring.buf + off); + ctlr->outdesc[i].size = Ioc | size; + } + + 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 */ + + ac97mixreset(adev, ac97mixw, ac97mixr); + + adev->read = ac97read; + adev->write = ac97write; + adev->close = ac97close; + adev->buffered = ac97buffered; + adev->status = ac97status; + + intrenable(irq, ac97interrupt, adev, tbdf, adev->name); + + return 0; +} + +void +audioac97link(void) +{ + addaudiocard("ac97", ac97reset); +} --- /sys/src/9/pc/audioac97mix.c Fri Feb 19 00:00:00 2016 +++ /sys/src/9/pc/audioac97mix.c Fri Feb 19 00:00:00 2016 @@ -0,0 +1,273 @@ +#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/audioif.h" + +enum { + Reset = 0x0, + Capmic = 0x1, + Captonectl = 0x4, + Capsimstereo = 0x8, + Capheadphones = 0x10, + Caploudness = 0x20, + Capdac18 = 0x40, + Capdac20 = 0x80, + Capadc18 = 0x100, + Capadc20 = 0x200, + Capenh = 0xfc00, + + Recsel = 0x1A, + General = 0x20, + ThreeDctl = 0x22, + Ac97RESER = 0x24, + Powerdowncsr = 0x26, + Adcpower = 0x1, + Dacpower = 0x2, + Anlpower = 0x4, + Refpower = 0x8, + Inpower = 0x100, + Outpower = 0x200, + Mixpower = 0x400, + Mixvrefpower = 0x800, + Aclinkpower = 0x1000, + Clkpower = 0x2000, + Auxpower = 0x4000, + Eamppower = 0x8000, + Extid = 0x28, + Extcsr = 0x2A, + Extvra = 1<<0, + Extdra = 1<<1, + Extspdif = 1<<2, + Extvrm = 1<<3, + Extiddsa0 = 0<<4, /* extid only */ + Extiddsa1 = 1<<4, /* extid only */ + Extiddsa2 = 2<<4, /* extid only */ + Extiddsa3 = 3<<4, /* extid only */ + Extcsrspsa34 = 0<<4, /* extcsr only */ + Extcsrspsa78 = 1<<4, /* extcsr only */ + Extcsrspsa69 = 2<<4, /* extcsr only */ + ExtcsrspsaAB = 3<<4, /* extcsr only */ + Extcdac = 1<<6, + Extsdac = 1<<7, + Extldac = 1<<8, + Extidamap = 1<<9, /* extid only */ + Extidrev11 = 0<<10, /* extid only */ + Extidrev22 = 1<<10, /* extid only */ + Extidrev23 = 2<<10, /* extid only */ + Extidprim = 0<<14, /* extid only */ + Extidsec0 = 1<<14, /* extid only */ + Extidsec1 = 2<<14, /* extid only */ + Extidsec2 = 3<<14, /* extid only */ + Extcsrmadc = 1<<9, /* extcsr only */ + Extcsrspcv = 1<<10, /* extcsr only */ + Extcsrpri = 1<<11, /* extcsr only */ + Extcsrprj = 1<<12, /* extcsr only */ + Extcsrprk = 1<<13, /* extcsr only */ + Extcsrprl = 1<<14, /* extcsr only */ + Extcsrvcfg = 1<<15, /* extcsr only */ + Pcmfrontdacrate = 0x2C, + Pcmsurrounddacrate = 0x2E, + Pcmlfedacrate = 0x30, + Pcmadcrate = 0x32, + Pcmmicadcrate = 0x34, + CenterLfe = 0x36, + LrSurround = 0x38, + Spdifcsr = 0x3a, + Spdifpro = 1<<0, + Spdifnonaudio = 1<<1, + Spdifcopy = 1<<2, + Spdifpre = 1<<3, + SpdifCC = 0x7f<<4, + Spdifl = 1<<11, + Spdif44k1 = 0<<12, + Spdif32k = 1<<12, + Spdif48k = 2<<12, + Spdifdsr = 1<<14, + Spdifv = 1<<15, + VID1 = 0x7c, + VID2 = 0x7e, +}; + +enum { + Vmaster, + Vhead, + Vaudio, + Vcd, + Vbass, + Vtreb, + Vbeep, + Vphone, + Vmic, + Vline, + Vvideo, + Vaux, + Vrecgain, + Vmicgain, + Vspeed, + Vdelay, +}; + +static Volume voltab[] = { + [Vmaster] "master", 0x02, -63, Stereo, 0, + [Vaudio] "audio", 0x18, -31, Stereo, 0, + [Vhead] "head", 0x04, -31, Stereo, Capheadphones, + [Vbass] "bass", 0x08, 15, Left, Captonectl, + [Vtreb] "treb", 0x08, 15, Right, Captonectl, + [Vbeep] "beep", 0x0a, -31, Right, 0, + [Vphone] "phone", 0x0c, -31, Right, 0, + [Vmic] "mic", 0x0e, -31, Right, Capmic, + [Vline] "line", 0x10, -31, Stereo, 0, + [Vcd] "cd", 0x12, -31, Stereo, 0, + [Vvideo] "video", 0x14, -31, Stereo, 0, + [Vaux] "aux", 0x16, -63, Stereo, 0, + [Vrecgain] "recgain", 0x1c, 15, Stereo, 0, + [Vmicgain] "micgain", 0x1e, 15, Right, Capmic, + [Vspeed] "speed", 0x2c, 0, Absolute, 0, + [Vdelay] "delay", 0, 0, Absolute, 0, + 0 +}; + +typedef struct Mixer Mixer; +struct Mixer +{ + ushort (*rr)(Audio *, int); + void (*wr)(Audio *, int, ushort); + int vra; +}; + +static int +ac97volget(Audio *adev, int x, int a[2]) +{ + Mixer *m = adev->mixer; + Volume *vol; + ushort v; + + vol = voltab+x; + switch(vol->type){ + case Absolute: + if(x == Vdelay){ + a[0] = adev->delay; + break; + } + a[0] = m->rr(adev, vol->reg); + break; + default: + v = m->rr(adev, vol->reg); + if(v & 0x8000){ + a[0] = a[1] = vol->range < 0 ? 0x7f : 0; + } else { + a[0] = ((v>>8) & 0x7f); + a[1] = (v & 0x7f); + } + } + return 0; +} + +static int +ac97volset(Audio *adev, int x, int a[2]) +{ + Mixer *m = adev->mixer; + Volume *vol; + ushort v, w; + + vol = voltab+x; + switch(vol->type){ + case Absolute: + if(x == Vdelay){ + adev->delay = a[0]; + return 0; + } + m->wr(adev, vol->reg, a[0]); + if(x == Vspeed){ + m->wr(adev, 0x32, a[0]); /* adc rate */ + adev->speed = m->rr(adev, vol->reg); + } + break; + case Left: + v = a[0] & 0x7f; + w = m->rr(adev, vol->reg) & 0x7f; + m->wr(adev, vol->reg, (v<<8)|w); + break; + case Right: + v = m->rr(adev, vol->reg) & 0x7f00; + w = a[1] & 0x7f; + m->wr(adev, vol->reg, v|w); + break; + case Stereo: + v = a[0] & 0x7f; + w = a[1] & 0x7f; + m->wr(adev, vol->reg, (v<<8)|w); + break; + } + return 0; +} + + +static long +ac97mixread(Audio *adev, void *a, long n, vlong) +{ + Mixer *m = adev->mixer; + ulong caps; + + caps = m->rr(adev, Reset); + caps |= m->rr(adev, Extid) << 16; + return genaudiovolread(adev, a, n, 0, voltab, ac97volget, caps); +} + +static long +ac97mixwrite(Audio *adev, void *a, long n, vlong) +{ + Mixer *m = adev->mixer; + ulong caps; + + caps = m->rr(adev, Reset); + caps |= m->rr(adev, Extid) << 16; + return genaudiovolwrite(adev, a, n, 0, voltab, ac97volset, caps); +} + +void +ac97mixreset(Audio *adev, void (*wr)(Audio*,int,ushort), ushort (*rr)(Audio*,int)) +{ + Mixer *m; + ushort t; + + m = malloc(sizeof(Mixer)); + if(m == nil){ + print("ac97mix: no memory for Mixer\n"); + return; + } + m->wr = wr; + m->rr = rr; + m->wr(adev, Reset, 0); + m->wr(adev, Powerdowncsr, 0); + delay(1000); + t = (Adcpower | Dacpower | Anlpower | Refpower); + if((m->rr(adev, Powerdowncsr) & t) != t) + print("#A%d: ac97 exhausted waiting powerup\n", adev->ctlrno); + + t = m->rr(adev, Extid); + print("#A%d: ac97 codec ext:%s%s%s%s%s%s%s\n", adev->ctlrno, + (t & Extvra) ? " vra" : "", + (t & Extdra) ? " dra" : "", + (t & Extspdif) ? " spdif" : "", + (t & Extvrm) ? " vrm" : "", + (t & Extcdac) ? " cdac" : "", + (t & Extsdac) ? " sdac" : "", + (t & Extldac) ? " ldac" : ""); + + if(t & Extvra){ + m->wr(adev, Extcsr, Extvra); + m->vra = 1; + } else { + print("#A%d: ac97 vra extension not supported\n", adev->ctlrno); + m->vra = 0; + } + + adev->mixer = m; + adev->volread = ac97mixread; + adev->volwrite = ac97mixwrite; +} --- /sys/src/9/pc/audiohda.c Fri Feb 19 00:00:00 2016 +++ /sys/src/9/pc/audiohda.c Fri Feb 19 00:00:00 2016 @@ -0,0 +1,1959 @@ +#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/audioif.h" + +typedef struct Codec Codec; +typedef struct Ctlr Ctlr; +typedef struct Bld Bld; +typedef struct Ring Ring; +typedef struct Stream Stream; + +typedef struct Id Id; +typedef struct Widget Widget; +typedef struct Codec Codec; +typedef struct Fungroup Fungroup; +typedef struct Pinprop Pinprop; + +enum { + Gcap = 0x00, + Gctl = 0x08, + Rst = 1, + Flush = 2, + Acc = 1<<8, + Wakeen = 0x0c, + Statests = 0x0e, + Sdiwake = 1 | 2 | 4, + Intctl = 0x20, + Gie = 1<<31, + Cie = 1<<30, + Intsts = 0x24, + Gis = 1<<31, + Cis = 1<<30, + Walclk = 0x30, + Corblbase = 0x40, + Corbubase = 0x44, + Corbwp = 0x48, + Corbrp = 0x4a, + Corbptrrst = 1<<15, + Corbctl = 0x4c, + Corbdma = 2, + Corbint = 1, + Corbsts = 0x4d, + Cmei = 1, + Corbsz = 0x4e, + Rirblbase = 0x50, + Rirbubase = 0x54, + Rirbwp = 0x58, + Rirbptrrst = 1<<15, + Rintcnt = 0x5a, + Rirbctl = 0x5c, + Rirbover = 4, + Rirbdma = 2, + Rirbint = 1, + Rirbsts = 0x5d, + Rirbrover = 4, + Rirbrint = 1, + Rirbsz = 0x5e, + Immcmd = 0x60, + Immresp = 0x64, + Immstat = 0x68, + Dplbase = 0x70, + Dpubase = 0x74, + /* Warning: Sdctl is 24bit register */ + Sdctl0 = 0x80, + Srst = 1<<0, + Srun = 1<<1, + Scie = 1<<2, + Seie = 1<<3, + Sdie = 1<<4, + Stagbit = 20, + Sdsts = 0x03, + Scompl = 1<<2, + Sfifoerr = 1<<3, + Sdescerr = 1<<4, + Sfifordy = 1<<5, + Sdlpib = 0x04, + Sdcbl = 0x08, + Sdlvi = 0x0c, + Sdfifow = 0x0e, + Sdfifos = 0x10, + Sdfmt = 0x12, + Fmtmono = 0, + Fmtstereo = 1, + Fmtsampw = 1<<4, + Fmtsampb = 0<<4, + Fmtdiv1 = 0<<8, + Fmtmul1 = 0<<11, + Fmtbase441 = 1<<14, + Fmtbase48 = 0<<14, + Sdbdplo = 0x18, + Sdbdphi = 0x1c, +}; + +enum { + Bufsize = 64 * 1024 * 4, + Nblocks = 256, + Blocksize = Bufsize / Nblocks, + BytesPerSample = 4, + + Maxrirbwait = 1000, /* microseconds */ + Maxwaitup = 500, /* microseconds */ + Codecdelay = 1000, /* microseconds */ +}; + +enum { + /* 12-bit cmd + 8-bit payload */ + Getparm = 0xf00, + Vendorid = 0x00, + Revid = 0x02, + Subnodecnt = 0x04, + Fungrtype = 0x05, + Graudio = 0x01, + Grmodem = 0x02, + Fungrcap = 0x08, + Widgetcap = 0x09, + Waout = 0, + Wain = 1, + Wamix = 2, + Wasel = 3, + Wpin = 4, + Wpower = 5, + Wknob = 6, + Wbeep = 7, + Winampcap = 0x0002, + Woutampcap = 0x0004, + Wampovrcap = 0x0008, + Wfmtovrcap = 0x0010, + Wstripecap = 0x0020, + Wproccap = 0x0040, + Wunsolcap = 0x0080, + Wconncap = 0x0100, + Wdigicap = 0x0200, + Wpwrcap = 0x0400, + Wlrcap = 0x0800, + Wcpcap = 0x1000, + Streamrate = 0x0a, + Streamfmt = 0x0b, + Pincap = 0x0c, + Psense = 1<<0, + Ptrigreq = 1<<1, + Pdetect = 1<<2, + Pheadphone = 1<<3, + Pout = 1<<4, + Pin = 1<<5, + Pbalanced = 1<<6, + Phdmi = 1<<7, + Peapd = 1<<16, + Inampcap = 0x0d, + Outampcap = 0x12, + Connlistlen = 0x0e, + Powerstates = 0x0f, + Processcap = 0x10, + Gpiocount = 0x11, + Knobcap = 0x13, + Getconn = 0xf01, + Setconn = 0x701, + Getconnlist = 0xf02, + Getstate = 0xf03, + Setstate = 0x703, + Setpower = 0x705, + Getpower = 0xf05, + Getstream = 0xf06, + Setstream = 0x706, + Getpinctl = 0xf07, + Setpinctl = 0x707, + Pinctlin = 1<<5, + Pinctlout = 1<<6, + Pinctlhphn = 1<<7, + Getunsolresp = 0xf08, + Setunsolresp = 0x708, + Getpinsense = 0xf09, + Exepinsense = 0x709, + Getgpi = 0xf10, + Setgpi = 0x710, + Getbeep = 0xf0a, + Setbeep = 0x70a, + Seteapd = 0x70c, + Btlenable = 1, + Eapdenable = 2, + LRswap = 4, + Getknob = 0xf0f, + Setknob = 0x70f, + Getdefault = 0xf1c, + Funreset = 0x7ff, + Getchancnt = 0xf2d, + Setchancnt = 0x72d, + + /* 4-bit cmd + 16-bit payload */ + Getcoef = 0xd, + Setcoef = 0x5, + Getproccoef = 0xc, + Setproccoef = 0x4, + Getamp = 0xb, + Setamp = 0x3, + Asetout = 1<<15, + Asetin = 1<<14, + Asetleft = 1<<13, + Asetright = 1<<12, + Asetmute = 1<<7, + Asetidx = 8, + Agetin = 0<<15, + Agetout = 1<<15, + Agetleft = 1<<13, + Agetright = 1<<15, + Agetidx = 0, + Again = 0, + Againmask = 0x7f, + Getconvfmt = 0xa, + Setconvfmt = 0x2, +}; + +enum { + Maxcodecs = 16, + Maxwidgets = 256, +}; + +struct Ring { + Rendez r; + + uchar *buf; + ulong nbuf; + + ulong ri; + ulong wi; +}; + +struct Stream { + Ring ring; + + Bld *blds; + + uint sdctl; + uint sdintr; + uint sdnum; + + uint afmt; + uint atag; + int active; + + uint pin; + uint cad; + + Widget *conv; /* DAC or ADC */ + Widget *jack; /* the pin jack */ +}; + +struct Id { + Ctlr *ctlr; + uint codec, nid; +}; + +struct Widget { + Id id; + Fungroup *fg; + uint cap, type; + uint nlist; + Widget **list; + union { + struct { + uint pin, pincap; + }; + struct { + uint convrate, convfmt; + }; + }; + Widget *next; /* next in function group */ + Widget *path; /* next in audio path */ + + Widget *link; /* temporary for findpath */ +}; + +struct Fungroup { + Id id; + Codec *codec; + uint type; + Widget *first; + Fungroup *next; +}; + +struct Codec { + Id id; + uint vid, rid; + Widget *widgets[Maxwidgets]; + Fungroup *fgroup; +}; + +/* hardware structures */ + +struct Bld { + ulong addrlo; + ulong addrhi; + ulong len; + ulong flags; +}; + +struct Ctlr { + Ctlr *next; + uint no; + + Lock; /* interrupt lock */ + QLock; /* command lock */ + + Audio *adev; + Pcidev *pcidev; + + uchar *mem; + ulong size; + + Queue *q; + ulong *corb; + ulong corbsize; + ulong *rirb; + ulong rirbsize; + + Stream sout; + Stream sin; + + uint iss, oss, bss; + + uint codecmask; + Codec *codec[Maxcodecs]; +}; + +#define csr32(c, r) (*(ulong *)&(c)->mem[r]) +#define csr16(c, r) (*(ushort *)&(c)->mem[r]) +#define csr8(c, r) (*(uchar *)&(c)->mem[r]) + +static char *widtype[] = { + "aout", + "ain", + "amix", + "asel", + "pin", + "power", + "knob", + "beep", +}; + +static char *pinport[] = { + "jack", + "nothing", + "fix", + "jack+fix", +}; + +static char *pinfunc[] = { + "lineout", + "speaker", + "hpout", + "cd", + "spdifout", + "digiout", + "modemline", + "modemhandset", + "linein", + "aux", + "micin", + "telephony", + "spdifin", + "digiin", + "resvd", + "other", +}; + + +static char *pincol[] = { + "?", + "black", + "grey", + "blue", + "green", + "red", + "orange", + "yellow", + "purple", + "pink", + "resvd", + "resvd", + "resvd", + "resvd", + "white", + "other", +}; + +static char *pinloc[] = { + "N/A", + "rear", + "front", + "left", + "right", + "top", + "bottom", + "special", + "special", + "special", + "resvd", + "resvd", + "resvd", + "resvd", + "resvd", + "resvd", +}; + +static char *pinloc2[] = { + "ext", + "int", + "sep", + "other", +}; + +Ctlr *lastcard; + +static int +waitup8(Ctlr *ctlr, int reg, uchar mask, uchar set) +{ + int i; + for(i=0; ino, reg, mask, set); + return -1; +} + +static int +waitup16(Ctlr *ctlr, int reg, ushort mask, ushort set) +{ + int i; + for(i=0; ino, reg, mask, set); + return -1; +} + +static int +waitup32(Ctlr *ctlr, int reg, uint mask, uint set) +{ + int i; + for(i=0; ino, reg, mask, set); + return -1; +} + +static int +hdacmd(Ctlr *ctlr, uint request, uint reply[2]) +{ + uint rp, wp; + uint re; + int wait; + + re = csr16(ctlr, Rirbwp); + rp = csr16(ctlr, Corbrp); + wp = (csr16(ctlr, Corbwp) + 1) % ctlr->corbsize; + if(rp == wp){ + print("#A%d: corb full\n", ctlr->no); + return -1; + } + ctlr->corb[wp] = request; + coherence(); + csr16(ctlr, Corbwp) = wp; + for(wait=0; wait < Maxrirbwait; wait++){ + if(csr16(ctlr, Rirbwp) != re){ + re = (re + 1) % ctlr->rirbsize; + memmove(reply, &ctlr->rirb[re*2], 8); + return 1; + } + microdelay(1); + } + return 0; +} + +static int +cmderr(Id id, uint verb, uint par, uint *ret) +{ + uint q, w[2]; + q = (id.codec << 28) | (id.nid << 20); + if((verb & 0x700) == 0x700) + q |= (verb << 8) | par; + else + q |= (verb << 16) | par; + if(hdacmd(id.ctlr, q, w) != 1) + return -1; + if(w[1] != id.codec) + return -1; + *ret = w[0]; + return 0; +} + +static uint +cmd(Id id, uint verb, uint par) +{ + uint w[2]; + if(cmderr(id, verb, par, w) == -1) + return ~0; + return w[0]; +} + +static Id +newnid(Id id, uint nid) +{ + id.nid = nid; + return id; +} + +static uint +getoutamprange(Widget *w) +{ + uint r; + + if((w->cap & Woutampcap) == 0) + return 0; + if((w->cap & Wampovrcap) == 0) + r = cmd(w->fg->id, Getparm, Outampcap); + else + r = cmd(w->id, Getparm, Outampcap); + return (r >> 8) & 0x7f; +} + +static void +getoutamp(Widget *w, int vol[2]) +{ + vol[0] = vol[1] = 0; + if((w->cap & Woutampcap) == 0) + return; + vol[0] = cmd(w->id, Getamp, Agetout | Agetleft) & Againmask; + vol[1] = cmd(w->id, Getamp, Agetout | Agetright) & Againmask; +} + +/* vol is 0...range or nil for 0dB; mute is 0/1 */ +static void +setoutamp(Widget *w, int mute, int *vol) +{ + uint q, r, i; + uint zerodb; + + if((w->cap & Woutampcap) == 0) + return; + if((w->cap & Wampovrcap) == 0) + r = cmd(w->fg->id, Getparm, Outampcap); + else + r = cmd(w->id, Getparm, Outampcap); + zerodb = r & 0x7f; + + for(i=0; i<2; i++){ + q = Asetout | (i == 0 ? Asetleft : Asetright); + if(mute) + q |= Asetmute; + else if(vol == nil) + q |= zerodb << Again; + else + q |= vol[i] << Again; + cmd(w->id, Setamp, q); + } +} + +static uint +getinamprange(Widget *w) +{ + uint r; + + if((w->cap & Winampcap) == 0) + return 0; + if((w->cap & Wampovrcap) == 0) + r = cmd(w->fg->id, Getparm, Inampcap); + else + r = cmd(w->id, Getparm, Inampcap); + return (r >> 8) & 0x7f; +} + +static void +getinamp(Widget *w, int vol[2]) +{ + vol[0] = vol[1] = 0; + if((w->cap & Winampcap) == 0) + return; + vol[0] = cmd(w->id, Getamp, Agetin | Agetleft) & Againmask; + vol[1] = cmd(w->id, Getamp, Agetin | Agetright) & Againmask; +} + +/* vol is 0...range or nil for 0dB; mute is 0/1; in is widget or nil for all */ +static void +setinamp(Widget *w, Widget *in, int mute, int *vol) +{ + uint q, r, i, j; + uint zerodb; + + if((w->cap & Winampcap) == 0) + return; + if((w->cap & Wampovrcap) == 0) + r = cmd(w->fg->id, Getparm, Inampcap); + else + r = cmd(w->id, Getparm, Inampcap); + zerodb = r & 0x7f; + + for(i=0; i<2; i++){ + q = Asetin | (i == 0 ? Asetleft : Asetright); + if(mute) + q |= Asetmute; + else if(vol == nil) + q |= zerodb << Again; + else + q |= vol[i] << Again; + for(j=0; jnlist; j++){ + if(in == nil || w->list[j] == in) + cmd(w->id, Setamp, q | (j << Asetidx)); + } + } +} + +static Widget * +findpath(Widget *jack, int type, char *route) +{ + Widget *q[Maxwidgets]; + uint l, r, i; + Widget *w, *to; + Fungroup *fg; + + fg = jack->fg; + + l = r = 0; + for(w=fg->first; w != nil; w = w->next) + w->link = nil; + + if(route != nil && *route != 0){ + w = jack; + while(*route++ == ','){ + i = strtoul(route, &route, 0); + if(i >= Maxwidgets) + return nil; + to = fg->codec->widgets[i]; + if(to == nil || to->fg != fg || to->link != nil) + return nil; + if(type == Waout) + to->link = w; + else + w->link = to; + w = to; + } + if(w == jack || w->type != type) + w = nil; + return w; + } + + if(type == Waout){ + q[r++] = jack; + jack->link = jack; + } else { + for(w=fg->first; w != nil; w = w->next) + if(w->type == type){ + q[r++] = w; + w->link = w; + } + } + + while(l < r){ + w = q[l++]; + if(type == Waout){ + if(w->type == type) + return w; + } else if(w == jack){ + for(w = jack->link; w != nil; w = w->link) + if(w->type == type) + return w; + break; + } + for(i=0; inlist; i++){ + to = w->list[i]; + if(to == nil || to->link) + continue; + to->link = w; + q[r++] = to; + } + } + + return nil; +} + +static void +disconnectpath(Widget *from, Widget *to) +{ + Widget *next; + + for(; from != nil && from != to; from = next){ + next = from->path; + from->path = nil; + setoutamp(from, 1, nil); + if(next != nil) + setinamp(next, from, 1, nil); + } + setoutamp(to, 1, nil); +} + +static void +muteall(Ctlr *ctlr) +{ + Fungroup *fg; + Widget *w; + int i; + + for(i=0; icodec[i] == nil) + continue; + for(fg=ctlr->codec[i]->fgroup; fg; fg=fg->next){ + for(w=fg->first; w != nil; w=w->next){ + setinamp(w, nil, 1, nil); + setoutamp(w, 1, nil); + switch(w->type){ + case Wain: + case Waout: + cmd(w->id, Setstream, 0); + break; + case Wpin: + cmd(w->id, Setpinctl, 0); + break; + } + } + } + } +} + +static void +connectpath(Widget *from, Widget *to) +{ + Widget *next; + uint i; + + for(; from != nil && from != to; from = next){ + next = from->link; + from->path = next; + setoutamp(from, 0, nil); + if(next != nil){ + setinamp(next, from, 0, nil); + for(i=0; i < next->nlist; i++){ + if(next->list[i] == from){ + cmd(next->id, Setconn, i); + break; + } + } + } + } + setoutamp(to, 0, nil); +} + +static void +addconn(Widget *w, uint nid) +{ + Widget *src; + + src = nil; + if(nid < Maxwidgets) + src = w->fg->codec->widgets[nid]; + if(src == nil || (src->fg != w->fg)){ + print("hda: invalid connection %d:%s[%d] -> %d\n", + w->id.nid, widtype[w->type & 7], w->nlist, nid); + src = nil; + } + if((w->nlist % 16) == 0){ + void *p; + + if((p = realloc(w->list, sizeof(Widget*) * (w->nlist+16))) == nil){ + print("hda: no memory for Widgetlist\n"); + return; + } + w->list = p; + } + w->list[w->nlist++] = src; +} + +static void +enumconns(Widget *w) +{ + uint r, f, b, m, i, n, x, y; + + if((w->cap & Wconncap) == 0) + return; + + r = cmd(w->id, Getparm, Connlistlen); + n = r & 0x7f; + b = (r & 0x80) ? 16 : 8; + m = (1<>= b; + else + r = cmd(w->id, Getconnlist, i); + y = r & (m>>1); + if(i && (r & m) != y) + while(++x < y) + addconn(w, x); + addconn(w, y); + x = y; + } +} + +static void +enumwidget(Widget *w) +{ + w->cap = cmd(w->id, Getparm, Widgetcap); + w->type = (w->cap >> 20) & 0x7; + if(w->cap & Wpwrcap){ + cmd(w->id, Setpower, 0); + delay(10); + } + switch(w->type){ + case Wpin: + w->pin = cmd(w->id, Getdefault, 0); + w->pincap = cmd(w->id, Getparm, Pincap); + if(w->pincap & Peapd) + cmd(w->id, Seteapd, Eapdenable); + break; + } +} + +static Fungroup * +enumfungroup(Codec *codec, Id id) +{ + Fungroup *fg; + Widget *w, **tail; + uint i, r, n, base; + + r = cmd(id, Getparm, Fungrtype) & 0x7f; + if(r != Graudio){ + cmd(id, Setpower, 3); /* turn off */ + return nil; + } + + /* open eyes */ + cmd(id, Setpower, 0); + delay(10); + + r = cmd(id, Getparm, Subnodecnt); + n = r & 0xff; + base = (r >> 16) & 0xff; + if(base >= Maxwidgets){ + print("hda: enumfungroup: base %d out of range\n", base); + return nil; + } + if(base+n > Maxwidgets){ + print("hda: enumfungroup: widgets %d - %d out of range\n", base, base+n); + n = Maxwidgets - base; + } + + fg = mallocz(sizeof *fg, 1); + if(fg == nil){ +Nomem: + print("hda: enumfungroup: out of memory\n"); + return nil; + } + fg->codec = codec; + fg->id = id; + fg->type = r; + + tail = &fg->first; + for(i=0; iwidgets[base + i] != nil){ + print("hda: enumfungroup: duplicate widget %d\n", base + i); + continue; + } + w = mallocz(sizeof(Widget), 1); + if(w == nil){ + while(w = fg->first){ + fg->first = w->next; + codec->widgets[w->id.nid] = nil; + free(w); + } + free(fg); + goto Nomem; + } + w->id = newnid(id, base + i); + w->fg = fg; + *tail = w; + tail = &w->next; + codec->widgets[w->id.nid] = w; + } + + for(i=0; iwidgets[base + i]); + for(i=0; iwidgets[base + i]); + + return fg; +} + +static int +enumcodec(Codec *codec, Id id) +{ + Fungroup *fg; + uint i, r, n, base; + uint vid, rid; + + if(cmderr(id, Getparm, Vendorid, &vid) < 0) + return -1; + if(cmderr(id, Getparm, Revid, &rid) < 0) + return -1; + + codec->id = id; + codec->vid = vid; + codec->rid = rid; + + r = cmd(id, Getparm, Subnodecnt); + n = r & 0xff; + base = (r >> 16) & 0xff; + + for(i=0; inext = codec->fgroup; + codec->fgroup = fg; + } + if(codec->fgroup == nil) + return -1; + + print("#A%d: codec #%d, vendor %08ux, rev %08ux\n", + id.ctlr->no, codec->id.codec, codec->vid, codec->rid); + + return 0; +} + +static int +enumdev(Ctlr *ctlr) +{ + Codec *codec; + int ret; + Id id; + int i; + + ret = -1; + id.ctlr = ctlr; + id.nid = 0; + for(i=0; icodecmask) == 0) + continue; + codec = mallocz(sizeof(Codec), 1); + if(codec == nil){ + print("hda: no memory for Codec\n"); + break; + } + id.codec = i; + ctlr->codec[i] = codec; + if(enumcodec(codec, id) < 0){ + ctlr->codec[i] = nil; + free(codec); + continue; + } + ret++; + } + return ret; +} + +static int +connectpin(Ctlr *ctlr, Stream *s, int type, uint pin, uint cad, char *route) +{ + Widget *jack, *conv; + + if(s->atag == 0) + return -1; + if(cad >= Maxcodecs || pin >= Maxwidgets || ctlr->codec[cad] == nil) + return -1; + jack = ctlr->codec[cad]->widgets[pin]; + if(jack == nil) + return -1; + if(jack->type != Wpin) + return -1; + + conv = findpath(jack, type, route); + if(conv == nil) + return -1; + + if(s->conv != nil && s->jack != nil){ + if(s->conv->type == Waout) + disconnectpath(s->conv, s->jack); + else + disconnectpath(s->jack, s->conv); + cmd(s->conv->id, Setstream, 0); + cmd(s->jack->id, Setpinctl, 0); + } + + if(type == Waout){ + connectpath(conv, jack); + cmd(jack->id, Setpinctl, Pinctlout); + } else { + connectpath(jack, conv); + cmd(jack->id, Setpinctl, Pinctlin); + } + + cmd(conv->id, Setconvfmt, s->afmt); + cmd(conv->id, Setstream, (s->atag << 4) | 0); + cmd(conv->id, Setchancnt, 1); + + s->conv = conv; + s->jack = jack; + s->pin = pin; + s->cad = cad; + + return 0; +} + +static int +scoreout(Widget *w) +{ + int score; + uint r; + + if((w->pincap & Pout) == 0) + return -1; + if(w->id.ctlr->sin.jack == w) + return -1; + + score = 0; + r = w->pin; + if(((r >> 30) & 0x3) >= 2) /* fix or fix+jack */ + score |= 32; + if(((r >> 12) & 0xf) == 4) /* green */ + score |= 32; + if(((r >> 24) & 0xf) == 1) /* rear */ + score |= 16; + if(((r >> 28) & 0x3) == 0) /* ext */ + score |= 8; + if(((r >> 20) & 0xf) == 2) /* hpout */ + score |= 4; + if(((r >> 20) & 0xf) == 0) /* lineout */ + score |= 4; + return score; +} + +static int +scorein(Widget *w) +{ + int score; + uint r; + + if((w->pincap & Pin) == 0) + return -1; + if(w->id.ctlr->sout.jack == w) + return -1; + + score = 0; + r = w->pin; + if(((r >> 30) & 0x3) >= 2) /* fix or fix+jack */ + score |= 4; + return score; +} + +static int +bestpin(Ctlr *ctlr, int *pcad, int (*fscore)(Widget *)) +{ + Fungroup *fg; + Widget *w; + int best, pin, score; + int i; + + pin = -1; + best = -1; + for(i=0; icodec[i] == nil) + continue; + for(fg=ctlr->codec[i]->fgroup; fg != nil; fg=fg->next){ + for(w=fg->first; w != nil; w=w->next){ + if(w->type != Wpin) + continue; + score = (*fscore)(w); + if(score >= 0 && score >= best){ + best = score; + pin = w->id.nid; + *pcad = i; + } + } + } + } + return pin; +} + +static long +buffered(Ring *r) +{ + ulong ri, wi; + + ri = r->ri; + wi = r->wi; + if(wi >= ri) + return wi - ri; + else + return r->nbuf - (ri - wi); +} + +static long +available(Ring *r) +{ + long m; + + m = (r->nbuf - BytesPerSample) - buffered(r); + if(m < 0) + m = 0; + return m; +} + +static long +readring(Ring *r, uchar *p, long n) +{ + long n0, m; + + n0 = n; + while(n > 0){ + if((m = buffered(r)) <= 0) + break; + if(m > n) + m = n; + if(p){ + if(r->ri + m > r->nbuf) + m = r->nbuf - r->ri; + memmove(p, r->buf + r->ri, m); + p += m; + } + r->ri = (r->ri + m) % r->nbuf; + n -= m; + } + return n0 - n; +} + +static long +writering(Ring *r, uchar *p, long n) +{ + long n0, m; + + n0 = n; + while(n > 0){ + if((m = available(r)) <= 0) + break; + if(m > n) + m = n; + if(p){ + if(r->wi + m > r->nbuf) + m = r->nbuf - r->wi; + memmove(r->buf + r->wi, p, m); + p += m; + } + r->wi = (r->wi + m) % r->nbuf; + n -= m; + } + return n0 - n; +} + +static int +streamalloc(Ctlr *ctlr, Stream *s, int num) +{ + Ring *r; + int i; + + r = &s->ring; + r->buf = xspanalloc(r->nbuf = Bufsize, 128, 0); + s->blds = xspanalloc(Nblocks * sizeof(Bld), 128, 0); + if(r->buf == nil || s->blds == nil){ + print("hda: no memory for stream\n"); + return -1; + } + for(i=0; iblds[i].addrlo = PADDR(r->buf) + i*Blocksize; + s->blds[i].addrhi = 0; + s->blds[i].len = Blocksize; + s->blds[i].flags = 0x01; /* interrupt on completion */ + } + + s->sdnum = num; + s->sdctl = Sdctl0 + s->sdnum*0x20; + s->sdintr = 1<sdnum; + s->atag = s->sdnum+1; + s->afmt = Fmtstereo | Fmtsampw | Fmtdiv1 | Fmtmul1 | Fmtbase441; + s->active = 0; + + /* perform reset */ + csr8(ctlr, s->sdctl) &= ~(Srst | Srun | Scie | Seie | Sdie); + csr8(ctlr, s->sdctl) |= Srst; + microdelay(Codecdelay); + waitup8(ctlr, s->sdctl, Srst, Srst); + csr8(ctlr, s->sdctl) &= ~Srst; + microdelay(Codecdelay); + waitup8(ctlr, s->sdctl, Srst, 0); + + /* set stream number */ + csr32(ctlr, s->sdctl) = (s->atag << Stagbit) | + (csr32(ctlr, s->sdctl) & ~(0xF << Stagbit)); + + /* set stream format */ + csr16(ctlr, Sdfmt+s->sdctl) = s->afmt; + + /* program stream DMA & parms */ + csr32(ctlr, Sdbdplo+s->sdctl) = PADDR(s->blds); + csr32(ctlr, Sdbdphi+s->sdctl) = 0; + csr32(ctlr, Sdcbl+s->sdctl) = r->nbuf; + csr16(ctlr, Sdlvi+s->sdctl) = (Nblocks - 1) & 0xff; + + /* mask out ints */ + csr8(ctlr, Sdsts+s->sdctl) = Scompl | Sfifoerr | Sdescerr; + + /* enable global intrs for this stream */ + csr32(ctlr, Intctl) |= s->sdintr; + csr8(ctlr, s->sdctl) |= Scie | Seie | Sdie; + + return 0; +} + +static void +streamstart(Ctlr *ctlr, Stream *s) +{ + s->active = 1; + csr8(ctlr, s->sdctl) |= Srun; + waitup8(ctlr, s->sdctl, Srun, Srun); +} + +static void +streamstop(Ctlr *ctlr, Stream *s) +{ + csr8(ctlr, s->sdctl) &= ~Srun; + waitup8(ctlr, s->sdctl, Srun, 0); + s->active = 0; +} + +static uint +streampos(Ctlr *ctlr, Stream *s) +{ + uint p; + + p = csr32(ctlr, Sdlpib+s->sdctl); + if(p >= s->ring.nbuf) + p = 0; + return p; +} + +static long +hdactl(Audio *adev, void *va, long n, vlong) +{ + char *p, *e, *x, *route, *tok[4]; + int ntok; + Ctlr *ctlr; + uint pin, cad; + + ctlr = adev->ctlr; + p = va; + e = p + n; + + for(; p < e; p = x){ + route = nil; + if(x = strchr(p, '\n')) + *x++ = 0; + else + x = e; + ntok = tokenize(p, tok, 4); + if(ntok <= 0) + continue; + if(cistrcmp(tok[0], "pin") == 0 && ntok >= 2){ + cad = ctlr->sout.cad; + pin = strtoul(tok[1], &route, 0); + if(ntok > 2) + cad = strtoul(tok[2], 0, 0); + if(connectpin(ctlr, &ctlr->sout, Waout, pin, cad, route) < 0) + error("connectpin failed"); + }else + if(cistrcmp(tok[0], "inpin") == 0 && ntok >= 2){ + cad = ctlr->sin.cad; + pin = strtoul(tok[1], &route, 0); + if(ntok > 2) + cad = strtoul(tok[2], 0, 0); + if(connectpin(ctlr, &ctlr->sin, Wain, pin, cad, route) < 0) + error("connectpin failed"); + }else + error(Ebadctl); + } + return n; +} + +static int +inavail(void *arg) +{ + Ring *r = arg; + return buffered(r) > 0; +} + +static int +outavail(void *arg) +{ + Ring *r = arg; + return available(r) > 0; +} + +static int +outrate(void *arg) +{ + Ctlr *ctlr = arg; + int delay = ctlr->adev->delay*BytesPerSample; + return (delay <= 0) || (buffered(&ctlr->sout.ring) <= delay) || (ctlr->sout.active == 0); +} + +static long +hdabuffered(Audio *adev) +{ + Ctlr *ctlr; + ctlr = adev->ctlr; + return buffered(&ctlr->sout.ring); +} + +static void +hdakick(Ctlr *ctlr) +{ + int delay; + + if(ctlr->sout.active) + return; + delay = ctlr->adev->delay*BytesPerSample; + if(buffered(&ctlr->sout.ring) >= delay) + streamstart(ctlr, &ctlr->sout); +} + +static long +hdaread(Audio *adev, void *vp, long n, vlong) +{ + uchar *p, *e; + Ctlr *ctlr; + Ring *ring; + + p = vp; + e = p + n; + ctlr = adev->ctlr; + ring = &ctlr->sin.ring; + if(ring->buf == nil || ctlr->sin.conv == nil) + return 0; + while(p < e) { + if((n = readring(ring, p, e - p)) <= 0){ + if(!ctlr->sin.active) + streamstart(ctlr, &ctlr->sin); + sleep(&ring->r, inavail, ring); + continue; + } + p += n; + } + return p - (uchar*)vp; +} + +static long +hdawrite(Audio *adev, void *vp, long n, vlong) +{ + uchar *p, *e; + Ctlr *ctlr; + Ring *ring; + + p = vp; + e = p + n; + ctlr = adev->ctlr; + ring = &ctlr->sout.ring; + if(ring->buf == nil || ctlr->sout.conv == nil) + return 0; + while(p < e) { + if((n = writering(ring, p, e - p)) <= 0){ + hdakick(ctlr); + sleep(&ring->r, outavail, ring); + continue; + } + p += n; + } + hdakick(ctlr); + while(outrate(ctlr) == 0) + sleep(&ring->r, outrate, ctlr); + return p - (uchar*)vp; +} + +static void +hdaclose(Audio *adev, int mode) +{ + Ctlr *ctlr; + Ring *ring; + + ctlr = adev->ctlr; + if(mode == OREAD || mode == ORDWR){ + if(ctlr->sin.active) + streamstop(ctlr, &ctlr->sin); + } + if(mode == OWRITE || mode == ORDWR){ + ring = &ctlr->sout.ring; + while(ring->wi % Blocksize) + if(writering(ring, (uchar*)"", 1) <= 0) + break; + } +} + +enum { + Vmaster, + Vrecord, + Vspeed, + Vdelay, + Nvol, +}; + +static Volume voltab[] = { + [Vmaster] "master", 0, 0x7f, Stereo, 0, + [Vrecord] "recgain", 0, 0x7f, Stereo, 0, + [Vspeed] "speed", 0, 0, Absolute, 0, + [Vdelay] "delay", 0, 0, Absolute, 0, + 0 +}; + +static Widget* +findoutamp(Stream *s) +{ + Widget *w; + + for(w = s->conv; w != nil; w = w->path){ + if(w->cap & Woutampcap) + return w; + if(w == s->jack) + break; + } + return nil; +} + +static Widget* +findinamp(Stream *s) +{ + Widget *w, *p, *a; + + a = nil; + for(p = nil, w = s->jack; w != nil; p = w, w = w->path){ + w->link = p; /* for setinamp */ + if(w->cap & Winampcap) + a = w; + if(w == s->conv) + break; + } + return a; +} + +static int +hdagetvol(Audio *adev, int x, int a[2]) +{ + Ctlr *ctlr = adev->ctlr; + Widget *w; + + switch(x){ + case Vmaster: + if((w = findoutamp(&ctlr->sout)) != nil) + getoutamp(w, a); + break; + case Vrecord: + if((w = findinamp(&ctlr->sin)) != nil) + getinamp(w, a); + break; + case Vspeed: + a[0] = adev->speed; + break; + case Vdelay: + a[0] = adev->delay; + break; + } + return 0; +} + +static int +hdasetvol(Audio *adev, int x, int a[2]) +{ + Ctlr *ctlr = adev->ctlr; + Widget *w; + + switch(x){ + case Vmaster: + if((w = findoutamp(&ctlr->sout)) != nil) + setoutamp(w, 0, a); + break; + case Vrecord: + if((w = findinamp(&ctlr->sin)) != nil) + setinamp(w, w->link, 0, a); + break; + case Vspeed: + adev->speed = a[0]; + break; + case Vdelay: + if(a[0] < Blocksize/BytesPerSample) { + adev->delay = Blocksize/BytesPerSample; + } else if(a[0] > (ctlr->sout.ring.nbuf/BytesPerSample)-1) { + adev->delay = (ctlr->sout.ring.nbuf/BytesPerSample)-1; + } else { + adev->delay = a[0]; + } + break; + } + return 0; +} + +static void +fillvoltab(Ctlr *ctlr, Volume *vt) +{ + Widget *w; + + memmove(vt, voltab, sizeof(voltab)); + if((w = findoutamp(&ctlr->sout)) != nil) + vt[Vmaster].range = getoutamprange(w); + if((w = findinamp(&ctlr->sin)) != nil) + vt[Vrecord].range = getinamprange(w); +} + +static long +hdavolread(Audio *adev, void *a, long n, vlong) +{ + Volume voltab[Nvol+1]; + fillvoltab(adev->ctlr, voltab); + return genaudiovolread(adev, a, n, 0, voltab, hdagetvol, 0); +} + +static long +hdavolwrite(Audio *adev, void *a, long n, vlong) +{ + Volume voltab[Nvol+1]; + fillvoltab(adev->ctlr, voltab); + return genaudiovolwrite(adev, a, n, 0, voltab, hdasetvol, 0); +} + +static void +hdainterrupt(Ureg *, void *arg) +{ + Ctlr *ctlr; + Audio *adev; + Ring *r; + uint sts; + + adev = arg; + ctlr = adev->ctlr; + ilock(ctlr); + sts = csr32(ctlr, Intsts); + if(sts & ctlr->sout.sdintr){ + csr8(ctlr, Sdsts+ctlr->sout.sdctl) |= Scompl; + + r = &ctlr->sout.ring; + r->ri = streampos(ctlr, &ctlr->sout); + if(ctlr->sout.active && buffered(r) < Blocksize){ + streamstop(ctlr, &ctlr->sout); + r->ri = r->wi = streampos(ctlr, &ctlr->sout); + } + wakeup(&r->r); + } + if(sts & ctlr->sin.sdintr){ + csr8(ctlr, Sdsts+ctlr->sin.sdctl) |= Scompl; + + r = &ctlr->sin.ring; + r->wi = streampos(ctlr, &ctlr->sin); + if(ctlr->sin.active && available(r) < Blocksize){ + streamstop(ctlr, &ctlr->sin); + r->ri = r->wi = streampos(ctlr, &ctlr->sin); + } + wakeup(&r->r); + } + iunlock(ctlr); +} + +static long +hdastatus(Audio *adev, void *a, long n, vlong) +{ + Ctlr *ctlr = adev->ctlr; + Codec *codec; + Widget *w; + uint r; + int i, j, k; + char *s, *e; + + s = a; + e = s + n; + s = seprint(s, e, "bufsize %6d buffered %6ld\n", Blocksize, buffered(&ctlr->sout.ring)); + for(i=0; icodec[i]) == nil) + continue; + s = seprint(s, e, "codec %d pin %d inpin %d\n", + codec->id.codec, ctlr->sout.pin, ctlr->sin.pin); + for(j=0; jwidgets[j]) == nil) + continue; + switch(w->type){ + case Wpin: + r = w->pin; + s = seprint(s, e, "%s %d %s%s %s %s %s %s %s%s%s", + widtype[w->type&7], w->id.nid, + (w->pincap & Pin) != 0 ? "in" : "", + (w->pincap & Pout) != 0 ? "out" : "", + pinport[(r >> 30) & 0x3], + pinloc2[(r >> 28) & 0x3], + pinloc[(r >> 24) & 0xf], + pinfunc[(r >> 20) & 0xf], + pincol[(r >> 12) & 0xf], + (w->pincap & Phdmi) ? " hdmi" : "", + (w->pincap & Peapd) ? " eapd" : "" + ); + break; + default: + s = seprint(s, e, "%s %d %lux", + widtype[w->type&7], w->id.nid, + (ulong)w->cap); + } + if(w->nlist > 0){ + s = seprint(s, e, " ← "); + for(k=0; knlist; k++){ + if(k > 0) + s = seprint(s, e, ", "); + if(w->list[k] != nil) + s = seprint(s, e, "%s %d", widtype[w->list[k]->type&7], w->list[k]->id.nid); + } + } + s = seprint(s, e, "\n"); + } + } + + if(ctlr->sout.conv != nil && ctlr->sout.jack != nil){ + s = seprint(s, e, "outpath "); + for(w=ctlr->sout.conv; w != nil; w = w->path){ + s = seprint(s, e, "%s %d", widtype[w->type&7], w->id.nid); + if(w == ctlr->sout.jack) + break; + s = seprint(s, e, " → "); + } + s = seprint(s, e, "\n"); + if((w = findoutamp(&ctlr->sout)) != nil) + s = seprint(s, e, "outamp %s %d\n", widtype[w->type&7], w->id.nid); + } + + if(ctlr->sin.conv != nil && ctlr->sin.jack != nil){ + s = seprint(s, e, "inpath "); + for(w=ctlr->sin.jack; w != nil; w = w->path){ + s = seprint(s, e, "%s %d", widtype[w->type&7], w->id.nid); + if(w == ctlr->sin.conv) + break; + s = seprint(s, e, " → "); + } + s = seprint(s, e, "\n"); + if((w = findinamp(&ctlr->sin)) != nil) + s = seprint(s, e, "inamp %s %d\n", widtype[w->type&7], w->id.nid); + } + + return s - (char*)a; +} + + +static int +hdastart(Ctlr *ctlr) +{ + static int cmdbufsize[] = { 2, 16, 256, 2048 }; + int n, size; + uint cap; + + /* reset controller */ + csr32(ctlr, Gctl) &= ~Rst; + waitup32(ctlr, Gctl, Rst, 0); + microdelay(Codecdelay); + csr32(ctlr, Gctl) |= Rst; + if(waitup32(ctlr, Gctl, Rst, Rst) && + waitup32(ctlr, Gctl, Rst, Rst)){ + print("#A%d: hda failed to reset\n", ctlr->no); + return -1; + } + microdelay(Codecdelay); + + ctlr->codecmask = csr16(ctlr, Statests); + if(ctlr->codecmask == 0){ + print("#A%d: hda no codecs\n", ctlr->no); + return -1; + } + + cap = csr16(ctlr, Gcap); + ctlr->bss = (cap>>3) & 0x1F; + ctlr->iss = (cap>>8) & 0xF; + ctlr->oss = (cap>>12) & 0xF; + + csr8(ctlr, Corbctl) = 0; + waitup8(ctlr, Corbctl, Corbdma, 0); + + csr8(ctlr, Rirbctl) = 0; + waitup8(ctlr, Rirbctl, Rirbdma, 0); + + /* alloc command buffers */ + size = csr8(ctlr, Corbsz); + n = cmdbufsize[size & 3]; + ctlr->corb = xspanalloc(n * 4, 128, 0); + memset(ctlr->corb, 0, n * 4); + ctlr->corbsize = n; + + size = csr8(ctlr, Rirbsz); + n = cmdbufsize[size & 3]; + ctlr->rirb = xspanalloc(n * 8, 128, 0); + memset(ctlr->rirb, 0, n * 8); + ctlr->rirbsize = n; + + /* setup controller */ + csr32(ctlr, Dplbase) = 0; + csr32(ctlr, Dpubase) = 0; + csr16(ctlr, Statests) = csr16(ctlr, Statests); + csr8(ctlr, Rirbsts) = csr8(ctlr, Rirbsts); + + /* setup CORB */ + csr32(ctlr, Corblbase) = PADDR(ctlr->corb); + csr32(ctlr, Corbubase) = 0; + csr16(ctlr, Corbwp) = 0; + csr16(ctlr, Corbrp) = Corbptrrst; + waitup16(ctlr, Corbrp, Corbptrrst, Corbptrrst); + csr16(ctlr, Corbrp) = 0; + waitup16(ctlr, Corbrp, Corbptrrst, 0); + csr8(ctlr, Corbctl) = Corbdma; + waitup8(ctlr, Corbctl, Corbdma, Corbdma); + + /* setup RIRB */ + csr32(ctlr, Rirblbase) = PADDR(ctlr->rirb); + csr32(ctlr, Rirbubase) = 0; + csr16(ctlr, Rirbwp) = Rirbptrrst; + csr8(ctlr, Rirbctl) = Rirbdma; + waitup8(ctlr, Rirbctl, Rirbdma, Rirbdma); + + /* enable interrupts */ + csr32(ctlr, Intctl) |= Gie | Cie; + + return 0; +} + +static Pcidev* +hdamatch(Pcidev *p) +{ + while(p = pcimatch(p, 0, 0)) + switch((p->vid << 16) | p->did){ + case (0x8086 << 16) | 0x2668: /* Intel ICH6 (untested) */ + case (0x8086 << 16) | 0x27d8: /* Intel ICH7 */ + case (0x8086 << 16) | 0x269a: /* Intel ESB2 (untested) */ + case (0x8086 << 16) | 0x284b: /* Intel ICH8 */ + case (0x8086 << 16) | 0x293f: /* Intel ICH9 (untested) */ + case (0x8086 << 16) | 0x293e: /* Intel P35 (untested) */ + case (0x8086 << 16) | 0x3b56: /* Intel P55 (Ibex Peak) */ + case (0x8086 << 16) | 0x811b: /* Intel SCH (Poulsbo) */ + case (0x8086 << 16) | 0x080a: /* Intel SCH (Oaktrail) */ + case (0x8086 << 16) | 0x1c20: /* Intel PCH */ + case (0x8086 << 16) | 0x1e20: /* Intel (Thinkpad x230t) */ + case (0x8086 << 16) | 0x8c20: /* Intel 8 Series/C220 Series */ + case (0x8086 << 16) | 0x9c20: /* Intel 8 Series Lynx Point */ + case (0x8086 << 16) | 0x9ca0: /* Intel Wildcat Point */ + + case (0x10de << 16) | 0x026c: /* NVidia MCP51 (untested) */ + case (0x10de << 16) | 0x0371: /* NVidia MCP55 (untested) */ + case (0x10de << 16) | 0x03e4: /* NVidia MCP61 (untested) */ + case (0x10de << 16) | 0x03f0: /* NVidia MCP61A (untested) */ + case (0x10de << 16) | 0x044a: /* NVidia MCP65 (untested) */ + case (0x10de << 16) | 0x055c: /* NVidia MCP67 (untested) */ + + case (0x1002 << 16) | 0x437b: /* ATI SB450 (untested) */ + case (0x1002 << 16) | 0x4383: /* ATI SB600 */ + case (0x1002 << 16) | 0xaa55: /* ATI HDMI (8500 series) */ + case (0x1002 << 16) | 0x7919: /* ATI HDMI */ + + case (0x1106 << 16) | 0x3288: /* VIA (untested) */ + case (0x1039 << 16) | 0x7502: /* SIS (untested) */ + case (0x10b9 << 16) | 0x5461: /* ULI (untested) */ + + case (0x1022 << 16) | 0x780d: /* AMD FCH Azalia Controller */ + + case (0x15ad << 16) | 0x1977: /* Vmware */ + return p; + } + return nil; +} + +static long +hdacmdread(Chan *, void *a, long n, vlong) +{ + Ctlr *ctlr; + + ctlr = lastcard; + if(ctlr == nil) + error(Enodev); + if(n & 7) + error(Ebadarg); + return qread(ctlr->q, a, n); +} + +static long +hdacmdwrite(Chan *, void *a, long n, vlong) +{ + Ctlr *ctlr; + ulong *lp; + int i; + uint w[2]; + + ctlr = lastcard; + if(ctlr == nil) + error(Enodev); + if(n & 3) + error(Ebadarg); + lp = a; + qlock(ctlr); + for(i=0; iq, w, sizeof(w)); + } + qunlock(ctlr); + return n; +} + +static int +hdareset(Audio *adev) +{ + static Ctlr *cards = nil; + int irq, tbdf, best, cad; + Ctlr *ctlr; + Pcidev *p; + + /* make a list of all cards if not already done */ + if(cards == nil){ + p = nil; + while(p = hdamatch(p)){ + ctlr = mallocz(sizeof(Ctlr), 1); + if(ctlr == nil){ + print("hda: can't allocate memory\n"); + return -1; + } + ctlr->pcidev = p; + ctlr->next = cards; + cards = ctlr; + } + } + + /* pick a card from the list */ + for(ctlr = cards; ctlr != nil; ctlr = ctlr->next){ + if(p = ctlr->pcidev){ + ctlr->pcidev = nil; + goto Found; + } + } + return -1; + +Found: + adev->ctlr = ctlr; + ctlr->adev = adev; + + irq = p->intl; + tbdf = p->tbdf; + + if(p->vid == 0x10de){ + /* magic for NVidia */ + pcicfgw8(p, 0x4e, (pcicfgr8(p, 0x4e) & 0xf0) | 0x0f); + } + if(p->vid == 0x10b9){ + /* magic for ULI */ + pcicfgw16(p, 0x40, pcicfgr16(p, 0x40) | 0x10); + pcicfgw32(p, PciBAR1, 0); + } + if(p->vid == 0x8086){ + /* magic for Intel */ + switch(p->did){ + case 0x1c20: /* PCH */ + case 0x1e20: + case 0x811b: /* SCH */ + case 0x080a: + case 0x8c20: + case 0x9c20: + case 0x9ca0: + pcicfgw16(p, 0x78, pcicfgr16(p, 0x78) & ~0x800); + } + } + if(p->vid == 0x1002){ + /* magic for ATI */ + pcicfgw8(p, 0x42, pcicfgr8(p, 0x42) | 0x02); + } else { + /* TCSEL */ + pcicfgw8(p, 0x44, pcicfgr8(p, 0x44) & 0xf8); + } + + pcisetbme(p); + pcisetpms(p, 0); + + ctlr->no = adev->ctlrno; + ctlr->size = p->mem[0].size; + ctlr->q = qopen(256, 0, 0, 0); + ctlr->mem = vmap(p->mem[0].bar & ~0x0F, ctlr->size); + if(ctlr->mem == nil){ + print("#A%d: can't map %.8lux\n", ctlr->no, p->mem[0].bar); + return -1; + } + print("#A%d: hda mem %p irq %d\n", ctlr->no, ctlr->mem, irq); + + if(hdastart(ctlr) < 0){ + print("#A%d: unable to start hda\n", ctlr->no); + return -1; + } + + /* iss + oss + bss */ + if(streamalloc(ctlr, &ctlr->sout, ctlr->iss) < 0) + print("#A%d: output streamalloc failed\n", ctlr->no); + if(ctlr->iss > 0){ + if(streamalloc(ctlr, &ctlr->sin, 0) < 0) + print("#A%d: input streamalloc failed\n", ctlr->no); + } + else if(ctlr->bss > 0){ + if(ctlr->oss > 0){ + if(streamalloc(ctlr, &ctlr->sin, ctlr->oss) < 0) + print("#A%d: input streamalloc failed\n", ctlr->no); + } else if(ctlr->bss > 1) { + if(streamalloc(ctlr, &ctlr->sin, 1) < 0) + print("#A%d: input streamalloc failed\n", ctlr->no); + } + } + + if(enumdev(ctlr) < 0){ + print("#A%d: no audio codecs found\n", ctlr->no); + return -1; + } + muteall(ctlr); + + best = bestpin(ctlr, &cad, scoreout); + if(best < 0) + print("#A%d: no output pins found\n", ctlr->no); + else if(connectpin(ctlr, &ctlr->sout, Waout, best, cad, nil) < 0) + print("#A%d: error connecting output pin\n", ctlr->no); + + best = bestpin(ctlr, &cad, scorein); + if(best < 0) + print("#A%d: no input pins found\n", ctlr->no); + else if(connectpin(ctlr, &ctlr->sin, Wain, best, cad, nil) < 0) + print("#A%d: error connecting input pin\n", ctlr->no); + + adev->read = hdaread; + adev->write = hdawrite; + adev->close = hdaclose; + adev->buffered = hdabuffered; + adev->volread = hdavolread; + adev->volwrite = hdavolwrite; + adev->status = hdastatus; + adev->ctl = hdactl; + + intrenable(irq, hdainterrupt, adev, tbdf, "hda"); + lastcard = ctlr; + addarchfile("hdacmd", 0664, hdacmdread, hdacmdwrite); + + return 0; +} + +void +audiohdalink(void) +{ + addaudiocard("hda", hdareset); +} + --- /sys/src/9/pc/audiosb16.c Fri Feb 19 00:00:00 2016 +++ /sys/src/9/pc/audiosb16.c Fri Feb 19 00:00:00 2016 @@ -0,0 +1,859 @@ +/* + * SB 16 driver + */ +#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/audioif.h" + +typedef struct Ring Ring; +typedef struct Blaster Blaster; +typedef struct Ctlr Ctlr; + +enum +{ + Vmaster, + Vaudio, + Vsynth, + Vcd, + Vline, + Vmic, + Vspeaker, + Vtreb, + Vbass, + Vigain, + Vogain, + Vspeed, + Vdelay, + Nvol, + + Blocksize = 4096, + Blocks = 65536/Blocksize, +}; + +struct Ring +{ + uchar *buf; + ulong nbuf; + + ulong ri; + ulong wi; +}; + +struct Blaster +{ + Lock; + int reset; /* io ports to the sound blaster */ + int read; + int write; + int wstatus; + int rstatus; + int mixaddr; + int mixdata; + int clri8; + int clri16; + int clri401; + + void (*startdma)(Ctlr*); + void (*intr)(Ctlr*); +}; + +struct Ctlr +{ + Rendez vous; + int active; /* boolean dma running */ + int major; /* SB16 major version number (sb 4) */ + int minor; /* SB16 minor version number */ + Ring ring; /* dma ring buffer */ + Blaster blaster; + + int lvol[Nvol]; + int rvol[Nvol]; + + /* for probe */ + Audio *adev; + ISAConf conf; + Ctlr *next; +}; + +static Volume voltab[] = { + [Vmaster] "master", 0x30, 0xff, Stereo, 0, + [Vaudio] "audio", 0x32, 0xff, Stereo, 0, + [Vsynth] "synth", 0x34, 0xff, Stereo, 0, + [Vcd] "cd", 0x36, 0xff, Stereo, 0, + [Vline] "line", 0x38, 0xff, Stereo, 0, + [Vmic] "mic", 0x3a, 0xff, Mono, 0, + [Vspeaker] "speaker", 0x3b, 0xff, Mono, 0, + [Vtreb] "treb", 0x44, 0xff, Stereo, 0, + [Vbass] "bass", 0x46, 0xff, Stereo, 0, + [Vigain] "recgain", 0x3f, 0xff, Stereo, 0, + [Vogain] "outgain", 0x41, 0xff, Stereo, 0, + [Vspeed] "speed", 0, 0, Absolute, 0, + [Vdelay] "delay", 0, 0, Absolute, 0, + 0, +}; + +static char Emajor[] = "soundblaster not responding/wrong version"; + +static long +buffered(Ring *r) +{ + ulong ri, wi; + + ri = r->ri; + wi = r->wi; + if(wi >= ri) + return wi - ri; + else + return r->nbuf - (ri - wi); +} + +static long +available(Ring *r) +{ + long m; + + m = (r->nbuf - 1) - buffered(r); + if(m < 0) + m = 0; + return m; +} + +static long +readring(Ring *r, uchar *p, long n) +{ + long n0, m; + + n0 = n; + while(n > 0){ + if((m = buffered(r)) <= 0) + break; + if(m > n) + m = n; + if(p){ + if(r->ri + m > r->nbuf) + m = r->nbuf - r->ri; + memmove(p, r->buf + r->ri, m); + p += m; + } + r->ri = (r->ri + m) % r->nbuf; + n -= m; + } + return n0 - n; +} + +static long +writering(Ring *r, uchar *p, long n) +{ + long n0, m; + + n0 = n; + while(n > 0){ + if((m = available(r)) <= 0) + break; + if(m > n) + m = n; + if(p){ + if(r->wi + m > r->nbuf) + m = r->nbuf - r->wi; + memmove(r->buf + r->wi, p, m); + p += m; + } + r->wi = (r->wi + m) % r->nbuf; + n -= m; + } + return n0 - n; +} + +static int +sbcmd(Blaster *blaster, int val) +{ + int i, s; + + for(i=1<<16; i!=0; i--) { + s = inb(blaster->wstatus); + if((s & 0x80) == 0) { + outb(blaster->write, val); + return 0; + } + } + return 1; +} + +static int +sbread(Blaster *blaster) +{ + int i, s; + + for(i=1<<16; i!=0; i--) { + s = inb(blaster->rstatus); + if((s & 0x80) != 0) { + return inb(blaster->read); + } + } + return -1; +} + +static int +ess1688w(Blaster *blaster, int reg, int val) +{ + if(sbcmd(blaster, reg) || sbcmd(blaster, val)) + return 1; + return 0; +} + +static int +ess1688r(Blaster *blaster, int reg) +{ + if(sbcmd(blaster, 0xC0) || sbcmd(blaster, reg)) + return -1; + return sbread(blaster); +} + +static int +mxcmd(Blaster *blaster, int addr, int val) +{ + outb(blaster->mixaddr, addr); + outb(blaster->mixdata, val); + return 1; +} + +static int +mxread(Blaster *blaster, int addr) +{ + int s; + + outb(blaster->mixaddr, addr); + s = inb(blaster->mixdata); + return s; +} + +static int +mxsetvol(Audio *adev, int x, int a[2]) +{ + Blaster *blaster; + Ctlr *ctlr = adev->ctlr; + Volume *vol; + + vol = voltab+x; + blaster = &ctlr->blaster; + ilock(blaster); + switch(vol->type){ + case Absolute: + switch(x){ + case Vdelay: + adev->delay = a[0]; + break; + case Vspeed: + adev->speed = a[0]; + break; + } + ctlr->lvol[x] = ctlr->rvol[x] = a[0]; + break; + case Stereo: + ctlr->rvol[x] = a[1]; + mxcmd(blaster, vol->reg+1, a[1]); + /* no break */ + case Mono: + ctlr->lvol[x] = a[0]; + mxcmd(blaster, vol->reg, a[0]); + } + iunlock(blaster); + + return 0; +} + +static int +mxgetvol(Audio *adev, int x, int a[2]) +{ + Ctlr *ctlr = adev->ctlr; + + a[0] = ctlr->lvol[x]; + a[1] = ctlr->rvol[x]; + + return 0; +} + +static void +contindma(Ctlr *ctlr) +{ + Blaster *blaster; + Ring *ring; + + blaster = &ctlr->blaster; + ring = &ctlr->ring; + if(buffered(ring) >= Blocksize) + ring->ri = ring->nbuf - dmacount(ctlr->conf.dma); + else{ + dmaend(ctlr->conf.dma); + sbcmd(blaster, 0xd9); /* exit at end of count */ + sbcmd(blaster, 0xd5); /* pause */ + ctlr->active = 0; + } + wakeup(&ctlr->vous); +} + +/* + * cause sb to get an interrupt per buffer. + * start first dma + */ +static void +sb16startdma(Ctlr *ctlr) +{ + Blaster *blaster; + Ring *ring; + long count; + int speed; + + blaster = &ctlr->blaster; + ring = &ctlr->ring; + ilock(blaster); + dmaend(ctlr->conf.dma); + if(0) + sbcmd(blaster, 0x42); /* input sampling rate */ + else + sbcmd(blaster, 0x41); /* output sampling rate */ + speed = ctlr->adev->speed; + sbcmd(blaster, speed>>8); + sbcmd(blaster, speed); + + if(0) + sbcmd(blaster, 0xbe); /* A/D, autoinit */ + else + sbcmd(blaster, 0xb6); /* D/A, autoinit */ + + sbcmd(blaster, 0x30); /* stereo, signed 16 bit */ + + count = (Blocksize>>1) - 1; + sbcmd(blaster, count); + sbcmd(blaster, count>>8); + + ctlr->active = 1; + if(dmasetup(ctlr->conf.dma, ring->buf, ring->nbuf, DMAWRITE|DMALOOP) < 0){ + ctlr->active = 0; + print("#A%d: dmasetup fail\n", ctlr->adev->ctlrno); + } + iunlock(blaster); +} + +static int +ess1688reset(Blaster *blaster, int ctlrno) +{ + int i; + + outb(blaster->reset, 3); + delay(1); /* >3 υs */ + outb(blaster->reset, 0); + delay(1); + + i = sbread(blaster); + if(i != 0xAA) { + print("#A%d: no response %#.2x\n", ctlrno, i); + return 1; + } + + if(sbcmd(blaster, 0xC6)){ /* extended mode */ + print("#A%d: barf 3\n", ctlrno); + return 1; + } + + return 0; +} + +static void +ess1688startdma(Ctlr *ctlr) +{ + Blaster *blaster; + Ring *ring; + ulong count; + int speed, x; + + blaster = &ctlr->blaster; + ring = &ctlr->ring; + + ilock(blaster); + dmaend(ctlr->conf.dma); + + ess1688reset(blaster, ctlr->adev->ctlrno); + + /* + * Set the speed. + */ + speed = ctlr->adev->speed; + if(speed < 4000) + speed = 4000; + else if(speed > 48000) + speed = 48000; + if(speed > 22000) + x = 0x80|(256-(795500+speed/2)/speed); + else + x = 128-(397700+speed/2)/speed; + ess1688w(blaster, 0xA1, x & 0xFF); + + speed = (speed * 9) / 20; + x = 256 - 7160000 / (speed * 82); + ess1688w(blaster, 0xA2, x & 0xFF); + + if(0) + ess1688w(blaster, 0xB8, 0x0E); /* A/D, autoinit */ + else + ess1688w(blaster, 0xB8, 0x04); /* D/A, autoinit */ + x = ess1688r(blaster, 0xA8) & ~0x03; + ess1688w(blaster, 0xA8, x|0x01); /* 2 channels */ + ess1688w(blaster, 0xB9, 2); /* demand mode, 4 bytes per request */ + + if(1) + ess1688w(blaster, 0xB6, 0); /* for output */ + + ess1688w(blaster, 0xB7, 0x71); + ess1688w(blaster, 0xB7, 0xBC); + + x = ess1688r(blaster, 0xB1) & 0x0F; + ess1688w(blaster, 0xB1, x|0x50); + x = ess1688r(blaster, 0xB2) & 0x0F; + ess1688w(blaster, 0xB2, x|0x50); + + if(1) + sbcmd(blaster, 0xD1); /* speaker on */ + + count = -Blocksize; + ess1688w(blaster, 0xA4, count & 0xFF); + ess1688w(blaster, 0xA5, (count>>8) & 0xFF); + x = ess1688r(blaster, 0xB8); + ess1688w(blaster, 0xB8, x|0x05); + + ctlr->active = 1; + if(dmasetup(ctlr->conf.dma, ring->buf, ring->nbuf, DMAWRITE|DMALOOP) < 0){ + ctlr->active = 0; + print("#A%d: dmasetup fail\n", ctlr->adev->ctlrno); + } + iunlock(blaster); +} + +static void +sb16intr(Ctlr *ctlr) +{ + Blaster *blaster; + int stat; + + blaster = &ctlr->blaster; + ilock(blaster); + stat = mxread(blaster, 0x82); /* get irq status */ + if(stat & 3){ + contindma(ctlr); + if(stat & 2) + inb(blaster->clri16); + else if(stat & 1) + inb(blaster->clri8); + } else if(stat & 4) + inb(blaster->clri401); + iunlock(blaster); +} + +static void +ess1688intr(Ctlr *ctlr) +{ + Blaster *blaster; + + blaster = &ctlr->blaster; + ilock(blaster); + contindma(ctlr); + inb(blaster->clri8); + iunlock(blaster); +} + +static void +audiointr(Ureg *, void *arg) +{ + Audio *adev; + Ctlr *ctlr; + + adev = arg; + ctlr = adev->ctlr; + if(!ctlr->active){ + iprint("#A%d: unexpected %s interrupt\n", + ctlr->adev->ctlrno, ctlr->adev->name); + return; + } + ctlr->blaster.intr(ctlr); +} + +static void +setempty(Ctlr *ctlr) +{ + ilock(&ctlr->blaster); + ctlr->ring.ri = 0; + ctlr->ring.wi = 0; + iunlock(&ctlr->blaster); +} + +static long +audiobuffered(Audio *adev) +{ + return buffered(&((Ctlr*)adev->ctlr)->ring); +} + +static long +audiostatus(Audio *adev, void *a, long n, vlong) +{ + Ctlr *ctlr = adev->ctlr; + return snprint((char*)a, n, "bufsize %6d buffered %6ld\n", + Blocksize, buffered(&ctlr->ring)); +} + +static int +inactive(void *arg) +{ + Ctlr *ctlr = arg; + return !ctlr->active; +} + +static int +anybuf(void *arg) +{ + Ctlr *ctlr = arg; + return available(&ctlr->ring) || inactive(ctlr); +} + +static int +ratebuf(void *arg) +{ + Ctlr *ctlr = arg; + int delay = ctlr->adev->delay*4; + return (delay <= 0) || (buffered(&ctlr->ring) <= delay) || inactive(ctlr); +} + +static long +audiowrite(Audio *adev, void *vp, long n, vlong) +{ + uchar *p, *e; + Ctlr *ctlr; + Ring *ring; + + p = vp; + e = p + n; + ctlr = adev->ctlr; + ring = &ctlr->ring; + while(p < e) { + if((n = writering(ring, p, e - p)) <= 0){ + if(!ctlr->active && ring->ri == 0) + ctlr->blaster.startdma(ctlr); + if(!ctlr->active) + setempty(ctlr); + else + sleep(&ctlr->vous, anybuf, ctlr); + } + p += n; + } + while(ratebuf(ctlr) == 0) + sleep(&ctlr->vous, ratebuf, ctlr); + return p - (uchar*)vp; +} + +static void +audioclose(Audio *adev, int mode) +{ + Ctlr *ctlr; + + if(mode == OREAD) + return; + ctlr = adev->ctlr; + sleep(&ctlr->vous, inactive, ctlr); + setempty(ctlr); +} + +static long +audiovolread(Audio *adev, void *a, long n, vlong) +{ + return genaudiovolread(adev, a, n, 0, voltab, mxgetvol, 0); +} + +static long +audiovolwrite(Audio *adev, void *a, long n, vlong) +{ + Blaster *blaster; + Ctlr *ctlr; + int source; + + ctlr = adev->ctlr; + blaster = &ctlr->blaster; + + n = genaudiovolwrite(adev, a, n, 0, voltab, mxsetvol, 0); + + source = 0; + if(ctlr->lvol[Vsynth]) + source |= 1<<6; + if(ctlr->rvol[Vsynth]) + source |= 1<<5; + if(ctlr->lvol[Vaudio]) + source |= 1<<4; + if(ctlr->rvol[Vaudio]) + source |= 1<<3; + if(ctlr->lvol[Vcd]) + source |= 1<<2; + if(ctlr->rvol[Vcd]) + source |= 1<<1; + if(ctlr->lvol[Vmic]) + source |= 1<<0; + + ilock(blaster); + mxcmd(blaster, 0x3c, source); /* output switch */ + mxcmd(blaster, 0x3d, source); /* input left switch */ + mxcmd(blaster, 0x3e, source); /* input right switch */ + iunlock(blaster); + + return n; +} + +static int +ess1688(ISAConf* sbconf, Blaster *blaster, int ctlrno) +{ + int i, major, minor; + + /* + * Try for ESS1688. + */ + sbcmd(blaster, 0xE7); /* get version */ + major = sbread(blaster); + minor = sbread(blaster); + if(major != 0x68 || minor != 0x8B){ + print("#A%d: model %#.2x %#.2x; not ESS1688 compatible\n", + ctlrno, major, minor); + return -1; + } + + ess1688reset(blaster, ctlrno); + + switch(sbconf->irq){ + case 2: + case 9: + i = 0x50|(0<<2); + break; + case 5: + i = 0x50|(1<<2); + break; + case 7: + i = 0x50|(2<<2); + break; + case 10: + i = 0x50|(3<<2); + break; + default: + print("#A%d: bad ESS1688 irq %d\n", ctlrno, sbconf->irq); + return 1; + } + ess1688w(blaster, 0xB1, i); + + switch(sbconf->dma){ + case 0: + i = 0x50|(1<<2); + break; + case 1: + i = 0xF0|(2<<2); + break; + case 3: + i = 0x50|(3<<2); + break; + default: + print("#A%d: bad ESS1688 dma %lud\n", ctlrno, sbconf->dma); + return 1; + } + ess1688w(blaster, 0xB2, i); + + ess1688reset(blaster, ctlrno); + + blaster->startdma = ess1688startdma; + blaster->intr = ess1688intr; + + return 0; +} + +static int +audioprobe(Audio *adev) +{ + static int irq[] = {9,5,7,10}; + static Ctlr *cards = nil; + + Ctlr *ctlr; + Blaster *blaster; + int i, x; + + /* make a list of audio isa cards if not already done */ + if(cards == nil){ + for(i=0; iconf.port = 0x220 + i*0x10; + ctlr->conf.irq = irq[i]; + ctlr->conf.dma = 0; + if(isaconfig("audio", i, &ctlr->conf) == 0){ + free(ctlr); + break; + } + ctlr->next = cards; + cards = ctlr; + } + } + + /* pick a card */ + for(ctlr = cards; ctlr; ctlr = ctlr->next){ + if(ctlr->conf.type && strcmp(adev->name, ctlr->conf.type) == 0){ + ctlr->conf.type = nil; + goto Found; + } + } + return -1; + +Found: + switch(ctlr->conf.port){ + case 0x220: + case 0x240: + case 0x260: + case 0x280: + break; + default: + print("#A%d: bad port %#lux\n", adev->ctlrno, ctlr->conf.port); + return -1; + } + + if(ioalloc(ctlr->conf.port, 0x10, 0, "audio") < 0){ + print("#A%d: cannot ioalloc range %lux+0x10\n", + adev->ctlrno, ctlr->conf.port); + return -1; + } + if(ioalloc(ctlr->conf.port+0x100, 1, 0, "audio.mpu401") < 0){ + iofree(ctlr->conf.port); + print("#A%d: cannot ioalloc range %lux+0x01\n", + adev->ctlrno, ctlr->conf.port+0x100); + return -1; + } + + ctlr->adev = adev; + adev->ctlr = ctlr; + + blaster = &ctlr->blaster; + blaster->reset = ctlr->conf.port + 0x6; + blaster->read = ctlr->conf.port + 0xa; + blaster->write = ctlr->conf.port + 0xc; + blaster->wstatus = ctlr->conf.port + 0xc; + blaster->rstatus = ctlr->conf.port + 0xe; + blaster->mixaddr = ctlr->conf.port + 0x4; + blaster->mixdata = ctlr->conf.port + 0x5; + blaster->clri8 = ctlr->conf.port + 0xe; + blaster->clri16 = ctlr->conf.port + 0xf; + blaster->clri401 = ctlr->conf.port + 0x100; + + blaster->startdma = sb16startdma; + blaster->intr = sb16intr; + + outb(blaster->reset, 1); + delay(1); /* >3 υs */ + outb(blaster->reset, 0); + delay(1); + + i = sbread(blaster); + if(i != 0xaa) { + print("#A%d: no response #%.2x\n", adev->ctlrno, i); +Errout: + iofree(ctlr->conf.port); + iofree(ctlr->conf.port+0x100); + return -1; + } + + sbcmd(blaster, 0xe1); /* get version */ + ctlr->major = sbread(blaster); + ctlr->minor = sbread(blaster); + + if(ctlr->major != 4) { + if(ctlr->major != 3 || ctlr->minor != 1 || + ess1688(&ctlr->conf, blaster, adev->ctlrno)){ + print("#A%d: model %#.2x %#.2x; not SB 16 compatible\n", + adev->ctlrno, ctlr->major, ctlr->minor); + goto Errout; + } + ctlr->major = 4; + } + + /* + * initialize the mixer + */ + mxcmd(blaster, 0x00, 0); /* Reset mixer */ + + for(i=0; iconf.irq == irq[i]){ + mxcmd(blaster, 0x80, 1<conf.irq = irq[i]; + break; + } + } + + for(;;){ + /* set 16bit dma */ + if(ctlr->conf.dma>=5 && ctlr->conf.dma<=7){ + x = mxread(blaster, 0x81); + mxcmd(blaster, 0x81, (1<conf.dma) & 0xF0 | (x & 0x0F)); + } + x = mxread(blaster, 0x81); + for(i=5; i<=7; i++){ + if(x & (1<conf.dma = i; + break; + } + } + if(ctlr->conf.dma>=5) + break; + ctlr->conf.dma = 7; + } + + print("#A%d: %s port 0x%04lux irq %d dma %lud\n", adev->ctlrno, adev->name, + ctlr->conf.port, ctlr->conf.irq, ctlr->conf.dma); + + ctlr->ring.nbuf = Blocks*Blocksize; + if(dmainit(ctlr->conf.dma, ctlr->ring.nbuf)) + goto Errout; + ctlr->ring.buf = dmabva(ctlr->conf.dma); + print("#A%d: %s dma buffer %p-%p\n", adev->ctlrno, adev->name, + ctlr->ring.buf, ctlr->ring.buf+ctlr->ring.nbuf); + + setempty(ctlr); + + adev->write = audiowrite; + adev->close = audioclose; + adev->volread = audiovolread; + adev->volwrite = audiovolwrite; + adev->status = audiostatus; + adev->buffered = audiobuffered; + + intrenable(ctlr->conf.irq, audiointr, adev, BUSUNKNOWN, adev->name); + + return 0; +} + +void +audiosb16link(void) +{ + addaudiocard("sb16", audioprobe); + addaudiocard("ess1688", audioprobe); +} --- /n/sources/plan9/sys/src/9/pc/dma.c Tue Mar 21 18:39:33 2006 +++ /sys/src/9/pc/dma.c Fri Feb 19 00:00:00 2016 @@ -18,7 +18,7 @@ int blen; /* bounce buffer length */ void* va; /* virtual address destination/src */ long len; /* bytes to be transferred */ - int isread; + int flags; }; /* @@ -81,7 +81,7 @@ if(i8237dma > 2) i8237dma = 2; - bva = xspanalloc(64*1024*i8237dma, BY2PG, 64*1024); + bva = xspanalloc(64*1024*i8237dma, 0, 64*1024); if(bva == nil || PADDR(bva)+64*1024*i8237dma > 16*MB){ /* * This will panic with the current @@ -137,11 +137,38 @@ xp->bpa = PADDR(xp->bva); xp->blen = maxtransfer; xp->len = 0; - xp->isread = 0; + xp->flags = 0; return 0; } +void* +dmabva(int chan) +{ + DMA *dp; + DMAxfer *xp; + + dp = &dma[(chan>>2)&1]; + chan = chan & 3; + xp = &dp->x[chan]; + return xp->bva; +} + +int +dmacount(int chan) +{ + int retval; + DMA *dp; + + dp = &dma[(chan>>2)&1]; + chan = chan & 3; + ilock(dp); + retval = inb(dp->count[chan]); + retval |= inb(dp->count[chan]) << 8; + iunlock(dp); + return ((retval<shift)+1) & 0xFFFF; +} + /* * setup a dma transfer. if the destination is not in kernel * memory, allocate a page for the transfer. @@ -153,7 +180,7 @@ * boundaries) */ long -dmasetup(int chan, void *va, long len, int isread) +dmasetup(int chan, void *va, long len, int flags) { DMA *dp; ulong pa; @@ -168,29 +195,32 @@ * if this isn't kernel memory or crossing 64k boundary or above 16 meg * use the bounce buffer. */ - if((ulong)va < KZERO + if((uintptr)va < KZERO || ((pa=PADDR(va))&0xFFFF0000) != ((pa+len)&0xFFFF0000) || pa >= 16*MB){ if(xp->bva == nil) return -1; if(len > xp->blen) len = xp->blen; - if(!isread) + if(!(flags & DMAREAD)) memmove(xp->bva, va, len); xp->va = va; xp->len = len; - xp->isread = isread; + xp->flags = flags; pa = xp->bpa; } else xp->len = 0; + mode = ((flags & DMAREAD) ? 0x44 : 0x48) | /* read or write */ + ((flags & DMALOOP) ? 0x10 : 0) | /* auto init mode */ + chan; + /* * this setup must be atomic */ ilock(dp); - mode = (isread ? 0x44 : 0x48) | chan; - outb(dp->mode, mode); /* single mode dma (give CPU a chance at mem) */ + outb(dp->mode, mode); outb(dp->page[chan], pa>>16); outb(dp->cbp, 0); /* set count & address to their first byte */ outb(dp->addr[chan], pa>>dp->shift); /* set address */ @@ -238,7 +268,7 @@ iunlock(dp); xp = &dp->x[chan]; - if(xp->len == 0 || !xp->isread) + if(xp->len == 0 || !(xp->flags & DMAREAD)) return; /* @@ -248,17 +278,3 @@ xp->len = 0; } -/* -int -dmacount(int chan) -{ - int retval; - DMA *dp; - - dp = &dma[(chan>>2)&1]; - outb(dp->cbp, 0); - retval = inb(dp->count[chan]); - retval |= inb(dp->count[chan]) << 8; - return((retval<shift)+1); -} - */ --- /n/sources/plan9/sys/src/9/pc/fns.h Wed May 14 14:45:00 2014 +++ /sys/src/9/pc/fns.h Fri Feb 19 00:00:00 2016 @@ -21,10 +21,14 @@ void cpuidprint(void); void (*cycles)(uvlong*); void delay(int); +void* dmabva(int); int dmacount(int); int dmadone(int); void dmaend(int); int dmainit(int, int); +#define DMAWRITE 0 +#define DMAREAD 1 +#define DMALOOP 2 long dmasetup(int, void*, long, int); void fpclear(void); void fpenv(FPsave*); --- /n/sources/plan9/sys/src/9/pc/pc Fri Aug 17 21:44:14 2012 +++ /sys/src/9/pc/pc Fri Feb 19 00:00:00 2016 @@ -31,7 +31,7 @@ floppy dma lpt - audio dma + audio pccard i82365 cis uart @@ -80,6 +80,10 @@ usbuhci usbohci usbehci usbehcipc + + audiosb16 dma + audioac97 audioac97mix + audiohda x86watchdog --- /n/sources/plan9/sys/src/9/pc/pccd Fri Aug 17 22:02:44 2012 +++ /sys/src/9/pc/pccd Fri Feb 19 00:00:00 2016 @@ -31,7 +31,7 @@ floppy dma lpt - audio dma + audio pccard i82365 cis uart @@ -78,6 +78,10 @@ usbuhci usbohci usbehci usbehcipc + + audiosb16 dma + audioac97 audioac97mix + audiohda misc archmp mp apic mpacpi --- /n/sources/plan9/sys/src/9/pc/pccpu Fri Aug 17 21:44:45 2012 +++ /sys/src/9/pc/pccpu Fri Feb 19 00:00:00 2016 @@ -29,7 +29,7 @@ floppy dma aoe - audio dma + audio uart usb @@ -73,6 +73,10 @@ usbuhci usbohci usbehci usbehcipc + + audiosb16 dma + audioac97 audioac97mix + audiohda x86watchdog --- /n/sources/plan9/sys/src/9/pc/pcdisk Fri Aug 17 21:45:40 2012 +++ /sys/src/9/pc/pcdisk Fri Feb 19 00:00:00 2016 @@ -31,7 +31,7 @@ aoe lpt - audio dma + audio pccard i82365 cis uart @@ -77,6 +77,10 @@ usbuhci usbohci usbehci usbehcipc + + audiosb16 dma + audioac97 audioac97mix + audiohda misc archmp mp apic mpacpi --- /n/sources/plan9/sys/src/9/pc/pcf Fri Aug 17 21:44:56 2012 +++ /sys/src/9/pc/pcf Fri Feb 19 00:00:00 2016 @@ -31,7 +31,7 @@ aoe lpt - audio dma + audio pccard i82365 cis uart @@ -77,6 +77,10 @@ usbuhci usbohci usbehci usbehcipc + + audiosb16 dma + audioac97 audioac97mix + audiohda misc archmp mp apic mpacpi --- /sys/src/9/port/audioif.h Fri Feb 19 00:00:00 2016 +++ /sys/src/9/port/audioif.h Fri Feb 19 00:00:00 2016 @@ -0,0 +1,56 @@ +typedef struct Audio Audio; +typedef struct Volume Volume; + +struct Audio +{ + char *name; + + void *ctlr; + void *mixer; + + Ref audioopenr; + Ref audioopenw; + + long (*read)(Audio *, void *, long, vlong); + long (*write)(Audio *, void *, long, vlong); + void (*close)(Audio *, int); + + long (*volread)(Audio *, void *, long, vlong); + long (*volwrite)(Audio *, void *, long, vlong); + + long (*ctl)(Audio *, void *, long, vlong); + long (*status)(Audio *, void *, long, vlong); + long (*buffered)(Audio *); + + int delay; + int speed; + + int ctlrno; + Audio *next; +}; + +enum { + Left, + Right, + Stereo, + Absolute, +}; + +#define Mono Left + +struct Volume +{ + char *name; + int reg; + int range; + int type; + int cap; +}; + +extern void addaudiocard(char *, int (*)(Audio *)); +extern long genaudiovolread(Audio *adev, void *a, long n, vlong off, + Volume *vol, int (*volget)(Audio *, int, int *), + ulong caps); +extern long genaudiovolwrite(Audio *adev, void *a, long n, vlong off, + Volume *vol, int (*volset)(Audio *, int, int *), + ulong caps); --- /n/sources/plan9/sys/src/9/port/devaudio.c Sun Aug 10 04:15:36 2008 +++ /sys/src/9/port/devaudio.c Fri Feb 19 00:00:00 2016 @@ -1,1250 +1,500 @@ -/* - * SB 16 driver - */ #include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" -#include "../port/error.h" #include "io.h" -#include "audio.h" - -typedef struct AQueue AQueue; -typedef struct Buf Buf; - -enum -{ - Qdir = 0, - Qaudio, - Qvolume, - Qstatus, - - Fmono = 1, - Fin = 2, - Fout = 4, - - Aclosed = 0, - Aread, - Awrite, - - Vaudio = 0, - Vsynth, - Vcd, - Vline, - Vmic, - Vspeaker, - Vtreb, - Vbass, - Vspeed, - Nvol, +#include "../port/error.h" +#include "../port/audioif.h" - Speed = 44100, - Ncmd = 50, /* max volume command words */ -}; +typedef struct Audioprobe Audioprobe; +typedef struct Audiochan Audiochan; -Dirtab -audiodir[] = +struct Audioprobe { - ".", {Qdir, 0, QTDIR}, 0, DMDIR|0555, - "audio", {Qaudio}, 0, 0666, - "volume", {Qvolume}, 0, 0666, - "audiostat",{Qstatus}, 0, 0444, + char *name; + int (*probe)(Audio*); }; -struct Buf -{ - uchar* virt; - ulong phys; - Buf* next; -}; -struct AQueue -{ - Lock; - Buf* first; - Buf* last; -}; -static struct +struct Audiochan { QLock; - Rendez vous; - int buffered; /* number of bytes en route */ - int bufinit; /* boolean if buffers allocated */ - int curcount; /* how much data in current buffer */ - int active; /* boolean dma running */ - int intr; /* boolean an interrupt has happened */ - int amode; /* Aclosed/Aread/Awrite for /audio */ - int rivol[Nvol]; /* right/left input/output volumes */ - int livol[Nvol]; - int rovol[Nvol]; - int lovol[Nvol]; - int major; /* SB16 major version number (sb 4) */ - int minor; /* SB16 minor version number */ - ulong totcount; /* how many bytes processed since open */ - vlong tottime; /* time at which totcount bytes were processed */ - - Buf buf[Nbuf]; /* buffers and queues */ - AQueue empty; - AQueue full; - Buf* current; - Buf* filling; -} audio; - -static struct -{ - char* name; - int flag; - int ilval; /* initial values */ - int irval; -} volumes[] = -{ -[Vaudio] "audio", Fout, 50, 50, -[Vsynth] "synth", Fin|Fout, 0, 0, -[Vcd] "cd", Fin|Fout, 0, 0, -[Vline] "line", Fin|Fout, 0, 0, -[Vmic] "mic", Fin|Fout|Fmono, 0, 0, -[Vspeaker] "speaker", Fout|Fmono, 0, 0, -[Vtreb] "treb", Fout, 50, 50, -[Vbass] "bass", Fout, 50, 50, + Chan *owner; + Audio *adev; -[Vspeed] "speed", Fin|Fout|Fmono, Speed, Speed, - 0 + char *data; + char buf[4000+1]; }; -static struct -{ - Lock; - int reset; /* io ports to the sound blaster */ - int read; - int write; - int wstatus; - int rstatus; - int mixaddr; - int mixdata; - int clri8; - int clri16; - int clri401; - int dma; - - void (*startdma)(void); - void (*intr)(void); -} blaster; - -static void swab(uchar*); - -static char Emajor[] = "soundblaster not responding/wrong version"; -static char Emode[] = "illegal open mode"; -static char Evolume[] = "illegal volume specifier"; - -static int -sbcmd(int val) -{ - int i, s; - - for(i=1<<16; i!=0; i--) { - s = inb(blaster.wstatus); - if((s & 0x80) == 0) { - outb(blaster.write, val); - return 0; - } - } -/* print("#A: sbcmd (%#.2x) timeout\n", val); /**/ - return 1; -} - -static int -sbread(void) -{ - int i, s; - - for(i=1<<16; i!=0; i--) { - s = inb(blaster.rstatus); - if((s & 0x80) != 0) { - return inb(blaster.read); - } - } -/* print("#A: sbread did not respond\n"); /**/ - return -1; -} - -static int -ess1688w(int reg, int val) -{ - if(sbcmd(reg) || sbcmd(val)) - return 1; - - return 0; -} - -static int -ess1688r(int reg) -{ - if(sbcmd(0xC0) || sbcmd(reg)) - return -1; - - return sbread(); -} - -static int -mxcmd(int addr, int val) -{ - - outb(blaster.mixaddr, addr); - outb(blaster.mixdata, val); - return 1; -} - -static int -mxread(int addr) -{ - int s; - - outb(blaster.mixaddr, addr); - s = inb(blaster.mixdata); - return s; -} - -static void -mxcmds(int s, int v) -{ - - if(v > 100) - v = 100; - if(v < 0) - v = 0; - mxcmd(s, (v*255)/100); -} - -static void -mxcmdt(int s, int v) -{ - - if(v > 100) - v = 100; - if(v <= 0) - mxcmd(s, 0); - else - mxcmd(s, 255-100+v); -} - -static void -mxcmdu(int s, int v) -{ - - if(v > 100) - v = 100; - if(v <= 0) - v = 0; - mxcmd(s, 128-50+v); -} +enum { + Qdir = 0, + Qaudio, + Qaudioctl, + Qaudiostat, + Qvolume, +}; -static void -mxvolume(void) -{ - int *left, *right; - int source; +static Dirtab audiodir[] = { + ".", {Qdir, 0, QTDIR}, 0, DMDIR|0555, + "audio", {Qaudio}, 0, 0666, + "audioctl", {Qaudioctl}, 0, 0222, + "audiostat", {Qaudiostat}, 0, 0444, + "volume", {Qvolume}, 0, 0666, +}; - if(audio.amode == Aread){ - left = audio.livol; - right = audio.rivol; - }else{ - left = audio.lovol; - right = audio.rovol; - } - ilock(&blaster); +static int naudioprobes; +static Audioprobe audioprobes[16]; +static Audio *audiodevs; - mxcmd(0x30, 255); /* left master */ - mxcmd(0x31, 255); /* right master */ - mxcmd(0x3f, 0); /* left igain */ - mxcmd(0x40, 0); /* right igain */ - mxcmd(0x41, 0); /* left ogain */ - mxcmd(0x42, 0); /* right ogain */ - - mxcmds(0x32, left[Vaudio]); - mxcmds(0x33, right[Vaudio]); - - mxcmds(0x34, left[Vsynth]); - mxcmds(0x35, right[Vsynth]); - - mxcmds(0x36, left[Vcd]); - mxcmds(0x37, right[Vcd]); - - mxcmds(0x38, left[Vline]); - mxcmds(0x39, right[Vline]); - - mxcmds(0x3a, left[Vmic]); - mxcmds(0x3b, left[Vspeaker]); - - mxcmdu(0x44, left[Vtreb]); - mxcmdu(0x45, right[Vtreb]); - - mxcmdu(0x46, left[Vbass]); - mxcmdu(0x47, right[Vbass]); - - source = 0; - if(left[Vsynth]) - source |= 1<<6; - if(right[Vsynth]) - source |= 1<<5; - if(left[Vaudio]) - source |= 1<<4; - if(right[Vaudio]) - source |= 1<<3; - if(left[Vcd]) - source |= 1<<2; - if(right[Vcd]) - source |= 1<<1; - if(left[Vmic]) - source |= 1<<0; - if(audio.amode == Aread) - mxcmd(0x3c, 0); /* output switch */ - else - mxcmd(0x3c, source); - mxcmd(0x3d, source); /* input left switch */ - mxcmd(0x3e, source); /* input right switch */ - iunlock(&blaster); -} +static char Evolume[] = "illegal volume specifier"; +static char Ebusy[] = "device is busy"; -static Buf* -getbuf(AQueue *q) +void +addaudiocard(char *name, int (*probefn)(Audio *)) { - Buf *b; + Audioprobe *probe; - ilock(q); - b = q->first; - if(b) - q->first = b->next; - iunlock(q); + if(naudioprobes >= nelem(audioprobes)) + return; - return b; + probe = &audioprobes[naudioprobes++]; + probe->name = name; + probe->probe = probefn; } -static void -putbuf(AQueue *q, Buf *b) +static void +audioreset(void) { + int i, ctlrno = 0; + Audio **pp; + Audioprobe *probe; - ilock(q); - b->next = 0; - if(q->first) - q->last->next = b; - else - q->first = b; - q->last = b; - iunlock(q); -} + pp = &audiodevs; + *pp = malloc(sizeof(Audio)); -/* - * move the dma to the next buffer - */ -static void -contindma(void) -{ - Buf *b; + for(i=0; ictlrno = ctlrno; + (*pp)->name = probe->name; + if(probe->probe(*pp)) + break; - b = audio.current; - if(b){ - audio.totcount += Bufsize; - audio.tottime = todget(nil); - } - if(audio.amode == Aread) { - if(b){ - putbuf(&audio.full, b); - audio.buffered += Bufsize; + ctlrno++; + pp = &(*pp)->next; + *pp = malloc(sizeof(Audio)); } - b = getbuf(&audio.empty); - } else { - if(b){ - putbuf(&audio.empty, b); - audio.buffered -= Bufsize; - } - b = getbuf(&audio.full); - } - audio.current = b; - if(b == 0) - goto shutdown; - - if(dmasetup(blaster.dma, b->virt, Bufsize, audio.amode == Aread) >= 0) - return; - print("#A: dmasetup fail\n"); - putbuf(&audio.empty, b); - -shutdown: - dmaend(blaster.dma); - sbcmd(0xd9); /* exit at end of count */ - sbcmd(0xd5); /* pause */ - audio.curcount = 0; - audio.active = 0; -} - -/* - * cause sb to get an interrupt per buffer. - * start first dma - */ -static void -sb16startdma(void) -{ - ulong count; - int speed; - - ilock(&blaster); - dmaend(blaster.dma); - if(audio.amode == Aread) { - sbcmd(0x42); /* input sampling rate */ - speed = audio.livol[Vspeed]; - } else { - sbcmd(0x41); /* output sampling rate */ - speed = audio.lovol[Vspeed]; } - sbcmd(speed>>8); - sbcmd(speed); - count = (Bufsize >> 1) - 1; - if(audio.amode == Aread) - sbcmd(0xbe); /* A/D, autoinit */ - else - sbcmd(0xb6); /* D/A, autoinit */ - sbcmd(0x30); /* stereo, 16 bit */ - sbcmd(count); - sbcmd(count>>8); - - audio.active = 1; - contindma(); - iunlock(&blaster); + free(*pp); + *pp = nil; } -static int -ess1688reset(void) +static Audiochan* +audioclone(Chan *c, Audio *adev) { - int i; + Audiochan *ac; - outb(blaster.reset, 3); - delay(1); /* >3 υs */ - outb(blaster.reset, 0); - delay(1); - - i = sbread(); - if(i != 0xAA) { - print("#A: no response %#.2x\n", i); - return 1; + ac = malloc(sizeof(Audiochan)); + if(ac == nil){ + cclose(c); + return nil; } - if(sbcmd(0xC6)){ /* extended mode */ - print("#A: barf 3\n"); - return 1; - } + c->aux = ac; + ac->owner = c; + ac->adev = adev; + ac->data = nil; - return 0; + return ac; } -static void -ess1688startdma(void) +static Chan* +audioattach(char *spec) { - ulong count; - int speed, x; - - ilock(&blaster); - dmaend(blaster.dma); - - if(audio.amode == Awrite) - ess1688reset(); - if(audio.amode == Aread) - sbcmd(0xD3); /* speaker off */ - - /* - * Set the speed. - */ - if(audio.amode == Aread) - speed = audio.livol[Vspeed]; - else - speed = audio.lovol[Vspeed]; - if(speed < 4000) - speed = 4000; - else if(speed > 48000) - speed = 48000; - - if(speed > 22000) - x = 0x80|(256-(795500+speed/2)/speed); - else - x = 128-(397700+speed/2)/speed; - ess1688w(0xA1, x & 0xFF); - - speed = (speed * 9) / 20; - x = 256 - 7160000 / (speed * 82); - ess1688w(0xA2, x & 0xFF); + static int attached = 0; + Audiochan *ac; + Audio *adev; + Chan *c; + int i; - if(audio.amode == Aread) - ess1688w(0xB8, 0x0E); /* A/D, autoinit */ + if(spec != nil && *spec != '\0') + i = strtol(spec, 0, 10); else - ess1688w(0xB8, 0x04); /* D/A, autoinit */ - x = ess1688r(0xA8) & ~0x03; - ess1688w(0xA8, x|0x01); /* 2 channels */ - ess1688w(0xB9, 2); /* demand mode, 4 bytes per request */ - - if(audio.amode == Awrite) - ess1688w(0xB6, 0); - ess1688w(0xB7, 0x71); - ess1688w(0xB7, 0xBC); - - x = ess1688r(0xB1) & 0x0F; - ess1688w(0xB1, x|0x50); - x = ess1688r(0xB2) & 0x0F; - ess1688w(0xB2, x|0x50); - if(audio.amode == Awrite) - sbcmd(0xD1); /* speaker on */ - - count = -Bufsize; - ess1688w(0xA4, count & 0xFF); - ess1688w(0xA5, (count>>8) & 0xFF); - x = ess1688r(0xB8); - ess1688w(0xB8, x|0x05); - - audio.active = 1; - contindma(); - iunlock(&blaster); -} + i = 0; + for(adev = audiodevs; adev; adev = adev->next) + if(adev->ctlrno == i) + break; + if(adev == nil) + error(Enodev); -/* - * if audio is stopped, - * start it up again. - */ -static void -pokeaudio(void) -{ - if(!audio.active) - blaster.startdma(); -} + c = devattach('A', spec); + c->qid.path = Qdir; -static void -sb16intr(void) -{ - int stat, dummy; + if((ac = audioclone(c, adev)) == nil) + error(Enomem); - stat = mxread(0x82) & 7; /* get irq status */ - if(stat) { - dummy = 0; - if(stat & 2) { - ilock(&blaster); - dummy = inb(blaster.clri16); - contindma(); - iunlock(&blaster); - audio.intr = 1; - wakeup(&audio.vous); - } - if(stat & 1) { - dummy = inb(blaster.clri8); - } - if(stat & 4) { - dummy = inb(blaster.clri401); + i = 1<ctlrno; + if((attached & i) == 0){ + static char *settings[] = { + "speed 44100", + "delay 1764", /* 40 ms */ + "master 100", + "audio 100", + "head 100", + "recgain 0", + }; + + attached |= i; + for(i=0; ivolwrite; i++){ + strcpy(ac->buf, settings[i]); + if(!waserror()){ + adev->volwrite(adev, ac->buf, strlen(ac->buf), 0); + poperror(); + } } - USED(dummy); - } -} - -static void -ess1688intr(void) -{ - int dummy; - - if(audio.active){ - ilock(&blaster); - contindma(); - dummy = inb(blaster.clri8); - iunlock(&blaster); - audio.intr = 1; - wakeup(&audio.vous); - USED(dummy); - } - else - print("#A: unexpected ess1688 interrupt\n"); -} - -void -audiosbintr(void) -{ - /* - * Carrera interrupt interface. - */ - blaster.intr(); -} - -static void -pcaudiosbintr(Ureg*, void*) -{ - /* - * x86 interrupt interface. - */ - blaster.intr(); -} - -void -audiodmaintr(void) -{ -/* print("#A: dma interrupt\n"); /**/ -} - -static int -anybuf(void*) -{ - return audio.intr; -} - -/* - * wait for some output to get - * empty buffers back. - */ -static void -waitaudio(void) -{ - - audio.intr = 0; - pokeaudio(); - tsleep(&audio.vous, anybuf, 0, 10000); - if(audio.intr == 0) { -/* print("#A: audio timeout\n"); /**/ - audio.active = 0; - pokeaudio(); } -} - -static void -sbbufinit(void) -{ - int i; - uchar *p; - p = (uchar*)(((ulong)xalloc((Nbuf+1) * Bufsize) + Bufsize-1) & - ~(Bufsize-1)); - if (p == nil) - panic("sbbufinit: no memory"); - for(i=0; iaux; + adev = ac->adev; + if(c->qid.path == Qaudio){ + mode = openmode(omode); + if(waserror()){ + if(mode == OREAD || mode == ORDWR) + decref(&adev->audioopenr); + nexterror(); + } + if(mode == OREAD || mode == ORDWR) + if(incref(&adev->audioopenr) != 1) + error(Ebusy); -static void -resetlevel(void) -{ - int i; + if(waserror()){ + if(mode == OWRITE || mode == ORDWR) + decref(&adev->audioopenw); + nexterror(); + } + if(mode == OWRITE || mode == ORDWR) + if(incref(&adev->audioopenw) != 1) + error(Ebusy); - 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; + c = devopen(c, omode, audiodir, nelem(audiodir), devgen); + poperror(); + poperror(); + return c; } + return devopen(c, omode, audiodir, nelem(audiodir), devgen); } -static int -ess1688(ISAConf* sbconf) +static long +audioread(Chan *c, void *a, long n, vlong off) { - int i, major, minor; + Audiochan *ac; + Audio *adev; + long (*fn)(Audio *, void *, long, vlong); - /* - * Try for ESS1688. - */ - sbcmd(0xE7); /* get version */ - major = sbread(); - minor = sbread(); - if(major != 0x68 || minor != 0x8B){ - print("#A: model %#.2x %#.2x; not ESS1688 compatible\n", major, minor); - return 1; - } + ac = c->aux; + adev = ac->adev; - ess1688reset(); - - switch(sbconf->irq){ - case 2: - case 9: - i = 0x50|(0<<2); - break; - case 5: - i = 0x50|(1<<2); + fn = nil; + switch((ulong)c->qid.path){ + case Qdir: + audiodir[Qaudio].length = adev->buffered ? adev->buffered(adev) : 0; + return devdirread(c, a, n, audiodir, nelem(audiodir), devgen); + case Qaudio: + fn = adev->read; break; - case 7: - i = 0x50|(2<<2); + case Qaudiostat: + fn = adev->status; break; - case 10: - i = 0x50|(3<<2); + case Qvolume: + fn = adev->volread; break; - default: - print("#A: bad ESS1688 irq %d\n", sbconf->irq); - return 1; } - ess1688w(0xB1, i); + if(fn == nil) + error(Egreg); - switch(sbconf->dma){ - case 0: - i = 0x50|(1<<2); - break; - case 1: - i = 0xF0|(2<<2); - break; - case 3: - i = 0x50|(3<<2); - break; - default: - print("#A: bad ESS1688 dma %lud\n", sbconf->dma); - return 1; + qlock(ac); + if(waserror()){ + qunlock(ac); + nexterror(); } - ess1688w(0xB2, i); - - ess1688reset(); - - blaster.startdma = ess1688startdma; - blaster.intr = ess1688intr; - - return 0; -} - -static void -audioinit(void) -{ - ISAConf sbconf; - int i, x; - static int irq[] = {2,5,7,10}; - - sbconf.port = 0x220; - sbconf.dma = Dma; - sbconf.irq = IrqAUDIO; - if(isaconfig("audio", 0, &sbconf) == 0) - return; - if(sbconf.type == nil || - (cistrcmp(sbconf.type, "sb16") != 0 && - cistrcmp(sbconf.type, "ess1688") != 0)) - return; - switch(sbconf.port){ - case 0x220: - case 0x240: - case 0x260: - case 0x280: + switch((ulong)c->qid.path){ + case Qaudiostat: + case Qvolume: + /* generate the text on first read */ + if(ac->data == nil || off == 0){ + long l; + + ac->data = nil; + l = fn(adev, ac->buf, sizeof(ac->buf)-1, 0); + if(l < 0) + l = 0; + ac->buf[l] = 0; + ac->data = ac->buf; + } + /* then serve all requests from buffer */ + n = readstr(off, a, n, ac->data); break; - default: - print("#A: bad port %#lux\n", sbconf.port); - return; - } - if(ioalloc(sbconf.port, 0x10, 0, "audio") < 0){ - print("#A: cannot ioalloc range %lux+0x10\n", sbconf.port); - return; - } - if(ioalloc(sbconf.port+0x100, 1, 0, "audio.mpu401") < 0){ - iofree(sbconf.port); - print("#A: cannot ioalloc range %lux+0x01\n", sbconf.port+0x100); - return; - } - - switch(sbconf.irq){ - case 2: - case 5: - case 7: - case 9: - case 10: - break; default: - print("#A: bad irq %d\n", sbconf.irq); - iofree(sbconf.port); - iofree(sbconf.port+0x100); - return; - } - - blaster.reset = sbconf.port + 0x6; - blaster.read = sbconf.port + 0xa; - blaster.write = sbconf.port + 0xc; - blaster.wstatus = sbconf.port + 0xc; - blaster.rstatus = sbconf.port + 0xe; - blaster.mixaddr = sbconf.port + 0x4; - blaster.mixdata = sbconf.port + 0x5; - blaster.clri8 = sbconf.port + 0xe; - blaster.clri16 = sbconf.port + 0xf; - blaster.clri401 = sbconf.port + 0x100; - blaster.dma = sbconf.dma; - - blaster.startdma = sb16startdma; - blaster.intr = sb16intr; - - audio.amode = Aclosed; - resetlevel(); - - outb(blaster.reset, 1); - delay(1); /* >3 υs */ - outb(blaster.reset, 0); - delay(1); - - i = sbread(); - if(i != 0xaa) { - print("#A: no response #%.2x\n", i); - iofree(sbconf.port); - iofree(sbconf.port+0x100); - return; + n = fn(adev, a, n, off); } - - sbcmd(0xe1); /* get version */ - audio.major = sbread(); - audio.minor = sbread(); - - if(audio.major != 4) { - if(audio.major != 3 || audio.minor != 1 || ess1688(&sbconf)){ - print("#A: model %#.2x %#.2x; not SB 16 compatible\n", - audio.major, audio.minor); - iofree(sbconf.port); - iofree(sbconf.port+0x100); - return; - } - audio.major = 4; - } - - /* - * initialize the mixer - */ - mxcmd(0x00, 0); /* Reset mixer */ - mxvolume(); - - /* - * Attempt to set IRQ/DMA channels. - * On old ISA boards, these registers are writable. - * On Plug-n-Play boards, these are read-only. - * - * To accomodate both, we write to the registers, - * but then use the contents in case the write is - * disallowed. - */ - mxcmd(0x80, /* irq */ - (sbconf.irq==2)? 1: - (sbconf.irq==5)? 2: - (sbconf.irq==7)? 4: - (sbconf.irq==9)? 1: - (sbconf.irq==10)? 8: - 0); - - mxcmd(0x81, 1<aux; + adev = ac->adev; - switch((ulong)c->qid.path) { - default: - error(Eperm); + fn = nil; + switch((ulong)c->qid.path){ + case Qaudio: + fn = adev->write; + break; + case Qaudioctl: + fn = adev->ctl; break; - - case Qstatus: - if((omode&7) != OREAD) - error(Eperm); case Qvolume: - case Qdir: + fn = adev->volwrite; break; + } + if(fn == nil) + error(Egreg); - case Qaudio: - amode = Awrite; - if((omode&7) == OREAD) - amode = Aread; - qlock(&audio); - if(audio.amode != Aclosed){ - qunlock(&audio); - error(Einuse); - } - if(audio.bufinit == 0) { - audio.bufinit = 1; - sbbufinit(); - } - audio.amode = amode; - setempty(); - audio.curcount = 0; - qunlock(&audio); - mxvolume(); - break; + qlock(ac); + if(waserror()){ + qunlock(ac); + nexterror(); } - c = devopen(c, omode, audiodir, nelem(audiodir), devgen); - c->mode = openmode(omode); - c->flag |= COPEN; - c->offset = 0; + switch((ulong)c->qid.path){ + case Qaudioctl: + case Qvolume: + if(n >= sizeof(ac->buf)) + error(Etoobig); - return c; + /* copy data to audiochan buffer so it can be modified */ + ac->data = nil; + memmove(ac->buf, a, n); + ac->buf[n] = 0; + a = ac->buf; + off = 0; + } + n = fn(adev, a, n, off); + qunlock(ac); + poperror(); + return n; } static void audioclose(Chan *c) { - Buf *b; - - switch((ulong)c->qid.path) { - default: - error(Eperm); - break; + Audiochan *ac; + Audio *adev; - case Qdir: - case Qvolume: - case Qstatus: - break; - - case Qaudio: - if(c->flag & COPEN) { - qlock(&audio); - if(audio.amode == Awrite) { - /* flush out last partial buffer */ - b = audio.filling; - if(b) { - audio.filling = 0; - memset(b->virt+audio.curcount, 0, Bufsize-audio.curcount); - audio.buffered += Bufsize-audio.curcount; - swab(b->virt); - putbuf(&audio.full, b); - } - if(!audio.active && audio.full.first) - pokeaudio(); - } - audio.amode = Aclosed; - if(waserror()){ - qunlock(&audio); - nexterror(); - } - while(audio.active) - waitaudio(); - setempty(); - poperror(); - qunlock(&audio); - } - break; + ac = c->aux; + adev = ac->adev; + if((c->qid.path == Qaudio) && (c->flag & COPEN)){ + if(adev->close){ + if(!waserror()){ + adev->close(adev, c->mode); + poperror(); + } + } + if(c->mode == OWRITE || c->mode == ORDWR) + decref(&adev->audioopenw); + if(c->mode == OREAD || c->mode == ORDWR) + decref(&adev->audioopenr); + } + if(ac->owner == c){ + ac->owner = nil; + c->aux = nil; + free(ac); } } -static long -audioread(Chan *c, void *v, long n, vlong off) +static Walkqid* +audiowalk(Chan *c, Chan *nc, char **name, int nname) { - int liv, riv, lov, rov; - long m, n0; - char buf[300]; - Buf *b; - int j; - ulong offset = off; - char *a; - - n0 = n; - a = v; - switch((ulong)c->qid.path) { - default: - error(Eperm); - break; - - case Qdir: - return devdirread(c, a, n, audiodir, nelem(audiodir), devgen); - - case Qaudio: - if(audio.amode != Aread) - error(Emode); - qlock(&audio); - if(waserror()){ - qunlock(&audio); - nexterror(); - } - while(n > 0) { - b = audio.filling; - if(b == 0) { - b = getbuf(&audio.full); - if(b == 0) { - waitaudio(); - continue; - } - audio.filling = b; - swab(b->virt); - audio.curcount = 0; - } - m = Bufsize-audio.curcount; - if(m > n) - m = n; - memmove(a, b->virt+audio.curcount, m); - - audio.curcount += m; - n -= m; - a += m; - audio.buffered -= m; - if(audio.curcount >= Bufsize) { - audio.filling = 0; - putbuf(&audio.empty, b); - } - } - poperror(); - qunlock(&audio); - break; - - case Qstatus: - buf[0] = 0; - snprint(buf, sizeof(buf), "bufsize %6d buffered %6d offset %10lud time %19lld\n", - Bufsize, audio.buffered, audio.totcount, audio.tottime); - return readstr(offset, a, n, buf); - - case Qvolume: - 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"); + Audiochan *ac; + Audio *adev; + Walkqid *wq; + + ac = c->aux; + adev = ac->adev; + wq = devwalk(c, nc, name, nname, audiodir, nelem(audiodir), devgen); + if(wq && wq->clone){ + if(audioclone(wq->clone, adev) == nil){ + free(wq); + wq = nil; } - return readstr(offset, a, n, buf); } - return n0-n; + return wq; } -static long -audiowrite(Chan *c, void *vp, long n, vlong) +static int +audiostat(Chan *c, uchar *dp, int n) { - long m, n0; - int i, v, left, right, in, out; - Cmdbuf *cb; - Buf *b; - char *a; - - a = vp; - n0 = n; - switch((ulong)c->qid.path) { - default: - error(Eperm); - break; + Audiochan *ac; + Audio *adev; - case Qvolume: - v = Vaudio; - left = 1; - right = 1; - in = 1; - out = 1; - cb = parsecmd(vp, n); - if(waserror()){ - free(cb); - nexterror(); - } - - for(i = 0; i < cb->nf; i++){ - /* - * a number is volume - */ - if(cb->f[i][0] >= '0' && cb->f[i][0] <= '9') { - m = strtoul(cb->f[i], 0, 10); - 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; - mxvolume(); - goto cont0; - } - - for(m=0; volumes[m].name; m++) { - if(strcmp(cb->f[i], volumes[m].name) == 0) { - v = m; - in = 1; - out = 1; - left = 1; - right = 1; - goto cont0; - } - } + ac = c->aux; + adev = ac->adev; + if((ulong)c->qid.path == Qaudio) + audiodir[Qaudio].length = adev->buffered ? adev->buffered(adev) : 0; + return devstat(c, dp, n, audiodir, nelem(audiodir), devgen); +} - if(strcmp(cb->f[i], "reset") == 0) { - resetlevel(); - mxvolume(); - goto cont0; - } - if(strcmp(cb->f[i], "in") == 0) { - in = 1; - out = 0; - goto cont0; - } - if(strcmp(cb->f[i], "out") == 0) { - in = 0; - out = 1; - goto cont0; - } - if(strcmp(cb->f[i], "left") == 0) { - left = 1; - right = 0; - goto cont0; - } - if(strcmp(cb->f[i], "right") == 0) { - left = 0; - right = 1; - goto cont0; +/* + * audioread() made sure the buffer is big enougth so a full volume + * table can be serialized in one pass. + */ +long +genaudiovolread(Audio *adev, void *a, long n, vlong, + Volume *vol, int (*volget)(Audio *, int, int *), ulong caps) +{ + int i, j, r, v[2]; + char *p, *e; + + p = a; + e = p + n; + for(i = 0; vol[i].name != 0; ++i){ + if(vol[i].cap && (vol[i].cap & caps) == 0) + continue; + v[0] = 0; + v[1] = 0; + if((*volget)(adev, i, v) != 0) + continue; + if(vol[i].type == Absolute) + p += snprint(p, e - p, "%s %d\n", vol[i].name, v[0]); + else { + r = abs(vol[i].range); + if(r == 0) + continue; + for(j=0; j<2; j++){ + if(v[j] < 0) + v[j] = 0; + if(v[j] > r) + v[j] = r; + if(vol[i].range < 0) + v[j] = r - v[j]; + v[j] = (v[j]*100)/r; + } + switch(vol[i].type){ + case Left: + p += snprint(p, e - p, "%s %d\n", vol[i].name, v[0]); + break; + case Right: + p += snprint(p, e - p, "%s %d\n", vol[i].name, v[1]); + break; + case Stereo: + p += snprint(p, e - p, "%s %d %d\n", vol[i].name, v[0], v[1]); + break; } - error(Evolume); - break; - cont0:; } - free(cb); - poperror(); - break; + } - case Qaudio: - if(audio.amode != Awrite) - error(Emode); - qlock(&audio); - if(waserror()){ - qunlock(&audio); - nexterror(); - } - while(n > 0) { - b = audio.filling; - if(b == 0) { - b = getbuf(&audio.empty); - if(b == 0) { - waitaudio(); - continue; - } - audio.filling = b; - audio.curcount = 0; - } + return p - (char*)a; +} - m = Bufsize-audio.curcount; - if(m > n) - m = n; - memmove(b->virt+audio.curcount, a, m); - - audio.curcount += m; - n -= m; - a += m; - audio.buffered += m; - if(audio.curcount >= Bufsize) { - audio.filling = 0; - swab(b->virt); - putbuf(&audio.full, b); - pokeaudio(); +/* + * genaudiovolwrite modifies the buffer that gets passed to it. this + * is ok as long as it is called from inside Audio.volwrite() because + * audiowrite() copies the data to Audiochan.buf[] and inserts a + * terminating \0 byte before calling Audio.volwrite(). + */ +long +genaudiovolwrite(Audio *adev, void *a, long n, vlong, + Volume *vol, int (*volset)(Audio *, int, int *), ulong caps) +{ + int ntok, i, j, r, v[2]; + char *p, *e, *x, *tok[4]; + + p = a; + e = p + n; + + for(;p < e; p = x){ + if(x = strchr(p, '\n')) + *x++ = 0; + else + x = e; + ntok = tokenize(p, tok, 4); + if(ntok <= 0) + continue; + if(ntok == 1){ + tok[1] = tok[0]; + tok[0] = "master"; + ntok = 2; + } + for(i = 0; vol[i].name != 0; i++){ + if(vol[i].cap && (vol[i].cap & caps) == 0) + continue; + if(cistrcmp(vol[i].name, tok[0])) + continue; + + if((ntok>2) && (!cistrcmp(tok[1], "out") || !cistrcmp(tok[1], "in"))) + memmove(tok+1, tok+2, --ntok); + + v[0] = 0; + v[1] = 0; + if(ntok > 1) + v[0] = v[1] = atoi(tok[1]); + if(ntok > 2) + v[1] = atoi(tok[2]); + if(vol[i].type == Absolute) + (*volset)(adev, i, v); + else { + r = abs(vol[i].range); + for(j=0; j<2; j++){ + v[j] = (50+(v[j]*r))/100; + if(v[j] < 0) + v[j] = 0; + if(v[j] > r) + v[j] = r; + if(vol[i].range < 0) + v[j] = r - v[j]; + } + (*volset)(adev, i, v); } + break; } - poperror(); - qunlock(&audio); - break; + if(vol[i].name == nil) + error(Evolume); } - return n0 - n; -} - -static void -swab(uchar *a) -{ - ulong *p, *ep, b; - if(!SBswab){ - USED(a); - return; - } - p = (ulong*)a; - ep = p + (Bufsize>>2); - while(p < ep) { - b = *p; - b = (b>>24) | (b<<24) | - ((b&0xff0000) >> 8) | - ((b&0x00ff00) << 8); - *p++ = b; - } + return n; } Dev audiodevtab = { 'A', "audio", - - devreset, - audioinit, + audioreset, + devinit, devshutdown, audioattach, audiowalk,