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

2001/0502/bitsy/devuda1341.c (diff list | history)

bitsy/devuda1341.c on 2000/1103
2000/1103    
/* 
 *	SAC/UDA 1341 Audio driver for the Bitsy 
 * 
 *	The Philips UDA 1341 sound chip is accessed through the Serial Audio 
 *	Controller (SAC) of the StrongARM SA-1110.  This is much more a SAC 
 *	controller than a UDA controller, but we have a devsac.c already. 
 * 
 *	The code morphs Nicolas Pitre's <nico@cam.org> Linux controller 
 *	and Ken's Soundblaster controller. 
 * 
 *	The interface should be identical to that of devaudio.c 
 */ 
#include	"u.h" 
#include	"../port/lib.h" 
#include	"mem.h" 
#include	"dat.h" 
#include	"fns.h" 
#include	"../port/error.h" 
#include	"io.h" 
2000/1110    
#include	"sa1110dma.h" 
2000/1103    
 
2001/0502    
static int debug = 0; 
2000/1113    
 
2000/1103    
/* 
 * GPIO based L3 bus support. 
 * 
 * This provides control of Philips L3 type devices.  
 * GPIO lines are used for clock, data and mode pins. 
 * 
 * Note: The L3 pins are shared with I2C devices. This should not present 
 * any problems as long as an I2C start sequence is not generated. This is 
 * defined as a 1->0 transition on the data lines when the clock is high. 
 * It is critical this code only allow data transitions when the clock 
 * is low. This is always legal in L3. 
 * 
 * The IIC interface requires the clock and data pin to be LOW when idle. We 
 * must make sure we leave them in this state. 
 * 
 * It appears the read data is generated on the falling edge of the clock 
 * and should be held stable during the clock high time. 
 */ 
 
/*  
2000/1117    
 * L3 setup and hold times (expressed in µs) 
2000/1103    
 */ 
2000/1118    
enum { 
2001/0428    
	L3_AcquireTime =		1, 
	L3_ReleaseTime =		1, 
	L3_DataSetupTime =	(190+999)/1000,	/* 190 ns */ 
	L3_DataHoldTime =		( 30+999)/1000, 
	L3_ModeSetupTime =	(190+999)/1000, 
	L3_ModeHoldTime =	(190+999)/1000, 
	L3_ClockHighTime =	(100+999)/1000, 
	L3_ClockLowTime =		(100+999)/1000, 
	L3_HaltTime =			(190+999)/1000, 
2000/1118    
}; 
2000/1103    
 
/* UDA 1341 Registers */ 
enum { 
	/* Status0 register */ 
	UdaStatusDC		= 0,	/* 1 bit */ 
	UdaStatusIF		= 1,	/* 3 bits */ 
	UdaStatusSC		= 4,	/* 2 bits */ 
2001/0421    
	UdaStatusRST		= 6,	/* 1 bit */ 
2000/1103    
}; 
 
enum { 
	/* Status1 register */ 
2001/0421    
	UdaStatusPC	= 0,	/* 2 bits */ 
	UdaStatusDS	= 2,	/* 1 bit */ 
2000/1103    
	UdaStatusPDA	= 3,	/* 1 bit */ 
	UdaStatusPAD	= 4,	/* 1 bit */ 
	UdaStatusIGS	= 5,	/* 1 bit */ 
	UdaStatusOGS	= 6,	/* 1 bit */ 
}; 
 
/* 
 * UDA1341 L3 address and command types 
 */ 
 
2000/1118    
enum { 
	UDA1341_DATA0 =	0, 
	UDA1341_DATA1, 
	UDA1341_STATUS, 
	UDA1341_L3Addr = 0x14, 
}; 
 
2000/1103    
typedef struct	AQueue	AQueue; 
typedef struct	Buf	Buf; 
2000/1109    
typedef struct	IOstate IOstate; 
2000/1103    
 
enum 
{ 
	Qdir		= 0, 
	Qaudio, 
	Qvolume, 
	Qstatus, 
 
	Fmono		= 1, 
2000/1117    
	Fin			= 2, 
2000/1103    
	Fout		= 4, 
 
	Aclosed		= 0, 
	Aread, 
	Awrite, 
 
	Vaudio		= 0, 
	Vmic, 
	Vtreb, 
	Vbass, 
	Vspeed, 
2000/1120    
	Vfilter, 
	Vinvert, 
2000/1103    
	Nvol, 
 
2000/1120    
	Bufsize		= 4*1024,	/* 46 ms each */ 
2000/1109    
	Nbuf		= 32,		/* 1.5 seconds total */ 
2000/1103    
 
	Speed		= 44100, 
	Ncmd		= 50,		/* max volume command words */ 
}; 
 
Dirtab 
audiodir[] = 
{ 
	"audio",	{Qaudio},		0,	0666, 
	"volume",	{Qvolume},		0,	0666, 
	"audiostat",{Qstatus},		0,	0444, 
}; 
 
struct	Buf 
{ 
	uchar*	virt; 
2000/1118    
	ulong	phys; 
2000/1109    
	uint	nbytes; 
2000/1103    
}; 
2000/1104    
 
2000/1109    
struct	IOstate 
2000/1103    
{ 
	QLock; 
2000/1115    
	Lock			ilock; 
2001/0502    
	Rendez		vous; 
	Chan		*chan;		/* chan of open */ 
	int			dma;			/* dma chan, alloc on open, free on close */ 
	int			bufinit;		/* boolean, if buffers allocated */ 
	Buf			buf[Nbuf];		/* buffers and queues */ 
2000/1110    
	volatile Buf	*current;		/* next dma to finish */ 
2001/0502    
	volatile Buf	*next;		/* next candidate for dma */ 
2000/1110    
	volatile Buf	*filling;		/* buffer being filled */ 
2001/0502    
/* to have defines just like linux — there's a real operating system */ 
2000/1118    
#define emptying filling 
2000/1109    
}; 
2000/1103    
 
2000/1104    
static	struct 
{ 
	QLock; 
2000/1109    
	int		amode;			/* Aclosed/Aread/Awrite for /audio */ 
	int		intr;			/* boolean an interrupt has happened */ 
	int		rivol[Nvol];	/* right/left input/output volumes */ 
	int		livol[Nvol]; 
	int		rovol[Nvol]; 
	int		lovol[Nvol]; 
	ulong	totcount;		/* how many bytes processed since open */ 
	vlong	tottime;		/* time at which totcount bytes were processed */ 
	IOstate	i; 
	IOstate	o; 
2000/1103    
} audio; 
 
2001/0502    
Buf	zeroes; 
int	zerodma;	/* dma buffer used for sending zero */ 
 
2000/1116    
static struct 
{ 
	ulong	bytes; 
	ulong	totaldma; 
	ulong	idledma; 
	ulong	faildma; 
2000/1117    
	ulong	samedma; 
2001/0502    
	ulong	empties; 
2000/1116    
} iostats; 
 
2000/1103    
static	struct 
{ 
	char*	name; 
	int	flag; 
	int	ilval;		/* initial values */ 
	int	irval; 
} volumes[] = 
{ 
2001/0502    
[Vaudio]	{"audio",	Fout|Fmono,	 	80,		80}, 
[Vmic]	{"mic",	Fin|Fmono,		  0,		  0}, 
[Vtreb]	{"treb",	Fout|Fmono,		50,		50}, 
[Vbass]	{"bass",	Fout|Fmono, 		50,		50}, 
2000/1207    
[Vspeed]	{"speed",	Fin|Fout|Fmono,	Speed,	Speed}, 
2001/0502    
[Vfilter]	{"filter",	Fout|Fmono,		  0,		  0}, 
[Vinvert]	{"invert",	Fin|Fout|Fmono,	  0,		  0}, 
[Nvol]	{0} 
2000/1103    
}; 
 
2001/0421    
static void	setreg(char *name, int val, int n); 
 
2000/1103    
/* 
 * Grab control of the IIC/L3 shared pins 
 */ 
2000/1110    
static void 
L3_acquirepins(void) 
2000/1103    
{ 
2001/0428    
	gpioregs->set = (GPIO_L3_MODE_o | GPIO_L3_SCLK_o | GPIO_L3_SDA_io); 
	gpioregs->direction |=  (GPIO_L3_MODE_o | GPIO_L3_SCLK_o | GPIO_L3_SDA_io); 
	µdelay(L3_AcquireTime); 
2000/1103    
} 
 
/* 
 * Release control of the IIC/L3 shared pins 
 */ 
2000/1110    
static void 
L3_releasepins(void) 
2000/1103    
{ 
2001/0428    
	gpioregs->direction &= ~(GPIO_L3_MODE_o | GPIO_L3_SCLK_o | GPIO_L3_SDA_io); 
	µdelay(L3_ReleaseTime); 
2000/1103    
} 
 
/* 
 * Initialize the interface 
 */ 
2000/1110    
static void  
L3_init(void) 
2000/1103    
{ 
2000/1110    
	gpioregs->altfunc &= ~(GPIO_L3_SDA_io | GPIO_L3_SCLK_o | GPIO_L3_MODE_o); 
2000/1103    
	L3_releasepins(); 
} 
 
/* 
 * Get a bit. The clock is high on entry and on exit. Data is read after 
 * the clock low time has expired. 
 */ 
2000/1110    
static int 
L3_getbit(void) 
2000/1103    
{ 
	int data; 
 
2000/1110    
	gpioregs->clear = GPIO_L3_SCLK_o; 
	µdelay(L3_ClockLowTime); 
2000/1103    
 
2000/1110    
	data = (gpioregs->level & GPIO_L3_SDA_io) ? 1 : 0; 
2000/1103    
 
2000/1110    
 	gpioregs->set = GPIO_L3_SCLK_o; 
	µdelay(L3_ClockHighTime); 
2000/1103    
 
	return data; 
} 
 
/* 
 * Send a bit. The clock is high on entry and on exit. Data is sent only 
 * when the clock is low (I2C compatibility). 
 */ 
2000/1110    
static void 
L3_sendbit(int bit) 
2000/1103    
{ 
2000/1110    
	gpioregs->clear = GPIO_L3_SCLK_o; 
2000/1103    
 
	if (bit & 1) 
2000/1110    
		gpioregs->set = GPIO_L3_SDA_io; 
2000/1103    
	else 
2000/1110    
		gpioregs->clear = GPIO_L3_SDA_io; 
2000/1103    
 
	/* Assumes L3_DataSetupTime < L3_ClockLowTime */ 
2000/1110    
	µdelay(L3_ClockLowTime); 
2000/1103    
 
2000/1110    
	gpioregs->set = GPIO_L3_SCLK_o; 
	µdelay(L3_ClockHighTime); 
2000/1103    
} 
 
/* 
 * Send a byte. The mode line is set or pulsed based on the mode sequence 
 * count. The mode line is high on entry and exit. The mod line is pulsed 
 * before the second data byte and before ech byte thereafter. 
 */ 
2000/1110    
static void 
L3_sendbyte(char data, int mode) 
2000/1103    
{ 
	int i; 
 
	switch(mode) { 
2000/1110    
	case 0: /* Address mode */ 
		gpioregs->clear = GPIO_L3_MODE_o; 
2000/1103    
		break; 
2000/1110    
	case 1: /* First data byte */ 
2000/1103    
		break; 
2000/1110    
	default: /* Subsequent bytes */ 
		gpioregs->clear = GPIO_L3_MODE_o; 
		µdelay(L3_HaltTime); 
		gpioregs->set = GPIO_L3_MODE_o; 
2000/1103    
		break; 
	} 
 
2000/1110    
	µdelay(L3_ModeSetupTime); 
2000/1103    
 
	for (i = 0; i < 8; i++) 
		L3_sendbit(data >> i); 
 
	if (mode == 0)  /* Address mode */ 
2000/1110    
		gpioregs->set = GPIO_L3_MODE_o; 
2000/1103    
 
2000/1110    
	µdelay(L3_ModeHoldTime); 
2000/1103    
} 
 
/* 
 * Get a byte. The mode line is set or pulsed based on the mode sequence 
 * count. The mode line is high on entry and exit. The mod line is pulsed 
 * before the second data byte and before each byte thereafter. This 
 * function is never valid with mode == 0 (address cycle) as the address 
 * is always sent on the bus, not read. 
 */ 
2000/1110    
static char 
L3_getbyte(int mode) 
2000/1103    
{ 
	char data = 0; 
	int i; 
 
	switch(mode) { 
2000/1110    
	case 0: /* Address mode - never valid */ 
2000/1103    
		break; 
2000/1110    
	case 1: /* First data byte */ 
2000/1103    
		break; 
2000/1110    
	default: /* Subsequent bytes */ 
		gpioregs->clear = GPIO_L3_MODE_o; 
		µdelay(L3_HaltTime); 
		gpioregs->set = GPIO_L3_MODE_o; 
2000/1103    
		break; 
	} 
 
2000/1110    
	µdelay(L3_ModeSetupTime); 
2000/1103    
 
	for (i = 0; i < 8; i++) 
		data |= (L3_getbit() << i); 
 
2000/1110    
	µdelay(L3_ModeHoldTime); 
2000/1103    
 
	return data; 
} 
 
/* 
 * Write data to a device on the L3 bus. The address is passed as well as 
 * the data and length. The length written is returned. The register space 
 * is encoded in the address (low two bits are set and device address is 
 * in the upper 6 bits). 
 */ 
2000/1110    
static int 
2000/1111    
L3_write(uchar addr, uchar *data, int len) 
2000/1103    
{ 
	int mode = 0; 
	int bytes = len; 
 
2001/0421    
	L3_acquirepins(); 
2000/1103    
	L3_sendbyte(addr, mode++); 
	while(len--) 
		L3_sendbyte(*data++, mode++); 
2001/0421    
	L3_releasepins(); 
2000/1103    
	return bytes; 
} 
 
/* 
 * Read data from a device on the L3 bus. The address is passed as well as 
 * the data and length. The length read is returned. The register space 
 * is encoded in the address (low two bits are set and device address is 
 * in the upper 6 bits). 
2001/0421    
 
 * Commented out, not used 
2000/1110    
static int 
2000/1111    
L3_read(uchar addr, uchar *data, int len) 
2000/1103    
{ 
	int mode = 0; 
	int bytes = len; 
 
2001/0421    
	L3_acquirepins(); 
2000/1103    
	L3_sendbyte(addr, mode++); 
2001/0421    
	gpioregs->direction &= ~(GPIO_L3_SDA_io); 
2000/1103    
	while(len--) 
		*data++ = L3_getbyte(mode++); 
2001/0421    
	L3_releasepins(); 
2000/1103    
	return bytes; 
} 
2001/0421    
 */ 
2000/1103    
 
2000/1122    
void 
audiomute(int on) 
{ 
	egpiobits(EGPIO_audio_mute, on); 
} 
 
2000/1103    
static	char	Emode[]		= "illegal open mode"; 
static	char	Evolume[]	= "illegal volume specifier"; 
 
2000/1109    
static void 
bufinit(IOstate *b) 
2000/1103    
{ 
2000/1109    
	int i; 
2000/1103    
 
2000/1122    
	if (debug) print("bufinit\n"); 
2000/1118    
	for (i = 0; i < Nbuf; i++) { 
2000/1109    
		b->buf[i].virt = xalloc(Bufsize); 
2000/1118    
		b->buf[i].phys = PADDR(b->buf[i].virt); 
2001/0502    
		memset(b->buf[i].virt, 0xAA, Bufsize); 
2000/1118    
	} 
2000/1109    
	b->bufinit = 1; 
2001/0502    
 
	if (b == &audio.o) { 
		zeroes.virt = xalloc(Bufsize); 
		zeroes.phys = PADDR(b->buf[i].virt); 
		memset(zeroes.virt, 0, Bufsize); 
	} 
2000/1109    
}; 
2000/1103    
 
2000/1109    
static void 
setempty(IOstate *b) 
2000/1103    
{ 
2000/1109    
	int i; 
2000/1103    
 
2000/1122    
	if (debug) print("setempty\n"); 
2000/1109    
	for (i = 0; i < Nbuf; i++) { 
		b->buf[i].nbytes = 0; 
	} 
	b->filling = b->buf; 
	b->current = b->buf; 
2000/1110    
	b->next = b->buf; 
2000/1103    
} 
 
2000/1110    
static int 
2000/1118    
audioqnotempty(void *x) 
{ 
	IOstate *s = x; 
 
2000/1121    
	return dmaidle(s->dma) || s->emptying != s->current; 
2000/1118    
} 
 
static int 
2000/1111    
audioqnotfull(void *x) 
2000/1110    
{ 
2000/1111    
	IOstate *s = x; 
 
2000/1116    
	return dmaidle(s->dma) || s->filling != s->current; 
2000/1110    
} 
 
2000/1121    
SSPregs *sspregs; 
MCPregs	*mcpregs; 
 
2000/1103    
static void 
audioinit(void) 
{ 
2000/1121    
	/* Turn MCP operations off */ 
	mcpregs = mapspecial(MCPREGS, 0x34); 
	mcpregs->status &= ~(1<<16); 
 
	sspregs = mapspecial(SSPREGS, 32); 
2001/0501    
 
2000/1110    
} 
 
2001/0421    
uchar	status0[1]		= {0x22}; 
uchar	status1[1]		= {0x80}; 
uchar	data00[1]		= {0x00};		/* volume control, bits 0 – 5 */ 
uchar	data01[1]		= {0x40}; 
uchar	data02[1]		= {0x80}; 
uchar	data0e0[2]	= {0xc0, 0xe0}; 
uchar	data0e1[2]	= {0xc1, 0xe0}; 
uchar	data0e2[2]	= {0xc2, 0xf2}; 
2000/1118    
/* there is no data0e3 */ 
2001/0421    
uchar	data0e4[2]	= {0xc4, 0xe0}; 
uchar	data0e5[2]	= {0xc5, 0xe0}; 
uchar	data0e6[2]	= {0xc6, 0xe3}; 
2000/1117    
 
2000/1110    
static void 
2000/1117    
enable(void) 
2000/1110    
{ 
2001/0421    
	uchar	data[1]; 
2000/1103    
 
	L3_init(); 
 
2000/1110    
	/* Setup the uarts */ 
2000/1117    
	ppcregs->assignment &= ~(1<<18); 
2000/1103    
 
2000/1116    
	sspregs->control0 = 0; 
	sspregs->control0 = 0x031f; /* 16 bits, TI frames, serial clock rate 3 */ 
	sspregs->control1 = 0x0020; /* ext clock */ 
	sspregs->control0 = 0x039f;	/* enable */ 
2000/1110    
 
	/* Enable the audio power */ 
2000/1122    
	egpiobits(EGPIO_audio_ic_power | EGPIO_codec_reset, 1); 
2000/1110    
 
2000/1116    
	/* external clock configured for 44100 samples/sec */ 
2000/1117    
	gpioregs->set	= GPIO_CLK_SET0_o; 
	gpioregs->clear	= GPIO_CLK_SET1_o; 
2000/1116    
 
2000/1110    
	/* Wait for the UDA1341 to wake up */ 
2000/1111    
	delay(100); 
2000/1110    
 
2000/1111    
	/* Reset the chip */ 
2001/0421    
	data[0] = status0[0] | 1<<UdaStatusRST; 
	L3_write(UDA1341_L3Addr | UDA1341_STATUS, data, 1 ); 
2000/1116    
	gpioregs->clear = EGPIO_codec_reset; 
2000/1117    
	gpioregs->set = EGPIO_codec_reset; 
2000/1116    
	/* write uda 1341 status[0] */ 
2001/0421    
	L3_write(UDA1341_L3Addr | UDA1341_STATUS, status0, 1 ); 
	L3_write(UDA1341_L3Addr | UDA1341_STATUS, status1, 1); 
	L3_write(UDA1341_L3Addr | UDA1341_DATA0, data02, 1); 
	L3_write(UDA1341_L3Addr | UDA1341_DATA0, data0e2, 2); 
	L3_write(UDA1341_L3Addr | UDA1341_DATA0, data0e6, 2 ); 
2000/1110    
 
2000/1117    
	if (debug) { 
2001/0421    
		print("enable:	status0	= 0x%2.2ux\n", status0[0]); 
		print("enable:	status1	= 0x%2.2ux\n", status1[0]); 
		print("enable:	data02	= 0x%2.2ux\n", data02[0]); 
		print("enable:	data0e2	= 0x%4.4ux\n", data0e2[0] | data0e2[1]<<8); 
		print("enable:	data0e4	= 0x%4.4ux\n", data0e4[0] | data0e4[1]<<8); 
		print("enable:	data0e6	= 0x%4.4ux\n", data0e6[0] | data0e6[1]<<8); 
2000/1122    
		print("enable:	sspregs->control0 = 0x%lux\n", sspregs->control0); 
		print("enable:	sspregs->control1 = 0x%lux\n", sspregs->control1); 
2000/1117    
	} 
} 
2000/1111    
 
2000/1117    
static	void 
resetlevel(void) 
{ 
	int i; 
2000/1111    
 
2000/1117    
	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; 
	} 
} 
2000/1111    
 
2000/1117    
static void 
mxvolume(void) { 
	int *left, *right; 
2000/1111    
 
2000/1117    
	if(audio.amode & Aread){ 
		left = audio.livol; 
		right = audio.rivol; 
2000/1118    
		if (left[Vmic]+right[Vmic] == 0) { 
			/* Turn on automatic gain control (AGC) */ 
2001/0421    
			data0e4[1] |= 0x10; 
			L3_write(UDA1341_L3Addr | UDA1341_DATA0, data0e4, 2 ); 
2000/1118    
		} else { 
			int v; 
			/* Turn on manual gain control */ 
			v = ((left[Vmic]+right[Vmic])*0x7f/200)&0x7f; 
2001/0421    
			data0e4[1] &= ~0x13; 
			data0e5[1] &= ~0x1f; 
			data0e4[1] |= v & 0x3; 
			data0e5[0] |= (v & 0x7c)<<6; 
			data0e5[1] |= (v & 0x7c)>>2; 
			L3_write(UDA1341_L3Addr | UDA1341_DATA0, data0e4, 2 ); 
			L3_write(UDA1341_L3Addr | UDA1341_DATA0, data0e5, 2 ); 
2000/1118    
		} 
2000/1122    
		if (left[Vinvert]+right[Vinvert] == 0) 
2001/0421    
			status1[0] &= ~0x04; 
2000/1122    
		else 
2001/0421    
			status1[0] |= 0x04; 
		L3_write(UDA1341_L3Addr | UDA1341_STATUS, status1, 1); 
2000/1122    
		if (debug) { 
2001/0421    
			print("mxvolume:	status1	= 0x%2.2ux\n", status1[0]); 
			print("mxvolume:	data0e4	= 0x%4.4ux\n", data0e4[0]|data0e4[0]<<8); 
			print("mxvolume:	data0e5	= 0x%4.4ux\n", data0e5[0]|data0e5[0]<<8); 
2000/1122    
		} 
2000/1117    
	} 
	if(audio.amode & Awrite){ 
		left = audio.lovol; 
		right = audio.rovol; 
2001/0421    
		data00[0] &= ~0x3f; 
		data00[0] |= ((200-left[Vaudio]-right[Vaudio])*0x3f/200)&0x3f; 
2000/1117    
		if (left[Vtreb]+right[Vtreb] <= 100 
2000/1120    
		 && left[Vbass]+right[Vbass] <= 100) 
2000/1117    
			/* settings neutral */ 
2001/0421    
			data02[0] &= ~0x03; 
2000/1120    
		else { 
2001/0421    
			data02[0] |= 0x03; 
			data01[0] &= ~0x3f; 
			data01[0] |= ((left[Vtreb]+right[Vtreb]-100)*0x3/100)&0x03; 
			data01[0] |= (((left[Vbass]+right[Vbass]-100)*0xf/100)&0xf)<<2; 
2000/1117    
		} 
2000/1120    
		if (left[Vfilter]+right[Vfilter] == 0) 
2001/0421    
			data02[0] &= ~0x10; 
2000/1120    
		else 
2001/0421    
			data02[0]|= 0x10; 
2000/1120    
		if (left[Vinvert]+right[Vinvert] == 0) 
2001/0421    
			status1[0] &= ~0x10; 
2000/1120    
		else 
2001/0421    
			status1[0] |= 0x10; 
		L3_write(UDA1341_L3Addr | UDA1341_STATUS, status1, 1); 
		L3_write(UDA1341_L3Addr | UDA1341_DATA0, data00, 1); 
		L3_write(UDA1341_L3Addr | UDA1341_DATA0, data01, 1); 
		L3_write(UDA1341_L3Addr | UDA1341_DATA0, data02, 1); 
2000/1120    
		if (debug) { 
2001/0421    
			print("mxvolume:	status1	= 0x%2.2ux\n", status1[0]); 
			print("mxvolume:	data00	= 0x%2.2ux\n", data00[0]); 
			print("mxvolume:	data01	= 0x%2.2ux\n", data01[0]); 
			print("mxvolume:	data02	= 0x%2.2ux\n", data02[0]); 
2000/1120    
		} 
2000/1117    
	} 
} 
2000/1111    
 
2000/1117    
static void 
2001/0421    
setreg(char *name, int val, int n) 
{ 
	uchar x[2]; 
	int i; 
 
	x[0] = val; 
	x[1] = val>>8; 
 
	if(strcmp(name, "pause") == 0){ 
		for(i = 0; i < n; i++) 
			µdelay(val); 
		return; 
	} 
 
	switch(n){ 
	case 1: 
	case 2: 
		break; 
	default: 
		error("setreg"); 
	} 
 
	if(strcmp(name, "status") == 0){ 
		L3_write(UDA1341_L3Addr | UDA1341_STATUS, x, n); 
	} else if(strcmp(name, "data0") == 0){ 
		L3_write(UDA1341_L3Addr | UDA1341_DATA0, x, n); 
	} else if(strcmp(name, "data1") == 0){ 
		L3_write(UDA1341_L3Addr | UDA1341_DATA1, x, n); 
	} else 
		error("setreg"); 
} 
 
static void 
2000/1117    
outenable(void) { 
	/* turn on DAC, set output gain switch */ 
2000/1122    
	egpiobits(EGPIO_audio_power, 1); 
2000/1121    
	audiomute(0); 
2001/0421    
	status1[0] |= 0x41; 
	L3_write(UDA1341_L3Addr | UDA1341_STATUS, status1, 1); 
2000/1117    
	/* set volume */ 
2001/0421    
	data00[0] |= 0xf; 
	L3_write(UDA1341_L3Addr | UDA1341_DATA0, data00, 1); 
2000/1116    
	if (debug) { 
2001/0421    
		print("outenable:	status1	= 0x%2.2ux\n", status1[0]); 
		print("outenable:	data00	= 0x%2.2ux\n", data00[0]); 
2000/1116    
	} 
2000/1103    
} 
2000/1107    
 
static void 
2000/1117    
outdisable(void) { 
2000/1122    
	dmastop(audio.o.dma); 
2000/1117    
	/* turn off DAC, clear output gain switch */ 
2000/1121    
	audiomute(1); 
2000/1122    
	egpiobits(EGPIO_audio_power, 0); 
2001/0421    
	status1[0] &= ~0x41; 
	L3_write(UDA1341_L3Addr | UDA1341_STATUS, status1, 1); 
2000/1117    
	if (debug) { 
2001/0421    
		print("outdisable:	status1	= 0x%2.2ux\n", status1[0]); 
2000/1117    
	} 
2000/1122    
	egpiobits(EGPIO_audio_power, 0); 
2000/1117    
} 
 
static void 
inenable(void) { 
	/* turn on ADC, set input gain switch */ 
2001/0421    
	status1[0] |= 0x22; 
	L3_write(UDA1341_L3Addr | UDA1341_STATUS, status1, 1); 
2000/1117    
	if (debug) { 
2001/0421    
		print("inenable:	status1	= 0x%2.2ux\n", status1[0]); 
2000/1117    
	} 
} 
 
static void 
indisable(void) { 
2000/1122    
	dmastop(audio.i.dma); 
2000/1117    
	/* turn off ADC, clear input gain switch */ 
2001/0421    
	status1[0] &= ~0x22; 
	L3_write(UDA1341_L3Addr | UDA1341_STATUS, status1, 1); 
2000/1117    
	if (debug) { 
2001/0421    
		print("indisable:	status1	= 0x%2.2ux\n", status1[0]); 
2000/1117    
	} 
} 
 
2000/1122    
static void 
2000/1117    
sendaudio(IOstate *s) { 
2000/1115    
	/* interrupt routine calls this too */ 
2000/1116    
	int n; 
 
2000/1115    
	if (debug > 1) print("#A: sendaudio\n"); 
2000/1117    
	ilock(&s->ilock); 
2001/0502    
	if ((audio.amode &  Aread) && s->next == s->filling && dmaidle(s->dma)) { 
		// send an empty buffer to provide an input clock 
		zerodma |= dmastart(s->dma, zeroes.phys, Bufsize) & 0xff; 
		if (zerodma == 0) 
			if (debug) print("emptyfail\n"); 
		iostats.empties++; 
		iunlock(&s->ilock); 
		return; 
	} 
2000/1117    
	while (s->next != s->filling) { 
		assert(s->next->nbytes); 
2000/1118    
		if ((n = dmastart(s->dma, s->next->phys, s->next->nbytes)) == 0) { 
2000/1116    
			iostats.faildma++; 
2000/1115    
			break; 
		} 
2000/1116    
		iostats.totaldma++; 
2001/0502    
		switch (n >> 8) { 
2000/1117    
		case 1: 
			iostats.idledma++; 
			break; 
		case 3: 
			iostats.faildma++; 
			break; 
		} 
2000/1122    
		if (debug) { 
			if (debug > 1) 
				print("dmastart @%p\n", s->next); 
			else 
				iprint("+"); 
		} 
2000/1117    
		s->next->nbytes = 0; 
		s->next++; 
		if (s->next == &s->buf[Nbuf]) 
			s->next = &s->buf[0]; 
2000/1110    
	} 
2000/1117    
	iunlock(&s->ilock); 
2000/1110    
} 
 
static void 
2000/1118    
recvaudio(IOstate *s) { 
	/* interrupt routine calls this too */ 
	int n; 
 
	if (debug > 1) print("#A: recvaudio\n"); 
	ilock(&s->ilock); 
	while (s->next != s->emptying) { 
		assert(s->next->nbytes == 0); 
		if ((n = dmastart(s->dma, s->next->phys, Bufsize)) == 0) { 
			iostats.faildma++; 
			break; 
		} 
		iostats.totaldma++; 
2001/0502    
		switch (n >> 8) { 
2000/1118    
		case 1: 
			iostats.idledma++; 
			break; 
		case 3: 
			iostats.faildma++; 
			break; 
		} 
2000/1122    
		if (debug) { 
			if (debug > 1) 
				print("dmastart @%p\n", s->next); 
			else 
				iprint("+"); 
		} 
2000/1118    
		s->next++; 
		if (s->next == &s->buf[Nbuf]) 
			s->next = &s->buf[0]; 
	} 
	iunlock(&s->ilock); 
} 
 
2000/1125    
void 
audiopower(int flag) { 
	IOstate *s; 
 
	if (debug) { 
		iprint("audiopower %d\n", flag); 
	} 
	if (flag) { 
		/* power on only when necessary */ 
		if (audio.amode) { 
			egpiobits(EGPIO_audio_power | EGPIO_audio_ic_power | EGPIO_codec_reset, 1); 
			enable(); 
			if (audio.amode & Aread) { 
				inenable(); 
				s = &audio.i; 
				dmareset(s->dma, 1, 0, 4, 2, SSPRecvDMA, Port4SSP); 
				recvaudio(s); 
			} 
			if (audio.amode & Awrite) { 
				outenable(); 
				s = &audio.o; 
				dmareset(s->dma, 0, 0, 4, 2, SSPXmitDMA, Port4SSP); 
				sendaudio(s); 
			} 
			mxvolume(); 
		} 
	} else { 
		/* power off */ 
		if (audio.amode & Aread) 
			indisable(); 
		if (audio.amode & Awrite) 
			outdisable(); 
		egpiobits(EGPIO_audio_ic_power | EGPIO_codec_reset | EGPIO_audio_power, 0); 
	} 
} 
 
2000/1118    
static void 
2000/1116    
audiointr(void *x, ulong ndma) { 
2000/1110    
	IOstate *s = x; 
 
2000/1122    
	if (debug) { 
		if (debug > 1) 
			iprint("#A: audio interrupt @%p\n", s->current); 
		else 
			iprint("-"); 
	} 
2001/0502    
	if (s == &audio.i || (ndma & ~zerodma)) { 
		/* A dma, not of a zero buffer completed, update current 
		 * Only interrupt routine touches s->current 
		 */ 
		s->current->nbytes = (s == &audio.i)? Bufsize: 0; 
		s->current++; 
		if (s->current == &s->buf[Nbuf]) 
			s->current = &s->buf[0]; 
	} 
	if (ndma) { 
		if (s == &audio.o) { 
			zerodma &= ~ndma; 
2000/1116    
			sendaudio(s); 
2001/0502    
		} else if (s == &audio.i) 
2000/1118    
			recvaudio(s); 
2000/1109    
	} 
2000/1111    
	wakeup(&s->vous); 
2000/1109    
} 
2000/1107    
 
static Chan* 
audioattach(char *param) 
{ 
	return devattach('A', param); 
} 
 
static int 
audiowalk(Chan *c, char *name) 
{ 
	return devwalk(c, name, audiodir, nelem(audiodir), devgen); 
} 
 
static void 
audiostat(Chan *c, char *db) 
{ 
	devstat(c, db, audiodir, nelem(audiodir), devgen); 
} 
 
static Chan* 
2000/1121    
audioopen(Chan *c, int mode) 
2000/1107    
{ 
2000/1117    
	IOstate *s; 
2000/1121    
	int omode = mode; 
2000/1107    
 
	switch(c->qid.path & ~CHDIR) { 
	default: 
		error(Eperm); 
		break; 
 
	case Qstatus: 
		if((omode&7) != OREAD) 
			error(Eperm); 
	case Qvolume: 
	case Qdir: 
		break; 
 
	case Qaudio: 
2000/1109    
		omode = (omode & 0x7) + 1; 
2000/1121    
		if (omode & ~(Aread | Awrite)) 
2000/1108    
			error(Ebadarg); 
2000/1107    
		qlock(&audio); 
2000/1108    
		if(audio.amode & omode){ 
2000/1107    
			qunlock(&audio); 
			error(Einuse); 
		} 
2000/1117    
		enable(); 
2000/1116    
		memset(&iostats, 0, sizeof(iostats)); 
2000/1109    
		if (omode & Aread) { 
2000/1117    
			inenable(); 
			s = &audio.i; 
2000/1121    
			if(s->bufinit == 0) 
2000/1118    
				bufinit(s); 
			setempty(s); 
2000/1121    
			s->emptying = &s->buf[Nbuf-1]; 
2000/1117    
			s->chan = c; 
2000/1122    
			s->dma = dmaalloc(1, 0, 4, 2, SSPRecvDMA, Port4SSP, audiointr, (void*)s); 
2000/1125    
			audio.amode |= Aread; 
2000/1108    
		} 
2001/0502    
		if (omode & (Aread|Awrite) && (audio.amode & Awrite) == 0) { 
2000/1117    
			s = &audio.o; 
			if(s->bufinit == 0) 
				bufinit(s); 
			setempty(s); 
			s->chan = c; 
			s->dma = dmaalloc(0, 0, 4, 2, SSPXmitDMA, Port4SSP, audiointr, (void*)s); 
2001/0502    
		}	 
		if (omode & Awrite) { 
2000/1125    
			audio.amode |= Awrite; 
2001/0502    
			outenable(); 
2000/1107    
		} 
2000/1117    
		mxvolume(); 
2000/1115    
		qunlock(&audio); 
2001/0502    
		if (audio.amode == Aread) 
			sendaudio(&audio.o); 
			 
2000/1122    
		if (debug) print("open done\n"); 
2000/1107    
		break; 
	} 
2000/1121    
	c = devopen(c, mode, audiodir, nelem(audiodir), devgen); 
	c->mode = openmode(mode); 
2000/1107    
	c->flag |= COPEN; 
	c->offset = 0; 
 
	return c; 
} 
 
static void 
audioclose(Chan *c) 
{ 
2000/1117    
	IOstate *s; 
2000/1107    
 
	switch(c->qid.path & ~CHDIR) { 
	default: 
		error(Eperm); 
		break; 
 
	case Qdir: 
	case Qvolume: 
	case Qstatus: 
		break; 
 
	case Qaudio: 
2000/1115    
		if (debug > 1) print("#A: close\n"); 
2000/1107    
		if(c->flag & COPEN) { 
			qlock(&audio); 
2001/0502    
			if (audio.i.chan == c) { 
				/* closing the read end */ 
				audio.amode &= ~Aread; 
				s = &audio.i; 
				qlock(s); 
				indisable(); 
				setempty(s); 
				dmafree(s->dma); 
				qunlock(s); 
				if ((audio.amode & Awrite) == 0) { 
					s = &audio.o; 
					qlock(s); 
					while(waserror()) { 
						dmawait(s->dma); 
						if (dmaidle(s->dma)) 
							break; 
					} 
					outdisable(); 
					setempty(s); 
					dmafree(s->dma); 
					qunlock(s); 
				} 
2000/1107    
			} 
2000/1110    
			if (audio.o.chan == c) { 
2000/1118    
				/* closing the write end */ 
				audio.amode &= ~Awrite; 
2000/1117    
				s = &audio.o; 
				qlock(s); 
				if (s->filling->nbytes) { 
					/* send remaining partial buffer */ 
					s->filling++; 
					if (s->filling == &s->buf[Nbuf]) 
						s->filling = &s->buf[0]; 
					sendaudio(s); 
2000/1110    
				} 
2001/0502    
				while(waserror()) { 
					dmawait(s->dma); 
					if (dmaidle(s->dma)) 
						break; 
				} 
2000/1117    
				outdisable(); 
				setempty(s); 
2001/0502    
				if ((audio.amode & Aread) == 0) 
					dmafree(s->dma); 
2000/1117    
				qunlock(s); 
2000/1110    
			} 
			if (audio.amode == 0) { 
				/* turn audio off */ 
2000/1122    
				egpiobits(EGPIO_audio_ic_power | EGPIO_codec_reset, 0); 
2000/1110    
			} 
2000/1107    
			qunlock(&audio); 
2000/1130    
			if (debug) { 
				print("total dmas: %lud\n", iostats.totaldma); 
				print("dmas while idle: %lud\n", iostats.idledma); 
				print("dmas while busy: %lud\n", iostats.faildma); 
				print("out of order dma: %lud\n", iostats.samedma); 
			} 
2000/1107    
		} 
		break; 
	} 
} 
 
static long 
audioread(Chan *c, void *v, long n, vlong off) 
{ 
	int liv, riv, lov, rov; 
	long m, n0; 
	char buf[300]; 
	int j; 
	ulong offset = off; 
2000/1118    
	char *p; 
	IOstate *s; 
2000/1107    
 
	n0 = n; 
2000/1118    
	p = v; 
2000/1107    
	switch(c->qid.path & ~CHDIR) { 
	default: 
		error(Eperm); 
		break; 
 
	case Qdir: 
2000/1118    
		return devdirread(c, p, n, audiodir, nelem(audiodir), devgen); 
2000/1107    
 
	case Qaudio: 
2000/1118    
		if (debug > 1) print("#A: read %ld\n", n); 
		if((audio.amode & Aread) == 0) 
			error(Emode); 
2000/1121    
		s = &audio.i; 
2000/1118    
		qlock(s); 
		if(waserror()){ 
			qunlock(s); 
			nexterror(); 
		} 
		while(n > 0) { 
2000/1121    
			if(s->emptying->nbytes == 0) { 
				if (debug > 1) print("#A: emptied @%p\n", s->emptying); 
				recvaudio(s); 
				s->emptying++; 
				if (s->emptying == &s->buf[Nbuf]) 
					s->emptying = s->buf; 
			} 
2000/1118    
			/* wait if dma in progress */ 
2000/1121    
			while (!dmaidle(s->dma) && s->emptying == s->current) { 
2000/1118    
				if (debug > 1) print("#A: sleep\n"); 
				sleep(&s->vous, audioqnotempty, s); 
			} 
 
2001/0502    
			m = (s->emptying->nbytes > n)? n: s->emptying->nbytes; 
			memmove(p, s->emptying->virt + Bufsize -  
					  s->emptying->nbytes, m); 
2000/1118    
 
			s->emptying->nbytes -= m; 
			n -= m; 
			p += m; 
		} 
		poperror(); 
		qunlock(s); 
2000/1107    
		break; 
2000/1118    
		break; 
2000/1107    
 
	case Qstatus: 
		buf[0] = 0; 
		snprint(buf, sizeof(buf), "bytes %lud\ntime %lld\n", 
			audio.totcount, audio.tottime); 
2000/1118    
		return readstr(offset, p, n, buf); 
2000/1107    
 
	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"); 
		} 
2000/1118    
		return readstr(offset, p, n, buf); 
2000/1107    
	} 
	return n0-n; 
} 
 
static long 
audiowrite(Chan *c, void *vp, long n, vlong) 
{ 
	long m, n0; 
	int i, nf, v, left, right, in, out; 
	char buf[255], *field[Ncmd]; 
2000/1109    
	char *p; 
	IOstate *a; 
2000/1107    
 
2000/1109    
	p = vp; 
2000/1107    
	n0 = n; 
	switch(c->qid.path & ~CHDIR) { 
	default: 
		error(Eperm); 
		break; 
 
	case Qvolume: 
		v = Vaudio; 
		left = 1; 
		right = 1; 
		in = 1; 
		out = 1; 
		if(n > sizeof(buf)-1) 
			n = sizeof(buf)-1; 
2000/1109    
		memmove(buf, p, n); 
2000/1107    
		buf[n] = '\0'; 
 
		nf = getfields(buf, field, Ncmd, 1, " \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); 
2000/1118    
				if (m < 0 || m > 100)  
					error(Evolume); 
2000/1107    
				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; 
				goto cont0; 
			} 
 
			for(m=0; volumes[m].name; m++) { 
				if(strcmp(field[i], volumes[m].name) == 0) { 
					v = m; 
					in = 1; 
					out = 1; 
					left = 1; 
					right = 1; 
					goto cont0; 
				} 
			} 
 
			if(strcmp(field[i], "reset") == 0) { 
2000/1117    
				resetlevel(); 
2000/1107    
				goto cont0; 
			} 
2000/1120    
			if(strcmp(field[i], "debug") == 0) { 
				debug = debug?0:1; 
				goto cont0; 
			} 
2000/1107    
			if(strcmp(field[i], "in") == 0) { 
				in = 1; 
				out = 0; 
				goto cont0; 
			} 
			if(strcmp(field[i], "out") == 0) { 
				in = 0; 
				out = 1; 
				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; 
2001/0421    
			} 
			if(strcmp(field[i], "reg") == 0) { 
				if(nf < 3) 
					error(Evolume); 
				setreg(field[1], atoi(field[2]), nf == 4 ? atoi(field[3]):1); 
				return n0; 
2000/1107    
			} 
			error(Evolume); 
			break; 
		cont0:; 
		} 
2000/1120    
		mxvolume(); 
2000/1107    
		break; 
 
	case Qaudio: 
2000/1115    
		if (debug > 1) print("#A: write %ld\n", n); 
2000/1109    
		if((audio.amode & Awrite) == 0) 
2000/1107    
			error(Emode); 
2000/1109    
		a = &audio.o; 
		qlock(a); 
2000/1107    
		if(waserror()){ 
2000/1109    
			qunlock(a); 
2000/1107    
			nexterror(); 
		} 
		while(n > 0) { 
2000/1109    
			/* wait if dma in progress */ 
2000/1116    
			while (!dmaidle(a->dma) && a->filling == a->current) { 
2000/1115    
				if (debug > 1) print("#A: sleep\n"); 
2000/1110    
				sleep(&a->vous, audioqnotfull, a); 
2000/1115    
			} 
2000/1107    
 
2000/1109    
			m = Bufsize - a->filling->nbytes; 
2000/1107    
			if(m > n) 
				m = n; 
2000/1111    
			memmove(a->filling->virt + a->filling->nbytes, p, m); 
2000/1107    
 
2000/1109    
			a->filling->nbytes += m; 
2000/1107    
			n -= m; 
2000/1109    
			p += m; 
			if(a->filling->nbytes >= Bufsize) { 
2000/1115    
				if (debug > 1) print("#A: filled @%p\n", a->filling); 
2000/1109    
				a->filling++; 
				if (a->filling == &a->buf[Nbuf]) 
					a->filling = a->buf; 
2000/1110    
				sendaudio(a); 
2000/1107    
			} 
		} 
		poperror(); 
2000/1109    
		qunlock(a); 
2000/1107    
		break; 
	} 
	return n0 - n; 
} 
 
2000/1111    
Dev uda1341devtab = { 
2000/1107    
	'A', 
	"audio", 
 
	devreset, 
	audioinit, 
	audioattach, 
	devclone, 
	audiowalk, 
	audiostat, 
	audioopen, 
	devcreate, 
	audioclose, 
	audioread, 
	devbread, 
	audiowrite, 
	devbwrite, 
	devremove, 
	devwstat, 
2000/1122    
	audiopower, 
2000/1107    
}; 


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