plan9fox/sys/src/9/pc/audioac97.c
cinap_lenrek 4f85115526 kernel: massive pci code rewrite
The new pci code is moved to port/pci.[hc] and shared by
all ports.

Each port has its own PCI controller implementation,
providing the pcicfgrw*() functions for low level pci
config space access. The locking for pcicfgrw*() is now
done by the caller (only port/pci.c).

Device drivers now need to include "../port/pci.h" in
addition to "io.h".

The new code now checks bridge windows and membars,
while enumerating the bus, giving the pc driver a chance
to re-assign them. This is needed because some UEFI
implementations fail to assign the bars for some devices,
so we need to do it outselfs. (See pcireservemem()).

While working on this, it was discovered that the pci
code assimed the smallest I/O bar size is 16 (pcibarsize()),
which is wrong. I/O bars can be as small as 4 bytes.
Bit 1 in an I/O bar is also reserved and should be masked off,
making the port mask: port = bar & ~3;
2020-09-13 20:33:17 +02:00

677 lines
14 KiB
C

#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/pci.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;
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 %llux\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 %llux\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%04llux 0x%04llux\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" : "");
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;
/* 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;
pcienable(ctlr->pcidev);
if(ac97reset1(adev, ctlr) == 0)
return 0;
pcidisable(ctlr->pcidev);
ctlr->pcidev = nil;
ctlr->adev = nil;
}
}
return -1;
}
void
audioac97link(void)
{
addaudiocard("ac97", ac97reset);
}