plan 9 kernel history: overview | file list | diff list

1995/0119/port/devaudio.c (diff list | history)

port/devaudio.c on 1995/0119
1995/0119    
/* 
 *	SB 16 driver 
 */ 
#include	"u.h" 
#include	"../port/lib.h" 
#include	"mem.h" 
#include	"dat.h" 
#include	"fns.h" 
#include	"../port/error.h" 
#include	"devtab.h" 
#include	"io.h" 
#include	"audio.h" 
 
 
#define	nelem(x)	(sizeof (x)/sizeof(x[0])) 
#define	NPORT		(sizeof audiodir/sizeof(Dirtab)) 
 
typedef struct	Buf	Buf; 
typedef struct	Level	Level; 
 
enum 
{ 
	Qdir		= 0, 
	Qaudio, 
	Qvolume, 
 
	Fmono		= 1, 
 
	Aread		= 0, 
	Awrite, 
 
	Speed		= 44100, 
	Ncmd		= 50,		/* max volume command words */ 
}; 
 
Dirtab 
audiodir[] = 
{ 
	"audio",	{Qaudio},		0,	0666, 
	"volume",	{Qvolume},		0,	0666, 
}; 
 
struct	Level 
{ 
	int	master;		/* mixer output volume control */ 
	int	ogain;		/* extra gain after master */ 
	int	pcm;		/* mixer volume on D/A (voice) */ 
	int	synth;		/* mixer volume on synthesizer (MIDI) */ 
	int	cd;		/* mixer volume on cd */ 
	int	line;		/* mixer volume on line */ 
	int	igain;		/* mixer volume to A/D */ 
	int	treb;		/* treb control */ 
	int	bass;		/* bass control */ 
	int	iswitch;	/* input on/off switches */ 
}; 
struct	Buf 
{ 
	uchar*	virt; 
	ulong	phys; 
	Buf*	next; 
}; 
struct	Queue 
{ 
	Lock; 
	Buf*	first; 
	Buf*	last; 
}; 
static	struct 
{ 
	Rendez	vous; 
	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;		/* Aread/Awrite for /audio */ 
	Level	left;		/* all of left volumes */ 
	Level	right;		/* all of right volumes */ 
	int	mic;		/* mono level */ 
	int	speaker;	/* mono level */ 
	int	oswitch;	/* output on/off switches */ 
	int	speed;		/* pcm sample rate, doesnt change w stereo */ 
	int	major;		/* SB16 major version number (sb 4) */ 
	int	minor;		/* SB16 minor version number */ 
	char	place[20];	/* static place to decode words */ 
 
	Buf	buf[Nbuf];	/* buffers and queues */ 
	Queue	empty; 
	Queue	full; 
	Buf*	current; 
	Buf*	filling; 
} audio; 
 
static	struct 
{ 
	char*	name; 
	int*	ptr1; 
	int*	ptr2; 
	int	flag; 
	int	ilval; 
	int	irval; 
} volumes[] = 
{ 
	"master",	&audio.left.master,	&audio.right.master,	0,	50,	50, 
	"ogain",	&audio.left.ogain,	&audio.right.ogain,	0, 	0,	0, 
	"igain",	&audio.left.igain,	&audio.right.igain,	0, 	0,	0, 
 
	"treb",		&audio.left.treb,	&audio.right.treb,	0, 	50,	50, 
	"bass",		&audio.left.bass,	&audio.right.bass,	0, 	50,	50, 
 
	"pcm",		&audio.left.pcm,	&audio.right.pcm,	0, 	90,	90, 
	"synth",	&audio.left.synth,	&audio.right.synth,	0,	90,	90, 
	"cd",		&audio.left.cd,		&audio.right.cd,	0,	81,	81, 
	"line",		&audio.left.line,	&audio.right.line,	0,	65,	65, 
 
	"mic",		&audio.mic,		&audio.mic,		Fmono,	0,	0, 
	"speaker",	&audio.speaker,		&audio.speaker,		Fmono,	0,	0, 
	"oswitch",	&audio.oswitch,		&audio.oswitch,		Fmono,	31,	31, 
	"iswitch",	&audio.left.iswitch,	&audio.right.iswitch,	0,	85,	43, 
 
	"speed",	&audio.speed,		&audio.speed,		Fmono,	Speed,	Speed, 
	0 
}; 
 
static	void	swab(uchar*); 
 
static	char	Emajor[]	= "SB16 version too old"; 
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(PORT_WSTATUS); 
		if((s & 0x80) == 0) { 
			outb(PORT_WRITE, val); 
			return 0; 
		} 
	} 
/*	print("SB16 sbcmd (#%.2x) timeout\n", val);	/**/ 
	return 1; 
} 
 
static	int 
sbread(void) 
{ 
	int i, s; 
 
	for(i=1<<16; i!=0; i--) { 
		s = inb(PORT_RSTATUS); 
		if((s & 0x80) != 0) { 
			return inb(PORT_READ); 
		} 
	} 
/*	print("SB16 sbread did not respond\n");	/**/ 
	return 0xbb; 
} 
 
static	int 
mxcmd(int addr, int val) 
{ 
 
	outb(PORT_MIXER_ADDR, addr); 
	outb(PORT_MIXER_DATA, val); 
	return 1; 
} 
 
static	int 
mxread(int addr) 
{ 
	int s; 
 
	outb(PORT_MIXER_ADDR, addr); 
	s = inb(PORT_MIXER_DATA); 
	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); 
} 
 
static	void 
mxvolume(void) 
{ 
	ulong sp; 
 
	sp = splhi(); 
	mxcmds(0x30, audio.left.master); 
	mxcmds(0x31, audio.right.master); 
 
	mxcmdt(0x32, audio.left.pcm); 
	mxcmdt(0x33, audio.right.pcm); 
 
	mxcmdt(0x34, audio.left.synth); 
	mxcmdt(0x35, audio.right.synth); 
 
	mxcmdt(0x36, audio.left.cd); 
	mxcmdt(0x37, audio.right.cd); 
 
	mxcmdt(0x38, audio.left.line); 
	mxcmdt(0x39, audio.right.line); 
 
	mxcmdt(0x3a, audio.mic); 
	mxcmdt(0x3b, audio.speaker); 
 
	mxcmds(0x3f, audio.left.igain); 
	mxcmds(0x40, audio.right.igain); 
	mxcmds(0x41, audio.left.ogain); 
	mxcmds(0x42, audio.right.ogain); 
 
	mxcmdu(0x44, audio.left.treb); 
	mxcmdu(0x45, audio.right.treb); 
 
	mxcmdu(0x46, audio.left.bass); 
	mxcmdu(0x47, audio.right.bass); 
 
	mxcmd(0x3c, audio.oswitch); 
	mxcmd(0x3d, audio.left.iswitch); 
	mxcmd(0x3e, audio.right.iswitch); 
	splx(sp); 
} 
 
static	Buf* 
getbuf(Queue *q) 
{ 
	Buf *b; 
 
	ilock(q); 
	b = q->first; 
	if(b) 
		q->first = b->next; 
	iunlock(q); 
 
	return b; 
} 
 
static	void 
putbuf(Queue *q, Buf *b) 
{ 
 
	ilock(q); 
	b->next = 0; 
	if(q->first) 
		q->last->next = b; 
	else 
		q->first = b; 
	q->last = b; 
	iunlock(q); 
} 
 
/* 
 * move the dma to the next buffer 
 */ 
static	void 
contindma(void) 
{ 
	Buf *b; 
	ulong count, addr; 
 
	if(!audio.active) 
		goto shutdown; 
 
	b = audio.current; 
	if(audio.amode == Aread) { 
		if(b)	/* shouldnt happen */ 
			putbuf(&audio.full, b); 
		b = getbuf(&audio.empty); 
	} else { 
		if(b)	/* shouldnt happen */ 
			putbuf(&audio.empty, b); 
		b = getbuf(&audio.full); 
	} 
	audio.current = b; 
	if(b == 0) 
		goto shutdown; 
 
	addr = b->phys; 
	count = ((Bufsize) >> 1) - 1; 
 
/*	outb(DMA2_WRMASK, 4|(Dma&3));	/* disable dma */ 
	outb(DMA6_LPAGE, (addr>>16) & 0xfe); 
	outb(DMA2_CLRBP, 0);		/* clear ff */ 
	outb(DMA6_ADDRESS, addr>>1); 
	outb(DMA6_ADDRESS, addr>>9); 
 
	outb(DMA2_CLRBP, 0);		/* clear ff */ 
	outb(DMA6_COUNT, count); 
	outb(DMA6_COUNT, count>>8); 
	outb(DMA2_WRMASK, (Dma&3));		/* enable dma */ 
 
	return; 
 
shutdown: 
	outb(DMA2_WRMASK, 4|(Dma&3));	/* disable 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 
startdma(void) 
{ 
	ulong count; 
 
	outb(DMA2_WRMASK, 4|(Dma&3));	/* disable dma */ 
	if(audio.amode == Aread) { 
		outb(DMA2_WRMODE, 0x44 | (Dma&3));	/* set mode */ 
		sbcmd(0x42);			/* input sampling rate */ 
	} else { 
		outb(DMA2_WRMODE, 0x48 | (Dma&3));	/* set mode */ 
		sbcmd(0x41);			/* output sampling rate */ 
	} 
	sbcmd(audio.speed>>8); 
	sbcmd(audio.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(); 
} 
 
/* 
 * if audio is stopped, 
 * start it up again. 
 */ 
static	void 
pokeaudio(void) 
{ 
	ulong sp; 
 
	sp = splhi(); 
	if(!audio.active) 
		startdma(); 
	splx(sp); 
} 
 
void 
audiosbintr(void) 
{ 
	int stat, dummy; 
 
	stat = mxread(0x82) & 7;		/* get irq status */ 
	if(stat) { 
		dummy = 0; 
		if(stat & 2) { 
			dummy = inb(PORT_CLRI16); 
			contindma(); 
			audio.intr = 1; 
			wakeup(&audio.vous); 
		} 
		if(stat & 1) { 
			dummy = inb(PORT_CLRI8); 
		} 
		if(stat & 4) { 
			dummy = inb(PORT_CLRI401); 
		} 
		USED(dummy); 
	} 
} 
 
void 
audiodmaintr(void) 
{ 
/*	print("sb16 dma interrupt\n");	/**/ 
} 
 
static int 
anybuf(void *p) 
{ 
	USED(p); 
	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, 10*1000); 
	if(audio.intr == 0) { 
/*		print("audio timeout\n");	/**/ 
		audio.active = 0; 
		pokeaudio(); 
	} 
} 
 
static void 
sbbufinit(void) 
{ 
	int i; 
	void *p; 
 
	for(i=0; i<Nbuf; i++) { 
		p = xspanalloc(Bufsize, CACHELINESZ, 64*1024); 
		dcflush(p, Bufsize); 
		audio.buf[i].virt = UNCACHED(uchar, p); 
		audio.buf[i].phys = (ulong)PADDR(p); 
	} 
} 
 
static	void 
setempty(void) 
{ 
	int i; 
 
	audio.empty.first = 0; 
	audio.empty.last = 0; 
	audio.full.first = 0; 
	audio.full.last = 0; 
	audio.current = 0; 
	audio.filling = 0; 
	for(i=0; i<Nbuf; i++) 
		putbuf(&audio.empty, &audio.buf[i]); 
} 
 
void 
audioreset(void) 
{ 
} 
 
static	void 
resetlevel(void) 
{ 
	int i; 
 
	for(i=0; volumes[i].name; i++) { 
		*volumes[i].ptr1 = volumes[i].ilval; 
		*volumes[i].ptr2 = volumes[i].irval; 
	} 
} 
 
void 
audioinit(void) 
{ 
	int i; 
 
	seteisadma(Dma, audiodmaintr); 
 
	resetlevel(); 
 
	outb(PORT_RESET, 1); 
	delay(1);			/* >3 υs */ 
	outb(PORT_RESET, 0); 
	delay(1); 
 
	i = sbread(); 
	if(i != 0xaa) { 
		print("sound blaster didnt respond #%.2x\n", i); 
		return; 
	} 
 
	sbcmd(0xe1);			/* get version */ 
	audio.major = sbread(); 
	audio.minor = sbread(); 
 
	if(audio.major != 4) { 
		print("bad soundblaster model #%.2x #.2x\n", audio.major, audio.minor); 
		return; 
	} 
	/* 
	 * initialize the mixer 
	 */ 
	mxcmd(0x00, 0);			/* Reset mixer */ 
	mxvolume(); 
 
	/* 
	 * set up irq/dma chans 
	 */ 
	mxcmd(0x80,			/* irq */ 
		(Irq==2)? 1: 
		(Irq==5)? 2: 
		(Irq==7)? 4: 
		(Irq==10)? 8: 
		0); 
	mxcmd(0x81, 1<<Dma);	/* dma */ 
} 
 
Chan* 
audioattach(char *param) 
{ 
	return devattach('A', param); 
} 
 
Chan* 
audioclone(Chan *c, Chan *nc) 
{ 
	return devclone(c, nc); 
} 
 
int 
audiowalk(Chan *c, char *name) 
{ 
	return devwalk(c, name, audiodir, NPORT, devgen); 
} 
 
void 
audiostat(Chan *c, char *db) 
{ 
	devstat(c, db, audiodir, NPORT, devgen); 
} 
 
Chan* 
audioopen(Chan *c, int omode) 
{ 
 
	if(audio.major != 4) 
		error(Emajor); 
 
	switch(c->qid.path & ~CHDIR) { 
	default: 
		error(Eperm); 
		break; 
 
	case Qvolume: 
	case Qdir: 
		break; 
 
	case Qaudio: 
		if(audio.bufinit == 0) { 
			audio.bufinit = 1; 
			sbbufinit(); 
		} 
 
		audio.amode = Awrite; 
		if((omode&7) == OREAD) 
			audio.amode = Aread; 
 
		setempty(); 
		audio.curcount = 0; 
		break; 
	} 
	c = devopen(c, omode, audiodir, NPORT, devgen); 
	c->mode = openmode(omode); 
	c->flag |= COPEN; 
	c->offset = 0; 
 
	return c; 
} 
 
void 
audiocreate(Chan *c, char *name, int omode, ulong perm) 
{ 
	USED(c); 
	USED(name); 
	USED(omode); 
	USED(perm); 
 
	error(Eperm); 
} 
 
void 
audioclose(Chan *c) 
{ 
 
	switch(c->qid.path & ~CHDIR) { 
	default: 
		error(Eperm); 
		break; 
 
	case Qdir: 
	case Qvolume: 
		break; 
 
	case Qaudio: 
		if(c->flag & COPEN) { 
			while(audio.active) 
				waitaudio(); 
			setempty(); 
		} 
		break; 
	} 
} 
 
long 
audioread(Chan *c, char *a, long n, ulong offset) 
{ 
	long m, o, n0, bn; 
	char buf[256]; 
	Buf *b; 
	int j; 
 
	n0 = n; 
	switch(c->qid.path & ~CHDIR) { 
	default: 
		error(Eperm); 
		break; 
 
	case Qdir: 
		return devdirread(c, a, n, audiodir, NPORT, devgen); 
 
	case Qaudio: 
		if(audio.amode != Aread) 
			error(Emode); 
		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; 
			if(audio.curcount >= Bufsize) { 
				audio.filling = 0; 
				putbuf(&audio.empty, b); 
			} 
		} 
		break; 
 
	case Qvolume: 
		j = 0; 
		for(m=0; volumes[m].name; m++) { 
			o = *volumes[m].ptr1; 
			if(volumes[m].flag & Fmono) 
				j += snprint(buf+j, sizeof(buf)-j, "%s %d\n", volumes[m].name, o); 
			else { 
				bn = *volumes[m].ptr2; 
				if(o == bn) 
					j += snprint(buf+j, sizeof(buf)-j, "%s both %d\n", volumes[m].name, o); 
				else 
					j += snprint(buf+j, sizeof(buf)-j, "%s left %d right %d\n", 
						volumes[m].name, o, bn); 
			} 
		} 
 
		return readstr(offset, a, n, buf); 
	} 
	return n0-n; 
} 
 
Block* 
audiobread(Chan *c, long n, ulong offset) 
{ 
	return devbread(c, n, offset); 
} 
 
long 
audiowrite(Chan *c, char *a, long n, ulong offset) 
{ 
	long m, n0; 
	int i, nf, v, left, right; 
	char buf[255], *field[Ncmd]; 
	Buf *b; 
 
	USED(offset); 
 
	n0 = n; 
	switch(c->qid.path & ~CHDIR) { 
	default: 
		error(Eperm); 
		break; 
 
	case Qvolume: 
		v = 0; 
		left = 1; 
		right = 1; 
		if(n > sizeof(buf)-1) 
			n = sizeof(buf)-1; 
		memmove(buf, a, n); 
		buf[n] = '\0'; 
 
		nf = getfields(buf, field, Ncmd, " \t\n"); 
		for(i = 0; i < nf; i++){ 
			/* 
			 * a number is volume 
			 */ 
			if(field[i][0] >= '0' && field[i][0] <= '9') { 
				m = strtoul(field[i], 0, 10); 
				if(left) 
					*volumes[v].ptr1 = m; 
				if(right) 
					*volumes[v].ptr2 = m; 
				mxvolume(); 
				goto cont0; 
			} 
 
			for(m=0; volumes[m].name; m++) { 
				if(strcmp(field[i], volumes[m].name) == 0) { 
					v = m; 
					goto cont0; 
				} 
			} 
 
			if(strcmp(field[i], "reset") == 0) { 
				resetlevel(); 
				mxvolume(); 
				goto cont0; 
			} 
			if(strcmp(field[i], "left") == 0) { 
				left = 1; 
				right = 0; 
				goto cont0; 
			} 
			if(strcmp(field[i], "right") == 0) { 
				left = 0; 
				right = 1; 
				goto cont0; 
			} 
			if(strcmp(field[i], "both") == 0) { 
				left = 1; 
				right = 1; 
				goto cont0; 
			} 
			error(Evolume); 
			break; 
		cont0:; 
		} 
		break; 
 
	case Qaudio: 
		if(audio.amode != Awrite) 
			error(Emode); 
		while(n > 0) { 
			b = audio.filling; 
			if(b == 0) { 
				b = getbuf(&audio.empty); 
				if(b == 0) { 
					waitaudio(); 
					continue; 
				} 
				audio.filling = b; 
				audio.curcount = 0; 
			} 
 
			m = Bufsize-audio.curcount; 
			if(m > n) 
				m = n; 
			memmove(b->virt+audio.curcount, a, m); 
 
			audio.curcount += m; 
			n -= m; 
			a += m; 
			if(audio.curcount >= Bufsize) { 
				audio.filling = 0; 
				swab(b->virt); 
				putbuf(&audio.full, b); 
			} 
		} 
		break; 
	} 
	return n0 - n; 
} 
 
long 
audiobwrite(Chan *c, Block *bp, ulong offset) 
{ 
	return devbwrite(c, bp, offset); 
} 
 
void 
audioremove(Chan *c) 
{ 
	USED(c); 
 
	error(Eperm); 
} 
 
void 
audiowstat(Chan *c, char *dp) 
{ 
	USED(c); 
	USED(dp); 
 
	error(Eperm); 
} 
 
static	void 
swab(uchar *a) 
{ 
	ulong *p, *ep, b; 
 
	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; 
	} 
} 


source code copyright © 1990-2005 Lucent Technologies; see license
Plan 9 distribution
comments to russ cox (rsc@swtch.com)