#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/error.h"
#include "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;
uchar *mmreg;
uchar *mmmix;
int ismmio;
Ring inring, micring, outring;
int sis7012;
/* for probe */
Audio *adev;
Pcidev *pcidev;
Ctlr *next;
};
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;
}
static uchar
csr8r(Ctlr *c, int r){
if(c->ismmio)
return *(uchar*)(c->mmreg+r);
return inb(c->port+r);
}
static ushort
csr16r(Ctlr *c, int r){
if(c->ismmio)
return *(ushort*)(c->mmreg+r);
return ins(c->port+r);
}
static ulong
csr32r(Ctlr *c, int r){
if(c->ismmio)
return *(ulong*)(c->mmreg+r);
return inl(c->port+r);
}
static void
csr8w(Ctlr *c, int r, uchar v){
if(c->ismmio)
*(uchar*)(c->mmreg+r) = v;
else
outb(c->port+r, (int)v);
}
static void
csr16w(Ctlr *c, int r, ushort v){
if(c->ismmio)
*(ushort*)(c->mmreg+r) = v;
else
outs(c->port+r, v);
}
static void
csr32w(Ctlr *c, int r, ulong v){
if(c->ismmio)
*(ulong*)(c->mmreg+r) = v;
else
outl(c->port+r, v);
}
/* 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)
return;
microdelay(10);
}
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;
if(ctlr->ismmio)
*(ushort*)(ctlr->mmmix+port) = val;
else
outs(ctlr->mixport+port, val);
}
static ushort
ac97mixr(Audio *adev, int port)
{
Ctlr *ctlr;
ac97waitcodec(adev);
ctlr = adev->ctlr;
if(ctlr->ismmio)
return *(ushort*)(ctlr->mmmix+port);
return ins(ctlr->mixport+port);
}
static void
ac97interrupt(Ureg *, void *arg)
{
Audio *adev;
Ctlr *ctlr;
ulong stat;
adev = arg;
ctlr = adev->ctlr;
if(ctlr == nil || ctlr->adev != adev)
return;
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
ac97reset1(Audio *adev, Ctlr *ctlr)
{
int i, irq, tbdf;
ulong ctl, stat = 0;
Pcidev *p;
p = ctlr->pcidev;
/* ICH4 through ICH7 may use memory-type base address registers */
if(p->vid == 0x8086 &&
(p->did == 0x24c5 || p->did == 0x24d5 || p->did == 0x266e || p->did == 0x27de) &&
(p->mem[2].bar != 0 && p->mem[3].bar != 0) &&
((p->mem[2].bar & 1) == 0 && (p->mem[3].bar & 1) == 0)){
ctlr->mmmix = vmap(p->mem[2].bar & ~0xF, p->mem[2].size);
if(ctlr->mmmix == nil){
print("ac97: vmap failed for mmmix %lux\n", p->mem[2].bar & ~0xF);
return -1;
}
ctlr->mmreg = vmap(p->mem[3].bar & ~0xF, p->mem[3].size);
if(ctlr->mmreg == nil){
print("ac97: vmap failed for mmreg %lux\n", p->mem[3].bar & ~0xF);
vunmap(ctlr->mmmix, p->mem[2].size);
return -1;
}
ctlr->ismmio = 1;
}else{
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;
}
if(p->vid == 0x1039 && p->did == 0x7012){
ctlr->sis7012 = 1; /* i/o bars swapped? */
}
ctlr->port = p->mem[1].bar & ~3;
if(ioalloc(ctlr->port, p->mem[1].size, 0, "ac97") < 0){
print("ac97: ioalloc failed for port 0x%04lux\n", ctlr->port);
return -1;
}
ctlr->mixport = p->mem[0].bar & ~3;
if(ioalloc(ctlr->mixport, p->mem[0].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;
adev->ctlr = ctlr;
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" : "");
*/
USED(stat);
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;
}
static int
ac97reset(Audio *adev)
{
static Ctlr *cards = nil;
Ctlr *ctlr;
Pcidev *p;
int i;
/* make a list of all ac97 cards if not already done */
if(cards == nil){
p = nil;
while((p = ac97match(p)) != nil){
ctlr = mallocz(sizeof(Ctlr), 1);
if(ctlr == nil){
print("ac97: can't allocate memory\n");
break;
}
ctlr->pcidev = p;
ctlr->next = cards;
cards = ctlr;
}
}
/* pick a card from the list */
for(ctlr = cards; ctlr; ctlr = ctlr->next){
if(ctlr->adev == nil && ctlr->pcidev != nil){
ctlr->adev = adev;
p = ctlr->pcidev;
if(pcigetpms(p) > 0){
pcisetpms(p, 0);
for(i = 0; i < 6; i++)
pcicfgw32(p, PciBAR0+i*4, p->mem[i].bar);
pcicfgw8(p, PciINTL, p->intl);
pcicfgw8(p, PciLTR, p->ltr);
pcicfgw8(p, PciCLS, p->cls);
pcicfgw16(p, PciPCR, p->pcr);
}
if(ac97reset1(adev, ctlr) == 0)
return 0;
pciclrbme(p);
ctlr->pcidev = nil;
ctlr->adev = nil;
}
}
return -1;
}
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;
}
void
audioac97link(void)
{
addaudiocard("ac97", ac97reset);
}
|