Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
diff --git a/sound/pci/trident/Makefile b/sound/pci/trident/Makefile
new file mode 100644
index 0000000..65bc5b7
--- /dev/null
+++ b/sound/pci/trident/Makefile
@@ -0,0 +1,19 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-trident-objs := trident.o trident_main.o trident_memory.o
+snd-trident-synth-objs := trident_synth.o
+
+#
+# this function returns:
+#   "m" - CONFIG_SND_SEQUENCER is m
+#   <empty string> - CONFIG_SND_SEQUENCER is undefined
+#   otherwise parameter #1 value
+#
+sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1)))
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_TRIDENT) += snd-trident.o
+obj-$(call sequencer,$(CONFIG_SND_TRIDENT)) += snd-trident-synth.o
diff --git a/sound/pci/trident/trident.c b/sound/pci/trident/trident.c
new file mode 100644
index 0000000..ad58e08
--- /dev/null
+++ b/sound/pci/trident/trident.c
@@ -0,0 +1,196 @@
+/*
+ *  Driver for Trident 4DWave DX/NX & SiS SI7018 Audio PCI soundcard
+ *
+ *  Driver was originated by Trident <audio@tridentmicro.com>
+ *  			     Fri Feb 19 15:55:28 MST 1999
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/trident.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>, <audio@tridentmicro.com>");
+MODULE_DESCRIPTION("Trident 4D-WaveDX/NX & SiS SI7018");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Trident,4DWave DX},"
+		"{Trident,4DWave NX},"
+		"{SiS,SI7018 PCI Audio},"
+		"{Best Union,Miss Melody 4DWave PCI},"
+		"{HIS,4DWave PCI},"
+		"{Warpspeed,ONSpeed 4DWave PCI},"
+		"{Aztech Systems,PCI 64-Q3D},"
+		"{Addonics,SV 750},"
+		"{CHIC,True Sound 4Dwave},"
+		"{Shark,Predator4D-PCI},"
+		"{Jaton,SonicWave 4D},"
+		"{Hoontech,SoundTrack Digital 4DWave NX}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 32};
+static int wavetable_size[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8192};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Trident 4DWave PCI soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Trident 4DWave PCI soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Trident 4DWave PCI soundcard.");
+module_param_array(pcm_channels, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_channels, "Number of hardware channels assigned for PCM.");
+module_param_array(wavetable_size, int, NULL, 0444);
+MODULE_PARM_DESC(wavetable_size, "Maximum memory size in kB for wavetable synth.");
+
+static struct pci_device_id snd_trident_ids[] = {
+	{ 0x1023, 0x2000, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },	/* Trident 4DWave DX PCI Audio */
+	{ 0x1023, 0x2001, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },	/* Trident 4DWave NX PCI Audio */
+	{ 0x1039, 0x7018, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },	/* SiS SI7018 PCI Audio */
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_trident_ids);
+
+static int __devinit snd_trident_probe(struct pci_dev *pci,
+				       const struct pci_device_id *pci_id)
+{
+	static int dev;
+	snd_card_t *card;
+	trident_t *trident;
+	const char *str;
+	int err, pcm_dev = 0;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
+	if (card == NULL)
+		return -ENOMEM;
+
+	if ((err = snd_trident_create(card, pci,
+				      pcm_channels[dev],
+				      ((pci->vendor << 16) | pci->device) == TRIDENT_DEVICE_ID_SI7018 ? 1 : 2,
+				      wavetable_size[dev],
+				      &trident)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	switch (trident->device) {
+	case TRIDENT_DEVICE_ID_DX:
+		str = "TRID4DWAVEDX";
+		break;
+	case TRIDENT_DEVICE_ID_NX:
+		str = "TRID4DWAVENX";
+		break;
+	case TRIDENT_DEVICE_ID_SI7018:
+		str = "SI7018";
+		break;
+	default:
+		str = "Unknown";
+	}
+	strcpy(card->driver, str);
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+		strcpy(card->shortname, "SiS ");
+	} else {
+		strcpy(card->shortname, "Trident ");
+	}
+	strcat(card->shortname, card->driver);
+	sprintf(card->longname, "%s PCI Audio at 0x%lx, irq %d",
+		card->shortname, trident->port, trident->irq);
+
+	if ((err = snd_trident_pcm(trident, pcm_dev++, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	switch (trident->device) {
+	case TRIDENT_DEVICE_ID_DX:
+	case TRIDENT_DEVICE_ID_NX:
+		if ((err = snd_trident_foldback_pcm(trident, pcm_dev++, NULL)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+		break;
+	}
+	if (trident->device == TRIDENT_DEVICE_ID_NX || trident->device == TRIDENT_DEVICE_ID_SI7018) {
+		if ((err = snd_trident_spdif_pcm(trident, pcm_dev++, NULL)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+	}
+	if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_TRID4DWAVE,
+				       trident->midi_port, 1,
+				       trident->irq, 0, &trident->rmidi)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+	if ((err = snd_trident_attach_synthesizer(trident)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+#endif
+
+	snd_trident_create_gameport(trident);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_trident_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "Trident4DWaveAudio",
+	.id_table = snd_trident_ids,
+	.probe = snd_trident_probe,
+	.remove = __devexit_p(snd_trident_remove),
+	SND_PCI_PM_CALLBACKS
+};
+
+static int __init alsa_card_trident_init(void)
+{
+	return pci_module_init(&driver);
+}
+
+static void __exit alsa_card_trident_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_trident_init)
+module_exit(alsa_card_trident_exit)
diff --git a/sound/pci/trident/trident_main.c b/sound/pci/trident/trident_main.c
new file mode 100644
index 0000000..ccd5ca2
--- /dev/null
+++ b/sound/pci/trident/trident_main.c
@@ -0,0 +1,3991 @@
+/*
+ *  Maintained by Jaroslav Kysela <perex@suse.cz>
+ *  Originated by audio@tridentmicro.com
+ *  Fri Feb 19 15:55:28 MST 1999
+ *  Routines for control of Trident 4DWave (DX and NX) chip
+ *
+ *  BUGS:
+ *
+ *  TODO:
+ *    ---
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ *
+ *  SiS7018 S/PDIF support by Thomas Winischhofer <thomas@winischhofer.net>
+ */
+
+#include <sound/driver.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/gameport.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/trident.h>
+#include <sound/asoundef.h>
+
+#include <asm/io.h>
+
+static int snd_trident_pcm_mixer_build(trident_t *trident, snd_trident_voice_t * voice, snd_pcm_substream_t *substream);
+static int snd_trident_pcm_mixer_free(trident_t *trident, snd_trident_voice_t * voice, snd_pcm_substream_t *substream);
+static irqreturn_t snd_trident_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+#ifdef CONFIG_PM
+static int snd_trident_suspend(snd_card_t *card, pm_message_t state);
+static int snd_trident_resume(snd_card_t *card);
+#endif
+static int snd_trident_sis_reset(trident_t *trident);
+
+static void snd_trident_clear_voices(trident_t * trident, unsigned short v_min, unsigned short v_max);
+static int snd_trident_free(trident_t *trident);
+
+/*
+ *  common I/O routines
+ */
+
+
+#if 0
+static void snd_trident_print_voice_regs(trident_t *trident, int voice)
+{
+	unsigned int val, tmp;
+
+	printk("Trident voice %i:\n", voice);
+	outb(voice, TRID_REG(trident, T4D_LFO_GC_CIR));
+	val = inl(TRID_REG(trident, CH_LBA));
+	printk("LBA: 0x%x\n", val);
+	val = inl(TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC));
+	printk("GVSel: %i\n", val >> 31);
+	printk("Pan: 0x%x\n", (val >> 24) & 0x7f);
+	printk("Vol: 0x%x\n", (val >> 16) & 0xff);
+	printk("CTRL: 0x%x\n", (val >> 12) & 0x0f);
+	printk("EC: 0x%x\n", val & 0x0fff);
+	if (trident->device != TRIDENT_DEVICE_ID_NX) {
+		val = inl(TRID_REG(trident, CH_DX_CSO_ALPHA_FMS));
+		printk("CSO: 0x%x\n", val >> 16);
+		printk("Alpha: 0x%x\n", (val >> 4) & 0x0fff);
+		printk("FMS: 0x%x\n", val & 0x0f);
+		val = inl(TRID_REG(trident, CH_DX_ESO_DELTA));
+		printk("ESO: 0x%x\n", val >> 16);
+		printk("Delta: 0x%x\n", val & 0xffff);
+		val = inl(TRID_REG(trident, CH_DX_FMC_RVOL_CVOL));
+	} else {		// TRIDENT_DEVICE_ID_NX
+		val = inl(TRID_REG(trident, CH_NX_DELTA_CSO));
+		tmp = (val >> 24) & 0xff;
+		printk("CSO: 0x%x\n", val & 0x00ffffff);
+		val = inl(TRID_REG(trident, CH_NX_DELTA_ESO));
+		tmp |= (val >> 16) & 0xff00;
+		printk("Delta: 0x%x\n", tmp);
+		printk("ESO: 0x%x\n", val & 0x00ffffff);
+		val = inl(TRID_REG(trident, CH_NX_ALPHA_FMS_FMC_RVOL_CVOL));
+		printk("Alpha: 0x%x\n", val >> 20);
+		printk("FMS: 0x%x\n", (val >> 16) & 0x0f);
+	}
+	printk("FMC: 0x%x\n", (val >> 14) & 3);
+	printk("RVol: 0x%x\n", (val >> 7) & 0x7f);
+	printk("CVol: 0x%x\n", val & 0x7f);
+}
+#endif
+
+/*---------------------------------------------------------------------------
+   unsigned short snd_trident_codec_read(ac97_t *ac97, unsigned short reg)
+  
+   Description: This routine will do all of the reading from the external
+                CODEC (AC97).
+  
+   Parameters:  ac97 - ac97 codec structure
+                reg - CODEC register index, from AC97 Hal.
+ 
+   returns:     16 bit value read from the AC97.
+  
+  ---------------------------------------------------------------------------*/
+static unsigned short snd_trident_codec_read(ac97_t *ac97, unsigned short reg)
+{
+	unsigned int data = 0, treg;
+	unsigned short count = 0xffff;
+	unsigned long flags;
+	trident_t *trident = ac97->private_data;
+
+	spin_lock_irqsave(&trident->reg_lock, flags);
+	if (trident->device == TRIDENT_DEVICE_ID_DX) {
+		data = (DX_AC97_BUSY_READ | (reg & 0x000000ff));
+		outl(data, TRID_REG(trident, DX_ACR1_AC97_R));
+		do {
+			data = inl(TRID_REG(trident, DX_ACR1_AC97_R));
+			if ((data & DX_AC97_BUSY_READ) == 0)
+				break;
+		} while (--count);
+	} else if (trident->device == TRIDENT_DEVICE_ID_NX) {
+		data = (NX_AC97_BUSY_READ | (reg & 0x000000ff));
+		treg = ac97->num == 0 ? NX_ACR2_AC97_R_PRIMARY : NX_ACR3_AC97_R_SECONDARY;
+		outl(data, TRID_REG(trident, treg));
+		do {
+			data = inl(TRID_REG(trident, treg));
+			if ((data & 0x00000C00) == 0)
+				break;
+		} while (--count);
+	} else if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+		data = SI_AC97_BUSY_READ | SI_AC97_AUDIO_BUSY | (reg & 0x000000ff);
+		if (ac97->num == 1)
+			data |= SI_AC97_SECONDARY;
+		outl(data, TRID_REG(trident, SI_AC97_READ));
+		do {
+			data = inl(TRID_REG(trident, SI_AC97_READ));
+			if ((data & (SI_AC97_BUSY_READ)) == 0)
+				break;
+		} while (--count);
+	}
+
+	if (count == 0 && !trident->ac97_detect) {
+		snd_printk("ac97 codec read TIMEOUT [0x%x/0x%x]!!!\n", reg, data);
+		data = 0;
+	}
+
+	spin_unlock_irqrestore(&trident->reg_lock, flags);
+	return ((unsigned short) (data >> 16));
+}
+
+/*---------------------------------------------------------------------------
+   void snd_trident_codec_write(ac97_t *ac97, unsigned short reg, unsigned short wdata)
+  
+   Description: This routine will do all of the writing to the external
+                CODEC (AC97).
+  
+   Parameters:	ac97 - ac97 codec structure
+   	        reg - CODEC register index, from AC97 Hal.
+                data  - Lower 16 bits are the data to write to CODEC.
+  
+   returns:     TRUE if everything went ok, else FALSE.
+  
+  ---------------------------------------------------------------------------*/
+static void snd_trident_codec_write(ac97_t *ac97, unsigned short reg, unsigned short wdata)
+{
+	unsigned int address, data;
+	unsigned short count = 0xffff;
+	unsigned long flags;
+	trident_t *trident = ac97->private_data;
+
+	data = ((unsigned long) wdata) << 16;
+
+	spin_lock_irqsave(&trident->reg_lock, flags);
+	if (trident->device == TRIDENT_DEVICE_ID_DX) {
+		address = DX_ACR0_AC97_W;
+
+		/* read AC-97 write register status */
+		do {
+			if ((inw(TRID_REG(trident, address)) & DX_AC97_BUSY_WRITE) == 0)
+				break;
+		} while (--count);
+
+		data |= (DX_AC97_BUSY_WRITE | (reg & 0x000000ff));
+	} else if (trident->device == TRIDENT_DEVICE_ID_NX) {
+		address = NX_ACR1_AC97_W;
+
+		/* read AC-97 write register status */
+		do {
+			if ((inw(TRID_REG(trident, address)) & NX_AC97_BUSY_WRITE) == 0)
+				break;
+		} while (--count);
+
+		data |= (NX_AC97_BUSY_WRITE | (ac97->num << 8) | (reg & 0x000000ff));
+	} else if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+		address = SI_AC97_WRITE;
+
+		/* read AC-97 write register status */
+		do {
+			if ((inw(TRID_REG(trident, address)) & (SI_AC97_BUSY_WRITE)) == 0)
+				break;
+		} while (--count);
+
+		data |= SI_AC97_BUSY_WRITE | SI_AC97_AUDIO_BUSY | (reg & 0x000000ff);
+		if (ac97->num == 1)
+			data |= SI_AC97_SECONDARY;
+	} else {
+		address = 0;	/* keep GCC happy */
+		count = 0;	/* return */
+	}
+
+	if (count == 0) {
+		spin_unlock_irqrestore(&trident->reg_lock, flags);
+		return;
+	}
+	outl(data, TRID_REG(trident, address));
+	spin_unlock_irqrestore(&trident->reg_lock, flags);
+}
+
+/*---------------------------------------------------------------------------
+   void snd_trident_enable_eso(trident_t *trident)
+  
+   Description: This routine will enable end of loop interrupts.
+                End of loop interrupts will occur when a running
+                channel reaches ESO.
+                Also enables middle of loop interrupts.
+  
+   Parameters:  trident - pointer to target device class for 4DWave.
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_enable_eso(trident_t * trident)
+{
+	unsigned int val;
+
+	val = inl(TRID_REG(trident, T4D_LFO_GC_CIR));
+	val |= ENDLP_IE;
+	val |= MIDLP_IE;
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018)
+		val |= BANK_B_EN;
+	outl(val, TRID_REG(trident, T4D_LFO_GC_CIR));
+}
+
+/*---------------------------------------------------------------------------
+   void snd_trident_disable_eso(trident_t *trident)
+  
+   Description: This routine will disable end of loop interrupts.
+                End of loop interrupts will occur when a running
+                channel reaches ESO.
+                Also disables middle of loop interrupts.
+  
+   Parameters:  
+                trident - pointer to target device class for 4DWave.
+  
+   returns:     TRUE if everything went ok, else FALSE.
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_disable_eso(trident_t * trident)
+{
+	unsigned int tmp;
+
+	tmp = inl(TRID_REG(trident, T4D_LFO_GC_CIR));
+	tmp &= ~ENDLP_IE;
+	tmp &= ~MIDLP_IE;
+	outl(tmp, TRID_REG(trident, T4D_LFO_GC_CIR));
+}
+
+/*---------------------------------------------------------------------------
+   void snd_trident_start_voice(trident_t * trident, unsigned int voice)
+
+    Description: Start a voice, any channel 0 thru 63.
+                 This routine automatically handles the fact that there are
+                 more than 32 channels available.
+
+    Parameters : voice - Voice number 0 thru n.
+                 trident - pointer to target device class for 4DWave.
+
+    Return Value: None.
+
+  ---------------------------------------------------------------------------*/
+
+void snd_trident_start_voice(trident_t * trident, unsigned int voice)
+{
+	unsigned int mask = 1 << (voice & 0x1f);
+	unsigned int reg = (voice & 0x20) ? T4D_START_B : T4D_START_A;
+
+	outl(mask, TRID_REG(trident, reg));
+}
+
+/*---------------------------------------------------------------------------
+   void snd_trident_stop_voice(trident_t * trident, unsigned int voice)
+
+    Description: Stop a voice, any channel 0 thru 63.
+                 This routine automatically handles the fact that there are
+                 more than 32 channels available.
+
+    Parameters : voice - Voice number 0 thru n.
+                 trident - pointer to target device class for 4DWave.
+
+    Return Value: None.
+
+  ---------------------------------------------------------------------------*/
+
+void snd_trident_stop_voice(trident_t * trident, unsigned int voice)
+{
+	unsigned int mask = 1 << (voice & 0x1f);
+	unsigned int reg = (voice & 0x20) ? T4D_STOP_B : T4D_STOP_A;
+
+	outl(mask, TRID_REG(trident, reg));
+}
+
+/*---------------------------------------------------------------------------
+    int snd_trident_allocate_pcm_channel(trident_t *trident)
+  
+    Description: Allocate hardware channel in Bank B (32-63).
+  
+    Parameters :  trident - pointer to target device class for 4DWave.
+  
+    Return Value: hardware channel - 32-63 or -1 when no channel is available
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_allocate_pcm_channel(trident_t * trident)
+{
+	int idx;
+
+	if (trident->ChanPCMcnt >= trident->ChanPCM)
+		return -1;
+	for (idx = 31; idx >= 0; idx--) {
+		if (!(trident->ChanMap[T4D_BANK_B] & (1 << idx))) {
+			trident->ChanMap[T4D_BANK_B] |= 1 << idx;
+			trident->ChanPCMcnt++;
+			return idx + 32;
+		}
+	}
+	return -1;
+}
+
+/*---------------------------------------------------------------------------
+    void snd_trident_free_pcm_channel(int channel)
+  
+    Description: Free hardware channel in Bank B (32-63)
+  
+    Parameters :  trident - pointer to target device class for 4DWave.
+	          channel - hardware channel number 0-63
+  
+    Return Value: none
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_free_pcm_channel(trident_t *trident, int channel)
+{
+	if (channel < 32 || channel > 63)
+		return;
+	channel &= 0x1f;
+	if (trident->ChanMap[T4D_BANK_B] & (1 << channel)) {
+		trident->ChanMap[T4D_BANK_B] &= ~(1 << channel);
+		trident->ChanPCMcnt--;
+	}
+}
+
+/*---------------------------------------------------------------------------
+    unsigned int snd_trident_allocate_synth_channel(void)
+  
+    Description: Allocate hardware channel in Bank A (0-31).
+  
+    Parameters :  trident - pointer to target device class for 4DWave.
+  
+    Return Value: hardware channel - 0-31 or -1 when no channel is available
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_allocate_synth_channel(trident_t * trident)
+{
+	int idx;
+
+	for (idx = 31; idx >= 0; idx--) {
+		if (!(trident->ChanMap[T4D_BANK_A] & (1 << idx))) {
+			trident->ChanMap[T4D_BANK_A] |= 1 << idx;
+			trident->synth.ChanSynthCount++;
+			return idx;
+		}
+	}
+	return -1;
+}
+
+/*---------------------------------------------------------------------------
+    void snd_trident_free_synth_channel( int channel )
+  
+    Description: Free hardware channel in Bank B (0-31).
+  
+    Parameters :  trident - pointer to target device class for 4DWave.
+	          channel - hardware channel number 0-63
+  
+    Return Value: none
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_free_synth_channel(trident_t *trident, int channel)
+{
+	if (channel < 0 || channel > 31)
+		return;
+	channel &= 0x1f;
+	if (trident->ChanMap[T4D_BANK_A] & (1 << channel)) {
+		trident->ChanMap[T4D_BANK_A] &= ~(1 << channel);
+		trident->synth.ChanSynthCount--;
+	}
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_write_voice_regs
+  
+   Description: This routine will complete and write the 5 hardware channel
+                registers to hardware.
+  
+   Paramters:   trident - pointer to target device class for 4DWave.
+                voice - synthesizer voice structure
+                Each register field.
+  
+  ---------------------------------------------------------------------------*/
+
+void snd_trident_write_voice_regs(trident_t * trident,
+				  snd_trident_voice_t * voice)
+{
+	unsigned int FmcRvolCvol;
+	unsigned int regs[5];
+
+	regs[1] = voice->LBA;
+	regs[4] = (voice->GVSel << 31) |
+		  ((voice->Pan & 0x0000007f) << 24) |
+		  ((voice->CTRL & 0x0000000f) << 12);
+	FmcRvolCvol = ((voice->FMC & 3) << 14) |
+	              ((voice->RVol & 0x7f) << 7) |
+	              (voice->CVol & 0x7f);
+
+	switch (trident->device) {
+	case TRIDENT_DEVICE_ID_SI7018:
+		regs[4] |= voice->number > 31 ?
+				(voice->Vol & 0x000003ff) :
+				((voice->Vol & 0x00003fc) << (16-2)) |
+				(voice->EC & 0x00000fff);
+		regs[0] = (voice->CSO << 16) | ((voice->Alpha & 0x00000fff) << 4) | (voice->FMS & 0x0000000f);
+		regs[2] = (voice->ESO << 16) | (voice->Delta & 0x0ffff);
+		regs[3] = (voice->Attribute << 16) | FmcRvolCvol;
+		break;
+	case TRIDENT_DEVICE_ID_DX:
+		regs[4] |= ((voice->Vol & 0x000003fc) << (16-2)) |
+			   (voice->EC & 0x00000fff);
+		regs[0] = (voice->CSO << 16) | ((voice->Alpha & 0x00000fff) << 4) | (voice->FMS & 0x0000000f);
+		regs[2] = (voice->ESO << 16) | (voice->Delta & 0x0ffff);
+		regs[3] = FmcRvolCvol;
+		break;
+	case TRIDENT_DEVICE_ID_NX:
+		regs[4] |= ((voice->Vol & 0x000003fc) << (16-2)) |
+			   (voice->EC & 0x00000fff);
+		regs[0] = (voice->Delta << 24) | (voice->CSO & 0x00ffffff);
+		regs[2] = ((voice->Delta << 16) & 0xff000000) | (voice->ESO & 0x00ffffff);
+		regs[3] = (voice->Alpha << 20) | ((voice->FMS & 0x0000000f) << 16) | FmcRvolCvol;
+		break;
+	default:
+		snd_BUG();
+	}
+
+	outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+	outl(regs[0], TRID_REG(trident, CH_START + 0));
+	outl(regs[1], TRID_REG(trident, CH_START + 4));
+	outl(regs[2], TRID_REG(trident, CH_START + 8));
+	outl(regs[3], TRID_REG(trident, CH_START + 12));
+	outl(regs[4], TRID_REG(trident, CH_START + 16));
+
+#if 0
+	printk("written %i channel:\n", voice->number);
+	printk("  regs[0] = 0x%x/0x%x\n", regs[0], inl(TRID_REG(trident, CH_START + 0)));
+	printk("  regs[1] = 0x%x/0x%x\n", regs[1], inl(TRID_REG(trident, CH_START + 4)));
+	printk("  regs[2] = 0x%x/0x%x\n", regs[2], inl(TRID_REG(trident, CH_START + 8)));
+	printk("  regs[3] = 0x%x/0x%x\n", regs[3], inl(TRID_REG(trident, CH_START + 12)));
+	printk("  regs[4] = 0x%x/0x%x\n", regs[4], inl(TRID_REG(trident, CH_START + 16)));
+#endif
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_write_cso_reg
+  
+   Description: This routine will write the new CSO offset
+                register to hardware.
+  
+   Paramters:   trident - pointer to target device class for 4DWave.
+                voice - synthesizer voice structure
+                CSO - new CSO value
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_write_cso_reg(trident_t * trident, snd_trident_voice_t * voice, unsigned int CSO)
+{
+	voice->CSO = CSO;
+	outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+	if (trident->device != TRIDENT_DEVICE_ID_NX) {
+		outw(voice->CSO, TRID_REG(trident, CH_DX_CSO_ALPHA_FMS) + 2);
+	} else {
+		outl((voice->Delta << 24) | (voice->CSO & 0x00ffffff), TRID_REG(trident, CH_NX_DELTA_CSO));
+	}
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_write_eso_reg
+  
+   Description: This routine will write the new ESO offset
+                register to hardware.
+  
+   Paramters:   trident - pointer to target device class for 4DWave.
+                voice - synthesizer voice structure
+                ESO - new ESO value
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_write_eso_reg(trident_t * trident, snd_trident_voice_t * voice, unsigned int ESO)
+{
+	voice->ESO = ESO;
+	outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+	if (trident->device != TRIDENT_DEVICE_ID_NX) {
+		outw(voice->ESO, TRID_REG(trident, CH_DX_ESO_DELTA) + 2);
+	} else {
+		outl(((voice->Delta << 16) & 0xff000000) | (voice->ESO & 0x00ffffff), TRID_REG(trident, CH_NX_DELTA_ESO));
+	}
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_write_vol_reg
+  
+   Description: This routine will write the new voice volume
+                register to hardware.
+  
+   Paramters:   trident - pointer to target device class for 4DWave.
+                voice - synthesizer voice structure
+                Vol - new voice volume
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_write_vol_reg(trident_t * trident, snd_trident_voice_t * voice, unsigned int Vol)
+{
+	voice->Vol = Vol;
+	outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+	switch (trident->device) {
+	case TRIDENT_DEVICE_ID_DX:
+	case TRIDENT_DEVICE_ID_NX:
+		outb(voice->Vol >> 2, TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC + 2));
+		break;
+	case TRIDENT_DEVICE_ID_SI7018:
+		// printk("voice->Vol = 0x%x\n", voice->Vol);
+		outw((voice->CTRL << 12) | voice->Vol, TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC));
+		break;
+	}
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_write_pan_reg
+  
+   Description: This routine will write the new voice pan
+                register to hardware.
+  
+   Paramters:   trident - pointer to target device class for 4DWave.
+                voice - synthesizer voice structure
+                Pan - new pan value
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_write_pan_reg(trident_t * trident, snd_trident_voice_t * voice, unsigned int Pan)
+{
+	voice->Pan = Pan;
+	outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+	outb(((voice->GVSel & 0x01) << 7) | (voice->Pan & 0x7f), TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC + 3));
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_write_rvol_reg
+  
+   Description: This routine will write the new reverb volume
+                register to hardware.
+  
+   Paramters:   trident - pointer to target device class for 4DWave.
+                voice - synthesizer voice structure
+                RVol - new reverb volume
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_write_rvol_reg(trident_t * trident, snd_trident_voice_t * voice, unsigned int RVol)
+{
+	voice->RVol = RVol;
+	outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+	outw(((voice->FMC & 0x0003) << 14) | ((voice->RVol & 0x007f) << 7) | (voice->CVol & 0x007f),
+	     TRID_REG(trident, trident->device == TRIDENT_DEVICE_ID_NX ? CH_NX_ALPHA_FMS_FMC_RVOL_CVOL : CH_DX_FMC_RVOL_CVOL));
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_write_cvol_reg
+  
+   Description: This routine will write the new chorus volume
+                register to hardware.
+  
+   Paramters:   trident - pointer to target device class for 4DWave.
+                voice - synthesizer voice structure
+                CVol - new chorus volume
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_write_cvol_reg(trident_t * trident, snd_trident_voice_t * voice, unsigned int CVol)
+{
+	voice->CVol = CVol;
+	outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+	outw(((voice->FMC & 0x0003) << 14) | ((voice->RVol & 0x007f) << 7) | (voice->CVol & 0x007f),
+	     TRID_REG(trident, trident->device == TRIDENT_DEVICE_ID_NX ? CH_NX_ALPHA_FMS_FMC_RVOL_CVOL : CH_DX_FMC_RVOL_CVOL));
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_convert_rate
+
+   Description: This routine converts rate in HZ to hardware delta value.
+  
+   Paramters:   trident - pointer to target device class for 4DWave.
+                rate - Real or Virtual channel number.
+  
+   Returns:     Delta value.
+  
+  ---------------------------------------------------------------------------*/
+static unsigned int snd_trident_convert_rate(unsigned int rate)
+{
+	unsigned int delta;
+
+	// We special case 44100 and 8000 since rounding with the equation
+	// does not give us an accurate enough value. For 11025 and 22050
+	// the equation gives us the best answer. All other frequencies will
+	// also use the equation. JDW
+	if (rate == 44100)
+		delta = 0xeb3;
+	else if (rate == 8000)
+		delta = 0x2ab;
+	else if (rate == 48000)
+		delta = 0x1000;
+	else
+		delta = (((rate << 12) + 24000) / 48000) & 0x0000ffff;
+	return delta;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_convert_adc_rate
+
+   Description: This routine converts rate in HZ to hardware delta value.
+  
+   Paramters:   trident - pointer to target device class for 4DWave.
+                rate - Real or Virtual channel number.
+  
+   Returns:     Delta value.
+  
+  ---------------------------------------------------------------------------*/
+static unsigned int snd_trident_convert_adc_rate(unsigned int rate)
+{
+	unsigned int delta;
+
+	// We special case 44100 and 8000 since rounding with the equation
+	// does not give us an accurate enough value. For 11025 and 22050
+	// the equation gives us the best answer. All other frequencies will
+	// also use the equation. JDW
+	if (rate == 44100)
+		delta = 0x116a;
+	else if (rate == 8000)
+		delta = 0x6000;
+	else if (rate == 48000)
+		delta = 0x1000;
+	else
+		delta = ((48000 << 12) / rate) & 0x0000ffff;
+	return delta;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_spurious_threshold
+
+   Description: This routine converts rate in HZ to spurious threshold.
+  
+   Paramters:   trident - pointer to target device class for 4DWave.
+                rate - Real or Virtual channel number.
+  
+   Returns:     Delta value.
+  
+  ---------------------------------------------------------------------------*/
+static unsigned int snd_trident_spurious_threshold(unsigned int rate, unsigned int period_size)
+{
+	unsigned int res = (rate * period_size) / 48000;
+	if (res < 64)
+		res = res / 2;
+	else
+		res -= 32;
+	return res;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_control_mode
+
+   Description: This routine returns a control mode for a PCM channel.
+  
+   Paramters:   trident - pointer to target device class for 4DWave.
+                substream  - PCM substream
+  
+   Returns:     Control value.
+  
+  ---------------------------------------------------------------------------*/
+static unsigned int snd_trident_control_mode(snd_pcm_substream_t *substream)
+{
+	unsigned int CTRL;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+
+	/* set ctrl mode
+	   CTRL default: 8-bit (unsigned) mono, loop mode enabled
+	 */
+	CTRL = 0x00000001;
+	if (snd_pcm_format_width(runtime->format) == 16)
+		CTRL |= 0x00000008;	// 16-bit data
+	if (snd_pcm_format_signed(runtime->format))
+		CTRL |= 0x00000002;	// signed data
+	if (runtime->channels > 1)
+		CTRL |= 0x00000004;	// stereo data
+	return CTRL;
+}
+
+/*
+ *  PCM part
+ */
+
+/*---------------------------------------------------------------------------
+   snd_trident_ioctl
+  
+   Description: Device I/O control handler for playback/capture parameters.
+  
+   Paramters:   substream  - PCM substream class
+                cmd     - what ioctl message to process
+                arg     - additional message infoarg     
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_ioctl(snd_pcm_substream_t * substream,
+			     unsigned int cmd,
+			     void *arg)
+{
+	/* FIXME: it seems that with small periods the behaviour of
+	          trident hardware is unpredictable and interrupt generator
+	          is broken */
+	return snd_pcm_lib_ioctl(substream, cmd, arg);
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_allocate_pcm_mem
+  
+   Description: Allocate PCM ring buffer for given substream
+  
+   Parameters:  substream  - PCM substream class
+		hw_params  - hardware parameters
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_allocate_pcm_mem(snd_pcm_substream_t * substream,
+					snd_pcm_hw_params_t * hw_params)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data;
+	int err;
+
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+	if (trident->tlb.entries) {
+		if (err > 0) { /* change */
+			if (voice->memblk)
+				snd_trident_free_pages(trident, voice->memblk);
+			voice->memblk = snd_trident_alloc_pages(trident, substream);
+			if (voice->memblk == NULL)
+				return -ENOMEM;
+		}
+	}
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_allocate_evoice
+  
+   Description: Allocate extra voice as interrupt generator
+  
+   Parameters:  substream  - PCM substream class
+		hw_params  - hardware parameters
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_allocate_evoice(snd_pcm_substream_t * substream,
+				       snd_pcm_hw_params_t * hw_params)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data;
+	snd_trident_voice_t *evoice = voice->extra;
+
+	/* voice management */
+
+	if (params_buffer_size(hw_params) / 2 != params_period_size(hw_params)) {
+		if (evoice == NULL) {
+			evoice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
+			if (evoice == NULL)
+				return -ENOMEM;
+			voice->extra = evoice;
+			evoice->substream = substream;
+		}
+	} else {
+		if (evoice != NULL) {
+			snd_trident_free_voice(trident, evoice);
+			voice->extra = evoice = NULL;
+		}
+	}
+
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_hw_params
+  
+   Description: Set the hardware parameters for the playback device.
+  
+   Parameters:  substream  - PCM substream class
+		hw_params  - hardware parameters
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_hw_params(snd_pcm_substream_t * substream,
+				 snd_pcm_hw_params_t * hw_params)
+{
+	int err;
+
+	err = snd_trident_allocate_pcm_mem(substream, hw_params);
+	if (err >= 0)
+		err = snd_trident_allocate_evoice(substream, hw_params);
+	return err;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_playback_hw_free
+  
+   Description: Release the hardware resources for the playback device.
+  
+   Parameters:  substream  - PCM substream class
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_hw_free(snd_pcm_substream_t * substream)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data;
+	snd_trident_voice_t *evoice = voice ? voice->extra : NULL;
+
+	if (trident->tlb.entries) {
+		if (voice && voice->memblk) {
+			snd_trident_free_pages(trident, voice->memblk);
+			voice->memblk = NULL;
+		}
+	}
+	snd_pcm_lib_free_pages(substream);
+	if (evoice != NULL) {
+		snd_trident_free_voice(trident, evoice);
+		voice->extra = NULL;
+	}
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_playback_prepare
+  
+   Description: Prepare playback device for playback.
+  
+   Parameters:  substream  - PCM substream class
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_playback_prepare(snd_pcm_substream_t * substream)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data;
+	snd_trident_voice_t *evoice = voice->extra;
+	snd_trident_pcm_mixer_t *mix = &trident->pcm_mixer[substream->number];
+
+	spin_lock_irq(&trident->reg_lock);	
+
+	/* set delta (rate) value */
+	voice->Delta = snd_trident_convert_rate(runtime->rate);
+	voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size);
+
+	/* set Loop Begin Address */
+	if (voice->memblk)
+		voice->LBA = voice->memblk->offset;
+	else
+		voice->LBA = runtime->dma_addr;
+ 
+	voice->CSO = 0;
+	voice->ESO = runtime->buffer_size - 1;	/* in samples */
+	voice->CTRL = snd_trident_control_mode(substream);
+	voice->FMC = 3;
+	voice->GVSel = 1;
+	voice->EC = 0;
+	voice->Alpha = 0;
+	voice->FMS = 0;
+	voice->Vol = mix->vol;
+	voice->RVol = mix->rvol;
+	voice->CVol = mix->cvol;
+	voice->Pan = mix->pan;
+	voice->Attribute = 0;
+#if 0
+	voice->Attribute = (1<<(30-16))|(2<<(26-16))|
+			   (0<<(24-16))|(0x1f<<(19-16));
+#else
+	voice->Attribute = 0;
+#endif
+
+	snd_trident_write_voice_regs(trident, voice);
+
+	if (evoice != NULL) {
+		evoice->Delta = voice->Delta;
+		evoice->spurious_threshold = voice->spurious_threshold;
+		evoice->LBA = voice->LBA;
+		evoice->CSO = 0;
+		evoice->ESO = (runtime->period_size * 2) + 4 - 1; /* in samples */
+		evoice->CTRL = voice->CTRL;
+		evoice->FMC = 3;
+		evoice->GVSel = trident->device == TRIDENT_DEVICE_ID_SI7018 ? 0 : 1;
+		evoice->EC = 0;
+		evoice->Alpha = 0;
+		evoice->FMS = 0;
+		evoice->Vol = 0x3ff;			/* mute */
+		evoice->RVol = evoice->CVol = 0x7f;	/* mute */
+		evoice->Pan = 0x7f;			/* mute */
+#if 0
+		evoice->Attribute = (1<<(30-16))|(2<<(26-16))|
+				    (0<<(24-16))|(0x1f<<(19-16));
+#else
+		evoice->Attribute = 0;
+#endif
+		snd_trident_write_voice_regs(trident, evoice);
+		evoice->isync2 = 1;
+		evoice->isync_mark = runtime->period_size;
+		evoice->ESO = (runtime->period_size * 2) - 1;
+	}
+
+	spin_unlock_irq(&trident->reg_lock);
+
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_capture_hw_params
+  
+   Description: Set the hardware parameters for the capture device.
+  
+   Parameters:  substream  - PCM substream class
+		hw_params  - hardware parameters
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_capture_hw_params(snd_pcm_substream_t * substream,
+					 snd_pcm_hw_params_t * hw_params)
+{
+	return snd_trident_allocate_pcm_mem(substream, hw_params);
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_capture_prepare
+  
+   Description: Prepare capture device for playback.
+  
+   Parameters:  substream  - PCM substream class
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_capture_prepare(snd_pcm_substream_t * substream)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data;
+	unsigned int val, ESO_bytes;
+
+	spin_lock_irq(&trident->reg_lock);
+
+	// Initilize the channel and set channel Mode
+	outb(0, TRID_REG(trident, LEGACY_DMAR15));
+
+	// Set DMA channel operation mode register
+	outb(0x54, TRID_REG(trident, LEGACY_DMAR11));
+
+	// Set channel buffer Address, DMAR0 expects contiguous PCI memory area	
+	voice->LBA = runtime->dma_addr;
+	outl(voice->LBA, TRID_REG(trident, LEGACY_DMAR0));
+	if (voice->memblk)
+		voice->LBA = voice->memblk->offset;
+
+	// set ESO
+	ESO_bytes = snd_pcm_lib_buffer_bytes(substream) - 1;
+	outb((ESO_bytes & 0x00ff0000) >> 16, TRID_REG(trident, LEGACY_DMAR6));
+	outw((ESO_bytes & 0x0000ffff), TRID_REG(trident, LEGACY_DMAR4));
+	ESO_bytes++;
+
+	// Set channel sample rate, 4.12 format
+	val = (((unsigned int) 48000L << 12) + (runtime->rate/2)) / runtime->rate;
+	outw(val, TRID_REG(trident, T4D_SBDELTA_DELTA_R));
+
+	// Set channel interrupt blk length
+	if (snd_pcm_format_width(runtime->format) == 16) {
+		val = (unsigned short) ((ESO_bytes >> 1) - 1);
+	} else {
+		val = (unsigned short) (ESO_bytes - 1);
+	}
+
+	outl((val << 16) | val, TRID_REG(trident, T4D_SBBL_SBCL));
+
+	// Right now, set format and start to run captureing, 
+	// continuous run loop enable.
+	trident->bDMAStart = 0x19;	// 0001 1001b
+
+	if (snd_pcm_format_width(runtime->format) == 16)
+		trident->bDMAStart |= 0x80;
+	if (snd_pcm_format_signed(runtime->format))
+		trident->bDMAStart |= 0x20;
+	if (runtime->channels > 1)
+		trident->bDMAStart |= 0x40;
+
+	// Prepare capture intr channel
+
+	voice->Delta = snd_trident_convert_rate(runtime->rate);
+	voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size);
+	voice->isync = 1;
+	voice->isync_mark = runtime->period_size;
+	voice->isync_max = runtime->buffer_size;
+
+	// Set voice parameters
+	voice->CSO = 0;
+	voice->ESO = voice->isync_ESO = (runtime->period_size * 2) + 6 - 1;
+	voice->CTRL = snd_trident_control_mode(substream);
+	voice->FMC = 3;
+	voice->RVol = 0x7f;
+	voice->CVol = 0x7f;
+	voice->GVSel = 1;
+	voice->Pan = 0x7f;		/* mute */
+	voice->Vol = 0x3ff;		/* mute */
+	voice->EC = 0;
+	voice->Alpha = 0;
+	voice->FMS = 0;
+	voice->Attribute = 0;
+
+	snd_trident_write_voice_regs(trident, voice);
+
+	spin_unlock_irq(&trident->reg_lock);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_si7018_capture_hw_params
+  
+   Description: Set the hardware parameters for the capture device.
+  
+   Parameters:  substream  - PCM substream class
+		hw_params  - hardware parameters
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_si7018_capture_hw_params(snd_pcm_substream_t * substream,
+						snd_pcm_hw_params_t * hw_params)
+{
+	int err;
+
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+
+	return snd_trident_allocate_evoice(substream, hw_params);
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_si7018_capture_hw_free
+  
+   Description: Release the hardware resources for the capture device.
+  
+   Parameters:  substream  - PCM substream class
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_si7018_capture_hw_free(snd_pcm_substream_t * substream)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data;
+	snd_trident_voice_t *evoice = voice ? voice->extra : NULL;
+
+	snd_pcm_lib_free_pages(substream);
+	if (evoice != NULL) {
+		snd_trident_free_voice(trident, evoice);
+		voice->extra = NULL;
+	}
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_si7018_capture_prepare
+  
+   Description: Prepare capture device for playback.
+  
+   Parameters:  substream  - PCM substream class
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_si7018_capture_prepare(snd_pcm_substream_t * substream)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data;
+	snd_trident_voice_t *evoice = voice->extra;
+
+	spin_lock_irq(&trident->reg_lock);
+
+	voice->LBA = runtime->dma_addr;
+	voice->Delta = snd_trident_convert_adc_rate(runtime->rate);
+	voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size);
+
+	// Set voice parameters
+	voice->CSO = 0;
+	voice->ESO = runtime->buffer_size - 1;		/* in samples */
+	voice->CTRL = snd_trident_control_mode(substream);
+	voice->FMC = 0;
+	voice->RVol = 0;
+	voice->CVol = 0;
+	voice->GVSel = 1;
+	voice->Pan = T4D_DEFAULT_PCM_PAN;
+	voice->Vol = 0;
+	voice->EC = 0;
+	voice->Alpha = 0;
+	voice->FMS = 0;
+
+	voice->Attribute = (2 << (30-16)) |
+			   (2 << (26-16)) |
+			   (2 << (24-16)) |
+			   (1 << (23-16));
+
+	snd_trident_write_voice_regs(trident, voice);
+
+	if (evoice != NULL) {
+		evoice->Delta = snd_trident_convert_rate(runtime->rate);
+		evoice->spurious_threshold = voice->spurious_threshold;
+		evoice->LBA = voice->LBA;
+		evoice->CSO = 0;
+		evoice->ESO = (runtime->period_size * 2) + 20 - 1; /* in samples, 20 means correction */
+		evoice->CTRL = voice->CTRL;
+		evoice->FMC = 3;
+		evoice->GVSel = 0;
+		evoice->EC = 0;
+		evoice->Alpha = 0;
+		evoice->FMS = 0;
+		evoice->Vol = 0x3ff;			/* mute */
+		evoice->RVol = evoice->CVol = 0x7f;	/* mute */
+		evoice->Pan = 0x7f;			/* mute */
+		evoice->Attribute = 0;
+		snd_trident_write_voice_regs(trident, evoice);
+		evoice->isync2 = 1;
+		evoice->isync_mark = runtime->period_size;
+		evoice->ESO = (runtime->period_size * 2) - 1;
+	}
+	
+	spin_unlock_irq(&trident->reg_lock);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_foldback_prepare
+  
+   Description: Prepare foldback capture device for playback.
+  
+   Parameters:  substream  - PCM substream class
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_foldback_prepare(snd_pcm_substream_t * substream)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data;
+	snd_trident_voice_t *evoice = voice->extra;
+
+	spin_lock_irq(&trident->reg_lock);
+
+	/* Set channel buffer Address */
+	if (voice->memblk)
+		voice->LBA = voice->memblk->offset;
+	else
+		voice->LBA = runtime->dma_addr;
+
+	/* set target ESO for channel */
+	voice->ESO = runtime->buffer_size - 1;	/* in samples */
+
+	/* set sample rate */
+	voice->Delta = 0x1000;
+	voice->spurious_threshold = snd_trident_spurious_threshold(48000, runtime->period_size);
+
+	voice->CSO = 0;
+	voice->CTRL = snd_trident_control_mode(substream);
+	voice->FMC = 3;
+	voice->RVol = 0x7f;
+	voice->CVol = 0x7f;
+	voice->GVSel = 1;
+	voice->Pan = 0x7f;	/* mute */
+	voice->Vol = 0x3ff;	/* mute */
+	voice->EC = 0;
+	voice->Alpha = 0;
+	voice->FMS = 0;
+	voice->Attribute = 0;
+
+	/* set up capture channel */
+	outb(((voice->number & 0x3f) | 0x80), TRID_REG(trident, T4D_RCI + voice->foldback_chan));
+
+	snd_trident_write_voice_regs(trident, voice);
+
+	if (evoice != NULL) {
+		evoice->Delta = voice->Delta;
+		evoice->spurious_threshold = voice->spurious_threshold;
+		evoice->LBA = voice->LBA;
+		evoice->CSO = 0;
+		evoice->ESO = (runtime->period_size * 2) + 4 - 1; /* in samples */
+		evoice->CTRL = voice->CTRL;
+		evoice->FMC = 3;
+		evoice->GVSel = trident->device == TRIDENT_DEVICE_ID_SI7018 ? 0 : 1;
+		evoice->EC = 0;
+		evoice->Alpha = 0;
+		evoice->FMS = 0;
+		evoice->Vol = 0x3ff;			/* mute */
+		evoice->RVol = evoice->CVol = 0x7f;	/* mute */
+		evoice->Pan = 0x7f;			/* mute */
+		evoice->Attribute = 0;
+		snd_trident_write_voice_regs(trident, evoice);
+		evoice->isync2 = 1;
+		evoice->isync_mark = runtime->period_size;
+		evoice->ESO = (runtime->period_size * 2) - 1;
+	}
+
+	spin_unlock_irq(&trident->reg_lock);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_spdif_hw_params
+  
+   Description: Set the hardware parameters for the spdif device.
+  
+   Parameters:  substream  - PCM substream class
+		hw_params  - hardware parameters
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_hw_params(snd_pcm_substream_t * substream,
+				       snd_pcm_hw_params_t * hw_params)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	unsigned int old_bits = 0, change = 0;
+	int err;
+
+	err = snd_trident_allocate_pcm_mem(substream, hw_params);
+	if (err < 0)
+		return err;
+
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+		err = snd_trident_allocate_evoice(substream, hw_params);
+		if (err < 0)
+			return err;
+	}
+
+	/* prepare SPDIF channel */
+	spin_lock_irq(&trident->reg_lock);
+	old_bits = trident->spdif_pcm_bits;
+	if (old_bits & IEC958_AES0_PROFESSIONAL)
+		trident->spdif_pcm_bits &= ~IEC958_AES0_PRO_FS;
+	else
+		trident->spdif_pcm_bits &= ~(IEC958_AES3_CON_FS << 24);
+	if (params_rate(hw_params) >= 48000) {
+		trident->spdif_pcm_ctrl = 0x3c;	// 48000 Hz
+		trident->spdif_pcm_bits |=
+			trident->spdif_bits & IEC958_AES0_PROFESSIONAL ?
+				IEC958_AES0_PRO_FS_48000 :
+				(IEC958_AES3_CON_FS_48000 << 24);
+	}
+	else if (params_rate(hw_params) >= 44100) {
+		trident->spdif_pcm_ctrl = 0x3e;	// 44100 Hz
+		trident->spdif_pcm_bits |=
+			trident->spdif_bits & IEC958_AES0_PROFESSIONAL ?
+				IEC958_AES0_PRO_FS_44100 :
+				(IEC958_AES3_CON_FS_44100 << 24);
+	}
+	else {
+		trident->spdif_pcm_ctrl = 0x3d;	// 32000 Hz
+		trident->spdif_pcm_bits |=
+			trident->spdif_bits & IEC958_AES0_PROFESSIONAL ?
+				IEC958_AES0_PRO_FS_32000 :
+				(IEC958_AES3_CON_FS_32000 << 24);
+	}
+	change = old_bits != trident->spdif_pcm_bits;
+	spin_unlock_irq(&trident->reg_lock);
+
+	if (change)
+		snd_ctl_notify(trident->card, SNDRV_CTL_EVENT_MASK_VALUE, &trident->spdif_pcm_ctl->id);
+
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_spdif_prepare
+  
+   Description: Prepare SPDIF device for playback.
+  
+   Parameters:  substream  - PCM substream class
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_prepare(snd_pcm_substream_t * substream)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data;
+	snd_trident_voice_t *evoice = voice->extra;
+	snd_trident_pcm_mixer_t *mix = &trident->pcm_mixer[substream->number];
+	unsigned int RESO, LBAO;
+	unsigned int temp;
+
+	spin_lock_irq(&trident->reg_lock);
+
+	if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+
+		/* set delta (rate) value */
+		voice->Delta = snd_trident_convert_rate(runtime->rate);
+		voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size);
+
+		/* set Loop Back Address */
+		LBAO = runtime->dma_addr;
+		if (voice->memblk)
+			voice->LBA = voice->memblk->offset;
+		else
+			voice->LBA = LBAO;
+
+		voice->isync = 1;
+		voice->isync3 = 1;
+		voice->isync_mark = runtime->period_size;
+		voice->isync_max = runtime->buffer_size;
+
+		/* set target ESO for channel */
+		RESO = runtime->buffer_size - 1;
+		voice->ESO = voice->isync_ESO = (runtime->period_size * 2) + 6 - 1;
+
+		/* set ctrl mode */
+		voice->CTRL = snd_trident_control_mode(substream);
+
+		voice->FMC = 3;
+		voice->RVol = 0x7f;
+		voice->CVol = 0x7f;
+		voice->GVSel = 1;
+		voice->Pan = 0x7f;
+		voice->Vol = 0x3ff;
+		voice->EC = 0;
+		voice->CSO = 0;
+		voice->Alpha = 0;
+		voice->FMS = 0;
+		voice->Attribute = 0;
+
+		/* prepare surrogate IRQ channel */
+		snd_trident_write_voice_regs(trident, voice);
+
+		outw((RESO & 0xffff), TRID_REG(trident, NX_SPESO));
+		outb((RESO >> 16), TRID_REG(trident, NX_SPESO + 2));
+		outl((LBAO & 0xfffffffc), TRID_REG(trident, NX_SPLBA));
+		outw((voice->CSO & 0xffff), TRID_REG(trident, NX_SPCTRL_SPCSO));
+		outb((voice->CSO >> 16), TRID_REG(trident, NX_SPCTRL_SPCSO + 2));
+
+		/* set SPDIF setting */
+		outb(trident->spdif_pcm_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
+		outl(trident->spdif_pcm_bits, TRID_REG(trident, NX_SPCSTATUS));
+
+	} else {	/* SiS */
+	
+		/* set delta (rate) value */
+		voice->Delta = 0x800;
+		voice->spurious_threshold = snd_trident_spurious_threshold(48000, runtime->period_size);
+
+		/* set Loop Begin Address */
+		if (voice->memblk)
+			voice->LBA = voice->memblk->offset;
+		else
+			voice->LBA = runtime->dma_addr;
+
+		voice->CSO = 0;
+		voice->ESO = runtime->buffer_size - 1;	/* in samples */
+		voice->CTRL = snd_trident_control_mode(substream);
+		voice->FMC = 3;
+		voice->GVSel = 1;
+		voice->EC = 0;
+		voice->Alpha = 0;
+		voice->FMS = 0;
+		voice->Vol = mix->vol;
+		voice->RVol = mix->rvol;
+		voice->CVol = mix->cvol;
+		voice->Pan = mix->pan;
+		voice->Attribute = (1<<(30-16))|(7<<(26-16))|
+				   (0<<(24-16))|(0<<(19-16));
+
+		snd_trident_write_voice_regs(trident, voice);
+
+		if (evoice != NULL) {
+			evoice->Delta = voice->Delta;
+			evoice->spurious_threshold = voice->spurious_threshold;
+			evoice->LBA = voice->LBA;
+			evoice->CSO = 0;
+			evoice->ESO = (runtime->period_size * 2) + 4 - 1; /* in samples */
+			evoice->CTRL = voice->CTRL;
+			evoice->FMC = 3;
+			evoice->GVSel = trident->device == TRIDENT_DEVICE_ID_SI7018 ? 0 : 1;
+			evoice->EC = 0;
+			evoice->Alpha = 0;
+			evoice->FMS = 0;
+			evoice->Vol = 0x3ff;			/* mute */
+			evoice->RVol = evoice->CVol = 0x7f;	/* mute */
+			evoice->Pan = 0x7f;			/* mute */
+			evoice->Attribute = 0;
+			snd_trident_write_voice_regs(trident, evoice);
+			evoice->isync2 = 1;
+			evoice->isync_mark = runtime->period_size;
+			evoice->ESO = (runtime->period_size * 2) - 1;
+		}
+
+		outl(trident->spdif_pcm_bits, TRID_REG(trident, SI_SPDIF_CS));
+		temp = inl(TRID_REG(trident, T4D_LFO_GC_CIR));
+		temp &= ~(1<<19);
+		outl(temp, TRID_REG(trident, T4D_LFO_GC_CIR));
+		temp = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+		temp |= SPDIF_EN;
+		outl(temp, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+	}
+
+	spin_unlock_irq(&trident->reg_lock);
+
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_trigger
+  
+   Description: Start/stop devices
+  
+   Parameters:  substream  - PCM substream class
+   		cmd	- trigger command (STOP, GO)
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_trigger(snd_pcm_substream_t *substream,
+			       int cmd)
+				    
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	struct list_head *pos;
+	snd_pcm_substream_t *s;
+	unsigned int what, whati, capture_flag, spdif_flag;
+	snd_trident_voice_t *voice, *evoice;
+	unsigned int val, go;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		go = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		go = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+	what = whati = capture_flag = spdif_flag = 0;
+	spin_lock(&trident->reg_lock);
+	val = inl(TRID_REG(trident, T4D_STIMER)) & 0x00ffffff;
+	snd_pcm_group_for_each(pos, substream) {
+		s = snd_pcm_group_substream_entry(pos);
+		if ((trident_t *) snd_pcm_substream_chip(s) == trident) {
+			voice = (snd_trident_voice_t *) s->runtime->private_data;
+			evoice = voice->extra;
+			what |= 1 << (voice->number & 0x1f);
+			if (evoice == NULL) {
+				whati |= 1 << (voice->number & 0x1f);
+			} else {
+				what |= 1 << (evoice->number & 0x1f);
+				whati |= 1 << (evoice->number & 0x1f);
+				if (go)
+					evoice->stimer = val;
+			}
+			if (go) {
+				voice->running = 1;
+				voice->stimer = val;
+			} else {
+				voice->running = 0;
+			}
+			snd_pcm_trigger_done(s, substream);
+			if (voice->capture)
+				capture_flag = 1;
+			if (voice->spdif)
+				spdif_flag = 1;
+		}
+	}
+	if (spdif_flag) {
+		if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+			outl(trident->spdif_pcm_bits, TRID_REG(trident, NX_SPCSTATUS));
+			outb(trident->spdif_pcm_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
+		} else {
+			outl(trident->spdif_pcm_bits, TRID_REG(trident, SI_SPDIF_CS));
+			val = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) | SPDIF_EN;
+			outl(val, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+		}
+	}
+	if (!go)
+		outl(what, TRID_REG(trident, T4D_STOP_B));
+	val = inl(TRID_REG(trident, T4D_AINTEN_B));
+	if (go) {
+		val |= whati;
+	} else {
+		val &= ~whati;
+	}
+	outl(val, TRID_REG(trident, T4D_AINTEN_B));
+	if (go) {
+		outl(what, TRID_REG(trident, T4D_START_B));
+
+		if (capture_flag && trident->device != TRIDENT_DEVICE_ID_SI7018)
+			outb(trident->bDMAStart, TRID_REG(trident, T4D_SBCTRL_SBE2R_SBDD));
+	} else {
+		if (capture_flag && trident->device != TRIDENT_DEVICE_ID_SI7018)
+			outb(0x00, TRID_REG(trident, T4D_SBCTRL_SBE2R_SBDD));
+	}
+	spin_unlock(&trident->reg_lock);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_playback_pointer
+  
+   Description: This routine return the playback position
+                
+   Parameters:	substream  - PCM substream class
+
+   Returns:     position of buffer
+  
+  ---------------------------------------------------------------------------*/
+
+static snd_pcm_uframes_t snd_trident_playback_pointer(snd_pcm_substream_t * substream)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data;
+	unsigned int cso;
+
+	if (!voice->running)
+		return 0;
+
+	spin_lock(&trident->reg_lock);
+
+	outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+
+	if (trident->device != TRIDENT_DEVICE_ID_NX) {
+		cso = inw(TRID_REG(trident, CH_DX_CSO_ALPHA_FMS + 2));
+	} else {		// ID_4DWAVE_NX
+		cso = (unsigned int) inl(TRID_REG(trident, CH_NX_DELTA_CSO)) & 0x00ffffff;
+	}
+
+	spin_unlock(&trident->reg_lock);
+
+	if (cso >= runtime->buffer_size)
+		cso = 0;
+
+	return cso;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_capture_pointer
+  
+   Description: This routine return the capture position
+                
+   Paramters:   pcm1    - PCM device class
+
+   Returns:     position of buffer
+  
+  ---------------------------------------------------------------------------*/
+
+static snd_pcm_uframes_t snd_trident_capture_pointer(snd_pcm_substream_t * substream)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data;
+	unsigned int result;
+
+	if (!voice->running)
+		return 0;
+
+	result = inw(TRID_REG(trident, T4D_SBBL_SBCL));
+	if (runtime->channels > 1)
+		result >>= 1;
+	if (result > 0)
+		result = runtime->buffer_size - result;
+
+	return result;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_spdif_pointer
+  
+   Description: This routine return the SPDIF playback position
+                
+   Parameters:	substream  - PCM substream class
+
+   Returns:     position of buffer
+  
+  ---------------------------------------------------------------------------*/
+
+static snd_pcm_uframes_t snd_trident_spdif_pointer(snd_pcm_substream_t * substream)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data;
+	unsigned int result;
+
+	if (!voice->running)
+		return 0;
+
+	result = inl(TRID_REG(trident, NX_SPCTRL_SPCSO)) & 0x00ffffff;
+
+	return result;
+}
+
+/*
+ *  Playback support device description
+ */
+
+static snd_pcm_hardware_t snd_trident_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
+	.formats =		(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
+				 SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U16_LE),
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(256*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(256*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/*
+ *  Capture support device description
+ */
+
+static snd_pcm_hardware_t snd_trident_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
+	.formats =		(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
+				 SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U16_LE),
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/*
+ *  Foldback capture support device description
+ */
+
+static snd_pcm_hardware_t snd_trident_foldback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_48000,
+	.rate_min =		48000,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/*
+ *  SPDIF playback support device description
+ */
+
+static snd_pcm_hardware_t snd_trident_spdif =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
+				 SNDRV_PCM_RATE_48000),
+	.rate_min =		32000,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static snd_pcm_hardware_t snd_trident_spdif_7018 =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_48000,
+	.rate_min =		48000,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static void snd_trident_pcm_free_substream(snd_pcm_runtime_t *runtime)
+{
+	snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data;
+	trident_t *trident;
+
+	if (voice) {
+		trident = voice->trident;
+		snd_trident_free_voice(trident, voice);
+	}
+}
+
+static int snd_trident_playback_open(snd_pcm_substream_t * substream)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_trident_voice_t *voice;
+
+	voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
+	if (voice == NULL)
+		return -EAGAIN;
+	snd_trident_pcm_mixer_build(trident, voice, substream);
+	voice->substream = substream;
+	runtime->private_data = voice;
+	runtime->private_free = snd_trident_pcm_free_substream;
+	runtime->hw = snd_trident_playback;
+	snd_pcm_set_sync(substream);
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_playback_close
+  
+   Description: This routine will close the 4DWave playback device. For now 
+                we will simply free the dma transfer buffer.
+                
+   Parameters:	substream  - PCM substream class
+
+  ---------------------------------------------------------------------------*/
+static int snd_trident_playback_close(snd_pcm_substream_t * substream)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data;
+
+	snd_trident_pcm_mixer_free(trident, voice, substream);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_spdif_open
+  
+   Description: This routine will open the 4DWave SPDIF device.
+
+   Parameters:	substream  - PCM substream class
+
+   Returns:     status  - success or failure flag
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_open(snd_pcm_substream_t * substream)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	snd_trident_voice_t *voice;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	
+	voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
+	if (voice == NULL)
+		return -EAGAIN;
+	voice->spdif = 1;
+	voice->substream = substream;
+	spin_lock_irq(&trident->reg_lock);
+	trident->spdif_pcm_bits = trident->spdif_bits;
+	spin_unlock_irq(&trident->reg_lock);
+
+	runtime->private_data = voice;
+	runtime->private_free = snd_trident_pcm_free_substream;
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+		runtime->hw = snd_trident_spdif;
+	} else {
+		runtime->hw = snd_trident_spdif_7018;
+	}
+
+	trident->spdif_pcm_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	snd_ctl_notify(trident->card, SNDRV_CTL_EVENT_MASK_VALUE |
+		       SNDRV_CTL_EVENT_MASK_INFO, &trident->spdif_pcm_ctl->id);
+
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024);
+	return 0;
+}
+
+
+/*---------------------------------------------------------------------------
+   snd_trident_spdif_close
+  
+   Description: This routine will close the 4DWave SPDIF device.
+                
+   Parameters:	substream  - PCM substream class
+
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_close(snd_pcm_substream_t * substream)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	unsigned int temp;
+
+	spin_lock_irq(&trident->reg_lock);
+	// restore default SPDIF setting
+	if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+		outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
+		outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
+	} else {
+		outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
+		temp = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+		if (trident->spdif_ctrl) {
+			temp |= SPDIF_EN;
+		} else {
+			temp &= ~SPDIF_EN;
+		}
+		outl(temp, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+	}
+	spin_unlock_irq(&trident->reg_lock);
+	trident->spdif_pcm_ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	snd_ctl_notify(trident->card, SNDRV_CTL_EVENT_MASK_VALUE |
+		       SNDRV_CTL_EVENT_MASK_INFO, &trident->spdif_pcm_ctl->id);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_capture_open
+  
+   Description: This routine will open the 4DWave capture device.
+
+   Parameters:	substream  - PCM substream class
+
+   Returns:     status  - success or failure flag
+
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_capture_open(snd_pcm_substream_t * substream)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	snd_trident_voice_t *voice;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+
+	voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
+	if (voice == NULL)
+		return -EAGAIN;
+	voice->capture = 1;
+	voice->substream = substream;
+	runtime->private_data = voice;
+	runtime->private_free = snd_trident_pcm_free_substream;
+	runtime->hw = snd_trident_capture;
+	snd_pcm_set_sync(substream);
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_capture_close
+  
+   Description: This routine will close the 4DWave capture device. For now 
+                we will simply free the dma transfer buffer.
+                
+   Parameters:	substream  - PCM substream class
+
+  ---------------------------------------------------------------------------*/
+static int snd_trident_capture_close(snd_pcm_substream_t * substream)
+{
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_foldback_open
+  
+   Description: This routine will open the 4DWave foldback capture device.
+
+   Parameters:	substream  - PCM substream class
+
+   Returns:     status  - success or failure flag
+
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_foldback_open(snd_pcm_substream_t * substream)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	snd_trident_voice_t *voice;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+
+	voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
+	if (voice == NULL)
+		return -EAGAIN;
+	voice->foldback_chan = substream->number;
+	voice->substream = substream;
+	runtime->private_data = voice;
+	runtime->private_free = snd_trident_pcm_free_substream;
+	runtime->hw = snd_trident_foldback;
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_foldback_close
+  
+   Description: This routine will close the 4DWave foldback capture device. 
+		For now we will simply free the dma transfer buffer.
+                
+   Parameters:	substream  - PCM substream class
+
+  ---------------------------------------------------------------------------*/
+static int snd_trident_foldback_close(snd_pcm_substream_t * substream)
+{
+	trident_t *trident = snd_pcm_substream_chip(substream);
+	snd_trident_voice_t *voice;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	voice = (snd_trident_voice_t *) runtime->private_data;
+	
+	/* stop capture channel */
+	spin_lock_irq(&trident->reg_lock);
+	outb(0x00, TRID_REG(trident, T4D_RCI + voice->foldback_chan));
+	spin_unlock_irq(&trident->reg_lock);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   PCM operations
+  ---------------------------------------------------------------------------*/
+
+static snd_pcm_ops_t snd_trident_playback_ops = {
+	.open =		snd_trident_playback_open,
+	.close =	snd_trident_playback_close,
+	.ioctl =	snd_trident_ioctl,
+	.hw_params =	snd_trident_hw_params,
+	.hw_free =	snd_trident_hw_free,
+	.prepare =	snd_trident_playback_prepare,
+	.trigger =	snd_trident_trigger,
+	.pointer =	snd_trident_playback_pointer,
+};
+
+static snd_pcm_ops_t snd_trident_nx_playback_ops = {
+	.open =		snd_trident_playback_open,
+	.close =	snd_trident_playback_close,
+	.ioctl =	snd_trident_ioctl,
+	.hw_params =	snd_trident_hw_params,
+	.hw_free =	snd_trident_hw_free,
+	.prepare =	snd_trident_playback_prepare,
+	.trigger =	snd_trident_trigger,
+	.pointer =	snd_trident_playback_pointer,
+	.page =		snd_pcm_sgbuf_ops_page,
+};
+
+static snd_pcm_ops_t snd_trident_capture_ops = {
+	.open =		snd_trident_capture_open,
+	.close =	snd_trident_capture_close,
+	.ioctl =	snd_trident_ioctl,
+	.hw_params =	snd_trident_capture_hw_params,
+	.hw_free =	snd_trident_hw_free,
+	.prepare =	snd_trident_capture_prepare,
+	.trigger =	snd_trident_trigger,
+	.pointer =	snd_trident_capture_pointer,
+};
+
+static snd_pcm_ops_t snd_trident_si7018_capture_ops = {
+	.open =		snd_trident_capture_open,
+	.close =	snd_trident_capture_close,
+	.ioctl =	snd_trident_ioctl,
+	.hw_params =	snd_trident_si7018_capture_hw_params,
+	.hw_free =	snd_trident_si7018_capture_hw_free,
+	.prepare =	snd_trident_si7018_capture_prepare,
+	.trigger =	snd_trident_trigger,
+	.pointer =	snd_trident_playback_pointer,
+};
+
+static snd_pcm_ops_t snd_trident_foldback_ops = {
+	.open =		snd_trident_foldback_open,
+	.close =	snd_trident_foldback_close,
+	.ioctl =	snd_trident_ioctl,
+	.hw_params =	snd_trident_hw_params,
+	.hw_free =	snd_trident_hw_free,
+	.prepare =	snd_trident_foldback_prepare,
+	.trigger =	snd_trident_trigger,
+	.pointer =	snd_trident_playback_pointer,
+};
+
+static snd_pcm_ops_t snd_trident_nx_foldback_ops = {
+	.open =		snd_trident_foldback_open,
+	.close =	snd_trident_foldback_close,
+	.ioctl =	snd_trident_ioctl,
+	.hw_params =	snd_trident_hw_params,
+	.hw_free =	snd_trident_hw_free,
+	.prepare =	snd_trident_foldback_prepare,
+	.trigger =	snd_trident_trigger,
+	.pointer =	snd_trident_playback_pointer,
+	.page =		snd_pcm_sgbuf_ops_page,
+};
+
+static snd_pcm_ops_t snd_trident_spdif_ops = {
+	.open =		snd_trident_spdif_open,
+	.close =	snd_trident_spdif_close,
+	.ioctl =	snd_trident_ioctl,
+	.hw_params =	snd_trident_spdif_hw_params,
+	.hw_free =	snd_trident_hw_free,
+	.prepare =	snd_trident_spdif_prepare,
+	.trigger =	snd_trident_trigger,
+	.pointer =	snd_trident_spdif_pointer,
+};
+
+static snd_pcm_ops_t snd_trident_spdif_7018_ops = {
+	.open =		snd_trident_spdif_open,
+	.close =	snd_trident_spdif_close,
+	.ioctl =	snd_trident_ioctl,
+	.hw_params =	snd_trident_spdif_hw_params,
+	.hw_free =	snd_trident_hw_free,
+	.prepare =	snd_trident_spdif_prepare,
+	.trigger =	snd_trident_trigger,
+	.pointer =	snd_trident_playback_pointer,
+};
+
+/*---------------------------------------------------------------------------
+   snd_trident_pcm_free
+  
+   Description: This routine release the 4DWave private data.
+                
+   Paramters:   private_data - pointer to 4DWave device info.
+
+   Returns:     None
+  
+  ---------------------------------------------------------------------------*/
+static void snd_trident_pcm_free(snd_pcm_t *pcm)
+{
+	trident_t *trident = pcm->private_data;
+	trident->pcm = NULL;
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static void snd_trident_foldback_pcm_free(snd_pcm_t *pcm)
+{
+	trident_t *trident = pcm->private_data;
+	trident->foldback = NULL;
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static void snd_trident_spdif_pcm_free(snd_pcm_t *pcm)
+{
+	trident_t *trident = pcm->private_data;
+	trident->spdif = NULL;
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_pcm
+  
+   Description: This routine registers the 4DWave device for PCM support.
+                
+   Paramters:   trident - pointer to target device class for 4DWave.
+
+   Returns:     None
+  
+  ---------------------------------------------------------------------------*/
+
+int __devinit snd_trident_pcm(trident_t * trident, int device, snd_pcm_t ** rpcm)
+{
+	snd_pcm_t *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if ((err = snd_pcm_new(trident->card, "trident_dx_nx", device, trident->ChanPCM, 1, &pcm)) < 0)
+		return err;
+
+	pcm->private_data = trident;
+	pcm->private_free = snd_trident_pcm_free;
+
+	if (trident->tlb.entries) {
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_nx_playback_ops);
+	} else {
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_playback_ops);
+	}
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+			trident->device != TRIDENT_DEVICE_ID_SI7018 ?
+			&snd_trident_capture_ops :
+			&snd_trident_si7018_capture_ops);
+
+	pcm->info_flags = 0;
+	pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+	strcpy(pcm->name, "Trident 4DWave");
+	trident->pcm = pcm;
+
+	if (trident->tlb.entries) {
+		snd_pcm_substream_t *substream;
+		for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next)
+			snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV_SG,
+						      snd_dma_pci_data(trident->pci),
+						      64*1024, 128*1024);
+		snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
+					      SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(trident->pci),
+					      64*1024, 128*1024);
+	} else {
+		snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+						      snd_dma_pci_data(trident->pci), 64*1024, 128*1024);
+	}
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_foldback_pcm
+  
+   Description: This routine registers the 4DWave device for foldback PCM support.
+                
+   Paramters:   trident - pointer to target device class for 4DWave.
+
+   Returns:     None
+  
+  ---------------------------------------------------------------------------*/
+
+int __devinit snd_trident_foldback_pcm(trident_t * trident, int device, snd_pcm_t ** rpcm)
+{
+	snd_pcm_t *foldback;
+	int err;
+	int num_chan = 3;
+	snd_pcm_substream_t *substream;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if (trident->device == TRIDENT_DEVICE_ID_NX)
+		num_chan = 4;
+	if ((err = snd_pcm_new(trident->card, "trident_dx_nx", device, 0, num_chan, &foldback)) < 0)
+		return err;
+
+	foldback->private_data = trident;
+	foldback->private_free = snd_trident_foldback_pcm_free;
+	if (trident->tlb.entries)
+		snd_pcm_set_ops(foldback, SNDRV_PCM_STREAM_CAPTURE, &snd_trident_nx_foldback_ops);
+	else
+		snd_pcm_set_ops(foldback, SNDRV_PCM_STREAM_CAPTURE, &snd_trident_foldback_ops);
+	foldback->info_flags = 0;
+	strcpy(foldback->name, "Trident 4DWave");
+	substream = foldback->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+	strcpy(substream->name, "Front Mixer");
+	substream = substream->next;
+	strcpy(substream->name, "Reverb Mixer");
+	substream = substream->next;
+	strcpy(substream->name, "Chorus Mixer");
+	if (num_chan == 4) {
+		substream = substream->next;
+		strcpy(substream->name, "Second AC'97 ADC");
+	}
+	trident->foldback = foldback;
+
+	if (trident->tlb.entries)
+		snd_pcm_lib_preallocate_pages_for_all(foldback, SNDRV_DMA_TYPE_DEV_SG,
+						      snd_dma_pci_data(trident->pci), 0, 128*1024);
+	else
+		snd_pcm_lib_preallocate_pages_for_all(foldback, SNDRV_DMA_TYPE_DEV,
+						      snd_dma_pci_data(trident->pci), 64*1024, 128*1024);
+
+	if (rpcm)
+		*rpcm = foldback;
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_spdif
+  
+   Description: This routine registers the 4DWave-NX device for SPDIF support.
+                
+   Paramters:   trident - pointer to target device class for 4DWave-NX.
+
+   Returns:     None
+  
+  ---------------------------------------------------------------------------*/
+
+int __devinit snd_trident_spdif_pcm(trident_t * trident, int device, snd_pcm_t ** rpcm)
+{
+	snd_pcm_t *spdif;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if ((err = snd_pcm_new(trident->card, "trident_dx_nx IEC958", device, 1, 0, &spdif)) < 0)
+		return err;
+
+	spdif->private_data = trident;
+	spdif->private_free = snd_trident_spdif_pcm_free;
+	if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+		snd_pcm_set_ops(spdif, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_spdif_ops);
+	} else {
+		snd_pcm_set_ops(spdif, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_spdif_7018_ops);
+	}
+	spdif->info_flags = 0;
+	strcpy(spdif->name, "Trident 4DWave IEC958");
+	trident->spdif = spdif;
+
+	snd_pcm_lib_preallocate_pages_for_all(spdif, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(trident->pci), 64*1024, 128*1024);
+
+	if (rpcm)
+		*rpcm = spdif;
+	return 0;
+}
+
+/*
+ *  Mixer part
+ */
+
+
+/*---------------------------------------------------------------------------
+    snd_trident_spdif_control
+
+    Description: enable/disable S/PDIF out from ac97 mixer
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_control_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int snd_trident_spdif_control_get(snd_kcontrol_t * kcontrol,
+					 snd_ctl_elem_value_t * ucontrol)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+	unsigned char val;
+
+	spin_lock_irq(&trident->reg_lock);
+	val = trident->spdif_ctrl;
+	ucontrol->value.integer.value[0] = val == kcontrol->private_value;
+	spin_unlock_irq(&trident->reg_lock);
+	return 0;
+}
+
+static int snd_trident_spdif_control_put(snd_kcontrol_t * kcontrol,
+					 snd_ctl_elem_value_t * ucontrol)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+	unsigned char val;
+	int change;
+
+	val = ucontrol->value.integer.value[0] ? (unsigned char) kcontrol->private_value : 0x00;
+	spin_lock_irq(&trident->reg_lock);
+	/* S/PDIF C Channel bits 0-31 : 48khz, SCMS disabled */
+	change = trident->spdif_ctrl != val;
+	trident->spdif_ctrl = val;
+	if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+		if ((inb(TRID_REG(trident, NX_SPCTRL_SPCSO + 3)) & 0x10) == 0) {
+			outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
+			outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
+		}
+	} else {
+		if (trident->spdif == NULL) {
+			unsigned int temp;
+			outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
+			temp = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & ~SPDIF_EN;
+			if (val)
+				temp |= SPDIF_EN;
+			outl(temp, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+		}
+	}
+	spin_unlock_irq(&trident->reg_lock);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_trident_spdif_control __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH),
+	.info =		snd_trident_spdif_control_info,
+	.get =		snd_trident_spdif_control_get,
+	.put =		snd_trident_spdif_control_put,
+	.private_value = 0x28,
+};
+
+/*---------------------------------------------------------------------------
+    snd_trident_spdif_default
+
+    Description: put/get the S/PDIF default settings
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_default_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_trident_spdif_default_get(snd_kcontrol_t * kcontrol,
+					 snd_ctl_elem_value_t * ucontrol)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&trident->reg_lock);
+	ucontrol->value.iec958.status[0] = (trident->spdif_bits >> 0) & 0xff;
+	ucontrol->value.iec958.status[1] = (trident->spdif_bits >> 8) & 0xff;
+	ucontrol->value.iec958.status[2] = (trident->spdif_bits >> 16) & 0xff;
+	ucontrol->value.iec958.status[3] = (trident->spdif_bits >> 24) & 0xff;
+	spin_unlock_irq(&trident->reg_lock);
+	return 0;
+}
+
+static int snd_trident_spdif_default_put(snd_kcontrol_t * kcontrol,
+					 snd_ctl_elem_value_t * ucontrol)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change;
+
+	val = (ucontrol->value.iec958.status[0] << 0) |
+	      (ucontrol->value.iec958.status[1] << 8) |
+	      (ucontrol->value.iec958.status[2] << 16) |
+	      (ucontrol->value.iec958.status[3] << 24);
+	spin_lock_irq(&trident->reg_lock);
+	change = trident->spdif_bits != val;
+	trident->spdif_bits = val;
+	if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+		if ((inb(TRID_REG(trident, NX_SPCTRL_SPCSO + 3)) & 0x10) == 0)
+			outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
+	} else {
+		if (trident->spdif == NULL)
+			outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
+	}
+	spin_unlock_irq(&trident->reg_lock);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_trident_spdif_default __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+	.info =		snd_trident_spdif_default_info,
+	.get =		snd_trident_spdif_default_get,
+	.put =		snd_trident_spdif_default_put
+};
+
+/*---------------------------------------------------------------------------
+    snd_trident_spdif_mask
+
+    Description: put/get the S/PDIF mask
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_mask_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_trident_spdif_mask_get(snd_kcontrol_t * kcontrol,
+				      snd_ctl_elem_value_t * ucontrol)
+{
+	ucontrol->value.iec958.status[0] = 0xff;
+	ucontrol->value.iec958.status[1] = 0xff;
+	ucontrol->value.iec958.status[2] = 0xff;
+	ucontrol->value.iec958.status[3] = 0xff;
+	return 0;
+}
+
+static snd_kcontrol_new_t snd_trident_spdif_mask __devinitdata =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
+	.info =		snd_trident_spdif_mask_info,
+	.get =		snd_trident_spdif_mask_get,
+};
+
+/*---------------------------------------------------------------------------
+    snd_trident_spdif_stream
+
+    Description: put/get the S/PDIF stream settings
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_stream_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_trident_spdif_stream_get(snd_kcontrol_t * kcontrol,
+					snd_ctl_elem_value_t * ucontrol)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&trident->reg_lock);
+	ucontrol->value.iec958.status[0] = (trident->spdif_pcm_bits >> 0) & 0xff;
+	ucontrol->value.iec958.status[1] = (trident->spdif_pcm_bits >> 8) & 0xff;
+	ucontrol->value.iec958.status[2] = (trident->spdif_pcm_bits >> 16) & 0xff;
+	ucontrol->value.iec958.status[3] = (trident->spdif_pcm_bits >> 24) & 0xff;
+	spin_unlock_irq(&trident->reg_lock);
+	return 0;
+}
+
+static int snd_trident_spdif_stream_put(snd_kcontrol_t * kcontrol,
+					snd_ctl_elem_value_t * ucontrol)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change;
+
+	val = (ucontrol->value.iec958.status[0] << 0) |
+	      (ucontrol->value.iec958.status[1] << 8) |
+	      (ucontrol->value.iec958.status[2] << 16) |
+	      (ucontrol->value.iec958.status[3] << 24);
+	spin_lock_irq(&trident->reg_lock);
+	change = trident->spdif_pcm_bits != val;
+	trident->spdif_pcm_bits = val;
+	if (trident->spdif != NULL) {
+		if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+			outl(trident->spdif_pcm_bits, TRID_REG(trident, NX_SPCSTATUS));
+		} else {
+			outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
+		}
+	}
+	spin_unlock_irq(&trident->reg_lock);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_trident_spdif_stream __devinitdata =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM),
+	.info =		snd_trident_spdif_stream_info,
+	.get =		snd_trident_spdif_stream_get,
+	.put =		snd_trident_spdif_stream_put
+};
+
+/*---------------------------------------------------------------------------
+    snd_trident_ac97_control
+
+    Description: enable/disable rear path for ac97
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_ac97_control_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int snd_trident_ac97_control_get(snd_kcontrol_t * kcontrol,
+					snd_ctl_elem_value_t * ucontrol)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+	unsigned char val;
+
+	spin_lock_irq(&trident->reg_lock);
+	val = trident->ac97_ctrl = inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+	ucontrol->value.integer.value[0] = (val & (1 << kcontrol->private_value)) ? 1 : 0;
+	spin_unlock_irq(&trident->reg_lock);
+	return 0;
+}
+
+static int snd_trident_ac97_control_put(snd_kcontrol_t * kcontrol,
+					snd_ctl_elem_value_t * ucontrol)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+	unsigned char val;
+	int change = 0;
+
+	spin_lock_irq(&trident->reg_lock);
+	val = trident->ac97_ctrl = inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+	val &= ~(1 << kcontrol->private_value);
+	if (ucontrol->value.integer.value[0])
+		val |= 1 << kcontrol->private_value;
+	change = val != trident->ac97_ctrl;
+	trident->ac97_ctrl = val;
+	outl(trident->ac97_ctrl = val, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+	spin_unlock_irq(&trident->reg_lock);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_trident_ac97_rear_control __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Rear Path",
+	.info =		snd_trident_ac97_control_info,
+	.get =		snd_trident_ac97_control_get,
+	.put =		snd_trident_ac97_control_put,
+	.private_value = 4,
+};
+
+/*---------------------------------------------------------------------------
+    snd_trident_vol_control
+
+    Description: wave & music volume control
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_vol_control_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 255;
+	return 0;
+}
+
+static int snd_trident_vol_control_get(snd_kcontrol_t * kcontrol,
+				       snd_ctl_elem_value_t * ucontrol)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+
+	val = trident->musicvol_wavevol;
+	ucontrol->value.integer.value[0] = 255 - ((val >> kcontrol->private_value) & 0xff);
+	ucontrol->value.integer.value[1] = 255 - ((val >> (kcontrol->private_value + 8)) & 0xff);
+	return 0;
+}
+
+static int snd_trident_vol_control_put(snd_kcontrol_t * kcontrol,
+				       snd_ctl_elem_value_t * ucontrol)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change = 0;
+
+	spin_lock_irq(&trident->reg_lock);
+	val = trident->musicvol_wavevol;
+	val &= ~(0xffff << kcontrol->private_value);
+	val |= ((255 - (ucontrol->value.integer.value[0] & 0xff)) |
+	        ((255 - (ucontrol->value.integer.value[1] & 0xff)) << 8)) << kcontrol->private_value;
+	change = val != trident->musicvol_wavevol;
+	outl(trident->musicvol_wavevol = val, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
+	spin_unlock_irq(&trident->reg_lock);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_trident_vol_music_control __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Music Playback Volume",
+	.info =		snd_trident_vol_control_info,
+	.get =		snd_trident_vol_control_get,
+	.put =		snd_trident_vol_control_put,
+	.private_value = 16,
+};
+
+static snd_kcontrol_new_t snd_trident_vol_wave_control __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Wave Playback Volume",
+	.info =		snd_trident_vol_control_info,
+	.get =		snd_trident_vol_control_get,
+	.put =		snd_trident_vol_control_put,
+	.private_value = 0,
+};
+
+/*---------------------------------------------------------------------------
+    snd_trident_pcm_vol_control
+
+    Description: PCM front volume control
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_pcm_vol_control_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 255;
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018)
+		uinfo->value.integer.max = 1023;
+	return 0;
+}
+
+static int snd_trident_pcm_vol_control_get(snd_kcontrol_t * kcontrol,
+					   snd_ctl_elem_value_t * ucontrol)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+	snd_trident_pcm_mixer_t *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+		ucontrol->value.integer.value[0] = 1023 - mix->vol;
+	} else {
+		ucontrol->value.integer.value[0] = 255 - (mix->vol>>2);
+	}
+	return 0;
+}
+
+static int snd_trident_pcm_vol_control_put(snd_kcontrol_t * kcontrol,
+					   snd_ctl_elem_value_t * ucontrol)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+	snd_trident_pcm_mixer_t *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+	unsigned int val;
+	int change = 0;
+
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+		val = 1023 - (ucontrol->value.integer.value[0] & 1023);
+	} else {
+		val = (255 - (ucontrol->value.integer.value[0] & 255)) << 2;
+	}
+	spin_lock_irq(&trident->reg_lock);
+	change = val != mix->vol;
+	mix->vol = val;
+	if (mix->voice != NULL)
+		snd_trident_write_vol_reg(trident, mix->voice, val);
+	spin_unlock_irq(&trident->reg_lock);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_trident_pcm_vol_control __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "PCM Front Playback Volume",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.count =	32,
+	.info =		snd_trident_pcm_vol_control_info,
+	.get =		snd_trident_pcm_vol_control_get,
+	.put =		snd_trident_pcm_vol_control_put,
+};
+
+/*---------------------------------------------------------------------------
+    snd_trident_pcm_pan_control
+
+    Description: PCM front pan control
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_pcm_pan_control_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 127;
+	return 0;
+}
+
+static int snd_trident_pcm_pan_control_get(snd_kcontrol_t * kcontrol,
+					   snd_ctl_elem_value_t * ucontrol)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+	snd_trident_pcm_mixer_t *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+
+	ucontrol->value.integer.value[0] = mix->pan;
+	if (ucontrol->value.integer.value[0] & 0x40) {
+		ucontrol->value.integer.value[0] = (0x3f - (ucontrol->value.integer.value[0] & 0x3f));
+	} else {
+		ucontrol->value.integer.value[0] |= 0x40;
+	}
+	return 0;
+}
+
+static int snd_trident_pcm_pan_control_put(snd_kcontrol_t * kcontrol,
+					   snd_ctl_elem_value_t * ucontrol)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+	snd_trident_pcm_mixer_t *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+	unsigned char val;
+	int change = 0;
+
+	if (ucontrol->value.integer.value[0] & 0x40)
+		val = ucontrol->value.integer.value[0] & 0x3f;
+	else
+		val = (0x3f - (ucontrol->value.integer.value[0] & 0x3f)) | 0x40;
+	spin_lock_irq(&trident->reg_lock);
+	change = val != mix->pan;
+	mix->pan = val;
+	if (mix->voice != NULL)
+		snd_trident_write_pan_reg(trident, mix->voice, val);
+	spin_unlock_irq(&trident->reg_lock);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_trident_pcm_pan_control __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "PCM Pan Playback Control",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.count =	32,
+	.info =		snd_trident_pcm_pan_control_info,
+	.get =		snd_trident_pcm_pan_control_get,
+	.put =		snd_trident_pcm_pan_control_put,
+};
+
+/*---------------------------------------------------------------------------
+    snd_trident_pcm_rvol_control
+
+    Description: PCM reverb volume control
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_pcm_rvol_control_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 127;
+	return 0;
+}
+
+static int snd_trident_pcm_rvol_control_get(snd_kcontrol_t * kcontrol,
+					    snd_ctl_elem_value_t * ucontrol)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+	snd_trident_pcm_mixer_t *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+
+	ucontrol->value.integer.value[0] = 127 - mix->rvol;
+	return 0;
+}
+
+static int snd_trident_pcm_rvol_control_put(snd_kcontrol_t * kcontrol,
+					    snd_ctl_elem_value_t * ucontrol)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+	snd_trident_pcm_mixer_t *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+	unsigned short val;
+	int change = 0;
+
+	val = 0x7f - (ucontrol->value.integer.value[0] & 0x7f);
+	spin_lock_irq(&trident->reg_lock);
+	change = val != mix->rvol;
+	mix->rvol = val;
+	if (mix->voice != NULL)
+		snd_trident_write_rvol_reg(trident, mix->voice, val);
+	spin_unlock_irq(&trident->reg_lock);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_trident_pcm_rvol_control __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "PCM Reverb Playback Volume",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.count = 	32,
+	.info =		snd_trident_pcm_rvol_control_info,
+	.get =		snd_trident_pcm_rvol_control_get,
+	.put =		snd_trident_pcm_rvol_control_put,
+};
+
+/*---------------------------------------------------------------------------
+    snd_trident_pcm_cvol_control
+
+    Description: PCM chorus volume control
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_pcm_cvol_control_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 127;
+	return 0;
+}
+
+static int snd_trident_pcm_cvol_control_get(snd_kcontrol_t * kcontrol,
+					    snd_ctl_elem_value_t * ucontrol)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+	snd_trident_pcm_mixer_t *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+
+	ucontrol->value.integer.value[0] = 127 - mix->cvol;
+	return 0;
+}
+
+static int snd_trident_pcm_cvol_control_put(snd_kcontrol_t * kcontrol,
+					    snd_ctl_elem_value_t * ucontrol)
+{
+	trident_t *trident = snd_kcontrol_chip(kcontrol);
+	snd_trident_pcm_mixer_t *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+	unsigned short val;
+	int change = 0;
+
+	val = 0x7f - (ucontrol->value.integer.value[0] & 0x7f);
+	spin_lock_irq(&trident->reg_lock);
+	change = val != mix->cvol;
+	mix->cvol = val;
+	if (mix->voice != NULL)
+		snd_trident_write_cvol_reg(trident, mix->voice, val);
+	spin_unlock_irq(&trident->reg_lock);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_trident_pcm_cvol_control __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "PCM Chorus Playback Volume",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.count =	32,
+	.info =		snd_trident_pcm_cvol_control_info,
+	.get =		snd_trident_pcm_cvol_control_get,
+	.put =		snd_trident_pcm_cvol_control_put,
+};
+
+static void snd_trident_notify_pcm_change1(snd_card_t * card, snd_kcontrol_t *kctl, int num, int activate)
+{
+	snd_ctl_elem_id_t id;
+
+	snd_runtime_check(kctl != NULL, return);
+	if (activate)
+		kctl->vd[num].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	else
+		kctl->vd[num].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE |
+		       SNDRV_CTL_EVENT_MASK_INFO,
+		       snd_ctl_build_ioff(&id, kctl, num));
+}
+
+static void snd_trident_notify_pcm_change(trident_t *trident, snd_trident_pcm_mixer_t *tmix, int num, int activate)
+{
+	snd_trident_notify_pcm_change1(trident->card, trident->ctl_vol, num, activate);
+	snd_trident_notify_pcm_change1(trident->card, trident->ctl_pan, num, activate);
+	snd_trident_notify_pcm_change1(trident->card, trident->ctl_rvol, num, activate);
+	snd_trident_notify_pcm_change1(trident->card, trident->ctl_cvol, num, activate);
+}
+
+static int snd_trident_pcm_mixer_build(trident_t *trident, snd_trident_voice_t *voice, snd_pcm_substream_t *substream)
+{
+	snd_trident_pcm_mixer_t *tmix;
+
+	snd_assert(trident != NULL && voice != NULL && substream != NULL, return -EINVAL);
+	tmix = &trident->pcm_mixer[substream->number];
+	tmix->voice = voice;
+	tmix->vol = T4D_DEFAULT_PCM_VOL;
+	tmix->pan = T4D_DEFAULT_PCM_PAN;
+	tmix->rvol = T4D_DEFAULT_PCM_RVOL;
+	tmix->cvol = T4D_DEFAULT_PCM_CVOL;
+	snd_trident_notify_pcm_change(trident, tmix, substream->number, 1);
+	return 0;
+}
+
+static int snd_trident_pcm_mixer_free(trident_t *trident, snd_trident_voice_t *voice, snd_pcm_substream_t *substream)
+{
+	snd_trident_pcm_mixer_t *tmix;
+
+	snd_assert(trident != NULL && substream != NULL, return -EINVAL);
+	tmix = &trident->pcm_mixer[substream->number];
+	tmix->voice = NULL;
+	snd_trident_notify_pcm_change(trident, tmix, substream->number, 0);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_mixer
+  
+   Description: This routine registers the 4DWave device for mixer support.
+                
+   Paramters:   trident - pointer to target device class for 4DWave.
+
+   Returns:     None
+  
+  ---------------------------------------------------------------------------*/
+
+static int __devinit snd_trident_mixer(trident_t * trident, int pcm_spdif_device)
+{
+	ac97_template_t _ac97;
+	snd_card_t * card = trident->card;
+	snd_kcontrol_t *kctl;
+	snd_ctl_elem_value_t *uctl;
+	int idx, err, retries = 2;
+	static ac97_bus_ops_t ops = {
+		.write = snd_trident_codec_write,
+		.read = snd_trident_codec_read,
+	};
+
+	uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL);
+	if (!uctl)
+		return -ENOMEM;
+
+	if ((err = snd_ac97_bus(trident->card, 0, &ops, NULL, &trident->ac97_bus)) < 0)
+		goto __out;
+
+	memset(&_ac97, 0, sizeof(_ac97));
+	_ac97.private_data = trident;
+	trident->ac97_detect = 1;
+
+      __again:
+	if ((err = snd_ac97_mixer(trident->ac97_bus, &_ac97, &trident->ac97)) < 0) {
+		if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+			if ((err = snd_trident_sis_reset(trident)) < 0)
+				goto __out;
+			if (retries-- > 0)
+				goto __again;
+			err = -EIO;
+		}
+		goto __out;
+	}
+	
+	/* secondary codec? */
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018 &&
+	    (inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & SI_AC97_PRIMARY_READY) != 0) {
+		_ac97.num = 1;
+		err = snd_ac97_mixer(trident->ac97_bus, &_ac97, &trident->ac97_sec);
+		if (err < 0)
+			snd_printk("SI7018: the secondary codec - invalid access\n");
+#if 0	// only for my testing purpose --jk
+		{
+			ac97_t *mc97;
+			err = snd_ac97_modem(trident->card, &_ac97, &mc97);
+			if (err < 0)
+				snd_printk("snd_ac97_modem returned error %i\n", err);
+		}
+#endif
+	}
+	
+	trident->ac97_detect = 0;
+
+	if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+		if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_trident_vol_wave_control, trident))) < 0)
+			goto __out;
+		kctl->put(kctl, uctl);
+		if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_trident_vol_music_control, trident))) < 0)
+			goto __out;
+		kctl->put(kctl, uctl);
+		outl(trident->musicvol_wavevol = 0x00000000, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
+	} else {
+		outl(trident->musicvol_wavevol = 0xffff0000, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
+	}
+
+	for (idx = 0; idx < 32; idx++) {
+		snd_trident_pcm_mixer_t *tmix;
+		
+		tmix = &trident->pcm_mixer[idx];
+		tmix->voice = NULL;
+	}
+	if ((trident->ctl_vol = snd_ctl_new1(&snd_trident_pcm_vol_control, trident)) == NULL)
+		goto __nomem;
+	if ((err = snd_ctl_add(card, trident->ctl_vol)))
+		goto __out;
+		
+	if ((trident->ctl_pan = snd_ctl_new1(&snd_trident_pcm_pan_control, trident)) == NULL)
+		goto __nomem;
+	if ((err = snd_ctl_add(card, trident->ctl_pan)))
+		goto __out;
+
+	if ((trident->ctl_rvol = snd_ctl_new1(&snd_trident_pcm_rvol_control, trident)) == NULL)
+		goto __nomem;
+	if ((err = snd_ctl_add(card, trident->ctl_rvol)))
+		goto __out;
+
+	if ((trident->ctl_cvol = snd_ctl_new1(&snd_trident_pcm_cvol_control, trident)) == NULL)
+		goto __nomem;
+	if ((err = snd_ctl_add(card, trident->ctl_cvol)))
+		goto __out;
+
+	if (trident->device == TRIDENT_DEVICE_ID_NX) {
+		if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_trident_ac97_rear_control, trident))) < 0)
+			goto __out;
+		kctl->put(kctl, uctl);
+	}
+	if (trident->device == TRIDENT_DEVICE_ID_NX || trident->device == TRIDENT_DEVICE_ID_SI7018) {
+
+		kctl = snd_ctl_new1(&snd_trident_spdif_control, trident);
+		if (kctl == NULL) {
+			err = -ENOMEM;
+			goto __out;
+		}
+		if (trident->ac97->ext_id & AC97_EI_SPDIF)
+			kctl->id.index++;
+		if (trident->ac97_sec && (trident->ac97_sec->ext_id & AC97_EI_SPDIF))
+			kctl->id.index++;
+		idx = kctl->id.index;
+		if ((err = snd_ctl_add(card, kctl)) < 0)
+			goto __out;
+		kctl->put(kctl, uctl);
+
+		kctl = snd_ctl_new1(&snd_trident_spdif_default, trident);
+		if (kctl == NULL) {
+			err = -ENOMEM;
+			goto __out;
+		}
+		kctl->id.index = idx;
+		kctl->id.device = pcm_spdif_device;
+		if ((err = snd_ctl_add(card, kctl)) < 0)
+			goto __out;
+
+		kctl = snd_ctl_new1(&snd_trident_spdif_mask, trident);
+		if (kctl == NULL) {
+			err = -ENOMEM;
+			goto __out;
+		}
+		kctl->id.index = idx;
+		kctl->id.device = pcm_spdif_device;
+		if ((err = snd_ctl_add(card, kctl)) < 0)
+			goto __out;
+
+		kctl = snd_ctl_new1(&snd_trident_spdif_stream, trident);
+		if (kctl == NULL) {
+			err = -ENOMEM;
+			goto __out;
+		}
+		kctl->id.index = idx;
+		kctl->id.device = pcm_spdif_device;
+		if ((err = snd_ctl_add(card, kctl)) < 0)
+			goto __out;
+		trident->spdif_pcm_ctl = kctl;
+	}
+
+	err = 0;
+	goto __out;
+
+ __nomem:
+	err = -ENOMEM;
+
+ __out:
+	kfree(uctl);
+
+	return err;
+}
+
+/*
+ * gameport interface
+ */
+
+#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))
+
+static unsigned char snd_trident_gameport_read(struct gameport *gameport)
+{
+	trident_t *chip = gameport_get_port_data(gameport);
+
+	snd_assert(chip, return 0);
+	return inb(TRID_REG(chip, GAMEPORT_LEGACY));
+}
+
+static void snd_trident_gameport_trigger(struct gameport *gameport)
+{
+	trident_t *chip = gameport_get_port_data(gameport);
+
+	snd_assert(chip, return);
+	outb(0xff, TRID_REG(chip, GAMEPORT_LEGACY));
+}
+
+static int snd_trident_gameport_cooked_read(struct gameport *gameport, int *axes, int *buttons)
+{
+	trident_t *chip = gameport_get_port_data(gameport);
+	int i;
+
+	snd_assert(chip, return 0);
+
+	*buttons = (~inb(TRID_REG(chip, GAMEPORT_LEGACY)) >> 4) & 0xf;
+
+	for (i = 0; i < 4; i++) {
+		axes[i] = inw(TRID_REG(chip, GAMEPORT_AXES + i * 2));
+		if (axes[i] == 0xffff) axes[i] = -1;
+	}
+        
+        return 0;
+}
+
+static int snd_trident_gameport_open(struct gameport *gameport, int mode)
+{
+	trident_t *chip = gameport_get_port_data(gameport);
+
+	snd_assert(chip, return 0);
+
+	switch (mode) {
+		case GAMEPORT_MODE_COOKED:
+			outb(GAMEPORT_MODE_ADC, TRID_REG(chip, GAMEPORT_GCR));
+			set_current_state(TASK_UNINTERRUPTIBLE);
+			schedule_timeout(1 + 20 * HZ / 1000); /* 20msec */
+			return 0;
+		case GAMEPORT_MODE_RAW:
+			outb(0, TRID_REG(chip, GAMEPORT_GCR));
+			return 0;
+		default:
+			return -1;
+	}
+}
+
+int __devinit snd_trident_create_gameport(trident_t *chip)
+{
+	struct gameport *gp;
+
+	chip->gameport = gp = gameport_allocate_port();
+	if (!gp) {
+		printk(KERN_ERR "trident: cannot allocate memory for gameport\n");
+		return -ENOMEM;
+	}
+
+	gameport_set_name(gp, "Trident 4DWave");
+	gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci));
+	gameport_set_dev_parent(gp, &chip->pci->dev);
+
+	gameport_set_port_data(gp, chip);
+	gp->fuzz = 64;
+	gp->read = snd_trident_gameport_read;
+	gp->trigger = snd_trident_gameport_trigger;
+	gp->cooked_read = snd_trident_gameport_cooked_read;
+	gp->open = snd_trident_gameport_open;
+
+	gameport_register_port(gp);
+
+	return 0;
+}
+
+static inline void snd_trident_free_gameport(trident_t *chip)
+{
+	if (chip->gameport) {
+		gameport_unregister_port(chip->gameport);
+		chip->gameport = NULL;
+	}
+}
+#else
+int __devinit snd_trident_create_gameport(trident_t *chip) { return -ENOSYS; }
+static inline void snd_trident_free_gameport(trident_t *chip) { }
+#endif /* CONFIG_GAMEPORT */
+
+/*
+ * delay for 1 tick
+ */
+inline static void do_delay(trident_t *chip)
+{
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	schedule_timeout(1);
+}
+
+/*
+ *  SiS reset routine
+ */
+
+static int snd_trident_sis_reset(trident_t *trident)
+{
+	unsigned long end_time;
+	unsigned int i;
+	int r;
+
+	r = trident->in_suspend ? 0 : 2;	/* count of retries */
+      __si7018_retry:
+	pci_write_config_byte(trident->pci, 0x46, 0x04);	/* SOFTWARE RESET */
+	udelay(100);
+	pci_write_config_byte(trident->pci, 0x46, 0x00);
+	udelay(100);
+	/* disable AC97 GPIO interrupt */
+	outb(0x00, TRID_REG(trident, SI_AC97_GPIO));
+	/* initialize serial interface, force cold reset */
+	i = PCMOUT|SURROUT|CENTEROUT|LFEOUT|SECONDARY_ID|COLD_RESET;
+	outl(i, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+	udelay(1000);
+	/* remove cold reset */
+	i &= ~COLD_RESET;
+	outl(i, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+	udelay(2000);
+	/* wait, until the codec is ready */
+	end_time = (jiffies + (HZ * 3) / 4) + 1;
+	do {
+		if ((inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & SI_AC97_PRIMARY_READY) != 0)
+			goto __si7018_ok;
+		do_delay(trident);
+	} while (time_after_eq(end_time, jiffies));
+	snd_printk("AC'97 codec ready error [0x%x]\n", inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)));
+	if (r-- > 0) {
+		end_time = jiffies + HZ;
+		do {
+			do_delay(trident);
+		} while (time_after_eq(end_time, jiffies));
+		goto __si7018_retry;
+	}
+      __si7018_ok:
+	/* wait for the second codec */
+	do {
+		if ((inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & SI_AC97_SECONDARY_READY) != 0)
+			break;
+		do_delay(trident);
+	} while (time_after_eq(end_time, jiffies));
+	/* enable 64 channel mode */
+	outl(BANK_B_EN, TRID_REG(trident, T4D_LFO_GC_CIR));
+	return 0;
+}
+
+/*  
+ *  /proc interface
+ */
+
+static void snd_trident_proc_read(snd_info_entry_t *entry, 
+				  snd_info_buffer_t * buffer)
+{
+	trident_t *trident = entry->private_data;
+	char *s;
+
+	switch (trident->device) {
+	case TRIDENT_DEVICE_ID_SI7018:
+		s = "SiS 7018 Audio";
+		break;
+	case TRIDENT_DEVICE_ID_DX:
+		s = "Trident 4DWave PCI DX";
+		break;
+	case TRIDENT_DEVICE_ID_NX:
+		s = "Trident 4DWave PCI NX";
+		break;
+	default:
+		s = "???";
+	}
+	snd_iprintf(buffer, "%s\n\n", s);
+	snd_iprintf(buffer, "Spurious IRQs    : %d\n", trident->spurious_irq_count);
+	snd_iprintf(buffer, "Spurious IRQ dlta: %d\n", trident->spurious_irq_max_delta);
+	if (trident->device == TRIDENT_DEVICE_ID_NX || trident->device == TRIDENT_DEVICE_ID_SI7018)
+		snd_iprintf(buffer, "IEC958 Mixer Out : %s\n", trident->spdif_ctrl == 0x28 ? "on" : "off");
+	if (trident->device == TRIDENT_DEVICE_ID_NX) {
+		snd_iprintf(buffer, "Rear Speakers    : %s\n", trident->ac97_ctrl & 0x00000010 ? "on" : "off");
+		if (trident->tlb.entries) {
+			snd_iprintf(buffer,"\nVirtual Memory\n");
+			snd_iprintf(buffer, "Memory Maximum : %d\n", trident->tlb.memhdr->size);
+			snd_iprintf(buffer, "Memory Used    : %d\n", trident->tlb.memhdr->used);
+			snd_iprintf(buffer, "Memory Free    : %d\n", snd_util_mem_avail(trident->tlb.memhdr));
+		}
+	}
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+	snd_iprintf(buffer,"\nWavetable Synth\n");
+	snd_iprintf(buffer, "Memory Maximum : %d\n", trident->synth.max_size);
+	snd_iprintf(buffer, "Memory Used    : %d\n", trident->synth.current_size);
+	snd_iprintf(buffer, "Memory Free    : %d\n", (trident->synth.max_size-trident->synth.current_size));
+#endif
+}
+
+static void __devinit snd_trident_proc_init(trident_t * trident)
+{
+	snd_info_entry_t *entry;
+	const char *s = "trident";
+	
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018)
+		s = "sis7018";
+	if (! snd_card_proc_new(trident->card, s, &entry))
+		snd_info_set_text_ops(entry, trident, 1024, snd_trident_proc_read);
+}
+
+static int snd_trident_dev_free(snd_device_t *device)
+{
+	trident_t *trident = device->device_data;
+	return snd_trident_free(trident);
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_tlb_alloc
+  
+   Description: Allocate and set up the TLB page table on 4D NX.
+		Each entry has 4 bytes (physical PCI address).
+                
+   Paramters:   trident - pointer to target device class for 4DWave.
+
+   Returns:     0 or negative error code
+  
+  ---------------------------------------------------------------------------*/
+
+static int __devinit snd_trident_tlb_alloc(trident_t *trident)
+{
+	int i;
+
+	/* TLB array must be aligned to 16kB !!! so we allocate
+	   32kB region and correct offset when necessary */
+
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(trident->pci),
+				2 * SNDRV_TRIDENT_MAX_PAGES * 4, &trident->tlb.buffer) < 0) {
+		snd_printk(KERN_ERR "trident: unable to allocate TLB buffer\n");
+		return -ENOMEM;
+	}
+	trident->tlb.entries = (unsigned int*)(((unsigned long)trident->tlb.buffer.area + SNDRV_TRIDENT_MAX_PAGES * 4 - 1) & ~(SNDRV_TRIDENT_MAX_PAGES * 4 - 1));
+	trident->tlb.entries_dmaaddr = (trident->tlb.buffer.addr + SNDRV_TRIDENT_MAX_PAGES * 4 - 1) & ~(SNDRV_TRIDENT_MAX_PAGES * 4 - 1);
+	/* allocate shadow TLB page table (virtual addresses) */
+	trident->tlb.shadow_entries = (unsigned long *)vmalloc(SNDRV_TRIDENT_MAX_PAGES*sizeof(unsigned long));
+	if (trident->tlb.shadow_entries == NULL) {
+		snd_printk(KERN_ERR "trident: unable to allocate shadow TLB entries\n");
+		return -ENOMEM;
+	}
+	/* allocate and setup silent page and initialise TLB entries */
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(trident->pci),
+				SNDRV_TRIDENT_PAGE_SIZE, &trident->tlb.silent_page) < 0) {
+		snd_printk(KERN_ERR "trident: unable to allocate silent page\n");
+		return -ENOMEM;
+	}
+	memset(trident->tlb.silent_page.area, 0, SNDRV_TRIDENT_PAGE_SIZE);
+	for (i = 0; i < SNDRV_TRIDENT_MAX_PAGES; i++) {
+		trident->tlb.entries[i] = cpu_to_le32(trident->tlb.silent_page.addr & ~(SNDRV_TRIDENT_PAGE_SIZE-1));
+		trident->tlb.shadow_entries[i] = (unsigned long)trident->tlb.silent_page.area;
+	}
+
+	/* use emu memory block manager code to manage tlb page allocation */
+	trident->tlb.memhdr = snd_util_memhdr_new(SNDRV_TRIDENT_PAGE_SIZE * SNDRV_TRIDENT_MAX_PAGES);
+	if (trident->tlb.memhdr == NULL)
+		return -ENOMEM;
+
+	trident->tlb.memhdr->block_extra_size = sizeof(snd_trident_memblk_arg_t);
+	return 0;
+}
+
+/*
+ * initialize 4D DX chip
+ */
+
+static void snd_trident_stop_all_voices(trident_t *trident)
+{
+	outl(0xffffffff, TRID_REG(trident, T4D_STOP_A));
+	outl(0xffffffff, TRID_REG(trident, T4D_STOP_B));
+	outl(0, TRID_REG(trident, T4D_AINTEN_A));
+	outl(0, TRID_REG(trident, T4D_AINTEN_B));
+}
+
+static int snd_trident_4d_dx_init(trident_t *trident)
+{
+	struct pci_dev *pci = trident->pci;
+	unsigned long end_time;
+
+	/* reset the legacy configuration and whole audio/wavetable block */
+	pci_write_config_dword(pci, 0x40, 0);	/* DDMA */
+	pci_write_config_byte(pci, 0x44, 0);	/* ports */
+	pci_write_config_byte(pci, 0x45, 0);	/* Legacy DMA */
+	pci_write_config_byte(pci, 0x46, 4); /* reset */
+	udelay(100);
+	pci_write_config_byte(pci, 0x46, 0); /* release reset */
+	udelay(100);
+	
+	/* warm reset of the AC'97 codec */
+	outl(0x00000001, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
+	udelay(100);
+	outl(0x00000000, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
+	/* DAC on, disable SB IRQ and try to force ADC valid signal */
+	trident->ac97_ctrl = 0x0000004a;
+	outl(trident->ac97_ctrl, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
+	/* wait, until the codec is ready */
+	end_time = (jiffies + (HZ * 3) / 4) + 1;
+	do {
+		if ((inl(TRID_REG(trident, DX_ACR2_AC97_COM_STAT)) & 0x0010) != 0)
+			goto __dx_ok;
+		do_delay(trident);
+	} while (time_after_eq(end_time, jiffies));
+	snd_printk(KERN_ERR "AC'97 codec ready error\n");
+	return -EIO;
+
+ __dx_ok:
+	snd_trident_stop_all_voices(trident);
+
+	return 0;
+}
+
+/*
+ * initialize 4D NX chip
+ */
+static int snd_trident_4d_nx_init(trident_t *trident)
+{
+	struct pci_dev *pci = trident->pci;
+	unsigned long end_time;
+
+	/* reset the legacy configuration and whole audio/wavetable block */
+	pci_write_config_dword(pci, 0x40, 0);	/* DDMA */
+	pci_write_config_byte(pci, 0x44, 0);	/* ports */
+	pci_write_config_byte(pci, 0x45, 0);	/* Legacy DMA */
+
+	pci_write_config_byte(pci, 0x46, 1); /* reset */
+	udelay(100);
+	pci_write_config_byte(pci, 0x46, 0); /* release reset */
+	udelay(100);
+
+	/* warm reset of the AC'97 codec */
+	outl(0x00000001, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+	udelay(100);
+	outl(0x00000000, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+	/* wait, until the codec is ready */
+	end_time = (jiffies + (HZ * 3) / 4) + 1;
+	do {
+		if ((inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT)) & 0x0008) != 0)
+			goto __nx_ok;
+		do_delay(trident);
+	} while (time_after_eq(end_time, jiffies));
+	snd_printk(KERN_ERR "AC'97 codec ready error [0x%x]\n", inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT)));
+	return -EIO;
+
+ __nx_ok:
+	/* DAC on */
+	trident->ac97_ctrl = 0x00000002;
+	outl(trident->ac97_ctrl, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+	/* disable SB IRQ */
+	outl(NX_SB_IRQ_DISABLE, TRID_REG(trident, T4D_MISCINT));
+
+	snd_trident_stop_all_voices(trident);
+
+	if (trident->tlb.entries != NULL) {
+		unsigned int i;
+		/* enable virtual addressing via TLB */
+		i = trident->tlb.entries_dmaaddr;
+		i |= 0x00000001;
+		outl(i, TRID_REG(trident, NX_TLBC));
+	} else {
+		outl(0, TRID_REG(trident, NX_TLBC));
+	}
+	/* initialize S/PDIF */
+	outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
+	outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
+
+	return 0;
+}
+
+/*
+ * initialize sis7018 chip
+ */
+static int snd_trident_sis_init(trident_t *trident)
+{
+	int err;
+
+	if ((err = snd_trident_sis_reset(trident)) < 0)
+		return err;
+
+	snd_trident_stop_all_voices(trident);
+
+	/* initialize S/PDIF */
+	outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
+
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_create
+  
+   Description: This routine will create the device specific class for
+                the 4DWave card. It will also perform basic initialization.
+                
+   Paramters:   card  - which card to create
+                pci   - interface to PCI bus resource info
+                dma1ptr - playback dma buffer
+                dma2ptr - capture dma buffer
+                irqptr  -  interrupt resource info
+
+   Returns:     4DWave device class private data
+  
+  ---------------------------------------------------------------------------*/
+
+int __devinit snd_trident_create(snd_card_t * card,
+		       struct pci_dev *pci,
+		       int pcm_streams,
+		       int pcm_spdif_device,
+		       int max_wavetable_size,
+		       trident_t ** rtrident)
+{
+	trident_t *trident;
+	int i, err;
+	snd_trident_voice_t *voice;
+	snd_trident_pcm_mixer_t *tmix;
+	static snd_device_ops_t ops = {
+		.dev_free =	snd_trident_dev_free,
+	};
+
+	*rtrident = NULL;
+
+	/* enable PCI device */
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+	/* check, if we can restrict PCI DMA transfers to 30 bits */
+	if (pci_set_dma_mask(pci, 0x3fffffff) < 0 ||
+	    pci_set_consistent_dma_mask(pci, 0x3fffffff) < 0) {
+		snd_printk("architecture does not support 30bit PCI busmaster DMA\n");
+		pci_disable_device(pci);
+		return -ENXIO;
+	}
+	
+	trident = kcalloc(1, sizeof(*trident), GFP_KERNEL);
+	if (trident == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+	trident->device = (pci->vendor << 16) | pci->device;
+	trident->card = card;
+	trident->pci = pci;
+	spin_lock_init(&trident->reg_lock);
+	spin_lock_init(&trident->event_lock);
+	spin_lock_init(&trident->voice_alloc);
+	if (pcm_streams < 1)
+		pcm_streams = 1;
+	if (pcm_streams > 32)
+		pcm_streams = 32;
+	trident->ChanPCM = pcm_streams;
+	if (max_wavetable_size < 0 )
+		max_wavetable_size = 0;
+	trident->synth.max_size = max_wavetable_size * 1024;
+	trident->irq = -1;
+
+	trident->midi_port = TRID_REG(trident, T4D_MPU401_BASE);
+	pci_set_master(pci);
+
+	if ((err = pci_request_regions(pci, "Trident Audio")) < 0) {
+		kfree(trident);
+		pci_disable_device(pci);
+		return err;
+	}
+	trident->port = pci_resource_start(pci, 0);
+
+	if (request_irq(pci->irq, snd_trident_interrupt, SA_INTERRUPT|SA_SHIRQ, "Trident Audio", (void *) trident)) {
+		snd_printk("unable to grab IRQ %d\n", pci->irq);
+		snd_trident_free(trident);
+		return -EBUSY;
+	}
+	trident->irq = pci->irq;
+
+	/* allocate 16k-aligned TLB for NX cards */
+	trident->tlb.entries = NULL;
+	trident->tlb.buffer.area = NULL;
+	if (trident->device == TRIDENT_DEVICE_ID_NX) {
+		if ((err = snd_trident_tlb_alloc(trident)) < 0) {
+			snd_trident_free(trident);
+			return err;
+		}
+	}
+
+	trident->spdif_bits = trident->spdif_pcm_bits = SNDRV_PCM_DEFAULT_CON_SPDIF;
+
+	/* initialize chip */
+	switch (trident->device) {
+	case TRIDENT_DEVICE_ID_DX:
+		err = snd_trident_4d_dx_init(trident);
+		break;
+	case TRIDENT_DEVICE_ID_NX:
+		err = snd_trident_4d_nx_init(trident);
+		break;
+	case TRIDENT_DEVICE_ID_SI7018:
+		err = snd_trident_sis_init(trident);
+		break;
+	default:
+		snd_BUG();
+		break;
+	}
+	if (err < 0) {
+		snd_trident_free(trident);
+		return err;
+	}
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, trident, &ops)) < 0) {
+		snd_trident_free(trident);
+		return err;
+	}
+
+	if ((err = snd_trident_mixer(trident, pcm_spdif_device)) < 0)
+		return err;
+	
+	/* initialise synth voices */
+	for (i = 0; i < 64; i++) {
+		voice = &trident->synth.voices[i];
+		voice->number = i;
+		voice->trident = trident;
+	}
+	/* initialize pcm mixer entries */
+	for (i = 0; i < 32; i++) {
+		tmix = &trident->pcm_mixer[i];
+		tmix->vol = T4D_DEFAULT_PCM_VOL;
+		tmix->pan = T4D_DEFAULT_PCM_PAN;
+		tmix->rvol = T4D_DEFAULT_PCM_RVOL;
+		tmix->cvol = T4D_DEFAULT_PCM_CVOL;
+	}
+
+	snd_trident_enable_eso(trident);
+
+	
+	snd_card_set_pm_callback(card, snd_trident_suspend, snd_trident_resume, trident);
+	snd_trident_proc_init(trident);
+	snd_card_set_dev(card, &pci->dev);
+	*rtrident = trident;
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_free
+  
+   Description: This routine will free the device specific class for
+                the 4DWave card. 
+                
+   Paramters:   trident  - device specific private data for 4DWave card
+
+   Returns:     None.
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_free(trident_t *trident)
+{
+	snd_trident_free_gameport(trident);
+	snd_trident_disable_eso(trident);
+	// Disable S/PDIF out
+	if (trident->device == TRIDENT_DEVICE_ID_NX)
+		outb(0x00, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
+	else if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+		outl(0, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+	}
+	if (trident->tlb.buffer.area) {
+		outl(0, TRID_REG(trident, NX_TLBC));
+		if (trident->tlb.memhdr)
+			snd_util_memhdr_free(trident->tlb.memhdr);
+		if (trident->tlb.silent_page.area)
+			snd_dma_free_pages(&trident->tlb.silent_page);
+		vfree(trident->tlb.shadow_entries);
+		snd_dma_free_pages(&trident->tlb.buffer);
+	}
+	if (trident->irq >= 0)
+		free_irq(trident->irq, (void *)trident);
+	pci_release_regions(trident->pci);
+	pci_disable_device(trident->pci);
+	kfree(trident);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_interrupt
+  
+   Description: ISR for Trident 4DWave device
+                
+   Paramters:   trident  - device specific private data for 4DWave card
+
+   Problems:    It seems that Trident chips generates interrupts more than
+                one time in special cases. The spurious interrupts are
+                detected via sample timer (T4D_STIMER) and computing
+                corresponding delta value. The limits are detected with
+                the method try & fail so it is possible that it won't
+                work on all computers. [jaroslav]
+
+   Returns:     None.
+  
+  ---------------------------------------------------------------------------*/
+
+static irqreturn_t snd_trident_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	trident_t *trident = dev_id;
+	unsigned int audio_int, chn_int, stimer, channel, mask, tmp;
+	int delta;
+	snd_trident_voice_t *voice;
+
+	audio_int = inl(TRID_REG(trident, T4D_MISCINT));
+	if ((audio_int & (ADDRESS_IRQ|MPU401_IRQ)) == 0)
+		return IRQ_NONE;
+	if (audio_int & ADDRESS_IRQ) {
+		// get interrupt status for all channels
+		spin_lock(&trident->reg_lock);
+		stimer = inl(TRID_REG(trident, T4D_STIMER)) & 0x00ffffff;
+		chn_int = inl(TRID_REG(trident, T4D_AINT_A));
+		if (chn_int == 0)
+			goto __skip1;
+		outl(chn_int, TRID_REG(trident, T4D_AINT_A));	/* ack */
+	      __skip1:
+		chn_int = inl(TRID_REG(trident, T4D_AINT_B));
+		if (chn_int == 0)
+			goto __skip2;
+		for (channel = 63; channel >= 32; channel--) {
+			mask = 1 << (channel&0x1f);
+			if ((chn_int & mask) == 0)
+				continue;
+			voice = &trident->synth.voices[channel];
+			if (!voice->pcm || voice->substream == NULL) {
+				outl(mask, TRID_REG(trident, T4D_STOP_B));
+				continue;
+			}
+			delta = (int)stimer - (int)voice->stimer;
+			if (delta < 0)
+				delta = -delta;
+			if ((unsigned int)delta < voice->spurious_threshold) {
+				/* do some statistics here */
+				trident->spurious_irq_count++;
+				if (trident->spurious_irq_max_delta < (unsigned int)delta)
+					trident->spurious_irq_max_delta = delta;
+				continue;
+			}
+			voice->stimer = stimer;
+			if (voice->isync) {
+				if (!voice->isync3) {
+					tmp = inw(TRID_REG(trident, T4D_SBBL_SBCL));
+					if (trident->bDMAStart & 0x40)
+						tmp >>= 1;
+					if (tmp > 0)
+						tmp = voice->isync_max - tmp;
+				} else {
+					tmp = inl(TRID_REG(trident, NX_SPCTRL_SPCSO)) & 0x00ffffff;
+				}
+				if (tmp < voice->isync_mark) {
+					if (tmp > 0x10)
+						tmp = voice->isync_ESO - 7;
+					else
+						tmp = voice->isync_ESO + 2;
+					/* update ESO for IRQ voice to preserve sync */
+					snd_trident_stop_voice(trident, voice->number);
+					snd_trident_write_eso_reg(trident, voice, tmp);
+					snd_trident_start_voice(trident, voice->number);
+				}
+			} else if (voice->isync2) {
+				voice->isync2 = 0;
+				/* write original ESO and update CSO for IRQ voice to preserve sync */
+				snd_trident_stop_voice(trident, voice->number);
+				snd_trident_write_cso_reg(trident, voice, voice->isync_mark);
+				snd_trident_write_eso_reg(trident, voice, voice->ESO);
+				snd_trident_start_voice(trident, voice->number);
+			}
+#if 0
+			if (voice->extra) {
+				/* update CSO for extra voice to preserve sync */
+				snd_trident_stop_voice(trident, voice->extra->number);
+				snd_trident_write_cso_reg(trident, voice->extra, 0);
+				snd_trident_start_voice(trident, voice->extra->number);
+			}
+#endif
+			spin_unlock(&trident->reg_lock);
+			snd_pcm_period_elapsed(voice->substream);
+			spin_lock(&trident->reg_lock);
+		}
+		outl(chn_int, TRID_REG(trident, T4D_AINT_B));	/* ack */
+	      __skip2:
+		spin_unlock(&trident->reg_lock);
+	}
+	if (audio_int & MPU401_IRQ) {
+		if (trident->rmidi) {
+			snd_mpu401_uart_interrupt(irq, trident->rmidi->private_data, regs);
+		} else {
+			inb(TRID_REG(trident, T4D_MPUR0));
+		}
+	}
+	// outl((ST_TARGET_REACHED | MIXER_OVERFLOW | MIXER_UNDERFLOW), TRID_REG(trident, T4D_MISCINT));
+	return IRQ_HANDLED;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_attach_synthesizer
+  
+   Description: Attach synthesizer hooks
+                
+   Paramters:   trident  - device specific private data for 4DWave card
+
+   Returns:     None.
+  
+  ---------------------------------------------------------------------------*/
+int snd_trident_attach_synthesizer(trident_t *trident)
+{	
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+	if (snd_seq_device_new(trident->card, 1, SNDRV_SEQ_DEV_ID_TRIDENT,
+			       sizeof(trident_t*), &trident->seq_dev) >= 0) {
+		strcpy(trident->seq_dev->name, "4DWave");
+		*(trident_t**)SNDRV_SEQ_DEVICE_ARGPTR(trident->seq_dev) = trident;
+	}
+#endif
+	return 0;
+}
+
+snd_trident_voice_t *snd_trident_alloc_voice(trident_t * trident, int type, int client, int port)
+{
+	snd_trident_voice_t *pvoice;
+	unsigned long flags;
+	int idx;
+
+	spin_lock_irqsave(&trident->voice_alloc, flags);
+	if (type == SNDRV_TRIDENT_VOICE_TYPE_PCM) {
+		idx = snd_trident_allocate_pcm_channel(trident);
+		if(idx < 0) {
+			spin_unlock_irqrestore(&trident->voice_alloc, flags);
+			return NULL;
+		}
+		pvoice = &trident->synth.voices[idx];
+		pvoice->use = 1;
+		pvoice->pcm = 1;
+		pvoice->capture = 0;
+		pvoice->spdif = 0;
+		pvoice->memblk = NULL;
+		pvoice->substream = NULL;
+		spin_unlock_irqrestore(&trident->voice_alloc, flags);
+		return pvoice;
+	}
+	if (type == SNDRV_TRIDENT_VOICE_TYPE_SYNTH) {
+		idx = snd_trident_allocate_synth_channel(trident);
+		if(idx < 0) {
+			spin_unlock_irqrestore(&trident->voice_alloc, flags);
+			return NULL;
+		}
+		pvoice = &trident->synth.voices[idx];
+		pvoice->use = 1;
+		pvoice->synth = 1;
+		pvoice->client = client;
+		pvoice->port = port;
+		pvoice->memblk = NULL;
+		spin_unlock_irqrestore(&trident->voice_alloc, flags);
+		return pvoice;
+	}
+	if (type == SNDRV_TRIDENT_VOICE_TYPE_MIDI) {
+	}
+	spin_unlock_irqrestore(&trident->voice_alloc, flags);
+	return NULL;
+}
+
+void snd_trident_free_voice(trident_t * trident, snd_trident_voice_t *voice)
+{
+	unsigned long flags;
+	void (*private_free)(snd_trident_voice_t *);
+	void *private_data;
+
+	if (voice == NULL || !voice->use)
+		return;
+	snd_trident_clear_voices(trident, voice->number, voice->number);
+	spin_lock_irqsave(&trident->voice_alloc, flags);
+	private_free = voice->private_free;
+	private_data = voice->private_data;
+	voice->private_free = NULL;
+	voice->private_data = NULL;
+	if (voice->pcm)
+		snd_trident_free_pcm_channel(trident, voice->number);
+	if (voice->synth)
+		snd_trident_free_synth_channel(trident, voice->number);
+	voice->use = voice->pcm = voice->synth = voice->midi = 0;
+	voice->capture = voice->spdif = 0;
+	voice->sample_ops = NULL;
+	voice->substream = NULL;
+	voice->extra = NULL;
+	spin_unlock_irqrestore(&trident->voice_alloc, flags);
+	if (private_free)
+		private_free(voice);
+}
+
+static void snd_trident_clear_voices(trident_t * trident, unsigned short v_min, unsigned short v_max)
+{
+	unsigned int i, val, mask[2] = { 0, 0 };
+
+	snd_assert(v_min <= 63, return);
+	snd_assert(v_max <= 63, return);
+	for (i = v_min; i <= v_max; i++)
+		mask[i >> 5] |= 1 << (i & 0x1f);
+	if (mask[0]) {
+		outl(mask[0], TRID_REG(trident, T4D_STOP_A));
+		val = inl(TRID_REG(trident, T4D_AINTEN_A));
+		outl(val & ~mask[0], TRID_REG(trident, T4D_AINTEN_A));
+	}
+	if (mask[1]) {
+		outl(mask[1], TRID_REG(trident, T4D_STOP_B));
+		val = inl(TRID_REG(trident, T4D_AINTEN_B));
+		outl(val & ~mask[1], TRID_REG(trident, T4D_AINTEN_B));
+	}
+}
+
+#ifdef CONFIG_PM
+static int snd_trident_suspend(snd_card_t *card, pm_message_t state)
+{
+	trident_t *trident = card->pm_private_data;
+
+	trident->in_suspend = 1;
+	snd_pcm_suspend_all(trident->pcm);
+	if (trident->foldback)
+		snd_pcm_suspend_all(trident->foldback);
+	if (trident->spdif)
+		snd_pcm_suspend_all(trident->spdif);
+
+	snd_ac97_suspend(trident->ac97);
+	if (trident->ac97_sec)
+		snd_ac97_suspend(trident->ac97_sec);
+
+	switch (trident->device) {
+	case TRIDENT_DEVICE_ID_DX:
+	case TRIDENT_DEVICE_ID_NX:
+		break;			/* TODO */
+	case TRIDENT_DEVICE_ID_SI7018:
+		break;
+	}
+	pci_disable_device(trident->pci);
+	return 0;
+}
+
+static int snd_trident_resume(snd_card_t *card)
+{
+	trident_t *trident = card->pm_private_data;
+
+	pci_enable_device(trident->pci);
+	if (pci_set_dma_mask(trident->pci, 0x3fffffff) < 0 ||
+	    pci_set_consistent_dma_mask(trident->pci, 0x3fffffff) < 0)
+		snd_printk(KERN_WARNING "trident: can't set the proper DMA mask\n");
+	pci_set_master(trident->pci); /* to be sure */
+
+	switch (trident->device) {
+	case TRIDENT_DEVICE_ID_DX:
+		snd_trident_4d_dx_init(trident);
+		break;
+	case TRIDENT_DEVICE_ID_NX:
+		snd_trident_4d_nx_init(trident);
+		break;
+	case TRIDENT_DEVICE_ID_SI7018:
+		snd_trident_sis_init(trident);
+		break;
+	}
+
+	snd_ac97_resume(trident->ac97);
+	if (trident->ac97_sec)
+		snd_ac97_resume(trident->ac97_sec);
+
+	/* restore some registers */
+	outl(trident->musicvol_wavevol, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
+
+	snd_trident_enable_eso(trident);
+
+	trident->in_suspend = 0;
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+EXPORT_SYMBOL(snd_trident_alloc_voice);
+EXPORT_SYMBOL(snd_trident_free_voice);
+EXPORT_SYMBOL(snd_trident_start_voice);
+EXPORT_SYMBOL(snd_trident_stop_voice);
+EXPORT_SYMBOL(snd_trident_write_voice_regs);
+/* trident_memory.c symbols */
+EXPORT_SYMBOL(snd_trident_synth_alloc);
+EXPORT_SYMBOL(snd_trident_synth_free);
+EXPORT_SYMBOL(snd_trident_synth_copy_from_user);
diff --git a/sound/pci/trident/trident_memory.c b/sound/pci/trident/trident_memory.c
new file mode 100644
index 0000000..6cc2826
--- /dev/null
+++ b/sound/pci/trident/trident_memory.c
@@ -0,0 +1,476 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ *  Copyright (c) by Scott McNab <sdm@fractalgraphics.com.au>
+ *
+ *  Trident 4DWave-NX memory page allocation (TLB area)
+ *  Trident chip can handle only 16MByte of the memory at the same time.
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/trident.h>
+
+/* page arguments of these two macros are Trident page (4096 bytes), not like
+ * aligned pages in others
+ */
+#define __set_tlb_bus(trident,page,ptr,addr) \
+	do { (trident)->tlb.entries[page] = cpu_to_le32((addr) & ~(SNDRV_TRIDENT_PAGE_SIZE-1)); \
+	     (trident)->tlb.shadow_entries[page] = (ptr); } while (0)
+#define __tlb_to_ptr(trident,page) \
+	(void*)((trident)->tlb.shadow_entries[page])
+#define __tlb_to_addr(trident,page) \
+	(dma_addr_t)le32_to_cpu((trident->tlb.entries[page]) & ~(SNDRV_TRIDENT_PAGE_SIZE - 1))
+
+#if PAGE_SIZE == 4096
+/* page size == SNDRV_TRIDENT_PAGE_SIZE */
+#define ALIGN_PAGE_SIZE		PAGE_SIZE	/* minimum page size for allocation */
+#define MAX_ALIGN_PAGES		SNDRV_TRIDENT_MAX_PAGES	/* maxmium aligned pages */
+/* fill TLB entrie(s) corresponding to page with ptr */
+#define set_tlb_bus(trident,page,ptr,addr) __set_tlb_bus(trident,page,ptr,addr)
+/* fill TLB entrie(s) corresponding to page with silence pointer */
+#define set_silent_tlb(trident,page)	__set_tlb_bus(trident, page, (unsigned long)trident->tlb.silent_page.area, trident->tlb.silent_page.addr)
+/* get aligned page from offset address */
+#define get_aligned_page(offset)	((offset) >> 12)
+/* get offset address from aligned page */
+#define aligned_page_offset(page)	((page) << 12)
+/* get buffer address from aligned page */
+#define page_to_ptr(trident,page)	__tlb_to_ptr(trident, page)
+/* get PCI physical address from aligned page */
+#define page_to_addr(trident,page)	__tlb_to_addr(trident, page)
+
+#elif PAGE_SIZE == 8192
+/* page size == SNDRV_TRIDENT_PAGE_SIZE x 2*/
+#define ALIGN_PAGE_SIZE		PAGE_SIZE
+#define MAX_ALIGN_PAGES		(SNDRV_TRIDENT_MAX_PAGES / 2)
+#define get_aligned_page(offset)	((offset) >> 13)
+#define aligned_page_offset(page)	((page) << 13)
+#define page_to_ptr(trident,page)	__tlb_to_ptr(trident, (page) << 1)
+#define page_to_addr(trident,page)	__tlb_to_addr(trident, (page) << 1)
+
+/* fill TLB entries -- we need to fill two entries */
+static inline void set_tlb_bus(trident_t *trident, int page, unsigned long ptr, dma_addr_t addr)
+{
+	page <<= 1;
+	__set_tlb_bus(trident, page, ptr, addr);
+	__set_tlb_bus(trident, page+1, ptr + SNDRV_TRIDENT_PAGE_SIZE, addr + SNDRV_TRIDENT_PAGE_SIZE);
+}
+static inline void set_silent_tlb(trident_t *trident, int page)
+{
+	page <<= 1;
+	__set_tlb_bus(trident, page, (unsigned long)trident->tlb.silent_page.area, trident->tlb.silent_page.addr);
+	__set_tlb_bus(trident, page+1, (unsigned long)trident->tlb.silent_page.area, trident->tlb.silent_page.addr);
+}
+
+#else
+/* arbitrary size */
+#define UNIT_PAGES		(PAGE_SIZE / SNDRV_TRIDENT_PAGE_SIZE)
+#define ALIGN_PAGE_SIZE		(SNDRV_TRIDENT_PAGE_SIZE * UNIT_PAGES)
+#define MAX_ALIGN_PAGES		(SNDRV_TRIDENT_MAX_PAGES / UNIT_PAGES)
+/* Note: if alignment doesn't match to the maximum size, the last few blocks
+ * become unusable.  To use such blocks, you'll need to check the validity
+ * of accessing page in set_tlb_bus and set_silent_tlb.  search_empty()
+ * should also check it, too.
+ */
+#define get_aligned_page(offset)	((offset) / ALIGN_PAGE_SIZE)
+#define aligned_page_offset(page)	((page) * ALIGN_PAGE_SIZE)
+#define page_to_ptr(trident,page)	__tlb_to_ptr(trident, (page) * UNIT_PAGES)
+#define page_to_addr(trident,page)	__tlb_to_addr(trident, (page) * UNIT_PAGES)
+
+/* fill TLB entries -- UNIT_PAGES entries must be filled */
+static inline void set_tlb_bus(trident_t *trident, int page, unsigned long ptr, dma_addr_t addr)
+{
+	int i;
+	page *= UNIT_PAGES;
+	for (i = 0; i < UNIT_PAGES; i++, page++) {
+		__set_tlb_bus(trident, page, ptr, addr);
+		ptr += SNDRV_TRIDENT_PAGE_SIZE;
+		addr += SNDRV_TRIDENT_PAGE_SIZE;
+	}
+}
+static inline void set_silent_tlb(trident_t *trident, int page)
+{
+	int i;
+	page *= UNIT_PAGES;
+	for (i = 0; i < UNIT_PAGES; i++, page++)
+		__set_tlb_bus(trident, page, (unsigned long)trident->tlb.silent_page.area, trident->tlb.silent_page.addr);
+}
+
+#endif /* PAGE_SIZE */
+
+/* calculate buffer pointer from offset address */
+inline static void *offset_ptr(trident_t *trident, int offset)
+{
+	char *ptr;
+	ptr = page_to_ptr(trident, get_aligned_page(offset));
+	ptr += offset % ALIGN_PAGE_SIZE;
+	return (void*)ptr;
+}
+
+/* first and last (aligned) pages of memory block */
+#define firstpg(blk)	(((snd_trident_memblk_arg_t*)snd_util_memblk_argptr(blk))->first_page)
+#define lastpg(blk)	(((snd_trident_memblk_arg_t*)snd_util_memblk_argptr(blk))->last_page)
+
+/*
+ * search empty pages which may contain given size
+ */
+static snd_util_memblk_t *
+search_empty(snd_util_memhdr_t *hdr, int size)
+{
+	snd_util_memblk_t *blk, *prev;
+	int page, psize;
+	struct list_head *p;
+
+	psize = get_aligned_page(size + ALIGN_PAGE_SIZE -1);
+	prev = NULL;
+	page = 0;
+	list_for_each(p, &hdr->block) {
+		blk = list_entry(p, snd_util_memblk_t, list);
+		if (page + psize <= firstpg(blk))
+			goto __found_pages;
+		page = lastpg(blk) + 1;
+	}
+	if (page + psize > MAX_ALIGN_PAGES)
+		return NULL;
+
+__found_pages:
+	/* create a new memory block */
+	blk = __snd_util_memblk_new(hdr, psize * ALIGN_PAGE_SIZE, p->prev);
+	if (blk == NULL)
+		return NULL;
+	blk->offset = aligned_page_offset(page); /* set aligned offset */
+	firstpg(blk) = page;
+	lastpg(blk) = page + psize - 1;
+	return blk;
+}
+
+
+/*
+ * check if the given pointer is valid for pages
+ */
+static int is_valid_page(unsigned long ptr)
+{
+	if (ptr & ~0x3fffffffUL) {
+		snd_printk("max memory size is 1GB!!\n");
+		return 0;
+	}
+	if (ptr & (SNDRV_TRIDENT_PAGE_SIZE-1)) {
+		snd_printk("page is not aligned\n");
+		return 0;
+	}
+	return 1;
+}
+
+/*
+ * page allocation for DMA (Scatter-Gather version)
+ */
+static snd_util_memblk_t *
+snd_trident_alloc_sg_pages(trident_t *trident, snd_pcm_substream_t *substream)
+{
+	snd_util_memhdr_t *hdr;
+	snd_util_memblk_t *blk;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int idx, page;
+	struct snd_sg_buf *sgbuf = snd_pcm_substream_sgbuf(substream);
+
+	snd_assert(runtime->dma_bytes > 0 && runtime->dma_bytes <= SNDRV_TRIDENT_MAX_PAGES * SNDRV_TRIDENT_PAGE_SIZE, return NULL);
+	hdr = trident->tlb.memhdr;
+	snd_assert(hdr != NULL, return NULL);
+
+	
+
+	down(&hdr->block_mutex);
+	blk = search_empty(hdr, runtime->dma_bytes);
+	if (blk == NULL) {
+		up(&hdr->block_mutex);
+		return NULL;
+	}
+	if (lastpg(blk) - firstpg(blk) >= sgbuf->pages) {
+		snd_printk(KERN_ERR "page calculation doesn't match: allocated pages = %d, trident = %d/%d\n", sgbuf->pages, firstpg(blk), lastpg(blk));
+		__snd_util_mem_free(hdr, blk);
+		up(&hdr->block_mutex);
+		return NULL;
+	}
+			   
+	/* set TLB entries */
+	idx = 0;
+	for (page = firstpg(blk); page <= lastpg(blk); page++, idx++) {
+		dma_addr_t addr = sgbuf->table[idx].addr;
+		unsigned long ptr = (unsigned long)sgbuf->table[idx].buf;
+		if (! is_valid_page(addr)) {
+			__snd_util_mem_free(hdr, blk);
+			up(&hdr->block_mutex);
+			return NULL;
+		}
+		set_tlb_bus(trident, page, ptr, addr);
+	}
+	up(&hdr->block_mutex);
+	return blk;
+}
+
+/*
+ * page allocation for DMA (contiguous version)
+ */
+static snd_util_memblk_t *
+snd_trident_alloc_cont_pages(trident_t *trident, snd_pcm_substream_t *substream)
+{
+	snd_util_memhdr_t *hdr;
+	snd_util_memblk_t *blk;
+	int page;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	dma_addr_t addr;
+	unsigned long ptr;
+
+	snd_assert(runtime->dma_bytes> 0 && runtime->dma_bytes <= SNDRV_TRIDENT_MAX_PAGES * SNDRV_TRIDENT_PAGE_SIZE, return NULL);
+	hdr = trident->tlb.memhdr;
+	snd_assert(hdr != NULL, return NULL);
+
+	down(&hdr->block_mutex);
+	blk = search_empty(hdr, runtime->dma_bytes);
+	if (blk == NULL) {
+		up(&hdr->block_mutex);
+		return NULL;
+	}
+			   
+	/* set TLB entries */
+	addr = runtime->dma_addr;
+	ptr = (unsigned long)runtime->dma_area;
+	for (page = firstpg(blk); page <= lastpg(blk); page++,
+	     ptr += SNDRV_TRIDENT_PAGE_SIZE, addr += SNDRV_TRIDENT_PAGE_SIZE) {
+		if (! is_valid_page(addr)) {
+			__snd_util_mem_free(hdr, blk);
+			up(&hdr->block_mutex);
+			return NULL;
+		}
+		set_tlb_bus(trident, page, ptr, addr);
+	}
+	up(&hdr->block_mutex);
+	return blk;
+}
+
+/*
+ * page allocation for DMA
+ */
+snd_util_memblk_t *
+snd_trident_alloc_pages(trident_t *trident, snd_pcm_substream_t *substream)
+{
+	snd_assert(trident != NULL, return NULL);
+	snd_assert(substream != NULL, return NULL);
+	if (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV_SG)
+		return snd_trident_alloc_sg_pages(trident, substream);
+	else
+		return snd_trident_alloc_cont_pages(trident, substream);
+}
+
+
+/*
+ * release DMA buffer from page table
+ */
+int snd_trident_free_pages(trident_t *trident, snd_util_memblk_t *blk)
+{
+	snd_util_memhdr_t *hdr;
+	int page;
+
+	snd_assert(trident != NULL, return -EINVAL);
+	snd_assert(blk != NULL, return -EINVAL);
+
+	hdr = trident->tlb.memhdr;
+	down(&hdr->block_mutex);
+	/* reset TLB entries */
+	for (page = firstpg(blk); page <= lastpg(blk); page++)
+		set_silent_tlb(trident, page);
+	/* free memory block */
+	__snd_util_mem_free(hdr, blk);
+	up(&hdr->block_mutex);
+	return 0;
+}
+
+
+/*----------------------------------------------------------------
+ * memory allocation using multiple pages (for synth)
+ *----------------------------------------------------------------
+ * Unlike the DMA allocation above, non-contiguous pages are
+ * assigned to TLB.
+ *----------------------------------------------------------------*/
+
+/*
+ */
+static int synth_alloc_pages(trident_t *hw, snd_util_memblk_t *blk);
+static int synth_free_pages(trident_t *hw, snd_util_memblk_t *blk);
+
+/*
+ * allocate a synth sample area
+ */
+snd_util_memblk_t *
+snd_trident_synth_alloc(trident_t *hw, unsigned int size)
+{
+	snd_util_memblk_t *blk;
+	snd_util_memhdr_t *hdr = hw->tlb.memhdr; 
+
+	down(&hdr->block_mutex);
+	blk = __snd_util_mem_alloc(hdr, size);
+	if (blk == NULL) {
+		up(&hdr->block_mutex);
+		return NULL;
+	}
+	if (synth_alloc_pages(hw, blk)) {
+		__snd_util_mem_free(hdr, blk);
+		up(&hdr->block_mutex);
+		return NULL;
+	}
+	up(&hdr->block_mutex);
+	return blk;
+}
+
+
+/*
+ * free a synth sample area
+ */
+int
+snd_trident_synth_free(trident_t *hw, snd_util_memblk_t *blk)
+{
+	snd_util_memhdr_t *hdr = hw->tlb.memhdr; 
+
+	down(&hdr->block_mutex);
+	synth_free_pages(hw, blk);
+	 __snd_util_mem_free(hdr, blk);
+	up(&hdr->block_mutex);
+	return 0;
+}
+
+
+/*
+ * reset TLB entry and free kernel page
+ */
+static void clear_tlb(trident_t *trident, int page)
+{
+	void *ptr = page_to_ptr(trident, page);
+	dma_addr_t addr = page_to_addr(trident, page);
+	set_silent_tlb(trident, page);
+	if (ptr) {
+		struct snd_dma_buffer dmab;
+		dmab.dev.type = SNDRV_DMA_TYPE_DEV;
+		dmab.dev.dev = snd_dma_pci_data(trident->pci);
+		dmab.area = ptr;
+		dmab.addr = addr;
+		dmab.bytes = ALIGN_PAGE_SIZE;
+		snd_dma_free_pages(&dmab);
+	}
+}
+
+/* check new allocation range */
+static void get_single_page_range(snd_util_memhdr_t *hdr, snd_util_memblk_t *blk, int *first_page_ret, int *last_page_ret)
+{
+	struct list_head *p;
+	snd_util_memblk_t *q;
+	int first_page, last_page;
+	first_page = firstpg(blk);
+	if ((p = blk->list.prev) != &hdr->block) {
+		q = list_entry(p, snd_util_memblk_t, list);
+		if (lastpg(q) == first_page)
+			first_page++;  /* first page was already allocated */
+	}
+	last_page = lastpg(blk);
+	if ((p = blk->list.next) != &hdr->block) {
+		q = list_entry(p, snd_util_memblk_t, list);
+		if (firstpg(q) == last_page)
+			last_page--; /* last page was already allocated */
+	}
+	*first_page_ret = first_page;
+	*last_page_ret = last_page;
+}
+
+/*
+ * allocate kernel pages and assign them to TLB
+ */
+static int synth_alloc_pages(trident_t *hw, snd_util_memblk_t *blk)
+{
+	int page, first_page, last_page;
+	struct snd_dma_buffer dmab;
+
+	firstpg(blk) = get_aligned_page(blk->offset);
+	lastpg(blk) = get_aligned_page(blk->offset + blk->size - 1);
+	get_single_page_range(hw->tlb.memhdr, blk, &first_page, &last_page);
+
+	/* allocate a kernel page for each Trident page -
+	 * fortunately Trident page size and kernel PAGE_SIZE is identical!
+	 */
+	for (page = first_page; page <= last_page; page++) {
+		if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(hw->pci),
+					ALIGN_PAGE_SIZE, &dmab) < 0)
+			goto __fail;
+		if (! is_valid_page(dmab.addr)) {
+			snd_dma_free_pages(&dmab);
+			goto __fail;
+		}
+		set_tlb_bus(hw, page, (unsigned long)dmab.area, dmab.addr);
+	}
+	return 0;
+
+__fail:
+	/* release allocated pages */
+	last_page = page - 1;
+	for (page = first_page; page <= last_page; page++)
+		clear_tlb(hw, page);
+
+	return -ENOMEM;
+}
+
+/*
+ * free pages
+ */
+static int synth_free_pages(trident_t *trident, snd_util_memblk_t *blk)
+{
+	int page, first_page, last_page;
+
+	get_single_page_range(trident->tlb.memhdr, blk, &first_page, &last_page);
+	for (page = first_page; page <= last_page; page++)
+		clear_tlb(trident, page);
+
+	return 0;
+}
+
+/*
+ * copy_from_user(blk + offset, data, size)
+ */
+int snd_trident_synth_copy_from_user(trident_t *trident, snd_util_memblk_t *blk, int offset, const char __user *data, int size)
+{
+	int page, nextofs, end_offset, temp, temp1;
+
+	offset += blk->offset;
+	end_offset = offset + size;
+	page = get_aligned_page(offset) + 1;
+	do {
+		nextofs = aligned_page_offset(page);
+		temp = nextofs - offset;
+		temp1 = end_offset - offset;
+		if (temp1 < temp)
+			temp = temp1;
+		if (copy_from_user(offset_ptr(trident, offset), data, temp))
+			return -EFAULT;
+		offset = nextofs;
+		data += temp;
+		page++;
+	} while (offset < end_offset);
+	return 0;
+}
+
diff --git a/sound/pci/trident/trident_synth.c b/sound/pci/trident/trident_synth.c
new file mode 100644
index 0000000..5d5a719
--- /dev/null
+++ b/sound/pci/trident/trident_synth.c
@@ -0,0 +1,1031 @@
+/*
+ *  Routines for Trident 4DWave NX/DX soundcards - Synthesizer
+ *  Copyright (c) by Scott McNab <jedi@tartarus.uwa.edu.au>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include <sound/trident.h>
+#include <sound/seq_device.h>
+
+MODULE_AUTHOR("Scott McNab <jedi@tartarus.uwa.edu.au>");
+MODULE_DESCRIPTION("Routines for Trident 4DWave NX/DX soundcards - Synthesizer");
+MODULE_LICENSE("GPL");
+
+/* linear to log pan conversion table (4.2 channel attenuation format) */
+static unsigned int pan_table[63] = {
+	7959, 7733, 7514, 7301, 7093, 6892, 6697, 6507, 
+	6322, 6143, 5968, 5799, 5634, 5475, 5319, 5168, 
+	5022, 4879, 4741, 4606, 4475, 4349, 4225, 4105, 
+	3989, 3876, 3766, 3659, 3555, 3454, 3356, 3261, 
+	3168, 3078, 2991, 2906, 2824, 2744, 2666, 2590, 
+	2517, 2445, 2376, 2308, 2243, 2179, 2117, 2057, 
+	1999, 1942, 1887, 1833, 1781, 1731, 1682, 1634, 
+	1588, 1543, 1499, 1456, 1415, 1375, 1336
+};
+
+#define LOG_TABLE_SIZE 386
+
+/* Linear half-attenuation to log conversion table in the format:
+ *   {linear volume, logarithmic attenuation equivalent}, ...
+ *
+ * Provides conversion from a linear half-volume value in the range
+ * [0,8192] to a logarithmic attenuation value in the range 0 to 6.02dB.
+ * Halving the linear volume is equivalent to an additional 6dB of 
+ * logarithmic attenuation. The algorithm used in log_from_linear()
+ * therefore uses this table as follows:
+ * 
+ * - loop and for every time the volume is less than half the maximum 
+ *   volume (16384), add another 6dB and halve the maximum value used
+ *   for this comparison.
+ * - when the volume is greater than half the maximum volume, take
+ *   the difference of the volume to half volume (in the range [0,8192])
+ *   and look up the log_table[] to find the nearest entry.
+ * - take the logarithic component of this entry and add it to the 
+ *   resulting attenuation.
+ *
+ * Thus this routine provides a linear->log conversion for a range of
+ * [0,16384] using only 386 table entries
+ *
+ * Note: although this table stores log attenuation in 8.8 format, values
+ * were only calculated for 6 bits fractional precision, since that is
+ * the most precision offered by the trident hardware.
+ */
+
+static unsigned short log_table[LOG_TABLE_SIZE*2] =
+{
+	4, 0x0604, 19, 0x0600, 34, 0x05fc, 
+	49, 0x05f8, 63, 0x05f4, 78, 0x05f0, 93, 0x05ec, 108, 0x05e8, 
+	123, 0x05e4, 138, 0x05e0, 153, 0x05dc, 168, 0x05d8, 183, 0x05d4, 
+	198, 0x05d0, 213, 0x05cc, 228, 0x05c8, 244, 0x05c4, 259, 0x05c0, 
+	274, 0x05bc, 289, 0x05b8, 304, 0x05b4, 320, 0x05b0, 335, 0x05ac, 
+	350, 0x05a8, 366, 0x05a4, 381, 0x05a0, 397, 0x059c, 412, 0x0598, 
+	428, 0x0594, 443, 0x0590, 459, 0x058c, 474, 0x0588, 490, 0x0584, 
+	506, 0x0580, 521, 0x057c, 537, 0x0578, 553, 0x0574, 568, 0x0570, 
+	584, 0x056c, 600, 0x0568, 616, 0x0564, 632, 0x0560, 647, 0x055c, 
+	663, 0x0558, 679, 0x0554, 695, 0x0550, 711, 0x054c, 727, 0x0548, 
+	743, 0x0544, 759, 0x0540, 776, 0x053c, 792, 0x0538, 808, 0x0534, 
+	824, 0x0530, 840, 0x052c, 857, 0x0528, 873, 0x0524, 889, 0x0520, 
+	906, 0x051c, 922, 0x0518, 938, 0x0514, 955, 0x0510, 971, 0x050c, 
+	988, 0x0508, 1004, 0x0504, 1021, 0x0500, 1037, 0x04fc, 1054, 0x04f8, 
+	1071, 0x04f4, 1087, 0x04f0, 1104, 0x04ec, 1121, 0x04e8, 1138, 0x04e4, 
+	1154, 0x04e0, 1171, 0x04dc, 1188, 0x04d8, 1205, 0x04d4, 1222, 0x04d0, 
+	1239, 0x04cc, 1256, 0x04c8, 1273, 0x04c4, 1290, 0x04c0, 1307, 0x04bc, 
+	1324, 0x04b8, 1341, 0x04b4, 1358, 0x04b0, 1376, 0x04ac, 1393, 0x04a8, 
+	1410, 0x04a4, 1427, 0x04a0, 1445, 0x049c, 1462, 0x0498, 1479, 0x0494, 
+	1497, 0x0490, 1514, 0x048c, 1532, 0x0488, 1549, 0x0484, 1567, 0x0480, 
+	1584, 0x047c, 1602, 0x0478, 1620, 0x0474, 1637, 0x0470, 1655, 0x046c, 
+	1673, 0x0468, 1690, 0x0464, 1708, 0x0460, 1726, 0x045c, 1744, 0x0458, 
+	1762, 0x0454, 1780, 0x0450, 1798, 0x044c, 1816, 0x0448, 1834, 0x0444, 
+	1852, 0x0440, 1870, 0x043c, 1888, 0x0438, 1906, 0x0434, 1924, 0x0430, 
+	1943, 0x042c, 1961, 0x0428, 1979, 0x0424, 1997, 0x0420, 2016, 0x041c, 
+	2034, 0x0418, 2053, 0x0414, 2071, 0x0410, 2089, 0x040c, 2108, 0x0408, 
+	2127, 0x0404, 2145, 0x0400, 2164, 0x03fc, 2182, 0x03f8, 2201, 0x03f4, 
+	2220, 0x03f0, 2239, 0x03ec, 2257, 0x03e8, 2276, 0x03e4, 2295, 0x03e0, 
+	2314, 0x03dc, 2333, 0x03d8, 2352, 0x03d4, 2371, 0x03d0, 2390, 0x03cc, 
+	2409, 0x03c8, 2428, 0x03c4, 2447, 0x03c0, 2466, 0x03bc, 2485, 0x03b8, 
+	2505, 0x03b4, 2524, 0x03b0, 2543, 0x03ac, 2562, 0x03a8, 2582, 0x03a4, 
+	2601, 0x03a0, 2621, 0x039c, 2640, 0x0398, 2660, 0x0394, 2679, 0x0390, 
+	2699, 0x038c, 2718, 0x0388, 2738, 0x0384, 2758, 0x0380, 2777, 0x037c, 
+	2797, 0x0378, 2817, 0x0374, 2837, 0x0370, 2857, 0x036c, 2876, 0x0368, 
+	2896, 0x0364, 2916, 0x0360, 2936, 0x035c, 2956, 0x0358, 2976, 0x0354, 
+	2997, 0x0350, 3017, 0x034c, 3037, 0x0348, 3057, 0x0344, 3077, 0x0340, 
+	3098, 0x033c, 3118, 0x0338, 3138, 0x0334, 3159, 0x0330, 3179, 0x032c, 
+	3200, 0x0328, 3220, 0x0324, 3241, 0x0320, 3261, 0x031c, 3282, 0x0318, 
+	3303, 0x0314, 3323, 0x0310, 3344, 0x030c, 3365, 0x0308, 3386, 0x0304, 
+	3406, 0x0300, 3427, 0x02fc, 3448, 0x02f8, 3469, 0x02f4, 3490, 0x02f0, 
+	3511, 0x02ec, 3532, 0x02e8, 3553, 0x02e4, 3575, 0x02e0, 3596, 0x02dc, 
+	3617, 0x02d8, 3638, 0x02d4, 3660, 0x02d0, 3681, 0x02cc, 3702, 0x02c8, 
+	3724, 0x02c4, 3745, 0x02c0, 3767, 0x02bc, 3788, 0x02b8, 3810, 0x02b4, 
+	3831, 0x02b0, 3853, 0x02ac, 3875, 0x02a8, 3896, 0x02a4, 3918, 0x02a0, 
+	3940, 0x029c, 3962, 0x0298, 3984, 0x0294, 4006, 0x0290, 4028, 0x028c, 
+	4050, 0x0288, 4072, 0x0284, 4094, 0x0280, 4116, 0x027c, 4138, 0x0278, 
+	4160, 0x0274, 4182, 0x0270, 4205, 0x026c, 4227, 0x0268, 4249, 0x0264, 
+	4272, 0x0260, 4294, 0x025c, 4317, 0x0258, 4339, 0x0254, 4362, 0x0250, 
+	4384, 0x024c, 4407, 0x0248, 4430, 0x0244, 4453, 0x0240, 4475, 0x023c, 
+	4498, 0x0238, 4521, 0x0234, 4544, 0x0230, 4567, 0x022c, 4590, 0x0228, 
+	4613, 0x0224, 4636, 0x0220, 4659, 0x021c, 4682, 0x0218, 4705, 0x0214, 
+	4728, 0x0210, 4752, 0x020c, 4775, 0x0208, 4798, 0x0204, 4822, 0x0200, 
+	4845, 0x01fc, 4869, 0x01f8, 4892, 0x01f4, 4916, 0x01f0, 4939, 0x01ec, 
+	4963, 0x01e8, 4987, 0x01e4, 5010, 0x01e0, 5034, 0x01dc, 5058, 0x01d8, 
+	5082, 0x01d4, 5106, 0x01d0, 5130, 0x01cc, 5154, 0x01c8, 5178, 0x01c4, 
+	5202, 0x01c0, 5226, 0x01bc, 5250, 0x01b8, 5274, 0x01b4, 5299, 0x01b0, 
+	5323, 0x01ac, 5347, 0x01a8, 5372, 0x01a4, 5396, 0x01a0, 5420, 0x019c, 
+	5445, 0x0198, 5469, 0x0194, 5494, 0x0190, 5519, 0x018c, 5543, 0x0188, 
+	5568, 0x0184, 5593, 0x0180, 5618, 0x017c, 5643, 0x0178, 5668, 0x0174, 
+	5692, 0x0170, 5717, 0x016c, 5743, 0x0168, 5768, 0x0164, 5793, 0x0160, 
+	5818, 0x015c, 5843, 0x0158, 5868, 0x0154, 5894, 0x0150, 5919, 0x014c, 
+	5945, 0x0148, 5970, 0x0144, 5995, 0x0140, 6021, 0x013c, 6047, 0x0138, 
+	6072, 0x0134, 6098, 0x0130, 6124, 0x012c, 6149, 0x0128, 6175, 0x0124, 
+	6201, 0x0120, 6227, 0x011c, 6253, 0x0118, 6279, 0x0114, 6305, 0x0110, 
+	6331, 0x010c, 6357, 0x0108, 6384, 0x0104, 6410, 0x0100, 6436, 0x00fc, 
+	6462, 0x00f8, 6489, 0x00f4, 6515, 0x00f0, 6542, 0x00ec, 6568, 0x00e8, 
+	6595, 0x00e4, 6621, 0x00e0, 6648, 0x00dc, 6675, 0x00d8, 6702, 0x00d4, 
+	6728, 0x00d0, 6755, 0x00cc, 6782, 0x00c8, 6809, 0x00c4, 6836, 0x00c0, 
+	6863, 0x00bc, 6890, 0x00b8, 6917, 0x00b4, 6945, 0x00b0, 6972, 0x00ac, 
+	6999, 0x00a8, 7027, 0x00a4, 7054, 0x00a0, 7081, 0x009c, 7109, 0x0098, 
+	7136, 0x0094, 7164, 0x0090, 7192, 0x008c, 7219, 0x0088, 7247, 0x0084, 
+	7275, 0x0080, 7303, 0x007c, 7331, 0x0078, 7359, 0x0074, 7387, 0x0070, 
+	7415, 0x006c, 7443, 0x0068, 7471, 0x0064, 7499, 0x0060, 7527, 0x005c, 
+	7556, 0x0058, 7584, 0x0054, 7613, 0x0050, 7641, 0x004c, 7669, 0x0048, 
+	7698, 0x0044, 7727, 0x0040, 7755, 0x003c, 7784, 0x0038, 7813, 0x0034, 
+	7842, 0x0030, 7870, 0x002c, 7899, 0x0028, 7928, 0x0024, 7957, 0x0020, 
+	7986, 0x001c, 8016, 0x0018, 8045, 0x0014, 8074, 0x0010, 8103, 0x000c, 
+	8133, 0x0008, 8162, 0x0004, 8192, 0x0000
+};
+
+static unsigned short lookup_volume_table( unsigned short value )
+{
+	/* This code is an optimised version of:
+	 *   int i = 0;
+	 *   while( volume_table[i*2] < value )
+	 *       i++;
+	 *   return volume_table[i*2+1];
+	 */
+	unsigned short *ptr = log_table;
+	while( *ptr < value )
+		ptr += 2;
+	return *(ptr+1);
+}
+
+/* this function calculates a 8.8 fixed point logarithmic attenuation
+ * value from a linear volume value in the range 0 to 16384 */
+static unsigned short log_from_linear( unsigned short value )
+{
+	if (value >= 16384)
+		return 0x0000;
+	if (value) {
+		unsigned short result = 0;
+		int v, c;
+		for( c = 0, v = 8192; c < 14; c++, v >>= 1 ) {
+			if( value >= v ) {
+				result += lookup_volume_table( (value - v) << c );
+				return result;
+			}
+			result += 0x0605;	/* 6.0205 (result of -20*log10(0.5)) */
+		}
+	}
+	return 0xffff;
+}
+
+/*
+ * Sample handling operations
+ */
+
+static void sample_start(trident_t * trident, snd_trident_voice_t * voice, snd_seq_position_t position);
+static void sample_stop(trident_t * trident, snd_trident_voice_t * voice, snd_seq_stop_mode_t mode);
+static void sample_freq(trident_t * trident, snd_trident_voice_t * voice, snd_seq_frequency_t freq);
+static void sample_volume(trident_t * trident, snd_trident_voice_t * voice, snd_seq_ev_volume_t * volume);
+static void sample_loop(trident_t * trident, snd_trident_voice_t * voice, snd_seq_ev_loop_t * loop);
+static void sample_pos(trident_t * trident, snd_trident_voice_t * voice, snd_seq_position_t position);
+static void sample_private1(trident_t * trident, snd_trident_voice_t * voice, unsigned char *data);
+
+static snd_trident_sample_ops_t sample_ops =
+{
+	sample_start,
+	sample_stop,
+	sample_freq,
+	sample_volume,
+	sample_loop,
+	sample_pos,
+	sample_private1
+};
+
+static void snd_trident_simple_init(snd_trident_voice_t * voice)
+{
+	//voice->handler_wave = interrupt_wave;
+	//voice->handler_volume = interrupt_volume;
+	//voice->handler_effect = interrupt_effect;
+	//voice->volume_change = NULL;
+	voice->sample_ops = &sample_ops;
+}
+
+static void sample_start(trident_t * trident, snd_trident_voice_t * voice, snd_seq_position_t position)
+{
+	simple_instrument_t *simple;
+	snd_seq_kinstr_t *instr;
+	unsigned long flags;
+	unsigned int loop_start, loop_end, sample_start, sample_end, start_offset;
+	unsigned int value;
+	unsigned int shift = 0;
+
+	instr = snd_seq_instr_find(trident->synth.ilist, &voice->instr, 0, 1);
+	if (instr == NULL)
+		return;
+	voice->instr = instr->instr;	/* copy ID to speedup aliases */
+	simple = KINSTR_DATA(instr);
+
+	spin_lock_irqsave(&trident->reg_lock, flags);
+
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018)
+		voice->GVSel = 1;	/* route to Wave volume */
+
+	voice->CTRL = 0;
+	voice->Alpha = 0;
+	voice->FMS = 0;
+
+	loop_start = simple->loop_start >> 4;
+	loop_end = simple->loop_end >> 4;
+	sample_start = (simple->start + position) >> 4;
+	if( sample_start >= simple->size )
+		sample_start = simple->start >> 4;
+	sample_end = simple->size;
+	start_offset = position >> 4;
+
+	if (simple->format & SIMPLE_WAVE_16BIT) {
+		voice->CTRL |= 8;
+		shift++;
+	}
+	if (simple->format & SIMPLE_WAVE_STEREO) {
+		voice->CTRL |= 4;
+		shift++;
+	}
+	if (!(simple->format & SIMPLE_WAVE_UNSIGNED))
+		voice->CTRL |= 2;
+
+	voice->LBA = simple->address.memory;
+
+	if (simple->format & SIMPLE_WAVE_LOOP) {
+		voice->CTRL |= 1;
+		voice->LBA += loop_start << shift;
+		if( start_offset >= loop_start ) {
+			voice->CSO = start_offset - loop_start;
+			voice->negCSO = 0;
+		} else {
+			voice->CSO = loop_start - start_offset;
+			voice->negCSO = 1;
+		}
+		voice->ESO = loop_end - loop_start - 1;
+	} else {
+		voice->LBA += start_offset << shift;
+		voice->CSO = sample_start;
+		voice->ESO = sample_end - 1;
+		voice->negCSO = 0;
+	}
+
+	if (voice->flags & SNDRV_TRIDENT_VFLG_RUNNING) {
+		snd_trident_stop_voice(trident, voice->number);
+		voice->flags &= ~SNDRV_TRIDENT_VFLG_RUNNING;
+	}
+
+	/* set CSO sign */
+	value = inl(TRID_REG(trident, T4D_SIGN_CSO_A));
+	if( voice->negCSO ) {
+		value |= 1 << (voice->number&31);
+	} else {
+		value &= ~(1 << (voice->number&31));
+	}
+	outl(value,TRID_REG(trident, T4D_SIGN_CSO_A));
+
+	voice->Attribute = 0;	
+	snd_trident_write_voice_regs(trident, voice);
+	snd_trident_start_voice(trident, voice->number);
+	voice->flags |= SNDRV_TRIDENT_VFLG_RUNNING;
+	spin_unlock_irqrestore(&trident->reg_lock, flags);
+	snd_seq_instr_free_use(trident->synth.ilist, instr);
+}
+
+static void sample_stop(trident_t * trident, snd_trident_voice_t * voice, snd_seq_stop_mode_t mode)
+{
+	unsigned long flags;
+
+	if (!(voice->flags & SNDRV_TRIDENT_VFLG_RUNNING))
+		return;
+
+	switch (mode) {
+	default:
+		spin_lock_irqsave(&trident->reg_lock, flags);
+		snd_trident_stop_voice(trident, voice->number);
+		voice->flags &= ~SNDRV_TRIDENT_VFLG_RUNNING;
+		spin_unlock_irqrestore(&trident->reg_lock, flags);
+		break;
+	case SAMPLE_STOP_LOOP:	/* disable loop only */
+		voice->CTRL &= ~1;
+		spin_lock_irqsave(&trident->reg_lock, flags);
+		outb((unsigned char) voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+		outw((((voice->CTRL << 12) | (voice->EC & 0x0fff)) & 0xffff), CH_GVSEL_PAN_VOL_CTRL_EC);
+		spin_unlock_irqrestore(&trident->reg_lock, flags);
+		break;
+	}
+}
+
+static void sample_freq(trident_t * trident, snd_trident_voice_t * voice, snd_seq_frequency_t freq)
+{
+	unsigned long flags;
+	freq >>= 4;
+
+	spin_lock_irqsave(&trident->reg_lock, flags);
+	if (freq == 44100)
+		voice->Delta = 0xeb3;
+	else if (freq == 8000)
+		voice->Delta = 0x2ab;
+	else if (freq == 48000)
+		voice->Delta = 0x1000;
+	else
+		voice->Delta = (((freq << 12) + freq) / 48000) & 0x0000ffff;
+
+	outb((unsigned char) voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+	if (trident->device == TRIDENT_DEVICE_ID_NX) {
+		outb((unsigned char) voice->Delta, TRID_REG(trident, CH_NX_DELTA_CSO + 3));
+		outb((unsigned char) (voice->Delta >> 8), TRID_REG(trident, CH_NX_DELTA_ESO + 3));
+	} else {
+		outw((unsigned short) voice->Delta, TRID_REG(trident, CH_DX_ESO_DELTA));
+	}
+
+	spin_unlock_irqrestore(&trident->reg_lock, flags);
+}
+
+static void sample_volume(trident_t * trident, snd_trident_voice_t * voice, snd_seq_ev_volume_t * volume)
+{
+	unsigned long flags;
+	unsigned short value;
+
+	spin_lock_irqsave(&trident->reg_lock, flags);
+	voice->GVSel = 0;	/* use global music volume */
+	voice->FMC = 0x03;	/* fixme: can we do something useful with FMC? */
+	if (volume->volume >= 0) {
+		volume->volume &= 0x3fff;
+		/* linear volume -> logarithmic attenuation conversion
+		 * uses EC register for greater resolution (6.6 bits) than Vol register (5.3 bits)
+		 * Vol register used when additional attenuation is required */
+		voice->RVol = 0;
+		voice->CVol = 0;
+		value = log_from_linear( volume->volume );
+		voice->Vol = 0;
+		voice->EC = (value & 0x3fff) >> 2;
+		if (value > 0x3fff) {
+			voice->EC |= 0xfc0;
+			if (value < 0x5f00 )
+				voice->Vol = ((value >> 8) - 0x3f) << 5;
+			else {
+				voice->Vol = 0x3ff;
+				voice->EC = 0xfff;
+			}
+		}
+	}
+	if (volume->lr >= 0) {
+		volume->lr &= 0x3fff;
+		/* approximate linear pan by attenuating channels */
+		if (volume->lr >= 0x2000) {	/* attenuate left (pan right) */
+			value = 0x3fff - volume->lr;
+			for (voice->Pan = 0; voice->Pan < 63; voice->Pan++ ) 
+				if (value >= pan_table[voice->Pan] )
+					break;
+		} else {			/* attenuate right (pan left) */
+			for (voice->Pan = 0; voice->Pan < 63; voice->Pan++ ) 
+				if ((unsigned int)volume->lr >= pan_table[voice->Pan] )
+					break;
+			voice->Pan |= 0x40;
+		}
+	}
+	outb((unsigned char) voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+	outl((voice->GVSel << 31) | ((voice->Pan & 0x0000007f) << 24) |
+		 ((voice->Vol & 0x000000ff) << 16) | ((voice->CTRL & 0x0000000f) << 12) |
+		 (voice->EC & 0x00000fff), TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC));
+	value = ((voice->FMC & 0x03) << 14) | ((voice->RVol & 0x7f) << 7) | (voice->CVol & 0x7f);
+	outw(value, TRID_REG(trident, CH_DX_FMC_RVOL_CVOL));
+	spin_unlock_irqrestore(&trident->reg_lock, flags);
+}
+
+static void sample_loop(trident_t * trident, snd_trident_voice_t * voice, snd_seq_ev_loop_t * loop)
+{
+	unsigned long flags;
+	simple_instrument_t *simple;
+	snd_seq_kinstr_t *instr;
+	unsigned int loop_start, loop_end;
+
+	instr = snd_seq_instr_find(trident->synth.ilist, &voice->instr, 0, 1);
+	if (instr == NULL)
+		return;
+	voice->instr = instr->instr;	/* copy ID to speedup aliases */
+	simple = KINSTR_DATA(instr);
+
+	loop_start = loop->start >> 4;
+	loop_end = loop->end >> 4;
+
+	spin_lock_irqsave(&trident->reg_lock, flags);
+
+	voice->LBA = simple->address.memory + loop_start;
+	voice->CSO = 0;
+	voice->ESO = loop_end - loop_start - 1;
+
+	outb((unsigned char) voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+	outb((voice->LBA >> 16), TRID_REG(trident, CH_LBA + 2));
+	outw((voice->LBA & 0xffff), TRID_REG(trident, CH_LBA));
+	if (trident->device == TRIDENT_DEVICE_ID_NX) {
+		outb((voice->ESO >> 16), TRID_REG(trident, CH_NX_DELTA_ESO + 2));
+		outw((voice->ESO & 0xffff), TRID_REG(trident, CH_NX_DELTA_ESO));
+		outb((voice->CSO >> 16), TRID_REG(trident, CH_NX_DELTA_CSO + 2));
+		outw((voice->CSO & 0xffff), TRID_REG(trident, CH_NX_DELTA_CSO));
+	} else {
+		outw((voice->ESO & 0xffff), TRID_REG(trident, CH_DX_ESO_DELTA + 2));
+		outw((voice->CSO & 0xffff), TRID_REG(trident, CH_DX_CSO_ALPHA_FMS + 2));
+	}
+
+	spin_unlock_irqrestore(&trident->reg_lock, flags);
+	snd_seq_instr_free_use(trident->synth.ilist, instr);
+}
+
+static void sample_pos(trident_t * trident, snd_trident_voice_t * voice, snd_seq_position_t position)
+{
+	unsigned long flags;
+	simple_instrument_t *simple;
+	snd_seq_kinstr_t *instr;
+	unsigned int value;
+
+	instr = snd_seq_instr_find(trident->synth.ilist, &voice->instr, 0, 1);
+	if (instr == NULL)
+		return;
+	voice->instr = instr->instr;	/* copy ID to speedup aliases */
+	simple = KINSTR_DATA(instr);
+
+	spin_lock_irqsave(&trident->reg_lock, flags);
+
+	if (simple->format & SIMPLE_WAVE_LOOP) {
+		if( position >= simple->loop_start ) {
+			voice->CSO = (position - simple->loop_start) >> 4;
+			voice->negCSO = 0;
+		} else {
+			voice->CSO = (simple->loop_start - position) >> 4;
+			voice->negCSO = 1;
+		}
+	} else {
+		voice->CSO = position >> 4;
+		voice->negCSO = 0;
+	}
+
+	/* set CSO sign */
+	value = inl(TRID_REG(trident, T4D_SIGN_CSO_A));
+	if( voice->negCSO ) {
+		value |= 1 << (voice->number&31);
+	} else {
+		value &= ~(1 << (voice->number&31));
+	}
+	outl(value,TRID_REG(trident, T4D_SIGN_CSO_A));
+	
+
+	outb((unsigned char) voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+	if (trident->device == TRIDENT_DEVICE_ID_NX) {
+		outw((voice->CSO & 0xffff), TRID_REG(trident, CH_NX_DELTA_CSO));
+		outb((voice->CSO >> 16), TRID_REG(trident, CH_NX_DELTA_CSO + 2));
+	} else {
+		outw((voice->CSO & 0xffff), TRID_REG(trident, CH_DX_CSO_ALPHA_FMS) + 2);
+	}
+
+	spin_unlock_irqrestore(&trident->reg_lock, flags);
+	snd_seq_instr_free_use(trident->synth.ilist, instr);
+}
+
+static void sample_private1(trident_t * trident, snd_trident_voice_t * voice, unsigned char *data)
+{
+}
+
+/*
+ * Memory management / sample loading
+ */
+
+static int snd_trident_simple_put_sample(void *private_data, simple_instrument_t * instr,
+					 char __user *data, long len, int atomic)
+{
+	trident_t *trident = private_data;
+	int size = instr->size;
+	int shift = 0;
+
+	if (instr->format & SIMPLE_WAVE_BACKWARD ||
+	    instr->format & SIMPLE_WAVE_BIDIR ||
+	    instr->format & SIMPLE_WAVE_ULAW) 
+		return -EINVAL;	/* not supported */
+
+	if (instr->format & SIMPLE_WAVE_16BIT)
+		shift++;
+	if (instr->format & SIMPLE_WAVE_STEREO)
+		shift++;
+	size <<= shift;
+
+	if (trident->synth.current_size + size > trident->synth.max_size)
+		return -ENOMEM;
+
+	if (!access_ok(VERIFY_READ, data, size))
+		return -EFAULT;
+
+	if (trident->tlb.entries) {
+		snd_util_memblk_t *memblk;
+		memblk = snd_trident_synth_alloc(trident, size); 
+		if (memblk == NULL)
+			return -ENOMEM;
+		if (snd_trident_synth_copy_from_user(trident, memblk, 0, data, size) ) {
+			snd_trident_synth_free(trident, memblk);
+			return -EFAULT;
+		}
+		instr->address.ptr = (unsigned char*)memblk;
+		instr->address.memory = memblk->offset;
+	} else {
+		struct snd_dma_buffer dmab;
+		if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(trident->pci),
+					size, &dmab) < 0)
+			return -ENOMEM;
+
+		if (copy_from_user(dmab.area, data, size)) {
+			snd_dma_free_pages(&dmab);
+			return -EFAULT;
+		}
+		instr->address.ptr = dmab.area;
+		instr->address.memory = dmab.addr;
+	}
+
+	trident->synth.current_size += size;
+	return 0;
+}
+
+static int snd_trident_simple_get_sample(void *private_data, simple_instrument_t * instr,
+					 char __user *data, long len, int atomic)
+{
+	//trident_t *trident = private_data;
+	int size = instr->size;
+	int shift = 0;
+
+	if (instr->format & SIMPLE_WAVE_16BIT)
+		shift++;
+	if (instr->format & SIMPLE_WAVE_STEREO)
+		shift++;
+	size <<= shift;
+
+	if (!access_ok(VERIFY_WRITE, data, size))
+		return -EFAULT;
+
+	/* FIXME: not implemented yet */
+
+	return -EBUSY;
+}
+
+static int snd_trident_simple_remove_sample(void *private_data, simple_instrument_t * instr,
+					    int atomic)
+{
+	trident_t *trident = private_data;
+	int size = instr->size;
+
+	if (instr->format & SIMPLE_WAVE_16BIT)
+		size <<= 1;
+	if (instr->format & SIMPLE_WAVE_STEREO)
+		size <<= 1;
+
+	if (trident->tlb.entries) {
+		snd_util_memblk_t *memblk = (snd_util_memblk_t*)instr->address.ptr;
+		if (memblk)
+			snd_trident_synth_free(trident, memblk);
+		else
+			return -EFAULT;
+	} else {
+		struct snd_dma_buffer dmab;
+		dmab.dev.type = SNDRV_DMA_TYPE_DEV;
+		dmab.dev.dev = snd_dma_pci_data(trident->pci);
+		dmab.area = instr->address.ptr;
+		dmab.addr = instr->address.memory;
+		dmab.bytes = size;
+		snd_dma_free_pages(&dmab);
+	}
+
+	trident->synth.current_size -= size;
+	if (trident->synth.current_size < 0)	/* shouldn't need this check... */
+		trident->synth.current_size = 0;
+
+	return 0;
+}
+
+static void select_instrument(trident_t * trident, snd_trident_voice_t * v)
+{
+	snd_seq_kinstr_t *instr;
+	instr = snd_seq_instr_find(trident->synth.ilist, &v->instr, 0, 1);
+	if (instr != NULL) {
+		if (instr->ops) {
+			if (!strcmp(instr->ops->instr_type, SNDRV_SEQ_INSTR_ID_SIMPLE))
+				snd_trident_simple_init(v);
+		}
+		snd_seq_instr_free_use(trident->synth.ilist, instr);
+	}
+}
+
+/*
+
+ */
+
+static void event_sample(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v)
+{
+	if (v->sample_ops && v->sample_ops->sample_stop)
+		v->sample_ops->sample_stop(p->trident, v, SAMPLE_STOP_IMMEDIATELY);
+	v->instr.std = ev->data.sample.param.sample.std;
+	if (v->instr.std & 0xff000000) {	/* private instrument */
+		v->instr.std &= 0x00ffffff;
+		v->instr.std |= (unsigned int)ev->source.client << 24;
+	}
+	v->instr.bank = ev->data.sample.param.sample.bank;
+	v->instr.prg = ev->data.sample.param.sample.prg;
+	select_instrument(p->trident, v);
+}
+
+static void event_cluster(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v)
+{
+	if (v->sample_ops && v->sample_ops->sample_stop)
+		v->sample_ops->sample_stop(p->trident, v, SAMPLE_STOP_IMMEDIATELY);
+	v->instr.cluster = ev->data.sample.param.cluster.cluster;
+	select_instrument(p->trident, v);
+}
+
+static void event_start(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v)
+{
+	if (v->sample_ops && v->sample_ops->sample_start)
+		v->sample_ops->sample_start(p->trident, v, ev->data.sample.param.position);
+}
+
+static void event_stop(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v)
+{
+	if (v->sample_ops && v->sample_ops->sample_stop)
+		v->sample_ops->sample_stop(p->trident, v, ev->data.sample.param.stop_mode);
+}
+
+static void event_freq(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v)
+{
+	if (v->sample_ops && v->sample_ops->sample_freq)
+		v->sample_ops->sample_freq(p->trident, v, ev->data.sample.param.frequency);
+}
+
+static void event_volume(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v)
+{
+	if (v->sample_ops && v->sample_ops->sample_volume)
+		v->sample_ops->sample_volume(p->trident, v, &ev->data.sample.param.volume);
+}
+
+static void event_loop(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v)
+{
+	if (v->sample_ops && v->sample_ops->sample_loop)
+		v->sample_ops->sample_loop(p->trident, v, &ev->data.sample.param.loop);
+}
+
+static void event_position(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v)
+{
+	if (v->sample_ops && v->sample_ops->sample_pos)
+		v->sample_ops->sample_pos(p->trident, v, ev->data.sample.param.position);
+}
+
+static void event_private1(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v)
+{
+	if (v->sample_ops && v->sample_ops->sample_private1)
+		v->sample_ops->sample_private1(p->trident, v, (unsigned char *) &ev->data.sample.param.raw8);
+}
+
+typedef void (trident_sample_event_handler_t) (snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v);
+
+static trident_sample_event_handler_t *trident_sample_event_handlers[9] =
+{
+	event_sample,
+	event_cluster,
+	event_start,
+	event_stop,
+	event_freq,
+	event_volume,
+	event_loop,
+	event_position,
+	event_private1
+};
+
+static void snd_trident_sample_event(snd_seq_event_t * ev, snd_trident_port_t * p)
+{
+	int idx, voice;
+	trident_t *trident = p->trident;
+	snd_trident_voice_t *v;
+	unsigned long flags;
+
+	idx = ev->type - SNDRV_SEQ_EVENT_SAMPLE;
+	if (idx < 0 || idx > 8)
+		return;
+	for (voice = 0; voice < 64; voice++) {
+		v = &trident->synth.voices[voice];
+		if (v->use && v->client == ev->source.client &&
+		    v->port == ev->source.port &&
+		    v->index == ev->data.sample.channel) {
+			spin_lock_irqsave(&trident->event_lock, flags);
+			trident_sample_event_handlers[idx] (ev, p, v);
+			spin_unlock_irqrestore(&trident->event_lock, flags);
+			return;
+		}
+	}
+}
+
+/*
+
+ */
+
+static void snd_trident_synth_free_voices(trident_t * trident, int client, int port)
+{
+	int idx;
+	snd_trident_voice_t *voice;
+
+	for (idx = 0; idx < 32; idx++) {
+		voice = &trident->synth.voices[idx];
+		if (voice->use && voice->client == client && voice->port == port)
+			snd_trident_free_voice(trident, voice);
+	}
+}
+
+static int snd_trident_synth_use(void *private_data, snd_seq_port_subscribe_t * info)
+{
+	snd_trident_port_t *port = (snd_trident_port_t *) private_data;
+	trident_t *trident = port->trident;
+	snd_trident_voice_t *voice;
+	unsigned int idx;
+	unsigned long flags;
+
+	if (info->voices > 32)
+		return -EINVAL;
+	spin_lock_irqsave(&trident->reg_lock, flags);
+	for (idx = 0; idx < info->voices; idx++) {
+		voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_SYNTH, info->sender.client, info->sender.port);
+		if (voice == NULL) {
+			snd_trident_synth_free_voices(trident, info->sender.client, info->sender.port);
+			spin_unlock_irqrestore(&trident->reg_lock, flags);
+			return -EBUSY;
+		}
+		voice->index = idx;
+		voice->Vol = 0x3ff;
+		voice->EC = 0x0fff;
+	}
+#if 0
+	for (idx = 0; idx < info->midi_voices; idx++) {
+		port->midi_has_voices = 1;
+		voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_MIDI, info->sender.client, info->sender.port);
+		if (voice == NULL) {
+			snd_trident_synth_free_voices(trident, info->sender.client, info->sender.port);
+			spin_unlock_irqrestore(&trident->reg_lock, flags);
+			return -EBUSY;
+		}
+		voice->Vol = 0x3ff;
+		voice->EC = 0x0fff;
+	}
+#endif
+	spin_unlock_irqrestore(&trident->reg_lock, flags);
+	return 0;
+}
+
+static int snd_trident_synth_unuse(void *private_data, snd_seq_port_subscribe_t * info)
+{
+	snd_trident_port_t *port = (snd_trident_port_t *) private_data;
+	trident_t *trident = port->trident;
+	unsigned long flags;
+
+	spin_lock_irqsave(&trident->reg_lock, flags);
+	snd_trident_synth_free_voices(trident, info->sender.client, info->sender.port);
+	spin_unlock_irqrestore(&trident->reg_lock, flags);
+	return 0;
+}
+
+/*
+
+ */
+
+static void snd_trident_synth_free_private_instruments(snd_trident_port_t * p, int client)
+{
+	snd_seq_instr_header_t ifree;
+
+	memset(&ifree, 0, sizeof(ifree));
+	ifree.cmd = SNDRV_SEQ_INSTR_FREE_CMD_PRIVATE;
+	snd_seq_instr_list_free_cond(p->trident->synth.ilist, &ifree, client, 0);
+}
+
+static int snd_trident_synth_event_input(snd_seq_event_t * ev, int direct, void *private_data, int atomic, int hop)
+{
+	snd_trident_port_t *p = (snd_trident_port_t *) private_data;
+
+	if (p == NULL)
+		return -EINVAL;
+	if (ev->type >= SNDRV_SEQ_EVENT_SAMPLE &&
+	    ev->type <= SNDRV_SEQ_EVENT_SAMPLE_PRIVATE1) {
+		snd_trident_sample_event(ev, p);
+		return 0;
+	}
+	if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM &&
+	    ev->source.port == SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE) {
+		if (ev->type == SNDRV_SEQ_EVENT_CLIENT_EXIT) {
+			snd_trident_synth_free_private_instruments(p, ev->data.addr.client);
+			return 0;
+		}
+	}
+	if (direct) {
+		if (ev->type >= SNDRV_SEQ_EVENT_INSTR_BEGIN) {
+			snd_seq_instr_event(&p->trident->synth.simple_ops.kops,
+					    p->trident->synth.ilist, ev,
+					    p->trident->synth.seq_client, atomic, hop);
+			return 0;
+		}
+	}
+	return 0;
+}
+
+static void snd_trident_synth_instr_notify(void *private_data,
+					   snd_seq_kinstr_t * instr,
+					   int what)
+{
+	int idx;
+	trident_t *trident = private_data;
+	snd_trident_voice_t *pvoice;
+	unsigned long flags;
+
+	spin_lock_irqsave(&trident->event_lock, flags);
+	for (idx = 0; idx < 64; idx++) {
+		pvoice = &trident->synth.voices[idx];
+		if (pvoice->use && !memcmp(&pvoice->instr, &instr->instr, sizeof(pvoice->instr))) {
+			if (pvoice->sample_ops && pvoice->sample_ops->sample_stop) {
+				pvoice->sample_ops->sample_stop(trident, pvoice, SAMPLE_STOP_IMMEDIATELY);
+			} else {
+				snd_trident_stop_voice(trident, pvoice->number);
+				pvoice->flags &= ~SNDRV_TRIDENT_VFLG_RUNNING;
+			}
+		}
+	}
+	spin_unlock_irqrestore(&trident->event_lock, flags);
+}
+
+/*
+
+ */
+
+static void snd_trident_synth_free_port(void *private_data)
+{
+	snd_trident_port_t *p = (snd_trident_port_t *) private_data;
+
+	if (p)
+		snd_midi_channel_free_set(p->chset);
+}
+
+static int snd_trident_synth_create_port(trident_t * trident, int idx)
+{
+	snd_trident_port_t *p;
+	snd_seq_port_callback_t callbacks;
+	char name[32];
+	char *str;
+	int result;
+
+	p = &trident->synth.seq_ports[idx];
+	p->chset = snd_midi_channel_alloc_set(16);
+	if (p->chset == NULL)
+		return -ENOMEM;
+	p->chset->private_data = p;
+	p->trident = trident;
+	p->client = trident->synth.seq_client;
+
+	memset(&callbacks, 0, sizeof(callbacks));
+	callbacks.owner = THIS_MODULE;
+	callbacks.use = snd_trident_synth_use;
+	callbacks.unuse = snd_trident_synth_unuse;
+	callbacks.event_input = snd_trident_synth_event_input;
+	callbacks.private_free = snd_trident_synth_free_port;
+	callbacks.private_data = p;
+
+	str = "???";
+	switch (trident->device) {
+	case TRIDENT_DEVICE_ID_DX:	str = "Trident 4DWave-DX"; break;
+	case TRIDENT_DEVICE_ID_NX:	str = "Trident 4DWave-NX"; break;
+	case TRIDENT_DEVICE_ID_SI7018:	str = "SiS 7018"; break;
+	}
+	sprintf(name, "%s port %i", str, idx);
+	p->chset->port = snd_seq_event_port_attach(trident->synth.seq_client,
+						   &callbacks,
+						   SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE,
+						   SNDRV_SEQ_PORT_TYPE_DIRECT_SAMPLE |
+						   SNDRV_SEQ_PORT_TYPE_SYNTH,
+						   16, 0,
+						   name);
+	if (p->chset->port < 0) {
+		result = p->chset->port;
+		snd_trident_synth_free_port(p);
+		return result;
+	}
+	p->port = p->chset->port;
+	return 0;
+}
+
+/*
+
+ */
+
+static int snd_trident_synth_new_device(snd_seq_device_t *dev)
+{
+	trident_t *trident;
+	int client, i;
+	snd_seq_client_callback_t callbacks;
+	snd_seq_client_info_t cinfo;
+	snd_seq_port_subscribe_t sub;
+	snd_simple_ops_t *simpleops;
+	char *str;
+
+	trident = *(trident_t **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	if (trident == NULL)
+		return -EINVAL;
+
+	trident->synth.seq_client = -1;
+
+	/* allocate new client */
+	memset(&callbacks, 0, sizeof(callbacks));
+	callbacks.private_data = trident;
+	callbacks.allow_output = callbacks.allow_input = 1;
+	client = trident->synth.seq_client =
+	    snd_seq_create_kernel_client(trident->card, 1, &callbacks);
+	if (client < 0)
+		return client;
+
+	/* change name of client */
+	memset(&cinfo, 0, sizeof(cinfo));
+	cinfo.client = client;
+	cinfo.type = KERNEL_CLIENT;
+	str = "???";
+	switch (trident->device) {
+	case TRIDENT_DEVICE_ID_DX:	str = "Trident 4DWave-DX"; break;
+	case TRIDENT_DEVICE_ID_NX:	str = "Trident 4DWave-NX"; break;
+	case TRIDENT_DEVICE_ID_SI7018:	str = "SiS 7018"; break;
+	}
+	sprintf(cinfo.name, str);
+	snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &cinfo);
+
+	for (i = 0; i < 4; i++)
+		snd_trident_synth_create_port(trident, i);
+
+	trident->synth.ilist = snd_seq_instr_list_new();
+	if (trident->synth.ilist == NULL) {
+		snd_seq_delete_kernel_client(client);
+		trident->synth.seq_client = -1;
+		return -ENOMEM;
+	}
+	trident->synth.ilist->flags = SNDRV_SEQ_INSTR_FLG_DIRECT;
+
+	simpleops = &trident->synth.simple_ops;
+	snd_seq_simple_init(simpleops, trident, NULL);
+	simpleops->put_sample = snd_trident_simple_put_sample;
+	simpleops->get_sample = snd_trident_simple_get_sample;
+	simpleops->remove_sample = snd_trident_simple_remove_sample;
+	simpleops->notify = snd_trident_synth_instr_notify;
+
+	memset(&sub, 0, sizeof(sub));
+	sub.sender.client = SNDRV_SEQ_CLIENT_SYSTEM;
+	sub.sender.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE;
+	sub.dest.client = client;
+	sub.dest.port = 0;
+	snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &sub);
+
+	return 0;
+}
+
+static int snd_trident_synth_delete_device(snd_seq_device_t *dev)
+{
+	trident_t *trident;
+
+	trident = *(trident_t **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	if (trident == NULL)
+		return -EINVAL;
+
+	if (trident->synth.seq_client >= 0) {
+		snd_seq_delete_kernel_client(trident->synth.seq_client);
+		trident->synth.seq_client = -1;
+	}
+	if (trident->synth.ilist)
+		snd_seq_instr_list_free(&trident->synth.ilist);
+	return 0;
+}
+
+static int __init alsa_trident_synth_init(void)
+{
+	static snd_seq_dev_ops_t ops =
+	{
+		snd_trident_synth_new_device,
+		snd_trident_synth_delete_device
+	};
+
+	return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_TRIDENT, &ops,
+					      sizeof(trident_t*));
+}
+
+static void __exit alsa_trident_synth_exit(void)
+{
+	snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_TRIDENT);
+}
+
+module_init(alsa_trident_synth_init)
+module_exit(alsa_trident_synth_exit)