diff -Nru linux-2.6.10-kdb/sound/pci/emu10k1/emu10k1.c /home/rlrevell/cvs/alsa-cvs/alsa-kernel/pci/emu10k1/emu10k1.c
--- linux-2.6.10-kdb/sound/pci/emu10k1/emu10k1.c	2005-01-18 22:28:22.000000000 -0500
+++ /home/rlrevell/cvs/alsa-cvs/alsa-kernel/pci/emu10k1/emu10k1.c	2005-01-24 20:12:45.000000000 -0500
@@ -149,6 +149,11 @@
 		return err;
 	}
 
+	if ((err = snd_emu10k1_pcm_multi(emu, 3, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}		
+
 	if (emu->audigy) {
 		if ((err = snd_emu10k1_audigy_midi(emu)) < 0) {
 			snd_card_free(card);
diff -Nru linux-2.6.10-kdb/sound/pci/emu10k1/emu10k1_callback.c /home/rlrevell/cvs/alsa-cvs/alsa-kernel/pci/emu10k1/emu10k1_callback.c
--- linux-2.6.10-kdb/sound/pci/emu10k1/emu10k1_callback.c	2004-12-24 16:34:30.000000000 -0500
+++ /home/rlrevell/cvs/alsa-cvs/alsa-kernel/pci/emu10k1/emu10k1_callback.c	2005-01-24 20:12:45.000000000 -0500
@@ -291,7 +291,7 @@
 			if (vp->ch < 0) {
 				/* allocate a voice */
 				emu10k1_voice_t *hwvoice;
-				if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 0, &hwvoice) < 0 || hwvoice == NULL)
+				if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 1, &hwvoice) < 0 || hwvoice == NULL)
 					continue;
 				vp->ch = hwvoice->number;
 				emu->num_voices++;
diff -Nru linux-2.6.10-kdb/sound/pci/emu10k1/emufx.c /home/rlrevell/cvs/alsa-cvs/alsa-kernel/pci/emu10k1/emufx.c
--- linux-2.6.10-kdb/sound/pci/emu10k1/emufx.c	2005-01-18 22:28:23.000000000 -0500
+++ /home/rlrevell/cvs/alsa-cvs/alsa-kernel/pci/emu10k1/emufx.c	2005-01-24 20:12:45.000000000 -0500
@@ -489,7 +489,7 @@
 #define A_OP(icode, ptr, op, r, a, x, y) \
 	snd_emu10k1_audigy_write_op(icode, ptr, op, r, a, x, y)
 
-static void snd_emu10k1_efx_write(emu10k1_t *emu, unsigned int pc, unsigned int data)
+void snd_emu10k1_efx_write(emu10k1_t *emu, unsigned int pc, unsigned int data)
 {
 	pc += emu->audigy ? A_MICROCODEBASE : MICROCODEBASE;
 	snd_emu10k1_ptr_write(emu, pc, 0, data);
@@ -1351,12 +1351,8 @@
 	A_PUT_OUTPUT(A_EXTOUT_LFE, playback+5 + SND_EMU10K1_PLAYBACK_CHANNELS);
 
 	/* ADC buffer */
-#ifdef EMU10K1_CAPTURE_DIGITAL_OUT
-	A_PUT_STEREO_OUTPUT(A_EXTOUT_ADC_CAP_L, A_EXTOUT_ADC_CAP_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS);
-#else
 	A_PUT_OUTPUT(A_EXTOUT_ADC_CAP_L, capture);
 	A_PUT_OUTPUT(A_EXTOUT_ADC_CAP_R, capture+1);
-#endif
 
 	/*
 	 * ok, set up done..
@@ -2014,7 +2010,7 @@
 		}
 		size = 0x2000 << size_reg;
 	}
-	if ((emu->fx8010.etram_pages.bytes / 2) == size)
+	if (emu->fx8010.etram_pages.bytes == size)
 		return 0;
 	spin_lock_irq(&emu->emu_lock);
 	outl(HCFG_LOCKTANKCACHE_MASK | inl(emu->port + HCFG), emu->port + HCFG);
diff -Nru linux-2.6.10-kdb/sound/pci/emu10k1/emumixer.c /home/rlrevell/cvs/alsa-cvs/alsa-kernel/pci/emu10k1/emumixer.c
--- linux-2.6.10-kdb/sound/pci/emu10k1/emumixer.c	2005-01-18 22:28:23.000000000 -0500
+++ /home/rlrevell/cvs/alsa-cvs/alsa-kernel/pci/emu10k1/emumixer.c	2005-01-24 20:12:45.000000000 -0500
@@ -202,6 +202,59 @@
 	return change;
 }
 
+static int snd_emu10k1_efx_send_routing_get(snd_kcontrol_t * kcontrol,
+                                        snd_ctl_elem_value_t * ucontrol)
+{
+	unsigned long flags;
+	emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+	emu10k1_pcm_mixer_t *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+	int voice, idx;
+	int num_efx = emu->audigy ? 8 : 4;
+	int mask = emu->audigy ? 0x3f : 0x0f;
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	for (voice = 0; voice < 3; voice++)
+		for (idx = 0; idx < num_efx; idx++)
+			ucontrol->value.integer.value[(voice * num_efx) + idx] = 
+				mix->send_routing[voice][idx] & mask;
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+        return 0;
+}
+
+static int snd_emu10k1_efx_send_routing_put(snd_kcontrol_t * kcontrol,
+                                        snd_ctl_elem_value_t * ucontrol)
+{
+	unsigned long flags;
+	emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+	emu10k1_pcm_mixer_t *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+	int change = 0, voice, idx, val;
+	int num_efx = emu->audigy ? 8 : 4;
+	int mask = emu->audigy ? 0x3f : 0x0f;
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	for (voice = 0; voice < 3; voice++)
+		for (idx = 0; idx < num_efx; idx++) {
+			val = ucontrol->value.integer.value[(voice * num_efx) + idx] & mask;
+			if (mix->send_routing[voice][idx] != val) {
+				mix->send_routing[voice][idx] = val;
+				change = 1;
+			}
+		}	
+	if (change && mix->epcm) {
+		if (mix->epcm->voices[0] && mix->epcm->voices[1]) {
+			update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number,
+					    &mix->send_routing[1][0]);
+			update_emu10k1_fxrt(emu, mix->epcm->voices[1]->number,
+					    &mix->send_routing[2][0]);
+		} else if (mix->epcm->voices[0]) {
+			update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number,
+					    &mix->send_routing[0][0]);
+		}
+	}
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+        return change;
+}
+
 static snd_kcontrol_new_t snd_emu10k1_send_routing_control =
 {
 	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
@@ -213,6 +266,65 @@
 	.put =          snd_emu10k1_send_routing_put
 };
 
+static int snd_emu10k1_efx_send_volume_get(snd_kcontrol_t * kcontrol,
+                                       snd_ctl_elem_value_t * ucontrol)
+{
+	unsigned long flags;
+	emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+	emu10k1_pcm_mixer_t *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+	int idx;
+	int num_efx = emu->audigy ? 8 : 4;
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	for (idx = 0; idx < 3*num_efx; idx++)
+		ucontrol->value.integer.value[idx] = mix->send_volume[idx/num_efx][idx%num_efx];
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+        return 0;
+}
+
+static int snd_emu10k1_efx_send_volume_put(snd_kcontrol_t * kcontrol,
+                                       snd_ctl_elem_value_t * ucontrol)
+{
+	unsigned long flags;
+	emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+	emu10k1_pcm_mixer_t *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+	int change = 0, idx, val;
+	int num_efx = emu->audigy ? 8 : 4;
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	for (idx = 0; idx < 3*num_efx; idx++) {
+		val = ucontrol->value.integer.value[idx] & 255;
+		if (mix->send_volume[idx/num_efx][idx%num_efx] != val) {
+			mix->send_volume[idx/num_efx][idx%num_efx] = val;
+			change = 1;
+		}
+	}
+	if (change && mix->epcm) {
+		if (mix->epcm->voices[0] && mix->epcm->voices[1]) {
+			update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number,
+						   &mix->send_volume[1][0]);
+			update_emu10k1_send_volume(emu, mix->epcm->voices[1]->number,
+						   &mix->send_volume[2][0]);
+		} else if (mix->epcm->voices[0]) {
+			update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number,
+						   &mix->send_volume[0][0]);
+		}
+	}
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+        return change;
+}
+
+static snd_kcontrol_new_t snd_emu10k1_efx_send_routing_control =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+        .iface =        SNDRV_CTL_ELEM_IFACE_MIXER,
+        .name =         "Multichannel PCM Send Routing",
+	.count =	16,
+        .info =         snd_emu10k1_send_routing_info,
+        .get =          snd_emu10k1_efx_send_routing_get,
+        .put =          snd_emu10k1_efx_send_routing_put
+};
+
 static int snd_emu10k1_send_volume_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
 {
 	emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
@@ -282,6 +394,17 @@
 	.put =          snd_emu10k1_send_volume_put
 };
 
+static snd_kcontrol_new_t snd_emu10k1_efx_send_volume_control =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+        .iface =        SNDRV_CTL_ELEM_IFACE_MIXER,
+        .name =         "Multichannel PCM Send Volume",
+	.count =	16,
+        .info =         snd_emu10k1_send_volume_info,
+        .get =          snd_emu10k1_efx_send_volume_get,
+        .put =          snd_emu10k1_efx_send_volume_put
+};
+
 static int snd_emu10k1_attn_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
 {
 	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
@@ -334,6 +457,49 @@
 	return change;
 }
 
+static int snd_emu10k1_efx_attn_get(snd_kcontrol_t * kcontrol,
+                                snd_ctl_elem_value_t * ucontrol)
+{
+	emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+	emu10k1_pcm_mixer_t *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+	unsigned long flags;
+	int idx;
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	for (idx = 0; idx < 3; idx++)
+		ucontrol->value.integer.value[idx] = mix->attn[idx];
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+        return 0;
+}
+
+static int snd_emu10k1_efx_attn_put(snd_kcontrol_t * kcontrol,
+				snd_ctl_elem_value_t * ucontrol)
+{
+	unsigned long flags;
+	emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
+	emu10k1_pcm_mixer_t *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+	int change = 0, idx, val;
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	for (idx = 0; idx < 3; idx++) {
+		val = ucontrol->value.integer.value[idx] & 0xffff;
+		if (mix->attn[idx] != val) {
+			mix->attn[idx] = val;
+			change = 1;
+		}
+	}
+	if (change && mix->epcm) {
+		if (mix->epcm->voices[0] && mix->epcm->voices[1]) {
+			snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number, mix->attn[1]);
+			snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[1]->number, mix->attn[2]);
+		} else if (mix->epcm->voices[0]) {
+			snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number, mix->attn[0]);
+		}
+	}
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+        return change;
+}
+
 static snd_kcontrol_new_t snd_emu10k1_attn_control =
 {
 	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
@@ -345,6 +511,17 @@
 	.put =          snd_emu10k1_attn_put
 };
 
+static snd_kcontrol_new_t snd_emu10k1_efx_attn_control =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+        .iface =        SNDRV_CTL_ELEM_IFACE_MIXER,
+        .name =         "Multichannel PCM Volume",
+	.count =	16,
+        .info =         snd_emu10k1_attn_info,
+        .get =          snd_emu10k1_efx_attn_get,
+        .put =          snd_emu10k1_efx_attn_put
+};
+
 static int snd_emu10k1_shared_spdif_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
 {
 	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
@@ -558,10 +735,8 @@
 			strcpy(emu->card->mixername, "Emu10k1");
 	}
 
-	if (emu->audigy)
-		c = audigy_rename_ctls;
-	else
-		c = emu10k1_rename_ctls;
+
+	c = (emu->audigy) ? audigy_rename_ctls : emu10k1_rename_ctls;
 	for (; *c; c += 2)
 		rename_ctl(card, c[0], c[1]);
 
@@ -569,16 +744,33 @@
 		return -ENOMEM;
 	if ((err = snd_ctl_add(card, kctl)))
 		return err;
+	
 	if ((kctl = emu->ctl_send_volume = snd_ctl_new1(&snd_emu10k1_send_volume_control, emu)) == NULL)
 		return -ENOMEM;
 	if ((err = snd_ctl_add(card, kctl)))
 		return err;
+	
 	if ((kctl = emu->ctl_attn = snd_ctl_new1(&snd_emu10k1_attn_control, emu)) == NULL)
 		return -ENOMEM;
 	if ((err = snd_ctl_add(card, kctl)))
 		return err;
 
-	/* intiailize the routing and volume table for each pcm playback stream */
+	if ((kctl = emu->ctl_efx_send_routing = snd_ctl_new1(&snd_emu10k1_efx_send_routing_control, emu)) == NULL)
+		return -ENOMEM;
+	if ((err = snd_ctl_add(card, kctl)))
+		return err;
+	
+	if ((kctl = emu->ctl_efx_send_volume = snd_ctl_new1(&snd_emu10k1_efx_send_volume_control, emu)) == NULL)
+		return -ENOMEM;
+	if ((err = snd_ctl_add(card, kctl)))
+		return err;
+	
+	if ((kctl = emu->ctl_efx_attn = snd_ctl_new1(&snd_emu10k1_efx_attn_control, emu)) == NULL)
+		return -ENOMEM;
+	if ((err = snd_ctl_add(card, kctl)))
+		return err;
+
+	/* initialize the routing and volume table for each pcm playback stream */
 	for (pcm = 0; pcm < 32; pcm++) {
 		emu10k1_pcm_mixer_t *mix;
 		int v;
@@ -598,6 +790,34 @@
 		mix->attn[0] = mix->attn[1] = mix->attn[2] = 0xffff;
 	}
 	
+	/* initialize the routing and volume table for the multichannel playback stream */
+	for (pcm = 0; pcm < 16; pcm++) {
+		emu10k1_pcm_mixer_t *mix;
+		int v;
+		
+		mix = &emu->efx_pcm_mixer[pcm];
+		mix->epcm = NULL;
+
+		mix->send_routing[0][0] = mix->send_routing[1][0]
+			= mix->send_routing[2][0] = pcm;
+		mix->send_routing[0][1] = mix->send_routing[1][1]
+			= mix->send_routing[2][1] = (pcm == 0) ? 1 : 0;
+		for (v = 0; v < 2; v++)
+			mix->send_routing[0][2+v] = 
+				mix->send_routing[1][2+v] = 
+				mix->send_routing[2][2+v] = 13+v;
+		for (v = 0; v < 4; v++)
+			mix->send_routing[0][4+v] = 
+				mix->send_routing[1][4+v] = 
+				mix->send_routing[2][4+v] = 60+v;
+		
+		memset(&mix->send_volume, 0, sizeof(mix->send_volume));
+		mix->send_volume[0][0] =
+		mix->send_volume[1][0] = mix->send_volume[2][0] = 255;
+		
+		mix->attn[0] = mix->attn[1] = mix->attn[2] = 0xffff;
+	}
+	
 	if (! emu->APS) { /* FIXME: APS has these controls? */
 		/* sb live! and audigy */
 		if ((kctl = snd_ctl_new1(&snd_emu10k1_spdif_mask_control, emu)) == NULL)
diff -Nru linux-2.6.10-kdb/sound/pci/emu10k1/emupcm.c /home/rlrevell/cvs/alsa-cvs/alsa-kernel/pci/emu10k1/emupcm.c
--- linux-2.6.10-kdb/sound/pci/emu10k1/emupcm.c	2005-01-18 22:28:23.000000000 -0500
+++ /home/rlrevell/cvs/alsa-cvs/alsa-kernel/pci/emu10k1/emupcm.c	2005-01-24 20:12:45.000000000 -0500
@@ -33,6 +33,7 @@
 #include <linux/init.h>
 #include <sound/core.h>
 #include <sound/emu10k1.h>
+#include <asm/current.h>
 
 static void snd_emu10k1_pcm_interrupt(emu10k1_t *emu, emu10k1_voice_t *voice)
 {
@@ -42,51 +43,49 @@
 		return;
 	if (epcm->substream == NULL)
 		return;
-#if 0
-	printk("IRQ: position = 0x%x, period = 0x%x, size = 0x%x\n",
-			epcm->substream->runtime->hw->pointer(emu, epcm->substream),
-			snd_pcm_lib_period_bytes(epcm->substream),
-			snd_pcm_lib_buffer_bytes(epcm->substream));
-#endif
+	
 	snd_pcm_period_elapsed(epcm->substream);
 }
 
 static void snd_emu10k1_pcm_ac97adc_interrupt(emu10k1_t *emu, unsigned int status)
 {
-#if 0
-	if (status & IPR_ADCBUFHALFFULL) {
-		if (emu->pcm_capture_substream->runtime->mode == SNDRV_PCM_MODE_FRAME)
-			return;
-	}
-#endif
 	snd_pcm_period_elapsed(emu->pcm_capture_substream);
 }
 
 static void snd_emu10k1_pcm_ac97mic_interrupt(emu10k1_t *emu, unsigned int status)
 {
-#if 0
-	if (status & IPR_MICBUFHALFFULL) {
-		if (emu->pcm_capture_mic_substream->runtime->mode == SNDRV_PCM_MODE_FRAME)
-			return;
-	}
-#endif
 	snd_pcm_period_elapsed(emu->pcm_capture_mic_substream);
 }
 
 static void snd_emu10k1_pcm_efx_interrupt(emu10k1_t *emu, unsigned int status)
 {
-#if 0
-	if (status & IPR_EFXBUFHALFFULL) {
-		if (emu->pcm_capture_efx_substream->runtime->mode == SNDRV_PCM_MODE_FRAME)
-			return;
-	}
-#endif
 	snd_pcm_period_elapsed(emu->pcm_capture_efx_substream);
+}	 
+
+static snd_pcm_uframes_t snd_emu10k1_efx_playback_pointer(snd_pcm_substream_t * substream)
+{
+	emu10k1_t *emu = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	emu10k1_pcm_t *epcm = runtime->private_data;
+	unsigned int ptr;
+
+	if (!epcm->running)
+		return 0;
+	ptr = snd_emu10k1_ptr_read(emu, CCCA, epcm->voices[0]->number) & 0x00ffffff;
+	ptr += runtime->buffer_size;
+	ptr -= epcm->ccca_start_addr;
+	ptr %= runtime->buffer_size;
+
+#if 0	
+	printk("efx_playback_pointer is %i\n", ptr); 
+#endif	
+	return ptr;
 }
 
 static int snd_emu10k1_pcm_channel_alloc(emu10k1_pcm_t * epcm, int voices)
 {
-	int err;
+	int err, i;
+	printk("pcm_channel_alloc: voices=%d\n", voices);
 
 	if (epcm->voices[1] != NULL && voices < 2) {
 		snd_emu10k1_voice_free(epcm->emu, epcm->voices[1]);
@@ -102,16 +101,25 @@
 			epcm->voices[0] = NULL;
 		}
 	}
-	err = snd_emu10k1_voice_alloc(epcm->emu, EMU10K1_PCM, voices > 1, &epcm->voices[0]);
+	if (epcm->type == PLAYBACK_EMUVOICE) {
+	    err = snd_emu10k1_voice_alloc(epcm->emu, EMU10K1_PCM, voices, &epcm->voices[0]);
+	} else if (epcm->type == PLAYBACK_EFX) {
+	    err = snd_emu10k1_voice_alloc(epcm->emu, EMU10K1_EFX, voices, &epcm->voices[0]);
+	} else {
+	    err = 0;
+	}
+	
 	if (err < 0)
 		return err;
 	epcm->voices[0]->epcm = epcm;
 	if (voices > 1) {
-		epcm->voices[1] = &epcm->emu->voices[epcm->voices[0]->number + 1];
-		epcm->voices[1]->epcm = epcm;
+		for (i = 1; i < voices; i++) {
+		    epcm->voices[i] = &epcm->emu->voices[epcm->voices[0]->number + i];
+		    epcm->voices[i]->epcm = epcm;
+		}
 	}
 	if (epcm->extra == NULL) {
-		err = snd_emu10k1_voice_alloc(epcm->emu, EMU10K1_PCM, 0, &epcm->extra);
+		err = snd_emu10k1_voice_alloc(epcm->emu, EMU10K1_PCM, 1, &epcm->extra);
 		if (err < 0) {
 			// printk("pcm_channel_alloc: failed extra: voices=%d, frame=%d\n", voices, frame);
 			snd_emu10k1_voice_free(epcm->emu, epcm->voices[0]);
@@ -234,7 +242,7 @@
 {
 	snd_pcm_substream_t *substream = evoice->epcm->substream;
 	snd_pcm_runtime_t *runtime = substream->runtime;
-	emu10k1_pcm_mixer_t *mix = &emu->pcm_mixer[substream->number];
+	emu10k1_pcm_mixer_t *mix;
 	unsigned int silent_page, tmp;
 	int voice, stereo, w_16;
 	unsigned char attn, send_amount[8];
@@ -243,6 +251,11 @@
 	unsigned int pitch_target;
 
 	voice = evoice->number;
+	if (evoice->epcm->type == PLAYBACK_EFX) 
+	    mix = &emu->efx_pcm_mixer[voice - evoice->epcm->voices[0]->number];
+	else
+	    mix = &emu->pcm_mixer[substream->number];
+
 	stereo = runtime->channels == 2;
 	w_16 = snd_pcm_format_width(runtime->format) == 16;
 
@@ -255,6 +268,12 @@
 		end_addr >>= 1;
 	}
 
+	printk("init voice - master %i extra %i start_addr %i end_addr %i\n",
+		master,
+		extra,
+		start_addr,
+		end_addr);
+
 	spin_lock_irqsave(&emu->reg_lock, flags);
 
 	/* volume parameters */
@@ -273,10 +292,11 @@
 		memcpy(send_amount, &mix->send_volume[tmp][0], 8);
 	}
 
+	unsigned int ccis = stereo ? 28 : 30;
+	if (w_16)
+	    ccis *= 2;
+	
 	if (master) {
-		unsigned int ccis = stereo ? 28 : 30;
-		if (w_16)
-			ccis *= 2;
 		evoice->epcm->ccca_start_addr = start_addr + ccis;
 		if (extra) {
 			start_addr += ccis;
@@ -310,7 +330,12 @@
 	snd_emu10k1_ptr_write(emu, DSL, voice, end_addr | (send_amount[3] << 24));
 	snd_emu10k1_ptr_write(emu, PSST, voice, start_addr | (send_amount[2] << 24));
 	pitch_target = emu10k1_calc_pitch_target(runtime->rate);
-	snd_emu10k1_ptr_write(emu, CCCA, voice, evoice->epcm->ccca_start_addr |
+	if (extra)
+		snd_emu10k1_ptr_write(emu, CCCA, voice, start_addr |
+			      emu10k1_select_interprom(pitch_target) |
+			      (w_16 ? 0 : CCCA_8BITSELECT));
+	else
+		snd_emu10k1_ptr_write(emu, CCCA, voice, (start_addr + ccis) |
 			      emu10k1_select_interprom(pitch_target) |
 			      (w_16 ? 0 : CCCA_8BITSELECT));
 	// Clear filter delay memory
@@ -398,6 +423,35 @@
 	return 0;
 }
 
+static int snd_emu10k1_efx_playback_hw_free(snd_pcm_substream_t * substream)
+{
+	emu10k1_t *emu = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	emu10k1_pcm_t *epcm;
+	int i;
+
+	if (runtime->private_data == NULL)
+		return 0;
+	epcm = runtime->private_data;
+	if (epcm->extra) {
+		snd_emu10k1_voice_free(epcm->emu, epcm->extra);
+		epcm->extra = NULL;
+	}
+	for (i=0; i < NUM_EFX_PLAYBACK; i++) {
+	    if (epcm->voices[i]) {
+		snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]);
+		epcm->voices[i] = NULL;
+	    }
+	}
+	if (epcm->memblk) {
+		snd_emu10k1_free_pages(emu, epcm->memblk);
+		epcm->memblk = NULL;
+		epcm->start_addr = 0;
+	}
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
 static int snd_emu10k1_playback_prepare(snd_pcm_substream_t * substream)
 {
 	emu10k1_t *emu = snd_pcm_substream_chip(substream);
@@ -421,6 +475,62 @@
 	return 0;
 }
 
+static int snd_emu10k1_efx_playback_prepare(snd_pcm_substream_t * substream)
+{
+	emu10k1_t *emu = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	emu10k1_pcm_t *epcm = runtime->private_data;
+	unsigned int start_addr, end_addr;
+
+	start_addr = epcm->start_addr;
+	end_addr = epcm->start_addr + snd_pcm_lib_buffer_bytes(substream);
+	printk("in snd_emu10k1_efx_playback_prepare - start_addr 0x%x end_addr 0x%x\n", start_addr,end_addr);
+	/*
+	 * now we need to divide the buffer into NUM_EFX_PLAYBACK chunks and initialize 
+	 * NUM_EFX_PLAYBACK voices to make the noninterleaved stream
+	 *
+	 * the kX driver leaves some space between voices
+	 */
+	unsigned int channel_size;
+	int i;
+	channel_size = ( end_addr - start_addr ) / NUM_EFX_PLAYBACK;
+
+	snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra,
+				   start_addr, start_addr + (channel_size / 2));
+
+	/* only difference with the master voice is we use it for the pointer */
+	snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0],
+				   start_addr, start_addr + channel_size);
+
+	start_addr += channel_size;
+	for (i = 1; i < NUM_EFX_PLAYBACK; i++) {
+	    snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[i],
+	    			   start_addr, start_addr+channel_size);
+	    start_addr += channel_size;
+	}
+
+	return 0;
+}
+
+static snd_pcm_hardware_t snd_emu10k1_efx_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_NONINTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_SYNC_START */ ),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_48000,
+	.rate_min =		48000,
+	.rate_max =		48000,
+	.channels_min =		NUM_EFX_PLAYBACK,
+	.channels_max =		NUM_EFX_PLAYBACK,
+	.buffer_bytes_max =	(64*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(64*1024),
+	.periods_min =		2,
+	.periods_max =		2,
+	.fifo_size =		0,
+};
+
 static int snd_emu10k1_capture_hw_params(snd_pcm_substream_t * substream,
 					 snd_pcm_hw_params_t * hw_params)
 {
@@ -439,6 +549,7 @@
 	emu10k1_pcm_t *epcm = runtime->private_data;
 	int idx;
 
+	/* zeroing the buffer size will stop capture */
 	snd_emu10k1_ptr_write(emu, epcm->capture_bs_reg, 0, 0);
 	switch (epcm->type) {
 	case CAPTURE_AC97ADC:
@@ -488,7 +599,7 @@
 	runtime = evoice->epcm->substream->runtime;
 	voice = evoice->number;
 	sample = snd_pcm_format_width(runtime->format) == 16 ? 0 : 0x80808080;
-	if (runtime->channels > 1) {
+	if (runtime->channels == 2) {
 		ccis = 28;
 		cs = 4;
 	} else {
@@ -499,10 +610,11 @@
 		ccis *= 2;
 	for (i = 0; i < cs; i++)
 		snd_emu10k1_ptr_write(emu, CD0 + i, voice, sample);
+		
 	// reset cache
 	snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice, 0);
 	snd_emu10k1_ptr_write(emu, CCR_READADDRESS, voice, cra);
-	if (runtime->channels > 1) {
+	if (runtime->channels == 2) {
 		snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice + 1, 0);
 		snd_emu10k1_ptr_write(emu, CCR_READADDRESS, voice + 1, cra);
 	}
@@ -522,8 +634,12 @@
 		return;
 	substream = evoice->epcm->substream;
 	runtime = substream->runtime;
-	mix = &emu->pcm_mixer[substream->number];
 	voice = evoice->number;
+
+	mix = evoice->epcm->type == PLAYBACK_EFX
+		? &emu->efx_pcm_mixer[voice - evoice->epcm->voices[0]->number]
+		: &emu->pcm_mixer[substream->number];
+
 	pitch = snd_emu10k1_rate_to_pitch(runtime->rate) >> 8;
 	pitch_target = emu10k1_calc_pitch_target(runtime->rate);
 	attn = extra ? 0 : 0x00ff;
@@ -532,11 +648,15 @@
 	snd_emu10k1_ptr_write(emu, VTFT, voice, (mix->attn[tmp] << 16) | 0xffff);
 	snd_emu10k1_ptr_write(emu, CVCF, voice, (mix->attn[tmp] << 16) | 0xffff);
 	snd_emu10k1_voice_clear_loop_stop(emu, voice);		
-	if (extra)
-		snd_emu10k1_voice_intr_enable(emu, voice);
+	if (extra) {
+	//	if (evoice->epcm->type == PLAYBACK_EFX)
+	//		snd_emu10k1_voice_half_loop_intr_enable(emu, voice);
+	//	else
+			snd_emu10k1_voice_intr_enable(emu, voice);
+	}
 	snd_emu10k1_ptr_write(emu, DCYSUSV, voice, 0x7f7f);
 	snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, pitch_target);
-	if (master)
+	if (master || evoice->epcm->type == PLAYBACK_EFX)
 		snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, pitch_target);
 	snd_emu10k1_ptr_write(emu, IP, voice, pitch);
 }
@@ -548,7 +668,10 @@
 	if (evoice == NULL)
 		return;
 	voice = evoice->number;
-	snd_emu10k1_voice_intr_disable(emu, voice);
+//	if (evoice->epcm->type == PLAYBACK_EFX)
+//		snd_emu10k1_voice_half_loop_intr_disable(emu, voice);
+//	else
+		snd_emu10k1_voice_intr_disable(emu, voice);
 	snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, 0);
 	snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, 0);
 	snd_emu10k1_ptr_write(emu, IFATN, voice, 0xffff);
@@ -564,7 +687,6 @@
 	snd_pcm_runtime_t *runtime = substream->runtime;
 	emu10k1_pcm_t *epcm = runtime->private_data;
 	int result = 0;
-
 	// printk("trigger - emu10k1 = 0x%x, cmd = %i, pointer = %i\n", (int)emu, cmd, substream->ops->pointer(substream));
 	spin_lock(&emu->reg_lock);
 	switch (cmd) {
@@ -601,10 +723,17 @@
 	emu10k1_pcm_t *epcm = runtime->private_data;
 	int result = 0;
 
-	// printk("trigger - emu10k1 = %p, cmd = %i, pointer = %i\n", emu, cmd, substream->ops->pointer(substream));
+	/*printk("capture trigger - emu10k1 = 0x%x, cmd = %i, pointer = %i, WC = %i\n", 
+	    (int)emu, 
+	    cmd, 
+	    (int)substream->ops->pointer(substream), 
+	    snd_emu10k1_wc(emu));
+	*/
+	
 	spin_lock(&emu->reg_lock);
 	switch (cmd) {
 	case SNDRV_PCM_TRIGGER_START:
+		// hmm this should cause full and half full interrupt to be raised?  
 		outl(epcm->capture_ipr, emu->port + IPR);
 		snd_emu10k1_intr_enable(emu, epcm->capture_inte);
 		// printk("adccr = 0x%x, adcbs = 0x%x\n", epcm->adccr, epcm->adcbs);
@@ -680,6 +809,65 @@
 	return ptr;
 }
 
+
+static int snd_emu10k1_efx_playback_trigger(snd_pcm_substream_t * substream,
+				        int cmd)
+{
+	emu10k1_t *emu = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	emu10k1_pcm_t *epcm = runtime->private_data;
+	int i = 0;
+	int result = 0;
+        unsigned int ptr0;
+
+	//printk("efx playback trigger. emu10k1 = 0x%x, cmd = %i, pointer = %i, WC = %i, exclusive %i\n", 
+	//    (int)emu, 
+	//    cmd, 
+	//    (int)substream->ops->pointer(substream), 
+	//    snd_emu10k1_wc(emu),
+	//    snd_emu10k1_efx_use_is_exclusive(emu));
+
+	spin_lock(&emu->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		// prepare voices
+		for (i = 0; i < NUM_EFX_PLAYBACK; i++) {	
+		    snd_emu10k1_playback_invalidate_cache(emu, epcm->voices[i]);
+		}
+		snd_emu10k1_playback_invalidate_cache(emu, epcm->extra);
+
+		/* follow thru */
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 0, 0);
+		snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1, 1);
+		for (i = 1; i < NUM_EFX_PLAYBACK; i++) {	
+		    snd_emu10k1_playback_trigger_voice(emu, epcm->voices[i], 0, 0);
+		}
+		epcm->running = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		epcm->running = 0;
+		for (i = 0; i < NUM_EFX_PLAYBACK; i++) {	
+			ptr0 = snd_emu10k1_ptr_read(emu, CCCA, epcm->voices[i]->number) & 0x00ffffff;
+			printk("stopping.                   channel %i curraddr = %i\n", 
+				i, 
+				ptr0);
+		} 
+		for (i = 0; i < NUM_EFX_PLAYBACK; i++) {	
+		    snd_emu10k1_playback_stop_voice(emu, epcm->voices[i]);
+		}
+		snd_emu10k1_playback_stop_voice(emu, epcm->extra);
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+	spin_unlock(&emu->reg_lock);
+	return result;
+}
+
+
 static snd_pcm_uframes_t snd_emu10k1_capture_pointer(snd_pcm_substream_t * substream)
 {
 	emu10k1_t *emu = snd_pcm_substream_chip(substream);
@@ -693,8 +881,15 @@
 		udelay(50);	// hack, it takes awhile until capture is started
 		epcm->first_ptr = 0;
 	}
-	ptr = snd_emu10k1_ptr_read(emu, epcm->capture_idx_reg, 0) & 0x0000ffff;
-	return bytes_to_frames(runtime, ptr);
+	ptr = bytes_to_frames(runtime, (snd_emu10k1_ptr_read(emu, epcm->capture_idx_reg, 0) & 0x0000ffff));
+	
+#if 0 
+	printk("capture_pointer: %i frames (%i WC ticks) since last irq\n", 
+		ptr, 
+		snd_emu10k1_wc(emu));
+#endif
+
+	return ptr;
 }
 
 /*
@@ -728,7 +923,7 @@
 {
 	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
 				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
-				 SNDRV_PCM_INFO_MMAP_VALID),
+				 SNDRV_PCM_INFO_MMAP_VALID /* | SNDRV_PCM_INFO_SYNC_START */),
 	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
 	.rates =		SNDRV_PCM_RATE_8000_48000,
 	.rate_min =		8000,
@@ -768,6 +963,13 @@
 	snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_attn, idx, activate);
 }
 
+static void snd_emu10k1_pcm_efx_mixer_notify(emu10k1_t *emu, int idx, int activate)
+{
+	snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_efx_send_routing, idx, activate);
+	snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_efx_send_volume, idx, activate);
+	snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_efx_attn, idx, activate);
+}
+
 static void snd_emu10k1_pcm_free_substream(snd_pcm_runtime_t *runtime)
 {
 	emu10k1_pcm_t *epcm = runtime->private_data;
@@ -775,6 +977,54 @@
 	kfree(epcm);
 }
 
+static int snd_emu10k1_efx_playback_close(snd_pcm_substream_t * substream)
+{
+	emu10k1_t *emu = snd_pcm_substream_chip(substream);
+	emu10k1_pcm_mixer_t *mix;
+	int i;
+
+	for (i=0; i < NUM_EFX_PLAYBACK; i++) {
+	    mix = &emu->efx_pcm_mixer[i];
+	    mix->epcm = NULL;
+	    snd_emu10k1_pcm_efx_mixer_notify(emu, i, 0);
+	}
+	return 0;
+}
+
+static int snd_emu10k1_efx_playback_open(snd_pcm_substream_t * substream)
+{
+	emu10k1_t *emu = snd_pcm_substream_chip(substream);
+	emu10k1_pcm_t *epcm;
+	emu10k1_pcm_mixer_t *mix;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int i;
+
+	epcm = kcalloc(1, sizeof(*epcm), GFP_KERNEL);
+	if (epcm == NULL)
+		return -ENOMEM;
+	epcm->emu = emu;
+	epcm->type = PLAYBACK_EFX;
+	epcm->substream = substream;
+	
+	emu->pcm_playback_efx_substream = substream;
+
+	runtime->private_data = epcm;
+	runtime->private_free = snd_emu10k1_pcm_free_substream;
+	runtime->hw = snd_emu10k1_efx_playback;
+	
+	for (i=0; i < NUM_EFX_PLAYBACK; i++) {
+	    mix = &emu->efx_pcm_mixer[i];
+	    mix->send_routing[0][0] = mix->send_routing[1][0] = mix->send_routing[2][0] = i;
+	    memset(&mix->send_volume, 0, sizeof(mix->send_volume));
+	    mix->send_volume[0][0] =
+	    mix->send_volume[1][0] = mix->send_volume[2][0] = 255;
+	    mix->attn[0] = mix->attn[1] = mix->attn[2] = 0xffff;
+	    mix->epcm = epcm;
+	    snd_emu10k1_pcm_efx_mixer_notify(emu, i, 1);
+	}
+	return 0;
+}
+
 static int snd_emu10k1_playback_open(snd_pcm_substream_t * substream)
 {
 	emu10k1_t *emu = snd_pcm_substream_chip(substream);
@@ -969,6 +1219,19 @@
 	.pointer =		snd_emu10k1_capture_pointer,
 };
 
+/* EFX playback */
+static snd_pcm_ops_t snd_emu10k1_efx_playback_ops = {
+	.open =			snd_emu10k1_efx_playback_open,
+	.close =		snd_emu10k1_efx_playback_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_emu10k1_playback_hw_params,
+	.hw_free =		snd_emu10k1_efx_playback_hw_free,
+	.prepare =		snd_emu10k1_efx_playback_prepare,
+	.trigger =		snd_emu10k1_efx_playback_trigger,
+	.pointer =		snd_emu10k1_efx_playback_pointer,
+	.page =			snd_pcm_sgbuf_ops_page,
+};
+
 static void snd_emu10k1_pcm_free(snd_pcm_t *pcm)
 {
 	emu10k1_t *emu = pcm->private_data;
@@ -1012,6 +1275,39 @@
 	return 0;
 }
 
+int __devinit snd_emu10k1_pcm_multi(emu10k1_t * emu, int device, snd_pcm_t ** rpcm)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_substream_t *substream;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+
+	if ((err = snd_pcm_new(emu->card, "emu10k1", device, 1, 0, &pcm)) < 0)
+		return err;
+
+	pcm->private_data = emu;
+	pcm->private_free = snd_emu10k1_pcm_free;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1_efx_playback_ops);
+
+	pcm->info_flags = 0;
+	pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+	strcpy(pcm->name, "EMU10K1 multichannel EFX");
+	emu->pcm = pcm;
+
+	for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next)
+		if ((err = snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV_SG, snd_dma_pci_data(emu->pci), 64*1024, 64*1024)) < 0)
+			return err;
+
+	if (rpcm)
+		*rpcm = pcm;
+
+	return 0;
+}
+
+
 static snd_pcm_ops_t snd_emu10k1_capture_mic_ops = {
 	.open =			snd_emu10k1_capture_mic_open,
 	.close =		snd_emu10k1_capture_mic_close,
@@ -1390,7 +1686,13 @@
 	if (rpcm)
 		*rpcm = pcm;
 
-	emu->efx_voices_mask[0] = FXWC_DEFAULTROUTE_C | FXWC_DEFAULTROUTE_A;
+	/* record the first NUM_EFX_PLAYBACK channels by default.  eventually we want 
+	 * to record the (mostly unused) upper outputs, so we only record the channels 
+	 * the user has connected to the EFX recording inputs in the DSP. - rlrevell
+	 */
+	
+	/* emu->efx_voices_mask[0] = FXWC_DEFAULTROUTE_C | FXWC_DEFAULTROUTE_A; */
+	emu->efx_voices_mask[0] = 0xffff;
 	emu->efx_voices_mask[1] = 0;
 	snd_ctl_add(emu->card, snd_ctl_new1(&snd_emu10k1_pcm_efx_voices_mask, emu));
 
diff -Nru linux-2.6.10-kdb/sound/pci/emu10k1/emuproc.c /home/rlrevell/cvs/alsa-cvs/alsa-kernel/pci/emu10k1/emuproc.c
--- linux-2.6.10-kdb/sound/pci/emu10k1/emuproc.c	2005-01-18 22:28:23.000000000 -0500
+++ /home/rlrevell/cvs/alsa-cvs/alsa-kernel/pci/emu10k1/emuproc.c	2005-01-24 20:12:45.000000000 -0500
@@ -405,7 +405,7 @@
 	snd_iprintf(buffer, "Registers 0x%x\n", iobase);
 	for(i = offset; i < offset+length; i++) {
 		snd_iprintf(buffer, "%02X: ",i);
-		for (j = 0; j < 4; j++) {
+		for (j = 0; j < 64; j++) {
 			if(iobase == 0)
                 		value = snd_ptr_read(emu, 0, i, j);
 			else
@@ -482,22 +482,22 @@
 		entry->c.text.write = snd_emu_proc_io_reg_write;
 	}
 	if (! snd_card_proc_new(emu->card, "ptr_regs00a", &entry)) {
-		snd_info_set_text_ops(entry, emu, 1024, snd_emu_proc_ptr_reg_read00a);
+		snd_info_set_text_ops(entry, emu, 32768, snd_emu_proc_ptr_reg_read00a);
 		entry->c.text.write_size = 64;
 		entry->c.text.write = snd_emu_proc_ptr_reg_write00;
 	}
 	if (! snd_card_proc_new(emu->card, "ptr_regs00b", &entry)) {
-		snd_info_set_text_ops(entry, emu, 1024, snd_emu_proc_ptr_reg_read00b);
+		snd_info_set_text_ops(entry, emu, 32768, snd_emu_proc_ptr_reg_read00b);
 		entry->c.text.write_size = 64;
 		entry->c.text.write = snd_emu_proc_ptr_reg_write00;
 	}
 	if (! snd_card_proc_new(emu->card, "ptr_regs20a", &entry)) {
-		snd_info_set_text_ops(entry, emu, 1024, snd_emu_proc_ptr_reg_read20a);
+		snd_info_set_text_ops(entry, emu, 32768, snd_emu_proc_ptr_reg_read20a);
 		entry->c.text.write_size = 64;
 		entry->c.text.write = snd_emu_proc_ptr_reg_write20;
 	}
 	if (! snd_card_proc_new(emu->card, "ptr_regs20b", &entry)) {
-		snd_info_set_text_ops(entry, emu, 1024, snd_emu_proc_ptr_reg_read20b);
+		snd_info_set_text_ops(entry, emu, 32768, snd_emu_proc_ptr_reg_read20b);
 		entry->c.text.write_size = 64;
 		entry->c.text.write = snd_emu_proc_ptr_reg_write20;
 	}
diff -Nru linux-2.6.10-kdb/sound/pci/emu10k1/io.c /home/rlrevell/cvs/alsa-cvs/alsa-kernel/pci/emu10k1/io.c
--- linux-2.6.10-kdb/sound/pci/emu10k1/io.c	2005-01-18 22:28:23.000000000 -0500
+++ /home/rlrevell/cvs/alsa-cvs/alsa-kernel/pci/emu10k1/io.c	2005-01-24 20:12:45.000000000 -0500
@@ -170,6 +170,63 @@
 	spin_unlock_irqrestore(&emu->emu_lock, flags);
 }
 
+void snd_emu10k1_voice_half_loop_intr_enable(emu10k1_t *emu, unsigned int voicenum)
+{
+	unsigned long flags;
+	unsigned int val;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	/* voice interrupt */
+	if (voicenum >= 32) {
+		outl(HLIEH << 16, emu->port + PTR);
+		val = inl(emu->port + DATA);
+		val |= 1 << (voicenum - 32);
+	} else {
+		outl(HLIEL << 16, emu->port + PTR);
+		val = inl(emu->port + DATA);
+		val |= 1 << voicenum;
+	}
+	outl(val, emu->port + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_half_loop_intr_disable(emu10k1_t *emu, unsigned int voicenum)
+{
+	unsigned long flags;
+	unsigned int val;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	/* voice interrupt */
+	if (voicenum >= 32) {
+		outl(HLIEH << 16, emu->port + PTR);
+		val = inl(emu->port + DATA);
+		val &= ~(1 << (voicenum - 32));
+	} else {
+		outl(HLIEL << 16, emu->port + PTR);
+		val = inl(emu->port + DATA);
+		val &= ~(1 << voicenum);
+	}
+	outl(val, emu->port + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_half_loop_intr_ack(emu10k1_t *emu, unsigned int voicenum)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	/* voice interrupt */
+	if (voicenum >= 32) {
+		outl(HLIPH << 16, emu->port + PTR);
+		voicenum = 1 << (voicenum - 32);
+	} else {
+		outl(HLIPL << 16, emu->port + PTR);
+		voicenum = 1 << voicenum;
+	}
+	outl(voicenum, emu->port + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
 void snd_emu10k1_voice_set_loop_stop(emu10k1_t *emu, unsigned int voicenum)
 {
 	unsigned long flags;
diff -Nru linux-2.6.10-kdb/sound/pci/emu10k1/irq.c /home/rlrevell/cvs/alsa-cvs/alsa-kernel/pci/emu10k1/irq.c
--- linux-2.6.10-kdb/sound/pci/emu10k1/irq.c	2005-01-18 22:28:23.000000000 -0500
+++ /home/rlrevell/cvs/alsa-cvs/alsa-kernel/pci/emu10k1/irq.c	2005-01-24 20:12:45.000000000 -0500
@@ -73,6 +73,21 @@
 				val >>= 1;
 				pvoice++;
 			}
+			val = snd_emu10k1_ptr_read(emu, HLIPL, 0);
+			for (voice = 0; voice <= voice_max; voice++) {
+				if (voice == 0x20)
+					val = snd_emu10k1_ptr_read(emu, HLIPH, 0);
+				if (val & 1) {
+					if (pvoice->use && pvoice->interrupt != NULL) {
+						pvoice->interrupt(emu, pvoice);
+						snd_emu10k1_voice_half_loop_intr_ack(emu, voice);
+					} else {
+						snd_emu10k1_voice_half_loop_intr_disable(emu, voice);
+					}
+				}
+				val >>= 1;
+				pvoice++;
+			}
 			status &= ~IPR_CHANNELLOOP;
 		}
 		status &= ~IPR_CHANNELNUMBERMASK;
diff -Nru linux-2.6.10-kdb/sound/pci/emu10k1/voice.c /home/rlrevell/cvs/alsa-cvs/alsa-kernel/pci/emu10k1/voice.c
--- linux-2.6.10-kdb/sound/pci/emu10k1/voice.c	2004-12-24 16:34:46.000000000 -0500
+++ /home/rlrevell/cvs/alsa-cvs/alsa-kernel/pci/emu10k1/voice.c	2005-01-24 20:12:45.000000000 -0500
@@ -1,8 +1,11 @@
 /*
  *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
  *                   Creative Labs, Inc.
+ *                   Lee Revell <rlrevell@joe-job.com>
  *  Routines for control of EMU10K1 chips - voice manager
  *
+ *  Rewrote voice allocator for multichannel support - rlrevell 12/2004
+ * 
  *  BUGS:
  *    --
  *
@@ -30,25 +33,67 @@
 #include <sound/core.h>
 #include <sound/emu10k1.h>
 
-static int voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int pair, emu10k1_voice_t **rvoice)
+/* Previously the voice allocator started at 0 every time.  The new voice 
+ * allocator uses a round robin scheme.  The next free voice is tracked in 
+ * the card record and each allocation begins where the last left off.  The 
+ * hardware requires stereo interleaved voices be aligned to an even/odd 
+ * boundary.  For multichannel voice allocation we ensure than the block of 
+ * voices does not cross the 32 voice boundary.  This simplifies the 
+ * multichannel support and ensures we can use a single write to the 
+ * (set|clear)_loop_stop registers.  Otherwise (for example) the voices would 
+ * get out of sync when pausing/resuming a stream.
+ *							--rlrevell
+ */
+
+static int voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int number, emu10k1_voice_t **rvoice)
 {
-	emu10k1_voice_t *voice, *voice2;
-	int idx;
+	emu10k1_voice_t *voice;
+	int idx, pair, i, j, k, first_voice, last_voice, skip;
 
+	first_voice = last_voice = skip = 0;	
 	*rvoice = NULL;
-	for (idx = 0; idx < 64; idx += pair ? 2 : 1) {
-		voice = &emu->voices[idx];
-		voice2 = pair ? &emu->voices[idx+1] : NULL;
-		if (voice->use || (voice2 && voice2->use))
+	for (i = emu->next_free_voice, j=0; j < NUM_G ; i += number, j+= number) {
+		// printk("i %d j %d next free %d!\n", i, j, emu->next_free_voice);
+		i %= NUM_G;
+		if ((i % 2) && (number == 2)) {
+			i++;
 			continue;
+		}
+			
+		/* make sure the block of voices does not cross the 32 voice boundary */
+		if (((i % 32) + number) > 32)
+			continue;
+		for (k = 0; k < number; k++) {
+			voice = &emu->voices[i+k];
+			if (voice->use) {
+				// printk("voice %d: use=1!\n", i+k);
+				skip = 1;
+			}
+		}
+		if (!skip) {
+			// printk("allocated voice %d\n", i);
+			first_voice = i;
+			last_voice = i + number;
+			emu->next_free_voice = last_voice;
+			emu->next_free_voice %= NUM_G;
+			break;
+		}
+	}
+	
+	if (first_voice == last_voice) {
+		printk("first==last, number %d, next free %d!\n", number, emu->next_free_voice);
+		/* BUG (or not enough voices)! */
+		return -ENOMEM;
+	}	
+	
+	pair = ( number == 2 ) ? 1 : 0;
+	for (idx=first_voice; idx < last_voice; idx++) {
+		voice = &emu->voices[idx];
+		// printk("voice alloc - %i, %i of %i\n", voice->number, idx-first_voice+1, number);
 		voice->use = 1;
-		if (voice2)
-			voice2->use = 1;
 		switch (type) {
 		case EMU10K1_PCM:
 			voice->pcm = 1;
-			if (voice2)
-				voice2->pcm = 1;
 			break;
 		case EMU10K1_SYNTH:
 			voice->synth = 1;
@@ -56,26 +101,27 @@
 		case EMU10K1_MIDI:
 			voice->midi = 1;
 			break;
+		case EMU10K1_EFX:
+			voice->efx = 1;
+			break;
 		}
-		// printk("voice alloc - %i, pair = %i\n", voice->number, pair);
-		*rvoice = voice;
-		return 0;
 	}
-	return -ENOMEM;
+	*rvoice = &emu->voices[first_voice];
+	return 0;
 }
 
-int snd_emu10k1_voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int pair, emu10k1_voice_t **rvoice)
+int snd_emu10k1_voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int number, emu10k1_voice_t **rvoice)
 {
 	unsigned long flags;
 	int result;
 
 	snd_assert(rvoice != NULL, return -EINVAL);
-	snd_assert(!pair || type == EMU10K1_PCM, return -EINVAL);
+	snd_assert(number, return -EINVAL);
 
 	spin_lock_irqsave(&emu->voice_lock, flags);
 	for (;;) {
-		result = voice_alloc(emu, type, pair, rvoice);
-		if (result == 0 || type != EMU10K1_PCM)
+		result = voice_alloc(emu, type, number, rvoice);
+		if (result == 0 || type == EMU10K1_SYNTH || type == EMU10K1_MIDI)
 			break;
 
 		/* free a voice from synth */
@@ -84,7 +130,7 @@
 			if (result >= 0) {
 				emu10k1_voice_t *pvoice = &emu->voices[result];
 				pvoice->interrupt = NULL;
-				pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = 0;
+				pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0;
 				pvoice->epcm = NULL;
 			}
 		}
@@ -103,7 +149,7 @@
 	snd_assert(pvoice != NULL, return -EINVAL);
 	spin_lock_irqsave(&emu->voice_lock, flags);
 	pvoice->interrupt = NULL;
-	pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = 0;
+	pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0;
 	pvoice->epcm = NULL;
 	snd_emu10k1_voice_init(emu, pvoice->number);
 	spin_unlock_irqrestore(&emu->voice_lock, flags);
--- linux-2.6.10-kdb/include/sound/emu10k1.h	2005-01-18 22:28:08.000000000 -0500
+++ /home/rlrevell/cvs/alsa-cvs/alsa-kernel/include/emu10k1.h	2005-01-24 20:12:45.000000000 -0500
@@ -23,6 +23,24 @@
  *
  */
 
+/* 
+ * v0.01 Added multichannel support.
+ *	 Added new register info, derived from the kX header file and 
+ *	 reverse engineering.
+ * 				- Lee Revell 1/2005
+ * v0.02 Fixed bug in multichannel support caused voice allocation 
+ *	 for synth to be broken
+ *	 Improved register dumps in /proc (probably breaks write)
+ * v0.03 Really fixed voice allocator
+ *	 Remove redundant p16v related comments (these are already 
+ *	 covered by James Courtier-Dutton's p16v patch)
+ * v0.04 Fixed invalid start address bug.  This should fix the problems 
+ *	reported with regular and multichannel PCM.
+ * v0.05 Improve accuracy of interrupt timing for multichannel PCM 
+ *	 by starting the extra voice immediately after the master 
+ *	 voice
+ */
+
 #ifdef __KERNEL__
 
 #include <sound/pcm.h>
@@ -51,7 +69,9 @@
 #define NUM_MIDI        16
 #define NUM_G           64              /* use all channels */
 #define NUM_FXSENDS     4
+#define NUM_EFX_PLAYBACK    16
 
+/* FIXME? - according to the OSS driver the EMU10K1 needs a 29 bit DMA mask */
 #define EMU10K1_DMA_MASK	0x7fffffffUL	/* 31bit */
 #define AUDIGY_DMA_MASK		0xffffffffUL	/* 32bit */
 
@@ -82,10 +102,16 @@
 						/* Clear pending interrupts by writing a 1 to	*/
 						/* the relevant bits and zero to the other bits	*/
 
+#define IPR_GPIOMSG		0x20000000	/* GPIO message interrupt (RE'd, still not sure 
+						   which INTE bits enable it)			*/
+
 /* The next two interrupts are for the midi port on the Audigy Drive (A_MPU1)			*/
 #define IPR_A_MIDITRANSBUFEMPTY2 0x10000000	/* MIDI UART transmit buffer empty		*/
 #define IPR_A_MIDIRECVBUFEMPTY2	0x08000000	/* MIDI UART receive buffer empty		*/
 
+#define IPR_SPDIFBUFFULL	0x04000000	/* SPDIF capture related, 10k2 only? (RE)	*/
+#define IPR_SPDIFBUFHALFFULL	0x02000000	/* SPDIF capture related? (RE)			*/
+
 #define IPR_SAMPLERATETRACKER	0x01000000	/* Sample rate tracker lock status change	*/
 #define IPR_FXDSP		0x00800000	/* Enable FX DSP interrupts			*/
 #define IPR_FORCEINT		0x00400000	/* Force Sound Blaster interrupt		*/
@@ -104,12 +130,12 @@
 #define IPR_INTERVALTIMER	0x00000200	/* Interval timer terminal count		*/
 #define IPR_MIDITRANSBUFEMPTY	0x00000100	/* MIDI UART transmit buffer empty		*/
 #define IPR_MIDIRECVBUFEMPTY	0x00000080	/* MIDI UART receive buffer empty		*/
-#define IPR_CHANNELLOOP		0x00000040	/* One or more channel loop interrupts pending	*/
+#define IPR_CHANNELLOOP		0x00000040	/* Channel (half) loop interrupt(s) pending	*/
 #define IPR_CHANNELNUMBERMASK	0x0000003f	/* When IPR_CHANNELLOOP is set, indicates the	*/
-						/* Highest set channel in CLIPL or CLIPH.  When	*/
-						/* IP is written with CL set, the bit in CLIPL	*/
-						/* or CLIPH corresponding to the CIN value 	*/
-						/* written will be cleared.			*/
+						/* highest set channel in CLIPL, CLIPH, HLIPL,  */
+						/* or HLIPH.  When IP is written with CL set,	*/
+						/* the bit in H/CLIPL or H/CLIPH corresponding	*/
+						/* to the CIN value written will be cleared.	*/
 
 #define INTE			0x0c		/* Interrupt enable register			*/
 #define INTE_VIRTUALSB_MASK	0xc0000000	/* Virtual Soundblaster I/O port capture	*/
@@ -236,9 +262,27 @@
 #define A_IOCFG			0x18		/* GPIO on Audigy card (16bits)			*/
 #define A_GPINPUT_MASK		0xff00
 #define A_GPOUTPUT_MASK		0x00ff
-#define A_IOCFG_GPOUT0		0x0044		/* analog/digital? */
-#define A_IOCFG_GPOUT1		0x0002		/* IR */
+
+// Audigy output/GPIO stuff taken from the kX drivers
+#define A_IOCFG_GPOUT0		0x0044		/* analog/digital				*/
+#define A_IOCFG_DISABLE_ANALOG	0x0040		/* = 'enable' for Audigy2 (chiprev=4)		*/
+#define A_IOCFG_ENABLE_DIGITAL	0x0004
+#define A_IOCFG_UNKNOWN_20      0x0020
+#define A_IOCFG_DISABLE_AC97_FRONT      0x0080  /* turn off ac97 front -> front (10k2.1)	*/
+#define A_IOCFG_GPOUT1		0x0002		/* IR? drive's internal bypass (?)		*/
 #define A_IOCFG_GPOUT2		0x0001		/* IR */
+#define A_IOCFG_MULTIPURPOSE_JACK	0x2000  /* center+lfe+rear_center (a2/a2ex)		*/
+                                                /* + digital for generic 10k2			*/
+#define A_IOCFG_DIGITAL_JACK    0x1000          /* digital for a2 platinum			*/
+#define A_IOCFG_FRONT_JACK      0x4000
+#define A_IOCFG_REAR_JACK       0x8000
+#define A_IOCFG_PHONES_JACK     0x0100          /* LiveDrive					*/
+
+/* outputs:
+ *	for audigy2 platinum:	0xa00
+ *	for a2 platinum ex:	0x1c00
+ *	for a1 platinum:	0x0
+ */
 
 #define TIMER			0x1a		/* Timer terminal count register		*/
 						/* NOTE: After the rate is changed, a maximum	*/
@@ -464,6 +508,8 @@
 						/* NOTE: All channels contain internal variables; do	*/
 						/* not write to these locations.			*/
 
+/* 1f something */
+
 #define CD0			0x20		/* Cache data 0 register				*/
 #define CD1			0x21		/* Cache data 1 register				*/
 #define CD2			0x22		/* Cache data 2 register				*/
@@ -481,6 +527,28 @@
 #define CDE			0x2e		/* Cache data E register				*/
 #define CDF			0x2f		/* Cache data F register				*/
 
+/* these registers usually (but not always) contain 
+ * the same samples as 0-F.  possibly related to
+ * "normal cache" and "start cache" referenced in 
+ * Creative/EMU patents			-rlrevell
+ */
+#define CD10			0x30		/* Cache data 10 register				*/
+#define CD11			0x31		/* Cache data 11 register				*/
+#define CD12			0x32		/* Cache data 12 register				*/
+#define CD13			0x33		/* Cache data 13 register				*/
+#define CD14			0x34		/* Cache data 14 register				*/
+#define CD15			0x35		/* Cache data 15 register				*/
+#define CD16			0x36		/* Cache data 16 register				*/
+#define CD17			0x37		/* Cache data 17 register				*/
+#define CD18			0x38		/* Cache data 18 register				*/
+#define CD19			0x39		/* Cache data 19 register				*/
+#define CD1A			0x3a		/* Cache data 1A register				*/
+#define CD1B			0x3b		/* Cache data 1B register				*/
+#define CD1C			0x3c		/* Cache data 1C register				*/
+#define CD1D			0x3d		/* Cache data 1D register				*/
+#define CD1E			0x3e		/* Cache data 1E register				*/
+#define CD1F			0x3f		/* Cache data 1F register				*/
+
 #define PTB			0x40		/* Page table base register				*/
 #define PTB_MASK		0xfffff000	/* Physical address of the page table in host memory	*/
 
@@ -511,7 +579,11 @@
 
 #define FXWC			0x43		/* FX output write channels register			*/
 						/* When set, each bit enables the writing of the	*/
-						/* corresponding FX output channel into host memory	*/
+						/* corresponding FX output channel (internal registers  */
+						/* 0x20-0x3f) to host memory.  This mode of recording   */
+						/* is 16bit, 48KHz only. All 32 channels can be enabled */
+						/* simultaneously.					*/
+
 #define FXWC_DEFAULTROUTE_C     (1<<0)		/* left emu out? */
 #define FXWC_DEFAULTROUTE_B     (1<<1)		/* right emu out? */
 #define FXWC_DEFAULTROUTE_A     (1<<12)
@@ -546,12 +618,16 @@
 #define FXBA			0x47		/* FX Buffer Address */
 #define FXBA_MASK		0xfffff000	/* 20 bit base address					*/
 
+/* 0x48 something - word access, defaults to 3f */
+
 #define MICBS			0x49		/* Microphone buffer size register			*/
 
 #define ADCBS			0x4a		/* ADC buffer size register				*/
 
 #define FXBS			0x4b		/* FX buffer size register				*/
 
+/* register: 0x4c..4f: ffff-ffff current amounts, per-channel */
+
 /* The following mask values define the size of the ADC, MIX and FX buffers in bytes */
 #define ADCBS_BUFSIZE_NONE	0x00000000
 #define ADCBS_BUFSIZE_384	0x00000001
@@ -602,6 +678,7 @@
 #define A_DBG_SATURATION_OCCURED 0x20000000
 #define A_DBG_SATURATION_ADDR	 0x0ffc0000
 
+// NOTE: 0x54,55,56: 64-bit
 #define SPCS0			0x54		/* SPDIF output Channel Status 0 register	*/
 
 #define SPCS1			0x55		/* SPDIF output Channel Status 1 register	*/
@@ -632,6 +709,19 @@
 #define SPCS_NOTAUDIODATA	0x00000002	/* 0 = Digital audio, 1 = not audio		*/
 #define SPCS_PROFESSIONAL	0x00000001	/* 0 = Consumer (IEC-958), 1 = pro (AES3-1992)	*/
 
+/* HACK!!!  CLI[EP][LH] are really 0x58-0x5b.  I temporarily 
+ * replaced them with the half loop interrupt registers 
+ * 0x66-0x69 for testing purposes, and because I found that 
+ * using the channel loop registers resulted in bad sound 
+ * with the multichannel device, undoubtedly due to some 
+ * timing bug.  Using the half loop interrupts does not 
+ * seem to impact standard PCM playback at all.
+ *
+ * Of course the real fix is to write enable/disable functions 
+ * for the half loop interrupts as well.
+ *							-rlrevell
+ */
+
 /* The 32-bit CLIx and SOLx registers all have one bit per channel control/status      		*/
 #define CLIEL			0x58		/* Channel loop interrupt enable low register	*/
 
@@ -657,6 +747,7 @@
 #define AC97SLOT_CNTR		0x10            /* Center enable */
 #define AC97SLOT_LFE		0x20            /* LFE enable */
 
+// NOTE: 0x60,61,62: 64-bit
 #define CDSRCS			0x60		/* CD-ROM Sample Rate Converter status register	*/
 
 #define GPSRCS			0x61		/* General Purpose SPDIF sample rate cvt status */
@@ -693,6 +784,20 @@
 #define FXIDX_MASK		0x0000ffff	/* 16-bit value					*/
 #define FXIDX_IDX		0x10000065
 
+/* The 32-bit HLIx and HLIPx registers all have one bit per channel control/status      		*/
+#define HLIEL			0x66		/* Channel half loop interrupt enable low register	*/
+
+#define HLIEH			0x67		/* Channel half loop interrupt enable high register	*/
+
+#define HLIPL			0x68		/* Channel half loop interrupt pending low register	*/
+
+#define HLIPH			0x69		/* Channel half loop interrupt pending high register	*/
+
+// 0x65 - something fx recording related? - kX driver zeroes it when starting FX capture
+// 0x6a,6b,6c used for some recording
+// 0x6d unused
+// 0x6e,6f - tanktable base / offset
+
 /* This is the MPU port on the card (via the game port)						*/
 #define A_MUDATA1		0x70
 #define A_MUCMD1		0x71
@@ -714,6 +819,9 @@
 #define A_SPDIF_44100		0x00000000
 #define A_SPDIF_96000		0x00000040
 
+/* 0x77,0x78,0x79 "something i2s-related" - default to 0x01080000 on my audigy 2 ZS --rlrevell	*/
+/* 0x7a, 0x7b - lookup tables */
+
 #define A_FXRT2			0x7c
 #define A_FXRT_CHANNELE		0x0000003f	/* Effects send bus number for channel's effects send E	*/
 #define A_FXRT_CHANNELF		0x00003f00	/* Effects send bus number for channel's effects send F	*/
@@ -725,7 +833,8 @@
 #define A_FXSENDAMOUNT_F_MASK	0x00FF0000
 #define A_FXSENDAMOUNT_G_MASK	0x0000FF00
 #define A_FXSENDAMOUNT_H_MASK	0x000000FF
-
+/* 0x7c, 0x7e "high bit is used for filtering" */
+ 
 /* The send amounts for this one are the same as used with the emu10k1 */
 #define A_FXRT1			0x7e
 #define A_FXRT_CHANNELA		0x0000003f
@@ -782,6 +891,7 @@
 typedef struct _snd_emu10k1_pcm emu10k1_pcm_t;
 
 typedef enum {
+	EMU10K1_EFX,
 	EMU10K1_PCM,
 	EMU10K1_SYNTH,
 	EMU10K1_MIDI
@@ -792,6 +902,7 @@
 	int number;
 	int use: 1,
 	    pcm: 1,
+	    efx: 1,
 	    synth: 1,
 	    midi: 1;
 	void (*interrupt)(emu10k1_t *emu, emu10k1_voice_t *pvoice);
@@ -801,6 +912,7 @@
 
 typedef enum {
 	PLAYBACK_EMUVOICE,
+	PLAYBACK_EFX,
 	CAPTURE_AC97ADC,
 	CAPTURE_AC97MIC,
 	CAPTURE_EFX
@@ -810,7 +922,7 @@
 	emu10k1_t *emu;
 	snd_emu10k1_pcm_type_t type;
 	snd_pcm_substream_t *substream;
-	emu10k1_voice_t *voices[2];
+	emu10k1_voice_t *voices[NUM_EFX_PLAYBACK];
 	emu10k1_voice_t *extra;
 	unsigned short running;
 	unsigned short first_ptr;
@@ -984,23 +1096,29 @@
 	spinlock_t voice_lock;
 	struct semaphore ptb_lock;
 
-	emu10k1_voice_t voices[64];
+	emu10k1_voice_t voices[NUM_G];
 	emu10k1_pcm_mixer_t pcm_mixer[32];
+	emu10k1_pcm_mixer_t efx_pcm_mixer[NUM_EFX_PLAYBACK];
 	snd_kcontrol_t *ctl_send_routing;
 	snd_kcontrol_t *ctl_send_volume;
 	snd_kcontrol_t *ctl_attn;
+	snd_kcontrol_t *ctl_efx_send_routing;
+	snd_kcontrol_t *ctl_efx_send_volume;
+	snd_kcontrol_t *ctl_efx_attn;
+
+	snd_info_entry_t *proc_voices_dir;
 
 	void (*hwvol_interrupt)(emu10k1_t *emu, unsigned int status);
 	void (*capture_interrupt)(emu10k1_t *emu, unsigned int status);
 	void (*capture_mic_interrupt)(emu10k1_t *emu, unsigned int status);
 	void (*capture_efx_interrupt)(emu10k1_t *emu, unsigned int status);
-	void (*timer_interrupt)(emu10k1_t *emu);
 	void (*spdif_interrupt)(emu10k1_t *emu, unsigned int status);
 	void (*dsp_interrupt)(emu10k1_t *emu);
 
 	snd_pcm_substream_t *pcm_capture_substream;
 	snd_pcm_substream_t *pcm_capture_mic_substream;
 	snd_pcm_substream_t *pcm_capture_efx_substream;
+	snd_pcm_substream_t *pcm_playback_efx_substream;
 
 	snd_timer_t *timer;
 
@@ -1008,6 +1126,7 @@
 	emu10k1_midi_t midi2; /* for audigy */
 
 	unsigned int efx_voices_mask[2];
+	unsigned int next_free_voice;
 };
 
 int snd_emu10k1_create(snd_card_t * card,
@@ -1021,6 +1140,7 @@
 int snd_emu10k1_pcm(emu10k1_t * emu, int device, snd_pcm_t ** rpcm);
 int snd_emu10k1_pcm_mic(emu10k1_t * emu, int device, snd_pcm_t ** rpcm);
 int snd_emu10k1_pcm_efx(emu10k1_t * emu, int device, snd_pcm_t ** rpcm);
+int snd_emu10k1_pcm_multi(emu10k1_t * emu, int device, snd_pcm_t ** rpcm);
 int snd_emu10k1_fx8010_pcm(emu10k1_t * emu, int device, snd_pcm_t ** rpcm);
 int snd_emu10k1_mixer(emu10k1_t * emu);
 int snd_emu10k1_timer(emu10k1_t * emu, int device);
@@ -1043,6 +1163,9 @@
 void snd_emu10k1_voice_intr_enable(emu10k1_t *emu, unsigned int voicenum);
 void snd_emu10k1_voice_intr_disable(emu10k1_t *emu, unsigned int voicenum);
 void snd_emu10k1_voice_intr_ack(emu10k1_t *emu, unsigned int voicenum);
+void snd_emu10k1_voice_half_loop_intr_enable(emu10k1_t *emu, unsigned int voicenum);
+void snd_emu10k1_voice_half_loop_intr_disable(emu10k1_t *emu, unsigned int voicenum);
+void snd_emu10k1_voice_half_loop_intr_ack(emu10k1_t *emu, unsigned int voicenum);
 void snd_emu10k1_voice_set_loop_stop(emu10k1_t *emu, unsigned int voicenum);
 void snd_emu10k1_voice_clear_loop_stop(emu10k1_t *emu, unsigned int voicenum);
 void snd_emu10k1_wait(emu10k1_t *emu, unsigned int wait);
@@ -1070,6 +1193,7 @@
 
 /* proc interface */
 int snd_emu10k1_proc_init(emu10k1_t * emu);
+void snd_emu10k1_proc_done(emu10k1_t * emu);
 
 /* fx8010 irq handler */
 int snd_emu10k1_fx8010_register_irq_handler(emu10k1_t *emu,

