Plan 9 from Bell Labs’s /usr/web/sources/contrib/yk/pc-devaudio-ac97/audioac97.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#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);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.