Subject: kern/18093: add emuxki(4) recording support
To: None <gnats-bugs@gnats.netbsd.org>
From: None <toshii@netbsd.org>
List: netbsd-bugs
Date: 08/28/2002 14:37:08
>Number:         18093
>Category:       kern
>Synopsis:       add emuxki(4) recording support
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    kern-bug-people
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Tue Aug 27 22:38:00 PDT 2002
>Closed-Date:
>Last-Modified:
>Originator:     IWAMOTO Toshihiro
>Release:        NetBSD 1.6F
>Organization:
	
>Environment:
System: NetBSD pepper.my.domain 1.6F NetBSD 1.6F (PEPPER) #40: Wed Aug 28 12:37:58 JST 2002 root@pepper.my.domain:/usr/src/syssrc/sys/arch/i386/compile/PEPPER i386
Architecture: i386
Machine: i386
emuxki0 at pci0 dev 12 function 0: Creative Labs SBLive! EMU 10000 (audio multimedia, revision 0x07)
emuxki0: interrupting at irq 5
emuxki0: EMC40 codec; 18 bit DAC, 18 bit ADC, no 3D stereo

>Description:
	This patch adds recording capability to emuxki(4).
>How-To-Repeat:
	Buy a SBLive! and notice recording doesn't work.
>Fix:
	Address problems in this patch and commit.
	I've only tested it with 48kHz stereo 16bit slinear.

	Most of problems which I'm aware are commented with "XXX".
	Recording is hardcoded to use AC97 ADC.
	There are two more recording sources (FX and mic),
	but I think they are much less useful or hard to program.
	And with our audio layer, there are no way to handle such issue
	other than attaching two more /dev/audios.
Index: emuxki.c
===================================================================
RCS file: /net/kiku/NetBSD/NetBSD-CVS/syssrc/sys/dev/pci/emuxki.c,v
retrieving revision 1.9
diff -u -r1.9 emuxki.c
--- emuxki.c	2002/02/02 18:10:28	1.9
+++ emuxki.c	2002/08/28 05:21:06
@@ -140,6 +140,7 @@
 				  struct audio_params *,
 				  struct audio_params *);
 
+static int	emuxki_round_blocksize(void *, int);
 static size_t	emuxki_round_buffersize(void *, int, size_t);
 
 static int	emuxki_trigger_output(void *, void *, void *, int,
@@ -189,7 +190,7 @@
 	NULL,			/* drain */
 	emuxki_query_encoding,
 	emuxki_set_params,
-	NULL,			/* round blocksize */
+	emuxki_round_blocksize,
 	NULL,			/* commit settings */
 	NULL,			/* init_output */
 	NULL,			/* init_input */
@@ -213,6 +214,23 @@
 	NULL,			/* dev_ioctl */
 };
 
+static const int emuxki_recsrc_adcrates[] =
+    { 48000, 44100, 32000, 24000, 22050, 16000, 11025, 8000, -1 };
+static const int emuxki_recsrc_intrmasks[EMU_NUMRECSRCS] =
+    { EMU_INTE_MICBUFENABLE, EMU_INTE_ADCBUFENABLE, EMU_INTE_EFXBUFENABLE };
+static const u_int32_t emuxki_recsrc_bufaddrreg[EMU_NUMRECSRCS] =
+    { EMU_MICBA, EMU_ADCBA, EMU_FXBA };
+static const u_int32_t emuxki_recsrc_idxreg[EMU_NUMRECSRCS] =
+    { EMU_RECIDX(EMU_MICIDX), EMU_RECIDX(EMU_ADCIDX), EMU_RECIDX(EMU_FXIDX) };
+static const u_int32_t emuxki_recsrc_szreg[EMU_NUMRECSRCS] =
+    { EMU_MICBS, EMU_ADCBS, EMU_FXBS };
+static const int emuxki_recbuf_sz[] = {
+	0, 384, 448, 512, 640, 768, 896, 1024, 1280, 1536, 1792,
+	2048, 2560, 3072, 3584, 4096, 5120, 6144, 7168, 8192, 10240,
+	12288, 14366, 16384, 20480, 24576, 28672, 32768, 40960, 49152,
+	57344, 65536
+};
+
 /*
  * Dma memory mgmt
  */
@@ -402,8 +420,16 @@
 	printf("%s: interrupting at %s\n", sc->sc_dev.dv_xname, intrstr);
 
 	if (emuxki_scinit(sc) || emuxki_ac97_init(sc) ||
-	    (sc->sc_audev = audio_attach_mi(&emuxki_hw_if, sc, self)) == NULL)
+	    (sc->sc_audev = audio_attach_mi(&emuxki_hw_if, sc, self)) == NULL) {
 		emuxki_pci_shutdown(sc);
+		return;
+	}
+#if 0
+	sc->rsourcectl.dev =
+	    sc->codecif->vtbl->get_portnum_by_name(sc->codec_if, AudioCrecord,
+						   AudioNsource, NULL);
+	sc->rsourcectl.cp = AUDIO_MIXER_ENUM;
+#endif
 }
 
 static int
@@ -600,6 +626,15 @@
 			  EMU_DSP_OUTR(EMU_DSP_OUT_AC97),
 			  EMU_DSP_FX(1), EMU_DSP_CST(4));
 #endif
+	/* ADC recording (l/r) = AC97 In (l/r) */
+	emuxki_dsp_addop(sc, &pc, EMU_DSP_OP_ACC3,
+			  EMU_DSP_OUTL(EMU_DSP_OUT_ADC),
+			  EMU_DSP_INL(EMU_DSP_IN_AC97),
+			  EMU_DSP_CST(0), EMU_DSP_CST(0));
+	emuxki_dsp_addop(sc, &pc, EMU_DSP_OP_MACINTS,
+			  EMU_DSP_OUTR(EMU_DSP_OUT_ADC),
+			  EMU_DSP_INR(EMU_DSP_IN_AC97),
+			  EMU_DSP_CST(0), EMU_DSP_CST(0));
 	/* zero out the rest of the microcode */
 	while (pc < 512)
 		emuxki_dsp_addop(sc, &pc, EMU_DSP_OP_ACC3,
@@ -1179,17 +1214,37 @@
 /*
  * Will come back when used in voice_dataloc_create
  */
-#if 0
 static int
 emuxki_recsrc_reserve(struct emuxki_voice *voice, emuxki_recsrc_t source)
 {
-	if (voice->emu->recsrc[source] != NULL)
+	if (source < 0 || source >= EMU_NUMRECSRCS) {
+#ifdef EMUXKI_DEBUG
+		printf("Tryed to reserve invalid source: %d\n", source);
+#endif
+		return (EINVAL);
+	}
+	if (voice->sc->recsrc[source] == voice)
+		return (0);			/* XXX */
+	if (voice->sc->recsrc[source] != NULL)
 		return (EBUSY);
-	voice->emu->recsrc[source] = voice;
+	voice->sc->recsrc[source] = voice;
 	return (0);
 }
-#endif
 
+static int
+emuxki_recsrc_rate_to_index(int srate)
+{
+	int index;
+
+	for(index = 0; ; index++) {
+		if (emuxki_recsrc_adcrates[index] == srate)
+			return (index);
+
+		if (emuxki_recsrc_adcrates[index] < 0)
+			return (-1);
+	}
+}
+
 /* When calling this function we assume the voice is stopped */
 static void
 emuxki_voice_recsrc_release(struct emuxki_softc *sc, emuxki_recsrc_t source)
@@ -1206,16 +1261,9 @@
 		if ((error = emuxki_voice_channel_create(voice)))
 			return (error);
 	} else {
-		/*
-		 * Commented out because i don't know how to get the selected
-		 * recording source
-		 */
-#if 0
-		if (emuxki_recsrc_reserve(voice, recsrc))
-			return (EBUSY);
-		printf("Which rec src do i have to create!!!\n");
-#endif
-		return (EBUSY);	/* just return an error, no real meaning */
+		if ((error =
+		    emuxki_recsrc_reserve(voice, voice->dataloc.source)))
+			return (error);
 	}
 	return (0);
 }
@@ -1246,25 +1294,25 @@
 	sc->lvoice = NULL;
 	splx(s);
 
-	if (!voice) {
+	if (!voice)
 		if (!(voice = malloc(sizeof(*voice), M_DEVBUF, M_WAITOK)))
 			return (NULL);
-		voice->sc = sc;
-		voice->state = !EMU_VOICE_STATE_STARTED;
-		voice->stereo = EMU_VOICE_STEREO_NOTSET;
-		voice->b16 = 0;
-		voice->sample_rate = 0;
-		if (use & EMU_VOICE_USE_PLAY)
-			voice->dataloc.chan[0] = voice->dataloc.chan[0] = NULL;
-		else
-			voice->dataloc.source = EMU_RECSRC_NOTSET;
-		voice->buffer = NULL;
-		voice->blksize = 0;
-		voice->trigblk = 0;
-		voice->blkmod = 0;
-		voice->inth = NULL;
-		voice->inthparam = NULL;
-	}
+
+	voice->sc = sc;
+	voice->state = !EMU_VOICE_STATE_STARTED;
+	voice->stereo = EMU_VOICE_STEREO_NOTSET;
+	voice->b16 = 0;
+	voice->sample_rate = 0;
+	if (use & EMU_VOICE_USE_PLAY)
+		voice->dataloc.chan[0] = voice->dataloc.chan[1] = NULL;
+	else
+		voice->dataloc.source = EMU_RECSRC_NOTSET;
+	voice->buffer = NULL;
+	voice->blksize = 0;
+	voice->trigblk = 0;
+	voice->blkmod = 0;
+	voice->inth = NULL;
+	voice->inthparam = NULL;
 	voice->use = use;
 
 	s = splaudio();
@@ -1300,9 +1348,14 @@
 emuxki_voice_set_stereo(struct emuxki_voice *voice, u_int8_t stereo)
 {
 	int	error;
+	emuxki_recsrc_t source;
 	struct emuxki_chanparms_fxsend fxsend;
 
+	if (! (voice->use & EMU_VOICE_USE_PLAY))
+		source = voice->dataloc.source;
 	emuxki_voice_dataloc_destroy(voice);
+	if (! (voice->use & EMU_VOICE_USE_PLAY))
+		voice->dataloc.source = source;
 	voice->stereo = stereo;
 	if ((error = emuxki_voice_dataloc_create(voice)))
 	  return (error);
@@ -1337,10 +1390,9 @@
 			emuxki_channel_set_srate(voice->dataloc.chan[1],
 						  srate);
 	} else {
-#ifdef EMUXKI_DEBUG
-		printf("Recording voice set_srate not implemented\n");
-#endif
-		return (EINVAL);
+		if (emuxki_recsrc_rate_to_index(srate) < 0)
+			return (EINVAL);
+		voice->sample_rate = srate;
 	}
 	return (0);
 }
@@ -1380,6 +1432,7 @@
 	struct emuxki_channel **chan;
 	u_int32_t start, end;
 	u_int8_t sample_size;
+	int idx;
 	int error = EFAULT;
 
 	LIST_FOREACH(mem, &voice->sc->mem, next) {
@@ -1388,7 +1441,6 @@
 
 		voice->buffer = mem;
 		sample_size = (voice->b16 + 1) * (voice->stereo + 1);
-		voice->blksize = blksize / sample_size;
 		voice->trigblk = 0;	/* This shouldn't be needed */
 		voice->blkmod = bufsize / blksize;
 		if (bufsize % blksize) 	  /* This should not happen */
@@ -1396,6 +1448,7 @@
 		error = 0;
 
 		if (voice->use & EMU_VOICE_USE_PLAY) {
+			voice->blksize = blksize / sample_size;
 			chan = voice->dataloc.chan;
 			start = mem->ptbidx << 12;
 			end = start + bufsize / sample_size;
@@ -1409,10 +1462,33 @@
 			if (voice->timerate < 5)
 				error = EINVAL;
 		} else {
+			voice->blksize = blksize;
+			for(idx = sizeof(emuxki_recbuf_sz) /
+			    sizeof(emuxki_recbuf_sz[0]); --idx >= 0;)
+				if (emuxki_recbuf_sz[idx] == bufsize)
+					break;
+			if (idx < 0) {
+#ifdef EMUXKI_DEBUG
+				printf("Invalid bufsize: %d\n", bufsize);
+#endif
+				return (EINVAL);
+}
+			emuxki_write(voice->sc, 0,
+			    emuxki_recsrc_szreg[voice->dataloc.source], idx);
+			emuxki_write(voice->sc, 0,
+			    emuxki_recsrc_bufaddrreg[voice->dataloc.source],
+			    DMAADDR(mem->dmamem));
+
+			/* Use timer to emulate DMA completion interrupt */
+			voice->timerate = (u_int32_t) 48000 * blksize /
+			    (voice->sample_rate * sample_size);
+			if (voice->timerate < 5) {
 #ifdef EMUXKI_DEBUG
-			printf("Rec voice set bufparms not implemented\n");
+				printf("Invalid timerate: %d, blksize %d\n",
+				    voice->timerate, blksize);
 #endif
-			error = ENODEV;
+				error = EINVAL;
+			}
 		}
 
 		break;
@@ -1434,11 +1510,26 @@
 static u_int32_t
 emuxki_voice_curaddr(struct emuxki_voice *voice)
 {
+
+	/* XXX different semantics in these cases */
 	if (voice->use & EMU_VOICE_USE_PLAY)
+		/* returns number of samples (an l/r pair counts 1) */
 		return (emuxki_read(voice->sc,
 				     voice->dataloc.chan[0]->num,
 				     EMU_CHAN_CCCA_CURRADDR) -
 			voice->dataloc.chan[0]->loop.start);
+	else
+		/* returns number of bytes */
+		/*
+		 * XXX Linux driver suggests something special is needed
+		 * XXX when precision == 8.
+		 * XXX It is not sure if subtracting DMAADDR is needed.
+		 */
+		return ((emuxki_read(voice->sc, 0,
+		    emuxki_recsrc_idxreg[voice->dataloc.source]) -
+		    DMAADDR(voice->buffer->dmamem)) &
+		    EMU_RECIDX_MASK);
+		
 	return (0);
 }
 
@@ -1452,8 +1543,7 @@
 
 	s = splaudio();
 	LIST_FOREACH(voice, &sc->voices, next) {
-		if ((voice->use & EMU_VOICE_USE_PLAY) == 0 ||
-		    (voice->state & EMU_VOICE_STATE_STARTED) == 0)
+		if ((voice->state & EMU_VOICE_STATE_STARTED) == 0)
 			continue;
 		active = 1;
 		if (voice->timerate < timerate)
@@ -1481,6 +1571,8 @@
 emuxki_voice_start(struct emuxki_voice *voice,
 		    void (*inth) (void *), void *inthparam)
 {
+	u_int32_t val;
+
 	voice->inth = inth;
 	voice->inthparam = inthparam;
 	if (voice->use & EMU_VOICE_USE_PLAY) {
@@ -1488,14 +1580,35 @@
 		emuxki_channel_start(voice->dataloc.chan[0]);
 		if (voice->stereo)
 			emuxki_channel_start(voice->dataloc.chan[1]);
-	}
-#ifdef EMUXKI_DEBUG
-	else
-		printf("Recording voice start not implemented\n");
+	} else {
+		switch ((int)voice->dataloc.source) {
+		case EMU_RECSRC_ADC:
+			val = EMU_ADCCR_LCHANENABLE;
+			/* XXX need to program DSP to output L+R
+			 * XXX in monaural case? */
+			if (voice->stereo)
+				val |= EMU_ADCCR_RCHANENABLE;
+			val |= emuxki_recsrc_rate_to_index(voice->sample_rate);
+                        emuxki_write(voice->sc, 0, EMU_ADCCR, 0);
+                        emuxki_write(voice->sc, 0, EMU_ADCCR, val);
+			break;
+		case EMU_RECSRC_MIC:
+		case EMU_RECSRC_FX:
+			printf("unimplemented\n");
+			break;
+		}
+#if 0
+		/* DMA completion interrupt is useless; use timer */
+		int s;
+		s = splaudio();
+                val = emu_rd(sc, INTE, 4);
+		val |= emuxki_recsrc_intrmasks[voice->dataloc.source];
+                emu_wr(sc, INTE, val, 4);
+		splx(s);
 #endif
+	}
 	voice->state |= EMU_VOICE_STATE_STARTED;
-	if (voice->use & EMU_VOICE_USE_PLAY)
-		emuxki_resched_timer(voice->sc);
+	emuxki_resched_timer(voice->sc);
 }
 
 static void
@@ -1505,14 +1618,29 @@
 		emuxki_channel_stop(voice->dataloc.chan[0]);
 		if (voice->stereo)
 			emuxki_channel_stop(voice->dataloc.chan[1]);
-	}
-#ifdef EMUXKI_DEBUG
-	else
-		printf("Recording voice halt not implemented\n");
+	} else {
+		switch (voice->dataloc.source) {
+		case EMU_RECSRC_ADC:
+                        emuxki_write(voice->sc, 0, EMU_ADCCR, 0);
+			break;
+		case EMU_RECSRC_FX:
+		case EMU_RECSRC_MIC:
+			printf("unimplemented\n");
+			break;
+		case EMU_RECSRC_NOTSET:
+			printf("Bad dataloc.source\n");
+		}
+#if 0
+		int s;
+		s = splaudio();
+                val = emu_rd(sc, INTE, 4);
+		val &= ~emuxki_recsrc_intrmasks[voice->dataloc.source];
+                emu_wr(sc, INTE, val, 4);
+		splx(s);
 #endif
+	}
 	voice->state &= ~EMU_VOICE_STATE_STARTED;
-	if (voice->use & EMU_VOICE_USE_PLAY)
-		emuxki_resched_timer(voice->sc);
+	emuxki_resched_timer(voice->sc);
 }
 
 /*
@@ -1528,8 +1656,7 @@
 	while ((ipr = bus_space_read_4(sc->sc_iot, sc->sc_ioh, EMU_IPR))) {
 		if (ipr & EMU_IPR_INTERVALTIMER) {
 			LIST_FOREACH(voice, &sc->voices, next) {
-				if ((voice->use & EMU_VOICE_USE_PLAY)==0 ||
-				    (voice->state &
+				if ((voice->state &
 				      EMU_VOICE_STATE_STARTED) == 0)
 					continue;
 
@@ -1576,8 +1703,14 @@
 	 * recording source(s) which is necessary when setting recording
 	 * params This will be adressed very soon
 	 */
-	if (flags & AUOPEN_READ)
-		return (EOPNOTSUPP);
+	if (flags & AUOPEN_READ) {
+		sc->rvoice = emuxki_voice_new(sc, 0 /* EMU_VOICE_USE_RECORD */);
+		if (sc->rvoice == NULL)
+			return (EBUSY);
+
+		/* XXX Hardcode RECSRC_ADC for now */
+		sc->rvoice->dataloc.source = EMU_RECSRC_ADC;
+	}
 
 	if (flags & AUOPEN_WRITE) {
 		sc->pvoice = emuxki_voice_new(sc, EMU_VOICE_USE_PLAY);
@@ -1890,15 +2023,40 @@
 	}
 }
 
+/* blocksize should be a divisor of allowable buffersize */
+/* XXX probably this could be done better */
+static int
+emuxki_round_blocksize(void *addr, int blksize)
+{
+#if 0
+	struct emuxki_softc *sc = addr;
+	struct audio_softc *au;
+#endif
+	int bufsize;
+#if 0
+	if (sc == NULL)
+		return blksize;
+
+	au = (void *)sc->sc_audev;
+	if (au == NULL)
+		return blksize;
+
+	bufsize = emuxki_round_buffersize(sc, AUMODE_RECORD,
+	    au->sc_rr.bufsize);
+#else
+	bufsize = 65536;
+#endif
+	printf("emuxki_round_blocksize: blksize %d\n", blksize);
+
+	while (bufsize > blksize)
+		bufsize /= 2;
+
+	return bufsize;
+}
+	
 static size_t
 emuxki_round_buffersize(void *addr, int direction, size_t bsize)
 {
-	static const int recbuf_sz[] = {
-		0, 384, 448, 512, 640, 768, 896, 1024, 1280, 1536, 1792,
-		2048, 2560, 3072, 3584, 4096, 5120, 6144, 7168, 8192, 10240,
-		12288, 14366, 16384, 20480, 24576, 28672, 32768, 40960, 49152,
-		57344, 65536
-	};
 
 	if (direction == AUMODE_PLAY) {
 		if (bsize < EMU_PTESIZE)
@@ -1914,9 +2072,10 @@
 		int idx;
 
 		/* find nearest lower recbuf size */
-		for(idx=32; --idx >= 0; ) {
-			if (bsize >= recbuf_sz[idx]) {
-				bsize = recbuf_sz[idx];
+		for(idx = sizeof(emuxki_recbuf_sz) /
+		    sizeof(emuxki_recbuf_sz[0]); --idx >= 0; ) {
+			if (bsize >= emuxki_recbuf_sz[idx]) {
+				bsize = emuxki_recbuf_sz[idx];
 				break;
 			}
 		}
@@ -1992,11 +2151,13 @@
 		return (ENXIO);
 	if ((error = emuxki_set_vparms(voice, params)))
 		return (error);
+#ifdef EMUXKI_DEBUG
+	memset(start, 1, (caddr_t)end - (caddr_t)start);
+#endif
 	if ((error = emuxki_voice_set_bufparms(voice, start,
 						(caddr_t)end - (caddr_t)start,
 						blksize)))
 		return (error);
-	emuxki_voice_commit_parms(voice); /* Useless for record ? */
 	emuxki_voice_start(voice, inth, inthparam);
 
 	return (0);
>Release-Note:
>Audit-Trail:
>Unformatted: