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/isa/Kconfig b/sound/isa/Kconfig
new file mode 100644
index 0000000..4a6be96
--- /dev/null
+++ b/sound/isa/Kconfig
@@ -0,0 +1,377 @@
+# ALSA ISA drivers
+
+menu "ISA devices"
+	depends on SND!=n && ISA
+
+config SND_AD1848_LIB
+        tristate
+        select SND_PCM
+	select SND_GENERIC_PM
+
+config SND_CS4231_LIB
+        tristate
+        select SND_PCM
+	select SND_GENERIC_PM
+
+config SND_AD1816A
+	tristate "Analog Devices SoundPort AD1816A"
+	depends on SND && ISAPNP
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  Say Y here to include support for Analog Devices SoundPort
+	  AD1816A or compatible sound chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-ad1816a.
+
+config SND_AD1848
+	tristate "Generic AD1848/CS4248 driver"
+	depends on SND
+	select SND_AD1848_LIB
+	help
+	  Say Y here to include support for AD1848 (Analog Devices) or
+	  CS4248 (Cirrus Logic - Crystal Semiconductors) chips.
+	  
+	  For newer chips from Cirrus Logic, use the CS4231, CS4232 or
+	  CS4236+ drivers.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-ad1848.
+
+config SND_CS4231
+	tristate "Generic Cirrus Logic CS4231 driver"
+	depends on SND
+	select SND_MPU401_UART
+	select SND_CS4231_LIB
+	help
+	  Say Y here to include support for CS4231 chips from Cirrus
+	  Logic - Crystal Semiconductors.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-cs4231.
+
+config SND_CS4232
+	tristate "Generic Cirrus Logic CS4232 driver"
+	depends on SND
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_CS4231_LIB
+	help
+	  Say Y here to include support for CS4232 chips from Cirrus
+	  Logic - Crystal Semiconductors.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-cs4232.
+
+config SND_CS4236
+	tristate "Generic Cirrus Logic CS4236+ driver"
+	depends on SND
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_CS4231_LIB
+	help
+	  Say Y to include support for CS4235,CS4236,CS4237B,CS4238B,
+	  CS4239 chips from Cirrus Logic - Crystal Semiconductors.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-cs4236.
+
+config SND_ES968
+	tristate "Generic ESS ES968 driver"
+	depends on SND && ISAPNP
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  Say Y here to include support for ESS AudioDrive ES968 chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-es968.
+
+config SND_ES1688
+	tristate "Generic ESS ES688/ES1688 driver"
+	depends on SND
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  Say Y here to include support for ESS AudioDrive ES688 or
+	  ES1688 chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-es1688.
+
+config SND_ES18XX
+	tristate "Generic ESS ES18xx driver"
+	depends on SND
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	select SND_GENERIC_PM
+	help
+	  Say Y here to include support for ESS AudioDrive ES18xx chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-es18xx.
+
+config SND_GUS_SYNTH
+	tristate
+
+config SND_GUSCLASSIC
+	tristate "Gravis UltraSound Classic"
+	depends on SND
+	select SND_RAWMIDI
+	select SND_PCM
+	select SND_GUS_SYNTH
+	help
+	  Say Y here to include support for Gravis UltraSound Classic
+	  soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-gusclassic.
+
+config SND_GUSEXTREME
+	tristate "Gravis UltraSound Extreme"
+	depends on SND
+	select SND_HWDEP
+	select SND_MPU401_UART
+	select SND_PCM
+	select SND_GUS_SYNTH
+	help
+	  Say Y here to include support for Gravis UltraSound Extreme
+	  soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-gusextreme.
+
+config SND_GUSMAX
+	tristate "Gravis UltraSound MAX"
+	depends on SND
+	select SND_RAWMIDI
+	select SND_CS4231_LIB
+	select SND_GUS_SYNTH
+	help
+	  Say Y here to include support for Gravis UltraSound MAX
+	  soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-gusmax.
+
+config SND_INTERWAVE
+	tristate "AMD InterWave, Gravis UltraSound PnP"
+	depends on SND
+	select SND_RAWMIDI
+	select SND_CS4231_LIB
+	select SND_GUS_SYNTH
+	help
+	  Say Y here to include support for AMD InterWave based
+	  soundcards (Gravis UltraSound Plug & Play, STB SoundRage32,
+	  MED3210, Dynasonic Pro, Panasonic PCA761AW).
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-interwave.
+
+config SND_INTERWAVE_STB
+	tristate "AMD InterWave + TEA6330T (UltraSound 32-Pro)"
+	depends on SND
+	select SND_RAWMIDI
+	select SND_CS4231_LIB
+	select SND_GUS_SYNTH
+	help
+	  Say Y here to include support for AMD InterWave based
+	  soundcards with a TEA6330T bass and treble regulator
+	  (UltraSound 32-Pro).
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-interwave-stb.
+
+config SND_OPTI92X_AD1848
+	tristate "OPTi 82C92x - AD1848"
+	depends on SND
+	select SND_OPL3_LIB
+	select SND_OPL4_LIB
+	select SND_MPU401_UART
+	select SND_AD1848_LIB
+	help
+	  Say Y here to include support for soundcards based on Opti
+	  82C92x or OTI-601 chips and using an AD1848 codec.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-opti92x-ad1848.
+
+config SND_OPTI92X_CS4231
+	tristate "OPTi 82C92x - CS4231"
+	depends on SND
+	select SND_OPL3_LIB
+	select SND_OPL4_LIB
+	select SND_MPU401_UART
+	select SND_CS4231_LIB
+	help
+	  Say Y here to include support for soundcards based on Opti
+	  82C92x chips and using a CS4231 codec.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-opti92x-cs4231.
+
+config SND_OPTI93X
+	tristate "OPTi 82C93x"
+	depends on SND
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  Say Y here to include support for soundcards based on Opti
+	  82C93x chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-opti93x.
+
+config SND_SB8
+	tristate "Sound Blaster 1.0/2.0/Pro (8-bit)"
+	depends on SND
+	select SND_OPL3_LIB
+	select SND_RAWMIDI
+	select SND_PCM
+	help
+	  Say Y here to include support for Creative Sound Blaster 1.0/
+	  2.0/Pro (8-bit) or 100% compatible soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-sb8.
+
+config SND_SB16
+	tristate "Sound Blaster 16 (PnP)"
+	depends on SND
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  Say Y here to include support for Sound Blaster 16 soundcards
+	  (including the Plug and Play version).
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-sb16.
+
+config SND_SBAWE
+	tristate "Sound Blaster AWE (32,64) (PnP)"
+	depends on SND
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  Say Y here to include support for Sound Blaster AWE soundcards
+	  (including the Plug and Play version).
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-sbawe.
+
+config SND_SB16_CSP
+	bool "Sound Blaster 16/AWE CSP support"
+	depends on (SND_SB16 || SND_SBAWE) && (BROKEN || !PPC)
+	help
+	  Say Y here to include support for the CSP core.  This special
+	  coprocessor can do variable tasks like various compression and
+	  decompression algorithms.
+
+config SND_WAVEFRONT
+	tristate "Turtle Beach Maui,Tropez,Tropez+ (Wavefront)"
+	depends on SND
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_CS4231_LIB
+	help
+	  Say Y here to include support for Turtle Beach Maui, Tropez
+	  and Tropez+ soundcards based on the Wavefront chip.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-wavefront.
+
+config SND_ALS100
+	tristate "Avance Logic ALS100/ALS120"
+	depends on SND && ISAPNP
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  Say Y here to include support for soundcards based on Avance
+	  Logic ALS100, ALS110, ALS120 and ALS200 chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-als100.
+
+config SND_AZT2320
+	tristate "Aztech Systems AZT2320"
+	depends on SND && ISAPNP
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_CS4231_LIB
+	help
+	  Say Y here to include support for soundcards based on the
+	  Aztech Systems AZT2320 chip.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-azt2320.
+
+config SND_CMI8330
+	tristate "C-Media CMI8330"
+	depends on SND
+	select SND_AD1848_LIB
+	help
+	  Say Y here to include support for soundcards based on the
+	  C-Media CMI8330 chip.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-cmi8330.
+
+config SND_DT019X
+	tristate "Diamond Technologies DT-019X, Avance Logic ALS-007"
+	depends on SND && ISAPNP
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  Say Y here to include support for soundcards based on the
+	  Diamond Technologies DT-019X or Avance Logic ALS-007 chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-dt019x.
+
+config SND_OPL3SA2
+	tristate "Yamaha OPL3-SA2/SA3"
+	depends on SND
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_CS4231_LIB
+	help
+	  Say Y here to include support for Yamaha OPL3-SA2 and OPL3-SA3
+	  chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-opl3sa2.
+
+config SND_SGALAXY
+	tristate "Aztech Sound Galaxy"
+	depends on SND
+	select SND_AD1848_LIB
+	help
+	  Say Y here to include support for Aztech Sound Galaxy
+	  soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-sgalaxy.
+
+config SND_SSCAPE
+	tristate "Ensoniq SoundScape PnP driver"
+	depends on SND
+	select SND_HWDEP
+	select SND_MPU401_UART
+	select SND_CS4231_LIB
+	help
+	  Say Y here to include support for Ensoniq SoundScape PnP
+	  soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-sscape.
+
+endmenu
diff --git a/sound/isa/Makefile b/sound/isa/Makefile
new file mode 100644
index 0000000..05724eb
--- /dev/null
+++ b/sound/isa/Makefile
@@ -0,0 +1,26 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-als100-objs := als100.o
+snd-azt2320-objs := azt2320.o
+snd-cmi8330-objs := cmi8330.o
+snd-dt019x-objs := dt019x.o
+snd-es18xx-objs := es18xx.o
+snd-opl3sa2-objs := opl3sa2.o
+snd-sgalaxy-objs := sgalaxy.o
+snd-sscape-objs := sscape.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_ALS100) += snd-als100.o
+obj-$(CONFIG_SND_AZT2320) += snd-azt2320.o
+obj-$(CONFIG_SND_CMI8330) += snd-cmi8330.o
+obj-$(CONFIG_SND_DT019X) += snd-dt019x.o
+obj-$(CONFIG_SND_ES18XX) += snd-es18xx.o
+obj-$(CONFIG_SND_OPL3SA2) += snd-opl3sa2.o
+obj-$(CONFIG_SND_SGALAXY) += snd-sgalaxy.o
+obj-$(CONFIG_SND_SSCAPE) += snd-sscape.o
+
+obj-$(CONFIG_SND) += ad1816a/ ad1848/ cs423x/ es1688/ gus/ opti9xx/ \
+		     sb/ wavefront/
diff --git a/sound/isa/ad1816a/Makefile b/sound/isa/ad1816a/Makefile
new file mode 100644
index 0000000..a42b29c
--- /dev/null
+++ b/sound/isa/ad1816a/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-ad1816a-lib-objs := ad1816a_lib.o
+snd-ad1816a-objs := ad1816a.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_AD1816A) += snd-ad1816a.o snd-ad1816a-lib.o
diff --git a/sound/isa/ad1816a/ad1816a.c b/sound/isa/ad1816a/ad1816a.c
new file mode 100644
index 0000000..9fa7a78
--- /dev/null
+++ b/sound/isa/ad1816a/ad1816a.c
@@ -0,0 +1,312 @@
+
+/*
+    card-ad1816a.c - driver for ADI SoundPort AD1816A based soundcards.
+    Copyright (C) 2000 by Massimo Piccioni <dafastidio@libero.it>
+
+    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/time.h>
+#include <linux/wait.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/ad1816a.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+
+#define PFX "ad1816a: "
+
+MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>");
+MODULE_DESCRIPTION("AD1816A, AD1815");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Highscreen,Sound-Boostar 16 3D},"
+		"{Analog Devices,AD1815},"
+		"{Analog Devices,AD1816A},"
+		"{TerraTec,Base 64},"
+		"{TerraTec,AudioSystem EWS64S},"
+		"{Aztech/Newcom SC-16 3D},"
+		"{Shark Predator ISA}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 1-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* Pnp setup */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* Pnp setup */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* PnP setup */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* PnP setup */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for ad1816a based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for ad1816a based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable ad1816a based soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for ad1816a driver.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for ad1816a driver.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for ad1816a driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for ad1816a driver.");
+module_param_array(mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for ad1816a driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "1st DMA # for ad1816a driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "2nd DMA # for ad1816a driver.");
+
+struct snd_card_ad1816a {
+	struct pnp_dev *dev;
+	struct pnp_dev *devmpu;
+};
+
+static struct pnp_card_device_id snd_ad1816a_pnpids[] = {
+	/* Analog Devices AD1815 */
+	{ .id = "ADS7150", .devs = { { .id = "ADS7150" }, { .id = "ADS7151" } } },
+	/* Analog Devices AD1816A - added by Kenneth Platz <kxp@atl.hp.com> */
+	{ .id = "ADS7181", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+	/* Analog Devices AD1816A - Aztech/Newcom SC-16 3D */
+	{ .id = "AZT1022", .devs = { { .id = "AZT1018" }, { .id = "AZT2002" } } },
+	/* Highscreen Sound-Boostar 16 3D - added by Stefan Behnel */
+	{ .id = "LWC1061", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+	/* Highscreen Sound-Boostar 16 3D */
+	{ .id = "MDK1605", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+	/* Shark Predator ISA - added by Ken Arromdee */
+	{ .id = "SMM7180", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+	/* Analog Devices AD1816A - Terratec AudioSystem EWS64S */
+	{ .id = "TER1112", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+	/* Analog Devices AD1816A - Terratec Base 64 */
+	{ .id = "TER1411", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+	/* end */
+	{ .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_ad1816a_pnpids);
+
+
+#define	DRIVER_NAME	"snd-card-ad1816a"
+
+
+static int __devinit snd_card_ad1816a_pnp(int dev, struct snd_card_ad1816a *acard,
+					  struct pnp_card_link *card,
+					  const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	struct pnp_resource_table *cfg = kmalloc(sizeof(*cfg), GFP_KERNEL);
+	int err;
+
+	acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->dev == NULL) {
+		kfree(cfg);
+		return -EBUSY;
+	}
+	acard->devmpu = pnp_request_card_device(card, id->devs[1].id, NULL);
+	if (acard->devmpu == NULL) {
+		kfree(cfg);
+		return -EBUSY;
+	}
+
+	pdev = acard->dev;
+	pnp_init_resource_table(cfg);
+
+	if (port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[2], port[dev], 16);
+	if (fm_port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[1], fm_port[dev], 4);
+	if (dma1[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[0], dma1[dev], 1);
+	if (dma2[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[1], dma2[dev], 1);
+	if (irq[dev] != SNDRV_AUTO_IRQ)
+		pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1);
+
+	if (pnp_manual_config_dev(pdev, cfg, 0) < 0)
+		snd_printk(KERN_ERR PFX "AUDIO the requested resources are invalid, using auto config\n");
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		printk(KERN_ERR PFX "AUDIO PnP configure failure\n");
+		kfree(cfg);
+		return -EBUSY;
+	}
+
+	port[dev] = pnp_port_start(pdev, 2);
+	fm_port[dev] = pnp_port_start(pdev, 1);
+	dma1[dev] = pnp_dma(pdev, 0);
+	dma2[dev] = pnp_dma(pdev, 1);
+	irq[dev] = pnp_irq(pdev, 0);
+
+	pdev = acard->devmpu;
+	pnp_init_resource_table(cfg);
+
+	if (mpu_port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[0], mpu_port[dev], 2);
+	if (mpu_irq[dev] != SNDRV_AUTO_IRQ)
+		pnp_resource_change(&cfg->irq_resource[0], mpu_irq[dev], 1);
+
+	if (pnp_manual_config_dev(pdev, cfg, 0) < 0)
+		snd_printk(KERN_ERR PFX "AUDIO the requested resources are invalid, using auto config\n");
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		printk(KERN_ERR PFX "MPU401 PnP configure failure\n");
+		mpu_port[dev] = -1;
+		acard->devmpu = NULL;
+	} else {
+		mpu_port[dev] = pnp_port_start(pdev, 0);
+		mpu_irq[dev] = pnp_irq(pdev, 0);
+	}
+
+	kfree(cfg);
+	return 0;
+}
+
+static int __devinit snd_card_ad1816a_probe(int dev, struct pnp_card_link *pcard,
+					    const struct pnp_card_device_id *pid)
+{
+	int error;
+	snd_card_t *card;
+	struct snd_card_ad1816a *acard;
+	ad1816a_t *chip;
+	opl3_t *opl3;
+
+	if ((card = snd_card_new(index[dev], id[dev], THIS_MODULE,
+				 sizeof(struct snd_card_ad1816a))) == NULL)
+		return -ENOMEM;
+	acard = (struct snd_card_ad1816a *)card->private_data;
+
+	if ((error = snd_card_ad1816a_pnp(dev, acard, pcard, pid))) {
+		snd_card_free(card);
+		return error;
+	}
+	snd_card_set_dev(card, &pcard->card->dev);
+
+	if ((error = snd_ad1816a_create(card, port[dev],
+					irq[dev],
+					dma1[dev],
+					dma2[dev],
+					&chip)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	strcpy(card->driver, "AD1816A");
+	strcpy(card->shortname, "ADI SoundPort AD1816A");
+	sprintf(card->longname, "%s, SS at 0x%lx, irq %d, dma %d&%d",
+		card->shortname, chip->port, irq[dev], dma1[dev], dma2[dev]);
+
+	if ((error = snd_ad1816a_pcm(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if ((error = snd_ad1816a_mixer(chip)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if (mpu_port[dev] > 0) {
+		if (snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+					mpu_port[dev], 0, mpu_irq[dev], SA_INTERRUPT,
+					NULL) < 0)
+			printk(KERN_ERR PFX "no MPU-401 device at 0x%lx.\n", mpu_port[dev]);
+	}
+
+	if (fm_port[dev] > 0) {
+		if (snd_opl3_create(card,
+				    fm_port[dev], fm_port[dev] + 2,
+				    OPL3_HW_AUTO, 0, &opl3) < 0) {
+			printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx.\n", fm_port[dev], fm_port[dev] + 2);
+		} else {
+			if ((error = snd_opl3_timer_new(opl3, 1, 2)) < 0) {
+				snd_card_free(card);
+				return error;
+			}
+			if ((error = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+				snd_card_free(card);
+				return error;
+			}
+		}
+	}
+
+	if ((error = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	return 0;
+}
+
+static int __devinit snd_ad1816a_pnp_detect(struct pnp_card_link *card,
+					    const struct pnp_card_device_id *id)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev])
+			continue;
+		res = snd_card_ad1816a_probe(dev, card, id);
+		if (res < 0)
+			return res;
+		dev++;
+		return 0;
+	}
+        return -ENODEV;
+}
+
+static void __devexit snd_ad1816a_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_t *card = (snd_card_t *) pnp_get_card_drvdata(pcard);
+
+	snd_card_disconnect(card);
+	snd_card_free_in_thread(card);
+}
+
+static struct pnp_card_driver ad1816a_pnpc_driver = {
+	.flags		= PNP_DRIVER_RES_DISABLE,
+	.name		= "ad1816a",
+	.id_table	= snd_ad1816a_pnpids,
+	.probe		= snd_ad1816a_pnp_detect,
+	.remove		= __devexit_p(snd_ad1816a_pnp_remove),
+};
+
+static int __init alsa_card_ad1816a_init(void)
+{
+	int cards = 0;
+
+	cards += pnp_register_card_driver(&ad1816a_pnpc_driver);
+#ifdef MODULE
+	if (!cards) {
+		pnp_unregister_card_driver(&ad1816a_pnpc_driver);
+		printk(KERN_ERR "no AD1816A based soundcards found.\n");
+	}
+#endif	/* MODULE */
+	return cards ? 0 : -ENODEV;
+}
+
+static void __exit alsa_card_ad1816a_exit(void)
+{
+	pnp_unregister_card_driver(&ad1816a_pnpc_driver);
+}
+
+module_init(alsa_card_ad1816a_init)
+module_exit(alsa_card_ad1816a_exit)
diff --git a/sound/isa/ad1816a/ad1816a_lib.c b/sound/isa/ad1816a/ad1816a_lib.c
new file mode 100644
index 0000000..625b2eff
--- /dev/null
+++ b/sound/isa/ad1816a/ad1816a_lib.c
@@ -0,0 +1,974 @@
+
+/*
+    ad1816a.c - lowlevel code for Analog Devices AD1816A chip.
+    Copyright (C) 1999-2000 by Massimo Piccioni <dafastidio@libero.it>
+
+    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/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <sound/core.h>
+#include <sound/ad1816a.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+
+MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>");
+MODULE_DESCRIPTION("lowlevel code for Analog Devices AD1816A chip");
+MODULE_LICENSE("GPL");
+
+static inline int snd_ad1816a_busy_wait(ad1816a_t *chip)
+{
+	int timeout;
+
+	for (timeout = 1000; timeout-- > 0; udelay(10))
+		if (inb(AD1816A_REG(AD1816A_CHIP_STATUS)) & AD1816A_READY)
+			return 0;
+
+	snd_printk("chip busy.\n");
+	return -EBUSY;
+}
+
+static inline unsigned char snd_ad1816a_in(ad1816a_t *chip, unsigned char reg)
+{
+	snd_ad1816a_busy_wait(chip);
+	return inb(AD1816A_REG(reg));
+}
+
+static inline void snd_ad1816a_out(ad1816a_t *chip, unsigned char reg,
+			    unsigned char value)
+{
+	snd_ad1816a_busy_wait(chip);
+	outb(value, AD1816A_REG(reg));
+}
+
+static inline void snd_ad1816a_out_mask(ad1816a_t *chip, unsigned char reg,
+				 unsigned char mask, unsigned char value)
+{
+	snd_ad1816a_out(chip, reg,
+		(value & mask) | (snd_ad1816a_in(chip, reg) & ~mask));
+}
+
+static unsigned short snd_ad1816a_read(ad1816a_t *chip, unsigned char reg)
+{
+	snd_ad1816a_out(chip, AD1816A_INDIR_ADDR, reg & 0x3f);
+	return snd_ad1816a_in(chip, AD1816A_INDIR_DATA_LOW) |
+		(snd_ad1816a_in(chip, AD1816A_INDIR_DATA_HIGH) << 8);
+}
+
+static void snd_ad1816a_write(ad1816a_t *chip, unsigned char reg,
+			      unsigned short value)
+{
+	snd_ad1816a_out(chip, AD1816A_INDIR_ADDR, reg & 0x3f);
+	snd_ad1816a_out(chip, AD1816A_INDIR_DATA_LOW, value & 0xff);
+	snd_ad1816a_out(chip, AD1816A_INDIR_DATA_HIGH, (value >> 8) & 0xff);
+}
+
+static void snd_ad1816a_write_mask(ad1816a_t *chip, unsigned char reg,
+				   unsigned short mask, unsigned short value)
+{
+	snd_ad1816a_write(chip, reg,
+		(value & mask) | (snd_ad1816a_read(chip, reg) & ~mask));
+}
+
+
+static unsigned char snd_ad1816a_get_format(ad1816a_t *chip,
+					    unsigned int format, int channels)
+{
+	unsigned char retval = AD1816A_FMT_LINEAR_8;
+
+	switch (format) {
+	case SNDRV_PCM_FORMAT_MU_LAW:
+		retval = AD1816A_FMT_ULAW_8;
+		break;
+	case SNDRV_PCM_FORMAT_A_LAW:
+		retval = AD1816A_FMT_ALAW_8;
+		break;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		retval = AD1816A_FMT_LINEAR_16_LIT;
+		break;
+	case SNDRV_PCM_FORMAT_S16_BE:
+		retval = AD1816A_FMT_LINEAR_16_BIG;
+	}
+	return (channels > 1) ? (retval | AD1816A_FMT_STEREO) : retval;
+}
+
+static int snd_ad1816a_open(ad1816a_t *chip, unsigned int mode)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	if (chip->mode & mode) {
+		spin_unlock_irqrestore(&chip->lock, flags);
+		return -EAGAIN;
+	}
+
+	switch ((mode &= AD1816A_MODE_OPEN)) {
+	case AD1816A_MODE_PLAYBACK:
+		snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
+			AD1816A_PLAYBACK_IRQ_PENDING, 0x00);
+		snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+			AD1816A_PLAYBACK_IRQ_ENABLE, 0xffff);
+		break;
+	case AD1816A_MODE_CAPTURE:
+		snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
+			AD1816A_CAPTURE_IRQ_PENDING, 0x00);
+		snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+			AD1816A_CAPTURE_IRQ_ENABLE, 0xffff);
+		break;
+	case AD1816A_MODE_TIMER:
+		snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
+			AD1816A_TIMER_IRQ_PENDING, 0x00);
+		snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+			AD1816A_TIMER_IRQ_ENABLE, 0xffff);
+	}
+	chip->mode |= mode;
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+static void snd_ad1816a_close(ad1816a_t *chip, unsigned int mode)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	switch ((mode &= AD1816A_MODE_OPEN)) {
+	case AD1816A_MODE_PLAYBACK:
+		snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
+			AD1816A_PLAYBACK_IRQ_PENDING, 0x00);
+		snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+			AD1816A_PLAYBACK_IRQ_ENABLE, 0x0000);
+		break;
+	case AD1816A_MODE_CAPTURE:
+		snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
+			AD1816A_CAPTURE_IRQ_PENDING, 0x00);
+		snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+			AD1816A_CAPTURE_IRQ_ENABLE, 0x0000);
+		break;
+	case AD1816A_MODE_TIMER:
+		snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
+			AD1816A_TIMER_IRQ_PENDING, 0x00);
+		snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+			AD1816A_TIMER_IRQ_ENABLE, 0x0000);
+	}
+	if (!((chip->mode &= ~mode) & AD1816A_MODE_OPEN))
+		chip->mode = 0;
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+
+static int snd_ad1816a_trigger(ad1816a_t *chip, unsigned char what,
+			       int channel, int cmd)
+{
+	int error = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_STOP:
+		spin_lock(&chip->lock);
+		cmd = (cmd == SNDRV_PCM_TRIGGER_START) ? 0xff: 0x00;
+		if (what & AD1816A_PLAYBACK_ENABLE)
+			snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG,
+				AD1816A_PLAYBACK_ENABLE, cmd);
+		if (what & AD1816A_CAPTURE_ENABLE)
+			snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG,
+				AD1816A_CAPTURE_ENABLE, cmd);
+		spin_unlock(&chip->lock);
+		break;
+	default:
+		snd_printk("invalid trigger mode 0x%x.\n", what);
+		error = -EINVAL;
+	}
+
+	return error;
+}
+
+static int snd_ad1816a_playback_trigger(snd_pcm_substream_t *substream, int cmd)
+{
+	ad1816a_t *chip = snd_pcm_substream_chip(substream);
+	return snd_ad1816a_trigger(chip, AD1816A_PLAYBACK_ENABLE,
+		SNDRV_PCM_STREAM_PLAYBACK, cmd);
+}
+
+static int snd_ad1816a_capture_trigger(snd_pcm_substream_t *substream, int cmd)
+{
+	ad1816a_t *chip = snd_pcm_substream_chip(substream);
+	return snd_ad1816a_trigger(chip, AD1816A_CAPTURE_ENABLE,
+		SNDRV_PCM_STREAM_CAPTURE, cmd);
+}
+
+static int snd_ad1816a_hw_params(snd_pcm_substream_t * substream,
+				 snd_pcm_hw_params_t * hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_ad1816a_hw_free(snd_pcm_substream_t * substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_ad1816a_playback_prepare(snd_pcm_substream_t *substream)
+{
+	ad1816a_t *chip = snd_pcm_substream_chip(substream);
+	unsigned long flags;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned int size;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	chip->p_dma_size = size = snd_pcm_lib_buffer_bytes(substream);
+	snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG,
+		AD1816A_PLAYBACK_ENABLE | AD1816A_PLAYBACK_PIO, 0x00);
+
+	snd_dma_program(chip->dma1, runtime->dma_addr, size,
+			DMA_MODE_WRITE | DMA_AUTOINIT);
+
+	snd_ad1816a_write(chip, AD1816A_PLAYBACK_SAMPLE_RATE, runtime->rate);
+	snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG,
+		AD1816A_FMT_ALL | AD1816A_FMT_STEREO,
+		snd_ad1816a_get_format(chip, runtime->format,
+			runtime->channels));
+
+	snd_ad1816a_write(chip, AD1816A_PLAYBACK_BASE_COUNT,
+		snd_pcm_lib_period_bytes(substream) / 4 - 1);
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+static int snd_ad1816a_capture_prepare(snd_pcm_substream_t *substream)
+{
+	ad1816a_t *chip = snd_pcm_substream_chip(substream);
+	unsigned long flags;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned int size;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	chip->c_dma_size = size = snd_pcm_lib_buffer_bytes(substream);
+	snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG,
+		AD1816A_CAPTURE_ENABLE | AD1816A_CAPTURE_PIO, 0x00);
+
+	snd_dma_program(chip->dma2, runtime->dma_addr, size,
+			DMA_MODE_READ | DMA_AUTOINIT);
+
+	snd_ad1816a_write(chip, AD1816A_CAPTURE_SAMPLE_RATE, runtime->rate);
+	snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG,
+		AD1816A_FMT_ALL | AD1816A_FMT_STEREO,
+		snd_ad1816a_get_format(chip, runtime->format,
+			runtime->channels));
+
+	snd_ad1816a_write(chip, AD1816A_CAPTURE_BASE_COUNT,
+		snd_pcm_lib_period_bytes(substream) / 4 - 1);
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+
+static snd_pcm_uframes_t snd_ad1816a_playback_pointer(snd_pcm_substream_t *substream)
+{
+	ad1816a_t *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	if (!(chip->mode & AD1816A_MODE_PLAYBACK))
+		return 0;
+	ptr = snd_dma_pointer(chip->dma1, chip->p_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_ad1816a_capture_pointer(snd_pcm_substream_t *substream)
+{
+	ad1816a_t *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	if (!(chip->mode & AD1816A_MODE_CAPTURE))
+		return 0;
+	ptr = snd_dma_pointer(chip->dma2, chip->c_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+
+static irqreturn_t snd_ad1816a_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	ad1816a_t *chip = dev_id;
+	unsigned char status;
+
+	spin_lock(&chip->lock);
+	status = snd_ad1816a_in(chip, AD1816A_INTERRUPT_STATUS);
+	spin_unlock(&chip->lock);
+
+	if ((status & AD1816A_PLAYBACK_IRQ_PENDING) && chip->playback_substream)
+		snd_pcm_period_elapsed(chip->playback_substream);
+
+	if ((status & AD1816A_CAPTURE_IRQ_PENDING) && chip->capture_substream)
+		snd_pcm_period_elapsed(chip->capture_substream);
+
+	if ((status & AD1816A_TIMER_IRQ_PENDING) && chip->timer)
+		snd_timer_interrupt(chip->timer, chip->timer->sticks);
+
+	spin_lock(&chip->lock);
+	snd_ad1816a_out(chip, AD1816A_INTERRUPT_STATUS, 0x00);
+	spin_unlock(&chip->lock);
+	return IRQ_HANDLED;
+}
+
+
+static snd_pcm_hardware_t snd_ad1816a_playback = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		(SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
+				 SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
+				 SNDRV_PCM_FMTBIT_S16_BE),
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		55200,
+	.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,
+};
+
+static snd_pcm_hardware_t snd_ad1816a_capture = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		(SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
+				 SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
+				 SNDRV_PCM_FMTBIT_S16_BE),
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		55200,
+	.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,
+};
+
+#if 0 /* not used now */
+static int snd_ad1816a_timer_close(snd_timer_t *timer)
+{
+	ad1816a_t *chip = snd_timer_chip(timer);
+	snd_ad1816a_close(chip, AD1816A_MODE_TIMER);
+	return 0;
+}
+
+static int snd_ad1816a_timer_open(snd_timer_t *timer)
+{
+	ad1816a_t *chip = snd_timer_chip(timer);
+	snd_ad1816a_open(chip, AD1816A_MODE_TIMER);
+	return 0;
+}
+
+static unsigned long snd_ad1816a_timer_resolution(snd_timer_t *timer)
+{
+	snd_assert(timer != NULL, return 0);
+
+	return 10000;
+}
+
+static int snd_ad1816a_timer_start(snd_timer_t *timer)
+{
+	unsigned short bits;
+	unsigned long flags;
+	ad1816a_t *chip = snd_timer_chip(timer);
+	spin_lock_irqsave(&chip->lock, flags);
+	bits = snd_ad1816a_read(chip, AD1816A_INTERRUPT_ENABLE);
+
+	if (!(bits & AD1816A_TIMER_ENABLE)) {
+		snd_ad1816a_write(chip, AD1816A_TIMER_BASE_COUNT,
+			timer->sticks & 0xffff);
+
+		snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+			AD1816A_TIMER_ENABLE, 0xffff);
+	}
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+static int snd_ad1816a_timer_stop(snd_timer_t *timer)
+{
+	unsigned long flags;
+	ad1816a_t *chip = snd_timer_chip(timer);
+	spin_lock_irqsave(&chip->lock, flags);
+
+	snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+		AD1816A_TIMER_ENABLE, 0x0000);
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+static struct _snd_timer_hardware snd_ad1816a_timer_table = {
+	.flags =	SNDRV_TIMER_HW_AUTO,
+	.resolution =	10000,
+	.ticks =	65535,
+	.open =		snd_ad1816a_timer_open,
+	.close =	snd_ad1816a_timer_close,
+	.c_resolution =	snd_ad1816a_timer_resolution,
+	.start =	snd_ad1816a_timer_start,
+	.stop =		snd_ad1816a_timer_stop,
+};
+#endif /* not used now */
+
+
+static int snd_ad1816a_playback_open(snd_pcm_substream_t *substream)
+{
+	ad1816a_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int error;
+
+	if ((error = snd_ad1816a_open(chip, AD1816A_MODE_PLAYBACK)) < 0)
+		return error;
+	snd_pcm_set_sync(substream);
+	runtime->hw = snd_ad1816a_playback;
+	snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.buffer_bytes_max);
+	snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.period_bytes_max);
+	chip->playback_substream = substream;
+	return 0;
+}
+
+static int snd_ad1816a_capture_open(snd_pcm_substream_t *substream)
+{
+	ad1816a_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int error;
+
+	if ((error = snd_ad1816a_open(chip, AD1816A_MODE_CAPTURE)) < 0)
+		return error;
+	snd_pcm_set_sync(substream);
+	runtime->hw = snd_ad1816a_capture;
+	snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.buffer_bytes_max);
+	snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.period_bytes_max);
+	chip->capture_substream = substream;
+	return 0;
+}
+
+static int snd_ad1816a_playback_close(snd_pcm_substream_t *substream)
+{
+	ad1816a_t *chip = snd_pcm_substream_chip(substream);
+
+	chip->playback_substream = NULL;
+	snd_ad1816a_close(chip, AD1816A_MODE_PLAYBACK);
+	return 0;
+}
+
+static int snd_ad1816a_capture_close(snd_pcm_substream_t *substream)
+{
+	ad1816a_t *chip = snd_pcm_substream_chip(substream);
+
+	chip->capture_substream = NULL;
+	snd_ad1816a_close(chip, AD1816A_MODE_CAPTURE);
+	return 0;
+}
+
+
+static void snd_ad1816a_init(ad1816a_t *chip)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	snd_ad1816a_out(chip, AD1816A_INTERRUPT_STATUS, 0x00);
+	snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG,
+		AD1816A_PLAYBACK_ENABLE | AD1816A_PLAYBACK_PIO, 0x00);
+	snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG,
+		AD1816A_CAPTURE_ENABLE | AD1816A_CAPTURE_PIO, 0x00);
+	snd_ad1816a_write(chip, AD1816A_INTERRUPT_ENABLE, 0x0000);
+	snd_ad1816a_write_mask(chip, AD1816A_CHIP_CONFIG,
+		AD1816A_CAPTURE_NOT_EQUAL | AD1816A_WSS_ENABLE, 0xffff);
+	snd_ad1816a_write(chip, AD1816A_DSP_CONFIG, 0x0000);
+	snd_ad1816a_write(chip, AD1816A_POWERDOWN_CTRL, 0x0000);
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+static int snd_ad1816a_probe(ad1816a_t *chip)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	switch (chip->version = snd_ad1816a_read(chip, AD1816A_VERSION_ID)) {
+	case 0:
+		chip->hardware = AD1816A_HW_AD1815;
+		break;
+	case 1:
+		chip->hardware = AD1816A_HW_AD18MAX10;
+		break;
+	case 3:
+		chip->hardware = AD1816A_HW_AD1816A;
+		break;
+	default:
+		chip->hardware = AD1816A_HW_AUTO;
+	}
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+static int snd_ad1816a_free(ad1816a_t *chip)
+{
+	if (chip->res_port) {
+		release_resource(chip->res_port);
+		kfree_nocheck(chip->res_port);
+	}
+	if (chip->irq >= 0)
+		free_irq(chip->irq, (void *) chip);
+	if (chip->dma1 >= 0) {
+		snd_dma_disable(chip->dma1);
+		free_dma(chip->dma1);
+	}
+	if (chip->dma2 >= 0) {
+		snd_dma_disable(chip->dma2);
+		free_dma(chip->dma2);
+	}
+	kfree(chip);
+	return 0;
+}
+
+static int snd_ad1816a_dev_free(snd_device_t *device)
+{
+	ad1816a_t *chip = device->device_data;
+	return snd_ad1816a_free(chip);
+}
+
+static const char *snd_ad1816a_chip_id(ad1816a_t *chip)
+{
+	switch (chip->hardware) {
+	case AD1816A_HW_AD1816A: return "AD1816A";
+	case AD1816A_HW_AD1815:	return "AD1815";
+	case AD1816A_HW_AD18MAX10: return "AD18max10";
+	default:
+		snd_printk("Unknown chip version %d:%d.\n",
+			chip->version, chip->hardware);
+		return "AD1816A - unknown";
+	}
+}
+
+int snd_ad1816a_create(snd_card_t *card,
+		       unsigned long port, int irq, int dma1, int dma2,
+		       ad1816a_t **rchip)
+{
+        static snd_device_ops_t ops = {
+		.dev_free =	snd_ad1816a_dev_free,
+	};
+	int error;
+	ad1816a_t *chip;
+
+	*rchip = NULL;
+
+	chip = kcalloc(1, sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+	chip->irq = -1;
+	chip->dma1 = -1;
+	chip->dma2 = -1;
+
+	if ((chip->res_port = request_region(port, 16, "AD1816A")) == NULL) {
+		snd_printk(KERN_ERR "ad1816a: can't grab port 0x%lx\n", port);
+		snd_ad1816a_free(chip);
+		return -EBUSY;
+	}
+	if (request_irq(irq, snd_ad1816a_interrupt, SA_INTERRUPT, "AD1816A", (void *) chip)) {
+		snd_printk(KERN_ERR "ad1816a: can't grab IRQ %d\n", irq);
+		snd_ad1816a_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = irq;
+	if (request_dma(dma1, "AD1816A - 1")) {
+		snd_printk(KERN_ERR "ad1816a: can't grab DMA1 %d\n", dma1);
+		snd_ad1816a_free(chip);
+		return -EBUSY;
+	}
+	chip->dma1 = dma1;
+	if (request_dma(dma2, "AD1816A - 2")) {
+		snd_printk(KERN_ERR "ad1816a: can't grab DMA2 %d\n", dma2);
+		snd_ad1816a_free(chip);
+		return -EBUSY;
+	}
+	chip->dma2 = dma2;
+
+	chip->card = card;
+	chip->port = port;
+	spin_lock_init(&chip->lock);
+
+	if ((error = snd_ad1816a_probe(chip))) {
+		snd_ad1816a_free(chip);
+		return error;
+	}
+
+	snd_ad1816a_init(chip);
+
+	/* Register device */
+	if ((error = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_ad1816a_free(chip);
+		return error;
+	}
+
+	*rchip = chip;
+	return 0;
+}
+
+static snd_pcm_ops_t snd_ad1816a_playback_ops = {
+	.open =		snd_ad1816a_playback_open,
+	.close =	snd_ad1816a_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_ad1816a_hw_params,
+	.hw_free =	snd_ad1816a_hw_free,
+	.prepare =	snd_ad1816a_playback_prepare,
+	.trigger =	snd_ad1816a_playback_trigger,
+	.pointer =	snd_ad1816a_playback_pointer,
+};
+
+static snd_pcm_ops_t snd_ad1816a_capture_ops = {
+	.open =		snd_ad1816a_capture_open,
+	.close =	snd_ad1816a_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_ad1816a_hw_params,
+	.hw_free =	snd_ad1816a_hw_free,
+	.prepare =	snd_ad1816a_capture_prepare,
+	.trigger =	snd_ad1816a_capture_trigger,
+	.pointer =	snd_ad1816a_capture_pointer,
+};
+
+static void snd_ad1816a_pcm_free(snd_pcm_t *pcm)
+{
+	ad1816a_t *chip = pcm->private_data;
+	chip->pcm = NULL;
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+int snd_ad1816a_pcm(ad1816a_t *chip, int device, snd_pcm_t **rpcm)
+{
+	int error;
+	snd_pcm_t *pcm;
+
+	if ((error = snd_pcm_new(chip->card, "AD1816A", device, 1, 1, &pcm)))
+		return error;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ad1816a_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ad1816a_capture_ops);
+
+	pcm->private_data = chip;
+	pcm->private_free = snd_ad1816a_pcm_free;
+	pcm->info_flags = (chip->dma1 == chip->dma2 ) ? SNDRV_PCM_INFO_JOINT_DUPLEX : 0;
+
+	strcpy(pcm->name, snd_ad1816a_chip_id(chip));
+	snd_ad1816a_init(chip);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, chip->dma1 > 3 || chip->dma2 > 3 ? 128*1024 : 64*1024);
+
+	chip->pcm = pcm;
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+#if 0 /* not used now */
+static void snd_ad1816a_timer_free(snd_timer_t *timer)
+{
+	ad1816a_t *chip = timer->private_data;
+	chip->timer = NULL;
+}
+
+int snd_ad1816a_timer(ad1816a_t *chip, int device, snd_timer_t **rtimer)
+{
+	snd_timer_t *timer;
+	snd_timer_id_t tid;
+	int error;
+
+	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
+	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+	tid.card = chip->card->number;
+	tid.device = device;
+	tid.subdevice = 0;
+	if ((error = snd_timer_new(chip->card, "AD1816A", &tid, &timer)) < 0)
+		return error;
+	strcpy(timer->name, snd_ad1816a_chip_id(chip));
+	timer->private_data = chip;
+	timer->private_free = snd_ad1816a_timer_free;
+	chip->timer = timer;
+	timer->hw = snd_ad1816a_timer_table;
+	if (rtimer)
+		*rtimer = timer;
+	return 0;
+}
+#endif /* not used now */
+
+/*
+ *
+ */
+
+static int snd_ad1816a_info_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	static char *texts[8] = {
+		"Line", "Mix", "CD", "Synth", "Video",
+		"Mic", "Phone",
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 2;
+	uinfo->value.enumerated.items = 7;
+	if (uinfo->value.enumerated.item > 6)
+		uinfo->value.enumerated.item = 6;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ad1816a_get_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	ad1816a_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned short val;
+	
+	spin_lock_irqsave(&chip->lock, flags);
+	val = snd_ad1816a_read(chip, AD1816A_ADC_SOURCE_SEL);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	ucontrol->value.enumerated.item[0] = (val >> 12) & 7;
+	ucontrol->value.enumerated.item[1] = (val >> 4) & 7;
+	return 0;
+}
+
+static int snd_ad1816a_put_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	ad1816a_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned short val;
+	int change;
+	
+	if (ucontrol->value.enumerated.item[0] > 6 ||
+	    ucontrol->value.enumerated.item[1] > 6)
+		return -EINVAL;
+	val = (ucontrol->value.enumerated.item[0] << 12) |
+	      (ucontrol->value.enumerated.item[1] << 4);
+	spin_lock_irqsave(&chip->lock, flags);
+	change = snd_ad1816a_read(chip, AD1816A_ADC_SOURCE_SEL) != val;
+	snd_ad1816a_write(chip, AD1816A_ADC_SOURCE_SEL, val);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return change;
+}
+
+#define AD1816A_SINGLE(xname, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ad1816a_info_single, \
+  .get = snd_ad1816a_get_single, .put = snd_ad1816a_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+static int snd_ad1816a_info_single(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_ad1816a_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	ad1816a_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	
+	spin_lock_irqsave(&chip->lock, flags);
+	ucontrol->value.integer.value[0] = (snd_ad1816a_read(chip, reg) >> shift) & mask;
+	spin_unlock_irqrestore(&chip->lock, flags);
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_ad1816a_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	ad1816a_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned short old_val, val;
+	
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	val <<= shift;
+	spin_lock_irqsave(&chip->lock, flags);
+	old_val = snd_ad1816a_read(chip, reg);
+	val = (old_val & ~(mask << shift)) | val;
+	change = val != old_val;
+	snd_ad1816a_write(chip, reg, val);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return change;
+}
+
+#define AD1816A_DOUBLE(xname, reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ad1816a_info_double, \
+  .get = snd_ad1816a_get_double, .put = snd_ad1816a_put_double, \
+  .private_value = reg | (shift_left << 8) | (shift_right << 12) | (mask << 16) | (invert << 24) }
+
+static int snd_ad1816a_info_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_ad1816a_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	ad1816a_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift_left = (kcontrol->private_value >> 8) & 0x0f;
+	int shift_right = (kcontrol->private_value >> 12) & 0x0f;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	unsigned short val;
+	
+	spin_lock_irqsave(&chip->lock, flags);
+	val = snd_ad1816a_read(chip, reg);
+	ucontrol->value.integer.value[0] = (val >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (val >> shift_right) & mask;
+	spin_unlock_irqrestore(&chip->lock, flags);
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+
+static int snd_ad1816a_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	ad1816a_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift_left = (kcontrol->private_value >> 8) & 0x0f;
+	int shift_right = (kcontrol->private_value >> 12) & 0x0f;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned short old_val, val1, val2;
+	
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	spin_lock_irqsave(&chip->lock, flags);
+	old_val = snd_ad1816a_read(chip, reg);
+	val1 = (old_val & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2;
+	change = val1 != old_val;
+	snd_ad1816a_write(chip, reg, val1);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_ad1816a_controls[] = {
+AD1816A_DOUBLE("Master Playback Switch", AD1816A_MASTER_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE("Master Playback Volume", AD1816A_MASTER_ATT, 8, 0, 31, 1),
+AD1816A_DOUBLE("PCM Playback Switch", AD1816A_VOICE_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE("PCM Playback Volume", AD1816A_VOICE_ATT, 8, 0, 63, 1),
+AD1816A_DOUBLE("Line Playback Switch", AD1816A_LINE_GAIN_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE("Line Playback Volume", AD1816A_LINE_GAIN_ATT, 8, 0, 31, 1),
+AD1816A_DOUBLE("CD Playback Switch", AD1816A_CD_GAIN_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE("CD Playback Volume", AD1816A_CD_GAIN_ATT, 8, 0, 31, 1),
+AD1816A_DOUBLE("Synth Playback Switch", AD1816A_SYNTH_GAIN_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE("Synth Playback Volume", AD1816A_SYNTH_GAIN_ATT, 8, 0, 31, 1),
+AD1816A_DOUBLE("FM Playback Switch", AD1816A_FM_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE("FM Playback Volume", AD1816A_FM_ATT, 8, 0, 63, 1),
+AD1816A_SINGLE("Mic Playback Switch", AD1816A_MIC_GAIN_ATT, 15, 1, 1),
+AD1816A_SINGLE("Mic Playback Volume", AD1816A_MIC_GAIN_ATT, 8, 31, 1),
+AD1816A_SINGLE("Mic Boost", AD1816A_MIC_GAIN_ATT, 14, 1, 0),
+AD1816A_DOUBLE("Video Playback Switch", AD1816A_VID_GAIN_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE("Video Playback Volume", AD1816A_VID_GAIN_ATT, 8, 0, 31, 1),
+AD1816A_SINGLE("Phone Capture Switch", AD1816A_PHONE_IN_GAIN_ATT, 15, 1, 1),
+AD1816A_SINGLE("Phone Capture Volume", AD1816A_PHONE_IN_GAIN_ATT, 0, 15, 1),
+AD1816A_SINGLE("Phone Playback Switch", AD1816A_PHONE_OUT_ATT, 7, 1, 1),
+AD1816A_SINGLE("Phone Playback Volume", AD1816A_PHONE_OUT_ATT, 0, 31, 1),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Capture Source",
+	.info = snd_ad1816a_info_mux,
+	.get = snd_ad1816a_get_mux,
+	.put = snd_ad1816a_put_mux,
+},
+AD1816A_DOUBLE("Capture Switch", AD1816A_ADC_PGA, 15, 7, 1, 1),
+AD1816A_DOUBLE("Capture Volume", AD1816A_ADC_PGA, 8, 0, 15, 0),
+AD1816A_SINGLE("3D Control - Switch", AD1816A_3D_PHAT_CTRL, 15, 1, 1),
+AD1816A_SINGLE("3D Control - Level", AD1816A_3D_PHAT_CTRL, 0, 15, 0),
+};
+                                        
+int snd_ad1816a_mixer(ad1816a_t *chip)
+{
+	snd_card_t *card;
+	unsigned int idx;
+	int err;
+
+	snd_assert(chip != NULL && chip->card != NULL, return -EINVAL);
+
+	card = chip->card;
+
+	strcpy(card->mixername, snd_ad1816a_chip_id(chip));
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_ad1816a_controls); idx++) {
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_ad1816a_controls[idx], chip))) < 0)
+			return err;
+	}
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_ad1816a_create);
+EXPORT_SYMBOL(snd_ad1816a_pcm);
+EXPORT_SYMBOL(snd_ad1816a_mixer);
+
+static int __init alsa_ad1816a_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_ad1816a_exit(void)
+{
+}
+
+module_init(alsa_ad1816a_init)
+module_exit(alsa_ad1816a_exit)
diff --git a/sound/isa/ad1848/Makefile b/sound/isa/ad1848/Makefile
new file mode 100644
index 0000000..45d5999
--- /dev/null
+++ b/sound/isa/ad1848/Makefile
@@ -0,0 +1,15 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-ad1848-lib-objs := ad1848_lib.o
+snd-ad1848-objs := ad1848.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_CMI8330) += snd-ad1848-lib.o
+obj-$(CONFIG_SND_SGALAXY) += snd-ad1848-lib.o
+obj-$(CONFIG_SND_AD1848) += snd-ad1848.o snd-ad1848-lib.o
+obj-$(CONFIG_SND_OPTI92X_AD1848) += snd-ad1848-lib.o
+
+obj-m := $(sort $(obj-m))
diff --git a/sound/isa/ad1848/ad1848.c b/sound/isa/ad1848/ad1848.c
new file mode 100644
index 0000000..8c39934
--- /dev/null
+++ b/sound/isa/ad1848/ad1848.c
@@ -0,0 +1,151 @@
+/*
+ *  Generic driver for AD1848/AD1847/CS4248 chips (0.1 Alpha)
+ *  Copyright (c) by Tugrul Galatali <galatalt@stuy.edu>,
+ *                   Jaroslav Kysela <perex@suse.cz>
+ *  Based on card-4232.c by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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/time.h>
+#include <linux/wait.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/ad1848.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Tugrul Galatali <galatalt@stuy.edu>, Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("AD1848/AD1847/CS4248");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Analog Devices,AD1848},"
+	        "{Analog Devices,AD1847},"
+		"{Crystal Semiconductors,CS4248}}");
+
+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;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3,5,6,7 */
+static int thinkpad[SNDRV_CARDS];			/* Thinkpad special case */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for AD1848 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for AD1848 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable AD1848 soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for AD1848 driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for AD1848 driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for AD1848 driver.");
+module_param_array(thinkpad, bool, NULL, 0444);
+MODULE_PARM_DESC(thinkpad, "Enable only for the onboard CS4248 of IBM Thinkpad 360/750/755 series.");
+
+static snd_card_t *snd_ad1848_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+
+static int __init snd_card_ad1848_probe(int dev)
+{
+	snd_card_t *card;
+	ad1848_t *chip;
+	snd_pcm_t *pcm;
+	int err;
+
+	if (port[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR "ad1848: specify port\n");
+		return -EINVAL;
+	}
+	if (irq[dev] == SNDRV_AUTO_IRQ) {
+		snd_printk(KERN_ERR "ad1848: specify irq\n");
+		return -EINVAL;
+	}
+	if (dma1[dev] == SNDRV_AUTO_DMA) {
+		snd_printk(KERN_ERR "ad1848: specify dma1\n");
+		return -EINVAL;
+	}
+
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
+	if (card == NULL)
+		return -ENOMEM;
+
+	if ((err = snd_ad1848_create(card, port[dev],
+				     irq[dev],
+				     dma1[dev],
+				     thinkpad[dev] ? AD1848_HW_THINKPAD : AD1848_HW_DETECT,
+				     &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if ((err = snd_ad1848_pcm(chip, 0, &pcm)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_ad1848_mixer(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	strcpy(card->driver, "AD1848");
+	strcpy(card->shortname, pcm->name);
+
+	sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+		pcm->name, chip->port, irq[dev], dma1[dev]);
+
+	if (thinkpad[dev]) {
+		strcat(card->longname, " [Thinkpad]");
+	}
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	snd_ad1848_cards[dev] = card;
+	return 0;
+}
+
+static int __init alsa_card_ad1848_init(void)
+{
+	int dev, cards;
+
+	for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev]; dev++)
+		if (snd_card_ad1848_probe(dev) >= 0)
+			cards++;
+
+	if (!cards) {
+#ifdef MODULE
+		printk(KERN_ERR "AD1848 soundcard not found or device busy\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_ad1848_exit(void)
+{
+	int idx;
+
+	for (idx = 0; idx < SNDRV_CARDS; idx++)
+		snd_card_free(snd_ad1848_cards[idx]);
+}
+
+module_init(alsa_card_ad1848_init)
+module_exit(alsa_card_ad1848_exit)
diff --git a/sound/isa/ad1848/ad1848_lib.c b/sound/isa/ad1848/ad1848_lib.c
new file mode 100644
index 0000000..8fb3db1
--- /dev/null
+++ b/sound/isa/ad1848/ad1848_lib.c
@@ -0,0 +1,1279 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  Routines for control of AD1848/AD1847/CS4248
+ *
+ *
+ *   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
+ *
+ */
+
+#define SNDRV_MAIN_OBJECT_FILE
+#include <sound/driver.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <sound/core.h>
+#include <sound/ad1848.h>
+#include <sound/control.h>
+#include <sound/pcm_params.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Routines for control of AD1848/AD1847/CS4248");
+MODULE_LICENSE("GPL");
+
+#if 0
+#define SNDRV_DEBUG_MCE
+#endif
+
+/*
+ *  Some variables
+ */
+
+static unsigned char freq_bits[14] = {
+	/* 5510 */	0x00 | AD1848_XTAL2,
+	/* 6620 */	0x0E | AD1848_XTAL2,
+	/* 8000 */	0x00 | AD1848_XTAL1,
+	/* 9600 */	0x0E | AD1848_XTAL1,
+	/* 11025 */	0x02 | AD1848_XTAL2,
+	/* 16000 */	0x02 | AD1848_XTAL1,
+	/* 18900 */	0x04 | AD1848_XTAL2,
+	/* 22050 */	0x06 | AD1848_XTAL2,
+	/* 27042 */	0x04 | AD1848_XTAL1,
+	/* 32000 */	0x06 | AD1848_XTAL1,
+	/* 33075 */	0x0C | AD1848_XTAL2,
+	/* 37800 */	0x08 | AD1848_XTAL2,
+	/* 44100 */	0x0A | AD1848_XTAL2,
+	/* 48000 */	0x0C | AD1848_XTAL1
+};
+
+static unsigned int rates[14] = {
+	5510, 6620, 8000, 9600, 11025, 16000, 18900, 22050,
+	27042, 32000, 33075, 37800, 44100, 48000
+};
+
+static snd_pcm_hw_constraint_list_t hw_constraints_rates = {
+	.count = 14,
+	.list = rates,
+	.mask = 0,
+};
+
+static unsigned char snd_ad1848_original_image[16] =
+{
+	0x00,			/* 00 - lic */
+	0x00,			/* 01 - ric */
+	0x9f,			/* 02 - la1ic */
+	0x9f,			/* 03 - ra1ic */
+	0x9f,			/* 04 - la2ic */
+	0x9f,			/* 05 - ra2ic */
+	0xbf,			/* 06 - loc */
+	0xbf,			/* 07 - roc */
+	0x20,			/* 08 - dfr */
+	AD1848_AUTOCALIB,	/* 09 - ic */
+	0x00,			/* 0a - pc */
+	0x00,			/* 0b - ti */
+	0x00,			/* 0c - mi */
+	0x00,			/* 0d - lbc */
+	0x00,			/* 0e - dru */
+	0x00,			/* 0f - drl */
+};
+
+/*
+ *  Basic I/O functions
+ */
+
+void snd_ad1848_out(ad1848_t *chip,
+			   unsigned char reg,
+			   unsigned char value)
+{
+	int timeout;
+
+	for (timeout = 250; timeout > 0 && (inb(AD1848P(chip, REGSEL)) & AD1848_INIT); timeout--)
+		udelay(100);
+#ifdef CONFIG_SND_DEBUG
+	if (inb(AD1848P(chip, REGSEL)) & AD1848_INIT)
+		snd_printk("auto calibration time out - reg = 0x%x, value = 0x%x\n", reg, value);
+#endif
+	outb(chip->mce_bit | reg, AD1848P(chip, REGSEL));
+	outb(chip->image[reg] = value, AD1848P(chip, REG));
+	mb();
+#if 0
+	printk("codec out - reg 0x%x = 0x%x\n", chip->mce_bit | reg, value);
+#endif
+}
+
+static void snd_ad1848_dout(ad1848_t *chip,
+			    unsigned char reg, unsigned char value)
+{
+	int timeout;
+
+	for (timeout = 250; timeout > 0 && (inb(AD1848P(chip, REGSEL)) & AD1848_INIT); timeout--)
+		udelay(100);
+	outb(chip->mce_bit | reg, AD1848P(chip, REGSEL));
+	outb(value, AD1848P(chip, REG));
+	mb();
+}
+
+static unsigned char snd_ad1848_in(ad1848_t *chip, unsigned char reg)
+{
+	int timeout;
+
+	for (timeout = 250; timeout > 0 && (inb(AD1848P(chip, REGSEL)) & AD1848_INIT); timeout--)
+		udelay(100);
+#ifdef CONFIG_SND_DEBUG
+	if (inb(AD1848P(chip, REGSEL)) & AD1848_INIT)
+		snd_printk("auto calibration time out - reg = 0x%x\n", reg);
+#endif
+	outb(chip->mce_bit | reg, AD1848P(chip, REGSEL));
+	mb();
+	return inb(AD1848P(chip, REG));
+}
+
+#if 0
+
+static void snd_ad1848_debug(ad1848_t *chip)
+{
+	printk("AD1848 REGS:      INDEX = 0x%02x  ", inb(AD1848P(chip, REGSEL)));
+	printk("                 STATUS = 0x%02x\n", inb(AD1848P(chip, STATUS)));
+	printk("  0x00: left input      = 0x%02x  ", snd_ad1848_in(chip, 0x00));
+	printk("  0x08: playback format = 0x%02x\n", snd_ad1848_in(chip, 0x08));
+	printk("  0x01: right input     = 0x%02x  ", snd_ad1848_in(chip, 0x01));
+	printk("  0x09: iface (CFIG 1)  = 0x%02x\n", snd_ad1848_in(chip, 0x09));
+	printk("  0x02: AUXA left       = 0x%02x  ", snd_ad1848_in(chip, 0x02));
+	printk("  0x0a: pin control     = 0x%02x\n", snd_ad1848_in(chip, 0x0a));
+	printk("  0x03: AUXA right      = 0x%02x  ", snd_ad1848_in(chip, 0x03));
+	printk("  0x0b: init & status   = 0x%02x\n", snd_ad1848_in(chip, 0x0b));
+	printk("  0x04: AUXB left       = 0x%02x  ", snd_ad1848_in(chip, 0x04));
+	printk("  0x0c: revision & mode = 0x%02x\n", snd_ad1848_in(chip, 0x0c));
+	printk("  0x05: AUXB right      = 0x%02x  ", snd_ad1848_in(chip, 0x05));
+	printk("  0x0d: loopback        = 0x%02x\n", snd_ad1848_in(chip, 0x0d));
+	printk("  0x06: left output     = 0x%02x  ", snd_ad1848_in(chip, 0x06));
+	printk("  0x0e: data upr count  = 0x%02x\n", snd_ad1848_in(chip, 0x0e));
+	printk("  0x07: right output    = 0x%02x  ", snd_ad1848_in(chip, 0x07));
+	printk("  0x0f: data lwr count  = 0x%02x\n", snd_ad1848_in(chip, 0x0f));
+}
+
+#endif
+
+/*
+ *  AD1848 detection / MCE routines
+ */
+
+static void snd_ad1848_mce_up(ad1848_t *chip)
+{
+	unsigned long flags;
+	int timeout;
+
+	for (timeout = 250; timeout > 0 && (inb(AD1848P(chip, REGSEL)) & AD1848_INIT); timeout--)
+		udelay(100);
+#ifdef CONFIG_SND_DEBUG
+	if (inb(AD1848P(chip, REGSEL)) & AD1848_INIT)
+		snd_printk("mce_up - auto calibration time out (0)\n");
+#endif
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->mce_bit |= AD1848_MCE;
+	timeout = inb(AD1848P(chip, REGSEL));
+	if (timeout == 0x80)
+		snd_printk("mce_up [0x%lx]: serious init problem - codec still busy\n", chip->port);
+	if (!(timeout & AD1848_MCE))
+		outb(chip->mce_bit | (timeout & 0x1f), AD1848P(chip, REGSEL));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_ad1848_mce_down(ad1848_t *chip)
+{
+	unsigned long flags;
+	int timeout;
+	signed long time;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	for (timeout = 5; timeout > 0; timeout--)
+		inb(AD1848P(chip, REGSEL));
+	/* end of cleanup sequence */
+	for (timeout = 12000; timeout > 0 && (inb(AD1848P(chip, REGSEL)) & AD1848_INIT); timeout--)
+		udelay(100);
+#if 0
+	printk("(1) timeout = %i\n", timeout);
+#endif
+#ifdef CONFIG_SND_DEBUG
+	if (inb(AD1848P(chip, REGSEL)) & AD1848_INIT)
+		snd_printk("mce_down [0x%lx] - auto calibration time out (0)\n", AD1848P(chip, REGSEL));
+#endif
+	chip->mce_bit &= ~AD1848_MCE;
+	timeout = inb(AD1848P(chip, REGSEL));
+	outb(chip->mce_bit | (timeout & 0x1f), AD1848P(chip, REGSEL));
+	if (timeout == 0x80)
+		snd_printk("mce_down [0x%lx]: serious init problem - codec still busy\n", chip->port);
+	if ((timeout & AD1848_MCE) == 0) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return;
+	}
+	/* calibration process */
+
+	for (timeout = 500; timeout > 0 && (snd_ad1848_in(chip, AD1848_TEST_INIT) & AD1848_CALIB_IN_PROGRESS) == 0; timeout--);
+	if ((snd_ad1848_in(chip, AD1848_TEST_INIT) & AD1848_CALIB_IN_PROGRESS) == 0) {
+		snd_printd("mce_down - auto calibration time out (1)\n");
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return;
+	}
+#if 0
+	printk("(2) timeout = %i, jiffies = %li\n", timeout, jiffies);
+#endif
+	time = HZ / 4;
+	while (snd_ad1848_in(chip, AD1848_TEST_INIT) & AD1848_CALIB_IN_PROGRESS) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		if (time <= 0) {
+			snd_printk("mce_down - auto calibration time out (2)\n");
+			return;
+		}
+		set_current_state(TASK_INTERRUPTIBLE);
+		time = schedule_timeout(time);
+		spin_lock_irqsave(&chip->reg_lock, flags);
+	}
+#if 0
+	printk("(3) jiffies = %li\n", jiffies);
+#endif
+	time = HZ / 10;
+	while (inb(AD1848P(chip, REGSEL)) & AD1848_INIT) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		if (time <= 0) {
+			snd_printk("mce_down - auto calibration time out (3)\n");
+			return;
+		}
+		set_current_state(TASK_INTERRUPTIBLE);
+		time = schedule_timeout(time);
+		spin_lock_irqsave(&chip->reg_lock, flags);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+#if 0
+	printk("(4) jiffies = %li\n", jiffies);
+	snd_printk("mce_down - exit = 0x%x\n", inb(AD1848P(chip, REGSEL)));
+#endif
+}
+
+static unsigned int snd_ad1848_get_count(unsigned char format,
+				         unsigned int size)
+{
+	switch (format & 0xe0) {
+	case AD1848_LINEAR_16:
+		size >>= 1;
+		break;
+	}
+	if (format & AD1848_STEREO)
+		size >>= 1;
+	return size;
+}
+
+static int snd_ad1848_trigger(ad1848_t *chip, unsigned char what,
+			      int channel, int cmd)
+{
+	int result = 0;
+
+#if 0
+	printk("codec trigger!!! - what = %i, enable = %i, status = 0x%x\n", what, enable, inb(AD1848P(card, STATUS)));
+#endif
+	spin_lock(&chip->reg_lock);
+	if (cmd == SNDRV_PCM_TRIGGER_START) {
+		if (chip->image[AD1848_IFACE_CTRL] & what) {
+			spin_unlock(&chip->reg_lock);
+			return 0;
+		}
+		snd_ad1848_out(chip, AD1848_IFACE_CTRL, chip->image[AD1848_IFACE_CTRL] |= what);
+		chip->mode |= AD1848_MODE_RUNNING;
+	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		if (!(chip->image[AD1848_IFACE_CTRL] & what)) {
+			spin_unlock(&chip->reg_lock);
+			return 0;
+		}
+		snd_ad1848_out(chip, AD1848_IFACE_CTRL, chip->image[AD1848_IFACE_CTRL] &= ~what);
+		chip->mode &= ~AD1848_MODE_RUNNING;
+	} else {
+		result = -EINVAL;
+	}
+	spin_unlock(&chip->reg_lock);
+	return result;
+}
+
+/*
+ *  CODEC I/O
+ */
+
+static unsigned char snd_ad1848_get_rate(unsigned int rate)
+{
+	int i;
+
+	for (i = 0; i < 14; i++)
+		if (rate == rates[i])
+			return freq_bits[i];
+	snd_BUG();
+	return freq_bits[13];
+}
+
+static int snd_ad1848_ioctl(snd_pcm_substream_t * substream,
+			    unsigned int cmd, void *arg)
+{
+	return snd_pcm_lib_ioctl(substream, cmd, arg);
+}
+
+static unsigned char snd_ad1848_get_format(int format, int channels)
+{
+	unsigned char rformat;
+
+	rformat = AD1848_LINEAR_8;
+	switch (format) {
+	case SNDRV_PCM_FORMAT_A_LAW:	rformat = AD1848_ALAW_8; break;
+	case SNDRV_PCM_FORMAT_MU_LAW:	rformat = AD1848_ULAW_8; break;
+	case SNDRV_PCM_FORMAT_S16_LE:	rformat = AD1848_LINEAR_16; break;
+	}
+	if (channels > 1)
+		rformat |= AD1848_STEREO;
+#if 0
+	snd_printk("get_format: 0x%x (mode=0x%x)\n", format, mode);
+#endif
+	return rformat;
+}
+
+static void snd_ad1848_calibrate_mute(ad1848_t *chip, int mute)
+{
+	unsigned long flags;
+	
+	mute = mute ? 1 : 0;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->calibrate_mute == mute) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return;
+	}
+	if (!mute) {
+		snd_ad1848_dout(chip, AD1848_LEFT_INPUT, chip->image[AD1848_LEFT_INPUT]);
+		snd_ad1848_dout(chip, AD1848_RIGHT_INPUT, chip->image[AD1848_RIGHT_INPUT]);
+	}
+	snd_ad1848_dout(chip, AD1848_AUX1_LEFT_INPUT, mute ? 0x80 : chip->image[AD1848_AUX1_LEFT_INPUT]);
+	snd_ad1848_dout(chip, AD1848_AUX1_RIGHT_INPUT, mute ? 0x80 : chip->image[AD1848_AUX1_RIGHT_INPUT]);
+	snd_ad1848_dout(chip, AD1848_AUX2_LEFT_INPUT, mute ? 0x80 : chip->image[AD1848_AUX2_LEFT_INPUT]);
+	snd_ad1848_dout(chip, AD1848_AUX2_RIGHT_INPUT, mute ? 0x80 : chip->image[AD1848_AUX2_RIGHT_INPUT]);
+	snd_ad1848_dout(chip, AD1848_LEFT_OUTPUT, mute ? 0x80 : chip->image[AD1848_LEFT_OUTPUT]);
+	snd_ad1848_dout(chip, AD1848_RIGHT_OUTPUT, mute ? 0x80 : chip->image[AD1848_RIGHT_OUTPUT]);
+	chip->calibrate_mute = mute;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_ad1848_set_data_format(ad1848_t *chip, snd_pcm_hw_params_t *hw_params)
+{
+	if (hw_params == NULL) {
+		chip->image[AD1848_DATA_FORMAT] = 0x20;
+	} else {
+		chip->image[AD1848_DATA_FORMAT] =
+		    snd_ad1848_get_format(params_format(hw_params), params_channels(hw_params)) |
+		    snd_ad1848_get_rate(params_rate(hw_params));
+	}
+	// snd_printk(">>> pmode = 0x%x, dfr = 0x%x\n", pstr->mode, chip->image[AD1848_DATA_FORMAT]);
+}
+
+static int snd_ad1848_open(ad1848_t *chip, unsigned int mode)
+{
+	unsigned long flags;
+
+	down(&chip->open_mutex);
+	if (chip->mode & AD1848_MODE_OPEN) {
+		up(&chip->open_mutex);
+		return -EAGAIN;
+	}
+	snd_ad1848_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printk("open: (1)\n");
+#endif
+	snd_ad1848_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->image[AD1848_IFACE_CTRL] &= ~(AD1848_PLAYBACK_ENABLE | AD1848_PLAYBACK_PIO |
+			     AD1848_CAPTURE_ENABLE | AD1848_CAPTURE_PIO |
+			     AD1848_CALIB_MODE);
+	chip->image[AD1848_IFACE_CTRL] |= AD1848_AUTOCALIB;
+	snd_ad1848_out(chip, AD1848_IFACE_CTRL, chip->image[AD1848_IFACE_CTRL]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_ad1848_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printk("open: (2)\n");
+#endif
+
+	snd_ad1848_set_data_format(chip, NULL);
+
+	snd_ad1848_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_ad1848_out(chip, AD1848_DATA_FORMAT, chip->image[AD1848_DATA_FORMAT]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_ad1848_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printk("open: (3)\n");
+#endif
+
+	/* ok. now enable and ack CODEC IRQ */
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	outb(0, AD1848P(chip, STATUS));	/* clear IRQ */
+	outb(0, AD1848P(chip, STATUS));	/* clear IRQ */
+	chip->image[AD1848_PIN_CTRL] |= AD1848_IRQ_ENABLE;
+	snd_ad1848_out(chip, AD1848_PIN_CTRL, chip->image[AD1848_PIN_CTRL]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	chip->mode = mode;
+	up(&chip->open_mutex);
+
+	return 0;
+}
+
+static void snd_ad1848_close(ad1848_t *chip)
+{
+	unsigned long flags;
+
+	down(&chip->open_mutex);
+	if (!chip->mode) {
+		up(&chip->open_mutex);
+		return;
+	}
+	/* disable IRQ */
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	outb(0, AD1848P(chip, STATUS));	/* clear IRQ */
+	outb(0, AD1848P(chip, STATUS));	/* clear IRQ */
+	chip->image[AD1848_PIN_CTRL] &= ~AD1848_IRQ_ENABLE;
+	snd_ad1848_out(chip, AD1848_PIN_CTRL, chip->image[AD1848_PIN_CTRL]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	/* now disable capture & playback */
+
+	snd_ad1848_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->image[AD1848_IFACE_CTRL] &= ~(AD1848_PLAYBACK_ENABLE | AD1848_PLAYBACK_PIO |
+			     AD1848_CAPTURE_ENABLE | AD1848_CAPTURE_PIO);
+	snd_ad1848_out(chip, AD1848_IFACE_CTRL, chip->image[AD1848_IFACE_CTRL]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_ad1848_mce_down(chip);
+
+	/* clear IRQ again */
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	outb(0, AD1848P(chip, STATUS));	/* clear IRQ */
+	outb(0, AD1848P(chip, STATUS));	/* clear IRQ */
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	chip->mode = 0;
+	up(&chip->open_mutex);
+}
+
+/*
+ *  ok.. exported functions..
+ */
+
+static int snd_ad1848_playback_trigger(snd_pcm_substream_t * substream,
+				       int cmd)
+{
+	ad1848_t *chip = snd_pcm_substream_chip(substream);
+	return snd_ad1848_trigger(chip, AD1848_PLAYBACK_ENABLE, SNDRV_PCM_STREAM_PLAYBACK, cmd);
+}
+
+static int snd_ad1848_capture_trigger(snd_pcm_substream_t * substream,
+				      int cmd)
+{
+	ad1848_t *chip = snd_pcm_substream_chip(substream);
+	return snd_ad1848_trigger(chip, AD1848_CAPTURE_ENABLE, SNDRV_PCM_STREAM_CAPTURE, cmd);
+}
+
+static int snd_ad1848_playback_hw_params(snd_pcm_substream_t * substream,
+					 snd_pcm_hw_params_t * hw_params)
+{
+	ad1848_t *chip = snd_pcm_substream_chip(substream);
+	unsigned long flags;
+	int err;
+
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+	snd_ad1848_calibrate_mute(chip, 1);
+	snd_ad1848_set_data_format(chip, hw_params);
+	snd_ad1848_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_ad1848_out(chip, AD1848_DATA_FORMAT, chip->image[AD1848_DATA_FORMAT]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_ad1848_mce_down(chip);
+	snd_ad1848_calibrate_mute(chip, 0);
+	return 0;
+}
+
+static int snd_ad1848_playback_hw_free(snd_pcm_substream_t * substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_ad1848_playback_prepare(snd_pcm_substream_t * substream)
+{
+	ad1848_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned long flags;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	chip->dma_size = size;
+	chip->image[AD1848_IFACE_CTRL] &= ~(AD1848_PLAYBACK_ENABLE | AD1848_PLAYBACK_PIO);
+	snd_dma_program(chip->dma, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
+	count = snd_ad1848_get_count(chip->image[AD1848_DATA_FORMAT], count) - 1;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_ad1848_out(chip, AD1848_DATA_LWR_CNT, (unsigned char) count);
+	snd_ad1848_out(chip, AD1848_DATA_UPR_CNT, (unsigned char) (count >> 8));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_ad1848_capture_hw_params(snd_pcm_substream_t * substream,
+					snd_pcm_hw_params_t * hw_params)
+{
+	ad1848_t *chip = snd_pcm_substream_chip(substream);
+	unsigned long flags;
+	int err;
+
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+	snd_ad1848_calibrate_mute(chip, 1);
+	snd_ad1848_set_data_format(chip, hw_params);
+	snd_ad1848_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_ad1848_out(chip, AD1848_DATA_FORMAT, chip->image[AD1848_DATA_FORMAT]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_ad1848_mce_down(chip);
+	snd_ad1848_calibrate_mute(chip, 0);
+	return 0;
+}
+
+static int snd_ad1848_capture_hw_free(snd_pcm_substream_t * substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_ad1848_capture_prepare(snd_pcm_substream_t * substream)
+{
+	ad1848_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned long flags;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	chip->dma_size = size;
+	chip->image[AD1848_IFACE_CTRL] &= ~(AD1848_CAPTURE_ENABLE | AD1848_CAPTURE_PIO);
+	snd_dma_program(chip->dma, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
+	count = snd_ad1848_get_count(chip->image[AD1848_DATA_FORMAT], count) - 1;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_ad1848_out(chip, AD1848_DATA_LWR_CNT, (unsigned char) count);
+	snd_ad1848_out(chip, AD1848_DATA_UPR_CNT, (unsigned char) (count >> 8));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static irqreturn_t snd_ad1848_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	ad1848_t *chip = dev_id;
+
+	if ((chip->mode & AD1848_MODE_PLAY) && chip->playback_substream &&
+	    (chip->mode & AD1848_MODE_RUNNING))
+		snd_pcm_period_elapsed(chip->playback_substream);
+	if ((chip->mode & AD1848_MODE_CAPTURE) && chip->capture_substream &&
+	    (chip->mode & AD1848_MODE_RUNNING))
+		snd_pcm_period_elapsed(chip->capture_substream);
+	outb(0, AD1848P(chip, STATUS));	/* clear global interrupt bit */
+	return IRQ_HANDLED;
+}
+
+static snd_pcm_uframes_t snd_ad1848_playback_pointer(snd_pcm_substream_t * substream)
+{
+	ad1848_t *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	
+	if (!(chip->image[AD1848_IFACE_CTRL] & AD1848_PLAYBACK_ENABLE))
+		return 0;
+	ptr = snd_dma_pointer(chip->dma, chip->dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_ad1848_capture_pointer(snd_pcm_substream_t * substream)
+{
+	ad1848_t *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (!(chip->image[AD1848_IFACE_CTRL] & AD1848_CAPTURE_ENABLE))
+		return 0;
+	ptr = snd_dma_pointer(chip->dma, chip->dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+/*
+
+ */
+
+static void snd_ad1848_thinkpad_twiddle(ad1848_t *chip, int on) {
+
+	int tmp;
+
+	if (!chip->thinkpad_flag) return;
+
+	outb(0x1c, AD1848_THINKPAD_CTL_PORT1);
+	tmp = inb(AD1848_THINKPAD_CTL_PORT2);
+
+	if (on)
+		/* turn it on */
+		tmp |= AD1848_THINKPAD_CS4248_ENABLE_BIT;
+	else
+		/* turn it off */
+		tmp &= ~AD1848_THINKPAD_CS4248_ENABLE_BIT;
+	
+	outb(tmp, AD1848_THINKPAD_CTL_PORT2);
+
+}
+
+#ifdef CONFIG_PM
+static int snd_ad1848_suspend(snd_card_t *card, pm_message_t state)
+{
+	ad1848_t *chip = card->pm_private_data;
+
+	snd_pcm_suspend_all(chip->pcm);
+	/* FIXME: save registers? */
+
+	if (chip->thinkpad_flag)
+		snd_ad1848_thinkpad_twiddle(chip, 0);
+
+	return 0;
+}
+
+static int snd_ad1848_resume(snd_card_t *card)
+{
+	ad1848_t *chip = card->pm_private_data;
+
+	if (chip->thinkpad_flag)
+		snd_ad1848_thinkpad_twiddle(chip, 1);
+
+	/* FIXME: restore registers? */
+
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+static int snd_ad1848_probe(ad1848_t * chip)
+{
+	unsigned long flags;
+	int i, id, rev, ad1847;
+	unsigned char *ptr;
+
+#if 0
+	snd_ad1848_debug(chip);
+#endif
+	id = ad1847 = 0;
+	for (i = 0; i < 1000; i++) {
+		mb();
+		if (inb(AD1848P(chip, REGSEL)) & AD1848_INIT)
+			udelay(500);
+		else {
+			spin_lock_irqsave(&chip->reg_lock, flags);
+			snd_ad1848_out(chip, AD1848_MISC_INFO, 0x00);
+			snd_ad1848_out(chip, AD1848_LEFT_INPUT, 0xaa);
+			snd_ad1848_out(chip, AD1848_RIGHT_INPUT, 0x45);
+			rev = snd_ad1848_in(chip, AD1848_RIGHT_INPUT);
+			if (rev == 0x65) {
+				spin_unlock_irqrestore(&chip->reg_lock, flags);
+				id = 1;
+				ad1847 = 1;
+				break;
+			}
+			if (snd_ad1848_in(chip, AD1848_LEFT_INPUT) == 0xaa && rev == 0x45) {
+				spin_unlock_irqrestore(&chip->reg_lock, flags);
+				id = 1;
+				break;
+			}
+			spin_unlock_irqrestore(&chip->reg_lock, flags);
+		}
+	}
+	if (id != 1)
+		return -ENODEV;	/* no valid device found */
+	if (chip->hardware == AD1848_HW_DETECT) {
+		if (ad1847) {
+			chip->hardware = AD1848_HW_AD1847;
+		} else {
+			chip->hardware = AD1848_HW_AD1848;
+			rev = snd_ad1848_in(chip, AD1848_MISC_INFO);
+			if (rev & 0x80) {
+				chip->hardware = AD1848_HW_CS4248;
+			} else if ((rev & 0x0f) == 0x0a) {
+				snd_ad1848_out(chip, AD1848_MISC_INFO, 0x40);
+				for (i = 0; i < 16; ++i) {
+					if (snd_ad1848_in(chip, i) != snd_ad1848_in(chip, i + 16)) {
+						chip->hardware = AD1848_HW_CMI8330;
+						break;
+					}
+				}
+				snd_ad1848_out(chip, AD1848_MISC_INFO, 0x00);
+			}
+		}
+	}
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	inb(AD1848P(chip, STATUS));	/* clear any pendings IRQ */
+	outb(0, AD1848P(chip, STATUS));
+	mb();
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	chip->image[AD1848_MISC_INFO] = 0x00;
+	chip->image[AD1848_IFACE_CTRL] =
+	    (chip->image[AD1848_IFACE_CTRL] & ~AD1848_SINGLE_DMA) | AD1848_SINGLE_DMA;
+	ptr = (unsigned char *) &chip->image;
+	snd_ad1848_mce_down(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	for (i = 0; i < 16; i++)	/* ok.. fill all AD1848 registers */
+		snd_ad1848_out(chip, i, *ptr++);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_ad1848_mce_up(chip);
+	snd_ad1848_mce_down(chip);
+	return 0;		/* all things are ok.. */
+}
+
+/*
+
+ */
+
+static snd_pcm_hardware_t snd_ad1848_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		(SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
+				 SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE),
+	.rates =		SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5510,
+	.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,
+};
+
+static snd_pcm_hardware_t snd_ad1848_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		(SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
+				 SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE),
+	.rates =		SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5510,
+	.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,
+};
+
+/*
+
+ */
+
+static int snd_ad1848_playback_open(snd_pcm_substream_t * substream)
+{
+	ad1848_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int err;
+
+	if ((err = snd_ad1848_open(chip, AD1848_MODE_PLAY)) < 0)
+		return err;
+	chip->playback_substream = substream;
+	runtime->hw = snd_ad1848_playback;
+	snd_pcm_limit_isa_dma_size(chip->dma, &runtime->hw.buffer_bytes_max);
+	snd_pcm_limit_isa_dma_size(chip->dma, &runtime->hw.period_bytes_max);
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);
+	return 0;
+}
+
+static int snd_ad1848_capture_open(snd_pcm_substream_t * substream)
+{
+	ad1848_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int err;
+
+	if ((err = snd_ad1848_open(chip, AD1848_MODE_CAPTURE)) < 0)
+		return err;
+	chip->capture_substream = substream;
+	runtime->hw = snd_ad1848_capture;
+	snd_pcm_limit_isa_dma_size(chip->dma, &runtime->hw.buffer_bytes_max);
+	snd_pcm_limit_isa_dma_size(chip->dma, &runtime->hw.period_bytes_max);
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);
+	return 0;
+}
+
+static int snd_ad1848_playback_close(snd_pcm_substream_t * substream)
+{
+	ad1848_t *chip = snd_pcm_substream_chip(substream);
+
+	chip->mode &= ~AD1848_MODE_PLAY;
+	chip->playback_substream = NULL;
+	snd_ad1848_close(chip);
+	return 0;
+}
+
+static int snd_ad1848_capture_close(snd_pcm_substream_t * substream)
+{
+	ad1848_t *chip = snd_pcm_substream_chip(substream);
+
+	chip->mode &= ~AD1848_MODE_CAPTURE;
+	chip->capture_substream = NULL;
+	snd_ad1848_close(chip);
+	return 0;
+}
+
+static int snd_ad1848_free(ad1848_t *chip)
+{
+	if (chip->res_port) {
+		release_resource(chip->res_port);
+		kfree_nocheck(chip->res_port);
+	}
+	if (chip->irq >= 0)
+		free_irq(chip->irq, (void *) chip);
+	if (chip->dma >= 0) {
+		snd_dma_disable(chip->dma);
+		free_dma(chip->dma);
+	}
+	kfree(chip);
+	return 0;
+}
+
+static int snd_ad1848_dev_free(snd_device_t *device)
+{
+	ad1848_t *chip = device->device_data;
+	return snd_ad1848_free(chip);
+}
+
+static const char *snd_ad1848_chip_id(ad1848_t *chip)
+{
+	switch (chip->hardware) {
+	case AD1848_HW_AD1847:	return "AD1847";
+	case AD1848_HW_AD1848:	return "AD1848";
+	case AD1848_HW_CS4248:	return "CS4248";
+	case AD1848_HW_CMI8330: return "CMI8330/C3D";
+	default:		return "???";
+	}
+}
+
+int snd_ad1848_create(snd_card_t * card,
+		      unsigned long port,
+		      int irq, int dma,
+		      unsigned short hardware,
+		      ad1848_t ** rchip)
+{
+	static snd_device_ops_t ops = {
+		.dev_free =	snd_ad1848_dev_free,
+	};
+	ad1848_t *chip;
+	int err;
+
+	*rchip = NULL;
+	chip = kcalloc(1, sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+	spin_lock_init(&chip->reg_lock);
+	init_MUTEX(&chip->open_mutex);
+	chip->card = card;
+	chip->port = port;
+	chip->irq = -1;
+	chip->dma = -1;
+	chip->hardware = hardware;
+	memcpy(&chip->image, &snd_ad1848_original_image, sizeof(snd_ad1848_original_image));
+	
+	if ((chip->res_port = request_region(port, 4, "AD1848")) == NULL) {
+		snd_printk(KERN_ERR "ad1848: can't grab port 0x%lx\n", port);
+		snd_ad1848_free(chip);
+		return -EBUSY;
+	}
+	if (request_irq(irq, snd_ad1848_interrupt, SA_INTERRUPT, "AD1848", (void *) chip)) {
+		snd_printk(KERN_ERR "ad1848: can't grab IRQ %d\n", irq);
+		snd_ad1848_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = irq;
+	if (request_dma(dma, "AD1848")) {
+		snd_printk(KERN_ERR "ad1848: can't grab DMA %d\n", dma);
+		snd_ad1848_free(chip);
+		return -EBUSY;
+	}
+	chip->dma = dma;
+
+	if (hardware == AD1848_HW_THINKPAD) {
+		chip->thinkpad_flag = 1;
+		chip->hardware = AD1848_HW_DETECT; /* reset */
+		snd_ad1848_thinkpad_twiddle(chip, 1);
+		snd_card_set_isa_pm_callback(card, snd_ad1848_suspend, snd_ad1848_resume, chip);
+	}
+
+	if (snd_ad1848_probe(chip) < 0) {
+		snd_ad1848_free(chip);
+		return -ENODEV;
+	}
+
+	/* Register device */
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_ad1848_free(chip);
+		return err;
+	}
+
+	*rchip = chip;
+	return 0;
+}
+
+static snd_pcm_ops_t snd_ad1848_playback_ops = {
+	.open =		snd_ad1848_playback_open,
+	.close =	snd_ad1848_playback_close,
+	.ioctl =	snd_ad1848_ioctl,
+	.hw_params =	snd_ad1848_playback_hw_params,
+	.hw_free =	snd_ad1848_playback_hw_free,
+	.prepare =	snd_ad1848_playback_prepare,
+	.trigger =	snd_ad1848_playback_trigger,
+	.pointer =	snd_ad1848_playback_pointer,
+};
+
+static snd_pcm_ops_t snd_ad1848_capture_ops = {
+	.open =		snd_ad1848_capture_open,
+	.close =	snd_ad1848_capture_close,
+	.ioctl =	snd_ad1848_ioctl,
+	.hw_params =	snd_ad1848_capture_hw_params,
+	.hw_free =	snd_ad1848_capture_hw_free,
+	.prepare =	snd_ad1848_capture_prepare,
+	.trigger =	snd_ad1848_capture_trigger,
+	.pointer =	snd_ad1848_capture_pointer,
+};
+
+static void snd_ad1848_pcm_free(snd_pcm_t *pcm)
+{
+	ad1848_t *chip = pcm->private_data;
+	chip->pcm = NULL;
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+int snd_ad1848_pcm(ad1848_t *chip, int device, snd_pcm_t **rpcm)
+{
+	snd_pcm_t *pcm;
+	int err;
+
+	if ((err = snd_pcm_new(chip->card, "AD1848", device, 1, 1, &pcm)) < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ad1848_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ad1848_capture_ops);
+
+	pcm->private_free = snd_ad1848_pcm_free;
+	pcm->private_data = chip;
+	pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+	strcpy(pcm->name, snd_ad1848_chip_id(chip));
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, chip->dma > 3 ? 128*1024 : 64*1024);
+
+	chip->pcm = pcm;
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+const snd_pcm_ops_t *snd_ad1848_get_pcm_ops(int direction)
+{
+	return direction == SNDRV_PCM_STREAM_PLAYBACK ?
+		&snd_ad1848_playback_ops : &snd_ad1848_capture_ops;
+}
+
+/*
+ *  MIXER part
+ */
+
+static int snd_ad1848_info_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	static char *texts[4] = {
+		"Line", "Aux", "Mic", "Mix"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 2;
+	uinfo->value.enumerated.items = 4;
+	if (uinfo->value.enumerated.item > 3)
+		uinfo->value.enumerated.item = 3;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ad1848_get_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	ad1848_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.enumerated.item[0] = (chip->image[AD1848_LEFT_INPUT] & AD1848_MIXS_ALL) >> 6;
+	ucontrol->value.enumerated.item[1] = (chip->image[AD1848_RIGHT_INPUT] & AD1848_MIXS_ALL) >> 6;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_ad1848_put_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	ad1848_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned short left, right;
+	int change;
+	
+	if (ucontrol->value.enumerated.item[0] > 3 ||
+	    ucontrol->value.enumerated.item[1] > 3)
+		return -EINVAL;
+	left = ucontrol->value.enumerated.item[0] << 6;
+	right = ucontrol->value.enumerated.item[1] << 6;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	left = (chip->image[AD1848_LEFT_INPUT] & ~AD1848_MIXS_ALL) | left;
+	right = (chip->image[AD1848_RIGHT_INPUT] & ~AD1848_MIXS_ALL) | right;
+	change = left != chip->image[AD1848_LEFT_INPUT] ||
+	         right != chip->image[AD1848_RIGHT_INPUT];
+	snd_ad1848_out(chip, AD1848_LEFT_INPUT, left);
+	snd_ad1848_out(chip, AD1848_RIGHT_INPUT, right);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+static int snd_ad1848_info_single(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_ad1848_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	ad1848_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (chip->image[reg] >> shift) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_ad1848_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	ad1848_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned short val;
+	
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	val <<= shift;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val = (chip->image[reg] & ~(mask << shift)) | val;
+	change = val != chip->image[reg];
+	snd_ad1848_out(chip, reg, val);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+static int snd_ad1848_info_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_ad1848_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	ad1848_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (chip->image[left_reg] >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (chip->image[right_reg] >> shift_right) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+
+static int snd_ad1848_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	ad1848_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	unsigned short val1, val2;
+	
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (left_reg != right_reg) {
+		val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1;
+		val2 = (chip->image[right_reg] & ~(mask << shift_right)) | val2;
+		change = val1 != chip->image[left_reg] || val2 != chip->image[right_reg];
+		snd_ad1848_out(chip, left_reg, val1);
+		snd_ad1848_out(chip, right_reg, val2);
+	} else {
+		val1 = (chip->image[left_reg] & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2;
+		change = val1 != chip->image[left_reg];
+		snd_ad1848_out(chip, left_reg, val1);		
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+/*
+ */
+int snd_ad1848_add_ctl(ad1848_t *chip, const char *name, int index, int type, unsigned long value)
+{
+	static snd_kcontrol_new_t newctls[] = {
+		[AD1848_MIX_SINGLE] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_ad1848_info_single,
+			.get = snd_ad1848_get_single,
+			.put = snd_ad1848_put_single,
+		},
+		[AD1848_MIX_DOUBLE] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_ad1848_info_double,
+			.get = snd_ad1848_get_double,
+			.put = snd_ad1848_put_double,
+		},
+		[AD1848_MIX_CAPTURE] = {
+			.info = snd_ad1848_info_mux,
+			.get = snd_ad1848_get_mux,
+			.put = snd_ad1848_put_mux,
+		},
+	};
+	snd_kcontrol_t *ctl;
+	int err;
+
+	ctl = snd_ctl_new1(&newctls[type], chip);
+	if (! ctl)
+		return -ENOMEM;
+	strlcpy(ctl->id.name, name, sizeof(ctl->id.name));
+	ctl->id.index = index;
+	ctl->private_value = value;
+	if ((err = snd_ctl_add(chip->card, ctl)) < 0) {
+		snd_ctl_free_one(ctl);
+		return err;
+	}
+	return 0;
+}
+
+
+static struct ad1848_mix_elem snd_ad1848_controls[] = {
+AD1848_DOUBLE("PCM Playback Switch", 0, AD1848_LEFT_OUTPUT, AD1848_RIGHT_OUTPUT, 7, 7, 1, 1),
+AD1848_DOUBLE("PCM Playback Volume", 0, AD1848_LEFT_OUTPUT, AD1848_RIGHT_OUTPUT, 0, 0, 63, 1),
+AD1848_DOUBLE("Aux Playback Switch", 0, AD1848_AUX1_LEFT_INPUT, AD1848_AUX1_RIGHT_INPUT, 7, 7, 1, 1),
+AD1848_DOUBLE("Aux Playback Volume", 0, AD1848_AUX1_LEFT_INPUT, AD1848_AUX1_RIGHT_INPUT, 0, 0, 31, 1),
+AD1848_DOUBLE("Aux Playback Switch", 1, AD1848_AUX2_LEFT_INPUT, AD1848_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
+AD1848_DOUBLE("Aux Playback Volume", 1, AD1848_AUX2_LEFT_INPUT, AD1848_AUX2_RIGHT_INPUT, 0, 0, 31, 1),
+AD1848_DOUBLE("Capture Volume", 0, AD1848_LEFT_INPUT, AD1848_RIGHT_INPUT, 0, 0, 15, 0),
+{
+	.name = "Capture Source",
+	.type = AD1848_MIX_CAPTURE,
+},
+AD1848_SINGLE("Loopback Capture Switch", 0, AD1848_LOOPBACK, 0, 1, 0),
+AD1848_SINGLE("Loopback Capture Volume", 0, AD1848_LOOPBACK, 1, 63, 0)
+};
+                                        
+int snd_ad1848_mixer(ad1848_t *chip)
+{
+	snd_card_t *card;
+	snd_pcm_t *pcm;
+	unsigned int idx;
+	int err;
+
+	snd_assert(chip != NULL && chip->pcm != NULL, return -EINVAL);
+
+	pcm = chip->pcm;
+	card = chip->card;
+
+	strcpy(card->mixername, pcm->name);
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_ad1848_controls); idx++)
+		if ((err = snd_ad1848_add_ctl_elem(chip, &snd_ad1848_controls[idx])) < 0)
+			return err;
+
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_ad1848_out);
+EXPORT_SYMBOL(snd_ad1848_create);
+EXPORT_SYMBOL(snd_ad1848_pcm);
+EXPORT_SYMBOL(snd_ad1848_get_pcm_ops);
+EXPORT_SYMBOL(snd_ad1848_mixer);
+EXPORT_SYMBOL(snd_ad1848_add_ctl);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_ad1848_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_ad1848_exit(void)
+{
+}
+
+module_init(alsa_ad1848_init)
+module_exit(alsa_ad1848_exit)
diff --git a/sound/isa/als100.c b/sound/isa/als100.c
new file mode 100644
index 0000000..ac8f136
--- /dev/null
+++ b/sound/isa/als100.c
@@ -0,0 +1,336 @@
+
+/*
+    card-als100.c - driver for Avance Logic ALS100 based soundcards.
+    Copyright (C) 1999-2000 by Massimo Piccioni <dafastidio@libero.it>
+
+    Thanks to Pierfrancesco 'qM2' Passerini.
+
+    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/wait.h>
+#include <linux/time.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/sb.h>
+
+#define PFX "als100: "
+
+MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>");
+MODULE_DESCRIPTION("Avance Logic ALS1X0");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Avance Logic,ALS100 - PRO16PNP},"
+	        "{Avance Logic,ALS110},"
+	        "{Avance Logic,ALS120},"
+	        "{Avance Logic,ALS200},"
+	        "{3D Melody,MF1000},"
+	        "{Digimate,3D Sound},"
+	        "{Avance Logic,ALS120},"
+	        "{RTL,RTL3000}}");
+
+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_ISAPNP; /* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* PnP setup */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* PnP setup */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* PnP setup */
+static int dma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* PnP setup */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for als100 based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for als100 based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable als100 based soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for als100 driver.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for als100 driver.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for als100 driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for als100 driver.");
+module_param_array(mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for als100 driver.");
+module_param_array(dma8, int, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for als100 driver.");
+module_param_array(dma16, int, NULL, 0444);
+MODULE_PARM_DESC(dma16, "16-bit DMA # for als100 driver.");
+
+struct snd_card_als100 {
+	int dev_no;
+	struct pnp_dev *dev;
+	struct pnp_dev *devmpu;
+	struct pnp_dev *devopl;
+};
+
+static struct pnp_card_device_id snd_als100_pnpids[] = {
+	/* ALS100 - PRO16PNP */
+	{ .id = "ALS0001", .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" } } },
+	/* ALS110 - MF1000 - Digimate 3D Sound */
+	{ .id = "ALS0110", .devs = { { "@@@1001" }, { "@X@1001" }, { "@H@1001" } } },
+	/* ALS120 */
+	{ .id = "ALS0120", .devs = { { "@@@2001" }, { "@X@2001" }, { "@H@2001" } } },
+	/* ALS200 */
+	{ .id = "ALS0200", .devs = { { "@@@0020" }, { "@X@0020" }, { "@H@0001" } } },
+	/* ALS200 OEM */
+	{ .id = "ALS0200", .devs = { { "@@@0020" }, { "@X@0020" }, { "@H@0020" } } },
+	/* RTL3000 */
+	{ .id = "RTL3000", .devs = { { "@@@2001" }, { "@X@2001" }, { "@H@2001" } } },
+	{ .id = "", } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_als100_pnpids);
+
+#define DRIVER_NAME	"snd-card-als100"
+
+static int __devinit snd_card_als100_pnp(int dev, struct snd_card_als100 *acard,
+					 struct pnp_card_link *card,
+					 const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	struct pnp_resource_table *cfg = kmalloc(sizeof(*cfg), GFP_KERNEL);
+	int err;
+
+	if (!cfg)
+		return -ENOMEM;
+	acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->dev == NULL) {
+		kfree(cfg);
+		return -ENODEV;
+	}
+	acard->devmpu = pnp_request_card_device(card, id->devs[1].id, acard->dev);
+	acard->devopl = pnp_request_card_device(card, id->devs[2].id, acard->dev);
+
+	pdev = acard->dev;
+
+	pnp_init_resource_table(cfg);
+
+	/* override resources */
+	if (port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[0], port[dev], 16);
+	if (dma8[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[0], dma8[dev], 1);
+	if (dma16[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[1], dma16[dev], 1);
+	if (irq[dev] != SNDRV_AUTO_IRQ)
+		pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1);
+	if (pnp_manual_config_dev(pdev, cfg, 0) < 0)
+		snd_printk(KERN_ERR PFX "AUDIO the requested resources are invalid, using auto config\n");
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n");
+		kfree(cfg);
+		return err;
+	}
+	port[dev] = pnp_port_start(pdev, 0);
+	dma8[dev] = pnp_dma(pdev, 1);
+	dma16[dev] = pnp_dma(pdev, 0);
+	irq[dev] = pnp_irq(pdev, 0);
+
+	pdev = acard->devmpu;
+	if (pdev != NULL) {
+		pnp_init_resource_table(cfg);
+		if (mpu_port[dev] != SNDRV_AUTO_PORT)
+			pnp_resource_change(&cfg->port_resource[0], mpu_port[dev], 2);
+		if (mpu_irq[dev] != SNDRV_AUTO_IRQ)
+			pnp_resource_change(&cfg->irq_resource[0], mpu_irq[dev], 1);
+		if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0)
+			snd_printk(KERN_ERR PFX "MPU401 the requested resources are invalid, using auto config\n");
+		err = pnp_activate_dev(pdev);
+		if (err < 0)
+			goto __mpu_error;
+		mpu_port[dev] = pnp_port_start(pdev, 0);
+		mpu_irq[dev] = pnp_irq(pdev, 0);
+	} else {
+	     __mpu_error:
+	     	if (pdev) {
+		     	pnp_release_card_device(pdev);
+	     		snd_printk(KERN_ERR PFX "MPU401 pnp configure failure, skipping\n");
+	     	}
+	     	acard->devmpu = NULL;
+	     	mpu_port[dev] = -1;
+	}
+
+	pdev = acard->devopl;
+	if (pdev != NULL) {
+		pnp_init_resource_table(cfg);
+		if (fm_port[dev] != SNDRV_AUTO_PORT)
+			pnp_resource_change(&cfg->port_resource[0], fm_port[dev], 4);
+		if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0)
+			snd_printk(KERN_ERR PFX "OPL3 the requested resources are invalid, using auto config\n");
+		err = pnp_activate_dev(pdev);
+		if (err < 0)
+			goto __fm_error;
+		fm_port[dev] = pnp_port_start(pdev, 0);
+	} else {
+	      __fm_error:
+	     	if (pdev) {
+		     	pnp_release_card_device(pdev);
+	     		snd_printk(KERN_ERR PFX "OPL3 pnp configure failure, skipping\n");
+	     	}
+	     	acard->devopl = NULL;
+	     	fm_port[dev] = -1;
+	}
+
+	kfree(cfg);
+	return 0;
+}
+
+static int __init snd_card_als100_probe(int dev,
+					struct pnp_card_link *pcard,
+					const struct pnp_card_device_id *pid)
+{
+	int error;
+	sb_t *chip;
+	snd_card_t *card;
+	struct snd_card_als100 *acard;
+	opl3_t *opl3;
+
+	if ((card = snd_card_new(index[dev], id[dev], THIS_MODULE,
+				 sizeof(struct snd_card_als100))) == NULL)
+		return -ENOMEM;
+	acard = (struct snd_card_als100 *)card->private_data;
+
+	if ((error = snd_card_als100_pnp(dev, acard, pcard, pid))) {
+		snd_card_free(card);
+		return error;
+	}
+	snd_card_set_dev(card, &pcard->card->dev);
+
+	if ((error = snd_sbdsp_create(card, port[dev],
+				      irq[dev],
+				      snd_sb16dsp_interrupt,
+				      dma8[dev],
+				      dma16[dev],
+				      SB_HW_ALS100, &chip)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	strcpy(card->driver, "ALS100");
+	strcpy(card->shortname, "Avance Logic ALS100");
+	sprintf(card->longname, "%s, %s at 0x%lx, irq %d, dma %d&%d",
+		card->shortname, chip->name, chip->port,
+		irq[dev], dma8[dev], dma16[dev]);
+
+	if ((error = snd_sb16dsp_pcm(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if ((error = snd_sbmixer_new(chip)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) {
+		if (snd_mpu401_uart_new(card, 0, MPU401_HW_ALS100,
+					mpu_port[dev], 0, 
+					mpu_irq[dev], SA_INTERRUPT,
+					NULL) < 0)
+			snd_printk(KERN_ERR PFX "no MPU-401 device at 0x%lx\n", mpu_port[dev]);
+	}
+
+	if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+		if (snd_opl3_create(card,
+				    fm_port[dev], fm_port[dev] + 2,
+				    OPL3_HW_AUTO, 0, &opl3) < 0) {
+			snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx\n",
+				   fm_port[dev], fm_port[dev] + 2);
+		} else {
+			if ((error = snd_opl3_timer_new(opl3, 0, 1)) < 0) {
+				snd_card_free(card);
+				return error;
+			}
+			if ((error = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+				snd_card_free(card);
+				return error;
+			}
+		}
+	}
+
+	if ((error = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	return 0;
+}
+
+static int __devinit snd_als100_pnp_detect(struct pnp_card_link *card,
+					   const struct pnp_card_device_id *id)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev])
+			continue;
+		res = snd_card_als100_probe(dev, card, id);
+		if (res < 0)
+			return res;
+		dev++;
+		return 0;
+	}
+	return -ENODEV;
+}
+
+static void __devexit snd_als100_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_t *card = (snd_card_t *) pnp_get_card_drvdata(pcard);
+
+	snd_card_disconnect(card);
+	snd_card_free_in_thread(card);
+}
+
+static struct pnp_card_driver als100_pnpc_driver = {
+	.flags          = PNP_DRIVER_RES_DISABLE,
+        .name           = "als100",
+        .id_table       = snd_als100_pnpids,
+        .probe          = snd_als100_pnp_detect,
+        .remove         = __devexit_p(snd_als100_pnp_remove),
+};
+
+static int __init alsa_card_als100_init(void)
+{
+	int cards = 0;
+
+	cards += pnp_register_card_driver(&als100_pnpc_driver);
+#ifdef MODULE
+	if (!cards) {
+		pnp_unregister_card_driver(&als100_pnpc_driver);
+		snd_printk(KERN_ERR "no ALS100 based soundcards found\n");
+	}
+#endif
+	return cards ? 0 : -ENODEV;
+}
+
+static void __exit alsa_card_als100_exit(void)
+{
+	pnp_unregister_card_driver(&als100_pnpc_driver);
+}
+
+module_init(alsa_card_als100_init)
+module_exit(alsa_card_als100_exit)
diff --git a/sound/isa/azt2320.c b/sound/isa/azt2320.c
new file mode 100644
index 0000000..bb41c6ec
--- /dev/null
+++ b/sound/isa/azt2320.c
@@ -0,0 +1,366 @@
+/*
+    card-azt2320.c - driver for Aztech Systems AZT2320 based soundcards.
+    Copyright (C) 1999-2000 by Massimo Piccioni <dafastidio@libero.it>
+
+    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
+*/
+
+/*
+    This driver should provide support for most Aztech AZT2320 based cards.
+    Several AZT2316 chips are also supported/tested, but autoprobe doesn't
+    work: all module option have to be set.
+
+    No docs available for us at Aztech headquarters !!!   Unbelievable ...
+    No other help obtained.
+
+    Thanks to Rainer Wiesner <rainer.wiesner@01019freenet.de> for the WSS
+    activation method (full-duplex audio!).
+*/
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/cs4231.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+
+#define PFX "azt2320: "
+
+MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>");
+MODULE_DESCRIPTION("Aztech Systems AZT2320");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Aztech Systems,PRO16V},"
+		"{Aztech Systems,AZT2320},"
+		"{Aztech Systems,AZT3300},"
+		"{Aztech Systems,AZT2320},"
+		"{Aztech Systems,AZT3000}}");
+
+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_ISAPNP; /* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* Pnp setup */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* Pnp setup */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* PnP setup */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* PnP setup */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for azt2320 based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for azt2320 based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable azt2320 based soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for azt2320 driver.");
+module_param_array(wss_port, long, NULL, 0444);
+MODULE_PARM_DESC(wss_port, "WSS Port # for azt2320 driver.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for azt2320 driver.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for azt2320 driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for azt2320 driver.");
+module_param_array(mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for azt2320 driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "1st DMA # for azt2320 driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "2nd DMA # for azt2320 driver.");
+
+struct snd_card_azt2320 {
+	int dev_no;
+	struct pnp_dev *dev;
+	struct pnp_dev *devmpu;
+};
+
+static struct pnp_card_device_id snd_azt2320_pnpids[] = {
+	/* PRO16V */
+	{ .id = "AZT1008", .devs = { { "AZT1008" }, { "AZT2001" }, } },
+	/* Aztech Sound Galaxy 16 */
+	{ .id = "AZT2320", .devs = { { "AZT0001" }, { "AZT0002" }, } },
+	/* Packard Bell Sound III 336 AM/SP */
+	{ .id = "AZT3000", .devs = { { "AZT1003" }, { "AZT2001" }, } },
+	/* AT3300 */
+	{ .id = "AZT3002", .devs = { { "AZT1004" }, { "AZT2001" }, } },
+	/* --- */
+	{ .id = "AZT3005", .devs = { { "AZT1003" }, { "AZT2001" }, } },
+	/* --- */
+	{ .id = "AZT3011", .devs = { { "AZT1003" }, { "AZT2001" }, } },
+	{ .id = "" }	/* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_azt2320_pnpids);
+
+#define	DRIVER_NAME	"snd-card-azt2320"
+
+static int __devinit snd_card_azt2320_pnp(int dev, struct snd_card_azt2320 *acard,
+					  struct pnp_card_link *card,
+					  const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	struct pnp_resource_table * cfg = kmalloc(sizeof(struct pnp_resource_table), GFP_KERNEL);
+	int err;
+
+	if (!cfg)
+		return -ENOMEM;
+
+	acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->dev == NULL) {
+		kfree(cfg);
+		return -ENODEV;
+	}
+
+	acard->devmpu = pnp_request_card_device(card, id->devs[1].id, NULL);
+
+	pdev = acard->dev;
+	pnp_init_resource_table(cfg);
+
+	/* override resources */
+	if (port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[0], port[dev], 16);
+	if (fm_port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[1], fm_port[dev], 4);
+	if (wss_port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[2], wss_port[dev], 4);
+	if (dma1[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[0], dma1[dev], 1);
+	if (dma2[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[1], dma2[dev], 1);
+	if (irq[dev] != SNDRV_AUTO_IRQ)
+		pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1);
+	if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0)
+		snd_printk(KERN_ERR PFX "AUDIO the requested resources are invalid, using auto config\n");
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n");
+		kfree(cfg);
+		return err;
+	}
+	port[dev] = pnp_port_start(pdev, 0);
+	fm_port[dev] = pnp_port_start(pdev, 1);
+	wss_port[dev] = pnp_port_start(pdev, 2);
+	dma1[dev] = pnp_dma(pdev, 0);
+	dma2[dev] = pnp_dma(pdev, 1);
+	irq[dev] = pnp_irq(pdev, 0);
+
+	pdev = acard->devmpu;
+	if (pdev != NULL) {
+		pnp_init_resource_table(cfg);
+		if (mpu_port[dev] != SNDRV_AUTO_PORT)
+			pnp_resource_change(&cfg->port_resource[0], mpu_port[dev], 2);
+		if (mpu_irq[dev] != SNDRV_AUTO_IRQ)
+			pnp_resource_change(&cfg->irq_resource[0], mpu_irq[dev], 1);
+		if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0)
+			snd_printk(KERN_ERR PFX "MPU401 the requested resources are invalid, using auto config\n");
+		err = pnp_activate_dev(pdev);
+		if (err < 0)
+			goto __mpu_error;
+		mpu_port[dev] = pnp_port_start(pdev, 0);
+		mpu_irq[dev] = pnp_irq(pdev, 0);
+	} else {
+	     __mpu_error:
+	     	if (pdev) {
+		     	pnp_release_card_device(pdev);
+	     		snd_printk(KERN_ERR PFX "MPU401 pnp configure failure, skipping\n");
+	     	}
+	     	acard->devmpu = NULL;
+	     	mpu_port[dev] = -1;
+	}
+
+	kfree (cfg);
+	return 0;
+}
+
+/* same of snd_sbdsp_command by Jaroslav Kysela */
+static int __devinit snd_card_azt2320_command(unsigned long port, unsigned char val)
+{
+	int i;
+	unsigned long limit;
+
+	limit = jiffies + HZ / 10;
+	for (i = 50000; i && time_after(limit, jiffies); i--)
+		if (!(inb(port + 0x0c) & 0x80)) {
+			outb(val, port + 0x0c);
+			return 0;
+		}
+	return -EBUSY;
+}
+
+static int __devinit snd_card_azt2320_enable_wss(unsigned long port)
+{
+	int error;
+
+	if ((error = snd_card_azt2320_command(port, 0x09)))
+		return error;
+	if ((error = snd_card_azt2320_command(port, 0x00)))
+		return error;
+
+	mdelay(5);
+	return 0;
+}
+
+static int __devinit snd_card_azt2320_probe(int dev,
+					    struct pnp_card_link *pcard,
+					    const struct pnp_card_device_id *pid)
+{
+	int error;
+	snd_card_t *card;
+	struct snd_card_azt2320 *acard;
+	cs4231_t *chip;
+	opl3_t *opl3;
+
+	if ((card = snd_card_new(index[dev], id[dev], THIS_MODULE,
+				 sizeof(struct snd_card_azt2320))) == NULL)
+		return -ENOMEM;
+	acard = (struct snd_card_azt2320 *)card->private_data;
+
+	if ((error = snd_card_azt2320_pnp(dev, acard, pcard, pid))) {
+		snd_card_free(card);
+		return error;
+	}
+	snd_card_set_dev(card, &pcard->card->dev);
+
+	if ((error = snd_card_azt2320_enable_wss(port[dev]))) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if ((error = snd_cs4231_create(card, wss_port[dev], -1,
+				       irq[dev],
+				       dma1[dev],
+				       dma2[dev],
+				       CS4231_HW_DETECT, 0, &chip)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	strcpy(card->driver, "AZT2320");
+	strcpy(card->shortname, "Aztech AZT2320");
+	sprintf(card->longname, "%s, WSS at 0x%lx, irq %i, dma %i&%i",
+		card->shortname, chip->port, irq[dev], dma1[dev], dma2[dev]);
+
+	if ((error = snd_cs4231_pcm(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	if ((error = snd_cs4231_mixer(chip)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	if ((error = snd_cs4231_timer(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) {
+		if (snd_mpu401_uart_new(card, 0, MPU401_HW_AZT2320,
+				mpu_port[dev], 0,
+				mpu_irq[dev], SA_INTERRUPT,
+				NULL) < 0)
+			snd_printk(KERN_ERR PFX "no MPU-401 device at 0x%lx\n", mpu_port[dev]);
+	}
+
+	if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+		if (snd_opl3_create(card,
+				    fm_port[dev], fm_port[dev] + 2,
+				    OPL3_HW_AUTO, 0, &opl3) < 0) {
+			snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx\n",
+				   fm_port[dev], fm_port[dev] + 2);
+		} else {
+			if ((error = snd_opl3_timer_new(opl3, 1, 2)) < 0) {
+				snd_card_free(card);
+				return error;
+			}
+			if ((error = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+				snd_card_free(card);
+				return error;
+			}
+		}
+	}
+
+	if ((error = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	return 0;
+}
+
+static int __devinit snd_azt2320_pnp_detect(struct pnp_card_link *card,
+					    const struct pnp_card_device_id *id)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev])
+			continue;
+		res = snd_card_azt2320_probe(dev, card, id);
+		if (res < 0)
+			return res;
+		dev++;
+		return 0;
+	}
+        return -ENODEV;
+}
+
+static void __devexit snd_azt2320_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_t *card = (snd_card_t *) pnp_get_card_drvdata(pcard);
+
+	snd_card_disconnect(card);
+	snd_card_free_in_thread(card);
+}
+
+static struct pnp_card_driver azt2320_pnpc_driver = {
+	.flags          = PNP_DRIVER_RES_DISABLE,
+	.name           = "azt2320",
+	.id_table       = snd_azt2320_pnpids,
+	.probe          = snd_azt2320_pnp_detect,
+	.remove         = __devexit_p(snd_azt2320_pnp_remove),
+};
+
+static int __init alsa_card_azt2320_init(void)
+{
+	int cards = 0;
+
+	cards += pnp_register_card_driver(&azt2320_pnpc_driver);
+#ifdef MODULE
+	if (!cards) {
+		pnp_unregister_card_driver(&azt2320_pnpc_driver);
+		snd_printk(KERN_ERR "no AZT2320 based soundcards found\n");
+	}
+#endif
+	return cards ? 0 : -ENODEV;
+}
+
+static void __exit alsa_card_azt2320_exit(void)
+{
+	pnp_unregister_card_driver(&azt2320_pnpc_driver);
+}
+
+module_init(alsa_card_azt2320_init)
+module_exit(alsa_card_azt2320_exit)
diff --git a/sound/isa/cmi8330.c b/sound/isa/cmi8330.c
new file mode 100644
index 0000000..46776cc
--- /dev/null
+++ b/sound/isa/cmi8330.c
@@ -0,0 +1,633 @@
+/*
+ *  Driver for C-Media's CMI8330 soundcards.
+ *  Copyright (c) by George Talusan <gstalusan@uwaterloo.ca>
+ *    http://www.undergrad.math.uwaterloo.ca/~gstalusa
+ *
+ *   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
+ *
+ */
+
+/*
+ * NOTES
+ *
+ *  The extended registers contain mixer settings which are largely
+ *  untapped for the time being.
+ *
+ *  MPU401 and SPDIF are not supported yet.  I don't have the hardware
+ *  to aid in coding and testing, so I won't bother.
+ *
+ *  To quickly load the module,
+ *
+ *  modprobe -a snd-cmi8330 sbport=0x220 sbirq=5 sbdma8=1
+ *    sbdma16=5 wssport=0x530 wssirq=11 wssdma=0
+ *
+ *  This card has two mixers and two PCM devices.  I've cheesed it such
+ *  that recording and playback can be done through the same device.
+ *  The driver "magically" routes the capturing to the AD1848 codec,
+ *  and playback to the SB16 codec.  This allows for full-duplex mode
+ *  to some extent.
+ *  The utilities in alsa-utils are aware of both devices, so passing
+ *  the appropriate parameters to amixer and alsactl will give you
+ *  full control over both mixers.
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/ad1848.h>
+#include <sound/sb.h>
+#include <sound/initval.h>
+
+/*
+ */
+/* #define ENABLE_SB_MIXER */
+#define PLAYBACK_ON_SB
+
+/*
+ */
+MODULE_AUTHOR("George Talusan <gstalusan@uwaterloo.ca>");
+MODULE_DESCRIPTION("C-Media CMI8330");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{C-Media,CMI8330,isapnp:{CMI0001,@@@0001,@X@0001}}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP;
+#ifdef CONFIG_PNP
+static int isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long sbport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int sbirq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int sbdma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static int sbdma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static long wssport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int wssirq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int wssdma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for CMI8330 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string  for CMI8330 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable CMI8330 soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+
+module_param_array(sbport, long, NULL, 0444);
+MODULE_PARM_DESC(sbport, "Port # for CMI8330 SB driver.");
+module_param_array(sbirq, int, NULL, 0444);
+MODULE_PARM_DESC(sbirq, "IRQ # for CMI8330 SB driver.");
+module_param_array(sbdma8, int, NULL, 0444);
+MODULE_PARM_DESC(sbdma8, "DMA8 for CMI8330 SB driver.");
+module_param_array(sbdma16, int, NULL, 0444);
+MODULE_PARM_DESC(sbdma16, "DMA16 for CMI8330 SB driver.");
+
+module_param_array(wssport, long, NULL, 0444);
+MODULE_PARM_DESC(wssport, "Port # for CMI8330 WSS driver.");
+module_param_array(wssirq, int, NULL, 0444);
+MODULE_PARM_DESC(wssirq, "IRQ # for CMI8330 WSS driver.");
+module_param_array(wssdma, int, NULL, 0444);
+MODULE_PARM_DESC(wssdma, "DMA for CMI8330 WSS driver.");
+
+#define CMI8330_RMUX3D    16
+#define CMI8330_MUTEMUX   17
+#define CMI8330_OUTPUTVOL 18
+#define CMI8330_MASTVOL   19
+#define CMI8330_LINVOL    20
+#define CMI8330_CDINVOL   21
+#define CMI8330_WAVVOL    22
+#define CMI8330_RECMUX    23
+#define CMI8330_WAVGAIN   24
+#define CMI8330_LINGAIN   25
+#define CMI8330_CDINGAIN  26
+
+static unsigned char snd_cmi8330_image[((CMI8330_CDINGAIN)-16) + 1] =
+{
+	0x40,			/* 16 - recording mux (SB-mixer-enabled) */
+#ifdef ENABLE_SB_MIXER
+	0x40,			/* 17 - mute mux (Mode2) */
+#else
+	0x0,			/* 17 - mute mux */
+#endif
+	0x0,			/* 18 - vol */
+	0x0,			/* 19 - master volume */
+	0x0,			/* 20 - line-in volume */
+	0x0,			/* 21 - cd-in volume */
+	0x0,			/* 22 - wave volume */
+	0x0,			/* 23 - mute/rec mux */
+	0x0,			/* 24 - wave rec gain */
+	0x0,			/* 25 - line-in rec gain */
+	0x0			/* 26 - cd-in rec gain */
+};
+
+typedef int (*snd_pcm_open_callback_t)(snd_pcm_substream_t *);
+
+struct snd_cmi8330 {
+#ifdef CONFIG_PNP
+	struct pnp_dev *cap;
+	struct pnp_dev *play;
+#endif
+	snd_card_t *card;
+	ad1848_t *wss;
+	sb_t *sb;
+
+	snd_pcm_t *pcm;
+	struct snd_cmi8330_stream {
+		snd_pcm_ops_t ops;
+		snd_pcm_open_callback_t open;
+		void *private_data; /* sb or wss */
+	} streams[2];
+};
+
+static snd_card_t *snd_cmi8330_legacy[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+#ifdef CONFIG_PNP
+
+static struct pnp_card_device_id snd_cmi8330_pnpids[] = {
+	{ .id = "CMI0001", .devs = { { "@@@0001" }, { "@X@0001" } } },
+	{ .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_cmi8330_pnpids);
+
+#endif
+
+
+static struct ad1848_mix_elem snd_cmi8330_controls[] __initdata = {
+AD1848_DOUBLE("Master Playback Volume", 0, CMI8330_MASTVOL, CMI8330_MASTVOL, 4, 0, 15, 0),
+AD1848_SINGLE("Loud Playback Switch", 0, CMI8330_MUTEMUX, 6, 1, 1),
+AD1848_DOUBLE("PCM Playback Switch", 0, AD1848_LEFT_OUTPUT, AD1848_RIGHT_OUTPUT, 7, 7, 1, 1),
+AD1848_DOUBLE("PCM Playback Volume", 0, AD1848_LEFT_OUTPUT, AD1848_RIGHT_OUTPUT, 0, 0, 63, 1),
+AD1848_DOUBLE("Line Playback Switch", 0, CMI8330_MUTEMUX, CMI8330_MUTEMUX, 4, 3, 1, 0),
+AD1848_DOUBLE("Line Playback Volume", 0, CMI8330_LINVOL, CMI8330_LINVOL, 4, 0, 15, 0),
+AD1848_DOUBLE("Line Capture Switch", 0, CMI8330_RMUX3D, CMI8330_RMUX3D, 2, 1, 1, 0),
+AD1848_DOUBLE("Line Capture Volume", 0, CMI8330_LINGAIN, CMI8330_LINGAIN, 4, 0, 15, 0),
+AD1848_DOUBLE("CD Playback Switch", 0, CMI8330_MUTEMUX, CMI8330_MUTEMUX, 2, 1, 1, 0),
+AD1848_DOUBLE("CD Capture Switch", 0, CMI8330_RMUX3D, CMI8330_RMUX3D, 4, 3, 1, 0),
+AD1848_DOUBLE("CD Playback Volume", 0, CMI8330_CDINVOL, CMI8330_CDINVOL, 4, 0, 15, 0),
+AD1848_DOUBLE("CD Capture Volume", 0, CMI8330_CDINGAIN, CMI8330_CDINGAIN, 4, 0, 15, 0),
+AD1848_SINGLE("Mic Playback Switch", 0, CMI8330_MUTEMUX, 0, 1, 0),
+AD1848_SINGLE("Mic Playback Volume", 0, CMI8330_OUTPUTVOL, 0, 7, 0),
+AD1848_SINGLE("Mic Capture Switch", 0, CMI8330_RMUX3D, 0, 1, 0),
+AD1848_SINGLE("Mic Capture Volume", 0, CMI8330_OUTPUTVOL, 5, 7, 0),
+AD1848_DOUBLE("Wavetable Playback Switch", 0, CMI8330_RECMUX, CMI8330_RECMUX, 1, 0, 1, 0),
+AD1848_DOUBLE("Wavetable Playback Volume", 0, CMI8330_WAVVOL, CMI8330_WAVVOL, 4, 0, 15, 0),
+AD1848_DOUBLE("Wavetable Capture Switch", 0, CMI8330_RECMUX, CMI8330_RECMUX, 5, 4, 1, 0),
+AD1848_DOUBLE("Wavetable Capture Volume", 0, CMI8330_WAVGAIN, CMI8330_WAVGAIN, 4, 0, 15, 0),
+AD1848_SINGLE("3D Control - Switch", 0, CMI8330_RMUX3D, 5, 1, 1),
+AD1848_SINGLE("PC Speaker Playback Volume", 0, CMI8330_OUTPUTVOL, 3, 3, 0),
+AD1848_SINGLE("FM Playback Switch", 0, CMI8330_RECMUX, 3, 1, 1),
+AD1848_SINGLE("IEC958 Input Capture Switch", 0, CMI8330_RMUX3D, 7, 1, 1),
+AD1848_SINGLE("IEC958 Input Playback Switch", 0, CMI8330_MUTEMUX, 7, 1, 1),
+};
+
+#ifdef ENABLE_SB_MIXER
+static struct sbmix_elem cmi8330_sb_mixers[] __initdata = {
+SB_DOUBLE("SB Master Playback Volume", SB_DSP4_MASTER_DEV, (SB_DSP4_MASTER_DEV + 1), 3, 3, 31),
+SB_DOUBLE("Tone Control - Bass", SB_DSP4_BASS_DEV, (SB_DSP4_BASS_DEV + 1), 4, 4, 15),
+SB_DOUBLE("Tone Control - Treble", SB_DSP4_TREBLE_DEV, (SB_DSP4_TREBLE_DEV + 1), 4, 4, 15),
+SB_DOUBLE("SB PCM Playback Volume", SB_DSP4_PCM_DEV, (SB_DSP4_PCM_DEV + 1), 3, 3, 31),
+SB_DOUBLE("SB Synth Playback Volume", SB_DSP4_SYNTH_DEV, (SB_DSP4_SYNTH_DEV + 1), 3, 3, 31),
+SB_DOUBLE("SB CD Playback Switch", SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 2, 1, 1),
+SB_DOUBLE("SB CD Playback Volume", SB_DSP4_CD_DEV, (SB_DSP4_CD_DEV + 1), 3, 3, 31),
+SB_DOUBLE("SB Line Playback Switch", SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 4, 3, 1),
+SB_DOUBLE("SB Line Playback Volume", SB_DSP4_LINE_DEV, (SB_DSP4_LINE_DEV + 1), 3, 3, 31),
+SB_SINGLE("SB Mic Playback Switch", SB_DSP4_OUTPUT_SW, 0, 1),
+SB_SINGLE("SB Mic Playback Volume", SB_DSP4_MIC_DEV, 3, 31),
+SB_SINGLE("SB PC Speaker Volume", SB_DSP4_SPEAKER_DEV, 6, 3),
+SB_DOUBLE("SB Capture Volume", SB_DSP4_IGAIN_DEV, (SB_DSP4_IGAIN_DEV + 1), 6, 6, 3),
+SB_DOUBLE("SB Playback Volume", SB_DSP4_OGAIN_DEV, (SB_DSP4_OGAIN_DEV + 1), 6, 6, 3),
+SB_SINGLE("SB Mic Auto Gain", SB_DSP4_MIC_AGC, 0, 1),
+};
+
+static unsigned char cmi8330_sb_init_values[][2] __initdata = {
+	{ SB_DSP4_MASTER_DEV + 0, 0 },
+	{ SB_DSP4_MASTER_DEV + 1, 0 },
+	{ SB_DSP4_PCM_DEV + 0, 0 },
+	{ SB_DSP4_PCM_DEV + 1, 0 },
+	{ SB_DSP4_SYNTH_DEV + 0, 0 },
+	{ SB_DSP4_SYNTH_DEV + 1, 0 },
+	{ SB_DSP4_INPUT_LEFT, 0 },
+	{ SB_DSP4_INPUT_RIGHT, 0 },
+	{ SB_DSP4_OUTPUT_SW, 0 },
+	{ SB_DSP4_SPEAKER_DEV, 0 },
+};
+
+
+static int __devinit cmi8330_add_sb_mixers(sb_t *chip)
+{
+	int idx, err;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+	snd_sbmixer_write(chip, 0x00, 0x00);		/* mixer reset */
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+
+	/* mute and zero volume channels */
+	for (idx = 0; idx < ARRAY_SIZE(cmi8330_sb_init_values); idx++) {
+		spin_lock_irqsave(&chip->mixer_lock, flags);
+		snd_sbmixer_write(chip, cmi8330_sb_init_values[idx][0],
+				  cmi8330_sb_init_values[idx][1]);
+		spin_unlock_irqrestore(&chip->mixer_lock, flags);
+	}
+
+	for (idx = 0; idx < ARRAY_SIZE(cmi8330_sb_mixers); idx++) {
+		if ((err = snd_sbmixer_add_ctl_elem(chip, &cmi8330_sb_mixers[idx])) < 0)
+			return err;
+	}
+	return 0;
+}
+#endif
+
+static int __devinit snd_cmi8330_mixer(snd_card_t *card, struct snd_cmi8330 *acard)
+{
+	unsigned int idx;
+	int err;
+
+	strcpy(card->mixername, "CMI8330/C3D");
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_cmi8330_controls); idx++) {
+		if ((err = snd_ad1848_add_ctl_elem(acard->wss, &snd_cmi8330_controls[idx])) < 0)
+			return err;
+	}
+
+#ifdef ENABLE_SB_MIXER
+	if ((err = cmi8330_add_sb_mixers(acard->sb)) < 0)
+		return err;
+#endif
+	return 0;
+}
+
+#ifdef CONFIG_PNP
+static int __devinit snd_cmi8330_pnp(int dev, struct snd_cmi8330 *acard,
+				     struct pnp_card_link *card,
+				     const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	struct pnp_resource_table * cfg = kmalloc(sizeof(struct pnp_resource_table), GFP_KERNEL);
+	int err;
+
+	acard->cap = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->cap == NULL) {
+		kfree(cfg);
+		return -EBUSY;
+	}
+	acard->play = pnp_request_card_device(card, id->devs[1].id, NULL);
+	if (acard->play == NULL) {
+		kfree(cfg);
+		return -EBUSY;
+	}
+
+	pdev = acard->cap;
+	pnp_init_resource_table(cfg);
+	/* allocate AD1848 resources */
+	if (wssport[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[0], wssport[dev], 8);
+	if (wssdma[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[0], wssdma[dev], 1);
+	if (wssirq[dev] != SNDRV_AUTO_IRQ)
+		pnp_resource_change(&cfg->irq_resource[0], wssirq[dev], 1);
+
+	err = pnp_manual_config_dev(pdev, cfg, 0);
+	if (err < 0)
+		snd_printk(KERN_ERR "CMI8330/C3D (AD1848) PnP manual resources are invalid, using auto config\n");
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "CMI8330/C3D (AD1848) PnP configure failure\n");
+		kfree(cfg);
+		return -EBUSY;
+	}
+	wssport[dev] = pnp_port_start(pdev, 0);
+	wssdma[dev] = pnp_dma(pdev, 0);
+	wssirq[dev] = pnp_irq(pdev, 0);
+
+	/* allocate SB16 resources */
+	pdev = acard->play;
+	pnp_init_resource_table(cfg);
+	if (sbport[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[0], sbport[dev], 16);
+	if (sbdma8[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[0], sbdma8[dev], 1);
+	if (sbdma16[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[1], sbdma16[dev], 1);
+	if (sbirq[dev] != SNDRV_AUTO_IRQ)
+		pnp_resource_change(&cfg->irq_resource[0], sbirq[dev], 1);
+
+	err = pnp_manual_config_dev(pdev, cfg, 0);
+	if (err < 0)
+		snd_printk(KERN_ERR "CMI8330/C3D (SB16) PnP manual resources are invalid, using auto config\n");
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "CMI8330/C3D (SB16) PnP configure failure\n");
+		kfree(cfg);
+		return -EBUSY;
+	}
+	sbport[dev] = pnp_port_start(pdev, 0);
+	sbdma8[dev] = pnp_dma(pdev, 0);
+	sbdma16[dev] = pnp_dma(pdev, 1);
+	sbirq[dev] = pnp_irq(pdev, 0);
+
+	kfree(cfg);
+	return 0;
+}
+#endif
+
+/*
+ * PCM interface
+ *
+ * since we call the different chip interfaces for playback and capture
+ * directions, we need a trick.
+ *
+ * - copy the ops for each direction into a local record.
+ * - replace the open callback with the new one, which replaces the
+ *   substream->private_data with the corresponding chip instance
+ *   and calls again the original open callback of the chip.
+ *
+ */
+
+#ifdef PLAYBACK_ON_SB
+#define CMI_SB_STREAM	SNDRV_PCM_STREAM_PLAYBACK
+#define CMI_AD_STREAM	SNDRV_PCM_STREAM_CAPTURE
+#else
+#define CMI_SB_STREAM	SNDRV_PCM_STREAM_CAPTURE
+#define CMI_AD_STREAM	SNDRV_PCM_STREAM_PLAYBACK
+#endif
+
+static int snd_cmi8330_playback_open(snd_pcm_substream_t * substream)
+{
+	struct snd_cmi8330 *chip = snd_pcm_substream_chip(substream);
+
+	/* replace the private_data and call the original open callback */
+	substream->private_data = chip->streams[SNDRV_PCM_STREAM_PLAYBACK].private_data;
+	return chip->streams[SNDRV_PCM_STREAM_PLAYBACK].open(substream);
+}
+
+static int snd_cmi8330_capture_open(snd_pcm_substream_t * substream)
+{
+	struct snd_cmi8330 *chip = snd_pcm_substream_chip(substream);
+
+	/* replace the private_data and call the original open callback */
+	substream->private_data = chip->streams[SNDRV_PCM_STREAM_CAPTURE].private_data;
+	return chip->streams[SNDRV_PCM_STREAM_CAPTURE].open(substream);
+}
+
+static void snd_cmi8330_pcm_free(snd_pcm_t *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int __devinit snd_cmi8330_pcm(snd_card_t *card, struct snd_cmi8330 *chip)
+{
+	snd_pcm_t *pcm;
+	const snd_pcm_ops_t *ops;
+	int err;
+	static snd_pcm_open_callback_t cmi_open_callbacks[2] = {
+		snd_cmi8330_playback_open,
+		snd_cmi8330_capture_open
+	};
+
+	if ((err = snd_pcm_new(card, "CMI8330", 0, 1, 1, &pcm)) < 0)
+		return err;
+	strcpy(pcm->name, "CMI8330");
+	pcm->private_data = chip;
+	pcm->private_free = snd_cmi8330_pcm_free;
+	
+	/* SB16 */
+	ops = snd_sb16dsp_get_pcm_ops(CMI_SB_STREAM);
+	chip->streams[CMI_SB_STREAM].ops = *ops;
+	chip->streams[CMI_SB_STREAM].open = ops->open;
+	chip->streams[CMI_SB_STREAM].ops.open = cmi_open_callbacks[CMI_SB_STREAM];
+	chip->streams[CMI_SB_STREAM].private_data = chip->sb;
+
+	/* AD1848 */
+	ops = snd_ad1848_get_pcm_ops(CMI_AD_STREAM);
+	chip->streams[CMI_AD_STREAM].ops = *ops;
+	chip->streams[CMI_AD_STREAM].open = ops->open;
+	chip->streams[CMI_AD_STREAM].ops.open = cmi_open_callbacks[CMI_AD_STREAM];
+	chip->streams[CMI_AD_STREAM].private_data = chip->wss;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &chip->streams[SNDRV_PCM_STREAM_PLAYBACK].ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &chip->streams[SNDRV_PCM_STREAM_CAPTURE].ops);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, 128*1024);
+	chip->pcm = pcm;
+
+	return 0;
+}
+
+
+/*
+ */
+
+static int __devinit snd_cmi8330_probe(int dev,
+				       struct pnp_card_link *pcard,
+				       const struct pnp_card_device_id *pid)
+{
+	snd_card_t *card;
+	struct snd_cmi8330 *acard;
+	unsigned long flags;
+	int i, err;
+
+#ifdef CONFIG_PNP
+	if (!isapnp[dev]) {
+#endif
+		if (wssport[dev] == SNDRV_AUTO_PORT) {
+			snd_printk("specify wssport\n");
+			return -EINVAL;
+		}
+		if (sbport[dev] == SNDRV_AUTO_PORT) {
+			snd_printk("specify sbport\n");
+			return -EINVAL;
+		}
+#ifdef CONFIG_PNP
+	}
+#endif
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE,
+			    sizeof(struct snd_cmi8330));
+	if (card == NULL) {
+		snd_printk("could not get a new card\n");
+		return -ENOMEM;
+	}
+	acard = (struct snd_cmi8330 *)card->private_data;
+	acard->card = card;
+
+#ifdef CONFIG_PNP
+	if (isapnp[dev]) {
+		if ((err = snd_cmi8330_pnp(dev, acard, pcard, pid)) < 0) {
+			snd_printk("PnP detection failed\n");
+			snd_card_free(card);
+			return err;
+		}
+		snd_card_set_dev(card, &pcard->card->dev);
+	}
+#endif
+
+	if ((err = snd_ad1848_create(card,
+				     wssport[dev] + 4,
+				     wssirq[dev],
+				     wssdma[dev],
+				     AD1848_HW_DETECT,
+				     &acard->wss)) < 0) {
+		snd_printk("(AD1848) device busy??\n");
+		snd_card_free(card);
+		return err;
+	}
+	if (acard->wss->hardware != AD1848_HW_CMI8330) {
+		snd_printk("(AD1848) not found during probe\n");
+		snd_card_free(card);
+		return -ENODEV;
+	}
+
+	if ((err = snd_sbdsp_create(card, sbport[dev],
+				    sbirq[dev],
+				    snd_sb16dsp_interrupt,
+				    sbdma8[dev],
+				    sbdma16[dev],
+				    SB_HW_AUTO, &acard->sb)) < 0) {
+		snd_printk("(SB16) device busy??\n");
+		snd_card_free(card);
+		return err;
+	}
+	if (acard->sb->hardware != SB_HW_16) {
+		snd_printk("(SB16) not found during probe\n");
+		snd_card_free(card);
+		return -ENODEV;
+	}
+
+	spin_lock_irqsave(&acard->wss->reg_lock, flags);
+	snd_ad1848_out(acard->wss, AD1848_MISC_INFO, 0x40); /* switch on MODE2 */
+	for (i = CMI8330_RMUX3D; i <= CMI8330_CDINGAIN; i++)
+		snd_ad1848_out(acard->wss, i, snd_cmi8330_image[i - CMI8330_RMUX3D]);
+	spin_unlock_irqrestore(&acard->wss->reg_lock, flags);
+
+	if ((err = snd_cmi8330_mixer(card, acard)) < 0) {
+		snd_printk("failed to create mixers\n");
+		snd_card_free(card);
+		return err;
+	}
+
+	if ((err = snd_cmi8330_pcm(card, acard)) < 0) {
+		snd_printk("failed to create pcms\n");
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->driver, "CMI8330/C3D");
+	strcpy(card->shortname, "C-Media CMI8330/C3D");
+	sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+		card->shortname,
+		acard->wss->port,
+		wssirq[dev],
+		wssdma[dev]);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if (pcard)
+		pnp_set_card_drvdata(pcard, card);
+	else
+		snd_cmi8330_legacy[dev] = card;
+	return 0;
+}
+
+#ifdef CONFIG_PNP
+static int __devinit snd_cmi8330_pnp_detect(struct pnp_card_link *card,
+					    const struct pnp_card_device_id *id)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || !isapnp[dev])
+			continue;
+		res = snd_cmi8330_probe(dev, card, id);
+		if (res < 0)
+			return res;
+		dev++;
+		return 0;
+	}
+	return -ENODEV;
+}
+
+static void __devexit snd_cmi8330_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_t *card = (snd_card_t *) pnp_get_card_drvdata(pcard);
+
+	snd_card_disconnect(card);
+	snd_card_free_in_thread(card);
+}
+
+static struct pnp_card_driver cmi8330_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DISABLE,
+	.name = "cmi8330",
+	.id_table = snd_cmi8330_pnpids,
+	.probe = snd_cmi8330_pnp_detect,
+	.remove = __devexit_p(snd_cmi8330_pnp_remove),
+};
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_cmi8330_init(void)
+{
+	int dev, cards = 0;
+
+	for (dev = 0; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev])
+			continue;
+#ifdef CONFIG_PNP
+		if (isapnp[dev])
+			continue;
+#endif
+		if (snd_cmi8330_probe(dev, NULL, NULL) >= 0)
+			cards++;
+	}
+#ifdef CONFIG_PNP
+	cards += pnp_register_card_driver(&cmi8330_pnpc_driver);
+#endif
+
+	if (!cards) {
+#ifdef CONFIG_PNP
+		pnp_unregister_card_driver(&cmi8330_pnpc_driver);
+#endif
+#ifdef MODULE
+		snd_printk(KERN_ERR "CMI8330 not found or device busy\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_cmi8330_exit(void)
+{
+	int i;
+
+#ifdef CONFIG_PNP
+	/* PnP cards first */
+	pnp_unregister_card_driver(&cmi8330_pnpc_driver);
+#endif
+	for (i = 0; i < SNDRV_CARDS; i++)
+		snd_card_free(snd_cmi8330_legacy[i]);
+}
+
+module_init(alsa_card_cmi8330_init)
+module_exit(alsa_card_cmi8330_exit)
diff --git a/sound/isa/cs423x/Makefile b/sound/isa/cs423x/Makefile
new file mode 100644
index 0000000..d2afaea
--- /dev/null
+++ b/sound/isa/cs423x/Makefile
@@ -0,0 +1,25 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-cs4231-lib-objs := cs4231_lib.o
+snd-cs4236-lib-objs := cs4236_lib.o
+snd-cs4231-objs := cs4231.o
+snd-cs4232-objs := cs4232.o
+snd-cs4236-objs := cs4236.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_AZT2320) += snd-cs4231-lib.o
+obj-$(CONFIG_SND_OPL3SA2) += snd-cs4231-lib.o
+obj-$(CONFIG_SND_CS4231) += snd-cs4231.o snd-cs4231-lib.o
+obj-$(CONFIG_SND_CS4232) += snd-cs4232.o snd-cs4231-lib.o
+obj-$(CONFIG_SND_CS4236) += snd-cs4236.o snd-cs4236-lib.o snd-cs4231-lib.o
+obj-$(CONFIG_SND_GUSMAX) += snd-cs4231-lib.o
+obj-$(CONFIG_SND_INTERWAVE) += snd-cs4231-lib.o
+obj-$(CONFIG_SND_INTERWAVE_STB) += snd-cs4231-lib.o
+obj-$(CONFIG_SND_OPTI92X_CS4231) += snd-cs4231-lib.o
+obj-$(CONFIG_SND_WAVEFRONT) += snd-cs4231-lib.o
+obj-$(CONFIG_SND_SSCAPE) += snd-cs4231-lib.o
+
+obj-m := $(sort $(obj-m))
diff --git a/sound/isa/cs423x/cs4231.c b/sound/isa/cs423x/cs4231.c
new file mode 100644
index 0000000..7640837
--- /dev/null
+++ b/sound/isa/cs423x/cs4231.c
@@ -0,0 +1,169 @@
+/*
+ *  Generic driver for CS4231 chips
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  Originally the CS4232/CS4232A driver, modified for use on CS4231 by
+ *  Tugrul Galatali <galatalt@stuy.edu>
+ *
+ *   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/time.h>
+#include <linux/wait.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/cs4231.h>
+#include <sound/mpu401.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Generic CS4231");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Crystal Semiconductors,CS4231}}");
+
+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;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,11,12,15 */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3,5,6,7 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for CS4231 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for CS4231 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable CS4231 soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for CS4231 driver.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for CS4231 driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for CS4231 driver.");
+module_param_array(mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for CS4231 driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for CS4231 driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for CS4231 driver.");
+
+static snd_card_t *snd_cs4231_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+
+static int __init snd_card_cs4231_probe(int dev)
+{
+	snd_card_t *card;
+	struct snd_card_cs4231 *acard;
+	snd_pcm_t *pcm = NULL;
+	cs4231_t *chip;
+	int err;
+
+	if (port[dev] == SNDRV_AUTO_PORT) {
+		snd_printk("specify port\n");
+		return -EINVAL;
+	}
+	if (irq[dev] == SNDRV_AUTO_IRQ) {
+		snd_printk("specify irq\n");
+		return -EINVAL;
+	}
+	if (dma1[dev] == SNDRV_AUTO_DMA) {
+		snd_printk("specify dma1\n");
+		return -EINVAL;
+	}
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
+	if (card == NULL)
+		return -ENOMEM;
+	acard = (struct snd_card_cs4231 *)card->private_data;
+	if ((err = snd_cs4231_create(card, port[dev], -1,
+				     irq[dev],
+				     dma1[dev],
+				     dma2[dev],
+				     CS4231_HW_DETECT,
+				     0, &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if ((err = snd_cs4231_pcm(chip, 0, &pcm)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->driver, "CS4231");
+	strcpy(card->shortname, pcm->name);
+	sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+		pcm->name, chip->port, irq[dev], dma1[dev]);
+	if (dma2[dev] >= 0)
+		sprintf(card->longname + strlen(card->longname), "&%d", dma2[dev]);
+
+	if ((err = snd_cs4231_mixer(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_cs4231_timer(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) {
+		if (mpu_irq[dev] == SNDRV_AUTO_IRQ)
+			mpu_irq[dev] = -1;
+		if (snd_mpu401_uart_new(card, 0, MPU401_HW_CS4232,
+					mpu_port[dev], 0,
+					mpu_irq[dev],
+					mpu_irq[dev] >= 0 ? SA_INTERRUPT : 0,
+					NULL) < 0)
+			printk(KERN_ERR "cs4231: MPU401 not detected\n");
+	}
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	snd_cs4231_cards[dev] = card;
+	return 0;
+}
+
+static int __init alsa_card_cs4231_init(void)
+{
+	int dev, cards;
+
+	for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev]; dev++) {
+		if (snd_card_cs4231_probe(dev) >= 0)
+			cards++;
+	}
+	if (!cards) {
+#ifdef MODULE
+		printk(KERN_ERR "CS4231 soundcard not found or device busy\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_cs4231_exit(void)
+{
+	int idx;
+
+	for (idx = 0; idx < SNDRV_CARDS; idx++)
+		snd_card_free(snd_cs4231_cards[idx]);
+}
+
+module_init(alsa_card_cs4231_init)
+module_exit(alsa_card_cs4231_exit)
diff --git a/sound/isa/cs423x/cs4231_lib.c b/sound/isa/cs423x/cs4231_lib.c
new file mode 100644
index 0000000..3e7a2a3
--- /dev/null
+++ b/sound/isa/cs423x/cs4231_lib.c
@@ -0,0 +1,1964 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  Routines for control of CS4231(A)/CS4232/InterWave & compatible chips
+ *
+ *  Bugs:
+ *     - sometimes record brokes playback with WSS portion of 
+ *       Yamaha OPL3-SA3 chip
+ *     - CS4231 (GUS MAX) - still trouble with occasional noises
+ *                        - broken initialization?
+ *
+ *   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/delay.h>
+#include <linux/pm.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <sound/core.h>
+#include <sound/cs4231.h>
+#include <sound/pcm_params.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/irq.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Routines for control of CS4231(A)/CS4232/InterWave & compatible chips");
+MODULE_LICENSE("GPL");
+
+#if 0
+#define SNDRV_DEBUG_MCE
+#endif
+
+/*
+ *  Some variables
+ */
+
+static unsigned char freq_bits[14] = {
+	/* 5510 */	0x00 | CS4231_XTAL2,
+	/* 6620 */	0x0E | CS4231_XTAL2,
+	/* 8000 */	0x00 | CS4231_XTAL1,
+	/* 9600 */	0x0E | CS4231_XTAL1,
+	/* 11025 */	0x02 | CS4231_XTAL2,
+	/* 16000 */	0x02 | CS4231_XTAL1,
+	/* 18900 */	0x04 | CS4231_XTAL2,
+	/* 22050 */	0x06 | CS4231_XTAL2,
+	/* 27042 */	0x04 | CS4231_XTAL1,
+	/* 32000 */	0x06 | CS4231_XTAL1,
+	/* 33075 */	0x0C | CS4231_XTAL2,
+	/* 37800 */	0x08 | CS4231_XTAL2,
+	/* 44100 */	0x0A | CS4231_XTAL2,
+	/* 48000 */	0x0C | CS4231_XTAL1
+};
+
+static unsigned int rates[14] = {
+	5510, 6620, 8000, 9600, 11025, 16000, 18900, 22050,
+	27042, 32000, 33075, 37800, 44100, 48000
+};
+
+static snd_pcm_hw_constraint_list_t hw_constraints_rates = {
+	.count = 14,
+	.list = rates,
+	.mask = 0,
+};
+
+static int snd_cs4231_xrate(snd_pcm_runtime_t *runtime)
+{
+	return snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);
+}
+
+static unsigned char snd_cs4231_original_image[32] =
+{
+	0x00,			/* 00/00 - lic */
+	0x00,			/* 01/01 - ric */
+	0x9f,			/* 02/02 - la1ic */
+	0x9f,			/* 03/03 - ra1ic */
+	0x9f,			/* 04/04 - la2ic */
+	0x9f,			/* 05/05 - ra2ic */
+	0xbf,			/* 06/06 - loc */
+	0xbf,			/* 07/07 - roc */
+	0x20,			/* 08/08 - pdfr */
+	CS4231_AUTOCALIB,	/* 09/09 - ic */
+	0x00,			/* 0a/10 - pc */
+	0x00,			/* 0b/11 - ti */
+	CS4231_MODE2,		/* 0c/12 - mi */
+	0xfc,			/* 0d/13 - lbc */
+	0x00,			/* 0e/14 - pbru */
+	0x00,			/* 0f/15 - pbrl */
+	0x80,			/* 10/16 - afei */
+	0x01,			/* 11/17 - afeii */
+	0x9f,			/* 12/18 - llic */
+	0x9f,			/* 13/19 - rlic */
+	0x00,			/* 14/20 - tlb */
+	0x00,			/* 15/21 - thb */
+	0x00,			/* 16/22 - la3mic/reserved */
+	0x00,			/* 17/23 - ra3mic/reserved */
+	0x00,			/* 18/24 - afs */
+	0x00,			/* 19/25 - lamoc/version */
+	0xcf,			/* 1a/26 - mioc */
+	0x00,			/* 1b/27 - ramoc/reserved */
+	0x20,			/* 1c/28 - cdfr */
+	0x00,			/* 1d/29 - res4 */
+	0x00,			/* 1e/30 - cbru */
+	0x00,			/* 1f/31 - cbrl */
+};
+
+/*
+ *  Basic I/O functions
+ */
+
+#if !defined(EBUS_SUPPORT) && !defined(SBUS_SUPPORT)
+#define __CS4231_INLINE__ inline
+#else
+#define __CS4231_INLINE__ /* nothing */
+#endif
+
+static __CS4231_INLINE__ void cs4231_outb(cs4231_t *chip, u8 offset, u8 val)
+{
+#ifdef EBUS_SUPPORT
+	if (chip->ebus->flag) {
+		writeb(val, chip->port + (offset << 2));
+	} else {
+#endif
+#ifdef SBUS_SUPPORT
+		sbus_writeb(val, chip->port + (offset << 2));
+#endif
+#ifdef EBUS_SUPPORT
+	}
+#endif
+#ifdef LEGACY_SUPPORT
+	outb(val, chip->port + offset);
+#endif
+}
+
+static __CS4231_INLINE__ u8 cs4231_inb(cs4231_t *chip, u8 offset)
+{
+#ifdef EBUS_SUPPORT
+	if (chip->ebus_flag) {
+		return readb(chip->port + (offset << 2));
+	} else {
+#endif
+#ifdef SBUS_SUPPORT
+		return sbus_readb(chip->port + (offset << 2));
+#endif
+#ifdef EBUS_SUPPORT
+	}
+#endif
+#ifdef LEGACY_SUPPORT
+	return inb(chip->port + offset);
+#endif
+}
+
+static void snd_cs4231_outm(cs4231_t *chip, unsigned char reg,
+			    unsigned char mask, unsigned char value)
+{
+	int timeout;
+	unsigned char tmp;
+
+	for (timeout = 250;
+	     timeout > 0 && (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
+	     timeout--)
+	     	udelay(100);
+#ifdef CONFIG_SND_DEBUG
+	if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+		snd_printk("outm: auto calibration time out - reg = 0x%x, value = 0x%x\n", reg, value);
+#endif
+	if (chip->calibrate_mute) {
+		chip->image[reg] &= mask;
+		chip->image[reg] |= value;
+	} else {
+		cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg);
+		mb();
+		tmp = (chip->image[reg] & mask) | value;
+		cs4231_outb(chip, CS4231P(REG), tmp);
+		chip->image[reg] = tmp;
+		mb();
+	}
+}
+
+static void snd_cs4231_dout(cs4231_t *chip, unsigned char reg, unsigned char value)
+{
+	int timeout;
+
+	for (timeout = 250;
+	     timeout > 0 && (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
+	     timeout--)
+	     	udelay(10);
+	cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg);
+	cs4231_outb(chip, CS4231P(REG), value);
+	mb();
+}
+
+void snd_cs4231_out(cs4231_t *chip, unsigned char reg, unsigned char value)
+{
+	int timeout;
+
+	for (timeout = 250;
+	     timeout > 0 && (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
+	     timeout--)
+	     	udelay(100);
+#ifdef CONFIG_SND_DEBUG
+	if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+		snd_printk("out: auto calibration time out - reg = 0x%x, value = 0x%x\n", reg, value);
+#endif
+	cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg);
+	cs4231_outb(chip, CS4231P(REG), value);
+	chip->image[reg] = value;
+	mb();
+#if 0
+	printk("codec out - reg 0x%x = 0x%x\n", chip->mce_bit | reg, value);
+#endif
+}
+
+unsigned char snd_cs4231_in(cs4231_t *chip, unsigned char reg)
+{
+	int timeout;
+
+	for (timeout = 250;
+	     timeout > 0 && (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
+	     timeout--)
+	     	udelay(100);
+#ifdef CONFIG_SND_DEBUG
+	if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+		snd_printk("in: auto calibration time out - reg = 0x%x\n", reg);
+#endif
+	cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg);
+	mb();
+	return cs4231_inb(chip, CS4231P(REG));
+}
+
+void snd_cs4236_ext_out(cs4231_t *chip, unsigned char reg, unsigned char val)
+{
+	cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | 0x17);
+	cs4231_outb(chip, CS4231P(REG), reg | (chip->image[CS4236_EXT_REG] & 0x01));
+	cs4231_outb(chip, CS4231P(REG), val);
+	chip->eimage[CS4236_REG(reg)] = val;
+#if 0
+	printk("ext out : reg = 0x%x, val = 0x%x\n", reg, val);
+#endif
+}
+
+unsigned char snd_cs4236_ext_in(cs4231_t *chip, unsigned char reg)
+{
+	cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | 0x17);
+	cs4231_outb(chip, CS4231P(REG), reg | (chip->image[CS4236_EXT_REG] & 0x01));
+#if 1
+	return cs4231_inb(chip, CS4231P(REG));
+#else
+	{
+		unsigned char res;
+		res = cs4231_inb(chip, CS4231P(REG));
+		printk("ext in : reg = 0x%x, val = 0x%x\n", reg, res);
+		return res;
+	}
+#endif
+}
+
+#if 0
+
+static void snd_cs4231_debug(cs4231_t *chip)
+{
+	printk("CS4231 REGS:      INDEX = 0x%02x  ", cs4231_inb(chip, CS4231P(REGSEL)));
+	printk("                 STATUS = 0x%02x\n", cs4231_inb(chip, CS4231P(STATUS)));
+	printk("  0x00: left input      = 0x%02x  ", snd_cs4231_in(chip, 0x00));
+	printk("  0x10: alt 1 (CFIG 2)  = 0x%02x\n", snd_cs4231_in(chip, 0x10));
+	printk("  0x01: right input     = 0x%02x  ", snd_cs4231_in(chip, 0x01));
+	printk("  0x11: alt 2 (CFIG 3)  = 0x%02x\n", snd_cs4231_in(chip, 0x11));
+	printk("  0x02: GF1 left input  = 0x%02x  ", snd_cs4231_in(chip, 0x02));
+	printk("  0x12: left line in    = 0x%02x\n", snd_cs4231_in(chip, 0x12));
+	printk("  0x03: GF1 right input = 0x%02x  ", snd_cs4231_in(chip, 0x03));
+	printk("  0x13: right line in   = 0x%02x\n", snd_cs4231_in(chip, 0x13));
+	printk("  0x04: CD left input   = 0x%02x  ", snd_cs4231_in(chip, 0x04));
+	printk("  0x14: timer low       = 0x%02x\n", snd_cs4231_in(chip, 0x14));
+	printk("  0x05: CD right input  = 0x%02x  ", snd_cs4231_in(chip, 0x05));
+	printk("  0x15: timer high      = 0x%02x\n", snd_cs4231_in(chip, 0x15));
+	printk("  0x06: left output     = 0x%02x  ", snd_cs4231_in(chip, 0x06));
+	printk("  0x16: left MIC (PnP)  = 0x%02x\n", snd_cs4231_in(chip, 0x16));
+	printk("  0x07: right output    = 0x%02x  ", snd_cs4231_in(chip, 0x07));
+	printk("  0x17: right MIC (PnP) = 0x%02x\n", snd_cs4231_in(chip, 0x17));
+	printk("  0x08: playback format = 0x%02x  ", snd_cs4231_in(chip, 0x08));
+	printk("  0x18: IRQ status      = 0x%02x\n", snd_cs4231_in(chip, 0x18));
+	printk("  0x09: iface (CFIG 1)  = 0x%02x  ", snd_cs4231_in(chip, 0x09));
+	printk("  0x19: left line out   = 0x%02x\n", snd_cs4231_in(chip, 0x19));
+	printk("  0x0a: pin control     = 0x%02x  ", snd_cs4231_in(chip, 0x0a));
+	printk("  0x1a: mono control    = 0x%02x\n", snd_cs4231_in(chip, 0x1a));
+	printk("  0x0b: init & status   = 0x%02x  ", snd_cs4231_in(chip, 0x0b));
+	printk("  0x1b: right line out  = 0x%02x\n", snd_cs4231_in(chip, 0x1b));
+	printk("  0x0c: revision & mode = 0x%02x  ", snd_cs4231_in(chip, 0x0c));
+	printk("  0x1c: record format   = 0x%02x\n", snd_cs4231_in(chip, 0x1c));
+	printk("  0x0d: loopback        = 0x%02x  ", snd_cs4231_in(chip, 0x0d));
+	printk("  0x1d: var freq (PnP)  = 0x%02x\n", snd_cs4231_in(chip, 0x1d));
+	printk("  0x0e: ply upr count   = 0x%02x  ", snd_cs4231_in(chip, 0x0e));
+	printk("  0x1e: ply lwr count   = 0x%02x\n", snd_cs4231_in(chip, 0x1e));
+	printk("  0x0f: rec upr count   = 0x%02x  ", snd_cs4231_in(chip, 0x0f));
+	printk("  0x1f: rec lwr count   = 0x%02x\n", snd_cs4231_in(chip, 0x1f));
+}
+
+#endif
+
+/*
+ *  CS4231 detection / MCE routines
+ */
+
+static void snd_cs4231_busy_wait(cs4231_t *chip)
+{
+	int timeout;
+
+	/* huh.. looks like this sequence is proper for CS4231A chip (GUS MAX) */
+	for (timeout = 5; timeout > 0; timeout--)
+		cs4231_inb(chip, CS4231P(REGSEL));
+	/* end of cleanup sequence */
+	for (timeout = 250;
+	     timeout > 0 && (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
+	     timeout--)
+	     	udelay(10);
+}
+
+void snd_cs4231_mce_up(cs4231_t *chip)
+{
+	unsigned long flags;
+	int timeout;
+
+	for (timeout = 250; timeout > 0 && (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT); timeout--)
+		udelay(100);
+#ifdef CONFIG_SND_DEBUG
+	if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+		snd_printk("mce_up - auto calibration time out (0)\n");
+#endif
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->mce_bit |= CS4231_MCE;
+	timeout = cs4231_inb(chip, CS4231P(REGSEL));
+	if (timeout == 0x80)
+		snd_printk("mce_up [0x%lx]: serious init problem - codec still busy\n", chip->port);
+	if (!(timeout & CS4231_MCE))
+		cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+void snd_cs4231_mce_down(cs4231_t *chip)
+{
+	unsigned long flags;
+	int timeout;
+
+	snd_cs4231_busy_wait(chip);
+#if 0
+	printk("(1) timeout = %i\n", timeout);
+#endif
+#ifdef CONFIG_SND_DEBUG
+	if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+		snd_printk("mce_down [0x%lx] - auto calibration time out (0)\n", (long)CS4231P(REGSEL));
+#endif
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->mce_bit &= ~CS4231_MCE;
+	timeout = cs4231_inb(chip, CS4231P(REGSEL));
+	cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (timeout == 0x80)
+		snd_printk("mce_down [0x%lx]: serious init problem - codec still busy\n", chip->port);
+	if ((timeout & CS4231_MCE) == 0 ||
+	    !(chip->hardware & (CS4231_HW_CS4231_MASK | CS4231_HW_CS4232_MASK))) {
+		return;
+	}
+	snd_cs4231_busy_wait(chip);
+
+	/* calibration process */
+
+	for (timeout = 500; timeout > 0 && (snd_cs4231_in(chip, CS4231_TEST_INIT) & CS4231_CALIB_IN_PROGRESS) == 0; timeout--)
+		udelay(10);
+	if ((snd_cs4231_in(chip, CS4231_TEST_INIT) & CS4231_CALIB_IN_PROGRESS) == 0) {
+		snd_printd("cs4231_mce_down - auto calibration time out (1)\n");
+		return;
+	}
+#if 0
+	printk("(2) timeout = %i, jiffies = %li\n", timeout, jiffies);
+#endif
+	/* in 10 ms increments, check condition, up to 250 ms */
+	timeout = 25;
+	while (snd_cs4231_in(chip, CS4231_TEST_INIT) & CS4231_CALIB_IN_PROGRESS) {
+		if (--timeout < 0) {
+			snd_printk("mce_down - auto calibration time out (2)\n");
+			return;
+		}
+		msleep(10);
+	}
+#if 0
+	printk("(3) jiffies = %li\n", jiffies);
+#endif
+	/* in 10 ms increments, check condition, up to 100 ms */
+	timeout = 10;
+	while (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) {
+		if (--timeout < 0) {
+			snd_printk(KERN_ERR "mce_down - auto calibration time out (3)\n");
+			return;
+		}
+		msleep(10);
+	}
+#if 0
+	printk("(4) jiffies = %li\n", jiffies);
+	snd_printk("mce_down - exit = 0x%x\n", cs4231_inb(chip, CS4231P(REGSEL)));
+#endif
+}
+
+static unsigned int snd_cs4231_get_count(unsigned char format, unsigned int size)
+{
+	switch (format & 0xe0) {
+	case CS4231_LINEAR_16:
+	case CS4231_LINEAR_16_BIG:
+		size >>= 1;
+		break;
+	case CS4231_ADPCM_16:
+		return size >> 2;
+	}
+	if (format & CS4231_STEREO)
+		size >>= 1;
+	return size;
+}
+
+static int snd_cs4231_trigger(snd_pcm_substream_t *substream,
+			      int cmd)
+{
+	cs4231_t *chip = snd_pcm_substream_chip(substream);
+	int result = 0;
+	unsigned int what;
+	struct list_head *pos;
+	snd_pcm_substream_t *s;
+	int do_start;
+
+#if 0
+	printk("codec trigger!!! - what = %i, enable = %i, status = 0x%x\n", what, enable, cs4231_inb(chip, CS4231P(STATUS)));
+#endif
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		do_start = 1; break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		do_start = 0; break;
+	default:
+		return -EINVAL;
+	}
+
+	what = 0;
+	snd_pcm_group_for_each(pos, substream) {
+		s = snd_pcm_group_substream_entry(pos);
+		if (s == chip->playback_substream) {
+			what |= CS4231_PLAYBACK_ENABLE;
+			snd_pcm_trigger_done(s, substream);
+		} else if (s == chip->capture_substream) {
+			what |= CS4231_RECORD_ENABLE;
+			snd_pcm_trigger_done(s, substream);
+		}
+	}
+	spin_lock(&chip->reg_lock);
+	if (do_start) {
+		chip->image[CS4231_IFACE_CTRL] |= what;
+		if (chip->trigger)
+			chip->trigger(chip, what, 1);
+	} else {
+		chip->image[CS4231_IFACE_CTRL] &= ~what;
+		if (chip->trigger)
+			chip->trigger(chip, what, 0);
+	}
+	snd_cs4231_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
+	spin_unlock(&chip->reg_lock);
+#if 0
+	snd_cs4231_debug(chip);
+#endif
+	return result;
+}
+
+/*
+ *  CODEC I/O
+ */
+
+static unsigned char snd_cs4231_get_rate(unsigned int rate)
+{
+	int i;
+
+	for (i = 0; i < 14; i++)
+		if (rate == rates[i])
+			return freq_bits[i];
+	// snd_BUG();
+	return freq_bits[13];
+}
+
+static unsigned char snd_cs4231_get_format(cs4231_t *chip,
+				           int format,
+                                           int channels)
+{
+	unsigned char rformat;
+
+	rformat = CS4231_LINEAR_8;
+	switch (format) {
+	case SNDRV_PCM_FORMAT_MU_LAW:	rformat = CS4231_ULAW_8; break;
+	case SNDRV_PCM_FORMAT_A_LAW:	rformat = CS4231_ALAW_8; break;
+	case SNDRV_PCM_FORMAT_S16_LE:	rformat = CS4231_LINEAR_16; break;
+	case SNDRV_PCM_FORMAT_S16_BE:	rformat = CS4231_LINEAR_16_BIG; break;
+	case SNDRV_PCM_FORMAT_IMA_ADPCM:	rformat = CS4231_ADPCM_16; break;
+	}
+	if (channels > 1)
+		rformat |= CS4231_STEREO;
+#if 0
+	snd_printk("get_format: 0x%x (mode=0x%x)\n", format, mode);
+#endif
+	return rformat;
+}
+
+static void snd_cs4231_calibrate_mute(cs4231_t *chip, int mute)
+{
+	unsigned long flags;
+
+	mute = mute ? 1 : 0;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->calibrate_mute == mute) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return;
+	}
+	if (!mute) {
+		snd_cs4231_dout(chip, CS4231_LEFT_INPUT, chip->image[CS4231_LEFT_INPUT]);
+		snd_cs4231_dout(chip, CS4231_RIGHT_INPUT, chip->image[CS4231_RIGHT_INPUT]);
+		snd_cs4231_dout(chip, CS4231_LOOPBACK, chip->image[CS4231_LOOPBACK]);
+	}
+	snd_cs4231_dout(chip, CS4231_AUX1_LEFT_INPUT, mute ? 0x80 : chip->image[CS4231_AUX1_LEFT_INPUT]);
+	snd_cs4231_dout(chip, CS4231_AUX1_RIGHT_INPUT, mute ? 0x80 : chip->image[CS4231_AUX1_RIGHT_INPUT]);
+	snd_cs4231_dout(chip, CS4231_AUX2_LEFT_INPUT, mute ? 0x80 : chip->image[CS4231_AUX2_LEFT_INPUT]);
+	snd_cs4231_dout(chip, CS4231_AUX2_RIGHT_INPUT, mute ? 0x80 : chip->image[CS4231_AUX2_RIGHT_INPUT]);
+	snd_cs4231_dout(chip, CS4231_LEFT_OUTPUT, mute ? 0x80 : chip->image[CS4231_LEFT_OUTPUT]);
+	snd_cs4231_dout(chip, CS4231_RIGHT_OUTPUT, mute ? 0x80 : chip->image[CS4231_RIGHT_OUTPUT]);
+	snd_cs4231_dout(chip, CS4231_LEFT_LINE_IN, mute ? 0x80 : chip->image[CS4231_LEFT_LINE_IN]);
+	snd_cs4231_dout(chip, CS4231_RIGHT_LINE_IN, mute ? 0x80 : chip->image[CS4231_RIGHT_LINE_IN]);
+	snd_cs4231_dout(chip, CS4231_MONO_CTRL, mute ? 0xc0 : chip->image[CS4231_MONO_CTRL]);
+	if (chip->hardware == CS4231_HW_INTERWAVE) {
+		snd_cs4231_dout(chip, CS4231_LEFT_MIC_INPUT, mute ? 0x80 : chip->image[CS4231_LEFT_MIC_INPUT]);
+		snd_cs4231_dout(chip, CS4231_RIGHT_MIC_INPUT, mute ? 0x80 : chip->image[CS4231_RIGHT_MIC_INPUT]);		
+		snd_cs4231_dout(chip, CS4231_LINE_LEFT_OUTPUT, mute ? 0x80 : chip->image[CS4231_LINE_LEFT_OUTPUT]);
+		snd_cs4231_dout(chip, CS4231_LINE_RIGHT_OUTPUT, mute ? 0x80 : chip->image[CS4231_LINE_RIGHT_OUTPUT]);
+	}
+	chip->calibrate_mute = mute;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_cs4231_playback_format(cs4231_t *chip,
+				       snd_pcm_hw_params_t *params,
+				       unsigned char pdfr)
+{
+	unsigned long flags;
+	int full_calib = 1;
+
+	down(&chip->mce_mutex);
+	snd_cs4231_calibrate_mute(chip, 1);
+	if (chip->hardware == CS4231_HW_CS4231A ||
+	    (chip->hardware & CS4231_HW_CS4232_MASK)) {
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		if ((chip->image[CS4231_PLAYBK_FORMAT] & 0x0f) == (pdfr & 0x0f)) {	/* rate is same? */
+			snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] | 0x10);
+			snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, chip->image[CS4231_PLAYBK_FORMAT] = pdfr);
+			snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] &= ~0x10);
+			udelay(100); /* Fixes audible clicks at least on GUS MAX */
+			full_calib = 0;
+		}
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+	}
+	if (full_calib) {
+		snd_cs4231_mce_up(chip);
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		if (chip->hardware != CS4231_HW_INTERWAVE && !chip->single_dma) {
+			snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT,
+					(chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE) ?
+					(pdfr & 0xf0) | (chip->image[CS4231_REC_FORMAT] & 0x0f) :
+				        pdfr);
+		} else {
+			snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, chip->image[CS4231_PLAYBK_FORMAT] = pdfr);
+		}
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		snd_cs4231_mce_down(chip);
+	}
+	snd_cs4231_calibrate_mute(chip, 0);
+	up(&chip->mce_mutex);
+}
+
+static void snd_cs4231_capture_format(cs4231_t *chip,
+				      snd_pcm_hw_params_t *params,
+                                      unsigned char cdfr)
+{
+	unsigned long flags;
+	int full_calib = 1;
+
+	down(&chip->mce_mutex);
+	snd_cs4231_calibrate_mute(chip, 1);
+	if (chip->hardware == CS4231_HW_CS4231A ||
+	    (chip->hardware & CS4231_HW_CS4232_MASK)) {
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		if ((chip->image[CS4231_PLAYBK_FORMAT] & 0x0f) == (cdfr & 0x0f) ||	/* rate is same? */
+		    (chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) {
+			snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] | 0x20);
+			snd_cs4231_out(chip, CS4231_REC_FORMAT, chip->image[CS4231_REC_FORMAT] = cdfr);
+			snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] &= ~0x20);
+			full_calib = 0;
+		}
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+	}
+	if (full_calib) {
+		snd_cs4231_mce_up(chip);
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		if (chip->hardware != CS4231_HW_INTERWAVE) {
+			if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) {
+				snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT,
+					       ((chip->single_dma ? cdfr : chip->image[CS4231_PLAYBK_FORMAT]) & 0xf0) |
+					       (cdfr & 0x0f));
+				spin_unlock_irqrestore(&chip->reg_lock, flags);
+				snd_cs4231_mce_down(chip);
+				snd_cs4231_mce_up(chip);
+				spin_lock_irqsave(&chip->reg_lock, flags);
+			}
+		}
+		snd_cs4231_out(chip, CS4231_REC_FORMAT, cdfr);
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		snd_cs4231_mce_down(chip);
+	}
+	snd_cs4231_calibrate_mute(chip, 0);
+	up(&chip->mce_mutex);
+}
+
+/*
+ *  Timer interface
+ */
+
+static unsigned long snd_cs4231_timer_resolution(snd_timer_t * timer)
+{
+	cs4231_t *chip = snd_timer_chip(timer);
+	if (chip->hardware & CS4231_HW_CS4236B_MASK)
+		return 14467;
+	else
+		return chip->image[CS4231_PLAYBK_FORMAT] & 1 ? 9969 : 9920;
+}
+
+static int snd_cs4231_timer_start(snd_timer_t * timer)
+{
+	unsigned long flags;
+	unsigned int ticks;
+	cs4231_t *chip = snd_timer_chip(timer);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ticks = timer->sticks;
+	if ((chip->image[CS4231_ALT_FEATURE_1] & CS4231_TIMER_ENABLE) == 0 ||
+	    (unsigned char)(ticks >> 8) != chip->image[CS4231_TIMER_HIGH] ||
+	    (unsigned char)ticks != chip->image[CS4231_TIMER_LOW]) {
+		snd_cs4231_out(chip, CS4231_TIMER_HIGH, chip->image[CS4231_TIMER_HIGH] = (unsigned char) (ticks >> 8));
+		snd_cs4231_out(chip, CS4231_TIMER_LOW, chip->image[CS4231_TIMER_LOW] = (unsigned char) ticks);
+		snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] | CS4231_TIMER_ENABLE);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_cs4231_timer_stop(snd_timer_t * timer)
+{
+	unsigned long flags;
+	cs4231_t *chip = snd_timer_chip(timer);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] &= ~CS4231_TIMER_ENABLE);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static void snd_cs4231_init(cs4231_t *chip)
+{
+	unsigned long flags;
+
+	snd_cs4231_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printk("init: (1)\n");
+#endif
+	snd_cs4231_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO |
+			     CS4231_RECORD_ENABLE | CS4231_RECORD_PIO |
+			     CS4231_CALIB_MODE);
+	chip->image[CS4231_IFACE_CTRL] |= CS4231_AUTOCALIB;
+	snd_cs4231_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_cs4231_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printk("init: (2)\n");
+#endif
+
+	snd_cs4231_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_cs4231_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printk("init: (3) - afei = 0x%x\n", chip->image[CS4231_ALT_FEATURE_1]);
+#endif
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_cs4231_out(chip, CS4231_ALT_FEATURE_2, chip->image[CS4231_ALT_FEATURE_2]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	snd_cs4231_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, chip->image[CS4231_PLAYBK_FORMAT]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_cs4231_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printk("init: (4)\n");
+#endif
+
+	snd_cs4231_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_cs4231_out(chip, CS4231_REC_FORMAT, chip->image[CS4231_REC_FORMAT]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_cs4231_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printk("init: (5)\n");
+#endif
+}
+
+static int snd_cs4231_open(cs4231_t *chip, unsigned int mode)
+{
+	unsigned long flags;
+
+	down(&chip->open_mutex);
+	if ((chip->mode & mode) ||
+	    ((chip->mode & CS4231_MODE_OPEN) && chip->single_dma)) {
+		up(&chip->open_mutex);
+		return -EAGAIN;
+	}
+	if (chip->mode & CS4231_MODE_OPEN) {
+		chip->mode |= mode;
+		up(&chip->open_mutex);
+		return 0;
+	}
+	/* ok. now enable and ack CODEC IRQ */
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_cs4231_out(chip, CS4231_IRQ_STATUS, CS4231_PLAYBACK_IRQ |
+		       CS4231_RECORD_IRQ |
+		       CS4231_TIMER_IRQ);
+	snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0);
+	cs4231_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
+	cs4231_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
+	chip->image[CS4231_PIN_CTRL] |= CS4231_IRQ_ENABLE;
+	snd_cs4231_out(chip, CS4231_PIN_CTRL, chip->image[CS4231_PIN_CTRL]);
+	snd_cs4231_out(chip, CS4231_IRQ_STATUS, CS4231_PLAYBACK_IRQ |
+		       CS4231_RECORD_IRQ |
+		       CS4231_TIMER_IRQ);
+	snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	chip->mode = mode;
+	up(&chip->open_mutex);
+	return 0;
+}
+
+static void snd_cs4231_close(cs4231_t *chip, unsigned int mode)
+{
+	unsigned long flags;
+
+	down(&chip->open_mutex);
+	chip->mode &= ~mode;
+	if (chip->mode & CS4231_MODE_OPEN) {
+		up(&chip->open_mutex);
+		return;
+	}
+	snd_cs4231_calibrate_mute(chip, 1);
+
+	/* disable IRQ */
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0);
+	cs4231_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
+	cs4231_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
+	chip->image[CS4231_PIN_CTRL] &= ~CS4231_IRQ_ENABLE;
+	snd_cs4231_out(chip, CS4231_PIN_CTRL, chip->image[CS4231_PIN_CTRL]);
+
+	/* now disable record & playback */
+
+	if (chip->image[CS4231_IFACE_CTRL] & (CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO |
+					       CS4231_RECORD_ENABLE | CS4231_RECORD_PIO)) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		snd_cs4231_mce_up(chip);
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO |
+						     CS4231_RECORD_ENABLE | CS4231_RECORD_PIO);
+		snd_cs4231_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		snd_cs4231_mce_down(chip);
+		spin_lock_irqsave(&chip->reg_lock, flags);
+	}
+
+	/* clear IRQ again */
+	snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0);
+	cs4231_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
+	cs4231_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	snd_cs4231_calibrate_mute(chip, 0);
+
+	chip->mode = 0;
+	up(&chip->open_mutex);
+}
+
+/*
+ *  timer open/close
+ */
+
+static int snd_cs4231_timer_open(snd_timer_t * timer)
+{
+	cs4231_t *chip = snd_timer_chip(timer);
+	snd_cs4231_open(chip, CS4231_MODE_TIMER);
+	return 0;
+}
+
+static int snd_cs4231_timer_close(snd_timer_t * timer)
+{
+	cs4231_t *chip = snd_timer_chip(timer);
+	snd_cs4231_close(chip, CS4231_MODE_TIMER);
+	return 0;
+}
+
+static struct _snd_timer_hardware snd_cs4231_timer_table =
+{
+	.flags =	SNDRV_TIMER_HW_AUTO,
+	.resolution =	9945,
+	.ticks =	65535,
+	.open =		snd_cs4231_timer_open,
+	.close =	snd_cs4231_timer_close,
+	.c_resolution = snd_cs4231_timer_resolution,
+	.start =	snd_cs4231_timer_start,
+	.stop =		snd_cs4231_timer_stop,
+};
+
+/*
+ *  ok.. exported functions..
+ */
+
+static int snd_cs4231_playback_hw_params(snd_pcm_substream_t * substream,
+					 snd_pcm_hw_params_t * hw_params)
+{
+	cs4231_t *chip = snd_pcm_substream_chip(substream);
+	unsigned char new_pdfr;
+	int err;
+
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+	new_pdfr = snd_cs4231_get_format(chip, params_format(hw_params), params_channels(hw_params)) |
+		   snd_cs4231_get_rate(params_rate(hw_params));
+	chip->set_playback_format(chip, hw_params, new_pdfr);
+	return 0;
+}
+
+static int snd_cs4231_playback_hw_free(snd_pcm_substream_t * substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+#ifdef LEGACY_SUPPORT
+static int snd_cs4231_playback_prepare(snd_pcm_substream_t * substream)
+{
+	cs4231_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned long flags;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->p_dma_size = size;
+	chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO);
+	snd_dma_program(chip->dma1, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
+	count = snd_cs4231_get_count(chip->image[CS4231_PLAYBK_FORMAT], count) - 1;
+	snd_cs4231_out(chip, CS4231_PLY_LWR_CNT, (unsigned char) count);
+	snd_cs4231_out(chip, CS4231_PLY_UPR_CNT, (unsigned char) (count >> 8));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+#if 0
+	snd_cs4231_debug(chip);
+#endif
+	return 0;
+}
+#endif /* LEGACY_SUPPORT */
+
+static int snd_cs4231_capture_hw_params(snd_pcm_substream_t * substream,
+					snd_pcm_hw_params_t * hw_params)
+{
+	cs4231_t *chip = snd_pcm_substream_chip(substream);
+	unsigned char new_cdfr;
+	int err;
+
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+	new_cdfr = snd_cs4231_get_format(chip, params_format(hw_params), params_channels(hw_params)) |
+		   snd_cs4231_get_rate(params_rate(hw_params));
+	chip->set_capture_format(chip, hw_params, new_cdfr);
+	return 0;
+}
+
+static int snd_cs4231_capture_hw_free(snd_pcm_substream_t * substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+#ifdef LEGACY_SUPPORT
+static int snd_cs4231_capture_prepare(snd_pcm_substream_t * substream)
+{
+	cs4231_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned long flags;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->c_dma_size = size;
+	chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_RECORD_ENABLE | CS4231_RECORD_PIO);
+	snd_dma_program(chip->dma2, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
+	count = snd_cs4231_get_count(chip->image[CS4231_REC_FORMAT], count) - 1;
+	if (chip->single_dma && chip->hardware != CS4231_HW_INTERWAVE) {
+		snd_cs4231_out(chip, CS4231_PLY_LWR_CNT, (unsigned char) count);
+		snd_cs4231_out(chip, CS4231_PLY_UPR_CNT, (unsigned char) (count >> 8));
+	} else {
+		snd_cs4231_out(chip, CS4231_REC_LWR_CNT, (unsigned char) count);
+		snd_cs4231_out(chip, CS4231_REC_UPR_CNT, (unsigned char) (count >> 8));
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+#endif
+
+static void snd_cs4231_overrange(cs4231_t *chip)
+{
+	unsigned long flags;
+	unsigned char res;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	res = snd_cs4231_in(chip, CS4231_TEST_INIT);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (res & (0x08 | 0x02))	/* detect overrange only above 0dB; may be user selectable? */
+		chip->capture_substream->runtime->overrange++;
+}
+
+irqreturn_t snd_cs4231_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	cs4231_t *chip = dev_id;
+	unsigned char status;
+
+	status = snd_cs4231_in(chip, CS4231_IRQ_STATUS);
+	if (status & CS4231_TIMER_IRQ) {
+		if (chip->timer)
+			snd_timer_interrupt(chip->timer, chip->timer->sticks);
+	}		
+	if (chip->single_dma && chip->hardware != CS4231_HW_INTERWAVE) {
+		if (status & CS4231_PLAYBACK_IRQ) {
+			if (chip->mode & CS4231_MODE_PLAY) {
+				if (chip->playback_substream)
+					snd_pcm_period_elapsed(chip->playback_substream);
+			}
+			if (chip->mode & CS4231_MODE_RECORD) {
+				if (chip->capture_substream) {
+					snd_cs4231_overrange(chip);
+					snd_pcm_period_elapsed(chip->capture_substream);
+				}
+			}
+		}
+	} else {
+		if (status & CS4231_PLAYBACK_IRQ) {
+			if (chip->playback_substream)
+				snd_pcm_period_elapsed(chip->playback_substream);
+		}
+		if (status & CS4231_RECORD_IRQ) {
+			if (chip->capture_substream) {
+				snd_cs4231_overrange(chip);
+				snd_pcm_period_elapsed(chip->capture_substream);
+			}
+		}
+	}
+
+	spin_lock(&chip->reg_lock);
+	snd_cs4231_outm(chip, CS4231_IRQ_STATUS, ~CS4231_ALL_IRQS | ~status, 0);
+	spin_unlock(&chip->reg_lock);
+	return IRQ_HANDLED;
+}
+
+#ifdef LEGACY_SUPPORT
+static snd_pcm_uframes_t snd_cs4231_playback_pointer(snd_pcm_substream_t * substream)
+{
+	cs4231_t *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE))
+		return 0;
+	ptr = snd_dma_pointer(chip->dma1, chip->p_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_cs4231_capture_pointer(snd_pcm_substream_t * substream)
+{
+	cs4231_t *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	
+	if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE))
+		return 0;
+	ptr = snd_dma_pointer(chip->dma2, chip->c_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+#endif /* LEGACY_SUPPORT */
+
+/*
+
+ */
+
+static int snd_cs4231_probe(cs4231_t *chip)
+{
+	unsigned long flags;
+	int i, id, rev;
+	unsigned char *ptr;
+	unsigned int hw;
+
+#if 0
+	snd_cs4231_debug(chip);
+#endif
+	id = 0;
+	for (i = 0; i < 50; i++) {
+		mb();
+		if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+			udelay(2000);
+		else {
+			spin_lock_irqsave(&chip->reg_lock, flags);
+			snd_cs4231_out(chip, CS4231_MISC_INFO, CS4231_MODE2);
+			id = snd_cs4231_in(chip, CS4231_MISC_INFO) & 0x0f;
+			spin_unlock_irqrestore(&chip->reg_lock, flags);
+			if (id == 0x0a)
+				break;	/* this is valid value */
+		}
+	}
+	snd_printdd("cs4231: port = 0x%lx, id = 0x%x\n", chip->port, id);
+	if (id != 0x0a)
+		return -ENODEV;	/* no valid device found */
+
+	if (((hw = chip->hardware) & CS4231_HW_TYPE_MASK) == CS4231_HW_DETECT) {
+		rev = snd_cs4231_in(chip, CS4231_VERSION) & 0xe7;
+		snd_printdd("CS4231: VERSION (I25) = 0x%x\n", rev);
+		if (rev == 0x80) {
+			unsigned char tmp = snd_cs4231_in(chip, 23);
+			snd_cs4231_out(chip, 23, ~tmp);
+			if (snd_cs4231_in(chip, 23) != tmp)
+				chip->hardware = CS4231_HW_AD1845;
+			else
+				chip->hardware = CS4231_HW_CS4231;
+		} else if (rev == 0xa0) {
+			chip->hardware = CS4231_HW_CS4231A;
+		} else if (rev == 0xa2) {
+			chip->hardware = CS4231_HW_CS4232;
+		} else if (rev == 0xb2) {
+			chip->hardware = CS4231_HW_CS4232A;
+		} else if (rev == 0x83) {
+			chip->hardware = CS4231_HW_CS4236;
+		} else if (rev == 0x03) {
+			chip->hardware = CS4231_HW_CS4236B;
+		} else {
+			snd_printk("unknown CS chip with version 0x%x\n", rev);
+			return -ENODEV;		/* unknown CS4231 chip? */
+		}
+	}
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	cs4231_inb(chip, CS4231P(STATUS));	/* clear any pendings IRQ */
+	cs4231_outb(chip, CS4231P(STATUS), 0);
+	mb();
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	chip->image[CS4231_MISC_INFO] = CS4231_MODE2;
+	switch (chip->hardware) {
+	case CS4231_HW_INTERWAVE:
+		chip->image[CS4231_MISC_INFO] = CS4231_IW_MODE3;
+		break;
+	case CS4231_HW_CS4235:
+	case CS4231_HW_CS4236B:
+	case CS4231_HW_CS4237B:
+	case CS4231_HW_CS4238B:
+	case CS4231_HW_CS4239:
+		if (hw == CS4231_HW_DETECT3)
+			chip->image[CS4231_MISC_INFO] = CS4231_4236_MODE3;
+		else
+			chip->hardware = CS4231_HW_CS4236;
+		break;
+	}
+
+	chip->image[CS4231_IFACE_CTRL] =
+	    (chip->image[CS4231_IFACE_CTRL] & ~CS4231_SINGLE_DMA) |
+	    (chip->single_dma ? CS4231_SINGLE_DMA : 0);
+	chip->image[CS4231_ALT_FEATURE_1] = 0x80;
+	chip->image[CS4231_ALT_FEATURE_2] = chip->hardware == CS4231_HW_INTERWAVE ? 0xc2 : 0x01;
+	ptr = (unsigned char *) &chip->image;
+	snd_cs4231_mce_down(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	for (i = 0; i < 32; i++)	/* ok.. fill all CS4231 registers */
+		snd_cs4231_out(chip, i, *ptr++);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_cs4231_mce_up(chip);
+	snd_cs4231_mce_down(chip);
+
+	mdelay(2);
+
+	/* ok.. try check hardware version for CS4236+ chips */
+	if ((hw & CS4231_HW_TYPE_MASK) == CS4231_HW_DETECT) {
+		if (chip->hardware == CS4231_HW_CS4236B) {
+			rev = snd_cs4236_ext_in(chip, CS4236_VERSION);
+			snd_cs4236_ext_out(chip, CS4236_VERSION, 0xff);
+			id = snd_cs4236_ext_in(chip, CS4236_VERSION);
+			snd_cs4236_ext_out(chip, CS4236_VERSION, rev);
+			snd_printdd("CS4231: ext version; rev = 0x%x, id = 0x%x\n", rev, id);
+			if ((id & 0x1f) == 0x1d) {	/* CS4235 */
+				chip->hardware = CS4231_HW_CS4235;
+				switch (id >> 5) {
+				case 4:
+				case 5:
+				case 6:
+					break;
+				default:
+					snd_printk("unknown CS4235 chip (enhanced version = 0x%x)\n", id);
+				}
+			} else if ((id & 0x1f) == 0x0b) {	/* CS4236/B */
+				switch (id >> 5) {
+				case 4:
+				case 5:
+				case 6:
+				case 7:
+					chip->hardware = CS4231_HW_CS4236B;
+					break;
+				default:
+					snd_printk("unknown CS4236 chip (enhanced version = 0x%x)\n", id);
+				}
+			} else if ((id & 0x1f) == 0x08) {	/* CS4237B */
+				chip->hardware = CS4231_HW_CS4237B;
+				switch (id >> 5) {
+				case 4:
+				case 5:
+				case 6:
+				case 7:
+					break;
+				default:
+					snd_printk("unknown CS4237B chip (enhanced version = 0x%x)\n", id);
+				}
+			} else if ((id & 0x1f) == 0x09) {	/* CS4238B */
+				chip->hardware = CS4231_HW_CS4238B;
+				switch (id >> 5) {
+				case 5:
+				case 6:
+				case 7:
+					break;
+				default:
+					snd_printk("unknown CS4238B chip (enhanced version = 0x%x)\n", id);
+				}
+			} else if ((id & 0x1f) == 0x1e) {	/* CS4239 */
+				chip->hardware = CS4231_HW_CS4239;
+				switch (id >> 5) {
+				case 4:
+				case 5:
+				case 6:
+					break;
+				default:
+					snd_printk("unknown CS4239 chip (enhanced version = 0x%x)\n", id);
+				}
+			} else {
+				snd_printk("unknown CS4236/CS423xB chip (enhanced version = 0x%x)\n", id);
+			}
+		}
+	}
+	return 0;		/* all things are ok.. */
+}
+
+/*
+
+ */
+
+static snd_pcm_hardware_t snd_cs4231_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_RESUME |
+				 SNDRV_PCM_INFO_SYNC_START),
+	.formats =		(SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | SNDRV_PCM_FMTBIT_IMA_ADPCM |
+				 SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE),
+	.rates =		SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5510,
+	.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,
+};
+
+static snd_pcm_hardware_t snd_cs4231_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_RESUME |
+				 SNDRV_PCM_INFO_SYNC_START),
+	.formats =		(SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | SNDRV_PCM_FMTBIT_IMA_ADPCM |
+				 SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE),
+	.rates =		SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5510,
+	.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,
+};
+
+/*
+
+ */
+
+static int snd_cs4231_playback_open(snd_pcm_substream_t * substream)
+{
+	cs4231_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int err;
+
+	runtime->hw = snd_cs4231_playback;
+
+	/* hardware bug in InterWave chipset */
+	if (chip->hardware == CS4231_HW_INTERWAVE && chip->dma1 > 3)
+	    	runtime->hw.formats &= ~SNDRV_PCM_FMTBIT_MU_LAW;
+	
+	/* hardware limitation of cheap chips */
+	if (chip->hardware == CS4231_HW_CS4235 ||
+	    chip->hardware == CS4231_HW_CS4239)
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE;
+
+#ifdef LEGACY_SUPPORT
+	snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.buffer_bytes_max);
+	snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.period_bytes_max);
+
+	if (chip->claim_dma) {
+		if ((err = chip->claim_dma(chip, chip->dma_private_data, chip->dma1)) < 0)
+			return err;
+	}
+#endif
+
+	if ((err = snd_cs4231_open(chip, CS4231_MODE_PLAY)) < 0) {
+#ifdef LEGACY_SUPPORT
+		if (chip->release_dma)
+			chip->release_dma(chip, chip->dma_private_data, chip->dma1);
+#endif
+		snd_free_pages(runtime->dma_area, runtime->dma_bytes);
+		return err;
+	}
+	chip->playback_substream = substream;
+#if defined(SBUS_SUPPORT) || defined(EBUS_SUPPORT)
+	chip->p_periods_sent = 0;
+#endif
+	snd_pcm_set_sync(substream);
+	chip->rate_constraint(runtime);
+	return 0;
+}
+
+static int snd_cs4231_capture_open(snd_pcm_substream_t * substream)
+{
+	cs4231_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int err;
+
+	runtime->hw = snd_cs4231_capture;
+
+	/* hardware limitation of cheap chips */
+	if (chip->hardware == CS4231_HW_CS4235 ||
+	    chip->hardware == CS4231_HW_CS4239)
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE;
+
+#ifdef LEGACY_SUPPORT
+	snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.buffer_bytes_max);
+	snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.period_bytes_max);
+
+	if (chip->claim_dma) {
+		if ((err = chip->claim_dma(chip, chip->dma_private_data, chip->dma2)) < 0)
+			return err;
+	}
+#endif
+
+	if ((err = snd_cs4231_open(chip, CS4231_MODE_RECORD)) < 0) {
+#ifdef LEGACY_SUPPORT
+		if (chip->release_dma)
+			chip->release_dma(chip, chip->dma_private_data, chip->dma2);
+#endif
+		snd_free_pages(runtime->dma_area, runtime->dma_bytes);
+		return err;
+	}
+	chip->capture_substream = substream;
+#if defined(SBUS_SUPPORT) || defined(EBUS_SUPPORT)
+	chip->c_periods_sent = 0;
+#endif
+	snd_pcm_set_sync(substream);
+	chip->rate_constraint(runtime);
+	return 0;
+}
+
+static int snd_cs4231_playback_close(snd_pcm_substream_t * substream)
+{
+	cs4231_t *chip = snd_pcm_substream_chip(substream);
+
+	chip->playback_substream = NULL;
+	snd_cs4231_close(chip, CS4231_MODE_PLAY);
+	return 0;
+}
+
+static int snd_cs4231_capture_close(snd_pcm_substream_t * substream)
+{
+	cs4231_t *chip = snd_pcm_substream_chip(substream);
+
+	chip->capture_substream = NULL;
+	snd_cs4231_close(chip, CS4231_MODE_RECORD);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+
+/* lowlevel suspend callback for CS4231 */
+static void snd_cs4231_suspend(cs4231_t *chip)
+{
+	int reg;
+	unsigned long flags;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	for (reg = 0; reg < 32; reg++)
+		chip->image[reg] = snd_cs4231_in(chip, reg);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+/* lowlevel resume callback for CS4231 */
+static void snd_cs4231_resume(cs4231_t *chip)
+{
+	int reg;
+	unsigned long flags;
+	int timeout;
+	
+	snd_cs4231_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	for (reg = 0; reg < 32; reg++) {
+		switch (reg) {
+		case CS4231_VERSION:
+			break;
+		default:
+			snd_cs4231_out(chip, reg, chip->image[reg]);
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+#if 0
+	snd_cs4231_mce_down(chip);
+#else
+	/* The following is a workaround to avoid freeze after resume on TP600E.
+	   This is the first half of copy of snd_cs4231_mce_down(), but doesn't
+	   include rescheduling.  -- iwai
+	   */
+	snd_cs4231_busy_wait(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->mce_bit &= ~CS4231_MCE;
+	timeout = cs4231_inb(chip, CS4231P(REGSEL));
+	cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (timeout == 0x80)
+		snd_printk("down [0x%lx]: serious init problem - codec still busy\n", chip->port);
+	if ((timeout & CS4231_MCE) == 0 ||
+	    !(chip->hardware & (CS4231_HW_CS4231_MASK | CS4231_HW_CS4232_MASK))) {
+		return;
+	}
+	snd_cs4231_busy_wait(chip);
+#endif
+}
+
+static int snd_cs4231_pm_suspend(snd_card_t *card, pm_message_t state)
+{
+	cs4231_t *chip = card->pm_private_data;
+	if (chip->suspend)
+		chip->suspend(chip);
+	return 0;
+}
+
+static int snd_cs4231_pm_resume(snd_card_t *card)
+{
+	cs4231_t *chip = card->pm_private_data;
+	if (chip->resume)
+		chip->resume(chip);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+#ifdef LEGACY_SUPPORT
+
+static int snd_cs4231_free(cs4231_t *chip)
+{
+	if (chip->res_port) {
+		release_resource(chip->res_port);
+		kfree_nocheck(chip->res_port);
+	}
+	if (chip->res_cport) {
+		release_resource(chip->res_cport);
+		kfree_nocheck(chip->res_cport);
+	}
+	if (chip->irq >= 0) {
+		disable_irq(chip->irq);
+		if (!(chip->hwshare & CS4231_HWSHARE_IRQ))
+			free_irq(chip->irq, (void *) chip);
+	}
+	if (!(chip->hwshare & CS4231_HWSHARE_DMA1) && chip->dma1 >= 0) {
+		snd_dma_disable(chip->dma1);
+		free_dma(chip->dma1);
+	}
+	if (!(chip->hwshare & CS4231_HWSHARE_DMA2) && chip->dma2 >= 0 && chip->dma2 != chip->dma1) {
+		snd_dma_disable(chip->dma2);
+		free_dma(chip->dma2);
+	}
+	if (chip->timer)
+		snd_device_free(chip->card, chip->timer);
+	kfree(chip);
+	return 0;
+}
+
+static int snd_cs4231_dev_free(snd_device_t *device)
+{
+	cs4231_t *chip = device->device_data;
+	return snd_cs4231_free(chip);	
+}
+
+#endif /* LEGACY_SUPPORT */
+
+const char *snd_cs4231_chip_id(cs4231_t *chip)
+{
+	switch (chip->hardware) {
+	case CS4231_HW_CS4231:	return "CS4231";
+	case CS4231_HW_CS4231A: return "CS4231A";
+	case CS4231_HW_CS4232:	return "CS4232";
+	case CS4231_HW_CS4232A:	return "CS4232A";
+	case CS4231_HW_CS4235:	return "CS4235";
+	case CS4231_HW_CS4236:  return "CS4236";
+	case CS4231_HW_CS4236B: return "CS4236B";
+	case CS4231_HW_CS4237B: return "CS4237B";
+	case CS4231_HW_CS4238B: return "CS4238B";
+	case CS4231_HW_CS4239:	return "CS4239";
+	case CS4231_HW_INTERWAVE: return "AMD InterWave";
+	case CS4231_HW_OPL3SA2: return chip->card->shortname;
+	case CS4231_HW_AD1845: return "AD1845";
+	default: return "???";
+	}
+}
+
+static int snd_cs4231_new(snd_card_t * card,
+			  unsigned short hardware,
+			  unsigned short hwshare,
+			  cs4231_t ** rchip)
+{
+	cs4231_t *chip;
+
+	*rchip = NULL;
+	chip = kcalloc(1, sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+	chip->hardware = hardware;
+	chip->hwshare = hwshare;
+
+	spin_lock_init(&chip->reg_lock);
+	init_MUTEX(&chip->mce_mutex);
+	init_MUTEX(&chip->open_mutex);
+	chip->card = card;
+	chip->rate_constraint = snd_cs4231_xrate;
+	chip->set_playback_format = snd_cs4231_playback_format;
+	chip->set_capture_format = snd_cs4231_capture_format;
+        memcpy(&chip->image, &snd_cs4231_original_image, sizeof(snd_cs4231_original_image));
+        
+        *rchip = chip;
+        return 0;
+}
+
+#ifdef LEGACY_SUPPORT
+
+int snd_cs4231_create(snd_card_t * card,
+	              unsigned long port,
+	              unsigned long cport,
+		      int irq, int dma1, int dma2,
+		      unsigned short hardware,
+		      unsigned short hwshare,
+		      cs4231_t ** rchip)
+{
+	static snd_device_ops_t ops = {
+		.dev_free =	snd_cs4231_dev_free,
+	};
+	cs4231_t *chip;
+	int err;
+
+	err = snd_cs4231_new(card, hardware, hwshare, &chip);
+	if (err < 0)
+		return err;
+	
+	chip->irq = -1;
+	chip->dma1 = -1;
+	chip->dma2 = -1;
+
+	if ((chip->res_port = request_region(port, 4, "CS4231")) == NULL) {
+		snd_printk(KERN_ERR "cs4231: can't grab port 0x%lx\n", port);
+		snd_cs4231_free(chip);
+		return -EBUSY;
+	}
+	chip->port = port;
+	if ((long)cport >= 0 && (chip->res_cport = request_region(cport, 8, "CS4232 Control")) == NULL) {
+		snd_printk(KERN_ERR "cs4231: can't grab control port 0x%lx\n", cport);
+		snd_cs4231_free(chip);
+		return -ENODEV;
+	}
+	chip->cport = cport;
+	if (!(hwshare & CS4231_HWSHARE_IRQ) && request_irq(irq, snd_cs4231_interrupt, SA_INTERRUPT, "CS4231", (void *) chip)) {
+		snd_printk(KERN_ERR "cs4231: can't grab IRQ %d\n", irq);
+		snd_cs4231_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = irq;
+	if (!(hwshare & CS4231_HWSHARE_DMA1) && request_dma(dma1, "CS4231 - 1")) {
+		snd_printk(KERN_ERR "cs4231: can't grab DMA1 %d\n", dma1);
+		snd_cs4231_free(chip);
+		return -EBUSY;
+	}
+	chip->dma1 = dma1;
+	if (!(hwshare & CS4231_HWSHARE_DMA2) && dma1 != dma2 && dma2 >= 0 && request_dma(dma2, "CS4231 - 2")) {
+		snd_printk(KERN_ERR "cs4231: can't grab DMA2 %d\n", dma2);
+		snd_cs4231_free(chip);
+		return -EBUSY;
+	}
+	if (dma1 == dma2 || dma2 < 0) {
+		chip->single_dma = 1;
+		chip->dma2 = chip->dma1;
+	} else
+		chip->dma2 = dma2;
+
+	/* global setup */
+	if (snd_cs4231_probe(chip) < 0) {
+		snd_cs4231_free(chip);
+		return -ENODEV;
+	}
+	snd_cs4231_init(chip);
+
+	if (chip->hardware & CS4231_HW_CS4232_MASK) {
+		if (chip->res_cport == NULL)
+			snd_printk("CS4232 control port features are not accessible\n");
+	}
+
+	/* Register device */
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_cs4231_free(chip);
+		return err;
+	}
+
+#ifdef CONFIG_PM
+	/* Power Management */
+	chip->suspend = snd_cs4231_suspend;
+	chip->resume = snd_cs4231_resume;
+	snd_card_set_isa_pm_callback(card, snd_cs4231_pm_suspend, snd_cs4231_pm_resume, chip);
+#endif
+
+	*rchip = chip;
+	return 0;
+}
+
+#endif /* LEGACY_SUPPORT */
+
+static snd_pcm_ops_t snd_cs4231_playback_ops = {
+	.open =		snd_cs4231_playback_open,
+	.close =	snd_cs4231_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_cs4231_playback_hw_params,
+	.hw_free =	snd_cs4231_playback_hw_free,
+	.prepare =	snd_cs4231_playback_prepare,
+	.trigger =	snd_cs4231_trigger,
+	.pointer =	snd_cs4231_playback_pointer,
+};
+
+static snd_pcm_ops_t snd_cs4231_capture_ops = {
+	.open =		snd_cs4231_capture_open,
+	.close =	snd_cs4231_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_cs4231_capture_hw_params,
+	.hw_free =	snd_cs4231_capture_hw_free,
+	.prepare =	snd_cs4231_capture_prepare,
+	.trigger =	snd_cs4231_trigger,
+	.pointer =	snd_cs4231_capture_pointer,
+};
+
+static void snd_cs4231_pcm_free(snd_pcm_t *pcm)
+{
+	cs4231_t *chip = pcm->private_data;
+	chip->pcm = NULL;
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+int snd_cs4231_pcm(cs4231_t *chip, int device, snd_pcm_t **rpcm)
+{
+	snd_pcm_t *pcm;
+	int err;
+
+	if ((err = snd_pcm_new(chip->card, "CS4231", device, 1, 1, &pcm)) < 0)
+		return err;
+
+	spin_lock_init(&chip->reg_lock);
+	init_MUTEX(&chip->mce_mutex);
+	init_MUTEX(&chip->open_mutex);
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cs4231_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cs4231_capture_ops);
+	
+	/* global setup */
+	pcm->private_data = chip;
+	pcm->private_free = snd_cs4231_pcm_free;
+	pcm->info_flags = 0;
+	if (chip->single_dma)
+		pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX;
+	if (chip->hardware != CS4231_HW_INTERWAVE)
+		pcm->info_flags |= SNDRV_PCM_INFO_JOINT_DUPLEX;
+	strcpy(pcm->name, snd_cs4231_chip_id(chip));
+
+#ifdef LEGACY_SUPPORT
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, chip->dma1 > 3 || chip->dma2 > 3 ? 128*1024 : 64*1024);
+#else
+#  ifdef EBUS_SUPPORT
+        if (chip->ebus_flag) {
+                snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+                				      chip->dev_u.pdev,
+						      64*1024, 128*1024);
+        } else {
+#  endif
+#  ifdef SBUS_SUPPORT
+                snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_SBUS,
+                				      chip->dev_u.sdev,
+						      64*1024, 128*1024);
+#  endif
+#  ifdef EBUS_SUPPORT
+        }
+#  endif
+#endif
+
+	chip->pcm = pcm;
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+static void snd_cs4231_timer_free(snd_timer_t *timer)
+{
+	cs4231_t *chip = timer->private_data;
+	chip->timer = NULL;
+}
+
+int snd_cs4231_timer(cs4231_t *chip, int device, snd_timer_t **rtimer)
+{
+	snd_timer_t *timer;
+	snd_timer_id_t tid;
+	int err;
+
+	/* Timer initialization */
+	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
+	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+	tid.card = chip->card->number;
+	tid.device = device;
+	tid.subdevice = 0;
+	if ((err = snd_timer_new(chip->card, "CS4231", &tid, &timer)) < 0)
+		return err;
+	strcpy(timer->name, snd_cs4231_chip_id(chip));
+	timer->private_data = chip;
+	timer->private_free = snd_cs4231_timer_free;
+	timer->hw = snd_cs4231_timer_table;
+	chip->timer = timer;
+	if (rtimer)
+		*rtimer = timer;
+	return 0;
+}
+	
+/*
+ *  MIXER part
+ */
+
+static int snd_cs4231_info_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	static char *texts[4] = {
+		"Line", "Aux", "Mic", "Mix"
+	};
+	static char *opl3sa_texts[4] = {
+		"Line", "CD", "Mic", "Mix"
+	};
+	static char *gusmax_texts[4] = {
+		"Line", "Synth", "Mic", "Mix"
+	};
+	char **ptexts = texts;
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+
+	snd_assert(chip->card != NULL, return -EINVAL);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 2;
+	uinfo->value.enumerated.items = 4;
+	if (uinfo->value.enumerated.item > 3)
+		uinfo->value.enumerated.item = 3;
+	if (!strcmp(chip->card->driver, "GUS MAX"))
+		ptexts = gusmax_texts;
+	switch (chip->hardware) {
+	case CS4231_HW_INTERWAVE: ptexts = gusmax_texts; break;
+	case CS4231_HW_OPL3SA2: ptexts = opl3sa_texts; break;
+	}
+	strcpy(uinfo->value.enumerated.name, ptexts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_cs4231_get_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.enumerated.item[0] = (chip->image[CS4231_LEFT_INPUT] & CS4231_MIXS_ALL) >> 6;
+	ucontrol->value.enumerated.item[1] = (chip->image[CS4231_RIGHT_INPUT] & CS4231_MIXS_ALL) >> 6;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_cs4231_put_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned short left, right;
+	int change;
+	
+	if (ucontrol->value.enumerated.item[0] > 3 ||
+	    ucontrol->value.enumerated.item[1] > 3)
+		return -EINVAL;
+	left = ucontrol->value.enumerated.item[0] << 6;
+	right = ucontrol->value.enumerated.item[1] << 6;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	left = (chip->image[CS4231_LEFT_INPUT] & ~CS4231_MIXS_ALL) | left;
+	right = (chip->image[CS4231_RIGHT_INPUT] & ~CS4231_MIXS_ALL) | right;
+	change = left != chip->image[CS4231_LEFT_INPUT] ||
+	         right != chip->image[CS4231_RIGHT_INPUT];
+	snd_cs4231_out(chip, CS4231_LEFT_INPUT, left);
+	snd_cs4231_out(chip, CS4231_RIGHT_INPUT, right);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+int snd_cs4231_info_single(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+int snd_cs4231_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (chip->image[reg] >> shift) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+int snd_cs4231_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned short val;
+	
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	val <<= shift;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val = (chip->image[reg] & ~(mask << shift)) | val;
+	change = val != chip->image[reg];
+	snd_cs4231_out(chip, reg, val);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+int snd_cs4231_info_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+int snd_cs4231_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (chip->image[left_reg] >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (chip->image[right_reg] >> shift_right) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+
+int snd_cs4231_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	unsigned short val1, val2;
+	
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1;
+	val2 = (chip->image[right_reg] & ~(mask << shift_right)) | val2;
+	change = val1 != chip->image[left_reg] || val2 != chip->image[right_reg];
+	snd_cs4231_out(chip, left_reg, val1);
+	snd_cs4231_out(chip, right_reg, val2);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_cs4231_controls[] = {
+CS4231_DOUBLE("PCM Playback Switch", 0, CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
+CS4231_DOUBLE("PCM Playback Volume", 0, CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1),
+CS4231_DOUBLE("Line Playback Switch", 0, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1),
+CS4231_DOUBLE("Line Playback Volume", 0, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1),
+CS4231_DOUBLE("Aux Playback Switch", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1),
+CS4231_DOUBLE("Aux Playback Volume", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1),
+CS4231_DOUBLE("Aux Playback Switch", 1, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
+CS4231_DOUBLE("Aux Playback Volume", 1, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1),
+CS4231_SINGLE("Mono Playback Switch", 0, CS4231_MONO_CTRL, 7, 1, 1),
+CS4231_SINGLE("Mono Playback Volume", 0, CS4231_MONO_CTRL, 0, 15, 1),
+CS4231_SINGLE("Mono Output Playback Switch", 0, CS4231_MONO_CTRL, 6, 1, 1),
+CS4231_SINGLE("Mono Output Playback Bypass", 0, CS4231_MONO_CTRL, 5, 1, 0),
+CS4231_DOUBLE("Capture Volume", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 0, 0, 15, 0),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Capture Source",
+	.info = snd_cs4231_info_mux,
+	.get = snd_cs4231_get_mux,
+	.put = snd_cs4231_put_mux,
+},
+CS4231_DOUBLE("Mic Boost", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 5, 5, 1, 0),
+CS4231_SINGLE("Loopback Capture Switch", 0, CS4231_LOOPBACK, 0, 1, 0),
+CS4231_SINGLE("Loopback Capture Volume", 0, CS4231_LOOPBACK, 2, 63, 1)
+};
+                                        
+int snd_cs4231_mixer(cs4231_t *chip)
+{
+	snd_card_t *card;
+	unsigned int idx;
+	int err;
+
+	snd_assert(chip != NULL && chip->pcm != NULL, return -EINVAL);
+
+	card = chip->card;
+
+	strcpy(card->mixername, chip->pcm->name);
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_cs4231_controls); idx++) {
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4231_controls[idx], chip))) < 0)
+			return err;
+	}
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_cs4231_out);
+EXPORT_SYMBOL(snd_cs4231_in);
+EXPORT_SYMBOL(snd_cs4236_ext_out);
+EXPORT_SYMBOL(snd_cs4236_ext_in);
+EXPORT_SYMBOL(snd_cs4231_mce_up);
+EXPORT_SYMBOL(snd_cs4231_mce_down);
+EXPORT_SYMBOL(snd_cs4231_interrupt);
+EXPORT_SYMBOL(snd_cs4231_chip_id);
+EXPORT_SYMBOL(snd_cs4231_create);
+EXPORT_SYMBOL(snd_cs4231_pcm);
+EXPORT_SYMBOL(snd_cs4231_mixer);
+EXPORT_SYMBOL(snd_cs4231_timer);
+EXPORT_SYMBOL(snd_cs4231_info_single);
+EXPORT_SYMBOL(snd_cs4231_get_single);
+EXPORT_SYMBOL(snd_cs4231_put_single);
+EXPORT_SYMBOL(snd_cs4231_info_double);
+EXPORT_SYMBOL(snd_cs4231_get_double);
+EXPORT_SYMBOL(snd_cs4231_put_double);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_cs4231_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_cs4231_exit(void)
+{
+}
+
+module_init(alsa_cs4231_init)
+module_exit(alsa_cs4231_exit)
diff --git a/sound/isa/cs423x/cs4232.c b/sound/isa/cs423x/cs4232.c
new file mode 100644
index 0000000..9fad2e6
--- /dev/null
+++ b/sound/isa/cs423x/cs4232.c
@@ -0,0 +1,2 @@
+#define CS4232
+#include "cs4236.c"
diff --git a/sound/isa/cs423x/cs4236.c b/sound/isa/cs423x/cs4236.c
new file mode 100644
index 0000000..e745a54
--- /dev/null
+++ b/sound/isa/cs423x/cs4236.c
@@ -0,0 +1,608 @@
+/*
+ *  Driver for generic CS4232/CS4235/CS4236/CS4236B/CS4237B/CS4238B/CS4239 chips
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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/slab.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/cs4231.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_LICENSE("GPL");
+#ifdef CS4232
+MODULE_DESCRIPTION("Cirrus Logic CS4232");
+MODULE_SUPPORTED_DEVICE("{{Turtle Beach,TBS-2000},"
+		"{Turtle Beach,Tropez Plus},"
+		"{SIC CrystalWave 32},"
+		"{Hewlett Packard,Omnibook 5500},"
+		"{TerraTec,Maestro 32/96},"
+		"{Philips,PCA70PS}}");
+#else
+MODULE_DESCRIPTION("Cirrus Logic CS4235-9");
+MODULE_SUPPORTED_DEVICE("{{Crystal Semiconductors,CS4235},"
+		"{Crystal Semiconductors,CS4236},"
+		"{Crystal Semiconductors,CS4237},"
+		"{Crystal Semiconductors,CS4238},"
+		"{Crystal Semiconductors,CS4239},"
+		"{Acer,AW37},"
+		"{Acer,AW35/Pro},"
+		"{Crystal,3D},"
+		"{Crystal Computer,TidalWave128},"
+		"{Dell,Optiplex GX1},"
+		"{Dell,Workstation 400 sound},"
+		"{EliteGroup,P5TX-LA sound},"
+		"{Gallant,SC-70P},"
+		"{Gateway,E1000 Onboard CS4236B},"
+		"{Genius,Sound Maker 3DJ},"
+		"{Hewlett Packard,HP6330 sound},"
+		"{IBM,PC 300PL sound},"
+		"{IBM,Aptiva 2137 E24},"
+		"{IBM,IntelliStation M Pro},"
+		"{Intel,Marlin Spike Mobo CS4235},"
+		"{Intel PR440FX Onboard},"
+		"{Guillemot,MaxiSound 16 PnP},"
+		"{NewClear,3D},"
+		"{TerraTec,AudioSystem EWS64L/XL},"
+		"{Typhoon Soundsystem,CS4236B},"
+		"{Turtle Beach,Malibu},"
+		"{Unknown,Digital PC 5000 Onboard}}");
+#endif
+
+#ifdef CS4232
+#define IDENT "CS4232"
+#else
+#define IDENT "CS4236+"
+#endif
+
+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_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static int isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long cport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;/* PnP setup */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long sb_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,11,12,15 */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3,5,6,7 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " IDENT " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " IDENT " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " IDENT " soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "ISA PnP detection for specified soundcard.");
+#endif
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " IDENT " driver.");
+module_param_array(cport, long, NULL, 0444);
+MODULE_PARM_DESC(cport, "Control port # for " IDENT " driver.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " IDENT " driver.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for " IDENT " driver.");
+module_param_array(sb_port, long, NULL, 0444);
+MODULE_PARM_DESC(sb_port, "SB port # for " IDENT " driver (optional).");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " IDENT " driver.");
+module_param_array(mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " IDENT " driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for " IDENT " driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for " IDENT " driver.");
+
+struct snd_card_cs4236 {
+	struct resource *res_sb_port;
+#ifdef CONFIG_PNP
+	struct pnp_dev *wss;
+	struct pnp_dev *ctrl;
+	struct pnp_dev *mpu;
+#endif
+};
+
+static snd_card_t *snd_cs4236_legacy[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+#ifdef CONFIG_PNP
+
+#define ISAPNP_CS4232(_va, _vb, _vc, _device, _wss, _ctrl, _mpu401) \
+	{ \
+		ISAPNP_CARD_ID(_va, _vb, _vc, _device), \
+		.devs = { ISAPNP_DEVICE_ID(_va, _vb, _vc, _wss), \
+                          ISAPNP_DEVICE_ID(_va, _vb, _vc, _ctrl), \
+			  ISAPNP_DEVICE_ID(_va, _vb, _vc, _mpu401) } \
+        }
+#define ISAPNP_CS4232_1(_va, _vb, _vc, _device, _wss, _ctrl, _mpu401) \
+	{ \
+		ISAPNP_CARD_ID(_va, _vb, _vc, _device), \
+		.devs = { ISAPNP_DEVICE_ID(_va, _vb, _vc, _wss), \
+                          ISAPNP_DEVICE_ID(_va, _vb, _vc, _ctrl), \
+		 	  ISAPNP_DEVICE_ID('P', 'N', 'P', _mpu401) } \
+        }
+#define ISAPNP_CS4232_WOMPU(_va, _vb, _vc, _device, _wss, _ctrl) \
+	{ \
+		ISAPNP_CARD_ID(_va, _vb, _vc, _device), \
+		.devs = { ISAPNP_DEVICE_ID(_va, _vb, _vc, _wss), \
+                          ISAPNP_DEVICE_ID(_va, _vb, _vc, _ctrl) } \
+        }
+
+
+#ifdef CS4232
+static struct pnp_card_device_id snd_cs423x_pnpids[] = {
+	/* Philips PCA70PS */
+	{ .id = "CSC0d32", .devs = { { "CSC0000" }, { "CSC0010" }, { "PNPb006" } } },
+	/* TerraTec Maestro 32/96 (CS4232) */
+	{ .id = "CSC1a32", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* HP Omnibook 5500 onboard */
+	{ .id = "CSC4232", .devs = { { "CSC0000" }, { "CSC0002" }, { "CSC0003" } } },
+	/* Unnamed CS4236 card (Made in Taiwan) */
+	{ .id = "CSC4236", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Turtle Beach TBS-2000 (CS4232) */
+	{ .id = "CSC7532", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSCb006" } } },
+	/* Turtle Beach Tropez Plus (CS4232) */
+	{ .id = "CSC7632", .devs = { { "CSC0000" }, { "CSC0010" }, { "PNPb006" } } },
+	/* SIC CrystalWave 32 (CS4232) */
+	{ .id = "CSCf032", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* --- */
+	{ .id = "" }	/* end */
+};
+#else /* CS4236 */
+static struct pnp_card_device_id snd_cs423x_pnpids[] = {
+	/* Intel Marlin Spike Motherboard - CS4235 */
+	{ .id = "CSC0225", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Intel Marlin Spike Motherboard (#2) - CS4235 */
+	{ .id = "CSC0225", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } },
+	/* Unknown Intel mainboard - CS4235 */
+	{ .id = "CSC0225", .devs = { { "CSC0100" }, { "CSC0110" } } },
+	/* Genius Sound Maker 3DJ - CS4237B */
+	{ .id = "CSC0437", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Digital PC 5000 Onboard - CS4236B */
+	{ .id = "CSC0735", .devs = { { "CSC0000" }, { "CSC0010" } } },
+	/* some uknown CS4236B */
+	{ .id = "CSC0b35", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Intel PR440FX Onboard sound */
+	{ .id = "CSC0b36", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* CS4235 on mainboard without MPU */
+	{ .id = "CSC1425", .devs = { { "CSC0100" }, { "CSC0110" } } },
+	/* Gateway E1000 Onboard CS4236B */
+	{ .id = "CSC1335", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* HP 6330 Onboard sound */
+	{ .id = "CSC1525", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } },
+	/* Crystal Computer TidalWave128 */
+	{ .id = "CSC1e37", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* ACER AW37 - CS4235 */
+	{ .id = "CSC4236", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* build-in soundcard in EliteGroup P5TX-LA motherboard - CS4237B */
+	{ .id = "CSC4237", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Crystal 3D - CS4237B */
+	{ .id = "CSC4336", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Typhoon Soundsystem PnP - CS4236B */
+	{ .id = "CSC4536", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Crystal CX4235-XQ3 EP - CS4235 */
+	{ .id = "CSC4625", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } },
+	/* Crystal Semiconductors CS4237B */
+	{ .id = "CSC4637", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* NewClear 3D - CX4237B-XQ3 */
+	{ .id = "CSC4837", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Dell Optiplex GX1 - CS4236B */
+	{ .id = "CSC6835", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Dell P410 motherboard - CS4236B */
+	{ .id = "CSC6835", .devs = { { "CSC0000" }, { "CSC0010" } } },
+	/* Dell Workstation 400 Onboard - CS4236B */
+	{ .id = "CSC6836", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Turtle Beach Malibu - CS4237B */
+	{ .id = "CSC7537", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* CS4235 - onboard */
+	{ .id = "CSC8025", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } },
+	/* IBM Aptiva 2137 E24 Onboard - CS4237B */
+	{ .id = "CSC8037", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* IBM IntelliStation M Pro motherboard */
+	{ .id = "CSCc835", .devs = { { "CSC0000" }, { "CSC0010" } } },
+	/* Guillemot MaxiSound 16 PnP - CS4236B */
+	{ .id = "CSC9836", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Gallant SC-70P */
+	{ .id = "CSC9837", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* TerraTec AudioSystem EWS64XL - CS4236B */
+	{ .id = "CSCa836", .devs = { { "CSCa800" }, { "CSCa810" }, { "CSCa803" } } },
+	/* TerraTec AudioSystem EWS64XL - CS4236B */
+	{ .id = "CSCa836", .devs = { { "CSCa800" }, { "CSCa810" } } },
+	/* ACER AW37/Pro - CS4235 */
+	{ .id = "CSCd925", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* ACER AW35/Pro - CS4237B */
+	{ .id = "CSCd937", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* CS4235 without MPU401 */
+	{ .id = "CSCe825", .devs = { { "CSC0100" }, { "CSC0110" } } },
+	/* Unknown SiS530 - CS4235 */
+	{ .id = "CSC4825", .devs = { { "CSC0100" }, { "CSC0110" } } },
+	/* IBM IntelliStation M Pro 6898 11U - CS4236B */
+	{ .id = "CSCe835", .devs = { { "CSC0000" }, { "CSC0010" } } },
+	/* IBM PC 300PL Onboard - CS4236B */
+	{ .id = "CSCe836", .devs = { { "CSC0000" }, { "CSC0010" } } },
+	/* Some noname CS4236 based card */
+	{ .id = "CSCe936", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* CS4236B */
+	{ .id = "CSCf235", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* CS4236B */
+	{ .id = "CSCf238", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* --- */
+	{ .id = "" }	/* end */
+};
+#endif
+
+MODULE_DEVICE_TABLE(pnp_card, snd_cs423x_pnpids);
+
+static int __devinit snd_card_cs4236_pnp(int dev, struct snd_card_cs4236 *acard,
+					 struct pnp_card_link *card,
+					 const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	struct pnp_resource_table * cfg = kmalloc(sizeof(struct pnp_resource_table), GFP_KERNEL);
+	int err;
+
+	if (!cfg)
+		return -ENOMEM;
+
+	acard->wss = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->wss == NULL) {
+		kfree(cfg);
+		return -EBUSY;
+	}
+	acard->ctrl = pnp_request_card_device(card, id->devs[1].id, NULL);
+	if (acard->ctrl == NULL) {
+		kfree(cfg);
+		return -EBUSY;
+	}
+	if (id->devs[2].id[0]) {
+		acard->mpu = pnp_request_card_device(card, id->devs[2].id, NULL);
+		if (acard->mpu == NULL) {
+			kfree(cfg);
+			return -EBUSY;
+		}
+	}
+
+	/* WSS initialization */
+	pdev = acard->wss;
+	pnp_init_resource_table(cfg);
+	if (port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[0], port[dev], 4);
+	if (fm_port[dev] != SNDRV_AUTO_PORT && fm_port[dev] > 0)
+		pnp_resource_change(&cfg->port_resource[1], fm_port[dev], 4);
+	if (sb_port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[2], sb_port[dev], 16);
+	if (irq[dev] != SNDRV_AUTO_IRQ)
+		pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1);
+	if (dma1[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[0], dma1[dev], 1);
+	if (dma2[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[1], dma2[dev] < 0 ? 4 : dma2[dev], 1);
+	err = pnp_manual_config_dev(pdev, cfg, 0);
+	if (err < 0)
+		snd_printk(KERN_ERR IDENT " WSS PnP manual resources are invalid, using auto config\n");
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		kfree(cfg);
+		printk(KERN_ERR IDENT " WSS PnP configure failed for WSS (out of resources?)\n");
+		return -EBUSY;
+	}
+	port[dev] = pnp_port_start(pdev, 0);
+	if (fm_port[dev] > 0)
+		fm_port[dev] = pnp_port_start(pdev, 1);
+	sb_port[dev] = pnp_port_start(pdev, 2);
+	irq[dev] = pnp_irq(pdev, 0);
+	dma1[dev] = pnp_dma(pdev, 0);
+	dma2[dev] = pnp_dma(pdev, 1) == 4 ? -1 : (int)pnp_dma(pdev, 1);
+	snd_printdd("isapnp WSS: wss port=0x%lx, fm port=0x%lx, sb port=0x%lx\n",
+			port[dev], fm_port[dev], sb_port[dev]);
+	snd_printdd("isapnp WSS: irq=%i, dma1=%i, dma2=%i\n",
+			irq[dev], dma1[dev], dma2[dev]);
+	/* CTRL initialization */
+	if (acard->ctrl && cport[dev] > 0) {
+		pdev = acard->ctrl;
+		pnp_init_resource_table(cfg);
+		if (cport[dev] != SNDRV_AUTO_PORT)
+			pnp_resource_change(&cfg->port_resource[0], cport[dev], 8);
+		err = pnp_manual_config_dev(pdev, cfg, 0);
+		if (err < 0)
+			snd_printk(KERN_ERR IDENT " CTRL PnP manual resources are invalid, using auto config\n");
+		err = pnp_activate_dev(pdev);
+		if (err < 0) {
+			kfree(cfg);
+			printk(KERN_ERR IDENT " CTRL PnP configure failed for WSS (out of resources?)\n");
+			return -EBUSY;
+		}
+		cport[dev] = pnp_port_start(pdev, 0);
+		snd_printdd("isapnp CTRL: control port=0x%lx\n", cport[dev]);
+	}
+	/* MPU initialization */
+	if (acard->mpu && mpu_port[dev] > 0) {
+		pdev = acard->mpu;
+		pnp_init_resource_table(cfg);
+		if (mpu_port[dev] != SNDRV_AUTO_PORT)
+			pnp_resource_change(&cfg->port_resource[0], mpu_port[dev], 2);
+		if (mpu_irq[dev] != SNDRV_AUTO_IRQ && mpu_irq[dev] >= 0 &&
+		    pnp_irq_valid(pdev, 0))
+			pnp_resource_change(&cfg->irq_resource[0], mpu_irq[dev], 1);
+		err = pnp_manual_config_dev(pdev, cfg, 0);
+		if (err < 0)
+			snd_printk(KERN_ERR IDENT " MPU401 PnP manual resources are invalid, using auto config\n");
+		err = pnp_activate_dev(pdev);
+		if (err < 0) {
+			printk(KERN_ERR IDENT " MPU401 PnP configure failed for WSS (out of resources?)\n");
+			mpu_port[dev] = SNDRV_AUTO_PORT;
+			mpu_irq[dev] = SNDRV_AUTO_IRQ;
+		} else {
+			mpu_port[dev] = pnp_port_start(pdev, 0);
+			if (mpu_irq[dev] >= 0 &&
+			    pnp_irq_valid(pdev, 0) && pnp_irq(pdev, 0) >= 0) {
+				mpu_irq[dev] = pnp_irq(pdev, 0);
+			} else {
+				mpu_irq[dev] = -1;	/* disable interrupt */
+			}
+		}
+		snd_printdd("isapnp MPU: port=0x%lx, irq=%i\n", mpu_port[dev], mpu_irq[dev]);
+	}
+	kfree(cfg);
+	return 0;
+}
+#endif /* CONFIG_PNP */
+
+static void snd_card_cs4236_free(snd_card_t *card)
+{
+	struct snd_card_cs4236 *acard = (struct snd_card_cs4236 *)card->private_data;
+
+	if (acard) {
+		if (acard->res_sb_port) {
+			release_resource(acard->res_sb_port);
+			kfree_nocheck(acard->res_sb_port);
+		}
+	}
+}
+
+static int __devinit snd_card_cs423x_probe(int dev, struct pnp_card_link *pcard,
+					   const struct pnp_card_device_id *pid)
+{
+	snd_card_t *card;
+	struct snd_card_cs4236 *acard;
+	snd_pcm_t *pcm = NULL;
+	cs4231_t *chip;
+	opl3_t *opl3;
+	int err;
+
+#ifdef CONFIG_PNP
+	if (!isapnp[dev]) {
+#endif
+		if (port[dev] == SNDRV_AUTO_PORT) {
+			snd_printk("specify port\n");
+			return -EINVAL;
+		}
+		if (cport[dev] == SNDRV_AUTO_PORT) {
+			snd_printk("specify cport\n");
+			return -EINVAL;
+		}
+#ifdef CONFIG_PNP
+	}
+#endif
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE,
+			    sizeof(struct snd_card_cs4236));
+	if (card == NULL)
+		return -ENOMEM;
+	acard = (struct snd_card_cs4236 *)card->private_data;
+	card->private_free = snd_card_cs4236_free;
+#ifdef CONFIG_PNP
+	if (isapnp[dev]) {
+		if ((err = snd_card_cs4236_pnp(dev, acard, pcard, pid))<0) {
+			printk(KERN_ERR "isapnp detection failed and probing for " IDENT " is not supported\n");
+			snd_card_free(card);
+			return -ENXIO;
+		}
+		snd_card_set_dev(card, &pcard->card->dev);
+	}
+#endif
+	if (sb_port[dev] > 0 && sb_port[dev] != SNDRV_AUTO_PORT)
+		if ((acard->res_sb_port = request_region(sb_port[dev], 16, IDENT " SB")) == NULL) {
+			printk(KERN_ERR IDENT ": unable to register SB port at 0x%lx\n", sb_port[dev]);
+			snd_card_free(card);
+			return -ENOMEM;
+		}
+
+#ifdef CS4232
+	if ((err = snd_cs4231_create(card,
+				     port[dev],
+				     cport[dev],
+				     irq[dev],
+				     dma1[dev],
+				     dma2[dev],
+				     CS4231_HW_DETECT,
+				     0,
+				     &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_cs4231_pcm(chip, 0, &pcm)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_cs4231_mixer(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+#else /* CS4236 */
+	if ((err = snd_cs4236_create(card,
+				     port[dev],
+				     cport[dev],
+				     irq[dev],
+				     dma1[dev],
+				     dma2[dev],
+				     CS4231_HW_DETECT,
+				     0,
+				     &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_cs4236_pcm(chip, 0, &pcm)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_cs4236_mixer(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+#endif
+	strcpy(card->driver, pcm->name);
+	strcpy(card->shortname, pcm->name);
+	sprintf(card->longname, "%s at 0x%lx, irq %i, dma %i",
+		pcm->name,
+		chip->port,
+		irq[dev],
+		dma1[dev]);
+	if (dma2[dev] >= 0)
+		sprintf(card->longname + strlen(card->longname), "&%d", dma2[dev]);
+
+	if ((err = snd_cs4231_timer(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+		if (snd_opl3_create(card,
+				    fm_port[dev], fm_port[dev] + 2,
+				    OPL3_HW_OPL3_CS, 0, &opl3) < 0) {
+			printk(KERN_ERR IDENT ": OPL3 not detected\n");
+		} else {
+			if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+				snd_card_free(card);
+				return err;
+			}
+		}
+	}
+
+	if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) {
+		if (mpu_irq[dev] == SNDRV_AUTO_IRQ)
+			mpu_irq[dev] = -1;
+		if (snd_mpu401_uart_new(card, 0, MPU401_HW_CS4232,
+					mpu_port[dev], 0,
+					mpu_irq[dev],
+					mpu_irq[dev] >= 0 ? SA_INTERRUPT : 0, NULL) < 0)
+			printk(KERN_ERR IDENT ": MPU401 not detected\n");
+	}
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (pcard)
+		pnp_set_card_drvdata(pcard, card);
+	else
+		snd_cs4236_legacy[dev] = card;
+	return 0;
+}
+
+#ifdef CONFIG_PNP
+static int __devinit snd_cs423x_pnp_detect(struct pnp_card_link *card,
+					   const struct pnp_card_device_id *id)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || !isapnp[dev])
+			continue;
+		res = snd_card_cs423x_probe(dev, card, id);
+		if (res < 0)
+			return res;
+		dev++;
+		return 0;
+	}
+	return -ENODEV;
+}
+
+static void __devexit snd_cs423x_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_t *card = (snd_card_t *) pnp_get_card_drvdata(pcard);
+        
+	snd_card_disconnect(card);
+	snd_card_free_in_thread(card);
+}
+                        
+static struct pnp_card_driver cs423x_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DISABLE,
+	.name = "cs423x",
+	.id_table = snd_cs423x_pnpids,
+	.probe = snd_cs423x_pnp_detect,
+	.remove = __devexit_p(snd_cs423x_pnp_remove),
+};
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_cs423x_init(void)
+{
+	int dev, cards = 0;
+
+	for (dev = 0; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev])
+			continue;
+#ifdef CONFIG_PNP
+		if (isapnp[dev])
+			continue;
+#endif
+		if (snd_card_cs423x_probe(dev, NULL, NULL) >= 0)
+			cards++;
+	}
+#ifdef CONFIG_PNP
+	cards += pnp_register_card_driver(&cs423x_pnpc_driver);
+#endif
+	if (!cards) {
+#ifdef CONFIG_PNP
+		pnp_unregister_card_driver(&cs423x_pnpc_driver);
+#endif
+#ifdef MODULE
+		printk(KERN_ERR IDENT " soundcard not found or device busy\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_cs423x_exit(void)
+{
+	int idx;
+
+#ifdef CONFIG_PNP
+	/* PnP cards first */
+	pnp_unregister_card_driver(&cs423x_pnpc_driver);
+#endif
+	for (idx = 0; idx < SNDRV_CARDS; idx++)
+		snd_card_free(snd_cs4236_legacy[idx]);
+}
+
+module_init(alsa_card_cs423x_init)
+module_exit(alsa_card_cs423x_exit)
diff --git a/sound/isa/cs423x/cs4236_lib.c b/sound/isa/cs423x/cs4236_lib.c
new file mode 100644
index 0000000..2128d4b
--- /dev/null
+++ b/sound/isa/cs423x/cs4236_lib.c
@@ -0,0 +1,970 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  Routines for control of CS4235/4236B/4237B/4238B/4239 chips
+ *
+ *  Note:
+ *     -----
+ *
+ *  Bugs:
+ *     -----
+ *
+ *   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
+ *
+ */
+
+/*
+ *  Indirect control registers (CS4236B+)
+ * 
+ *  C0
+ *     D8: WSS reset (all chips)
+ *
+ *  C1 (all chips except CS4236)
+ *     D7-D5: version 
+ *     D4-D0: chip id
+ *             11101 - CS4235
+ *             01011 - CS4236B
+ *             01000 - CS4237B
+ *             01001 - CS4238B
+ *             11110 - CS4239
+ *
+ *  C2
+ *     D7-D4: 3D Space (CS4235,CS4237B,CS4238B,CS4239)
+ *     D3-D0: 3D Center (CS4237B); 3D Volume (CS4238B)
+ * 
+ *  C3
+ *     D7: 3D Enable (CS4237B)
+ *     D6: 3D Mono Enable (CS4237B)
+ *     D5: 3D Serial Output (CS4237B,CS4238B)
+ *     D4: 3D Enable (CS4235,CS4238B,CS4239)
+ *
+ *  C4
+ *     D7: consumer serial port enable (CS4237B,CS4238B)
+ *     D6: channels status block reset (CS4237B,CS4238B)
+ *     D5: user bit in sub-frame of digital audio data (CS4237B,CS4238B)
+ *     D4: validity bit bit in sub-frame of digital audio data (CS4237B,CS4238B)
+ * 
+ *  C5  lower channel status (digital serial data description) (CS4237B,CS4238B)
+ *     D7-D6: first two bits of category code
+ *     D5: lock
+ *     D4-D3: pre-emphasis (0 = none, 1 = 50/15us)
+ *     D2: copy/copyright (0 = copy inhibited)
+ *     D1: 0 = digital audio / 1 = non-digital audio
+ *     
+ *  C6  upper channel status (digital serial data description) (CS4237B,CS4238B)
+ *     D7-D6: sample frequency (0 = 44.1kHz)
+ *     D5: generation status (0 = no indication, 1 = original/commercially precaptureed data)
+ *     D4-D0: category code (upper bits)
+ *
+ *  C7  reserved (must write 0)
+ *
+ *  C8  wavetable control
+ *     D7: volume control interrupt enable (CS4235,CS4239)
+ *     D6: hardware volume control format (CS4235,CS4239)
+ *     D3: wavetable serial port enable (all chips)
+ *     D2: DSP serial port switch (all chips)
+ *     D1: disable MCLK (all chips)
+ *     D0: force BRESET low (all chips)
+ *
+ */
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <sound/core.h>
+#include <sound/cs4231.h>
+#include <sound/asoundef.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Routines for control of CS4235/4236B/4237B/4238B/4239 chips");
+MODULE_LICENSE("GPL");
+
+/*
+ *
+ */
+
+static unsigned char snd_cs4236_ext_map[18] = {
+	/* CS4236_LEFT_LINE */		0xff,
+	/* CS4236_RIGHT_LINE */		0xff,
+	/* CS4236_LEFT_MIC */		0xdf,
+	/* CS4236_RIGHT_MIC */		0xdf,
+	/* CS4236_LEFT_MIX_CTRL */	0xe0 | 0x18,
+	/* CS4236_RIGHT_MIX_CTRL */	0xe0,
+	/* CS4236_LEFT_FM */		0xbf,
+	/* CS4236_RIGHT_FM */		0xbf,
+	/* CS4236_LEFT_DSP */		0xbf,
+	/* CS4236_RIGHT_DSP */		0xbf,
+	/* CS4236_RIGHT_LOOPBACK */	0xbf,
+	/* CS4236_DAC_MUTE */		0xe0,
+	/* CS4236_ADC_RATE */		0x01,	/* 48kHz */
+	/* CS4236_DAC_RATE */		0x01,	/* 48kHz */
+	/* CS4236_LEFT_MASTER */	0xbf,
+	/* CS4236_RIGHT_MASTER */	0xbf,
+	/* CS4236_LEFT_WAVE */		0xbf,
+	/* CS4236_RIGHT_WAVE */		0xbf
+};
+
+/*
+ *
+ */
+
+static void snd_cs4236_ctrl_out(cs4231_t *chip, unsigned char reg, unsigned char val)
+{
+	outb(reg, chip->cport + 3);
+	outb(chip->cimage[reg] = val, chip->cport + 4);
+}
+
+static unsigned char snd_cs4236_ctrl_in(cs4231_t *chip, unsigned char reg)
+{
+	outb(reg, chip->cport + 3);
+	return inb(chip->cport + 4);
+}
+
+/*
+ *  PCM
+ */
+
+#define CLOCKS 8
+
+static ratnum_t clocks[CLOCKS] = {
+	{ .num = 16934400, .den_min = 353, .den_max = 353, .den_step = 1 },
+	{ .num = 16934400, .den_min = 529, .den_max = 529, .den_step = 1 },
+	{ .num = 16934400, .den_min = 617, .den_max = 617, .den_step = 1 },
+	{ .num = 16934400, .den_min = 1058, .den_max = 1058, .den_step = 1 },
+	{ .num = 16934400, .den_min = 1764, .den_max = 1764, .den_step = 1 },
+	{ .num = 16934400, .den_min = 2117, .den_max = 2117, .den_step = 1 },
+	{ .num = 16934400, .den_min = 2558, .den_max = 2558, .den_step = 1 },
+	{ .num = 16934400/16, .den_min = 21, .den_max = 192, .den_step = 1 }
+};
+
+static snd_pcm_hw_constraint_ratnums_t hw_constraints_clocks = {
+	.nrats = CLOCKS,
+	.rats = clocks,
+};
+
+static int snd_cs4236_xrate(snd_pcm_runtime_t *runtime)
+{
+	return snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+					     &hw_constraints_clocks);
+}
+
+static unsigned char divisor_to_rate_register(unsigned int divisor)
+{
+	switch (divisor) {
+	case 353:	return 1;
+	case 529:	return 2;
+	case 617:	return 3;
+	case 1058:	return 4;
+	case 1764:	return 5;
+	case 2117:	return 6;
+	case 2558:	return 7;
+	default:
+		snd_runtime_check(divisor >= 21 && divisor <= 192, return 192);
+		return divisor;
+	}
+}
+
+static void snd_cs4236_playback_format(cs4231_t *chip, snd_pcm_hw_params_t *params, unsigned char pdfr)
+{
+	unsigned long flags;
+	unsigned char rate = divisor_to_rate_register(params->rate_den);
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	/* set fast playback format change and clean playback FIFO */
+	snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] | 0x10);
+	snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, pdfr & 0xf0);
+	snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] & ~0x10);
+	snd_cs4236_ext_out(chip, CS4236_DAC_RATE, rate);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_cs4236_capture_format(cs4231_t *chip, snd_pcm_hw_params_t *params, unsigned char cdfr)
+{
+	unsigned long flags;
+	unsigned char rate = divisor_to_rate_register(params->rate_den);
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	/* set fast capture format change and clean capture FIFO */
+	snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] | 0x20);
+	snd_cs4231_out(chip, CS4231_REC_FORMAT, cdfr & 0xf0);
+	snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] & ~0x20);
+	snd_cs4236_ext_out(chip, CS4236_ADC_RATE, rate);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+#ifdef CONFIG_PM
+
+static void snd_cs4236_suspend(cs4231_t *chip)
+{
+	int reg;
+	unsigned long flags;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	for (reg = 0; reg < 32; reg++)
+		chip->image[reg] = snd_cs4231_in(chip, reg);
+	for (reg = 0; reg < 18; reg++)
+		chip->eimage[reg] = snd_cs4236_ext_in(chip, CS4236_I23VAL(reg));
+	for (reg = 2; reg < 9; reg++)
+		chip->cimage[reg] = snd_cs4236_ctrl_in(chip, reg);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_cs4236_resume(cs4231_t *chip)
+{
+	int reg;
+	unsigned long flags;
+	
+	snd_cs4231_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	for (reg = 0; reg < 32; reg++) {
+		switch (reg) {
+		case CS4236_EXT_REG:
+		case CS4231_VERSION:
+		case 27:	/* why? CS4235 - master left */
+		case 29:	/* why? CS4235 - master right */
+			break;
+		default:
+			snd_cs4231_out(chip, reg, chip->image[reg]);
+			break;
+		}
+	}
+	for (reg = 0; reg < 18; reg++)
+		snd_cs4236_ext_out(chip, CS4236_I23VAL(reg), chip->eimage[reg]);
+	for (reg = 2; reg < 9; reg++) {
+		switch (reg) {
+		case 7:
+			break;
+		default:
+			snd_cs4236_ctrl_out(chip, reg, chip->cimage[reg]);
+		}
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_cs4231_mce_down(chip);
+}
+
+#endif /* CONFIG_PM */
+
+int snd_cs4236_create(snd_card_t * card,
+		      unsigned long port,
+		      unsigned long cport,
+		      int irq, int dma1, int dma2,
+		      unsigned short hardware,
+		      unsigned short hwshare,
+		      cs4231_t ** rchip)
+{
+	cs4231_t *chip;
+	unsigned char ver1, ver2;
+	unsigned int reg;
+	int err;
+
+	*rchip = NULL;
+	if (hardware == CS4231_HW_DETECT)
+		hardware = CS4231_HW_DETECT3;
+	if (cport < 0x100) {
+		snd_printk("please, specify control port for CS4236+ chips\n");
+		return -ENODEV;
+	}
+	if ((err = snd_cs4231_create(card, port, cport, irq, dma1, dma2, hardware, hwshare, &chip)) < 0)
+		return err;
+
+	if (!(chip->hardware & CS4231_HW_CS4236B_MASK)) {
+	        snd_printk("CS4236+: MODE3 and extended registers not available, hardware=0x%x\n",chip->hardware);
+		snd_device_free(card, chip);
+		return -ENODEV;
+	}
+#if 0
+	{
+		int idx;
+		for (idx = 0; idx < 8; idx++)
+			snd_printk("CD%i = 0x%x\n", idx, inb(chip->cport + idx));
+		for (idx = 0; idx < 9; idx++)
+			snd_printk("C%i = 0x%x\n", idx, snd_cs4236_ctrl_in(chip, idx));
+	}
+#endif
+	ver1 = snd_cs4236_ctrl_in(chip, 1);
+	ver2 = snd_cs4236_ext_in(chip, CS4236_VERSION);
+	snd_printdd("CS4236: [0x%lx] C1 (version) = 0x%x, ext = 0x%x\n", cport, ver1, ver2);
+	if (ver1 != ver2) {
+		snd_printk("CS4236+ chip detected, but control port 0x%lx is not valid\n", cport);
+		snd_device_free(card, chip);
+		return -ENODEV;
+	}
+	snd_cs4236_ctrl_out(chip, 0, 0x00);
+	snd_cs4236_ctrl_out(chip, 2, 0xff);
+	snd_cs4236_ctrl_out(chip, 3, 0x00);
+	snd_cs4236_ctrl_out(chip, 4, 0x80);
+	snd_cs4236_ctrl_out(chip, 5, ((IEC958_AES1_CON_PCM_CODER & 3) << 6) | IEC958_AES0_CON_EMPHASIS_NONE);
+	snd_cs4236_ctrl_out(chip, 6, IEC958_AES1_CON_PCM_CODER >> 2);
+	snd_cs4236_ctrl_out(chip, 7, 0x00);
+	/* 0x8c for C8 is valid for Turtle Beach Malibu - the IEC-958 output */
+	/* is working with this setup, other hardware should have */
+	/* different signal paths and this value should be selectable */
+	/* in the future */
+	snd_cs4236_ctrl_out(chip, 8, 0x8c);
+	chip->rate_constraint = snd_cs4236_xrate;
+	chip->set_playback_format = snd_cs4236_playback_format;
+	chip->set_capture_format = snd_cs4236_capture_format;
+#ifdef CONFIG_PM
+	chip->suspend = snd_cs4236_suspend;
+	chip->resume = snd_cs4236_resume;
+#endif
+
+	/* initialize extended registers */
+	for (reg = 0; reg < sizeof(snd_cs4236_ext_map); reg++)
+		snd_cs4236_ext_out(chip, CS4236_I23VAL(reg), snd_cs4236_ext_map[reg]);
+
+        /* initialize compatible but more featured registers */
+	snd_cs4231_out(chip, CS4231_LEFT_INPUT, 0x40);
+	snd_cs4231_out(chip, CS4231_RIGHT_INPUT, 0x40);
+	snd_cs4231_out(chip, CS4231_AUX1_LEFT_INPUT, 0xff);
+	snd_cs4231_out(chip, CS4231_AUX1_RIGHT_INPUT, 0xff);
+	snd_cs4231_out(chip, CS4231_AUX2_LEFT_INPUT, 0xdf);
+	snd_cs4231_out(chip, CS4231_AUX2_RIGHT_INPUT, 0xdf);
+	snd_cs4231_out(chip, CS4231_RIGHT_LINE_IN, 0xff);
+	snd_cs4231_out(chip, CS4231_LEFT_LINE_IN, 0xff);
+	snd_cs4231_out(chip, CS4231_RIGHT_LINE_IN, 0xff);
+	switch (chip->hardware) {
+	case CS4231_HW_CS4235:
+	case CS4231_HW_CS4239:
+		snd_cs4231_out(chip, CS4235_LEFT_MASTER, 0xff);
+		snd_cs4231_out(chip, CS4235_RIGHT_MASTER, 0xff);
+		break;
+	}
+
+	*rchip = chip;
+	return 0;
+}
+
+int snd_cs4236_pcm(cs4231_t *chip, int device, snd_pcm_t **rpcm)
+{
+	snd_pcm_t *pcm;
+	int err;
+	
+	if ((err = snd_cs4231_pcm(chip, device, &pcm)) < 0)
+		return err;
+	pcm->info_flags &= ~SNDRV_PCM_INFO_JOINT_DUPLEX;
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+/*
+ *  MIXER
+ */
+
+#define CS4236_SINGLE(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_cs4236_info_single, \
+  .get = snd_cs4236_get_single, .put = snd_cs4236_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+static int snd_cs4236_info_single(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_cs4236_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (chip->eimage[CS4236_REG(reg)] >> shift) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_cs4236_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned short val;
+	
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	val <<= shift;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val = (chip->eimage[CS4236_REG(reg)] & ~(mask << shift)) | val;
+	change = val != chip->eimage[CS4236_REG(reg)];
+	snd_cs4236_ext_out(chip, reg, val);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+#define CS4236_SINGLEC(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_cs4236_info_single, \
+  .get = snd_cs4236_get_singlec, .put = snd_cs4236_put_singlec, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+static int snd_cs4236_get_singlec(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (chip->cimage[reg] >> shift) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_cs4236_put_singlec(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned short val;
+	
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	val <<= shift;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val = (chip->cimage[reg] & ~(mask << shift)) | val;
+	change = val != chip->cimage[reg];
+	snd_cs4236_ctrl_out(chip, reg, val);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+#define CS4236_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_cs4236_info_double, \
+  .get = snd_cs4236_get_double, .put = snd_cs4236_put_double, \
+  .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
+
+static int snd_cs4236_info_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_cs4236_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (chip->eimage[CS4236_REG(left_reg)] >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (chip->eimage[CS4236_REG(right_reg)] >> shift_right) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+
+static int snd_cs4236_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	unsigned short val1, val2;
+	
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (left_reg != right_reg) {
+		val1 = (chip->eimage[CS4236_REG(left_reg)] & ~(mask << shift_left)) | val1;
+		val2 = (chip->eimage[CS4236_REG(right_reg)] & ~(mask << shift_right)) | val2;
+		change = val1 != chip->eimage[CS4236_REG(left_reg)] || val2 != chip->eimage[CS4236_REG(right_reg)];
+		snd_cs4236_ext_out(chip, left_reg, val1);
+		snd_cs4236_ext_out(chip, right_reg, val2);
+	} else {
+		val1 = (chip->eimage[CS4236_REG(left_reg)] & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2;
+		change = val1 != chip->eimage[CS4236_REG(left_reg)];
+		snd_cs4236_ext_out(chip, left_reg, val1);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+#define CS4236_DOUBLE1(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_cs4236_info_double, \
+  .get = snd_cs4236_get_double1, .put = snd_cs4236_put_double1, \
+  .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
+
+static int snd_cs4236_get_double1(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (chip->image[left_reg] >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (chip->eimage[CS4236_REG(right_reg)] >> shift_right) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+
+static int snd_cs4236_put_double1(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	unsigned short val1, val2;
+	
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1;
+	val2 = (chip->eimage[CS4236_REG(right_reg)] & ~(mask << shift_right)) | val2;
+	change = val1 != chip->image[left_reg] || val2 != chip->eimage[CS4236_REG(right_reg)];
+	snd_cs4231_out(chip, left_reg, val1);
+	snd_cs4236_ext_out(chip, right_reg, val2);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+#define CS4236_MASTER_DIGITAL(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_cs4236_info_double, \
+  .get = snd_cs4236_get_master_digital, .put = snd_cs4236_put_master_digital, \
+  .private_value = 71 << 24 }
+
+static inline int snd_cs4236_mixer_master_digital_invert_volume(int vol)
+{
+	return (vol < 64) ? 63 - vol : 64 + (71 - vol);
+}        
+
+static int snd_cs4236_get_master_digital(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = snd_cs4236_mixer_master_digital_invert_volume(chip->eimage[CS4236_REG(CS4236_LEFT_MASTER)] & 0x7f);
+	ucontrol->value.integer.value[1] = snd_cs4236_mixer_master_digital_invert_volume(chip->eimage[CS4236_REG(CS4236_RIGHT_MASTER)] & 0x7f);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_cs4236_put_master_digital(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned short val1, val2;
+	
+	val1 = snd_cs4236_mixer_master_digital_invert_volume(ucontrol->value.integer.value[0] & 0x7f);
+	val2 = snd_cs4236_mixer_master_digital_invert_volume(ucontrol->value.integer.value[1] & 0x7f);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val1 = (chip->eimage[CS4236_REG(CS4236_LEFT_MASTER)] & ~0x7f) | val1;
+	val2 = (chip->eimage[CS4236_REG(CS4236_RIGHT_MASTER)] & ~0x7f) | val2;
+	change = val1 != chip->eimage[CS4236_REG(CS4236_LEFT_MASTER)] || val2 != chip->eimage[CS4236_REG(CS4236_RIGHT_MASTER)];
+	snd_cs4236_ext_out(chip, CS4236_LEFT_MASTER, val1);
+	snd_cs4236_ext_out(chip, CS4236_RIGHT_MASTER, val1);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+#define CS4235_OUTPUT_ACCU(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_cs4236_info_double, \
+  .get = snd_cs4235_get_output_accu, .put = snd_cs4235_put_output_accu, \
+  .private_value = 3 << 24 }
+
+static inline int snd_cs4235_mixer_output_accu_get_volume(int vol)
+{
+	switch ((vol >> 5) & 3) {
+	case 0: return 1;
+	case 1: return 3;
+	case 2: return 2;
+	case 3: return 0;
+ 	}
+	return 3;
+}
+
+static inline int snd_cs4235_mixer_output_accu_set_volume(int vol)
+{
+	switch (vol & 3) {
+	case 0: return 3 << 5;
+	case 1: return 0 << 5;
+	case 2: return 2 << 5;
+	case 3: return 1 << 5;
+	}
+	return 1 << 5;
+}
+
+static int snd_cs4235_get_output_accu(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = snd_cs4235_mixer_output_accu_get_volume(chip->image[CS4235_LEFT_MASTER]);
+	ucontrol->value.integer.value[1] = snd_cs4235_mixer_output_accu_get_volume(chip->image[CS4235_RIGHT_MASTER]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_cs4235_put_output_accu(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned short val1, val2;
+	
+	val1 = snd_cs4235_mixer_output_accu_set_volume(ucontrol->value.integer.value[0]);
+	val2 = snd_cs4235_mixer_output_accu_set_volume(ucontrol->value.integer.value[1]);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val1 = (chip->image[CS4235_LEFT_MASTER] & ~(3 << 5)) | val1;
+	val2 = (chip->image[CS4235_RIGHT_MASTER] & ~(3 << 5)) | val2;
+	change = val1 != chip->image[CS4235_LEFT_MASTER] || val2 != chip->image[CS4235_RIGHT_MASTER];
+	snd_cs4231_out(chip, CS4235_LEFT_MASTER, val1);
+	snd_cs4231_out(chip, CS4235_RIGHT_MASTER, val2);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_cs4236_controls[] = {
+
+CS4236_DOUBLE("Master Digital Playback Switch", 0, CS4236_LEFT_MASTER, CS4236_RIGHT_MASTER, 7, 7, 1, 1),
+CS4236_DOUBLE("Master Digital Capture Switch", 0, CS4236_DAC_MUTE, CS4236_DAC_MUTE, 7, 6, 1, 1),
+CS4236_MASTER_DIGITAL("Master Digital Volume", 0),
+
+CS4236_DOUBLE("Capture Boost Volume", 0, CS4236_LEFT_MIX_CTRL, CS4236_RIGHT_MIX_CTRL, 5, 5, 3, 1),
+
+CS4231_DOUBLE("PCM Playback Switch", 0, CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
+CS4231_DOUBLE("PCM Playback Volume", 0, CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1),
+
+CS4236_DOUBLE("DSP Playback Switch", 0, CS4236_LEFT_DSP, CS4236_RIGHT_DSP, 7, 7, 1, 1),
+CS4236_DOUBLE("DSP Playback Volume", 0, CS4236_LEFT_DSP, CS4236_RIGHT_DSP, 0, 0, 63, 1),
+
+CS4236_DOUBLE("FM Playback Switch", 0, CS4236_LEFT_FM, CS4236_RIGHT_FM, 7, 7, 1, 1),
+CS4236_DOUBLE("FM Playback Volume", 0, CS4236_LEFT_FM, CS4236_RIGHT_FM, 0, 0, 63, 1),
+
+CS4236_DOUBLE("Wavetable Playback Switch", 0, CS4236_LEFT_WAVE, CS4236_RIGHT_WAVE, 7, 7, 1, 1),
+CS4236_DOUBLE("Wavetable Playback Volume", 0, CS4236_LEFT_WAVE, CS4236_RIGHT_WAVE, 0, 0, 63, 1),
+
+CS4231_DOUBLE("Synth Playback Switch", 0, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1),
+CS4231_DOUBLE("Synth Volume", 0, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1),
+CS4231_DOUBLE("Synth Capture Switch", 0, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 6, 6, 1, 1),
+CS4231_DOUBLE("Synth Capture Bypass", 0, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 5, 5, 1, 1),
+
+CS4236_DOUBLE("Mic Playback Switch", 0, CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 6, 6, 1, 1),
+CS4236_DOUBLE("Mic Capture Switch", 0, CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 7, 7, 1, 1),
+CS4236_DOUBLE("Mic Volume", 0, CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 0, 0, 31, 1),
+CS4236_DOUBLE("Mic Playback Boost", 0, CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 5, 5, 1, 0),
+
+CS4231_DOUBLE("Line Playback Switch", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1),
+CS4231_DOUBLE("Line Volume", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1),
+CS4231_DOUBLE("Line Capture Switch", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 6, 6, 1, 1),
+CS4231_DOUBLE("Line Capture Bypass", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 5, 5, 1, 1),
+
+CS4231_DOUBLE("CD Playback Switch", 0, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
+CS4231_DOUBLE("CD Volume", 0, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1),
+CS4231_DOUBLE("CD Capture Switch", 0, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 6, 6, 1, 1),
+
+CS4236_DOUBLE1("Mono Output Playback Switch", 0, CS4231_MONO_CTRL, CS4236_RIGHT_MIX_CTRL, 6, 7, 1, 1),
+CS4236_DOUBLE1("Mono Playback Switch", 0, CS4231_MONO_CTRL, CS4236_LEFT_MIX_CTRL, 7, 7, 1, 1),
+CS4231_SINGLE("Mono Playback Volume", 0, CS4231_MONO_CTRL, 0, 15, 1),
+CS4231_SINGLE("Mono Playback Bypass", 0, CS4231_MONO_CTRL, 5, 1, 0),
+
+CS4231_DOUBLE("Capture Volume", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 0, 0, 15, 0),
+CS4231_DOUBLE("Analog Loopback Capture Switch", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 7, 7, 1, 0),
+
+CS4231_SINGLE("Digital Loopback Playback Switch", 0, CS4231_LOOPBACK, 0, 1, 0),
+CS4236_DOUBLE1("Digital Loopback Playback Volume", 0, CS4231_LOOPBACK, CS4236_RIGHT_LOOPBACK, 2, 0, 63, 1)
+};
+
+static snd_kcontrol_new_t snd_cs4235_controls[] = {
+
+CS4231_DOUBLE("Master Switch", 0, CS4235_LEFT_MASTER, CS4235_RIGHT_MASTER, 7, 7, 1, 1),
+CS4231_DOUBLE("Master Volume", 0, CS4235_LEFT_MASTER, CS4235_RIGHT_MASTER, 0, 0, 31, 1),
+
+CS4235_OUTPUT_ACCU("Playback Volume", 0),
+
+CS4236_DOUBLE("Master Digital Playback Switch", 0, CS4236_LEFT_MASTER, CS4236_RIGHT_MASTER, 7, 7, 1, 1),
+CS4236_DOUBLE("Master Digital Capture Switch", 0, CS4236_DAC_MUTE, CS4236_DAC_MUTE, 7, 6, 1, 1),
+CS4236_MASTER_DIGITAL("Master Digital Volume", 0),
+
+CS4231_DOUBLE("Master Digital Playback Switch", 1, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1),
+CS4231_DOUBLE("Master Digital Capture Switch", 1, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 6, 6, 1, 1),
+CS4231_DOUBLE("Master Digital Volume", 1, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1),
+
+CS4236_DOUBLE("Capture Volume", 0, CS4236_LEFT_MIX_CTRL, CS4236_RIGHT_MIX_CTRL, 5, 5, 3, 1),
+
+CS4231_DOUBLE("PCM Switch", 0, CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
+CS4231_DOUBLE("PCM Volume", 0, CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1),
+
+CS4236_DOUBLE("DSP Switch", 0, CS4236_LEFT_DSP, CS4236_RIGHT_DSP, 7, 7, 1, 1),
+
+CS4236_DOUBLE("FM Switch", 0, CS4236_LEFT_FM, CS4236_RIGHT_FM, 7, 7, 1, 1),
+
+CS4236_DOUBLE("Wavetable Switch", 0, CS4236_LEFT_WAVE, CS4236_RIGHT_WAVE, 7, 7, 1, 1),
+
+CS4236_DOUBLE("Mic Capture Switch", 0, CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 7, 7, 1, 1),
+CS4236_DOUBLE("Mic Playback Switch", 0, CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 6, 6, 1, 1),
+CS4236_SINGLE("Mic Volume", 0, CS4236_LEFT_MIC, 0, 31, 1),
+CS4236_SINGLE("Mic Playback Boost", 0, CS4236_LEFT_MIC, 5, 1, 0),
+
+CS4231_DOUBLE("Aux Playback Switch", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1),
+CS4231_DOUBLE("Aux Capture Switch", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 6, 6, 1, 1),
+CS4231_DOUBLE("Aux Volume", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1),
+
+CS4231_DOUBLE("Aux Playback Switch", 1, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
+CS4231_DOUBLE("Aux Capture Switch", 1, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 6, 6, 1, 1),
+CS4231_DOUBLE("Aux Volume", 1, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1),
+
+CS4236_DOUBLE1("Master Mono Switch", 0, CS4231_MONO_CTRL, CS4236_RIGHT_MIX_CTRL, 6, 7, 1, 1),
+
+CS4236_DOUBLE1("Mono Switch", 0, CS4231_MONO_CTRL, CS4236_LEFT_MIX_CTRL, 7, 7, 1, 1),
+CS4231_SINGLE("Mono Volume", 0, CS4231_MONO_CTRL, 0, 15, 1),
+
+CS4231_DOUBLE("Analog Loopback Switch", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 7, 7, 1, 0),
+};
+
+#define CS4236_IEC958_ENABLE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_cs4236_info_single, \
+  .get = snd_cs4236_get_iec958_switch, .put = snd_cs4236_put_iec958_switch, \
+  .private_value = 1 << 16 }
+
+static int snd_cs4236_get_iec958_switch(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = chip->image[CS4231_ALT_FEATURE_1] & 0x02 ? 1 : 0;
+#if 0
+	printk("get valid: ALT = 0x%x, C3 = 0x%x, C4 = 0x%x, C5 = 0x%x, C6 = 0x%x, C8 = 0x%x\n",
+			snd_cs4231_in(chip, CS4231_ALT_FEATURE_1),
+			snd_cs4236_ctrl_in(chip, 3),
+			snd_cs4236_ctrl_in(chip, 4),
+			snd_cs4236_ctrl_in(chip, 5),
+			snd_cs4236_ctrl_in(chip, 6),
+			snd_cs4236_ctrl_in(chip, 8));
+#endif
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_cs4236_put_iec958_switch(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned short enable, val;
+	
+	enable = ucontrol->value.integer.value[0] & 1;
+
+	down(&chip->mce_mutex);
+	snd_cs4231_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val = (chip->image[CS4231_ALT_FEATURE_1] & ~0x0e) | (0<<2) | (enable << 1);
+	change = val != chip->image[CS4231_ALT_FEATURE_1];
+	snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, val);
+	val = snd_cs4236_ctrl_in(chip, 4) | 0xc0;
+	snd_cs4236_ctrl_out(chip, 4, val);
+	udelay(100);
+	val &= ~0x40;
+	snd_cs4236_ctrl_out(chip, 4, val);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_cs4231_mce_down(chip);
+	up(&chip->mce_mutex);
+
+#if 0
+	printk("set valid: ALT = 0x%x, C3 = 0x%x, C4 = 0x%x, C5 = 0x%x, C6 = 0x%x, C8 = 0x%x\n",
+			snd_cs4231_in(chip, CS4231_ALT_FEATURE_1),
+			snd_cs4236_ctrl_in(chip, 3),
+			snd_cs4236_ctrl_in(chip, 4),
+			snd_cs4236_ctrl_in(chip, 5),
+			snd_cs4236_ctrl_in(chip, 6),
+			snd_cs4236_ctrl_in(chip, 8));
+#endif
+	return change;
+}
+
+static snd_kcontrol_new_t snd_cs4236_iec958_controls[] = {
+CS4236_IEC958_ENABLE("IEC958 Output Enable", 0),
+CS4236_SINGLEC("IEC958 Output Validity", 0, 4, 4, 1, 0),
+CS4236_SINGLEC("IEC958 Output User", 0, 4, 5, 1, 0),
+CS4236_SINGLEC("IEC958 Output CSBR", 0, 4, 6, 1, 0),
+CS4236_SINGLEC("IEC958 Output Channel Status Low", 0, 5, 1, 127, 0),
+CS4236_SINGLEC("IEC958 Output Channel Status High", 0, 6, 0, 255, 0)
+};
+
+static snd_kcontrol_new_t snd_cs4236_3d_controls_cs4235[] = {
+CS4236_SINGLEC("3D Control - Switch", 0, 3, 4, 1, 0),
+CS4236_SINGLEC("3D Control - Space", 0, 2, 4, 15, 1)
+};
+
+static snd_kcontrol_new_t snd_cs4236_3d_controls_cs4237[] = {
+CS4236_SINGLEC("3D Control - Switch", 0, 3, 7, 1, 0),
+CS4236_SINGLEC("3D Control - Space", 0, 2, 4, 15, 1),
+CS4236_SINGLEC("3D Control - Center", 0, 2, 0, 15, 1),
+CS4236_SINGLEC("3D Control - Mono", 0, 3, 6, 1, 0),
+CS4236_SINGLEC("3D Control - IEC958", 0, 3, 5, 1, 0)
+};
+
+static snd_kcontrol_new_t snd_cs4236_3d_controls_cs4238[] = {
+CS4236_SINGLEC("3D Control - Switch", 0, 3, 4, 1, 0),
+CS4236_SINGLEC("3D Control - Space", 0, 2, 4, 15, 1),
+CS4236_SINGLEC("3D Control - Volume", 0, 2, 0, 15, 1),
+CS4236_SINGLEC("3D Control - IEC958", 0, 3, 5, 1, 0)
+};
+
+int snd_cs4236_mixer(cs4231_t *chip)
+{
+	snd_card_t *card;
+	unsigned int idx, count;
+	int err;
+	snd_kcontrol_new_t *kcontrol;
+
+	snd_assert(chip != NULL && chip->card != NULL, return -EINVAL);
+	card = chip->card;
+	strcpy(card->mixername, snd_cs4231_chip_id(chip));
+
+	if (chip->hardware == CS4231_HW_CS4235 ||
+	    chip->hardware == CS4231_HW_CS4239) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_cs4235_controls); idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4235_controls[idx], chip))) < 0)
+				return err;
+		}
+	} else {
+		for (idx = 0; idx < ARRAY_SIZE(snd_cs4236_controls); idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4236_controls[idx], chip))) < 0)
+				return err;
+		}
+	}
+	switch (chip->hardware) {
+	case CS4231_HW_CS4235:
+	case CS4231_HW_CS4239:
+		count = ARRAY_SIZE(snd_cs4236_3d_controls_cs4235);
+		kcontrol = snd_cs4236_3d_controls_cs4235;
+		break;
+	case CS4231_HW_CS4237B:
+		count = ARRAY_SIZE(snd_cs4236_3d_controls_cs4237);
+		kcontrol = snd_cs4236_3d_controls_cs4237;
+		break;
+	case CS4231_HW_CS4238B:
+		count = ARRAY_SIZE(snd_cs4236_3d_controls_cs4238);
+		kcontrol = snd_cs4236_3d_controls_cs4238;
+		break;
+	default:
+		count = 0;
+		kcontrol = NULL;
+	}
+	for (idx = 0; idx < count; idx++, kcontrol++) {
+		if ((err = snd_ctl_add(card, snd_ctl_new1(kcontrol, chip))) < 0)
+			return err;
+	}
+	if (chip->hardware == CS4231_HW_CS4237B ||
+	    chip->hardware == CS4231_HW_CS4238B) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_cs4236_iec958_controls); idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4236_iec958_controls[idx], chip))) < 0)
+				return err;
+		}
+	}
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_cs4236_create);
+EXPORT_SYMBOL(snd_cs4236_pcm);
+EXPORT_SYMBOL(snd_cs4236_mixer);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_cs4236_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_cs4236_exit(void)
+{
+}
+
+module_init(alsa_cs4236_init)
+module_exit(alsa_cs4236_exit)
diff --git a/sound/isa/dt019x.c b/sound/isa/dt019x.c
new file mode 100644
index 0000000..db7c339
--- /dev/null
+++ b/sound/isa/dt019x.c
@@ -0,0 +1,327 @@
+
+/*
+    dt019x.c - driver for Diamond Technologies DT-0197H based soundcards.
+    Copyright (C) 1999, 2002 by Massimo Piccioni <dafastidio@libero.it>
+
+    Generalised for soundcards based on DT-0196 and ALS-007 chips 
+    by Jonathan Woithe <jwoithe@physics.adelaide.edu.au>: June 2002.
+
+    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/sched.h>
+#include <linux/wait.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/sb.h>
+
+#define PFX "dt019x: "
+
+MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>");
+MODULE_DESCRIPTION("Diamond Technologies DT-019X / Avance Logic ALS-007");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Diamond Technologies DT-019X},"
+	       "{Avance Logic ALS-007}}");
+
+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;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* PnP setup */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* PnP setup */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* PnP setup */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for DT-019X based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for DT-019X based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable DT-019X based soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for dt019x driver.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for dt019x driver.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for dt019x driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for dt019x driver.");
+module_param_array(mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for dt019x driver.");
+module_param_array(dma8, int, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for dt019x driver.");
+
+struct snd_card_dt019x {
+	struct pnp_dev *dev;
+	struct pnp_dev *devmpu;
+	struct pnp_dev *devopl;
+};
+
+static struct pnp_card_device_id snd_dt019x_pnpids[] = {
+	/* DT197A30 */
+	{ .id = "RWB1688", .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" }, } },
+	/* DT0196 / ALS-007 */
+	{ .id = "ALS0007", .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" }, } },
+	{ .id = "",  }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_dt019x_pnpids);
+
+
+#define DRIVER_NAME	"snd-card-dt019x"
+
+
+static int __devinit snd_card_dt019x_pnp(int dev, struct snd_card_dt019x *acard,
+					 struct pnp_card_link *card,
+					 const struct pnp_card_device_id *pid)
+{
+	struct pnp_dev *pdev;
+	struct pnp_resource_table * cfg = kmalloc(sizeof(struct pnp_resource_table), GFP_KERNEL);
+	int err;
+
+	if (!cfg)
+		return -ENOMEM;
+
+	acard->dev = pnp_request_card_device(card, pid->devs[0].id, NULL);
+	if (acard->dev == NULL) {
+		kfree (cfg);
+		return -ENODEV;
+	}
+	acard->devmpu = pnp_request_card_device(card, pid->devs[1].id, NULL);
+	acard->devopl = pnp_request_card_device(card, pid->devs[2].id, NULL);
+
+	pdev = acard->dev;
+	pnp_init_resource_table(cfg);
+
+	if (port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[0], port[dev], 16);
+	if (dma8[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[0], dma8[dev], 1);
+	if (irq[dev] != SNDRV_AUTO_IRQ)
+		pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1);
+
+	if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0)
+		snd_printk(KERN_ERR PFX "DT-019X AUDIO the requested resources are invalid, using auto config\n");
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "DT-019X AUDIO pnp configure failure\n");
+		kfree(cfg);
+		return err;
+	}
+
+	port[dev] = pnp_port_start(pdev, 0);
+	dma8[dev] = pnp_dma(pdev, 0);
+	irq[dev] = pnp_irq(pdev, 0);
+	snd_printdd("dt019x: found audio interface: port=0x%lx, irq=0x%x, dma=0x%x\n",
+			port[dev],irq[dev],dma8[dev]);
+
+	pdev = acard->devmpu;
+
+	if (pdev != NULL) {
+		pnp_init_resource_table(cfg);
+		if (mpu_port[dev] != SNDRV_AUTO_PORT)
+			pnp_resource_change(&cfg->port_resource[0], mpu_port[dev], 2);
+		if (mpu_irq[dev] != SNDRV_AUTO_IRQ)
+			pnp_resource_change(&cfg->irq_resource[0], mpu_irq[dev], 1);
+		if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0)
+			snd_printk(KERN_ERR PFX "DT-019X MPU401 the requested resources are invalid, using auto config\n");
+		err = pnp_activate_dev(pdev);
+		if (err < 0) {
+			pnp_release_card_device(pdev);
+			snd_printk(KERN_ERR PFX "DT-019X MPU401 pnp configure failure, skipping\n");
+			goto __mpu_error;
+		}
+		mpu_port[dev] = pnp_port_start(pdev, 0);
+		mpu_irq[dev] = pnp_irq(pdev, 0);
+		snd_printdd("dt019x: found MPU-401: port=0x%lx, irq=0x%x\n",
+			 	mpu_port[dev],mpu_irq[dev]);
+	} else {
+	__mpu_error:
+		acard->devmpu = NULL;
+		mpu_port[dev] = -1;
+	}
+
+	pdev = acard->devopl;
+	if (pdev != NULL) {
+		pnp_init_resource_table(cfg);
+		if (fm_port[dev] != SNDRV_AUTO_PORT)
+			pnp_resource_change(&cfg->port_resource[0], fm_port[dev], 4);
+		if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0)
+			snd_printk(KERN_ERR PFX "DT-019X OPL3 the requested resources are invalid, using auto config\n");
+		err = pnp_activate_dev(pdev);
+		if (err < 0) {
+			pnp_release_card_device(pdev);
+			snd_printk(KERN_ERR PFX "DT-019X OPL3 pnp configure failure, skipping\n");
+			goto __fm_error;
+		}
+		fm_port[dev] = pnp_port_start(pdev, 0);
+		snd_printdd("dt019x: found OPL3 synth: port=0x%lx\n",fm_port[dev]);
+	} else {
+	__fm_error:
+		acard->devopl = NULL;
+		fm_port[dev] = -1;
+	}
+
+	kfree(cfg);
+	return 0;
+}
+
+static int __devinit snd_card_dt019x_probe(int dev, struct pnp_card_link *pcard, const struct pnp_card_device_id *pid)
+{
+	int error;
+	sb_t *chip;
+	snd_card_t *card;
+	struct snd_card_dt019x *acard;
+	opl3_t *opl3;
+
+	if ((card = snd_card_new(index[dev], id[dev], THIS_MODULE,
+				 sizeof(struct snd_card_dt019x))) == NULL)
+		return -ENOMEM;
+	acard = (struct snd_card_dt019x *)card->private_data;
+
+	snd_card_set_dev(card, &pcard->card->dev);
+	if ((error = snd_card_dt019x_pnp(dev, acard, pcard, pid))) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if ((error = snd_sbdsp_create(card, port[dev],
+				      irq[dev],
+				      snd_sb16dsp_interrupt,
+				      dma8[dev],
+				      -1,
+				      SB_HW_DT019X,
+				      &chip)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	strcpy(card->driver, "DT-019X");
+	strcpy(card->shortname, "Diamond Tech. DT-019X");
+	sprintf(card->longname, "%s, %s at 0x%lx, irq %d, dma %d",
+		card->shortname, chip->name, chip->port,
+		irq[dev], dma8[dev]);
+
+	if ((error = snd_sb16dsp_pcm(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	if ((error = snd_sbmixer_new(chip)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) {
+		if (mpu_irq[dev] == SNDRV_AUTO_IRQ)
+			mpu_irq[dev] = -1;
+		if (snd_mpu401_uart_new(card, 0,
+/*					MPU401_HW_SB,*/
+					MPU401_HW_MPU401,
+					mpu_port[dev], 0,
+					mpu_irq[dev],
+					mpu_irq[dev] >= 0 ? SA_INTERRUPT : 0,
+					NULL) < 0)
+			snd_printk(KERN_ERR PFX "no MPU-401 device at 0x%lx ?\n", mpu_port[dev]);
+	}
+
+	if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+		if (snd_opl3_create(card,
+				    fm_port[dev],
+				    fm_port[dev] + 2,
+				    OPL3_HW_AUTO, 0, &opl3) < 0) {
+			snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx ?\n",
+				   fm_port[dev], fm_port[dev] + 2);
+		} else {
+			if ((error = snd_opl3_timer_new(opl3, 0, 1)) < 0) {
+				snd_card_free(card);
+				return error;
+			}
+			if ((error = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+				snd_card_free(card);
+				return error;
+			}
+		}
+	}
+
+	if ((error = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	return 0;
+}
+
+static int __devinit snd_dt019x_pnp_probe(struct pnp_card_link *card,
+					  const struct pnp_card_device_id *pid)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev])
+			continue;
+		res = snd_card_dt019x_probe(dev, card, pid);
+		if (res < 0)
+			return res;
+		dev++;
+		return 0;
+	}
+	return -ENODEV;
+}
+
+static void __devexit snd_dt019x_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_t *card = (snd_card_t *) pnp_get_card_drvdata(pcard);
+	snd_card_disconnect(card);
+	snd_card_free_in_thread(card);
+}
+
+static struct pnp_card_driver dt019x_pnpc_driver = {
+	.flags          = PNP_DRIVER_RES_DISABLE,
+	.name           = "dt019x",
+	.id_table       = snd_dt019x_pnpids,
+	.probe          = snd_dt019x_pnp_probe,
+	.remove         = __devexit_p(snd_dt019x_pnp_remove),
+};
+
+static int __init alsa_card_dt019x_init(void)
+{
+	int cards = 0;
+
+	cards += pnp_register_card_driver(&dt019x_pnpc_driver);
+
+#ifdef MODULE
+	if (!cards) {
+		pnp_unregister_card_driver(&dt019x_pnpc_driver);
+		snd_printk(KERN_ERR "no DT-019X / ALS-007 based soundcards found\n");
+	}
+#endif
+	return cards ? 0 : -ENODEV;
+}
+
+static void __exit alsa_card_dt019x_exit(void)
+{
+	pnp_unregister_card_driver(&dt019x_pnpc_driver);
+}
+
+module_init(alsa_card_dt019x_init)
+module_exit(alsa_card_dt019x_exit)
diff --git a/sound/isa/es1688/Makefile b/sound/isa/es1688/Makefile
new file mode 100644
index 0000000..501c8bf
--- /dev/null
+++ b/sound/isa/es1688/Makefile
@@ -0,0 +1,11 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-es1688-lib-objs := es1688_lib.o
+snd-es1688-objs := es1688.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_ES1688) += snd-es1688.o snd-es1688-lib.o
+obj-$(CONFIG_SND_GUSEXTREME) += snd-es1688-lib.o
diff --git a/sound/isa/es1688/es1688.c b/sound/isa/es1688/es1688.c
new file mode 100644
index 0000000..c5eaec0
--- /dev/null
+++ b/sound/isa/es1688/es1688.c
@@ -0,0 +1,204 @@
+/*
+ *  Driver for generic ESS AudioDrive ESx688 soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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/dma.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/es1688.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#define SNDRV_LEGACY_AUTO_PROBE
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("ESS ESx688 AudioDrive");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ESS,ES688 PnP AudioDrive,pnp:ESS0100},"
+	        "{ESS,ES1688 PnP AudioDrive,pnp:ESS0102},"
+	        "{ESS,ES688 AudioDrive,pnp:ESS6881},"
+	        "{ESS,ES1688 AudioDrive,pnp:ESS1681}}");
+
+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;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x240,0x260 */
+static long mpu_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -1};
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,10 */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,10 */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for ESx688 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for ESx688 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable ESx688 soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for ESx688 driver.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for ESx688 driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for ESx688 driver.");
+module_param_array(mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for ESx688 driver.");
+module_param_array(dma8, int, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for ESx688 driver.");
+
+static snd_card_t *snd_audiodrive_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+
+static int __init snd_audiodrive_probe(int dev)
+{
+	static int possible_irqs[] = {5, 9, 10, 7, -1};
+	static int possible_dmas[] = {1, 3, 0, -1};
+	int xirq, xdma, xmpu_irq;
+	snd_card_t *card;
+	es1688_t *chip;
+	opl3_t *opl3;
+	snd_pcm_t *pcm;
+	int err;
+
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
+	if (card == NULL)
+		return -ENOMEM;
+
+	xirq = irq[dev];
+	if (xirq == SNDRV_AUTO_IRQ) {
+		if ((xirq = snd_legacy_find_free_irq(possible_irqs)) < 0) {
+			snd_card_free(card);
+			snd_printk("unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	xmpu_irq = mpu_irq[dev];
+	xdma = dma8[dev];
+	if (xdma == SNDRV_AUTO_DMA) {
+		if ((xdma = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_card_free(card);
+			snd_printk("unable to find a free DMA\n");
+			return -EBUSY;
+		}
+	}
+
+	if ((err = snd_es1688_create(card, port[dev], mpu_port[dev],
+				     xirq, xmpu_irq, xdma,
+				     ES1688_HW_AUTO, &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_es1688_pcm(chip, 0, &pcm)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_es1688_mixer(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->driver, "ES1688");
+	strcpy(card->shortname, pcm->name);
+	sprintf(card->longname, "%s at 0x%lx, irq %i, dma %i", pcm->name, chip->port, xirq, xdma);
+
+	if ((snd_opl3_create(card, chip->port, chip->port + 2, OPL3_HW_OPL3, 0, &opl3)) < 0) {
+		printk(KERN_ERR "es1688: opl3 not detected at 0x%lx\n", chip->port);
+	} else {
+		if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+	}
+
+	if (xmpu_irq >= 0 && xmpu_irq != SNDRV_AUTO_IRQ && chip->mpu_port > 0) {
+		if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_ES1688,
+					       chip->mpu_port, 0,
+					       xmpu_irq,
+					       SA_INTERRUPT,
+					       NULL)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+	}
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	snd_audiodrive_cards[dev] = card;
+	return 0;
+
+}
+
+static int __init snd_audiodrive_legacy_auto_probe(unsigned long xport)
+{
+	static int dev;
+	int res;
+	
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || port[dev] != SNDRV_AUTO_PORT)
+			continue;
+		port[dev] = xport;
+		res = snd_audiodrive_probe(dev);
+		if (res < 0)
+			port[dev] = SNDRV_AUTO_PORT;
+		return res;
+	}
+	return -ENODEV;
+}
+
+static int __init alsa_card_es1688_init(void)
+{
+	static unsigned long possible_ports[] = {0x220, 0x240, 0x260, -1};
+	int dev, cards = 0, i;
+
+	for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev]; dev++) {
+		if (port[dev] == SNDRV_AUTO_PORT)
+			continue;
+		if (snd_audiodrive_probe(dev) >= 0)
+			cards++;
+	}
+	i = snd_legacy_auto_probe(possible_ports, snd_audiodrive_legacy_auto_probe);
+	if (i > 0)
+		cards += i;
+
+	if (!cards) {
+#ifdef MODULE
+		printk(KERN_ERR "ESS AudioDrive ES1688 soundcard not found or device busy\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_es1688_exit(void)
+{
+	int idx;
+
+	for (idx = 0; idx < SNDRV_CARDS; idx++)
+		snd_card_free(snd_audiodrive_cards[idx]);
+}
+
+module_init(alsa_card_es1688_init)
+module_exit(alsa_card_es1688_exit)
diff --git a/sound/isa/es1688/es1688_lib.c b/sound/isa/es1688/es1688_lib.c
new file mode 100644
index 0000000..17f68d0
--- /dev/null
+++ b/sound/isa/es1688/es1688_lib.c
@@ -0,0 +1,1062 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  Routines for control of ESS ES1688/688/488 chip
+ *
+ *
+ *   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/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <sound/core.h>
+#include <sound/es1688.h>
+#include <sound/initval.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("ESS ESx688 lowlevel module");
+MODULE_LICENSE("GPL");
+
+static int snd_es1688_dsp_command(es1688_t *chip, unsigned char val)
+{
+	int i;
+
+	for (i = 10000; i; i--)
+		if ((inb(ES1688P(chip, STATUS)) & 0x80) == 0) {
+			outb(val, ES1688P(chip, COMMAND));
+			return 1;
+		}
+#ifdef CONFIG_SND_DEBUG
+	printk("snd_es1688_dsp_command: timeout (0x%x)\n", val);
+#endif
+	return 0;
+}
+
+static int snd_es1688_dsp_get_byte(es1688_t *chip)
+{
+	int i;
+
+	for (i = 1000; i; i--)
+		if (inb(ES1688P(chip, DATA_AVAIL)) & 0x80)
+			return inb(ES1688P(chip, READ));
+	snd_printd("es1688 get byte failed: 0x%lx = 0x%x!!!\n", ES1688P(chip, DATA_AVAIL), inb(ES1688P(chip, DATA_AVAIL)));
+	return -ENODEV;
+}
+
+static int snd_es1688_write(es1688_t *chip,
+			    unsigned char reg, unsigned char data)
+{
+	if (!snd_es1688_dsp_command(chip, reg))
+		return 0;
+	return snd_es1688_dsp_command(chip, data);
+}
+
+static int snd_es1688_read(es1688_t *chip, unsigned char reg)
+{
+	/* Read a byte from an extended mode register of ES1688 */
+	if (!snd_es1688_dsp_command(chip, 0xc0))
+		return -1;
+	if (!snd_es1688_dsp_command(chip, reg))
+		return -1;
+	return snd_es1688_dsp_get_byte(chip);
+}
+
+void snd_es1688_mixer_write(es1688_t *chip,
+			    unsigned char reg, unsigned char data)
+{
+	outb(reg, ES1688P(chip, MIXER_ADDR));
+	udelay(10);
+	outb(data, ES1688P(chip, MIXER_DATA));
+	udelay(10);
+}
+
+static unsigned char snd_es1688_mixer_read(es1688_t *chip, unsigned char reg)
+{
+	unsigned char result;
+
+	outb(reg, ES1688P(chip, MIXER_ADDR));
+	udelay(10);
+	result = inb(ES1688P(chip, MIXER_DATA));
+	udelay(10);
+	return result;
+}
+
+static int snd_es1688_reset(es1688_t *chip)
+{
+	int i;
+
+	outb(3, ES1688P(chip, RESET));		/* valid only for ESS chips, SB -> 1 */
+	udelay(10);
+	outb(0, ES1688P(chip, RESET));
+	udelay(30);
+	for (i = 0; i < 1000 && !(inb(ES1688P(chip, DATA_AVAIL)) & 0x80); i++);
+	if (inb(ES1688P(chip, READ)) != 0xaa) {
+		snd_printd("ess_reset at 0x%lx: failed!!!\n", chip->port);
+		return -ENODEV;
+	}
+	snd_es1688_dsp_command(chip, 0xc6);	/* enable extended mode */
+	return 0;
+}
+
+static int snd_es1688_probe(es1688_t *chip)
+{
+	unsigned long flags;
+	unsigned short major, minor, hw;
+	int i;
+
+	/*
+	 *  initialization sequence
+	 */
+
+	spin_lock_irqsave(&chip->reg_lock, flags);	/* Some ESS1688 cards need this */
+	inb(ES1688P(chip, ENABLE1));	/* ENABLE1 */
+	inb(ES1688P(chip, ENABLE1));	/* ENABLE1 */
+	inb(ES1688P(chip, ENABLE1));	/* ENABLE1 */
+	inb(ES1688P(chip, ENABLE2));	/* ENABLE2 */
+	inb(ES1688P(chip, ENABLE1));	/* ENABLE1 */
+	inb(ES1688P(chip, ENABLE2));	/* ENABLE2 */
+	inb(ES1688P(chip, ENABLE1));	/* ENABLE1 */
+	inb(ES1688P(chip, ENABLE1));	/* ENABLE1 */
+	inb(ES1688P(chip, ENABLE2));	/* ENABLE2 */
+	inb(ES1688P(chip, ENABLE1));	/* ENABLE1 */
+	inb(ES1688P(chip, ENABLE0));	/* ENABLE0 */
+
+	if (snd_es1688_reset(chip) < 0) {
+		snd_printdd("ESS: [0x%lx] reset failed... 0x%x\n", chip->port, inb(ES1688P(chip, READ)));
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return -ENODEV;
+	}
+	snd_es1688_dsp_command(chip, 0xe7);	/* return identification */
+
+	for (i = 1000, major = minor = 0; i; i--) {
+		if (inb(ES1688P(chip, DATA_AVAIL)) & 0x80) {
+			if (major == 0) {
+				major = inb(ES1688P(chip, READ));
+			} else {
+				minor = inb(ES1688P(chip, READ));
+			}
+		}
+	}
+
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	snd_printdd("ESS: [0x%lx] found.. major = 0x%x, minor = 0x%x\n", chip->port, major, minor);
+
+	chip->version = (major << 8) | minor;
+	if (!chip->version)
+		return -ENODEV;	/* probably SB */
+
+	hw = ES1688_HW_AUTO;
+	switch (chip->version & 0xfff0) {
+	case 0x4880:
+		snd_printk("[0x%lx] ESS: AudioDrive ES488 detected, but driver is in another place\n", chip->port);
+		return -ENODEV;
+	case 0x6880:
+		hw = (chip->version & 0x0f) >= 8 ? ES1688_HW_1688 : ES1688_HW_688;
+		break;
+	default:
+		snd_printk("[0x%lx] ESS: unknown AudioDrive chip with version 0x%x (Jazz16 soundcard?)\n", chip->port, chip->version);
+		return -ENODEV;
+	}
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1688_write(chip, 0xb1, 0x10);	/* disable IRQ */
+	snd_es1688_write(chip, 0xb2, 0x00);	/* disable DMA */
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	/* enable joystick, but disable OPL3 */
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+	snd_es1688_mixer_write(chip, 0x40, 0x01);
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+
+	return 0;
+}
+
+static int snd_es1688_init(es1688_t * chip, int enable)
+{
+	static int irqs[16] = {-1, -1, 0, -1, -1, 1, -1, 2, -1, 0, 3, -1, -1, -1, -1, -1};
+	unsigned long flags;
+	int cfg, irq_bits, dma, dma_bits, tmp, tmp1;
+
+	/* ok.. setup MPU-401 port and joystick and OPL3 */
+	cfg = 0x01;		/* enable joystick, but disable OPL3 */
+	if (enable && chip->mpu_port >= 0x300 && chip->mpu_irq > 0 && chip->hardware != ES1688_HW_688) {
+		tmp = (chip->mpu_port & 0x0f0) >> 4;
+		if (tmp <= 3) {
+			switch (chip->mpu_irq) {
+			case 9:
+				tmp1 = 4;
+				break;
+			case 5:
+				tmp1 = 5;
+				break;
+			case 7:
+				tmp1 = 6;
+				break;
+			case 10:
+				tmp1 = 7;
+				break;
+			default:
+				tmp1 = 0;
+			}
+			if (tmp1) {
+				cfg |= (tmp << 3) | (tmp1 << 5);
+			}
+		}
+	}
+#if 0
+	snd_printk("mpu cfg = 0x%x\n", cfg);
+#endif
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1688_mixer_write(chip, 0x40, cfg);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	/* --- */
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1688_read(chip, 0xb1);
+	snd_es1688_read(chip, 0xb2);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (enable) {
+		cfg = 0xf0;	/* enable only DMA counter interrupt */
+		irq_bits = irqs[chip->irq & 0x0f];
+		if (irq_bits < 0) {
+			snd_printk("[0x%lx] ESS: bad IRQ %d for ES1688 chip!!\n", chip->port, chip->irq);
+#if 0
+			irq_bits = 0;
+			cfg = 0x10;
+#endif
+			return -EINVAL;
+		}
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		snd_es1688_write(chip, 0xb1, cfg | (irq_bits << 2));
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		cfg = 0xf0;	/* extended mode DMA enable */
+		dma = chip->dma8;
+		if (dma > 3 || dma == 2) {
+			snd_printk("[0x%lx] ESS: bad DMA channel %d for ES1688 chip!!\n", chip->port, dma);
+#if 0
+			dma_bits = 0;
+			cfg = 0x00;	/* disable all DMA */
+#endif
+			return -EINVAL;
+		} else {
+			dma_bits = dma;
+			if (dma != 3)
+				dma_bits++;
+		}
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		snd_es1688_write(chip, 0xb2, cfg | (dma_bits << 2));
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+	} else {
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		snd_es1688_write(chip, 0xb1, 0x10);	/* disable IRQ */
+		snd_es1688_write(chip, 0xb2, 0x00);	/* disable DMA */
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+	}
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1688_read(chip, 0xb1);
+	snd_es1688_read(chip, 0xb2);
+	snd_es1688_reset(chip);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+/*
+
+ */
+
+static ratnum_t clocks[2] = {
+	{
+		.num = 795444,
+		.den_min = 1,
+		.den_max = 128,
+		.den_step = 1,
+	},
+	{
+		.num = 397722,
+		.den_min = 1,
+		.den_max = 128,
+		.den_step = 1,
+	}
+};
+
+static snd_pcm_hw_constraint_ratnums_t hw_constraints_clocks  = {
+	.nrats = 2,
+	.rats = clocks,
+};
+
+static void snd_es1688_set_rate(es1688_t *chip, snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned int bits, divider;
+
+	if (runtime->rate_num == clocks[0].num)
+		bits = 256 - runtime->rate_den;
+	else
+		bits = 128 - runtime->rate_den;
+	/* set filter register */
+	divider = 256 - 7160000*20/(8*82*runtime->rate);
+	/* write result to hardware */
+	snd_es1688_write(chip, 0xa1, bits);
+	snd_es1688_write(chip, 0xa2, divider);
+}
+
+static int snd_es1688_ioctl(snd_pcm_substream_t * substream,
+			    unsigned int cmd, void *arg)
+{
+	return snd_pcm_lib_ioctl(substream, cmd, arg);
+}
+
+static int snd_es1688_trigger(es1688_t *chip, int cmd, unsigned char value)
+{
+	int val;
+
+	if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		value = 0x00;
+	} else if (cmd != SNDRV_PCM_TRIGGER_START) {
+		return -EINVAL;
+	}
+	spin_lock(&chip->reg_lock);
+	chip->trigger_value = value;
+	val = snd_es1688_read(chip, 0xb8);
+	if ((val < 0) || (val & 0x0f) == value) {
+		spin_unlock(&chip->reg_lock);
+		return -EINVAL;	/* something is wrong */
+	}
+#if 0
+	printk("trigger: val = 0x%x, value = 0x%x\n", val, value);
+	printk("trigger: pointer = 0x%x\n", snd_dma_pointer(chip->dma8, chip->dma_size));
+#endif
+	snd_es1688_write(chip, 0xb8, (val & 0xf0) | value);
+	spin_unlock(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_es1688_hw_params(snd_pcm_substream_t * substream,
+				snd_pcm_hw_params_t * hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_es1688_hw_free(snd_pcm_substream_t * substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_es1688_playback_prepare(snd_pcm_substream_t * substream)
+{
+	unsigned long flags;
+	es1688_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	chip->dma_size = size;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1688_reset(chip);
+	snd_es1688_set_rate(chip, substream);
+	snd_es1688_write(chip, 0xb8, 4);	/* auto init DMA mode */
+	snd_es1688_write(chip, 0xa8, (snd_es1688_read(chip, 0xa8) & ~0x03) | (3 - runtime->channels));
+	snd_es1688_write(chip, 0xb9, 2);	/* demand mode (4 bytes/request) */
+	if (runtime->channels == 1) {
+		if (snd_pcm_format_width(runtime->format) == 8) {
+			/* 8. bit mono */
+			snd_es1688_write(chip, 0xb6, 0x80);
+			snd_es1688_write(chip, 0xb7, 0x51);
+			snd_es1688_write(chip, 0xb7, 0xd0);
+		} else {
+			/* 16. bit mono */
+			snd_es1688_write(chip, 0xb6, 0x00);
+			snd_es1688_write(chip, 0xb7, 0x71);
+			snd_es1688_write(chip, 0xb7, 0xf4);
+		}
+	} else {
+		if (snd_pcm_format_width(runtime->format) == 8) {
+			/* 8. bit stereo */
+			snd_es1688_write(chip, 0xb6, 0x80);
+			snd_es1688_write(chip, 0xb7, 0x51);
+			snd_es1688_write(chip, 0xb7, 0x98);
+		} else {
+			/* 16. bit stereo */
+			snd_es1688_write(chip, 0xb6, 0x00);
+			snd_es1688_write(chip, 0xb7, 0x71);
+			snd_es1688_write(chip, 0xb7, 0xbc);
+		}
+	}
+	snd_es1688_write(chip, 0xb1, (snd_es1688_read(chip, 0xb1) & 0x0f) | 0x50);
+	snd_es1688_write(chip, 0xb2, (snd_es1688_read(chip, 0xb2) & 0x0f) | 0x50);
+	snd_es1688_dsp_command(chip, ES1688_DSP_CMD_SPKON);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	/* --- */
+	count = -count;
+	snd_dma_program(chip->dma8, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1688_write(chip, 0xa4, (unsigned char) count);
+	snd_es1688_write(chip, 0xa5, (unsigned char) (count >> 8));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_es1688_playback_trigger(snd_pcm_substream_t * substream,
+				       int cmd)
+{
+	es1688_t *chip = snd_pcm_substream_chip(substream);
+	return snd_es1688_trigger(chip, cmd, 0x05);
+}
+
+static int snd_es1688_capture_prepare(snd_pcm_substream_t * substream)
+{
+	unsigned long flags;
+	es1688_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	chip->dma_size = size;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1688_reset(chip);
+	snd_es1688_set_rate(chip, substream);
+	snd_es1688_dsp_command(chip, ES1688_DSP_CMD_SPKOFF);
+	snd_es1688_write(chip, 0xb8, 0x0e);	/* auto init DMA mode */
+	snd_es1688_write(chip, 0xa8, (snd_es1688_read(chip, 0xa8) & ~0x03) | (3 - runtime->channels));
+	snd_es1688_write(chip, 0xb9, 2);	/* demand mode (4 bytes/request) */
+	if (runtime->channels == 1) {
+		if (snd_pcm_format_width(runtime->format) == 8) {
+			/* 8. bit mono */
+			snd_es1688_write(chip, 0xb7, 0x51);
+			snd_es1688_write(chip, 0xb7, 0xd0);
+		} else {
+			/* 16. bit mono */
+			snd_es1688_write(chip, 0xb7, 0x71);
+			snd_es1688_write(chip, 0xb7, 0xf4);
+		}
+	} else {
+		if (snd_pcm_format_width(runtime->format) == 8) {
+			/* 8. bit stereo */
+			snd_es1688_write(chip, 0xb7, 0x51);
+			snd_es1688_write(chip, 0xb7, 0x98);
+		} else {
+			/* 16. bit stereo */
+			snd_es1688_write(chip, 0xb7, 0x71);
+			snd_es1688_write(chip, 0xb7, 0xbc);
+		}
+	}
+	snd_es1688_write(chip, 0xb1, (snd_es1688_read(chip, 0xb1) & 0x0f) | 0x50);
+	snd_es1688_write(chip, 0xb2, (snd_es1688_read(chip, 0xb2) & 0x0f) | 0x50);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	/* --- */
+	count = -count;
+	snd_dma_program(chip->dma8, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1688_write(chip, 0xa4, (unsigned char) count);
+	snd_es1688_write(chip, 0xa5, (unsigned char) (count >> 8));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_es1688_capture_trigger(snd_pcm_substream_t * substream,
+				      int cmd)
+{
+	es1688_t *chip = snd_pcm_substream_chip(substream);
+	return snd_es1688_trigger(chip, cmd, 0x0f);
+}
+
+static irqreturn_t snd_es1688_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	es1688_t *chip = dev_id;
+
+	if (chip->trigger_value == 0x05)	/* ok.. playback is active */
+		snd_pcm_period_elapsed(chip->playback_substream);
+	if (chip->trigger_value == 0x0f)	/* ok.. capture is active */
+		snd_pcm_period_elapsed(chip->capture_substream);
+
+	inb(ES1688P(chip, DATA_AVAIL));	/* ack interrupt */
+	return IRQ_HANDLED;
+}
+
+static snd_pcm_uframes_t snd_es1688_playback_pointer(snd_pcm_substream_t * substream)
+{
+	es1688_t *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	
+	if (chip->trigger_value != 0x05)
+		return 0;
+	ptr = snd_dma_pointer(chip->dma8, chip->dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_es1688_capture_pointer(snd_pcm_substream_t * substream)
+{
+	es1688_t *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	
+	if (chip->trigger_value != 0x0f)
+		return 0;
+	ptr = snd_dma_pointer(chip->dma8, chip->dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+/*
+
+ */
+
+static snd_pcm_hardware_t snd_es1688_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_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 =	65536,
+	.period_bytes_min =	64,
+	.period_bytes_max =	65536,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static snd_pcm_hardware_t snd_es1688_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_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 =	65536,
+	.period_bytes_min =	64,
+	.period_bytes_max =	65536,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/*
+
+ */
+
+static int snd_es1688_playback_open(snd_pcm_substream_t * substream)
+{
+	es1688_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+
+	if (chip->capture_substream != NULL)
+		return -EAGAIN;
+	chip->playback_substream = substream;
+	runtime->hw = snd_es1688_playback;
+	snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      &hw_constraints_clocks);
+	return 0;
+}
+
+static int snd_es1688_capture_open(snd_pcm_substream_t * substream)
+{
+	es1688_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+
+	if (chip->playback_substream != NULL)
+		return -EAGAIN;
+	chip->capture_substream = substream;
+	runtime->hw = snd_es1688_capture;
+	snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      &hw_constraints_clocks);
+	return 0;
+}
+
+static int snd_es1688_playback_close(snd_pcm_substream_t * substream)
+{
+	es1688_t *chip = snd_pcm_substream_chip(substream);
+
+	chip->playback_substream = NULL;
+	return 0;
+}
+
+static int snd_es1688_capture_close(snd_pcm_substream_t * substream)
+{
+	es1688_t *chip = snd_pcm_substream_chip(substream);
+
+	chip->capture_substream = NULL;
+	return 0;
+}
+
+static int snd_es1688_free(es1688_t *chip)
+{
+	if (chip->res_port) {
+		snd_es1688_init(chip, 0);
+		release_resource(chip->res_port);
+		kfree_nocheck(chip->res_port);
+	}
+	if (chip->irq >= 0)
+		free_irq(chip->irq, (void *) chip);
+	if (chip->dma8 >= 0) {
+		disable_dma(chip->dma8);
+		free_dma(chip->dma8);
+	}
+	kfree(chip);
+	return 0;
+}
+
+static int snd_es1688_dev_free(snd_device_t *device)
+{
+	es1688_t *chip = device->device_data;
+	return snd_es1688_free(chip);
+}
+
+static const char *snd_es1688_chip_id(es1688_t *chip)
+{
+	static char tmp[16];
+	sprintf(tmp, "ES%s688 rev %i", chip->hardware == ES1688_HW_688 ? "" : "1", chip->version & 0x0f);
+	return tmp;
+}
+
+int snd_es1688_create(snd_card_t * card,
+		      unsigned long port,
+		      unsigned long mpu_port,
+		      int irq,
+		      int mpu_irq,
+		      int dma8,
+		      unsigned short hardware,
+		      es1688_t **rchip)
+{
+	static snd_device_ops_t ops = {
+		.dev_free =	snd_es1688_dev_free,
+	};
+                                
+	es1688_t *chip;
+	int err;
+
+	*rchip = NULL;
+	chip = kcalloc(1, sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+	chip->irq = -1;
+	chip->dma8 = -1;
+	
+	if ((chip->res_port = request_region(port + 4, 12, "ES1688")) == NULL) {
+		snd_printk(KERN_ERR "es1688: can't grab port 0x%lx\n", port + 4);
+		snd_es1688_free(chip);
+		return -EBUSY;
+	}
+	if (request_irq(irq, snd_es1688_interrupt, SA_INTERRUPT, "ES1688", (void *) chip)) {
+		snd_printk(KERN_ERR "es1688: can't grab IRQ %d\n", irq);
+		snd_es1688_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = irq;
+	if (request_dma(dma8, "ES1688")) {
+		snd_printk(KERN_ERR "es1688: can't grab DMA8 %d\n", dma8);
+		snd_es1688_free(chip);
+		return -EBUSY;
+	}
+	chip->dma8 = dma8;
+
+	spin_lock_init(&chip->reg_lock);
+	spin_lock_init(&chip->mixer_lock);
+	chip->card = card;
+	chip->port = port;
+	mpu_port &= ~0x000f;
+	if (mpu_port < 0x300 || mpu_port > 0x330)
+		mpu_port = 0;
+	chip->mpu_port = mpu_port;
+	chip->mpu_irq = mpu_irq;
+	chip->hardware = hardware;
+
+	if ((err = snd_es1688_probe(chip)) < 0) {
+		snd_es1688_free(chip);
+		return err;
+	}
+	if ((err = snd_es1688_init(chip, 1)) < 0) {
+		snd_es1688_free(chip);
+		return err;
+	}
+
+	/* Register device */
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_es1688_free(chip);
+		return err;
+	}
+
+	*rchip = chip;
+	return 0;
+}
+
+static snd_pcm_ops_t snd_es1688_playback_ops = {
+	.open =			snd_es1688_playback_open,
+	.close =		snd_es1688_playback_close,
+	.ioctl =		snd_es1688_ioctl,
+	.hw_params =		snd_es1688_hw_params,
+	.hw_free =		snd_es1688_hw_free,
+	.prepare =		snd_es1688_playback_prepare,
+	.trigger =		snd_es1688_playback_trigger,
+	.pointer =		snd_es1688_playback_pointer,
+};
+
+static snd_pcm_ops_t snd_es1688_capture_ops = {
+	.open =			snd_es1688_capture_open,
+	.close =		snd_es1688_capture_close,
+	.ioctl =		snd_es1688_ioctl,
+	.hw_params =		snd_es1688_hw_params,
+	.hw_free =		snd_es1688_hw_free,
+	.prepare =		snd_es1688_capture_prepare,
+	.trigger =		snd_es1688_capture_trigger,
+	.pointer =		snd_es1688_capture_pointer,
+};
+
+static void snd_es1688_pcm_free(snd_pcm_t *pcm)
+{
+	es1688_t *chip = pcm->private_data;
+	chip->pcm = NULL;
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+int snd_es1688_pcm(es1688_t * chip, int device, snd_pcm_t ** rpcm)
+{
+	snd_pcm_t *pcm;
+	int err;
+
+	if ((err = snd_pcm_new(chip->card, "ESx688", device, 1, 1, &pcm)) < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_es1688_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_es1688_capture_ops);
+
+	pcm->private_data = chip;
+	pcm->private_free = snd_es1688_pcm_free;
+	pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+	sprintf(pcm->name, snd_es1688_chip_id(chip));
+	chip->pcm = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, 64*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+/*
+ *  MIXER part
+ */
+
+static int snd_es1688_info_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	static char *texts[9] = {
+		"Mic", "Mic Master", "CD", "AOUT",
+		"Mic1", "Mix", "Line", "Master"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 8;
+	if (uinfo->value.enumerated.item > 7)
+		uinfo->value.enumerated.item = 7;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_es1688_get_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	es1688_t *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.enumerated.item[0] = snd_es1688_mixer_read(chip, ES1688_REC_DEV) & 7;
+	return 0;
+}
+
+static int snd_es1688_put_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	es1688_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned char oval, nval;
+	int change;
+	
+	if (ucontrol->value.enumerated.item[0] > 8)
+		return -EINVAL;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	oval = snd_es1688_mixer_read(chip, ES1688_REC_DEV);
+	nval = (ucontrol->value.enumerated.item[0] & 7) | (oval & ~15);
+	change = nval != oval;
+	if (change)
+		snd_es1688_mixer_write(chip, ES1688_REC_DEV, nval);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+#define ES1688_SINGLE(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_es1688_info_single, \
+  .get = snd_es1688_get_single, .put = snd_es1688_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+static int snd_es1688_info_single(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_es1688_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	es1688_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (snd_es1688_mixer_read(chip, reg) >> shift) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_es1688_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	es1688_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned char oval, nval;
+	
+	nval = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		nval = mask - nval;
+	nval <<= shift;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	oval = snd_es1688_mixer_read(chip, reg);
+	nval = (oval & ~(mask << shift)) | nval;
+	change = nval != oval;
+	if (change)
+		snd_es1688_mixer_write(chip, reg, nval);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+#define ES1688_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_es1688_info_double, \
+  .get = snd_es1688_get_double, .put = snd_es1688_put_double, \
+  .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
+
+static int snd_es1688_info_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_es1688_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	es1688_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	unsigned char left, right;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (left_reg < 0xa0)
+		left = snd_es1688_mixer_read(chip, left_reg);
+	else
+		left = snd_es1688_read(chip, left_reg);
+	if (left_reg != right_reg) {
+		if (right_reg < 0xa0) 
+			right = snd_es1688_mixer_read(chip, right_reg);
+		else
+			right = snd_es1688_read(chip, right_reg);
+	} else
+		right = left;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (left >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (right >> shift_right) & mask;
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+
+static int snd_es1688_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	es1688_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	unsigned char val1, val2, oval1, oval2;
+	
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (left_reg != right_reg) {
+		if (left_reg < 0xa0)
+			oval1 = snd_es1688_mixer_read(chip, left_reg);
+		else
+			oval1 = snd_es1688_read(chip, left_reg);
+		if (right_reg < 0xa0)
+			oval2 = snd_es1688_mixer_read(chip, right_reg);
+		else
+			oval2 = snd_es1688_read(chip, right_reg);
+		val1 = (oval1 & ~(mask << shift_left)) | val1;
+		val2 = (oval2 & ~(mask << shift_right)) | val2;
+		change = val1 != oval1 || val2 != oval2;
+		if (change) {
+			if (left_reg < 0xa0)
+				snd_es1688_mixer_write(chip, left_reg, val1);
+			else
+				snd_es1688_write(chip, left_reg, val1);
+			if (right_reg < 0xa0)
+				snd_es1688_mixer_write(chip, right_reg, val1);
+			else
+				snd_es1688_write(chip, right_reg, val1);
+		}
+	} else {
+		if (left_reg < 0xa0)
+			oval1 = snd_es1688_mixer_read(chip, left_reg);
+		else
+			oval1 = snd_es1688_read(chip, left_reg);
+		val1 = (oval1 & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2;
+		change = val1 != oval1;
+		if (change) {
+			if (left_reg < 0xa0)
+				snd_es1688_mixer_write(chip, left_reg, val1);
+			else
+				snd_es1688_write(chip, left_reg, val1);
+		}
+			
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_es1688_controls[] = {
+ES1688_DOUBLE("Master Playback Volume", 0, ES1688_MASTER_DEV, ES1688_MASTER_DEV, 4, 0, 15, 0),
+ES1688_DOUBLE("PCM Playback Volume", 0, ES1688_PCM_DEV, ES1688_PCM_DEV, 4, 0, 15, 0),
+ES1688_DOUBLE("Line Playback Volume", 0, ES1688_LINE_DEV, ES1688_LINE_DEV, 4, 0, 15, 0),
+ES1688_DOUBLE("CD Playback Volume", 0, ES1688_CD_DEV, ES1688_CD_DEV, 4, 0, 15, 0),
+ES1688_DOUBLE("FM Playback Volume", 0, ES1688_FM_DEV, ES1688_FM_DEV, 4, 0, 15, 0),
+ES1688_DOUBLE("Mic Playback Volume", 0, ES1688_MIC_DEV, ES1688_MIC_DEV, 4, 0, 15, 0),
+ES1688_DOUBLE("Aux Playback Volume", 0, ES1688_AUX_DEV, ES1688_AUX_DEV, 4, 0, 15, 0),
+ES1688_SINGLE("PC Speaker Playback Volume", 0, ES1688_SPEAKER_DEV, 0, 7, 0),
+ES1688_DOUBLE("Capture Volume", 0, ES1688_RECLEV_DEV, ES1688_RECLEV_DEV, 4, 0, 15, 0),
+ES1688_SINGLE("Capture Switch", 0, ES1688_REC_DEV, 4, 1, 1),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Capture Source",
+	.info = snd_es1688_info_mux,
+	.get = snd_es1688_get_mux,
+	.put = snd_es1688_put_mux,
+},
+};
+
+#define ES1688_INIT_TABLE_SIZE (sizeof(snd_es1688_init_table)/2)
+
+static unsigned char snd_es1688_init_table[][2] = {
+	{ ES1688_MASTER_DEV, 0 },
+	{ ES1688_PCM_DEV, 0 },
+	{ ES1688_LINE_DEV, 0 },
+	{ ES1688_CD_DEV, 0 },
+	{ ES1688_FM_DEV, 0 },
+	{ ES1688_MIC_DEV, 0 },
+	{ ES1688_AUX_DEV, 0 },
+	{ ES1688_SPEAKER_DEV, 0 },
+	{ ES1688_RECLEV_DEV, 0 },
+	{ ES1688_REC_DEV, 0x17 }
+};
+                                        
+int snd_es1688_mixer(es1688_t *chip)
+{
+	snd_card_t *card;
+	unsigned int idx;
+	int err;
+	unsigned char reg, val;
+
+	snd_assert(chip != NULL && chip->card != NULL, return -EINVAL);
+
+	card = chip->card;
+
+	strcpy(card->mixername, snd_es1688_chip_id(chip));
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_es1688_controls); idx++) {
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es1688_controls[idx], chip))) < 0)
+			return err;
+	}
+	for (idx = 0; idx < ES1688_INIT_TABLE_SIZE; idx++) {
+		reg = snd_es1688_init_table[idx][0];
+		val = snd_es1688_init_table[idx][1];
+		if (reg < 0xa0)
+			snd_es1688_mixer_write(chip, reg, val);
+		else
+			snd_es1688_write(chip, reg, val);
+	}
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_es1688_mixer_write);
+EXPORT_SYMBOL(snd_es1688_create);
+EXPORT_SYMBOL(snd_es1688_pcm);
+EXPORT_SYMBOL(snd_es1688_mixer);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_es1688_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_es1688_exit(void)
+{
+}
+
+module_init(alsa_es1688_init)
+module_exit(alsa_es1688_exit)
diff --git a/sound/isa/es18xx.c b/sound/isa/es18xx.c
new file mode 100644
index 0000000..1d832b2
--- /dev/null
+++ b/sound/isa/es18xx.c
@@ -0,0 +1,2224 @@
+/*
+ *  Driver for generic ESS AudioDrive ES18xx soundcards
+ *  Copyright (c) by Christian Fischbach <fishbach@pool.informatik.rwth-aachen.de>
+ *  Copyright (c) by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   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
+ *
+ */
+/* GENERAL NOTES:
+ *
+ * BUGS:
+ * - There are pops (we can't delay in trigger function, cause midlevel 
+ *   often need to trigger down and then up very quickly).
+ *   Any ideas?
+ * - Support for 16 bit DMA seems to be broken. I've no hardware to tune it.
+ */
+
+/*
+ * ES1868  NOTES:
+ * - The chip has one half duplex pcm (with very limited full duplex support).
+ *
+ * - Duplex stereophonic sound is impossible.
+ * - Record and playback must share the same frequency rate.
+ *
+ * - The driver use dma2 for playback and dma1 for capture.
+ */
+
+/*
+ * ES1869 NOTES:
+ *
+ * - there are a first full duplex pcm and a second playback only pcm
+ *   (incompatible with first pcm capture)
+ * 
+ * - there is support for the capture volume and ESS Spatializer 3D effect.
+ *
+ * - contrarily to some pages in DS_1869.PDF the rates can be set
+ *   independently.
+ *
+ * BUGS:
+ *
+ * - There is a major trouble I noted:
+ *
+ *   using both channel for playback stereo 16 bit samples at 44100 Hz
+ *   the second pcm (Audio1) DMA slows down irregularly and sound is garbled.
+ *   
+ *   The same happens using Audio1 for captureing.
+ *
+ *   The Windows driver does not suffer of this (although it use Audio1
+ *   only for captureing). I'm unable to discover why.
+ *
+ */
+
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <linux/init.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/pnp.h>
+#include <linux/isapnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#define SNDRV_LEGACY_AUTO_PROBE
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+#define PFX "es18xx: "
+
+struct _snd_es18xx {
+	unsigned long port;		/* port of ESS chip */
+	unsigned long mpu_port;		/* MPU-401 port of ESS chip */
+	unsigned long fm_port;		/* FM port */
+	unsigned long ctrl_port;	/* Control port of ESS chip */
+	struct resource *res_port;
+	struct resource *res_mpu_port;
+	struct resource *res_ctrl_port;
+	int irq;			/* IRQ number of ESS chip */
+	int dma1;			/* DMA1 */
+	int dma2;			/* DMA2 */
+	unsigned short version;		/* version of ESS chip */
+	int caps;			/* Chip capabilities */
+	unsigned short audio2_vol;	/* volume level of audio2 */
+
+	unsigned short active;		/* active channel mask */
+	unsigned int dma1_size;
+	unsigned int dma2_size;
+	unsigned int dma1_shift;
+	unsigned int dma2_shift;
+
+	snd_card_t *card;
+	snd_pcm_t *pcm;
+	snd_pcm_substream_t *playback_a_substream;
+	snd_pcm_substream_t *capture_a_substream;
+	snd_pcm_substream_t *playback_b_substream;
+
+	snd_rawmidi_t *rmidi;
+
+	snd_kcontrol_t *hw_volume;
+	snd_kcontrol_t *hw_switch;
+	snd_kcontrol_t *master_volume;
+	snd_kcontrol_t *master_switch;
+
+	spinlock_t reg_lock;
+	spinlock_t mixer_lock;
+	spinlock_t ctrl_lock;
+#ifdef CONFIG_PM
+	unsigned char pm_reg;
+#endif
+};
+
+#define AUDIO1_IRQ	0x01
+#define AUDIO2_IRQ	0x02
+#define HWV_IRQ		0x04
+#define MPU_IRQ		0x08
+
+#define ES18XX_PCM2	0x0001	/* Has two useable PCM */
+#define ES18XX_SPATIALIZER 0x0002	/* Has 3D Spatializer */
+#define ES18XX_RECMIX	0x0004	/* Has record mixer */
+#define ES18XX_DUPLEX_MONO 0x0008	/* Has mono duplex only */
+#define ES18XX_DUPLEX_SAME 0x0010	/* Playback and record must share the same rate */
+#define ES18XX_NEW_RATE	0x0020	/* More precise rate setting */
+#define ES18XX_AUXB	0x0040	/* AuxB mixer control */
+#define ES18XX_HWV	0x0080	/* Has hardware volume */
+#define ES18XX_MONO	0x0100	/* Mono_in mixer control */
+#define ES18XX_I2S	0x0200	/* I2S mixer control */
+#define ES18XX_MUTEREC	0x0400	/* Record source can be muted */
+#define ES18XX_CONTROL	0x0800	/* Has control ports */
+
+/* Power Management */
+#define ES18XX_PM	0x07
+#define ES18XX_PM_GPO0	0x01
+#define ES18XX_PM_GPO1	0x02
+#define ES18XX_PM_PDR	0x04
+#define ES18XX_PM_ANA	0x08
+#define ES18XX_PM_FM	0x020
+#define ES18XX_PM_SUS	0x080
+
+typedef struct _snd_es18xx es18xx_t;
+
+/* Lowlevel */
+
+#define DAC1 0x01
+#define ADC1 0x02
+#define DAC2 0x04
+#define MILLISECOND 10000
+
+static int snd_es18xx_dsp_command(es18xx_t *chip, unsigned char val)
+{
+        int i;
+
+        for(i = MILLISECOND; i; i--)
+                if ((inb(chip->port + 0x0C) & 0x80) == 0) {
+                        outb(val, chip->port + 0x0C);
+                        return 0;
+                }
+        snd_printk("dsp_command: timeout (0x%x)\n", val);
+        return -EINVAL;
+}
+
+static int snd_es18xx_dsp_get_byte(es18xx_t *chip)
+{
+        int i;
+
+        for(i = MILLISECOND/10; i; i--)
+                if (inb(chip->port + 0x0C) & 0x40)
+                        return inb(chip->port + 0x0A);
+        snd_printk("dsp_get_byte failed: 0x%lx = 0x%x!!!\n", chip->port + 0x0A, inb(chip->port + 0x0A));
+        return -ENODEV;
+}
+
+#undef REG_DEBUG
+
+static int snd_es18xx_write(es18xx_t *chip,
+			    unsigned char reg, unsigned char data)
+{
+	unsigned long flags;
+	int ret;
+	
+        spin_lock_irqsave(&chip->reg_lock, flags);
+	ret = snd_es18xx_dsp_command(chip, reg);
+	if (ret < 0)
+		goto end;
+        ret = snd_es18xx_dsp_command(chip, data);
+ end:
+        spin_unlock_irqrestore(&chip->reg_lock, flags);
+#ifdef REG_DEBUG
+	snd_printk("Reg %02x set to %02x\n", reg, data);
+#endif
+	return ret;
+}
+
+static int snd_es18xx_read(es18xx_t *chip, unsigned char reg)
+{
+	unsigned long flags;
+	int ret, data;
+        spin_lock_irqsave(&chip->reg_lock, flags);
+	ret = snd_es18xx_dsp_command(chip, 0xC0);
+	if (ret < 0)
+		goto end;
+        ret = snd_es18xx_dsp_command(chip, reg);
+	if (ret < 0)
+		goto end;
+	data = snd_es18xx_dsp_get_byte(chip);
+	ret = data;
+#ifdef REG_DEBUG
+	snd_printk("Reg %02x now is %02x (%d)\n", reg, data, ret);
+#endif
+ end:
+        spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return ret;
+}
+
+/* Return old value */
+static int snd_es18xx_bits(es18xx_t *chip, unsigned char reg,
+			   unsigned char mask, unsigned char val)
+{
+        int ret;
+	unsigned char old, new, oval;
+	unsigned long flags;
+        spin_lock_irqsave(&chip->reg_lock, flags);
+        ret = snd_es18xx_dsp_command(chip, 0xC0);
+	if (ret < 0)
+		goto end;
+        ret = snd_es18xx_dsp_command(chip, reg);
+	if (ret < 0)
+		goto end;
+	ret = snd_es18xx_dsp_get_byte(chip);
+	if (ret < 0) {
+		goto end;
+	}
+	old = ret;
+	oval = old & mask;
+	if (val != oval) {
+		ret = snd_es18xx_dsp_command(chip, reg);
+		if (ret < 0)
+			goto end;
+		new = (old & ~mask) | (val & mask);
+		ret = snd_es18xx_dsp_command(chip, new);
+		if (ret < 0)
+			goto end;
+#ifdef REG_DEBUG
+		snd_printk("Reg %02x was %02x, set to %02x (%d)\n", reg, old, new, ret);
+#endif
+	}
+	ret = oval;
+ end:
+        spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return ret;
+}
+
+static inline void snd_es18xx_mixer_write(es18xx_t *chip,
+			    unsigned char reg, unsigned char data)
+{
+	unsigned long flags;
+        spin_lock_irqsave(&chip->mixer_lock, flags);
+        outb(reg, chip->port + 0x04);
+        outb(data, chip->port + 0x05);
+        spin_unlock_irqrestore(&chip->mixer_lock, flags);
+#ifdef REG_DEBUG
+	snd_printk("Mixer reg %02x set to %02x\n", reg, data);
+#endif
+}
+
+static inline int snd_es18xx_mixer_read(es18xx_t *chip, unsigned char reg)
+{
+	unsigned long flags;
+	int data;
+        spin_lock_irqsave(&chip->mixer_lock, flags);
+        outb(reg, chip->port + 0x04);
+	data = inb(chip->port + 0x05);
+        spin_unlock_irqrestore(&chip->mixer_lock, flags);
+#ifdef REG_DEBUG
+	snd_printk("Mixer reg %02x now is %02x\n", reg, data);
+#endif
+        return data;
+}
+
+/* Return old value */
+static inline int snd_es18xx_mixer_bits(es18xx_t *chip, unsigned char reg,
+					unsigned char mask, unsigned char val)
+{
+	unsigned char old, new, oval;
+	unsigned long flags;
+        spin_lock_irqsave(&chip->mixer_lock, flags);
+        outb(reg, chip->port + 0x04);
+	old = inb(chip->port + 0x05);
+	oval = old & mask;
+	if (val != oval) {
+		new = (old & ~mask) | (val & mask);
+		outb(new, chip->port + 0x05);
+#ifdef REG_DEBUG
+		snd_printk("Mixer reg %02x was %02x, set to %02x\n", reg, old, new);
+#endif
+	}
+        spin_unlock_irqrestore(&chip->mixer_lock, flags);
+	return oval;
+}
+
+static inline int snd_es18xx_mixer_writable(es18xx_t *chip, unsigned char reg,
+					    unsigned char mask)
+{
+	int old, expected, new;
+	unsigned long flags;
+        spin_lock_irqsave(&chip->mixer_lock, flags);
+        outb(reg, chip->port + 0x04);
+	old = inb(chip->port + 0x05);
+	expected = old ^ mask;
+	outb(expected, chip->port + 0x05);
+	new = inb(chip->port + 0x05);
+        spin_unlock_irqrestore(&chip->mixer_lock, flags);
+#ifdef REG_DEBUG
+	snd_printk("Mixer reg %02x was %02x, set to %02x, now is %02x\n", reg, old, expected, new);
+#endif
+	return expected == new;
+}
+
+
+static int snd_es18xx_reset(es18xx_t *chip)
+{
+	int i;
+        outb(0x03, chip->port + 0x06);
+        inb(chip->port + 0x06);
+        outb(0x00, chip->port + 0x06);
+        for(i = 0; i < MILLISECOND && !(inb(chip->port + 0x0E) & 0x80); i++);
+        if (inb(chip->port + 0x0A) != 0xAA)
+                return -1;
+	return 0;
+}
+
+static int snd_es18xx_reset_fifo(es18xx_t *chip)
+{
+        outb(0x02, chip->port + 0x06);
+        inb(chip->port + 0x06);
+        outb(0x00, chip->port + 0x06);
+	return 0;
+}
+
+static ratnum_t new_clocks[2] = {
+	{
+		.num = 793800,
+		.den_min = 1,
+		.den_max = 128,
+		.den_step = 1,
+	},
+	{
+		.num = 768000,
+		.den_min = 1,
+		.den_max = 128,
+		.den_step = 1,
+	}
+};
+
+static snd_pcm_hw_constraint_ratnums_t new_hw_constraints_clocks = {
+	.nrats = 2,
+	.rats = new_clocks,
+};
+
+static ratnum_t old_clocks[2] = {
+	{
+		.num = 795444,
+		.den_min = 1,
+		.den_max = 128,
+		.den_step = 1,
+	},
+	{
+		.num = 397722,
+		.den_min = 1,
+		.den_max = 128,
+		.den_step = 1,
+	}
+};
+
+static snd_pcm_hw_constraint_ratnums_t old_hw_constraints_clocks  = {
+	.nrats = 2,
+	.rats = old_clocks,
+};
+
+
+static void snd_es18xx_rate_set(es18xx_t *chip, 
+				snd_pcm_substream_t *substream,
+				int mode)
+{
+	unsigned int bits, div0;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	if (chip->caps & ES18XX_NEW_RATE) {
+		if (runtime->rate_num == new_clocks[0].num)
+			bits = 128 - runtime->rate_den;
+		else
+			bits = 256 - runtime->rate_den;
+	} else {
+		if (runtime->rate_num == old_clocks[0].num)
+			bits = 256 - runtime->rate_den;
+		else
+			bits = 128 - runtime->rate_den;
+	}
+
+	/* set filter register */
+	div0 = 256 - 7160000*20/(8*82*runtime->rate);
+		
+	if ((chip->caps & ES18XX_PCM2) && mode == DAC2) {
+		snd_es18xx_mixer_write(chip, 0x70, bits);
+		/*
+		 * Comment from kernel oss driver:
+		 * FKS: fascinating: 0x72 doesn't seem to work.
+		 */
+		snd_es18xx_write(chip, 0xA2, div0);
+		snd_es18xx_mixer_write(chip, 0x72, div0);
+	} else {
+		snd_es18xx_write(chip, 0xA1, bits);
+		snd_es18xx_write(chip, 0xA2, div0);
+	}
+}
+
+static int snd_es18xx_playback_hw_params(snd_pcm_substream_t * substream,
+					 snd_pcm_hw_params_t * hw_params)
+{
+	es18xx_t *chip = snd_pcm_substream_chip(substream);
+	int shift, err;
+
+	shift = 0;
+	if (params_channels(hw_params) == 2)
+		shift++;
+	if (snd_pcm_format_width(params_format(hw_params)) == 16)
+		shift++;
+
+	if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) {
+		if ((chip->caps & ES18XX_DUPLEX_MONO) &&
+		    (chip->capture_a_substream) &&
+		    params_channels(hw_params) != 1) {
+			_snd_pcm_hw_param_setempty(hw_params, SNDRV_PCM_HW_PARAM_CHANNELS);
+			return -EBUSY;
+		}
+		chip->dma2_shift = shift;
+	} else {
+		chip->dma1_shift = shift;
+	}
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+	return 0;
+}
+
+static int snd_es18xx_pcm_hw_free(snd_pcm_substream_t * substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_es18xx_playback1_prepare(es18xx_t *chip,
+					snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	chip->dma2_size = size;
+
+        snd_es18xx_rate_set(chip, substream, DAC2);
+
+        /* Transfer Count Reload */
+        count = 0x10000 - count;
+        snd_es18xx_mixer_write(chip, 0x74, count & 0xff);
+        snd_es18xx_mixer_write(chip, 0x76, count >> 8);
+
+	/* Set format */
+        snd_es18xx_mixer_bits(chip, 0x7A, 0x07,
+			      ((runtime->channels == 1) ? 0x00 : 0x02) |
+			      (snd_pcm_format_width(runtime->format) == 16 ? 0x01 : 0x00) |
+			      (snd_pcm_format_unsigned(runtime->format) ? 0x00 : 0x04));
+
+        /* Set DMA controller */
+        snd_dma_program(chip->dma2, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
+
+	return 0;
+}
+
+static int snd_es18xx_playback1_trigger(es18xx_t *chip,
+					snd_pcm_substream_t * substream,
+					int cmd)
+{
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		if (chip->active & DAC2)
+			return 0;
+		chip->active |= DAC2;
+                /* Start DMA */
+		if (chip->dma2 >= 4)
+			snd_es18xx_mixer_write(chip, 0x78, 0xb3);
+		else
+			snd_es18xx_mixer_write(chip, 0x78, 0x93);
+#ifdef AVOID_POPS
+		/* Avoid pops */
+                udelay(100000);
+		if (chip->caps & ES18XX_PCM2)
+			/* Restore Audio 2 volume */
+			snd_es18xx_mixer_write(chip, 0x7C, chip->audio2_vol);
+		else
+			/* Enable PCM output */
+			snd_es18xx_dsp_command(chip, 0xD1);
+#endif
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		if (!(chip->active & DAC2))
+			return 0;
+		chip->active &= ~DAC2;
+                /* Stop DMA */
+                snd_es18xx_mixer_write(chip, 0x78, 0x00);
+#ifdef AVOID_POPS
+                udelay(25000);
+		if (chip->caps & ES18XX_PCM2)
+			/* Set Audio 2 volume to 0 */
+			snd_es18xx_mixer_write(chip, 0x7C, 0);
+		else
+			/* Disable PCM output */
+			snd_es18xx_dsp_command(chip, 0xD3);
+#endif
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int snd_es18xx_capture_hw_params(snd_pcm_substream_t * substream,
+					snd_pcm_hw_params_t * hw_params)
+{
+	es18xx_t *chip = snd_pcm_substream_chip(substream);
+	int shift, err;
+
+	shift = 0;
+	if ((chip->caps & ES18XX_DUPLEX_MONO) &&
+	    chip->playback_a_substream &&
+	    params_channels(hw_params) != 1) {
+		_snd_pcm_hw_param_setempty(hw_params, SNDRV_PCM_HW_PARAM_CHANNELS);
+		return -EBUSY;
+	}
+	if (params_channels(hw_params) == 2)
+		shift++;
+	if (snd_pcm_format_width(params_format(hw_params)) == 16)
+		shift++;
+	chip->dma1_shift = shift;
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+	return 0;
+}
+
+static int snd_es18xx_capture_prepare(snd_pcm_substream_t *substream)
+{
+        es18xx_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	chip->dma1_size = size;
+
+	snd_es18xx_reset_fifo(chip);
+
+        /* Set stereo/mono */
+        snd_es18xx_bits(chip, 0xA8, 0x03, runtime->channels == 1 ? 0x02 : 0x01);
+
+        snd_es18xx_rate_set(chip, substream, ADC1);
+
+        /* Transfer Count Reload */
+	count = 0x10000 - count;
+	snd_es18xx_write(chip, 0xA4, count & 0xff);
+	snd_es18xx_write(chip, 0xA5, count >> 8);
+
+#ifdef AVOID_POPS
+	udelay(100000);
+#endif
+
+        /* Set format */
+        snd_es18xx_write(chip, 0xB7, 
+                         snd_pcm_format_unsigned(runtime->format) ? 0x51 : 0x71);
+        snd_es18xx_write(chip, 0xB7, 0x90 |
+                         ((runtime->channels == 1) ? 0x40 : 0x08) |
+                         (snd_pcm_format_width(runtime->format) == 16 ? 0x04 : 0x00) |
+                         (snd_pcm_format_unsigned(runtime->format) ? 0x00 : 0x20));
+
+        /* Set DMA controler */
+        snd_dma_program(chip->dma1, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
+
+	return 0;
+}
+
+static int snd_es18xx_capture_trigger(snd_pcm_substream_t *substream,
+				      int cmd)
+{
+        es18xx_t *chip = snd_pcm_substream_chip(substream);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		if (chip->active & ADC1)
+			return 0;
+		chip->active |= ADC1;
+                /* Start DMA */
+                snd_es18xx_write(chip, 0xB8, 0x0f);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		if (!(chip->active & ADC1))
+			return 0;
+		chip->active &= ~ADC1;
+                /* Stop DMA */
+                snd_es18xx_write(chip, 0xB8, 0x00);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int snd_es18xx_playback2_prepare(es18xx_t *chip,
+					snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	chip->dma1_size = size;
+
+	snd_es18xx_reset_fifo(chip);
+
+        /* Set stereo/mono */
+        snd_es18xx_bits(chip, 0xA8, 0x03, runtime->channels == 1 ? 0x02 : 0x01);
+
+        snd_es18xx_rate_set(chip, substream, DAC1);
+
+        /* Transfer Count Reload */
+	count = 0x10000 - count;
+	snd_es18xx_write(chip, 0xA4, count & 0xff);
+	snd_es18xx_write(chip, 0xA5, count >> 8);
+
+        /* Set format */
+        snd_es18xx_write(chip, 0xB6,
+                         snd_pcm_format_unsigned(runtime->format) ? 0x80 : 0x00);
+        snd_es18xx_write(chip, 0xB7, 
+                         snd_pcm_format_unsigned(runtime->format) ? 0x51 : 0x71);
+        snd_es18xx_write(chip, 0xB7, 0x90 |
+                         (runtime->channels == 1 ? 0x40 : 0x08) |
+                         (snd_pcm_format_width(runtime->format) == 16 ? 0x04 : 0x00) |
+                         (snd_pcm_format_unsigned(runtime->format) ? 0x00 : 0x20));
+
+        /* Set DMA controler */
+        snd_dma_program(chip->dma1, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
+
+	return 0;
+}
+
+static int snd_es18xx_playback2_trigger(es18xx_t *chip,
+					snd_pcm_substream_t *substream,
+					int cmd)
+{
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		if (chip->active & DAC1)
+			return 0;
+		chip->active |= DAC1;
+                /* Start DMA */
+                snd_es18xx_write(chip, 0xB8, 0x05);
+#ifdef AVOID_POPS
+		/* Avoid pops */
+                udelay(100000);
+                /* Enable Audio 1 */
+                snd_es18xx_dsp_command(chip, 0xD1);
+#endif
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		if (!(chip->active & DAC1))
+			return 0;
+		chip->active &= ~DAC1;
+                /* Stop DMA */
+                snd_es18xx_write(chip, 0xB8, 0x00);
+#ifdef AVOID_POPS
+		/* Avoid pops */
+                udelay(25000);
+                /* Disable Audio 1 */
+                snd_es18xx_dsp_command(chip, 0xD3);
+#endif
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int snd_es18xx_playback_prepare(snd_pcm_substream_t *substream)
+{
+        es18xx_t *chip = snd_pcm_substream_chip(substream);
+	if (substream->number == 0 && (chip->caps & ES18XX_PCM2))
+		return snd_es18xx_playback1_prepare(chip, substream);
+	else
+		return snd_es18xx_playback2_prepare(chip, substream);
+}
+
+static int snd_es18xx_playback_trigger(snd_pcm_substream_t *substream,
+				       int cmd)
+{
+        es18xx_t *chip = snd_pcm_substream_chip(substream);
+	if (substream->number == 0 && (chip->caps & ES18XX_PCM2))
+		return snd_es18xx_playback1_trigger(chip, substream, cmd);
+	else
+		return snd_es18xx_playback2_trigger(chip, substream, cmd);
+}
+
+static irqreturn_t snd_es18xx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	es18xx_t *chip = dev_id;
+	unsigned char status;
+
+	if (chip->caps & ES18XX_CONTROL) {
+		/* Read Interrupt status */
+		status = inb(chip->ctrl_port + 6);
+	} else {
+		/* Read Interrupt status */
+		status = snd_es18xx_mixer_read(chip, 0x7f) >> 4;
+	}
+#if 0
+	else {
+		status = 0;
+		if (inb(chip->port + 0x0C) & 0x01)
+			status |= AUDIO1_IRQ;
+		if (snd_es18xx_mixer_read(chip, 0x7A) & 0x80)
+			status |= AUDIO2_IRQ;
+		if ((chip->caps & ES18XX_HWV) &&
+		    snd_es18xx_mixer_read(chip, 0x64) & 0x10)
+			status |= HWV_IRQ;
+	}
+#endif
+
+	/* Audio 1 & Audio 2 */
+        if (status & AUDIO2_IRQ) {
+                if (chip->active & DAC2)
+                	snd_pcm_period_elapsed(chip->playback_a_substream);
+		/* ack interrupt */
+                snd_es18xx_mixer_bits(chip, 0x7A, 0x80, 0x00);
+        }
+        if (status & AUDIO1_IRQ) {
+                /* ok.. capture is active */
+                if (chip->active & ADC1)
+                	snd_pcm_period_elapsed(chip->capture_a_substream);
+                /* ok.. playback2 is active */
+                else if (chip->active & DAC1)
+                	snd_pcm_period_elapsed(chip->playback_b_substream);
+		/* ack interrupt */
+		inb(chip->port + 0x0E);
+        }
+
+	/* MPU */
+	if ((status & MPU_IRQ) && chip->rmidi)
+		snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data, regs);
+
+	/* Hardware volume */
+	if (status & HWV_IRQ) {
+		int split = snd_es18xx_mixer_read(chip, 0x64) & 0x80;
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->hw_switch->id);
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->hw_volume->id);
+		if (!split) {
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->master_switch->id);
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->master_volume->id);
+		}
+		/* ack interrupt */
+		snd_es18xx_mixer_write(chip, 0x66, 0x00);
+	}
+	return IRQ_HANDLED;
+}
+
+static snd_pcm_uframes_t snd_es18xx_playback_pointer(snd_pcm_substream_t * substream)
+{
+        es18xx_t *chip = snd_pcm_substream_chip(substream);
+	int pos;
+
+	if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) {
+		if (!(chip->active & DAC2))
+			return 0;
+		pos = snd_dma_pointer(chip->dma2, chip->dma2_size);
+		return pos >> chip->dma2_shift;
+	} else {
+		if (!(chip->active & DAC1))
+			return 0;
+		pos = snd_dma_pointer(chip->dma1, chip->dma1_size);
+		return pos >> chip->dma1_shift;
+	}
+}
+
+static snd_pcm_uframes_t snd_es18xx_capture_pointer(snd_pcm_substream_t * substream)
+{
+        es18xx_t *chip = snd_pcm_substream_chip(substream);
+	int pos;
+
+        if (!(chip->active & ADC1))
+                return 0;
+	pos = snd_dma_pointer(chip->dma1, chip->dma1_size);
+	return pos >> chip->dma1_shift;
+}
+
+static snd_pcm_hardware_t snd_es18xx_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_RESUME |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 | 
+				 SNDRV_PCM_FMTBIT_S16_LE | 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 =	65536,
+	.period_bytes_min =	64,
+	.period_bytes_max =	65536,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static snd_pcm_hardware_t snd_es18xx_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_RESUME |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 | 
+				 SNDRV_PCM_FMTBIT_S16_LE | 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 =	65536,
+	.period_bytes_min =	64,
+	.period_bytes_max =	65536,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static int snd_es18xx_playback_open(snd_pcm_substream_t * substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+        es18xx_t *chip = snd_pcm_substream_chip(substream);
+
+	if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) {
+		if ((chip->caps & ES18XX_DUPLEX_MONO) &&
+		    chip->capture_a_substream && 
+		    chip->capture_a_substream->runtime->channels != 1)
+			return -EAGAIN;
+		chip->playback_a_substream = substream;
+	} else if (substream->number <= 1) {
+		if (chip->capture_a_substream)
+			return -EAGAIN;
+		chip->playback_b_substream = substream;
+	} else {
+		snd_BUG();
+		return -EINVAL;
+	}
+	substream->runtime->hw = snd_es18xx_playback;
+	snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      (chip->caps & ES18XX_NEW_RATE) ? &new_hw_constraints_clocks : &old_hw_constraints_clocks);
+        return 0;
+}
+
+static int snd_es18xx_capture_open(snd_pcm_substream_t * substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+        es18xx_t *chip = snd_pcm_substream_chip(substream);
+
+        if (chip->playback_b_substream)
+                return -EAGAIN;
+	if ((chip->caps & ES18XX_DUPLEX_MONO) &&
+	    chip->playback_a_substream &&
+	    chip->playback_a_substream->runtime->channels != 1)
+		return -EAGAIN;
+        chip->capture_a_substream = substream;
+	substream->runtime->hw = snd_es18xx_capture;
+	snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      (chip->caps & ES18XX_NEW_RATE) ? &new_hw_constraints_clocks : &old_hw_constraints_clocks);
+        return 0;
+}
+
+static int snd_es18xx_playback_close(snd_pcm_substream_t * substream)
+{
+        es18xx_t *chip = snd_pcm_substream_chip(substream);
+
+	if (substream->number == 0 && (chip->caps & ES18XX_PCM2))
+		chip->playback_a_substream = NULL;
+	else
+		chip->playback_b_substream = NULL;
+	
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int snd_es18xx_capture_close(snd_pcm_substream_t * substream)
+{
+        es18xx_t *chip = snd_pcm_substream_chip(substream);
+
+        chip->capture_a_substream = NULL;
+	snd_pcm_lib_free_pages(substream);
+        return 0;
+}
+
+/*
+ *  MIXER part
+ */
+
+static int snd_es18xx_info_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	static char *texts[8] = {
+		"Mic", "Mic Master", "CD", "AOUT",
+		"Mic1", "Mix", "Line", "Master"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 8;
+	if (uinfo->value.enumerated.item > 7)
+		uinfo->value.enumerated.item = 7;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_es18xx_get_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	es18xx_t *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.enumerated.item[0] = snd_es18xx_mixer_read(chip, 0x1c) & 0x07;
+	return 0;
+}
+
+static int snd_es18xx_put_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	es18xx_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned char val = ucontrol->value.enumerated.item[0];
+	
+	if (val > 7)
+		return -EINVAL;
+	return snd_es18xx_mixer_bits(chip, 0x1c, 0x07, val) != val;
+}
+
+static int snd_es18xx_info_spatializer_enable(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_es18xx_get_spatializer_enable(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	es18xx_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned char val = snd_es18xx_mixer_read(chip, 0x50);
+	ucontrol->value.integer.value[0] = !!(val & 8);
+	return 0;
+}
+
+static int snd_es18xx_put_spatializer_enable(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	es18xx_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned char oval, nval;
+	int change;
+	nval = ucontrol->value.integer.value[0] ? 0x0c : 0x04;
+	oval = snd_es18xx_mixer_read(chip, 0x50) & 0x0c;
+	change = nval != oval;
+	if (change) {
+		snd_es18xx_mixer_write(chip, 0x50, nval & ~0x04);
+		snd_es18xx_mixer_write(chip, 0x50, nval);
+	}
+	return change;
+}
+
+static int snd_es18xx_info_hw_volume(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 = 63;
+	return 0;
+}
+
+static int snd_es18xx_get_hw_volume(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	es18xx_t *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = snd_es18xx_mixer_read(chip, 0x61) & 0x3f;
+	ucontrol->value.integer.value[1] = snd_es18xx_mixer_read(chip, 0x63) & 0x3f;
+	return 0;
+}
+
+static int snd_es18xx_info_hw_switch(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int snd_es18xx_get_hw_switch(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	es18xx_t *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = !(snd_es18xx_mixer_read(chip, 0x61) & 0x40);
+	ucontrol->value.integer.value[1] = !(snd_es18xx_mixer_read(chip, 0x63) & 0x40);
+	return 0;
+}
+
+static void snd_es18xx_hwv_free(snd_kcontrol_t *kcontrol)
+{
+	es18xx_t *chip = snd_kcontrol_chip(kcontrol);
+	chip->master_volume = NULL;
+	chip->master_switch = NULL;
+	chip->hw_volume = NULL;
+	chip->hw_switch = NULL;
+}
+
+static int snd_es18xx_reg_bits(es18xx_t *chip, unsigned char reg,
+			       unsigned char mask, unsigned char val)
+{
+	if (reg < 0xa0)
+		return snd_es18xx_mixer_bits(chip, reg, mask, val);
+	else
+		return snd_es18xx_bits(chip, reg, mask, val);
+}
+
+static int snd_es18xx_reg_read(es18xx_t *chip, unsigned char reg)
+{
+	if (reg < 0xa0)
+		return snd_es18xx_mixer_read(chip, reg);
+	else
+		return snd_es18xx_read(chip, reg);
+}
+
+#define ES18XX_SINGLE(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_es18xx_info_single, \
+  .get = snd_es18xx_get_single, .put = snd_es18xx_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+static int snd_es18xx_info_single(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_es18xx_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	es18xx_t *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int val;
+	
+	val = snd_es18xx_reg_read(chip, reg);
+	ucontrol->value.integer.value[0] = (val >> shift) & mask;
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_es18xx_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	es18xx_t *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	unsigned char val;
+	
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	mask <<= shift;
+	val <<= shift;
+	return snd_es18xx_reg_bits(chip, reg, mask, val) != val;
+}
+
+#define ES18XX_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_es18xx_info_double, \
+  .get = snd_es18xx_get_double, .put = snd_es18xx_put_double, \
+  .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
+
+static int snd_es18xx_info_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_es18xx_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	es18xx_t *chip = snd_kcontrol_chip(kcontrol);
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	unsigned char left, right;
+	
+	left = snd_es18xx_reg_read(chip, left_reg);
+	if (left_reg != right_reg)
+		right = snd_es18xx_reg_read(chip, right_reg);
+	else
+		right = left;
+	ucontrol->value.integer.value[0] = (left >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (right >> shift_right) & mask;
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+
+static int snd_es18xx_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	es18xx_t *chip = snd_kcontrol_chip(kcontrol);
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	unsigned char val1, val2, mask1, mask2;
+	
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	mask1 = mask << shift_left;
+	mask2 = mask << shift_right;
+	if (left_reg != right_reg) {
+		change = 0;
+		if (snd_es18xx_reg_bits(chip, left_reg, mask1, val1) != val1)
+			change = 1;
+		if (snd_es18xx_reg_bits(chip, right_reg, mask2, val2) != val2)
+			change = 1;
+	} else {
+		change = (snd_es18xx_reg_bits(chip, left_reg, mask1 | mask2, 
+					      val1 | val2) != (val1 | val2));
+	}
+	return change;
+}
+
+static snd_kcontrol_new_t snd_es18xx_base_controls[] = {
+ES18XX_DOUBLE("Master Playback Volume", 0, 0x60, 0x62, 0, 0, 63, 0),
+ES18XX_DOUBLE("Master Playback Switch", 0, 0x60, 0x62, 6, 6, 1, 1),
+ES18XX_DOUBLE("Line Playback Volume", 0, 0x3e, 0x3e, 4, 0, 15, 0),
+ES18XX_DOUBLE("CD Playback Volume", 0, 0x38, 0x38, 4, 0, 15, 0),
+ES18XX_DOUBLE("FM Playback Volume", 0, 0x36, 0x36, 4, 0, 15, 0),
+ES18XX_DOUBLE("Mono Playback Volume", 0, 0x6d, 0x6d, 4, 0, 15, 0),
+ES18XX_DOUBLE("Mic Playback Volume", 0, 0x1a, 0x1a, 4, 0, 15, 0),
+ES18XX_DOUBLE("Aux Playback Volume", 0, 0x3a, 0x3a, 4, 0, 15, 0),
+ES18XX_SINGLE("PC Speaker Playback Volume", 0, 0x3c, 0, 7, 0),
+ES18XX_SINGLE("Record Monitor", 0, 0xa8, 3, 1, 0),
+ES18XX_DOUBLE("Capture Volume", 0, 0xb4, 0xb4, 4, 0, 15, 0),
+ES18XX_SINGLE("Capture Switch", 0, 0x1c, 4, 1, 1),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Capture Source",
+	.info = snd_es18xx_info_mux,
+	.get = snd_es18xx_get_mux,
+	.put = snd_es18xx_put_mux,
+}
+};
+
+static snd_kcontrol_new_t snd_es18xx_mono_in_control = 
+ES18XX_DOUBLE("Mono Input Playback Volume", 0, 0x6d, 0x6d, 4, 0, 15, 0);
+
+static snd_kcontrol_new_t snd_es18xx_recmix_controls[] = {
+ES18XX_DOUBLE("PCM Capture Volume", 0, 0x69, 0x69, 4, 0, 15, 0),
+ES18XX_DOUBLE("Mic Capture Volume", 0, 0x68, 0x68, 4, 0, 15, 0),
+ES18XX_DOUBLE("Line Capture Volume", 0, 0x6e, 0x6e, 4, 0, 15, 0),
+ES18XX_DOUBLE("FM Capture Volume", 0, 0x6b, 0x6b, 4, 0, 15, 0),
+ES18XX_DOUBLE("Mono Capture Volume", 0, 0x6f, 0x6f, 4, 0, 15, 0),
+ES18XX_DOUBLE("CD Capture Volume", 0, 0x6a, 0x6a, 4, 0, 15, 0),
+ES18XX_DOUBLE("Aux Capture Volume", 0, 0x6c, 0x6c, 4, 0, 15, 0)
+};
+
+static snd_kcontrol_new_t snd_es18xx_pcm1_controls[] = {
+ES18XX_DOUBLE("PCM Playback Volume", 0, 0x14, 0x14, 4, 0, 15, 0),
+};
+
+static snd_kcontrol_new_t snd_es18xx_pcm2_controls[] = {
+ES18XX_DOUBLE("PCM Playback Volume", 0, 0x7c, 0x7c, 4, 0, 15, 0),
+ES18XX_DOUBLE("PCM Playback Volume", 1, 0x14, 0x14, 4, 0, 15, 0)
+};
+
+static snd_kcontrol_new_t snd_es18xx_spatializer_controls[] = {
+ES18XX_SINGLE("3D Control - Level", 0, 0x52, 0, 63, 0),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "3D Control - Switch",
+	.info = snd_es18xx_info_spatializer_enable,
+	.get = snd_es18xx_get_spatializer_enable,
+	.put = snd_es18xx_put_spatializer_enable,
+}
+};
+
+static snd_kcontrol_new_t snd_es18xx_micpre1_control = 
+ES18XX_SINGLE("Mic Boost (+26dB)", 0, 0xa9, 2, 1, 0);
+
+static snd_kcontrol_new_t snd_es18xx_micpre2_control =
+ES18XX_SINGLE("Mic Boost (+26dB)", 0, 0x7d, 3, 1, 0);
+
+static snd_kcontrol_new_t snd_es18xx_hw_volume_controls[] = {
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Hardware Master Playback Volume",
+	.access = SNDRV_CTL_ELEM_ACCESS_READ,
+	.info = snd_es18xx_info_hw_volume,
+	.get = snd_es18xx_get_hw_volume,
+},
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Hardware Master Playback Switch",
+	.access = SNDRV_CTL_ELEM_ACCESS_READ,
+	.info = snd_es18xx_info_hw_switch,
+	.get = snd_es18xx_get_hw_switch,
+},
+ES18XX_SINGLE("Hardware Master Volume Split", 0, 0x64, 7, 1, 0),
+};
+
+#if 0
+static int __devinit snd_es18xx_config_read(es18xx_t *chip, unsigned char reg)
+{
+	int data;
+	unsigned long flags;
+        spin_lock_irqsave(&chip->ctrl_lock, flags);
+	outb(reg, chip->ctrl_port);
+	data = inb(chip->ctrl_port + 1);
+        spin_unlock_irqrestore(&chip->ctrl_lock, flags);
+	return data;
+}
+#endif
+
+static void __devinit snd_es18xx_config_write(es18xx_t *chip, 
+					      unsigned char reg, unsigned char data)
+{
+	/* No need for spinlocks, this function is used only in
+	   otherwise protected init code */
+	outb(reg, chip->ctrl_port);
+	outb(data, chip->ctrl_port + 1);
+#ifdef REG_DEBUG
+	snd_printk("Config reg %02x set to %02x\n", reg, data);
+#endif
+}
+
+static int __devinit snd_es18xx_initialize(es18xx_t *chip)
+{
+	int mask = 0;
+
+        /* enable extended mode */
+        snd_es18xx_dsp_command(chip, 0xC6);
+	/* Reset mixer registers */
+	snd_es18xx_mixer_write(chip, 0x00, 0x00);
+
+        /* Audio 1 DMA demand mode (4 bytes/request) */
+        snd_es18xx_write(chip, 0xB9, 2);
+	if (chip->caps & ES18XX_CONTROL) {
+		/* Hardware volume IRQ */
+		snd_es18xx_config_write(chip, 0x27, chip->irq);
+		if (chip->fm_port > 0 && chip->fm_port != SNDRV_AUTO_PORT) {
+			/* FM I/O */
+			snd_es18xx_config_write(chip, 0x62, chip->fm_port >> 8);
+			snd_es18xx_config_write(chip, 0x63, chip->fm_port & 0xff);
+		}
+		if (chip->mpu_port > 0 && chip->mpu_port != SNDRV_AUTO_PORT) {
+			/* MPU-401 I/O */
+			snd_es18xx_config_write(chip, 0x64, chip->mpu_port >> 8);
+			snd_es18xx_config_write(chip, 0x65, chip->mpu_port & 0xff);
+			/* MPU-401 IRQ */
+			snd_es18xx_config_write(chip, 0x28, chip->irq);
+		}
+		/* Audio1 IRQ */
+		snd_es18xx_config_write(chip, 0x70, chip->irq);
+		/* Audio2 IRQ */
+		snd_es18xx_config_write(chip, 0x72, chip->irq);
+		/* Audio1 DMA */
+		snd_es18xx_config_write(chip, 0x74, chip->dma1);
+		/* Audio2 DMA */
+		snd_es18xx_config_write(chip, 0x75, chip->dma2);
+
+		/* Enable Audio 1 IRQ */
+		snd_es18xx_write(chip, 0xB1, 0x50);
+		/* Enable Audio 2 IRQ */
+		snd_es18xx_mixer_write(chip, 0x7A, 0x40);
+		/* Enable Audio 1 DMA */
+		snd_es18xx_write(chip, 0xB2, 0x50);
+		/* Enable MPU and hardware volume interrupt */
+		snd_es18xx_mixer_write(chip, 0x64, 0x42);
+	}
+	else {
+		int irqmask, dma1mask, dma2mask;
+		switch (chip->irq) {
+		case 2:
+		case 9:
+			irqmask = 0;
+			break;
+		case 5:
+			irqmask = 1;
+			break;
+		case 7:
+			irqmask = 2;
+			break;
+		case 10:
+			irqmask = 3;
+			break;
+		default:
+			snd_printk("invalid irq %d\n", chip->irq);
+			return -ENODEV;
+		}
+		switch (chip->dma1) {
+		case 0:
+			dma1mask = 1;
+			break;
+		case 1:
+			dma1mask = 2;
+			break;
+		case 3:
+			dma1mask = 3;
+			break;
+		default:
+			snd_printk("invalid dma1 %d\n", chip->dma1);
+			return -ENODEV;
+		}
+		switch (chip->dma2) {
+		case 0:
+			dma2mask = 0;
+			break;
+		case 1:
+			dma2mask = 1;
+			break;
+		case 3:
+			dma2mask = 2;
+			break;
+		case 5:
+			dma2mask = 3;
+			break;
+		default:
+			snd_printk("invalid dma2 %d\n", chip->dma2);
+			return -ENODEV;
+		}
+
+		/* Enable and set Audio 1 IRQ */
+		snd_es18xx_write(chip, 0xB1, 0x50 | (irqmask << 2));
+		/* Enable and set Audio 1 DMA */
+		snd_es18xx_write(chip, 0xB2, 0x50 | (dma1mask << 2));
+		/* Set Audio 2 DMA */
+		snd_es18xx_mixer_bits(chip, 0x7d, 0x07, 0x04 | dma2mask);
+		/* Enable Audio 2 IRQ and DMA
+		   Set capture mixer input */
+		snd_es18xx_mixer_write(chip, 0x7A, 0x68);
+		/* Enable and set hardware volume interrupt */
+		snd_es18xx_mixer_write(chip, 0x64, 0x06);
+		if (chip->mpu_port > 0 && chip->mpu_port != SNDRV_AUTO_PORT) {
+			/* MPU401 share irq with audio
+			   Joystick enabled
+			   FM enabled */
+			snd_es18xx_mixer_write(chip, 0x40, 0x43 | (chip->mpu_port & 0xf0) >> 1);
+		}
+		snd_es18xx_mixer_write(chip, 0x7f, ((irqmask + 1) << 1) | 0x01);
+	}
+	if (chip->caps & ES18XX_NEW_RATE) {
+		/* Change behaviour of register A1
+		   4x oversampling
+		   2nd channel DAC asynchronous */
+		snd_es18xx_mixer_write(chip, 0x71, 0x32);
+	}
+	if (!(chip->caps & ES18XX_PCM2)) {
+		/* Enable DMA FIFO */
+		snd_es18xx_write(chip, 0xB7, 0x80);
+	}
+	if (chip->caps & ES18XX_SPATIALIZER) {
+		/* Set spatializer parameters to recommended values */
+		snd_es18xx_mixer_write(chip, 0x54, 0x8f);
+		snd_es18xx_mixer_write(chip, 0x56, 0x95);
+		snd_es18xx_mixer_write(chip, 0x58, 0x94);
+		snd_es18xx_mixer_write(chip, 0x5a, 0x80);
+	}
+	/* Mute input source */
+	if (chip->caps & ES18XX_MUTEREC)
+		mask = 0x10;
+	if (chip->caps & ES18XX_RECMIX)
+		snd_es18xx_mixer_write(chip, 0x1c, 0x05 | mask);
+	else {
+		snd_es18xx_mixer_write(chip, 0x1c, 0x00 | mask);
+		snd_es18xx_write(chip, 0xb4, 0x00);
+	}
+#ifndef AVOID_POPS
+	/* Enable PCM output */
+	snd_es18xx_dsp_command(chip, 0xD1);
+#endif
+
+        return 0;
+}
+
+static int __devinit snd_es18xx_identify(es18xx_t *chip)
+{
+	int hi,lo;
+
+	/* reset */
+	if (snd_es18xx_reset(chip) < 0) {
+                snd_printk("reset at 0x%lx failed!!!\n", chip->port);
+		return -ENODEV;
+	}
+
+	snd_es18xx_dsp_command(chip, 0xe7);
+	hi = snd_es18xx_dsp_get_byte(chip);
+	if (hi < 0) {
+		return hi;
+	}
+	lo = snd_es18xx_dsp_get_byte(chip);
+	if ((lo & 0xf0) != 0x80) {
+		return -ENODEV;
+	}
+	if (hi == 0x48) {
+		chip->version = 0x488;
+		return 0;
+	}
+	if (hi != 0x68) {
+		return -ENODEV;
+	}
+	if ((lo & 0x0f) < 8) {
+		chip->version = 0x688;
+		return 0;
+	}
+			
+        outb(0x40, chip->port + 0x04);
+	hi = inb(chip->port + 0x05);
+	lo = inb(chip->port + 0x05);
+	if (hi != lo) {
+		chip->version = hi << 8 | lo;
+		chip->ctrl_port = inb(chip->port + 0x05) << 8;
+		chip->ctrl_port += inb(chip->port + 0x05);
+
+		if ((chip->res_ctrl_port = request_region(chip->ctrl_port, 8, "ES18xx - CTRL")) == NULL) {
+			snd_printk(KERN_ERR PFX "unable go grab port 0x%lx\n", chip->ctrl_port);
+			return -EBUSY;
+		}
+
+		return 0;
+	}
+
+	/* If has Hardware volume */
+	if (snd_es18xx_mixer_writable(chip, 0x64, 0x04)) {
+		/* If has Audio2 */
+		if (snd_es18xx_mixer_writable(chip, 0x70, 0x7f)) {
+			/* If has volume count */
+			if (snd_es18xx_mixer_writable(chip, 0x64, 0x20)) {
+				chip->version = 0x1887;
+			} else {
+				chip->version = 0x1888;
+			}
+		} else {
+			chip->version = 0x1788;
+		}
+	}
+	else
+		chip->version = 0x1688;
+	return 0;
+}
+
+static int __devinit snd_es18xx_probe(es18xx_t *chip)
+{
+	if (snd_es18xx_identify(chip) < 0) {
+		snd_printk(KERN_ERR PFX "[0x%lx] ESS chip not found\n", chip->port);
+                return -ENODEV;
+	}
+
+	switch (chip->version) {
+	case 0x1868:
+		chip->caps = ES18XX_DUPLEX_MONO | ES18XX_DUPLEX_SAME | ES18XX_CONTROL | ES18XX_HWV;
+		break;
+	case 0x1869:
+		chip->caps = ES18XX_PCM2 | ES18XX_SPATIALIZER | ES18XX_RECMIX | ES18XX_NEW_RATE | ES18XX_AUXB | ES18XX_MONO | ES18XX_MUTEREC | ES18XX_CONTROL | ES18XX_HWV;
+		break;
+	case 0x1878:
+		chip->caps = ES18XX_DUPLEX_MONO | ES18XX_DUPLEX_SAME | ES18XX_I2S | ES18XX_CONTROL | ES18XX_HWV;
+		break;
+	case 0x1879:
+		chip->caps = ES18XX_PCM2 | ES18XX_SPATIALIZER | ES18XX_RECMIX | ES18XX_NEW_RATE | ES18XX_AUXB | ES18XX_I2S | ES18XX_CONTROL | ES18XX_HWV;
+		break;
+	case 0x1887:
+		chip->caps = ES18XX_PCM2 | ES18XX_RECMIX | ES18XX_AUXB | ES18XX_DUPLEX_SAME | ES18XX_HWV;
+		break;
+	case 0x1888:
+		chip->caps = ES18XX_PCM2 | ES18XX_RECMIX | ES18XX_AUXB | ES18XX_DUPLEX_SAME | ES18XX_HWV;
+		break;
+	default:
+                snd_printk("[0x%lx] unsupported chip ES%x\n",
+                           chip->port, chip->version);
+                return -ENODEV;
+        }
+
+        snd_printd("[0x%lx] ESS%x chip found\n", chip->port, chip->version);
+
+	if (chip->dma1 == chip->dma2)
+		chip->caps &= ~(ES18XX_PCM2 | ES18XX_DUPLEX_SAME);
+
+        return snd_es18xx_initialize(chip);
+}
+
+static snd_pcm_ops_t snd_es18xx_playback_ops = {
+	.open =		snd_es18xx_playback_open,
+	.close =	snd_es18xx_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_es18xx_playback_hw_params,
+	.hw_free =	snd_es18xx_pcm_hw_free,
+	.prepare =	snd_es18xx_playback_prepare,
+	.trigger =	snd_es18xx_playback_trigger,
+	.pointer =	snd_es18xx_playback_pointer,
+};
+
+static snd_pcm_ops_t snd_es18xx_capture_ops = {
+	.open =		snd_es18xx_capture_open,
+	.close =	snd_es18xx_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_es18xx_capture_hw_params,
+	.hw_free =	snd_es18xx_pcm_hw_free,
+	.prepare =	snd_es18xx_capture_prepare,
+	.trigger =	snd_es18xx_capture_trigger,
+	.pointer =	snd_es18xx_capture_pointer,
+};
+
+static void snd_es18xx_pcm_free(snd_pcm_t *pcm)
+{
+	es18xx_t *codec = pcm->private_data;
+	codec->pcm = NULL;
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int __devinit snd_es18xx_pcm(es18xx_t *chip, int device, snd_pcm_t ** rpcm)
+{
+        snd_pcm_t *pcm;
+	char str[16];
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	sprintf(str, "ES%x", chip->version);
+	if (chip->caps & ES18XX_PCM2) {
+		err = snd_pcm_new(chip->card, str, device, 2, 1, &pcm);
+	} else {
+		err = snd_pcm_new(chip->card, str, device, 1, 1, &pcm);
+	}
+        if (err < 0)
+                return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_es18xx_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_es18xx_capture_ops);
+
+	/* global setup */
+        pcm->private_data = chip;
+	pcm->private_free = snd_es18xx_pcm_free;
+        pcm->info_flags = 0;
+	if (chip->caps & ES18XX_DUPLEX_SAME)
+		pcm->info_flags |= SNDRV_PCM_INFO_JOINT_DUPLEX;
+	if (! (chip->caps & ES18XX_PCM2))
+		pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX;
+	sprintf(pcm->name, "ESS AudioDrive ES%x", chip->version);
+        chip->pcm = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024,
+					      chip->dma1 > 3 || chip->dma2 > 3 ? 128*1024 : 64*1024);
+
+        if (rpcm)
+        	*rpcm = pcm;
+	return 0;
+}
+
+/* Power Management support functions */
+#ifdef CONFIG_PM
+static int snd_es18xx_suspend(snd_card_t *card, pm_message_t state)
+{
+	es18xx_t *chip = card->pm_private_data;
+
+	snd_pcm_suspend_all(chip->pcm);
+
+	/* power down */
+	chip->pm_reg = (unsigned char)snd_es18xx_read(chip, ES18XX_PM);
+	chip->pm_reg |= (ES18XX_PM_FM | ES18XX_PM_SUS);
+	snd_es18xx_write(chip, ES18XX_PM, chip->pm_reg);
+	snd_es18xx_write(chip, ES18XX_PM, chip->pm_reg ^= ES18XX_PM_SUS);
+
+	return 0;
+}
+
+static int snd_es18xx_resume(snd_card_t *card)
+{
+	es18xx_t *chip = card->pm_private_data;
+
+	/* restore PM register, we won't wake till (not 0x07) i/o activity though */
+	snd_es18xx_write(chip, ES18XX_PM, chip->pm_reg ^= ES18XX_PM_FM);
+
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+static int snd_es18xx_free(es18xx_t *chip)
+{
+	if (chip->res_port) {
+		release_resource(chip->res_port);
+		kfree_nocheck(chip->res_port);
+	}
+	if (chip->res_ctrl_port) {
+		release_resource(chip->res_ctrl_port);
+		kfree_nocheck(chip->res_ctrl_port);
+	}
+	if (chip->res_mpu_port) {
+		release_resource(chip->res_mpu_port);
+		kfree_nocheck(chip->res_mpu_port);
+	}
+	if (chip->irq >= 0)
+		free_irq(chip->irq, (void *) chip);
+	if (chip->dma1 >= 0) {
+		disable_dma(chip->dma1);
+		free_dma(chip->dma1);
+	}
+	if (chip->dma2 >= 0 && chip->dma1 != chip->dma2) {
+		disable_dma(chip->dma2);
+		free_dma(chip->dma2);
+	}
+	kfree(chip);
+	return 0;
+}
+
+static int snd_es18xx_dev_free(snd_device_t *device)
+{
+	es18xx_t *chip = device->device_data;
+	return snd_es18xx_free(chip);
+}
+
+static int __devinit snd_es18xx_new_device(snd_card_t * card,
+					   unsigned long port,
+					   unsigned long mpu_port,
+					   unsigned long fm_port,
+					   int irq, int dma1, int dma2,
+					   es18xx_t ** rchip)
+{
+        es18xx_t *chip;
+	static snd_device_ops_t ops = {
+		.dev_free =	snd_es18xx_dev_free,
+        };
+	int err;
+
+	*rchip = NULL;
+        chip = kcalloc(1, sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+	spin_lock_init(&chip->reg_lock);
+ 	spin_lock_init(&chip->mixer_lock);
+ 	spin_lock_init(&chip->ctrl_lock);
+        chip->card = card;
+        chip->port = port;
+        chip->mpu_port = mpu_port;
+        chip->fm_port = fm_port;
+        chip->irq = -1;
+        chip->dma1 = -1;
+        chip->dma2 = -1;
+        chip->audio2_vol = 0x00;
+	chip->active = 0;
+
+	if ((chip->res_port = request_region(port, 16, "ES18xx")) == NULL) {
+		snd_es18xx_free(chip);
+		snd_printk(KERN_ERR PFX "unable to grap ports 0x%lx-0x%lx\n", port, port + 16 - 1);
+		return -EBUSY;
+	}
+
+	if (request_irq(irq, snd_es18xx_interrupt, SA_INTERRUPT, "ES18xx", (void *) chip)) {
+		snd_es18xx_free(chip);
+		snd_printk(KERN_ERR PFX "unable to grap IRQ %d\n", irq);
+		return -EBUSY;
+	}
+	chip->irq = irq;
+
+	if (request_dma(dma1, "ES18xx DMA 1")) {
+		snd_es18xx_free(chip);
+		snd_printk(KERN_ERR PFX "unable to grap DMA1 %d\n", dma1);
+		return -EBUSY;
+	}
+	chip->dma1 = dma1;
+
+	if (dma2 != dma1 && request_dma(dma2, "ES18xx DMA 2")) {
+		snd_es18xx_free(chip);
+		snd_printk(KERN_ERR PFX "unable to grap DMA2 %d\n", dma2);
+		return -EBUSY;
+	}
+	chip->dma2 = dma2;
+
+        if (snd_es18xx_probe(chip) < 0) {
+                snd_es18xx_free(chip);
+                return -ENODEV;
+        }
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_es18xx_free(chip);
+		return err;
+	}
+        *rchip = chip;
+        return 0;
+}
+
+static int __devinit snd_es18xx_mixer(es18xx_t *chip)
+{
+	snd_card_t *card;
+	int err;
+	unsigned int idx;
+
+	card = chip->card;
+
+	strcpy(card->mixername, chip->pcm->name);
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_base_controls); idx++) {
+		snd_kcontrol_t *kctl;
+		kctl = snd_ctl_new1(&snd_es18xx_base_controls[idx], chip);
+		if (chip->caps & ES18XX_HWV) {
+			switch (idx) {
+			case 0:
+				chip->master_volume = kctl;
+				kctl->private_free = snd_es18xx_hwv_free;
+				break;
+			case 1:
+				chip->master_switch = kctl;
+				kctl->private_free = snd_es18xx_hwv_free;
+				break;
+			}
+		}
+		if ((err = snd_ctl_add(card, kctl)) < 0)
+			return err;
+	}
+	if (chip->caps & ES18XX_PCM2) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_pcm2_controls); idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_pcm2_controls[idx], chip))) < 0)
+				return err;
+		} 
+	} else {
+		for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_pcm1_controls); idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_pcm1_controls[idx], chip))) < 0)
+				return err;
+		}
+	}
+
+	if (chip->caps & ES18XX_MONO) {
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_mono_in_control, chip))) < 0)
+			return err;
+	}
+	if (chip->caps & ES18XX_RECMIX) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_recmix_controls); idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_recmix_controls[idx], chip))) < 0)
+				return err;
+		}
+	}
+	switch (chip->version) {
+	default:
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_micpre1_control, chip))) < 0)
+			return err;
+		break;
+	case 0x1869:
+	case 0x1879:
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_micpre2_control, chip))) < 0)
+			return err;
+		break;
+	}
+	if (chip->caps & ES18XX_SPATIALIZER) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_spatializer_controls); idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_spatializer_controls[idx], chip))) < 0)
+				return err;
+		}
+	}
+	if (chip->caps & ES18XX_HWV) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_hw_volume_controls); idx++) {
+			snd_kcontrol_t *kctl;
+			kctl = snd_ctl_new1(&snd_es18xx_hw_volume_controls[idx], chip);
+			if (idx == 0)
+				chip->hw_volume = kctl;
+			else
+				chip->hw_switch = kctl;
+			kctl->private_free = snd_es18xx_hwv_free;
+			if ((err = snd_ctl_add(card, kctl)) < 0)
+				return err;
+			
+		}
+	}
+	return 0;
+}
+       
+
+/* Card level */
+
+MODULE_AUTHOR("Christian Fischbach <fishbach@pool.informatik.rwth-aachen.de>, Abramo Bagnara <abramo@alsa-project.org>");  
+MODULE_DESCRIPTION("ESS ES18xx AudioDrive");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ESS,ES1868 PnP AudioDrive},"
+		"{ESS,ES1869 PnP AudioDrive},"
+		"{ESS,ES1878 PnP AudioDrive},"
+		"{ESS,ES1879 PnP AudioDrive},"
+		"{ESS,ES1887 PnP AudioDrive},"
+		"{ESS,ES1888 PnP AudioDrive},"
+		"{ESS,ES1887 AudioDrive},"
+		"{ESS,ES1888 AudioDrive}}");
+
+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_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static int isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x240,0x260,0x280 */
+#ifndef CONFIG_PNP
+static long mpu_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -1};
+#else
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+#endif
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,10 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for ES18xx soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for ES18xx soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable ES18xx soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for ES18xx driver.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for ES18xx driver.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for ES18xx driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for ES18xx driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA 1 # for ES18xx driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA 2 # for ES18xx driver.");
+
+struct snd_audiodrive {
+#ifdef CONFIG_PNP
+	struct pnp_dev *dev;
+	struct pnp_dev *devc;
+#endif
+};
+
+static snd_card_t *snd_audiodrive_legacy[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+#ifdef CONFIG_PNP
+
+static struct pnp_card_device_id snd_audiodrive_pnpids[] = {
+	/* ESS 1868 (integrated on Compaq dual P-Pro motherboard and Genius 18PnP 3D) */
+	{ .id = "ESS1868", .devs = { { "ESS1868" }, { "ESS0000" } } },
+	/* ESS 1868 (integrated on Maxisound Cards) */
+	{ .id = "ESS1868", .devs = { { "ESS8601" }, { "ESS8600" } } },
+	/* ESS 1868 (integrated on Maxisound Cards) */
+	{ .id = "ESS1868", .devs = { { "ESS8611" }, { "ESS8610" } } },
+	/* ESS ES1869 Plug and Play AudioDrive */
+	{ .id = "ESS0003", .devs = { { "ESS1869" }, { "ESS0006" } } },
+	/* ESS 1869 */
+	{ .id = "ESS1869", .devs = { { "ESS1869" }, { "ESS0006" } } },
+	/* ESS 1878 */
+	{ .id = "ESS1878", .devs = { { "ESS1878" }, { "ESS0004" } } },
+	/* ESS 1879 */
+	{ .id = "ESS1879", .devs = { { "ESS1879" }, { "ESS0009" } } },
+	/* --- */
+	{ .id = "" } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_audiodrive_pnpids);
+
+static int __devinit snd_audiodrive_pnp(int dev, struct snd_audiodrive *acard,
+					struct pnp_card_link *card,
+					const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	struct pnp_resource_table * cfg = kmalloc(sizeof(struct pnp_resource_table), GFP_KERNEL);
+	int err;
+
+	if (!cfg)
+		return -ENOMEM;
+	acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->dev == NULL) {
+		kfree(cfg);
+		return -EBUSY;
+	}
+	acard->devc = pnp_request_card_device(card, id->devs[1].id, NULL);
+	if (acard->devc == NULL) {
+		kfree(cfg);
+		return -EBUSY;
+	}
+	/* Control port initialization */
+	err = pnp_activate_dev(acard->devc);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "PnP control configure failure (out of resources?)\n");
+		return -EAGAIN;
+	}
+	snd_printdd("pnp: port=0x%lx\n", pnp_port_start(acard->devc, 0));
+	/* PnP initialization */
+	pdev = acard->dev;
+	pnp_init_resource_table(cfg);
+	if (port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[0], port[dev], 16);
+	if (fm_port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[1], fm_port[dev], 4);
+	if (mpu_port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[2], mpu_port[dev], 2);
+	if (dma1[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[0], dma1[dev], 1);
+	if (dma2[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[1], dma2[dev], 1);
+	if (irq[dev] != SNDRV_AUTO_IRQ)
+		pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1);
+	err = pnp_manual_config_dev(pdev, cfg, 0);
+	if (err < 0)
+		snd_printk(KERN_ERR PFX "PnP manual resources are invalid, using auto config\n");
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "PnP configure failure (out of resources?)\n");
+		kfree(cfg);
+		return -EBUSY;
+	}
+	/* ok. hack using Vendor-Defined Card-Level registers */
+	/* skip csn and logdev initialization - already done in isapnp_configure */
+	if (pnp_device_is_isapnp(pdev)) {
+		isapnp_cfg_begin(isapnp_card_number(pdev), isapnp_csn_number(pdev));
+		isapnp_write_byte(0x27, pnp_irq(pdev, 0));	/* Hardware Volume IRQ Number */
+		if (mpu_port[dev] != SNDRV_AUTO_PORT)
+			isapnp_write_byte(0x28, pnp_irq(pdev, 0)); /* MPU-401 IRQ Number */
+		isapnp_write_byte(0x72, pnp_irq(pdev, 0));	/* second IRQ */
+		isapnp_cfg_end();
+	} else {
+		snd_printk(KERN_ERR PFX "unable to install ISA PnP hack, expect malfunction\n");
+	}
+	port[dev] = pnp_port_start(pdev, 0);
+	fm_port[dev] = pnp_port_start(pdev, 1);
+	mpu_port[dev] = pnp_port_start(pdev, 2);
+	dma1[dev] = pnp_dma(pdev, 0);
+	dma2[dev] = pnp_dma(pdev, 1);
+	irq[dev] = pnp_irq(pdev, 0);
+	snd_printdd("PnP ES18xx: port=0x%lx, fm port=0x%lx, mpu port=0x%lx\n", port[dev], fm_port[dev], mpu_port[dev]);
+	snd_printdd("PnP ES18xx: dma1=%i, dma2=%i, irq=%i\n", dma1[dev], dma2[dev], irq[dev]);
+	kfree(cfg);
+	return 0;
+}
+#endif /* CONFIG_PNP */
+
+static int __devinit snd_audiodrive_probe(int dev, struct pnp_card_link *pcard,
+					  const struct pnp_card_device_id *pid)
+{
+	static int possible_irqs[] = {5, 9, 10, 7, 11, 12, -1};
+	static int possible_dmas[] = {1, 0, 3, 5, -1};
+	int xirq, xdma1, xdma2;
+	snd_card_t *card;
+	struct snd_audiodrive *acard;
+	snd_rawmidi_t *rmidi = NULL;
+	es18xx_t *chip;
+	opl3_t *opl3;
+	int err;
+
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE,
+			    sizeof(struct snd_audiodrive));
+	if (card == NULL)
+		return -ENOMEM;
+	acard = (struct snd_audiodrive *)card->private_data;
+#ifdef CONFIG_PNP
+	if (isapnp[dev]) {
+		if ((err = snd_audiodrive_pnp(dev, acard, pcard, pid)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+		snd_card_set_dev(card, &pcard->card->dev);
+	}
+#endif
+
+	xirq = irq[dev];
+	if (xirq == SNDRV_AUTO_IRQ) {
+		if ((xirq = snd_legacy_find_free_irq(possible_irqs)) < 0) {
+			snd_card_free(card);
+			snd_printk("unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	xdma1 = dma1[dev];
+        if (xdma1 == SNDRV_AUTO_DMA) {
+                if ((xdma1 = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+                        snd_card_free(card);
+                        snd_printk("unable to find a free DMA1\n");
+                        return -EBUSY;
+                }
+        }
+	xdma2 = dma2[dev];
+        if (xdma2 == SNDRV_AUTO_DMA) {
+                if ((xdma2 = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+                        snd_card_free(card);
+                        snd_printk("unable to find a free DMA2\n");
+                        return -EBUSY;
+                }
+        }
+
+	if ((err = snd_es18xx_new_device(card,
+					 port[dev],
+					 mpu_port[dev],
+					 fm_port[dev],
+					 xirq, xdma1, xdma2,
+					 &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	sprintf(card->driver, "ES%x", chip->version);
+	sprintf(card->shortname, "ESS AudioDrive ES%x", chip->version);
+	if (xdma1 != xdma2)
+		sprintf(card->longname, "%s at 0x%lx, irq %d, dma1 %d, dma2 %d",
+			card->shortname,
+			chip->port,
+			xirq, xdma1, xdma2);
+	else
+		sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+			card->shortname,
+			chip->port,
+			xirq, xdma1);
+
+	if ((err = snd_es18xx_pcm(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_es18xx_mixer(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+		if (snd_opl3_create(card, chip->fm_port, chip->fm_port + 2, OPL3_HW_OPL3, 0, &opl3) < 0) {
+			snd_printk(KERN_ERR PFX "opl3 not detected at 0x%lx\n", chip->fm_port);
+		} else {
+			if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+				snd_card_free(card);
+				return err;
+			}
+		}
+	}
+
+	if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) {
+		if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_ES18XX,
+					       chip->mpu_port, 0,
+					       xirq, 0,
+					       &rmidi)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+		chip->rmidi = rmidi;
+	}
+
+	/* Power Management */
+	snd_card_set_isa_pm_callback(card, snd_es18xx_suspend, snd_es18xx_resume, chip);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (pcard)
+		pnp_set_card_drvdata(pcard, card);
+	else
+		snd_audiodrive_legacy[dev] = card;
+	return 0;
+}
+
+static int __devinit snd_audiodrive_probe_legacy_port(unsigned long xport)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || port[dev] != SNDRV_AUTO_PORT)
+			continue;
+#ifdef CONFIG_PNP
+		if (isapnp[dev])
+			continue;
+#endif
+		port[dev] = xport;
+		res = snd_audiodrive_probe(dev, NULL, NULL);
+		if (res < 0)
+			port[dev] = SNDRV_AUTO_PORT;
+		return res;
+	}
+	return -ENODEV;
+}
+
+
+#ifdef CONFIG_PNP
+static int __devinit snd_audiodrive_pnp_detect(struct pnp_card_link *card,
+					       const struct pnp_card_device_id *id)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || !isapnp[dev])
+			continue;
+                res = snd_audiodrive_probe(dev, card, id);
+		if (res < 0)
+			return res;
+		dev++;
+		return 0;
+        }
+
+        return -ENODEV;
+}
+
+static void __devexit snd_audiodrive_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_t *card = (snd_card_t *) pnp_get_card_drvdata(pcard);
+
+	snd_card_disconnect(card);
+	snd_card_free_in_thread(card);
+}
+
+static struct pnp_card_driver es18xx_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DISABLE,
+	.name = "es18xx",
+	.id_table = snd_audiodrive_pnpids,
+	.probe = snd_audiodrive_pnp_detect,
+	.remove = __devexit_p(snd_audiodrive_pnp_remove),
+};
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_es18xx_init(void)
+{
+	static unsigned long possible_ports[] = {0x220, 0x240, 0x260, 0x280, -1};
+	int dev, cards = 0, i;
+
+	/* legacy non-auto cards at first */
+	for (dev = 0; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || port[dev] == SNDRV_AUTO_PORT)
+			continue;
+#ifdef CONFIG_PNP
+		if (isapnp[dev])
+			continue;
+#endif
+		if (snd_audiodrive_probe(dev, NULL, NULL) >= 0)
+			cards++;
+	}
+	/* legacy auto configured cards */
+	i = snd_legacy_auto_probe(possible_ports, snd_audiodrive_probe_legacy_port);
+	if (i > 0)
+		cards += i;
+
+#ifdef CONFIG_PNP
+	/* ISA PnP cards at last */
+	i = pnp_register_card_driver(&es18xx_pnpc_driver);
+	if (i > 0)
+		cards += i;
+
+#endif
+	if(!cards) {
+#ifdef CONFIG_PNP
+		pnp_unregister_card_driver(&es18xx_pnpc_driver);
+#endif
+#ifdef MODULE
+		snd_printk(KERN_ERR "ESS AudioDrive ES18xx soundcard not found or device busy\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_es18xx_exit(void)
+{
+	int idx;
+
+#ifdef CONFIG_PNP
+	/* PnP cards first */
+	pnp_unregister_card_driver(&es18xx_pnpc_driver);
+#endif
+	for(idx = 0; idx < SNDRV_CARDS; idx++)
+		snd_card_free(snd_audiodrive_legacy[idx]);
+}
+
+module_init(alsa_card_es18xx_init)
+module_exit(alsa_card_es18xx_exit)
diff --git a/sound/isa/gus/Makefile b/sound/isa/gus/Makefile
new file mode 100644
index 0000000..bae5dbd
--- /dev/null
+++ b/sound/isa/gus/Makefile
@@ -0,0 +1,36 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-gus-lib-objs := gus_main.o \
+		    gus_io.o gus_irq.o gus_timer.o \
+		    gus_mem.o gus_mem_proc.o gus_dram.o gus_dma.o gus_volume.o \
+		    gus_pcm.o gus_mixer.o \
+		    gus_uart.o \
+		    gus_reset.o
+snd-gus-synth-objs := gus_synth.o gus_sample.o gus_simple.o gus_instr.o
+
+snd-gusclassic-objs := gusclassic.o
+snd-gusextreme-objs := gusextreme.o
+snd-gusmax-objs := gusmax.o
+snd-interwave-objs := interwave.o
+snd-interwave-stb-objs := interwave-stb.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_GUSCLASSIC) += snd-gusclassic.o snd-gus-lib.o
+obj-$(CONFIG_SND_GUSMAX) += snd-gusmax.o snd-gus-lib.o
+obj-$(CONFIG_SND_GUSEXTREME) += snd-gusextreme.o snd-gus-lib.o
+obj-$(CONFIG_SND_INTERWAVE) += snd-interwave.o snd-gus-lib.o
+obj-$(CONFIG_SND_INTERWAVE_STB) += snd-interwave-stb.o snd-gus-lib.o
+obj-$(call sequencer,$(CONFIG_SND_GUS_SYNTH)) += snd-gus-synth.o
+
+obj-m := $(sort $(obj-m))
diff --git a/sound/isa/gus/gus_dma.c b/sound/isa/gus/gus_dma.c
new file mode 100644
index 0000000..de4b56d
--- /dev/null
+++ b/sound/isa/gus/gus_dma.c
@@ -0,0 +1,244 @@
+/*
+ *  Routines for GF1 DMA control
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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/dma.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+static void snd_gf1_dma_ack(snd_gus_card_t * gus)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL, 0x00);
+	snd_gf1_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+static void snd_gf1_dma_program(snd_gus_card_t * gus,
+				unsigned int addr,
+				unsigned long buf_addr,
+				unsigned int count,
+				unsigned int cmd)
+{
+	unsigned long flags;
+	unsigned int address;
+	unsigned char dma_cmd;
+	unsigned int address_high;
+
+	// snd_printk("dma_transfer: addr=0x%x, buf=0x%lx, count=0x%x\n", addr, (long) buf, count);
+
+	if (gus->gf1.dma1 > 3) {
+		if (gus->gf1.enh_mode) {
+			address = addr >> 1;
+		} else {
+			if (addr & 0x1f) {
+				snd_printd("snd_gf1_dma_transfer: unaligned address (0x%x)?\n", addr);
+				return;
+			}
+			address = (addr & 0x000c0000) | ((addr & 0x0003ffff) >> 1);
+		}
+	} else {
+		address = addr;
+	}
+
+	dma_cmd = SNDRV_GF1_DMA_ENABLE | (unsigned short) cmd;
+#if 0
+	dma_cmd |= 0x08;
+#endif
+	if (dma_cmd & SNDRV_GF1_DMA_16BIT) {
+		count++;
+		count &= ~1;	/* align */
+	}
+	if (gus->gf1.dma1 > 3) {
+		dma_cmd |= SNDRV_GF1_DMA_WIDTH16;
+		count++;
+		count &= ~1;	/* align */
+	}
+	snd_gf1_dma_ack(gus);
+	snd_dma_program(gus->gf1.dma1, buf_addr, count, dma_cmd & SNDRV_GF1_DMA_READ ? DMA_MODE_READ : DMA_MODE_WRITE);
+#if 0
+	snd_printk("address = 0x%x, count = 0x%x, dma_cmd = 0x%x\n", address << 1, count, dma_cmd);
+#endif
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	if (gus->gf1.enh_mode) {
+		address_high = ((address >> 16) & 0x000000f0) | (address & 0x0000000f);
+		snd_gf1_write16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW, (unsigned short) (address >> 4));
+		snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_HIGH, (unsigned char) address_high);
+	} else
+		snd_gf1_write16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW, (unsigned short) (address >> 4));
+	snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL, dma_cmd);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+static snd_gf1_dma_block_t *snd_gf1_dma_next_block(snd_gus_card_t * gus)
+{
+	snd_gf1_dma_block_t *block;
+
+	/* PCM block have bigger priority than synthesizer one */
+	if (gus->gf1.dma_data_pcm) {
+		block = gus->gf1.dma_data_pcm;
+		if (gus->gf1.dma_data_pcm_last == block) {
+			gus->gf1.dma_data_pcm =
+			gus->gf1.dma_data_pcm_last = NULL;
+		} else {
+			gus->gf1.dma_data_pcm = block->next;
+		}
+	} else if (gus->gf1.dma_data_synth) {
+		block = gus->gf1.dma_data_synth;
+		if (gus->gf1.dma_data_synth_last == block) {
+			gus->gf1.dma_data_synth =
+			gus->gf1.dma_data_synth_last = NULL;
+		} else {
+			gus->gf1.dma_data_synth = block->next;
+		}
+	} else {
+		block = NULL;
+	}
+	if (block) {
+		gus->gf1.dma_ack = block->ack;
+		gus->gf1.dma_private_data = block->private_data;
+	}
+	return block;
+}
+
+
+static void snd_gf1_dma_interrupt(snd_gus_card_t * gus)
+{
+	snd_gf1_dma_block_t *block;
+
+	snd_gf1_dma_ack(gus);
+	if (gus->gf1.dma_ack)
+		gus->gf1.dma_ack(gus, gus->gf1.dma_private_data);
+	spin_lock(&gus->dma_lock);
+	if (gus->gf1.dma_data_pcm == NULL &&
+	    gus->gf1.dma_data_synth == NULL) {
+	    	gus->gf1.dma_ack = NULL;
+		gus->gf1.dma_flags &= ~SNDRV_GF1_DMA_TRIGGER;
+		spin_unlock(&gus->dma_lock);
+		return;
+	}
+	block = snd_gf1_dma_next_block(gus);
+	spin_unlock(&gus->dma_lock);
+	snd_gf1_dma_program(gus, block->addr, block->buf_addr, block->count, (unsigned short) block->cmd);
+	kfree(block);
+#if 0
+	printk("program dma (IRQ) - addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n", addr, (long) buffer, count, cmd);
+#endif
+}
+
+int snd_gf1_dma_init(snd_gus_card_t * gus)
+{
+	down(&gus->dma_mutex);
+	gus->gf1.dma_shared++;
+	if (gus->gf1.dma_shared > 1) {
+		up(&gus->dma_mutex);
+		return 0;
+	}
+	gus->gf1.interrupt_handler_dma_write = snd_gf1_dma_interrupt;
+	gus->gf1.dma_data_pcm = 
+	gus->gf1.dma_data_pcm_last =
+	gus->gf1.dma_data_synth = 
+	gus->gf1.dma_data_synth_last = NULL;
+	up(&gus->dma_mutex);
+	return 0;
+}
+
+int snd_gf1_dma_done(snd_gus_card_t * gus)
+{
+	snd_gf1_dma_block_t *block;
+
+	down(&gus->dma_mutex);
+	gus->gf1.dma_shared--;
+	if (!gus->gf1.dma_shared) {
+		snd_dma_disable(gus->gf1.dma1);
+		snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_DMA_WRITE);
+		snd_gf1_dma_ack(gus);
+		while ((block = gus->gf1.dma_data_pcm)) {
+			gus->gf1.dma_data_pcm = block->next;
+			kfree(block);
+		}
+		while ((block = gus->gf1.dma_data_synth)) {
+			gus->gf1.dma_data_synth = block->next;
+			kfree(block);
+		}
+		gus->gf1.dma_data_pcm_last =
+		gus->gf1.dma_data_synth_last = NULL;
+	}
+	up(&gus->dma_mutex);
+	return 0;
+}
+
+int snd_gf1_dma_transfer_block(snd_gus_card_t * gus,
+			       snd_gf1_dma_block_t * __block,
+			       int atomic,
+			       int synth)
+{
+	unsigned long flags;
+	snd_gf1_dma_block_t *block;
+
+	block = kmalloc(sizeof(*block), atomic ? GFP_ATOMIC : GFP_KERNEL);
+	if (block == NULL) {
+		snd_printk("gf1: DMA transfer failure; not enough memory\n");
+		return -ENOMEM;
+	}
+	*block = *__block;
+	block->next = NULL;
+#if 0
+	printk("addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n", block->addr, (long) block->buffer, block->count, block->cmd);
+#endif
+#if 0
+	printk("gus->gf1.dma_data_pcm_last = 0x%lx\n", (long)gus->gf1.dma_data_pcm_last);
+	printk("gus->gf1.dma_data_pcm = 0x%lx\n", (long)gus->gf1.dma_data_pcm);
+#endif
+	spin_lock_irqsave(&gus->dma_lock, flags);
+	if (synth) {
+		if (gus->gf1.dma_data_synth_last) {
+			gus->gf1.dma_data_synth_last->next = block;
+			gus->gf1.dma_data_synth_last = block;
+		} else {
+			gus->gf1.dma_data_synth = 
+			gus->gf1.dma_data_synth_last = block;
+		}
+	} else {
+		if (gus->gf1.dma_data_pcm_last) {
+			gus->gf1.dma_data_pcm_last->next = block;
+			gus->gf1.dma_data_pcm_last = block;
+		} else {
+			gus->gf1.dma_data_pcm = 
+			gus->gf1.dma_data_pcm_last = block;
+		}
+	}
+	if (!(gus->gf1.dma_flags & SNDRV_GF1_DMA_TRIGGER)) {
+		gus->gf1.dma_flags |= SNDRV_GF1_DMA_TRIGGER;
+		block = snd_gf1_dma_next_block(gus);
+		spin_unlock_irqrestore(&gus->dma_lock, flags);
+		if (block == NULL)
+			return 0;
+		snd_gf1_dma_program(gus, block->addr, block->buf_addr, block->count, (unsigned short) block->cmd);
+		kfree(block);
+		return 0;
+	}
+	spin_unlock_irqrestore(&gus->dma_lock, flags);
+	return 0;
+}
diff --git a/sound/isa/gus/gus_dram.c b/sound/isa/gus/gus_dram.c
new file mode 100644
index 0000000..22120b8
--- /dev/null
+++ b/sound/isa/gus/gus_dram.c
@@ -0,0 +1,103 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  DRAM access routines
+ *
+ *
+ *   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/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/info.h>
+
+
+static int snd_gus_dram_poke(snd_gus_card_t *gus, char __user *_buffer,
+			     unsigned int address, unsigned int size)
+{
+	unsigned long flags;
+	unsigned int size1, size2;
+	char buffer[256], *pbuffer;
+
+	while (size > 0) {
+		size1 = size > sizeof(buffer) ? sizeof(buffer) : size;
+		if (copy_from_user(buffer, _buffer, size1))
+			return -EFAULT;
+		if (gus->interwave) {
+			spin_lock_irqsave(&gus->reg_lock, flags);
+			snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01);
+			snd_gf1_dram_addr(gus, address);
+			outsb(GUSP(gus, DRAM), buffer, size1);
+			spin_unlock_irqrestore(&gus->reg_lock, flags);
+			address += size1;
+		} else {
+			pbuffer = buffer;
+			size2 = size1;
+			while (size2--)
+				snd_gf1_poke(gus, address++, *pbuffer++);
+		}
+		size -= size1;
+		_buffer += size1;
+	}
+	return 0;
+}
+
+
+int snd_gus_dram_write(snd_gus_card_t *gus, char __user *buffer,
+		       unsigned int address, unsigned int size)
+{
+	return snd_gus_dram_poke(gus, buffer, address, size);
+}
+
+static int snd_gus_dram_peek(snd_gus_card_t *gus, char __user *_buffer,
+			     unsigned int address, unsigned int size,
+			     int rom)
+{
+	unsigned long flags;
+	unsigned int size1, size2;
+	char buffer[256], *pbuffer;
+
+	while (size > 0) {
+		size1 = size > sizeof(buffer) ? sizeof(buffer) : size;
+		if (gus->interwave) {
+			spin_lock_irqsave(&gus->reg_lock, flags);
+			snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, rom ? 0x03 : 0x01);
+			snd_gf1_dram_addr(gus, address);
+			insb(GUSP(gus, DRAM), buffer, size1);
+			snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01);
+			spin_unlock_irqrestore(&gus->reg_lock, flags);
+			address += size1;
+		} else {
+			pbuffer = buffer;
+			size2 = size1;
+			while (size2--)
+				*pbuffer++ = snd_gf1_peek(gus, address++);
+		}
+		if (copy_to_user(_buffer, buffer, size1))
+			return -EFAULT;
+		size -= size1;
+		_buffer += size1;
+	}
+	return 0;
+}
+
+int snd_gus_dram_read(snd_gus_card_t *gus, char __user *buffer,
+		      unsigned int address, unsigned int size,
+		      int rom)
+{
+	return snd_gus_dram_peek(gus, buffer, address, size, rom);
+}
diff --git a/sound/isa/gus/gus_instr.c b/sound/isa/gus/gus_instr.c
new file mode 100644
index 0000000..591a9a1
--- /dev/null
+++ b/sound/isa/gus/gus_instr.c
@@ -0,0 +1,173 @@
+/*
+ *  Routines for Gravis UltraSound soundcards - Synthesizer
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+/*
+ *
+ */
+
+int snd_gus_iwffff_put_sample(void *private_data, iwffff_wave_t *wave,
+			      char __user *data, long len, int atomic)
+{
+	snd_gus_card_t *gus = private_data;
+	snd_gf1_mem_block_t *block;
+	int err;
+
+	if (wave->format & IWFFFF_WAVE_ROM)
+		return 0;	/* it's probably ok - verify the address? */
+	if (wave->format & IWFFFF_WAVE_STEREO)
+		return -EINVAL;	/* not supported */
+	block = snd_gf1_mem_alloc(&gus->gf1.mem_alloc,
+				  SNDRV_GF1_MEM_OWNER_WAVE_IWFFFF,
+				  NULL, wave->size,
+				  wave->format & IWFFFF_WAVE_16BIT, 1,
+				  wave->share_id);
+	if (block == NULL)
+		return -ENOMEM;
+	err = snd_gus_dram_write(gus, data,
+				 block->ptr, wave->size);
+	if (err < 0) {
+		snd_gf1_mem_lock(&gus->gf1.mem_alloc, 0);
+		snd_gf1_mem_xfree(&gus->gf1.mem_alloc, block);
+		snd_gf1_mem_lock(&gus->gf1.mem_alloc, 1);
+		return err;
+	}
+	wave->address.memory = block->ptr;
+	return 0;
+}
+
+int snd_gus_iwffff_get_sample(void *private_data, iwffff_wave_t *wave,
+			      char __user *data, long len, int atomic)
+{
+	snd_gus_card_t *gus = private_data;
+
+	return snd_gus_dram_read(gus, data, wave->address.memory, wave->size,
+				 wave->format & IWFFFF_WAVE_ROM ? 1 : 0);
+}
+
+int snd_gus_iwffff_remove_sample(void *private_data, iwffff_wave_t *wave,
+				 int atomic)
+{
+	snd_gus_card_t *gus = private_data;
+
+	if (wave->format & IWFFFF_WAVE_ROM)
+		return 0;	/* it's probably ok - verify the address? */	
+	return snd_gf1_mem_free(&gus->gf1.mem_alloc, wave->address.memory);
+}
+
+/*
+ *
+ */
+
+int snd_gus_gf1_put_sample(void *private_data, gf1_wave_t *wave,
+			   char __user *data, long len, int atomic)
+{
+	snd_gus_card_t *gus = private_data;
+	snd_gf1_mem_block_t *block;
+	int err;
+
+	if (wave->format & GF1_WAVE_STEREO)
+		return -EINVAL;	/* not supported */
+	block = snd_gf1_mem_alloc(&gus->gf1.mem_alloc,
+				  SNDRV_GF1_MEM_OWNER_WAVE_GF1,
+				  NULL, wave->size,
+				  wave->format & GF1_WAVE_16BIT, 1,
+				  wave->share_id);
+	if (block == NULL)
+		return -ENOMEM;
+	err = snd_gus_dram_write(gus, data,
+				 block->ptr, wave->size);
+	if (err < 0) {
+		snd_gf1_mem_lock(&gus->gf1.mem_alloc, 0);
+		snd_gf1_mem_xfree(&gus->gf1.mem_alloc, block);
+		snd_gf1_mem_lock(&gus->gf1.mem_alloc, 1);
+		return err;
+	}
+	wave->address.memory = block->ptr;
+	return 0;
+}
+
+int snd_gus_gf1_get_sample(void *private_data, gf1_wave_t *wave,
+			   char __user *data, long len, int atomic)
+{
+	snd_gus_card_t *gus = private_data;
+
+	return snd_gus_dram_read(gus, data, wave->address.memory, wave->size, 0);
+}
+
+int snd_gus_gf1_remove_sample(void *private_data, gf1_wave_t *wave,
+			      int atomic)
+{
+	snd_gus_card_t *gus = private_data;
+
+	return snd_gf1_mem_free(&gus->gf1.mem_alloc, wave->address.memory);
+}
+
+/*
+ *
+ */
+
+int snd_gus_simple_put_sample(void *private_data, simple_instrument_t *instr,
+			      char __user *data, long len, int atomic)
+{
+	snd_gus_card_t *gus = private_data;
+	snd_gf1_mem_block_t *block;
+	int err;
+
+	if (instr->format & SIMPLE_WAVE_STEREO)
+		return -EINVAL;	/* not supported */
+	block = snd_gf1_mem_alloc(&gus->gf1.mem_alloc,
+				  SNDRV_GF1_MEM_OWNER_WAVE_SIMPLE,
+				  NULL, instr->size,
+				  instr->format & SIMPLE_WAVE_16BIT, 1,
+				  instr->share_id);
+	if (block == NULL)
+		return -ENOMEM;
+	err = snd_gus_dram_write(gus, data, block->ptr, instr->size);
+	if (err < 0) {
+		snd_gf1_mem_lock(&gus->gf1.mem_alloc, 0);
+		snd_gf1_mem_xfree(&gus->gf1.mem_alloc, block);
+		snd_gf1_mem_lock(&gus->gf1.mem_alloc, 1);
+		return err;
+	}
+	instr->address.memory = block->ptr;
+	return 0;
+}
+
+int snd_gus_simple_get_sample(void *private_data, simple_instrument_t *instr,
+			      char __user *data, long len, int atomic)
+{
+	snd_gus_card_t *gus = private_data;
+
+	return snd_gus_dram_read(gus, data, instr->address.memory, instr->size, 0);
+}
+
+int snd_gus_simple_remove_sample(void *private_data, simple_instrument_t *instr,
+			         int atomic)
+{
+	snd_gus_card_t *gus = private_data;
+
+	return snd_gf1_mem_free(&gus->gf1.mem_alloc, instr->address.memory);
+}
diff --git a/sound/isa/gus/gus_io.c b/sound/isa/gus/gus_io.c
new file mode 100644
index 0000000..f0570f2
--- /dev/null
+++ b/sound/isa/gus/gus_io.c
@@ -0,0 +1,531 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  I/O routines for GF1/InterWave synthesizer chips
+ *
+ *
+ *   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/delay.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+void snd_gf1_delay(snd_gus_card_t * gus)
+{
+	int i;
+
+	for (i = 0; i < 6; i++) {
+		mb();
+		inb(GUSP(gus, DRAM));
+	}
+}
+
+/*
+ *  =======================================================================
+ */
+
+/*
+ *  ok.. stop of control registers (wave & ramp) need some special things..
+ *       big UltraClick (tm) elimination...
+ */
+
+static inline void __snd_gf1_ctrl_stop(snd_gus_card_t * gus, unsigned char reg)
+{
+	unsigned char value;
+
+	outb(reg | 0x80, gus->gf1.reg_regsel);
+	mb();
+	value = inb(gus->gf1.reg_data8);
+	mb();
+	outb(reg, gus->gf1.reg_regsel);
+	mb();
+	outb((value | 0x03) & ~(0x80 | 0x20), gus->gf1.reg_data8);
+	mb();
+}
+
+static inline void __snd_gf1_write8(snd_gus_card_t * gus,
+				    unsigned char reg,
+				    unsigned char data)
+{
+	outb(reg, gus->gf1.reg_regsel);
+	mb();
+	outb(data, gus->gf1.reg_data8);
+	mb();
+}
+
+static inline unsigned char __snd_gf1_look8(snd_gus_card_t * gus,
+					    unsigned char reg)
+{
+	outb(reg, gus->gf1.reg_regsel);
+	mb();
+	return inb(gus->gf1.reg_data8);
+}
+
+static inline void __snd_gf1_write16(snd_gus_card_t * gus,
+				     unsigned char reg, unsigned int data)
+{
+	outb(reg, gus->gf1.reg_regsel);
+	mb();
+	outw((unsigned short) data, gus->gf1.reg_data16);
+	mb();
+}
+
+static inline unsigned short __snd_gf1_look16(snd_gus_card_t * gus,
+					      unsigned char reg)
+{
+	outb(reg, gus->gf1.reg_regsel);
+	mb();
+	return inw(gus->gf1.reg_data16);
+}
+
+static inline void __snd_gf1_adlib_write(snd_gus_card_t * gus,
+					 unsigned char reg, unsigned char data)
+{
+	outb(reg, gus->gf1.reg_timerctrl);
+	inb(gus->gf1.reg_timerctrl);
+	inb(gus->gf1.reg_timerctrl);
+	outb(data, gus->gf1.reg_timerdata);
+	inb(gus->gf1.reg_timerctrl);
+	inb(gus->gf1.reg_timerctrl);
+}
+
+static inline void __snd_gf1_write_addr(snd_gus_card_t * gus, unsigned char reg,
+                                        unsigned int addr, int w_16bit)
+{
+	if (gus->gf1.enh_mode) {
+		if (w_16bit)
+			addr = ((addr >> 1) & ~0x0000000f) | (addr & 0x0000000f);
+		__snd_gf1_write8(gus, SNDRV_GF1_VB_UPPER_ADDRESS, (unsigned char) ((addr >> 26) & 0x03));
+	} else if (w_16bit)
+		addr = (addr & 0x00c0000f) | ((addr & 0x003ffff0) >> 1);
+	__snd_gf1_write16(gus, reg, (unsigned short) (addr >> 11));
+	__snd_gf1_write16(gus, reg + 1, (unsigned short) (addr << 5));
+}
+
+static inline unsigned int __snd_gf1_read_addr(snd_gus_card_t * gus,
+					       unsigned char reg, short w_16bit)
+{
+	unsigned int res;
+
+	res = ((unsigned int) __snd_gf1_look16(gus, reg | 0x80) << 11) & 0xfff800;
+	res |= ((unsigned int) __snd_gf1_look16(gus, (reg + 1) | 0x80) >> 5) & 0x0007ff;
+	if (gus->gf1.enh_mode) {
+		res |= (unsigned int) __snd_gf1_look8(gus, SNDRV_GF1_VB_UPPER_ADDRESS | 0x80) << 26;
+		if (w_16bit)
+			res = ((res << 1) & 0xffffffe0) | (res & 0x0000000f);
+	} else if (w_16bit)
+		res = ((res & 0x001ffff0) << 1) | (res & 0x00c0000f);
+	return res;
+}
+
+
+/*
+ *  =======================================================================
+ */
+
+void snd_gf1_ctrl_stop(snd_gus_card_t * gus, unsigned char reg)
+{
+	__snd_gf1_ctrl_stop(gus, reg);
+}
+
+void snd_gf1_write8(snd_gus_card_t * gus,
+		    unsigned char reg,
+		    unsigned char data)
+{
+	__snd_gf1_write8(gus, reg, data);
+}
+
+unsigned char snd_gf1_look8(snd_gus_card_t * gus, unsigned char reg)
+{
+	return __snd_gf1_look8(gus, reg);
+}
+
+void snd_gf1_write16(snd_gus_card_t * gus,
+		     unsigned char reg,
+		     unsigned int data)
+{
+	__snd_gf1_write16(gus, reg, data);
+}
+
+unsigned short snd_gf1_look16(snd_gus_card_t * gus, unsigned char reg)
+{
+	return __snd_gf1_look16(gus, reg);
+}
+
+void snd_gf1_adlib_write(snd_gus_card_t * gus,
+                         unsigned char reg,
+                         unsigned char data)
+{
+	__snd_gf1_adlib_write(gus, reg, data);
+}
+
+void snd_gf1_write_addr(snd_gus_card_t * gus, unsigned char reg,
+                        unsigned int addr, short w_16bit)
+{
+	__snd_gf1_write_addr(gus, reg, addr, w_16bit);
+}
+
+unsigned int snd_gf1_read_addr(snd_gus_card_t * gus,
+                               unsigned char reg,
+                               short w_16bit)
+{
+	return __snd_gf1_read_addr(gus, reg, w_16bit);
+}
+
+/*
+
+ */
+
+void snd_gf1_i_ctrl_stop(snd_gus_card_t * gus, unsigned char reg)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	__snd_gf1_ctrl_stop(gus, reg);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+void snd_gf1_i_write8(snd_gus_card_t * gus,
+		      unsigned char reg,
+                      unsigned char data)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	__snd_gf1_write8(gus, reg, data);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+unsigned char snd_gf1_i_look8(snd_gus_card_t * gus, unsigned char reg)
+{
+	unsigned long flags;
+	unsigned char res;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	res = __snd_gf1_look8(gus, reg);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return res;
+}
+
+void snd_gf1_i_write16(snd_gus_card_t * gus,
+		       unsigned char reg,
+		       unsigned int data)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	__snd_gf1_write16(gus, reg, data);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+unsigned short snd_gf1_i_look16(snd_gus_card_t * gus, unsigned char reg)
+{
+	unsigned long flags;
+	unsigned short res;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	res = __snd_gf1_look16(gus, reg);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return res;
+}
+
+void snd_gf1_i_adlib_write(snd_gus_card_t * gus,
+		           unsigned char reg,
+		           unsigned char data)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	__snd_gf1_adlib_write(gus, reg, data);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+void snd_gf1_i_write_addr(snd_gus_card_t * gus, unsigned char reg,
+			  unsigned int addr, short w_16bit)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	__snd_gf1_write_addr(gus, reg, addr, w_16bit);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+unsigned int snd_gf1_i_read_addr(snd_gus_card_t * gus,
+				 unsigned char reg, short w_16bit)
+{
+	unsigned int res;
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	res = __snd_gf1_read_addr(gus, reg, w_16bit);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return res;
+}
+
+/*
+
+ */
+
+void snd_gf1_dram_addr(snd_gus_card_t * gus, unsigned int addr)
+{
+	outb(0x43, gus->gf1.reg_regsel);
+	mb();
+	outw((unsigned short) addr, gus->gf1.reg_data16);
+	mb();
+	outb(0x44, gus->gf1.reg_regsel);
+	mb();
+	outb((unsigned char) (addr >> 16), gus->gf1.reg_data8);
+	mb();
+}
+
+void snd_gf1_poke(snd_gus_card_t * gus, unsigned int addr, unsigned char data)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel);
+	mb();
+	outw((unsigned short) addr, gus->gf1.reg_data16);
+	mb();
+	outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel);
+	mb();
+	outb((unsigned char) (addr >> 16), gus->gf1.reg_data8);
+	mb();
+	outb(data, gus->gf1.reg_dram);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+unsigned char snd_gf1_peek(snd_gus_card_t * gus, unsigned int addr)
+{
+	unsigned long flags;
+	unsigned char res;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel);
+	mb();
+	outw((unsigned short) addr, gus->gf1.reg_data16);
+	mb();
+	outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel);
+	mb();
+	outb((unsigned char) (addr >> 16), gus->gf1.reg_data8);
+	mb();
+	res = inb(gus->gf1.reg_dram);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return res;
+}
+
+void snd_gf1_pokew(snd_gus_card_t * gus, unsigned int addr, unsigned short data)
+{
+	unsigned long flags;
+
+#ifdef CONFIG_SND_DEBUG
+	if (!gus->interwave)
+		snd_printk("snd_gf1_pokew - GF1!!!\n");
+#endif
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel);
+	mb();
+	outw((unsigned short) addr, gus->gf1.reg_data16);
+	mb();
+	outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel);
+	mb();
+	outb((unsigned char) (addr >> 16), gus->gf1.reg_data8);
+	mb();
+	outb(SNDRV_GF1_GW_DRAM_IO16, gus->gf1.reg_regsel);
+	mb();
+	outw(data, gus->gf1.reg_data16);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+unsigned short snd_gf1_peekw(snd_gus_card_t * gus, unsigned int addr)
+{
+	unsigned long flags;
+	unsigned short res;
+
+#ifdef CONFIG_SND_DEBUG
+	if (!gus->interwave)
+		snd_printk("snd_gf1_peekw - GF1!!!\n");
+#endif
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel);
+	mb();
+	outw((unsigned short) addr, gus->gf1.reg_data16);
+	mb();
+	outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel);
+	mb();
+	outb((unsigned char) (addr >> 16), gus->gf1.reg_data8);
+	mb();
+	outb(SNDRV_GF1_GW_DRAM_IO16, gus->gf1.reg_regsel);
+	mb();
+	res = inw(gus->gf1.reg_data16);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return res;
+}
+
+void snd_gf1_dram_setmem(snd_gus_card_t * gus, unsigned int addr,
+			 unsigned short value, unsigned int count)
+{
+	unsigned long port;
+	unsigned long flags;
+
+#ifdef CONFIG_SND_DEBUG
+	if (!gus->interwave)
+		snd_printk("snd_gf1_dram_setmem - GF1!!!\n");
+#endif
+	addr &= ~1;
+	count >>= 1;
+	port = GUSP(gus, GF1DATALOW);
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel);
+	mb();
+	outw((unsigned short) addr, gus->gf1.reg_data16);
+	mb();
+	outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel);
+	mb();
+	outb((unsigned char) (addr >> 16), gus->gf1.reg_data8);
+	mb();
+	outb(SNDRV_GF1_GW_DRAM_IO16, gus->gf1.reg_regsel);
+	while (count--)
+		outw(value, port);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+/*
+
+ */
+
+void snd_gf1_select_active_voices(snd_gus_card_t * gus)
+{
+	unsigned short voices;
+
+	static unsigned short voices_tbl[32 - 14 + 1] =
+	{
+	    44100, 41160, 38587, 36317, 34300, 32494, 30870, 29400, 28063, 26843,
+	    25725, 24696, 23746, 22866, 22050, 21289, 20580, 19916, 19293
+	};
+
+	voices = gus->gf1.active_voices;
+	if (voices > 32)
+		voices = 32;
+	if (voices < 14)
+		voices = 14;
+	if (gus->gf1.enh_mode)
+		voices = 32;
+	gus->gf1.active_voices = voices;
+	gus->gf1.playback_freq =
+	    gus->gf1.enh_mode ? 44100 : voices_tbl[voices - 14];
+	if (!gus->gf1.enh_mode) {
+		snd_gf1_i_write8(gus, SNDRV_GF1_GB_ACTIVE_VOICES, 0xc0 | (voices - 1));
+		udelay(100);
+	}
+}
+
+#ifdef CONFIG_SND_DEBUG
+
+void snd_gf1_print_voice_registers(snd_gus_card_t * gus)
+{
+	unsigned char mode;
+	int voice, ctrl;
+
+	voice = gus->gf1.active_voice;
+	printk(" -%i- GF1  voice ctrl, ramp ctrl  = 0x%x, 0x%x\n", voice, ctrl = snd_gf1_i_read8(gus, 0), snd_gf1_i_read8(gus, 0x0d));
+	printk(" -%i- GF1  frequency              = 0x%x\n", voice, snd_gf1_i_read16(gus, 1));
+	printk(" -%i- GF1  loop start, end        = 0x%x (0x%x), 0x%x (0x%x)\n", voice, snd_gf1_i_read_addr(gus, 2, ctrl & 4), snd_gf1_i_read_addr(gus, 2, (ctrl & 4) ^ 4), snd_gf1_i_read_addr(gus, 4, ctrl & 4), snd_gf1_i_read_addr(gus, 4, (ctrl & 4) ^ 4));
+	printk(" -%i- GF1  ramp start, end, rate  = 0x%x, 0x%x, 0x%x\n", voice, snd_gf1_i_read8(gus, 7), snd_gf1_i_read8(gus, 8), snd_gf1_i_read8(gus, 6));
+	printk(" -%i- GF1  volume                 = 0x%x\n", voice, snd_gf1_i_read16(gus, 9));
+	printk(" -%i- GF1  position               = 0x%x (0x%x)\n", voice, snd_gf1_i_read_addr(gus, 0x0a, ctrl & 4), snd_gf1_i_read_addr(gus, 0x0a, (ctrl & 4) ^ 4));
+	if (gus->interwave && snd_gf1_i_read8(gus, 0x19) & 0x01) {	/* enhanced mode */
+		mode = snd_gf1_i_read8(gus, 0x15);
+		printk(" -%i- GFA1 mode                   = 0x%x\n", voice, mode);
+		if (mode & 0x01) {	/* Effect processor */
+			printk(" -%i- GFA1 effect address         = 0x%x\n", voice, snd_gf1_i_read_addr(gus, 0x11, ctrl & 4));
+			printk(" -%i- GFA1 effect volume          = 0x%x\n", voice, snd_gf1_i_read16(gus, 0x16));
+			printk(" -%i- GFA1 effect volume final    = 0x%x\n", voice, snd_gf1_i_read16(gus, 0x1d));
+			printk(" -%i- GFA1 effect acumulator      = 0x%x\n", voice, snd_gf1_i_read8(gus, 0x14));
+		}
+		if (mode & 0x20) {
+			printk(" -%i- GFA1 left offset            = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x13), snd_gf1_i_read16(gus, 0x13) >> 4);
+			printk(" -%i- GFA1 left offset final      = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x1c), snd_gf1_i_read16(gus, 0x1c) >> 4);
+			printk(" -%i- GFA1 right offset           = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x0c), snd_gf1_i_read16(gus, 0x0c) >> 4);
+			printk(" -%i- GFA1 right offset final     = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x1b), snd_gf1_i_read16(gus, 0x1b) >> 4);
+		} else
+			printk(" -%i- GF1  pan                    = 0x%x\n", voice, snd_gf1_i_read8(gus, 0x0c));
+	} else
+		printk(" -%i- GF1  pan                    = 0x%x\n", voice, snd_gf1_i_read8(gus, 0x0c));
+}
+
+void snd_gf1_print_global_registers(snd_gus_card_t * gus)
+{
+	unsigned char global_mode = 0x00;
+
+	printk(" -G- GF1 active voices            = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_ACTIVE_VOICES));
+	if (gus->interwave) {
+		global_mode = snd_gf1_i_read8(gus, SNDRV_GF1_GB_GLOBAL_MODE);
+		printk(" -G- GF1 global mode              = 0x%x\n", global_mode);
+	}
+	if (global_mode & 0x02)	/* LFO enabled? */
+		printk(" -G- GF1 LFO base                 = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_LFO_BASE));
+	printk(" -G- GF1 voices IRQ read          = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_VOICES_IRQ_READ));
+	printk(" -G- GF1 DRAM DMA control         = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL));
+	printk(" -G- GF1 DRAM DMA high/low        = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_DMA_HIGH), snd_gf1_i_read16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW));
+	printk(" -G- GF1 DRAM IO high/low         = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_IO_HIGH), snd_gf1_i_read16(gus, SNDRV_GF1_GW_DRAM_IO_LOW));
+	if (!gus->interwave)
+		printk(" -G- GF1 record DMA control       = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL));
+	printk(" -G- GF1 DRAM IO 16               = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_DRAM_IO16));
+	if (gus->gf1.enh_mode) {
+		printk(" -G- GFA1 memory config           = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG));
+		printk(" -G- GFA1 memory control          = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_MEMORY_CONTROL));
+		printk(" -G- GFA1 FIFO record base        = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_FIFO_RECORD_BASE_ADDR));
+		printk(" -G- GFA1 FIFO playback base      = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_FIFO_PLAY_BASE_ADDR));
+		printk(" -G- GFA1 interleave control      = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_INTERLEAVE));
+	}
+}
+
+void snd_gf1_print_setup_registers(snd_gus_card_t * gus)
+{
+	printk(" -S- mix control                  = 0x%x\n", inb(GUSP(gus, MIXCNTRLREG)));
+	printk(" -S- IRQ status                   = 0x%x\n", inb(GUSP(gus, IRQSTAT)));
+	printk(" -S- timer control                = 0x%x\n", inb(GUSP(gus, TIMERCNTRL)));
+	printk(" -S- timer data                   = 0x%x\n", inb(GUSP(gus, TIMERDATA)));
+	printk(" -S- status read                  = 0x%x\n", inb(GUSP(gus, REGCNTRLS)));
+	printk(" -S- Sound Blaster control        = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL));
+	printk(" -S- AdLib timer 1/2              = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_ADLIB_TIMER_1), snd_gf1_i_look8(gus, SNDRV_GF1_GB_ADLIB_TIMER_2));
+	printk(" -S- reset                        = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET));
+	if (gus->interwave) {
+		printk(" -S- compatibility                = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_COMPATIBILITY));
+		printk(" -S- decode control               = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DECODE_CONTROL));
+		printk(" -S- version number               = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_VERSION_NUMBER));
+		printk(" -S- MPU-401 emul. control A/B    = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_MPU401_CONTROL_A), snd_gf1_i_look8(gus, SNDRV_GF1_GB_MPU401_CONTROL_B));
+		printk(" -S- emulation IRQ                = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_EMULATION_IRQ));
+	}
+}
+
+void snd_gf1_peek_print_block(snd_gus_card_t * gus, unsigned int addr, int count, int w_16bit)
+{
+	if (!w_16bit) {
+		while (count-- > 0)
+			printk(count > 0 ? "%02x:" : "%02x", snd_gf1_peek(gus, addr++));
+	} else {
+		while (count-- > 0) {
+			printk(count > 0 ? "%04x:" : "%04x", snd_gf1_peek(gus, addr) | (snd_gf1_peek(gus, addr + 1) << 8));
+			addr += 2;
+		}
+	}
+}
+
+#endif
diff --git a/sound/isa/gus/gus_irq.c b/sound/isa/gus/gus_irq.c
new file mode 100644
index 0000000..1e2a15e
--- /dev/null
+++ b/sound/isa/gus/gus_irq.c
@@ -0,0 +1,142 @@
+/*
+ *  Routine for IRQ handling from GF1/InterWave chip
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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 <sound/core.h>
+#include <sound/info.h>
+#include <sound/gus.h>
+
+#ifdef CONFIG_SND_DEBUG
+#define STAT_ADD(x)	((x)++)
+#else
+#define STAT_ADD(x)	while (0) { ; }
+#endif
+
+irqreturn_t snd_gus_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	snd_gus_card_t * gus = dev_id;
+	unsigned char status;
+	int loop = 100;
+	int handled = 0;
+
+__again:
+	status = inb(gus->gf1.reg_irqstat);
+	if (status == 0)
+		return IRQ_RETVAL(handled);
+	handled = 1;
+	// snd_printk("IRQ: status = 0x%x\n", status);
+	if (status & 0x02) {
+		STAT_ADD(gus->gf1.interrupt_stat_midi_in);
+		gus->gf1.interrupt_handler_midi_in(gus);
+	}
+	if (status & 0x01) {
+		STAT_ADD(gus->gf1.interrupt_stat_midi_out);
+		gus->gf1.interrupt_handler_midi_out(gus);
+	}
+	if (status & (0x20 | 0x40)) {
+		unsigned int already, _current_;
+		unsigned char voice_status, voice;
+		snd_gus_voice_t *pvoice;
+
+		already = 0;
+		while (((voice_status = snd_gf1_i_read8(gus, SNDRV_GF1_GB_VOICES_IRQ)) & 0xc0) != 0xc0) {
+			voice = voice_status & 0x1f;
+			_current_ = 1 << voice;
+			if (already & _current_)
+				continue;	/* multi request */
+			already |= _current_;	/* mark request */
+#if 0
+			printk("voice = %i, voice_status = 0x%x, voice_verify = %i\n", voice, voice_status, inb(GUSP(gus, GF1PAGE)));
+#endif
+			pvoice = &gus->gf1.voices[voice]; 
+			if (pvoice->use) {
+				if (!(voice_status & 0x80)) {	/* voice position IRQ */
+					STAT_ADD(pvoice->interrupt_stat_wave);
+					pvoice->handler_wave(gus, pvoice);
+				}
+				if (!(voice_status & 0x40)) {	/* volume ramp IRQ */
+					STAT_ADD(pvoice->interrupt_stat_volume);
+					pvoice->handler_volume(gus, pvoice);
+				}
+			} else {
+				STAT_ADD(gus->gf1.interrupt_stat_voice_lost);
+				snd_gf1_i_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL);
+				snd_gf1_i_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+			}
+		}
+	}
+	if (status & 0x04) {
+		STAT_ADD(gus->gf1.interrupt_stat_timer1);
+		gus->gf1.interrupt_handler_timer1(gus);
+	}
+	if (status & 0x08) {
+		STAT_ADD(gus->gf1.interrupt_stat_timer2);
+		gus->gf1.interrupt_handler_timer2(gus);
+	}
+	if (status & 0x80) {
+		if (snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL) & 0x40) {
+			STAT_ADD(gus->gf1.interrupt_stat_dma_write);
+			gus->gf1.interrupt_handler_dma_write(gus);
+		}
+		if (snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL) & 0x40) {
+			STAT_ADD(gus->gf1.interrupt_stat_dma_read);
+			gus->gf1.interrupt_handler_dma_read(gus);
+		}
+	}
+	if (--loop > 0)
+		goto __again;
+	return IRQ_NONE;
+}
+
+#ifdef CONFIG_SND_DEBUG
+static void snd_gus_irq_info_read(snd_info_entry_t *entry, 
+				  snd_info_buffer_t * buffer)
+{
+	snd_gus_card_t *gus;
+	snd_gus_voice_t *pvoice;
+	int idx;
+
+	gus = entry->private_data;
+	snd_iprintf(buffer, "midi out = %u\n", gus->gf1.interrupt_stat_midi_out);
+	snd_iprintf(buffer, "midi in = %u\n", gus->gf1.interrupt_stat_midi_in);
+	snd_iprintf(buffer, "timer1 = %u\n", gus->gf1.interrupt_stat_timer1);
+	snd_iprintf(buffer, "timer2 = %u\n", gus->gf1.interrupt_stat_timer2);
+	snd_iprintf(buffer, "dma write = %u\n", gus->gf1.interrupt_stat_dma_write);
+	snd_iprintf(buffer, "dma read = %u\n", gus->gf1.interrupt_stat_dma_read);
+	snd_iprintf(buffer, "voice lost = %u\n", gus->gf1.interrupt_stat_voice_lost);
+	for (idx = 0; idx < 32; idx++) {
+		pvoice = &gus->gf1.voices[idx];
+		snd_iprintf(buffer, "voice %i: wave = %u, volume = %u\n",
+					idx,
+					pvoice->interrupt_stat_wave,
+					pvoice->interrupt_stat_volume);
+	}
+}
+
+void snd_gus_irq_profile_init(snd_gus_card_t *gus)
+{
+	snd_info_entry_t *entry;
+
+	if (! snd_card_proc_new(gus->card, "gusirq", &entry))
+		snd_info_set_text_ops(entry, gus, 1024, snd_gus_irq_info_read);
+}
+
+#endif
diff --git a/sound/isa/gus/gus_main.c b/sound/isa/gus/gus_main.c
new file mode 100644
index 0000000..73f81c1
--- /dev/null
+++ b/sound/isa/gus/gus_main.c
@@ -0,0 +1,514 @@
+/*
+ *  Routines for Gravis UltraSound soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/control.h>
+
+#include <asm/dma.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Routines for Gravis UltraSound soundcards");
+MODULE_LICENSE("GPL");
+
+static int snd_gus_init_dma_irq(snd_gus_card_t * gus, int latches);
+
+int snd_gus_use_inc(snd_gus_card_t * gus)
+{
+	if (!try_module_get(gus->card->module))
+		return 0;
+	return 1;
+}
+
+void snd_gus_use_dec(snd_gus_card_t * gus)
+{
+	module_put(gus->card->module);
+}
+
+static int snd_gus_joystick_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 = 31;
+	return 0;
+}
+
+static int snd_gus_joystick_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_gus_card_t *gus = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = gus->joystick_dac & 31;
+	return 0;
+}
+
+static int snd_gus_joystick_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_gus_card_t *gus = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned char nval;
+	
+	nval = ucontrol->value.integer.value[0] & 31;
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	change = gus->joystick_dac != nval;
+	gus->joystick_dac = nval;
+	snd_gf1_write8(gus, SNDRV_GF1_GB_JOYSTICK_DAC_LEVEL, gus->joystick_dac);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_gus_joystick_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
+	.name = "Joystick Speed",
+	.info = snd_gus_joystick_info,
+	.get = snd_gus_joystick_get,
+	.put = snd_gus_joystick_put
+};
+
+static void snd_gus_init_control(snd_gus_card_t *gus)
+{
+	if (!gus->ace_flag)
+		snd_ctl_add(gus->card, snd_ctl_new1(&snd_gus_joystick_control, gus));
+}
+
+/*
+ *
+ */
+
+static int snd_gus_free(snd_gus_card_t *gus)
+{
+	if (gus->gf1.res_port2 == NULL)
+		goto __hw_end;
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+	if (gus->seq_dev) {
+		snd_device_free(gus->card, gus->seq_dev);
+		gus->seq_dev = NULL;
+	}
+#endif
+	snd_gf1_stop(gus);
+	snd_gus_init_dma_irq(gus, 0);
+      __hw_end:
+	if (gus->gf1.res_port1) {
+		release_resource(gus->gf1.res_port1);
+		kfree_nocheck(gus->gf1.res_port1);
+	}
+	if (gus->gf1.res_port2) {
+		release_resource(gus->gf1.res_port2);
+		kfree_nocheck(gus->gf1.res_port2);
+	}
+	if (gus->gf1.irq >= 0)
+		free_irq(gus->gf1.irq, (void *) gus);
+	if (gus->gf1.dma1 >= 0) {
+		disable_dma(gus->gf1.dma1);
+		free_dma(gus->gf1.dma1);
+	}
+	if (!gus->equal_dma && gus->gf1.dma2 >= 0) {
+		disable_dma(gus->gf1.dma2);
+		free_dma(gus->gf1.dma2);
+	}
+	kfree(gus);
+	return 0;
+}
+
+static int snd_gus_dev_free(snd_device_t *device)
+{
+	snd_gus_card_t *gus = device->device_data;
+	return snd_gus_free(gus);
+}
+
+int snd_gus_create(snd_card_t * card,
+		   unsigned long port,
+		   int irq, int dma1, int dma2,
+		   int timer_dev,
+		   int voices,
+		   int pcm_channels,
+		   int effect,
+		   snd_gus_card_t **rgus)
+{
+	snd_gus_card_t *gus;
+	int err;
+	static snd_device_ops_t ops = {
+		.dev_free =	snd_gus_dev_free,
+	};
+
+	*rgus = NULL;
+	gus = kcalloc(1, sizeof(*gus), GFP_KERNEL);
+	if (gus == NULL)
+		return -ENOMEM;
+	gus->gf1.irq = -1;
+	gus->gf1.dma1 = -1;
+	gus->gf1.dma2 = -1;
+	gus->card = card;
+	gus->gf1.port = port;
+	/* fill register variables for speedup */
+	gus->gf1.reg_page = GUSP(gus, GF1PAGE);
+	gus->gf1.reg_regsel = GUSP(gus, GF1REGSEL);
+	gus->gf1.reg_data8 = GUSP(gus, GF1DATAHIGH);
+	gus->gf1.reg_data16 = GUSP(gus, GF1DATALOW);
+	gus->gf1.reg_irqstat = GUSP(gus, IRQSTAT);
+	gus->gf1.reg_dram = GUSP(gus, DRAM);
+	gus->gf1.reg_timerctrl = GUSP(gus, TIMERCNTRL);
+	gus->gf1.reg_timerdata = GUSP(gus, TIMERDATA);
+	/* allocate resources */
+	if ((gus->gf1.res_port1 = request_region(port, 16, "GUS GF1 (Adlib/SB)")) == NULL) {
+		snd_printk(KERN_ERR "gus: can't grab SB port 0x%lx\n", port);
+		snd_gus_free(gus);
+		return -EBUSY;
+	}
+	if ((gus->gf1.res_port2 = request_region(port + 0x100, 12, "GUS GF1 (Synth)")) == NULL) {
+		snd_printk(KERN_ERR "gus: can't grab synth port 0x%lx\n", port + 0x100);
+		snd_gus_free(gus);
+		return -EBUSY;
+	}
+	if (irq >= 0 && request_irq(irq, snd_gus_interrupt, SA_INTERRUPT, "GUS GF1", (void *) gus)) {
+		snd_printk(KERN_ERR "gus: can't grab irq %d\n", irq);
+		snd_gus_free(gus);
+		return -EBUSY;
+	}
+	gus->gf1.irq = irq;
+	if (request_dma(dma1, "GUS - 1")) {
+		snd_printk(KERN_ERR "gus: can't grab DMA1 %d\n", dma1);
+		snd_gus_free(gus);
+		return -EBUSY;
+	}
+	gus->gf1.dma1 = dma1;
+	if (dma2 >= 0 && dma1 != dma2) {
+		if (request_dma(dma2, "GUS - 2")) {
+			snd_printk(KERN_ERR "gus: can't grab DMA2 %d\n", dma2);
+			snd_gus_free(gus);
+			return -EBUSY;
+		}
+		gus->gf1.dma2 = dma2;
+	} else {
+		gus->gf1.dma2 = gus->gf1.dma1;
+		gus->equal_dma = 1;
+	}
+	gus->timer_dev = timer_dev;
+	if (voices < 14)
+		voices = 14;
+	if (voices > 32)
+		voices = 32;
+	if (pcm_channels < 0)
+		pcm_channels = 0;
+	if (pcm_channels > 8)
+		pcm_channels = 8;
+	pcm_channels++;
+	pcm_channels &= ~1;
+	gus->gf1.effect = effect ? 1 : 0;
+	gus->gf1.active_voices = voices;
+	gus->gf1.pcm_channels = pcm_channels;
+	gus->gf1.volume_ramp = 25;
+	gus->gf1.smooth_pan = 1;
+	spin_lock_init(&gus->reg_lock);
+	spin_lock_init(&gus->voice_alloc);
+	spin_lock_init(&gus->active_voice_lock);
+	spin_lock_init(&gus->event_lock);
+	spin_lock_init(&gus->dma_lock);
+	spin_lock_init(&gus->pcm_volume_level_lock);
+	spin_lock_init(&gus->uart_cmd_lock);
+	init_MUTEX(&gus->dma_mutex);
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, gus, &ops)) < 0) {
+		snd_gus_free(gus);
+		return err;
+	}
+	*rgus = gus;
+	return 0;
+}
+
+/*
+ *  Memory detection routine for plain GF1 soundcards
+ */
+
+static int snd_gus_detect_memory(snd_gus_card_t * gus)
+{
+	int l, idx, local;
+	unsigned char d;
+
+	snd_gf1_poke(gus, 0L, 0xaa);
+	snd_gf1_poke(gus, 1L, 0x55);
+	if (snd_gf1_peek(gus, 0L) != 0xaa || snd_gf1_peek(gus, 1L) != 0x55) {
+		snd_printk("plain GF1 card at 0x%lx without onboard DRAM?\n", gus->gf1.port);
+		return -ENOMEM;
+	}
+	for (idx = 1, d = 0xab; idx < 4; idx++, d++) {
+		local = idx << 18;
+		snd_gf1_poke(gus, local, d);
+		snd_gf1_poke(gus, local + 1, d + 1);
+		if (snd_gf1_peek(gus, local) != d ||
+		    snd_gf1_peek(gus, local + 1) != d + 1 ||
+		    snd_gf1_peek(gus, 0L) != 0xaa)
+			break;
+	}
+#if 1
+	gus->gf1.memory = idx << 18;
+#else
+	gus->gf1.memory = 256 * 1024;
+#endif
+	for (l = 0, local = gus->gf1.memory; l < 4; l++, local -= 256 * 1024) {
+		gus->gf1.mem_alloc.banks_8[l].address =
+		    gus->gf1.mem_alloc.banks_8[l].size = 0;
+		gus->gf1.mem_alloc.banks_16[l].address = l << 18;
+		gus->gf1.mem_alloc.banks_16[l].size = local > 0 ? 256 * 1024 : 0;
+	}
+	gus->gf1.mem_alloc.banks_8[0].size = gus->gf1.memory;
+	return 0;		/* some memory were detected */
+}
+
+static int snd_gus_init_dma_irq(snd_gus_card_t * gus, int latches)
+{
+	snd_card_t *card;
+	unsigned long flags;
+	int irq, dma1, dma2;
+	static unsigned char irqs[16] =
+		{0, 0, 1, 3, 0, 2, 0, 4, 0, 1, 0, 5, 6, 0, 0, 7};
+	static unsigned char dmas[8] =
+		{6, 1, 0, 2, 0, 3, 4, 5};
+
+	snd_assert(gus != NULL, return -EINVAL);
+	card = gus->card;
+	snd_assert(card != NULL, return -EINVAL);
+
+	gus->mix_cntrl_reg &= 0xf8;
+	gus->mix_cntrl_reg |= 0x01;	/* disable MIC, LINE IN, enable LINE OUT */
+	if (gus->codec_flag || gus->ess_flag) {
+		gus->mix_cntrl_reg &= ~1;	/* enable LINE IN */
+		gus->mix_cntrl_reg |= 4;	/* enable MIC */
+	}
+	dma1 = gus->gf1.dma1;
+	dma1 = dma1 < 0 ? -dma1 : dma1;
+	dma1 = dmas[dma1 & 7];
+	dma2 = gus->gf1.dma2;
+	dma2 = dma2 < 0 ? -dma2 : dma2;
+	dma2 = dmas[dma2 & 7];
+#if 0
+	printk("dma1 = %i, dma2 = %i\n", gus->gf1.dma1, gus->gf1.dma2);
+#endif
+	dma1 |= gus->equal_dma ? 0x40 : (dma2 << 3);
+
+	if ((dma1 & 7) == 0 || (dma2 & 7) == 0) {
+		snd_printk("Error! DMA isn't defined.\n");
+		return -EINVAL;
+	}
+	irq = gus->gf1.irq;
+	irq = irq < 0 ? -irq : irq;
+	irq = irqs[irq & 0x0f];
+	if (irq == 0) {
+		snd_printk("Error! IRQ isn't defined.\n");
+		return -EINVAL;
+	}
+	irq |= 0x40;
+#if 0
+	card->mixer.mix_ctrl_reg |= 0x10;
+#endif
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(5, GUSP(gus, REGCNTRLS));
+	outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+	outb(0x00, GUSP(gus, IRQDMACNTRLREG));
+	outb(0, GUSP(gus, REGCNTRLS));
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+
+	udelay(100);
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(0x00 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+	outb(dma1, GUSP(gus, IRQDMACNTRLREG));
+	if (latches) {
+		outb(0x40 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+		outb(irq, GUSP(gus, IRQDMACNTRLREG));
+	}
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+
+	udelay(100);
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(0x00 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+	outb(dma1, GUSP(gus, IRQDMACNTRLREG));
+	if (latches) {
+		outb(0x40 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+		outb(irq, GUSP(gus, IRQDMACNTRLREG));
+	}
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+
+	snd_gf1_delay(gus);
+
+	if (latches)
+		gus->mix_cntrl_reg |= 0x08;	/* enable latches */
+	else
+		gus->mix_cntrl_reg &= ~0x08;	/* disable latches */
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+	outb(0, GUSP(gus, GF1PAGE));
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+
+	return 0;
+}
+
+static int snd_gus_check_version(snd_gus_card_t * gus)
+{
+	unsigned long flags;
+	unsigned char val, rev;
+	snd_card_t *card;
+
+	card = gus->card;
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(0x20, GUSP(gus, REGCNTRLS));
+	val = inb(GUSP(gus, REGCNTRLS));
+	rev = inb(GUSP(gus, BOARDVERSION));
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	snd_printdd("GF1 [0x%lx] init - val = 0x%x, rev = 0x%x\n", gus->gf1.port, val, rev);
+	strcpy(card->driver, "GUS");
+	strcpy(card->longname, "Gravis UltraSound Classic (2.4)");
+	if ((val != 255 && (val & 0x06)) || (rev >= 5 && rev != 255)) {
+		if (rev >= 5 && rev <= 9) {
+			gus->ics_flag = 1;
+			if (rev == 5)
+				gus->ics_flipped = 1;
+			card->longname[27] = '3';
+			card->longname[29] = rev == 5 ? '5' : '7';
+		}
+		if (rev >= 10 && rev != 255) {
+			if (rev >= 10 && rev <= 11) {
+				strcpy(card->driver, "GUS MAX");
+				strcpy(card->longname, "Gravis UltraSound MAX");
+				gus->max_flag = 1;
+			} else if (rev == 0x30) {
+				strcpy(card->driver, "GUS ACE");
+				strcpy(card->longname, "Gravis UltraSound Ace");
+				gus->ace_flag = 1;
+			} else if (rev == 0x50) {
+				strcpy(card->driver, "GUS Extreme");
+				strcpy(card->longname, "Gravis UltraSound Extreme");
+				gus->ess_flag = 1;
+			} else {
+				snd_printk("unknown GF1 revision number at 0x%lx - 0x%x (0x%x)\n", gus->gf1.port, rev, val);
+				snd_printk("  please - report to <perex@suse.cz>\n");
+			}
+		}
+	}
+	strcpy(card->shortname, card->longname);
+	gus->uart_enable = 1;	/* standard GUSes doesn't have midi uart trouble */
+	snd_gus_init_control(gus);
+	return 0;
+}
+
+static void snd_gus_seq_dev_free(snd_seq_device_t *seq_dev)
+{
+	snd_gus_card_t *gus = seq_dev->private_data;
+	gus->seq_dev = NULL;
+}
+
+int snd_gus_initialize(snd_gus_card_t *gus)
+{
+	int err;
+
+	if (!gus->interwave) {
+		if ((err = snd_gus_check_version(gus)) < 0) {
+			snd_printk("version check failed\n");
+			return err;
+		}
+		if ((err = snd_gus_detect_memory(gus)) < 0)
+			return err;
+	}
+	if ((err = snd_gus_init_dma_irq(gus, 1)) < 0)
+		return err;
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+	if (snd_seq_device_new(gus->card, 1, SNDRV_SEQ_DEV_ID_GUS,
+			       sizeof(snd_gus_card_t*), &gus->seq_dev) >= 0) {
+		strcpy(gus->seq_dev->name, "GUS");
+		*(snd_gus_card_t**)SNDRV_SEQ_DEVICE_ARGPTR(gus->seq_dev) = gus;
+		gus->seq_dev->private_data = gus;
+		gus->seq_dev->private_free = snd_gus_seq_dev_free;
+	}
+#endif
+	snd_gf1_start(gus);
+	gus->initialized = 1;
+	return 0;
+}
+
+  /* gus_io.c */
+EXPORT_SYMBOL(snd_gf1_delay);
+EXPORT_SYMBOL(snd_gf1_write8);
+EXPORT_SYMBOL(snd_gf1_look8);
+EXPORT_SYMBOL(snd_gf1_write16);
+EXPORT_SYMBOL(snd_gf1_look16);
+EXPORT_SYMBOL(snd_gf1_i_write8);
+EXPORT_SYMBOL(snd_gf1_i_look8);
+EXPORT_SYMBOL(snd_gf1_i_write16);
+EXPORT_SYMBOL(snd_gf1_i_look16);
+EXPORT_SYMBOL(snd_gf1_dram_addr);
+EXPORT_SYMBOL(snd_gf1_write_addr);
+EXPORT_SYMBOL(snd_gf1_poke);
+EXPORT_SYMBOL(snd_gf1_peek);
+  /* gus_reset.c */
+EXPORT_SYMBOL(snd_gf1_alloc_voice);
+EXPORT_SYMBOL(snd_gf1_free_voice);
+EXPORT_SYMBOL(snd_gf1_ctrl_stop);
+EXPORT_SYMBOL(snd_gf1_stop_voice);
+EXPORT_SYMBOL(snd_gf1_start);
+EXPORT_SYMBOL(snd_gf1_stop);
+  /* gus_mixer.c */
+EXPORT_SYMBOL(snd_gf1_new_mixer);
+  /* gus_pcm.c */
+EXPORT_SYMBOL(snd_gf1_pcm_new);
+  /* gus.c */
+EXPORT_SYMBOL(snd_gus_use_inc);
+EXPORT_SYMBOL(snd_gus_use_dec);
+EXPORT_SYMBOL(snd_gus_create);
+EXPORT_SYMBOL(snd_gus_initialize);
+  /* gus_irq.c */
+EXPORT_SYMBOL(snd_gus_interrupt);
+  /* gus_uart.c */
+EXPORT_SYMBOL(snd_gf1_rawmidi_new);
+  /* gus_dram.c */
+EXPORT_SYMBOL(snd_gus_dram_write);
+EXPORT_SYMBOL(snd_gus_dram_read);
+  /* gus_volume.c */
+EXPORT_SYMBOL(snd_gf1_lvol_to_gvol_raw);
+EXPORT_SYMBOL(snd_gf1_translate_freq);
+  /* gus_mem.c */
+EXPORT_SYMBOL(snd_gf1_mem_alloc);
+EXPORT_SYMBOL(snd_gf1_mem_xfree);
+EXPORT_SYMBOL(snd_gf1_mem_free);
+EXPORT_SYMBOL(snd_gf1_mem_lock);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_gus_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_gus_exit(void)
+{
+}
+
+module_init(alsa_gus_init)
+module_exit(alsa_gus_exit)
diff --git a/sound/isa/gus/gus_mem.c b/sound/isa/gus/gus_mem.c
new file mode 100644
index 0000000..bfc2b91
--- /dev/null
+++ b/sound/isa/gus/gus_mem.c
@@ -0,0 +1,353 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  GUS's memory allocation routines / bottom layer
+ *
+ *
+ *   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/slab.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/info.h>
+
+#ifdef CONFIG_SND_DEBUG
+static void snd_gf1_mem_info_read(snd_info_entry_t *entry, 
+				  snd_info_buffer_t * buffer);
+#endif
+
+void snd_gf1_mem_lock(snd_gf1_mem_t * alloc, int xup)
+{
+	if (!xup) {
+		down(&alloc->memory_mutex);
+	} else {
+		up(&alloc->memory_mutex);
+	}
+}
+
+snd_gf1_mem_block_t *snd_gf1_mem_xalloc(snd_gf1_mem_t * alloc,
+				        snd_gf1_mem_block_t * block)
+{
+	snd_gf1_mem_block_t *pblock, *nblock;
+
+	nblock = (snd_gf1_mem_block_t *) kmalloc(sizeof(snd_gf1_mem_block_t), GFP_KERNEL);
+	if (nblock == NULL)
+		return NULL;
+	*nblock = *block;
+	pblock = alloc->first;
+	while (pblock) {
+		if (pblock->ptr > nblock->ptr) {
+			nblock->prev = pblock->prev;
+			nblock->next = pblock;
+			pblock->prev = nblock;
+			if (pblock == alloc->first)
+				alloc->first = nblock;
+			else
+				nblock->prev->next = nblock;
+			up(&alloc->memory_mutex);
+			return NULL;
+		}
+		pblock = pblock->next;
+	}
+	nblock->next = NULL;
+	if (alloc->last == NULL) {
+		nblock->prev = NULL;
+		alloc->first = alloc->last = nblock;
+	} else {
+		nblock->prev = alloc->last;
+		alloc->last->next = nblock;
+		alloc->last = nblock;
+	}
+	return nblock;
+}
+
+int snd_gf1_mem_xfree(snd_gf1_mem_t * alloc, snd_gf1_mem_block_t * block)
+{
+	if (block->share) {	/* ok.. shared block */
+		block->share--;
+		up(&alloc->memory_mutex);
+		return 0;
+	}
+	if (alloc->first == block) {
+		alloc->first = block->next;
+		if (block->next)
+			block->next->prev = NULL;
+	} else {
+		block->prev->next = block->next;
+		if (block->next)
+			block->next->prev = block->prev;
+	}
+	if (alloc->last == block) {
+		alloc->last = block->prev;
+		if (block->prev)
+			block->prev->next = NULL;
+	} else {
+		block->next->prev = block->prev;
+		if (block->prev)
+			block->prev->next = block->next;
+	}
+	kfree(block->name);
+	kfree(block);
+	return 0;
+}
+
+snd_gf1_mem_block_t *snd_gf1_mem_look(snd_gf1_mem_t * alloc,
+				      unsigned int address)
+{
+	snd_gf1_mem_block_t *block;
+
+	for (block = alloc->first; block; block = block->next) {
+		if (block->ptr == address) {
+			return block;
+		}
+	}
+	return NULL;
+}
+
+snd_gf1_mem_block_t *snd_gf1_mem_share(snd_gf1_mem_t * alloc,
+				       unsigned int *share_id)
+{
+	snd_gf1_mem_block_t *block;
+
+	if (!share_id[0] && !share_id[1] &&
+	    !share_id[2] && !share_id[3])
+		return NULL;
+	for (block = alloc->first; block; block = block->next)
+		if (!memcmp(share_id, block->share_id, sizeof(share_id)))
+			return block;
+	return NULL;
+}
+
+static int snd_gf1_mem_find(snd_gf1_mem_t * alloc,
+			    snd_gf1_mem_block_t * block,
+			    unsigned int size, int w_16, int align)
+{
+	snd_gf1_bank_info_t *info = w_16 ? alloc->banks_16 : alloc->banks_8;
+	unsigned int idx, boundary;
+	int size1;
+	snd_gf1_mem_block_t *pblock;
+	unsigned int ptr1, ptr2;
+
+	align--;
+	if (w_16 && align < 1)
+		align = 1;
+	block->flags = w_16 ? SNDRV_GF1_MEM_BLOCK_16BIT : 0;
+	block->owner = SNDRV_GF1_MEM_OWNER_DRIVER;
+	block->share = 0;
+	block->share_id[0] = block->share_id[1] =
+	block->share_id[2] = block->share_id[3] = 0;
+	block->name = NULL;
+	block->prev = block->next = NULL;
+	for (pblock = alloc->first, idx = 0; pblock; pblock = pblock->next) {
+		while (pblock->ptr >= (boundary = info[idx].address + info[idx].size))
+			idx++;
+		while (pblock->ptr + pblock->size >= (boundary = info[idx].address + info[idx].size))
+			idx++;
+		ptr2 = boundary;
+		if (pblock->next) {
+			if (pblock->ptr + pblock->size == pblock->next->ptr)
+				continue;
+			if (pblock->next->ptr < boundary)
+				ptr2 = pblock->next->ptr;
+		}
+		ptr1 = (pblock->ptr + pblock->size + align) & ~align;
+		if (ptr1 >= ptr2)
+			continue;
+		size1 = ptr2 - ptr1;
+		if ((int)size <= size1) {
+			block->ptr = ptr1;
+			block->size = size;
+			return 0;
+		}
+	}
+	while (++idx < 4) {
+		if (size <= info[idx].size) {
+			/* I assume that bank address is already aligned.. */
+			block->ptr = info[idx].address;
+			block->size = size;
+			return 0;
+		}
+	}
+	return -ENOMEM;
+}
+
+snd_gf1_mem_block_t *snd_gf1_mem_alloc(snd_gf1_mem_t * alloc, int owner,
+				       char *name, int size, int w_16, int align,
+				       unsigned int *share_id)
+{
+	snd_gf1_mem_block_t block, *nblock;
+
+	snd_gf1_mem_lock(alloc, 0);
+	if (share_id != NULL) {
+		nblock = snd_gf1_mem_share(alloc, share_id);
+		if (nblock != NULL) {
+			if (size != (int)nblock->size) {
+				/* TODO: remove in the future */
+				snd_printk("snd_gf1_mem_alloc - share: sizes differ\n");
+				goto __std;
+			}
+			nblock->share++;
+			snd_gf1_mem_lock(alloc, 1);
+			return NULL;
+		}
+	}
+      __std:
+	if (snd_gf1_mem_find(alloc, &block, size, w_16, align) < 0) {
+		snd_gf1_mem_lock(alloc, 1);
+		return NULL;
+	}
+	if (share_id != NULL)
+		memcpy(&block.share_id, share_id, sizeof(block.share_id));
+	block.owner = owner;
+	block.name = snd_kmalloc_strdup(name, GFP_KERNEL);
+	nblock = snd_gf1_mem_xalloc(alloc, &block);
+	snd_gf1_mem_lock(alloc, 1);
+	return nblock;
+}
+
+int snd_gf1_mem_free(snd_gf1_mem_t * alloc, unsigned int address)
+{
+	int result;
+	snd_gf1_mem_block_t *block;
+
+	snd_gf1_mem_lock(alloc, 0);
+	if ((block = snd_gf1_mem_look(alloc, address)) != NULL) {
+		result = snd_gf1_mem_xfree(alloc, block);
+		snd_gf1_mem_lock(alloc, 1);
+		return result;
+	}
+	snd_gf1_mem_lock(alloc, 1);
+	return -EINVAL;
+}
+
+int snd_gf1_mem_init(snd_gus_card_t * gus)
+{
+	snd_gf1_mem_t *alloc;
+	snd_gf1_mem_block_t block;
+#ifdef CONFIG_SND_DEBUG
+	snd_info_entry_t *entry;
+#endif
+
+	alloc = &gus->gf1.mem_alloc;
+	init_MUTEX(&alloc->memory_mutex);
+	alloc->first = alloc->last = NULL;
+	if (!gus->gf1.memory)
+		return 0;
+
+	memset(&block, 0, sizeof(block));
+	block.owner = SNDRV_GF1_MEM_OWNER_DRIVER;
+	if (gus->gf1.enh_mode) {
+		block.ptr = 0;
+		block.size = 1024;
+		block.name = snd_kmalloc_strdup("InterWave LFOs", GFP_KERNEL);
+		if (snd_gf1_mem_xalloc(alloc, &block) == NULL)
+			return -ENOMEM;
+	}
+	block.ptr = gus->gf1.default_voice_address;
+	block.size = 4;
+	block.name = snd_kmalloc_strdup("Voice default (NULL's)", GFP_KERNEL);
+	if (snd_gf1_mem_xalloc(alloc, &block) == NULL)
+		return -ENOMEM;
+#ifdef CONFIG_SND_DEBUG
+	if (! snd_card_proc_new(gus->card, "gusmem", &entry)) {
+		snd_info_set_text_ops(entry, gus, 1024, snd_gf1_mem_info_read);
+		entry->c.text.read_size = 256 * 1024;
+	}
+#endif
+	return 0;
+}
+
+int snd_gf1_mem_done(snd_gus_card_t * gus)
+{
+	snd_gf1_mem_t *alloc;
+	snd_gf1_mem_block_t *block, *nblock;
+
+	alloc = &gus->gf1.mem_alloc;
+	block = alloc->first;
+	while (block) {
+		nblock = block->next;
+		snd_gf1_mem_xfree(alloc, block);
+		block = nblock;
+	}
+	return 0;
+}
+
+#ifdef CONFIG_SND_DEBUG
+static void snd_gf1_mem_info_read(snd_info_entry_t *entry, 
+				  snd_info_buffer_t * buffer)
+{
+	snd_gus_card_t *gus;
+	snd_gf1_mem_t *alloc;
+	snd_gf1_mem_block_t *block;
+	unsigned int total, used;
+	int i;
+
+	gus = entry->private_data;
+	alloc = &gus->gf1.mem_alloc;
+	down(&alloc->memory_mutex);
+	snd_iprintf(buffer, "8-bit banks       : \n    ");
+	for (i = 0; i < 4; i++)
+		snd_iprintf(buffer, "0x%06x (%04ik)%s", alloc->banks_8[i].address, alloc->banks_8[i].size >> 10, i + 1 < 4 ? "," : "");
+	snd_iprintf(buffer, "\n"
+		    "16-bit banks      : \n    ");
+	for (i = total = 0; i < 4; i++) {
+		snd_iprintf(buffer, "0x%06x (%04ik)%s", alloc->banks_16[i].address, alloc->banks_16[i].size >> 10, i + 1 < 4 ? "," : "");
+		total += alloc->banks_16[i].size;
+	}
+	snd_iprintf(buffer, "\n");
+	used = 0;
+	for (block = alloc->first, i = 0; block; block = block->next, i++) {
+		used += block->size;
+		snd_iprintf(buffer, "Block %i at 0x%lx onboard 0x%x size %i (0x%x):\n", i, (long) block, block->ptr, block->size, block->size);
+		if (block->share ||
+		    block->share_id[0] || block->share_id[1] ||
+		    block->share_id[2] || block->share_id[3])
+			snd_iprintf(buffer, "  Share           : %i [id0 0x%x] [id1 0x%x] [id2 0x%x] [id3 0x%x]\n",
+				block->share,
+				block->share_id[0], block->share_id[1],
+				block->share_id[2], block->share_id[3]);
+		snd_iprintf(buffer, "  Flags           :%s\n",
+		block->flags & SNDRV_GF1_MEM_BLOCK_16BIT ? " 16-bit" : "");
+		snd_iprintf(buffer, "  Owner           : ");
+		switch (block->owner) {
+		case SNDRV_GF1_MEM_OWNER_DRIVER:
+			snd_iprintf(buffer, "driver - %s\n", block->name);
+			break;
+		case SNDRV_GF1_MEM_OWNER_WAVE_SIMPLE:
+			snd_iprintf(buffer, "SIMPLE wave\n");
+			break;
+		case SNDRV_GF1_MEM_OWNER_WAVE_GF1:
+			snd_iprintf(buffer, "GF1 wave\n");
+			break;
+		case SNDRV_GF1_MEM_OWNER_WAVE_IWFFFF:
+			snd_iprintf(buffer, "IWFFFF wave\n");
+			break;
+		default:
+			snd_iprintf(buffer, "unknown\n");
+		}
+	}
+	snd_iprintf(buffer, "  Total: memory = %i, used = %i, free = %i\n",
+		    total, used, total - used);
+	up(&alloc->memory_mutex);
+#if 0
+	ultra_iprintf(buffer, "  Verify: free = %i, max 8-bit block = %i, max 16-bit block = %i\n",
+		      ultra_memory_free_size(card, &card->gf1.mem_alloc),
+		  ultra_memory_free_block(card, &card->gf1.mem_alloc, 0),
+		 ultra_memory_free_block(card, &card->gf1.mem_alloc, 1));
+#endif
+}
+#endif
diff --git a/sound/isa/gus/gus_mem_proc.c b/sound/isa/gus/gus_mem_proc.c
new file mode 100644
index 0000000..886763f
--- /dev/null
+++ b/sound/isa/gus/gus_mem_proc.c
@@ -0,0 +1,135 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  GUS's memory access via proc filesystem
+ *
+ *
+ *   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/slab.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/info.h>
+
+typedef struct gus_proc_private {
+	int rom;		/* data are in ROM */
+	unsigned int address;
+	unsigned int size;
+	snd_gus_card_t * gus;
+} gus_proc_private_t;
+
+static long snd_gf1_mem_proc_dump(snd_info_entry_t *entry, void *file_private_data,
+			          struct file *file, char __user *buf,
+			          unsigned long count, unsigned long pos)
+{
+	long size;
+	gus_proc_private_t *priv = entry->private_data;
+	snd_gus_card_t *gus = priv->gus;
+	int err;
+
+	size = count;
+	if (pos + size > priv->size)
+		size = (long)priv->size - pos;
+	if (size > 0) {
+		if ((err = snd_gus_dram_read(gus, buf, pos, size, priv->rom)) < 0)
+			return err;
+		return size;
+	}
+	return 0;
+}			
+
+static long long snd_gf1_mem_proc_llseek(snd_info_entry_t *entry,
+					void *private_file_data,
+					struct file *file,
+					long long offset,
+					int orig)
+{
+	gus_proc_private_t *priv = entry->private_data;
+
+	switch (orig) {
+	case 0:	/* SEEK_SET */
+		file->f_pos = offset;
+		break;
+	case 1:	/* SEEK_CUR */
+		file->f_pos += offset;
+		break;
+	case 2: /* SEEK_END, offset is negative */
+		file->f_pos = priv->size + offset;
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (file->f_pos > priv->size)
+		file->f_pos = priv->size;
+	return file->f_pos;
+}
+
+static void snd_gf1_mem_proc_free(snd_info_entry_t *entry)
+{
+	gus_proc_private_t *priv = entry->private_data;
+	kfree(priv);
+}
+
+static struct snd_info_entry_ops snd_gf1_mem_proc_ops = {
+	.read = snd_gf1_mem_proc_dump,
+	.llseek = snd_gf1_mem_proc_llseek,
+};
+
+int snd_gf1_mem_proc_init(snd_gus_card_t * gus)
+{
+	int idx;
+	char name[16];
+	gus_proc_private_t *priv;
+	snd_info_entry_t *entry;
+
+	for (idx = 0; idx < 4; idx++) {
+		if (gus->gf1.mem_alloc.banks_8[idx].size > 0) {
+			priv = kcalloc(1, sizeof(*priv), GFP_KERNEL);
+			if (priv == NULL)
+				return -ENOMEM;
+			priv->gus = gus;
+			sprintf(name, "gus-ram-%i", idx);
+			if (! snd_card_proc_new(gus->card, name, &entry)) {
+				entry->content = SNDRV_INFO_CONTENT_DATA;
+				entry->private_data = priv;
+				entry->private_free = snd_gf1_mem_proc_free;
+				entry->c.ops = &snd_gf1_mem_proc_ops;
+				priv->address = gus->gf1.mem_alloc.banks_8[idx].address;
+				priv->size = entry->size = gus->gf1.mem_alloc.banks_8[idx].size;
+			}
+		}
+	}
+	for (idx = 0; idx < 4; idx++) {
+		if (gus->gf1.rom_present & (1 << idx)) {
+			priv = kcalloc(1, sizeof(*priv), GFP_KERNEL);
+			if (priv == NULL)
+				return -ENOMEM;
+			priv->rom = 1;
+			priv->gus = gus;
+			sprintf(name, "gus-rom-%i", idx);
+			if (! snd_card_proc_new(gus->card, name, &entry)) {
+				entry->content = SNDRV_INFO_CONTENT_DATA;
+				entry->private_data = priv;
+				entry->private_free = snd_gf1_mem_proc_free;
+				entry->c.ops = &snd_gf1_mem_proc_ops;
+				priv->address = idx * 4096 * 1024;
+				priv->size = entry->size = gus->gf1.rom_memory;
+			}
+		}
+	}
+	return 0;
+}
diff --git a/sound/isa/gus/gus_mixer.c b/sound/isa/gus/gus_mixer.c
new file mode 100644
index 0000000..a051094
--- /dev/null
+++ b/sound/isa/gus/gus_mixer.c
@@ -0,0 +1,199 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  Routines for control of ICS 2101 chip and "mixer" in GF1 chip
+ *
+ *
+ *   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/time.h>
+#include <linux/wait.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/gus.h>
+
+/*
+ *
+ */
+
+#define GF1_SINGLE(xname, xindex, shift, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_gf1_info_single, \
+  .get = snd_gf1_get_single, .put = snd_gf1_put_single, \
+  .private_value = shift | (invert << 8) }
+
+static int snd_gf1_info_single(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_gf1_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_gus_card_t *gus = snd_kcontrol_chip(kcontrol);
+	int shift = kcontrol->private_value & 0xff;
+	int invert = (kcontrol->private_value >> 8) & 1;
+	
+	ucontrol->value.integer.value[0] = (gus->mix_cntrl_reg >> shift) & 1;
+	if (invert)
+		ucontrol->value.integer.value[0] ^= 1;
+	return 0;
+}
+
+static int snd_gf1_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_gus_card_t *gus = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int shift = kcontrol->private_value & 0xff;
+	int invert = (kcontrol->private_value >> 8) & 1;
+	int change;
+	unsigned char oval, nval;
+	
+	nval = ucontrol->value.integer.value[0] & 1;
+	if (invert)
+		nval ^= 1;
+	nval <<= shift;
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	oval = gus->mix_cntrl_reg;
+	nval = (oval & ~(1 << shift)) | nval;
+	change = nval != oval;
+	outb(gus->mix_cntrl_reg = nval, GUSP(gus, MIXCNTRLREG));
+	outb(gus->gf1.active_voice = 0, GUSP(gus, GF1PAGE));
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return change;
+}
+
+#define ICS_DOUBLE(xname, xindex, addr) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_ics_info_double, \
+  .get = snd_ics_get_double, .put = snd_ics_put_double, \
+  .private_value = addr }
+
+static int snd_ics_info_double(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 = 127;
+	return 0;
+}
+
+static int snd_ics_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_gus_card_t *gus = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int addr = kcontrol->private_value & 0xff;
+	unsigned char left, right;
+	
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	left = gus->gf1.ics_regs[addr][0];
+	right = gus->gf1.ics_regs[addr][1];
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	ucontrol->value.integer.value[0] = left & 127;
+	ucontrol->value.integer.value[1] = right & 127;
+	return 0;
+}
+
+static int snd_ics_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_gus_card_t *gus = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int addr = kcontrol->private_value & 0xff;
+	int change;
+	unsigned char val1, val2, oval1, oval2, tmp;
+	
+	val1 = ucontrol->value.integer.value[0] & 127;
+	val2 = ucontrol->value.integer.value[1] & 127;
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	oval1 = gus->gf1.ics_regs[addr][0];
+	oval2 = gus->gf1.ics_regs[addr][1];
+	change = val1 != oval1 || val2 != oval2;
+	gus->gf1.ics_regs[addr][0] = val1;
+	gus->gf1.ics_regs[addr][1] = val2;
+	if (gus->ics_flag && gus->ics_flipped &&
+	    (addr == SNDRV_ICS_GF1_DEV || addr == SNDRV_ICS_MASTER_DEV)) {
+		tmp = val1;
+		val1 = val2;
+		val2 = tmp;
+	}
+	addr <<= 3;
+	outb(addr | 0, GUSP(gus, MIXCNTRLPORT));
+	outb(1, GUSP(gus, MIXDATAPORT));
+	outb(addr | 2, GUSP(gus, MIXCNTRLPORT));
+	outb((unsigned char) val1, GUSP(gus, MIXDATAPORT));
+	outb(addr | 1, GUSP(gus, MIXCNTRLPORT));
+	outb(2, GUSP(gus, MIXDATAPORT));
+	outb(addr | 3, GUSP(gus, MIXCNTRLPORT));
+	outb((unsigned char) val2, GUSP(gus, MIXDATAPORT));
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_gf1_controls[] = {
+GF1_SINGLE("Master Playback Switch", 0, 1, 1),
+GF1_SINGLE("Line Switch", 0, 0, 1),
+GF1_SINGLE("Mic Switch", 0, 2, 0)
+};
+
+static snd_kcontrol_new_t snd_ics_controls[] = {
+GF1_SINGLE("Master Playback Switch", 0, 1, 1),
+ICS_DOUBLE("Master Playback Volume", 0, SNDRV_ICS_MASTER_DEV),
+ICS_DOUBLE("Synth Playback Volume", 0, SNDRV_ICS_GF1_DEV),
+GF1_SINGLE("Line Switch", 0, 0, 1),
+ICS_DOUBLE("Line Playback Volume", 0, SNDRV_ICS_LINE_DEV),
+GF1_SINGLE("Mic Switch", 0, 2, 0),
+ICS_DOUBLE("Mic Playback Volume", 0, SNDRV_ICS_MIC_DEV),
+ICS_DOUBLE("CD Playback Volume", 0, SNDRV_ICS_CD_DEV)
+};
+
+int snd_gf1_new_mixer(snd_gus_card_t * gus)
+{
+	snd_card_t *card;
+	unsigned int idx, max;
+	int err;
+
+	snd_assert(gus != NULL, return -EINVAL);
+	card = gus->card;
+	snd_assert(card != NULL, return -EINVAL);
+
+	if (gus->ics_flag)
+		snd_component_add(card, "ICS2101");
+	if (card->mixername[0] == '\0') {
+		strcpy(card->mixername, gus->ics_flag ? "GF1,ICS2101" : "GF1");
+	} else {
+		if (gus->ics_flag)
+			strcat(card->mixername, ",ICS2101");
+		strcat(card->mixername, ",GF1");
+	}
+
+	if (!gus->ics_flag) {
+		max = gus->ess_flag ? 1 : ARRAY_SIZE(snd_gf1_controls);
+		for (idx = 0; idx < max; idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_gf1_controls[idx], gus))) < 0)
+				return err;
+		}
+	} else {
+		for (idx = 0; idx < ARRAY_SIZE(snd_ics_controls); idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_ics_controls[idx], gus))) < 0)
+				return err;
+		}
+	}
+	return 0;
+}
diff --git a/sound/isa/gus/gus_pcm.c b/sound/isa/gus/gus_pcm.c
new file mode 100644
index 0000000..8995ad9
--- /dev/null
+++ b/sound/isa/gus/gus_pcm.c
@@ -0,0 +1,903 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  Routines for control of GF1 chip (PCM things)
+ *
+ *  InterWave chips supports interleaved DMA, but this feature isn't used in
+ *  this code.
+ *  
+ *  This code emulates autoinit DMA transfer for playback, recording by GF1
+ *  chip doesn't support autoinit DMA.
+ *
+ *
+ *   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/dma.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/gus.h>
+#include <sound/pcm_params.h>
+#include "gus_tables.h"
+
+/* maximum rate */
+
+#define SNDRV_GF1_PCM_RATE		48000
+
+#define SNDRV_GF1_PCM_PFLG_NONE		0
+#define SNDRV_GF1_PCM_PFLG_ACTIVE	(1<<0)
+#define SNDRV_GF1_PCM_PFLG_NEUTRAL	(2<<0)
+
+typedef struct {
+	snd_gus_card_t * gus;
+	snd_pcm_substream_t * substream;
+	spinlock_t lock;
+	unsigned int voices;
+	snd_gus_voice_t *pvoices[2];
+	unsigned int memory;
+	unsigned short flags;
+	unsigned char voice_ctrl, ramp_ctrl;
+	unsigned int bpos;
+	unsigned int blocks;
+	unsigned int block_size;
+	unsigned int dma_size;
+	wait_queue_head_t sleep;
+	atomic_t dma_count;
+	int final_volume;
+} gus_pcm_private_t;
+
+static int snd_gf1_pcm_use_dma = 1;
+
+static void snd_gf1_pcm_block_change_ack(snd_gus_card_t * gus, void *private_data)
+{
+	gus_pcm_private_t *pcmp = private_data;
+
+	if (pcmp) {
+		atomic_dec(&pcmp->dma_count);
+		wake_up(&pcmp->sleep);
+	}
+}
+
+static int snd_gf1_pcm_block_change(snd_pcm_substream_t * substream,
+				    unsigned int offset,
+				    unsigned int addr,
+				    unsigned int count)
+{
+	snd_gf1_dma_block_t block;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	gus_pcm_private_t *pcmp = runtime->private_data;
+
+	count += offset & 31;
+	offset &= ~31;
+	// snd_printk("block change - offset = 0x%x, count = 0x%x\n", offset, count);
+	memset(&block, 0, sizeof(block));
+	block.cmd = SNDRV_GF1_DMA_IRQ;
+	if (snd_pcm_format_unsigned(runtime->format))
+		block.cmd |= SNDRV_GF1_DMA_UNSIGNED;
+	if (snd_pcm_format_width(runtime->format) == 16)
+		block.cmd |= SNDRV_GF1_DMA_16BIT;
+	block.addr = addr & ~31;
+	block.buffer = runtime->dma_area + offset;
+	block.buf_addr = runtime->dma_addr + offset;
+	block.count = count;
+	block.private_data = pcmp;
+	block.ack = snd_gf1_pcm_block_change_ack;
+	if (!snd_gf1_dma_transfer_block(pcmp->gus, &block, 0, 0))
+		atomic_inc(&pcmp->dma_count);
+	return 0;
+}
+
+static void snd_gf1_pcm_trigger_up(snd_pcm_substream_t * substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	gus_pcm_private_t *pcmp = runtime->private_data;
+	snd_gus_card_t * gus = pcmp->gus;
+	unsigned long flags;
+	unsigned char voice_ctrl, ramp_ctrl;
+	unsigned short rate;
+	unsigned int curr, begin, end;
+	unsigned short vol;
+	unsigned char pan;
+	unsigned int voice;
+
+	if (substream == NULL)
+		return;
+	spin_lock_irqsave(&pcmp->lock, flags);
+	if (pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE) {
+		spin_unlock_irqrestore(&pcmp->lock, flags);
+		return;
+	}
+	pcmp->flags |= SNDRV_GF1_PCM_PFLG_ACTIVE;
+	pcmp->final_volume = 0;
+	spin_unlock_irqrestore(&pcmp->lock, flags);
+	rate = snd_gf1_translate_freq(gus, runtime->rate << 4);
+	/* enable WAVE IRQ */
+	voice_ctrl = snd_pcm_format_width(runtime->format) == 16 ? 0x24 : 0x20;
+	/* enable RAMP IRQ + rollover */
+	ramp_ctrl = 0x24;
+	if (pcmp->blocks == 1) {
+		voice_ctrl |= 0x08;	/* loop enable */
+		ramp_ctrl &= ~0x04;	/* disable rollover */
+	}
+	for (voice = 0; voice < pcmp->voices; voice++) {
+		begin = pcmp->memory + voice * (pcmp->dma_size / runtime->channels);
+		curr = begin + (pcmp->bpos * pcmp->block_size) / runtime->channels;
+		end = curr + (pcmp->block_size / runtime->channels);
+		end -= snd_pcm_format_width(runtime->format) == 16 ? 2 : 1;
+		// snd_printk("init: curr=0x%x, begin=0x%x, end=0x%x, ctrl=0x%x, ramp=0x%x, rate=0x%x\n", curr, begin, end, voice_ctrl, ramp_ctrl, rate);
+		pan = runtime->channels == 2 ? (!voice ? 1 : 14) : 8;
+		vol = !voice ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right;
+		spin_lock_irqsave(&gus->reg_lock, flags);
+		snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, pan);
+		snd_gf1_write16(gus, SNDRV_GF1_VW_FREQUENCY, rate);
+		snd_gf1_write_addr(gus, SNDRV_GF1_VA_START, begin << 4, voice_ctrl & 4);
+		snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, end << 4, voice_ctrl & 4);
+		snd_gf1_write_addr(gus, SNDRV_GF1_VA_CURRENT, curr << 4, voice_ctrl & 4);
+		snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, SNDRV_GF1_MIN_VOLUME << 4);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 0x2f);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, SNDRV_GF1_MIN_OFFSET);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, vol >> 8);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl);
+		if (!gus->gf1.enh_mode) {
+			snd_gf1_delay(gus);
+			snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl);
+		}
+		spin_unlock_irqrestore(&gus->reg_lock, flags);
+	}
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	for (voice = 0; voice < pcmp->voices; voice++) {
+		snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number);
+		if (gus->gf1.enh_mode)
+			snd_gf1_write8(gus, SNDRV_GF1_VB_MODE, 0x00);	/* deactivate voice */
+		snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
+		voice_ctrl &= ~0x20;
+	}
+	voice_ctrl |= 0x20;
+	if (!gus->gf1.enh_mode) {
+		snd_gf1_delay(gus);
+		for (voice = 0; voice < pcmp->voices; voice++) {
+			snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number);
+			snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
+			voice_ctrl &= ~0x20;	/* disable IRQ for next voice */
+		}
+	}
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+static void snd_gf1_pcm_interrupt_wave(snd_gus_card_t * gus, snd_gus_voice_t *pvoice)
+{
+	gus_pcm_private_t * pcmp;
+	snd_pcm_runtime_t * runtime;
+	unsigned char voice_ctrl, ramp_ctrl;
+	unsigned int idx;
+	unsigned int end, step;
+
+	if (!pvoice->private_data) {
+		snd_printd("snd_gf1_pcm: unknown wave irq?\n");
+		snd_gf1_smart_stop_voice(gus, pvoice->number);
+		return;
+	}
+	pcmp = pvoice->private_data;
+	if (pcmp == NULL) {
+		snd_printd("snd_gf1_pcm: unknown wave irq?\n");
+		snd_gf1_smart_stop_voice(gus, pvoice->number);
+		return;
+	}		
+	gus = pcmp->gus;
+	runtime = pcmp->substream->runtime;
+
+	spin_lock(&gus->reg_lock);
+	snd_gf1_select_voice(gus, pvoice->number);
+	voice_ctrl = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL) & ~0x8b;
+	ramp_ctrl = (snd_gf1_read8(gus, SNDRV_GF1_VB_VOLUME_CONTROL) & ~0xa4) | 0x03;
+#if 0
+	snd_gf1_select_voice(gus, pvoice->number);
+	printk("position = 0x%x\n", (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4));
+	snd_gf1_select_voice(gus, pcmp->pvoices[1]->number);
+	printk("position = 0x%x\n", (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4));
+	snd_gf1_select_voice(gus, pvoice->number);
+#endif
+	pcmp->bpos++;
+	pcmp->bpos %= pcmp->blocks;
+	if (pcmp->bpos + 1 >= pcmp->blocks) {	/* last block? */
+		voice_ctrl |= 0x08;	/* enable loop */
+	} else {
+		ramp_ctrl |= 0x04;	/* enable rollover */
+	}
+	end = pcmp->memory + (((pcmp->bpos + 1) * pcmp->block_size) / runtime->channels);
+	end -= voice_ctrl & 4 ? 2 : 1;
+	step = pcmp->dma_size / runtime->channels;
+	voice_ctrl |= 0x20;
+	if (!pcmp->final_volume) {
+		ramp_ctrl |= 0x20;
+		ramp_ctrl &= ~0x03;
+	}
+	for (idx = 0; idx < pcmp->voices; idx++, end += step) {
+		snd_gf1_select_voice(gus, pcmp->pvoices[idx]->number);
+		snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, end << 4, voice_ctrl & 4);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl);
+		voice_ctrl &= ~0x20;
+	}
+	if (!gus->gf1.enh_mode) {
+		snd_gf1_delay(gus);
+		voice_ctrl |= 0x20;
+		for (idx = 0; idx < pcmp->voices; idx++) {
+			snd_gf1_select_voice(gus, pcmp->pvoices[idx]->number);
+			snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
+			snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl);
+			voice_ctrl &= ~0x20;
+		}
+	}
+	spin_unlock(&gus->reg_lock);
+
+	snd_pcm_period_elapsed(pcmp->substream);
+#if 0
+	if ((runtime->flags & SNDRV_PCM_FLG_MMAP) &&
+	    *runtime->state == SNDRV_PCM_STATE_RUNNING) {
+		end = pcmp->bpos * pcmp->block_size;
+		if (runtime->channels > 1) {
+			snd_gf1_pcm_block_change(pcmp->substream, end, pcmp->memory + (end / 2), pcmp->block_size / 2);
+			snd_gf1_pcm_block_change(pcmp->substream, end + (pcmp->block_size / 2), pcmp->memory + (pcmp->dma_size / 2) + (end / 2), pcmp->block_size / 2);
+		} else {
+			snd_gf1_pcm_block_change(pcmp->substream, end, pcmp->memory + end, pcmp->block_size);
+		}
+	}
+#endif
+}
+
+static void snd_gf1_pcm_interrupt_volume(snd_gus_card_t * gus, snd_gus_voice_t * pvoice)
+{
+	unsigned short vol;
+	int cvoice;
+	gus_pcm_private_t *pcmp = pvoice->private_data;
+
+	/* stop ramp, but leave rollover bit untouched */
+	spin_lock(&gus->reg_lock);
+	snd_gf1_select_voice(gus, pvoice->number);
+	snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+	spin_unlock(&gus->reg_lock);
+	if (pcmp == NULL)
+		return;
+	/* are we active? */
+	if (!(pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE))
+		return;
+	/* load real volume - better precision */
+	cvoice = pcmp->pvoices[0] == pvoice ? 0 : 1;
+	if (pcmp->substream == NULL)
+		return;
+	vol = !cvoice ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right;
+	spin_lock(&gus->reg_lock);
+	snd_gf1_select_voice(gus, pvoice->number);
+	snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, vol);
+	pcmp->final_volume = 1;
+	spin_unlock(&gus->reg_lock);
+}
+
+static void snd_gf1_pcm_volume_change(snd_gus_card_t * gus)
+{
+}
+
+static int snd_gf1_pcm_poke_block(snd_gus_card_t *gus, unsigned char *buf,
+				  unsigned int pos, unsigned int count,
+				  int w16, int invert)
+{
+	unsigned int len;
+	unsigned long flags;
+
+	// printk("poke block; buf = 0x%x, pos = %i, count = %i, port = 0x%x\n", (int)buf, pos, count, gus->gf1.port);
+	while (count > 0) {
+		len = count;
+		if (len > 512)		/* limit, to allow IRQ */
+			len = 512;
+		count -= len;
+		if (gus->interwave) {
+			spin_lock_irqsave(&gus->reg_lock, flags);
+			snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01 | (invert ? 0x08 : 0x00));
+			snd_gf1_dram_addr(gus, pos);
+			if (w16) {
+				outb(SNDRV_GF1_GW_DRAM_IO16, GUSP(gus, GF1REGSEL));
+				outsw(GUSP(gus, GF1DATALOW), buf, len >> 1);
+			} else {
+				outsb(GUSP(gus, DRAM), buf, len);
+			}
+			spin_unlock_irqrestore(&gus->reg_lock, flags);
+			buf += 512;
+			pos += 512;
+		} else {
+			invert = invert ? 0x80 : 0x00;
+			if (w16) {
+				len >>= 1;
+				while (len--) {
+					snd_gf1_poke(gus, pos++, *buf++);
+					snd_gf1_poke(gus, pos++, *buf++ ^ invert);
+				}
+			} else {
+				while (len--)
+					snd_gf1_poke(gus, pos++, *buf++ ^ invert);
+			}
+		}
+		if (count > 0 && !in_interrupt()) {
+			set_current_state(TASK_INTERRUPTIBLE);
+			schedule_timeout(1);
+			if (signal_pending(current))
+				return -EAGAIN;
+		}
+	}
+	return 0;
+}
+
+static int snd_gf1_pcm_playback_copy(snd_pcm_substream_t *substream,
+				     int voice,
+				     snd_pcm_uframes_t pos,
+				     void __user *src,
+				     snd_pcm_uframes_t count)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	gus_pcm_private_t *pcmp = runtime->private_data;
+	unsigned int bpos, len;
+	
+	bpos = samples_to_bytes(runtime, pos) + (voice * (pcmp->dma_size / 2));
+	len = samples_to_bytes(runtime, count);
+	snd_assert(bpos <= pcmp->dma_size, return -EIO);
+	snd_assert(bpos + len <= pcmp->dma_size, return -EIO);
+	if (copy_from_user(runtime->dma_area + bpos, src, len))
+		return -EFAULT;
+	if (snd_gf1_pcm_use_dma && len > 32) {
+		return snd_gf1_pcm_block_change(substream, bpos, pcmp->memory + bpos, len);
+	} else {
+		snd_gus_card_t *gus = pcmp->gus;
+		int err, w16, invert;
+
+		w16 = (snd_pcm_format_width(runtime->format) == 16);
+		invert = snd_pcm_format_unsigned(runtime->format);
+		if ((err = snd_gf1_pcm_poke_block(gus, runtime->dma_area + bpos, pcmp->memory + bpos, len, w16, invert)) < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int snd_gf1_pcm_playback_silence(snd_pcm_substream_t *substream,
+					int voice,
+					snd_pcm_uframes_t pos,
+					snd_pcm_uframes_t count)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	gus_pcm_private_t *pcmp = runtime->private_data;
+	unsigned int bpos, len;
+	
+	bpos = samples_to_bytes(runtime, pos) + (voice * (pcmp->dma_size / 2));
+	len = samples_to_bytes(runtime, count);
+	snd_assert(bpos <= pcmp->dma_size, return -EIO);
+	snd_assert(bpos + len <= pcmp->dma_size, return -EIO);
+	snd_pcm_format_set_silence(runtime->format, runtime->dma_area + bpos, count);
+	if (snd_gf1_pcm_use_dma && len > 32) {
+		return snd_gf1_pcm_block_change(substream, bpos, pcmp->memory + bpos, len);
+	} else {
+		snd_gus_card_t *gus = pcmp->gus;
+		int err, w16, invert;
+
+		w16 = (snd_pcm_format_width(runtime->format) == 16);
+		invert = snd_pcm_format_unsigned(runtime->format);
+		if ((err = snd_gf1_pcm_poke_block(gus, runtime->dma_area + bpos, pcmp->memory + bpos, len, w16, invert)) < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int snd_gf1_pcm_playback_hw_params(snd_pcm_substream_t * substream,
+					  snd_pcm_hw_params_t * hw_params)
+{
+	snd_gus_card_t *gus = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	gus_pcm_private_t *pcmp = runtime->private_data;
+	int err;
+	
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+	if (err > 0) {	/* change */
+		snd_gf1_mem_block_t *block;
+		if (pcmp->memory > 0) {
+			snd_gf1_mem_free(&gus->gf1.mem_alloc, pcmp->memory);
+			pcmp->memory = 0;
+		}
+		if ((block = snd_gf1_mem_alloc(&gus->gf1.mem_alloc,
+		                               SNDRV_GF1_MEM_OWNER_DRIVER,
+					       "GF1 PCM",
+		                               runtime->dma_bytes, 1, 32,
+		                               NULL)) == NULL)
+			return -ENOMEM;
+		pcmp->memory = block->ptr;
+	}
+	pcmp->voices = params_channels(hw_params);
+	if (pcmp->pvoices[0] == NULL) {
+		if ((pcmp->pvoices[0] = snd_gf1_alloc_voice(pcmp->gus, SNDRV_GF1_VOICE_TYPE_PCM, 0, 0)) == NULL)
+			return -ENOMEM;
+		pcmp->pvoices[0]->handler_wave = snd_gf1_pcm_interrupt_wave;
+		pcmp->pvoices[0]->handler_volume = snd_gf1_pcm_interrupt_volume;
+		pcmp->pvoices[0]->volume_change = snd_gf1_pcm_volume_change;
+		pcmp->pvoices[0]->private_data = pcmp;
+	}
+	if (pcmp->voices > 1 && pcmp->pvoices[1] == NULL) {
+		if ((pcmp->pvoices[1] = snd_gf1_alloc_voice(pcmp->gus, SNDRV_GF1_VOICE_TYPE_PCM, 0, 0)) == NULL)
+			return -ENOMEM;
+		pcmp->pvoices[1]->handler_wave = snd_gf1_pcm_interrupt_wave;
+		pcmp->pvoices[1]->handler_volume = snd_gf1_pcm_interrupt_volume;
+		pcmp->pvoices[1]->volume_change = snd_gf1_pcm_volume_change;
+		pcmp->pvoices[1]->private_data = pcmp;
+	} else if (pcmp->voices == 1) {
+		if (pcmp->pvoices[1]) {
+			snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[1]);
+			pcmp->pvoices[1] = NULL;
+		}
+	}
+	return 0;
+}
+
+static int snd_gf1_pcm_playback_hw_free(snd_pcm_substream_t * substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	gus_pcm_private_t *pcmp = runtime->private_data;
+
+	snd_pcm_lib_free_pages(substream);
+	if (pcmp->pvoices[0]) {
+		snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[0]);
+		pcmp->pvoices[0] = NULL;
+	}
+	if (pcmp->pvoices[1]) {
+		snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[1]);
+		pcmp->pvoices[1] = NULL;
+	}
+	if (pcmp->memory > 0) {
+		snd_gf1_mem_free(&pcmp->gus->gf1.mem_alloc, pcmp->memory);
+		pcmp->memory = 0;
+	}
+	return 0;
+}
+
+static int snd_gf1_pcm_playback_prepare(snd_pcm_substream_t * substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	gus_pcm_private_t *pcmp = runtime->private_data;
+
+	pcmp->bpos = 0;
+	pcmp->dma_size = snd_pcm_lib_buffer_bytes(substream);
+	pcmp->block_size = snd_pcm_lib_period_bytes(substream);
+	pcmp->blocks = pcmp->dma_size / pcmp->block_size;
+	return 0;
+}
+
+static int snd_gf1_pcm_playback_trigger(snd_pcm_substream_t * substream,
+					int cmd)
+{
+	snd_gus_card_t *gus = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	gus_pcm_private_t *pcmp = runtime->private_data;
+	int voice;
+
+	if (cmd == SNDRV_PCM_TRIGGER_START) {
+		snd_gf1_pcm_trigger_up(substream);
+	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		spin_lock(&pcmp->lock);
+		pcmp->flags &= ~SNDRV_GF1_PCM_PFLG_ACTIVE;
+		spin_unlock(&pcmp->lock);
+		voice = pcmp->pvoices[0]->number;
+		snd_gf1_stop_voices(gus, voice, voice);
+		if (pcmp->pvoices[1]) {
+			voice = pcmp->pvoices[1]->number;
+			snd_gf1_stop_voices(gus, voice, voice);
+		}
+	} else {
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_gf1_pcm_playback_pointer(snd_pcm_substream_t * substream)
+{
+	snd_gus_card_t *gus = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	gus_pcm_private_t *pcmp = runtime->private_data;
+	unsigned int pos;
+	unsigned char voice_ctrl;
+
+	pos = 0;
+	spin_lock(&gus->reg_lock);
+	if (pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE) {
+		snd_gf1_select_voice(gus, pcmp->pvoices[0]->number);
+		voice_ctrl = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL);
+		pos = (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4) - pcmp->memory;
+		if (substream->runtime->channels > 1)
+			pos <<= 1;
+		pos = bytes_to_frames(runtime, pos);
+	}
+	spin_unlock(&gus->reg_lock);
+	return pos;
+}
+
+static ratnum_t clock = {
+	.num = 9878400/16,
+	.den_min = 2,
+	.den_max = 257,
+	.den_step = 1,
+};
+
+static snd_pcm_hw_constraint_ratnums_t hw_constraints_clocks  = {
+	.nrats = 1,
+	.rats = &clock,
+};
+
+static int snd_gf1_pcm_capture_hw_params(snd_pcm_substream_t * substream,
+					 snd_pcm_hw_params_t * hw_params)
+{
+	snd_gus_card_t *gus = snd_pcm_substream_chip(substream);
+
+	gus->c_dma_size = params_buffer_bytes(hw_params);
+	gus->c_period_size = params_period_bytes(hw_params);
+	gus->c_pos = 0;
+	gus->gf1.pcm_rcntrl_reg = 0x21;		/* IRQ at end, enable & start */
+	if (params_channels(hw_params) > 1)
+		gus->gf1.pcm_rcntrl_reg |= 2;
+	if (gus->gf1.dma2 > 3)
+		gus->gf1.pcm_rcntrl_reg |= 4;
+	if (snd_pcm_format_unsigned(params_format(hw_params)))
+		gus->gf1.pcm_rcntrl_reg |= 0x80;
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_gf1_pcm_capture_hw_free(snd_pcm_substream_t * substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_gf1_pcm_capture_prepare(snd_pcm_substream_t * substream)
+{
+	snd_gus_card_t *gus = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RECORD_RATE, runtime->rate_den - 2);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, 0);	/* disable sampling */
+	snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL);	/* Sampling Control Register */
+	snd_dma_program(gus->gf1.dma2, runtime->dma_addr, gus->c_period_size, DMA_MODE_READ);
+	return 0;
+}
+
+static int snd_gf1_pcm_capture_trigger(snd_pcm_substream_t * substream,
+				       int cmd)
+{
+	snd_gus_card_t *gus = snd_pcm_substream_chip(substream);
+	int val;
+	
+	if (cmd == SNDRV_PCM_TRIGGER_START) {
+		val = gus->gf1.pcm_rcntrl_reg;
+	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		val = 0;
+	} else {
+		return -EINVAL;
+	}
+
+	spin_lock(&gus->reg_lock);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, val);
+	snd_gf1_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL);
+	spin_unlock(&gus->reg_lock);
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_gf1_pcm_capture_pointer(snd_pcm_substream_t * substream)
+{
+	snd_gus_card_t *gus = snd_pcm_substream_chip(substream);
+	int pos = snd_dma_pointer(gus->gf1.dma2, gus->c_period_size);
+	pos = bytes_to_frames(substream->runtime, (gus->c_pos + pos) % gus->c_dma_size);
+	return pos;
+}
+
+static void snd_gf1_pcm_interrupt_dma_read(snd_gus_card_t * gus)
+{
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, 0);	/* disable sampling */
+	snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL);	/* Sampling Control Register */
+	if (gus->pcm_cap_substream != NULL) {
+		snd_gf1_pcm_capture_prepare(gus->pcm_cap_substream); 
+		snd_gf1_pcm_capture_trigger(gus->pcm_cap_substream, SNDRV_PCM_TRIGGER_START);
+		gus->c_pos += gus->c_period_size;
+		snd_pcm_period_elapsed(gus->pcm_cap_substream);
+	}
+}
+
+static snd_pcm_hardware_t snd_gf1_pcm_playback =
+{
+	.info =			SNDRV_PCM_INFO_NONINTERLEAVED,
+	.formats		= (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
+				 SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE),
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5510,
+	.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,
+};
+
+static snd_pcm_hardware_t snd_gf1_pcm_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100,
+	.rate_min =		5510,
+	.rate_max =		44100,
+	.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,
+};
+
+static void snd_gf1_pcm_playback_free(snd_pcm_runtime_t *runtime)
+{
+	gus_pcm_private_t * pcmp = runtime->private_data;
+	kfree(pcmp);
+}
+
+static int snd_gf1_pcm_playback_open(snd_pcm_substream_t *substream)
+{
+	gus_pcm_private_t *pcmp;
+	snd_gus_card_t *gus = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int err;
+
+	pcmp = kcalloc(1, sizeof(*pcmp), GFP_KERNEL);
+	if (pcmp == NULL)
+		return -ENOMEM;
+	pcmp->gus = gus;
+	spin_lock_init(&pcmp->lock);
+	init_waitqueue_head(&pcmp->sleep);
+	atomic_set(&pcmp->dma_count, 0);
+
+	runtime->private_data = pcmp;
+	runtime->private_free = snd_gf1_pcm_playback_free;
+
+#if 0
+	printk("playback.buffer = 0x%lx, gf1.pcm_buffer = 0x%lx\n", (long) pcm->playback.buffer, (long) gus->gf1.pcm_buffer);
+#endif
+	if ((err = snd_gf1_dma_init(gus)) < 0)
+		return err;
+	pcmp->flags = SNDRV_GF1_PCM_PFLG_NONE;
+	pcmp->substream = substream;
+	runtime->hw = snd_gf1_pcm_playback;
+	snd_pcm_limit_isa_dma_size(gus->gf1.dma1, &runtime->hw.buffer_bytes_max);
+	snd_pcm_limit_isa_dma_size(gus->gf1.dma1, &runtime->hw.period_bytes_max);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64);
+	return 0;
+}
+
+static int snd_gf1_pcm_playback_close(snd_pcm_substream_t * substream)
+{
+	snd_gus_card_t *gus = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	gus_pcm_private_t *pcmp = runtime->private_data;
+	
+	if (!wait_event_timeout(pcmp->sleep, (atomic_read(&pcmp->dma_count) <= 0), 2*HZ))
+		snd_printk("gf1 pcm - serious DMA problem\n");
+
+	snd_gf1_dma_done(gus);	
+	return 0;
+}
+
+static int snd_gf1_pcm_capture_open(snd_pcm_substream_t * substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_gus_card_t *gus = snd_pcm_substream_chip(substream);
+
+	gus->gf1.interrupt_handler_dma_read = snd_gf1_pcm_interrupt_dma_read;
+	gus->pcm_cap_substream = substream;
+	substream->runtime->hw = snd_gf1_pcm_capture;
+	snd_pcm_limit_isa_dma_size(gus->gf1.dma2, &runtime->hw.buffer_bytes_max);
+	snd_pcm_limit_isa_dma_size(gus->gf1.dma2, &runtime->hw.period_bytes_max);
+	snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      &hw_constraints_clocks);
+	return 0;
+}
+
+static int snd_gf1_pcm_capture_close(snd_pcm_substream_t * substream)
+{
+	snd_gus_card_t *gus = snd_pcm_substream_chip(substream);
+
+	gus->pcm_cap_substream = NULL;
+	snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_DMA_READ);
+	return 0;
+}
+
+static void snd_gf1_pcm_free(snd_pcm_t *pcm)
+{
+	snd_gus_card_t *gus = pcm->private_data;
+	gus->pcm = NULL;
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int snd_gf1_pcm_volume_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 = 127;
+	return 0;
+}
+
+static int snd_gf1_pcm_volume_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_gus_card_t *gus = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&gus->pcm_volume_level_lock, flags);
+	ucontrol->value.integer.value[0] = gus->gf1.pcm_volume_level_left1;
+	ucontrol->value.integer.value[1] = gus->gf1.pcm_volume_level_right1;
+	spin_unlock_irqrestore(&gus->pcm_volume_level_lock, flags);
+	return 0;
+}
+
+static int snd_gf1_pcm_volume_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_gus_card_t *gus = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned int idx;
+	unsigned short val1, val2, vol;
+	gus_pcm_private_t *pcmp;
+	snd_gus_voice_t *pvoice;
+	
+	val1 = ucontrol->value.integer.value[0] & 127;
+	val2 = ucontrol->value.integer.value[1] & 127;
+	spin_lock_irqsave(&gus->pcm_volume_level_lock, flags);
+	change = val1 != gus->gf1.pcm_volume_level_left1 ||
+	         val2 != gus->gf1.pcm_volume_level_right1;
+	gus->gf1.pcm_volume_level_left1 = val1;
+	gus->gf1.pcm_volume_level_right1 = val2;
+	gus->gf1.pcm_volume_level_left = snd_gf1_lvol_to_gvol_raw(val1 << 9) << 4;
+	gus->gf1.pcm_volume_level_right = snd_gf1_lvol_to_gvol_raw(val2 << 9) << 4;
+	spin_unlock_irqrestore(&gus->pcm_volume_level_lock, flags);
+	/* are we active? */
+	spin_lock_irqsave(&gus->voice_alloc, flags);
+	for (idx = 0; idx < 32; idx++) {
+		pvoice = &gus->gf1.voices[idx];
+		if (!pvoice->pcm)
+			continue;
+		pcmp = pvoice->private_data;
+		if (!(pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE))
+			continue;
+		/* load real volume - better precision */
+		spin_lock_irqsave(&gus->reg_lock, flags);
+		snd_gf1_select_voice(gus, pvoice->number);
+		snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+		vol = pvoice == pcmp->pvoices[0] ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right;
+		snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, vol);
+		pcmp->final_volume = 1;
+		spin_unlock_irqrestore(&gus->reg_lock, flags);
+	}
+	spin_unlock_irqrestore(&gus->voice_alloc, flags);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_gf1_pcm_volume_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "PCM Playback Volume",
+	.info = snd_gf1_pcm_volume_info,
+	.get = snd_gf1_pcm_volume_get,
+	.put = snd_gf1_pcm_volume_put
+};
+
+static snd_kcontrol_new_t snd_gf1_pcm_volume_control1 =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "GPCM Playback Volume",
+	.info = snd_gf1_pcm_volume_info,
+	.get = snd_gf1_pcm_volume_get,
+	.put = snd_gf1_pcm_volume_put
+};
+
+static snd_pcm_ops_t snd_gf1_pcm_playback_ops = {
+	.open =		snd_gf1_pcm_playback_open,
+	.close =	snd_gf1_pcm_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_gf1_pcm_playback_hw_params,
+	.hw_free =	snd_gf1_pcm_playback_hw_free,
+	.prepare =	snd_gf1_pcm_playback_prepare,
+	.trigger =	snd_gf1_pcm_playback_trigger,
+	.pointer =	snd_gf1_pcm_playback_pointer,
+	.copy =		snd_gf1_pcm_playback_copy,
+	.silence =	snd_gf1_pcm_playback_silence,
+};
+
+static snd_pcm_ops_t snd_gf1_pcm_capture_ops = {
+	.open =		snd_gf1_pcm_capture_open,
+	.close =	snd_gf1_pcm_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_gf1_pcm_capture_hw_params,
+	.hw_free =	snd_gf1_pcm_capture_hw_free,
+	.prepare =	snd_gf1_pcm_capture_prepare,
+	.trigger =	snd_gf1_pcm_capture_trigger,
+	.pointer =	snd_gf1_pcm_capture_pointer,
+};
+
+int snd_gf1_pcm_new(snd_gus_card_t * gus, int pcm_dev, int control_index, snd_pcm_t ** rpcm)
+{
+	snd_card_t *card;
+	snd_kcontrol_t *kctl;
+	snd_pcm_t *pcm;
+	snd_pcm_substream_t *substream;
+	int capture, err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	card = gus->card;
+	capture = !gus->interwave && !gus->ess_flag && !gus->ace_flag ? 1 : 0;
+	err = snd_pcm_new(card,
+			  gus->interwave ? "AMD InterWave" : "GF1",
+			  pcm_dev,
+			  gus->gf1.pcm_channels / 2,
+			  capture,
+			  &pcm);
+	if (err < 0)
+		return err;
+	pcm->private_data = gus;
+	pcm->private_free = snd_gf1_pcm_free;
+	/* playback setup */
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_gf1_pcm_playback_ops);
+
+	for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next)
+		snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, gus->gf1.dma1 > 3 ? 128*1024 : 64*1024);
+	
+	pcm->info_flags = 0;
+	pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+	if (capture) {
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_gf1_pcm_capture_ops);
+		if (gus->gf1.dma2 == gus->gf1.dma1)
+			pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX;
+		snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
+					      SNDRV_DMA_TYPE_DEV, snd_dma_isa_data(),
+					      64*1024, gus->gf1.dma2 > 3 ? 128*1024 : 64*1024);
+	}
+	strcpy(pcm->name, pcm->id);
+	if (gus->interwave) {
+		sprintf(pcm->name + strlen(pcm->name), " rev %c", gus->revision + 'A');
+	}
+	strcat(pcm->name, " (synth)");
+	gus->pcm = pcm;
+
+	if (gus->codec_flag)
+		kctl = snd_ctl_new1(&snd_gf1_pcm_volume_control1, gus);
+	else
+		kctl = snd_ctl_new1(&snd_gf1_pcm_volume_control, gus);
+	if ((err = snd_ctl_add(card, kctl)) < 0)
+		return err;
+	kctl->id.index = control_index;
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
diff --git a/sound/isa/gus/gus_reset.c b/sound/isa/gus/gus_reset.c
new file mode 100644
index 0000000..b4e66f6
--- /dev/null
+++ b/sound/isa/gus/gus_reset.c
@@ -0,0 +1,413 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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/delay.h>
+#include <linux/interrupt.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+extern void snd_gf1_timers_init(snd_gus_card_t * gus);
+extern void snd_gf1_timers_done(snd_gus_card_t * gus);
+extern int snd_gf1_synth_init(snd_gus_card_t * gus);
+extern void snd_gf1_synth_done(snd_gus_card_t * gus);
+
+/*
+ *  ok.. default interrupt handlers...
+ */
+
+static void snd_gf1_default_interrupt_handler_midi_out(snd_gus_card_t * gus)
+{
+	snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd &= ~0x20);
+}
+
+static void snd_gf1_default_interrupt_handler_midi_in(snd_gus_card_t * gus)
+{
+	snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd &= ~0x80);
+}
+
+static void snd_gf1_default_interrupt_handler_timer1(snd_gus_card_t * gus)
+{
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, gus->gf1.timer_enabled &= ~4);
+}
+
+static void snd_gf1_default_interrupt_handler_timer2(snd_gus_card_t * gus)
+{
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, gus->gf1.timer_enabled &= ~8);
+}
+
+static void snd_gf1_default_interrupt_handler_wave_and_volume(snd_gus_card_t * gus, snd_gus_voice_t * voice)
+{
+	snd_gf1_i_ctrl_stop(gus, 0x00);
+	snd_gf1_i_ctrl_stop(gus, 0x0d);
+}
+
+static void snd_gf1_default_interrupt_handler_dma_write(snd_gus_card_t * gus)
+{
+	snd_gf1_i_write8(gus, 0x41, 0x00);
+}
+
+static void snd_gf1_default_interrupt_handler_dma_read(snd_gus_card_t * gus)
+{
+	snd_gf1_i_write8(gus, 0x49, 0x00);
+}
+
+void snd_gf1_set_default_handlers(snd_gus_card_t * gus, unsigned int what)
+{
+	if (what & SNDRV_GF1_HANDLER_MIDI_OUT)
+		gus->gf1.interrupt_handler_midi_out = snd_gf1_default_interrupt_handler_midi_out;
+	if (what & SNDRV_GF1_HANDLER_MIDI_IN)
+		gus->gf1.interrupt_handler_midi_in = snd_gf1_default_interrupt_handler_midi_in;
+	if (what & SNDRV_GF1_HANDLER_TIMER1)
+		gus->gf1.interrupt_handler_timer1 = snd_gf1_default_interrupt_handler_timer1;
+	if (what & SNDRV_GF1_HANDLER_TIMER2)
+		gus->gf1.interrupt_handler_timer2 = snd_gf1_default_interrupt_handler_timer2;
+	if (what & SNDRV_GF1_HANDLER_VOICE) {
+		snd_gus_voice_t *voice;
+		
+		voice = &gus->gf1.voices[what & 0xffff];
+		voice->handler_wave =
+		voice->handler_volume = snd_gf1_default_interrupt_handler_wave_and_volume;
+		voice->handler_effect = NULL;
+		voice->volume_change = NULL;
+	}
+	if (what & SNDRV_GF1_HANDLER_DMA_WRITE)
+		gus->gf1.interrupt_handler_dma_write = snd_gf1_default_interrupt_handler_dma_write;
+	if (what & SNDRV_GF1_HANDLER_DMA_READ)
+		gus->gf1.interrupt_handler_dma_read = snd_gf1_default_interrupt_handler_dma_read;
+}
+
+/*
+
+ */
+
+static void snd_gf1_clear_regs(snd_gus_card_t * gus)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	inb(GUSP(gus, IRQSTAT));
+	snd_gf1_write8(gus, 0x41, 0);	/* DRAM DMA Control Register */
+	snd_gf1_write8(gus, 0x45, 0);	/* Timer Control */
+	snd_gf1_write8(gus, 0x49, 0);	/* Sampling Control Register */
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+static void snd_gf1_look_regs(snd_gus_card_t * gus)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	snd_gf1_look8(gus, 0x41);	/* DRAM DMA Control Register */
+	snd_gf1_look8(gus, 0x49);	/* Sampling Control Register */
+	inb(GUSP(gus, IRQSTAT));
+	snd_gf1_read8(gus, 0x0f);	/* IRQ Source Register */
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+/*
+ *  put selected GF1 voices to initial stage...
+ */
+
+void snd_gf1_smart_stop_voice(snd_gus_card_t * gus, unsigned short voice)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	snd_gf1_select_voice(gus, voice);
+#if 0
+	printk(" -%i- smart stop voice - volume = 0x%x\n", voice, snd_gf1_i_read16(gus, SNDRV_GF1_VW_VOLUME));
+#endif
+	snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL);
+	snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+void snd_gf1_stop_voice(snd_gus_card_t * gus, unsigned short voice)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	snd_gf1_select_voice(gus, voice);
+#if 0
+	printk(" -%i- stop voice - volume = 0x%x\n", voice, snd_gf1_i_read16(gus, SNDRV_GF1_VW_VOLUME));
+#endif
+	snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL);
+	snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+	if (gus->gf1.enh_mode)
+		snd_gf1_write8(gus, SNDRV_GF1_VB_ACCUMULATOR, 0);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+#if 0
+	snd_gf1_lfo_shutdown(gus, voice, ULTRA_LFO_VIBRATO);
+	snd_gf1_lfo_shutdown(gus, voice, ULTRA_LFO_TREMOLO);
+#endif
+}
+
+void snd_gf1_clear_voices(snd_gus_card_t * gus, unsigned short v_min, unsigned short v_max)
+{
+	unsigned long flags;
+	unsigned int daddr;
+	unsigned short i, w_16;
+
+	daddr = gus->gf1.default_voice_address << 4;
+	for (i = v_min; i <= v_max; i++) {
+#if 0
+		if (gus->gf1.syn_voices)
+			gus->gf1.syn_voices[i].flags = ~VFLG_DYNAMIC;
+#endif
+		spin_lock_irqsave(&gus->reg_lock, flags);
+		snd_gf1_select_voice(gus, i);
+		snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL);	/* Voice Control Register = voice stop */
+		snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);	/* Volume Ramp Control Register = ramp off */
+		if (gus->gf1.enh_mode)
+			snd_gf1_write8(gus, SNDRV_GF1_VB_MODE, gus->gf1.memory ? 0x02 : 0x82);	/* Deactivate voice */
+		w_16 = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL) & 0x04;
+		snd_gf1_write16(gus, SNDRV_GF1_VW_FREQUENCY, 0x400);
+		snd_gf1_write_addr(gus, SNDRV_GF1_VA_START, daddr, w_16);
+		snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, daddr, w_16);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, 0);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, 0);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 0);
+		snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, 0);
+		snd_gf1_write_addr(gus, SNDRV_GF1_VA_CURRENT, daddr, w_16);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, 7);
+		if (gus->gf1.enh_mode) {
+			snd_gf1_write8(gus, SNDRV_GF1_VB_ACCUMULATOR, 0);
+			snd_gf1_write16(gus, SNDRV_GF1_VW_EFFECT_VOLUME, 0);
+			snd_gf1_write16(gus, SNDRV_GF1_VW_EFFECT_VOLUME_FINAL, 0);
+		}
+		spin_unlock_irqrestore(&gus->reg_lock, flags);
+#if 0
+		snd_gf1_lfo_shutdown(gus, i, ULTRA_LFO_VIBRATO);
+		snd_gf1_lfo_shutdown(gus, i, ULTRA_LFO_TREMOLO);
+#endif
+	}
+}
+
+void snd_gf1_stop_voices(snd_gus_card_t * gus, unsigned short v_min, unsigned short v_max)
+{
+	unsigned long flags;
+	short i, ramp_ok;
+	unsigned short ramp_end;
+
+	if (!in_interrupt()) {	/* this can't be done in interrupt */
+		for (i = v_min, ramp_ok = 0; i <= v_max; i++) {
+			spin_lock_irqsave(&gus->reg_lock, flags);
+			snd_gf1_select_voice(gus, i);
+			ramp_end = snd_gf1_read16(gus, 9) >> 8;
+			if (ramp_end > SNDRV_GF1_MIN_OFFSET) {
+				ramp_ok++;
+				snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 20);	/* ramp rate */
+				snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, SNDRV_GF1_MIN_OFFSET);	/* ramp start */
+				snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, ramp_end);	/* ramp end */
+				snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, 0x40);	/* ramp down */
+				if (gus->gf1.enh_mode) {
+					snd_gf1_delay(gus);
+					snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, 0x40);
+				}
+			}
+			spin_unlock_irqrestore(&gus->reg_lock, flags);
+		}
+		msleep_interruptible(50);
+	}
+	snd_gf1_clear_voices(gus, v_min, v_max);
+}
+
+static void snd_gf1_alloc_voice_use(snd_gus_card_t * gus, 
+				    snd_gus_voice_t * pvoice,
+				    int type, int client, int port)
+{
+	pvoice->use = 1;
+	switch (type) {
+	case SNDRV_GF1_VOICE_TYPE_PCM:
+		gus->gf1.pcm_alloc_voices++;
+		pvoice->pcm = 1;
+		break;
+	case SNDRV_GF1_VOICE_TYPE_SYNTH:
+		pvoice->synth = 1;
+		pvoice->client = client;
+		pvoice->port = port;
+		break;
+	case SNDRV_GF1_VOICE_TYPE_MIDI:
+		pvoice->midi = 1;
+		pvoice->client = client;
+		pvoice->port = port;
+		break;
+	}
+}
+
+snd_gus_voice_t *snd_gf1_alloc_voice(snd_gus_card_t * gus, int type, int client, int port)
+{
+	snd_gus_voice_t *pvoice;
+	unsigned long flags;
+	int idx;
+
+	spin_lock_irqsave(&gus->voice_alloc, flags);
+	if (type == SNDRV_GF1_VOICE_TYPE_PCM) {
+		if (gus->gf1.pcm_alloc_voices >= gus->gf1.pcm_channels) {
+			spin_unlock_irqrestore(&gus->voice_alloc, flags);
+			return NULL;
+		}
+	}
+	for (idx = 0; idx < 32; idx++) {
+		pvoice = &gus->gf1.voices[idx];
+		if (!pvoice->use) {
+			snd_gf1_alloc_voice_use(gus, pvoice, type, client, port);
+			spin_unlock_irqrestore(&gus->voice_alloc, flags);
+			return pvoice;
+		}
+	} 
+	for (idx = 0; idx < 32; idx++) {
+		pvoice = &gus->gf1.voices[idx];
+		if (pvoice->midi && !pvoice->client) {
+			snd_gf1_clear_voices(gus, pvoice->number, pvoice->number);
+			snd_gf1_alloc_voice_use(gus, pvoice, type, client, port);
+			spin_unlock_irqrestore(&gus->voice_alloc, flags);
+			return pvoice;
+		}
+	} 
+	spin_unlock_irqrestore(&gus->voice_alloc, flags);
+	return NULL;
+}
+
+void snd_gf1_free_voice(snd_gus_card_t * gus, snd_gus_voice_t *voice)
+{
+	unsigned long flags;
+	void (*private_free)(snd_gus_voice_t *voice);
+	void *private_data;
+
+	if (voice == NULL || !voice->use)
+		return;
+	snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | voice->number);
+	snd_gf1_clear_voices(gus, voice->number, voice->number);
+	spin_lock_irqsave(&gus->voice_alloc, flags);
+	private_free = voice->private_free;
+	private_data = voice->private_data;
+	voice->private_free = NULL;
+	voice->private_data = NULL;
+	if (voice->pcm)
+		gus->gf1.pcm_alloc_voices--;
+	voice->use = voice->pcm = 0;
+	voice->sample_ops = NULL;
+	spin_unlock_irqrestore(&gus->voice_alloc, flags);
+	if (private_free)
+		private_free(voice);
+}
+
+/*
+ *  call this function only by start of driver
+ */
+
+int snd_gf1_start(snd_gus_card_t * gus)
+{
+	unsigned long flags;
+	unsigned int i;
+
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0);	/* reset GF1 */
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);	/* disable IRQ & DAC */
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_JOYSTICK_DAC_LEVEL, gus->joystick_dac);
+
+	snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_ALL);
+	for (i = 0; i < 32; i++) {
+		gus->gf1.voices[i].number = i;
+		snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | i);
+	}
+
+	snd_gf1_uart_cmd(gus, 0x03);	/* huh.. this cleanup took me some time... */
+
+	if (gus->gf1.enh_mode) {	/* enhanced mode !!!! */
+		snd_gf1_i_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_i_look8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01);
+		snd_gf1_i_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01);
+	}
+	snd_gf1_clear_regs(gus);
+	snd_gf1_select_active_voices(gus);
+	snd_gf1_delay(gus);
+	gus->gf1.default_voice_address = gus->gf1.memory > 0 ? 0 : 512 - 8;
+	/* initialize LFOs & clear LFOs memory */
+	if (gus->gf1.enh_mode && gus->gf1.memory) {
+		gus->gf1.hw_lfo = 1;
+		gus->gf1.default_voice_address += 1024;
+	} else {
+		gus->gf1.sw_lfo = 1;
+	}
+#if 0
+	snd_gf1_lfo_init(gus);
+#endif
+	if (gus->gf1.memory > 0)
+		for (i = 0; i < 4; i++)
+			snd_gf1_poke(gus, gus->gf1.default_voice_address + i, 0);
+	snd_gf1_clear_regs(gus);
+	snd_gf1_clear_voices(gus, 0, 31);
+	snd_gf1_look_regs(gus);
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 7);	/* Reset Register = IRQ enable, DAC enable */
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 7);	/* Reset Register = IRQ enable, DAC enable */
+	if (gus->gf1.enh_mode) {	/* enhanced mode !!!! */
+		snd_gf1_i_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_i_look8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01);
+		snd_gf1_i_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01);
+	}
+	while ((snd_gf1_i_read8(gus, SNDRV_GF1_GB_VOICES_IRQ) & 0xc0) != 0xc0);
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(gus->gf1.active_voice = 0, GUSP(gus, GF1PAGE));
+	outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+
+	snd_gf1_timers_init(gus);
+	snd_gf1_look_regs(gus);
+	snd_gf1_mem_init(gus);
+	snd_gf1_mem_proc_init(gus);
+#ifdef CONFIG_SND_DEBUG
+	snd_gus_irq_profile_init(gus);
+#endif
+
+#if 0
+	if (gus->pnp_flag) {
+		if (gus->chip.playback_fifo_size > 0)
+			snd_gf1_i_write16(gus, SNDRV_GF1_GW_FIFO_RECORD_BASE_ADDR, gus->chip.playback_fifo_block->ptr >> 8);
+		if (gus->chip.record_fifo_size > 0)
+			snd_gf1_i_write16(gus, SNDRV_GF1_GW_FIFO_PLAY_BASE_ADDR, gus->chip.record_fifo_block->ptr >> 8);
+		snd_gf1_i_write16(gus, SNDRV_GF1_GW_FIFO_SIZE, gus->chip.interwave_fifo_reg);
+	}
+#endif
+
+	return 0;
+}
+
+/*
+ *  call this function only by shutdown of driver
+ */
+
+int snd_gf1_stop(snd_gus_card_t * gus)
+{
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0); /* stop all timers */
+	snd_gf1_stop_voices(gus, 0, 31);		/* stop all voices */
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);	/* disable IRQ & DAC */
+	snd_gf1_timers_done(gus);
+	snd_gf1_mem_done(gus);
+#if 0
+	snd_gf1_lfo_done(gus);
+#endif
+	return 0;
+}
diff --git a/sound/isa/gus/gus_sample.c b/sound/isa/gus/gus_sample.c
new file mode 100644
index 0000000..4290e03
--- /dev/null
+++ b/sound/isa/gus/gus_sample.c
@@ -0,0 +1,155 @@
+/*
+ *  Routines for Gravis UltraSound soundcards - Sample support
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+/*
+ *
+ */
+
+static void select_instrument(snd_gus_card_t * gus, snd_gus_voice_t * v)
+{
+	snd_seq_kinstr_t *instr;
+
+#if 0
+	printk("select instrument: cluster = %li, std = 0x%x, bank = %i, prg = %i\n",
+					v->instr.cluster,
+					v->instr.std,
+					v->instr.bank,
+					v->instr.prg);
+#endif
+	instr = snd_seq_instr_find(gus->gf1.ilist, &v->instr, 0, 1);
+	if (instr != NULL) {
+		if (instr->ops) {
+			if (!strcmp(instr->ops->instr_type, SNDRV_SEQ_INSTR_ID_SIMPLE))
+				snd_gf1_simple_init(v);
+		}
+		snd_seq_instr_free_use(gus->gf1.ilist, instr);
+	}
+}
+
+/*
+ *
+ */
+
+static void event_sample(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v)
+{
+	if (v->sample_ops && v->sample_ops->sample_stop)
+		v->sample_ops->sample_stop(p->gus, 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->gus, v);
+}
+
+static void event_cluster(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v)
+{
+	if (v->sample_ops && v->sample_ops->sample_stop)
+		v->sample_ops->sample_stop(p->gus, v, SAMPLE_STOP_IMMEDIATELY);
+	v->instr.cluster = ev->data.sample.param.cluster.cluster;
+	select_instrument(p->gus, v);
+}
+
+static void event_start(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v)
+{
+	if (v->sample_ops && v->sample_ops->sample_start)
+		v->sample_ops->sample_start(p->gus, v, ev->data.sample.param.position);
+}
+
+static void event_stop(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v)
+{
+	if (v->sample_ops && v->sample_ops->sample_stop)
+		v->sample_ops->sample_stop(p->gus, v, ev->data.sample.param.stop_mode);
+}
+
+static void event_freq(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v)
+{
+	if (v->sample_ops && v->sample_ops->sample_freq)
+		v->sample_ops->sample_freq(p->gus, v, ev->data.sample.param.frequency);
+}
+
+static void event_volume(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v)
+{
+	if (v->sample_ops && v->sample_ops->sample_volume)
+		v->sample_ops->sample_volume(p->gus, v, &ev->data.sample.param.volume);
+}
+
+static void event_loop(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v)
+{
+	if (v->sample_ops && v->sample_ops->sample_loop)
+		v->sample_ops->sample_loop(p->gus, v, &ev->data.sample.param.loop);
+}
+
+static void event_position(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v)
+{
+	if (v->sample_ops && v->sample_ops->sample_pos)
+		v->sample_ops->sample_pos(p->gus, v, ev->data.sample.param.position);
+}
+
+static void event_private1(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v)
+{
+	if (v->sample_ops && v->sample_ops->sample_private1)
+		v->sample_ops->sample_private1(p->gus, v, (unsigned char *)&ev->data.sample.param.raw8);
+}
+
+typedef void (gus_sample_event_handler_t)(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v);
+
+static gus_sample_event_handler_t *gus_sample_event_handlers[9] = {
+	event_sample,
+	event_cluster,
+	event_start,
+	event_stop,
+	event_freq,
+	event_volume,
+	event_loop,
+	event_position,
+	event_private1
+};
+
+void snd_gus_sample_event(snd_seq_event_t *ev, snd_gus_port_t *p)
+{
+	int idx, voice;
+	snd_gus_card_t *gus = p->gus;
+	snd_gus_voice_t *v;
+	unsigned long flags;
+	
+	idx = ev->type - SNDRV_SEQ_EVENT_SAMPLE;
+	if (idx < 0 || idx > 8)
+		return;
+	for (voice = 0; voice < 32; voice++) {
+		v = &gus->gf1.voices[voice];
+		if (v->use && v->client == ev->source.client &&
+		    v->port == ev->source.port &&
+		    v->index == ev->data.sample.channel) {
+		    	spin_lock_irqsave(&gus->event_lock, flags);
+			gus_sample_event_handlers[idx](ev, p, v);
+			spin_unlock_irqrestore(&gus->event_lock, flags);
+			return;
+		}
+	}
+}
diff --git a/sound/isa/gus/gus_simple.c b/sound/isa/gus/gus_simple.c
new file mode 100644
index 0000000..c122e7b
--- /dev/null
+++ b/sound/isa/gus/gus_simple.c
@@ -0,0 +1,634 @@
+/*
+ *  Routines for Gravis UltraSound soundcards - Simple instrument handlers
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include "gus_tables.h"
+
+/*
+ *
+ */
+
+static void interrupt_wave(snd_gus_card_t *gus, snd_gus_voice_t *voice);
+static void interrupt_volume(snd_gus_card_t *gus, snd_gus_voice_t *voice);
+static void interrupt_effect(snd_gus_card_t *gus, snd_gus_voice_t *voice);
+
+static void sample_start(snd_gus_card_t *gus, snd_gus_voice_t *voice, snd_seq_position_t position);
+static void sample_stop(snd_gus_card_t *gus, snd_gus_voice_t *voice, snd_seq_stop_mode_t mode);
+static void sample_freq(snd_gus_card_t *gus, snd_gus_voice_t *voice, snd_seq_frequency_t freq);
+static void sample_volume(snd_gus_card_t *card, snd_gus_voice_t *voice, snd_seq_ev_volume_t *volume);
+static void sample_loop(snd_gus_card_t *card, snd_gus_voice_t *voice, snd_seq_ev_loop_t *loop);
+static void sample_pos(snd_gus_card_t *card, snd_gus_voice_t *voice, snd_seq_position_t position);
+static void sample_private1(snd_gus_card_t *card, snd_gus_voice_t *voice, unsigned char *data);
+
+static snd_gus_sample_ops_t sample_ops = {
+	sample_start,
+	sample_stop,
+	sample_freq,
+	sample_volume,
+	sample_loop,
+	sample_pos,
+	sample_private1
+};
+
+#if 0
+
+static void note_stop(snd_gus_card_t *gus, snd_gus_voice_t *voice, int wait);
+static void note_wait(snd_gus_card_t *gus, snd_gus_voice_t *voice);
+static void note_off(snd_gus_card_t *gus, snd_gus_voice_t *voice);
+static void note_volume(snd_gus_card_t *card, snd_gus_voice_t *voice);
+static void note_pitchbend(snd_gus_card_t *card, snd_gus_voice_t *voice);
+static void note_vibrato(snd_gus_card_t *card, snd_gus_voice_t *voice);
+static void note_tremolo(snd_gus_card_t *card, snd_gus_voice_t *voice);
+
+static struct snd_gus_note_handlers note_commands = {
+	note_stop,
+	note_wait,
+	note_off,
+	note_volume,
+	note_pitchbend,
+	note_vibrato,
+	note_tremolo
+};
+
+static void chn_trigger_down(snd_gus_card_t *card, ultra_channel_t *channel, ultra_instrument_t *instrument, unsigned char note, unsigned char velocity, unsigned char priority );
+static void chn_trigger_up( ultra_card_t *card, ultra_note_t *note );
+static void chn_control( ultra_card_t *card, ultra_channel_t *channel, unsigned short p1, unsigned short p2 );
+
+static struct ULTRA_STRU_INSTRUMENT_CHANNEL_COMMANDS channel_commands = {
+  chn_trigger_down,
+  chn_trigger_up,
+  chn_control
+};
+
+#endif
+
+static void do_volume_envelope(snd_gus_card_t *card, snd_gus_voice_t *voice);
+static void do_pan_envelope(snd_gus_card_t *card, snd_gus_voice_t *voice);
+
+/*
+ *
+ */
+
+static void interrupt_wave(snd_gus_card_t *gus, snd_gus_voice_t *voice)
+{
+	spin_lock(&gus->event_lock);
+	snd_gf1_stop_voice(gus, voice->number);
+	spin_lock(&gus->reg_lock);
+	snd_gf1_select_voice(gus, voice->number);
+	snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, 0);
+	spin_unlock(&gus->reg_lock);
+	voice->flags &= ~SNDRV_GF1_VFLG_RUNNING;
+	spin_unlock(&gus->event_lock);
+}
+
+static void interrupt_volume(snd_gus_card_t *gus, snd_gus_voice_t *voice)
+{
+	spin_lock(&gus->event_lock);
+	if (voice->flags & SNDRV_GF1_VFLG_RUNNING)
+		do_volume_envelope(gus, voice);
+	else
+		snd_gf1_stop_voice(gus, voice->number);
+	spin_unlock(&gus->event_lock);
+}
+
+static void interrupt_effect(snd_gus_card_t *gus, snd_gus_voice_t *voice)
+{
+	spin_lock(&gus->event_lock);
+	if ((voice->flags & (SNDRV_GF1_VFLG_RUNNING|SNDRV_GF1_VFLG_EFFECT_TIMER1)) ==
+	                    (SNDRV_GF1_VFLG_RUNNING|SNDRV_GF1_VFLG_EFFECT_TIMER1))
+		do_pan_envelope(gus, voice);
+	spin_unlock(&gus->event_lock);
+}
+
+/*
+ *
+ */
+
+static void do_volume_envelope(snd_gus_card_t *gus, snd_gus_voice_t *voice)
+{
+	unsigned short next, rate, old_volume;
+	int program_next_ramp;
+	unsigned long flags;
+  
+	if (!gus->gf1.volume_ramp) {
+		spin_lock_irqsave(&gus->reg_lock, flags);
+		snd_gf1_select_voice(gus, voice->number);
+		snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+		snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, voice->gf1_volume);
+		printk("gf1_volume = 0x%x\n", voice->gf1_volume);
+		spin_unlock_irqrestore(&gus->reg_lock, flags);
+		return;
+	}
+	program_next_ramp = 0;
+	rate = next = 0;
+	while (1) {
+		program_next_ramp = 0;
+		rate = next = 0;
+		switch (voice->venv_state) {
+		case VENV_BEFORE:
+			voice->venv_state = VENV_ATTACK;
+			voice->venv_value_next = 0;
+			spin_lock_irqsave(&gus->reg_lock, flags);
+			snd_gf1_select_voice(gus, voice->number);
+			snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+			snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, SNDRV_GF1_MIN_VOLUME);
+			spin_unlock_irqrestore(&gus->reg_lock, flags);
+			break;
+		case VENV_ATTACK:
+			voice->venv_state = VENV_SUSTAIN;
+			program_next_ramp++;
+			next = 255;
+			rate = gus->gf1.volume_ramp;
+			break;
+		case VENV_SUSTAIN:
+			voice->venv_state = VENV_RELEASE;
+			spin_lock_irqsave(&gus->reg_lock, flags);
+			snd_gf1_select_voice(gus, voice->number);
+			snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+ 			snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, ((int)voice->gf1_volume * (int)voice->venv_value_next) / 255);
+			spin_unlock_irqrestore(&gus->reg_lock, flags);
+			return;
+		case VENV_RELEASE:
+			voice->venv_state = VENV_DONE;
+			program_next_ramp++;
+			next = 0;
+			rate = gus->gf1.volume_ramp;
+			break;
+		case VENV_DONE:
+			snd_gf1_stop_voice(gus, voice->number);
+			voice->flags &= ~SNDRV_GF1_VFLG_RUNNING;
+			return;
+		case VENV_VOLUME:
+			program_next_ramp++;
+			next = voice->venv_value_next;
+			rate = gus->gf1.volume_ramp;
+			voice->venv_state = voice->venv_state_prev;
+			break;
+		}
+		voice->venv_value_next = next;
+		if (!program_next_ramp)
+			continue;
+		spin_lock_irqsave(&gus->reg_lock, flags);
+		snd_gf1_select_voice(gus, voice->number);
+		snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+		old_volume = snd_gf1_read16(gus, SNDRV_GF1_VW_VOLUME) >> 8;
+		if (!rate) {
+			spin_unlock_irqrestore(&gus->reg_lock, flags);			
+			continue;
+		}
+		next = (((int)voice->gf1_volume * (int)next) / 255) >> 8;
+		if (old_volume < SNDRV_GF1_MIN_OFFSET)
+			old_volume = SNDRV_GF1_MIN_OFFSET;
+		if (next < SNDRV_GF1_MIN_OFFSET)
+			next = SNDRV_GF1_MIN_OFFSET;
+		if (next > SNDRV_GF1_MAX_OFFSET)
+			next = SNDRV_GF1_MAX_OFFSET;
+		if (old_volume == next) {
+			spin_unlock_irqrestore(&gus->reg_lock, flags);
+			continue;
+		}
+		voice->volume_control &= ~0xc3;
+		voice->volume_control |= 0x20;
+		if (old_volume > next) {
+			snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, next);
+			snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, old_volume);
+			voice->volume_control |= 0x40;
+		} else {
+			snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, old_volume);
+			snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, next);
+		}
+		snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, rate);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, voice->volume_control);
+		if (!gus->gf1.enh_mode) {
+			snd_gf1_delay(gus);
+			snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, voice->volume_control);
+		}
+		spin_unlock_irqrestore(&gus->reg_lock, flags);			
+		return;
+	}
+}
+
+static void do_pan_envelope(snd_gus_card_t *gus, snd_gus_voice_t *voice)
+{
+	unsigned long flags;
+	unsigned char old_pan;
+
+#if 0
+	snd_gf1_select_voice(gus, voice->number);
+	printk(" -%i- do_pan_envelope - flags = 0x%x (0x%x -> 0x%x)\n",
+		voice->number,
+		voice->flags,
+		voice->gf1_pan,
+		snd_gf1_i_read8(gus, SNDRV_GF1_VB_PAN) & 0x0f);
+#endif
+	if (gus->gf1.enh_mode) {
+		voice->flags &= ~(SNDRV_GF1_VFLG_EFFECT_TIMER1|SNDRV_GF1_VFLG_PAN);
+		return;
+	}
+	if (!gus->gf1.smooth_pan) {
+		spin_lock_irqsave(&gus->reg_lock, flags);			
+		snd_gf1_select_voice(gus, voice->number);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, voice->gf1_pan);
+		spin_unlock_irqrestore(&gus->reg_lock, flags);
+		return;
+	}
+	if (!(voice->flags & SNDRV_GF1_VFLG_PAN))		/* before */
+		voice->flags |= SNDRV_GF1_VFLG_EFFECT_TIMER1|SNDRV_GF1_VFLG_PAN;
+	spin_lock_irqsave(&gus->reg_lock, flags);			
+	snd_gf1_select_voice(gus, voice->number);
+	old_pan = snd_gf1_read8(gus, SNDRV_GF1_VB_PAN) & 0x0f;
+	if (old_pan > voice->gf1_pan )
+		old_pan--;
+	if (old_pan < voice->gf1_pan)
+		old_pan++;
+	snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, old_pan);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	if (old_pan == voice->gf1_pan)			/* the goal was reached */
+		voice->flags &= ~(SNDRV_GF1_VFLG_EFFECT_TIMER1|SNDRV_GF1_VFLG_PAN);
+#if 0
+	snd_gf1_select_voice(gus, voice->number);
+	printk(" -%i- (1) do_pan_envelope - flags = 0x%x (0x%x -> 0x%x)\n",
+	       voice->number,
+	       voice->flags,
+	       voice->gf1_pan,
+	       snd_gf1_i_read8(gus, GF1_VB_PAN) & 0x0f);
+#endif
+}
+
+static void set_enhanced_pan(snd_gus_card_t *gus, snd_gus_voice_t *voice, unsigned short pan)
+{
+	unsigned long flags;
+	unsigned short vlo, vro;
+  
+	vlo = SNDRV_GF1_ATTEN((SNDRV_GF1_ATTEN_TABLE_SIZE-1) - pan);
+	vro = SNDRV_GF1_ATTEN(pan);
+	if (pan != SNDRV_GF1_ATTEN_TABLE_SIZE - 1 && pan != 0) {
+		vlo >>= 1;
+		vro >>= 1;
+	}
+	vlo <<= 4;
+	vro <<= 4;
+#if 0
+	printk("vlo = 0x%x (0x%x), vro = 0x%x (0x%x)\n",
+			vlo, snd_gf1_i_read16(gus, GF1_VW_OFFSET_LEFT),
+			vro, snd_gf1_i_read16(gus, GF1_VW_OFFSET_RIGHT));
+#endif
+	spin_lock_irqsave(&gus->reg_lock, flags);			
+	snd_gf1_select_voice(gus, voice->number);
+        snd_gf1_write16(gus, SNDRV_GF1_VW_OFFSET_LEFT_FINAL, vlo);
+	snd_gf1_write16(gus, SNDRV_GF1_VW_OFFSET_RIGHT_FINAL, vro);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);			
+	voice->vlo = vlo;
+	voice->vro = vro;
+}
+
+/*
+ *
+ */
+
+static void sample_start(snd_gus_card_t *gus, snd_gus_voice_t *voice, snd_seq_position_t position)
+{
+	unsigned long flags;
+	unsigned int begin, addr, addr_end, addr_start;
+	int w_16;
+	simple_instrument_t *simple;
+	snd_seq_kinstr_t *instr;
+
+	instr = snd_seq_instr_find(gus->gf1.ilist, &voice->instr, 0, 1);
+	if (instr == NULL)
+		return;
+	voice->instr = instr->instr;	/* copy ID to speedup aliases */
+	simple = KINSTR_DATA(instr);
+	begin = simple->address.memory << 4;
+	w_16 = simple->format & SIMPLE_WAVE_16BIT ? 0x04 : 0;
+	addr_start = simple->loop_start;
+	if (simple->format & SIMPLE_WAVE_LOOP) {
+		addr_end = simple->loop_end;
+	} else {
+		addr_end = (simple->size << 4) - (w_16 ? 40 : 24);
+	}
+	if (simple->format & SIMPLE_WAVE_BACKWARD) {
+		addr = simple->loop_end;
+		if (position < simple->loop_end)
+			addr -= position;
+	} else {
+		addr = position;
+	}
+	voice->control = 0x00;
+	voice->mode = 0x20;		/* enable offset registers */
+	if (simple->format & SIMPLE_WAVE_16BIT)
+		voice->control |= 0x04;
+	if (simple->format & SIMPLE_WAVE_BACKWARD)
+		voice->control |= 0x40;
+	if (simple->format & SIMPLE_WAVE_LOOP) {
+		voice->control |= 0x08;
+	} else {
+		voice->control |= 0x20;
+	}
+	if (simple->format & SIMPLE_WAVE_BIDIR)
+		voice->control |= 0x10;
+	if (simple->format & SIMPLE_WAVE_ULAW)
+		voice->mode |= 0x40;
+	if (w_16) {
+		addr = ((addr << 1) & ~0x1f) | (addr & 0x0f);
+		addr_start = ((addr_start << 1) & ~0x1f) | (addr_start & 0x0f);
+		addr_end = ((addr_end << 1) & ~0x1f) | (addr_end & 0x0f);
+	}
+	addr += begin;
+	addr_start += begin;
+	addr_end += begin;
+	snd_gf1_stop_voice(gus, voice->number);	
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	snd_gf1_select_voice(gus, voice->number);
+	snd_gf1_write16(gus, SNDRV_GF1_VW_FREQUENCY, voice->fc_register + voice->fc_lfo);
+	voice->venv_state = VENV_BEFORE;
+	voice->volume_control = 0x03;
+	snd_gf1_write_addr(gus, SNDRV_GF1_VA_START, addr_start, w_16);
+	snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, addr_end, w_16);
+	snd_gf1_write_addr(gus, SNDRV_GF1_VA_CURRENT, addr, w_16);
+	if (!gus->gf1.enh_mode) {
+		snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, voice->gf1_pan);
+	} else {
+		snd_gf1_write16(gus, SNDRV_GF1_VW_OFFSET_LEFT, voice->vlo);
+		snd_gf1_write16(gus, SNDRV_GF1_VW_OFFSET_LEFT_FINAL, voice->vlo);
+		snd_gf1_write16(gus, SNDRV_GF1_VW_OFFSET_RIGHT, voice->vro);
+		snd_gf1_write16(gus, SNDRV_GF1_VW_OFFSET_RIGHT_FINAL, voice->vro);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_ACCUMULATOR, voice->effect_accumulator);
+		snd_gf1_write16(gus, SNDRV_GF1_VW_EFFECT_VOLUME, voice->gf1_effect_volume);
+		snd_gf1_write16(gus, SNDRV_GF1_VW_EFFECT_VOLUME_FINAL, voice->gf1_effect_volume);
+	}
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	do_volume_envelope(gus, voice);
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	snd_gf1_select_voice(gus, voice->number);
+	if (gus->gf1.enh_mode)
+		snd_gf1_write8(gus, SNDRV_GF1_VB_MODE, voice->mode);
+	snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice->control);
+	if (!gus->gf1.enh_mode) {
+		snd_gf1_delay(gus);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice->control );
+	}
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+#if 0
+	snd_gf1_print_voice_registers(gus);
+#endif
+	voice->flags |= SNDRV_GF1_VFLG_RUNNING;
+	snd_seq_instr_free_use(gus->gf1.ilist, instr);
+}
+
+static void sample_stop(snd_gus_card_t *gus, snd_gus_voice_t *voice, snd_seq_stop_mode_t mode)
+{
+	unsigned char control;
+	unsigned long flags;
+
+	if (!(voice->flags & SNDRV_GF1_VFLG_RUNNING))
+		return;
+	switch (mode) {
+	default:
+		if (gus->gf1.volume_ramp > 0) {
+			if (voice->venv_state < VENV_RELEASE) {
+				voice->venv_state = VENV_RELEASE;
+				do_volume_envelope(gus, voice);
+			}
+		}
+		if (mode != SAMPLE_STOP_VENVELOPE) {
+			snd_gf1_stop_voice(gus, voice->number);
+			spin_lock_irqsave(&gus->reg_lock, flags);
+			snd_gf1_select_voice(gus, voice->number);
+			snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, SNDRV_GF1_MIN_VOLUME);
+			spin_unlock_irqrestore(&gus->reg_lock, flags);
+			voice->flags &= ~SNDRV_GF1_VFLG_RUNNING;
+		}
+		break;
+	case SAMPLE_STOP_LOOP:		/* disable loop only */
+		spin_lock_irqsave(&gus->reg_lock, flags);
+		snd_gf1_select_voice(gus, voice->number);
+		control = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL);
+		control &= ~(0x83 | 0x04);
+		control |= 0x20;
+		snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, control);
+		spin_unlock_irqrestore(&gus->reg_lock, flags);
+		break;
+	}
+}
+
+static void sample_freq(snd_gus_card_t *gus, snd_gus_voice_t *voice, snd_seq_frequency_t freq)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	voice->fc_register = snd_gf1_translate_freq(gus, freq);
+	snd_gf1_select_voice(gus, voice->number);
+	snd_gf1_write16(gus, SNDRV_GF1_VW_FREQUENCY, voice->fc_register + voice->fc_lfo);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+static void sample_volume(snd_gus_card_t *gus, snd_gus_voice_t *voice, snd_seq_ev_volume_t *volume)
+{
+	if (volume->volume >= 0) {
+		volume->volume &= 0x3fff;
+		voice->gf1_volume = snd_gf1_lvol_to_gvol_raw(volume->volume << 2) << 4;
+		voice->venv_state_prev = VENV_SUSTAIN;
+		voice->venv_state = VENV_VOLUME;
+		do_volume_envelope(gus, voice);
+        }
+	if (volume->lr >= 0) {
+		volume->lr &= 0x3fff;
+		if (!gus->gf1.enh_mode) {
+			voice->gf1_pan = (volume->lr >> 10) & 15;
+			if (!gus->gf1.full_range_pan) {
+				if (voice->gf1_pan == 0)
+					voice->gf1_pan++;
+				if (voice->gf1_pan == 15)
+					voice->gf1_pan--;
+			}
+			voice->flags &= ~SNDRV_GF1_VFLG_PAN;	/* before */
+			do_pan_envelope(gus, voice);
+		} else {
+			set_enhanced_pan(gus, voice, volume->lr >> 7);
+		}
+	}
+}
+
+static void sample_loop(snd_gus_card_t *gus, snd_gus_voice_t *voice, snd_seq_ev_loop_t *loop)
+{
+	unsigned long flags;
+	int w_16 = voice->control & 0x04;
+	unsigned int begin, addr_start, addr_end;
+	simple_instrument_t *simple;
+	snd_seq_kinstr_t *instr;
+
+#if 0
+	printk("voice_loop: start = 0x%x, end = 0x%x\n", loop->start, loop->end);
+#endif
+	instr = snd_seq_instr_find(gus->gf1.ilist, &voice->instr, 0, 1);
+	if (instr == NULL)
+		return;
+	voice->instr = instr->instr;	/* copy ID to speedup aliases */
+	simple = KINSTR_DATA(instr);
+	begin = simple->address.memory;
+	addr_start = loop->start;
+	addr_end = loop->end;
+	addr_start = (((addr_start << 1) & ~0x1f) | (addr_start & 0x0f)) + begin;
+	addr_end = (((addr_end << 1) & ~0x1f) | (addr_end & 0x0f)) + begin;
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	snd_gf1_select_voice(gus, voice->number);
+	snd_gf1_write_addr(gus, SNDRV_GF1_VA_START, addr_start, w_16);
+	snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, addr_end, w_16);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	snd_seq_instr_free_use(gus->gf1.ilist, instr);
+}
+
+static void sample_pos(snd_gus_card_t *gus, snd_gus_voice_t *voice, snd_seq_position_t position)
+{
+	unsigned long flags;
+	int w_16 = voice->control & 0x04;
+	unsigned int begin, addr;
+	simple_instrument_t *simple;
+	snd_seq_kinstr_t *instr;
+
+#if 0
+	printk("voice_loop: start = 0x%x, end = 0x%x\n", loop->start, loop->end);
+#endif
+	instr = snd_seq_instr_find(gus->gf1.ilist, &voice->instr, 0, 1);
+	if (instr == NULL)
+		return;
+	voice->instr = instr->instr;	/* copy ID to speedup aliases */
+	simple = KINSTR_DATA(instr);
+	begin = simple->address.memory;
+	addr = (((position << 1) & ~0x1f) | (position & 0x0f)) + begin;
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	snd_gf1_select_voice(gus, voice->number);
+	snd_gf1_write_addr(gus, SNDRV_GF1_VA_CURRENT, addr, w_16);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	snd_seq_instr_free_use(gus->gf1.ilist, instr);
+}
+
+#if 0
+
+static unsigned char get_effects_mask( ultra_card_t *card, int value )
+{
+  if ( value > 7 ) return 0;
+  if ( card -> gf1.effects && card -> gf1.effects -> chip_type == ULTRA_EFFECT_CHIP_INTERWAVE )
+    return card -> gf1.effects -> chip.interwave.voice_output[ value ];
+  return 0;
+}
+
+#endif
+
+static void sample_private1(snd_gus_card_t *card, snd_gus_voice_t *voice, unsigned char *data)
+{
+#if 0
+  unsigned long flags;
+  unsigned char uc;
+
+  switch ( *data ) {
+    case ULTRA_PRIV1_IW_EFFECT:
+      uc = get_effects_mask( card, ultra_get_byte( data, 4 ) );
+      uc |= get_effects_mask( card, ultra_get_byte( data, 4 ) >> 4 );
+      uc |= get_effects_mask( card, ultra_get_byte( data, 5 ) );
+      uc |= get_effects_mask( card, ultra_get_byte( data, 5 ) >> 4 );
+      voice -> data.simple.effect_accumulator = uc;
+      voice -> data.simple.effect_volume = ultra_translate_voice_volume( card, ultra_get_word( data, 2 ) ) << 4;
+      if ( !card -> gf1.enh_mode ) return;
+      if ( voice -> flags & VFLG_WAIT_FOR_START ) return;
+      if ( voice -> flags & VFLG_RUNNING )
+        {
+          CLI( &flags );
+          gf1_select_voice( card, voice -> number );
+          ultra_write8( card, GF1_VB_ACCUMULATOR, voice -> data.simple.effect_accumulator );
+          ultra_write16( card, GF1_VW_EFFECT_VOLUME_FINAL, voice -> data.simple.effect_volume );
+          STI( &flags );
+        }
+      break;
+   case ULTRA_PRIV1_IW_LFO:
+     ultra_lfo_command( card, voice -> number, data );
+  }
+#endif
+}
+
+#if 0
+
+/*
+ *
+ */
+
+static void note_stop( ultra_card_t *card, ultra_voice_t *voice, int wait )
+{
+}
+
+static void note_wait( ultra_card_t *card, ultra_voice_t *voice )
+{
+}
+
+static void note_off( ultra_card_t *card, ultra_voice_t *voice )
+{
+}
+
+static void note_volume( ultra_card_t *card, ultra_voice_t *voice )
+{
+}
+
+static void note_pitchbend( ultra_card_t *card, ultra_voice_t *voice )
+{
+}
+
+static void note_vibrato( ultra_card_t *card, ultra_voice_t *voice )
+{
+}
+
+static void note_tremolo( ultra_card_t *card, ultra_voice_t *voice )
+{
+}
+
+/*
+ *
+ */
+ 
+static void chn_trigger_down( ultra_card_t *card, ultra_channel_t *channel, ultra_instrument_t *instrument, unsigned char note, unsigned char velocity, unsigned char priority )
+{
+}
+
+static void chn_trigger_up( ultra_card_t *card, ultra_note_t *note )
+{
+}
+
+static void chn_control( ultra_card_t *card, ultra_channel_t *channel, unsigned short p1, unsigned short p2 )
+{
+}
+
+/*
+ *
+ */
+ 
+#endif
+
+void snd_gf1_simple_init(snd_gus_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;
+}
diff --git a/sound/isa/gus/gus_synth.c b/sound/isa/gus/gus_synth.c
new file mode 100644
index 0000000..66552e6
--- /dev/null
+++ b/sound/isa/gus/gus_synth.c
@@ -0,0 +1,329 @@
+/*
+ *  Routines for Gravis UltraSound soundcards - Synthesizer
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/seq_device.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Routines for Gravis UltraSound soundcards - Synthesizer");
+MODULE_LICENSE("GPL");
+
+/*
+ *
+ */
+
+static void snd_gus_synth_free_voices(snd_gus_card_t * gus, int client, int port)
+{
+	int idx;
+	snd_gus_voice_t * voice;
+	
+	for (idx = 0; idx < 32; idx++) {
+		voice = &gus->gf1.voices[idx];
+		if (voice->use && voice->client == client && voice->port == port)
+			snd_gf1_free_voice(gus, voice);
+	}
+}
+
+static int snd_gus_synth_use(void *private_data, snd_seq_port_subscribe_t *info)
+{
+	snd_gus_port_t * port = (snd_gus_port_t *)private_data;
+	snd_gus_card_t * gus = port->gus;
+	snd_gus_voice_t * voice;
+	unsigned int idx;
+
+	if (info->voices > 32)
+		return -EINVAL;
+	down(&gus->register_mutex);
+	if (!snd_gus_use_inc(gus)) {
+		up(&gus->register_mutex);
+		return -EFAULT;
+	}
+	for (idx = 0; idx < info->voices; idx++) {
+		voice = snd_gf1_alloc_voice(gus, SNDRV_GF1_VOICE_TYPE_SYNTH, info->sender.client, info->sender.port);
+		if (voice == NULL) {
+			snd_gus_synth_free_voices(gus, info->sender.client, info->sender.port);
+			snd_gus_use_dec(gus);
+			up(&gus->register_mutex);
+			return -EBUSY;
+		}
+		voice->index = idx;
+	}
+	up(&gus->register_mutex);
+	return 0;
+}
+
+static int snd_gus_synth_unuse(void *private_data, snd_seq_port_subscribe_t *info)
+{
+	snd_gus_port_t * port = (snd_gus_port_t *)private_data;
+	snd_gus_card_t * gus = port->gus;
+
+	down(&gus->register_mutex);
+	snd_gus_synth_free_voices(gus, info->sender.client, info->sender.port);
+	snd_gus_use_dec(gus);
+	up(&gus->register_mutex);
+	return 0;
+}
+
+/*
+ *
+ */
+
+static void snd_gus_synth_free_private_instruments(snd_gus_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->gus->gf1.ilist, &ifree, client, 0);
+}
+ 
+int snd_gus_synth_event_input(snd_seq_event_t *ev, int direct, void *private_data, int atomic, int hop)
+{
+	snd_gus_port_t * p = (snd_gus_port_t *) private_data;
+	
+	snd_assert(p != NULL, return -EINVAL);
+	if (ev->type >= SNDRV_SEQ_EVENT_SAMPLE &&
+	    ev->type <= SNDRV_SEQ_EVENT_SAMPLE_PRIVATE1) {
+		snd_gus_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_gus_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->gus->gf1.iwffff_ops.kops,
+					    p->gus->gf1.ilist,
+					    ev,
+					    p->gus->gf1.seq_client,
+					    atomic, hop);
+			return 0;
+		}
+	}
+	return 0;
+}
+
+static void snd_gus_synth_instr_notify(void *private_data,
+				       snd_seq_kinstr_t *instr,
+				       int what)
+{
+	unsigned int idx;
+	snd_gus_card_t *gus = private_data;
+	snd_gus_voice_t *pvoice;
+	unsigned long flags;
+	
+	spin_lock_irqsave(&gus->event_lock, flags);
+	for (idx = 0; idx < 32; idx++) {
+		pvoice = &gus->gf1.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(gus, pvoice, SAMPLE_STOP_IMMEDIATELY);
+			} else {
+				snd_gf1_stop_voice(gus, pvoice->number);
+				pvoice->flags &= ~SNDRV_GF1_VFLG_RUNNING;
+			}
+		}
+	}
+	spin_unlock_irqrestore(&gus->event_lock, flags);
+}
+
+/*
+ *
+ */
+
+static void snd_gus_synth_free_port(void *private_data)
+{
+	snd_gus_port_t * p = (snd_gus_port_t *)private_data;
+	
+	if (p)
+		snd_midi_channel_free_set(p->chset);
+}
+
+static int snd_gus_synth_create_port(snd_gus_card_t * gus, int idx)
+{
+	snd_gus_port_t * p;
+	snd_seq_port_callback_t callbacks;
+	char name[32];
+	int result;
+	
+	p = &gus->gf1.seq_ports[idx];
+	p->chset = snd_midi_channel_alloc_set(16);
+	if (p->chset == NULL)
+		return -ENOMEM;
+	p->chset->private_data = p;
+	p->gus = gus;
+	p->client = gus->gf1.seq_client;
+
+	memset(&callbacks, 0, sizeof(callbacks));
+	callbacks.owner = THIS_MODULE;
+	callbacks.use = snd_gus_synth_use;
+	callbacks.unuse = snd_gus_synth_unuse;
+	callbacks.event_input = snd_gus_synth_event_input;
+	callbacks.private_free = snd_gus_synth_free_port;
+	callbacks.private_data = p;
+	
+	sprintf(name, "%s port %i", gus->interwave ? "AMD InterWave" : "GF1", idx);
+	p->chset->port = snd_seq_event_port_attach(gus->gf1.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_gus_synth_free_port(p);
+		return result;
+	}
+	p->port = p->chset->port;
+	return 0;
+}						 
+
+/*
+ *
+ */
+
+static int snd_gus_synth_new_device(snd_seq_device_t *dev)
+{
+	snd_gus_card_t *gus;
+	int client, i;
+	snd_seq_client_callback_t callbacks;
+	snd_seq_client_info_t *cinfo;
+	snd_seq_port_subscribe_t sub;
+	snd_iwffff_ops_t *iwops;
+	snd_gf1_ops_t *gf1ops;
+	snd_simple_ops_t *simpleops;
+
+	gus = *(snd_gus_card_t**)SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	if (gus == NULL)
+		return -EINVAL;
+
+	init_MUTEX(&gus->register_mutex);
+	gus->gf1.seq_client = -1;
+	
+	cinfo = kmalloc(sizeof(*cinfo), GFP_KERNEL);
+	if (! cinfo)
+		return -ENOMEM;
+
+	/* allocate new client */
+	memset(&callbacks, 0, sizeof(callbacks));
+	callbacks.private_data = gus;
+	callbacks.allow_output = callbacks.allow_input = 1;
+	client = gus->gf1.seq_client =
+			snd_seq_create_kernel_client(gus->card, 1, &callbacks);
+	if (client < 0) {
+		kfree(cinfo);
+		return client;
+	}
+
+	/* change name of client */
+	memset(cinfo, 0, sizeof(*cinfo));
+	cinfo->client = client;
+	cinfo->type = KERNEL_CLIENT;
+	sprintf(cinfo->name, gus->interwave ? "AMD InterWave" : "GF1");
+	snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, cinfo);
+	kfree(cinfo);
+
+	for (i = 0; i < 4; i++)
+		snd_gus_synth_create_port(gus, i);
+		
+	gus->gf1.ilist = snd_seq_instr_list_new();
+	if (gus->gf1.ilist == NULL) {
+		snd_seq_delete_kernel_client(client);	
+		gus->gf1.seq_client = -1;
+		return -ENOMEM;
+	}
+	gus->gf1.ilist->flags = SNDRV_SEQ_INSTR_FLG_DIRECT;
+
+	simpleops = &gus->gf1.simple_ops;
+	snd_seq_simple_init(simpleops, gus, NULL);
+	simpleops->put_sample = snd_gus_simple_put_sample;
+	simpleops->get_sample = snd_gus_simple_get_sample;
+	simpleops->remove_sample = snd_gus_simple_remove_sample;
+	simpleops->notify = snd_gus_synth_instr_notify;
+
+	gf1ops = &gus->gf1.gf1_ops;
+	snd_seq_gf1_init(gf1ops, gus, &simpleops->kops);
+	gf1ops->put_sample = snd_gus_gf1_put_sample;
+	gf1ops->get_sample = snd_gus_gf1_get_sample;
+	gf1ops->remove_sample = snd_gus_gf1_remove_sample;
+	gf1ops->notify = snd_gus_synth_instr_notify;
+
+	iwops = &gus->gf1.iwffff_ops;
+	snd_seq_iwffff_init(iwops, gus, &gf1ops->kops);
+	iwops->put_sample = snd_gus_iwffff_put_sample;
+	iwops->get_sample = snd_gus_iwffff_get_sample;
+	iwops->remove_sample = snd_gus_iwffff_remove_sample;
+	iwops->notify = snd_gus_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_gus_synth_delete_device(snd_seq_device_t *dev)
+{
+	snd_gus_card_t *gus;
+
+	gus = *(snd_gus_card_t**)SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	if (gus == NULL)
+		return -EINVAL;
+
+	if (gus->gf1.seq_client >= 0) {
+		snd_seq_delete_kernel_client(gus->gf1.seq_client);	
+		gus->gf1.seq_client = -1;
+	}
+	if (gus->gf1.ilist)
+		snd_seq_instr_list_free(&gus->gf1.ilist);
+	return 0;
+}
+
+static int __init alsa_gus_synth_init(void)
+{
+	static snd_seq_dev_ops_t ops = {
+		snd_gus_synth_new_device,
+		snd_gus_synth_delete_device
+	};
+
+	return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_GUS, &ops,
+					      sizeof(snd_gus_card_t*));
+}
+
+static void __exit alsa_gus_synth_exit(void)
+{
+	snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_GUS);
+}
+
+module_init(alsa_gus_synth_init)
+module_exit(alsa_gus_synth_exit)
diff --git a/sound/isa/gus/gus_tables.h b/sound/isa/gus/gus_tables.h
new file mode 100644
index 0000000..ed8e9d8
--- /dev/null
+++ b/sound/isa/gus/gus_tables.h
@@ -0,0 +1,86 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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
+ *
+ */
+
+#define SNDRV_GF1_SCALE_TABLE_SIZE	128
+#define SNDRV_GF1_ATTEN_TABLE_SIZE	128
+
+#ifdef __GUS_TABLES_ALLOC__
+
+unsigned int snd_gf1_scale_table[SNDRV_GF1_SCALE_TABLE_SIZE] =
+{
+      8372,      8870,      9397,      9956,     10548,     11175,
+     11840,     12544,     13290,     14080,     14917,     15804,
+     16744,     17740,     18795,     19912,     21096,     22351,
+     23680,     25088,     26580,     28160,     29834,     31609,
+     33488,     35479,     37589,     39824,     42192,     44701,
+     47359,     50175,     53159,     56320,     59669,     63217,
+     66976,     70959,     75178,     79649,     84385,     89402,
+     94719,    100351,    106318,    112640,    119338,    126434,
+    133952,    141918,    150356,    159297,    168769,    178805,
+    189437,    200702,    212636,    225280,    238676,    252868,
+    267905,    283835,    300713,    318594,    337539,    357610,
+    378874,    401403,    425272,    450560,    477352,    505737,
+    535809,    567670,    601425,    637188,    675077,    715219,
+    757749,    802807,    850544,    901120,    954703,   1011473,
+   1071618,   1135340,   1202851,   1274376,   1350154,   1430439,
+   1515497,   1605613,   1701088,   1802240,   1909407,   2022946,
+   2143237,   2270680,   2405702,   2548752,   2700309,   2860878,
+   3030994,   3211227,   3402176,   3604480,   3818814,   4045892,
+   4286473,   4541360,   4811404,   5097505,   5400618,   5721755,
+   6061989,   6422453,   6804352,   7208960,   7637627,   8091784,
+   8572947,   9082720,   9622807,  10195009,  10801236,  11443511,
+  12123977,  12844906
+};
+
+unsigned short snd_gf1_atten_table[SNDRV_GF1_ATTEN_TABLE_SIZE] = {
+  4095 /* 0   */,1789 /* 1   */,1533 /* 2   */,1383 /* 3   */,1277 /* 4   */,
+  1195 /* 5   */,1127 /* 6   */,1070 /* 7   */,1021 /* 8   */,978  /* 9   */,
+  939  /* 10  */,903  /* 11  */,871  /* 12  */,842  /* 13  */,814  /* 14  */,
+  789  /* 15  */,765  /* 16  */,743  /* 17  */,722  /* 18  */,702  /* 19  */,
+  683  /* 20  */,665  /* 21  */,647  /* 22  */,631  /* 23  */,615  /* 24  */,
+  600  /* 25  */,586  /* 26  */,572  /* 27  */,558  /* 28  */,545  /* 29  */,
+  533  /* 30  */,521  /* 31  */,509  /* 32  */,498  /* 33  */,487  /* 34  */,
+  476  /* 35  */,466  /* 36  */,455  /* 37  */,446  /* 38  */,436  /* 39  */,
+  427  /* 40  */,418  /* 41  */,409  /* 42  */,400  /* 43  */,391  /* 44  */,
+  383  /* 45  */,375  /* 46  */,367  /* 47  */,359  /* 48  */,352  /* 49  */,
+  344  /* 50  */,337  /* 51  */,330  /* 52  */,323  /* 53  */,316  /* 54  */,
+  309  /* 55  */,302  /* 56  */,296  /* 57  */,289  /* 58  */,283  /* 59  */,
+  277  /* 60  */,271  /* 61  */,265  /* 62  */,259  /* 63  */,253  /* 64  */,
+  247  /* 65  */,242  /* 66  */,236  /* 67  */,231  /* 68  */,225  /* 69  */,
+  220  /* 70  */,215  /* 71  */,210  /* 72  */,205  /* 73  */,199  /* 74  */,
+  195  /* 75  */,190  /* 76  */,185  /* 77  */,180  /* 78  */,175  /* 79  */,
+  171  /* 80  */,166  /* 81  */,162  /* 82  */,157  /* 83  */,153  /* 84  */,
+  148  /* 85  */,144  /* 86  */,140  /* 87  */,135  /* 88  */,131  /* 89  */,
+  127  /* 90  */,123  /* 91  */,119  /* 92  */,115  /* 93  */,111  /* 94  */,
+  107  /* 95  */,103  /* 96  */,100  /* 97  */,96   /* 98  */,92   /* 99  */,
+  88   /* 100 */,85   /* 101 */,81   /* 102 */,77   /* 103 */,74   /* 104 */,
+  70   /* 105 */,67   /* 106 */,63   /* 107 */,60   /* 108 */,56   /* 109 */,
+  53   /* 110 */,50   /* 111 */,46   /* 112 */,43   /* 113 */,40   /* 114 */,
+  37   /* 115 */,33   /* 116 */,30   /* 117 */,27   /* 118 */,24   /* 119 */,
+  21   /* 120 */,18   /* 121 */,15   /* 122 */,12   /* 123 */,9    /* 124 */,
+  6    /* 125 */,3    /* 126 */,0    /* 127 */,
+};
+
+#else
+
+extern unsigned int snd_gf1_scale_table[SNDRV_GF1_SCALE_TABLE_SIZE];
+extern unsigned short snd_gf1_atten_table[SNDRV_GF1_ATTEN_TABLE_SIZE];
+
+#endif
diff --git a/sound/isa/gus/gus_timer.c b/sound/isa/gus/gus_timer.c
new file mode 100644
index 0000000..9876603
--- /dev/null
+++ b/sound/isa/gus/gus_timer.c
@@ -0,0 +1,204 @@
+/*
+ *  Routines for Gravis UltraSound soundcards - Timers
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *  GUS have similar timers as AdLib (OPL2/OPL3 chips).
+ *
+ *
+ *   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/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+/*
+ *  Timer 1 - 80us
+ */
+
+static int snd_gf1_timer1_start(snd_timer_t * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	unsigned int ticks;
+	snd_gus_card_t *gus;
+
+	gus = snd_timer_chip(timer);
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	ticks = timer->sticks;
+	tmp = (gus->gf1.timer_enabled |= 4);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_ADLIB_TIMER_1, 256 - ticks);	/* timer 1 count */
+	snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp);	/* enable timer 1 IRQ */
+	snd_gf1_adlib_write(gus, 0x04, tmp >> 2);	/* timer 2 start */
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return 0;
+}
+
+static int snd_gf1_timer1_stop(snd_timer_t * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	snd_gus_card_t *gus;
+
+	gus = snd_timer_chip(timer);
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	tmp = (gus->gf1.timer_enabled &= ~4);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp);	/* disable timer #1 */
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return 0;
+}
+
+/*
+ *  Timer 2 - 320us
+ */
+
+static int snd_gf1_timer2_start(snd_timer_t * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	unsigned int ticks;
+	snd_gus_card_t *gus;
+
+	gus = snd_timer_chip(timer);
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	ticks = timer->sticks;
+	tmp = (gus->gf1.timer_enabled |= 8);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_ADLIB_TIMER_2, 256 - ticks);	/* timer 2 count */
+	snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp);	/* enable timer 2 IRQ */
+	snd_gf1_adlib_write(gus, 0x04, tmp >> 2);	/* timer 2 start */
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return 0;
+}
+
+static int snd_gf1_timer2_stop(snd_timer_t * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	snd_gus_card_t *gus;
+
+	gus = snd_timer_chip(timer);
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	tmp = (gus->gf1.timer_enabled &= ~8);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp);	/* disable timer #1 */
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return 0;
+}
+
+/*
+
+ */
+
+static void snd_gf1_interrupt_timer1(snd_gus_card_t * gus)
+{
+	snd_timer_t *timer = gus->gf1.timer1;
+
+	if (timer == NULL)
+		return;
+	snd_timer_interrupt(timer, timer->sticks);
+}
+
+static void snd_gf1_interrupt_timer2(snd_gus_card_t * gus)
+{
+	snd_timer_t *timer = gus->gf1.timer2;
+
+	if (timer == NULL)
+		return;
+	snd_timer_interrupt(timer, timer->sticks);
+}
+
+/*
+
+ */
+
+static struct _snd_timer_hardware snd_gf1_timer1 =
+{
+	.flags =	SNDRV_TIMER_HW_STOP,
+	.resolution =	80000,
+	.ticks =	256,
+	.start =	snd_gf1_timer1_start,
+	.stop =		snd_gf1_timer1_stop,
+};
+
+static struct _snd_timer_hardware snd_gf1_timer2 =
+{
+	.flags =	SNDRV_TIMER_HW_STOP,
+	.resolution =	320000,
+	.ticks =	256,
+	.start =	snd_gf1_timer2_start,
+	.stop =		snd_gf1_timer2_stop,
+};
+
+static void snd_gf1_timer1_free(snd_timer_t *timer)
+{
+	snd_gus_card_t *gus = timer->private_data;
+	gus->gf1.timer1 = NULL;
+}
+
+static void snd_gf1_timer2_free(snd_timer_t *timer)
+{
+	snd_gus_card_t *gus = timer->private_data;
+	gus->gf1.timer2 = NULL;
+}
+
+void snd_gf1_timers_init(snd_gus_card_t * gus)
+{
+	snd_timer_t *timer;
+	snd_timer_id_t tid;
+
+	if (gus->gf1.timer1 != NULL || gus->gf1.timer2 != NULL)
+		return;
+
+	gus->gf1.interrupt_handler_timer1 = snd_gf1_interrupt_timer1;
+	gus->gf1.interrupt_handler_timer2 = snd_gf1_interrupt_timer2;
+
+	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
+	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+	tid.card = gus->card->number;
+	tid.device = gus->timer_dev;
+	tid.subdevice = 0;
+
+	if (snd_timer_new(gus->card, "GF1 timer", &tid, &timer) >= 0) {
+		strcpy(timer->name, "GF1 timer #1");
+		timer->private_data = gus;
+		timer->private_free = snd_gf1_timer1_free;
+		timer->hw = snd_gf1_timer1;
+	}
+	gus->gf1.timer1 = timer;
+
+	tid.device++;
+
+	if (snd_timer_new(gus->card, "GF1 timer", &tid, &timer) >= 0) {
+		strcpy(timer->name, "GF1 timer #2");
+		timer->private_data = gus;
+		timer->private_free = snd_gf1_timer2_free;
+		timer->hw = snd_gf1_timer2;
+	}
+	gus->gf1.timer2 = timer;
+}
+
+void snd_gf1_timers_done(snd_gus_card_t * gus)
+{
+	snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_TIMER1 | SNDRV_GF1_HANDLER_TIMER2);
+	if (gus->gf1.timer1) {
+		snd_device_free(gus->card, gus->gf1.timer1);
+		gus->gf1.timer1 = NULL;
+	}
+	if (gus->gf1.timer2) {
+		snd_device_free(gus->card, gus->gf1.timer2);
+		gus->gf1.timer2 = NULL;
+	}
+}
diff --git a/sound/isa/gus/gus_uart.c b/sound/isa/gus/gus_uart.c
new file mode 100644
index 0000000..1bc2da8
--- /dev/null
+++ b/sound/isa/gus/gus_uart.c
@@ -0,0 +1,257 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  Routines for the GF1 MIDI interface - like UART 6850
+ *
+ *
+ *   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/delay.h>
+#include <linux/interrupt.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+static void snd_gf1_interrupt_midi_in(snd_gus_card_t * gus)
+{
+	int count;
+	unsigned char stat, data, byte;
+	unsigned long flags;
+
+	count = 10;
+	while (count) {
+		spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+		stat = snd_gf1_uart_stat(gus);
+		if (!(stat & 0x01)) {	/* data in Rx FIFO? */
+			spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+			count--;
+			continue;
+		}
+		count = 100;	/* arm counter to new value */
+		data = snd_gf1_uart_get(gus);
+		if (!(gus->gf1.uart_cmd & 0x80)) {
+			spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+			continue;
+		}			
+		if (stat & 0x10) {	/* framing error */
+			gus->gf1.uart_framing++;
+			spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+			continue;
+		}
+		byte = snd_gf1_uart_get(gus);
+		spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+		snd_rawmidi_receive(gus->midi_substream_input, &byte, 1);
+		if (stat & 0x20) {
+			gus->gf1.uart_overrun++;
+		}
+	}
+}
+
+static void snd_gf1_interrupt_midi_out(snd_gus_card_t * gus)
+{
+	char byte;
+	unsigned long flags;
+
+	/* try unlock output */
+	if (snd_gf1_uart_stat(gus) & 0x01)
+		snd_gf1_interrupt_midi_in(gus);
+
+	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+	if (snd_gf1_uart_stat(gus) & 0x02) {	/* Tx FIFO free? */
+		if (snd_rawmidi_transmit(gus->midi_substream_output, &byte, 1) != 1) {	/* no other bytes or error */
+			snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x20); /* disable Tx interrupt */
+		} else {
+			snd_gf1_uart_put(gus, byte);
+		}
+	}
+	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+}
+
+static void snd_gf1_uart_reset(snd_gus_card_t * gus, int close)
+{
+	snd_gf1_uart_cmd(gus, 0x03);	/* reset */
+	if (!close && gus->uart_enable) {
+		udelay(160);
+		snd_gf1_uart_cmd(gus, 0x00);	/* normal operations */
+	}
+}
+
+static int snd_gf1_uart_output_open(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	snd_gus_card_t *gus;
+
+	gus = substream->rmidi->private_data;
+	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+	if (!(gus->gf1.uart_cmd & 0x80)) {	/* input active? */
+		snd_gf1_uart_reset(gus, 0);
+	}
+	gus->gf1.interrupt_handler_midi_out = snd_gf1_interrupt_midi_out;
+	gus->midi_substream_output = substream;
+	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+#if 0
+	snd_printk("write init - cmd = 0x%x, stat = 0x%x\n", gus->gf1.uart_cmd, snd_gf1_uart_stat(gus));
+#endif
+	return 0;
+}
+
+static int snd_gf1_uart_input_open(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	snd_gus_card_t *gus;
+	int i;
+
+	gus = substream->rmidi->private_data;
+	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+	if (gus->gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out) {
+		snd_gf1_uart_reset(gus, 0);
+	}
+	gus->gf1.interrupt_handler_midi_in = snd_gf1_interrupt_midi_in;
+	gus->midi_substream_input = substream;
+	if (gus->uart_enable) {
+		for (i = 0; i < 1000 && (snd_gf1_uart_stat(gus) & 0x01); i++)
+			snd_gf1_uart_get(gus);	/* clean Rx */
+		if (i >= 1000)
+			snd_printk("gus midi uart init read - cleanup error\n");
+	}
+	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+#if 0
+	snd_printk("read init - enable = %i, cmd = 0x%x, stat = 0x%x\n", gus->uart_enable, gus->gf1.uart_cmd, snd_gf1_uart_stat(gus));
+	snd_printk("[0x%x] reg (ctrl/status) = 0x%x, reg (data) = 0x%x (page = 0x%x)\n", gus->gf1.port + 0x100, inb(gus->gf1.port + 0x100), inb(gus->gf1.port + 0x101), inb(gus->gf1.port + 0x102));
+#endif
+	return 0;
+}
+
+static int snd_gf1_uart_output_close(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	snd_gus_card_t *gus;
+
+	gus = substream->rmidi->private_data;
+	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+	if (gus->gf1.interrupt_handler_midi_in != snd_gf1_interrupt_midi_in)
+		snd_gf1_uart_reset(gus, 1);
+	snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_MIDI_OUT);
+	gus->midi_substream_output = NULL;
+	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+	return 0;
+}
+
+static int snd_gf1_uart_input_close(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	snd_gus_card_t *gus;
+
+	gus = substream->rmidi->private_data;
+	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+	if (gus->gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out)
+		snd_gf1_uart_reset(gus, 1);
+	snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_MIDI_IN);
+	gus->midi_substream_input = NULL;
+	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+	return 0;
+}
+
+static void snd_gf1_uart_input_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+	snd_gus_card_t *gus;
+	unsigned long flags;
+
+	gus = substream->rmidi->private_data;
+
+	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+	if (up) {
+		if ((gus->gf1.uart_cmd & 0x80) == 0)
+			snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd | 0x80); /* enable Rx interrupts */
+	} else {
+		if (gus->gf1.uart_cmd & 0x80)
+			snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x80); /* disable Rx interrupts */
+	}
+	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+}
+
+static void snd_gf1_uart_output_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+	unsigned long flags;
+	snd_gus_card_t *gus;
+	char byte;
+	int timeout;
+
+	gus = substream->rmidi->private_data;
+
+	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+	if (up) {
+		if ((gus->gf1.uart_cmd & 0x20) == 0) {
+			spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+			/* wait for empty Rx - Tx is probably unlocked */
+			timeout = 10000;
+			while (timeout-- > 0 && snd_gf1_uart_stat(gus) & 0x01);
+			/* Tx FIFO free? */
+			spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+			if (gus->gf1.uart_cmd & 0x20) {
+				spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+				return;
+			}
+			if (snd_gf1_uart_stat(gus) & 0x02) {
+				if (snd_rawmidi_transmit(substream, &byte, 1) != 1) {
+					spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+					return;
+				}
+				snd_gf1_uart_put(gus, byte);
+			}
+			snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd | 0x20);	/* enable Tx interrupt */
+		}
+	} else {
+		if (gus->gf1.uart_cmd & 0x20)
+			snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x20);
+	}
+	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+}
+
+static snd_rawmidi_ops_t snd_gf1_uart_output =
+{
+	.open =		snd_gf1_uart_output_open,
+	.close =	snd_gf1_uart_output_close,
+	.trigger =	snd_gf1_uart_output_trigger,
+};
+
+static snd_rawmidi_ops_t snd_gf1_uart_input =
+{
+	.open =		snd_gf1_uart_input_open,
+	.close =	snd_gf1_uart_input_close,
+	.trigger =	snd_gf1_uart_input_trigger,
+};
+
+int snd_gf1_rawmidi_new(snd_gus_card_t * gus, int device, snd_rawmidi_t ** rrawmidi)
+{
+	snd_rawmidi_t *rmidi;
+	int err;
+
+	if (rrawmidi)
+		*rrawmidi = NULL;
+	if ((err = snd_rawmidi_new(gus->card, "GF1", device, 1, 1, &rmidi)) < 0)
+		return err;
+	strcpy(rmidi->name, gus->interwave ? "AMD InterWave" : "GF1");
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_gf1_uart_output);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_gf1_uart_input);
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX;
+	rmidi->private_data = gus;
+	gus->midi_uart = rmidi;
+	if (rrawmidi)
+		*rrawmidi = rmidi;
+	return err;
+}
diff --git a/sound/isa/gus/gus_volume.c b/sound/isa/gus/gus_volume.c
new file mode 100644
index 0000000..b72bcfb
--- /dev/null
+++ b/sound/isa/gus/gus_volume.c
@@ -0,0 +1,210 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#define __GUS_TABLES_ALLOC__
+#include "gus_tables.h"
+
+EXPORT_SYMBOL(snd_gf1_atten_table); /* for snd-gus-synth module */
+
+unsigned short snd_gf1_lvol_to_gvol_raw(unsigned int vol)
+{
+	unsigned short e, m, tmp;
+
+	if (vol > 65535)
+		vol = 65535;
+	tmp = vol;
+	e = 7;
+	if (tmp < 128) {
+		while (e > 0 && tmp < (1 << e))
+			e--;
+	} else {
+		while (tmp > 255) {
+			tmp >>= 1;
+			e++;
+		}
+	}
+	m = vol - (1 << e);
+	if (m > 0) {
+		if (e > 8)
+			m >>= e - 8;
+		else if (e < 8)
+			m <<= 8 - e;
+		m &= 255;
+	}
+	return (e << 8) | m;
+}
+
+unsigned int snd_gf1_gvol_to_lvol_raw(unsigned short gf1_vol)
+{
+	unsigned int rvol;
+	unsigned short e, m;
+
+	if (!gf1_vol)
+		return 0;
+	e = gf1_vol >> 8;
+	m = (unsigned char) gf1_vol;
+	rvol = 1 << e;
+	if (e > 8)
+		return rvol | (m << (e - 8));
+	return rvol | (m >> (8 - e));
+}
+
+unsigned int snd_gf1_calc_ramp_rate(snd_gus_card_t * gus,
+				    unsigned short start,
+				    unsigned short end,
+				    unsigned int us)
+{
+	static unsigned char vol_rates[19] =
+	{
+		23, 24, 26, 28, 29, 31, 32, 34,
+		36, 37, 39, 40, 42, 44, 45, 47,
+		49, 50, 52
+	};
+	unsigned short range, increment, value, i;
+
+	start >>= 4;
+	end >>= 4;
+	if (start < end)
+		us /= end - start;
+	else
+		us /= start - end;
+	range = 4;
+	value = gus->gf1.enh_mode ?
+	    vol_rates[0] :
+	    vol_rates[gus->gf1.active_voices - 14];
+	for (i = 0; i < 3; i++) {
+		if (us < value) {
+			range = i;
+			break;
+		} else
+			value <<= 3;
+	}
+	if (range == 4) {
+		range = 3;
+		increment = 1;
+	} else
+		increment = (value + (value >> 1)) / us;
+	return (range << 6) | (increment & 0x3f);
+}
+
+unsigned short snd_gf1_translate_freq(snd_gus_card_t * gus, unsigned int freq16)
+{
+	freq16 >>= 3;
+	if (freq16 < 50)
+		freq16 = 50;
+	if (freq16 & 0xf8000000) {
+		freq16 = ~0xf8000000;
+		snd_printk("snd_gf1_translate_freq: overflow - freq = 0x%x\n", freq16);
+	}
+	return ((freq16 << 9) + (gus->gf1.playback_freq >> 1)) / gus->gf1.playback_freq;
+}
+
+short snd_gf1_compute_vibrato(short cents, unsigned short fc_register)
+{
+	static short vibrato_table[] =
+	{
+		0, 0, 32, 592, 61, 1175, 93, 1808,
+		124, 2433, 152, 3007, 182, 3632, 213, 4290,
+		241, 4834, 255, 5200
+	};
+
+	long depth;
+	short *vi1, *vi2, pcents, v1;
+
+	pcents = cents < 0 ? -cents : cents;
+	for (vi1 = vibrato_table, vi2 = vi1 + 2; pcents > *vi2; vi1 = vi2, vi2 += 2);
+	v1 = *(vi1 + 1);
+	/* The FC table above is a list of pairs. The first number in the pair     */
+	/* is the cents index from 0-255 cents, and the second number in the       */
+	/* pair is the FC adjustment needed to change the pitch by the indexed     */
+	/* number of cents. The table was created for an FC of 32768.              */
+	/* The following expression does a linear interpolation against the        */
+	/* approximated log curve in the table above, and then scales the number   */
+	/* by the FC before the LFO. This calculation also adjusts the output      */
+	/* value to produce the appropriate depth for the hardware. The depth      */
+	/* is 2 * desired FC + 1.                                                  */
+	depth = (((int) (*(vi2 + 1) - *vi1) * (pcents - *vi1) / (*vi2 - *vi1)) + v1) * fc_register >> 14;
+	if (depth)
+		depth++;
+	if (depth > 255)
+		depth = 255;
+	return cents < 0 ? -(short) depth : (short) depth;
+}
+
+unsigned short snd_gf1_compute_pitchbend(unsigned short pitchbend, unsigned short sens)
+{
+	static long log_table[] = {1024, 1085, 1149, 1218, 1290, 1367, 1448, 1534, 1625, 1722, 1825, 1933};
+	int wheel, sensitivity;
+	unsigned int mantissa, f1, f2;
+	unsigned short semitones, f1_index, f2_index, f1_power, f2_power;
+	char bend_down = 0;
+	int bend;
+
+	if (!sens)
+		return 1024;
+	wheel = (int) pitchbend - 8192;
+	sensitivity = ((int) sens * wheel) / 128;
+	if (sensitivity < 0) {
+		bend_down = 1;
+		sensitivity = -sensitivity;
+	}
+	semitones = (unsigned int) (sensitivity >> 13);
+	mantissa = sensitivity % 8192;
+	f1_index = semitones % 12;
+	f2_index = (semitones + 1) % 12;
+	f1_power = semitones / 12;
+	f2_power = (semitones + 1) / 12;
+	f1 = log_table[f1_index] << f1_power;
+	f2 = log_table[f2_index] << f2_power;
+	bend = (int) ((((f2 - f1) * mantissa) >> 13) + f1);
+	if (bend_down)
+		bend = 1048576L / bend;
+	return bend;
+}
+
+unsigned short snd_gf1_compute_freq(unsigned int freq,
+				    unsigned int rate,
+				    unsigned short mix_rate)
+{
+	unsigned int fc;
+	int scale = 0;
+
+	while (freq >= 4194304L) {
+		scale++;
+		freq >>= 1;
+	}
+	fc = (freq << 10) / rate;
+	if (fc > 97391L) {
+		fc = 97391;
+		snd_printk("patch: (1) fc frequency overflow - %u\n", fc);
+	}
+	fc = (fc * 44100UL) / mix_rate;
+	while (scale--)
+		fc <<= 1;
+	if (fc > 65535L) {
+		fc = 65535;
+		snd_printk("patch: (2) fc frequency overflow - %u\n", fc);
+	}
+	return (unsigned short) fc;
+}
diff --git a/sound/isa/gus/gusclassic.c b/sound/isa/gus/gusclassic.c
new file mode 100644
index 0000000..a99fa50
--- /dev/null
+++ b/sound/isa/gus/gusclassic.c
@@ -0,0 +1,260 @@
+/*
+ *  Driver for Gravis UltraSound Classic soundcard
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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/dma.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#define SNDRV_LEGACY_AUTO_PROBE
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Gravis UltraSound Classic");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Gravis,UltraSound Classic}}");
+
+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;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x230,0x240,0x250,0x260 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 3,5,9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 1,3,5,6,7 */
+static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29};
+				/* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */
+static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24};
+static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for GUS Classic soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for GUS Classic soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable GUS Classic soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for GUS Classic driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for GUS Classic driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for GUS Classic driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for GUS Classic driver.");
+module_param_array(joystick_dac, int, NULL, 0444);
+MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for GUS Classic driver.");
+module_param_array(channels, int, NULL, 0444);
+MODULE_PARM_DESC(channels, "GF1 channels for GUS Classic driver.");
+module_param_array(pcm_channels, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for GUS Classic driver.");
+
+static snd_card_t *snd_gusclassic_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+
+static int __init snd_gusclassic_detect(snd_gus_card_t * gus)
+{
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0);	/* reset GF1 */
+#ifdef CONFIG_SND_DEBUG_DETECT
+	{
+		unsigned char d;
+
+		if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) {
+			snd_printk("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d);
+			return -ENODEV;
+		}
+	}
+#else
+	if ((snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET) & 0x07) != 0)
+		return -ENODEV;
+#endif
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);	/* release reset */
+	udelay(160);
+#ifdef CONFIG_SND_DEBUG_DETECT
+	{
+		unsigned char d;
+
+		if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) {
+			snd_printk("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d);
+			return -ENODEV;
+		}
+	}
+#else
+	if ((snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET) & 0x07) != 1)
+		return -ENODEV;
+#endif
+
+	return 0;
+}
+
+static void __init snd_gusclassic_init(int dev, snd_gus_card_t * gus)
+{
+	gus->equal_irq = 0;
+	gus->codec_flag = 0;
+	gus->max_flag = 0;
+	gus->joystick_dac = joystick_dac[dev];
+}
+
+static int __init snd_gusclassic_probe(int dev)
+{
+	static int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, 4, -1};
+	static int possible_dmas[] = {5, 6, 7, 1, 3, -1};
+	int xirq, xdma1, xdma2;
+	snd_card_t *card;
+	struct snd_gusclassic *guscard;
+	snd_gus_card_t *gus = NULL;
+	int err;
+
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
+	if (card == NULL)
+		return -ENOMEM;
+	guscard = (struct snd_gusclassic *)card->private_data;
+	if (pcm_channels[dev] < 2)
+		pcm_channels[dev] = 2;
+
+	xirq = irq[dev];
+	if (xirq == SNDRV_AUTO_IRQ) {
+		if ((xirq = snd_legacy_find_free_irq(possible_irqs)) < 0) {
+			snd_card_free(card);
+			snd_printk("unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	xdma1 = dma1[dev];
+	if (xdma1 == SNDRV_AUTO_DMA) {
+		if ((xdma1 = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_card_free(card);
+			snd_printk("unable to find a free DMA1\n");
+			return -EBUSY;
+		}
+	}
+	xdma2 = dma2[dev];
+	if (xdma2 == SNDRV_AUTO_DMA) {
+		if ((xdma2 = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_card_free(card);
+			snd_printk("unable to find a free DMA2\n");
+			return -EBUSY;
+		}
+	}
+
+
+	if ((err = snd_gus_create(card,
+				  port[dev],
+				  xirq, xdma1, xdma2,
+			          0, channels[dev], pcm_channels[dev],
+			          0, &gus)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_gusclassic_detect(gus)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	snd_gusclassic_init(dev, gus);
+	if ((err = snd_gus_initialize(gus)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (gus->max_flag || gus->ess_flag) {
+		snd_printdd("GUS Classic or ACE soundcard was not detected at 0x%lx\n", gus->gf1.port);
+		snd_card_free(card);
+		return -ENODEV;
+	}
+	if ((err = snd_gf1_new_mixer(gus)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_gf1_pcm_new(gus, 0, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (!gus->ace_flag) {
+		if ((err = snd_gf1_rawmidi_new(gus, 0, NULL)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+	}
+	sprintf(card->longname + strlen(card->longname), " at 0x%lx, irq %d, dma %d", gus->gf1.port, xirq, xdma1);
+	if (dma2 >= 0)
+		sprintf(card->longname + strlen(card->longname), "&%d", xdma2);
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	snd_gusclassic_cards[dev] = card;
+	return 0;
+}
+
+static int __init snd_gusclassic_legacy_auto_probe(unsigned long xport)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || port[dev] != SNDRV_AUTO_PORT)
+			continue;
+		port[dev] = xport;
+		res = snd_gusclassic_probe(dev);
+		if (res < 0)
+			port[dev] = SNDRV_AUTO_PORT;
+		return res;
+	}
+	return -ENODEV;
+}
+
+static int __init alsa_card_gusclassic_init(void)
+{
+	static unsigned long possible_ports[] = {0x220, 0x230, 0x240, 0x250, 0x260, -1};
+	int dev, cards, i;
+
+	for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev]; dev++) {
+		if (port[dev] == SNDRV_AUTO_PORT)
+			continue;
+		if (snd_gusclassic_probe(dev) >= 0)
+			cards++;
+	}
+	i = snd_legacy_auto_probe(possible_ports, snd_gusclassic_legacy_auto_probe);
+	if (i > 0)
+		cards += i;
+
+	if (!cards) {
+#ifdef MODULE
+		printk(KERN_ERR "GUS Classic soundcard not found or device busy\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_gusclassic_exit(void)
+{
+	int idx;
+	
+	for (idx = 0; idx < SNDRV_CARDS; idx++)
+		snd_card_free(snd_gusclassic_cards[idx]);
+}
+
+module_init(alsa_card_gusclassic_init)
+module_exit(alsa_card_gusclassic_exit)
diff --git a/sound/isa/gus/gusextreme.c b/sound/isa/gus/gusextreme.c
new file mode 100644
index 0000000..bc6fecb
--- /dev/null
+++ b/sound/isa/gus/gusextreme.c
@@ -0,0 +1,374 @@
+/*
+ *  Driver for Gravis UltraSound Extreme soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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/dma.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/es1688.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#define SNDRV_LEGACY_AUTO_PROBE
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Gravis UltraSound Extreme");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Gravis,UltraSound Extreme}}");
+
+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;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x240,0x260 */
+static long gf1_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS) - 1] = -1}; /* 0x210,0x220,0x230,0x240,0x250,0x260,0x270 */
+static long mpu_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS) - 1] = -1}; /* 0x300,0x310,0x320 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,10 */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,10 */
+static int gf1_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 2,3,5,9,11,12,15 */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29};
+				/* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */
+static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24};
+static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for GUS Extreme soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for GUS Extreme soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable GUS Extreme soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for GUS Extreme driver.");
+module_param_array(gf1_port, long, NULL, 0444);
+MODULE_PARM_DESC(gf1_port, "GF1 port # for GUS Extreme driver (optional).");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for GUS Extreme driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for GUS Extreme driver.");
+module_param_array(mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for GUS Extreme driver.");
+module_param_array(gf1_irq, int, NULL, 0444);
+MODULE_PARM_DESC(gf1_irq, "GF1 IRQ # for GUS Extreme driver.");
+module_param_array(dma8, int, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for GUS Extreme driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "GF1 DMA # for GUS Extreme driver.");
+module_param_array(joystick_dac, int, NULL, 0444);
+MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for GUS Extreme driver.");
+module_param_array(channels, int, NULL, 0444);
+MODULE_PARM_DESC(channels, "GF1 channels for GUS Extreme driver.");
+module_param_array(pcm_channels, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for GUS Extreme driver.");
+
+static snd_card_t *snd_gusextreme_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+
+static int __init snd_gusextreme_detect(int dev,
+					snd_card_t * card,
+					snd_gus_card_t * gus,
+					es1688_t *es1688)
+{
+	unsigned long flags;
+
+	/*
+	 * This is main stuff - enable access to GF1 chip...
+	 * I'm not sure, if this will work for card which have
+	 * ES1688 chip in another place than 0x220.
+         *
+         * I used reverse-engineering in DOSEMU. [--jk]
+	 *
+	 * ULTRINIT.EXE:
+	 * 0x230 = 0,2,3
+	 * 0x240 = 2,0,1
+	 * 0x250 = 2,0,3
+	 * 0x260 = 2,2,1
+	 */
+
+	spin_lock_irqsave(&es1688->mixer_lock, flags);
+	snd_es1688_mixer_write(es1688, 0x40, 0x0b);	/* don't change!!! */
+	spin_unlock_irqrestore(&es1688->mixer_lock, flags);
+	spin_lock_irqsave(&es1688->reg_lock, flags);
+	outb(gf1_port[dev] & 0x040 ? 2 : 0, ES1688P(es1688, INIT1));
+	outb(0, 0x201);
+	outb(gf1_port[dev] & 0x020 ? 2 : 0, ES1688P(es1688, INIT1));
+	outb(0, 0x201);
+	outb(gf1_port[dev] & 0x010 ? 3 : 1, ES1688P(es1688, INIT1));
+	spin_unlock_irqrestore(&es1688->reg_lock, flags);
+
+	udelay(100);
+
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0);	/* reset GF1 */
+#ifdef CONFIG_SND_DEBUG_DETECT
+	{
+		unsigned char d;
+
+		if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) {
+			snd_printk("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d);
+			return -EIO;
+		}
+	}
+#else
+	if ((snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET) & 0x07) != 0)
+		return -EIO;
+#endif
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);	/* release reset */
+	udelay(160);
+#ifdef CONFIG_SND_DEBUG_DETECT
+	{
+		unsigned char d;
+
+		if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) {
+			snd_printk("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d);
+			return -EIO;
+		}
+	}
+#else
+	if ((snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET) & 0x07) != 1)
+		return -EIO;
+#endif
+
+	return 0;
+}
+
+static void __init snd_gusextreme_init(int dev, snd_gus_card_t * gus)
+{
+	gus->joystick_dac = joystick_dac[dev];
+}
+
+static int __init snd_gusextreme_mixer(es1688_t *chip)
+{
+	snd_card_t *card = chip->card;
+	snd_ctl_elem_id_t id1, id2;
+	int err;
+
+	memset(&id1, 0, sizeof(id1));
+	memset(&id2, 0, sizeof(id2));
+	id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	/* reassign AUX to SYNTHESIZER */
+	strcpy(id1.name, "Aux Playback Volume");
+	strcpy(id2.name, "Synth Playback Volume");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	/* reassign Master Playback Switch to Synth Playback Switch */
+	strcpy(id1.name, "Master Playback Switch");
+	strcpy(id2.name, "Synth Playback Switch");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	return 0;
+}
+
+static int __init snd_gusextreme_probe(int dev)
+{
+	static int possible_ess_irqs[] = {5, 9, 10, 7, -1};
+	static int possible_ess_dmas[] = {1, 3, 0, -1};
+	static int possible_gf1_irqs[] = {5, 11, 12, 9, 7, 15, 3, -1};
+	static int possible_gf1_dmas[] = {5, 6, 7, 1, 3, -1};
+	int xgf1_irq, xgf1_dma, xess_irq, xmpu_irq, xess_dma;
+	snd_card_t *card;
+	struct snd_gusextreme *acard;
+	snd_gus_card_t *gus;
+	es1688_t *es1688;
+	opl3_t *opl3;
+	int err;
+
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
+	if (card == NULL)
+		return -ENOMEM;
+	acard = (struct snd_gusextreme *)card->private_data;
+
+	xgf1_irq = gf1_irq[dev];
+	if (xgf1_irq == SNDRV_AUTO_IRQ) {
+		if ((xgf1_irq = snd_legacy_find_free_irq(possible_gf1_irqs)) < 0) {
+			snd_printk("unable to find a free IRQ for GF1\n");
+			err = -EBUSY;
+			goto out;
+		}
+	}
+	xess_irq = irq[dev];
+	if (xess_irq == SNDRV_AUTO_IRQ) {
+		if ((xess_irq = snd_legacy_find_free_irq(possible_ess_irqs)) < 0) {
+			snd_printk("unable to find a free IRQ for ES1688\n");
+			err = -EBUSY;
+			goto out;
+		}
+	}
+	if (mpu_port[dev] == SNDRV_AUTO_PORT)
+		mpu_port[dev] = 0;
+	xmpu_irq = mpu_irq[dev];
+	if (xmpu_irq > 15)
+		xmpu_irq = -1;
+	xgf1_dma = dma1[dev];
+	if (xgf1_dma == SNDRV_AUTO_DMA) {
+		if ((xgf1_dma = snd_legacy_find_free_dma(possible_gf1_dmas)) < 0) {
+			snd_printk("unable to find a free DMA for GF1\n");
+			err = -EBUSY;
+			goto out;
+		}
+	}
+	xess_dma = dma8[dev];
+	if (xess_dma == SNDRV_AUTO_DMA) {
+		if ((xess_dma = snd_legacy_find_free_dma(possible_ess_dmas)) < 0) {
+			snd_printk("unable to find a free DMA for ES1688\n");
+			err = -EBUSY;
+			goto out;
+		}
+	}
+
+	if ((err = snd_es1688_create(card, port[dev], mpu_port[dev],
+				     xess_irq, xmpu_irq, xess_dma,
+				     ES1688_HW_1688, &es1688)) < 0)
+		goto out;
+	if (gf1_port[dev] < 0)
+		gf1_port[dev] = port[dev] + 0x20;
+	if ((err = snd_gus_create(card,
+				  gf1_port[dev],
+				  xgf1_irq,
+				  xgf1_dma,
+				  -1,
+				  0, channels[dev],
+				  pcm_channels[dev], 0,
+				  &gus)) < 0)
+		goto out;
+
+	if ((err = snd_gusextreme_detect(dev, card, gus, es1688)) < 0)
+		goto out;
+
+	snd_gusextreme_init(dev, gus);
+	if ((err = snd_gus_initialize(gus)) < 0)
+		goto out;
+
+	if (!gus->ess_flag) {
+		snd_printdd("GUS Extreme soundcard was not detected at 0x%lx\n", gus->gf1.port);
+		err = -ENODEV;
+		goto out;
+	}
+	if ((err = snd_es1688_pcm(es1688, 0, NULL)) < 0)
+		goto out;
+
+	if ((err = snd_es1688_mixer(es1688)) < 0)
+		goto out;
+
+	snd_component_add(card, "ES1688");
+	if (pcm_channels[dev] > 0) {
+		if ((err = snd_gf1_pcm_new(gus, 1, 1, NULL)) < 0)
+			goto out;
+	}
+	if ((err = snd_gf1_new_mixer(gus)) < 0)
+		goto out;
+
+	if ((err = snd_gusextreme_mixer(es1688)) < 0)
+		goto out;
+
+	if (snd_opl3_create(card, es1688->port, es1688->port + 2,
+			    OPL3_HW_OPL3, 0, &opl3) < 0) {
+		printk(KERN_ERR "gusextreme: opl3 not detected at 0x%lx\n", es1688->port);
+	} else {
+		if ((err = snd_opl3_hwdep_new(opl3, 0, 2, NULL)) < 0)
+			goto out;
+	}
+
+	if (es1688->mpu_port >= 0x300 &&
+	    (err = snd_mpu401_uart_new(card, 0, MPU401_HW_ES1688,
+					       es1688->mpu_port, 0,
+					       xmpu_irq,
+					       SA_INTERRUPT,
+					       NULL)) < 0)
+		goto out;
+
+	sprintf(card->longname, "Gravis UltraSound Extreme at 0x%lx, irq %i&%i, dma %i&%i",
+		es1688->port, xgf1_irq, xess_irq, xgf1_dma, xess_dma);
+	if ((err = snd_card_register(card)) < 0)
+		goto out;
+
+	snd_gusextreme_cards[dev] = card;
+	return 0;
+
+      out:
+	snd_card_free(card);
+	return err;
+}
+
+static int __init snd_gusextreme_legacy_auto_probe(unsigned long xport)
+{
+        static int dev;
+        int res;
+
+        for ( ; dev < SNDRV_CARDS; dev++) {
+                if (!enable[dev] || port[dev] != SNDRV_AUTO_PORT)
+                        continue;
+                port[dev] = xport;
+                res = snd_gusextreme_probe(dev);
+                if (res < 0)
+                        port[dev] = SNDRV_AUTO_PORT;
+                return res;
+        }
+        return -ENODEV;
+}
+
+static int __init alsa_card_gusextreme_init(void)
+{
+	static unsigned long possible_ports[] = {0x220, 0x240, 0x260, -1};
+	int dev, cards, i;
+
+	for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev] > 0; dev++) {
+		if (port[dev] == SNDRV_AUTO_PORT)
+			continue;
+		if (snd_gusextreme_probe(dev) >= 0)
+			cards++;
+	}
+	i = snd_legacy_auto_probe(possible_ports, snd_gusextreme_legacy_auto_probe);
+	if (i > 0)
+		cards += i;
+
+	if (!cards) {
+#ifdef MODULE
+		printk(KERN_ERR "GUS Extreme soundcard not found or device busy\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_gusextreme_exit(void)
+{
+	int idx;
+	snd_card_t *card;
+	struct snd_gusextreme *acard;
+
+	for (idx = 0; idx < SNDRV_CARDS; idx++) {
+		card = snd_gusextreme_cards[idx];
+		if (card == NULL)
+			continue;
+		acard = (struct snd_gusextreme *)card->private_data;
+		snd_card_free(snd_gusextreme_cards[idx]);
+	}
+}
+
+module_init(alsa_card_gusextreme_init)
+module_exit(alsa_card_gusextreme_exit)
diff --git a/sound/isa/gus/gusmax.c b/sound/isa/gus/gusmax.c
new file mode 100644
index 0000000..400ff34
--- /dev/null
+++ b/sound/isa/gus/gusmax.c
@@ -0,0 +1,400 @@
+/*
+ *  Driver for Gravis UltraSound MAX soundcard
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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/dma.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/cs4231.h>
+#define SNDRV_LEGACY_AUTO_PROBE
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Gravis UltraSound MAX");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Gravis,UltraSound MAX}}");
+
+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;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x230,0x240,0x250,0x260 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 2,3,5,9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 1,3,5,6,7 */
+static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29};
+				/* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */
+static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24};
+static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for GUS MAX soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for GUS MAX soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable GUS MAX soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for GUS MAX driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for GUS MAX driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for GUS MAX driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for GUS MAX driver.");
+module_param_array(joystick_dac, int, NULL, 0444);
+MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for GUS MAX driver.");
+module_param_array(channels, int, NULL, 0444);
+MODULE_PARM_DESC(channels, "Used GF1 channels for GUS MAX driver.");
+module_param_array(pcm_channels, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for GUS MAX driver.");
+
+struct snd_gusmax {
+	int irq;
+	snd_card_t *card;
+	snd_gus_card_t *gus;
+	cs4231_t *cs4231;
+	unsigned short gus_status_reg;
+	unsigned short pcm_status_reg;
+};
+
+static snd_card_t *snd_gusmax_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+
+static int __init snd_gusmax_detect(snd_gus_card_t * gus)
+{
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0);	/* reset GF1 */
+#ifdef CONFIG_SND_DEBUG_DETECT
+	{
+		unsigned char d;
+
+		if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) {
+			snd_printk("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d);
+			return -ENODEV;
+		}
+	}
+#else
+	if ((snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET) & 0x07) != 0)
+		return -ENODEV;
+#endif
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);	/* release reset */
+	udelay(160);
+#ifdef CONFIG_SND_DEBUG_DETECT
+	{
+		unsigned char d;
+
+		if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) {
+			snd_printk("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d);
+			return -ENODEV;
+		}
+	}
+#else
+	if ((snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET) & 0x07) != 1)
+		return -ENODEV;
+#endif
+	return 0;
+}
+
+static irqreturn_t snd_gusmax_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct snd_gusmax *maxcard = (struct snd_gusmax *) dev_id;
+	int loop, max = 5;
+	int handled = 0;
+
+	do {
+		loop = 0;
+		if (inb(maxcard->gus_status_reg)) {
+			handled = 1;
+			snd_gus_interrupt(irq, maxcard->gus, regs);
+			loop++;
+		}
+		if (inb(maxcard->pcm_status_reg) & 0x01) { /* IRQ bit is set? */
+			handled = 1;
+			snd_cs4231_interrupt(irq, maxcard->cs4231, regs);
+			loop++;
+		}
+	} while (loop && --max > 0);
+	return IRQ_RETVAL(handled);
+}
+
+static void __init snd_gusmax_init(int dev, snd_card_t * card, snd_gus_card_t * gus)
+{
+	gus->equal_irq = 1;
+	gus->codec_flag = 1;
+	gus->joystick_dac = joystick_dac[dev];
+	/* init control register */
+	gus->max_cntrl_val = (gus->gf1.port >> 4) & 0x0f;
+	if (gus->gf1.dma1 > 3)
+		gus->max_cntrl_val |= 0x10;
+	if (gus->gf1.dma2 > 3)
+		gus->max_cntrl_val |= 0x20;
+	gus->max_cntrl_val |= 0x40;
+	outb(gus->max_cntrl_val, GUSP(gus, MAXCNTRLPORT));
+}
+
+#define CS4231_PRIVATE( left, right, shift, mute ) \
+			((left << 24)|(right << 16)|(shift<<8)|mute)
+
+static int __init snd_gusmax_mixer(cs4231_t *chip)
+{
+	snd_card_t *card = chip->card;
+	snd_ctl_elem_id_t id1, id2;
+	int err;
+	
+	memset(&id1, 0, sizeof(id1));
+	memset(&id2, 0, sizeof(id2));
+	id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	/* reassign AUXA to SYNTHESIZER */
+	strcpy(id1.name, "Aux Playback Switch");
+	strcpy(id2.name, "Synth Playback Switch");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	strcpy(id1.name, "Aux Playback Volume");
+	strcpy(id2.name, "Synth Playback Volume");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	/* reassign AUXB to CD */
+	strcpy(id1.name, "Aux Playback Switch"); id1.index = 1;
+	strcpy(id2.name, "CD Playback Switch");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	strcpy(id1.name, "Aux Playback Volume");
+	strcpy(id2.name, "CD Playback Volume");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+#if 0
+	/* reassign Mono Input to MIC */
+	if (snd_mixer_group_rename(mixer,
+				SNDRV_MIXER_IN_MONO, 0,
+				SNDRV_MIXER_IN_MIC, 0) < 0)
+		goto __error;
+	if (snd_mixer_elem_rename(mixer,
+				SNDRV_MIXER_IN_MONO, 0, SNDRV_MIXER_ETYPE_INPUT,
+				SNDRV_MIXER_IN_MIC, 0) < 0)
+		goto __error;
+	if (snd_mixer_elem_rename(mixer,
+				"Mono Capture Volume", 0, SNDRV_MIXER_ETYPE_VOLUME1,
+				"Mic Capture Volume", 0) < 0)
+		goto __error;
+	if (snd_mixer_elem_rename(mixer,
+				"Mono Capture Switch", 0, SNDRV_MIXER_ETYPE_SWITCH1,
+				"Mic Capture Switch", 0) < 0)
+		goto __error;
+#endif
+	return 0;
+}
+
+static void snd_gusmax_free(snd_card_t *card)
+{
+	struct snd_gusmax *maxcard = (struct snd_gusmax *)card->private_data;
+	
+	if (maxcard == NULL)
+		return;
+	if (maxcard->irq >= 0)
+		free_irq(maxcard->irq, (void *)maxcard);
+}
+
+static int __init snd_gusmax_probe(int dev)
+{
+	static int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, -1};
+	static int possible_dmas[] = {5, 6, 7, 1, 3, -1};
+	int xirq, xdma1, xdma2, err;
+	snd_card_t *card;
+	snd_gus_card_t *gus = NULL;
+	cs4231_t *cs4231;
+	struct snd_gusmax *maxcard;
+
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE,
+			    sizeof(struct snd_gusmax));
+	if (card == NULL)
+		return -ENOMEM;
+	card->private_free = snd_gusmax_free;
+	maxcard = (struct snd_gusmax *)card->private_data;
+	maxcard->card = card;
+	maxcard->irq = -1;
+	
+	xirq = irq[dev];
+	if (xirq == SNDRV_AUTO_IRQ) {
+		if ((xirq = snd_legacy_find_free_irq(possible_irqs)) < 0) {
+			snd_card_free(card);
+			snd_printk("unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	xdma1 = dma1[dev];
+	if (xdma1 == SNDRV_AUTO_DMA) {
+		if ((xdma1 = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_card_free(card);
+			snd_printk("unable to find a free DMA1\n");
+			return -EBUSY;
+		}
+	}
+	xdma2 = dma2[dev];
+	if (xdma2 == SNDRV_AUTO_DMA) {
+		if ((xdma2 = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_card_free(card);
+			snd_printk("unable to find a free DMA2\n");
+			return -EBUSY;
+		}
+	}
+
+	if ((err = snd_gus_create(card,
+				  port[dev],
+				  -xirq, xdma1, xdma2,
+				  0, channels[dev],
+				  pcm_channels[dev],
+				  0, &gus)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_gusmax_detect(gus)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	maxcard->gus_status_reg = gus->gf1.reg_irqstat;
+	maxcard->pcm_status_reg = gus->gf1.port + 0x10c + 2;
+	snd_gusmax_init(dev, card, gus);
+	if ((err = snd_gus_initialize(gus)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (!gus->max_flag) {
+		printk(KERN_ERR "GUS MAX soundcard was not detected at 0x%lx\n", gus->gf1.port);
+		snd_card_free(card);
+		return -ENODEV;
+	}
+
+	if (request_irq(xirq, snd_gusmax_interrupt, SA_INTERRUPT, "GUS MAX", (void *)maxcard)) {
+		snd_card_free(card);
+		printk(KERN_ERR "gusmax: unable to grab IRQ %d\n", xirq);
+		return -EBUSY;
+	}
+	maxcard->irq = xirq;
+	
+	if ((err = snd_cs4231_create(card,
+				     gus->gf1.port + 0x10c, -1, xirq,
+				     xdma2 < 0 ? xdma1 : xdma2, xdma1,
+				     CS4231_HW_DETECT,
+				     CS4231_HWSHARE_IRQ |
+				     CS4231_HWSHARE_DMA1 |
+				     CS4231_HWSHARE_DMA2,
+				     &cs4231)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_cs4231_pcm(cs4231, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_cs4231_mixer(cs4231)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_cs4231_timer(cs4231, 2, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (pcm_channels[dev] > 0) {
+		if ((err = snd_gf1_pcm_new(gus, 1, 1, NULL)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+	}
+	if ((err = snd_gusmax_mixer(cs4231)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if ((err = snd_gf1_rawmidi_new(gus, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	sprintf(card->longname + strlen(card->longname), " at 0x%lx, irq %i, dma %i", gus->gf1.port, xirq, xdma1);
+	if (xdma2 >= 0)
+		sprintf(card->longname + strlen(card->longname), "&%i", xdma2);
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+		
+	maxcard->gus = gus;
+	maxcard->cs4231 = cs4231;
+	snd_gusmax_cards[dev] = card;
+	return 0;
+}
+
+static int __init snd_gusmax_legacy_auto_probe(unsigned long xport)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || port[dev] != SNDRV_AUTO_PORT)
+			continue;
+		port[dev] = xport;
+		res = snd_gusmax_probe(dev);
+		if (res < 0)
+			port[dev] = SNDRV_AUTO_PORT;
+		return res;
+	}
+	return -ENODEV;
+}
+
+static int __init alsa_card_gusmax_init(void)
+{
+	static unsigned long possible_ports[] = {0x220, 0x230, 0x240, 0x250, 0x260, -1};
+	int dev, cards, i;
+
+	for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev] > 0; dev++) {
+		if (port[dev] == SNDRV_AUTO_PORT)
+			continue;
+		if (snd_gusmax_probe(dev) >= 0)
+			cards++;
+	}
+	i = snd_legacy_auto_probe(possible_ports, snd_gusmax_legacy_auto_probe);
+	if (i > 0)
+		cards += i;
+
+	if (!cards) {
+#ifdef MODULE
+		printk(KERN_ERR "GUS MAX soundcard not found or device busy\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_gusmax_exit(void)
+{
+	int idx;
+
+	for (idx = 0; idx < SNDRV_CARDS; idx++)
+		snd_card_free(snd_gusmax_cards[idx]);
+}
+
+module_init(alsa_card_gusmax_init)
+module_exit(alsa_card_gusmax_exit)
diff --git a/sound/isa/gus/interwave-stb.c b/sound/isa/gus/interwave-stb.c
new file mode 100644
index 0000000..dbe4f48
--- /dev/null
+++ b/sound/isa/gus/interwave-stb.c
@@ -0,0 +1,2 @@
+#define SNDRV_STB
+#include "interwave.c"
diff --git a/sound/isa/gus/interwave.c b/sound/isa/gus/interwave.c
new file mode 100644
index 0000000..46e867d
--- /dev/null
+++ b/sound/isa/gus/interwave.c
@@ -0,0 +1,969 @@
+/*
+ *  Driver for AMD InterWave soundcard
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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
+ *
+ *   1999/07/22		Erik Inge Bolso <knan@mo.himolde.no>
+ *			* mixer group handlers
+ *
+ */
+
+#include <sound/driver.h>
+#include <asm/dma.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/cs4231.h>
+#ifdef SNDRV_STB
+#include <sound/tea6330t.h>
+#endif
+#define SNDRV_LEGACY_AUTO_PROBE
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_LICENSE("GPL");
+#ifndef SNDRV_STB
+MODULE_DESCRIPTION("AMD InterWave");
+MODULE_SUPPORTED_DEVICE("{{Gravis,UltraSound Plug & Play},"
+		"{STB,SoundRage32},"
+		"{MED,MED3210},"
+		"{Dynasonix,Dynasonix Pro},"
+		"{Panasonic,PCA761AW}}");
+#else
+MODULE_DESCRIPTION("AMD InterWave STB with TEA6330T");
+MODULE_SUPPORTED_DEVICE("{{AMD,InterWave STB with TEA6330T}}");
+#endif
+
+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_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static int isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x210,0x220,0x230,0x240,0x250,0x260 */
+#ifdef SNDRV_STB
+static long port_tc[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x350,0x360,0x370,0x380 */
+#endif
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 2,3,5,9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3,5,6,7 */
+static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29};
+				/* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */
+static int midi[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 0};
+static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+static int effect[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 0};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for InterWave soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for InterWave soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable InterWave soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "ISA PnP detection for specified soundcard.");
+#endif
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for InterWave driver.");
+#ifdef SNDRV_STB
+module_param_array(port_tc, long, NULL, 0444);
+MODULE_PARM_DESC(port_tc, "Tone control (TEA6330T - i2c bus) port # for InterWave driver.");
+#endif
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for InterWave driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for InterWave driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for InterWave driver.");
+module_param_array(joystick_dac, int, NULL, 0444);
+MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for InterWave driver.");
+module_param_array(midi, int, NULL, 0444);
+MODULE_PARM_DESC(midi, "MIDI UART enable for InterWave driver.");
+module_param_array(pcm_channels, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for InterWave driver.");
+module_param_array(effect, int, NULL, 0444);
+MODULE_PARM_DESC(effect, "Effects enable for InterWave driver.");
+
+struct snd_interwave {
+	int irq;
+	snd_card_t *card;
+	snd_gus_card_t *gus;
+	cs4231_t *cs4231;
+#ifdef SNDRV_STB
+	struct resource *i2c_res;
+#endif
+	unsigned short gus_status_reg;
+	unsigned short pcm_status_reg;
+#ifdef CONFIG_PNP
+	struct pnp_dev *dev;
+#ifdef SNDRV_STB
+	struct pnp_dev *devtc;
+#endif
+#endif
+};
+
+static snd_card_t *snd_interwave_legacy[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+#ifdef CONFIG_PNP
+
+static struct pnp_card_device_id snd_interwave_pnpids[] = {
+#ifndef SNDRV_STB
+	/* Gravis UltraSound Plug & Play */
+	{ .id = "GRV0001", .devs = { { .id = "GRV0000" } } },
+	/* STB SoundRage32 */
+	{ .id = "STB011a", .devs = { { .id = "STB0010" } } },
+	/* MED3210 */
+	{ .id = "DXP3201", .devs = { { .id = "DXP0010" } } },
+	/* Dynasonic Pro */
+	/* This device also have CDC1117:DynaSonix Pro Audio Effects Processor */
+	{ .id = "CDC1111", .devs = { { .id = "CDC1112" } } },
+	/* Panasonic PCA761AW Audio Card */
+	{ .id = "ADV55ff", .devs = { { .id = "ADV0010" } } },
+	/* InterWave STB without TEA6330T */
+	{ .id = "ADV550a", .devs = { { .id = "ADV0010" } } },
+#else
+	/* InterWave STB with TEA6330T */
+	{ .id = "ADV550a", .devs = { { .id = "ADV0010" }, { .id = "ADV0015" } } },
+#endif
+	{ .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_interwave_pnpids);
+
+#endif /* CONFIG_PNP */
+
+
+#ifdef SNDRV_STB
+static void snd_interwave_i2c_setlines(snd_i2c_bus_t *bus, int ctrl, int data)
+{
+	unsigned long port = bus->private_value;
+
+#if 0
+	printk("i2c_setlines - 0x%lx <- %i,%i\n", port, ctrl, data);
+#endif
+	outb((data << 1) | ctrl, port);
+	udelay(10);
+}
+
+static int snd_interwave_i2c_getclockline(snd_i2c_bus_t *bus)
+{
+	unsigned long port = bus->private_value;
+	unsigned char res;
+
+	res = inb(port) & 1;
+#if 0
+	printk("i2c_getclockline - 0x%lx -> %i\n", port, res);
+#endif
+	return res;
+}
+
+static int snd_interwave_i2c_getdataline(snd_i2c_bus_t *bus, int ack)
+{
+	unsigned long port = bus->private_value;
+	unsigned char res;
+
+	if (ack)
+		udelay(10);
+	res = (inb(port) & 2) >> 1;
+#if 0
+	printk("i2c_getdataline - 0x%lx -> %i\n", port, res);
+#endif
+	return res;
+}
+
+static snd_i2c_bit_ops_t snd_interwave_i2c_bit_ops = {
+	.setlines = snd_interwave_i2c_setlines,
+	.getclock = snd_interwave_i2c_getclockline,
+	.getdata  = snd_interwave_i2c_getdataline,
+};
+
+static int __devinit snd_interwave_detect_stb(struct snd_interwave *iwcard,
+					      snd_gus_card_t * gus, int dev,
+					      snd_i2c_bus_t **rbus)
+{
+	unsigned long port;
+	snd_i2c_bus_t *bus;
+	snd_card_t *card = iwcard->card;
+	char name[32];
+	int err;
+
+	*rbus = NULL;
+	port = port_tc[dev];
+	if (port == SNDRV_AUTO_PORT) {
+		port = 0x350;
+		if (gus->gf1.port == 0x250) {
+			port = 0x360;
+		}
+		while (port <= 0x380) {
+			if ((iwcard->i2c_res = request_region(port, 1, "InterWave (I2C bus)")) != NULL)
+				break;
+			port += 0x10;
+		}
+	} else {
+		iwcard->i2c_res = request_region(port, 1, "InterWave (I2C bus)");
+	}
+	if (iwcard->i2c_res == NULL) {
+		snd_printk(KERN_ERR "interwave: can't grab i2c bus port\n");
+		return -ENODEV;
+	}
+
+	sprintf(name, "InterWave-%i", card->number);
+	if ((err = snd_i2c_bus_create(card, name, NULL, &bus)) < 0)
+		return err;
+	bus->private_value = port;
+	bus->hw_ops.bit = &snd_interwave_i2c_bit_ops;
+	if ((err = snd_tea6330t_detect(bus, 0)) < 0)
+		return err;
+	*rbus = bus;
+	return 0;
+}
+#endif
+
+static int __devinit snd_interwave_detect(struct snd_interwave *iwcard,
+				          snd_gus_card_t * gus,
+				          int dev
+#ifdef SNDRV_STB
+				          , snd_i2c_bus_t **rbus
+#endif
+				          )
+{
+	unsigned long flags;
+	unsigned char rev1, rev2;
+
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0);	/* reset GF1 */
+#ifdef CONFIG_SND_DEBUG_DETECT
+	{
+		int d;
+
+		if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) {
+			snd_printk("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d);
+			return -ENODEV;
+		}
+	}
+#else
+	if ((snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET) & 0x07) != 0)
+		return -ENODEV;
+#endif
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);	/* release reset */
+	udelay(160);
+#ifdef CONFIG_SND_DEBUG_DETECT
+	{
+		int d;
+
+		if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) {
+			snd_printk("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d);
+			return -ENODEV;
+		}
+	}
+#else
+	if ((snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET) & 0x07) != 1)
+		return -ENODEV;
+#endif
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	rev1 = snd_gf1_look8(gus, SNDRV_GF1_GB_VERSION_NUMBER);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, ~rev1);
+	rev2 = snd_gf1_look8(gus, SNDRV_GF1_GB_VERSION_NUMBER);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, rev1);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	snd_printdd("[0x%lx] InterWave check - rev1=0x%x, rev2=0x%x\n", gus->gf1.port, rev1, rev2);
+	if ((rev1 & 0xf0) == (rev2 & 0xf0) &&
+	    (rev1 & 0x0f) != (rev2 & 0x0f)) {
+		snd_printdd("[0x%lx] InterWave check - passed\n", gus->gf1.port);
+		gus->interwave = 1;
+		strcpy(gus->card->shortname, "AMD InterWave");
+		gus->revision = rev1 >> 4;
+#ifndef SNDRV_STB
+		return 0;	/* ok.. We have an InterWave board */
+#else
+		return snd_interwave_detect_stb(iwcard, gus, dev, rbus);
+#endif
+	}
+	snd_printdd("[0x%lx] InterWave check - failed\n", gus->gf1.port);
+	return -ENODEV;
+}
+
+static irqreturn_t snd_interwave_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct snd_interwave *iwcard = (struct snd_interwave *) dev_id;
+	int loop, max = 5;
+	int handled = 0;
+
+	do {
+		loop = 0;
+		if (inb(iwcard->gus_status_reg)) {
+			handled = 1;
+			snd_gus_interrupt(irq, iwcard->gus, regs);
+			loop++;
+		}
+		if (inb(iwcard->pcm_status_reg) & 0x01) {	/* IRQ bit is set? */
+			handled = 1;
+			snd_cs4231_interrupt(irq, iwcard->cs4231, regs);
+			loop++;
+		}
+	} while (loop && --max > 0);
+	return IRQ_RETVAL(handled);
+}
+
+static void __devinit snd_interwave_reset(snd_gus_card_t * gus)
+{
+	snd_gf1_write8(gus, SNDRV_GF1_GB_RESET, 0x00);
+	udelay(160);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_RESET, 0x01);
+	udelay(160);
+}
+
+static void __devinit snd_interwave_bank_sizes(snd_gus_card_t * gus, int *sizes)
+{
+	unsigned int idx;
+	unsigned int local;
+	unsigned char d;
+
+	for (idx = 0; idx < 4; idx++) {
+		sizes[idx] = 0;
+		d = 0x55;
+		for (local = idx << 22;
+		     local < (idx << 22) + 0x400000;
+		     local += 0x40000, d++) {
+			snd_gf1_poke(gus, local, d);
+			snd_gf1_poke(gus, local + 1, d + 1);
+#if 0
+			printk("d = 0x%x, local = 0x%x, local + 1 = 0x%x, idx << 22 = 0x%x\n",
+			       d,
+			       snd_gf1_peek(gus, local),
+			       snd_gf1_peek(gus, local + 1),
+			       snd_gf1_peek(gus, idx << 22));
+#endif
+			if (snd_gf1_peek(gus, local) != d ||
+			    snd_gf1_peek(gus, local + 1) != d + 1 ||
+			    snd_gf1_peek(gus, idx << 22) != 0x55)
+				break;
+			sizes[idx]++;
+		}
+	}
+#if 0
+	printk("sizes: %i %i %i %i\n", sizes[0], sizes[1], sizes[2], sizes[3]);
+#endif
+}
+
+struct rom_hdr {
+	/* 000 */ unsigned char iwave[8];
+	/* 008 */ unsigned char rom_hdr_revision;
+	/* 009 */ unsigned char series_number;
+	/* 010 */ unsigned char series_name[16];
+	/* 026 */ unsigned char date[10];
+	/* 036 */ unsigned short vendor_revision_major;
+	/* 038 */ unsigned short vendor_revision_minor;
+	/* 040 */ unsigned int rom_size;
+	/* 044 */ unsigned char copyright[128];
+	/* 172 */ unsigned char vendor_name[64];
+	/* 236 */ unsigned char rom_description[128];
+	/* 364 */ unsigned char pad[147];
+	/* 511 */ unsigned char csum;
+};
+
+static void __devinit snd_interwave_detect_memory(snd_gus_card_t * gus)
+{
+	static unsigned int lmc[13] =
+	{
+		0x00000001, 0x00000101, 0x01010101, 0x00000401,
+		0x04040401, 0x00040101, 0x04040101, 0x00000004,
+		0x00000404, 0x04040404, 0x00000010, 0x00001010,
+		0x10101010
+	};
+
+	int bank_pos, pages;
+	unsigned int i, lmct;
+	int psizes[4];
+	unsigned char iwave[8];
+	unsigned char csum;
+
+	snd_interwave_reset(gus);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_read8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01);		/* enhanced mode */
+	snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01);	/* DRAM I/O cycles selected */
+	snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xff10) | 0x004c);
+	/* ok.. simple test of memory size */
+	pages = 0;
+	snd_gf1_poke(gus, 0, 0x55);
+	snd_gf1_poke(gus, 1, 0xaa);
+#if 1
+	if (snd_gf1_peek(gus, 0) == 0x55 && snd_gf1_peek(gus, 1) == 0xaa)
+#else
+	if (0)			/* ok.. for testing of 0k RAM */
+#endif
+	{
+		snd_interwave_bank_sizes(gus, psizes);
+		lmct = (psizes[3] << 24) | (psizes[2] << 16) |
+		    (psizes[1] << 8) | psizes[0];
+#if 0
+		printk("lmct = 0x%08x\n", lmct);
+#endif
+		for (i = 0; i < ARRAY_SIZE(lmc); i++)
+			if (lmct == lmc[i]) {
+#if 0
+				printk("found !!! %i\n", i);
+#endif
+				snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | i);
+				snd_interwave_bank_sizes(gus, psizes);
+				break;
+			}
+		if (i >= ARRAY_SIZE(lmc) && !gus->gf1.enh_mode)
+			 snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | 2);
+		for (i = 0; i < 4; i++) {
+			gus->gf1.mem_alloc.banks_8[i].address =
+			    gus->gf1.mem_alloc.banks_16[i].address = i << 22;
+			gus->gf1.mem_alloc.banks_8[i].size =
+			    gus->gf1.mem_alloc.banks_16[i].size = psizes[i] << 18;
+			pages += psizes[i];
+		}
+	}
+	pages <<= 18;
+	gus->gf1.memory = pages;
+
+	snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x03);	/* select ROM */
+	snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xff1f) | (4 << 5));
+	gus->gf1.rom_banks = 0;
+	gus->gf1.rom_memory = 0;
+	for (bank_pos = 0; bank_pos < 16L * 1024L * 1024L; bank_pos += 4L * 1024L * 1024L) {
+		for (i = 0; i < 8; ++i)
+			iwave[i] = snd_gf1_peek(gus, bank_pos + i);
+#ifdef CONFIG_SND_DEBUG_ROM
+		printk("ROM at 0x%06x = %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", bank_pos,
+		       iwave[0], iwave[1], iwave[2], iwave[3],
+		       iwave[4], iwave[5], iwave[6], iwave[7]);
+#endif
+		if (strncmp(iwave, "INTRWAVE", 8))
+			continue;	/* first check */
+		csum = 0;
+		for (i = 0; i < sizeof(struct rom_hdr); i++)
+			csum += snd_gf1_peek(gus, bank_pos + i);
+#ifdef CONFIG_SND_DEBUG_ROM
+		printk("ROM checksum = 0x%x (computed)\n", csum);
+#endif
+		if (csum != 0)
+			continue;	/* not valid rom */
+		gus->gf1.rom_banks++;
+		gus->gf1.rom_present |= 1 << (bank_pos >> 22);
+		gus->gf1.rom_memory = snd_gf1_peek(gus, bank_pos + 40) |
+				     (snd_gf1_peek(gus, bank_pos + 41) << 8) |
+				     (snd_gf1_peek(gus, bank_pos + 42) << 16) |
+				     (snd_gf1_peek(gus, bank_pos + 43) << 24);
+	}
+#if 0
+	if (gus->gf1.rom_memory > 0) {
+		if (gus->gf1.rom_banks == 1 && gus->gf1.rom_present == 8)
+			gus->card->type = SNDRV_CARD_TYPE_IW_DYNASONIC;
+	}
+#endif
+	snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x00);	/* select RAM */
+
+	if (!gus->gf1.enh_mode)
+		snd_interwave_reset(gus);
+}
+
+static void __devinit snd_interwave_init(int dev, snd_gus_card_t * gus)
+{
+	unsigned long flags;
+
+	/* ok.. some InterWave specific initialization */
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0x00);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_COMPATIBILITY, 0x1f);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_DECODE_CONTROL, 0x49);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, 0x11);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_A, 0x00);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_B, 0x30);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_EMULATION_IRQ, 0x00);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	gus->equal_irq = 1;
+	gus->codec_flag = 1;
+	gus->interwave = 1;
+	gus->max_flag = 1;
+	gus->joystick_dac = joystick_dac[dev];
+
+}
+
+static snd_kcontrol_new_t snd_interwave_controls[] = {
+CS4231_DOUBLE("Master Playback Switch", 0, CS4231_LINE_LEFT_OUTPUT, CS4231_LINE_RIGHT_OUTPUT, 7, 7, 1, 1),
+CS4231_DOUBLE("Master Playback Volume", 0, CS4231_LINE_LEFT_OUTPUT, CS4231_LINE_RIGHT_OUTPUT, 0, 0, 31, 1),
+CS4231_DOUBLE("Mic Playback Switch", 0, CS4231_LEFT_MIC_INPUT, CS4231_RIGHT_MIC_INPUT, 7, 7, 1, 1),
+CS4231_DOUBLE("Mic Playback Volume", 0, CS4231_LEFT_MIC_INPUT, CS4231_RIGHT_MIC_INPUT, 0, 0, 31, 1)
+};
+
+static int __devinit snd_interwave_mixer(cs4231_t *chip)
+{
+	snd_card_t *card = chip->card;
+	snd_ctl_elem_id_t id1, id2;
+	unsigned int idx;
+	int err;
+
+	memset(&id1, 0, sizeof(id1));
+	memset(&id2, 0, sizeof(id2));
+	id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+#if 0
+	/* remove mono microphone controls */
+	strcpy(id1.name, "Mic Playback Switch");
+	if ((err = snd_ctl_remove_id(card, &id1)) < 0)
+		return err;
+	strcpy(id1.name, "Mic Playback Volume");
+	if ((err = snd_ctl_remove_id(card, &id1)) < 0)
+		return err;
+#endif
+	/* add new master and mic controls */
+	for (idx = 0; idx < ARRAY_SIZE(snd_interwave_controls); idx++)
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_interwave_controls[idx], chip))) < 0)
+			return err;
+	snd_cs4231_out(chip, CS4231_LINE_LEFT_OUTPUT, 0x9f);
+	snd_cs4231_out(chip, CS4231_LINE_RIGHT_OUTPUT, 0x9f);
+	snd_cs4231_out(chip, CS4231_LEFT_MIC_INPUT, 0x9f);
+	snd_cs4231_out(chip, CS4231_RIGHT_MIC_INPUT, 0x9f);
+	/* reassign AUXA to SYNTHESIZER */
+	strcpy(id1.name, "Aux Playback Switch");
+	strcpy(id2.name, "Synth Playback Switch");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	strcpy(id1.name, "Aux Playback Volume");
+	strcpy(id2.name, "Synth Playback Volume");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	/* reassign AUXB to CD */
+	strcpy(id1.name, "Aux Playback Switch"); id1.index = 1;
+	strcpy(id2.name, "CD Playback Switch");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	strcpy(id1.name, "Aux Playback Volume");
+	strcpy(id2.name, "CD Playback Volume");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	return 0;
+}
+
+#ifdef CONFIG_PNP
+
+static int __devinit snd_interwave_pnp(int dev, struct snd_interwave *iwcard,
+				       struct pnp_card_link *card,
+				       const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	struct pnp_resource_table * cfg = kmalloc(sizeof(struct pnp_resource_table), GFP_KERNEL);
+	int err;
+
+	iwcard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (iwcard->dev == NULL) {
+		kfree(cfg);
+		return -EBUSY;
+	}
+#ifdef SNDRV_STB
+	iwcard->devtc = pnp_request_card_device(card, id->devs[1].id, NULL);
+	if (iwcard->devtc == NULL) {
+		kfree(cfg);
+		return -EBUSY;
+	}
+#endif
+	/* Synth & Codec initialization */
+	pdev = iwcard->dev;
+	pnp_init_resource_table(cfg);
+	if (port[dev] != SNDRV_AUTO_PORT) {
+		pnp_resource_change(&cfg->port_resource[0], port[dev], 16);
+		pnp_resource_change(&cfg->port_resource[1], port[dev] + 0x100, 12);
+		pnp_resource_change(&cfg->port_resource[2], port[dev] + 0x10c, 4);
+	}
+	if (dma1[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[0], dma1[dev], 1);
+	if (dma2[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[1], dma2[dev], 1);
+	if (dma2[dev] < 0)
+		pnp_resource_change(&cfg->dma_resource[1], 4, 1);
+	if (irq[dev] != SNDRV_AUTO_IRQ)
+		pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1);
+        if (pnp_manual_config_dev(pdev, cfg, 0) < 0)
+		snd_printk(KERN_ERR "InterWave - Synth - the requested resources are invalid, using auto config\n");
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		kfree(cfg);
+		snd_printk(KERN_ERR "InterWave PnP configure failure (out of resources?)\n");
+		return err;
+	}
+	if (pnp_port_start(pdev, 0) + 0x100 != pnp_port_start(pdev, 1) ||
+	    pnp_port_start(pdev, 0) + 0x10c != pnp_port_start(pdev, 2)) {
+		kfree(cfg);
+		snd_printk(KERN_ERR "PnP configure failure (wrong ports)\n");
+		return -ENOENT;
+	}
+	port[dev] = pnp_port_start(pdev, 0);
+	dma1[dev] = pnp_dma(pdev, 0);
+	if (dma2[dev] >= 0)
+		dma2[dev] = pnp_dma(pdev, 1);
+	irq[dev] = pnp_irq(pdev, 0);
+	snd_printdd("isapnp IW: sb port=0x%lx, gf1 port=0x%lx, codec port=0x%lx\n",
+				pnp_port_start(pdev, 0),
+				pnp_port_start(pdev, 1),
+				pnp_port_start(pdev, 2));
+	snd_printdd("isapnp IW: dma1=%i, dma2=%i, irq=%i\n", dma1[dev], dma2[dev], irq[dev]);
+#ifdef SNDRV_STB
+	/* Tone Control initialization */
+	pdev = iwcard->devtc;
+	pnp_init_resource_table(cfg);
+	if (port_tc[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[0], port_tc[dev], 1);
+        if (pnp_manual_config_dev(pdev, cfg, 0) < 0)
+		snd_printk(KERN_ERR "InterWave - ToneControl - the requested resources are invalid, using auto config\n");
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		kfree(cfg);
+		snd_printk(KERN_ERR "InterWave ToneControl PnP configure failure (out of resources?)\n");
+		return err;
+	}
+	port_tc[dev] = pnp_port_start(pdev, 0);
+	snd_printdd("isapnp IW: tone control port=0x%lx\n", port_tc[dev]);
+#endif
+	kfree(cfg);
+	return 0;
+}
+#endif /* CONFIG_PNP */
+
+static void snd_interwave_free(snd_card_t *card)
+{
+	struct snd_interwave *iwcard = (struct snd_interwave *)card->private_data;
+
+	if (iwcard == NULL)
+		return;
+#ifdef SNDRV_STB
+	if (iwcard->i2c_res) {
+		release_resource(iwcard->i2c_res);
+		kfree_nocheck(iwcard->i2c_res);
+	}
+#endif
+	if (iwcard->irq >= 0)
+		free_irq(iwcard->irq, (void *)iwcard);
+}
+
+static int __devinit snd_interwave_probe(int dev, struct pnp_card_link *pcard,
+				         const struct pnp_card_device_id *pid)
+{
+	static int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, -1};
+	static int possible_dmas[] = {0, 1, 3, 5, 6, 7, -1};
+	int xirq, xdma1, xdma2;
+	snd_card_t *card;
+	struct snd_interwave *iwcard;
+	cs4231_t *cs4231;
+	snd_gus_card_t *gus;
+#ifdef SNDRV_STB
+	snd_i2c_bus_t *i2c_bus;
+#endif
+	snd_pcm_t *pcm;
+	char *str;
+	int err;
+
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE,
+			    sizeof(struct snd_interwave));
+	if (card == NULL)
+		return -ENOMEM;
+	iwcard = (struct snd_interwave *)card->private_data;
+	iwcard->card = card;
+	iwcard->irq = -1;
+	card->private_free = snd_interwave_free;
+#ifdef CONFIG_PNP
+	if (isapnp[dev]) {
+		if (snd_interwave_pnp(dev, iwcard, pcard, pid)) {
+			snd_card_free(card);
+			return -ENODEV;
+		}
+		snd_card_set_dev(card, &pcard->card->dev);
+	}
+#endif
+	xirq = irq[dev];
+	if (xirq == SNDRV_AUTO_IRQ) {
+		if ((xirq = snd_legacy_find_free_irq(possible_irqs)) < 0) {
+			snd_card_free(card);
+			snd_printk("unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	xdma1 = dma1[dev];
+	if (xdma1 == SNDRV_AUTO_DMA) {
+		if ((xdma1 = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_card_free(card);
+			snd_printk("unable to find a free DMA1\n");
+			return -EBUSY;
+		}
+	}
+	xdma2 = dma2[dev];
+	if (xdma2 == SNDRV_AUTO_DMA) {
+		if ((xdma2 = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_card_free(card);
+			snd_printk("unable to find a free DMA2\n");
+			return -EBUSY;
+		}
+	}
+
+	if ((err = snd_gus_create(card,
+				  port[dev],
+				  -xirq, xdma1, xdma2,
+				  0, 32,
+				  pcm_channels[dev], effect[dev], &gus)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_interwave_detect(iwcard, gus, dev
+#ifdef SNDRV_STB
+            , &i2c_bus
+#endif
+	    )) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	iwcard->gus_status_reg = gus->gf1.reg_irqstat;
+	iwcard->pcm_status_reg = gus->gf1.port + 0x10c + 2;
+
+	snd_interwave_init(dev, gus);
+	snd_interwave_detect_memory(gus);
+	if ((err = snd_gus_initialize(gus)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if (request_irq(xirq, snd_interwave_interrupt, SA_INTERRUPT, "InterWave", (void *)iwcard)) {
+		snd_card_free(card);
+		snd_printk("unable to grab IRQ %d\n", xirq);
+		return -EBUSY;
+	}
+	iwcard->irq = xirq;
+
+	if ((err = snd_cs4231_create(card,
+				     gus->gf1.port + 0x10c, -1, xirq,
+				     xdma2 < 0 ? xdma1 : xdma2, xdma1,
+				     CS4231_HW_INTERWAVE,
+				     CS4231_HWSHARE_IRQ |
+				     CS4231_HWSHARE_DMA1 |
+				     CS4231_HWSHARE_DMA2,
+				     &cs4231)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_cs4231_pcm(cs4231, 0, &pcm)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	sprintf(pcm->name + strlen(pcm->name), " rev %c", gus->revision + 'A');
+	strcat(pcm->name, " (codec)");
+	if ((err = snd_cs4231_timer(cs4231, 2, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_cs4231_mixer(cs4231)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (pcm_channels[dev] > 0) {
+		if ((err = snd_gf1_pcm_new(gus, 1, 1, NULL)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+	}
+	if ((err = snd_interwave_mixer(cs4231)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+#ifdef SNDRV_STB
+	{
+		snd_ctl_elem_id_t id1, id2;
+		memset(&id1, 0, sizeof(id1));
+		memset(&id2, 0, sizeof(id2));
+		id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+		strcpy(id1.name, "Master Playback Switch");
+		strcpy(id2.name, id1.name);
+		id2.index = 1;
+		if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+		strcpy(id1.name, "Master Playback Volume");
+		strcpy(id2.name, id1.name);
+		if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+		if ((err = snd_tea6330t_update_mixer(card, i2c_bus, 0, 1)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+	}
+#endif
+
+	gus->uart_enable = midi[dev];
+	if ((err = snd_gf1_rawmidi_new(gus, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+#ifndef SNDRV_STB
+	str = "AMD InterWave";
+	if (gus->gf1.rom_banks == 1 && gus->gf1.rom_present == 8)
+		str = "Dynasonic 3-D";
+#else
+	str = "InterWave STB";
+#endif
+	strcpy(card->driver, str);
+	strcpy(card->shortname, str);
+	sprintf(card->longname, "%s at 0x%lx, irq %i, dma %d",
+		str,
+		gus->gf1.port,
+		xirq,
+		xdma1);
+	if (xdma2 >= 0)
+		sprintf(card->longname + strlen(card->longname), "&%d", xdma2);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	
+	iwcard->cs4231 = cs4231;
+	iwcard->gus = gus;
+	if (pcard)
+		pnp_set_card_drvdata(pcard, card);
+	else
+		snd_interwave_legacy[dev++] = card;
+	return 0;
+}
+
+static int __devinit snd_interwave_probe_legacy_port(unsigned long xport)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || port[dev] != SNDRV_AUTO_PORT)
+                        continue;
+#ifdef CONFIG_PNP
+		if (isapnp[dev])
+			continue;
+#endif
+		port[dev] = xport;
+		res = snd_interwave_probe(dev, NULL, NULL);
+		if (res < 0)
+			port[dev] = SNDRV_AUTO_PORT;
+		return res;
+	}
+	return -ENODEV;
+}
+
+#ifdef CONFIG_PNP
+
+static int __devinit snd_interwave_pnp_detect(struct pnp_card_link *card,
+					   const struct pnp_card_device_id *id)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || !isapnp[dev])
+			continue;
+		res = snd_interwave_probe(dev, card, id);
+		if (res < 0)
+			return res;
+		dev++;
+		return 0;
+        }
+
+        return -ENODEV;
+}
+
+static void __devexit snd_interwave_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_t *card = (snd_card_t *) pnp_get_card_drvdata(pcard);
+
+	snd_card_disconnect(card);
+	snd_card_free_in_thread(card);
+}
+
+static struct pnp_card_driver interwave_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DISABLE,
+	.name = "interwave",
+	.id_table = snd_interwave_pnpids,
+	.probe = snd_interwave_pnp_detect,
+	.remove = __devexit_p(snd_interwave_pnp_remove),
+};
+
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_interwave_init(void)
+{
+	int cards = 0, i;
+	static long possible_ports[] = {0x210, 0x220, 0x230, 0x240, 0x250, 0x260, -1};
+	int dev;
+
+	for (dev = 0; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || port[dev] == SNDRV_AUTO_PORT)
+			continue;
+#ifdef CONFIG_PNP
+		if (isapnp[dev])
+			continue;
+#endif
+		if (!snd_interwave_probe(dev, NULL, NULL)) {
+			cards++;
+			continue;
+		}
+#ifdef MODULE
+		printk(KERN_ERR "InterWave soundcard #%i not found at 0x%lx or device busy\n", dev, port[dev]);
+#endif
+	}
+	/* legacy auto configured cards */
+	i = snd_legacy_auto_probe(possible_ports, snd_interwave_probe_legacy_port);
+	if (i > 0)
+		cards += i;
+#ifdef CONFIG_PNP
+	/* ISA PnP cards */
+	i = pnp_register_card_driver(&interwave_pnpc_driver);
+	if (i > 0)
+		cards += i;
+#endif
+
+	if (!cards) {
+#ifdef CONFIG_PNP
+		pnp_unregister_card_driver(&interwave_pnpc_driver);
+#endif
+#ifdef MODULE
+		printk(KERN_ERR "InterWave soundcard not found or device busy\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_interwave_exit(void)
+{
+	int dev;
+
+#ifdef CONFIG_PNP
+	/* PnP cards first */
+	pnp_unregister_card_driver(&interwave_pnpc_driver);
+#endif
+	for (dev = 0; dev < SNDRV_CARDS; dev++)
+		snd_card_free(snd_interwave_legacy[dev]);
+}
+
+module_init(alsa_card_interwave_init)
+module_exit(alsa_card_interwave_exit)
diff --git a/sound/isa/opl3sa2.c b/sound/isa/opl3sa2.c
new file mode 100644
index 0000000..95c7b3e
--- /dev/null
+++ b/sound/isa/opl3sa2.c
@@ -0,0 +1,860 @@
+/*
+ *  Driver for Yamaha OPL3-SA[2,3] soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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/interrupt.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/cs4231.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+
+#include <asm/io.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Yamaha OPL3SA2+");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Yamaha,YMF719E-S},"
+		"{Genius,Sound Maker 3DX},"
+		"{Yamaha,OPL3SA3},"
+		"{Intel,AL440LX sound},"
+	        "{NeoMagic,MagicWave 3DX}}");
+
+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_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static int isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0xf86,0x370,0x100 */
+static long sb_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x240,0x260 */
+static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;/* 0x530,0xe80,0xf40,0x604 */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x388 */
+static long midi_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;/* 0x330,0x300 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 0,1,3,5,9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 1,3,5,6,7 */
+static int opl3sa3_ymode[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = 0 };   /* 0,1,2,3 */ /*SL Added*/
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for OPL3-SA soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for OPL3-SA soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable OPL3-SA soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for OPL3-SA driver.");
+module_param_array(sb_port, long, NULL, 0444);
+MODULE_PARM_DESC(sb_port, "SB port # for OPL3-SA driver.");
+module_param_array(wss_port, long, NULL, 0444);
+MODULE_PARM_DESC(wss_port, "WSS port # for OPL3-SA driver.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for OPL3-SA driver.");
+module_param_array(midi_port, long, NULL, 0444);
+MODULE_PARM_DESC(midi_port, "MIDI port # for OPL3-SA driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for OPL3-SA driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for OPL3-SA driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for OPL3-SA driver.");
+module_param_array(opl3sa3_ymode, int, NULL, 0444);
+MODULE_PARM_DESC(opl3sa3_ymode, "Speaker size selection for 3D Enhancement mode: Desktop/Large Notebook/Small Notebook/HiFi.");
+
+/* control ports */
+#define OPL3SA2_PM_CTRL		0x01
+#define OPL3SA2_SYS_CTRL		0x02
+#define OPL3SA2_IRQ_CONFIG	0x03
+#define OPL3SA2_IRQ_STATUS	0x04
+#define OPL3SA2_DMA_CONFIG	0x06
+#define OPL3SA2_MASTER_LEFT	0x07
+#define OPL3SA2_MASTER_RIGHT	0x08
+#define OPL3SA2_MIC		0x09
+#define OPL3SA2_MISC		0x0A
+
+/* opl3sa3 only */
+#define OPL3SA3_DGTL_DOWN	0x12
+#define OPL3SA3_ANLG_DOWN	0x13
+#define OPL3SA3_WIDE		0x14
+#define OPL3SA3_BASS		0x15
+#define OPL3SA3_TREBLE		0x16
+
+/* power management bits */
+#define OPL3SA2_PM_ADOWN		0x20
+#define OPL3SA2_PM_PSV		0x04		
+#define OPL3SA2_PM_PDN		0x02
+#define OPL3SA2_PM_PDX		0x01
+
+#define OPL3SA2_PM_D0	0x00
+#define OPL3SA2_PM_D3	(OPL3SA2_PM_ADOWN|OPL3SA2_PM_PSV|OPL3SA2_PM_PDN|OPL3SA2_PM_PDX)
+
+typedef struct snd_opl3sa2 opl3sa2_t;
+
+struct snd_opl3sa2 {
+	snd_card_t *card;
+	int version;		/* 2 or 3 */
+	unsigned long port;	/* control port */
+	struct resource *res_port; /* control port resource */
+	int irq;
+	int single_dma;
+	spinlock_t reg_lock;
+	snd_hwdep_t *synth;
+	snd_rawmidi_t *rmidi;
+	cs4231_t *cs4231;
+#ifdef CONFIG_PNP
+	struct pnp_dev *dev;
+#endif
+	unsigned char ctlregs[0x20];
+	int ymode;		/* SL added */
+	snd_kcontrol_t *master_switch;
+	snd_kcontrol_t *master_volume;
+#ifdef CONFIG_PM
+	void (*cs4231_suspend)(cs4231_t *);
+	void (*cs4231_resume)(cs4231_t *);
+#endif
+};
+
+static snd_card_t *snd_opl3sa2_legacy[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+#ifdef CONFIG_PNP
+
+static struct pnp_card_device_id snd_opl3sa2_pnpids[] = {
+	/* Yamaha YMF719E-S (Genius Sound Maker 3DX) */
+	{ .id = "YMH0020", .devs = { { "YMH0021" } } },
+	/* Yamaha OPL3-SA3 (integrated on Intel's Pentium II AL440LX motherboard) */
+	{ .id = "YMH0030", .devs = { { "YMH0021" } } },
+	/* Yamaha OPL3-SA2 */
+	{ .id = "YMH0800", .devs = { { "YMH0021" } } },
+	/* Yamaha OPL3-SA2 */
+	{ .id = "YMH0801", .devs = { { "YMH0021" } } },
+	/* NeoMagic MagicWave 3DX */
+	{ .id = "NMX2200", .devs = { { "YMH2210" } } },
+	/* --- */
+	{ .id = "" }	/* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_opl3sa2_pnpids);
+
+#endif /* CONFIG_PNP */
+
+
+/* read control port (w/o spinlock) */
+static unsigned char __snd_opl3sa2_read(opl3sa2_t *chip, unsigned char reg)
+{
+	unsigned char result;
+#if 0
+	outb(0x1d, port);	/* password */
+	printk("read [0x%lx] = 0x%x\n", port, inb(port));
+#endif
+	outb(reg, chip->port);	/* register */
+	result = inb(chip->port + 1);
+#if 0
+	printk("read [0x%lx] = 0x%x [0x%x]\n", port, result, inb(port));
+#endif
+	return result;
+}
+
+/* read control port (with spinlock) */
+static unsigned char snd_opl3sa2_read(opl3sa2_t *chip, unsigned char reg)
+{
+	unsigned long flags;
+	unsigned char result;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	result = __snd_opl3sa2_read(chip, reg);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return result;
+}
+
+/* write control port (w/o spinlock) */
+static void __snd_opl3sa2_write(opl3sa2_t *chip, unsigned char reg, unsigned char value)
+{
+#if 0
+	outb(0x1d, port);	/* password */
+#endif
+	outb(reg, chip->port);	/* register */
+	outb(value, chip->port + 1);
+	chip->ctlregs[reg] = value;
+}
+
+/* write control port (with spinlock) */
+static void snd_opl3sa2_write(opl3sa2_t *chip, unsigned char reg, unsigned char value)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	__snd_opl3sa2_write(chip, reg, value);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static int __init snd_opl3sa2_detect(opl3sa2_t *chip)
+{
+	snd_card_t *card;
+	unsigned long port;
+	unsigned char tmp, tmp1;
+	char str[2];
+
+	card = chip->card;
+	port = chip->port;
+	if ((chip->res_port = request_region(port, 2, "OPL3-SA control")) == NULL) {
+		snd_printk(KERN_ERR "opl3sa2: can't grab port 0x%lx\n", port);
+		return -EBUSY;
+	}
+	// snd_printk("REG 0A = 0x%x\n", snd_opl3sa2_read(chip, 0x0a));
+	chip->version = 0;
+	tmp = snd_opl3sa2_read(chip, OPL3SA2_MISC);
+	if (tmp == 0xff) {
+		snd_printd("OPL3-SA [0x%lx] detect = 0x%x\n", port, tmp);
+		return -ENODEV;
+	}
+	switch (tmp & 0x07) {
+	case 0x01:
+		chip->version = 2; /* YMF711 */
+		break;
+	default:
+		chip->version = 3;
+		/* 0x02 - standard */
+		/* 0x03 - YM715B */
+		/* 0x04 - YM719 - OPL-SA4? */
+		/* 0x05 - OPL3-SA3 - Libretto 100 */
+		break;
+	}
+	str[0] = chip->version + '0';
+	str[1] = 0;
+	strcat(card->shortname, str);
+	snd_opl3sa2_write(chip, OPL3SA2_MISC, tmp ^ 7);
+	if ((tmp1 = snd_opl3sa2_read(chip, OPL3SA2_MISC)) != tmp) {
+		snd_printd("OPL3-SA [0x%lx] detect (1) = 0x%x (0x%x)\n", port, tmp, tmp1);
+		return -ENODEV;
+	}
+	/* try if the MIC register is accesible */
+	tmp = snd_opl3sa2_read(chip, OPL3SA2_MIC);
+	snd_opl3sa2_write(chip, OPL3SA2_MIC, 0x8a);
+	if (((tmp1 = snd_opl3sa2_read(chip, OPL3SA2_MIC)) & 0x9f) != 0x8a) {
+		snd_printd("OPL3-SA [0x%lx] detect (2) = 0x%x (0x%x)\n", port, tmp, tmp1);
+		return -ENODEV;
+	}
+	snd_opl3sa2_write(chip, OPL3SA2_MIC, 0x9f);
+	/* initialization */
+	/* Power Management - full on */
+	snd_opl3sa2_write(chip, OPL3SA2_PM_CTRL, OPL3SA2_PM_D0);
+	if (chip->version > 2) {
+		/* ymode is bits 4&5 (of 0 to 7) on all but opl3sa2 versions */
+		snd_opl3sa2_write(chip, OPL3SA2_SYS_CTRL, (chip->ymode << 4));
+	} else {
+		/* default for opl3sa2 versions */
+		snd_opl3sa2_write(chip, OPL3SA2_SYS_CTRL, 0x00);
+	}
+	snd_opl3sa2_write(chip, OPL3SA2_IRQ_CONFIG, 0x0d);	/* Interrupt Channel Configuration - IRQ A = OPL3 + MPU + WSS */
+	if (chip->single_dma) {
+		snd_opl3sa2_write(chip, OPL3SA2_DMA_CONFIG, 0x03);	/* DMA Configuration - DMA A = WSS-R + WSS-P */
+	} else {
+		snd_opl3sa2_write(chip, OPL3SA2_DMA_CONFIG, 0x21);	/* DMA Configuration - DMA B = WSS-R, DMA A = WSS-P */
+	}
+	snd_opl3sa2_write(chip, OPL3SA2_MISC, 0x80 | (tmp & 7));	/* Miscellaneous - default */
+	if (chip->version > 2) {
+		snd_opl3sa2_write(chip, OPL3SA3_DGTL_DOWN, 0x00);	/* Digital Block Partial Power Down - default */
+		snd_opl3sa2_write(chip, OPL3SA3_ANLG_DOWN, 0x00);	/* Analog Block Partial Power Down - default */
+	}
+	return 0;
+}
+
+static irqreturn_t snd_opl3sa2_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	unsigned short status;
+	opl3sa2_t *chip = dev_id;
+	int handled = 0;
+
+	if (chip == NULL || chip->card == NULL)
+		return IRQ_NONE;
+
+	status = snd_opl3sa2_read(chip, OPL3SA2_IRQ_STATUS);
+
+	if (status & 0x20) {
+		handled = 1;
+		snd_opl3_interrupt(chip->synth);
+	}
+
+	if ((status & 0x10) && chip->rmidi != NULL) {
+		handled = 1;
+		snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data, regs);
+	}
+
+	if (status & 0x07) {	/* TI,CI,PI */
+		handled = 1;
+		snd_cs4231_interrupt(irq, chip->cs4231, regs);
+	}
+
+	if (status & 0x40) { /* hardware volume change */
+		handled = 1;
+		/* reading from Master Lch register at 0x07 clears this bit */
+		snd_opl3sa2_read(chip, OPL3SA2_MASTER_RIGHT);
+		snd_opl3sa2_read(chip, OPL3SA2_MASTER_LEFT);
+		if (chip->master_switch && chip->master_volume) {
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->master_switch->id);
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->master_volume->id);
+		}
+	}
+	return IRQ_RETVAL(handled);
+}
+
+#define OPL3SA2_SINGLE(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_opl3sa2_info_single, \
+  .get = snd_opl3sa2_get_single, .put = snd_opl3sa2_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+static int snd_opl3sa2_info_single(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_opl3sa2_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	opl3sa2_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (chip->ctlregs[reg] >> shift) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_opl3sa2_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	opl3sa2_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned short val, oval;
+	
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	val <<= shift;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	oval = chip->ctlregs[reg];
+	val = (oval & ~(mask << shift)) | val;
+	change = val != oval;
+	__snd_opl3sa2_write(chip, reg, val);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+#define OPL3SA2_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_opl3sa2_info_double, \
+  .get = snd_opl3sa2_get_double, .put = snd_opl3sa2_put_double, \
+  .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
+
+static int snd_opl3sa2_info_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_opl3sa2_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	opl3sa2_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (chip->ctlregs[left_reg] >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (chip->ctlregs[right_reg] >> shift_right) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+
+static int snd_opl3sa2_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	opl3sa2_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	unsigned short val1, val2, oval1, oval2;
+	
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (left_reg != right_reg) {
+		oval1 = chip->ctlregs[left_reg];
+		oval2 = chip->ctlregs[right_reg];
+		val1 = (oval1 & ~(mask << shift_left)) | val1;
+		val2 = (oval2 & ~(mask << shift_right)) | val2;
+		change = val1 != oval1 || val2 != oval2;
+		__snd_opl3sa2_write(chip, left_reg, val1);
+		__snd_opl3sa2_write(chip, right_reg, val2);
+	} else {
+		oval1 = chip->ctlregs[left_reg];
+		val1 = (oval1 & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2;
+		change = val1 != oval1;
+		__snd_opl3sa2_write(chip, left_reg, val1);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_opl3sa2_controls[] = {
+OPL3SA2_DOUBLE("Master Playback Switch", 0, 0x07, 0x08, 7, 7, 1, 1),
+OPL3SA2_DOUBLE("Master Playback Volume", 0, 0x07, 0x08, 0, 0, 15, 1),
+OPL3SA2_SINGLE("Mic Playback Switch", 0, 0x09, 7, 1, 1),
+OPL3SA2_SINGLE("Mic Playback Volume", 0, 0x09, 0, 31, 1)
+};
+
+static snd_kcontrol_new_t snd_opl3sa2_tone_controls[] = {
+OPL3SA2_DOUBLE("3D Control - Wide", 0, 0x14, 0x14, 4, 0, 7, 0),
+OPL3SA2_DOUBLE("Tone Control - Bass", 0, 0x15, 0x15, 4, 0, 7, 0),
+OPL3SA2_DOUBLE("Tone Control - Treble", 0, 0x16, 0x16, 4, 0, 7, 0)
+};
+
+static void snd_opl3sa2_master_free(snd_kcontrol_t *kcontrol)
+{
+	opl3sa2_t *chip = snd_kcontrol_chip(kcontrol);
+	chip->master_switch = NULL;
+	chip->master_volume = NULL;
+}
+
+static int __init snd_opl3sa2_mixer(opl3sa2_t *chip)
+{
+	snd_card_t *card = chip->card;
+	snd_ctl_elem_id_t id1, id2;
+	snd_kcontrol_t *kctl;
+	unsigned int idx;
+	int err;
+
+	memset(&id1, 0, sizeof(id1));
+	memset(&id2, 0, sizeof(id2));
+	id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	/* reassign AUX0 to CD */
+        strcpy(id1.name, "Aux Playback Switch");
+        strcpy(id2.name, "CD Playback Switch");
+        if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+                return err;
+        strcpy(id1.name, "Aux Playback Volume");
+        strcpy(id2.name, "CD Playback Volume");
+        if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+                return err;
+	/* reassign AUX1 to FM */
+        strcpy(id1.name, "Aux Playback Switch"); id1.index = 1;
+        strcpy(id2.name, "FM Playback Switch");
+        if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+                return err;
+        strcpy(id1.name, "Aux Playback Volume");
+        strcpy(id2.name, "FM Playback Volume");
+        if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+                return err;
+	/* add OPL3SA2 controls */
+	for (idx = 0; idx < ARRAY_SIZE(snd_opl3sa2_controls); idx++) {
+		if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_opl3sa2_controls[idx], chip))) < 0)
+			return err;
+		switch (idx) {
+		case 0: chip->master_switch = kctl; kctl->private_free = snd_opl3sa2_master_free; break;
+		case 1: chip->master_volume = kctl; kctl->private_free = snd_opl3sa2_master_free; break;
+		}
+	}
+	if (chip->version > 2) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_opl3sa2_tone_controls); idx++)
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_opl3sa2_tone_controls[idx], chip))) < 0)
+				return err;
+	}
+	return 0;
+}
+
+/* Power Management support functions */
+#ifdef CONFIG_PM
+static int snd_opl3sa2_suspend(snd_card_t *card, pm_message_t state)
+{
+	opl3sa2_t *chip = card->pm_private_data;
+
+	snd_pcm_suspend_all(chip->cs4231->pcm); /* stop before saving regs */
+	chip->cs4231_suspend(chip->cs4231);
+
+	/* power down */
+	snd_opl3sa2_write(chip, OPL3SA2_PM_CTRL, OPL3SA2_PM_D3);
+
+	return 0;
+}
+
+static int snd_opl3sa2_resume(snd_card_t *card)
+{
+	opl3sa2_t *chip = card->pm_private_data;
+	int i;
+
+	/* power up */
+	snd_opl3sa2_write(chip, OPL3SA2_PM_CTRL, OPL3SA2_PM_D0);
+
+	/* restore registers */
+	for (i = 2; i <= 0x0a; i++) {
+		if (i != OPL3SA2_IRQ_STATUS)
+			snd_opl3sa2_write(chip, i, chip->ctlregs[i]);
+	}
+	if (chip->version > 2) {
+		for (i = 0x12; i <= 0x16; i++)
+			snd_opl3sa2_write(chip, i, chip->ctlregs[i]);
+	}
+	/* restore cs4231 */
+	chip->cs4231_resume(chip->cs4231);
+
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+#ifdef CONFIG_PNP
+static int __init snd_opl3sa2_pnp(int dev, opl3sa2_t *chip,
+				  struct pnp_card_link *card,
+				  const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	struct pnp_resource_table * cfg = kmalloc(sizeof(struct pnp_resource_table), GFP_KERNEL);
+	int err;
+
+	if (!cfg)
+		return -ENOMEM;
+	pdev = chip->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (chip->dev == NULL) {
+		kfree(cfg);
+		return -EBUSY;
+	}
+	/* PnP initialization */
+	pnp_init_resource_table(cfg);
+	if (sb_port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[0], sb_port[dev], 16);
+	if (wss_port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[1], wss_port[dev], 8);
+	if (fm_port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[2], fm_port[dev], 4);
+	if (midi_port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[3], midi_port[dev], 2);
+	if (port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[4], port[dev], 2);
+	if (dma1[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[0], dma1[dev], 1);
+	if (dma2[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[1], dma2[dev], 1);
+	if (irq[dev] != SNDRV_AUTO_IRQ)
+		pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1);
+	err = pnp_manual_config_dev(pdev, cfg, 0);
+	if (err < 0)
+		snd_printk(KERN_ERR "PnP manual resources are invalid, using auto config\n");
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		kfree(cfg);
+		snd_printk(KERN_ERR "PnP configure failure (out of resources?) err = %d\n", err);
+		return -EBUSY;
+	}
+	sb_port[dev] = pnp_port_start(pdev, 0);
+	wss_port[dev] = pnp_port_start(pdev, 1);
+	fm_port[dev] = pnp_port_start(pdev, 2);
+	midi_port[dev] = pnp_port_start(pdev, 3);
+	port[dev] = pnp_port_start(pdev, 4);
+	dma1[dev] = pnp_dma(pdev, 0);
+	dma2[dev] = pnp_dma(pdev, 1);
+	irq[dev] = pnp_irq(pdev, 0);
+	snd_printdd("PnP OPL3-SA: sb port=0x%lx, wss port=0x%lx, fm port=0x%lx, midi port=0x%lx\n",
+		sb_port[dev], wss_port[dev], fm_port[dev], midi_port[dev]);
+	snd_printdd("PnP OPL3-SA: control port=0x%lx, dma1=%i, dma2=%i, irq=%i\n",
+		port[dev], dma1[dev], dma2[dev], irq[dev]);
+	kfree(cfg);
+	return 0;
+}
+#endif /* CONFIG_PNP */
+
+static int snd_opl3sa2_free(opl3sa2_t *chip)
+{
+	if (chip->irq >= 0)
+		free_irq(chip->irq, (void *)chip);
+	if (chip->res_port) {
+		release_resource(chip->res_port);
+		kfree_nocheck(chip->res_port);
+	}
+	kfree(chip);
+	return 0;
+}
+
+static int snd_opl3sa2_dev_free(snd_device_t *device)
+{
+	opl3sa2_t *chip = device->device_data;
+	return snd_opl3sa2_free(chip);
+}
+
+static int __devinit snd_opl3sa2_probe(int dev,
+				       struct pnp_card_link *pcard,
+				       const struct pnp_card_device_id *pid)
+{
+	int xirq, xdma1, xdma2;
+	snd_card_t *card;
+	struct snd_opl3sa2 *chip;
+	cs4231_t *cs4231;
+	opl3_t *opl3;
+	static snd_device_ops_t ops = {
+		.dev_free =	snd_opl3sa2_dev_free,
+	};
+	int err;
+
+#ifdef CONFIG_PNP
+	if (!isapnp[dev]) {
+#endif
+		if (port[dev] == SNDRV_AUTO_PORT) {
+			snd_printk("specify port\n");
+			return -EINVAL;
+		}
+		if (wss_port[dev] == SNDRV_AUTO_PORT) {
+			snd_printk("specify wss_port\n");
+			return -EINVAL;
+		}
+		if (fm_port[dev] == SNDRV_AUTO_PORT) {
+			snd_printk("specify fm_port\n");
+			return -EINVAL;
+		}
+		if (midi_port[dev] == SNDRV_AUTO_PORT) {
+			snd_printk("specify midi_port\n");
+			return -EINVAL;
+		}
+#ifdef CONFIG_PNP
+	}
+#endif
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
+	if (card == NULL)
+		return -ENOMEM;
+	strcpy(card->driver, "OPL3SA2");
+	strcpy(card->shortname, "Yamaha OPL3-SA2");
+	chip = kcalloc(1, sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		err = -ENOMEM;
+		goto __error;
+	}
+	spin_lock_init(&chip->reg_lock);
+	chip->irq = -1;
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0)
+		goto __error;
+#ifdef CONFIG_PNP
+	if (isapnp[dev]) {
+		if ((err = snd_opl3sa2_pnp(dev, chip, pcard, pid)) < 0)
+			goto __error;
+		snd_card_set_dev(card, &pcard->card->dev);
+	}
+#endif
+	chip->ymode = opl3sa3_ymode[dev] & 0x03 ; /* initialise this card from supplied (or default) parameter*/ 
+	chip->card = card;
+	chip->port = port[dev];
+	xirq = irq[dev];
+	xdma1 = dma1[dev];
+	xdma2 = dma2[dev];
+	if (xdma2 < 0)
+		chip->single_dma = 1;
+	if ((err = snd_opl3sa2_detect(chip)) < 0)
+		goto __error;
+	if (request_irq(xirq, snd_opl3sa2_interrupt, SA_INTERRUPT, "OPL3-SA2", (void *)chip)) {
+		snd_printk(KERN_ERR "opl3sa2: can't grab IRQ %d\n", xirq);
+		err = -ENODEV;
+		goto __error;
+	}
+	chip->irq = xirq;
+	if ((err = snd_cs4231_create(card,
+				     wss_port[dev] + 4, -1,
+				     xirq, xdma1, xdma2,
+				     CS4231_HW_OPL3SA2,
+				     CS4231_HWSHARE_IRQ,
+				     &cs4231)) < 0) {
+		snd_printd("Oops, WSS not detected at 0x%lx\n", wss_port[dev] + 4);
+		goto __error;
+	}
+	chip->cs4231 = cs4231;
+	if ((err = snd_cs4231_pcm(cs4231, 0, NULL)) < 0)
+		goto __error;
+	if ((err = snd_cs4231_mixer(cs4231)) < 0)
+		goto __error;
+	if ((err = snd_opl3sa2_mixer(chip)) < 0)
+		goto __error;
+	if ((err = snd_cs4231_timer(cs4231, 0, NULL)) < 0)
+		goto __error;
+	if (fm_port[dev] >= 0x340 && fm_port[dev] < 0x400) {
+		if ((err = snd_opl3_create(card, fm_port[dev],
+					   fm_port[dev] + 2,
+					   OPL3_HW_OPL3, 0, &opl3)) < 0)
+			goto __error;
+		if ((err = snd_opl3_timer_new(opl3, 1, 2)) < 0)
+			goto __error;
+		if ((err = snd_opl3_hwdep_new(opl3, 0, 1, &chip->synth)) < 0)
+			goto __error;
+	}
+	if (midi_port[dev] >= 0x300 && midi_port[dev] < 0x340) {
+		if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_OPL3SA2,
+					       midi_port[dev], 0,
+					       xirq, 0, &chip->rmidi)) < 0)
+			goto __error;
+	}
+#ifdef CONFIG_PM
+	chip->cs4231_suspend = chip->cs4231->suspend;
+	chip->cs4231_resume = chip->cs4231->resume;
+	/* now clear callbacks for cs4231 */
+	chip->cs4231->suspend = NULL;
+	chip->cs4231->resume = NULL;
+	snd_card_set_isa_pm_callback(card, snd_opl3sa2_suspend, snd_opl3sa2_resume, chip);
+#endif
+
+	sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+		card->shortname, chip->port, xirq, xdma1);
+	if (dma2 >= 0)
+		sprintf(card->longname + strlen(card->longname), "&%d", xdma2);
+
+	if ((err = snd_card_register(card)) < 0)
+		goto __error;
+
+	if (pcard)
+		pnp_set_card_drvdata(pcard, card);
+	else
+		snd_opl3sa2_legacy[dev] = card;
+	return 0;
+
+ __error:
+	snd_card_free(card);
+	return err;
+}
+
+#ifdef CONFIG_PNP
+static int __devinit snd_opl3sa2_pnp_detect(struct pnp_card_link *card,
+					    const struct pnp_card_device_id *id)
+{
+        static int dev;
+        int res;
+
+        for ( ; dev < SNDRV_CARDS; dev++) {
+                if (!enable[dev] || !isapnp[dev])
+                        continue;
+                res = snd_opl3sa2_probe(dev, card, id);
+                if (res < 0)
+                        return res;
+                dev++;
+                return 0;
+        }
+        return -ENODEV;
+}
+
+static void __devexit snd_opl3sa2_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_t *card = (snd_card_t *) pnp_get_card_drvdata(pcard);
+        
+	snd_card_disconnect(card);
+	snd_card_free_in_thread(card);
+}
+
+static struct pnp_card_driver opl3sa2_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DISABLE,
+	.name = "opl3sa2",
+	.id_table = snd_opl3sa2_pnpids,
+	.probe = snd_opl3sa2_pnp_detect,
+	.remove = __devexit_p(snd_opl3sa2_pnp_remove),
+};
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_opl3sa2_init(void)
+{
+	int dev, cards = 0;
+
+	for (dev = 0; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev])
+			continue;
+#ifdef CONFIG_PNP
+		if (isapnp[dev])
+			continue;
+#endif
+		if (snd_opl3sa2_probe(dev, NULL, NULL) >= 0)
+			cards++;
+	}
+#ifdef CONFIG_PNP
+	cards += pnp_register_card_driver(&opl3sa2_pnpc_driver);
+#endif
+	if (!cards) {
+#ifdef MODULE
+		snd_printk(KERN_ERR "Yamaha OPL3-SA soundcard not found or device busy\n");
+#endif
+#ifdef CONFIG_PNP
+		pnp_unregister_card_driver(&opl3sa2_pnpc_driver);
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_opl3sa2_exit(void)
+{
+	int idx;
+
+#ifdef CONFIG_PNP
+	/* PnP cards first */
+	pnp_unregister_card_driver(&opl3sa2_pnpc_driver);
+#endif
+	for (idx = 0; idx < SNDRV_CARDS; idx++)
+		snd_card_free(snd_opl3sa2_legacy[idx]);
+}
+
+module_init(alsa_card_opl3sa2_init)
+module_exit(alsa_card_opl3sa2_exit)
diff --git a/sound/isa/opti9xx/Makefile b/sound/isa/opti9xx/Makefile
new file mode 100644
index 0000000..28c6407
--- /dev/null
+++ b/sound/isa/opti9xx/Makefile
@@ -0,0 +1,13 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-opti92x-ad1848-objs := opti92x-ad1848.o
+snd-opti92x-cs4231-objs := opti92x-cs4231.o
+snd-opti93x-objs := opti93x.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_OPTI92X_AD1848) += snd-opti92x-ad1848.o
+obj-$(CONFIG_SND_OPTI92X_CS4231) += snd-opti92x-cs4231.o
+obj-$(CONFIG_SND_OPTI93X) += snd-opti93x.o
diff --git a/sound/isa/opti9xx/opti92x-ad1848.c b/sound/isa/opti9xx/opti92x-ad1848.c
new file mode 100644
index 0000000..411a702
--- /dev/null
+++ b/sound/isa/opti9xx/opti92x-ad1848.c
@@ -0,0 +1,2226 @@
+/*
+    card-opti92x-ad1848.c - driver for OPTi 82c92x based soundcards.
+    Copyright (C) 1998-2000 by Massimo Piccioni <dafastidio@libero.it>
+
+    Part of this code was developed at the Italian Ministry of Air Defence,
+    Sixth Division (oh, che pace ...), Rome.
+
+    Thanks to Maria Grazia Pollarini, Salvatore Vassallo.
+
+    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 <asm/dma.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#ifdef CS4231
+#include <sound/cs4231.h>
+#else
+#ifndef OPTi93X
+#include <sound/ad1848.h>
+#else
+#include <sound/control.h>
+#include <sound/pcm.h>
+#endif	/* OPTi93X */
+#endif	/* CS4231 */
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#ifndef OPTi93X
+#include <sound/opl4.h>
+#endif
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>");
+MODULE_LICENSE("GPL");
+#ifdef OPTi93X
+MODULE_DESCRIPTION("OPTi93X");
+MODULE_SUPPORTED_DEVICE("{{OPTi,82C931/3}}");
+#else	/* OPTi93X */
+#ifdef CS4231
+MODULE_DESCRIPTION("OPTi92X - CS4231");
+MODULE_SUPPORTED_DEVICE("{{OPTi,82C924 (CS4231)},"
+		"{OPTi,82C925 (CS4231)}}");
+#else	/* CS4231 */
+MODULE_DESCRIPTION("OPTi92X - AD1848");
+MODULE_SUPPORTED_DEVICE("{{OPTi,82C924 (AD1848)},"
+		"{OPTi,82C925 (AD1848)},"
+	        "{OAK,Mozart}}");
+#endif	/* CS4231 */
+#endif	/* OPTi93X */
+
+static int index = SNDRV_DEFAULT_IDX1;	/* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1;		/* ID for this card */
+//static int enable = SNDRV_DEFAULT_ENABLE1;	/* Enable this card */
+static int isapnp = 1;			/* Enable ISA PnP detection */
+static long port = SNDRV_DEFAULT_PORT1; 	/* 0x530,0xe80,0xf40,0x604 */
+static long mpu_port = SNDRV_DEFAULT_PORT1;	/* 0x300,0x310,0x320,0x330 */
+static long fm_port = SNDRV_DEFAULT_PORT1;	/* 0x388 */
+static int irq = SNDRV_DEFAULT_IRQ1;		/* 5,7,9,10,11 */
+static int mpu_irq = SNDRV_DEFAULT_IRQ1;	/* 5,7,9,10 */
+static int dma1 = SNDRV_DEFAULT_DMA1;		/* 0,1,3 */
+#if defined(CS4231) || defined(OPTi93X)
+static int dma2 = SNDRV_DEFAULT_DMA1;		/* 0,1,3 */
+#endif	/* CS4231 || OPTi93X */
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for opti9xx based soundcard.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for opti9xx based soundcard.");
+//module_param(enable, bool, 0444);
+//MODULE_PARM_DESC(enable, "Enable opti9xx soundcard.");
+module_param(isapnp, bool, 0444);
+MODULE_PARM_DESC(isapnp, "Enable ISA PnP detection for specified soundcard.");
+module_param(port, long, 0444);
+MODULE_PARM_DESC(port, "WSS port # for opti9xx driver.");
+module_param(mpu_port, long, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for opti9xx driver.");
+module_param(fm_port, long, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for opti9xx driver.");
+module_param(irq, int, 0444);
+MODULE_PARM_DESC(irq, "WSS irq # for opti9xx driver.");
+module_param(mpu_irq, int, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 irq # for opti9xx driver.");
+module_param(dma1, int, 0444);
+MODULE_PARM_DESC(dma1, "1st dma # for opti9xx driver.");
+#if defined(CS4231) || defined(OPTi93X)
+module_param(dma2, int, 0444);
+MODULE_PARM_DESC(dma2, "2nd dma # for opti9xx driver.");
+#endif	/* CS4231 || OPTi93X */
+
+#define OPTi9XX_HW_DETECT	0
+#define OPTi9XX_HW_82C928	1
+#define OPTi9XX_HW_82C929	2
+#define OPTi9XX_HW_82C924	3
+#define OPTi9XX_HW_82C925	4
+#define OPTi9XX_HW_82C930	5
+#define OPTi9XX_HW_82C931	6
+#define OPTi9XX_HW_82C933	7
+#define OPTi9XX_HW_LAST		OPTi9XX_HW_82C933
+
+#define OPTi9XX_MC_REG(n)	n
+
+typedef struct _snd_opti9xx opti9xx_t;
+
+#ifdef OPTi93X
+
+#define OPTi93X_INDEX			0x00
+#define OPTi93X_DATA			0x01
+#define OPTi93X_STATUS			0x02
+#define OPTi93X_DDATA			0x03
+#define OPTi93X_PORT(chip, r)		((chip)->port + OPTi93X_##r)
+
+#define OPTi93X_MIXOUT_LEFT		0x00
+#define OPTi93X_MIXOUT_RIGHT		0x01
+#define OPTi93X_CD_LEFT_INPUT		0x02
+#define OPTi93X_CD_RIGHT_INPUT		0x03
+#define OPTi930_AUX_LEFT_INPUT		0x04
+#define OPTi930_AUX_RIGHT_INPUT		0x05
+#define OPTi931_FM_LEFT_INPUT		0x04
+#define OPTi931_FM_RIGHT_INPUT		0x05
+#define OPTi93X_DAC_LEFT		0x06
+#define OPTi93X_DAC_RIGHT		0x07
+#define OPTi93X_PLAY_FORMAT		0x08
+#define OPTi93X_IFACE_CONF		0x09
+#define OPTi93X_PIN_CTRL		0x0a
+#define OPTi93X_ERR_INIT		0x0b
+#define OPTi93X_ID			0x0c
+#define OPTi93X_PLAY_UPR_CNT		0x0e
+#define OPTi93X_PLAY_LWR_CNT		0x0f
+#define OPTi931_AUX_LEFT_INPUT		0x10
+#define OPTi931_AUX_RIGHT_INPUT		0x11
+#define OPTi93X_LINE_LEFT_INPUT		0x12
+#define OPTi93X_LINE_RIGHT_INPUT	0x13
+#define OPTi93X_MIC_LEFT_INPUT		0x14
+#define OPTi93X_MIC_RIGHT_INPUT		0x15
+#define OPTi93X_OUT_LEFT		0x16
+#define OPTi93X_OUT_RIGHT		0x17
+#define OPTi93X_CAPT_FORMAT		0x1c
+#define OPTi93X_CAPT_UPR_CNT		0x1e
+#define OPTi93X_CAPT_LWR_CNT		0x1f
+
+#define OPTi93X_TRD			0x20
+#define OPTi93X_MCE			0x40
+#define OPTi93X_INIT			0x80
+
+#define OPTi93X_MIXOUT_MIC_GAIN		0x20
+#define OPTi93X_MIXOUT_LINE		0x00
+#define OPTi93X_MIXOUT_CD		0x40
+#define OPTi93X_MIXOUT_MIC		0x80
+#define OPTi93X_MIXOUT_MIXER		0xc0
+
+#define OPTi93X_STEREO			0x10
+#define OPTi93X_LINEAR_8		0x00
+#define OPTi93X_ULAW_8			0x20
+#define OPTi93X_LINEAR_16_LIT		0x40
+#define OPTi93X_ALAW_8			0x60
+#define OPTi93X_ADPCM_16		0xa0
+#define OPTi93X_LINEAR_16_BIG		0xc0
+
+#define OPTi93X_CAPTURE_PIO		0x80
+#define OPTi93X_PLAYBACK_PIO		0x40
+#define OPTi93X_AUTOCALIB		0x08
+#define OPTi93X_SINGLE_DMA		0x04
+#define OPTi93X_CAPTURE_ENABLE		0x02
+#define OPTi93X_PLAYBACK_ENABLE		0x01
+
+#define OPTi93X_IRQ_ENABLE		0x02
+
+#define OPTi93X_DMA_REQUEST		0x10
+#define OPTi93X_CALIB_IN_PROGRESS	0x20
+
+#define OPTi93X_IRQ_PLAYBACK		0x04
+#define OPTi93X_IRQ_CAPTURE		0x08
+
+
+typedef struct _snd_opti93x opti93x_t;
+
+struct _snd_opti93x {
+	unsigned long port;
+	struct resource *res_port;
+	int irq;
+	int dma1;
+	int dma2;
+
+	opti9xx_t *chip;
+	unsigned short hardware;
+	unsigned char image[32];
+
+	unsigned char mce_bit;
+	unsigned short mode;
+	int mute;
+
+	spinlock_t lock;
+
+	snd_card_t *card;
+	snd_pcm_t *pcm;
+	snd_pcm_substream_t *playback_substream;
+	snd_pcm_substream_t *capture_substream;
+	unsigned int p_dma_size;
+	unsigned int c_dma_size;
+};
+
+#define OPTi93X_MODE_NONE	0x00
+#define OPTi93X_MODE_PLAY	0x01
+#define OPTi93X_MODE_CAPTURE	0x02
+#define OPTi93X_MODE_OPEN	(OPTi93X_MODE_PLAY | OPTi93X_MODE_CAPTURE)
+
+#endif /* OPTi93X */
+
+struct _snd_opti9xx {
+	unsigned short hardware;
+	unsigned char password;
+	char name[7];
+
+	unsigned long mc_base;
+	struct resource *res_mc_base;
+	unsigned long mc_base_size;
+#ifdef OPTi93X
+	unsigned long mc_indir_index;
+#endif	/* OPTi93X */
+	unsigned long pwd_reg;
+
+	spinlock_t lock;
+
+	long wss_base;
+	int irq;
+	int dma1;
+#if defined(CS4231) || defined(OPTi93X)
+	int dma2;
+#endif	/* CS4231 || OPTi93X */
+
+	long fm_port;
+
+	long mpu_port;
+	int mpu_irq;
+
+#ifdef CONFIG_PNP
+	struct pnp_dev *dev;
+	struct pnp_dev *devmpu;
+#endif	/* CONFIG_PNP */
+};
+
+static int snd_opti9xx_first_hit = 1;
+static snd_card_t *snd_opti9xx_legacy = SNDRV_DEFAULT_PTR1;
+
+#ifdef CONFIG_PNP
+
+static struct pnp_card_device_id snd_opti9xx_pnpids[] = {
+#ifndef OPTi93X
+	/* OPTi 82C924 */
+	{ .id = "OPT0924", .devs = { { "OPT0000" }, { "OPT0002" } }, .driver_data = 0x0924 },
+	/* OPTi 82C925 */
+	{ .id = "OPT0925", .devs = { { "OPT9250" }, { "OPT0002" } }, .driver_data = 0x0925 },
+#else
+	/* OPTi 82C931/3 */
+	{ .id = "OPT0931", .devs = { { "OPT9310" }, { "OPT0002" } }, .driver_data = 0x0931 },
+#endif	/* OPTi93X */
+	{ .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_opti9xx_pnpids);
+
+#endif	/* CONFIG_PNP */
+
+#ifdef OPTi93X
+#define DRIVER_NAME	"snd-card-opti93x"
+#else
+#define DRIVER_NAME	"snd-card-opti92x"
+#endif	/* OPTi93X */
+
+static char * snd_opti9xx_names[] = {
+	"unkown",
+	"82C928",	"82C929",
+	"82C924",	"82C925",
+	"82C930",	"82C931",	"82C933"
+};
+
+
+static long snd_legacy_find_free_ioport(long *port_table, long size)
+{
+	while (*port_table != -1) {
+		struct resource *res;
+		if ((res = request_region(*port_table, size, "ALSA test")) != NULL) {
+			release_resource(res);
+			kfree_nocheck(res);
+			return *port_table;
+		}
+		port_table++;
+	}
+	return -1;
+}
+
+static int __devinit snd_opti9xx_init(opti9xx_t *chip, unsigned short hardware)
+{
+	static int opti9xx_mc_size[] = {7, 7, 10, 10, 2, 2, 2};
+
+	chip->hardware = hardware;
+	strcpy(chip->name, snd_opti9xx_names[hardware]);
+
+	chip->mc_base_size = opti9xx_mc_size[hardware];  
+
+	spin_lock_init(&chip->lock);
+
+	chip->wss_base = -1;
+	chip->irq = -1;
+	chip->dma1 = -1;
+#if defined(CS4231) || defined (OPTi93X)
+	chip->dma2 = -1;
+#endif 	/* CS4231 || OPTi93X */
+	chip->fm_port = -1;
+	chip->mpu_port = -1;
+	chip->mpu_irq = -1;
+
+	switch (hardware) {
+#ifndef OPTi93X
+	case OPTi9XX_HW_82C928:
+	case OPTi9XX_HW_82C929:
+		chip->mc_base = 0xf8c;
+		chip->password = (hardware == OPTi9XX_HW_82C928) ? 0xe2 : 0xe3;
+		chip->pwd_reg = 3;
+		break;
+
+	case OPTi9XX_HW_82C924:
+	case OPTi9XX_HW_82C925:
+		chip->mc_base = 0xf8c;
+		chip->password = 0xe5;
+		chip->pwd_reg = 3;
+		break;
+#else	/* OPTi93X */
+
+	case OPTi9XX_HW_82C930:
+	case OPTi9XX_HW_82C931:
+	case OPTi9XX_HW_82C933:
+		chip->mc_base = (hardware == OPTi9XX_HW_82C930) ? 0xf8f : 0xf8d;
+		chip->mc_indir_index = 0xe0e;
+		chip->password = 0xe4;
+		chip->pwd_reg = 0;
+		break;
+#endif	/* OPTi93X */
+
+	default:
+		snd_printk("chip %d not supported\n", hardware);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static unsigned char snd_opti9xx_read(opti9xx_t *chip,
+				      unsigned char reg)
+{
+	unsigned long flags;
+	unsigned char retval = 0xff;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	outb(chip->password, chip->mc_base + chip->pwd_reg);
+
+	switch (chip->hardware) {
+#ifndef OPTi93X
+	case OPTi9XX_HW_82C924:
+	case OPTi9XX_HW_82C925:
+		if (reg > 7) {
+			outb(reg, chip->mc_base + 8);
+			outb(chip->password, chip->mc_base + chip->pwd_reg);
+			retval = inb(chip->mc_base + 9);
+			break;
+		}
+
+	case OPTi9XX_HW_82C928:
+	case OPTi9XX_HW_82C929:
+		retval = inb(chip->mc_base + reg);
+		break;
+#else	/* OPTi93X */
+
+	case OPTi9XX_HW_82C930:
+	case OPTi9XX_HW_82C931:
+	case OPTi9XX_HW_82C933:
+		outb(reg, chip->mc_indir_index);
+		outb(chip->password, chip->mc_base + chip->pwd_reg);
+		retval = inb(chip->mc_indir_index + 1);
+		break;
+#endif	/* OPTi93X */
+
+	default:
+		snd_printk("chip %d not supported\n", chip->hardware);
+	}
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return retval;
+}
+	
+static void snd_opti9xx_write(opti9xx_t *chip, unsigned char reg,
+			      unsigned char value)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	outb(chip->password, chip->mc_base + chip->pwd_reg);
+
+	switch (chip->hardware) {
+#ifndef OPTi93X
+	case OPTi9XX_HW_82C924:
+	case OPTi9XX_HW_82C925:
+		if (reg > 7) {
+			outb(reg, chip->mc_base + 8);
+			outb(chip->password, chip->mc_base + chip->pwd_reg);
+			outb(value, chip->mc_base + 9);
+			break;
+		}
+
+	case OPTi9XX_HW_82C928:
+	case OPTi9XX_HW_82C929:
+		outb(value, chip->mc_base + reg);
+		break;
+#else	/* OPTi93X */
+
+	case OPTi9XX_HW_82C930:
+	case OPTi9XX_HW_82C931:
+	case OPTi9XX_HW_82C933:
+		outb(reg, chip->mc_indir_index);
+		outb(chip->password, chip->mc_base + chip->pwd_reg);
+		outb(value, chip->mc_indir_index + 1);
+		break;
+#endif	/* OPTi93X */
+
+	default:
+		snd_printk("chip %d not supported\n", chip->hardware);
+	}
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+
+#define snd_opti9xx_write_mask(chip, reg, value, mask)	\
+	snd_opti9xx_write(chip, reg,			\
+		(snd_opti9xx_read(chip, reg) & ~(mask)) | ((value) & (mask)))
+
+
+static int __devinit snd_opti9xx_configure(opti9xx_t *chip)
+{
+	unsigned char wss_base_bits;
+	unsigned char irq_bits;
+	unsigned char dma_bits;
+	unsigned char mpu_port_bits = 0;
+	unsigned char mpu_irq_bits;
+
+	switch (chip->hardware) {
+#ifndef OPTi93X
+	case OPTi9XX_HW_82C924:
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0xf0, 0xfc);
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x02);
+
+	case OPTi9XX_HW_82C925:
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80);
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), 0x00, 0x20);
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0xf0, 0xff);
+#ifdef CS4231
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02);
+#else
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x00, 0x02);
+#endif	/* CS4231 */
+		break;
+
+	case OPTi9XX_HW_82C928:
+	case OPTi9XX_HW_82C929:
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80);
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), 0x00, 0x20);
+		/*
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0xa2, 0xae);
+		*/
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0x00, 0x0c);
+#ifdef CS4231
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02);
+#else
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x00, 0x02);
+#endif	/* CS4231 */
+		break;
+
+#else	/* OPTi93X */
+	case OPTi9XX_HW_82C930:
+	case OPTi9XX_HW_82C931:
+	case OPTi9XX_HW_82C933:
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x03);
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0x00, 0xff);
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0x10 |
+			(chip->hardware == OPTi9XX_HW_82C930 ? 0x00 : 0x04),
+			0x34);
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x20, 0xbf);
+		break;
+#endif	/* OPTi93X */
+
+	default:
+		snd_printk("chip %d not supported\n", chip->hardware);
+		return -EINVAL;
+	}
+
+	switch (chip->wss_base) {
+	case 0x530:
+		wss_base_bits = 0x00;
+		break;
+	case 0x604:
+		wss_base_bits = 0x03;
+		break;
+	case 0xe80:
+		wss_base_bits = 0x01;
+		break;
+	case 0xf40:
+		wss_base_bits = 0x02;
+		break;
+	default:
+		snd_printk("WSS port 0x%lx not valid\n", chip->wss_base);
+		goto __skip_base;
+	}
+	snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), wss_base_bits << 4, 0x30);
+
+__skip_base:
+	switch (chip->irq) {
+//#ifdef OPTi93X
+	case 5:
+		irq_bits = 0x05;
+		break;
+//#endif	/* OPTi93X */
+	case 7:
+		irq_bits = 0x01;
+		break;
+	case 9:
+		irq_bits = 0x02;
+		break;
+	case 10:
+		irq_bits = 0x03;
+		break;
+	case 11:
+		irq_bits = 0x04;
+		break;
+	default:
+		snd_printk("WSS irq # %d not valid\n", chip->irq);
+		goto __skip_resources;
+	}
+
+	switch (chip->dma1) {
+	case 0:
+		dma_bits = 0x01;
+		break;
+	case 1:
+		dma_bits = 0x02;
+		break;
+	case 3:
+		dma_bits = 0x03;
+		break;
+	default:
+		snd_printk("WSS dma1 # %d not valid\n", chip->dma1);
+		goto __skip_resources;
+	}
+
+#if defined(CS4231) || defined(OPTi93X)
+	if (chip->dma1 == chip->dma2) {
+		snd_printk("don't want to share dmas\n");
+		return -EBUSY;
+	}
+
+	switch (chip->dma2) {
+	case 0:
+	case 1:
+		break;
+	default:
+		snd_printk("WSS dma2 # %d not valid\n", chip->dma2);
+		goto __skip_resources;
+	}
+	dma_bits |= 0x04;
+#endif	/* CS4231 || OPTi93X */
+
+#ifndef OPTi93X
+	 outb(irq_bits << 3 | dma_bits, chip->wss_base);
+#else /* OPTi93X */
+	snd_opti9xx_write(chip, OPTi9XX_MC_REG(3), (irq_bits << 3 | dma_bits));
+#endif /* OPTi93X */
+
+__skip_resources:
+	if (chip->hardware > OPTi9XX_HW_82C928) {
+		switch (chip->mpu_port) {
+		case 0:
+		case -1:
+			break;
+		case 0x300:
+			mpu_port_bits = 0x03;
+			break;
+		case 0x310:
+			mpu_port_bits = 0x02;
+			break;
+		case 0x320:
+			mpu_port_bits = 0x01;
+			break;
+		case 0x330:
+			mpu_port_bits = 0x00;
+			break;
+		default:
+			snd_printk("MPU-401 port 0x%lx not valid\n",
+				chip->mpu_port);
+			goto __skip_mpu;
+		}
+
+		switch (chip->mpu_irq) {
+		case 5:
+			mpu_irq_bits = 0x02;
+			break;
+		case 7:
+			mpu_irq_bits = 0x03;
+			break;
+		case 9:
+			mpu_irq_bits = 0x00;
+			break;
+		case 10:
+			mpu_irq_bits = 0x01;
+			break;
+		default:
+			snd_printk("MPU-401 irq # %d not valid\n",
+				chip->mpu_irq);
+			goto __skip_mpu;
+		}
+
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6),
+			(chip->mpu_port <= 0) ? 0x00 :
+				0x80 | mpu_port_bits << 5 | mpu_irq_bits << 3,
+			0xf8);
+	}
+__skip_mpu:
+
+	return 0;
+}
+
+#ifdef OPTi93X
+
+static unsigned char snd_opti93x_default_image[32] =
+{
+	0x00,		/* 00/00 - l_mixout_outctrl */
+	0x00,		/* 01/01 - r_mixout_outctrl */
+	0x88,		/* 02/02 - l_cd_inctrl */
+	0x88,		/* 03/03 - r_cd_inctrl */
+	0x88,		/* 04/04 - l_a1/fm_inctrl */
+	0x88,		/* 05/05 - r_a1/fm_inctrl */
+	0x80,		/* 06/06 - l_dac_inctrl */
+	0x80,		/* 07/07 - r_dac_inctrl */
+	0x00,		/* 08/08 - ply_dataform_reg */
+	0x00,		/* 09/09 - if_conf */
+	0x00,		/* 0a/10 - pin_ctrl */
+	0x00,		/* 0b/11 - err_init_reg */
+	0x0a,		/* 0c/12 - id_reg */
+	0x00,		/* 0d/13 - reserved */
+	0x00,		/* 0e/14 - ply_upcount_reg */
+	0x00,		/* 0f/15 - ply_lowcount_reg */
+	0x88,		/* 10/16 - reserved/l_a1_inctrl */
+	0x88,		/* 11/17 - reserved/r_a1_inctrl */
+	0x88,		/* 12/18 - l_line_inctrl */
+	0x88,		/* 13/19 - r_line_inctrl */
+	0x88,		/* 14/20 - l_mic_inctrl */
+	0x88,		/* 15/21 - r_mic_inctrl */
+	0x80,		/* 16/22 - l_out_outctrl */
+	0x80,		/* 17/23 - r_out_outctrl */
+	0x00,		/* 18/24 - reserved */
+	0x00,		/* 19/25 - reserved */
+	0x00,		/* 1a/26 - reserved */
+	0x00,		/* 1b/27 - reserved */
+	0x00,		/* 1c/28 - cap_dataform_reg */
+	0x00,		/* 1d/29 - reserved */
+	0x00,		/* 1e/30 - cap_upcount_reg */
+	0x00		/* 1f/31 - cap_lowcount_reg */
+};
+
+
+static int snd_opti93x_busy_wait(opti93x_t *chip)
+{
+	int timeout;
+
+	for (timeout = 250; timeout-- > 0; udelay(10))
+		if (!(inb(OPTi93X_PORT(chip, INDEX)) & OPTi93X_INIT))
+			return 0;
+
+	snd_printk("chip still busy.\n");
+	return -EBUSY;
+}
+
+static unsigned char snd_opti93x_in(opti93x_t *chip, unsigned char reg)
+{
+	snd_opti93x_busy_wait(chip);
+	outb(chip->mce_bit | (reg & 0x1f), OPTi93X_PORT(chip, INDEX));
+	return inb(OPTi93X_PORT(chip, DATA));
+}
+
+static void snd_opti93x_out(opti93x_t *chip, unsigned char reg,
+			    unsigned char value)
+{
+	snd_opti93x_busy_wait(chip);
+	outb(chip->mce_bit | (reg & 0x1f), OPTi93X_PORT(chip, INDEX));
+	outb(value, OPTi93X_PORT(chip, DATA));
+}
+
+static void snd_opti93x_out_image(opti93x_t *chip, unsigned char reg,
+				  unsigned char value)
+{
+	snd_opti93x_out(chip, reg, chip->image[reg] = value);
+}
+
+static void snd_opti93x_out_mask(opti93x_t *chip, unsigned char reg,
+				 unsigned char mask, unsigned char value)
+{
+	snd_opti93x_out_image(chip, reg,
+		(chip->image[reg] & ~mask) | (value & mask));
+}
+
+
+static void snd_opti93x_mce_up(opti93x_t *chip)
+{
+	snd_opti93x_busy_wait(chip);
+
+	chip->mce_bit = OPTi93X_MCE;
+	if (!(inb(OPTi93X_PORT(chip, INDEX)) & OPTi93X_MCE))
+		outb(chip->mce_bit, OPTi93X_PORT(chip, INDEX));
+}
+
+static void snd_opti93x_mce_down(opti93x_t *chip)
+{
+	snd_opti93x_busy_wait(chip);
+
+	chip->mce_bit = 0;
+	if (inb(OPTi93X_PORT(chip, INDEX)) & OPTi93X_MCE)
+		outb(chip->mce_bit, OPTi93X_PORT(chip, INDEX));
+}
+
+#define snd_opti93x_mute_reg(chip, reg, mute)	\
+	snd_opti93x_out(chip, reg, mute ? 0x80 : chip->image[reg]);
+
+static void snd_opti93x_mute(opti93x_t *chip, int mute)
+{
+	mute = mute ? 1 : 0;
+	if (chip->mute == mute)
+		return;
+
+	chip->mute = mute;
+
+	snd_opti93x_mute_reg(chip, OPTi93X_CD_LEFT_INPUT, mute);
+	snd_opti93x_mute_reg(chip, OPTi93X_CD_RIGHT_INPUT, mute);
+	switch (chip->hardware) {
+	case OPTi9XX_HW_82C930:
+		snd_opti93x_mute_reg(chip, OPTi930_AUX_LEFT_INPUT, mute);
+		snd_opti93x_mute_reg(chip, OPTi930_AUX_RIGHT_INPUT, mute);
+		break;
+	case OPTi9XX_HW_82C931:
+	case OPTi9XX_HW_82C933:
+		snd_opti93x_mute_reg(chip, OPTi931_FM_LEFT_INPUT, mute);
+		snd_opti93x_mute_reg(chip, OPTi931_FM_RIGHT_INPUT, mute);
+		snd_opti93x_mute_reg(chip, OPTi931_AUX_LEFT_INPUT, mute);
+		snd_opti93x_mute_reg(chip, OPTi931_AUX_RIGHT_INPUT, mute);
+	}
+	snd_opti93x_mute_reg(chip, OPTi93X_DAC_LEFT, mute);
+	snd_opti93x_mute_reg(chip, OPTi93X_DAC_RIGHT, mute);
+	snd_opti93x_mute_reg(chip, OPTi93X_LINE_LEFT_INPUT, mute);
+	snd_opti93x_mute_reg(chip, OPTi93X_LINE_RIGHT_INPUT, mute);
+	snd_opti93x_mute_reg(chip, OPTi93X_MIC_LEFT_INPUT, mute);
+	snd_opti93x_mute_reg(chip, OPTi93X_MIC_RIGHT_INPUT, mute);
+	snd_opti93x_mute_reg(chip, OPTi93X_OUT_LEFT, mute);
+	snd_opti93x_mute_reg(chip, OPTi93X_OUT_RIGHT, mute);
+}
+
+
+static unsigned int snd_opti93x_get_count(unsigned char format,
+					  unsigned int size)
+{
+	switch (format & 0xe0) {
+	case OPTi93X_LINEAR_16_LIT:
+	case OPTi93X_LINEAR_16_BIG:
+		size >>= 1;
+		break;
+	case OPTi93X_ADPCM_16:
+		return size >> 2;
+	}
+	return (format & OPTi93X_STEREO) ? (size >> 1) : size;
+}
+
+static unsigned int rates[] = {  5512,  6615,  8000,  9600, 11025, 16000, 
+				18900, 22050, 27428, 32000, 33075, 37800,
+				44100, 48000 };
+#define RATES ARRAY_SIZE(rates)
+
+static snd_pcm_hw_constraint_list_t hw_constraints_rates = {
+	.count = RATES,
+	.list = rates,
+	.mask = 0,
+};
+
+static unsigned char bits[] = {  0x01,  0x0f,  0x00,  0x0e,  0x03,  0x02,
+				 0x05,  0x07,  0x04,  0x06,  0x0d,  0x09,
+				 0x0b,  0x0c};
+
+static unsigned char snd_opti93x_get_freq(unsigned int rate)
+{
+	unsigned int i;
+
+	for (i = 0; i < RATES; i++) {
+		if (rate == rates[i])
+			return bits[i];
+	}
+	snd_BUG();
+	return bits[RATES-1];
+}
+
+static unsigned char snd_opti93x_get_format(opti93x_t *chip,
+					    unsigned int format, int channels)
+{
+	unsigned char retval = OPTi93X_LINEAR_8;
+
+	switch (format) {
+	case SNDRV_PCM_FORMAT_MU_LAW:
+		retval = OPTi93X_ULAW_8;
+		break;
+	case SNDRV_PCM_FORMAT_A_LAW:
+		retval = OPTi93X_ALAW_8;
+		break;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		retval = OPTi93X_LINEAR_16_LIT;
+		break;
+	case SNDRV_PCM_FORMAT_S16_BE:
+		retval = OPTi93X_LINEAR_16_BIG;
+		break;
+	case SNDRV_PCM_FORMAT_IMA_ADPCM:
+		retval = OPTi93X_ADPCM_16;
+	}
+	return (channels > 1) ? (retval | OPTi93X_STEREO) : retval;
+}
+
+
+static void snd_opti93x_playback_format(opti93x_t *chip, unsigned char fmt)
+{
+	unsigned char mask;
+
+	snd_opti93x_mute(chip, 1);
+
+	snd_opti93x_mce_up(chip);
+	mask = (chip->mode & OPTi93X_MODE_CAPTURE) ? 0xf0 : 0xff;
+	snd_opti93x_out_mask(chip, OPTi93X_PLAY_FORMAT, mask, fmt);
+	snd_opti93x_mce_down(chip);
+
+	snd_opti93x_mute(chip, 0);
+}
+
+static void snd_opti93x_capture_format(opti93x_t *chip, unsigned char fmt)
+{
+	snd_opti93x_mute(chip, 1);
+
+	snd_opti93x_mce_up(chip);
+	if (!(chip->mode & OPTi93X_MODE_PLAY))
+		snd_opti93x_out_mask(chip, OPTi93X_PLAY_FORMAT, 0x0f, fmt);
+	else
+		fmt = chip->image[OPTi93X_PLAY_FORMAT] & 0xf0;
+	snd_opti93x_out_image(chip, OPTi93X_CAPT_FORMAT, fmt);
+	snd_opti93x_mce_down(chip);
+
+	snd_opti93x_mute(chip, 0);
+}
+
+
+static int snd_opti93x_open(opti93x_t *chip, unsigned int mode)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	if (chip->mode & mode) {
+		spin_unlock_irqrestore(&chip->lock, flags);
+		return -EAGAIN;
+	}
+
+	if (!(chip->mode & OPTi93X_MODE_OPEN)) {
+		outb(0x00, OPTi93X_PORT(chip, STATUS));
+		snd_opti93x_out_mask(chip, OPTi93X_PIN_CTRL,
+			OPTi93X_IRQ_ENABLE, OPTi93X_IRQ_ENABLE);
+		chip->mode = mode;
+	}
+	else
+		chip->mode |= mode;
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+static void snd_opti93x_close(opti93x_t *chip, unsigned int mode)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	chip->mode &= ~mode;
+	if (chip->mode & OPTi93X_MODE_OPEN) {
+		spin_unlock_irqrestore(&chip->lock, flags);
+		return;
+	}
+
+	snd_opti93x_mute(chip, 1);
+
+	outb(0, OPTi93X_PORT(chip, STATUS));
+	snd_opti93x_out_mask(chip, OPTi93X_PIN_CTRL, OPTi93X_IRQ_ENABLE,
+		~OPTi93X_IRQ_ENABLE);
+
+	snd_opti93x_mce_up(chip);
+	snd_opti93x_out_image(chip, OPTi93X_IFACE_CONF, 0x00);
+	snd_opti93x_mce_down(chip);
+	chip->mode = 0;
+
+	snd_opti93x_mute(chip, 0);
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+static int snd_opti93x_trigger(snd_pcm_substream_t *substream, 
+			       unsigned char what, int cmd)
+{
+	opti93x_t *chip = snd_pcm_substream_chip(substream);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_STOP:
+	{
+		unsigned int what = 0;
+		struct list_head *pos;
+		snd_pcm_substream_t *s;
+		snd_pcm_group_for_each(pos, substream) {
+			s = snd_pcm_group_substream_entry(pos);
+			if (s == chip->playback_substream) {
+				what |= OPTi93X_PLAYBACK_ENABLE;
+				snd_pcm_trigger_done(s, substream);
+			} else if (s == chip->capture_substream) {
+				what |= OPTi93X_CAPTURE_ENABLE;
+				snd_pcm_trigger_done(s, substream);
+			}
+		}
+		spin_lock(&chip->lock);
+		if (cmd == SNDRV_PCM_TRIGGER_START) {
+			snd_opti93x_out_mask(chip, OPTi93X_IFACE_CONF, what, what);
+			if (what & OPTi93X_CAPTURE_ENABLE)
+				udelay(50);
+		} else
+			snd_opti93x_out_mask(chip, OPTi93X_IFACE_CONF, what, 0x00);
+		spin_unlock(&chip->lock);
+		break;
+	}
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int snd_opti93x_playback_trigger(snd_pcm_substream_t *substream, int cmd)
+{
+	return snd_opti93x_trigger(substream,
+				   OPTi93X_PLAYBACK_ENABLE, cmd);
+}
+
+static int snd_opti93x_capture_trigger(snd_pcm_substream_t * substream, int cmd)
+{
+	return snd_opti93x_trigger(substream,
+				   OPTi93X_CAPTURE_ENABLE, cmd);
+}
+
+static int snd_opti93x_hw_params(snd_pcm_substream_t * substream,
+				 snd_pcm_hw_params_t * hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+
+static int snd_opti93x_hw_free(snd_pcm_substream_t * substream)
+{
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+
+static int snd_opti93x_playback_prepare(snd_pcm_substream_t * substream)
+{
+	opti93x_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned long flags;
+	unsigned char format;
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	chip->p_dma_size = size;
+	snd_opti93x_out_mask(chip, OPTi93X_IFACE_CONF,
+		OPTi93X_PLAYBACK_ENABLE | OPTi93X_PLAYBACK_PIO,
+		~(OPTi93X_PLAYBACK_ENABLE | OPTi93X_PLAYBACK_PIO));
+
+	snd_dma_program(chip->dma1, runtime->dma_addr, size,
+		DMA_MODE_WRITE | DMA_AUTOINIT);
+
+	format = snd_opti93x_get_freq(runtime->rate);
+	format |= snd_opti93x_get_format(chip, runtime->format,
+		runtime->channels);
+	snd_opti93x_playback_format(chip, format);
+	format = chip->image[OPTi93X_PLAY_FORMAT];
+
+	count = snd_opti93x_get_count(format, count) - 1;
+	snd_opti93x_out_image(chip, OPTi93X_PLAY_LWR_CNT, count);
+	snd_opti93x_out_image(chip, OPTi93X_PLAY_UPR_CNT, count >> 8);
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+static int snd_opti93x_capture_prepare(snd_pcm_substream_t *substream)
+{
+	opti93x_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned long flags;
+	unsigned char format;
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	chip->c_dma_size = size;
+	snd_opti93x_out_mask(chip, OPTi93X_IFACE_CONF,
+		OPTi93X_CAPTURE_ENABLE | OPTi93X_CAPTURE_PIO,
+		(unsigned char)~(OPTi93X_CAPTURE_ENABLE | OPTi93X_CAPTURE_PIO));
+
+	snd_dma_program(chip->dma2, runtime->dma_addr, size,
+		DMA_MODE_READ | DMA_AUTOINIT);
+
+	format = snd_opti93x_get_freq(runtime->rate);
+	format |= snd_opti93x_get_format(chip, runtime->format,
+		runtime->channels);
+	snd_opti93x_capture_format(chip, format);
+	format = chip->image[OPTi93X_CAPT_FORMAT];
+
+	count = snd_opti93x_get_count(format, count) - 1;
+	snd_opti93x_out_image(chip, OPTi93X_CAPT_LWR_CNT, count);
+	snd_opti93x_out_image(chip, OPTi93X_CAPT_UPR_CNT, count >> 8);
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_opti93x_playback_pointer(snd_pcm_substream_t *substream)
+{
+	opti93x_t *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (!(chip->image[OPTi93X_IFACE_CONF] & OPTi93X_PLAYBACK_ENABLE))
+		return 0;
+
+	ptr = snd_dma_pointer(chip->dma1, chip->p_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_opti93x_capture_pointer(snd_pcm_substream_t *substream)
+{
+	opti93x_t *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	
+	if (!(chip->image[OPTi93X_IFACE_CONF] & OPTi93X_CAPTURE_ENABLE))
+		return 0;
+
+	ptr = snd_dma_pointer(chip->dma2, chip->c_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+
+static void snd_opti93x_overrange(opti93x_t *chip)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	if (snd_opti93x_in(chip, OPTi93X_ERR_INIT) & (0x08 | 0x02))
+		chip->capture_substream->runtime->overrange++;
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+static irqreturn_t snd_opti93x_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	opti93x_t *codec = dev_id;
+	unsigned char status;
+
+	status = snd_opti9xx_read(codec->chip, OPTi9XX_MC_REG(11));
+	if ((status & OPTi93X_IRQ_PLAYBACK) && codec->playback_substream)
+		snd_pcm_period_elapsed(codec->playback_substream);
+	if ((status & OPTi93X_IRQ_CAPTURE) && codec->capture_substream) {
+		snd_opti93x_overrange(codec);
+		snd_pcm_period_elapsed(codec->capture_substream);
+	}
+	outb(0x00, OPTi93X_PORT(codec, STATUS));
+	return IRQ_HANDLED;
+}
+
+
+static snd_pcm_hardware_t snd_opti93x_playback = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START),
+	.formats =		(SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | SNDRV_PCM_FMTBIT_IMA_ADPCM |
+				 SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE),
+	.rates =		SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5512,
+	.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,
+};
+
+static snd_pcm_hardware_t snd_opti93x_capture = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START),
+	.formats =		(SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | SNDRV_PCM_FMTBIT_IMA_ADPCM |
+				 SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE),
+	.rates =		SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5512,
+	.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,
+};
+
+static int snd_opti93x_playback_open(snd_pcm_substream_t *substream)
+{
+	int error;
+	opti93x_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+
+	if ((error = snd_opti93x_open(chip, OPTi93X_MODE_PLAY)) < 0)
+		return error;
+	snd_pcm_set_sync(substream);
+	chip->playback_substream = substream;
+	runtime->hw = snd_opti93x_playback;
+	snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.buffer_bytes_max);
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);
+	return error;
+}
+
+static int snd_opti93x_capture_open(snd_pcm_substream_t *substream)
+{
+	int error;
+	opti93x_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+
+	if ((error = snd_opti93x_open(chip, OPTi93X_MODE_CAPTURE)) < 0)
+		return error;
+	runtime->hw = snd_opti93x_capture;
+	snd_pcm_set_sync(substream);
+	chip->capture_substream = substream;
+	snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.buffer_bytes_max);
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);
+	return error;
+}
+
+static int snd_opti93x_playback_close(snd_pcm_substream_t *substream)
+{
+	opti93x_t *chip = snd_pcm_substream_chip(substream);
+
+	chip->playback_substream = NULL;
+	snd_opti93x_close(chip, OPTi93X_MODE_PLAY);
+	return 0;
+}
+
+static int snd_opti93x_capture_close(snd_pcm_substream_t *substream)
+{
+	opti93x_t *chip = snd_pcm_substream_chip(substream);
+
+	chip->capture_substream = NULL;
+	snd_opti93x_close(chip, OPTi93X_MODE_CAPTURE);
+	return 0;
+}
+
+
+static void snd_opti93x_init(opti93x_t *chip)
+{
+	unsigned long flags;
+	int i;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	snd_opti93x_mce_up(chip);
+
+	for (i = 0; i < 32; i++)
+		snd_opti93x_out_image(chip, i, snd_opti93x_default_image[i]);
+
+	snd_opti93x_mce_down(chip);
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+static int snd_opti93x_probe(opti93x_t *chip)
+{
+	unsigned long flags;
+	unsigned char val;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	val = snd_opti93x_in(chip, OPTi93X_ID) & 0x0f;
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	return (val == 0x0a) ? 0 : -ENODEV;
+}
+
+static int snd_opti93x_free(opti93x_t *chip)
+{
+	if (chip->res_port) {
+		release_resource(chip->res_port);
+		kfree_nocheck(chip->res_port);
+	}
+	if (chip->dma1 >= 0) {
+		disable_dma(chip->dma1);
+		free_dma(chip->dma1);
+	}
+	if (chip->dma2 >= 0) {
+		disable_dma(chip->dma2);
+		free_dma(chip->dma2);
+	}
+	if (chip->irq >= 0) {
+	  free_irq(chip->irq, chip);
+	}
+	kfree(chip);
+	return 0;
+}
+
+static int snd_opti93x_dev_free(snd_device_t *device)
+{
+	opti93x_t *chip = device->device_data;
+	return snd_opti93x_free(chip);
+}
+
+static const char *snd_opti93x_chip_id(opti93x_t *codec)
+{
+	switch (codec->hardware) {
+	case OPTi9XX_HW_82C930: return "82C930";
+	case OPTi9XX_HW_82C931: return "82C931";
+	case OPTi9XX_HW_82C933: return "82C933";
+	default:		return "???";
+	}
+}
+
+static int snd_opti93x_create(snd_card_t *card, opti9xx_t *chip,
+			      int dma1, int dma2,
+			      opti93x_t **rcodec)
+{
+	static snd_device_ops_t ops = {
+		.dev_free =	snd_opti93x_dev_free,
+	};
+	int error;
+	opti93x_t *codec;
+
+	*rcodec = NULL;
+	codec = kcalloc(1, sizeof(*codec), GFP_KERNEL);
+	if (codec == NULL)
+		return -ENOMEM;
+	codec->irq = -1;
+	codec->dma1 = -1;
+	codec->dma2 = -1;
+
+	if ((codec->res_port = request_region(chip->wss_base + 4, 4, "OPTI93x CODEC")) == NULL) {
+		snd_printk(KERN_ERR "opti9xx: can't grab port 0x%lx\n", chip->wss_base + 4);
+		snd_opti93x_free(codec);
+		return -EBUSY;
+	}
+	if (request_dma(dma1, "OPTI93x - 1")) {
+		snd_printk(KERN_ERR "opti9xx: can't grab DMA1 %d\n", dma1);
+		snd_opti93x_free(codec);
+		return -EBUSY;
+	}
+	codec->dma1 = chip->dma1;
+	if (request_dma(dma2, "OPTI93x - 2")) {
+		snd_printk(KERN_ERR "opti9xx: can't grab DMA2 %d\n", dma2);
+		snd_opti93x_free(codec);
+		return -EBUSY;
+	}
+	codec->dma2 = chip->dma2;
+
+	if (request_irq(chip->irq, snd_opti93x_interrupt, SA_INTERRUPT, DRIVER_NAME" - WSS", codec)) {
+		snd_printk(KERN_ERR "opti9xx: can't grab IRQ %d\n", chip->irq);
+		snd_opti93x_free(codec);
+		return -EBUSY;
+	}
+
+	codec->card = card;
+	codec->port = chip->wss_base + 4;
+	codec->irq = chip->irq;
+
+	spin_lock_init(&codec->lock);
+	codec->hardware = chip->hardware;
+	codec->chip = chip;
+
+	if ((error = snd_opti93x_probe(codec))) {
+		snd_opti93x_free(codec);
+		return error;
+	}
+
+	snd_opti93x_init(codec);
+
+	/* Register device */
+	if ((error = snd_device_new(card, SNDRV_DEV_LOWLEVEL, codec, &ops)) < 0) {
+		snd_opti93x_free(codec);
+		return error;
+	}
+
+	*rcodec = codec;
+	return 0;
+}
+
+static snd_pcm_ops_t snd_opti93x_playback_ops = {
+	.open =		snd_opti93x_playback_open,
+	.close =	snd_opti93x_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_opti93x_hw_params,
+	.hw_free =	snd_opti93x_hw_free,
+	.prepare =	snd_opti93x_playback_prepare,
+	.trigger =	snd_opti93x_playback_trigger,
+	.pointer =	snd_opti93x_playback_pointer,
+};
+
+static snd_pcm_ops_t snd_opti93x_capture_ops = {
+	.open =		snd_opti93x_capture_open,
+	.close =	snd_opti93x_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_opti93x_hw_params,
+	.hw_free =	snd_opti93x_hw_free,
+	.prepare =	snd_opti93x_capture_prepare,
+	.trigger =	snd_opti93x_capture_trigger,
+	.pointer =	snd_opti93x_capture_pointer,
+};
+
+static void snd_opti93x_pcm_free(snd_pcm_t *pcm)
+{
+	opti93x_t *codec = pcm->private_data;
+	codec->pcm = NULL;
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int snd_opti93x_pcm(opti93x_t *codec, int device, snd_pcm_t **rpcm)
+{
+	int error;
+	snd_pcm_t *pcm;
+
+	if ((error = snd_pcm_new(codec->card, "OPTi 82C93X", device, 1, 1, &pcm)))
+		return error;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_opti93x_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_opti93x_capture_ops);
+
+	pcm->private_data = codec;
+	pcm->private_free = snd_opti93x_pcm_free;
+	pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
+
+	strcpy(pcm->name, snd_opti93x_chip_id(codec));
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, codec->dma1 > 3 || codec->dma2 > 3 ? 128*1024 : 64*1024);
+
+	codec->pcm = pcm;
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+/*
+ *  MIXER part
+ */
+
+static int snd_opti93x_info_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	static char *texts[4] = {
+		"Line1", "Aux", "Mic", "Mix"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 2;
+	uinfo->value.enumerated.items = 4;
+	if (uinfo->value.enumerated.item > 3)
+		uinfo->value.enumerated.item = 3;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_opti93x_get_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	opti93x_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&chip->lock, flags);
+	ucontrol->value.enumerated.item[0] = (chip->image[OPTi93X_MIXOUT_LEFT] & OPTi93X_MIXOUT_MIXER) >> 6;
+	ucontrol->value.enumerated.item[1] = (chip->image[OPTi93X_MIXOUT_RIGHT] & OPTi93X_MIXOUT_MIXER) >> 6;
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+static int snd_opti93x_put_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	opti93x_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned short left, right;
+	int change;
+	
+	if (ucontrol->value.enumerated.item[0] > 3 ||
+	    ucontrol->value.enumerated.item[1] > 3)
+		return -EINVAL;
+	left = ucontrol->value.enumerated.item[0] << 6;
+	right = ucontrol->value.enumerated.item[1] << 6;
+	spin_lock_irqsave(&chip->lock, flags);
+	left = (chip->image[OPTi93X_MIXOUT_LEFT] & ~OPTi93X_MIXOUT_MIXER) | left;
+	right = (chip->image[OPTi93X_MIXOUT_RIGHT] & ~OPTi93X_MIXOUT_MIXER) | right;
+	change = left != chip->image[OPTi93X_MIXOUT_LEFT] ||
+	         right != chip->image[OPTi93X_MIXOUT_RIGHT];
+	snd_opti93x_out_image(chip, OPTi93X_MIXOUT_LEFT, left);
+	snd_opti93x_out_image(chip, OPTi93X_MIXOUT_RIGHT, right);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return change;
+}
+
+#if 0
+
+#define OPTi93X_SINGLE(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_opti93x_info_single, \
+  .get = snd_opti93x_get_single, .put = snd_opti93x_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+static int snd_opti93x_info_single(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_opti93x_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	opti93x_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	
+	spin_lock_irqsave(&chip->lock, flags);
+	ucontrol->value.integer.value[0] = (chip->image[reg] >> shift) & mask;
+	spin_unlock_irqrestore(&chip->lock, flags);
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_opti93x_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	opti93x_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned short val;
+	
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	val <<= shift;
+	spin_lock_irqsave(&chip->lock, flags);
+	val = (chip->image[reg] & ~(mask << shift)) | val;
+	change = val != chip->image[reg];
+	snd_opti93x_out(chip, reg, val);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return change;
+}
+
+#endif /* single */
+
+#define OPTi93X_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_opti93x_info_double, \
+  .get = snd_opti93x_get_double, .put = snd_opti93x_put_double, \
+  .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
+
+#define OPTi93X_DOUBLE_INVERT_INVERT(xctl) \
+	do { xctl.private_value ^= 22; } while (0)
+#define OPTi93X_DOUBLE_CHANGE_REGS(xctl, left_reg, right_reg) \
+	do { xctl.private_value &= ~0x0000ffff; \
+	     xctl.private_value |= left_reg | (right_reg << 8); } while (0)
+
+static int snd_opti93x_info_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_opti93x_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	opti93x_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	
+	spin_lock_irqsave(&chip->lock, flags);
+	ucontrol->value.integer.value[0] = (chip->image[left_reg] >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (chip->image[right_reg] >> shift_right) & mask;
+	spin_unlock_irqrestore(&chip->lock, flags);
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+
+static int snd_opti93x_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	opti93x_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	unsigned short val1, val2;
+	
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	spin_lock_irqsave(&chip->lock, flags);
+	val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1;
+	val2 = (chip->image[right_reg] & ~(mask << shift_right)) | val2;
+	change = val1 != chip->image[left_reg] || val2 != chip->image[right_reg];
+	snd_opti93x_out_image(chip, left_reg, val1);
+	snd_opti93x_out_image(chip, right_reg, val2);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_opti93x_controls[] = {
+OPTi93X_DOUBLE("Master Playback Switch", 0, OPTi93X_OUT_LEFT, OPTi93X_OUT_RIGHT, 7, 7, 1, 1),
+OPTi93X_DOUBLE("Master Playback Volume", 0, OPTi93X_OUT_LEFT, OPTi93X_OUT_RIGHT, 1, 1, 31, 1), 
+OPTi93X_DOUBLE("PCM Playback Switch", 0, OPTi93X_DAC_LEFT, OPTi93X_DAC_RIGHT, 7, 7, 1, 1),
+OPTi93X_DOUBLE("PCM Playback Volume", 0, OPTi93X_DAC_LEFT, OPTi93X_DAC_RIGHT, 0, 0, 31, 1),
+OPTi93X_DOUBLE("FM Playback Switch", 0, OPTi931_FM_LEFT_INPUT, OPTi931_FM_RIGHT_INPUT, 7, 7, 1, 1),
+OPTi93X_DOUBLE("FM Playback Volume", 0, OPTi931_FM_LEFT_INPUT, OPTi931_FM_RIGHT_INPUT, 1, 1, 15, 1),
+OPTi93X_DOUBLE("Line Playback Switch", 0, OPTi93X_LINE_LEFT_INPUT, OPTi93X_LINE_RIGHT_INPUT, 7, 7, 1, 1),
+OPTi93X_DOUBLE("Line Playback Volume", 0, OPTi93X_LINE_LEFT_INPUT, OPTi93X_LINE_RIGHT_INPUT, 1, 1, 15, 1), 
+OPTi93X_DOUBLE("Mic Playback Switch", 0, OPTi93X_MIC_LEFT_INPUT, OPTi93X_MIC_RIGHT_INPUT, 7, 7, 1, 1),
+OPTi93X_DOUBLE("Mic Playback Volume", 0, OPTi93X_MIC_LEFT_INPUT, OPTi93X_MIC_RIGHT_INPUT, 1, 1, 15, 1), 
+OPTi93X_DOUBLE("Mic Boost", 0, OPTi93X_MIXOUT_LEFT, OPTi93X_MIXOUT_RIGHT, 5, 5, 1, 1),
+OPTi93X_DOUBLE("CD Playback Switch", 0, OPTi93X_CD_LEFT_INPUT, OPTi93X_CD_RIGHT_INPUT, 7, 7, 1, 1),
+OPTi93X_DOUBLE("CD Playback Volume", 0, OPTi93X_CD_LEFT_INPUT, OPTi93X_CD_RIGHT_INPUT, 1, 1, 15, 1),
+OPTi93X_DOUBLE("Aux Playback Switch", 0, OPTi931_AUX_LEFT_INPUT, OPTi931_AUX_RIGHT_INPUT, 7, 7, 1, 1),
+OPTi93X_DOUBLE("Aux Playback Volume", 0, OPTi931_AUX_LEFT_INPUT, OPTi931_AUX_RIGHT_INPUT, 1, 1, 15, 1), 
+OPTi93X_DOUBLE("Capture Volume", 0, OPTi93X_MIXOUT_LEFT, OPTi93X_MIXOUT_RIGHT, 0, 0, 15, 0),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Capture Source",
+	.info = snd_opti93x_info_mux,
+	.get = snd_opti93x_get_mux,
+	.put = snd_opti93x_put_mux,
+}
+};
+                                        
+static int snd_opti93x_mixer(opti93x_t *chip)
+{
+	snd_card_t *card;
+	snd_kcontrol_new_t knew;
+	int err;
+	unsigned int idx;
+
+	snd_assert(chip != NULL && chip->card != NULL, return -EINVAL);
+
+	card = chip->card;
+
+	strcpy(card->mixername, snd_opti93x_chip_id(chip));
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_opti93x_controls); idx++) {
+		knew = snd_opti93x_controls[idx];
+		if (chip->hardware == OPTi9XX_HW_82C930) {
+			if (strstr(knew.name, "FM"))	/* skip FM controls */
+				continue;
+			else if (strcmp(knew.name, "Mic Playback Volume"))
+				OPTi93X_DOUBLE_INVERT_INVERT(knew);
+			else if (strstr(knew.name, "Aux"))
+				OPTi93X_DOUBLE_CHANGE_REGS(knew, OPTi930_AUX_LEFT_INPUT, OPTi930_AUX_RIGHT_INPUT);
+			else if (strcmp(knew.name, "PCM Playback Volume"))
+				OPTi93X_DOUBLE_INVERT_INVERT(knew);
+			else if (strcmp(knew.name, "Master Playback Volume"))
+				OPTi93X_DOUBLE_INVERT_INVERT(knew);
+		}
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_opti93x_controls[idx], chip))) < 0)
+			return err;
+	}
+	return 0;
+}
+
+#endif /* OPTi93X */
+
+static int __devinit snd_card_opti9xx_detect(snd_card_t *card, opti9xx_t *chip)
+{
+	int i, err;
+
+#ifndef OPTi93X
+	for (i = OPTi9XX_HW_82C928; i < OPTi9XX_HW_82C930; i++) {
+		unsigned char value;
+
+		if ((err = snd_opti9xx_init(chip, i)) < 0)
+			return err;
+
+		if ((chip->res_mc_base = request_region(chip->mc_base, chip->mc_base_size, "OPTi9xx MC")) == NULL)
+			continue;
+
+		value = snd_opti9xx_read(chip, OPTi9XX_MC_REG(1));
+		if ((value != 0xff) && (value != inb(chip->mc_base + 1)))
+			if (value == snd_opti9xx_read(chip, OPTi9XX_MC_REG(1)))
+				return 1;
+
+		release_resource(chip->res_mc_base);
+		kfree_nocheck(chip->res_mc_base);
+		chip->res_mc_base = NULL;
+
+	}
+#else	/* OPTi93X */
+	for (i = OPTi9XX_HW_82C931; i >= OPTi9XX_HW_82C930; i--) {
+		unsigned long flags;
+		unsigned char value;
+
+		if ((err = snd_opti9xx_init(chip, i)) < 0)
+			return err;
+
+		if ((chip->res_mc_base = request_region(chip->mc_base, chip->mc_base_size, "OPTi9xx MC")) == NULL)
+			continue;
+
+		spin_lock_irqsave(&chip->lock, flags);
+		outb(chip->password, chip->mc_base + chip->pwd_reg);
+		outb(((chip->mc_indir_index & (1 << 8)) >> 4) |
+			((chip->mc_indir_index & 0xf0) >> 4), chip->mc_base);
+		spin_unlock_irqrestore(&chip->lock, flags);
+
+		value = snd_opti9xx_read(chip, OPTi9XX_MC_REG(7));
+		snd_opti9xx_write(chip, OPTi9XX_MC_REG(7), 0xff - value);
+		if (snd_opti9xx_read(chip, OPTi9XX_MC_REG(7)) == 0xff - value)
+			return 1;
+
+		release_resource(chip->res_mc_base);
+		kfree_nocheck(chip->res_mc_base);
+		chip->res_mc_base = NULL;
+	}
+#endif	/* OPTi93X */
+
+	return -ENODEV;
+}
+
+#ifdef CONFIG_PNP
+static int __devinit snd_card_opti9xx_pnp(opti9xx_t *chip, struct pnp_card_link *card,
+					  const struct pnp_card_device_id *pid)
+{
+	struct pnp_dev *pdev;
+	struct pnp_resource_table *cfg = kmalloc(sizeof(*cfg), GFP_KERNEL);
+	int err;
+
+	chip->dev = pnp_request_card_device(card, pid->devs[0].id, NULL);
+	if (chip->dev == NULL) {
+		kfree(cfg);
+		return -EBUSY;
+	}
+	chip->devmpu = pnp_request_card_device(card, pid->devs[1].id, NULL);
+
+	pdev = chip->dev;
+	pnp_init_resource_table(cfg);
+
+#ifdef OPTi93X
+	if (port != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[0], port + 4, 4);
+#else
+	if (pid->driver_data != 0x0924 && port != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[1], port, 4);
+#endif	/* OPTi93X */
+	if (irq != SNDRV_AUTO_IRQ)
+		pnp_resource_change(&cfg->irq_resource[0], irq, 1);
+	if (dma1 != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[0], dma1, 1);
+#if defined(CS4231) || defined(OPTi93X)
+	if (dma2 != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[1], dma2, 1);
+#else
+#ifdef snd_opti9xx_fixup_dma2
+	snd_opti9xx_fixup_dma2(pdev);
+#endif
+#endif	/* CS4231 || OPTi93X */
+#ifdef OPTi93X
+	if (fm_port > 0 && fm_port != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[1], fm_port, 4);
+#else
+	if (fm_port > 0 && fm_port != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[2], fm_port, 4);
+#endif
+	if (pnp_manual_config_dev(pdev, cfg, 0) < 0)
+		snd_printk(KERN_ERR "AUDIO the requested resources are invalid, using auto config\n");
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "AUDIO pnp configure failure: %d\n", err);
+		kfree(cfg);
+		return err;
+	}
+
+#ifdef OPTi93X
+	port = pnp_port_start(pdev, 0) - 4;
+	fm_port = pnp_port_start(pdev, 1);
+#else
+	if (pid->driver_data != 0x0924)
+		port = pnp_port_start(pdev, 1);
+	fm_port = pnp_port_start(pdev, 2);
+#endif	/* OPTi93X */
+	irq = pnp_irq(pdev, 0);
+	dma1 = pnp_dma(pdev, 0);
+#if defined(CS4231) || defined(OPTi93X)
+	dma2 = pnp_dma(pdev, 1);
+#endif	/* CS4231 || OPTi93X */
+
+	pdev = chip->devmpu;
+	if (pdev && mpu_port > 0) {
+		pnp_init_resource_table(cfg);
+
+		if (mpu_port != SNDRV_AUTO_PORT)
+			pnp_resource_change(&cfg->port_resource[0], mpu_port, 2);
+		if (mpu_irq != SNDRV_AUTO_IRQ)
+			pnp_resource_change(&cfg->irq_resource[0], mpu_irq, 1);
+
+		if (pnp_manual_config_dev(pdev, cfg, 0) < 0)
+			snd_printk(KERN_ERR "AUDIO the requested resources are invalid, using auto config\n");
+		err = pnp_activate_dev(pdev);
+		if (err < 0) {
+			snd_printk(KERN_ERR "AUDIO pnp configure failure\n");
+			mpu_port = -1;
+			chip->devmpu = NULL;
+		} else {
+			mpu_port = pnp_port_start(pdev, 0);
+			mpu_irq = pnp_irq(pdev, 0);
+		}
+	}
+	kfree(cfg);
+	return pid->driver_data;
+}
+#endif	/* CONFIG_PNP */
+
+#if 0
+static int __devinit snd_card_opti9xx_resources(struct snd_card_opti9xx *chip,
+						snd_card_t *card)
+{
+	int error, i, pnp = 0;
+
+#ifdef CONFIG_PNP
+	pnp = chip->dev != NULL;
+#endif	/* CONFIG_PNP */
+
+#ifndef OPTi93X
+	if (chip->chip->hardware == OPTi9XX_HW_82C928)
+		mpu_port = -1;
+#endif	/* OPTi93X */
+	error = 0;
+	if (!pnp && (mpu_port == SNDRV_DEFAULT_PORT1)) {
+		for (i = 0; possible_mpu_ports[i] != -1; i++)
+			if (!snd_register_ioport(card, possible_mpu_ports[i], 2,
+					DRIVER_NAME" - MPU-401", NULL)) {
+				mpu_port = possible_mpu_ports[i];
+				break;
+			}
+		if (mpu_port == SNDRV_DEFAULT_PORT1)
+			error = -EBUSY;
+	}
+	else
+		error = (mpu_port == -1) ? -ENODEV :
+			snd_register_ioport(card, mpu_port, 2,
+			DRIVER_NAME" - MPU-401", NULL);
+	if (error)
+		chip->chip->mpu_port = -1;
+	else if (pnp && (irq == mpu_irq))
+		chip->chip->mpu_irq = mpu_irq;
+	else if (!snd_register_interrupt(card,
+			DRIVER_NAME" - MPU-401",
+			mpu_irq, SNDRV_IRQ_TYPE_ISA,
+			snd_card_opti9xx_mpu_interrupt, chip,
+			pnp ? no_alternatives : possible_mpu_irqs,
+			&chip->mpuirqptr)) {
+		chip->chip->mpu_port = mpu_port;
+		chip->chip->mpu_irq = chip->mpuirqptr->irq;
+	}
+	else
+		chip->chip->mpu_port = -1;
+
+	if (!pnp && (port == SNDRV_DEFAULT_PORT1)) {
+		for (i = 0; possible_ports[i] != -1; i++)
+			if (!snd_register_ioport(card, possible_ports[i], 8,
+					DRIVER_NAME" - WSS", NULL)) {
+				port = possible_ports[i];
+				break;
+			}
+		if (port == SNDRV_DEFAULT_PORT1)
+			return -EBUSY;
+	}
+	else if ((error = snd_register_ioport(card, port, 8,
+			DRIVER_NAME" - WSS", NULL)) < 0)
+		return error;
+	chip->chip->wss_base = port;
+	if ((error = snd_register_interrupt(card, DRIVER_NAME" - WSS",
+			irq, SNDRV_IRQ_TYPE_ISA,
+			snd_card_opti9xx_interrupt, chip,
+			pnp ? no_alternatives : possible_irqs,
+			&chip->irqptr)) < 0)
+		return error;
+	chip->chip->irq = chip->irqptr->irq;
+	if ((error = snd_register_dma_channel(card,
+#if defined(CS4231) || defined(OPTi93X)
+			DRIVER_NAME" - WSS playback",
+#else
+			DRIVER_NAME" - WSS",
+#endif	/* CS4231 || OPTi93X */
+			dma1, SNDRV_DMA_TYPE_ISA, dma1_size,
+			pnp ? no_alternatives : possible_dma1s,
+			&chip->dma1ptr)) < 0)
+		return error;
+	chip->chip->dma1 = chip->dma1ptr->dma;
+#if defined(CS4231) || defined(OPTi93X)
+	if ((error = snd_register_dma_channel(card, DRIVER_NAME" - WSS capture",
+			dma2, SNDRV_DMA_TYPE_ISA, dma2_size,
+			pnp ? no_alternatives :
+				possible_dma2s[chip->dma1ptr->dma],
+			&chip->dma2ptr)) < 0)
+		return error;
+	chip->chip->dma2 = chip->dma2ptr->dma;
+#endif	/* CS4231 || OPTi93X */
+
+	if (snd_register_ioport(card,
+			pnp ? fm_port : fm_port = 0x388, 4,
+			DRIVER_NAME" - OPL", NULL) < 0)
+		fm_port = -1;
+	chip->chip->fm_port = fm_port;
+
+	return 0;
+}
+#endif
+
+static void snd_card_opti9xx_free(snd_card_t *card)
+{
+	opti9xx_t *chip = (opti9xx_t *)card->private_data;
+        
+	if (chip) {
+		if (chip->res_mc_base) {
+			release_resource(chip->res_mc_base);
+			kfree_nocheck(chip->res_mc_base);
+		}
+	}
+}
+
+static int __devinit snd_card_opti9xx_probe(struct pnp_card_link *pcard,
+					    const struct pnp_card_device_id *pid)
+{
+	static long possible_ports[] = {0x530, 0xe80, 0xf40, 0x604, -1};
+	static long possible_mpu_ports[] = {0x300, 0x310, 0x320, 0x330, -1};
+#ifdef OPTi93X
+	static int possible_irqs[] = {5, 9, 10, 11, 7, -1};
+#else
+	static int possible_irqs[] = {9, 10, 11, 7, -1};
+#endif	/* OPTi93X */
+	static int possible_mpu_irqs[] = {5, 9, 10, 7, -1};
+	static int possible_dma1s[] = {3, 1, 0, -1};
+#if defined(CS4231) || defined(OPTi93X)
+	static int possible_dma2s[][2] = {{1,-1}, {0,-1}, {-1,-1}, {0,-1}};
+#endif	/* CS4231 || OPTi93X */
+	int error;
+	opti9xx_t *chip;
+#if defined(OPTi93X)
+	opti93x_t *codec;
+#elif defined(CS4231)
+	cs4231_t *codec;
+	snd_timer_t *timer;
+#else
+	ad1848_t *codec;
+#endif
+	snd_card_t *card;
+	snd_pcm_t *pcm;
+	snd_rawmidi_t *rmidi;
+	snd_hwdep_t *synth;
+#ifdef CONFIG_PNP
+	int hw;
+#endif	/* CONFIG_PNP */
+
+	if (pcard && !snd_opti9xx_first_hit)
+		return -EBUSY;
+	if (!(card = snd_card_new(index, id, THIS_MODULE,
+				  sizeof(opti9xx_t))))
+		return -ENOMEM;
+	card->private_free = snd_card_opti9xx_free;
+	chip = (opti9xx_t *)card->private_data;
+
+#ifdef CONFIG_PNP
+	if (isapnp && pcard && (hw = snd_card_opti9xx_pnp(chip, pcard, pid)) > 0) {
+		switch (hw) {
+		case 0x0924:
+			hw = OPTi9XX_HW_82C924;
+			break;
+		case 0x0925:
+			hw = OPTi9XX_HW_82C925;
+			break;
+		case 0x0931:
+			hw = OPTi9XX_HW_82C931;
+			break;
+		default:
+			snd_card_free(card);
+			return -ENODEV;
+		}
+
+		if ((error = snd_opti9xx_init(chip, hw))) {
+			snd_card_free(card);
+			return error;
+		}
+		if (hw <= OPTi9XX_HW_82C930)
+			chip->mc_base -= 0x80;
+		snd_card_set_dev(card, &pcard->card->dev);
+	} else {
+#endif	/* CONFIG_PNP */
+		if ((error = snd_card_opti9xx_detect(card, chip)) < 0) {
+			snd_card_free(card);
+			return error;
+		}
+#ifdef CONFIG_PNP
+	}
+#endif	/* CONFIG_PNP */
+
+	if (! chip->res_mc_base &&
+	    (chip->res_mc_base = request_region(chip->mc_base, chip->mc_base_size, "OPTi9xx MC")) == NULL) {
+		snd_card_free(card);
+		return -ENOMEM;
+	}
+
+	chip->wss_base = port;
+	chip->fm_port = fm_port;
+	chip->mpu_port = mpu_port;
+	chip->irq = irq;
+	chip->mpu_irq = mpu_irq;
+	chip->dma1 = dma1;
+#if defined(CS4231) || defined(OPTi93X)
+	chip->dma2 = dma2;
+#endif
+
+	if (chip->wss_base == SNDRV_AUTO_PORT) {
+		if ((chip->wss_base = snd_legacy_find_free_ioport(possible_ports, 4)) < 0) {
+			snd_card_free(card);
+			snd_printk("unable to find a free WSS port\n");
+			return -EBUSY;
+		}
+	}
+#ifdef CONFIG_PNP
+	if (!isapnp) {
+#endif
+	if (chip->mpu_port == SNDRV_AUTO_PORT) {
+		if ((chip->mpu_port = snd_legacy_find_free_ioport(possible_mpu_ports, 2)) < 0) {
+			snd_card_free(card);
+			snd_printk("unable to find a free MPU401 port\n");
+			return -EBUSY;
+		}
+	}
+	if (chip->irq == SNDRV_AUTO_IRQ) {
+		if ((chip->irq = snd_legacy_find_free_irq(possible_irqs)) < 0) {
+			snd_card_free(card);
+			snd_printk("unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (chip->mpu_irq == SNDRV_AUTO_IRQ) {
+		if ((chip->mpu_irq = snd_legacy_find_free_irq(possible_mpu_irqs)) < 0) {
+			snd_card_free(card);
+			snd_printk("unable to find a free MPU401 IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (chip->dma1 == SNDRV_AUTO_DMA) {
+                if ((chip->dma1 = snd_legacy_find_free_dma(possible_dma1s)) < 0) {
+                        snd_card_free(card);
+			snd_printk("unable to find a free DMA1\n");
+			return -EBUSY;
+		}
+        }
+#if defined(CS4231) || defined(OPTi93X)
+	if (chip->dma2 == SNDRV_AUTO_DMA) {
+                if ((chip->dma2 = snd_legacy_find_free_dma(possible_dma2s[chip->dma1 % 4])) < 0) {
+                        snd_card_free(card);
+			snd_printk("unable to find a free DMA2\n");
+			return -EBUSY;
+		}
+        }
+#endif
+
+#ifdef CONFIG_PNP
+	}
+#endif
+
+	if ((error = snd_opti9xx_configure(chip))) {
+		snd_card_free(card);
+		return error;
+	}
+
+#if defined(OPTi93X)
+	if ((error = snd_opti93x_create(card, chip, chip->dma1, chip->dma2, &codec))) {
+		snd_card_free(card);
+		return error;
+	}
+	if ((error = snd_opti93x_pcm(codec, 0, &pcm)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	if ((error = snd_opti93x_mixer(codec)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+#elif defined(CS4231)
+	if ((error = snd_cs4231_create(card, chip->wss_base + 4, -1,
+				       chip->irq, chip->dma1, chip->dma2,
+				       CS4231_HW_DETECT,
+				       0,
+				       &codec)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	if ((error = snd_cs4231_pcm(codec, 0, &pcm)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	if ((error = snd_cs4231_mixer(codec)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	if ((error = snd_cs4231_timer(codec, 0, &timer)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+#else
+	if ((error = snd_ad1848_create(card, chip->wss_base + 4,
+				       chip->irq, chip->dma1,
+				       AD1848_HW_DETECT, &codec)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	if ((error = snd_ad1848_pcm(codec, 0, &pcm)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	if ((error = snd_ad1848_mixer(codec)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+#endif
+	strcpy(card->driver, chip->name);
+	sprintf(card->shortname, "OPTi %s", card->driver);
+#if defined(CS4231) || defined(OPTi93X)
+	sprintf(card->longname, "%s, %s at 0x%lx, irq %d, dma %d&%d",
+		card->shortname, pcm->name, chip->wss_base + 4,
+		chip->irq, chip->dma1, chip->dma2);
+#else
+	sprintf(card->longname, "%s, %s at 0x%lx, irq %d, dma %d",
+		card->shortname, pcm->name, chip->wss_base + 4,
+		chip->irq, chip->dma1);
+#endif	/* CS4231 || OPTi93X */
+
+	if (chip->mpu_port <= 0 || chip->mpu_port == SNDRV_AUTO_PORT)
+		rmidi = NULL;
+	else
+		if ((error = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+				chip->mpu_port, 0, chip->mpu_irq, SA_INTERRUPT,
+				&rmidi)))
+			snd_printk("no MPU-401 device at 0x%lx?\n", chip->mpu_port);
+
+	if (chip->fm_port > 0 && chip->fm_port != SNDRV_AUTO_PORT) {
+		opl3_t *opl3 = NULL;
+#ifndef OPTi93X
+		if (chip->hardware == OPTi9XX_HW_82C928 ||
+		    chip->hardware == OPTi9XX_HW_82C929 ||
+		    chip->hardware == OPTi9XX_HW_82C924) {
+			opl4_t *opl4;
+			/* assume we have an OPL4 */
+			snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2),
+					       0x20, 0x20);
+			if (snd_opl4_create(card,
+					    chip->fm_port,
+					    chip->fm_port - 8,
+					    2, &opl3, &opl4) < 0) {
+				/* no luck, use OPL3 instead */
+				snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2),
+						       0x00, 0x20);
+			}
+		}
+#endif	/* !OPTi93X */
+		if (!opl3 && snd_opl3_create(card,
+					     chip->fm_port,
+					     chip->fm_port + 2,
+					     OPL3_HW_AUTO, 0, &opl3) < 0) {
+			snd_printk("no OPL device at 0x%lx-0x%lx\n",
+				   chip->fm_port, chip->fm_port + 4 - 1);
+		}
+		if (opl3) {
+			if ((error = snd_opl3_timer_new(opl3,
+#ifdef CS4231
+							1, 2)) < 0) {
+#else
+							0, 1)) < 0) {
+#endif	/* CS4231 */
+				snd_card_free(card);
+				return error;
+			}
+			if ((error = snd_opl3_hwdep_new(opl3, 0, 1, &synth)) < 0) {
+				snd_card_free(card);
+				return error;
+			}
+		}
+	}
+
+	if ((error = snd_card_register(card))) {
+		snd_card_free(card);
+		return error;
+	}
+	snd_opti9xx_first_hit = 0;
+	if (pcard)
+		pnp_set_card_drvdata(pcard, card);
+	else
+		snd_opti9xx_legacy = card;
+	return 0;
+}
+
+#ifdef CONFIG_PNP
+static void __devexit snd_opti9xx_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_t *card = (snd_card_t *) pnp_get_card_drvdata(pcard);
+
+	snd_card_disconnect(card);
+	snd_card_free_in_thread(card);
+	snd_opti9xx_first_hit = 0;
+}
+
+static struct pnp_card_driver opti9xx_pnpc_driver = {
+	.flags		= PNP_DRIVER_RES_DISABLE,
+	.name		= "opti9xx",
+	.id_table	= snd_opti9xx_pnpids,
+	.probe		= snd_card_opti9xx_probe,
+	.remove		= __devexit_p(snd_opti9xx_pnp_remove),
+};
+#endif
+
+static int __init alsa_card_opti9xx_init(void)
+{
+	int cards, error;
+
+#ifdef CONFIG_PNP
+	cards = pnp_register_card_driver(&opti9xx_pnpc_driver);
+#else
+	cards = 0;
+#endif
+	if (cards == 0 && (error = snd_card_opti9xx_probe(NULL, NULL)) < 0) {
+#ifdef CONFIG_PNP
+		pnp_unregister_card_driver(&opti9xx_pnpc_driver);
+#endif
+#ifdef MODULE
+#ifdef OPTi93X
+		printk(KERN_ERR "no OPTi 82C93x soundcard found\n");
+#else
+		printk(KERN_ERR "no OPTi 82C92x soundcard found\n");
+#endif	/* OPTi93X */
+#endif
+		return error;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_opti9xx_exit(void)
+{
+#ifdef CONFIG_PNP
+	pnp_unregister_card_driver(&opti9xx_pnpc_driver);
+#endif
+	if (snd_opti9xx_legacy)
+		snd_card_free(snd_opti9xx_legacy);
+}
+
+module_init(alsa_card_opti9xx_init)
+module_exit(alsa_card_opti9xx_exit)
diff --git a/sound/isa/opti9xx/opti92x-cs4231.c b/sound/isa/opti9xx/opti92x-cs4231.c
new file mode 100644
index 0000000..b17ab19
--- /dev/null
+++ b/sound/isa/opti9xx/opti92x-cs4231.c
@@ -0,0 +1,2 @@
+#define CS4231
+#include "opti92x-ad1848.c"
diff --git a/sound/isa/opti9xx/opti93x.c b/sound/isa/opti9xx/opti93x.c
new file mode 100644
index 0000000..bad9da5
--- /dev/null
+++ b/sound/isa/opti9xx/opti93x.c
@@ -0,0 +1,3 @@
+#define OPTi93X
+#include "opti92x-ad1848.c"
+
diff --git a/sound/isa/sb/Makefile b/sound/isa/sb/Makefile
new file mode 100644
index 0000000..fd9d9c5
--- /dev/null
+++ b/sound/isa/sb/Makefile
@@ -0,0 +1,39 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-sb-common-objs := sb_common.o sb_mixer.o
+snd-sb8-dsp-objs := sb8_main.o sb8_midi.o
+snd-sb16-dsp-objs := sb16_main.o
+snd-sb16-csp-objs := sb16_csp.o
+snd-sb8-objs := sb8.o
+snd-sb16-objs := sb16.o
+snd-sbawe-objs := sbawe.o emu8000.o
+snd-emu8000-synth-objs := emu8000_synth.o emu8000_callback.o emu8000_patch.o emu8000_pcm.o
+snd-es968-objs := es968.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_ALS100) += snd-sb16-dsp.o snd-sb-common.o
+obj-$(CONFIG_SND_CMI8330) += snd-sb16-dsp.o snd-sb-common.o
+obj-$(CONFIG_SND_DT019X) += snd-sb16-dsp.o snd-sb-common.o
+obj-$(CONFIG_SND_SB8) += snd-sb8.o snd-sb8-dsp.o snd-sb-common.o
+obj-$(CONFIG_SND_SB16) += snd-sb16.o snd-sb16-dsp.o snd-sb-common.o
+obj-$(CONFIG_SND_SBAWE) += snd-sbawe.o snd-sb16-dsp.o snd-sb-common.o
+obj-$(CONFIG_SND_ES968) += snd-es968.o snd-sb8-dsp.o snd-sb-common.o
+obj-$(CONFIG_SND_ALS4000) += snd-sb-common.o
+ifeq ($(CONFIG_SND_SB16_CSP),y)
+  obj-$(CONFIG_SND_SB16) += snd-sb16-csp.o
+  obj-$(CONFIG_SND_SBAWE) += snd-sb16-csp.o
+endif
+obj-$(call sequencer,$(CONFIG_SND_SBAWE)) += snd-emu8000-synth.o
+
+obj-m := $(sort $(obj-m))
diff --git a/sound/isa/sb/emu8000.c b/sound/isa/sb/emu8000.c
new file mode 100644
index 0000000..028af40
--- /dev/null
+++ b/sound/isa/sb/emu8000.c
@@ -0,0 +1,1170 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *     and (c) 1999 Steve Ratcliffe <steve@parabola.demon.co.uk>
+ *  Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *  Routines for control of EMU8000 chip
+ *
+ *   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/wait.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/emu8000.h>
+#include <sound/emu8000_reg.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/init.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+
+/*
+ * emu8000 register controls
+ */
+
+/*
+ * The following routines read and write registers on the emu8000.  They
+ * should always be called via the EMU8000*READ/WRITE macros and never
+ * directly.  The macros handle the port number and command word.
+ */
+/* Write a word */
+void snd_emu8000_poke(emu8000_t *emu, unsigned int port, unsigned int reg, unsigned int val)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	if (reg != emu->last_reg) {
+		outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
+		emu->last_reg = reg;
+	}
+	outw((unsigned short)val, port); /* Send data */
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+}
+
+/* Read a word */
+unsigned short snd_emu8000_peek(emu8000_t *emu, unsigned int port, unsigned int reg)
+{
+	unsigned short res;
+	unsigned long flags;
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	if (reg != emu->last_reg) {
+		outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
+		emu->last_reg = reg;
+	}
+	res = inw(port);	/* Read data */
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return res;
+}
+
+/* Write a double word */
+void snd_emu8000_poke_dw(emu8000_t *emu, unsigned int port, unsigned int reg, unsigned int val)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	if (reg != emu->last_reg) {
+		outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
+		emu->last_reg = reg;
+	}
+	outw((unsigned short)val, port); /* Send low word of data */
+	outw((unsigned short)(val>>16), port+2); /* Send high word of data */
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+}
+
+/* Read a double word */
+unsigned int snd_emu8000_peek_dw(emu8000_t *emu, unsigned int port, unsigned int reg)
+{
+	unsigned short low;
+	unsigned int res;
+	unsigned long flags;
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	if (reg != emu->last_reg) {
+		outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
+		emu->last_reg = reg;
+	}
+	low = inw(port);	/* Read low word of data */
+	res = low + (inw(port+2) << 16);
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return res;
+}
+
+/*
+ * Set up / close a channel to be used for DMA.
+ */
+/*exported*/ void
+snd_emu8000_dma_chan(emu8000_t *emu, int ch, int mode)
+{
+	unsigned right_bit = (mode & EMU8000_RAM_RIGHT) ? 0x01000000 : 0;
+	mode &= EMU8000_RAM_MODE_MASK;
+	if (mode == EMU8000_RAM_CLOSE) {
+		EMU8000_CCCA_WRITE(emu, ch, 0);
+		EMU8000_DCYSUSV_WRITE(emu, ch, 0x807F);
+		return;
+	}
+	EMU8000_DCYSUSV_WRITE(emu, ch, 0x80);
+	EMU8000_VTFT_WRITE(emu, ch, 0);
+	EMU8000_CVCF_WRITE(emu, ch, 0);
+	EMU8000_PTRX_WRITE(emu, ch, 0x40000000);
+	EMU8000_CPF_WRITE(emu, ch, 0x40000000);
+	EMU8000_PSST_WRITE(emu, ch, 0);
+	EMU8000_CSL_WRITE(emu, ch, 0);
+	if (mode == EMU8000_RAM_WRITE) /* DMA write */
+		EMU8000_CCCA_WRITE(emu, ch, 0x06000000 | right_bit);
+	else	   /* DMA read */
+		EMU8000_CCCA_WRITE(emu, ch, 0x04000000 | right_bit);
+}
+
+/*
+ */
+static void __init
+snd_emu8000_read_wait(emu8000_t *emu)
+{
+	while ((EMU8000_SMALR_READ(emu) & 0x80000000) != 0) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(1);
+		if (signal_pending(current))
+			break;
+	}
+}
+
+/*
+ */
+static void __init
+snd_emu8000_write_wait(emu8000_t *emu)
+{
+	while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(1);
+		if (signal_pending(current))
+			break;
+	}
+}
+
+/*
+ * detect a card at the given port
+ */
+static int __init
+snd_emu8000_detect(emu8000_t *emu)
+{
+	/* Initialise */
+	EMU8000_HWCF1_WRITE(emu, 0x0059);
+	EMU8000_HWCF2_WRITE(emu, 0x0020);
+	EMU8000_HWCF3_WRITE(emu, 0x0000);
+	/* Check for a recognisable emu8000 */
+	/*
+	if ((EMU8000_U1_READ(emu) & 0x000f) != 0x000c)
+		return -ENODEV;
+		*/
+	if ((EMU8000_HWCF1_READ(emu) & 0x007e) != 0x0058)
+		return -ENODEV;
+	if ((EMU8000_HWCF2_READ(emu) & 0x0003) != 0x0003)
+		return -ENODEV;
+
+	snd_printdd("EMU8000 [0x%lx]: Synth chip found\n",
+                    emu->port1);
+	return 0;
+}
+
+
+/*
+ * intiailize audio channels
+ */
+static void __init
+init_audio(emu8000_t *emu)
+{
+	int ch;
+
+	/* turn off envelope engines */
+	for (ch = 0; ch < EMU8000_CHANNELS; ch++)
+		EMU8000_DCYSUSV_WRITE(emu, ch, 0x80);
+  
+	/* reset all other parameters to zero */
+	for (ch = 0; ch < EMU8000_CHANNELS; ch++) {
+		EMU8000_ENVVOL_WRITE(emu, ch, 0);
+		EMU8000_ENVVAL_WRITE(emu, ch, 0);
+		EMU8000_DCYSUS_WRITE(emu, ch, 0);
+		EMU8000_ATKHLDV_WRITE(emu, ch, 0);
+		EMU8000_LFO1VAL_WRITE(emu, ch, 0);
+		EMU8000_ATKHLD_WRITE(emu, ch, 0);
+		EMU8000_LFO2VAL_WRITE(emu, ch, 0);
+		EMU8000_IP_WRITE(emu, ch, 0);
+		EMU8000_IFATN_WRITE(emu, ch, 0);
+		EMU8000_PEFE_WRITE(emu, ch, 0);
+		EMU8000_FMMOD_WRITE(emu, ch, 0);
+		EMU8000_TREMFRQ_WRITE(emu, ch, 0);
+		EMU8000_FM2FRQ2_WRITE(emu, ch, 0);
+		EMU8000_PTRX_WRITE(emu, ch, 0);
+		EMU8000_VTFT_WRITE(emu, ch, 0);
+		EMU8000_PSST_WRITE(emu, ch, 0);
+		EMU8000_CSL_WRITE(emu, ch, 0);
+		EMU8000_CCCA_WRITE(emu, ch, 0);
+	}
+
+	for (ch = 0; ch < EMU8000_CHANNELS; ch++) {
+		EMU8000_CPF_WRITE(emu, ch, 0);
+		EMU8000_CVCF_WRITE(emu, ch, 0);
+	}
+}
+
+
+/*
+ * initialize DMA address
+ */
+static void __init
+init_dma(emu8000_t *emu)
+{
+	EMU8000_SMALR_WRITE(emu, 0);
+	EMU8000_SMARR_WRITE(emu, 0);
+	EMU8000_SMALW_WRITE(emu, 0);
+	EMU8000_SMARW_WRITE(emu, 0);
+}
+
+/*
+ * initialization arrays; from ADIP
+ */
+static unsigned short init1[128] /*__devinitdata*/ = {
+	0x03ff, 0x0030,  0x07ff, 0x0130, 0x0bff, 0x0230,  0x0fff, 0x0330,
+	0x13ff, 0x0430,  0x17ff, 0x0530, 0x1bff, 0x0630,  0x1fff, 0x0730,
+	0x23ff, 0x0830,  0x27ff, 0x0930, 0x2bff, 0x0a30,  0x2fff, 0x0b30,
+	0x33ff, 0x0c30,  0x37ff, 0x0d30, 0x3bff, 0x0e30,  0x3fff, 0x0f30,
+
+	0x43ff, 0x0030,  0x47ff, 0x0130, 0x4bff, 0x0230,  0x4fff, 0x0330,
+	0x53ff, 0x0430,  0x57ff, 0x0530, 0x5bff, 0x0630,  0x5fff, 0x0730,
+	0x63ff, 0x0830,  0x67ff, 0x0930, 0x6bff, 0x0a30,  0x6fff, 0x0b30,
+	0x73ff, 0x0c30,  0x77ff, 0x0d30, 0x7bff, 0x0e30,  0x7fff, 0x0f30,
+
+	0x83ff, 0x0030,  0x87ff, 0x0130, 0x8bff, 0x0230,  0x8fff, 0x0330,
+	0x93ff, 0x0430,  0x97ff, 0x0530, 0x9bff, 0x0630,  0x9fff, 0x0730,
+	0xa3ff, 0x0830,  0xa7ff, 0x0930, 0xabff, 0x0a30,  0xafff, 0x0b30,
+	0xb3ff, 0x0c30,  0xb7ff, 0x0d30, 0xbbff, 0x0e30,  0xbfff, 0x0f30,
+
+	0xc3ff, 0x0030,  0xc7ff, 0x0130, 0xcbff, 0x0230,  0xcfff, 0x0330,
+	0xd3ff, 0x0430,  0xd7ff, 0x0530, 0xdbff, 0x0630,  0xdfff, 0x0730,
+	0xe3ff, 0x0830,  0xe7ff, 0x0930, 0xebff, 0x0a30,  0xefff, 0x0b30,
+	0xf3ff, 0x0c30,  0xf7ff, 0x0d30, 0xfbff, 0x0e30,  0xffff, 0x0f30,
+};
+
+static unsigned short init2[128] /*__devinitdata*/ = {
+	0x03ff, 0x8030, 0x07ff, 0x8130, 0x0bff, 0x8230, 0x0fff, 0x8330,
+	0x13ff, 0x8430, 0x17ff, 0x8530, 0x1bff, 0x8630, 0x1fff, 0x8730,
+	0x23ff, 0x8830, 0x27ff, 0x8930, 0x2bff, 0x8a30, 0x2fff, 0x8b30,
+	0x33ff, 0x8c30, 0x37ff, 0x8d30, 0x3bff, 0x8e30, 0x3fff, 0x8f30,
+
+	0x43ff, 0x8030, 0x47ff, 0x8130, 0x4bff, 0x8230, 0x4fff, 0x8330,
+	0x53ff, 0x8430, 0x57ff, 0x8530, 0x5bff, 0x8630, 0x5fff, 0x8730,
+	0x63ff, 0x8830, 0x67ff, 0x8930, 0x6bff, 0x8a30, 0x6fff, 0x8b30,
+	0x73ff, 0x8c30, 0x77ff, 0x8d30, 0x7bff, 0x8e30, 0x7fff, 0x8f30,
+
+	0x83ff, 0x8030, 0x87ff, 0x8130, 0x8bff, 0x8230, 0x8fff, 0x8330,
+	0x93ff, 0x8430, 0x97ff, 0x8530, 0x9bff, 0x8630, 0x9fff, 0x8730,
+	0xa3ff, 0x8830, 0xa7ff, 0x8930, 0xabff, 0x8a30, 0xafff, 0x8b30,
+	0xb3ff, 0x8c30, 0xb7ff, 0x8d30, 0xbbff, 0x8e30, 0xbfff, 0x8f30,
+
+	0xc3ff, 0x8030, 0xc7ff, 0x8130, 0xcbff, 0x8230, 0xcfff, 0x8330,
+	0xd3ff, 0x8430, 0xd7ff, 0x8530, 0xdbff, 0x8630, 0xdfff, 0x8730,
+	0xe3ff, 0x8830, 0xe7ff, 0x8930, 0xebff, 0x8a30, 0xefff, 0x8b30,
+	0xf3ff, 0x8c30, 0xf7ff, 0x8d30, 0xfbff, 0x8e30, 0xffff, 0x8f30,
+};
+
+static unsigned short init3[128] /*__devinitdata*/ = {
+	0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5,
+	0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x8F7C, 0x167E, 0xF254,
+	0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x8BAA, 0x1B6D, 0xF234,
+	0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x86E7, 0x229E, 0xF224,
+
+	0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x87F6, 0x2C28, 0xF254,
+	0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x8F02, 0x1341, 0xF264,
+	0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x8FA9, 0x3EB5, 0xF294,
+	0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0xC4C3, 0x3EBB, 0xC5C3,
+
+	0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x8671, 0x14FD, 0x8287,
+	0x3EBC, 0xE610, 0x3EC8, 0x8C7B, 0x031A, 0x87E6, 0x3EC8, 0x86F7,
+	0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x821F, 0x3ECA, 0x8386,
+	0x3EC1, 0x8C03, 0x3EC9, 0x831E, 0x3ECA, 0x8C4C, 0x3EBF, 0x8C55,
+
+	0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x8EAD, 0x3EC8, 0xD308,
+	0x3EC2, 0x8F7E, 0x3ECB, 0x8219, 0x3ECB, 0xD26E, 0x3EC5, 0x831F,
+	0x3EC6, 0xC308, 0x3EC3, 0xB2FF, 0x3EC9, 0x8265, 0x3EC9, 0x8319,
+	0x1342, 0xD36E, 0x3EC7, 0xB3FF, 0x0000, 0x8365, 0x1420, 0x9570,
+};
+
+static unsigned short init4[128] /*__devinitdata*/ = {
+	0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5,
+	0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x0F7C, 0x167E, 0x7254,
+	0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x0BAA, 0x1B6D, 0x7234,
+	0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x06E7, 0x229E, 0x7224,
+
+	0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x07F6, 0x2C28, 0x7254,
+	0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x0F02, 0x1341, 0x7264,
+	0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x0FA9, 0x3EB5, 0x7294,
+	0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0x44C3, 0x3EBB, 0x45C3,
+
+	0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x0671, 0x14FD, 0x0287,
+	0x3EBC, 0xE610, 0x3EC8, 0x0C7B, 0x031A, 0x07E6, 0x3EC8, 0x86F7,
+	0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x021F, 0x3ECA, 0x0386,
+	0x3EC1, 0x0C03, 0x3EC9, 0x031E, 0x3ECA, 0x8C4C, 0x3EBF, 0x0C55,
+
+	0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x0EAD, 0x3EC8, 0xD308,
+	0x3EC2, 0x8F7E, 0x3ECB, 0x0219, 0x3ECB, 0xD26E, 0x3EC5, 0x031F,
+	0x3EC6, 0xC308, 0x3EC3, 0x32FF, 0x3EC9, 0x0265, 0x3EC9, 0x8319,
+	0x1342, 0xD36E, 0x3EC7, 0x33FF, 0x0000, 0x8365, 0x1420, 0x9570,
+};
+
+/* send an initialization array
+ * Taken from the oss driver, not obvious from the doc how this
+ * is meant to work
+ */
+static void __init
+send_array(emu8000_t *emu, unsigned short *data, int size)
+{
+	int i;
+	unsigned short *p;
+
+	p = data;
+	for (i = 0; i < size; i++, p++)
+		EMU8000_INIT1_WRITE(emu, i, *p);
+	for (i = 0; i < size; i++, p++)
+		EMU8000_INIT2_WRITE(emu, i, *p);
+	for (i = 0; i < size; i++, p++)
+		EMU8000_INIT3_WRITE(emu, i, *p);
+	for (i = 0; i < size; i++, p++)
+		EMU8000_INIT4_WRITE(emu, i, *p);
+}
+
+
+/*
+ * Send initialization arrays to start up, this just follows the
+ * initialisation sequence in the adip.
+ */
+static void __init
+init_arrays(emu8000_t *emu)
+{
+	send_array(emu, init1, ARRAY_SIZE(init1)/4);
+
+	msleep((1024 * 1000) / 44100); /* wait for 1024 clocks */
+	send_array(emu, init2, ARRAY_SIZE(init2)/4);
+	send_array(emu, init3, ARRAY_SIZE(init3)/4);
+
+	EMU8000_HWCF4_WRITE(emu, 0);
+	EMU8000_HWCF5_WRITE(emu, 0x83);
+	EMU8000_HWCF6_WRITE(emu, 0x8000);
+
+	send_array(emu, init4, ARRAY_SIZE(init4)/4);
+}
+
+
+#define UNIQUE_ID1	0xa5b9
+#define UNIQUE_ID2	0x9d53
+
+/*
+ * Size the onboard memory.
+ * This is written so as not to need arbitary delays after the write. It
+ * seems that the only way to do this is to use the one channel and keep
+ * reallocating between read and write.
+ */
+static void __init
+size_dram(emu8000_t *emu)
+{
+	int i, size;
+
+	if (emu->dram_checked)
+		return;
+
+	size = 0;
+
+	/* write out a magic number */
+	snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_WRITE);
+	snd_emu8000_dma_chan(emu, 1, EMU8000_RAM_READ);
+	EMU8000_SMALW_WRITE(emu, EMU8000_DRAM_OFFSET);
+	EMU8000_SMLD_WRITE(emu, UNIQUE_ID1);
+	snd_emu8000_init_fm(emu); /* This must really be here and not 2 lines back even */
+
+	while (size < EMU8000_MAX_DRAM) {
+
+		size += 512 * 1024;  /* increment 512kbytes */
+
+		/* Write a unique data on the test address.
+		 * if the address is out of range, the data is written on
+		 * 0x200000(=EMU8000_DRAM_OFFSET).  Then the id word is
+		 * changed by this data.
+		 */
+		/*snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_WRITE);*/
+		EMU8000_SMALW_WRITE(emu, EMU8000_DRAM_OFFSET + (size>>1));
+		EMU8000_SMLD_WRITE(emu, UNIQUE_ID2);
+		snd_emu8000_write_wait(emu);
+
+		/*
+		 * read the data on the just written DRAM address
+		 * if not the same then we have reached the end of ram.
+		 */
+		/*snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_READ);*/
+		EMU8000_SMALR_WRITE(emu, EMU8000_DRAM_OFFSET + (size>>1));
+		/*snd_emu8000_read_wait(emu);*/
+		EMU8000_SMLD_READ(emu); /* discard stale data  */
+		if (EMU8000_SMLD_READ(emu) != UNIQUE_ID2)
+			break; /* we must have wrapped around */
+
+		snd_emu8000_read_wait(emu);
+
+		/*
+		 * If it is the same it could be that the address just
+		 * wraps back to the beginning; so check to see if the
+		 * initial value has been overwritten.
+		 */
+		EMU8000_SMALR_WRITE(emu, EMU8000_DRAM_OFFSET);
+		EMU8000_SMLD_READ(emu); /* discard stale data  */
+		if (EMU8000_SMLD_READ(emu) != UNIQUE_ID1)
+			break; /* we must have wrapped around */
+		snd_emu8000_read_wait(emu);
+	}
+
+	/* wait until FULL bit in SMAxW register is false */
+	for (i = 0; i < 10000; i++) {
+		if ((EMU8000_SMALW_READ(emu) & 0x80000000) == 0)
+			break;
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(1);
+		if (signal_pending(current))
+			break;
+	}
+	snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_CLOSE);
+	snd_emu8000_dma_chan(emu, 1, EMU8000_RAM_CLOSE);
+
+	snd_printdd("EMU8000 [0x%lx]: %d Kb on-board memory detected\n",
+		    emu->port1, size/1024);
+
+	emu->mem_size = size;
+	emu->dram_checked = 1;
+}
+
+
+/*
+ * Initiailise the FM section.  You have to do this to use sample RAM
+ * and therefore lose 2 voices.
+ */
+/*exported*/ void
+snd_emu8000_init_fm(emu8000_t *emu)
+{
+	unsigned long flags;
+
+	/* Initialize the last two channels for DRAM refresh and producing
+	   the reverb and chorus effects for Yamaha OPL-3 synthesizer */
+
+	/* 31: FM left channel, 0xffffe0-0xffffe8 */
+	EMU8000_DCYSUSV_WRITE(emu, 30, 0x80);
+	EMU8000_PSST_WRITE(emu, 30, 0xFFFFFFE0); /* full left */
+	EMU8000_CSL_WRITE(emu, 30, 0x00FFFFE8 | (emu->fm_chorus_depth << 24));
+	EMU8000_PTRX_WRITE(emu, 30, (emu->fm_reverb_depth << 8));
+	EMU8000_CPF_WRITE(emu, 30, 0);
+	EMU8000_CCCA_WRITE(emu, 30, 0x00FFFFE3);
+
+	/* 32: FM right channel, 0xfffff0-0xfffff8 */
+	EMU8000_DCYSUSV_WRITE(emu, 31, 0x80);
+	EMU8000_PSST_WRITE(emu, 31, 0x00FFFFF0); /* full right */
+	EMU8000_CSL_WRITE(emu, 31, 0x00FFFFF8 | (emu->fm_chorus_depth << 24));
+	EMU8000_PTRX_WRITE(emu, 31, (emu->fm_reverb_depth << 8));
+	EMU8000_CPF_WRITE(emu, 31, 0x8000);
+	EMU8000_CCCA_WRITE(emu, 31, 0x00FFFFF3);
+
+	snd_emu8000_poke((emu), EMU8000_DATA0(emu), EMU8000_CMD(1, (30)), 0);
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	while (!(inw(EMU8000_PTR(emu)) & 0x1000))
+		;
+	while ((inw(EMU8000_PTR(emu)) & 0x1000))
+		;
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	snd_emu8000_poke((emu), EMU8000_DATA0(emu), EMU8000_CMD(1, (30)), 0x4828);
+	/* this is really odd part.. */
+	outb(0x3C, EMU8000_PTR(emu));
+	outb(0, EMU8000_DATA1(emu));
+
+	/* skew volume & cutoff */
+	EMU8000_VTFT_WRITE(emu, 30, 0x8000FFFF);
+	EMU8000_VTFT_WRITE(emu, 31, 0x8000FFFF);
+}
+
+
+/*
+ * The main initialization routine.
+ */
+static void __init
+snd_emu8000_init_hw(emu8000_t *emu)
+{
+	int i;
+
+	emu->last_reg = 0xffff; /* reset the last register index */
+
+	/* initialize hardware configuration */
+	EMU8000_HWCF1_WRITE(emu, 0x0059);
+	EMU8000_HWCF2_WRITE(emu, 0x0020);
+
+	/* disable audio; this seems to reduce a clicking noise a bit.. */
+	EMU8000_HWCF3_WRITE(emu, 0);
+
+	/* initialize audio channels */
+	init_audio(emu);
+
+	/* initialize DMA */
+	init_dma(emu);
+
+	/* initialize init arrays */
+	init_arrays(emu);
+
+	/*
+	 * Initialize the FM section of the AWE32, this is needed
+	 * for DRAM refresh as well
+	 */
+	snd_emu8000_init_fm(emu);
+
+	/* terminate all voices */
+	for (i = 0; i < EMU8000_DRAM_VOICES; i++)
+		EMU8000_DCYSUSV_WRITE(emu, 0, 0x807F);
+	
+	/* check DRAM memory size */
+	size_dram(emu);
+
+	/* enable audio */
+	EMU8000_HWCF3_WRITE(emu, 0x4);
+
+	/* set equzlier, chorus and reverb modes */
+	snd_emu8000_update_equalizer(emu);
+	snd_emu8000_update_chorus_mode(emu);
+	snd_emu8000_update_reverb_mode(emu);
+}
+
+
+/*----------------------------------------------------------------
+ * Bass/Treble Equalizer
+ *----------------------------------------------------------------*/
+
+static unsigned short bass_parm[12][3] = {
+	{0xD26A, 0xD36A, 0x0000}, /* -12 dB */
+	{0xD25B, 0xD35B, 0x0000}, /*  -8 */
+	{0xD24C, 0xD34C, 0x0000}, /*  -6 */
+	{0xD23D, 0xD33D, 0x0000}, /*  -4 */
+	{0xD21F, 0xD31F, 0x0000}, /*  -2 */
+	{0xC208, 0xC308, 0x0001}, /*   0 (HW default) */
+	{0xC219, 0xC319, 0x0001}, /*  +2 */
+	{0xC22A, 0xC32A, 0x0001}, /*  +4 */
+	{0xC24C, 0xC34C, 0x0001}, /*  +6 */
+	{0xC26E, 0xC36E, 0x0001}, /*  +8 */
+	{0xC248, 0xC384, 0x0002}, /* +10 */
+	{0xC26A, 0xC36A, 0x0002}, /* +12 dB */
+};
+
+static unsigned short treble_parm[12][9] = {
+	{0x821E, 0xC26A, 0x031E, 0xC36A, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, /* -12 dB */
+	{0x821E, 0xC25B, 0x031E, 0xC35B, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001},
+	{0x821E, 0xC24C, 0x031E, 0xC34C, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001},
+	{0x821E, 0xC23D, 0x031E, 0xC33D, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001},
+	{0x821E, 0xC21F, 0x031E, 0xC31F, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001},
+	{0x821E, 0xD208, 0x031E, 0xD308, 0x021E, 0xD208, 0x831E, 0xD308, 0x0002},
+	{0x821E, 0xD208, 0x031E, 0xD308, 0x021D, 0xD219, 0x831D, 0xD319, 0x0002},
+	{0x821E, 0xD208, 0x031E, 0xD308, 0x021C, 0xD22A, 0x831C, 0xD32A, 0x0002},
+	{0x821E, 0xD208, 0x031E, 0xD308, 0x021A, 0xD24C, 0x831A, 0xD34C, 0x0002},
+	{0x821E, 0xD208, 0x031E, 0xD308, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002}, /* +8 (HW default) */
+	{0x821D, 0xD219, 0x031D, 0xD319, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002},
+	{0x821C, 0xD22A, 0x031C, 0xD32A, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002}  /* +12 dB */
+};
+
+
+/*
+ * set Emu8000 digital equalizer; from 0 to 11 [-12dB - 12dB]
+ */
+/*exported*/ void
+snd_emu8000_update_equalizer(emu8000_t *emu)
+{
+	unsigned short w;
+	int bass = emu->bass_level;
+	int treble = emu->treble_level;
+
+	if (bass < 0 || bass > 11 || treble < 0 || treble > 11)
+		return;
+	EMU8000_INIT4_WRITE(emu, 0x01, bass_parm[bass][0]);
+	EMU8000_INIT4_WRITE(emu, 0x11, bass_parm[bass][1]);
+	EMU8000_INIT3_WRITE(emu, 0x11, treble_parm[treble][0]);
+	EMU8000_INIT3_WRITE(emu, 0x13, treble_parm[treble][1]);
+	EMU8000_INIT3_WRITE(emu, 0x1b, treble_parm[treble][2]);
+	EMU8000_INIT4_WRITE(emu, 0x07, treble_parm[treble][3]);
+	EMU8000_INIT4_WRITE(emu, 0x0b, treble_parm[treble][4]);
+	EMU8000_INIT4_WRITE(emu, 0x0d, treble_parm[treble][5]);
+	EMU8000_INIT4_WRITE(emu, 0x17, treble_parm[treble][6]);
+	EMU8000_INIT4_WRITE(emu, 0x19, treble_parm[treble][7]);
+	w = bass_parm[bass][2] + treble_parm[treble][8];
+	EMU8000_INIT4_WRITE(emu, 0x15, (unsigned short)(w + 0x0262));
+	EMU8000_INIT4_WRITE(emu, 0x1d, (unsigned short)(w + 0x8362));
+}
+
+
+/*----------------------------------------------------------------
+ * Chorus mode control
+ *----------------------------------------------------------------*/
+
+/*
+ * chorus mode parameters
+ */
+#define SNDRV_EMU8000_CHORUS_1		0
+#define	SNDRV_EMU8000_CHORUS_2		1
+#define	SNDRV_EMU8000_CHORUS_3		2
+#define	SNDRV_EMU8000_CHORUS_4		3
+#define	SNDRV_EMU8000_CHORUS_FEEDBACK	4
+#define	SNDRV_EMU8000_CHORUS_FLANGER	5
+#define	SNDRV_EMU8000_CHORUS_SHORTDELAY	6
+#define	SNDRV_EMU8000_CHORUS_SHORTDELAY2	7
+#define SNDRV_EMU8000_CHORUS_PREDEFINED	8
+/* user can define chorus modes up to 32 */
+#define SNDRV_EMU8000_CHORUS_NUMBERS	32
+
+typedef struct soundfont_chorus_fx_t {
+	unsigned short feedback;	/* feedback level (0xE600-0xE6FF) */
+	unsigned short delay_offset;	/* delay (0-0x0DA3) [1/44100 sec] */
+	unsigned short lfo_depth;	/* LFO depth (0xBC00-0xBCFF) */
+	unsigned int delay;	/* right delay (0-0xFFFFFFFF) [1/256/44100 sec] */
+	unsigned int lfo_freq;		/* LFO freq LFO freq (0-0xFFFFFFFF) */
+} soundfont_chorus_fx_t;
+
+/* 5 parameters for each chorus mode; 3 x 16bit, 2 x 32bit */
+static char chorus_defined[SNDRV_EMU8000_CHORUS_NUMBERS];
+static soundfont_chorus_fx_t chorus_parm[SNDRV_EMU8000_CHORUS_NUMBERS] = {
+	{0xE600, 0x03F6, 0xBC2C ,0x00000000, 0x0000006D}, /* chorus 1 */
+	{0xE608, 0x031A, 0xBC6E, 0x00000000, 0x0000017C}, /* chorus 2 */
+	{0xE610, 0x031A, 0xBC84, 0x00000000, 0x00000083}, /* chorus 3 */
+	{0xE620, 0x0269, 0xBC6E, 0x00000000, 0x0000017C}, /* chorus 4 */
+	{0xE680, 0x04D3, 0xBCA6, 0x00000000, 0x0000005B}, /* feedback */
+	{0xE6E0, 0x044E, 0xBC37, 0x00000000, 0x00000026}, /* flanger */
+	{0xE600, 0x0B06, 0xBC00, 0x0006E000, 0x00000083}, /* short delay */
+	{0xE6C0, 0x0B06, 0xBC00, 0x0006E000, 0x00000083}, /* short delay + feedback */
+};
+
+/*exported*/ int
+snd_emu8000_load_chorus_fx(emu8000_t *emu, int mode, const void __user *buf, long len)
+{
+	soundfont_chorus_fx_t rec;
+	if (mode < SNDRV_EMU8000_CHORUS_PREDEFINED || mode >= SNDRV_EMU8000_CHORUS_NUMBERS) {
+		snd_printk(KERN_WARNING "invalid chorus mode %d for uploading\n", mode);
+		return -EINVAL;
+	}
+	if (len < (long)sizeof(rec) || copy_from_user(&rec, buf, sizeof(rec)))
+		return -EFAULT;
+	chorus_parm[mode] = rec;
+	chorus_defined[mode] = 1;
+	return 0;
+}
+
+/*exported*/ void
+snd_emu8000_update_chorus_mode(emu8000_t *emu)
+{
+	int effect = emu->chorus_mode;
+	if (effect < 0 || effect >= SNDRV_EMU8000_CHORUS_NUMBERS ||
+	    (effect >= SNDRV_EMU8000_CHORUS_PREDEFINED && !chorus_defined[effect]))
+		return;
+	EMU8000_INIT3_WRITE(emu, 0x09, chorus_parm[effect].feedback);
+	EMU8000_INIT3_WRITE(emu, 0x0c, chorus_parm[effect].delay_offset);
+	EMU8000_INIT4_WRITE(emu, 0x03, chorus_parm[effect].lfo_depth);
+	EMU8000_HWCF4_WRITE(emu, chorus_parm[effect].delay);
+	EMU8000_HWCF5_WRITE(emu, chorus_parm[effect].lfo_freq);
+	EMU8000_HWCF6_WRITE(emu, 0x8000);
+	EMU8000_HWCF7_WRITE(emu, 0x0000);
+}
+
+/*----------------------------------------------------------------
+ * Reverb mode control
+ *----------------------------------------------------------------*/
+
+/*
+ * reverb mode parameters
+ */
+#define	SNDRV_EMU8000_REVERB_ROOM1	0
+#define SNDRV_EMU8000_REVERB_ROOM2	1
+#define	SNDRV_EMU8000_REVERB_ROOM3	2
+#define	SNDRV_EMU8000_REVERB_HALL1	3
+#define	SNDRV_EMU8000_REVERB_HALL2	4
+#define	SNDRV_EMU8000_REVERB_PLATE	5
+#define	SNDRV_EMU8000_REVERB_DELAY	6
+#define	SNDRV_EMU8000_REVERB_PANNINGDELAY 7
+#define SNDRV_EMU8000_REVERB_PREDEFINED	8
+/* user can define reverb modes up to 32 */
+#define SNDRV_EMU8000_REVERB_NUMBERS	32
+
+typedef struct soundfont_reverb_fx_t {
+	unsigned short parms[28];
+} soundfont_reverb_fx_t;
+
+/* reverb mode settings; write the following 28 data of 16 bit length
+ *   on the corresponding ports in the reverb_cmds array
+ */
+static char reverb_defined[SNDRV_EMU8000_CHORUS_NUMBERS];
+static soundfont_reverb_fx_t reverb_parm[SNDRV_EMU8000_REVERB_NUMBERS] = {
+{{  /* room 1 */
+	0xB488, 0xA450, 0x9550, 0x84B5, 0x383A, 0x3EB5, 0x72F4,
+	0x72A4, 0x7254, 0x7204, 0x7204, 0x7204, 0x4416, 0x4516,
+	0xA490, 0xA590, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429,
+	0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528,
+}},
+{{  /* room 2 */
+	0xB488, 0xA458, 0x9558, 0x84B5, 0x383A, 0x3EB5, 0x7284,
+	0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4448, 0x4548,
+	0xA440, 0xA540, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429,
+	0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528,
+}},
+{{  /* room 3 */
+	0xB488, 0xA460, 0x9560, 0x84B5, 0x383A, 0x3EB5, 0x7284,
+	0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4416, 0x4516,
+	0xA490, 0xA590, 0x842C, 0x852C, 0x842C, 0x852C, 0x842B,
+	0x852B, 0x842B, 0x852B, 0x842A, 0x852A, 0x842A, 0x852A,
+}},
+{{  /* hall 1 */
+	0xB488, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7284,
+	0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4448, 0x4548,
+	0xA440, 0xA540, 0x842B, 0x852B, 0x842B, 0x852B, 0x842A,
+	0x852A, 0x842A, 0x852A, 0x8429, 0x8529, 0x8429, 0x8529,
+}},
+{{  /* hall 2 */
+	0xB488, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7254,
+	0x7234, 0x7224, 0x7254, 0x7264, 0x7294, 0x44C3, 0x45C3,
+	0xA404, 0xA504, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429,
+	0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528,
+}},
+{{  /* plate */
+	0xB4FF, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7234,
+	0x7234, 0x7234, 0x7234, 0x7234, 0x7234, 0x4448, 0x4548,
+	0xA440, 0xA540, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429,
+	0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528,
+}},
+{{  /* delay */
+	0xB4FF, 0xA470, 0x9500, 0x84B5, 0x333A, 0x39B5, 0x7204,
+	0x7204, 0x7204, 0x7204, 0x7204, 0x72F4, 0x4400, 0x4500,
+	0xA4FF, 0xA5FF, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420,
+	0x8520, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, 0x8520,
+}},
+{{  /* panning delay */
+	0xB4FF, 0xA490, 0x9590, 0x8474, 0x333A, 0x39B5, 0x7204,
+	0x7204, 0x7204, 0x7204, 0x7204, 0x72F4, 0x4400, 0x4500,
+	0xA4FF, 0xA5FF, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420,
+	0x8520, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, 0x8520,
+}},
+};
+
+enum { DATA1, DATA2 };
+#define AWE_INIT1(c)	EMU8000_CMD(2,c), DATA1
+#define AWE_INIT2(c)	EMU8000_CMD(2,c), DATA2
+#define AWE_INIT3(c)	EMU8000_CMD(3,c), DATA1
+#define AWE_INIT4(c)	EMU8000_CMD(3,c), DATA2
+
+static struct reverb_cmd_pair {
+	unsigned short cmd, port;
+} reverb_cmds[28] = {
+  {AWE_INIT1(0x03)}, {AWE_INIT1(0x05)}, {AWE_INIT4(0x1F)}, {AWE_INIT1(0x07)},
+  {AWE_INIT2(0x14)}, {AWE_INIT2(0x16)}, {AWE_INIT1(0x0F)}, {AWE_INIT1(0x17)},
+  {AWE_INIT1(0x1F)}, {AWE_INIT2(0x07)}, {AWE_INIT2(0x0F)}, {AWE_INIT2(0x17)},
+  {AWE_INIT2(0x1D)}, {AWE_INIT2(0x1F)}, {AWE_INIT3(0x01)}, {AWE_INIT3(0x03)},
+  {AWE_INIT1(0x09)}, {AWE_INIT1(0x0B)}, {AWE_INIT1(0x11)}, {AWE_INIT1(0x13)},
+  {AWE_INIT1(0x19)}, {AWE_INIT1(0x1B)}, {AWE_INIT2(0x01)}, {AWE_INIT2(0x03)},
+  {AWE_INIT2(0x09)}, {AWE_INIT2(0x0B)}, {AWE_INIT2(0x11)}, {AWE_INIT2(0x13)},
+};
+
+/*exported*/ int
+snd_emu8000_load_reverb_fx(emu8000_t *emu, int mode, const void __user *buf, long len)
+{
+	soundfont_reverb_fx_t rec;
+
+	if (mode < SNDRV_EMU8000_REVERB_PREDEFINED || mode >= SNDRV_EMU8000_REVERB_NUMBERS) {
+		snd_printk(KERN_WARNING "invalid reverb mode %d for uploading\n", mode);
+		return -EINVAL;
+	}
+	if (len < (long)sizeof(rec) || copy_from_user(&rec, buf, sizeof(rec)))
+		return -EFAULT;
+	reverb_parm[mode] = rec;
+	reverb_defined[mode] = 1;
+	return 0;
+}
+
+/*exported*/ void
+snd_emu8000_update_reverb_mode(emu8000_t *emu)
+{
+	int effect = emu->reverb_mode;
+	int i;
+
+	if (effect < 0 || effect >= SNDRV_EMU8000_REVERB_NUMBERS ||
+	    (effect >= SNDRV_EMU8000_REVERB_PREDEFINED && !reverb_defined[effect]))
+		return;
+	for (i = 0; i < 28; i++) {
+		int port;
+		if (reverb_cmds[i].port == DATA1)
+			port = EMU8000_DATA1(emu);
+		else
+			port = EMU8000_DATA2(emu);
+		snd_emu8000_poke(emu, port, reverb_cmds[i].cmd, reverb_parm[effect].parms[i]);
+	}
+}
+
+
+/*----------------------------------------------------------------
+ * mixer interface
+ *----------------------------------------------------------------*/
+
+/*
+ * bass/treble
+ */
+static int mixer_bass_treble_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 = 11;
+	return 0;
+}
+
+static int mixer_bass_treble_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	emu8000_t *emu = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = kcontrol->private_value ? emu->treble_level : emu->bass_level;
+	return 0;
+}
+
+static int mixer_bass_treble_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	emu8000_t *emu = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned short val1;
+	
+	val1 = ucontrol->value.integer.value[0] % 12;
+	spin_lock_irqsave(&emu->control_lock, flags);
+	if (kcontrol->private_value) {
+		change = val1 != emu->treble_level;
+		emu->treble_level = val1;
+	} else {
+		change = val1 != emu->bass_level;
+		emu->bass_level = val1;
+	}
+	spin_unlock_irqrestore(&emu->control_lock, flags);
+	snd_emu8000_update_equalizer(emu);
+	return change;
+}
+
+static snd_kcontrol_new_t mixer_bass_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Synth Tone Control - Bass",
+	.info = mixer_bass_treble_info,
+	.get = mixer_bass_treble_get,
+	.put = mixer_bass_treble_put,
+	.private_value = 0,
+};
+
+static snd_kcontrol_new_t mixer_treble_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Synth Tone Control - Treble",
+	.info = mixer_bass_treble_info,
+	.get = mixer_bass_treble_get,
+	.put = mixer_bass_treble_put,
+	.private_value = 1,
+};
+
+/*
+ * chorus/reverb mode
+ */
+static int mixer_chorus_reverb_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 = kcontrol->private_value ? (SNDRV_EMU8000_CHORUS_NUMBERS-1) : (SNDRV_EMU8000_REVERB_NUMBERS-1);
+	return 0;
+}
+
+static int mixer_chorus_reverb_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	emu8000_t *emu = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = kcontrol->private_value ? emu->chorus_mode : emu->reverb_mode;
+	return 0;
+}
+
+static int mixer_chorus_reverb_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	emu8000_t *emu = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned short val1;
+	
+	spin_lock_irqsave(&emu->control_lock, flags);
+	if (kcontrol->private_value) {
+		val1 = ucontrol->value.integer.value[0] % SNDRV_EMU8000_CHORUS_NUMBERS;
+		change = val1 != emu->chorus_mode;
+		emu->chorus_mode = val1;
+	} else {
+		val1 = ucontrol->value.integer.value[0] % SNDRV_EMU8000_REVERB_NUMBERS;
+		change = val1 != emu->reverb_mode;
+		emu->reverb_mode = val1;
+	}
+	spin_unlock_irqrestore(&emu->control_lock, flags);
+	if (change) {
+		if (kcontrol->private_value)
+			snd_emu8000_update_chorus_mode(emu);
+		else
+			snd_emu8000_update_reverb_mode(emu);
+	}
+	return change;
+}
+
+static snd_kcontrol_new_t mixer_chorus_mode_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Chorus Mode",
+	.info = mixer_chorus_reverb_info,
+	.get = mixer_chorus_reverb_get,
+	.put = mixer_chorus_reverb_put,
+	.private_value = 1,
+};
+
+static snd_kcontrol_new_t mixer_reverb_mode_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Reverb Mode",
+	.info = mixer_chorus_reverb_info,
+	.get = mixer_chorus_reverb_get,
+	.put = mixer_chorus_reverb_put,
+	.private_value = 0,
+};
+
+/*
+ * FM OPL3 chorus/reverb depth
+ */
+static int mixer_fm_depth_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 = 255;
+	return 0;
+}
+
+static int mixer_fm_depth_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	emu8000_t *emu = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = kcontrol->private_value ? emu->fm_chorus_depth : emu->fm_reverb_depth;
+	return 0;
+}
+
+static int mixer_fm_depth_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	emu8000_t *emu = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned short val1;
+	
+	val1 = ucontrol->value.integer.value[0] % 256;
+	spin_lock_irqsave(&emu->control_lock, flags);
+	if (kcontrol->private_value) {
+		change = val1 != emu->fm_chorus_depth;
+		emu->fm_chorus_depth = val1;
+	} else {
+		change = val1 != emu->fm_reverb_depth;
+		emu->fm_reverb_depth = val1;
+	}
+	spin_unlock_irqrestore(&emu->control_lock, flags);
+	if (change)
+		snd_emu8000_init_fm(emu);
+	return change;
+}
+
+static snd_kcontrol_new_t mixer_fm_chorus_depth_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "FM Chorus Depth",
+	.info = mixer_fm_depth_info,
+	.get = mixer_fm_depth_get,
+	.put = mixer_fm_depth_put,
+	.private_value = 1,
+};
+
+static snd_kcontrol_new_t mixer_fm_reverb_depth_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "FM Reverb Depth",
+	.info = mixer_fm_depth_info,
+	.get = mixer_fm_depth_get,
+	.put = mixer_fm_depth_put,
+	.private_value = 0,
+};
+
+
+static snd_kcontrol_new_t *mixer_defs[EMU8000_NUM_CONTROLS] = {
+	&mixer_bass_control,
+	&mixer_treble_control,
+	&mixer_chorus_mode_control,
+	&mixer_reverb_mode_control,
+	&mixer_fm_chorus_depth_control,
+	&mixer_fm_reverb_depth_control,
+};
+
+/*
+ * create and attach mixer elements for WaveTable treble/bass controls
+ */
+static int __init
+snd_emu8000_create_mixer(snd_card_t *card, emu8000_t *emu)
+{
+	int i, err = 0;
+
+	snd_assert(emu != NULL && card != NULL, return -EINVAL);
+
+	spin_lock_init(&emu->control_lock);
+
+	memset(emu->controls, 0, sizeof(emu->controls));
+	for (i = 0; i < EMU8000_NUM_CONTROLS; i++) {
+		if ((err = snd_ctl_add(card, emu->controls[i] = snd_ctl_new1(mixer_defs[i], emu))) < 0)
+			goto __error;
+	}
+	return 0;
+
+__error:
+	for (i = 0; i < EMU8000_NUM_CONTROLS; i++) {
+		down_write(&card->controls_rwsem);
+		if (emu->controls[i])
+			snd_ctl_remove(card, emu->controls[i]);
+		up_write(&card->controls_rwsem);
+	}
+	return err;
+}
+
+
+/*
+ * free resources
+ */
+static int snd_emu8000_free(emu8000_t *hw)
+{
+	if (hw->res_port1) {
+		release_resource(hw->res_port1);
+		kfree_nocheck(hw->res_port1);
+	}
+	if (hw->res_port2) {
+		release_resource(hw->res_port2);
+		kfree_nocheck(hw->res_port2);
+	}
+	if (hw->res_port3) {
+		release_resource(hw->res_port3);
+		kfree_nocheck(hw->res_port3);
+	}
+	kfree(hw);
+	return 0;
+}
+
+/*
+ */
+static int snd_emu8000_dev_free(snd_device_t *device)
+{
+	emu8000_t *hw = device->device_data;
+	return snd_emu8000_free(hw);
+}
+
+/*
+ * initialize and register emu8000 synth device.
+ */
+int __init
+snd_emu8000_new(snd_card_t *card, int index, long port, int seq_ports, snd_seq_device_t **awe_ret)
+{
+	snd_seq_device_t *awe;
+	emu8000_t *hw;
+	int err;
+	static snd_device_ops_t ops = {
+		.dev_free = snd_emu8000_dev_free,
+	};
+
+	if (awe_ret)
+		*awe_ret = NULL;
+
+	if (seq_ports <= 0)
+		return 0;
+
+	hw = kcalloc(1, sizeof(*hw), GFP_KERNEL);
+	if (hw == NULL)
+		return -ENOMEM;
+	spin_lock_init(&hw->reg_lock);
+	hw->index = index;
+	hw->port1 = port;
+	hw->port2 = port + 0x400;
+	hw->port3 = port + 0x800;
+	if (!(hw->res_port1 = request_region(hw->port1, 4, "Emu8000-1")) ||
+	    !(hw->res_port2 = request_region(hw->port2, 4, "Emu8000-2")) ||
+	    !(hw->res_port3 = request_region(hw->port3, 4, "Emu8000-3"))) {
+		snd_printk(KERN_ERR "sbawe: can't grab ports 0x%lx, 0x%lx, 0x%lx\n", hw->port1, hw->port2, hw->port3);
+		snd_emu8000_free(hw);
+		return -EBUSY;
+	}
+	hw->mem_size = 0;
+	hw->card = card;
+	hw->seq_ports = seq_ports;
+	hw->bass_level = 5;
+	hw->treble_level = 9;
+	hw->chorus_mode = 2;
+	hw->reverb_mode = 4;
+	hw->fm_chorus_depth = 0;
+	hw->fm_reverb_depth = 0;
+
+	if (snd_emu8000_detect(hw) < 0) {
+		snd_emu8000_free(hw);
+		return -ENODEV;
+	}
+
+	snd_emu8000_init_hw(hw);
+	if ((err = snd_emu8000_create_mixer(card, hw)) < 0) {
+		snd_emu8000_free(hw);
+		return err;
+	}
+	
+	if ((err = snd_device_new(card, SNDRV_DEV_CODEC, hw, &ops)) < 0) {
+		snd_emu8000_free(hw);
+		return err;
+	}
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+	if (snd_seq_device_new(card, index, SNDRV_SEQ_DEV_ID_EMU8000,
+			       sizeof(emu8000_t*), &awe) >= 0) {
+		strcpy(awe->name, "EMU-8000");
+		*(emu8000_t**)SNDRV_SEQ_DEVICE_ARGPTR(awe) = hw;
+	}
+#else
+	awe = NULL;
+#endif
+	if (awe_ret)
+		*awe_ret = awe;
+
+	return 0;
+}
+
+
+/*
+ * exported stuff
+ */
+
+EXPORT_SYMBOL(snd_emu8000_poke);
+EXPORT_SYMBOL(snd_emu8000_peek);
+EXPORT_SYMBOL(snd_emu8000_poke_dw);
+EXPORT_SYMBOL(snd_emu8000_peek_dw);
+EXPORT_SYMBOL(snd_emu8000_dma_chan);
+EXPORT_SYMBOL(snd_emu8000_init_fm);
+EXPORT_SYMBOL(snd_emu8000_load_chorus_fx);
+EXPORT_SYMBOL(snd_emu8000_load_reverb_fx);
+EXPORT_SYMBOL(snd_emu8000_update_chorus_mode);
+EXPORT_SYMBOL(snd_emu8000_update_reverb_mode);
+EXPORT_SYMBOL(snd_emu8000_update_equalizer);
diff --git a/sound/isa/sb/emu8000_callback.c b/sound/isa/sb/emu8000_callback.c
new file mode 100644
index 0000000..1cc4101
--- /dev/null
+++ b/sound/isa/sb/emu8000_callback.c
@@ -0,0 +1,543 @@
+/*
+ *  synth callback routines for the emu8000 (AWE32/64)
+ *
+ *  Copyright (C) 1999 Steve Ratcliffe
+ *  Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *   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 "emu8000_local.h"
+#include <sound/asoundef.h>
+
+/*
+ * prototypes
+ */
+static snd_emux_voice_t *get_voice(snd_emux_t *emu, snd_emux_port_t *port);
+static int start_voice(snd_emux_voice_t *vp);
+static void trigger_voice(snd_emux_voice_t *vp);
+static void release_voice(snd_emux_voice_t *vp);
+static void update_voice(snd_emux_voice_t *vp, int update);
+static void reset_voice(snd_emux_t *emu, int ch);
+static void terminate_voice(snd_emux_voice_t *vp);
+static void sysex(snd_emux_t *emu, char *buf, int len, int parsed, snd_midi_channel_set_t *chset);
+#ifdef CONFIG_SND_SEQUENCER_OSS
+static int oss_ioctl(snd_emux_t *emu, int cmd, int p1, int p2);
+#endif
+static int load_fx(snd_emux_t *emu, int type, int mode, const void __user *buf, long len);
+
+static void set_pitch(emu8000_t *hw, snd_emux_voice_t *vp);
+static void set_volume(emu8000_t *hw, snd_emux_voice_t *vp);
+static void set_pan(emu8000_t *hw, snd_emux_voice_t *vp);
+static void set_fmmod(emu8000_t *hw, snd_emux_voice_t *vp);
+static void set_tremfreq(emu8000_t *hw, snd_emux_voice_t *vp);
+static void set_fm2frq2(emu8000_t *hw, snd_emux_voice_t *vp);
+static void set_filterQ(emu8000_t *hw, snd_emux_voice_t *vp);
+static void snd_emu8000_tweak_voice(emu8000_t *emu, int ch);
+
+/*
+ * Ensure a value is between two points
+ * macro evaluates its args more than once, so changed to upper-case.
+ */
+#define LIMITVALUE(x, a, b) do { if ((x) < (a)) (x) = (a); else if ((x) > (b)) (x) = (b); } while (0)
+#define LIMITMAX(x, a) do {if ((x) > (a)) (x) = (a); } while (0)
+
+
+/*
+ * set up operators
+ */
+static snd_emux_operators_t emu8000_ops = {
+	.owner =	THIS_MODULE,
+	.get_voice =	get_voice,
+	.prepare =	start_voice,
+	.trigger =	trigger_voice,
+	.release =	release_voice,
+	.update =	update_voice,
+	.terminate =	terminate_voice,
+	.reset =	reset_voice,
+	.sample_new =	snd_emu8000_sample_new,
+	.sample_free =	snd_emu8000_sample_free,
+	.sample_reset = snd_emu8000_sample_reset,
+	.load_fx =	load_fx,
+	.sysex =	sysex,
+#ifdef CONFIG_SND_SEQUENCER_OSS
+	.oss_ioctl =	oss_ioctl,
+#endif
+};
+
+void
+snd_emu8000_ops_setup(emu8000_t *hw)
+{
+	hw->emu->ops = emu8000_ops;
+}
+
+
+
+/*
+ * Terminate a voice
+ */
+static void
+release_voice(snd_emux_voice_t *vp)
+{
+	int dcysusv;
+	emu8000_t *hw;
+
+	hw = vp->hw;
+	dcysusv = 0x8000 | (unsigned char)vp->reg.parm.modrelease;
+	EMU8000_DCYSUS_WRITE(hw, vp->ch, dcysusv);
+	dcysusv = 0x8000 | (unsigned char)vp->reg.parm.volrelease;
+	EMU8000_DCYSUSV_WRITE(hw, vp->ch, dcysusv);
+}
+
+
+/*
+ */
+static void
+terminate_voice(snd_emux_voice_t *vp)
+{
+	emu8000_t *hw; 
+
+	hw = vp->hw;
+	EMU8000_DCYSUSV_WRITE(hw, vp->ch, 0x807F);
+}
+
+
+/*
+ */
+static void
+update_voice(snd_emux_voice_t *vp, int update)
+{
+	emu8000_t *hw;
+
+	hw = vp->hw;
+	if (update & SNDRV_EMUX_UPDATE_VOLUME)
+		set_volume(hw, vp);
+	if (update & SNDRV_EMUX_UPDATE_PITCH)
+		set_pitch(hw, vp);
+	if ((update & SNDRV_EMUX_UPDATE_PAN) &&
+	    vp->port->ctrls[EMUX_MD_REALTIME_PAN])
+		set_pan(hw, vp);
+	if (update & SNDRV_EMUX_UPDATE_FMMOD)
+		set_fmmod(hw, vp);
+	if (update & SNDRV_EMUX_UPDATE_TREMFREQ)
+		set_tremfreq(hw, vp);
+	if (update & SNDRV_EMUX_UPDATE_FM2FRQ2)
+		set_fm2frq2(hw, vp);
+	if (update & SNDRV_EMUX_UPDATE_Q)
+		set_filterQ(hw, vp);
+}
+
+
+/*
+ * Find a channel (voice) within the EMU that is not in use or at least
+ * less in use than other channels.  Always returns a valid pointer
+ * no matter what.  If there is a real shortage of voices then one
+ * will be cut. Such is life.
+ *
+ * The channel index (vp->ch) must be initialized in this routine.
+ * In Emu8k, it is identical with the array index.
+ */
+static snd_emux_voice_t *
+get_voice(snd_emux_t *emu, snd_emux_port_t *port)
+{
+	int  i;
+	snd_emux_voice_t *vp;
+	emu8000_t *hw;
+
+	/* what we are looking for, in order of preference */
+	enum {
+		OFF=0, RELEASED, PLAYING, END
+	};
+
+	/* Keeps track of what we are finding */
+	struct best {
+		unsigned int  time;
+		int voice;
+	} best[END];
+	struct best *bp;
+
+	hw = emu->hw;
+
+	for (i = 0; i < END; i++) {
+		best[i].time = (unsigned int)(-1); /* XXX MAX_?INT really */;
+		best[i].voice = -1;
+	}
+
+	/*
+	 * Go through them all and get a best one to use.
+	 */
+	for (i = 0; i < emu->max_voices; i++) {
+		int state, val;
+
+		vp = &emu->voices[i];
+		state = vp->state;
+
+		if (state == SNDRV_EMUX_ST_OFF)
+			bp = best + OFF;
+		else if (state == SNDRV_EMUX_ST_RELEASED ||
+			 state == SNDRV_EMUX_ST_PENDING) {
+			bp = best + RELEASED;
+			val = (EMU8000_CVCF_READ(hw, vp->ch) >> 16) & 0xffff;
+			if (! val)
+				bp = best + OFF;
+		}
+		else if (state & SNDRV_EMUX_ST_ON)
+			bp = best + PLAYING;
+		else
+			continue;
+
+		/* check if sample is finished playing (non-looping only) */
+		if (state != SNDRV_EMUX_ST_OFF &&
+		    (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_SINGLESHOT)) {
+			val = EMU8000_CCCA_READ(hw, vp->ch) & 0xffffff;
+			if (val >= vp->reg.loopstart)
+				bp = best + OFF;
+		}
+
+		if (vp->time < bp->time) {
+			bp->time = vp->time;
+			bp->voice = i;
+		}
+	}
+
+	for (i = 0; i < END; i++) {
+		if (best[i].voice >= 0) {
+			vp = &emu->voices[best[i].voice];
+			vp->ch = best[i].voice;
+			return vp;
+		}
+	}
+
+	/* not found */
+	return NULL;
+}
+
+/*
+ */
+static int
+start_voice(snd_emux_voice_t *vp)
+{
+	unsigned int temp;
+	int ch;
+	int addr;
+	snd_midi_channel_t *chan;
+	emu8000_t *hw;
+
+	hw = vp->hw;
+	ch = vp->ch;
+	chan = vp->chan;
+
+	/* channel to be silent and idle */
+	EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080);
+	EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF);
+	EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF);
+	EMU8000_PTRX_WRITE(hw, ch, 0);
+	EMU8000_CPF_WRITE(hw, ch, 0);
+
+	/* set pitch offset */
+	set_pitch(hw, vp);
+
+	/* set envelope parameters */
+	EMU8000_ENVVAL_WRITE(hw, ch, vp->reg.parm.moddelay);
+	EMU8000_ATKHLD_WRITE(hw, ch, vp->reg.parm.modatkhld);
+	EMU8000_DCYSUS_WRITE(hw, ch, vp->reg.parm.moddcysus);
+	EMU8000_ENVVOL_WRITE(hw, ch, vp->reg.parm.voldelay);
+	EMU8000_ATKHLDV_WRITE(hw, ch, vp->reg.parm.volatkhld);
+	/* decay/sustain parameter for volume envelope is used
+	   for triggerg the voice */
+
+	/* cutoff and volume */
+	set_volume(hw, vp);
+
+	/* modulation envelope heights */
+	EMU8000_PEFE_WRITE(hw, ch, vp->reg.parm.pefe);
+
+	/* lfo1/2 delay */
+	EMU8000_LFO1VAL_WRITE(hw, ch, vp->reg.parm.lfo1delay);
+	EMU8000_LFO2VAL_WRITE(hw, ch, vp->reg.parm.lfo2delay);
+
+	/* lfo1 pitch & cutoff shift */
+	set_fmmod(hw, vp);
+	/* lfo1 volume & freq */
+	set_tremfreq(hw, vp);
+	/* lfo2 pitch & freq */
+	set_fm2frq2(hw, vp);
+	/* pan & loop start */
+	set_pan(hw, vp);
+
+	/* chorus & loop end (chorus 8bit, MSB) */
+	addr = vp->reg.loopend - 1;
+	temp = vp->reg.parm.chorus;
+	temp += (int)chan->control[MIDI_CTL_E3_CHORUS_DEPTH] * 9 / 10;
+	LIMITMAX(temp, 255);
+	temp = (temp <<24) | (unsigned int)addr;
+	EMU8000_CSL_WRITE(hw, ch, temp);
+
+	/* Q & current address (Q 4bit value, MSB) */
+	addr = vp->reg.start - 1;
+	temp = vp->reg.parm.filterQ;
+	temp = (temp<<28) | (unsigned int)addr;
+	EMU8000_CCCA_WRITE(hw, ch, temp);
+
+	/* clear unknown registers */
+	EMU8000_00A0_WRITE(hw, ch, 0);
+	EMU8000_0080_WRITE(hw, ch, 0);
+
+	/* reset volume */
+	temp = vp->vtarget << 16;
+	EMU8000_VTFT_WRITE(hw, ch, temp | vp->ftarget);
+	EMU8000_CVCF_WRITE(hw, ch, temp | 0xff00);
+
+	return 0;
+}
+
+/*
+ * Start envelope
+ */
+static void
+trigger_voice(snd_emux_voice_t *vp)
+{
+	int ch = vp->ch;
+	unsigned int temp;
+	emu8000_t *hw;
+
+	hw = vp->hw;
+
+	/* set reverb and pitch target */
+	temp = vp->reg.parm.reverb;
+	temp += (int)vp->chan->control[MIDI_CTL_E1_REVERB_DEPTH] * 9 / 10;
+	LIMITMAX(temp, 255);
+	temp = (temp << 8) | (vp->ptarget << 16) | vp->aaux;
+	EMU8000_PTRX_WRITE(hw, ch, temp);
+	EMU8000_CPF_WRITE(hw, ch, vp->ptarget << 16);
+	EMU8000_DCYSUSV_WRITE(hw, ch, vp->reg.parm.voldcysus);
+}
+
+/*
+ * reset voice parameters
+ */
+static void
+reset_voice(snd_emux_t *emu, int ch)
+{
+	emu8000_t *hw;
+
+	hw = emu->hw;
+	EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F);
+	snd_emu8000_tweak_voice(hw, ch);
+}
+
+/*
+ * Set the pitch of a possibly playing note.
+ */
+static void
+set_pitch(emu8000_t *hw, snd_emux_voice_t *vp)
+{
+	EMU8000_IP_WRITE(hw, vp->ch, vp->apitch);
+}
+
+/*
+ * Set the volume of a possibly already playing note
+ */
+static void
+set_volume(emu8000_t *hw, snd_emux_voice_t *vp)
+{
+	int  ifatn;
+
+	ifatn = (unsigned char)vp->acutoff;
+	ifatn = (ifatn << 8);
+	ifatn |= (unsigned char)vp->avol;
+	EMU8000_IFATN_WRITE(hw, vp->ch, ifatn);
+}
+
+/*
+ * Set pan and loop start address.
+ */
+static void
+set_pan(emu8000_t *hw, snd_emux_voice_t *vp)
+{
+	unsigned int temp;
+
+	temp = ((unsigned int)vp->apan<<24) | ((unsigned int)vp->reg.loopstart - 1);
+	EMU8000_PSST_WRITE(hw, vp->ch, temp);
+}
+
+#define MOD_SENSE 18
+
+static void
+set_fmmod(emu8000_t *hw, snd_emux_voice_t *vp)
+{
+	unsigned short fmmod;
+	short pitch;
+	unsigned char cutoff;
+	int modulation;
+
+	pitch = (char)(vp->reg.parm.fmmod>>8);
+	cutoff = (vp->reg.parm.fmmod & 0xff);
+	modulation = vp->chan->gm_modulation + vp->chan->midi_pressure;
+	pitch += (MOD_SENSE * modulation) / 1200;
+	LIMITVALUE(pitch, -128, 127);
+	fmmod = ((unsigned char)pitch<<8) | cutoff;
+	EMU8000_FMMOD_WRITE(hw, vp->ch, fmmod);
+}
+
+/* set tremolo (lfo1) volume & frequency */
+static void
+set_tremfreq(emu8000_t *hw, snd_emux_voice_t *vp)
+{
+	EMU8000_TREMFRQ_WRITE(hw, vp->ch, vp->reg.parm.tremfrq);
+}
+
+/* set lfo2 pitch & frequency */
+static void
+set_fm2frq2(emu8000_t *hw, snd_emux_voice_t *vp)
+{
+	unsigned short fm2frq2;
+	short pitch;
+	unsigned char freq;
+	int modulation;
+
+	pitch = (char)(vp->reg.parm.fm2frq2>>8);
+	freq = vp->reg.parm.fm2frq2 & 0xff;
+	modulation = vp->chan->gm_modulation + vp->chan->midi_pressure;
+	pitch += (MOD_SENSE * modulation) / 1200;
+	LIMITVALUE(pitch, -128, 127);
+	fm2frq2 = ((unsigned char)pitch<<8) | freq;
+	EMU8000_FM2FRQ2_WRITE(hw, vp->ch, fm2frq2);
+}
+
+/* set filterQ */
+static void
+set_filterQ(emu8000_t *hw, snd_emux_voice_t *vp)
+{
+	unsigned int addr;
+	addr = EMU8000_CCCA_READ(hw, vp->ch) & 0xffffff;
+	addr |= (vp->reg.parm.filterQ << 28);
+	EMU8000_CCCA_WRITE(hw, vp->ch, addr);
+}
+
+/*
+ * set the envelope & LFO parameters to the default values
+ */
+static void
+snd_emu8000_tweak_voice(emu8000_t *emu, int i)
+{
+	/* set all mod/vol envelope shape to minimum */
+	EMU8000_ENVVOL_WRITE(emu, i, 0x8000);
+	EMU8000_ENVVAL_WRITE(emu, i, 0x8000);
+	EMU8000_DCYSUS_WRITE(emu, i, 0x7F7F);
+	EMU8000_ATKHLDV_WRITE(emu, i, 0x7F7F);
+	EMU8000_ATKHLD_WRITE(emu, i, 0x7F7F);
+	EMU8000_PEFE_WRITE(emu, i, 0);  /* mod envelope height to zero */
+	EMU8000_LFO1VAL_WRITE(emu, i, 0x8000); /* no delay for LFO1 */
+	EMU8000_LFO2VAL_WRITE(emu, i, 0x8000);
+	EMU8000_IP_WRITE(emu, i, 0xE000);	/* no pitch shift */
+	EMU8000_IFATN_WRITE(emu, i, 0xFF00);	/* volume to minimum */
+	EMU8000_FMMOD_WRITE(emu, i, 0);
+	EMU8000_TREMFRQ_WRITE(emu, i, 0);
+	EMU8000_FM2FRQ2_WRITE(emu, i, 0);
+}
+
+/*
+ * sysex callback
+ */
+static void
+sysex(snd_emux_t *emu, char *buf, int len, int parsed, snd_midi_channel_set_t *chset)
+{
+	emu8000_t *hw;
+
+	hw = emu->hw;
+
+	switch (parsed) {
+	case SNDRV_MIDI_SYSEX_GS_CHORUS_MODE:
+		hw->chorus_mode = chset->gs_chorus_mode;
+		snd_emu8000_update_chorus_mode(hw);
+		break;
+
+	case SNDRV_MIDI_SYSEX_GS_REVERB_MODE:
+		hw->reverb_mode = chset->gs_reverb_mode;
+		snd_emu8000_update_reverb_mode(hw);
+		break;
+	}
+}
+
+
+#ifdef CONFIG_SND_SEQUENCER_OSS
+/*
+ * OSS ioctl callback
+ */
+static int
+oss_ioctl(snd_emux_t *emu, int cmd, int p1, int p2)
+{
+	emu8000_t *hw;
+
+	hw = emu->hw;
+
+	switch (cmd) {
+	case _EMUX_OSS_REVERB_MODE:
+		hw->reverb_mode = p1;
+		snd_emu8000_update_reverb_mode(hw);
+		break;
+
+	case _EMUX_OSS_CHORUS_MODE:
+		hw->chorus_mode = p1;
+		snd_emu8000_update_chorus_mode(hw);
+		break;
+
+	case _EMUX_OSS_INITIALIZE_CHIP:
+		/* snd_emu8000_init(hw); */ /*ignored*/
+		break;
+
+	case _EMUX_OSS_EQUALIZER:
+		hw->bass_level = p1;
+		hw->treble_level = p2;
+		snd_emu8000_update_equalizer(hw);
+		break;
+	}
+	return 0;
+}
+#endif
+
+
+/*
+ * additional patch keys
+ */
+
+#define SNDRV_EMU8000_LOAD_CHORUS_FX	0x10	/* optarg=mode */
+#define SNDRV_EMU8000_LOAD_REVERB_FX	0x11	/* optarg=mode */
+
+
+/*
+ * callback routine
+ */
+
+static int
+load_fx(snd_emux_t *emu, int type, int mode, const void __user *buf, long len)
+{
+	emu8000_t *hw;
+	hw = emu->hw;
+
+	/* skip header */
+	buf += 16;
+	len -= 16;
+
+	switch (type) {
+	case SNDRV_EMU8000_LOAD_CHORUS_FX:
+		return snd_emu8000_load_chorus_fx(hw, mode, buf, len);
+	case SNDRV_EMU8000_LOAD_REVERB_FX:
+		return snd_emu8000_load_reverb_fx(hw, mode, buf, len);
+	}
+	return -EINVAL;
+}
+
diff --git a/sound/isa/sb/emu8000_local.h b/sound/isa/sb/emu8000_local.h
new file mode 100644
index 0000000..ea4996a
--- /dev/null
+++ b/sound/isa/sb/emu8000_local.h
@@ -0,0 +1,43 @@
+#ifndef __EMU8000_LOCAL_H
+#define __EMU8000_LOCAL_H
+/*
+ *  Local defininitons for the emu8000 (AWE32/64)
+ *
+ *  Copyright (C) 1999 Steve Ratcliffe
+ *  Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *   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/wait.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/emu8000.h>
+#include <sound/emu8000_reg.h>
+
+/* emu8000_patch.c */
+int snd_emu8000_sample_new(snd_emux_t *rec, snd_sf_sample_t *sp, snd_util_memhdr_t *hdr, const void __user *data, long count);
+int snd_emu8000_sample_free(snd_emux_t *rec, snd_sf_sample_t *sp, snd_util_memhdr_t *hdr);
+void snd_emu8000_sample_reset(snd_emux_t *rec);
+
+/* emu8000_callback.c */
+void snd_emu8000_ops_setup(emu8000_t *emu);
+
+/* emu8000_pcm.c */
+int snd_emu8000_pcm_new(snd_card_t *card, emu8000_t *emu, int index);
+
+#endif	/* __EMU8000_LOCAL_H */
diff --git a/sound/isa/sb/emu8000_patch.c b/sound/isa/sb/emu8000_patch.c
new file mode 100644
index 0000000..4afc4a1
--- /dev/null
+++ b/sound/isa/sb/emu8000_patch.c
@@ -0,0 +1,303 @@
+/*
+ *  Patch routines for the emu8000 (AWE32/64)
+ *
+ *  Copyright (C) 1999 Steve Ratcliffe
+ *  Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *   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 "emu8000_local.h"
+#include <asm/uaccess.h>
+#include <linux/moduleparam.h>
+
+static int emu8000_reset_addr = 0;
+module_param(emu8000_reset_addr, int, 0444);
+MODULE_PARM_DESC(emu8000_reset_addr, "reset write address at each time (makes slowdown)");
+
+
+/*
+ * Open up channels.
+ */
+static int
+snd_emu8000_open_dma(emu8000_t *emu, int write)
+{
+	int i;
+
+	/* reserve all 30 voices for loading */
+	for (i = 0; i < EMU8000_DRAM_VOICES; i++) {
+		snd_emux_lock_voice(emu->emu, i);
+		snd_emu8000_dma_chan(emu, i, write);
+	}
+
+	/* assign voice 31 and 32 to ROM */
+	EMU8000_VTFT_WRITE(emu, 30, 0);
+	EMU8000_PSST_WRITE(emu, 30, 0x1d8);
+	EMU8000_CSL_WRITE(emu, 30, 0x1e0);
+	EMU8000_CCCA_WRITE(emu, 30, 0x1d8);
+	EMU8000_VTFT_WRITE(emu, 31, 0);
+	EMU8000_PSST_WRITE(emu, 31, 0x1d8);
+	EMU8000_CSL_WRITE(emu, 31, 0x1e0);
+	EMU8000_CCCA_WRITE(emu, 31, 0x1d8);
+
+	return 0;
+}
+
+/*
+ * Close all dram channels.
+ */
+static void
+snd_emu8000_close_dma(emu8000_t *emu)
+{
+	int i;
+
+	for (i = 0; i < EMU8000_DRAM_VOICES; i++) {
+		snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE);
+		snd_emux_unlock_voice(emu->emu, i);
+	}
+}
+
+/*
+ */
+
+#define BLANK_LOOP_START	4
+#define BLANK_LOOP_END		8
+#define BLANK_LOOP_SIZE		12
+#define BLANK_HEAD_SIZE		48
+
+/*
+ * Read a word from userland, taking care of conversions from
+ * 8bit samples etc.
+ */
+static unsigned short
+read_word(const void __user *buf, int offset, int mode)
+{
+	unsigned short c;
+	if (mode & SNDRV_SFNT_SAMPLE_8BITS) {
+		unsigned char cc;
+		get_user(cc, (unsigned char __user *)buf + offset);
+		c = cc << 8; /* convert 8bit -> 16bit */
+	} else {
+#ifdef SNDRV_LITTLE_ENDIAN
+		get_user(c, (unsigned short __user *)buf + offset);
+#else
+		unsigned short cc;
+		get_user(cc, (unsigned short __user *)buf + offset);
+		c = swab16(cc);
+#endif
+	}
+	if (mode & SNDRV_SFNT_SAMPLE_UNSIGNED)
+		c ^= 0x8000; /* unsigned -> signed */
+	return c;
+}
+
+/*
+ */
+static void
+snd_emu8000_write_wait(emu8000_t *emu)
+{
+	while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(1);
+		if (signal_pending(current))
+			break;
+	}
+}
+
+/*
+ * write sample word data
+ *
+ * You should not have to keep resetting the address each time
+ * as the chip is supposed to step on the next address automatically.
+ * It mostly does, but during writes of some samples at random it
+ * completely loses words (every one in 16 roughly but with no
+ * obvious pattern).
+ *
+ * This is therefore much slower than need be, but is at least
+ * working.
+ */
+inline static void
+write_word(emu8000_t *emu, int *offset, unsigned short data)
+{
+	if (emu8000_reset_addr) {
+		if (emu8000_reset_addr > 1)
+			snd_emu8000_write_wait(emu);
+		EMU8000_SMALW_WRITE(emu, *offset);
+	}
+	EMU8000_SMLD_WRITE(emu, data);
+	*offset += 1;
+}
+
+/*
+ * Write the sample to EMU800 memory.  This routine is invoked out of
+ * the generic soundfont routines as a callback.
+ */
+int
+snd_emu8000_sample_new(snd_emux_t *rec, snd_sf_sample_t *sp,
+		       snd_util_memhdr_t *hdr, const void __user *data, long count)
+{
+	int  i;
+	int  rc;
+	int  offset;
+	int  truesize;
+	int  dram_offset, dram_start;
+	emu8000_t *emu;
+
+	emu = rec->hw;
+	snd_assert(sp != NULL, return -EINVAL);
+
+	if (sp->v.size == 0)
+		return 0;
+
+	/* be sure loop points start < end */
+	if (sp->v.loopstart > sp->v.loopend) {
+		int tmp = sp->v.loopstart;
+		sp->v.loopstart = sp->v.loopend;
+		sp->v.loopend = tmp;
+	}
+
+	/* compute true data size to be loaded */
+	truesize = sp->v.size;
+	if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP))
+		truesize += sp->v.loopend - sp->v.loopstart;
+	if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK)
+		truesize += BLANK_LOOP_SIZE;
+
+	sp->block = snd_util_mem_alloc(hdr, truesize * 2);
+	if (sp->block == NULL) {
+		/*snd_printd("EMU8000: out of memory\n");*/
+		/* not ENOMEM (for compatibility) */
+		return -ENOSPC;
+	}
+
+	if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS) {
+		if (!access_ok(VERIFY_READ, data, sp->v.size))
+			return -EFAULT;
+	} else {
+		if (!access_ok(VERIFY_READ, data, sp->v.size * 2))
+			return -EFAULT;
+	}
+
+	/* recalculate address offset */
+	sp->v.end -= sp->v.start;
+	sp->v.loopstart -= sp->v.start;
+	sp->v.loopend -= sp->v.start;
+	sp->v.start = 0;
+
+	/* dram position (in word) -- mem_offset is byte */
+	dram_offset = EMU8000_DRAM_OFFSET + (sp->block->offset >> 1);
+	dram_start = dram_offset;
+
+	/* set the total size (store onto obsolete checksum value) */
+	sp->v.truesize = truesize * 2; /* in bytes */
+
+	snd_emux_terminate_all(emu->emu);
+	if ((rc = snd_emu8000_open_dma(emu, EMU8000_RAM_WRITE)) != 0)
+		return rc;
+
+	/* Set the address to start writing at */
+	snd_emu8000_write_wait(emu);
+	EMU8000_SMALW_WRITE(emu, dram_offset);
+
+	/*snd_emu8000_init_fm(emu);*/
+
+#if 0
+	/* first block - write 48 samples for silence */
+	if (! sp->block->offset) {
+		for (i = 0; i < BLANK_HEAD_SIZE; i++) {
+			write_word(emu, &dram_offset, 0);
+		}
+	}
+#endif
+
+	offset = 0;
+	for (i = 0; i < sp->v.size; i++) {
+		unsigned short s;
+
+		s = read_word(data, offset, sp->v.mode_flags);
+		offset++;
+		write_word(emu, &dram_offset, s);
+
+		/* we may take too long time in this loop.
+		 * so give controls back to kernel if needed.
+		 */
+		cond_resched();
+
+		if (i == sp->v.loopend &&
+		    (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP)))
+		{
+			int looplen = sp->v.loopend - sp->v.loopstart;
+			int k;
+
+			/* copy reverse loop */
+			for (k = 1; k <= looplen; k++) {
+				s = read_word(data, offset - k, sp->v.mode_flags);
+				write_word(emu, &dram_offset, s);
+			}
+			if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_BIDIR_LOOP) {
+				sp->v.loopend += looplen;
+			} else {
+				sp->v.loopstart += looplen;
+				sp->v.loopend += looplen;
+			}
+			sp->v.end += looplen;
+		}
+	}
+
+	/* if no blank loop is attached in the sample, add it */
+	if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK) {
+		for (i = 0; i < BLANK_LOOP_SIZE; i++) {
+			write_word(emu, &dram_offset, 0);
+		}
+		if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_SINGLESHOT) {
+			sp->v.loopstart = sp->v.end + BLANK_LOOP_START;
+			sp->v.loopend = sp->v.end + BLANK_LOOP_END;
+		}
+	}
+
+	/* add dram offset */
+	sp->v.start += dram_start;
+	sp->v.end += dram_start;
+	sp->v.loopstart += dram_start;
+	sp->v.loopend += dram_start;
+
+	snd_emu8000_close_dma(emu);
+	snd_emu8000_init_fm(emu);
+
+	return 0;
+}
+
+/*
+ * free a sample block
+ */
+int
+snd_emu8000_sample_free(snd_emux_t *rec, snd_sf_sample_t *sp, snd_util_memhdr_t *hdr)
+{
+	if (sp->block) {
+		snd_util_mem_free(hdr, sp->block);
+		sp->block = NULL;
+	}
+	return 0;
+}
+
+
+/*
+ * sample_reset callback - terminate voices
+ */
+void
+snd_emu8000_sample_reset(snd_emux_t *rec)
+{
+	snd_emux_terminate_all(rec);
+}
diff --git a/sound/isa/sb/emu8000_pcm.c b/sound/isa/sb/emu8000_pcm.c
new file mode 100644
index 0000000..db5eb8b
--- /dev/null
+++ b/sound/isa/sb/emu8000_pcm.c
@@ -0,0 +1,704 @@
+/*
+ * pcm emulation on emu8000 wavetable
+ *
+ *  Copyright (C) 2002 Takashi Iwai <tiwai@suse.de>
+ *
+ *   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 "emu8000_local.h"
+#include <linux/init.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+
+/*
+ * define the following if you want to use this pcm with non-interleaved mode
+ */
+/* #define USE_NONINTERLEAVE */
+
+/* NOTE: for using the non-interleaved mode with alsa-lib, you have to set
+ * mmap_emulation flag to 1 in your .asoundrc, such like
+ *
+ *	pcm.emu8k {
+ *		type plug
+ *		slave.pcm {
+ *			type hw
+ *			card 0
+ *			device 1
+ *			mmap_emulation 1
+ *		}
+ *	}
+ *
+ * besides, for the time being, the non-interleaved mode doesn't work well on
+ * alsa-lib...
+ */
+
+
+typedef struct snd_emu8k_pcm emu8k_pcm_t;
+
+struct snd_emu8k_pcm {
+	emu8000_t *emu;
+	snd_pcm_substream_t *substream;
+
+	unsigned int allocated_bytes;
+	snd_util_memblk_t *block;
+	unsigned int offset;
+	unsigned int buf_size;
+	unsigned int period_size;
+	unsigned int loop_start[2];
+	unsigned int pitch;
+	int panning[2];
+	int last_ptr;
+	int period_pos;
+	int voices;
+	unsigned int dram_opened: 1;
+	unsigned int running: 1;
+	unsigned int timer_running: 1;
+	struct timer_list timer;
+	spinlock_t timer_lock;
+};
+
+#define LOOP_BLANK_SIZE		8
+
+
+/*
+ * open up channels for the simultaneous data transfer and playback
+ */
+static int
+emu8k_open_dram_for_pcm(emu8000_t *emu, int channels)
+{
+	int i;
+
+	/* reserve up to 2 voices for playback */
+	snd_emux_lock_voice(emu->emu, 0);
+	if (channels > 1)
+		snd_emux_lock_voice(emu->emu, 1);
+
+	/* reserve 28 voices for loading */
+	for (i = channels + 1; i < EMU8000_DRAM_VOICES; i++) {
+		unsigned int mode = EMU8000_RAM_WRITE;
+		snd_emux_lock_voice(emu->emu, i);
+#ifndef USE_NONINTERLEAVE
+		if (channels > 1 && (i & 1) != 0)
+			mode |= EMU8000_RAM_RIGHT;
+#endif
+		snd_emu8000_dma_chan(emu, i, mode);
+	}
+
+	/* assign voice 31 and 32 to ROM */
+	EMU8000_VTFT_WRITE(emu, 30, 0);
+	EMU8000_PSST_WRITE(emu, 30, 0x1d8);
+	EMU8000_CSL_WRITE(emu, 30, 0x1e0);
+	EMU8000_CCCA_WRITE(emu, 30, 0x1d8);
+	EMU8000_VTFT_WRITE(emu, 31, 0);
+	EMU8000_PSST_WRITE(emu, 31, 0x1d8);
+	EMU8000_CSL_WRITE(emu, 31, 0x1e0);
+	EMU8000_CCCA_WRITE(emu, 31, 0x1d8);
+
+	return 0;
+}
+
+/*
+ */
+static void
+snd_emu8000_write_wait(emu8000_t *emu, int can_schedule)
+{
+	while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
+		if (can_schedule) {
+			set_current_state(TASK_INTERRUPTIBLE);
+			schedule_timeout(1);
+			if (signal_pending(current))
+				break;
+		}
+	}
+}
+
+/*
+ * close all channels
+ */
+static void
+emu8k_close_dram(emu8000_t *emu)
+{
+	int i;
+
+	for (i = 0; i < 2; i++)
+		snd_emux_unlock_voice(emu->emu, i);
+	for (; i < EMU8000_DRAM_VOICES; i++) {
+		snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE);
+		snd_emux_unlock_voice(emu->emu, i);
+	}
+}
+
+/*
+ * convert Hz to AWE32 rate offset (see emux/soundfont.c)
+ */
+
+#define OFFSET_SAMPLERATE	1011119		/* base = 44100 */
+#define SAMPLERATE_RATIO	4096
+
+static int calc_rate_offset(int hz)
+{
+	return snd_sf_linear_to_log(hz, OFFSET_SAMPLERATE, SAMPLERATE_RATIO);
+}
+
+
+/*
+ */
+
+static snd_pcm_hardware_t emu8k_pcm_hw = {
+#ifdef USE_NONINTERLEAVE
+	.info =			SNDRV_PCM_INFO_NONINTERLEAVED,
+#else
+	.info =			SNDRV_PCM_INFO_INTERLEAVED,
+#endif
+	.formats =		SNDRV_PCM_FMTBIT_S16_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 =	1024,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		1024,
+	.fifo_size =		0,
+
+};
+
+/*
+ * get the current position at the given channel from CCCA register
+ */
+static inline int emu8k_get_curpos(emu8k_pcm_t *rec, int ch)
+{
+	int val = EMU8000_CCCA_READ(rec->emu, ch) & 0xfffffff;
+	val -= rec->loop_start[ch] - 1;
+	return val;
+}
+
+
+/*
+ * timer interrupt handler
+ * check the current position and update the period if necessary.
+ */
+static void emu8k_pcm_timer_func(unsigned long data)
+{
+	emu8k_pcm_t *rec = (emu8k_pcm_t *)data;
+	int ptr, delta;
+
+	spin_lock(&rec->timer_lock);
+	/* update the current pointer */
+	ptr = emu8k_get_curpos(rec, 0);
+	if (ptr < rec->last_ptr)
+		delta = ptr + rec->buf_size - rec->last_ptr;
+	else
+		delta = ptr - rec->last_ptr;
+	rec->period_pos += delta;
+	rec->last_ptr = ptr;
+
+	/* reprogram timer */
+	rec->timer.expires = jiffies + 1;
+	add_timer(&rec->timer);
+
+	/* update period */
+	if (rec->period_pos >= (int)rec->period_size) {
+		rec->period_pos %= rec->period_size;
+		spin_unlock(&rec->timer_lock);
+		snd_pcm_period_elapsed(rec->substream);
+		return;
+	}
+	spin_unlock(&rec->timer_lock);
+}
+
+
+/*
+ * open pcm
+ * creating an instance here
+ */
+static int emu8k_pcm_open(snd_pcm_substream_t *subs)
+{
+	emu8000_t *emu = snd_pcm_substream_chip(subs);
+	emu8k_pcm_t *rec;
+	snd_pcm_runtime_t *runtime = subs->runtime;
+
+	rec = kcalloc(1, sizeof(*rec), GFP_KERNEL);
+	if (! rec)
+		return -ENOMEM;
+
+	rec->emu = emu;
+	rec->substream = subs;
+	runtime->private_data = rec;
+
+	spin_lock_init(&rec->timer_lock);
+	init_timer(&rec->timer);
+	rec->timer.function = emu8k_pcm_timer_func;
+	rec->timer.data = (unsigned long)rec;
+
+	runtime->hw = emu8k_pcm_hw;
+	runtime->hw.buffer_bytes_max = emu->mem_size - LOOP_BLANK_SIZE * 3;
+	runtime->hw.period_bytes_max = runtime->hw.buffer_bytes_max / 2;
+
+	/* use timer to update periods.. (specified in msec) */
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+				     (1000000 + HZ - 1) / HZ, UINT_MAX);
+
+	return 0;
+}
+
+static int emu8k_pcm_close(snd_pcm_substream_t *subs)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+	kfree(rec);
+	subs->runtime->private_data = NULL;
+	return 0;
+}
+
+/*
+ * calculate pitch target
+ */
+static int calc_pitch_target(int pitch)
+{
+	int ptarget = 1 << (pitch >> 12);
+	if (pitch & 0x800) ptarget += (ptarget * 0x102e) / 0x2710;
+	if (pitch & 0x400) ptarget += (ptarget * 0x764) / 0x2710;
+	if (pitch & 0x200) ptarget += (ptarget * 0x389) / 0x2710;
+	ptarget += (ptarget >> 1);
+	if (ptarget > 0xffff) ptarget = 0xffff;
+	return ptarget;
+}
+
+/*
+ * set up the voice
+ */
+static void setup_voice(emu8k_pcm_t *rec, int ch)
+{
+	emu8000_t *hw = rec->emu;
+	unsigned int temp;
+
+	/* channel to be silent and idle */
+	EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080);
+	EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF);
+	EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF);
+	EMU8000_PTRX_WRITE(hw, ch, 0);
+	EMU8000_CPF_WRITE(hw, ch, 0);
+
+	/* pitch offset */
+	EMU8000_IP_WRITE(hw, ch, rec->pitch);
+	/* set envelope parameters */
+	EMU8000_ENVVAL_WRITE(hw, ch, 0x8000);
+	EMU8000_ATKHLD_WRITE(hw, ch, 0x7f7f);
+	EMU8000_DCYSUS_WRITE(hw, ch, 0x7f7f);
+	EMU8000_ENVVOL_WRITE(hw, ch, 0x8000);
+	EMU8000_ATKHLDV_WRITE(hw, ch, 0x7f7f);
+	/* decay/sustain parameter for volume envelope is used
+	   for triggerg the voice */
+	/* modulation envelope heights */
+	EMU8000_PEFE_WRITE(hw, ch, 0x0);
+	/* lfo1/2 delay */
+	EMU8000_LFO1VAL_WRITE(hw, ch, 0x8000);
+	EMU8000_LFO2VAL_WRITE(hw, ch, 0x8000);
+	/* lfo1 pitch & cutoff shift */
+	EMU8000_FMMOD_WRITE(hw, ch, 0);
+	/* lfo1 volume & freq */
+	EMU8000_TREMFRQ_WRITE(hw, ch, 0);
+	/* lfo2 pitch & freq */
+	EMU8000_FM2FRQ2_WRITE(hw, ch, 0);
+	/* pan & loop start */
+	temp = rec->panning[ch];
+	temp = (temp <<24) | ((unsigned int)rec->loop_start[ch] - 1);
+	EMU8000_PSST_WRITE(hw, ch, temp);
+	/* chorus & loop end (chorus 8bit, MSB) */
+	temp = 0; // chorus
+	temp = (temp << 24) | ((unsigned int)rec->loop_start[ch] + rec->buf_size - 1);
+	EMU8000_CSL_WRITE(hw, ch, temp);
+	/* Q & current address (Q 4bit value, MSB) */
+	temp = 0; // filterQ
+	temp = (temp << 28) | ((unsigned int)rec->loop_start[ch] - 1);
+	EMU8000_CCCA_WRITE(hw, ch, temp);
+	/* clear unknown registers */
+	EMU8000_00A0_WRITE(hw, ch, 0);
+	EMU8000_0080_WRITE(hw, ch, 0);
+}
+
+/*
+ * trigger the voice
+ */
+static void start_voice(emu8k_pcm_t *rec, int ch)
+{
+	unsigned long flags;
+	emu8000_t *hw = rec->emu;
+	unsigned int temp, aux;
+	int pt = calc_pitch_target(rec->pitch);
+
+	/* cutoff and volume */
+	EMU8000_IFATN_WRITE(hw, ch, 0xff00);
+	EMU8000_VTFT_WRITE(hw, ch, 0xffff);
+	EMU8000_CVCF_WRITE(hw, ch, 0xffff);
+	/* trigger envelope */
+	EMU8000_DCYSUSV_WRITE(hw, ch, 0x7f7f);
+	/* set reverb and pitch target */
+	temp = 0; // reverb
+	if (rec->panning[ch] == 0)
+		aux = 0xff;
+	else
+		aux = (-rec->panning[ch]) & 0xff;
+	temp = (temp << 8) | (pt << 16) | aux;
+	EMU8000_PTRX_WRITE(hw, ch, temp);
+	EMU8000_CPF_WRITE(hw, ch, pt << 16);
+
+	/* start timer */
+	spin_lock_irqsave(&rec->timer_lock, flags);
+	if (! rec->timer_running) {
+		rec->timer.expires = jiffies + 1;
+		add_timer(&rec->timer);
+		rec->timer_running = 1;
+	}
+	spin_unlock_irqrestore(&rec->timer_lock, flags);
+}
+
+/*
+ * stop the voice immediately
+ */
+static void stop_voice(emu8k_pcm_t *rec, int ch)
+{
+	unsigned long flags;
+	emu8000_t *hw = rec->emu;
+
+	EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F);
+
+	/* stop timer */
+	spin_lock_irqsave(&rec->timer_lock, flags);
+	if (rec->timer_running) {
+		del_timer(&rec->timer);
+		rec->timer_running = 0;
+	}
+	spin_unlock_irqrestore(&rec->timer_lock, flags);
+}
+
+static int emu8k_pcm_trigger(snd_pcm_substream_t *subs, int cmd)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+	int ch;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		for (ch = 0; ch < rec->voices; ch++)
+			start_voice(rec, ch);
+		rec->running = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		rec->running = 0;
+		for (ch = 0; ch < rec->voices; ch++)
+			stop_voice(rec, ch);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+
+/*
+ * copy / silence ops
+ */
+
+/*
+ * this macro should be inserted in the copy/silence loops
+ * to reduce the latency.  without this, the system will hang up
+ * during the whole loop.
+ */
+#define CHECK_SCHEDULER() \
+do { \
+	cond_resched();\
+	if (signal_pending(current))\
+		return -EAGAIN;\
+} while (0)
+
+
+#ifdef USE_NONINTERLEAVE
+/* copy one channel block */
+static int emu8k_transfer_block(emu8000_t *emu, int offset, unsigned short *buf, int count)
+{
+	EMU8000_SMALW_WRITE(emu, offset);
+	while (count > 0) {
+		unsigned short sval;
+		CHECK_SCHEDULER();
+		get_user(sval, buf);
+		EMU8000_SMLD_WRITE(emu, sval);
+		buf++;
+		count--;
+	}
+	return 0;
+}
+
+static int emu8k_pcm_copy(snd_pcm_substream_t *subs,
+			  int voice,
+			  snd_pcm_uframes_t pos,
+			  void *src,
+			  snd_pcm_uframes_t count)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+	emu8000_t *emu = rec->emu;
+
+	snd_emu8000_write_wait(emu, 1);
+	if (voice == -1) {
+		unsigned short *buf = src;
+		int i, err;
+		count /= rec->voices;
+		for (i = 0; i < rec->voices; i++) {
+			err = emu8k_transfer_block(emu, pos + rec->loop_start[i], buf, count);
+			if (err < 0)
+				return err;
+			buf += count;
+		}
+		return 0;
+	} else {
+		return emu8k_transfer_block(emu, pos + rec->loop_start[voice], src, count);
+	}
+}
+
+/* make a channel block silence */
+static int emu8k_silence_block(emu8000_t *emu, int offset, int count)
+{
+	EMU8000_SMALW_WRITE(emu, offset);
+	while (count > 0) {
+		CHECK_SCHEDULER();
+		EMU8000_SMLD_WRITE(emu, 0);
+		count--;
+	}
+	return 0;
+}
+
+static int emu8k_pcm_silence(snd_pcm_substream_t *subs,
+			     int voice,
+			     snd_pcm_uframes_t pos,
+			     snd_pcm_uframes_t count)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+	emu8000_t *emu = rec->emu;
+
+	snd_emu8000_write_wait(emu, 1);
+	if (voice == -1 && rec->voices == 1)
+		voice = 0;
+	if (voice == -1) {
+		int err;
+		err = emu8k_silence_block(emu, pos + rec->loop_start[0], count / 2);
+		if (err < 0)
+			return err;
+		return emu8k_silence_block(emu, pos + rec->loop_start[1], count / 2);
+	} else {
+		return emu8k_silence_block(emu, pos + rec->loop_start[voice], count);
+	}
+}
+
+#else /* interleave */
+
+/*
+ * copy the interleaved data can be done easily by using
+ * DMA "left" and "right" channels on emu8k engine.
+ */
+static int emu8k_pcm_copy(snd_pcm_substream_t *subs,
+			  int voice,
+			  snd_pcm_uframes_t pos,
+			  void __user *src,
+			  snd_pcm_uframes_t count)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+	emu8000_t *emu = rec->emu;
+	unsigned short __user *buf = src;
+
+	snd_emu8000_write_wait(emu, 1);
+	EMU8000_SMALW_WRITE(emu, pos + rec->loop_start[0]);
+	if (rec->voices > 1)
+		EMU8000_SMARW_WRITE(emu, pos + rec->loop_start[1]);
+
+	while (count-- > 0) {
+		unsigned short sval;
+		CHECK_SCHEDULER();
+		get_user(sval, buf);
+		EMU8000_SMLD_WRITE(emu, sval);
+		buf++;
+		if (rec->voices > 1) {
+			CHECK_SCHEDULER();
+			get_user(sval, buf);
+			EMU8000_SMRD_WRITE(emu, sval);
+			buf++;
+		}
+	}
+	return 0;
+}
+
+static int emu8k_pcm_silence(snd_pcm_substream_t *subs,
+			     int voice,
+			     snd_pcm_uframes_t pos,
+			     snd_pcm_uframes_t count)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+	emu8000_t *emu = rec->emu;
+
+	snd_emu8000_write_wait(emu, 1);
+	EMU8000_SMALW_WRITE(emu, rec->loop_start[0] + pos);
+	if (rec->voices > 1)
+		EMU8000_SMARW_WRITE(emu, rec->loop_start[1] + pos);
+	while (count-- > 0) {
+		CHECK_SCHEDULER();
+		EMU8000_SMLD_WRITE(emu, 0);
+		if (rec->voices > 1) {
+			CHECK_SCHEDULER();
+			EMU8000_SMRD_WRITE(emu, 0);
+		}
+	}
+	return 0;
+}
+#endif
+
+
+/*
+ * allocate a memory block
+ */
+static int emu8k_pcm_hw_params(snd_pcm_substream_t *subs,
+			       snd_pcm_hw_params_t *hw_params)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+
+	if (rec->block) {
+		/* reallocation - release the old block */
+		snd_util_mem_free(rec->emu->memhdr, rec->block);
+		rec->block = NULL;
+	}
+
+	rec->allocated_bytes = params_buffer_bytes(hw_params) + LOOP_BLANK_SIZE * 4;
+	rec->block = snd_util_mem_alloc(rec->emu->memhdr, rec->allocated_bytes);
+	if (! rec->block)
+		return -ENOMEM;
+	rec->offset = EMU8000_DRAM_OFFSET + (rec->block->offset >> 1); /* in word */
+	/* at least dma_bytes must be set for non-interleaved mode */
+	subs->dma_buffer.bytes = params_buffer_bytes(hw_params);
+
+	return 0;
+}
+
+/*
+ * free the memory block
+ */
+static int emu8k_pcm_hw_free(snd_pcm_substream_t *subs)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+
+	if (rec->block) {
+		int ch;
+		for (ch = 0; ch < rec->voices; ch++)
+			stop_voice(rec, ch); // to be sure
+		if (rec->dram_opened)
+			emu8k_close_dram(rec->emu);
+		snd_util_mem_free(rec->emu->memhdr, rec->block);
+		rec->block = NULL;
+	}
+	return 0;
+}
+
+/*
+ */
+static int emu8k_pcm_prepare(snd_pcm_substream_t *subs)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+
+	rec->pitch = 0xe000 + calc_rate_offset(subs->runtime->rate);
+	rec->last_ptr = 0;
+	rec->period_pos = 0;
+
+	rec->buf_size = subs->runtime->buffer_size;
+	rec->period_size = subs->runtime->period_size;
+	rec->voices = subs->runtime->channels;
+	rec->loop_start[0] = rec->offset + LOOP_BLANK_SIZE;
+	if (rec->voices > 1)
+		rec->loop_start[1] = rec->loop_start[0] + rec->buf_size + LOOP_BLANK_SIZE;
+	if (rec->voices > 1) {
+		rec->panning[0] = 0xff;
+		rec->panning[1] = 0x00;
+	} else
+		rec->panning[0] = 0x80;
+
+	if (! rec->dram_opened) {
+		int err, i, ch;
+
+		snd_emux_terminate_all(rec->emu->emu);
+		if ((err = emu8k_open_dram_for_pcm(rec->emu, rec->voices)) != 0)
+			return err;
+		rec->dram_opened = 1;
+
+		/* clear loop blanks */
+		snd_emu8000_write_wait(rec->emu, 0);
+		EMU8000_SMALW_WRITE(rec->emu, rec->offset);
+		for (i = 0; i < LOOP_BLANK_SIZE; i++)
+			EMU8000_SMLD_WRITE(rec->emu, 0);
+		for (ch = 0; ch < rec->voices; ch++) {
+			EMU8000_SMALW_WRITE(rec->emu, rec->loop_start[ch] + rec->buf_size);
+			for (i = 0; i < LOOP_BLANK_SIZE; i++)
+				EMU8000_SMLD_WRITE(rec->emu, 0);
+		}
+	}
+
+	setup_voice(rec, 0);
+	if (rec->voices > 1)
+		setup_voice(rec, 1);
+	return 0;
+}
+
+static snd_pcm_uframes_t emu8k_pcm_pointer(snd_pcm_substream_t *subs)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+	if (rec->running)
+		return emu8k_get_curpos(rec, 0);
+	return 0;
+}
+
+
+static snd_pcm_ops_t emu8k_pcm_ops = {
+	.open =		emu8k_pcm_open,
+	.close =	emu8k_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	emu8k_pcm_hw_params,
+	.hw_free =	emu8k_pcm_hw_free,
+	.prepare =	emu8k_pcm_prepare,
+	.trigger =	emu8k_pcm_trigger,
+	.pointer =	emu8k_pcm_pointer,
+	.copy =		emu8k_pcm_copy,
+	.silence =	emu8k_pcm_silence,
+};
+
+
+static void snd_emu8000_pcm_free(snd_pcm_t *pcm)
+{
+	emu8000_t *emu = pcm->private_data;
+	emu->pcm = NULL;
+}
+
+int snd_emu8000_pcm_new(snd_card_t *card, emu8000_t *emu, int index)
+{
+	snd_pcm_t *pcm;
+	int err;
+
+	if ((err = snd_pcm_new(card, "Emu8000 PCM", index, 1, 0, &pcm)) < 0)
+		return err;
+	pcm->private_data = emu;
+	pcm->private_free = snd_emu8000_pcm_free;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &emu8k_pcm_ops);
+	emu->pcm = pcm;
+
+	snd_device_register(card, pcm);
+
+	return 0;
+}
diff --git a/sound/isa/sb/emu8000_synth.c b/sound/isa/sb/emu8000_synth.c
new file mode 100644
index 0000000..1f63aa5
--- /dev/null
+++ b/sound/isa/sb/emu8000_synth.c
@@ -0,0 +1,134 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *     and (c) 1999 Steve Ratcliffe <steve@parabola.demon.co.uk>
+ *  Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *  Emu8000 synth plug-in routine
+ *
+ *   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 "emu8000_local.h"
+#include <linux/init.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Takashi Iwai, Steve Ratcliffe");
+MODULE_DESCRIPTION("Emu8000 synth plug-in routine");
+MODULE_LICENSE("GPL");
+
+/*----------------------------------------------------------------*/
+
+/*
+ * create a new hardware dependent device for Emu8000
+ */
+static int snd_emu8000_new_device(snd_seq_device_t *dev)
+{
+	emu8000_t *hw;
+	snd_emux_t *emu;
+
+	hw = *(emu8000_t**)SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	if (hw == NULL)
+		return -EINVAL;
+
+	if (hw->emu)
+		return -EBUSY; /* already exists..? */
+
+	if (snd_emux_new(&emu) < 0)
+		return -ENOMEM;
+
+	hw->emu = emu;
+	snd_emu8000_ops_setup(hw);
+
+	emu->hw = hw;
+	emu->max_voices = EMU8000_DRAM_VOICES;
+	emu->num_ports = hw->seq_ports;
+
+	if (hw->memhdr) {
+		snd_printk("memhdr is already initialized!?\n");
+		snd_util_memhdr_free(hw->memhdr);
+	}
+	hw->memhdr = snd_util_memhdr_new(hw->mem_size);
+	if (hw->memhdr == NULL) {
+		snd_emux_free(emu);
+		hw->emu = NULL;
+		return -ENOMEM;
+	}
+
+	emu->memhdr = hw->memhdr;
+	emu->midi_ports = hw->seq_ports < 2 ? hw->seq_ports : 2; /* number of virmidi ports */
+	emu->midi_devidx = 1;
+	emu->linear_panning = 1;
+	emu->hwdep_idx = 2; /* FIXED */
+
+	if (snd_emux_register(emu, dev->card, hw->index, "Emu8000") < 0) {
+		snd_emux_free(emu);
+		snd_util_memhdr_free(hw->memhdr);
+		hw->emu = NULL;
+		hw->memhdr = NULL;
+		return -ENOMEM;
+	}
+
+	if (hw->mem_size > 0)
+		snd_emu8000_pcm_new(dev->card, hw, 1);
+
+	dev->driver_data = hw;
+
+	return 0;
+}
+
+
+/*
+ * free all resources
+ */
+static int snd_emu8000_delete_device(snd_seq_device_t *dev)
+{
+	emu8000_t *hw;
+
+	if (dev->driver_data == NULL)
+		return 0; /* no synth was allocated actually */
+
+	hw = dev->driver_data;
+	if (hw->pcm)
+		snd_device_free(dev->card, hw->pcm);
+	if (hw->emu)
+		snd_emux_free(hw->emu);
+	if (hw->memhdr)
+		snd_util_memhdr_free(hw->memhdr);
+	hw->emu = NULL;
+	hw->memhdr = NULL;
+	return 0;
+}
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_emu8000_init(void)
+{
+	
+	static snd_seq_dev_ops_t ops = {
+		snd_emu8000_new_device,
+		snd_emu8000_delete_device,
+	};
+	return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_EMU8000, &ops, sizeof(emu8000_t*));
+}
+
+static void __exit alsa_emu8000_exit(void)
+{
+	snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_EMU8000);
+}
+
+module_init(alsa_emu8000_init)
+module_exit(alsa_emu8000_exit)
diff --git a/sound/isa/sb/es968.c b/sound/isa/sb/es968.c
new file mode 100644
index 0000000..c859917
--- /dev/null
+++ b/sound/isa/sb/es968.c
@@ -0,0 +1,235 @@
+
+/*
+    card-es968.c - driver for ESS AudioDrive ES968 based soundcards.
+    Copyright (C) 1999 by Massimo Piccioni <dafastidio@libero.it>
+
+    Thanks to Pierfrancesco 'qM2' Passerini.
+
+    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/time.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/sb.h>
+
+#define PFX "es968: "
+
+MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>");
+MODULE_DESCRIPTION("ESS AudioDrive ES968");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ESS,AudioDrive ES968}}");
+
+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_ISAPNP; /* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* Pnp setup */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* PnP setup */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for es968 based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for es968 based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable es968 based soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for es968 driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for es968 driver.");
+module_param_array(dma8, int, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for es968 driver.");
+
+struct snd_card_es968 {
+	struct pnp_dev *dev;
+};
+
+static struct pnp_card_device_id snd_es968_pnpids[] = {
+	{ .id = "ESS0968", .devs = { { "@@@0968" }, } },
+	{ .id = "", } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_es968_pnpids);
+
+#define	DRIVER_NAME	"snd-card-es968"
+
+static irqreturn_t snd_card_es968_interrupt(int irq, void *dev_id,
+					    struct pt_regs *regs)
+{
+	sb_t *chip = dev_id;
+
+	if (chip->open & SB_OPEN_PCM) {
+		return snd_sb8dsp_interrupt(chip);
+	} else {
+		return snd_sb8dsp_midi_interrupt(chip);
+	}
+}
+
+static int __devinit snd_card_es968_pnp(int dev, struct snd_card_es968 *acard,
+					struct pnp_card_link *card,
+					const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	struct pnp_resource_table *cfg = kmalloc(sizeof(*cfg), GFP_KERNEL);
+	int err;
+	if (!cfg)
+		return -ENOMEM;
+	acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->dev == NULL) {
+		kfree(cfg);
+		return -ENODEV;
+	}
+
+	pdev = acard->dev;
+
+	pnp_init_resource_table(cfg);
+
+	/* override resources */
+	if (port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[0], port[dev], 16);
+	if (dma8[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[0], dma8[dev], 1);
+	if (irq[dev] != SNDRV_AUTO_IRQ)
+		pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1);
+	if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0)
+		snd_printk(KERN_ERR PFX "AUDIO the requested resources are invalid, using auto config\n");
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n");
+		kfree(cfg);
+		return err;
+	}
+	port[dev] = pnp_port_start(pdev, 0);
+	dma8[dev] = pnp_dma(pdev, 1);
+	irq[dev] = pnp_irq(pdev, 0);
+
+	kfree(cfg);
+	return 0;
+}
+
+static int __init snd_card_es968_probe(int dev,
+					struct pnp_card_link *pcard,
+					const struct pnp_card_device_id *pid)
+{
+	int error;
+	sb_t *chip;
+	snd_card_t *card;
+	struct snd_card_es968 *acard;
+
+	if ((card = snd_card_new(index[dev], id[dev], THIS_MODULE,
+				 sizeof(struct snd_card_es968))) == NULL)
+		return -ENOMEM;
+	acard = (struct snd_card_es968 *)card->private_data;
+	if ((error = snd_card_es968_pnp(dev, acard, pcard, pid))) {
+		snd_card_free(card);
+		return error;
+	}
+	snd_card_set_dev(card, &pcard->card->dev);
+
+	if ((error = snd_sbdsp_create(card, port[dev],
+				      irq[dev],
+				      snd_card_es968_interrupt,
+				      dma8[dev],
+				      -1,
+				      SB_HW_AUTO, &chip)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if ((error = snd_sb8dsp_pcm(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if ((error = snd_sbmixer_new(chip)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if ((error = snd_sb8dsp_midi(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	strcpy(card->driver, "ES968");
+	strcpy(card->shortname, "ESS ES968");
+	sprintf(card->longname, "%s soundcard, %s at 0x%lx, irq %d, dma %d",
+		card->shortname, chip->name, chip->port, irq[dev], dma8[dev]);
+
+	if ((error = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	return 0;
+}
+
+static int __devinit snd_es968_pnp_detect(struct pnp_card_link *card,
+                                          const struct pnp_card_device_id *id)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev])
+			continue;
+		res = snd_card_es968_probe(dev, card, id);
+		if (res < 0)
+			return res;
+		dev++;
+		return 0;
+	}
+	return -ENODEV;
+}
+
+static void __devexit snd_es968_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_t *card = (snd_card_t *) pnp_get_card_drvdata(pcard);
+
+	snd_card_disconnect(card);
+	snd_card_free_in_thread(card);
+}
+
+static struct pnp_card_driver es968_pnpc_driver = {
+	.flags		= PNP_DRIVER_RES_DISABLE,
+	.name		= "es968",
+	.id_table	= snd_es968_pnpids,
+	.probe		= snd_es968_pnp_detect,
+	.remove		= __devexit_p(snd_es968_pnp_remove),
+};
+
+static int __init alsa_card_es968_init(void)
+{
+	int cards = pnp_register_card_driver(&es968_pnpc_driver);
+#ifdef MODULE
+	if (cards == 0) {
+		pnp_unregister_card_driver(&es968_pnpc_driver);
+		snd_printk(KERN_ERR "no ES968 based soundcards found\n");
+	}
+#endif
+	return cards ? 0 : -ENODEV;
+}
+
+static void __exit alsa_card_es968_exit(void)
+{
+	pnp_unregister_card_driver(&es968_pnpc_driver);
+}
+
+module_init(alsa_card_es968_init)
+module_exit(alsa_card_es968_exit)
diff --git a/sound/isa/sb/sb16.c b/sound/isa/sb/sb16.c
new file mode 100644
index 0000000..60e2c53
--- /dev/null
+++ b/sound/isa/sb/sb16.c
@@ -0,0 +1,678 @@
+/*
+ *  Driver for SoundBlaster 16/AWE32/AWE64 soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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/dma.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/sb16_csp.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/emu8000.h>
+#include <sound/seq_device.h>
+#define SNDRV_LEGACY_AUTO_PROBE
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+#ifdef SNDRV_SBAWE
+#define PFX "sbawe: "
+#else
+#define PFX "sb16: "
+#endif
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_LICENSE("GPL");
+#ifndef SNDRV_SBAWE
+MODULE_DESCRIPTION("Sound Blaster 16");
+MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB 16},"
+		"{Creative Labs,SB Vibra16S},"
+		"{Creative Labs,SB Vibra16C},"
+		"{Creative Labs,SB Vibra16CL},"
+		"{Creative Labs,SB Vibra16X}}");
+#else
+MODULE_DESCRIPTION("Sound Blaster AWE");
+MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB AWE 32},"
+		"{Creative Labs,SB AWE 64},"
+		"{Creative Labs,SB AWE 64 Gold}}");
+#endif
+
+#if 0
+#define SNDRV_DEBUG_IRQ
+#endif
+
+#if defined(SNDRV_SBAWE) && (defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE)))
+#define SNDRV_SBAWE_EMU8000
+#endif
+
+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_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static int isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x240,0x260,0x280 */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x330,0x300 */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+#ifdef SNDRV_SBAWE_EMU8000
+static long awe_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+#endif
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,10 */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3 */
+static int dma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 5,6,7 */
+static int mic_agc[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#ifdef CONFIG_SND_SB16_CSP
+static int csp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 0};
+#endif
+#ifdef SNDRV_SBAWE_EMU8000
+static int seq_ports[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 4};
+#endif
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for SoundBlaster 16 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for SoundBlaster 16 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable SoundBlaster 16 soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for SB16 driver.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for SB16 driver.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for SB16 PnP driver.");
+#ifdef SNDRV_SBAWE_EMU8000
+module_param_array(awe_port, long, NULL, 0444);
+MODULE_PARM_DESC(awe_port, "AWE port # for SB16 PnP driver.");
+#endif
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for SB16 driver.");
+module_param_array(dma8, int, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for SB16 driver.");
+module_param_array(dma16, int, NULL, 0444);
+MODULE_PARM_DESC(dma16, "16-bit DMA # for SB16 driver.");
+module_param_array(mic_agc, int, NULL, 0444);
+MODULE_PARM_DESC(mic_agc, "Mic Auto-Gain-Control switch.");
+#ifdef CONFIG_SND_SB16_CSP
+module_param_array(csp, int, NULL, 0444);
+MODULE_PARM_DESC(csp, "ASP/CSP chip support.");
+#endif
+#ifdef SNDRV_SBAWE_EMU8000
+module_param_array(seq_ports, int, NULL, 0444);
+MODULE_PARM_DESC(seq_ports, "Number of sequencer ports for WaveTable synth.");
+#endif
+
+struct snd_card_sb16 {
+	struct resource *fm_res;	/* used to block FM i/o region for legacy cards */
+#ifdef CONFIG_PNP
+	int dev_no;
+	struct pnp_dev *dev;
+#ifdef SNDRV_SBAWE_EMU8000
+	struct pnp_dev *devwt;
+#endif
+#endif
+};
+
+static snd_card_t *snd_sb16_legacy[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+#ifdef CONFIG_PNP
+
+static struct pnp_card_device_id snd_sb16_pnpids[] = {
+#ifndef SNDRV_SBAWE
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL0024", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL0025", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL0026", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL0027", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL0028", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL0029", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL002a", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	/* Note: This card has also a CTL0051:StereoEnhance device!!! */
+	{ .id = "CTL002b", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL002c", .devs = { { "CTL0031" } } },
+	/* Sound Blaster Vibra16S */
+	{ .id = "CTL0051", .devs = { { "CTL0001" } } },
+	/* Sound Blaster Vibra16C */
+	{ .id = "CTL0070", .devs = { { "CTL0001" } } },
+	/* Sound Blaster Vibra16CL - added by ctm@ardi.com */
+	{ .id = "CTL0080", .devs = { { "CTL0041" } } },
+	/* Sound Blaster 16 'value' PnP. It says model ct4130 on the pcb, */
+	/* but ct4131 on a sticker on the board.. */
+	{ .id = "CTL0086", .devs = { { "CTL0041" } } },
+	/* Sound Blaster Vibra16X */
+	{ .id = "CTL00f0", .devs = { { "CTL0043" } } },
+#else  /* SNDRV_SBAWE defined */
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0035", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0039", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0042", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0043", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	/* Note: This card has also a CTL0051:StereoEnhance device!!! */
+	{ .id = "CTL0044", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	/* Note: This card has also a CTL0051:StereoEnhance device!!! */
+	{ .id = "CTL0045", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0046", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0047", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0048", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0054", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL009a", .devs = { { "CTL0041" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL009c", .devs = { { "CTL0041" }, { "CTL0021" } } },
+	/* Sound Blaster 32 PnP */
+	{ .id = "CTL009f", .devs = { { "CTL0041" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL009d", .devs = { { "CTL0042" }, { "CTL0022" } } },
+	/* Sound Blaster AWE 64 PnP Gold */
+	{ .id = "CTL009e", .devs = { { "CTL0044" }, { "CTL0023" } } },
+	/* Sound Blaster AWE 64 PnP Gold */
+	{ .id = "CTL00b2", .devs = { { "CTL0044" }, { "CTL0023" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL00c1", .devs = { { "CTL0042" }, { "CTL0022" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL00c3", .devs = { { "CTL0045" }, { "CTL0022" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL00c5", .devs = { { "CTL0045" }, { "CTL0022" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL00c7", .devs = { { "CTL0045" }, { "CTL0022" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL00e4", .devs = { { "CTL0045" }, { "CTL0022" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL00e9", .devs = { { "CTL0045" }, { "CTL0022" } } },
+	/* Sound Blaster 16 PnP (AWE) */
+	{ .id = "CTL00ed", .devs = { { "CTL0041" }, { "CTL0070" } } },
+	/* Generic entries */
+	{ .id = "CTLXXXX" , .devs = { { "CTL0031" }, { "CTL0021" } } },
+	{ .id = "CTLXXXX" , .devs = { { "CTL0041" }, { "CTL0021" } } },
+	{ .id = "CTLXXXX" , .devs = { { "CTL0042" }, { "CTL0022" } } },
+	{ .id = "CTLXXXX" , .devs = { { "CTL0044" }, { "CTL0023" } } },
+	{ .id = "CTLXXXX" , .devs = { { "CTL0045" }, { "CTL0022" } } },
+#endif /* SNDRV_SBAWE */
+	/* Sound Blaster 16 PnP (Virtual PC 2004)*/
+	{ .id = "tBA03b0", .devs = { { "PNPb003" } } },
+	{ .id = "", }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_sb16_pnpids);
+
+#endif /* CONFIG_PNP */
+
+#ifdef SNDRV_SBAWE_EMU8000
+#define DRIVER_NAME	"snd-card-sbawe"
+#else
+#define DRIVER_NAME	"snd-card-sb16"
+#endif
+
+#ifdef CONFIG_PNP
+
+static int __devinit snd_card_sb16_pnp(int dev, struct snd_card_sb16 *acard,
+				       struct pnp_card_link *card,
+				       const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	struct pnp_resource_table * cfg = kmalloc(sizeof(struct pnp_resource_table), GFP_KERNEL);
+	int err;
+
+	if (!cfg) 
+		return -ENOMEM; 
+	acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->dev == NULL) { 
+		kfree(cfg); 
+		return -ENODEV; 
+	} 
+#ifdef SNDRV_SBAWE_EMU8000
+	acard->devwt = pnp_request_card_device(card, id->devs[1].id, acard->dev);
+#endif
+	/* Audio initialization */
+	pdev = acard->dev;
+
+	pnp_init_resource_table(cfg); 
+	 
+	/* override resources */ 
+
+	if (port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[0], port[dev], 16);
+	if (mpu_port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[1], mpu_port[dev], 2);
+	if (fm_port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[2], fm_port[dev], 4);
+	if (dma8[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[0], dma8[dev], 1);
+	if (dma16[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[1], dma16[dev], 1);
+	if (irq[dev] != SNDRV_AUTO_IRQ)
+		pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1);
+	if (pnp_manual_config_dev(pdev, cfg, 0) < 0) 
+		snd_printk(KERN_ERR PFX "AUDIO the requested resources are invalid, using auto config\n"); 
+	err = pnp_activate_dev(pdev); 
+	if (err < 0) { 
+		snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n"); 
+		kfree(cfg);
+		return err; 
+	} 
+	port[dev] = pnp_port_start(pdev, 0);
+	mpu_port[dev] = pnp_port_start(pdev, 1);
+	fm_port[dev] = pnp_port_start(pdev, 2);
+	dma8[dev] = pnp_dma(pdev, 0);
+	dma16[dev] = pnp_dma(pdev, 1);
+	irq[dev] = pnp_irq(pdev, 0);
+	snd_printdd("pnp SB16: port=0x%lx, mpu port=0x%lx, fm port=0x%lx\n",
+			port[dev], mpu_port[dev], fm_port[dev]);
+	snd_printdd("pnp SB16: dma1=%i, dma2=%i, irq=%i\n",
+			dma8[dev], dma16[dev], irq[dev]);
+#ifdef SNDRV_SBAWE_EMU8000
+	/* WaveTable initialization */
+	pdev = acard->devwt;
+	if (pdev != NULL) {
+		pnp_init_resource_table(cfg); 
+	 
+		/* override resources */ 
+
+		if (awe_port[dev] != SNDRV_AUTO_PORT) {
+			pnp_resource_change(&cfg->port_resource[0], awe_port[dev], 4);
+			pnp_resource_change(&cfg->port_resource[1], awe_port[dev] + 0x400, 4);
+			pnp_resource_change(&cfg->port_resource[2], awe_port[dev] + 0x800, 4);
+		}
+		if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0) 
+			snd_printk(KERN_ERR PFX "WaveTable the requested resources are invalid, using auto config\n"); 
+		err = pnp_activate_dev(pdev); 
+		if (err < 0) { 
+			goto __wt_error; 
+		} 
+		awe_port[dev] = pnp_port_start(pdev, 0);
+		snd_printdd("pnp SB16: wavetable port=0x%lx\n", pnp_port_start(pdev, 0));
+	} else {
+__wt_error:
+		if (pdev) {
+			pnp_release_card_device(pdev);
+			snd_printk(KERN_ERR PFX "WaveTable pnp configure failure\n");
+		}
+		acard->devwt = NULL;
+		awe_port[dev] = -1;
+	}
+#endif
+	kfree(cfg);
+	return 0;
+}
+
+#endif /* CONFIG_PNP */
+
+static void snd_sb16_free(snd_card_t *card)
+{
+	struct snd_card_sb16 *acard = (struct snd_card_sb16 *)card->private_data;
+        
+	if (acard == NULL)
+		return;
+	if (acard->fm_res) {
+		release_resource(acard->fm_res);
+		kfree_nocheck(acard->fm_res);
+	}
+}
+
+static int __init snd_sb16_probe(int dev,
+				 struct pnp_card_link *pcard,
+				 const struct pnp_card_device_id *pid)
+{
+	static int possible_irqs[] = {5, 9, 10, 7, -1};
+	static int possible_dmas8[] = {1, 3, 0, -1};
+	static int possible_dmas16[] = {5, 6, 7, -1};
+	int xirq, xdma8, xdma16;
+	sb_t *chip;
+	snd_card_t *card;
+	struct snd_card_sb16 *acard;
+	opl3_t *opl3;
+	snd_hwdep_t *synth = NULL;
+#ifdef CONFIG_SND_SB16_CSP
+	snd_hwdep_t *xcsp = NULL;
+#endif
+	unsigned long flags;
+	int err;
+
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE,
+			    sizeof(struct snd_card_sb16));
+	if (card == NULL)
+		return -ENOMEM;
+	acard = (struct snd_card_sb16 *) card->private_data;
+	card->private_free = snd_sb16_free;
+#ifdef CONFIG_PNP
+	if (isapnp[dev]) {
+		if ((err = snd_card_sb16_pnp(dev, acard, pcard, pid))) {
+			snd_card_free(card);
+			return err;
+		}
+		snd_card_set_dev(card, &pcard->card->dev);
+	}
+#endif
+
+	xirq = irq[dev];
+	xdma8 = dma8[dev];
+	xdma16 = dma16[dev];
+#ifdef CONFIG_PNP
+	if (!isapnp[dev]) {
+#endif
+	if (xirq == SNDRV_AUTO_IRQ) {
+		if ((xirq = snd_legacy_find_free_irq(possible_irqs)) < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR PFX "unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (xdma8 == SNDRV_AUTO_DMA) {
+		if ((xdma8 = snd_legacy_find_free_dma(possible_dmas8)) < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR PFX "unable to find a free 8-bit DMA\n");
+			return -EBUSY;
+		}
+	}
+	if (xdma16 == SNDRV_AUTO_DMA) {
+		if ((xdma16 = snd_legacy_find_free_dma(possible_dmas16)) < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR PFX "unable to find a free 16-bit DMA\n");
+			return -EBUSY;
+		}
+	}
+	/* non-PnP FM port address is hardwired with base port address */
+	fm_port[dev] = port[dev];
+	/* block the 0x388 port to avoid PnP conflicts */
+	acard->fm_res = request_region(0x388, 4, "SoundBlaster FM");
+#ifdef SNDRV_SBAWE_EMU8000
+	/* non-PnP AWE port address is hardwired with base port address */
+	awe_port[dev] = port[dev] + 0x400;
+#endif
+#ifdef CONFIG_PNP
+	}
+#endif
+
+	if ((err = snd_sbdsp_create(card,
+				    port[dev],
+				    xirq,
+				    snd_sb16dsp_interrupt,
+				    xdma8,
+				    xdma16,
+				    SB_HW_AUTO,
+				    &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (chip->hardware != SB_HW_16) {
+		snd_card_free(card);
+		snd_printdd("SB 16 chip was not detected at 0x%lx\n", port[dev]);
+		return -ENODEV;
+	}
+	chip->mpu_port = mpu_port[dev];
+#ifdef CONFIG_PNP
+	if (!isapnp[dev] && (err = snd_sb16dsp_configure(chip)) < 0) {
+#else
+	if ((err = snd_sb16dsp_configure(chip)) < 0) {
+#endif
+		snd_card_free(card);
+		return -ENXIO;
+	}
+	if ((err = snd_sb16dsp_pcm(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return -ENXIO;
+	}
+
+	strcpy(card->driver,
+#ifdef SNDRV_SBAWE_EMU8000
+			awe_port[dev] > 0 ? "SB AWE" :
+#endif
+			"SB16");
+	strcpy(card->shortname, chip->name);
+	sprintf(card->longname, "%s at 0x%lx, irq %i, dma ",
+		chip->name,
+		chip->port,
+		xirq);
+	if (xdma8 >= 0)
+		sprintf(card->longname + strlen(card->longname), "%d", xdma8);
+	if (xdma16 >= 0)
+		sprintf(card->longname + strlen(card->longname), "%s%d",
+			xdma8 >= 0 ? "&" : "", xdma16);
+
+	if (chip->mpu_port > 0 && chip->mpu_port != SNDRV_AUTO_PORT) {
+		if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_SB,
+					       chip->mpu_port, 0,
+					       xirq, 0, &chip->rmidi)) < 0) {
+			snd_card_free(card);
+			return -ENXIO;
+		}
+		chip->rmidi_callback = snd_mpu401_uart_interrupt;
+	}
+
+#ifdef SNDRV_SBAWE_EMU8000
+	if (awe_port[dev] == SNDRV_AUTO_PORT)
+		awe_port[dev] = 0; /* disable */
+#endif
+
+	if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+		if (snd_opl3_create(card, fm_port[dev], fm_port[dev] + 2,
+				    OPL3_HW_OPL3,
+				    acard->fm_res != NULL || fm_port[dev] == port[dev],
+				    &opl3) < 0) {
+			snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx\n",
+				   fm_port[dev], fm_port[dev] + 2);
+		} else {
+#ifdef SNDRV_SBAWE_EMU8000
+			int seqdev = awe_port[dev] > 0 ? 2 : 1;
+#else
+			int seqdev = 1;
+#endif
+			if ((err = snd_opl3_hwdep_new(opl3, 0, seqdev, &synth)) < 0) {
+				snd_card_free(card);
+				return -ENXIO;
+			}
+		}
+	}
+
+	if ((err = snd_sbmixer_new(chip)) < 0) {
+		snd_card_free(card);
+		return -ENXIO;
+	}
+
+#ifdef CONFIG_SND_SB16_CSP
+	/* CSP chip on SB16ASP/AWE32 */
+	if ((chip->hardware == SB_HW_16) && csp[dev]) {
+		snd_sb_csp_new(chip, synth != NULL ? 1 : 0, &xcsp);
+		if (xcsp) {
+			chip->csp = xcsp->private_data;
+			chip->hardware = SB_HW_16CSP;
+		} else {
+			snd_printk(KERN_INFO PFX "warning - CSP chip not detected on soundcard #%i\n", dev + 1);
+		}
+	}
+#endif
+#ifdef SNDRV_SBAWE_EMU8000
+	if (awe_port[dev] > 0) {
+		if (snd_emu8000_new(card, 1, awe_port[dev],
+				    seq_ports[dev], NULL) < 0) {
+			snd_printk(KERN_ERR PFX "fatal error - EMU-8000 synthesizer not detected at 0x%lx\n", awe_port[dev]);
+			snd_card_free(card);
+			return -ENXIO;
+		}
+	}
+#endif
+
+	/* setup Mic AGC */
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+	snd_sbmixer_write(chip, SB_DSP4_MIC_AGC,
+		(snd_sbmixer_read(chip, SB_DSP4_MIC_AGC) & 0x01) |
+		(mic_agc[dev] ? 0x00 : 0x01));
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (pcard)
+		pnp_set_card_drvdata(pcard, card);
+	else
+		snd_sb16_legacy[dev] = card;
+	return 0;
+}
+
+static int __init snd_sb16_probe_legacy_port(unsigned long xport)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || port[dev] != SNDRV_AUTO_PORT)
+			continue;
+#ifdef CONFIG_PNP
+		if (isapnp[dev])
+			continue;
+#endif
+		port[dev] = xport;
+		res = snd_sb16_probe(dev, NULL, NULL);
+		if (res < 0)
+			port[dev] = SNDRV_AUTO_PORT;
+		return res;
+	}
+	return -ENODEV;
+}
+
+#ifdef CONFIG_PNP
+
+static int __devinit snd_sb16_pnp_detect(struct pnp_card_link *card,
+					 const struct pnp_card_device_id *id)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || !isapnp[dev])
+			continue;
+		res = snd_sb16_probe(dev, card, id);
+		if (res < 0)
+			return res;
+		dev++;
+		return 0;
+	}
+
+	return -ENODEV;
+}
+
+static void __devexit snd_sb16_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_t *card = (snd_card_t *) pnp_get_card_drvdata(pcard);
+
+	snd_card_disconnect(card);
+	snd_card_free_in_thread(card);
+}
+
+static struct pnp_card_driver sb16_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DISABLE,
+	.name = "sb16",
+	.id_table = snd_sb16_pnpids,
+	.probe = snd_sb16_pnp_detect,
+	.remove = __devexit_p(snd_sb16_pnp_remove),
+};
+
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_sb16_init(void)
+{
+	int dev, cards = 0, i;
+	static unsigned long possible_ports[] = {0x220, 0x240, 0x260, 0x280, -1};
+
+	/* legacy non-auto cards at first */
+	for (dev = 0; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || port[dev] == SNDRV_AUTO_PORT)
+			continue;
+#ifdef CONFIG_PNP
+		if (isapnp[dev])
+			continue;
+#endif
+		if (!snd_sb16_probe(dev, NULL, NULL)) {
+			cards++;
+			continue;
+		}
+#ifdef MODULE
+		snd_printk(KERN_ERR "Sound Blaster 16+ soundcard #%i not found at 0x%lx or device busy\n", dev, port[dev]);
+#endif
+	}
+	/* legacy auto configured cards */
+	i = snd_legacy_auto_probe(possible_ports, snd_sb16_probe_legacy_port);
+	if (i > 0)
+		cards += i;
+
+#ifdef CONFIG_PNP
+	/* PnP cards at last */
+	i = pnp_register_card_driver(&sb16_pnpc_driver);
+	if (i >0)
+		cards += i;
+#endif
+
+	if (!cards) {
+#ifdef CONFIG_PNP
+		pnp_unregister_card_driver(&sb16_pnpc_driver);
+#endif
+#ifdef MODULE
+		snd_printk(KERN_ERR "Sound Blaster 16 soundcard not found or device busy\n");
+#ifdef SNDRV_SBAWE_EMU8000
+		snd_printk(KERN_ERR "In case, if you have non-AWE card, try snd-sb16 module\n");
+#else
+		snd_printk(KERN_ERR "In case, if you have AWE card, try snd-sbawe module\n");
+#endif
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_sb16_exit(void)
+{
+	int dev;
+
+#ifdef CONFIG_PNP
+	/* PnP cards first */
+	pnp_unregister_card_driver(&sb16_pnpc_driver);
+#endif
+	for (dev = 0; dev < SNDRV_CARDS; dev++)
+		snd_card_free(snd_sb16_legacy[dev]);
+}
+
+module_init(alsa_card_sb16_init)
+module_exit(alsa_card_sb16_exit)
diff --git a/sound/isa/sb/sb16_csp.c b/sound/isa/sb/sb16_csp.c
new file mode 100644
index 0000000..b62920e
--- /dev/null
+++ b/sound/isa/sb/sb16_csp.c
@@ -0,0 +1,1175 @@
+/*
+ *  Copyright (c) 1999 by Uros Bizjak <uros@kss-loka.si>
+ *                        Takashi Iwai <tiwai@suse.de>
+ *
+ *  SB16ASP/AWE32 CSP control
+ *
+ *  CSP microcode loader:
+ *   alsa-tools/sb16_csp/ 
+ *
+ *   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/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/sb16_csp.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Uros Bizjak <uros@kss-loka.si>");
+MODULE_DESCRIPTION("ALSA driver for SB16 Creative Signal Processor");
+MODULE_LICENSE("GPL");
+
+#ifdef SNDRV_LITTLE_ENDIAN
+#define CSP_HDR_VALUE(a,b,c,d)	((a) | ((b)<<8) | ((c)<<16) | ((d)<<24))
+#else
+#define CSP_HDR_VALUE(a,b,c,d)	((d) | ((c)<<8) | ((b)<<16) | ((a)<<24))
+#endif
+#define LE_SHORT(v)		le16_to_cpu(v)
+#define LE_INT(v)		le32_to_cpu(v)
+
+#define RIFF_HEADER	CSP_HDR_VALUE('R', 'I', 'F', 'F')
+#define CSP__HEADER	CSP_HDR_VALUE('C', 'S', 'P', ' ')
+#define LIST_HEADER	CSP_HDR_VALUE('L', 'I', 'S', 'T')
+#define FUNC_HEADER	CSP_HDR_VALUE('f', 'u', 'n', 'c')
+#define CODE_HEADER	CSP_HDR_VALUE('c', 'o', 'd', 'e')
+#define INIT_HEADER	CSP_HDR_VALUE('i', 'n', 'i', 't')
+#define MAIN_HEADER	CSP_HDR_VALUE('m', 'a', 'i', 'n')
+
+/*
+ * RIFF data format
+ */
+typedef struct riff_header {
+	__u32 name;
+	__u32 len;
+} riff_header_t;
+
+typedef struct desc_header {
+	riff_header_t info;
+	__u16 func_nr;
+	__u16 VOC_type;
+	__u16 flags_play_rec;
+	__u16 flags_16bit_8bit;
+	__u16 flags_stereo_mono;
+	__u16 flags_rates;
+} desc_header_t;
+
+/*
+ * prototypes
+ */
+static void snd_sb_csp_free(snd_hwdep_t *hw);
+static int snd_sb_csp_open(snd_hwdep_t * hw, struct file *file);
+static int snd_sb_csp_ioctl(snd_hwdep_t * hw, struct file *file, unsigned int cmd, unsigned long arg);
+static int snd_sb_csp_release(snd_hwdep_t * hw, struct file *file);
+
+static int csp_detect(sb_t *chip, int *version);
+static int set_codec_parameter(sb_t *chip, unsigned char par, unsigned char val);
+static int set_register(sb_t *chip, unsigned char reg, unsigned char val);
+static int read_register(sb_t *chip, unsigned char reg);
+static int set_mode_register(sb_t *chip, unsigned char mode);
+static int get_version(sb_t *chip);
+
+static int snd_sb_csp_riff_load(snd_sb_csp_t * p, snd_sb_csp_microcode_t __user * code);
+static int snd_sb_csp_unload(snd_sb_csp_t * p);
+static int snd_sb_csp_load_user(snd_sb_csp_t * p, const unsigned char __user *buf, int size, int load_flags);
+static int snd_sb_csp_autoload(snd_sb_csp_t * p, int pcm_sfmt, int play_rec_mode);
+static int snd_sb_csp_check_version(snd_sb_csp_t * p);
+
+static int snd_sb_csp_use(snd_sb_csp_t * p);
+static int snd_sb_csp_unuse(snd_sb_csp_t * p);
+static int snd_sb_csp_start(snd_sb_csp_t * p, int sample_width, int channels);
+static int snd_sb_csp_stop(snd_sb_csp_t * p);
+static int snd_sb_csp_pause(snd_sb_csp_t * p);
+static int snd_sb_csp_restart(snd_sb_csp_t * p);
+
+static int snd_sb_qsound_build(snd_sb_csp_t * p);
+static void snd_sb_qsound_destroy(snd_sb_csp_t * p);
+static int snd_sb_csp_qsound_transfer(snd_sb_csp_t * p);
+
+static int init_proc_entry(snd_sb_csp_t * p, int device);
+static void info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer);
+
+/*
+ * Detect CSP chip and create a new instance
+ */
+int snd_sb_csp_new(sb_t *chip, int device, snd_hwdep_t ** rhwdep)
+{
+	snd_sb_csp_t *p;
+	int version, err;
+	snd_hwdep_t *hw;
+
+	if (rhwdep)
+		*rhwdep = NULL;
+
+	if (csp_detect(chip, &version))
+		return -ENODEV;
+
+	if ((err = snd_hwdep_new(chip->card, "SB16-CSP", device, &hw)) < 0)
+		return err;
+
+	if ((p = kcalloc(1, sizeof(*p), GFP_KERNEL)) == NULL) {
+		snd_device_free(chip->card, hw);
+		return -ENOMEM;
+	}
+	p->chip = chip;
+	p->version = version;
+
+	/* CSP operators */
+	p->ops.csp_use = snd_sb_csp_use;
+	p->ops.csp_unuse = snd_sb_csp_unuse;
+	p->ops.csp_autoload = snd_sb_csp_autoload;
+	p->ops.csp_start = snd_sb_csp_start;
+	p->ops.csp_stop = snd_sb_csp_stop;
+	p->ops.csp_qsound_transfer = snd_sb_csp_qsound_transfer;
+
+	init_MUTEX(&p->access_mutex);
+	sprintf(hw->name, "CSP v%d.%d", (version >> 4), (version & 0x0f));
+	hw->iface = SNDRV_HWDEP_IFACE_SB16CSP;
+	hw->private_data = p;
+	hw->private_free = snd_sb_csp_free;
+
+	/* operators - only write/ioctl */
+	hw->ops.open = snd_sb_csp_open;
+	hw->ops.ioctl = snd_sb_csp_ioctl;
+	hw->ops.release = snd_sb_csp_release;
+
+	/* create a proc entry */
+	init_proc_entry(p, device);
+	if (rhwdep)
+		*rhwdep = hw;
+	return 0;
+}
+
+/*
+ * free_private for hwdep instance
+ */
+static void snd_sb_csp_free(snd_hwdep_t *hwdep)
+{
+	snd_sb_csp_t *p = hwdep->private_data;
+	if (p) {
+		if (p->running & SNDRV_SB_CSP_ST_RUNNING)
+			snd_sb_csp_stop(p);
+		kfree(p);
+	}
+}
+
+/* ------------------------------ */
+
+/*
+ * open the device exclusively
+ */
+static int snd_sb_csp_open(snd_hwdep_t * hw, struct file *file)
+{
+	snd_sb_csp_t *p = hw->private_data;
+	return (snd_sb_csp_use(p));
+}
+
+/*
+ * ioctl for hwdep device:
+ */
+static int snd_sb_csp_ioctl(snd_hwdep_t * hw, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	snd_sb_csp_t *p = hw->private_data;
+	snd_sb_csp_info_t info;
+	snd_sb_csp_start_t start_info;
+	int err;
+
+	snd_assert(p != NULL, return -EINVAL);
+
+	if (snd_sb_csp_check_version(p))
+		return -ENODEV;
+
+	switch (cmd) {
+		/* get information */
+	case SNDRV_SB_CSP_IOCTL_INFO:
+		*info.codec_name = *p->codec_name;
+		info.func_nr = p->func_nr;
+		info.acc_format = p->acc_format;
+		info.acc_channels = p->acc_channels;
+		info.acc_width = p->acc_width;
+		info.acc_rates = p->acc_rates;
+		info.csp_mode = p->mode;
+		info.run_channels = p->run_channels;
+		info.run_width = p->run_width;
+		info.version = p->version;
+		info.state = p->running;
+		if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+			err = -EFAULT;
+		else
+			err = 0;
+		break;
+
+		/* load CSP microcode */
+	case SNDRV_SB_CSP_IOCTL_LOAD_CODE:
+		err = (p->running & SNDRV_SB_CSP_ST_RUNNING ?
+		       -EBUSY : snd_sb_csp_riff_load(p, (snd_sb_csp_microcode_t __user *) arg));
+		break;
+	case SNDRV_SB_CSP_IOCTL_UNLOAD_CODE:
+		err = (p->running & SNDRV_SB_CSP_ST_RUNNING ?
+		       -EBUSY : snd_sb_csp_unload(p));
+		break;
+
+		/* change CSP running state */
+	case SNDRV_SB_CSP_IOCTL_START:
+		if (copy_from_user(&start_info, (void __user *) arg, sizeof(start_info)))
+			err = -EFAULT;
+		else
+			err = snd_sb_csp_start(p, start_info.sample_width, start_info.channels);
+		break;
+	case SNDRV_SB_CSP_IOCTL_STOP:
+		err = snd_sb_csp_stop(p);
+		break;
+	case SNDRV_SB_CSP_IOCTL_PAUSE:
+		err = snd_sb_csp_pause(p);
+		break;
+	case SNDRV_SB_CSP_IOCTL_RESTART:
+		err = snd_sb_csp_restart(p);
+		break;
+	default:
+		err = -ENOTTY;
+		break;
+	}
+
+	return err;
+}
+
+/*
+ * close the device
+ */
+static int snd_sb_csp_release(snd_hwdep_t * hw, struct file *file)
+{
+	snd_sb_csp_t *p = hw->private_data;
+	return (snd_sb_csp_unuse(p));
+}
+
+/* ------------------------------ */
+
+/*
+ * acquire device
+ */
+static int snd_sb_csp_use(snd_sb_csp_t * p)
+{
+	down(&p->access_mutex);
+	if (p->used) {
+		up(&p->access_mutex);
+		return -EAGAIN;
+	}
+	p->used++;
+	up(&p->access_mutex);
+
+	return 0;
+
+}
+
+/*
+ * release device
+ */
+static int snd_sb_csp_unuse(snd_sb_csp_t * p)
+{
+	down(&p->access_mutex);
+	p->used--;
+	up(&p->access_mutex);
+
+	return 0;
+}
+
+/*
+ * load microcode via ioctl: 
+ * code is user-space pointer
+ */
+static int snd_sb_csp_riff_load(snd_sb_csp_t * p, snd_sb_csp_microcode_t __user * mcode)
+{
+	snd_sb_csp_mc_header_t info;
+
+	unsigned char __user *data_ptr;
+	unsigned char __user *data_end;
+	unsigned short func_nr = 0;
+
+	riff_header_t file_h, item_h, code_h;
+	__u32 item_type;
+	desc_header_t funcdesc_h;
+
+	unsigned long flags;
+	int err;
+
+	if (copy_from_user(&info, mcode, sizeof(info)))
+		return -EFAULT;
+	data_ptr = mcode->data;
+
+	if (copy_from_user(&file_h, data_ptr, sizeof(file_h)))
+		return -EFAULT;
+	if ((file_h.name != RIFF_HEADER) ||
+	    (LE_INT(file_h.len) >= SNDRV_SB_CSP_MAX_MICROCODE_FILE_SIZE - sizeof(file_h))) {
+		snd_printd("%s: Invalid RIFF header\n", __FUNCTION__);
+		return -EINVAL;
+	}
+	data_ptr += sizeof(file_h);
+	data_end = data_ptr + LE_INT(file_h.len);
+
+	if (copy_from_user(&item_type, data_ptr, sizeof(item_type)))
+		return -EFAULT;
+	if (item_type != CSP__HEADER) {
+		snd_printd("%s: Invalid RIFF file type\n", __FUNCTION__);
+		return -EINVAL;
+	}
+	data_ptr += sizeof (item_type);
+
+	for (; data_ptr < data_end; data_ptr += LE_INT(item_h.len)) {
+		if (copy_from_user(&item_h, data_ptr, sizeof(item_h)))
+			return -EFAULT;
+		data_ptr += sizeof(item_h);
+		if (item_h.name != LIST_HEADER)
+			continue;
+
+		if (copy_from_user(&item_type, data_ptr, sizeof(item_type)))
+			 return -EFAULT;
+		switch (item_type) {
+		case FUNC_HEADER:
+			if (copy_from_user(&funcdesc_h, data_ptr + sizeof(item_type), sizeof(funcdesc_h)))
+				return -EFAULT;
+			func_nr = LE_SHORT(funcdesc_h.func_nr);
+			break;
+		case CODE_HEADER:
+			if (func_nr != info.func_req)
+				break;	/* not required function, try next */
+			data_ptr += sizeof(item_type);
+
+			/* destroy QSound mixer element */
+			if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) {
+				snd_sb_qsound_destroy(p);
+			}
+			/* Clear all flags */
+			p->running = 0;
+			p->mode = 0;
+
+			/* load microcode blocks */
+			for (;;) {
+				if (data_ptr >= data_end)
+					return -EINVAL;
+				if (copy_from_user(&code_h, data_ptr, sizeof(code_h)))
+					return -EFAULT;
+
+				/* init microcode blocks */
+				if (code_h.name != INIT_HEADER)
+					break;
+				data_ptr += sizeof(code_h);
+				err = snd_sb_csp_load_user(p, data_ptr, LE_INT(code_h.len),
+						      SNDRV_SB_CSP_LOAD_INITBLOCK);
+				if (err)
+					return err;
+				data_ptr += LE_INT(code_h.len);
+			}
+			/* main microcode block */
+			if (copy_from_user(&code_h, data_ptr, sizeof(code_h)))
+				return -EFAULT;
+
+			if (code_h.name != MAIN_HEADER) {
+				snd_printd("%s: Missing 'main' microcode\n", __FUNCTION__);
+				return -EINVAL;
+			}
+			data_ptr += sizeof(code_h);
+			err = snd_sb_csp_load_user(p, data_ptr,
+						   LE_INT(code_h.len), 0);
+			if (err)
+				return err;
+
+			/* fill in codec header */
+			strlcpy(p->codec_name, info.codec_name, sizeof(p->codec_name));
+			p->func_nr = func_nr;
+			p->mode = LE_SHORT(funcdesc_h.flags_play_rec);
+			switch (LE_SHORT(funcdesc_h.VOC_type)) {
+			case 0x0001:	/* QSound decoder */
+				if (LE_SHORT(funcdesc_h.flags_play_rec) == SNDRV_SB_CSP_MODE_DSP_WRITE) {
+					if (snd_sb_qsound_build(p) == 0)
+						/* set QSound flag and clear all other mode flags */
+						p->mode = SNDRV_SB_CSP_MODE_QSOUND;
+				}
+				p->acc_format = 0;
+				break;
+			case 0x0006:	/* A Law codec */
+				p->acc_format = SNDRV_PCM_FMTBIT_A_LAW;
+				break;
+			case 0x0007:	/* Mu Law codec */
+				p->acc_format = SNDRV_PCM_FMTBIT_MU_LAW;
+				break;
+			case 0x0011:	/* what Creative thinks is IMA ADPCM codec */
+			case 0x0200:	/* Creative ADPCM codec */
+				p->acc_format = SNDRV_PCM_FMTBIT_IMA_ADPCM;
+				break;
+			case    201:	/* Text 2 Speech decoder */
+				/* TODO: Text2Speech handling routines */
+				p->acc_format = 0;
+				break;
+			case 0x0202:	/* Fast Speech 8 codec */
+			case 0x0203:	/* Fast Speech 10 codec */
+				p->acc_format = SNDRV_PCM_FMTBIT_SPECIAL;
+				break;
+			default:	/* other codecs are unsupported */
+				p->acc_format = p->acc_width = p->acc_rates = 0;
+				p->mode = 0;
+				snd_printd("%s: Unsupported CSP codec type: 0x%04x\n",
+					   __FUNCTION__,
+					   LE_SHORT(funcdesc_h.VOC_type));
+				return -EINVAL;
+			}
+			p->acc_channels = LE_SHORT(funcdesc_h.flags_stereo_mono);
+			p->acc_width = LE_SHORT(funcdesc_h.flags_16bit_8bit);
+			p->acc_rates = LE_SHORT(funcdesc_h.flags_rates);
+
+			/* Decouple CSP from IRQ and DMAREQ lines */
+			spin_lock_irqsave(&p->chip->reg_lock, flags);
+			set_mode_register(p->chip, 0xfc);
+			set_mode_register(p->chip, 0x00);
+			spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+
+			/* finished loading successfully */
+			p->running = SNDRV_SB_CSP_ST_LOADED;	/* set LOADED flag */
+			return 0;
+		}
+	}
+	snd_printd("%s: Function #%d not found\n", __FUNCTION__, info.func_req);
+	return -EINVAL;
+}
+
+/*
+ * unload CSP microcode
+ */
+static int snd_sb_csp_unload(snd_sb_csp_t * p)
+{
+	if (p->running & SNDRV_SB_CSP_ST_RUNNING)
+		return -EBUSY;
+	if (!(p->running & SNDRV_SB_CSP_ST_LOADED))
+		return -ENXIO;
+
+	/* clear supported formats */
+	p->acc_format = 0;
+	p->acc_channels = p->acc_width = p->acc_rates = 0;
+	/* destroy QSound mixer element */
+	if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) {
+		snd_sb_qsound_destroy(p);
+	}
+	/* clear all flags */
+	p->running = 0;
+	p->mode = 0;
+	return 0;
+}
+
+/*
+ * send command sequence to DSP
+ */
+static inline int command_seq(sb_t *chip, const unsigned char *seq, int size)
+{
+	int i;
+	for (i = 0; i < size; i++) {
+		if (!snd_sbdsp_command(chip, seq[i]))
+			return -EIO;
+	}
+	return 0;
+}
+
+/*
+ * set CSP codec parameter
+ */
+static int set_codec_parameter(sb_t *chip, unsigned char par, unsigned char val)
+{
+	unsigned char dsp_cmd[3];
+
+	dsp_cmd[0] = 0x05;	/* CSP set codec parameter */
+	dsp_cmd[1] = val;	/* Parameter value */
+	dsp_cmd[2] = par;	/* Parameter */
+	command_seq(chip, dsp_cmd, 3);
+	snd_sbdsp_command(chip, 0x03);	/* DSP read? */
+	if (snd_sbdsp_get_byte(chip) != par)
+		return -EIO;
+	return 0;
+}
+
+/*
+ * set CSP register
+ */
+static int set_register(sb_t *chip, unsigned char reg, unsigned char val)
+{
+	unsigned char dsp_cmd[3];
+
+	dsp_cmd[0] = 0x0e;	/* CSP set register */
+	dsp_cmd[1] = reg;	/* CSP Register */
+	dsp_cmd[2] = val;	/* value */
+	return command_seq(chip, dsp_cmd, 3);
+}
+
+/*
+ * read CSP register
+ * return < 0 -> error
+ */
+static int read_register(sb_t *chip, unsigned char reg)
+{
+	unsigned char dsp_cmd[2];
+
+	dsp_cmd[0] = 0x0f;	/* CSP read register */
+	dsp_cmd[1] = reg;	/* CSP Register */
+	command_seq(chip, dsp_cmd, 2);
+	return snd_sbdsp_get_byte(chip);	/* Read DSP value */
+}
+
+/*
+ * set CSP mode register
+ */
+static int set_mode_register(sb_t *chip, unsigned char mode)
+{
+	unsigned char dsp_cmd[2];
+
+	dsp_cmd[0] = 0x04;	/* CSP set mode register */
+	dsp_cmd[1] = mode;	/* mode */
+	return command_seq(chip, dsp_cmd, 2);
+}
+
+/*
+ * Detect CSP
+ * return 0 if CSP exists.
+ */
+static int csp_detect(sb_t *chip, int *version)
+{
+	unsigned char csp_test1, csp_test2;
+	unsigned long flags;
+	int result = -ENODEV;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+
+	set_codec_parameter(chip, 0x00, 0x00);
+	set_mode_register(chip, 0xfc);		/* 0xfc = ?? */
+
+	csp_test1 = read_register(chip, 0x83);
+	set_register(chip, 0x83, ~csp_test1);
+	csp_test2 = read_register(chip, 0x83);
+	if (csp_test2 != (csp_test1 ^ 0xff))
+		goto __fail;
+
+	set_register(chip, 0x83, csp_test1);
+	csp_test2 = read_register(chip, 0x83);
+	if (csp_test2 != csp_test1)
+		goto __fail;
+
+	set_mode_register(chip, 0x00);		/* 0x00 = ? */
+
+	*version = get_version(chip);
+	snd_sbdsp_reset(chip);	/* reset DSP after getversion! */
+	if (*version >= 0x10 && *version <= 0x1f)
+		result = 0;		/* valid version id */
+
+      __fail:
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return result;
+}
+
+/*
+ * get CSP version number
+ */
+static int get_version(sb_t *chip)
+{
+	unsigned char dsp_cmd[2];
+
+	dsp_cmd[0] = 0x08;	/* SB_DSP_!something! */
+	dsp_cmd[1] = 0x03;	/* get chip version id? */
+	command_seq(chip, dsp_cmd, 2);
+
+	return (snd_sbdsp_get_byte(chip));
+}
+
+/*
+ * check if the CSP version is valid
+ */
+static int snd_sb_csp_check_version(snd_sb_csp_t * p)
+{
+	if (p->version < 0x10 || p->version > 0x1f) {
+		snd_printd("%s: Invalid CSP version: 0x%x\n", __FUNCTION__, p->version);
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ * download microcode to CSP (microcode should have one "main" block).
+ */
+static int snd_sb_csp_load(snd_sb_csp_t * p, const unsigned char *buf, int size, int load_flags)
+{
+	int status, i;
+	int err;
+	int result = -EIO;
+	unsigned long flags;
+
+	spin_lock_irqsave(&p->chip->reg_lock, flags);
+	snd_sbdsp_command(p->chip, 0x01);	/* CSP download command */
+	if (snd_sbdsp_get_byte(p->chip)) {
+		snd_printd("%s: Download command failed\n", __FUNCTION__);
+		goto __fail;
+	}
+	/* Send CSP low byte (size - 1) */
+	snd_sbdsp_command(p->chip, (unsigned char)(size - 1));
+	/* Send high byte */
+	snd_sbdsp_command(p->chip, (unsigned char)((size - 1) >> 8));
+	/* send microcode sequence */
+	/* load from kernel space */
+	while (size--) {
+		if (!snd_sbdsp_command(p->chip, *buf++))
+			goto __fail;
+	}
+	if (snd_sbdsp_get_byte(p->chip))
+		goto __fail;
+
+	if (load_flags & SNDRV_SB_CSP_LOAD_INITBLOCK) {
+		i = 0;
+		/* some codecs (FastSpeech) take some time to initialize */
+		while (1) {
+			snd_sbdsp_command(p->chip, 0x03);
+			status = snd_sbdsp_get_byte(p->chip);
+			if (status == 0x55 || ++i >= 10)
+				break;
+			udelay (10);
+		}
+		if (status != 0x55) {
+			snd_printd("%s: Microcode initialization failed\n", __FUNCTION__);
+			goto __fail;
+		}
+	} else {
+		/*
+		 * Read mixer register SB_DSP4_DMASETUP after loading 'main' code.
+		 * Start CSP chip if no 16bit DMA channel is set - some kind
+		 * of autorun or perhaps a bugfix?
+		 */
+		spin_lock(&p->chip->mixer_lock);
+		status = snd_sbmixer_read(p->chip, SB_DSP4_DMASETUP);
+		spin_unlock(&p->chip->mixer_lock);
+		if (!(status & (SB_DMASETUP_DMA7 | SB_DMASETUP_DMA6 | SB_DMASETUP_DMA5))) {
+			err = (set_codec_parameter(p->chip, 0xaa, 0x00) ||
+			       set_codec_parameter(p->chip, 0xff, 0x00));
+			snd_sbdsp_reset(p->chip);		/* really! */
+			if (err)
+				goto __fail;
+			set_mode_register(p->chip, 0xc0);	/* c0 = STOP */
+			set_mode_register(p->chip, 0x70);	/* 70 = RUN */
+		}
+	}
+	result = 0;
+
+      __fail:
+	spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+	return result;
+}
+ 
+static int snd_sb_csp_load_user(snd_sb_csp_t * p, const unsigned char __user *buf, int size, int load_flags)
+{
+	int err = -ENOMEM;
+	unsigned char *kbuf = kmalloc(size, GFP_KERNEL);
+	if (kbuf) {
+		if (copy_from_user(kbuf, buf, size))
+			err = -EFAULT;
+		else
+			err = snd_sb_csp_load(p, kbuf, size, load_flags);
+		kfree(kbuf);
+	}
+	return err;
+}
+
+#include "sb16_csp_codecs.h"
+
+/*
+ * autoload hardware codec if necessary
+ * return 0 if CSP is loaded and ready to run (p->running != 0)
+ */
+static int snd_sb_csp_autoload(snd_sb_csp_t * p, int pcm_sfmt, int play_rec_mode)
+{
+	unsigned long flags;
+	int err = 0;
+
+	/* if CSP is running or manually loaded then exit */
+	if (p->running & (SNDRV_SB_CSP_ST_RUNNING | SNDRV_SB_CSP_ST_LOADED)) 
+		return -EBUSY;
+
+	/* autoload microcode only if requested hardware codec is not already loaded */
+	if (((1 << pcm_sfmt) & p->acc_format) && (play_rec_mode & p->mode)) {
+		p->running = SNDRV_SB_CSP_ST_AUTO;
+	} else {
+		switch (pcm_sfmt) {
+		case SNDRV_PCM_FORMAT_MU_LAW:
+			err = snd_sb_csp_load(p, &mulaw_main[0], sizeof(mulaw_main), 0);
+			p->acc_format = SNDRV_PCM_FMTBIT_MU_LAW;
+			p->mode = SNDRV_SB_CSP_MODE_DSP_READ | SNDRV_SB_CSP_MODE_DSP_WRITE;
+			break;
+		case SNDRV_PCM_FORMAT_A_LAW:
+			err = snd_sb_csp_load(p, &alaw_main[0], sizeof(alaw_main), 0);
+			p->acc_format = SNDRV_PCM_FMTBIT_A_LAW;
+			p->mode = SNDRV_SB_CSP_MODE_DSP_READ | SNDRV_SB_CSP_MODE_DSP_WRITE;
+			break;
+		case SNDRV_PCM_FORMAT_IMA_ADPCM:
+			err = snd_sb_csp_load(p, &ima_adpcm_init[0], sizeof(ima_adpcm_init),
+					      SNDRV_SB_CSP_LOAD_INITBLOCK);
+			if (err)
+				break;
+			if (play_rec_mode == SNDRV_SB_CSP_MODE_DSP_WRITE) {
+				err = snd_sb_csp_load(p, &ima_adpcm_playback[0],
+						      sizeof(ima_adpcm_playback), 0);
+				p->mode = SNDRV_SB_CSP_MODE_DSP_WRITE;
+			} else {
+				err = snd_sb_csp_load(p, &ima_adpcm_capture[0],
+						      sizeof(ima_adpcm_capture), 0);
+				p->mode = SNDRV_SB_CSP_MODE_DSP_READ;
+			}
+			p->acc_format = SNDRV_PCM_FMTBIT_IMA_ADPCM;
+			break;				  
+		default:
+			/* Decouple CSP from IRQ and DMAREQ lines */
+			if (p->running & SNDRV_SB_CSP_ST_AUTO) {
+				spin_lock_irqsave(&p->chip->reg_lock, flags);
+				set_mode_register(p->chip, 0xfc);
+				set_mode_register(p->chip, 0x00);
+				spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+				p->running = 0;			/* clear autoloaded flag */
+			}
+			return -EINVAL;
+		}
+		if (err) {
+			p->acc_format = 0;
+			p->acc_channels = p->acc_width = p->acc_rates = 0;
+
+			p->running = 0;				/* clear autoloaded flag */
+			p->mode = 0;
+			return (err);
+		} else {
+			p->running = SNDRV_SB_CSP_ST_AUTO;	/* set autoloaded flag */
+			p->acc_width = SNDRV_SB_CSP_SAMPLE_16BIT;	/* only 16 bit data */
+			p->acc_channels = SNDRV_SB_CSP_MONO | SNDRV_SB_CSP_STEREO;
+			p->acc_rates = SNDRV_SB_CSP_RATE_ALL;	/* HW codecs accept all rates */
+		}   
+
+	}
+	return (p->running & SNDRV_SB_CSP_ST_AUTO) ? 0 : -ENXIO;
+}
+
+/*
+ * start CSP
+ */
+static int snd_sb_csp_start(snd_sb_csp_t * p, int sample_width, int channels)
+{
+	unsigned char s_type;	/* sample type */
+	unsigned char mixL, mixR;
+	int result = -EIO;
+	unsigned long flags;
+
+	if (!(p->running & (SNDRV_SB_CSP_ST_LOADED | SNDRV_SB_CSP_ST_AUTO))) {
+		snd_printd("%s: Microcode not loaded\n", __FUNCTION__);
+		return -ENXIO;
+	}
+	if (p->running & SNDRV_SB_CSP_ST_RUNNING) {
+		snd_printd("%s: CSP already running\n", __FUNCTION__);
+		return -EBUSY;
+	}
+	if (!(sample_width & p->acc_width)) {
+		snd_printd("%s: Unsupported PCM sample width\n", __FUNCTION__);
+		return -EINVAL;
+	}
+	if (!(channels & p->acc_channels)) {
+		snd_printd("%s: Invalid number of channels\n", __FUNCTION__);
+		return -EINVAL;
+	}
+
+	/* Mute PCM volume */
+	spin_lock_irqsave(&p->chip->mixer_lock, flags);
+	mixL = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV);
+	mixR = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV + 1);
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL & 0x7);
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR & 0x7);
+
+	spin_lock(&p->chip->reg_lock);
+	set_mode_register(p->chip, 0xc0);	/* c0 = STOP */
+	set_mode_register(p->chip, 0x70);	/* 70 = RUN */
+
+	s_type = 0x00;
+	if (channels == SNDRV_SB_CSP_MONO)
+		s_type = 0x11;	/* 000n 000n    (n = 1 if mono) */
+	if (sample_width == SNDRV_SB_CSP_SAMPLE_8BIT)
+		s_type |= 0x22;	/* 00dX 00dX    (d = 1 if 8 bit samples) */
+
+	if (set_codec_parameter(p->chip, 0x81, s_type)) {
+		snd_printd("%s: Set sample type command failed\n", __FUNCTION__);
+		goto __fail;
+	}
+	if (set_codec_parameter(p->chip, 0x80, 0x00)) {
+		snd_printd("%s: Codec start command failed\n", __FUNCTION__);
+		goto __fail;
+	}
+	p->run_width = sample_width;
+	p->run_channels = channels;
+
+	p->running |= SNDRV_SB_CSP_ST_RUNNING;
+
+	if (p->mode & SNDRV_SB_CSP_MODE_QSOUND) {
+		set_codec_parameter(p->chip, 0xe0, 0x01);
+		/* enable QSound decoder */
+		set_codec_parameter(p->chip, 0x00, 0xff);
+		set_codec_parameter(p->chip, 0x01, 0xff);
+		p->running |= SNDRV_SB_CSP_ST_QSOUND;
+		/* set QSound startup value */
+		snd_sb_csp_qsound_transfer(p);
+	}
+	result = 0;
+
+      __fail:
+	spin_unlock(&p->chip->reg_lock);
+
+	/* restore PCM volume */
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL);
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR);
+	spin_unlock_irqrestore(&p->chip->mixer_lock, flags);
+
+	return result;
+}
+
+/*
+ * stop CSP
+ */
+static int snd_sb_csp_stop(snd_sb_csp_t * p)
+{
+	int result;
+	unsigned char mixL, mixR;
+	unsigned long flags;
+
+	if (!(p->running & SNDRV_SB_CSP_ST_RUNNING))
+		return 0;
+
+	/* Mute PCM volume */
+	spin_lock_irqsave(&p->chip->mixer_lock, flags);
+	mixL = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV);
+	mixR = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV + 1);
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL & 0x7);
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR & 0x7);
+
+	spin_lock(&p->chip->reg_lock);
+	if (p->running & SNDRV_SB_CSP_ST_QSOUND) {
+		set_codec_parameter(p->chip, 0xe0, 0x01);
+		/* disable QSound decoder */
+		set_codec_parameter(p->chip, 0x00, 0x00);
+		set_codec_parameter(p->chip, 0x01, 0x00);
+
+		p->running &= ~SNDRV_SB_CSP_ST_QSOUND;
+	}
+	result = set_mode_register(p->chip, 0xc0);	/* c0 = STOP */
+	spin_unlock(&p->chip->reg_lock);
+
+	/* restore PCM volume */
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL);
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR);
+	spin_unlock_irqrestore(&p->chip->mixer_lock, flags);
+
+	if (!(result))
+		p->running &= ~(SNDRV_SB_CSP_ST_PAUSED | SNDRV_SB_CSP_ST_RUNNING);
+	return result;
+}
+
+/*
+ * pause CSP codec and hold DMA transfer
+ */
+static int snd_sb_csp_pause(snd_sb_csp_t * p)
+{
+	int result;
+	unsigned long flags;
+
+	if (!(p->running & SNDRV_SB_CSP_ST_RUNNING))
+		return -EBUSY;
+
+	spin_lock_irqsave(&p->chip->reg_lock, flags);
+	result = set_codec_parameter(p->chip, 0x80, 0xff);
+	spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+	if (!(result))
+		p->running |= SNDRV_SB_CSP_ST_PAUSED;
+
+	return result;
+}
+
+/*
+ * restart CSP codec and resume DMA transfer
+ */
+static int snd_sb_csp_restart(snd_sb_csp_t * p)
+{
+	int result;
+	unsigned long flags;
+
+	if (!(p->running & SNDRV_SB_CSP_ST_PAUSED))
+		return -EBUSY;
+
+	spin_lock_irqsave(&p->chip->reg_lock, flags);
+	result = set_codec_parameter(p->chip, 0x80, 0x00);
+	spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+	if (!(result))
+		p->running &= ~SNDRV_SB_CSP_ST_PAUSED;
+
+	return result;
+}
+
+/* ------------------------------ */
+
+/*
+ * QSound mixer control for PCM
+ */
+
+static int snd_sb_qsound_switch_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_sb_qsound_switch_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_sb_csp_t *p = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = p->q_enabled ? 1 : 0;
+	return 0;
+}
+
+static int snd_sb_qsound_switch_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_sb_csp_t *p = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned char nval;
+	
+	nval = ucontrol->value.integer.value[0] & 0x01;
+	spin_lock_irqsave(&p->q_lock, flags);
+	change = p->q_enabled != nval;
+	p->q_enabled = nval;
+	spin_unlock_irqrestore(&p->q_lock, flags);
+	return change;
+}
+
+static int snd_sb_qsound_space_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 = SNDRV_SB_CSP_QSOUND_MAX_RIGHT;
+	return 0;
+}
+
+static int snd_sb_qsound_space_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_sb_csp_t *p = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&p->q_lock, flags);
+	ucontrol->value.integer.value[0] = p->qpos_left;
+	ucontrol->value.integer.value[1] = p->qpos_right;
+	spin_unlock_irqrestore(&p->q_lock, flags);
+	return 0;
+}
+
+static int snd_sb_qsound_space_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_sb_csp_t *p = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned char nval1, nval2;
+	
+	nval1 = ucontrol->value.integer.value[0];
+	if (nval1 > SNDRV_SB_CSP_QSOUND_MAX_RIGHT)
+		nval1 = SNDRV_SB_CSP_QSOUND_MAX_RIGHT;
+	nval2 = ucontrol->value.integer.value[1];
+	if (nval2 > SNDRV_SB_CSP_QSOUND_MAX_RIGHT)
+		nval2 = SNDRV_SB_CSP_QSOUND_MAX_RIGHT;
+	spin_lock_irqsave(&p->q_lock, flags);
+	change = p->qpos_left != nval1 || p->qpos_right != nval2;
+	p->qpos_left = nval1;
+	p->qpos_right = nval2;
+	p->qpos_changed = change;
+	spin_unlock_irqrestore(&p->q_lock, flags);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_sb_qsound_switch = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "3D Control - Switch",
+	.info = snd_sb_qsound_switch_info,
+	.get = snd_sb_qsound_switch_get,
+	.put = snd_sb_qsound_switch_put
+};
+
+static snd_kcontrol_new_t snd_sb_qsound_space = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "3D Control - Space",
+	.info = snd_sb_qsound_space_info,
+	.get = snd_sb_qsound_space_get,
+	.put = snd_sb_qsound_space_put
+};
+
+static int snd_sb_qsound_build(snd_sb_csp_t * p)
+{
+	snd_card_t * card;
+	int err;
+
+	snd_assert(p != NULL, return -EINVAL);
+
+	card = p->chip->card;
+	p->qpos_left = p->qpos_right = SNDRV_SB_CSP_QSOUND_MAX_RIGHT / 2;
+	p->qpos_changed = 0;
+
+	spin_lock_init(&p->q_lock);
+
+	if ((err = snd_ctl_add(card, p->qsound_switch = snd_ctl_new1(&snd_sb_qsound_switch, p))) < 0)
+		goto __error;
+	if ((err = snd_ctl_add(card, p->qsound_space = snd_ctl_new1(&snd_sb_qsound_space, p))) < 0)
+		goto __error;
+
+	return 0;
+
+     __error:
+	snd_sb_qsound_destroy(p);
+	return err;
+}
+
+static void snd_sb_qsound_destroy(snd_sb_csp_t * p)
+{
+	snd_card_t * card;
+	unsigned long flags;
+
+	snd_assert(p != NULL, return);
+
+	card = p->chip->card;	
+	
+	down_write(&card->controls_rwsem);
+	if (p->qsound_switch)
+		snd_ctl_remove(card, p->qsound_switch);
+	if (p->qsound_space)
+		snd_ctl_remove(card, p->qsound_space);
+	up_write(&card->controls_rwsem);
+
+	/* cancel pending transfer of QSound parameters */
+	spin_lock_irqsave (&p->q_lock, flags);
+	p->qpos_changed = 0;
+	spin_unlock_irqrestore (&p->q_lock, flags);
+}
+
+/*
+ * Transfer qsound parameters to CSP,
+ * function should be called from interrupt routine
+ */
+static int snd_sb_csp_qsound_transfer(snd_sb_csp_t * p)
+{
+	int err = -ENXIO;
+
+	spin_lock(&p->q_lock);
+	if (p->running & SNDRV_SB_CSP_ST_QSOUND) {
+		set_codec_parameter(p->chip, 0xe0, 0x01);
+		/* left channel */
+		set_codec_parameter(p->chip, 0x00, p->qpos_left);
+		set_codec_parameter(p->chip, 0x02, 0x00);
+		/* right channel */
+		set_codec_parameter(p->chip, 0x00, p->qpos_right);
+		set_codec_parameter(p->chip, 0x03, 0x00);
+		err = 0;
+	}
+	p->qpos_changed = 0;
+	spin_unlock(&p->q_lock);
+	return err;
+}
+
+/* ------------------------------ */
+
+/*
+ * proc interface
+ */
+static int init_proc_entry(snd_sb_csp_t * p, int device)
+{
+	char name[16];
+	snd_info_entry_t *entry;
+	sprintf(name, "cspD%d", device);
+	if (! snd_card_proc_new(p->chip->card, name, &entry))
+		snd_info_set_text_ops(entry, p, 1024, info_read);
+	return 0;
+}
+
+static void info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+	snd_sb_csp_t *p = entry->private_data;
+
+	snd_iprintf(buffer, "Creative Signal Processor [v%d.%d]\n", (p->version >> 4), (p->version & 0x0f));
+	snd_iprintf(buffer, "State: %cx%c%c%c\n", ((p->running & SNDRV_SB_CSP_ST_QSOUND) ? 'Q' : '-'),
+		    ((p->running & SNDRV_SB_CSP_ST_PAUSED) ? 'P' : '-'),
+		    ((p->running & SNDRV_SB_CSP_ST_RUNNING) ? 'R' : '-'),
+		    ((p->running & SNDRV_SB_CSP_ST_LOADED) ? 'L' : '-'));
+	if (p->running & SNDRV_SB_CSP_ST_LOADED) {
+		snd_iprintf(buffer, "Codec: %s [func #%d]\n", p->codec_name, p->func_nr);
+		snd_iprintf(buffer, "Sample rates: ");
+		if (p->acc_rates == SNDRV_SB_CSP_RATE_ALL) {
+			snd_iprintf(buffer, "All\n");
+		} else {
+			snd_iprintf(buffer, "%s%s%s%s\n",
+				    ((p->acc_rates & SNDRV_SB_CSP_RATE_8000) ? "8000Hz " : ""),
+				    ((p->acc_rates & SNDRV_SB_CSP_RATE_11025) ? "11025Hz " : ""),
+				    ((p->acc_rates & SNDRV_SB_CSP_RATE_22050) ? "22050Hz " : ""),
+				    ((p->acc_rates & SNDRV_SB_CSP_RATE_44100) ? "44100Hz" : ""));
+		}
+		if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) {
+			snd_iprintf(buffer, "QSound decoder %sabled\n",
+				    p->q_enabled ? "en" : "dis");
+		} else {
+			snd_iprintf(buffer, "PCM format ID: 0x%x (%s/%s) [%s/%s] [%s/%s]\n",
+				    p->acc_format,
+				    ((p->acc_width & SNDRV_SB_CSP_SAMPLE_16BIT) ? "16bit" : "-"),
+				    ((p->acc_width & SNDRV_SB_CSP_SAMPLE_8BIT) ? "8bit" : "-"),
+				    ((p->acc_channels & SNDRV_SB_CSP_MONO) ? "mono" : "-"),
+				    ((p->acc_channels & SNDRV_SB_CSP_STEREO) ? "stereo" : "-"),
+				    ((p->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) ? "playback" : "-"),
+				    ((p->mode & SNDRV_SB_CSP_MODE_DSP_READ) ? "capture" : "-"));
+		}
+	}
+	if (p->running & SNDRV_SB_CSP_ST_AUTO) {
+		snd_iprintf(buffer, "Autoloaded Mu-Law, A-Law or Ima-ADPCM hardware codec\n");
+	}
+	if (p->running & SNDRV_SB_CSP_ST_RUNNING) {
+		snd_iprintf(buffer, "Processing %dbit %s PCM samples\n",
+			    ((p->run_width & SNDRV_SB_CSP_SAMPLE_16BIT) ? 16 : 8),
+			    ((p->run_channels & SNDRV_SB_CSP_MONO) ? "mono" : "stereo"));
+	}
+	if (p->running & SNDRV_SB_CSP_ST_QSOUND) {
+		snd_iprintf(buffer, "Qsound position: left = 0x%x, right = 0x%x\n",
+			    p->qpos_left, p->qpos_right);
+	}
+}
+
+/* */
+
+EXPORT_SYMBOL(snd_sb_csp_new);
+
+/*
+ * INIT part
+ */
+
+static int __init alsa_sb_csp_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_sb_csp_exit(void)
+{
+}
+
+module_init(alsa_sb_csp_init)
+module_exit(alsa_sb_csp_exit)
diff --git a/sound/isa/sb/sb16_csp_codecs.h b/sound/isa/sb/sb16_csp_codecs.h
new file mode 100644
index 0000000..f0e8b0d
--- /dev/null
+++ b/sound/isa/sb/sb16_csp_codecs.h
@@ -0,0 +1,949 @@
+/*
+ *  Copyright (c) 1994 Creative Technology Ltd.
+ *  Microcode files for SB16 Advanced Signal Processor
+ *
+ *   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
+ *
+ */
+
+static unsigned char mulaw_main[] = {
+	0x00, 0x10, 0x00, 0x44, 0x08, 0x00, 0x00, 0x44,
+	0x00, 0xb1, 0x00, 0x44, 0x00, 0x61, 0x00, 0x44,
+	0x08, 0x50, 0x00, 0x44, 0x0d, 0xf2, 0x61, 0xa8,
+	0x44, 0x04, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45,
+	0x40, 0x49, 0x39, 0xac, 0x55, 0x55, 0x71, 0x8b,
+	0x50, 0x05, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39,
+	0xff, 0x2e, 0x21, 0x49, 0xff, 0x0f, 0xd4, 0x49,
+	0x20, 0x01, 0x09, 0x0e, 0x20, 0x00, 0x71, 0x8b,
+	0xa8, 0x01, 0xa8, 0x80, 0x88, 0x01, 0xa8, 0x80,
+	0xa8, 0x00, 0x00, 0x80, 0xd2, 0x00, 0x71, 0x8b,
+	0x88, 0x00, 0xa8, 0x80, 0xa8, 0x04, 0xb3, 0x80,
+	0x20, 0x07, 0xb3, 0x80, 0x88, 0x03, 0xb1, 0x80,
+	0xc0, 0x00, 0x09, 0x5c, 0xc2, 0x01, 0x00, 0x82,
+	0xa1, 0x00, 0x71, 0x8b, 0xcd, 0x00, 0x04, 0x19,
+	0xa2, 0x20, 0x71, 0x8b, 0xcf, 0x00, 0x04, 0x19,
+	0x00, 0x00, 0xb1, 0x80, 0xc2, 0x00, 0x04, 0x19,
+	0x00, 0x40, 0x00, 0x14, 0x08, 0x40, 0x04, 0x24,
+	0x00, 0x00, 0x34, 0x49, 0x0c, 0x40, 0x00, 0x44,
+	0x44, 0x04, 0x04, 0x39, 0x00, 0x00, 0x40, 0x45,
+	0x32, 0x00, 0x09, 0x5c, 0x00, 0x00, 0x0c, 0x39,
+	0x00, 0x00, 0x40, 0x45, 0x40, 0x40, 0x09, 0xef,
+	0xff, 0x20, 0x09, 0xcf, 0x00, 0x04, 0x63, 0xa1,
+	0x50, 0x03, 0x33, 0x80, 0x00, 0x04, 0xa3, 0x80,
+	0x00, 0xff, 0xc2, 0x8b, 0x00, 0xd0, 0x04, 0x54,
+	0x04, 0xe0, 0x00, 0xc4, 0x20, 0x03, 0x80, 0xc0,
+	0x30, 0x00, 0x00, 0x88, 0x00, 0x00, 0x7a, 0x0a,
+	0xd0, 0x01, 0x00, 0x82, 0x00, 0x60, 0x00, 0x44,
+	0xc0, 0x00, 0x00, 0x99, 0x00, 0x60, 0x00, 0x44,
+	0x00, 0xff, 0xc2, 0x8b, 0x20, 0x00, 0x00, 0x80,
+	0x00, 0x0d, 0x42, 0x8b, 0x08, 0x32, 0x00, 0xc4,
+	0x00, 0x0e, 0x42, 0x8b, 0x00, 0xa2, 0x00, 0xc4,
+	0x00, 0x1e, 0x42, 0x8b, 0x0c, 0xb2, 0x00, 0xc4,
+	0x00, 0x8e, 0x42, 0x8b, 0x00, 0x62, 0x00, 0xc4,
+	0x00, 0x9e, 0x42, 0x8b, 0x08, 0x52, 0x00, 0xc4,
+	0x00, 0xbe, 0x42, 0x8b, 0x08, 0x52, 0x00, 0xc4,
+	0x00, 0x04, 0x42, 0x8b, 0x04, 0x72, 0x00, 0xc4,
+	0x00, 0x24, 0x42, 0x8b, 0x00, 0xd2, 0x00, 0xc4,
+	0x00, 0x55, 0x42, 0x8b, 0x00, 0x60, 0x00, 0xc4,
+	0x00, 0x00, 0x40, 0x45, 0x20, 0x01, 0x79, 0x80,
+	0x00, 0x30, 0x42, 0x8b, 0x08, 0x82, 0x00, 0xc4,
+	0x00, 0x00, 0x40, 0x45, 0x00, 0x00, 0x71, 0x8b,
+	0x40, 0x01, 0x00, 0x80, 0x00, 0x60, 0x00, 0x44,
+	0xff, 0x00, 0xe2, 0xab, 0x00, 0xb2, 0x00, 0xc4,
+	0x0f, 0xf2, 0xa8, 0xa8, 0x20, 0x00, 0xb1, 0x88,
+	0x00, 0x00, 0x41, 0x02, 0x4d, 0xf2, 0x00, 0x39,
+	0xc0, 0x01, 0x00, 0x82, 0x00, 0x60, 0x00, 0x44,
+	0x0d, 0xf2, 0xa3, 0xa8, 0x4d, 0xf2, 0x00, 0x39,
+	0x00, 0x60, 0x00, 0x44, 0xff, 0x00, 0xe2, 0xab,
+	0x20, 0x00, 0x00, 0x88, 0x00, 0x00, 0x61, 0x02,
+	0x4d, 0xf2, 0x04, 0x19, 0x00, 0x60, 0x00, 0x44,
+	0xff, 0x00, 0xe2, 0xab, 0xa0, 0x00, 0x00, 0x88,
+	0x00, 0x00, 0x61, 0x10, 0x4d, 0xf2, 0x04, 0x19,
+	0x00, 0x60, 0x00, 0x44, 0xff, 0x20, 0xe2, 0xab,
+	0x60, 0x00, 0x00, 0x88, 0x00, 0x00, 0x71, 0xc0,
+	0x4d, 0xf2, 0x04, 0x19, 0x00, 0x60, 0x00, 0x44,
+	0x00, 0x00, 0x79, 0x80, 0x00, 0xe2, 0x00, 0x84,
+	0x03, 0x03, 0x04, 0x49, 0x08, 0xc2, 0x00, 0x54,
+	0x00, 0x60, 0x04, 0x64, 0x00, 0x60, 0x00, 0x44,
+	0x00, 0x00, 0x63, 0x80, 0x00, 0x00, 0x06, 0x19,
+	0x03, 0x00, 0x04, 0x49, 0x00, 0x60, 0x00, 0x44,
+	0x20, 0x01, 0x63, 0x80, 0x00, 0x00, 0x06, 0x19,
+	0x00, 0x20, 0xe2, 0x8b, 0x0c, 0xf2, 0x00, 0x84,
+	0x3e, 0x00, 0x51, 0x8b, 0xc0, 0x20, 0x00, 0x39,
+	0x08, 0x01, 0x00, 0x44, 0x6c, 0x00, 0x51, 0x8b,
+	0xc0, 0x20, 0x00, 0x39, 0x00, 0x02, 0xe2, 0x8b,
+	0x04, 0x21, 0x00, 0x84, 0xfd, 0x00, 0x51, 0x8b,
+	0xc2, 0x20, 0x00, 0x39, 0x00, 0x11, 0x00, 0x44,
+	0xfe, 0x00, 0x51, 0x8b, 0xc2, 0x20, 0x00, 0x39,
+	0xe5, 0x00, 0x71, 0x8b, 0xcd, 0x00, 0x00, 0x39,
+	0x00, 0x00, 0xb1, 0x80, 0xc9, 0x20, 0x04, 0x19,
+	0xcb, 0x20, 0x04, 0x19, 0xc1, 0x20, 0x04, 0x19,
+	0xc3, 0x20, 0x04, 0x19, 0x10, 0x00, 0x71, 0x8b,
+	0xc7, 0x20, 0x04, 0x19, 0x5e, 0x00, 0x71, 0x8b,
+	0xcf, 0x00, 0x00, 0x39, 0x00, 0x00, 0xb1, 0x80,
+	0xc4, 0x20, 0x04, 0x19, 0xc6, 0x20, 0x04, 0x19,
+	0xc8, 0x20, 0x04, 0x19, 0xca, 0x20, 0x04, 0x19,
+	0x20, 0x00, 0x71, 0x8b, 0xcc, 0x20, 0x04, 0x19,
+	0x03, 0x00, 0x04, 0x49, 0x00, 0x60, 0x00, 0x44,
+	0x09, 0x04, 0x61, 0xa8, 0xc1, 0x00, 0x04, 0x19,
+	0x0b, 0x04, 0x61, 0xa8, 0xca, 0x00, 0x04, 0x19,
+	0x04, 0x60, 0x00, 0xd4, 0x0d, 0x00, 0x61, 0x0a,
+	0x90, 0x40, 0x09, 0x8f, 0x00, 0x01, 0x00, 0x45,
+	0x0f, 0x00, 0x61, 0x0a, 0x00, 0x40, 0x09, 0x8f,
+	0x00, 0x01, 0x00, 0x45, 0x82, 0x00, 0x09, 0x2e,
+	0x80, 0x40, 0x09, 0xcf, 0x02, 0x00, 0x61, 0x22,
+	0x43, 0x25, 0x61, 0x22, 0x40, 0x33, 0x00, 0x80,
+	0x08, 0xa8, 0x00, 0x44, 0x20, 0x31, 0x49, 0x5c,
+	0x92, 0x00, 0x09, 0x4e, 0x02, 0x03, 0x09, 0x2e,
+	0x00, 0x00, 0xa3, 0x02, 0xc0, 0x00, 0x71, 0xc0,
+	0x20, 0x00, 0xeb, 0x80, 0x00, 0x04, 0xc2, 0x8b,
+	0x20, 0x04, 0x61, 0x80, 0x00, 0x04, 0x7a, 0x02,
+	0xcb, 0x00, 0xa8, 0x58, 0xb0, 0x05, 0xf3, 0x80,
+	0x20, 0x04, 0xa8, 0x10, 0x00, 0x00, 0x10, 0x39,
+	0xb0, 0x00, 0xe0, 0x8b, 0x20, 0x01, 0x00, 0x80,
+	0x00, 0x00, 0x63, 0xcb, 0x00, 0x00, 0x7a, 0x02,
+	0x40, 0x00, 0x01, 0x5b, 0x20, 0x00, 0x00, 0x80,
+	0x00, 0x00, 0x4a, 0xcb, 0x20, 0x00, 0x13, 0x80,
+	0x20, 0x00, 0x7a, 0x80, 0xe0, 0x21, 0x00, 0xc0,
+	0x08, 0x00, 0x08, 0x49, 0x10, 0x41, 0x09, 0x8e,
+	0xff, 0xff, 0x62, 0x8b, 0x00, 0x04, 0x61, 0x22,
+	0x00, 0x03, 0x00, 0x45, 0x22, 0x01, 0x33, 0x80,
+	0x20, 0x01, 0xa3, 0x02, 0x00, 0x00, 0x7a, 0x80,
+	0xc0, 0x00, 0x00, 0x82, 0x07, 0x20, 0x40, 0x0a,
+	0x08, 0x83, 0x00, 0x84, 0x40, 0x21, 0x00, 0x80,
+	0x40, 0x05, 0x93, 0x10, 0xc7, 0x20, 0x00, 0x39,
+	0x00, 0x00, 0x40, 0x45, 0x07, 0x20, 0x40, 0x0a,
+	0x0c, 0xa3, 0x00, 0x84, 0x08, 0x00, 0x00, 0x82,
+	0x0c, 0x24, 0x61, 0x50, 0x40, 0x01, 0x00, 0x80,
+	0xc7, 0x20, 0x00, 0x39, 0x00, 0x00, 0x40, 0x45,
+	0x00, 0x04, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39,
+	0x42, 0x01, 0x09, 0x0e, 0x02, 0x20, 0x61, 0x0a,
+	0x00, 0x01, 0x00, 0x45, 0x0c, 0x20, 0x60, 0x0a,
+	0x00, 0x73, 0x00, 0x84, 0x00, 0x04, 0xb1, 0x80,
+	0x00, 0x00, 0x06, 0x39, 0x0c, 0x61, 0x04, 0xd4,
+	0x00, 0x24, 0x71, 0xc0, 0x20, 0x33, 0x33, 0xc0,
+	0xe0, 0x01, 0xa3, 0x82, 0x22, 0x03, 0x7a, 0x02,
+	0xc3, 0x01, 0xa3, 0x82, 0x20, 0x01, 0x33, 0x80,
+	0x00, 0x00, 0x7a, 0x80, 0xc2, 0x01, 0xb3, 0x50,
+	0xcc, 0x20, 0x00, 0x39, 0x00, 0x00, 0x71, 0x80,
+	0x00, 0xf3, 0x00, 0x44, 0x0c, 0x20, 0x60, 0x0a,
+	0x00, 0xd3, 0x00, 0x84, 0x00, 0x04, 0xb1, 0x80,
+	0x00, 0x00, 0x06, 0x39, 0x0c, 0x61, 0x04, 0xd4,
+	0x00, 0x00, 0xb3, 0x10, 0xcc, 0x20, 0x00, 0x39,
+	0x00, 0x00, 0x71, 0xc0, 0x00, 0xf3, 0x00, 0x44,
+	0xcc, 0x20, 0x00, 0x39, 0x00, 0x20, 0x71, 0xc0,
+	0x00, 0x30, 0x71, 0xc0, 0x00, 0xf3, 0x00, 0x44,
+	0x20, 0x01, 0x00, 0x80, 0xff, 0xff, 0x62, 0x8b,
+	0x20, 0x01, 0x33, 0x80, 0x00, 0x00, 0x83, 0x80,
+	0x20, 0x00, 0x7a, 0x80, 0x20, 0xe1, 0x09, 0x5c,
+	0x82, 0x00, 0x09, 0x2f, 0x80, 0x4a, 0x09, 0x8e,
+	0xe0, 0x01, 0xb3, 0x82, 0x20, 0x04, 0xa3, 0x80,
+	0x00, 0x00, 0x7a, 0xcb, 0x03, 0x00, 0xa8, 0x18,
+	0x00, 0x00, 0x10, 0x39, 0x08, 0x04, 0xea, 0x10,
+	0x08, 0x04, 0x7a, 0x10, 0x20, 0x00, 0x00, 0x80,
+	0x40, 0x00, 0x21, 0xcb, 0x0c, 0x00, 0xe8, 0x10,
+	0x00, 0x00, 0x41, 0x02, 0x0c, 0x00, 0xeb, 0x10,
+	0xf2, 0x01, 0x00, 0x82, 0x40, 0x21, 0x33, 0x02,
+	0x08, 0x20, 0x61, 0x0a, 0xc4, 0x00, 0x04, 0x19,
+	0xc7, 0x00, 0x00, 0x99, 0x02, 0x00, 0x61, 0x0a,
+	0x0c, 0xe8, 0x04, 0x14, 0x01, 0x00, 0x61, 0x0a,
+	0x03, 0x00, 0x48, 0x0a, 0x00, 0xb8, 0x04, 0x54,
+	0xc3, 0x00, 0x04, 0x19, 0x0c, 0xb8, 0x00, 0x44,
+	0x08, 0x00, 0xc8, 0x0a, 0x0c, 0xb8, 0x04, 0x54,
+	0xc8, 0x00, 0x04, 0x19, 0x0a, 0x00, 0x61, 0x0a,
+	0x09, 0x00, 0x48, 0x0a, 0x00, 0x68, 0x04, 0x54,
+	0xc9, 0x00, 0x04, 0x19, 0x0c, 0x68, 0x00, 0x44,
+	0x0b, 0x00, 0xc8, 0x0a, 0x0c, 0x68, 0x04, 0x54,
+	0xcb, 0x00, 0x04, 0x19, 0x04, 0x00, 0x61, 0x0a,
+	0x06, 0x00, 0x48, 0x0a, 0x00, 0x78, 0x04, 0x54,
+	0xc6, 0x00, 0x04, 0x19, 0x0c, 0x78, 0x00, 0x44,
+	0x05, 0x00, 0xc8, 0x0a, 0x0c, 0x78, 0x04, 0x54,
+	0xc5, 0x00, 0x04, 0x19, 0x07, 0x00, 0x61, 0x0a,
+	0x0c, 0x00, 0x48, 0x0a, 0x00, 0xe8, 0x04, 0x54,
+	0xcc, 0x00, 0x04, 0x19, 0x0c, 0xe8, 0x00, 0x44,
+	0x0e, 0x00, 0xc8, 0x0a, 0x0c, 0xe8, 0x04, 0x54,
+	0xce, 0x00, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45,
+	0x20, 0x10, 0x71, 0x8b, 0x09, 0x3f, 0x07, 0x00
+};
+
+static unsigned char alaw_main[] = {
+	0x00, 0x10, 0x00, 0x44, 0x08, 0x00, 0x00, 0x44,
+	0x00, 0xb1, 0x00, 0x44, 0x00, 0x61, 0x00, 0x44,
+	0x08, 0x50, 0x00, 0x44, 0x0d, 0xf2, 0x61, 0xa8,
+	0x44, 0x04, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45,
+	0x40, 0x49, 0x39, 0xac, 0x55, 0x55, 0x71, 0x8b,
+	0x50, 0x05, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39,
+	0xff, 0x2e, 0x21, 0x49, 0xff, 0x0f, 0xd4, 0x49,
+	0x20, 0x01, 0x09, 0x0e, 0x20, 0x00, 0x71, 0x8b,
+	0xa8, 0x01, 0xa8, 0x80, 0x88, 0x01, 0xa8, 0x80,
+	0xa8, 0x00, 0x00, 0x80, 0xd2, 0x00, 0x71, 0x8b,
+	0x88, 0x00, 0xa8, 0x80, 0xa8, 0x04, 0xb3, 0x80,
+	0x20, 0x07, 0xb3, 0x80, 0x88, 0x03, 0xb1, 0x80,
+	0xc0, 0x00, 0x09, 0x5c, 0xc2, 0x01, 0x00, 0x82,
+	0xa1, 0x00, 0x71, 0x8b, 0xcd, 0x00, 0x04, 0x19,
+	0x21, 0x20, 0x71, 0x8b, 0xcf, 0x00, 0x04, 0x19,
+	0x00, 0x00, 0xb1, 0x80, 0xc2, 0x00, 0x04, 0x19,
+	0x00, 0x40, 0x00, 0x14, 0x08, 0x40, 0x04, 0x24,
+	0x00, 0x00, 0x34, 0x49, 0x0c, 0x40, 0x00, 0x44,
+	0x44, 0x04, 0x04, 0x39, 0x00, 0x00, 0x40, 0x45,
+	0x32, 0x00, 0x09, 0x5c, 0x00, 0x00, 0x0c, 0x39,
+	0x00, 0x00, 0x40, 0x45, 0x40, 0x40, 0x09, 0xef,
+	0xff, 0x20, 0x09, 0xcf, 0x00, 0x04, 0x63, 0xa1,
+	0x50, 0x03, 0x33, 0x80, 0x00, 0x04, 0xa3, 0x80,
+	0x00, 0xff, 0xc2, 0x8b, 0x00, 0xd0, 0x04, 0x54,
+	0x04, 0xe0, 0x00, 0xc4, 0x20, 0x03, 0x80, 0xc0,
+	0x30, 0x00, 0x00, 0x88, 0x00, 0x00, 0x7a, 0x0a,
+	0xd0, 0x01, 0x00, 0x82, 0x00, 0x60, 0x00, 0x44,
+	0xc0, 0x00, 0x00, 0x99, 0x00, 0x60, 0x00, 0x44,
+	0x00, 0xff, 0xc2, 0x8b, 0x20, 0x00, 0x00, 0x80,
+	0x00, 0x0d, 0x42, 0x8b, 0x08, 0x32, 0x00, 0xc4,
+	0x00, 0x0e, 0x42, 0x8b, 0x00, 0xa2, 0x00, 0xc4,
+	0x00, 0x1e, 0x42, 0x8b, 0x0c, 0xb2, 0x00, 0xc4,
+	0x00, 0x8e, 0x42, 0x8b, 0x00, 0x62, 0x00, 0xc4,
+	0x00, 0x9e, 0x42, 0x8b, 0x08, 0x52, 0x00, 0xc4,
+	0x00, 0xbe, 0x42, 0x8b, 0x08, 0x52, 0x00, 0xc4,
+	0x00, 0x04, 0x42, 0x8b, 0x04, 0x72, 0x00, 0xc4,
+	0x00, 0x24, 0x42, 0x8b, 0x00, 0xd2, 0x00, 0xc4,
+	0x00, 0x55, 0x42, 0x8b, 0x00, 0x60, 0x00, 0xc4,
+	0x00, 0x00, 0x40, 0x45, 0x20, 0x01, 0x79, 0x80,
+	0x00, 0x30, 0x42, 0x8b, 0x08, 0x82, 0x00, 0xc4,
+	0x00, 0x00, 0x40, 0x45, 0x00, 0x00, 0x71, 0x8b,
+	0x40, 0x01, 0x00, 0x80, 0x00, 0x60, 0x00, 0x44,
+	0xff, 0x00, 0xe2, 0xab, 0x00, 0xb2, 0x00, 0xc4,
+	0x0f, 0xf2, 0xa8, 0xa8, 0x20, 0x00, 0xb1, 0x88,
+	0x00, 0x00, 0x41, 0x02, 0x4d, 0xf2, 0x00, 0x39,
+	0xc0, 0x01, 0x00, 0x82, 0x00, 0x60, 0x00, 0x44,
+	0x0d, 0xf2, 0xa3, 0xa8, 0x4d, 0xf2, 0x00, 0x39,
+	0x00, 0x60, 0x00, 0x44, 0xff, 0x00, 0xe2, 0xab,
+	0x20, 0x00, 0x00, 0x88, 0x00, 0x00, 0x61, 0x02,
+	0x4d, 0xf2, 0x04, 0x19, 0x00, 0x60, 0x00, 0x44,
+	0xff, 0x00, 0xe2, 0xab, 0xa0, 0x00, 0x00, 0x88,
+	0x00, 0x00, 0x61, 0x10, 0x4d, 0xf2, 0x04, 0x19,
+	0x00, 0x60, 0x00, 0x44, 0xff, 0x20, 0xe2, 0xab,
+	0x60, 0x00, 0x00, 0x88, 0x00, 0x00, 0x71, 0xc0,
+	0x4d, 0xf2, 0x04, 0x19, 0x00, 0x60, 0x00, 0x44,
+	0x00, 0x00, 0x79, 0x80, 0x00, 0xe2, 0x00, 0x84,
+	0x03, 0x03, 0x04, 0x49, 0x04, 0xc2, 0x00, 0x54,
+	0x00, 0x60, 0x04, 0x64, 0x00, 0x60, 0x00, 0x44,
+	0x00, 0x00, 0x63, 0x80, 0x00, 0x00, 0x06, 0x19,
+	0x03, 0x00, 0x04, 0x49, 0x00, 0x60, 0x00, 0x44,
+	0x20, 0x01, 0x63, 0x80, 0x00, 0x00, 0x06, 0x19,
+	0x00, 0x20, 0xe2, 0x8b, 0x0c, 0xf2, 0x00, 0x84,
+	0xbe, 0x00, 0x51, 0x8b, 0xc0, 0x20, 0x00, 0x39,
+	0x08, 0x01, 0x00, 0x44, 0xec, 0x00, 0x51, 0x8b,
+	0xc0, 0x20, 0x00, 0x39, 0x00, 0x02, 0xe2, 0x8b,
+	0x04, 0x21, 0x00, 0x84, 0x3f, 0x00, 0x51, 0x8b,
+	0xc2, 0x20, 0x00, 0x39, 0x00, 0x11, 0x00, 0x44,
+	0x3d, 0x00, 0x51, 0x8b, 0xc2, 0x20, 0x00, 0x39,
+	0xe5, 0x00, 0x71, 0x8b, 0xcd, 0x00, 0x00, 0x39,
+	0x00, 0x00, 0xb1, 0x80, 0xc9, 0x20, 0x04, 0x19,
+	0xcb, 0x20, 0x04, 0x19, 0xc1, 0x20, 0x04, 0x19,
+	0xc3, 0x20, 0x04, 0x19, 0x10, 0x00, 0x71, 0x8b,
+	0xc7, 0x20, 0x04, 0x19, 0xde, 0x00, 0x51, 0x8b,
+	0xcf, 0x00, 0x00, 0x39, 0x00, 0x01, 0xb1, 0x80,
+	0xc4, 0x20, 0x04, 0x19, 0xc6, 0x20, 0x04, 0x19,
+	0xc8, 0x20, 0x04, 0x19, 0xca, 0x20, 0x04, 0x19,
+	0x20, 0x00, 0x71, 0x8b, 0xcc, 0x20, 0x04, 0x19,
+	0x03, 0x00, 0x04, 0x49, 0x00, 0x60, 0x00, 0x44,
+	0x09, 0x04, 0x61, 0xa8, 0xc1, 0x00, 0x04, 0x19,
+	0x0b, 0x04, 0x61, 0xa8, 0xca, 0x00, 0x04, 0x19,
+	0x04, 0x60, 0x00, 0xd4, 0x0d, 0x00, 0x61, 0x0a,
+	0x90, 0x40, 0x09, 0x8f, 0x00, 0x01, 0x00, 0x45,
+	0x0f, 0x00, 0x61, 0x0a, 0x00, 0x40, 0x09, 0x8f,
+	0x00, 0x01, 0x00, 0x45, 0x82, 0x00, 0x09, 0x2e,
+	0x80, 0x40, 0x09, 0xcf, 0x02, 0x00, 0x61, 0x22,
+	0x43, 0x25, 0x61, 0x22, 0x40, 0x33, 0x00, 0x80,
+	0x08, 0x48, 0x00, 0x44, 0x20, 0xb1, 0x49, 0x5c,
+	0x92, 0x00, 0x09, 0x4e, 0x02, 0x03, 0x09, 0x2e,
+	0x00, 0x00, 0xa3, 0x02, 0xc0, 0x00, 0x71, 0xc0,
+	0x20, 0x00, 0xeb, 0x80, 0x00, 0x04, 0xc2, 0x8b,
+	0x20, 0x04, 0x61, 0x80, 0x00, 0x04, 0x7a, 0x02,
+	0xc0, 0x00, 0x00, 0x82, 0x0c, 0xc3, 0x08, 0x49,
+	0xb0, 0x01, 0xf3, 0x80, 0x00, 0x00, 0x10, 0x39,
+	0x20, 0x00, 0x0c, 0x89, 0x0c, 0x88, 0x08, 0x49,
+	0x03, 0x00, 0xa8, 0x18, 0x00, 0x00, 0x10, 0x39,
+	0xbd, 0xff, 0x62, 0x8b, 0x20, 0x01, 0x00, 0x80,
+	0x00, 0x00, 0x63, 0xcb, 0x00, 0x00, 0x7a, 0x02,
+	0x40, 0x00, 0x01, 0x5b, 0x20, 0x00, 0x00, 0x80,
+	0x00, 0x00, 0x4a, 0xcb, 0x20, 0x00, 0x13, 0x80,
+	0x20, 0x00, 0x7a, 0x80, 0xe0, 0x21, 0x00, 0xc0,
+	0x08, 0x00, 0x08, 0x49, 0x10, 0x41, 0x09, 0x8e,
+	0xae, 0xae, 0x62, 0x8b, 0x00, 0x04, 0x61, 0x22,
+	0x00, 0x03, 0x00, 0x45, 0x22, 0x01, 0x33, 0x80,
+	0x20, 0x01, 0xa3, 0x02, 0x00, 0x00, 0x7a, 0x80,
+	0xc0, 0x00, 0x00, 0x82, 0x07, 0x20, 0x40, 0x0a,
+	0x08, 0xa3, 0x00, 0x84, 0x40, 0x21, 0x00, 0x80,
+	0x40, 0x05, 0x93, 0x10, 0xc7, 0x20, 0x00, 0x39,
+	0x00, 0x00, 0x40, 0x45, 0x07, 0x20, 0x40, 0x0a,
+	0x0c, 0x93, 0x00, 0x84, 0x08, 0x00, 0x00, 0x82,
+	0x0c, 0x24, 0x61, 0x50, 0x40, 0x01, 0x00, 0x80,
+	0xc7, 0x20, 0x00, 0x39, 0x00, 0x00, 0x40, 0x45,
+	0x00, 0x04, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39,
+	0x42, 0x01, 0x09, 0x0e, 0x02, 0x20, 0x61, 0x0a,
+	0x00, 0x01, 0x00, 0x45, 0x0c, 0x20, 0x60, 0x0a,
+	0x00, 0xc3, 0x00, 0x84, 0x00, 0x04, 0xb1, 0x80,
+	0x00, 0x00, 0x06, 0x39, 0x0c, 0x61, 0x04, 0xd4,
+	0x00, 0x24, 0x71, 0xc0, 0x20, 0x33, 0x33, 0xc0,
+	0xe0, 0x01, 0xa3, 0x82, 0x22, 0x03, 0x7a, 0x02,
+	0xc3, 0x01, 0xa3, 0x82, 0x20, 0x01, 0x33, 0x80,
+	0x00, 0x00, 0x7a, 0x80, 0xc2, 0x01, 0xb3, 0x50,
+	0xcc, 0x20, 0x00, 0x39, 0x00, 0x00, 0x71, 0x80,
+	0x00, 0x08, 0x00, 0x44, 0x0c, 0x20, 0x60, 0x0a,
+	0x00, 0xf3, 0x00, 0x84, 0x00, 0x04, 0xb1, 0x80,
+	0x00, 0x00, 0x06, 0x39, 0x0c, 0x61, 0x04, 0xd4,
+	0x00, 0x00, 0x71, 0xc0, 0x00, 0x00, 0x93, 0x10,
+	0xcc, 0x20, 0x00, 0x39, 0x00, 0x08, 0x00, 0x44,
+	0xcc, 0x20, 0x00, 0x39, 0x00, 0x20, 0x00, 0xc0,
+	0x00, 0x30, 0x71, 0xc0, 0x00, 0x08, 0x00, 0x44,
+	0x20, 0x01, 0x00, 0x80, 0xae, 0xae, 0x62, 0x8b,
+	0x20, 0x01, 0x33, 0x80, 0x00, 0x00, 0x83, 0x80,
+	0x20, 0x00, 0x7a, 0x80, 0x20, 0xa1, 0x49, 0x5c,
+	0x82, 0x00, 0x09, 0x6e, 0x80, 0x4a, 0x09, 0x8e,
+	0xe0, 0x01, 0xb3, 0x82, 0x20, 0x04, 0xa3, 0x80,
+	0x00, 0x00, 0x7a, 0xcb, 0x28, 0x04, 0xea, 0x10,
+	0x0c, 0x04, 0x7a, 0x10, 0x70, 0x00, 0xc0, 0x8b,
+	0x00, 0x00, 0x10, 0x39, 0x90, 0x03, 0x00, 0x80,
+	0x40, 0x00, 0x21, 0x5b, 0x90, 0x00, 0x61, 0x80,
+	0x0c, 0x8a, 0x08, 0x49, 0x00, 0x00, 0x1c, 0x19,
+	0x40, 0x00, 0x08, 0x5b, 0x08, 0x00, 0x08, 0x49,
+	0x20, 0x02, 0x00, 0x80, 0x03, 0x00, 0xa8, 0x18,
+	0x00, 0x00, 0x14, 0x19, 0x40, 0x00, 0x21, 0xcb,
+	0x00, 0x00, 0x41, 0x02, 0x00, 0x00, 0xeb, 0x80,
+	0xf2, 0x01, 0x00, 0x82, 0x40, 0x21, 0x33, 0x02,
+	0x08, 0x20, 0x61, 0x0a, 0xc4, 0x00, 0x04, 0x19,
+	0xc7, 0x00, 0x00, 0x99, 0x02, 0x00, 0x61, 0x0a,
+	0x0c, 0x0a, 0x04, 0x14, 0x01, 0x00, 0x61, 0x0a,
+	0x03, 0x00, 0x48, 0x0a, 0x00, 0x58, 0x04, 0x54,
+	0xc3, 0x00, 0x04, 0x19, 0x0c, 0x58, 0x00, 0x44,
+	0x08, 0x00, 0xc8, 0x0a, 0x0c, 0x58, 0x04, 0x54,
+	0xc8, 0x00, 0x04, 0x19, 0x0a, 0x00, 0x61, 0x0a,
+	0x09, 0x00, 0x48, 0x0a, 0x00, 0xc8, 0x04, 0x54,
+	0xc9, 0x00, 0x04, 0x19, 0x0c, 0xc8, 0x00, 0x44,
+	0x0b, 0x00, 0xc8, 0x0a, 0x0c, 0xc8, 0x04, 0x54,
+	0xcb, 0x00, 0x04, 0x19, 0x04, 0x00, 0x61, 0x0a,
+	0x06, 0x00, 0x48, 0x0a, 0x00, 0xd8, 0x04, 0x54,
+	0xc6, 0x00, 0x04, 0x19, 0x0c, 0xd8, 0x00, 0x44,
+	0x05, 0x00, 0xc8, 0x0a, 0x0c, 0xd8, 0x04, 0x54,
+	0xc5, 0x00, 0x04, 0x19, 0x07, 0x00, 0x61, 0x0a,
+	0x0c, 0x00, 0x48, 0x0a, 0x00, 0x0a, 0x04, 0x54,
+	0xcc, 0x00, 0x04, 0x19, 0x0c, 0x0a, 0x00, 0x44,
+	0x0e, 0x00, 0xc8, 0x0a, 0x0c, 0x0a, 0x04, 0x54,
+	0xce, 0x00, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45,
+	0x20, 0x10, 0x71, 0x8b, 0x08, 0x42, 0x06, 0x00
+};
+
+
+static unsigned char ima_adpcm_init[] = {
+	0x00, 0x10, 0x00, 0x44, 0x00, 0x00, 0x40, 0x45,
+	0x00, 0x00, 0x40, 0x45, 0x00, 0x00, 0x40, 0x45,
+	0x00, 0x00, 0x40, 0x45, 0xaa, 0xaa, 0x71, 0x8b,
+	0x44, 0x04, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45,
+	0xff, 0x6e, 0x21, 0x49, 0xff, 0x0f, 0xd4, 0x49,
+	0x40, 0x49, 0x39, 0xac, 0x55, 0x55, 0x71, 0x8b,
+	0x50, 0x05, 0xb1, 0x80, 0x62, 0x00, 0x19, 0x0e,
+	0x21, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xb0, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x40, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x60, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x50, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x70, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xc0, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xe0, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xd0, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x02, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x22, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x32, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xa2, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xb2, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x62, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xc2, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xf2, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x11, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xa1, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x61, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xe1, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x13, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xb3, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xc3, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x18, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x68, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x0a, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x4a, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x29, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x79, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x9b, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x14, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xf4, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xe6, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xe5, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xd7, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x2e, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x9d, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xef, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xb2, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x33, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x2a, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x3b, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x46, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x2c, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xdd, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x01, 0x10, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x9a, 0x10, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x16, 0x10, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x8e, 0x10, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xc2, 0x30, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xc9, 0x30, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x3c, 0x30, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x81, 0x80, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xd4, 0x80, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x10, 0xa0, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x34, 0xa0, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x02, 0x90, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x75, 0x90, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x9a, 0xb0, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x12, 0x40, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x0d, 0x40, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x3c, 0x60, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xe7, 0x50, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x0e, 0x70, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xff, 0xc0, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xc8, 0xd0, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x57, 0xf0, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xc8, 0x22, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xb0, 0x32, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xdd, 0x82, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x90, 0xb2, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x8a, 0x62, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xce, 0x72, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xa5, 0xd2, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x97, 0x21, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xa2, 0xa1, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x5c, 0x41, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xfe, 0xc1, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x7a, 0x23, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x78, 0x93, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x67, 0x73, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x17, 0x28, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x88, 0x48, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xdb, 0xf8, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x2b, 0xba, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xf1, 0x09, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xdc, 0x69, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x19, 0x8b, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xff, 0xfb, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x20, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x52, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0xff, 0xff, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0xc2, 0x00, 0x00, 0x82, 0xc2, 0x00, 0x00, 0x82,
+	0xc2, 0x00, 0x00, 0x82, 0x10, 0x00, 0x71, 0x8b,
+	0xc2, 0x00, 0x00, 0x82, 0x80, 0x00, 0x71, 0x8b,
+	0xc2, 0x00, 0x00, 0x82, 0x90, 0x00, 0x71, 0x8b,
+	0xc2, 0x00, 0x00, 0x82, 0x40, 0x00, 0x71, 0x8b,
+	0xc2, 0x00, 0x00, 0x82, 0xff, 0xff, 0x71, 0x8b,
+	0xc2, 0x00, 0x00, 0x82, 0xc2, 0x00, 0x00, 0x82,
+	0xc2, 0x00, 0x00, 0x82, 0xc2, 0x00, 0x00, 0x82,
+	0x10, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0x80, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0x90, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0x40, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0xff, 0xfb, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0x00, 0x04, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0x4a, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0x00, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0x00, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0xc2, 0x00, 0x00, 0x82, 0xc2, 0x30, 0x04, 0x19,
+	0x10, 0x00, 0x09, 0x4f, 0xc2, 0x01, 0x00, 0x82,
+	0xc2, 0x01, 0x00, 0x82, 0xc2, 0x01, 0x00, 0x82,
+	0xc2, 0x01, 0x00, 0x82, 0xc2, 0x01, 0x00, 0x82,
+	0xc2, 0x01, 0x00, 0x82, 0xc2, 0x01, 0x00, 0x82,
+	0xc2, 0x01, 0x00, 0x82, 0xc2, 0x01, 0x00, 0x82,
+	0xc2, 0x01, 0x00, 0x82, 0xc2, 0x01, 0x00, 0x82,
+	0xc2, 0x01, 0x00, 0x82, 0xc2, 0x01, 0x00, 0x82,
+	0x00, 0x10, 0x71, 0x8b, 0xc1, 0x30, 0x04, 0x19,
+	0x93, 0x00, 0x01, 0x4f, 0xcd, 0x30, 0x00, 0x09,
+	0xcf, 0x30, 0x00, 0x09, 0x00, 0x00, 0x34, 0x49,
+	0x00, 0x08, 0x00, 0x44, 0xc8, 0x54, 0x11, 0x00
+};
+
+static unsigned char ima_adpcm_playback[] = {
+	0x00, 0x10, 0x00, 0x44, 0x08, 0x00, 0x00, 0x44,
+	0x0c, 0x50, 0x00, 0x44, 0x00, 0x70, 0x00, 0x44,
+	0x04, 0x70, 0x00, 0x44, 0x0d, 0xf2, 0x61, 0xa8,
+	0x44, 0x04, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45,
+	0x00, 0x04, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39,
+	0xff, 0x2e, 0x21, 0x49, 0xff, 0x0d, 0xd4, 0x49,
+	0x40, 0x49, 0x39, 0xac, 0x55, 0x55, 0x71, 0x8b,
+	0x50, 0x01, 0xb1, 0x80, 0x00, 0x01, 0xb1, 0x80,
+	0xc9, 0x20, 0x04, 0x19, 0x51, 0x00, 0x71, 0x8b,
+	0xcd, 0x00, 0x04, 0x19, 0xe4, 0x20, 0x71, 0x8b,
+	0xcf, 0x00, 0x04, 0x19, 0x80, 0x00, 0x71, 0x8b,
+	0xcb, 0x20, 0x04, 0x19, 0x10, 0x00, 0x71, 0x8b,
+	0xc4, 0x20, 0x04, 0x19, 0x65, 0x00, 0x51, 0x8b,
+	0xc2, 0x20, 0x00, 0x39, 0x00, 0x00, 0xb1, 0x80,
+	0xc2, 0x30, 0x04, 0x19, 0x00, 0x00, 0x63, 0x80,
+	0xc1, 0xa0, 0x04, 0x19, 0x93, 0x00, 0x01, 0x4f,
+	0xcd, 0x30, 0x00, 0x09, 0xcf, 0x30, 0x00, 0x09,
+	0x04, 0x40, 0x00, 0x14, 0x0c, 0x40, 0x00, 0x14,
+	0x00, 0x04, 0x61, 0xa8, 0x02, 0x04, 0x61, 0xa8,
+	0x04, 0x60, 0x04, 0x24, 0x00, 0x00, 0x34, 0x49,
+	0x00, 0x50, 0x00, 0x44, 0x44, 0x04, 0x04, 0x39,
+	0x00, 0x00, 0x40, 0x45, 0x00, 0x00, 0x40, 0x45,
+	0x0f, 0x00, 0x61, 0x0a, 0x00, 0x01, 0x00, 0x45,
+	0x40, 0x40, 0x09, 0xef, 0xff, 0x20, 0x09, 0xcf,
+	0x00, 0x04, 0x63, 0xa1, 0x50, 0x03, 0x33, 0x80,
+	0x00, 0x04, 0xa3, 0x80, 0x00, 0xff, 0xc2, 0x8b,
+	0x08, 0xf0, 0x04, 0x54, 0x0c, 0xd0, 0x00, 0xc4,
+	0x20, 0x03, 0x80, 0xc0, 0x30, 0x00, 0x00, 0x88,
+	0x00, 0x00, 0x7a, 0x0a, 0xd0, 0x01, 0x00, 0x82,
+	0x08, 0x50, 0x00, 0x44, 0xc0, 0x00, 0x00, 0x99,
+	0x08, 0x50, 0x00, 0x44, 0x00, 0xff, 0xc2, 0x8b,
+	0x20, 0x00, 0x00, 0x80, 0x00, 0x0d, 0x42, 0x8b,
+	0x00, 0xa2, 0x00, 0xc4, 0x00, 0x0e, 0x42, 0x8b,
+	0x0c, 0x92, 0x00, 0xc4, 0x00, 0x1e, 0x42, 0x8b,
+	0x04, 0x62, 0x00, 0xc4, 0x00, 0x8e, 0x42, 0x8b,
+	0x0c, 0x52, 0x00, 0xc4, 0x00, 0x9e, 0x42, 0x8b,
+	0x00, 0xc2, 0x00, 0xc4, 0x00, 0xbe, 0x42, 0x8b,
+	0x00, 0xc2, 0x00, 0xc4, 0x00, 0x04, 0x42, 0x8b,
+	0x00, 0xf2, 0x00, 0xc4, 0x00, 0x24, 0x42, 0x8b,
+	0x00, 0x91, 0x00, 0xc4, 0x00, 0x55, 0x42, 0x8b,
+	0x08, 0x50, 0x00, 0xc4, 0x00, 0x3f, 0x42, 0x8b,
+	0x08, 0xe2, 0x00, 0xc4, 0x00, 0x00, 0x40, 0x45,
+	0x20, 0x01, 0x79, 0x80, 0x00, 0x30, 0x42, 0x8b,
+	0x00, 0x92, 0x00, 0xc4, 0x00, 0x00, 0x40, 0x45,
+	0x00, 0x00, 0x71, 0x8b, 0x40, 0x01, 0x00, 0x80,
+	0x08, 0x50, 0x00, 0x44, 0xff, 0x00, 0xe2, 0xab,
+	0x0c, 0x42, 0x00, 0xc4, 0x0f, 0xf2, 0xa8, 0xa8,
+	0x20, 0x00, 0xb1, 0x88, 0x00, 0x00, 0x41, 0x02,
+	0x4d, 0xf2, 0x00, 0x39, 0xc0, 0x01, 0x00, 0x82,
+	0x08, 0x50, 0x00, 0x44, 0x0d, 0xf2, 0xa3, 0xa8,
+	0x4d, 0xf2, 0x00, 0x39, 0x08, 0x50, 0x00, 0x44,
+	0xff, 0x00, 0xe2, 0xab, 0x20, 0x00, 0x00, 0x88,
+	0x00, 0x00, 0x61, 0x02, 0x4d, 0xf2, 0x04, 0x19,
+	0x08, 0x50, 0x00, 0x44, 0xff, 0x00, 0xe2, 0xab,
+	0xa0, 0x00, 0x00, 0x88, 0x00, 0x00, 0x61, 0x10,
+	0x4d, 0xf2, 0x04, 0x19, 0x08, 0x50, 0x00, 0x44,
+	0xff, 0x20, 0xe2, 0xab, 0x60, 0x00, 0x00, 0x88,
+	0x00, 0x00, 0x71, 0xc0, 0x4d, 0xf2, 0x04, 0x19,
+	0x08, 0x50, 0x00, 0x44, 0x00, 0x00, 0x7a, 0x0a,
+	0x20, 0x01, 0xf0, 0x80, 0x01, 0xa0, 0x41, 0x0a,
+	0x04, 0xd2, 0x00, 0xc4, 0x20, 0x01, 0xf0, 0x80,
+	0xc1, 0x30, 0x04, 0x19, 0x08, 0x50, 0x00, 0x44,
+	0x00, 0x00, 0x79, 0x80, 0x00, 0xa1, 0x00, 0x84,
+	0xb5, 0x00, 0x51, 0x8b, 0xcf, 0x00, 0x00, 0x39,
+	0x00, 0x01, 0xb1, 0x80, 0x88, 0x00, 0x04, 0x19,
+	0x8a, 0x00, 0x04, 0x19, 0xc8, 0x20, 0x04, 0x19,
+	0xca, 0x20, 0x04, 0x19, 0xc2, 0x30, 0x04, 0x19,
+	0xcd, 0x10, 0x04, 0x19, 0xcf, 0x10, 0x04, 0x19,
+	0xb0, 0x00, 0x71, 0x8b, 0x8c, 0x00, 0x04, 0x19,
+	0x8e, 0x00, 0x04, 0x19, 0x10, 0x00, 0x71, 0x8b,
+	0xc4, 0x20, 0x04, 0x19, 0x93, 0x00, 0x01, 0x4f,
+	0xcd, 0x30, 0x00, 0x09, 0xcf, 0x30, 0x00, 0x09,
+	0x03, 0x03, 0x04, 0x49, 0x04, 0x81, 0x00, 0x54,
+	0x08, 0x50, 0x04, 0x64, 0x08, 0x50, 0x00, 0x44,
+	0x00, 0x00, 0x63, 0x80, 0x00, 0x00, 0x06, 0x19,
+	0x03, 0x00, 0x04, 0x49, 0x08, 0x50, 0x00, 0x44,
+	0x20, 0x01, 0x63, 0x80, 0x00, 0x00, 0x06, 0x19,
+	0x00, 0x02, 0xe2, 0x8b, 0x08, 0x41, 0x00, 0x84,
+	0x65, 0x00, 0x51, 0x8b, 0xc2, 0x20, 0x00, 0x39,
+	0x00, 0x00, 0x63, 0x80, 0xc1, 0xa0, 0x04, 0x19,
+	0x08, 0x61, 0x00, 0x44, 0x2d, 0x00, 0x51, 0x8b,
+	0xc2, 0x20, 0x00, 0x39, 0x00, 0x00, 0xb1, 0x80,
+	0xc1, 0xa0, 0x04, 0x19, 0x03, 0x00, 0x04, 0x49,
+	0x08, 0x50, 0x00, 0x44, 0x02, 0x20, 0x61, 0x0a,
+	0x00, 0x01, 0x00, 0x45, 0x02, 0x30, 0x61, 0x0a,
+	0x04, 0x03, 0x00, 0xc4, 0x05, 0xb0, 0xc8, 0x18,
+	0x04, 0x71, 0x00, 0xc4, 0x00, 0x13, 0x00, 0x44,
+	0x00, 0x79, 0x08, 0x44, 0x00, 0x04, 0x79, 0x80,
+	0x00, 0x49, 0x00, 0xc4, 0xca, 0x20, 0x04, 0x19,
+	0x4a, 0x04, 0x04, 0x19, 0xff, 0x00, 0xe2, 0x8b,
+	0x0c, 0xf9, 0x08, 0x44, 0xcf, 0x10, 0x04, 0x19,
+	0x0c, 0x2b, 0x08, 0x44, 0x8e, 0x00, 0x04, 0x19,
+	0x03, 0x30, 0x61, 0x0a, 0xc8, 0x20, 0x00, 0x39,
+	0x48, 0x04, 0x00, 0x39, 0x0a, 0x30, 0x61, 0x0a,
+	0x0c, 0xf9, 0x08, 0x44, 0xcd, 0x10, 0x04, 0x19,
+	0x0c, 0x2b, 0x08, 0x44, 0x8c, 0x00, 0x04, 0x19,
+	0x0c, 0xd9, 0x08, 0x44, 0x0c, 0x5a, 0x00, 0x44,
+	0x00, 0x79, 0x08, 0x44, 0x00, 0x04, 0x79, 0x80,
+	0x00, 0x49, 0x00, 0xc4, 0xc3, 0x30, 0x04, 0x19,
+	0xca, 0x30, 0x00, 0x99, 0x0c, 0xd9, 0x08, 0x44,
+	0x42, 0x0a, 0x09, 0x0e, 0x00, 0x01, 0x33, 0x11,
+	0x8c, 0x01, 0xa3, 0x80, 0x00, 0x01, 0x7a, 0x10,
+	0x80, 0x05, 0xb1, 0x80, 0x05, 0xb0, 0xe0, 0x18,
+	0x00, 0x93, 0x00, 0x84, 0x00, 0x79, 0x08, 0x44,
+	0x00, 0x04, 0x79, 0x80, 0x00, 0x49, 0x00, 0xc4,
+	0x0c, 0x1b, 0x08, 0x44, 0x88, 0x00, 0x04, 0x19,
+	0x8a, 0x00, 0x00, 0x99, 0x0c, 0xd9, 0x08, 0x44,
+	0x42, 0x0a, 0x09, 0x0e, 0x80, 0x00, 0x71, 0x8b,
+	0xc0, 0x04, 0xb1, 0x82, 0x10, 0x00, 0xe0, 0x0b,
+	0x00, 0x43, 0x00, 0x84, 0x02, 0x30, 0x61, 0x0a,
+	0x01, 0x30, 0xc8, 0x0a, 0x00, 0x43, 0x00, 0x84,
+	0x00, 0x00, 0xb1, 0x80, 0xc2, 0x30, 0x04, 0x19,
+	0x0c, 0xa8, 0x00, 0x44, 0x02, 0x30, 0x61, 0x0a,
+	0x00, 0xd3, 0x00, 0xc4, 0x05, 0xb0, 0xc8, 0x18,
+	0x04, 0x63, 0x00, 0xc4, 0x08, 0xf3, 0x00, 0x44,
+	0x00, 0x79, 0x08, 0x44, 0x00, 0x04, 0x79, 0x80,
+	0x00, 0x49, 0x00, 0xc4, 0x20, 0x00, 0x04, 0x19,
+	0xff, 0x00, 0xe2, 0x8b, 0x0c, 0xf9, 0x08, 0x44,
+	0xcd, 0x10, 0x04, 0x19, 0xcf, 0x10, 0x04, 0x19,
+	0x0c, 0x2b, 0x08, 0x44, 0x8c, 0x00, 0x04, 0x19,
+	0x8e, 0x00, 0x04, 0x19, 0x03, 0x30, 0x61, 0x0a,
+	0xc8, 0x20, 0x00, 0x39, 0xca, 0x20, 0x00, 0x39,
+	0x48, 0x04, 0x00, 0x39, 0x4a, 0x04, 0x00, 0x39,
+	0x0c, 0xd9, 0x08, 0x44, 0x0c, 0x5a, 0x00, 0x44,
+	0x00, 0x79, 0x08, 0x44, 0x00, 0x04, 0x79, 0x80,
+	0x00, 0x49, 0x00, 0xc4, 0xc3, 0x30, 0x04, 0x19,
+	0x0c, 0xd9, 0x08, 0x44, 0x42, 0x0a, 0x09, 0x0e,
+	0x05, 0xb0, 0xe0, 0x18, 0x00, 0x18, 0x00, 0x84,
+	0x00, 0x79, 0x08, 0x44, 0x00, 0x04, 0x79, 0x80,
+	0x00, 0x49, 0x00, 0xc4, 0x0c, 0x1b, 0x08, 0x44,
+	0x80, 0x01, 0x00, 0x80, 0x0c, 0xd9, 0x08, 0x44,
+	0x42, 0x0a, 0x09, 0x0e, 0x80, 0x00, 0x71, 0x8b,
+	0xc0, 0x04, 0xb1, 0x82, 0x10, 0x00, 0xe0, 0x0b,
+	0x00, 0x88, 0x00, 0x84, 0x02, 0x30, 0x61, 0x0a,
+	0x01, 0x30, 0xc8, 0x0a, 0x00, 0x88, 0x00, 0x84,
+	0x00, 0x00, 0xb1, 0x80, 0xc2, 0x30, 0x04, 0x19,
+	0x00, 0x01, 0x00, 0x11, 0x00, 0x0f, 0xe2, 0x8b,
+	0x00, 0x00, 0x41, 0xcb, 0x8c, 0x00, 0x00, 0x80,
+	0x00, 0x00, 0x48, 0xcb, 0x20, 0x00, 0x7a, 0x80,
+	0x80, 0x01, 0x00, 0x80, 0x82, 0x0c, 0x09, 0x6e,
+	0x03, 0x08, 0x09, 0x0e, 0x80, 0x40, 0x09, 0xcf,
+	0x00, 0x01, 0x71, 0xc2, 0x00, 0x08, 0xc2, 0x1b,
+	0x04, 0xb8, 0x00, 0xc4, 0x20, 0x05, 0xa8, 0x80,
+	0x20, 0x01, 0xf0, 0x80, 0x00, 0x01, 0xc2, 0x1b,
+	0x04, 0x48, 0x00, 0xc4, 0x20, 0x05, 0xa8, 0x80,
+	0x20, 0x01, 0xf0, 0x80, 0x00, 0x02, 0xc2, 0x1b,
+	0x04, 0x68, 0x00, 0xc4, 0x20, 0x05, 0xa8, 0x80,
+	0x20, 0x01, 0xf0, 0x80, 0x20, 0x03, 0xa8, 0x80,
+	0x00, 0x01, 0x00, 0x11, 0x00, 0x04, 0xc2, 0x8b,
+	0x08, 0x78, 0x00, 0xc4, 0x00, 0x00, 0xe9, 0x80,
+	0x05, 0xb0, 0xa8, 0x18, 0x00, 0x00, 0x4a, 0xcb,
+	0x20, 0x00, 0xa8, 0x22, 0xd0, 0x01, 0x00, 0x82,
+	0x40, 0x01, 0x00, 0x80, 0xc4, 0x00, 0x04, 0x19,
+	0xb0, 0x00, 0xe2, 0x8b, 0x06, 0x20, 0xa8, 0x0a,
+	0x2d, 0x10, 0x61, 0x0a, 0xd1, 0x08, 0x09, 0x2e,
+	0x00, 0x01, 0xa8, 0x02, 0x0c, 0xf9, 0x08, 0x44,
+	0xcd, 0x10, 0x04, 0x19, 0x0c, 0x2b, 0x08, 0x44,
+	0x03, 0x08, 0x09, 0x0e, 0x9a, 0x25, 0xb1, 0x60,
+	0xa2, 0x0e, 0x09, 0x6e, 0x03, 0x00, 0x09, 0x0f,
+	0x00, 0x01, 0x71, 0x82, 0x20, 0x01, 0x00, 0x80,
+	0x00, 0x00, 0x61, 0xcb, 0x80, 0x01, 0x00, 0x80,
+	0x03, 0x00, 0x09, 0x0f, 0x00, 0x01, 0x71, 0xc2,
+	0x00, 0x08, 0xc2, 0x1b, 0x0c, 0x2a, 0x00, 0xc4,
+	0x20, 0x05, 0xa8, 0x80, 0x20, 0x01, 0xf0, 0x80,
+	0x00, 0x01, 0xc2, 0x1b, 0x0c, 0x1a, 0x00, 0xc4,
+	0x20, 0x05, 0xa8, 0x80, 0x20, 0x01, 0xf0, 0x80,
+	0x00, 0x02, 0xc2, 0x1b, 0x0c, 0x3a, 0x00, 0xc4,
+	0x20, 0x05, 0xa8, 0x80, 0x20, 0x01, 0xf0, 0x80,
+	0x20, 0x03, 0xa8, 0x80, 0x00, 0x01, 0x00, 0x11,
+	0x00, 0x04, 0xc2, 0x8b, 0x04, 0xaa, 0x00, 0xc4,
+	0x00, 0x00, 0xe9, 0x80, 0x05, 0xb0, 0xa8, 0x18,
+	0x00, 0x00, 0x4a, 0xcb, 0x20, 0x00, 0xa8, 0x22,
+	0xd0, 0x01, 0x00, 0x82, 0x40, 0x01, 0x00, 0x80,
+	0xc7, 0x00, 0x04, 0x19, 0xb0, 0x00, 0xe2, 0x8b,
+	0x06, 0x20, 0xa8, 0x0a, 0x2f, 0x10, 0x61, 0x0a,
+	0xf1, 0x08, 0x09, 0x2e, 0x00, 0x01, 0xa8, 0x02,
+	0x0c, 0xf9, 0x08, 0x44, 0xcf, 0x10, 0x04, 0x19,
+	0x0c, 0x2b, 0x08, 0x44, 0x9f, 0x35, 0xb1, 0x60,
+	0x03, 0x08, 0x09, 0x0e, 0x00, 0x01, 0x71, 0x82,
+	0x20, 0x01, 0x00, 0x80, 0x00, 0x00, 0x61, 0xcb,
+	0x80, 0x01, 0x00, 0x80, 0xe4, 0x20, 0x71, 0x8b,
+	0x00, 0x01, 0x00, 0x45, 0x90, 0x40, 0x09, 0x8f,
+	0x00, 0x05, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39,
+	0x08, 0x19, 0x04, 0xd4, 0x93, 0x00, 0x01, 0x4f,
+	0xe7, 0x00, 0x01, 0x6f, 0x0d, 0x30, 0x61, 0x0a,
+	0x20, 0x04, 0x61, 0xa8, 0xc2, 0x00, 0x00, 0x82,
+	0x02, 0x04, 0x61, 0xa8, 0xc2, 0x00, 0x00, 0x82,
+	0xcd, 0x30, 0x00, 0x09, 0x02, 0x00, 0x00, 0x02,
+	0x02, 0x00, 0x00, 0x02, 0xc0, 0x80, 0x00, 0x09,
+	0x20, 0x00, 0x09, 0x49, 0x0f, 0x30, 0x61, 0x0a,
+	0x0d, 0x30, 0xc8, 0x0a, 0x00, 0x29, 0x00, 0xc4,
+	0x00, 0x80, 0xc8, 0x0a, 0x00, 0x29, 0x00, 0xc4,
+	0x00, 0x04, 0xb1, 0x80, 0x00, 0x00, 0x06, 0x39,
+	0xc9, 0x20, 0x04, 0x39, 0x00, 0x39, 0x00, 0x44,
+	0x00, 0x04, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39,
+	0x00, 0x04, 0xb1, 0x80, 0xc9, 0x20, 0x04, 0x39,
+	0x00, 0x39, 0x00, 0x44, 0x09, 0x20, 0x23, 0x0a,
+	0x00, 0x00, 0x06, 0x19, 0xc9, 0x20, 0x04, 0x19,
+	0x00, 0x00, 0x40, 0x45, 0x02, 0x00, 0x61, 0x0a,
+	0x0c, 0xb9, 0x04, 0x14, 0x04, 0x00, 0x61, 0x0a,
+	0x06, 0x00, 0x48, 0x0a, 0x00, 0xa9, 0x04, 0x54,
+	0xc6, 0x00, 0x04, 0x19, 0x0c, 0xa9, 0x00, 0x44,
+	0x05, 0x00, 0xc8, 0x0a, 0x0c, 0xa9, 0x04, 0x54,
+	0xc5, 0x00, 0x04, 0x19, 0x07, 0x00, 0x61, 0x0a,
+	0x0c, 0x00, 0x48, 0x0a, 0x00, 0xb9, 0x04, 0x54,
+	0xcc, 0x00, 0x04, 0x19, 0x0c, 0xb9, 0x00, 0x44,
+	0x0e, 0x00, 0xc8, 0x0a, 0x0c, 0xb9, 0x04, 0x54,
+	0xce, 0x00, 0x04, 0x19, 0x0c, 0x5a, 0x00, 0x44,
+	0x82, 0x0d, 0x09, 0x2e, 0x80, 0x40, 0x09, 0xcf,
+	0x00, 0xdf, 0x71, 0x8b, 0x80, 0x01, 0x00, 0x80,
+	0x02, 0xc1, 0x00, 0x22, 0x03, 0xc1, 0x00, 0x22,
+	0x00, 0x01, 0x65, 0x80, 0xd2, 0x05, 0x65, 0x82,
+	0x40, 0x21, 0x00, 0x80, 0xd3, 0x03, 0x00, 0x82,
+	0x40, 0x33, 0x00, 0x80, 0x0c, 0x5a, 0x00, 0x44,
+	0x0f, 0x30, 0x61, 0x0a, 0x0d, 0x30, 0xc8, 0x0a,
+	0x08, 0xd9, 0x00, 0xc4, 0x93, 0x00, 0x01, 0x4f,
+	0xe7, 0x00, 0x01, 0x6f, 0x0f, 0x30, 0x61, 0x0a,
+	0x20, 0x00, 0x00, 0x88, 0x02, 0x00, 0x61, 0x02,
+	0x02, 0x00, 0x00, 0x03, 0xcf, 0x30, 0x00, 0x09,
+	0x20, 0x00, 0x09, 0x49, 0x00, 0x04, 0x63, 0x80,
+	0x04, 0xd9, 0x00, 0x44, 0x00, 0x04, 0xb1, 0x80,
+	0x00, 0x00, 0x00, 0x46, 0x02, 0x30, 0x61, 0x0a,
+	0x05, 0xb0, 0xa8, 0x18, 0xc2, 0x30, 0x04, 0x19,
+	0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0xc8, 0x0a,
+	0x0c, 0x0b, 0x04, 0x14, 0x0e, 0x10, 0x61, 0x0a,
+	0x04, 0x2b, 0x00, 0x44, 0x0c, 0x10, 0xc8, 0x0a,
+	0x04, 0x2b, 0x04, 0x54, 0x0c, 0x10, 0x61, 0x0a,
+	0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0xa8, 0x18,
+	0xa0, 0x00, 0x00, 0x88, 0x00, 0x01, 0x71, 0x82,
+	0x00, 0x00, 0x00, 0x46, 0x00, 0x04, 0x33, 0x80,
+	0x00, 0x00, 0x83, 0x80, 0x20, 0x04, 0x7a, 0x80,
+	0x20, 0x01, 0x33, 0x80, 0x00, 0x00, 0x83, 0x80,
+	0x20, 0x00, 0x7a, 0x80, 0x20, 0x03, 0x00, 0x80,
+	0x00, 0x00, 0x00, 0x46, 0x16, 0xce, 0x11, 0x00
+};
+
+static unsigned char ima_adpcm_capture[] = {
+	0x00, 0x10, 0x00, 0x44, 0x08, 0x00, 0x00, 0x44,
+	0x00, 0x70, 0x00, 0x44, 0x08, 0xd0, 0x00, 0x44,
+	0x00, 0xf0, 0x00, 0x44, 0x0d, 0xf2, 0x61, 0xa8,
+	0x44, 0x04, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45,
+	0x00, 0x04, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39,
+	0xff, 0x2e, 0x21, 0x49, 0xff, 0x0c, 0xd4, 0x49,
+	0x40, 0x49, 0x39, 0xac, 0x55, 0x55, 0x71, 0x8b,
+	0x50, 0x01, 0xb1, 0x80, 0x00, 0x00, 0x71, 0x8b,
+	0xc2, 0x30, 0x04, 0x19, 0xc0, 0xa0, 0x04, 0x19,
+	0xc2, 0xa0, 0x04, 0x19, 0x89, 0x00, 0x71, 0x8b,
+	0xc8, 0x30, 0x04, 0x19, 0x71, 0x00, 0x71, 0x8b,
+	0xcd, 0x00, 0x04, 0x19, 0xcf, 0x00, 0x04, 0x19,
+	0x80, 0x00, 0x71, 0x8b, 0xcb, 0x20, 0x04, 0x19,
+	0x20, 0x00, 0x71, 0x8b, 0xc4, 0x20, 0x04, 0x19,
+	0x47, 0x00, 0x51, 0x8b, 0xc0, 0x20, 0x00, 0x39,
+	0x00, 0x00, 0x63, 0x80, 0xc1, 0xa0, 0x04, 0x19,
+	0x93, 0x00, 0x01, 0x4f, 0xcd, 0x30, 0x00, 0x09,
+	0xcf, 0x30, 0x00, 0x09, 0x0c, 0x40, 0x00, 0x14,
+	0x00, 0x60, 0x00, 0x14, 0x00, 0x04, 0x61, 0xa8,
+	0x02, 0x04, 0x61, 0xa8, 0x0c, 0x60, 0x04, 0x24,
+	0x00, 0x00, 0x34, 0x49, 0x08, 0x50, 0x00, 0x44,
+	0x44, 0x04, 0x04, 0x39, 0x00, 0x00, 0x40, 0x45,
+	0x08, 0x30, 0x61, 0x0a, 0x05, 0xb0, 0xe8, 0x18,
+	0x0c, 0xc0, 0x04, 0x54, 0xc8, 0x30, 0x04, 0x19,
+	0x09, 0x04, 0x00, 0xa8, 0x0b, 0x04, 0x00, 0xa8,
+	0x00, 0x00, 0x40, 0x45, 0x09, 0x04, 0x61, 0xa8,
+	0xc1, 0x00, 0x04, 0x19, 0x0b, 0x04, 0x61, 0xa8,
+	0xca, 0x00, 0x04, 0x19, 0x0d, 0x00, 0x61, 0x0a,
+	0x00, 0x01, 0x00, 0x45, 0x0f, 0x00, 0x61, 0x0a,
+	0x00, 0x40, 0x09, 0x8f, 0x00, 0x01, 0x00, 0x45,
+	0x40, 0x40, 0x09, 0xef, 0xff, 0x20, 0x09, 0xcf,
+	0x00, 0x04, 0x63, 0xa1, 0x50, 0x03, 0x33, 0x80,
+	0x00, 0x04, 0xa3, 0x80, 0x00, 0xff, 0xc2, 0x8b,
+	0x0c, 0x12, 0x04, 0x54, 0x08, 0x12, 0x00, 0xc4,
+	0x20, 0x03, 0x80, 0xc0, 0x30, 0x00, 0x00, 0x88,
+	0x00, 0x00, 0x7a, 0x0a, 0xd0, 0x01, 0x00, 0x82,
+	0x04, 0x50, 0x00, 0x44, 0xc0, 0x00, 0x00, 0x99,
+	0x04, 0x50, 0x00, 0x44, 0x00, 0xff, 0xc2, 0x8b,
+	0x20, 0x00, 0x00, 0x80, 0x00, 0x0d, 0x42, 0x8b,
+	0x04, 0x42, 0x00, 0xc4, 0x00, 0x0e, 0x42, 0x8b,
+	0x08, 0x52, 0x00, 0xc4, 0x00, 0x1e, 0x42, 0x8b,
+	0x00, 0xe2, 0x00, 0xc4, 0x00, 0x8e, 0x42, 0x8b,
+	0x08, 0xd2, 0x00, 0xc4, 0x00, 0x9e, 0x42, 0x8b,
+	0x04, 0xf2, 0x00, 0xc4, 0x00, 0xbe, 0x42, 0x8b,
+	0x04, 0xf2, 0x00, 0xc4, 0x00, 0x04, 0x42, 0x8b,
+	0x04, 0x11, 0x00, 0xc4, 0x00, 0x24, 0x42, 0x8b,
+	0x0c, 0x61, 0x00, 0xc4, 0x00, 0x55, 0x42, 0x8b,
+	0x04, 0x50, 0x00, 0xc4, 0x00, 0x3f, 0x42, 0x8b,
+	0x0c, 0x01, 0x00, 0xc4, 0x00, 0x00, 0x40, 0x45,
+	0x20, 0x01, 0x79, 0x80, 0x00, 0x30, 0x42, 0x8b,
+	0x04, 0x62, 0x00, 0xc4, 0x00, 0x00, 0x40, 0x45,
+	0x00, 0x00, 0x71, 0x8b, 0x40, 0x01, 0x00, 0x80,
+	0x04, 0x50, 0x00, 0x44, 0xff, 0x00, 0xe2, 0xab,
+	0x08, 0xc2, 0x00, 0xc4, 0x0f, 0xf2, 0xa8, 0xa8,
+	0x20, 0x00, 0xb1, 0x88, 0x00, 0x00, 0x41, 0x02,
+	0x4d, 0xf2, 0x00, 0x39, 0xc0, 0x01, 0x00, 0x82,
+	0x04, 0x50, 0x00, 0x44, 0x0d, 0xf2, 0xa3, 0xa8,
+	0x4d, 0xf2, 0x00, 0x39, 0x04, 0x50, 0x00, 0x44,
+	0xff, 0x00, 0xe2, 0xab, 0x20, 0x00, 0x00, 0x88,
+	0x00, 0x00, 0x61, 0x02, 0x4d, 0xf2, 0x04, 0x19,
+	0x04, 0x50, 0x00, 0x44, 0xff, 0x00, 0xe2, 0xab,
+	0xa0, 0x00, 0x00, 0x88, 0x00, 0x00, 0x61, 0x10,
+	0x4d, 0xf2, 0x04, 0x19, 0x04, 0x50, 0x00, 0x44,
+	0xff, 0x20, 0xe2, 0xab, 0x60, 0x00, 0x00, 0x88,
+	0x00, 0x00, 0x71, 0xc0, 0x4d, 0xf2, 0x04, 0x19,
+	0x04, 0x50, 0x00, 0x44, 0x00, 0x00, 0x7a, 0x0a,
+	0x20, 0x01, 0xf0, 0x80, 0x01, 0xa0, 0x41, 0x0a,
+	0x00, 0x11, 0x00, 0xc4, 0x20, 0x01, 0xf0, 0x80,
+	0xc1, 0x30, 0x04, 0x19, 0x04, 0x50, 0x00, 0x44,
+	0x00, 0x00, 0x79, 0x80, 0x0c, 0x41, 0x00, 0x84,
+	0x89, 0x00, 0x71, 0x8b, 0xc8, 0x30, 0x04, 0x19,
+	0x97, 0x00, 0x71, 0x8b, 0xcd, 0x00, 0x00, 0x39,
+	0x00, 0x01, 0xb1, 0x80, 0x80, 0x00, 0x04, 0x19,
+	0x82, 0x00, 0x04, 0x19, 0xc1, 0x20, 0x04, 0x19,
+	0xc3, 0x20, 0x04, 0x19, 0xc2, 0x30, 0x04, 0x19,
+	0xcd, 0x10, 0x04, 0x19, 0xcf, 0x10, 0x04, 0x19,
+	0xb0, 0x00, 0x71, 0x8b, 0x84, 0x00, 0x04, 0x19,
+	0x86, 0x00, 0x04, 0x19, 0x80, 0x00, 0x71, 0x8b,
+	0xcb, 0x20, 0x04, 0x19, 0x93, 0x00, 0x01, 0x4f,
+	0xcd, 0x30, 0x00, 0x09, 0xcf, 0x30, 0x00, 0x09,
+	0x03, 0x02, 0x04, 0x49, 0x08, 0x41, 0x00, 0x14,
+	0x04, 0x50, 0x00, 0x44, 0x00, 0x00, 0x63, 0x80,
+	0x00, 0x00, 0x06, 0x19, 0x03, 0x00, 0x04, 0x49,
+	0x04, 0x50, 0x00, 0x44, 0x20, 0x01, 0x63, 0x80,
+	0x00, 0x00, 0x06, 0x19, 0x00, 0x20, 0xe2, 0x8b,
+	0x00, 0xc1, 0x00, 0x84, 0x47, 0x00, 0x51, 0x8b,
+	0xc0, 0x20, 0x00, 0x39, 0x00, 0x00, 0x63, 0x80,
+	0xc1, 0xa0, 0x04, 0x19, 0x00, 0xe1, 0x00, 0x44,
+	0xbd, 0x00, 0x51, 0x8b, 0xc0, 0x20, 0x00, 0x39,
+	0x00, 0x00, 0xb1, 0x80, 0xc1, 0xa0, 0x04, 0x19,
+	0x03, 0x00, 0x04, 0x49, 0x04, 0x50, 0x00, 0x44,
+	0x00, 0x20, 0x61, 0x0a, 0x00, 0x01, 0x00, 0x45,
+	0x02, 0x30, 0x61, 0x0a, 0x0c, 0x83, 0x00, 0xc4,
+	0x0c, 0x78, 0x08, 0x44, 0x04, 0x5a, 0x08, 0x44,
+	0xb2, 0x00, 0x09, 0x4f, 0x10, 0x42, 0x09, 0x8e,
+	0x05, 0xb0, 0xe0, 0x18, 0x04, 0x23, 0x00, 0x84,
+	0x0c, 0x01, 0x00, 0x11, 0x08, 0x05, 0x61, 0x10,
+	0x00, 0x49, 0x08, 0x44, 0x00, 0x48, 0x08, 0x44,
+	0xb2, 0x00, 0x09, 0x4f, 0x80, 0x00, 0x71, 0x8b,
+	0xc0, 0x00, 0x00, 0x82, 0x0c, 0x01, 0x33, 0x10,
+	0x28, 0x01, 0xa3, 0x10, 0x00, 0x01, 0x7a, 0x80,
+	0x8c, 0x01, 0x00, 0x80, 0x02, 0x30, 0x61, 0x0a,
+	0x20, 0x00, 0x04, 0x19, 0x0c, 0x83, 0x00, 0xc4,
+	0x05, 0xb0, 0xc8, 0x18, 0x08, 0x43, 0x00, 0xc4,
+	0x01, 0x30, 0xc8, 0x0a, 0x0c, 0x38, 0x00, 0xc4,
+	0x08, 0x88, 0x00, 0x44, 0x0c, 0x78, 0x08, 0x44,
+	0x04, 0x5a, 0x08, 0x44, 0x00, 0x00, 0xa3, 0x18,
+	0x80, 0x00, 0x04, 0x19, 0x0b, 0x04, 0x61, 0xa8,
+	0xc3, 0x20, 0x00, 0x39, 0xc3, 0x30, 0x04, 0x19,
+	0x0f, 0x10, 0x61, 0x0a, 0xca, 0x30, 0x04, 0x19,
+	0x09, 0x04, 0x41, 0xa8, 0xe1, 0x20, 0x00, 0x39,
+	0xd1, 0x00, 0x09, 0x4f, 0x00, 0x04, 0x61, 0x02,
+	0x08, 0x63, 0x00, 0x44, 0x03, 0x30, 0x41, 0x0a,
+	0x20, 0x00, 0x00, 0x39, 0xa3, 0x00, 0x09, 0x4f,
+	0x00, 0x04, 0x61, 0x02, 0x00, 0x48, 0x08, 0x44,
+	0x08, 0x88, 0x00, 0x44, 0x02, 0x30, 0x61, 0x0a,
+	0x00, 0x08, 0x00, 0xc4, 0x0c, 0x78, 0x08, 0x44,
+	0x04, 0x5a, 0x08, 0x44, 0xb2, 0x00, 0x09, 0x0f,
+	0x10, 0x40, 0x09, 0x8e, 0x00, 0x00, 0x68, 0x5b,
+	0x20, 0x04, 0xb1, 0x80, 0x02, 0x00, 0x61, 0x5b,
+	0x88, 0x03, 0x7a, 0x80, 0xac, 0x01, 0x00, 0x80,
+	0x05, 0xb0, 0xe0, 0x18, 0x00, 0xd3, 0x00, 0x84,
+	0x00, 0x49, 0x08, 0x44, 0x00, 0x48, 0x08, 0x44,
+	0xb2, 0x00, 0x09, 0x0f, 0x80, 0x00, 0x71, 0x8b,
+	0xc0, 0x00, 0x00, 0x82, 0x02, 0x30, 0x61, 0x0a,
+	0x00, 0x08, 0x00, 0xc4, 0x05, 0xb0, 0xc8, 0x18,
+	0x0c, 0x18, 0x00, 0xc4, 0x01, 0x30, 0xc8, 0x0a,
+	0x0c, 0x38, 0x00, 0xc4, 0x08, 0x88, 0x00, 0x44,
+	0x0c, 0x78, 0x08, 0x44, 0x00, 0x00, 0x61, 0x18,
+	0x20, 0x05, 0xb1, 0x80, 0x00, 0x00, 0x68, 0xcb,
+	0x80, 0x00, 0x04, 0x19, 0x0d, 0x10, 0x61, 0x0a,
+	0xc3, 0x30, 0x04, 0x19, 0x0b, 0x04, 0x41, 0xa8,
+	0x09, 0x04, 0x41, 0xa8, 0xe1, 0x20, 0x00, 0x39,
+	0x08, 0x38, 0x00, 0x44, 0x03, 0x30, 0x41, 0x0a,
+	0x20, 0x04, 0xb1, 0x80, 0x00, 0x48, 0x08, 0x44,
+	0x08, 0x88, 0x00, 0x44, 0x00, 0x00, 0xb1, 0x80,
+	0xc2, 0x30, 0x04, 0x19, 0x0c, 0xb8, 0x00, 0xd4,
+	0x0f, 0x30, 0x61, 0x0a, 0x0d, 0x30, 0xc8, 0x0a,
+	0x0c, 0xb8, 0x00, 0xc4, 0x93, 0x00, 0x01, 0x4f,
+	0xe7, 0x00, 0x01, 0x6f, 0x0f, 0x30, 0x61, 0x0a,
+	0x20, 0x00, 0x00, 0x88, 0x02, 0x00, 0x61, 0x02,
+	0x41, 0x04, 0x04, 0x19, 0x02, 0x04, 0x61, 0x02,
+	0x43, 0x04, 0x04, 0x39, 0xcf, 0x30, 0x00, 0x09,
+	0x20, 0x00, 0x09, 0x49, 0x00, 0x59, 0x00, 0x44,
+	0x93, 0x00, 0x01, 0x4f, 0xe7, 0x00, 0x01, 0x6f,
+	0x0d, 0x30, 0x61, 0x0a, 0x20, 0x00, 0x61, 0x88,
+	0xc2, 0x00, 0x00, 0x82, 0xc2, 0x03, 0x00, 0x82,
+	0xcd, 0x30, 0x00, 0x09, 0x20, 0x00, 0x09, 0x49,
+	0x0f, 0x30, 0x61, 0x0a, 0x0d, 0x30, 0xc8, 0x0a,
+	0x0c, 0x58, 0x00, 0x84, 0x02, 0x30, 0x61, 0x0a,
+	0x05, 0xb0, 0xa8, 0x18, 0xc2, 0x30, 0x04, 0x19,
+	0x00, 0x00, 0x00, 0x46, 0x90, 0x40, 0x09, 0x8f,
+	0x12, 0x04, 0x09, 0x6e, 0x03, 0x00, 0x09, 0x0e,
+	0x00, 0x01, 0x71, 0x82, 0x20, 0x01, 0x00, 0x80,
+	0x00, 0x00, 0x61, 0xcb, 0x80, 0x04, 0xb1, 0x80,
+	0x00, 0x01, 0xe0, 0x60, 0x0c, 0xd8, 0x04, 0x14,
+	0x00, 0x01, 0xeb, 0x80, 0x40, 0x00, 0x52, 0x1b,
+	0x80, 0x00, 0x79, 0x80, 0xc0, 0x01, 0x71, 0xc2,
+	0x20, 0x00, 0xc0, 0x80, 0x08, 0x0a, 0x04, 0x54,
+	0xc0, 0x04, 0xa8, 0x82, 0x80, 0x00, 0x72, 0x1b,
+	0x80, 0x00, 0x00, 0x80, 0x00, 0x01, 0xf0, 0x80,
+	0x20, 0x00, 0xc0, 0x80, 0x0c, 0x2a, 0x04, 0x54,
+	0xc0, 0x04, 0xa8, 0x82, 0x10, 0x00, 0x72, 0x1b,
+	0x80, 0x00, 0x00, 0x80, 0x00, 0x01, 0xf0, 0x80,
+	0x20, 0x00, 0xc0, 0x80, 0x08, 0x3a, 0x04, 0x54,
+	0xc0, 0x04, 0xa8, 0x82, 0x20, 0x00, 0x72, 0x1b,
+	0x80, 0x00, 0x00, 0x80, 0xc0, 0x03, 0xf0, 0x82,
+	0x20, 0x00, 0xa0, 0x80, 0x00, 0x01, 0x00, 0x11,
+	0x40, 0x00, 0xc2, 0x8b, 0x00, 0xaa, 0x00, 0xc4,
+	0x00, 0x00, 0xe9, 0x80, 0x05, 0xb0, 0xa8, 0x18,
+	0x00, 0x01, 0xa8, 0x22, 0xd0, 0x01, 0x00, 0x82,
+	0xf0, 0x00, 0xe2, 0x1b, 0x06, 0x20, 0xa8, 0x0a,
+	0x2d, 0x10, 0x61, 0x0a, 0xd1, 0x00, 0x09, 0x2e,
+	0x00, 0x01, 0xa8, 0x02, 0x0e, 0x10, 0xc8, 0x0a,
+	0x0c, 0xba, 0x04, 0x14, 0x0e, 0x10, 0x61, 0x0a,
+	0x04, 0x4a, 0x00, 0x44, 0x0c, 0x10, 0xc8, 0x0a,
+	0x04, 0x4a, 0x04, 0x54, 0x0c, 0x10, 0x61, 0x0a,
+	0xd0, 0x01, 0x00, 0x82, 0x00, 0x10, 0xa8, 0x18,
+	0xa0, 0x00, 0x00, 0x88, 0x00, 0x01, 0x71, 0x82,
+	0x03, 0x00, 0x09, 0x0e, 0x9a, 0x01, 0x00, 0x60,
+	0x32, 0x00, 0x09, 0x2e, 0x00, 0x00, 0x00, 0x46,
+	0x00, 0x01, 0x71, 0x82, 0x20, 0x01, 0x00, 0x80,
+	0x00, 0x00, 0x61, 0xcb, 0x80, 0x24, 0xb1, 0xc0,
+	0x00, 0x31, 0xe0, 0x60, 0x0c, 0xca, 0x04, 0x14,
+	0x00, 0x01, 0xeb, 0x80, 0x40, 0x00, 0x52, 0x1b,
+	0x80, 0x00, 0x79, 0x80, 0xc0, 0x01, 0x71, 0xc2,
+	0x20, 0x00, 0xc0, 0x80, 0x08, 0xda, 0x04, 0x54,
+	0xc0, 0x04, 0xa8, 0x82, 0x80, 0x00, 0x72, 0x1b,
+	0x80, 0x00, 0x00, 0x80, 0x00, 0x01, 0xf0, 0x80,
+	0x20, 0x00, 0xc0, 0x80, 0x0c, 0xfa, 0x04, 0x54,
+	0xc0, 0x04, 0xa8, 0x82, 0x10, 0x00, 0x72, 0x1b,
+	0x80, 0x00, 0x00, 0x80, 0x00, 0x01, 0xf0, 0x80,
+	0x20, 0x00, 0xc0, 0x80, 0x08, 0x29, 0x04, 0x54,
+	0xc0, 0x04, 0xa8, 0x82, 0x20, 0x00, 0x72, 0x1b,
+	0x80, 0x00, 0x00, 0x80, 0xc0, 0x03, 0xf0, 0x82,
+	0x20, 0x00, 0xa0, 0x80, 0x00, 0x01, 0x00, 0x11,
+	0x40, 0x00, 0xc2, 0x8b, 0x00, 0x39, 0x00, 0xc4,
+	0x00, 0x00, 0xe9, 0x80, 0x05, 0xb0, 0xa8, 0x18,
+	0x00, 0x01, 0xa8, 0x22, 0xd0, 0x01, 0x00, 0x82,
+	0xb0, 0x00, 0xe2, 0x1b, 0x06, 0x20, 0xa8, 0x0a,
+	0x2f, 0x10, 0x61, 0x0a, 0xf1, 0x00, 0x09, 0x2e,
+	0x00, 0x01, 0xa8, 0x02, 0x0e, 0x10, 0xc8, 0x0a,
+	0x0c, 0xa9, 0x04, 0x14, 0x0e, 0x10, 0x61, 0x0a,
+	0x04, 0x99, 0x00, 0x44, 0x0c, 0x10, 0xc8, 0x0a,
+	0x04, 0x99, 0x04, 0x54, 0x0c, 0x10, 0x61, 0x0a,
+	0xd0, 0x01, 0x00, 0x82, 0x00, 0x10, 0xa8, 0x18,
+	0xa0, 0x00, 0x00, 0x88, 0x00, 0x01, 0x71, 0x82,
+	0x9f, 0x01, 0x00, 0x60, 0x00, 0x00, 0x00, 0x46,
+	0x00, 0x00, 0x33, 0x80, 0x00, 0x00, 0x83, 0x80,
+	0x20, 0x00, 0x7a, 0x80, 0x20, 0x07, 0x33, 0x80,
+	0x00, 0x00, 0x83, 0x80, 0x20, 0x04, 0x7a, 0x80,
+	0x20, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x46,
+	0x02, 0x00, 0x61, 0x0a, 0x04, 0x1b, 0x04, 0x14,
+	0x01, 0x00, 0x61, 0x0a, 0x03, 0x00, 0x48, 0x0a,
+	0x0c, 0x79, 0x04, 0x54, 0xc3, 0x00, 0x04, 0x19,
+	0x04, 0xc9, 0x00, 0x44, 0x08, 0x00, 0xc8, 0x0a,
+	0x04, 0xc9, 0x04, 0x54, 0xc8, 0x00, 0x04, 0x19,
+	0x0a, 0x00, 0x61, 0x0a, 0x09, 0x00, 0x48, 0x0a,
+	0x0c, 0xe9, 0x04, 0x54, 0xc9, 0x00, 0x04, 0x19,
+	0x04, 0xd9, 0x00, 0x44, 0x0b, 0x00, 0xc8, 0x0a,
+	0x04, 0xd9, 0x04, 0x54, 0xcb, 0x00, 0x04, 0x19,
+	0x04, 0x00, 0x61, 0x0a, 0x06, 0x00, 0x48, 0x0a,
+	0x0c, 0xf9, 0x04, 0x54, 0xc6, 0x00, 0x04, 0x19,
+	0x04, 0x0b, 0x00, 0x44, 0x05, 0x00, 0xc8, 0x0a,
+	0x04, 0x0b, 0x04, 0x54, 0xc5, 0x00, 0x04, 0x19,
+	0x07, 0x00, 0x61, 0x0a, 0x0c, 0x00, 0x48, 0x0a,
+	0x0c, 0x2b, 0x04, 0x54, 0xcc, 0x00, 0x04, 0x19,
+	0x04, 0x1b, 0x00, 0x44, 0x0e, 0x00, 0xc8, 0x0a,
+	0x04, 0x1b, 0x04, 0x54, 0xce, 0x00, 0x04, 0x19,
+	0x00, 0x00, 0x40, 0x45, 0x92, 0x20, 0x71, 0x8b,
+	0xa6, 0xc5, 0x11, 0x00
+};
diff --git a/sound/isa/sb/sb16_main.c b/sound/isa/sb/sb16_main.c
new file mode 100644
index 0000000..a6a0fa5
--- /dev/null
+++ b/sound/isa/sb/sb16_main.c
@@ -0,0 +1,916 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  Routines for control of 16-bit SoundBlaster cards and clones
+ *  Note: This is very ugly hardware which uses one 8-bit DMA channel and
+ *        second 16-bit DMA channel. Unfortunately 8-bit DMA channel can't
+ *        transfer 16-bit samples and 16-bit DMA channels can't transfer
+ *        8-bit samples. This make full duplex more complicated than
+ *        can be... People, don't buy these soundcards for full 16-bit
+ *        duplex!!!
+ *  Note: 16-bit wide is assigned to first direction which made request.
+ *        With full duplex - playback is preferred with abstract layer.
+ *
+ *  Note: Some chip revisions have hardware bug. Changing capture
+ *        channel from full-duplex 8bit DMA to 16bit DMA will block
+ *        16bit DMA transfers from DSP chip (capture) until 8bit transfer
+ *        to DSP chip (playback) starts. This bug can be avoided with
+ *        "16bit DMA Allocation" setting set to Playback or Capture.
+ *
+ *
+ *   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 <asm/dma.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/sb16_csp.h>
+#include <sound/mpu401.h>
+#include <sound/control.h>
+#include <sound/info.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Routines for control of 16-bit SoundBlaster cards and clones");
+MODULE_LICENSE("GPL");
+
+#ifdef CONFIG_SND_SB16_CSP
+static void snd_sb16_csp_playback_prepare(sb_t *chip, snd_pcm_runtime_t *runtime)
+{
+	if (chip->hardware == SB_HW_16CSP) {
+		snd_sb_csp_t *csp = chip->csp;
+
+		if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
+			/* manually loaded codec */
+			if ((csp->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) &&
+			    ((1U << runtime->format) == csp->acc_format)) {
+				/* Supported runtime PCM format for playback */
+				if (csp->ops.csp_use(csp) == 0) {
+					/* If CSP was successfully acquired */
+					goto __start_CSP;
+				}
+			} else if ((csp->mode & SNDRV_SB_CSP_MODE_QSOUND) && (csp->q_enabled)) {
+				/* QSound decoder is loaded and enabled */
+				if ((1 << runtime->format) & (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
+							      SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE)) {
+					/* Only for simple PCM formats */
+					if (csp->ops.csp_use(csp) == 0) {
+						/* If CSP was successfully acquired */
+						goto __start_CSP;
+					}
+				}
+			}
+		} else if (csp->ops.csp_use(csp) == 0) {
+			/* Acquire CSP and try to autoload hardware codec */
+			if (csp->ops.csp_autoload(csp, runtime->format, SNDRV_SB_CSP_MODE_DSP_WRITE)) {
+				/* Unsupported format, release CSP */
+				csp->ops.csp_unuse(csp);
+			} else {
+		      __start_CSP:
+				/* Try to start CSP */
+				if (csp->ops.csp_start(csp, (chip->mode & SB_MODE_PLAYBACK_16) ?
+						       SNDRV_SB_CSP_SAMPLE_16BIT : SNDRV_SB_CSP_SAMPLE_8BIT,
+						       (runtime->channels > 1) ?
+						       SNDRV_SB_CSP_STEREO : SNDRV_SB_CSP_MONO)) {
+					/* Failed, release CSP */
+					csp->ops.csp_unuse(csp);
+				} else {
+					/* Success, CSP acquired and running */
+					chip->open = SNDRV_SB_CSP_MODE_DSP_WRITE;
+				}
+			}
+		}
+	}
+}
+
+static void snd_sb16_csp_capture_prepare(sb_t *chip, snd_pcm_runtime_t *runtime)
+{
+	if (chip->hardware == SB_HW_16CSP) {
+		snd_sb_csp_t *csp = chip->csp;
+
+		if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
+			/* manually loaded codec */
+			if ((csp->mode & SNDRV_SB_CSP_MODE_DSP_READ) &&
+			    ((1U << runtime->format) == csp->acc_format)) {
+				/* Supported runtime PCM format for capture */
+				if (csp->ops.csp_use(csp) == 0) {
+					/* If CSP was successfully acquired */
+					goto __start_CSP;
+				}
+			}
+		} else if (csp->ops.csp_use(csp) == 0) {
+			/* Acquire CSP and try to autoload hardware codec */
+			if (csp->ops.csp_autoload(csp, runtime->format, SNDRV_SB_CSP_MODE_DSP_READ)) {
+				/* Unsupported format, release CSP */
+				csp->ops.csp_unuse(csp);
+			} else {
+		      __start_CSP:
+				/* Try to start CSP */
+				if (csp->ops.csp_start(csp, (chip->mode & SB_MODE_CAPTURE_16) ?
+						       SNDRV_SB_CSP_SAMPLE_16BIT : SNDRV_SB_CSP_SAMPLE_8BIT,
+						       (runtime->channels > 1) ?
+						       SNDRV_SB_CSP_STEREO : SNDRV_SB_CSP_MONO)) {
+					/* Failed, release CSP */
+					csp->ops.csp_unuse(csp);
+				} else {
+					/* Success, CSP acquired and running */
+					chip->open = SNDRV_SB_CSP_MODE_DSP_READ;
+				}
+			}
+		}
+	}
+}
+
+static void snd_sb16_csp_update(sb_t *chip)
+{
+	if (chip->hardware == SB_HW_16CSP) {
+		snd_sb_csp_t *csp = chip->csp;
+
+		if (csp->qpos_changed) {
+			spin_lock(&chip->reg_lock);
+			csp->ops.csp_qsound_transfer (csp);
+			spin_unlock(&chip->reg_lock);
+		}
+	}
+}
+
+static void snd_sb16_csp_playback_open(sb_t *chip, snd_pcm_runtime_t *runtime)
+{
+	/* CSP decoders (QSound excluded) support only 16bit transfers */
+	if (chip->hardware == SB_HW_16CSP) {
+		snd_sb_csp_t *csp = chip->csp;
+
+		if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
+			/* manually loaded codec */
+			if (csp->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) {
+				runtime->hw.formats |= csp->acc_format;
+			}
+		} else {
+			/* autoloaded codecs */
+			runtime->hw.formats |= SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
+					       SNDRV_PCM_FMTBIT_IMA_ADPCM;
+		}
+	}
+}
+
+static void snd_sb16_csp_playback_close(sb_t *chip)
+{
+	if ((chip->hardware == SB_HW_16CSP) && (chip->open == SNDRV_SB_CSP_MODE_DSP_WRITE)) {
+		snd_sb_csp_t *csp = chip->csp;
+
+		if (csp->ops.csp_stop(csp) == 0) {
+			csp->ops.csp_unuse(csp);
+			chip->open = 0;
+		}
+	}
+}
+
+static void snd_sb16_csp_capture_open(sb_t *chip, snd_pcm_runtime_t *runtime)
+{
+	/* CSP coders support only 16bit transfers */
+	if (chip->hardware == SB_HW_16CSP) {
+		snd_sb_csp_t *csp = chip->csp;
+
+		if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
+			/* manually loaded codec */
+			if (csp->mode & SNDRV_SB_CSP_MODE_DSP_READ) {
+				runtime->hw.formats |= csp->acc_format;
+			}
+		} else {
+			/* autoloaded codecs */
+			runtime->hw.formats |= SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
+					       SNDRV_PCM_FMTBIT_IMA_ADPCM;
+		}
+	}
+}
+
+static void snd_sb16_csp_capture_close(sb_t *chip)
+{
+	if ((chip->hardware == SB_HW_16CSP) && (chip->open == SNDRV_SB_CSP_MODE_DSP_READ)) {
+		snd_sb_csp_t *csp = chip->csp;
+
+		if (csp->ops.csp_stop(csp) == 0) {
+			csp->ops.csp_unuse(csp);
+			chip->open = 0;
+		}
+	}
+}
+#else
+#define snd_sb16_csp_playback_prepare(chip, runtime)	/*nop*/
+#define snd_sb16_csp_capture_prepare(chip, runtime)	/*nop*/
+#define snd_sb16_csp_update(chip)			/*nop*/
+#define snd_sb16_csp_playback_open(chip, runtime)	/*nop*/
+#define snd_sb16_csp_playback_close(chip)		/*nop*/
+#define snd_sb16_csp_capture_open(chip, runtime)	/*nop*/
+#define snd_sb16_csp_capture_close(chip)      	 	/*nop*/
+#endif
+
+
+static void snd_sb16_setup_rate(sb_t *chip,
+				unsigned short rate,
+				int channel)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->mode & (channel == SNDRV_PCM_STREAM_PLAYBACK ? SB_MODE_PLAYBACK_16 : SB_MODE_CAPTURE_16))
+		snd_sb_ack_16bit(chip);
+	else
+		snd_sb_ack_8bit(chip);
+	if (!(chip->mode & SB_RATE_LOCK)) {
+		chip->locked_rate = rate;
+		snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE_IN);
+		snd_sbdsp_command(chip, rate >> 8);
+		snd_sbdsp_command(chip, rate & 0xff);
+		snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE_OUT);
+		snd_sbdsp_command(chip, rate >> 8);
+		snd_sbdsp_command(chip, rate & 0xff);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static int snd_sb16_hw_params(snd_pcm_substream_t * substream,
+			      snd_pcm_hw_params_t * hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_sb16_hw_free(snd_pcm_substream_t * substream)
+{
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int snd_sb16_playback_prepare(snd_pcm_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned char format;
+	unsigned int size, count, dma;
+
+	snd_sb16_csp_playback_prepare(chip, runtime);
+	if (snd_pcm_format_unsigned(runtime->format) > 0) {
+		format = runtime->channels > 1 ? SB_DSP4_MODE_UNS_STEREO : SB_DSP4_MODE_UNS_MONO;
+	} else {
+		format = runtime->channels > 1 ? SB_DSP4_MODE_SIGN_STEREO : SB_DSP4_MODE_SIGN_MONO;
+	}
+
+	snd_sb16_setup_rate(chip, runtime->rate, SNDRV_PCM_STREAM_PLAYBACK);
+	size = chip->p_dma_size = snd_pcm_lib_buffer_bytes(substream);
+	dma = (chip->mode & SB_MODE_PLAYBACK_8) ? chip->dma8 : chip->dma16;
+	snd_dma_program(dma, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
+
+	count = snd_pcm_lib_period_bytes(substream);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->mode & SB_MODE_PLAYBACK_16) {
+		count >>= 1;
+		count--;
+		snd_sbdsp_command(chip, SB_DSP4_OUT16_AI);
+		snd_sbdsp_command(chip, format);
+		snd_sbdsp_command(chip, count & 0xff);
+		snd_sbdsp_command(chip, count >> 8);
+		snd_sbdsp_command(chip, SB_DSP_DMA16_OFF);
+	} else {
+		count--;
+		snd_sbdsp_command(chip, SB_DSP4_OUT8_AI);
+		snd_sbdsp_command(chip, format);
+		snd_sbdsp_command(chip, count & 0xff);
+		snd_sbdsp_command(chip, count >> 8);
+		snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_sb16_playback_trigger(snd_pcm_substream_t * substream,
+				     int cmd)
+{
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	int result = 0;
+
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		chip->mode |= SB_RATE_LOCK_PLAYBACK;
+		snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_OFF : SB_DSP_DMA8_OFF);
+		/* next two lines are needed for some types of DSP4 (SB AWE 32 - 4.13) */
+		if (chip->mode & SB_RATE_LOCK_CAPTURE)
+			snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
+		chip->mode &= ~SB_RATE_LOCK_PLAYBACK;
+		break;
+	default:
+		result = -EINVAL;
+	}
+	spin_unlock(&chip->reg_lock);
+	return result;
+}
+
+static int snd_sb16_capture_prepare(snd_pcm_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned char format;
+	unsigned int size, count, dma;
+
+	snd_sb16_csp_capture_prepare(chip, runtime);
+	if (snd_pcm_format_unsigned(runtime->format) > 0) {
+		format = runtime->channels > 1 ? SB_DSP4_MODE_UNS_STEREO : SB_DSP4_MODE_UNS_MONO;
+	} else {
+		format = runtime->channels > 1 ? SB_DSP4_MODE_SIGN_STEREO : SB_DSP4_MODE_SIGN_MONO;
+	}
+	snd_sb16_setup_rate(chip, runtime->rate, SNDRV_PCM_STREAM_CAPTURE);
+	size = chip->c_dma_size = snd_pcm_lib_buffer_bytes(substream);
+	dma = (chip->mode & SB_MODE_CAPTURE_8) ? chip->dma8 : chip->dma16;
+	snd_dma_program(dma, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
+
+	count = snd_pcm_lib_period_bytes(substream);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->mode & SB_MODE_CAPTURE_16) {
+		count >>= 1;
+		count--;
+		snd_sbdsp_command(chip, SB_DSP4_IN16_AI);
+		snd_sbdsp_command(chip, format);
+		snd_sbdsp_command(chip, count & 0xff);
+		snd_sbdsp_command(chip, count >> 8);
+		snd_sbdsp_command(chip, SB_DSP_DMA16_OFF);
+	} else {
+		count--;
+		snd_sbdsp_command(chip, SB_DSP4_IN8_AI);
+		snd_sbdsp_command(chip, format);
+		snd_sbdsp_command(chip, count & 0xff);
+		snd_sbdsp_command(chip, count >> 8);
+		snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_sb16_capture_trigger(snd_pcm_substream_t * substream,
+				    int cmd)
+{
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	int result = 0;
+
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		chip->mode |= SB_RATE_LOCK_CAPTURE;
+		snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_OFF : SB_DSP_DMA8_OFF);
+		/* next two lines are needed for some types of DSP4 (SB AWE 32 - 4.13) */
+		if (chip->mode & SB_RATE_LOCK_PLAYBACK)
+			snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
+		chip->mode &= ~SB_RATE_LOCK_CAPTURE;
+		break;
+	default:
+		result = -EINVAL;
+	}
+	spin_unlock(&chip->reg_lock);
+	return result;
+}
+
+irqreturn_t snd_sb16dsp_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	sb_t *chip = dev_id;
+	unsigned char status;
+	int ok;
+
+	spin_lock(&chip->mixer_lock);
+	status = snd_sbmixer_read(chip, SB_DSP4_IRQSTATUS);
+	spin_unlock(&chip->mixer_lock);
+	if ((status & SB_IRQTYPE_MPUIN) && chip->rmidi_callback)
+		chip->rmidi_callback(irq, chip->rmidi->private_data, regs);
+	if (status & SB_IRQTYPE_8BIT) {
+		ok = 0;
+		if (chip->mode & SB_MODE_PLAYBACK_8) {
+			snd_pcm_period_elapsed(chip->playback_substream);
+			snd_sb16_csp_update(chip);
+			ok++;
+		}
+		if (chip->mode & SB_MODE_CAPTURE_8) {
+			snd_pcm_period_elapsed(chip->capture_substream);
+			ok++;
+		}
+		spin_lock(&chip->reg_lock);
+		if (!ok)
+			snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+		snd_sb_ack_8bit(chip);
+		spin_unlock(&chip->reg_lock);
+	}
+	if (status & SB_IRQTYPE_16BIT) {
+		ok = 0;
+		if (chip->mode & SB_MODE_PLAYBACK_16) {
+			snd_pcm_period_elapsed(chip->playback_substream);
+			snd_sb16_csp_update(chip);
+			ok++;
+		}
+		if (chip->mode & SB_MODE_CAPTURE_16) {
+			snd_pcm_period_elapsed(chip->capture_substream);
+			ok++;
+		}
+		spin_lock(&chip->reg_lock);
+		if (!ok)
+			snd_sbdsp_command(chip, SB_DSP_DMA16_OFF);
+		snd_sb_ack_16bit(chip);
+		spin_unlock(&chip->reg_lock);
+	}
+	return IRQ_HANDLED;
+}
+
+/*
+
+ */
+
+static snd_pcm_uframes_t snd_sb16_playback_pointer(snd_pcm_substream_t * substream)
+{
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	unsigned int dma;
+	size_t ptr;
+
+	dma = (chip->mode & SB_MODE_PLAYBACK_8) ? chip->dma8 : chip->dma16;
+	ptr = snd_dma_pointer(dma, chip->p_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_sb16_capture_pointer(snd_pcm_substream_t * substream)
+{
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	unsigned int dma;
+	size_t ptr;
+
+	dma = (chip->mode & SB_MODE_CAPTURE_8) ? chip->dma8 : chip->dma16;
+	ptr = snd_dma_pointer(dma, chip->c_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+/*
+
+ */
+
+static snd_pcm_hardware_t snd_sb16_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		0,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100,
+	.rate_min =		4000,
+	.rate_max =		44100,
+	.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,
+};
+
+static snd_pcm_hardware_t snd_sb16_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		0,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100,
+	.rate_min =		4000,
+	.rate_max =		44100,
+	.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,
+};
+
+/*
+ *  open/close
+ */
+
+static int snd_sb16_playback_open(snd_pcm_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (chip->mode & SB_MODE_PLAYBACK) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		return -EAGAIN;
+	}
+	runtime->hw = snd_sb16_playback;
+
+	/* skip if 16 bit DMA was reserved for capture */
+	if (chip->force_mode16 & SB_MODE_CAPTURE_16)
+		goto __skip_16bit;
+
+	if (chip->dma16 >= 0 && !(chip->mode & SB_MODE_CAPTURE_16)) {
+		chip->mode |= SB_MODE_PLAYBACK_16;
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
+		/* Vibra16X hack */
+		if (chip->dma16 <= 3) {
+			runtime->hw.buffer_bytes_max =
+			runtime->hw.period_bytes_max = 64 * 1024;
+		} else {
+			snd_sb16_csp_playback_open(chip, runtime);
+		}
+		goto __open_ok;
+	}
+
+      __skip_16bit:
+	if (chip->dma8 >= 0 && !(chip->mode & SB_MODE_CAPTURE_8)) {
+		chip->mode |= SB_MODE_PLAYBACK_8;
+		/* DSP v 4.xx can transfer 16bit data through 8bit DMA channel, SBHWPG 2-7 */
+		if (chip->dma16 < 0) {
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
+			chip->mode |= SB_MODE_PLAYBACK_16;
+		} else {
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8;
+		}
+		runtime->hw.buffer_bytes_max =
+		runtime->hw.period_bytes_max = 64 * 1024;
+		goto __open_ok;
+	}
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return -EAGAIN;
+
+      __open_ok:
+	if (chip->hardware == SB_HW_ALS100)
+		runtime->hw.rate_max = 48000;
+	if (chip->mode & SB_RATE_LOCK)
+		runtime->hw.rate_min = runtime->hw.rate_max = chip->locked_rate;
+	chip->playback_substream = substream;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return 0;
+}
+
+static int snd_sb16_playback_close(snd_pcm_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+
+	snd_sb16_csp_playback_close(chip);
+	spin_lock_irqsave(&chip->open_lock, flags);
+	chip->playback_substream = NULL;
+	chip->mode &= ~SB_MODE_PLAYBACK;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return 0;
+}
+
+static int snd_sb16_capture_open(snd_pcm_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (chip->mode & SB_MODE_CAPTURE) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		return -EAGAIN;
+	}
+	runtime->hw = snd_sb16_capture;
+
+	/* skip if 16 bit DMA was reserved for playback */
+	if (chip->force_mode16 & SB_MODE_PLAYBACK_16)
+		goto __skip_16bit;
+
+	if (chip->dma16 >= 0 && !(chip->mode & SB_MODE_PLAYBACK_16)) {
+		chip->mode |= SB_MODE_CAPTURE_16;
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
+		/* Vibra16X hack */
+		if (chip->dma16 <= 3) {
+			runtime->hw.buffer_bytes_max =
+			runtime->hw.period_bytes_max = 64 * 1024;
+		} else {
+			snd_sb16_csp_capture_open(chip, runtime);
+		}
+		goto __open_ok;
+	}
+
+      __skip_16bit:
+	if (chip->dma8 >= 0 && !(chip->mode & SB_MODE_PLAYBACK_8)) {
+		chip->mode |= SB_MODE_CAPTURE_8;
+		/* DSP v 4.xx can transfer 16bit data through 8bit DMA channel, SBHWPG 2-7 */
+		if (chip->dma16 < 0) {
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
+			chip->mode |= SB_MODE_CAPTURE_16;
+		} else {
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8;
+		}
+		runtime->hw.buffer_bytes_max =
+		runtime->hw.period_bytes_max = 64 * 1024;
+		goto __open_ok;
+	}
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return -EAGAIN;
+
+      __open_ok:
+	if (chip->hardware == SB_HW_ALS100)
+		runtime->hw.rate_max = 48000;
+	if (chip->mode & SB_RATE_LOCK)
+		runtime->hw.rate_min = runtime->hw.rate_max = chip->locked_rate;
+	chip->capture_substream = substream;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return 0;
+}
+
+static int snd_sb16_capture_close(snd_pcm_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+
+	snd_sb16_csp_capture_close(chip);
+	spin_lock_irqsave(&chip->open_lock, flags);
+	chip->capture_substream = NULL;
+	chip->mode &= ~SB_MODE_CAPTURE;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return 0;
+}
+
+/*
+ *  DMA control interface
+ */
+
+static int snd_sb16_set_dma_mode(sb_t *chip, int what)
+{
+	if (chip->dma8 < 0 || chip->dma16 < 0) {
+		snd_assert(what == 0, return -EINVAL);
+		return 0;
+	}
+	if (what == 0) {
+		chip->force_mode16 = 0;
+	} else if (what == 1) {
+		chip->force_mode16 = SB_MODE_PLAYBACK_16;
+	} else if (what == 2) {
+		chip->force_mode16 = SB_MODE_CAPTURE_16;
+	} else {
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int snd_sb16_get_dma_mode(sb_t *chip)
+{
+	if (chip->dma8 < 0 || chip->dma16 < 0)
+		return 0;
+	switch (chip->force_mode16) {
+	case SB_MODE_PLAYBACK_16:
+		return 1;
+	case SB_MODE_CAPTURE_16:
+		return 2;
+	default:
+		return 0;
+	}
+}
+
+static int snd_sb16_dma_control_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	static char *texts[3] = {
+		"Auto", "Playback", "Capture"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item > 2)
+		uinfo->value.enumerated.item = 2;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_sb16_dma_control_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.enumerated.item[0] = snd_sb16_get_dma_mode(chip);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_sb16_dma_control_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned char nval, oval;
+	int change;
+	
+	if ((nval = ucontrol->value.enumerated.item[0]) > 2)
+		return -EINVAL;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	oval = snd_sb16_get_dma_mode(chip);
+	change = nval != oval;
+	snd_sb16_set_dma_mode(chip, nval);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_sb16_dma_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+	.name = "16-bit DMA Allocation",
+	.info = snd_sb16_dma_control_info,
+	.get = snd_sb16_dma_control_get,
+	.put = snd_sb16_dma_control_put
+};
+
+/*
+ *  Initialization part
+ */
+ 
+int snd_sb16dsp_configure(sb_t * chip)
+{
+	unsigned long flags;
+	unsigned char irqreg = 0, dmareg = 0, mpureg;
+	unsigned char realirq, realdma, realmpureg;
+	/* note: mpu register should be present only on SB16 Vibra soundcards */
+
+	// printk("codec->irq=%i, codec->dma8=%i, codec->dma16=%i\n", chip->irq, chip->dma8, chip->dma16);
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+	mpureg = snd_sbmixer_read(chip, SB_DSP4_MPUSETUP) & ~0x06;
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+	switch (chip->irq) {
+	case 2:
+	case 9:
+		irqreg |= SB_IRQSETUP_IRQ9;
+		break;
+	case 5:
+		irqreg |= SB_IRQSETUP_IRQ5;
+		break;
+	case 7:
+		irqreg |= SB_IRQSETUP_IRQ7;
+		break;
+	case 10:
+		irqreg |= SB_IRQSETUP_IRQ10;
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (chip->dma8 >= 0) {
+		switch (chip->dma8) {
+		case 0:
+			dmareg |= SB_DMASETUP_DMA0;
+			break;
+		case 1:
+			dmareg |= SB_DMASETUP_DMA1;
+			break;
+		case 3:
+			dmareg |= SB_DMASETUP_DMA3;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+	if (chip->dma16 >= 0 && chip->dma16 != chip->dma8) {
+		switch (chip->dma16) {
+		case 5:
+			dmareg |= SB_DMASETUP_DMA5;
+			break;
+		case 6:
+			dmareg |= SB_DMASETUP_DMA6;
+			break;
+		case 7:
+			dmareg |= SB_DMASETUP_DMA7;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+	switch (chip->mpu_port) {
+	case 0x300:
+		mpureg |= 0x04;
+		break;
+	case 0x330:
+		mpureg |= 0x00;
+		break;
+	default:
+		mpureg |= 0x02;	/* disable MPU */
+	}
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+
+	snd_sbmixer_write(chip, SB_DSP4_IRQSETUP, irqreg);
+	realirq = snd_sbmixer_read(chip, SB_DSP4_IRQSETUP);
+
+	snd_sbmixer_write(chip, SB_DSP4_DMASETUP, dmareg);
+	realdma = snd_sbmixer_read(chip, SB_DSP4_DMASETUP);
+
+	snd_sbmixer_write(chip, SB_DSP4_MPUSETUP, mpureg);
+	realmpureg = snd_sbmixer_read(chip, SB_DSP4_MPUSETUP);
+
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+	if ((~realirq) & irqreg || (~realdma) & dmareg) {
+		snd_printk("SB16 [0x%lx]: unable to set DMA & IRQ (PnP device?)\n", chip->port);
+		snd_printk("SB16 [0x%lx]: wanted: irqreg=0x%x, dmareg=0x%x, mpureg = 0x%x\n", chip->port, realirq, realdma, realmpureg);
+		snd_printk("SB16 [0x%lx]:    got: irqreg=0x%x, dmareg=0x%x, mpureg = 0x%x\n", chip->port, irqreg, dmareg, mpureg);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static snd_pcm_ops_t snd_sb16_playback_ops = {
+	.open =		snd_sb16_playback_open,
+	.close =	snd_sb16_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_sb16_hw_params,
+	.hw_free =	snd_sb16_hw_free,
+	.prepare =	snd_sb16_playback_prepare,
+	.trigger =	snd_sb16_playback_trigger,
+	.pointer =	snd_sb16_playback_pointer,
+};
+
+static snd_pcm_ops_t snd_sb16_capture_ops = {
+	.open =		snd_sb16_capture_open,
+	.close =	snd_sb16_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_sb16_hw_params,
+	.hw_free =	snd_sb16_hw_free,
+	.prepare =	snd_sb16_capture_prepare,
+	.trigger =	snd_sb16_capture_trigger,
+	.pointer =	snd_sb16_capture_pointer,
+};
+
+static void snd_sb16dsp_pcm_free(snd_pcm_t *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+int snd_sb16dsp_pcm(sb_t * chip, int device, snd_pcm_t ** rpcm)
+{
+	snd_card_t *card = chip->card;
+	snd_pcm_t *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if ((err = snd_pcm_new(card, "SB16 DSP", device, 1, 1, &pcm)) < 0)
+		return err;
+	sprintf(pcm->name, "DSP v%i.%i", chip->version >> 8, chip->version & 0xff);
+	pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
+	pcm->private_data = chip;
+	pcm->private_free = snd_sb16dsp_pcm_free;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sb16_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_sb16_capture_ops);
+
+	if (chip->dma16 >= 0 && chip->dma8 != chip->dma16)
+		snd_ctl_add(card, snd_ctl_new1(&snd_sb16_dma_control, chip));
+	else
+		pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, 128*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+const snd_pcm_ops_t *snd_sb16dsp_get_pcm_ops(int direction)
+{
+	return direction == SNDRV_PCM_STREAM_PLAYBACK ?
+		&snd_sb16_playback_ops : &snd_sb16_capture_ops;
+}
+
+EXPORT_SYMBOL(snd_sb16dsp_pcm);
+EXPORT_SYMBOL(snd_sb16dsp_get_pcm_ops);
+EXPORT_SYMBOL(snd_sb16dsp_configure);
+EXPORT_SYMBOL(snd_sb16dsp_interrupt);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_sb16_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_sb16_exit(void)
+{
+}
+
+module_init(alsa_sb16_init)
+module_exit(alsa_sb16_exit)
diff --git a/sound/isa/sb/sb8.c b/sound/isa/sb/sb8.c
new file mode 100644
index 0000000..e2cbc42
--- /dev/null
+++ b/sound/isa/sb/sb8.c
@@ -0,0 +1,223 @@
+/*
+ *  Driver for SoundBlaster 1.0/2.0/Pro soundcards and compatible
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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/slab.h>
+#include <linux/ioport.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/opl3.h>
+#define SNDRV_LEGACY_AUTO_PROBE
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Sound Blaster 1.0/2.0/Pro");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB 1.0/SB 2.0/SB Pro}}");
+
+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;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x240,0x260 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,10 */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 1,3 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Sound Blaster soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Sound Blaster soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Sound Blaster soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for SB8 driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for SB8 driver.");
+module_param_array(dma8, int, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for SB8 driver.");
+
+struct snd_sb8 {
+	struct resource *fm_res;	/* used to block FM i/o region for legacy cards */
+};
+
+static snd_card_t *snd_sb8_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+static irqreturn_t snd_sb8_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	sb_t *chip = dev_id;
+
+	if (chip->open & SB_OPEN_PCM) {
+		return snd_sb8dsp_interrupt(chip);
+	} else {
+		return snd_sb8dsp_midi_interrupt(chip);
+	}
+}
+
+static void snd_sb8_free(snd_card_t *card)
+{
+	struct snd_sb8 *acard = (struct snd_sb8 *)card->private_data;
+
+	if (acard == NULL)
+		return;
+	if (acard->fm_res) {
+		release_resource(acard->fm_res);
+		kfree_nocheck(acard->fm_res);
+	}
+}
+
+static int __init snd_sb8_probe(int dev)
+{
+	sb_t *chip;
+	snd_card_t *card;
+	struct snd_sb8 *acard;
+	opl3_t *opl3;
+	int err;
+
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE,
+			    sizeof(struct snd_sb8));
+	if (card == NULL)
+		return -ENOMEM;
+	acard = (struct snd_sb8 *)card->private_data;
+	card->private_free = snd_sb8_free;
+
+	/* block the 0x388 port to avoid PnP conflicts */
+	acard->fm_res = request_region(0x388, 4, "SoundBlaster FM");
+
+	if ((err = snd_sbdsp_create(card, port[dev], irq[dev],
+				    snd_sb8_interrupt,
+				    dma8[dev],
+				    -1,
+				    SB_HW_AUTO,
+				    &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (chip->hardware >= SB_HW_16) {
+		snd_card_free(card);
+		if (chip->hardware == SB_HW_ALS100)
+			snd_printdd("ALS100 chip detected at 0x%lx, try snd-als100 module\n",
+				    port[dev]);
+		else
+			snd_printdd("SB 16 chip detected at 0x%lx, try snd-sb16 module\n",
+				    port[dev]);
+		return -ENODEV;
+	}
+
+	if ((err = snd_sb8dsp_pcm(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_sbmixer_new(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (chip->hardware == SB_HW_10 || chip->hardware == SB_HW_20) {
+		if ((err = snd_opl3_create(card, chip->port + 8, 0,
+					   OPL3_HW_AUTO, 1,
+					   &opl3)) < 0) {
+			snd_printk(KERN_ERR "sb8: no OPL device at 0x%lx\n", chip->port + 8);
+		}
+	} else {
+		if ((err = snd_opl3_create(card, chip->port, chip->port + 2,
+					   OPL3_HW_AUTO, 1,
+					   &opl3)) < 0) {
+			snd_printk(KERN_ERR "sb8: no OPL device at 0x%lx-0x%lx\n",
+				   chip->port, chip->port + 2);
+		}
+	}
+	if (err >= 0) {
+		if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+	}
+
+	if ((err = snd_sb8dsp_midi(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->driver, chip->hardware == SB_HW_PRO ? "SB Pro" : "SB8");
+	strcpy(card->shortname, chip->name);
+	sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+		chip->name,
+		chip->port,
+		irq[dev], dma8[dev]);
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	snd_sb8_cards[dev] = card;
+	return 0;
+}
+
+static int __init snd_card_sb8_legacy_auto_probe(unsigned long xport)
+{
+        static int dev;
+        int res;
+
+        for ( ; dev < SNDRV_CARDS; dev++) {
+                if (!enable[dev] || port[dev] != SNDRV_AUTO_PORT)
+                        continue;
+                port[dev] = xport;
+                res = snd_sb8_probe(dev);
+                if (res < 0)
+                        port[dev] = SNDRV_AUTO_PORT;
+                return res;
+        }
+        return -ENODEV;
+}
+
+static int __init alsa_card_sb8_init(void)
+{
+	static unsigned long possible_ports[] = {0x220, 0x240, 0x260, -1};
+	int dev, cards, i;
+
+	for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev]; dev++) {
+		if (port[dev] == SNDRV_AUTO_PORT)
+			continue;
+		if (snd_sb8_probe(dev) >= 0)
+			cards++;
+	}
+	i = snd_legacy_auto_probe(possible_ports, snd_card_sb8_legacy_auto_probe);
+	if (i > 0)
+		cards += i;
+
+	if (!cards) {
+#ifdef MODULE
+		snd_printk(KERN_ERR "Sound Blaster soundcard not found or device busy\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_sb8_exit(void)
+{
+	int idx;
+
+	for (idx = 0; idx < SNDRV_CARDS; idx++)
+		snd_card_free(snd_sb8_cards[idx]);
+}
+
+module_init(alsa_card_sb8_init)
+module_exit(alsa_card_sb8_exit)
diff --git a/sound/isa/sb/sb8_main.c b/sound/isa/sb/sb8_main.c
new file mode 100644
index 0000000..87c9b1b
--- /dev/null
+++ b/sound/isa/sb/sb8_main.c
@@ -0,0 +1,565 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *                   Uros Bizjak <uros@kss-loka.si>
+ *
+ *  Routines for control of 8-bit SoundBlaster cards and clones
+ *  Please note: I don't have access to old SB8 soundcards.
+ *
+ *
+ *   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
+ *
+ * --
+ *
+ * Thu Apr 29 20:36:17 BST 1999 George David Morrison <gdm@gedamo.demon.co.uk>
+ *   DSP can't respond to commands whilst in "high speed" mode. Caused 
+ *   glitching during playback. Fixed.
+ *
+ * Wed Jul 12 22:02:55 CEST 2000 Uros Bizjak <uros@kss-loka.si>
+ *   Cleaned up and rewrote lowlevel routines.
+ */
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>, Uros Bizjak <uros@kss-loka.si>");
+MODULE_DESCRIPTION("Routines for control of 8-bit SoundBlaster cards and clones");
+MODULE_LICENSE("GPL");
+
+#define SB8_CLOCK	1000000
+#define SB8_DEN(v)	((SB8_CLOCK + (v) / 2) / (v))
+#define SB8_RATE(v)	(SB8_CLOCK / SB8_DEN(v))
+
+static ratnum_t clock = {
+	.num = SB8_CLOCK,
+	.den_min = 1,
+	.den_max = 256,
+	.den_step = 1,
+};
+
+static snd_pcm_hw_constraint_ratnums_t hw_constraints_clock = {
+	.nrats = 1,
+	.rats = &clock,
+};
+
+static ratnum_t stereo_clocks[] = {
+	{
+		.num = SB8_CLOCK,
+		.den_min = SB8_DEN(22050),
+		.den_max = SB8_DEN(22050),
+		.den_step = 1,
+	},
+	{
+		.num = SB8_CLOCK,
+		.den_min = SB8_DEN(11025),
+		.den_max = SB8_DEN(11025),
+		.den_step = 1,
+	}
+};
+
+static int snd_sb8_hw_constraint_rate_channels(snd_pcm_hw_params_t *params,
+					       snd_pcm_hw_rule_t *rule)
+{
+	snd_interval_t *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	if (c->min > 1) {
+	  	unsigned int num = 0, den = 0;
+		int err = snd_interval_ratnum(hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE),
+					  2, stereo_clocks, &num, &den);
+		if (err >= 0 && den) {
+			params->rate_num = num;
+			params->rate_den = den;
+		}
+		return err;
+	}
+	return 0;
+}
+
+static int snd_sb8_hw_constraint_channels_rate(snd_pcm_hw_params_t *params,
+					       snd_pcm_hw_rule_t *rule)
+{
+	snd_interval_t *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	if (r->min > SB8_RATE(22050) || r->max <= SB8_RATE(11025)) {
+		snd_interval_t t = { .min = 1, .max = 1 };
+		return snd_interval_refine(hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS), &t);
+	}
+	return 0;
+}
+
+static int snd_sb8_playback_prepare(snd_pcm_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned int mixreg, rate, size, count;
+
+	rate = runtime->rate;
+	switch (chip->hardware) {
+	case SB_HW_PRO:
+		if (runtime->channels > 1) {
+			snd_assert(rate == SB8_RATE(11025) || rate == SB8_RATE(22050), return -EINVAL);
+			chip->playback_format = SB_DSP_HI_OUTPUT_AUTO;
+			break;
+		}
+		/* fallthru */
+	case SB_HW_201:
+		if (rate > 23000) {
+			chip->playback_format = SB_DSP_HI_OUTPUT_AUTO;
+			break;
+		}
+		/* fallthru */
+	case SB_HW_20:
+		chip->playback_format = SB_DSP_LO_OUTPUT_AUTO;
+		break;
+	case SB_HW_10:
+		chip->playback_format = SB_DSP_OUTPUT;
+		break;
+	default:
+		return -EINVAL;
+	}
+	size = chip->p_dma_size = snd_pcm_lib_buffer_bytes(substream);
+	count = chip->p_period_size = snd_pcm_lib_period_bytes(substream);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_sbdsp_command(chip, SB_DSP_SPEAKER_ON);
+	if (runtime->channels > 1) {
+		/* set playback stereo mode */
+		spin_lock(&chip->mixer_lock);
+		mixreg = snd_sbmixer_read(chip, SB_DSP_STEREO_SW);
+		snd_sbmixer_write(chip, SB_DSP_STEREO_SW, mixreg | 0x02);
+		spin_unlock(&chip->mixer_lock);
+
+		/* Soundblaster hardware programming reference guide, 3-23 */
+		snd_sbdsp_command(chip, SB_DSP_DMA8_EXIT);
+		runtime->dma_area[0] = 0x80;
+		snd_dma_program(chip->dma8, runtime->dma_addr, 1, DMA_MODE_WRITE);
+		/* force interrupt */
+		chip->mode = SB_MODE_HALT;
+		snd_sbdsp_command(chip, SB_DSP_OUTPUT);
+		snd_sbdsp_command(chip, 0);
+		snd_sbdsp_command(chip, 0);
+	}
+	snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE);
+	if (runtime->channels > 1) {
+		snd_sbdsp_command(chip, 256 - runtime->rate_den / 2);
+		spin_lock(&chip->mixer_lock);
+		/* save output filter status and turn it off */
+		mixreg = snd_sbmixer_read(chip, SB_DSP_PLAYBACK_FILT);
+		snd_sbmixer_write(chip, SB_DSP_PLAYBACK_FILT, mixreg | 0x20);
+		spin_unlock(&chip->mixer_lock);
+		/* just use force_mode16 for temporary storate... */
+		chip->force_mode16 = mixreg;
+	} else {
+		snd_sbdsp_command(chip, 256 - runtime->rate_den);
+	}
+	if (chip->playback_format != SB_DSP_OUTPUT) {
+		count--;
+		snd_sbdsp_command(chip, SB_DSP_BLOCK_SIZE);
+		snd_sbdsp_command(chip, count & 0xff);
+		snd_sbdsp_command(chip, count >> 8);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_dma_program(chip->dma8, runtime->dma_addr,
+			size, DMA_MODE_WRITE | DMA_AUTOINIT);
+	return 0;
+}
+
+static int snd_sb8_playback_trigger(snd_pcm_substream_t * substream,
+				    int cmd)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	unsigned int count;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		snd_sbdsp_command(chip, chip->playback_format);
+		if (chip->playback_format == SB_DSP_OUTPUT) {
+			count = chip->p_period_size - 1;
+			snd_sbdsp_command(chip, count & 0xff);
+			snd_sbdsp_command(chip, count >> 8);
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		if (chip->playback_format == SB_DSP_HI_OUTPUT_AUTO) {
+			snd_pcm_runtime_t *runtime = substream->runtime;
+			snd_sbdsp_reset(chip);
+			if (runtime->channels > 1) {
+				spin_lock(&chip->mixer_lock);
+				/* restore output filter and set hardware to mono mode */ 
+				snd_sbmixer_write(chip, SB_DSP_STEREO_SW, chip->force_mode16 & ~0x02);
+				spin_unlock(&chip->mixer_lock);
+			}
+		} else {
+			snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+		}
+		snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	chip->mode = (cmd == SNDRV_PCM_TRIGGER_START) ? SB_MODE_PLAYBACK_8 : SB_MODE_HALT;
+	return 0;
+}
+
+static int snd_sb8_hw_params(snd_pcm_substream_t * substream,
+			     snd_pcm_hw_params_t * hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_sb8_hw_free(snd_pcm_substream_t * substream)
+{
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int snd_sb8_capture_prepare(snd_pcm_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned int mixreg, rate, size, count;
+
+	rate = runtime->rate;
+	switch (chip->hardware) {
+	case SB_HW_PRO:
+		if (runtime->channels > 1) {
+			snd_assert(rate == SB8_RATE(11025) || rate == SB8_RATE(22050), return -EINVAL);
+			chip->capture_format = SB_DSP_HI_INPUT_AUTO;
+			break;
+		}
+		chip->capture_format = (rate > 23000) ? SB_DSP_HI_INPUT_AUTO : SB_DSP_LO_INPUT_AUTO;
+		break;
+	case SB_HW_201:
+		if (rate > 13000) {
+			chip->capture_format = SB_DSP_HI_INPUT_AUTO;
+			break;
+		}
+		/* fallthru */
+	case SB_HW_20:
+		chip->capture_format = SB_DSP_LO_INPUT_AUTO;
+		break;
+	case SB_HW_10:
+		chip->capture_format = SB_DSP_INPUT;
+		break;
+	default:
+		return -EINVAL;
+	}
+	size = chip->c_dma_size = snd_pcm_lib_buffer_bytes(substream);
+	count = chip->c_period_size = snd_pcm_lib_period_bytes(substream);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF);
+	if (runtime->channels > 1)
+		snd_sbdsp_command(chip, SB_DSP_STEREO_8BIT);
+	snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE);
+	if (runtime->channels > 1) {
+		snd_sbdsp_command(chip, 256 - runtime->rate_den / 2);
+		spin_lock(&chip->mixer_lock);
+		/* save input filter status and turn it off */
+		mixreg = snd_sbmixer_read(chip, SB_DSP_CAPTURE_FILT);
+		snd_sbmixer_write(chip, SB_DSP_CAPTURE_FILT, mixreg | 0x20);
+		spin_unlock(&chip->mixer_lock);
+		/* just use force_mode16 for temporary storate... */
+		chip->force_mode16 = mixreg;
+	} else {
+		snd_sbdsp_command(chip, 256 - runtime->rate_den);
+	}
+	if (chip->capture_format != SB_DSP_OUTPUT) {
+		count--;
+		snd_sbdsp_command(chip, SB_DSP_BLOCK_SIZE);
+		snd_sbdsp_command(chip, count & 0xff);
+		snd_sbdsp_command(chip, count >> 8);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_dma_program(chip->dma8, runtime->dma_addr,
+			size, DMA_MODE_READ | DMA_AUTOINIT);
+	return 0;
+}
+
+static int snd_sb8_capture_trigger(snd_pcm_substream_t * substream,
+				   int cmd)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	unsigned int count;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		snd_sbdsp_command(chip, chip->capture_format);
+		if (chip->capture_format == SB_DSP_INPUT) {
+			count = chip->c_period_size - 1;
+			snd_sbdsp_command(chip, count & 0xff);
+			snd_sbdsp_command(chip, count >> 8);
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		if (chip->capture_format == SB_DSP_HI_INPUT_AUTO) {
+			snd_pcm_runtime_t *runtime = substream->runtime;
+			snd_sbdsp_reset(chip);
+			if (runtime->channels > 1) {
+				/* restore input filter status */
+				spin_lock(&chip->mixer_lock);
+				snd_sbmixer_write(chip, SB_DSP_CAPTURE_FILT, chip->force_mode16);
+				spin_unlock(&chip->mixer_lock);
+				/* set hardware to mono mode */
+				snd_sbdsp_command(chip, SB_DSP_MONO_8BIT);
+			}
+		} else {
+			snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+		}
+		snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	chip->mode = (cmd == SNDRV_PCM_TRIGGER_START) ? SB_MODE_CAPTURE_8 : SB_MODE_HALT;
+	return 0;
+}
+
+irqreturn_t snd_sb8dsp_interrupt(sb_t *chip)
+{
+	snd_pcm_substream_t *substream;
+	snd_pcm_runtime_t *runtime;
+
+#if 0
+	snd_printk("sb8: interrupt\n");
+#endif
+	snd_sb_ack_8bit(chip);
+	switch (chip->mode) {
+	case SB_MODE_PLAYBACK_8:	/* ok.. playback is active */
+		substream = chip->playback_substream;
+		runtime = substream->runtime;
+		if (chip->playback_format == SB_DSP_OUTPUT)
+		    	snd_sb8_playback_trigger(substream, SNDRV_PCM_TRIGGER_START);
+		snd_pcm_period_elapsed(substream);
+		break;
+	case SB_MODE_CAPTURE_8:
+		substream = chip->capture_substream;
+		runtime = substream->runtime;
+		if (chip->capture_format == SB_DSP_INPUT)
+		    	snd_sb8_capture_trigger(substream, SNDRV_PCM_TRIGGER_START);
+		snd_pcm_period_elapsed(substream);
+		break;
+	}
+	return IRQ_HANDLED;
+}
+
+static snd_pcm_uframes_t snd_sb8_playback_pointer(snd_pcm_substream_t * substream)
+{
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (chip->mode != SB_MODE_PLAYBACK_8)
+		return 0;
+	ptr = snd_dma_pointer(chip->dma8, chip->p_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_sb8_capture_pointer(snd_pcm_substream_t * substream)
+{
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (chip->mode != SB_MODE_CAPTURE_8)
+		return 0;
+	ptr = snd_dma_pointer(chip->dma8, chip->c_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+/*
+
+ */
+
+static snd_pcm_hardware_t snd_sb8_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		 SNDRV_PCM_FMTBIT_U8,
+	.rates =		(SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000 |
+				 SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_22050),
+	.rate_min =		4000,
+	.rate_max =		23000,
+	.channels_min =		1,
+	.channels_max =		1,
+	.buffer_bytes_max =	65536,
+	.period_bytes_min =	64,
+	.period_bytes_max =	65536,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static snd_pcm_hardware_t snd_sb8_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_U8,
+	.rates =		(SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000 |
+				 SNDRV_PCM_RATE_11025),
+	.rate_min =		4000,
+	.rate_max =		13000,
+	.channels_min =		1,
+	.channels_max =		1,
+	.buffer_bytes_max =	65536,
+	.period_bytes_min =	64,
+	.period_bytes_max =	65536,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/*
+ *
+ */
+ 
+static int snd_sb8_open(snd_pcm_substream_t *substream)
+{
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (chip->open) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		return -EAGAIN;
+	}
+	chip->open |= SB_OPEN_PCM;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		chip->playback_substream = substream;
+		runtime->hw = snd_sb8_playback;
+	} else {
+		chip->capture_substream = substream;
+		runtime->hw = snd_sb8_capture;
+	}
+	switch (chip->hardware) {
+	case SB_HW_PRO:
+		runtime->hw.rate_max = 44100;
+		runtime->hw.channels_max = 2;
+		snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				    snd_sb8_hw_constraint_rate_channels, NULL,
+				    SNDRV_PCM_HW_PARAM_CHANNELS,
+				    SNDRV_PCM_HW_PARAM_RATE, -1);
+		snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				     snd_sb8_hw_constraint_channels_rate, NULL,
+				     SNDRV_PCM_HW_PARAM_RATE, -1);
+		break;
+	case SB_HW_201:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			runtime->hw.rate_max = 44100;
+		} else {
+			runtime->hw.rate_max = 15000;
+		}
+	default:
+		break;
+	}
+	snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      &hw_constraints_clock);
+	return 0;	
+}
+
+static int snd_sb8_close(snd_pcm_substream_t *substream)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+
+	chip->playback_substream = NULL;
+	chip->capture_substream = NULL;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	chip->open &= ~SB_OPEN_PCM;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return 0;
+}
+
+/*
+ *  Initialization part
+ */
+ 
+static snd_pcm_ops_t snd_sb8_playback_ops = {
+	.open =			snd_sb8_open,
+	.close =		snd_sb8_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_sb8_hw_params,
+	.hw_free =		snd_sb8_hw_free,
+	.prepare =		snd_sb8_playback_prepare,
+	.trigger =		snd_sb8_playback_trigger,
+	.pointer =		snd_sb8_playback_pointer,
+};
+
+static snd_pcm_ops_t snd_sb8_capture_ops = {
+	.open =			snd_sb8_open,
+	.close =		snd_sb8_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_sb8_hw_params,
+	.hw_free =		snd_sb8_hw_free,
+	.prepare =		snd_sb8_capture_prepare,
+	.trigger =		snd_sb8_capture_trigger,
+	.pointer =		snd_sb8_capture_pointer,
+};
+
+static void snd_sb8dsp_pcm_free(snd_pcm_t *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+int snd_sb8dsp_pcm(sb_t *chip, int device, snd_pcm_t ** rpcm)
+{
+	snd_card_t *card = chip->card;
+	snd_pcm_t *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if ((err = snd_pcm_new(card, "SB8 DSP", device, 1, 1, &pcm)) < 0)
+		return err;
+	sprintf(pcm->name, "DSP v%i.%i", chip->version >> 8, chip->version & 0xff);
+	pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+	pcm->private_data = chip;
+	pcm->private_free = snd_sb8dsp_pcm_free;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sb8_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_sb8_capture_ops);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, 64*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_sb8dsp_pcm);
+EXPORT_SYMBOL(snd_sb8dsp_interrupt);
+  /* sb8_midi.c */
+EXPORT_SYMBOL(snd_sb8dsp_midi_interrupt);
+EXPORT_SYMBOL(snd_sb8dsp_midi);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_sb8_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_sb8_exit(void)
+{
+}
+
+module_init(alsa_sb8_init)
+module_exit(alsa_sb8_exit)
diff --git a/sound/isa/sb/sb8_midi.c b/sound/isa/sb/sb8_midi.c
new file mode 100644
index 0000000..d2c633a
--- /dev/null
+++ b/sound/isa/sb/sb8_midi.c
@@ -0,0 +1,293 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  Routines for control of SoundBlaster cards - MIDI interface
+ *
+ *   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
+ *
+ * --
+ *
+ * Sun May  9 22:54:38 BST 1999 George David Morrison <gdm@gedamo.demon.co.uk>
+ *   Fixed typo in snd_sb8dsp_midi_new_device which prevented midi from 
+ *   working.
+ *
+ * Sun May 11 12:34:56 UTC 2003 Clemens Ladisch <clemens@ladisch.de>
+ *   Added full duplex UART mode for DSP version 2.0 and later.
+ */
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+
+/*
+
+ */
+
+irqreturn_t snd_sb8dsp_midi_interrupt(sb_t * chip)
+{
+	snd_rawmidi_t *rmidi;
+	int max = 64;
+	char byte;
+
+	if (chip == NULL || (rmidi = chip->rmidi) == NULL) {
+		inb(SBP(chip, DATA_AVAIL));	/* ack interrupt */
+		return IRQ_NONE;
+	}
+	spin_lock(&chip->midi_input_lock);
+	while (max-- > 0) {
+		if (inb(SBP(chip, DATA_AVAIL)) & 0x80) {
+			byte = inb(SBP(chip, READ));
+			if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) {
+				snd_rawmidi_receive(chip->midi_substream_input, &byte, 1);
+			}
+		}
+	}
+	spin_unlock(&chip->midi_input_lock);
+	return IRQ_HANDLED;
+}
+
+/*
+
+ */
+
+static int snd_sb8dsp_midi_input_open(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip;
+	unsigned int valid_open_flags;
+
+	chip = substream->rmidi->private_data;
+	valid_open_flags = chip->hardware >= SB_HW_20
+		? SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER : 0;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (chip->open & ~valid_open_flags) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		return -EAGAIN;
+	}
+	chip->open |= SB_OPEN_MIDI_INPUT;
+	chip->midi_substream_input = substream;
+	if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		snd_sbdsp_reset(chip);		/* reset DSP */
+		if (chip->hardware >= SB_HW_20)
+			snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ);
+	} else {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+	}
+	return 0;
+}
+
+static int snd_sb8dsp_midi_output_open(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip;
+	unsigned int valid_open_flags;
+
+	chip = substream->rmidi->private_data;
+	valid_open_flags = chip->hardware >= SB_HW_20
+		? SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER : 0;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (chip->open & ~valid_open_flags) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		return -EAGAIN;
+	}
+	chip->open |= SB_OPEN_MIDI_OUTPUT;
+	chip->midi_substream_output = substream;
+	if (!(chip->open & SB_OPEN_MIDI_INPUT)) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		snd_sbdsp_reset(chip);		/* reset DSP */
+		if (chip->hardware >= SB_HW_20)
+			snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ);
+	} else {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+	}
+	return 0;
+}
+
+static int snd_sb8dsp_midi_input_close(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip;
+
+	chip = substream->rmidi->private_data;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	chip->open &= ~(SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER);
+	chip->midi_substream_input = NULL;
+	if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		snd_sbdsp_reset(chip);		/* reset DSP */
+	} else {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+	}
+	return 0;
+}
+
+static int snd_sb8dsp_midi_output_close(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip;
+
+	chip = substream->rmidi->private_data;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	chip->open &= ~(SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER);
+	chip->midi_substream_output = NULL;
+	if (!(chip->open & SB_OPEN_MIDI_INPUT)) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		snd_sbdsp_reset(chip);		/* reset DSP */
+	} else {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+	}
+	return 0;
+}
+
+static void snd_sb8dsp_midi_input_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+	unsigned long flags;
+	sb_t *chip;
+
+	chip = substream->rmidi->private_data;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (up) {
+		if (!(chip->open & SB_OPEN_MIDI_INPUT_TRIGGER)) {
+			if (chip->hardware < SB_HW_20)
+				snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ);
+			chip->open |= SB_OPEN_MIDI_INPUT_TRIGGER;
+		}
+	} else {
+		if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) {
+			if (chip->hardware < SB_HW_20)
+				snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ);
+			chip->open &= ~SB_OPEN_MIDI_INPUT_TRIGGER;
+		}
+	}
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+}
+
+static void snd_sb8dsp_midi_output_write(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip;
+	char byte;
+	int max = 32;
+
+	/* how big is Tx FIFO? */
+	chip = substream->rmidi->private_data;
+	while (max-- > 0) {
+		spin_lock_irqsave(&chip->open_lock, flags);
+		if (snd_rawmidi_transmit_peek(substream, &byte, 1) != 1) {
+			chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER;
+			del_timer(&chip->midi_timer);
+			spin_unlock_irqrestore(&chip->open_lock, flags);
+			break;
+		}
+		if (chip->hardware >= SB_HW_20) {
+			int timeout = 8;
+			while ((inb(SBP(chip, STATUS)) & 0x80) != 0 && --timeout > 0)
+				;
+			if (timeout == 0) {
+				/* Tx FIFO full - try again later */
+				spin_unlock_irqrestore(&chip->open_lock, flags);
+				break;
+			}
+			outb(byte, SBP(chip, WRITE));
+		} else {
+			snd_sbdsp_command(chip, SB_DSP_MIDI_OUTPUT);
+			snd_sbdsp_command(chip, byte);
+		}
+		snd_rawmidi_transmit_ack(substream, 1);
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+	}
+}
+
+static void snd_sb8dsp_midi_output_timer(unsigned long data)
+{
+	snd_rawmidi_substream_t * substream = (snd_rawmidi_substream_t *) data;
+	sb_t * chip = substream->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->open_lock, flags);
+	chip->midi_timer.expires = 1 + jiffies;
+	add_timer(&chip->midi_timer);
+	spin_unlock_irqrestore(&chip->open_lock, flags);	
+	snd_sb8dsp_midi_output_write(substream);
+}
+
+static void snd_sb8dsp_midi_output_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+	unsigned long flags;
+	sb_t *chip;
+
+	chip = substream->rmidi->private_data;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (up) {
+		if (!(chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER)) {
+			init_timer(&chip->midi_timer);
+			chip->midi_timer.function = snd_sb8dsp_midi_output_timer;
+			chip->midi_timer.data = (unsigned long) substream;
+			chip->midi_timer.expires = 1 + jiffies;
+			add_timer(&chip->midi_timer);
+			chip->open |= SB_OPEN_MIDI_OUTPUT_TRIGGER;
+		}
+	} else {
+		if (chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER) {
+			chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER;
+		}
+	}
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+
+	if (up)
+		snd_sb8dsp_midi_output_write(substream);
+}
+
+/*
+
+ */
+
+static snd_rawmidi_ops_t snd_sb8dsp_midi_output =
+{
+	.open =		snd_sb8dsp_midi_output_open,
+	.close =	snd_sb8dsp_midi_output_close,
+	.trigger =	snd_sb8dsp_midi_output_trigger,
+};
+
+static snd_rawmidi_ops_t snd_sb8dsp_midi_input =
+{
+	.open =		snd_sb8dsp_midi_input_open,
+	.close =	snd_sb8dsp_midi_input_close,
+	.trigger =	snd_sb8dsp_midi_input_trigger,
+};
+
+int snd_sb8dsp_midi(sb_t *chip, int device, snd_rawmidi_t ** rrawmidi)
+{
+	snd_rawmidi_t *rmidi;
+	int err;
+
+	if (rrawmidi)
+		*rrawmidi = NULL;
+	if ((err = snd_rawmidi_new(chip->card, "SB8 MIDI", device, 1, 1, &rmidi)) < 0)
+		return err;
+	strcpy(rmidi->name, "SB8 MIDI");
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_sb8dsp_midi_output);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_sb8dsp_midi_input);
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT;
+	if (chip->hardware >= SB_HW_20)
+		rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
+	rmidi->private_data = chip;
+	chip->rmidi = rmidi;
+	if (rrawmidi)
+		*rrawmidi = rmidi;
+	return 0;
+}
diff --git a/sound/isa/sb/sb_common.c b/sound/isa/sb/sb_common.c
new file mode 100644
index 0000000..5b6bde2
--- /dev/null
+++ b/sound/isa/sb/sb_common.c
@@ -0,0 +1,313 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *                   Uros Bizjak <uros@kss-loka.si>
+ *
+ *  Lowlevel routines for control of Sound Blaster cards
+ *
+ *   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/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/initval.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("ALSA lowlevel driver for Sound Blaster cards");
+MODULE_LICENSE("GPL");
+
+#define BUSY_LOOPS 100000
+
+#undef IO_DEBUG
+
+int snd_sbdsp_command(sb_t *chip, unsigned char val)
+{
+	int i;
+#ifdef IO_DEBUG
+	snd_printk("command 0x%x\n", val);
+#endif
+	for (i = BUSY_LOOPS; i; i--)
+		if ((inb(SBP(chip, STATUS)) & 0x80) == 0) {
+			outb(val, SBP(chip, COMMAND));
+			return 1;
+		}
+	snd_printd("%s [0x%lx]: timeout (0x%x)\n", __FUNCTION__, chip->port, val);
+	return 0;
+}
+
+int snd_sbdsp_get_byte(sb_t *chip)
+{
+	int val;
+	int i;
+	for (i = BUSY_LOOPS; i; i--) {
+		if (inb(SBP(chip, DATA_AVAIL)) & 0x80) {
+			val = inb(SBP(chip, READ));
+#ifdef IO_DEBUG
+			snd_printk("get_byte 0x%x\n", val);
+#endif
+			return val;
+		}
+	}
+	snd_printd("%s [0x%lx]: timeout\n", __FUNCTION__, chip->port);
+	return -ENODEV;
+}
+
+int snd_sbdsp_reset(sb_t *chip)
+{
+	int i;
+
+	outb(1, SBP(chip, RESET));
+	udelay(10);
+	outb(0, SBP(chip, RESET));
+	udelay(30);
+	for (i = BUSY_LOOPS; i; i--)
+		if (inb(SBP(chip, DATA_AVAIL)) & 0x80) {
+			if (inb(SBP(chip, READ)) == 0xaa)
+				return 0;
+			else
+				break;
+		}
+	snd_printdd("%s [0x%lx] failed...\n", __FUNCTION__, chip->port);
+	return -ENODEV;
+}
+
+static int snd_sbdsp_version(sb_t * chip)
+{
+	unsigned int result = -ENODEV;
+
+	snd_sbdsp_command(chip, SB_DSP_GET_VERSION);
+	result = (short) snd_sbdsp_get_byte(chip) << 8;
+	result |= (short) snd_sbdsp_get_byte(chip);
+	return result;
+}
+
+static int snd_sbdsp_probe(sb_t * chip)
+{
+	int version;
+	int major, minor;
+	char *str;
+	unsigned long flags;
+
+	/*
+	 *  initialization sequence
+	 */
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (snd_sbdsp_reset(chip) < 0) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return -ENODEV;
+	}
+	version = snd_sbdsp_version(chip);
+	if (version < 0) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return -ENODEV;
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	major = version >> 8;
+	minor = version & 0xff;
+	snd_printdd("SB [0x%lx]: DSP chip found, version = %i.%i\n",
+		    chip->port, major, minor);
+	
+	switch (chip->hardware) {
+	case SB_HW_AUTO:
+		switch (major) {
+		case 1:
+			chip->hardware = SB_HW_10;
+			str = "1.0";
+			break;
+		case 2:
+			if (minor) {
+				chip->hardware = SB_HW_201;
+				str = "2.01+";
+			} else {
+				chip->hardware = SB_HW_20;
+				str = "2.0";
+			}
+			break;
+		case 3:
+			chip->hardware = SB_HW_PRO;
+			str = "Pro";
+			break;
+		case 4:
+			chip->hardware = SB_HW_16;
+			str = "16";
+			break;
+		default:
+			snd_printk("SB [0x%lx]: unknown DSP chip version %i.%i\n",
+				   chip->port, major, minor);
+			return -ENODEV;
+		}
+		break;
+	case SB_HW_ALS100:
+		str = "16 (ALS-100)";
+		break;
+	case SB_HW_ALS4000:
+		str = "16 (ALS-4000)";
+		break;
+	case SB_HW_DT019X:
+		str = "(DT019X/ALS007)";
+		break;
+	default:
+		return -ENODEV;
+	}
+	sprintf(chip->name, "Sound Blaster %s", str);
+	chip->version = (major << 8) | minor;
+	return 0;
+}
+
+static int snd_sbdsp_free(sb_t *chip)
+{
+	if (chip->res_port) {
+		release_resource(chip->res_port);
+		kfree_nocheck(chip->res_port);
+	}
+	if (chip->irq >= 0)
+		free_irq(chip->irq, (void *) chip);
+#ifdef CONFIG_ISA
+	if (chip->dma8 >= 0) {
+		disable_dma(chip->dma8);
+		free_dma(chip->dma8);
+	}
+	if (chip->dma16 >= 0 && chip->dma16 != chip->dma8) {
+		disable_dma(chip->dma16);
+		free_dma(chip->dma16);
+	}
+#endif
+	kfree(chip);
+	return 0;
+}
+
+static int snd_sbdsp_dev_free(snd_device_t *device)
+{
+	sb_t *chip = device->device_data;
+	return snd_sbdsp_free(chip);
+}
+
+int snd_sbdsp_create(snd_card_t *card,
+		     unsigned long port,
+		     int irq,
+		     irqreturn_t (*irq_handler)(int, void *, struct pt_regs *),
+		     int dma8,
+		     int dma16,
+		     unsigned short hardware,
+		     sb_t **r_chip)
+{
+	sb_t *chip;
+	int err;
+	static snd_device_ops_t ops = {
+		.dev_free =	snd_sbdsp_dev_free,
+	};
+
+	snd_assert(r_chip != NULL, return -EINVAL);
+	*r_chip = NULL;
+	chip = kcalloc(1, sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+	spin_lock_init(&chip->reg_lock);
+	spin_lock_init(&chip->open_lock);
+	spin_lock_init(&chip->midi_input_lock);
+	spin_lock_init(&chip->mixer_lock);
+	chip->irq = -1;
+	chip->dma8 = -1;
+	chip->dma16 = -1;
+	chip->port = port;
+	
+	if (request_irq(irq, irq_handler, hardware == SB_HW_ALS4000 ?
+			SA_INTERRUPT | SA_SHIRQ : SA_INTERRUPT,
+			"SoundBlaster", (void *) chip)) {
+		snd_printk(KERN_ERR "sb: can't grab irq %d\n", irq);
+		snd_sbdsp_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = irq;
+
+	if (hardware == SB_HW_ALS4000)
+		goto __skip_allocation;
+	
+	if ((chip->res_port = request_region(port, 16, "SoundBlaster")) == NULL) {
+		snd_printk(KERN_ERR "sb: can't grab port 0x%lx\n", port);
+		snd_sbdsp_free(chip);
+		return -EBUSY;
+	}
+
+#ifdef CONFIG_ISA
+	if (dma8 >= 0 && request_dma(dma8, "SoundBlaster - 8bit")) {
+		snd_printk(KERN_ERR "sb: can't grab DMA8 %d\n", dma8);
+		snd_sbdsp_free(chip);
+		return -EBUSY;
+	}
+	chip->dma8 = dma8;
+	if (dma16 >= 0) {
+		if (hardware != SB_HW_ALS100 && (dma16 < 5 || dma16 > 7)) {
+			/* no duplex */
+			dma16 = -1;
+		} else if (request_dma(dma16, "SoundBlaster - 16bit")) {
+			snd_printk(KERN_ERR "sb: can't grab DMA16 %d\n", dma16);
+			snd_sbdsp_free(chip);
+			return -EBUSY;
+		}
+	}
+	chip->dma16 = dma16;
+#endif
+
+      __skip_allocation:
+	chip->card = card;
+	chip->hardware = hardware;
+	if ((err = snd_sbdsp_probe(chip)) < 0) {
+		snd_sbdsp_free(chip);
+		return err;
+	}
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_sbdsp_free(chip);
+		return err;
+	}
+	*r_chip = chip;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_sbdsp_command);
+EXPORT_SYMBOL(snd_sbdsp_get_byte);
+EXPORT_SYMBOL(snd_sbdsp_reset);
+EXPORT_SYMBOL(snd_sbdsp_create);
+/* sb_mixer.c */
+EXPORT_SYMBOL(snd_sbmixer_write);
+EXPORT_SYMBOL(snd_sbmixer_read);
+EXPORT_SYMBOL(snd_sbmixer_new);
+EXPORT_SYMBOL(snd_sbmixer_add_ctl);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_sb_common_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_sb_common_exit(void)
+{
+}
+
+module_init(alsa_sb_common_init)
+module_exit(alsa_sb_common_exit)
diff --git a/sound/isa/sb/sb_mixer.c b/sound/isa/sb/sb_mixer.c
new file mode 100644
index 0000000..cc5a2c6
--- /dev/null
+++ b/sound/isa/sb/sb_mixer.c
@@ -0,0 +1,844 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  Routines for Sound Blaster mixer control
+ *
+ *
+ *   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/delay.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/control.h>
+
+#undef IO_DEBUG
+
+void snd_sbmixer_write(sb_t *chip, unsigned char reg, unsigned char data)
+{
+	outb(reg, SBP(chip, MIXER_ADDR));
+	udelay(10);
+	outb(data, SBP(chip, MIXER_DATA));
+	udelay(10);
+#ifdef IO_DEBUG
+	snd_printk("mixer_write 0x%x 0x%x\n", reg, data);
+#endif
+}
+
+unsigned char snd_sbmixer_read(sb_t *chip, unsigned char reg)
+{
+	unsigned char result;
+
+	outb(reg, SBP(chip, MIXER_ADDR));
+	udelay(10);
+	result = inb(SBP(chip, MIXER_DATA));
+	udelay(10);
+#ifdef IO_DEBUG
+	snd_printk("mixer_read 0x%x 0x%x\n", reg, result);
+#endif
+	return result;
+}
+
+/*
+ * Single channel mixer element
+ */
+
+static int snd_sbmixer_info_single(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_sbmixer_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 16) & 0xff;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	unsigned char val;
+
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	val = (snd_sbmixer_read(sb, reg) >> shift) & mask;
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	ucontrol->value.integer.value[0] = val;
+	return 0;
+}
+
+static int snd_sbmixer_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 16) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned char val, oval;
+
+	val = (ucontrol->value.integer.value[0] & mask) << shift;
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, reg);
+	val = (oval & ~(mask << shift)) | val;
+	change = val != oval;
+	if (change)
+		snd_sbmixer_write(sb, reg, val);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	return change;
+}
+
+/*
+ * Double channel mixer element
+ */
+
+static int snd_sbmixer_info_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_sbmixer_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int left_shift = (kcontrol->private_value >> 16) & 0x07;
+	int right_shift = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	unsigned char left, right;
+
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	left = (snd_sbmixer_read(sb, left_reg) >> left_shift) & mask;
+	right = (snd_sbmixer_read(sb, right_reg) >> right_shift) & mask;
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	ucontrol->value.integer.value[0] = left;
+	ucontrol->value.integer.value[1] = right;
+	return 0;
+}
+
+static int snd_sbmixer_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int left_shift = (kcontrol->private_value >> 16) & 0x07;
+	int right_shift = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned char left, right, oleft, oright;
+
+	left = (ucontrol->value.integer.value[0] & mask) << left_shift;
+	right = (ucontrol->value.integer.value[1] & mask) << right_shift;
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	if (left_reg == right_reg) {
+		oleft = snd_sbmixer_read(sb, left_reg);
+		left = (oleft & ~((mask << left_shift) | (mask << right_shift))) | left | right;
+		change = left != oleft;
+		if (change)
+			snd_sbmixer_write(sb, left_reg, left);
+	} else {
+		oleft = snd_sbmixer_read(sb, left_reg);
+		oright = snd_sbmixer_read(sb, right_reg);
+		left = (oleft & ~(mask << left_shift)) | left;
+		right = (oright & ~(mask << right_shift)) | right;
+		change = left != oleft || right != oright;
+		if (change) {
+			snd_sbmixer_write(sb, left_reg, left);
+			snd_sbmixer_write(sb, right_reg, right);
+		}
+	}
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	return change;
+}
+
+/*
+ * DT-019x / ALS-007 capture/input switch
+ */
+
+static int snd_dt019x_input_sw_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	static char *texts[5] = {
+		"CD", "Mic", "Line", "Synth", "Master"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 5;
+	if (uinfo->value.enumerated.item > 4)
+		uinfo->value.enumerated.item = 4;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_dt019x_input_sw_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned char oval;
+	
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, SB_DT019X_CAPTURE_SW);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	switch (oval & 0x07) {
+	case SB_DT019X_CAP_CD:
+		ucontrol->value.enumerated.item[0] = 0;
+		break;
+	case SB_DT019X_CAP_MIC:
+		ucontrol->value.enumerated.item[0] = 1;
+		break;
+	case SB_DT019X_CAP_LINE:
+		ucontrol->value.enumerated.item[0] = 2;
+		break;
+	case SB_DT019X_CAP_MAIN:
+		ucontrol->value.enumerated.item[0] = 4;
+		break;
+	/* To record the synth on these cards you must record the main.   */
+	/* Thus SB_DT019X_CAP_SYNTH == SB_DT019X_CAP_MAIN and would cause */
+	/* duplicate case labels if left uncommented. */
+	/* case SB_DT019X_CAP_SYNTH:
+	 *	ucontrol->value.enumerated.item[0] = 3;
+	 *	break;
+	 */
+	default:
+		ucontrol->value.enumerated.item[0] = 4;
+		break;
+	}
+	return 0;
+}
+
+static int snd_dt019x_input_sw_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned char nval, oval;
+	
+	if (ucontrol->value.enumerated.item[0] > 4)
+		return -EINVAL;
+	switch (ucontrol->value.enumerated.item[0]) {
+	case 0:
+		nval = SB_DT019X_CAP_CD;
+		break;
+	case 1:
+		nval = SB_DT019X_CAP_MIC;
+		break;
+	case 2:
+		nval = SB_DT019X_CAP_LINE;
+		break;
+	case 3:
+		nval = SB_DT019X_CAP_SYNTH;
+		break;
+	case 4:
+		nval = SB_DT019X_CAP_MAIN;
+		break;
+	default:
+		nval = SB_DT019X_CAP_MAIN;
+	}
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, SB_DT019X_CAPTURE_SW);
+	change = nval != oval;
+	if (change)
+		snd_sbmixer_write(sb, SB_DT019X_CAPTURE_SW, nval);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	return change;
+}
+
+/*
+ * SBPRO input multiplexer
+ */
+
+static int snd_sb8mixer_info_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	static char *texts[3] = {
+		"Mic", "CD", "Line"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item > 2)
+		uinfo->value.enumerated.item = 2;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+
+static int snd_sb8mixer_get_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned char oval;
+	
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, SB_DSP_CAPTURE_SOURCE);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	switch ((oval >> 0x01) & 0x03) {
+	case SB_DSP_MIXS_CD:
+		ucontrol->value.enumerated.item[0] = 1;
+		break;
+	case SB_DSP_MIXS_LINE:
+		ucontrol->value.enumerated.item[0] = 2;
+		break;
+	default:
+		ucontrol->value.enumerated.item[0] = 0;
+		break;
+	}
+	return 0;
+}
+
+static int snd_sb8mixer_put_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned char nval, oval;
+	
+	if (ucontrol->value.enumerated.item[0] > 2)
+		return -EINVAL;
+	switch (ucontrol->value.enumerated.item[0]) {
+	case 1:
+		nval = SB_DSP_MIXS_CD;
+		break;
+	case 2:
+		nval = SB_DSP_MIXS_LINE;
+		break;
+	default:
+		nval = SB_DSP_MIXS_MIC;
+	}
+	nval <<= 1;
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, SB_DSP_CAPTURE_SOURCE);
+	nval |= oval & ~0x06;
+	change = nval != oval;
+	if (change)
+		snd_sbmixer_write(sb, SB_DSP_CAPTURE_SOURCE, nval);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	return change;
+}
+
+/*
+ * SB16 input switch
+ */
+
+static int snd_sb16mixer_info_input_sw(snd_kcontrol_t * kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 4;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int snd_sb16mixer_get_input_sw(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg1 = kcontrol->private_value & 0xff;
+	int reg2 = (kcontrol->private_value >> 8) & 0xff;
+	int left_shift = (kcontrol->private_value >> 16) & 0x0f;
+	int right_shift = (kcontrol->private_value >> 24) & 0x0f;
+	unsigned char val1, val2;
+
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	val1 = snd_sbmixer_read(sb, reg1);
+	val2 = snd_sbmixer_read(sb, reg2);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	ucontrol->value.integer.value[0] = (val1 >> left_shift) & 0x01;
+	ucontrol->value.integer.value[1] = (val2 >> left_shift) & 0x01;
+	ucontrol->value.integer.value[2] = (val1 >> right_shift) & 0x01;
+	ucontrol->value.integer.value[3] = (val2 >> right_shift) & 0x01;
+	return 0;
+}                                                                                                                   
+
+static int snd_sb16mixer_put_input_sw(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg1 = kcontrol->private_value & 0xff;
+	int reg2 = (kcontrol->private_value >> 8) & 0xff;
+	int left_shift = (kcontrol->private_value >> 16) & 0x0f;
+	int right_shift = (kcontrol->private_value >> 24) & 0x0f;
+	int change;
+	unsigned char val1, val2, oval1, oval2;
+
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval1 = snd_sbmixer_read(sb, reg1);
+	oval2 = snd_sbmixer_read(sb, reg2);
+	val1 = oval1 & ~((1 << left_shift) | (1 << right_shift));
+	val2 = oval2 & ~((1 << left_shift) | (1 << right_shift));
+	val1 |= (ucontrol->value.integer.value[0] & 1) << left_shift;
+	val2 |= (ucontrol->value.integer.value[1] & 1) << left_shift;
+	val1 |= (ucontrol->value.integer.value[2] & 1) << right_shift;
+	val2 |= (ucontrol->value.integer.value[3] & 1) << right_shift;
+	change = val1 != oval1 || val2 != oval2;
+	if (change) {
+		snd_sbmixer_write(sb, reg1, val1);
+		snd_sbmixer_write(sb, reg2, val2);
+	}
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	return change;
+}
+
+
+/*
+ */
+/*
+ */
+int snd_sbmixer_add_ctl(sb_t *chip, const char *name, int index, int type, unsigned long value)
+{
+	static snd_kcontrol_new_t newctls[] = {
+		[SB_MIX_SINGLE] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_sbmixer_info_single,
+			.get = snd_sbmixer_get_single,
+			.put = snd_sbmixer_put_single,
+		},
+		[SB_MIX_DOUBLE] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_sbmixer_info_double,
+			.get = snd_sbmixer_get_double,
+			.put = snd_sbmixer_put_double,
+		},
+		[SB_MIX_INPUT_SW] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_sb16mixer_info_input_sw,
+			.get = snd_sb16mixer_get_input_sw,
+			.put = snd_sb16mixer_put_input_sw,
+		},
+		[SB_MIX_CAPTURE_PRO] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_sb8mixer_info_mux,
+			.get = snd_sb8mixer_get_mux,
+			.put = snd_sb8mixer_put_mux,
+		},
+		[SB_MIX_CAPTURE_DT019X] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_dt019x_input_sw_info,
+			.get = snd_dt019x_input_sw_get,
+			.put = snd_dt019x_input_sw_put,
+		},
+	};
+	snd_kcontrol_t *ctl;
+	int err;
+
+	ctl = snd_ctl_new1(&newctls[type], chip);
+	if (! ctl)
+		return -ENOMEM;
+	strlcpy(ctl->id.name, name, sizeof(ctl->id.name));
+	ctl->id.index = index;
+	ctl->private_value = value;
+	if ((err = snd_ctl_add(chip->card, ctl)) < 0) {
+		snd_ctl_free_one(ctl);
+		return err;
+	}
+	return 0;
+}
+
+/*
+ * SB 2.0 specific mixer elements
+ */
+
+static struct sbmix_elem snd_sb20_ctl_master_play_vol =
+	SB_SINGLE("Master Playback Volume", SB_DSP20_MASTER_DEV, 1, 7);
+static struct sbmix_elem snd_sb20_ctl_pcm_play_vol =
+	SB_SINGLE("PCM Playback Volume", SB_DSP20_PCM_DEV, 1, 3);
+static struct sbmix_elem snd_sb20_ctl_synth_play_vol =
+	SB_SINGLE("Synth Playback Volume", SB_DSP20_FM_DEV, 1, 7);
+static struct sbmix_elem snd_sb20_ctl_cd_play_vol =
+	SB_SINGLE("CD Playback Volume", SB_DSP20_CD_DEV, 1, 7);
+
+static struct sbmix_elem *snd_sb20_controls[] = {
+	&snd_sb20_ctl_master_play_vol,
+	&snd_sb20_ctl_pcm_play_vol,
+	&snd_sb20_ctl_synth_play_vol,
+	&snd_sb20_ctl_cd_play_vol
+};
+
+static unsigned char snd_sb20_init_values[][2] = {
+	{ SB_DSP20_MASTER_DEV, 0 },
+	{ SB_DSP20_FM_DEV, 0 },
+};
+
+/*
+ * SB Pro specific mixer elements
+ */
+static struct sbmix_elem snd_sbpro_ctl_master_play_vol =
+	SB_DOUBLE("Master Playback Volume", SB_DSP_MASTER_DEV, SB_DSP_MASTER_DEV, 5, 1, 7);
+static struct sbmix_elem snd_sbpro_ctl_pcm_play_vol =
+	SB_DOUBLE("PCM Playback Volume", SB_DSP_PCM_DEV, SB_DSP_PCM_DEV, 5, 1, 7);
+static struct sbmix_elem snd_sbpro_ctl_pcm_play_filter =
+	SB_SINGLE("PCM Playback Filter", SB_DSP_PLAYBACK_FILT, 5, 1);
+static struct sbmix_elem snd_sbpro_ctl_synth_play_vol =
+	SB_DOUBLE("Synth Playback Volume", SB_DSP_FM_DEV, SB_DSP_FM_DEV, 5, 1, 7);
+static struct sbmix_elem snd_sbpro_ctl_cd_play_vol =
+	SB_DOUBLE("CD Playback Volume", SB_DSP_CD_DEV, SB_DSP_CD_DEV, 5, 1, 7);
+static struct sbmix_elem snd_sbpro_ctl_line_play_vol =
+	SB_DOUBLE("Line Playback Volume", SB_DSP_LINE_DEV, SB_DSP_LINE_DEV, 5, 1, 7);
+static struct sbmix_elem snd_sbpro_ctl_mic_play_vol =
+	SB_SINGLE("Mic Playback Volume", SB_DSP_MIC_DEV, 1, 3);
+static struct sbmix_elem snd_sbpro_ctl_capture_source =
+	{
+		.name = "Capture Source",
+		.type = SB_MIX_CAPTURE_PRO
+	};
+static struct sbmix_elem snd_sbpro_ctl_capture_filter =
+	SB_SINGLE("Capture Filter", SB_DSP_CAPTURE_FILT, 5, 1);
+static struct sbmix_elem snd_sbpro_ctl_capture_low_filter =
+	SB_SINGLE("Capture Low-Pass Filter", SB_DSP_CAPTURE_FILT, 3, 1);
+
+static struct sbmix_elem *snd_sbpro_controls[] = {
+	&snd_sbpro_ctl_master_play_vol,
+	&snd_sbpro_ctl_pcm_play_vol,
+	&snd_sbpro_ctl_pcm_play_filter,
+	&snd_sbpro_ctl_synth_play_vol,
+	&snd_sbpro_ctl_cd_play_vol,
+	&snd_sbpro_ctl_line_play_vol,
+	&snd_sbpro_ctl_mic_play_vol,
+	&snd_sbpro_ctl_capture_source,
+	&snd_sbpro_ctl_capture_filter,
+	&snd_sbpro_ctl_capture_low_filter
+};
+
+static unsigned char snd_sbpro_init_values[][2] = {
+	{ SB_DSP_MASTER_DEV, 0 },
+	{ SB_DSP_PCM_DEV, 0 },
+	{ SB_DSP_FM_DEV, 0 },
+};
+
+/*
+ * SB16 specific mixer elements
+ */
+static struct sbmix_elem snd_sb16_ctl_master_play_vol =
+	SB_DOUBLE("Master Playback Volume", SB_DSP4_MASTER_DEV, (SB_DSP4_MASTER_DEV + 1), 3, 3, 31);
+static struct sbmix_elem snd_sb16_ctl_3d_enhance_switch =
+	SB_SINGLE("3D Enhancement Switch", SB_DSP4_3DSE, 0, 1);
+static struct sbmix_elem snd_sb16_ctl_tone_bass =
+	SB_DOUBLE("Tone Control - Bass", SB_DSP4_BASS_DEV, (SB_DSP4_BASS_DEV + 1), 4, 4, 15);
+static struct sbmix_elem snd_sb16_ctl_tone_treble =
+	SB_DOUBLE("Tone Control - Treble", SB_DSP4_TREBLE_DEV, (SB_DSP4_TREBLE_DEV + 1), 4, 4, 15);
+static struct sbmix_elem snd_sb16_ctl_pcm_play_vol =
+	SB_DOUBLE("PCM Playback Volume", SB_DSP4_PCM_DEV, (SB_DSP4_PCM_DEV + 1), 3, 3, 31);
+static struct sbmix_elem snd_sb16_ctl_synth_capture_route =
+	SB16_INPUT_SW("Synth Capture Route", SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 6, 5);
+static struct sbmix_elem snd_sb16_ctl_synth_play_vol =
+	SB_DOUBLE("Synth Playback Volume", SB_DSP4_SYNTH_DEV, (SB_DSP4_SYNTH_DEV + 1), 3, 3, 31);
+static struct sbmix_elem snd_sb16_ctl_cd_capture_route =
+	SB16_INPUT_SW("CD Capture Route", SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 2, 1);
+static struct sbmix_elem snd_sb16_ctl_cd_play_switch =
+	SB_DOUBLE("CD Playback Switch", SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 2, 1, 1);
+static struct sbmix_elem snd_sb16_ctl_cd_play_vol =
+	SB_DOUBLE("CD Playback Volume", SB_DSP4_CD_DEV, (SB_DSP4_CD_DEV + 1), 3, 3, 31);
+static struct sbmix_elem snd_sb16_ctl_line_capture_route =
+	SB16_INPUT_SW("Line Capture Route", SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 4, 3);
+static struct sbmix_elem snd_sb16_ctl_line_play_switch =
+	SB_DOUBLE("Line Playback Switch", SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 4, 3, 1);
+static struct sbmix_elem snd_sb16_ctl_line_play_vol =
+	SB_DOUBLE("Line Playback Volume", SB_DSP4_LINE_DEV, (SB_DSP4_LINE_DEV + 1), 3, 3, 31);
+static struct sbmix_elem snd_sb16_ctl_mic_capture_route =
+	SB16_INPUT_SW("Mic Capture Route", SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 0, 0);
+static struct sbmix_elem snd_sb16_ctl_mic_play_switch =
+	SB_SINGLE("Mic Playback Switch", SB_DSP4_OUTPUT_SW, 0, 1);
+static struct sbmix_elem snd_sb16_ctl_mic_play_vol =
+	SB_SINGLE("Mic Playback Volume", SB_DSP4_MIC_DEV, 3, 31);
+static struct sbmix_elem snd_sb16_ctl_pc_speaker_vol =
+	SB_SINGLE("PC Speaker Volume", SB_DSP4_SPEAKER_DEV, 6, 3);
+static struct sbmix_elem snd_sb16_ctl_capture_vol =
+	SB_DOUBLE("Capture Volume", SB_DSP4_IGAIN_DEV, (SB_DSP4_IGAIN_DEV + 1), 6, 6, 3);
+static struct sbmix_elem snd_sb16_ctl_play_vol =
+	SB_DOUBLE("Playback Volume", SB_DSP4_OGAIN_DEV, (SB_DSP4_OGAIN_DEV + 1), 6, 6, 3);
+static struct sbmix_elem snd_sb16_ctl_auto_mic_gain =
+	SB_SINGLE("Mic Auto Gain", SB_DSP4_MIC_AGC, 0, 1);
+
+static struct sbmix_elem *snd_sb16_controls[] = {
+	&snd_sb16_ctl_master_play_vol,
+	&snd_sb16_ctl_3d_enhance_switch,
+	&snd_sb16_ctl_tone_bass,
+	&snd_sb16_ctl_tone_treble,
+	&snd_sb16_ctl_pcm_play_vol,
+	&snd_sb16_ctl_synth_capture_route,
+	&snd_sb16_ctl_synth_play_vol,
+	&snd_sb16_ctl_cd_capture_route,
+	&snd_sb16_ctl_cd_play_switch,
+	&snd_sb16_ctl_cd_play_vol,
+	&snd_sb16_ctl_line_capture_route,
+	&snd_sb16_ctl_line_play_switch,
+	&snd_sb16_ctl_line_play_vol,
+	&snd_sb16_ctl_mic_capture_route,
+	&snd_sb16_ctl_mic_play_switch,
+	&snd_sb16_ctl_mic_play_vol,
+	&snd_sb16_ctl_pc_speaker_vol,
+	&snd_sb16_ctl_capture_vol,
+	&snd_sb16_ctl_play_vol,
+	&snd_sb16_ctl_auto_mic_gain
+};
+
+static unsigned char snd_sb16_init_values[][2] = {
+	{ SB_DSP4_MASTER_DEV + 0, 0 },
+	{ SB_DSP4_MASTER_DEV + 1, 0 },
+	{ SB_DSP4_PCM_DEV + 0, 0 },
+	{ SB_DSP4_PCM_DEV + 1, 0 },
+	{ SB_DSP4_SYNTH_DEV + 0, 0 },
+	{ SB_DSP4_SYNTH_DEV + 1, 0 },
+	{ SB_DSP4_INPUT_LEFT, 0 },
+	{ SB_DSP4_INPUT_RIGHT, 0 },
+	{ SB_DSP4_OUTPUT_SW, 0 },
+	{ SB_DSP4_SPEAKER_DEV, 0 },
+};
+
+/*
+ * DT019x specific mixer elements
+ */
+static struct sbmix_elem snd_dt019x_ctl_master_play_vol =
+	SB_DOUBLE("Master Playback Volume", SB_DT019X_MASTER_DEV, SB_DT019X_MASTER_DEV, 4,0, 15);
+static struct sbmix_elem snd_dt019x_ctl_pcm_play_vol =
+	SB_DOUBLE("PCM Playback Volume", SB_DT019X_PCM_DEV, SB_DT019X_PCM_DEV, 4,0, 15);
+static struct sbmix_elem snd_dt019x_ctl_synth_play_vol =
+	SB_DOUBLE("Synth Playback Volume", SB_DT019X_SYNTH_DEV, SB_DT019X_SYNTH_DEV, 4,0, 15);
+static struct sbmix_elem snd_dt019x_ctl_cd_play_vol =
+	SB_DOUBLE("CD Playback Volume", SB_DT019X_CD_DEV, SB_DT019X_CD_DEV, 4,0, 15);
+static struct sbmix_elem snd_dt019x_ctl_mic_play_vol =
+	SB_SINGLE("Mic Playback Volume", SB_DT019X_MIC_DEV, 4, 7);
+static struct sbmix_elem snd_dt019x_ctl_pc_speaker_vol =
+	SB_SINGLE("PC Speaker Volume", SB_DT019X_SPKR_DEV, 0,  7);
+static struct sbmix_elem snd_dt019x_ctl_line_play_vol =
+	SB_DOUBLE("Line Playback Volume", SB_DT019X_LINE_DEV, SB_DT019X_LINE_DEV, 4,0, 15);
+static struct sbmix_elem snd_dt019x_ctl_pcm_play_switch =
+	SB_DOUBLE("PCM Playback Switch", SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 2,1, 1);
+static struct sbmix_elem snd_dt019x_ctl_synth_play_switch =
+	SB_DOUBLE("Synth Playback Switch", SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 4,3, 1);
+static struct sbmix_elem snd_dt019x_ctl_capture_source =
+	{
+		.name = "Capture Source",
+		.type = SB_MIX_CAPTURE_DT019X
+	};
+
+static struct sbmix_elem *snd_dt019x_controls[] = {
+	&snd_dt019x_ctl_master_play_vol,
+	&snd_dt019x_ctl_pcm_play_vol,
+	&snd_dt019x_ctl_synth_play_vol,
+	&snd_dt019x_ctl_cd_play_vol,
+	&snd_dt019x_ctl_mic_play_vol,
+	&snd_dt019x_ctl_pc_speaker_vol,
+	&snd_dt019x_ctl_line_play_vol,
+	&snd_sb16_ctl_mic_play_switch,
+	&snd_sb16_ctl_cd_play_switch,
+	&snd_sb16_ctl_line_play_switch,
+	&snd_dt019x_ctl_pcm_play_switch,
+	&snd_dt019x_ctl_synth_play_switch,
+	&snd_dt019x_ctl_capture_source
+};
+
+static unsigned char snd_dt019x_init_values[][2] = {
+        { SB_DT019X_MASTER_DEV, 0 },
+        { SB_DT019X_PCM_DEV, 0 },
+        { SB_DT019X_SYNTH_DEV, 0 },
+        { SB_DT019X_CD_DEV, 0 },
+        { SB_DT019X_MIC_DEV, 0 },	/* Includes PC-speaker in high nibble */
+        { SB_DT019X_LINE_DEV, 0 },
+        { SB_DSP4_OUTPUT_SW, 0 },
+        { SB_DT019X_OUTPUT_SW2, 0 },
+        { SB_DT019X_CAPTURE_SW, 0x06 },
+};
+
+/*
+ * ALS4000 specific mixer elements
+ */
+/* FIXME: SB_ALS4000_MONO_IO_CTRL needs output select ctrl ! */
+static struct sbmix_elem snd_als4000_ctl_mono_output_switch =
+	SB_SINGLE("Mono Output Switch", SB_ALS4000_MONO_IO_CTRL, 5, 1);
+/* FIXME: mono input switch also available on DT019X ? */
+static struct sbmix_elem snd_als4000_ctl_mono_input_switch =
+	SB_SINGLE("Mono Input Switch", SB_DT019X_OUTPUT_SW2, 0, 1);
+static struct sbmix_elem snd_als4000_ctl_mic_20db_boost =
+	SB_SINGLE("Mic Boost (+20dB)", SB_ALS4000_MIC_IN_GAIN, 0, 0x03);
+static struct sbmix_elem snd_als4000_ctl_mixer_out_to_in =
+	SB_SINGLE("Mixer Out To In", SB_ALS4000_MIC_IN_GAIN, 7, 0x01);
+/* FIXME: 3D needs much more sophisticated controls, many more features ! */
+static struct sbmix_elem snd_als4000_ctl_3d_output_switch =
+	SB_SINGLE("3D Output Switch", SB_ALS4000_3D_SND_FX, 6, 0x01);
+static struct sbmix_elem snd_als4000_ctl_3d_output_ratio =
+	SB_SINGLE("3D Output Ratio", SB_ALS4000_3D_SND_FX, 0, 0x07);
+static struct sbmix_elem snd_als4000_ctl_3d_poweroff_switch =
+	SB_SINGLE("3D PowerOff Switch", SB_ALS4000_3D_TIME_DELAY, 4, 0x01);
+static struct sbmix_elem snd_als4000_ctl_3d_delay =
+	SB_SINGLE("3D Delay", SB_ALS4000_3D_TIME_DELAY, 0, 0x0f);
+#if NOT_AVAILABLE
+static struct sbmix_elem snd_als4000_ctl_fmdac =
+	SB_SINGLE("FMDAC Switch (Option ?)", SB_ALS4000_FMDAC, 0, 0x01);
+static struct sbmix_elem snd_als4000_ctl_qsound =
+	SB_SINGLE("QSound Mode", SB_ALS4000_QSOUND, 1, 0x1f);
+#endif
+
+static struct sbmix_elem *snd_als4000_controls[] = {
+	&snd_sb16_ctl_master_play_vol,
+	&snd_dt019x_ctl_pcm_play_switch,
+	&snd_sb16_ctl_pcm_play_vol,
+	&snd_sb16_ctl_synth_capture_route,
+	&snd_dt019x_ctl_synth_play_switch,
+	&snd_sb16_ctl_synth_play_vol,
+	&snd_sb16_ctl_cd_capture_route,
+	&snd_sb16_ctl_cd_play_switch,
+	&snd_sb16_ctl_cd_play_vol,
+	&snd_sb16_ctl_line_capture_route,
+	&snd_sb16_ctl_line_play_switch,
+	&snd_sb16_ctl_line_play_vol,
+	&snd_sb16_ctl_mic_capture_route,
+	&snd_als4000_ctl_mic_20db_boost,
+	&snd_sb16_ctl_auto_mic_gain,
+	&snd_sb16_ctl_mic_play_switch,
+	&snd_sb16_ctl_mic_play_vol,
+	&snd_sb16_ctl_pc_speaker_vol,
+	&snd_sb16_ctl_capture_vol,
+	&snd_sb16_ctl_play_vol,
+	&snd_als4000_ctl_mono_output_switch,
+	&snd_als4000_ctl_mono_input_switch,
+	&snd_als4000_ctl_mixer_out_to_in,
+	&snd_als4000_ctl_3d_output_switch,
+	&snd_als4000_ctl_3d_output_ratio,
+	&snd_als4000_ctl_3d_delay,
+	&snd_als4000_ctl_3d_poweroff_switch,
+#if NOT_AVAILABLE
+	&snd_als4000_ctl_fmdac,
+	&snd_als4000_ctl_qsound,
+#endif
+};
+
+static unsigned char snd_als4000_init_values[][2] = {
+	{ SB_DSP4_MASTER_DEV + 0, 0 },
+	{ SB_DSP4_MASTER_DEV + 1, 0 },
+	{ SB_DSP4_PCM_DEV + 0, 0 },
+	{ SB_DSP4_PCM_DEV + 1, 0 },
+	{ SB_DSP4_SYNTH_DEV + 0, 0 },
+	{ SB_DSP4_SYNTH_DEV + 1, 0 },
+	{ SB_DSP4_SPEAKER_DEV, 0 },
+	{ SB_DSP4_OUTPUT_SW, 0 },
+	{ SB_DSP4_INPUT_LEFT, 0 },
+	{ SB_DSP4_INPUT_RIGHT, 0 },
+	{ SB_DT019X_OUTPUT_SW2, 0 },
+	{ SB_ALS4000_MIC_IN_GAIN, 0 },
+};
+
+
+/*
+ */
+static int snd_sbmixer_init(sb_t *chip,
+			    struct sbmix_elem **controls,
+			    int controls_count,
+			    unsigned char map[][2],
+			    int map_count,
+			    char *name)
+{
+	unsigned long flags;
+	snd_card_t *card = chip->card;
+	int idx, err;
+
+	/* mixer reset */
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+	snd_sbmixer_write(chip, 0x00, 0x00);
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+
+	/* mute and zero volume channels */
+	for (idx = 0; idx < map_count; idx++) {
+		spin_lock_irqsave(&chip->mixer_lock, flags);
+		snd_sbmixer_write(chip, map[idx][0], map[idx][1]);
+		spin_unlock_irqrestore(&chip->mixer_lock, flags);
+	}
+
+	for (idx = 0; idx < controls_count; idx++) {
+		if ((err = snd_sbmixer_add_ctl_elem(chip, controls[idx])) < 0)
+			return err;
+	}
+	snd_component_add(card, name);
+	strcpy(card->mixername, name);
+	return 0;
+}
+
+int snd_sbmixer_new(sb_t *chip)
+{
+	snd_card_t * card;
+	int err;
+
+	snd_assert(chip != NULL && chip->card != NULL, return -EINVAL);
+
+	card = chip->card;
+
+	switch (chip->hardware) {
+	case SB_HW_10:
+		return 0; /* no mixer chip on SB1.x */
+	case SB_HW_20:
+	case SB_HW_201:
+		if ((err = snd_sbmixer_init(chip,
+					    snd_sb20_controls,
+					    ARRAY_SIZE(snd_sb20_controls),
+					    snd_sb20_init_values,
+					    ARRAY_SIZE(snd_sb20_init_values),
+					    "CTL1335")) < 0)
+			return err;
+		break;
+	case SB_HW_PRO:
+		if ((err = snd_sbmixer_init(chip,
+					    snd_sbpro_controls,
+					    ARRAY_SIZE(snd_sbpro_controls),
+					    snd_sbpro_init_values,
+					    ARRAY_SIZE(snd_sbpro_init_values),
+					    "CTL1345")) < 0)
+			return err;
+		break;
+	case SB_HW_16:
+	case SB_HW_ALS100:
+		if ((err = snd_sbmixer_init(chip,
+					    snd_sb16_controls,
+					    ARRAY_SIZE(snd_sb16_controls),
+					    snd_sb16_init_values,
+					    ARRAY_SIZE(snd_sb16_init_values),
+					    "CTL1745")) < 0)
+			return err;
+		break;
+	case SB_HW_ALS4000:
+		if ((err = snd_sbmixer_init(chip,
+					    snd_als4000_controls,
+					    ARRAY_SIZE(snd_als4000_controls),
+					    snd_als4000_init_values,
+					    ARRAY_SIZE(snd_als4000_init_values),
+					    "ALS4000")) < 0)
+			return err;
+		break;
+	case SB_HW_DT019X:
+		if ((err = snd_sbmixer_init(chip,
+					    snd_dt019x_controls,
+					    ARRAY_SIZE(snd_dt019x_controls),
+					    snd_dt019x_init_values,
+					    ARRAY_SIZE(snd_dt019x_init_values),
+					    "DT019X")) < 0)
+		break;
+	default:
+		strcpy(card->mixername, "???");
+	}
+	return 0;
+}
diff --git a/sound/isa/sb/sbawe.c b/sound/isa/sb/sbawe.c
new file mode 100644
index 0000000..2ec52a3
--- /dev/null
+++ b/sound/isa/sb/sbawe.c
@@ -0,0 +1,2 @@
+#define SNDRV_SBAWE
+#include "sb16.c"
diff --git a/sound/isa/sgalaxy.c b/sound/isa/sgalaxy.c
new file mode 100644
index 0000000..17f585b
--- /dev/null
+++ b/sound/isa/sgalaxy.c
@@ -0,0 +1,322 @@
+/*
+ *  Driver for Aztech Sound Galaxy cards
+ *  Copyright (c) by Christopher Butler <chrisb@sandy.force9.co.uk.
+ *
+ *  I don't have documentation for this card, I based this driver on the
+ *  driver for OSS/Free included in the kernel source (drivers/sound/sgalaxy.c)
+ *
+ *   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/dma.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/ad1848.h>
+#include <sound/control.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Christopher Butler <chrisb@sandy.force9.co.uk>");
+MODULE_DESCRIPTION("Aztech Sound Galaxy");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Aztech Systems,Sound Galaxy}}");
+
+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;	/* Enable this card */
+static long sbport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x240 */
+static long wssport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x530,0xe80,0xf40,0x604 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 7,9,10,11 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Sound Galaxy soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Sound Galaxy soundcard.");
+module_param_array(sbport, long, NULL, 0444);
+MODULE_PARM_DESC(sbport, "Port # for Sound Galaxy SB driver.");
+module_param_array(wssport, long, NULL, 0444);
+MODULE_PARM_DESC(wssport, "Port # for Sound Galaxy WSS driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for Sound Galaxy driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for Sound Galaxy driver.");
+
+#define SGALAXY_AUXC_LEFT 18
+#define SGALAXY_AUXC_RIGHT 19
+
+static snd_card_t *snd_sgalaxy_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+/*
+
+ */
+
+#define AD1848P1( port, x ) ( port + c_d_c_AD1848##x )
+
+/* from lowlevel/sb/sb.c - to avoid having to allocate a sb_t for the */
+/* short time we actually need it.. */
+
+static int snd_sgalaxy_sbdsp_reset(unsigned long port)
+{
+	int i;
+
+	outb(1, SBP1(port, RESET));
+	udelay(10);
+	outb(0, SBP1(port, RESET));
+	udelay(30);
+	for (i = 0; i < 1000 && !(inb(SBP1(port, DATA_AVAIL)) & 0x80); i++);
+	if (inb(SBP1(port, READ)) != 0xaa) {
+		snd_printd("sb_reset: failed at 0x%lx!!!\n", port);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static int __init snd_sgalaxy_sbdsp_command(unsigned long port, unsigned char val)
+{
+	int i;
+       	
+	for (i = 10000; i; i--)
+		if ((inb(SBP1(port, STATUS)) & 0x80) == 0) {
+			outb(val, SBP1(port, COMMAND));
+			return 1;
+		}
+
+	return 0;
+}
+
+static irqreturn_t snd_sgalaxy_dummy_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	return IRQ_NONE;
+}
+
+static int __init snd_sgalaxy_setup_wss(unsigned long port, int irq, int dma)
+{
+	static int interrupt_bits[] = {-1, -1, -1, -1, -1, -1, -1, 0x08, -1, 
+				       0x10, 0x18, 0x20, -1, -1, -1, -1};
+	static int dma_bits[] = {1, 2, 0, 3};
+	int tmp, tmp1;
+
+	if ((tmp = inb(port + 3)) == 0xff)
+	{
+		snd_printdd("I/O address dead (0x%lx)\n", port);
+		return 0;
+	}
+#if 0
+	snd_printdd("WSS signature = 0x%x\n", tmp);
+#endif
+
+        if ((tmp & 0x3f) != 0x04 &&
+            (tmp & 0x3f) != 0x0f &&
+            (tmp & 0x3f) != 0x00) {
+		snd_printdd("No WSS signature detected on port 0x%lx\n",
+			    port + 3);
+		return 0;
+	}
+
+#if 0
+	snd_printdd("sgalaxy - setting up IRQ/DMA for WSS\n");
+#endif
+
+        /* initialize IRQ for WSS codec */
+        tmp = interrupt_bits[irq % 16];
+        if (tmp < 0)
+                return -EINVAL;
+
+	if (request_irq(irq, snd_sgalaxy_dummy_interrupt, SA_INTERRUPT, "sgalaxy", NULL)) {
+		snd_printk(KERN_ERR "sgalaxy: can't grab irq %d\n", irq);
+		return -EIO;
+	}
+
+        outb(tmp | 0x40, port);
+        tmp1 = dma_bits[dma % 4];
+        outb(tmp | tmp1, port);
+
+	free_irq(irq, NULL);
+
+	return 0;
+}
+
+static int __init snd_sgalaxy_detect(int dev, int irq, int dma)
+{
+#if 0
+	snd_printdd("sgalaxy - switching to WSS mode\n");
+#endif
+
+	/* switch to WSS mode */
+	snd_sgalaxy_sbdsp_reset(sbport[dev]);
+
+	snd_sgalaxy_sbdsp_command(sbport[dev], 9);
+	snd_sgalaxy_sbdsp_command(sbport[dev], 0);
+
+	udelay(400);
+	return snd_sgalaxy_setup_wss(wssport[dev], irq, dma);
+}
+
+static struct ad1848_mix_elem snd_sgalaxy_controls[] = {
+AD1848_DOUBLE("Aux Playback Switch", 0, SGALAXY_AUXC_LEFT, SGALAXY_AUXC_RIGHT, 7, 7, 1, 1),
+AD1848_DOUBLE("Aux Playback Volume", 0, SGALAXY_AUXC_LEFT, SGALAXY_AUXC_RIGHT, 0, 0, 31, 0)
+};
+
+static int __init snd_sgalaxy_mixer(ad1848_t *chip)
+{
+	snd_card_t *card = chip->card;
+	snd_ctl_elem_id_t id1, id2;
+	unsigned int idx;
+	int err;
+
+	memset(&id1, 0, sizeof(id1));
+	memset(&id2, 0, sizeof(id2));
+	id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	/* reassign AUX0 to LINE */
+	strcpy(id1.name, "Aux Playback Switch");
+	strcpy(id2.name, "Line Playback Switch");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	strcpy(id1.name, "Aux Playback Volume");
+	strcpy(id2.name, "Line Playback Volume");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	/* reassign AUX1 to FM */
+	strcpy(id1.name, "Aux Playback Switch"); id1.index = 1;
+	strcpy(id2.name, "FM Playback Switch");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	strcpy(id1.name, "Aux Playback Volume");
+	strcpy(id2.name, "FM Playback Volume");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	/* build AUX2 input */
+	for (idx = 0; idx < ARRAY_SIZE(snd_sgalaxy_controls); idx++) {
+		if ((err = snd_ad1848_add_ctl_elem(chip, &snd_sgalaxy_controls[idx])) < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int __init snd_sgalaxy_probe(int dev)
+{
+	static int possible_irqs[] = {7, 9, 10, 11, -1};
+	static int possible_dmas[] = {1, 3, 0, -1};
+	int err, xirq, xdma1;
+	snd_card_t *card;
+	ad1848_t *chip;
+
+	if (sbport[dev] == SNDRV_AUTO_PORT) {
+		snd_printk("specify SB port\n");
+		return -EINVAL;
+	}
+	if (wssport[dev] == SNDRV_AUTO_PORT) {
+		snd_printk("specify WSS port\n");
+		return -EINVAL;
+	}
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
+	if (card == NULL)
+		return -ENOMEM;
+
+	xirq = irq[dev];
+	if (xirq == SNDRV_AUTO_IRQ) {
+		if ((xirq = snd_legacy_find_free_irq(possible_irqs)) < 0) {
+			snd_card_free(card);
+			snd_printk("unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	xdma1 = dma1[dev];
+        if (xdma1 == SNDRV_AUTO_DMA) {
+		if ((xdma1 = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_card_free(card);
+			snd_printk("unable to find a free DMA\n");
+			return -EBUSY;
+		}
+	}
+
+	if ((err = snd_sgalaxy_detect(dev, xirq, xdma1)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if ((err = snd_ad1848_create(card, wssport[dev] + 4,
+				     xirq, xdma1,
+				     AD1848_HW_DETECT, &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if ((err = snd_ad1848_pcm(chip, 0, NULL)) < 0) {
+		snd_printdd("sgalaxy - error creating new ad1848 PCM device\n");
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_ad1848_mixer(chip)) < 0) {
+		snd_printdd("sgalaxy - error creating new ad1848 mixer\n");
+		snd_card_free(card);
+		return err;
+	}
+	if (snd_sgalaxy_mixer(chip) < 0) {
+		snd_printdd("sgalaxy - the mixer rewrite failed\n");
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->driver, "Sound Galaxy");
+	strcpy(card->shortname, "Sound Galaxy");
+	sprintf(card->longname, "Sound Galaxy at 0x%lx, irq %d, dma %d",
+		wssport[dev], xirq, xdma1);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	snd_sgalaxy_cards[dev] = card;
+	return 0;
+}
+
+static int __init alsa_card_sgalaxy_init(void)
+{
+	int dev, cards;
+
+	for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev]; dev++) {
+		if (snd_sgalaxy_probe(dev) >= 0)
+			cards++;
+	}
+	if (!cards) {
+#ifdef MODULE
+		snd_printk(KERN_ERR "Sound Galaxy soundcard not found or device busy\n");
+#endif
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void __exit alsa_card_sgalaxy_exit(void)
+{
+	int idx;
+
+	for (idx = 0; idx < SNDRV_CARDS; idx++)
+		snd_card_free(snd_sgalaxy_cards[idx]);
+}
+
+module_init(alsa_card_sgalaxy_init)
+module_exit(alsa_card_sgalaxy_exit)
diff --git a/sound/isa/sscape.c b/sound/isa/sscape.c
new file mode 100644
index 0000000..3959ed6
--- /dev/null
+++ b/sound/isa/sscape.c
@@ -0,0 +1,1517 @@
+/*
+ *   Low-level ALSA driver for the ENSONIQ SoundScape PnP
+ *   Copyright (c) by Chris Rankin
+ *
+ *   This driver was written in part using information obtained from
+ *   the OSS/Free SoundScape driver, written by Hannu Savolainen.
+ *
+ *
+ *   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/delay.h>
+#include <linux/pnp.h>
+#include <linux/spinlock.h>
+#include <linux/moduleparam.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/hwdep.h>
+#include <sound/cs4231.h>
+#include <sound/mpu401.h>
+#include <sound/initval.h>
+
+#include <sound/sscape_ioctl.h>
+
+
+MODULE_AUTHOR("Chris Rankin");
+MODULE_DESCRIPTION("ENSONIQ SoundScape PnP driver");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] __devinitdata = SNDRV_DEFAULT_IDX;
+static char* id[SNDRV_CARDS] __devinitdata = SNDRV_DEFAULT_STR;
+static long port[SNDRV_CARDS] __devinitdata = { [0 ... (SNDRV_CARDS-1)] = SNDRV_AUTO_PORT };
+static int irq[SNDRV_CARDS] __devinitdata = SNDRV_DEFAULT_IRQ;
+static int mpu_irq[SNDRV_CARDS] __devinitdata = SNDRV_DEFAULT_IRQ;
+static int dma[SNDRV_CARDS] __devinitdata = SNDRV_DEFAULT_DMA;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index number for SoundScape soundcard");
+
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "Description for SoundScape card");
+
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for SoundScape driver.");
+
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for SoundScape driver.");
+
+module_param_array(mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU401 IRQ # for SoundScape driver.");
+
+module_param_array(dma, int, NULL, 0444);
+MODULE_PARM_DESC(dma, "DMA # for SoundScape driver.");
+  
+#ifdef CONFIG_PNP
+static struct pnp_card_device_id sscape_pnpids[] = {
+	{ .id = "ENS3081", .devs = { { "ENS0000" } } },
+	{ .id = "" }	/* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, sscape_pnpids);
+#endif
+
+static snd_card_t *sscape_card[SNDRV_CARDS];
+
+
+#define MPU401_IO(i)     ((i) + 0)
+#define MIDI_DATA_IO(i)  ((i) + 0)
+#define MIDI_CTRL_IO(i)  ((i) + 1)
+#define HOST_CTRL_IO(i)  ((i) + 2)
+#define HOST_DATA_IO(i)  ((i) + 3)
+#define ODIE_ADDR_IO(i)  ((i) + 4)
+#define ODIE_DATA_IO(i)  ((i) + 5)
+#define CODEC_IO(i)      ((i) + 8)
+
+#define IC_ODIE  1
+#define IC_OPUS  2
+
+#define RX_READY 0x01
+#define TX_READY 0x02
+
+#define CMD_ACK           0x80
+#define CMD_SET_MIDI_VOL  0x84
+#define CMD_GET_MIDI_VOL  0x85
+#define CMD_XXX_MIDI_VOL  0x86
+#define CMD_SET_EXTMIDI   0x8a
+#define CMD_GET_EXTMIDI   0x8b
+#define CMD_SET_MT32      0x8c
+#define CMD_GET_MT32      0x8d
+
+enum GA_REG {
+	GA_INTSTAT_REG = 0,
+	GA_INTENA_REG,
+	GA_DMAA_REG,
+	GA_DMAB_REG,
+	GA_INTCFG_REG,
+	GA_DMACFG_REG,
+	GA_CDCFG_REG,
+	GA_SMCFGA_REG,
+	GA_SMCFGB_REG,
+	GA_HMCTL_REG
+};
+
+#define DMA_8BIT  0x80
+
+
+#define AD1845_FREQ_SEL_MSB    0x16
+#define AD1845_FREQ_SEL_LSB    0x17
+
+struct soundscape {
+	spinlock_t lock;
+	unsigned io_base;
+	int codec_type;
+	int ic_type;
+	struct resource *io_res;
+	cs4231_t *chip;
+	mpu401_t *mpu;
+	snd_hwdep_t *hw;
+
+	/*
+	 * The MIDI device won't work until we've loaded
+	 * its firmware via a hardware-dependent device IOCTL
+	 */
+	spinlock_t fwlock;
+	int hw_in_use;
+	unsigned long midi_usage;
+	unsigned char midi_vol;
+};
+
+#define INVALID_IRQ  ((unsigned)-1)
+
+
+static inline struct soundscape *get_card_soundscape(snd_card_t * c)
+{
+	return (struct soundscape *) (c->private_data);
+}
+
+static inline struct soundscape *get_mpu401_soundscape(mpu401_t * mpu)
+{
+	return (struct soundscape *) (mpu->private_data);
+}
+
+static inline struct soundscape *get_hwdep_soundscape(snd_hwdep_t * hw)
+{
+	return (struct soundscape *) (hw->private_data);
+}
+
+
+/*
+ * Allocates some kernel memory that we can use for DMA.
+ * I think this means that the memory has to map to
+ * contiguous pages of physical memory.
+ */
+static struct snd_dma_buffer *get_dmabuf(struct snd_dma_buffer *buf, unsigned long size)
+{
+	if (buf) {
+		if (snd_dma_alloc_pages_fallback(SNDRV_DMA_TYPE_DEV, snd_dma_isa_data(),
+						 size, buf) < 0) {
+			snd_printk(KERN_ERR "sscape: Failed to allocate %lu bytes for DMA\n", size);
+			return NULL;
+		}
+	}
+
+	return buf;
+}
+
+/*
+ * Release the DMA-able kernel memory ...
+ */
+static void free_dmabuf(struct snd_dma_buffer *buf)
+{
+	if (buf && buf->area)
+		snd_dma_free_pages(buf);
+}
+
+
+/*
+ * This function writes to the SoundScape's control registers,
+ * but doesn't do any locking. It's up to the caller to do that.
+ * This is why this function is "unsafe" ...
+ */
+static inline void sscape_write_unsafe(unsigned io_base, enum GA_REG reg, unsigned char val)
+{
+	outb(reg, ODIE_ADDR_IO(io_base));
+	outb(val, ODIE_DATA_IO(io_base));
+}
+
+/*
+ * Write to the SoundScape's control registers, and do the
+ * necessary locking ...
+ */
+static void sscape_write(struct soundscape *s, enum GA_REG reg, unsigned char val)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&s->lock, flags);
+	sscape_write_unsafe(s->io_base, reg, val);
+	spin_unlock_irqrestore(&s->lock, flags);
+}
+
+/*
+ * Read from the SoundScape's control registers, but leave any
+ * locking to the caller. This is why the function is "unsafe" ...
+ */
+static inline unsigned char sscape_read_unsafe(unsigned io_base, enum GA_REG reg)
+{
+	outb(reg, ODIE_ADDR_IO(io_base));
+	return inb(ODIE_DATA_IO(io_base));
+}
+
+/*
+ * Puts the SoundScape into "host" mode, as compared to "MIDI" mode
+ */
+static inline void set_host_mode_unsafe(unsigned io_base)
+{
+	outb(0x0, HOST_CTRL_IO(io_base));
+}
+
+/*
+ * Puts the SoundScape into "MIDI" mode, as compared to "host" mode
+ */
+static inline void set_midi_mode_unsafe(unsigned io_base)
+{
+	outb(0x3, HOST_CTRL_IO(io_base));
+}
+
+/*
+ * Read the SoundScape's host-mode control register, but leave
+ * any locking issues to the caller ...
+ */
+static inline int host_read_unsafe(unsigned io_base)
+{
+	int data = -1;
+	if ((inb(HOST_CTRL_IO(io_base)) & RX_READY) != 0) {
+		data = inb(HOST_DATA_IO(io_base));
+	}
+
+	return data;
+}
+
+/*
+ * Read the SoundScape's host-mode control register, performing
+ * a limited amount of busy-waiting if the register isn't ready.
+ * Also leaves all locking-issues to the caller ...
+ */
+static int host_read_ctrl_unsafe(unsigned io_base, unsigned timeout)
+{
+	int data;
+
+	while (((data = host_read_unsafe(io_base)) < 0) && (timeout != 0)) {
+		udelay(100);
+		--timeout;
+	} /* while */
+
+	return data;
+}
+
+/*
+ * Write to the SoundScape's host-mode control registers, but
+ * leave any locking issues to the caller ...
+ */
+static inline int host_write_unsafe(unsigned io_base, unsigned char data)
+{
+	if ((inb(HOST_CTRL_IO(io_base)) & TX_READY) != 0) {
+		outb(data, HOST_DATA_IO(io_base));
+		return 1;
+	}
+
+	return 0;
+}
+
+/*
+ * Write to the SoundScape's host-mode control registers, performing
+ * a limited amount of busy-waiting if the register isn't ready.
+ * Also leaves all locking-issues to the caller ...
+ */
+static int host_write_ctrl_unsafe(unsigned io_base, unsigned char data,
+                                  unsigned timeout)
+{
+	int err;
+
+	while (!(err = host_write_unsafe(io_base, data)) && (timeout != 0)) {
+		udelay(100);
+		--timeout;
+	} /* while */
+
+	return err;
+}
+
+
+/*
+ * Check that the MIDI subsystem is operational. If it isn't,
+ * then we will hang the computer if we try to use it ...
+ *
+ * NOTE: This check is based upon observation, not documentation.
+ */
+static inline int verify_mpu401(const mpu401_t * mpu)
+{
+	return ((inb(MIDI_CTRL_IO(mpu->port)) & 0xc0) == 0x80);
+}
+
+/*
+ * This is apparently the standard way to initailise an MPU-401
+ */
+static inline void initialise_mpu401(const mpu401_t * mpu)
+{
+	outb(0, MIDI_DATA_IO(mpu->port));
+}
+
+/*
+ * Tell the SoundScape to activate the AD1845 chip (I think).
+ * The AD1845 detection fails if we *don't* do this, so I
+ * think that this is a good idea ...
+ */
+static inline void activate_ad1845_unsafe(unsigned io_base)
+{
+	sscape_write_unsafe(io_base, GA_HMCTL_REG, (sscape_read_unsafe(io_base, GA_HMCTL_REG) & 0xcf) | 0x10);
+	sscape_write_unsafe(io_base, GA_CDCFG_REG, 0x80);
+}
+
+/*
+ * Do the necessary ALSA-level cleanup to deallocate our driver ...
+ */
+static void soundscape_free(snd_card_t * c)
+{
+	register struct soundscape *sscape = get_card_soundscape(c);
+	release_resource(sscape->io_res);
+	kfree_nocheck(sscape->io_res);
+	free_dma(sscape->chip->dma1);
+}
+
+/*
+ * Put this process into an idle wait-state for a certain number
+ * of "jiffies". The process can almost certainly be rescheduled
+ * while we're waiting, and so we must NOT be holding any spinlocks
+ * when we call this function. If we are then we risk DEADLOCK in
+ * SMP (Ha!) or pre-emptible kernels.
+ */
+static inline void sleep(long jiffs, int state)
+{
+	set_current_state(state);
+	schedule_timeout(jiffs);
+}
+
+/*
+ * Tell the SoundScape to begin a DMA tranfer using the given channel.
+ * All locking issues are left to the caller.
+ */
+static inline void sscape_start_dma_unsafe(unsigned io_base, enum GA_REG reg)
+{
+	sscape_write_unsafe(io_base, reg, sscape_read_unsafe(io_base, reg) | 0x01);
+	sscape_write_unsafe(io_base, reg, sscape_read_unsafe(io_base, reg) & 0xfe);
+}
+
+/*
+ * Wait for a DMA transfer to complete. This is a "limited busy-wait",
+ * and all locking issues are left to the caller.
+ */
+static int sscape_wait_dma_unsafe(unsigned io_base, enum GA_REG reg, unsigned timeout)
+{
+	while (!(sscape_read_unsafe(io_base, reg) & 0x01) && (timeout != 0)) {
+		udelay(100);
+		--timeout;
+	} /* while */
+
+	return (sscape_read_unsafe(io_base, reg) & 0x01);
+}
+
+/*
+ * Wait for the On-Board Processor to return its start-up
+ * acknowledgement sequence. This wait is too long for
+ * us to perform "busy-waiting", and so we must sleep.
+ * This in turn means that we must not be holding any
+ * spinlocks when we call this function.
+ */
+static int obp_startup_ack(struct soundscape *s, unsigned timeout)
+{
+	while (timeout != 0) {
+		unsigned long flags;
+		unsigned char x;
+
+		sleep(1, TASK_INTERRUPTIBLE);
+
+		spin_lock_irqsave(&s->lock, flags);
+		x = inb(HOST_DATA_IO(s->io_base));
+		spin_unlock_irqrestore(&s->lock, flags);
+		if ((x & 0xfe) == 0xfe)
+			return 1;
+
+		--timeout;
+	} /* while */
+
+	return 0;
+}
+
+/*
+ * Wait for the host to return its start-up acknowledgement
+ * sequence. This wait is too long for us to perform
+ * "busy-waiting", and so we must sleep. This in turn means
+ * that we must not be holding any spinlocks when we call
+ * this function.
+ */
+static int host_startup_ack(struct soundscape *s, unsigned timeout)
+{
+	while (timeout != 0) {
+		unsigned long flags;
+		unsigned char x;
+
+		sleep(1, TASK_INTERRUPTIBLE);
+
+		spin_lock_irqsave(&s->lock, flags);
+		x = inb(HOST_DATA_IO(s->io_base));
+		spin_unlock_irqrestore(&s->lock, flags);
+		if (x == 0xfe)
+			return 1;
+
+		--timeout;
+	} /* while */
+
+	return 0;
+}
+
+/*
+ * Upload a byte-stream into the SoundScape using DMA channel A.
+ */
+static int upload_dma_data(struct soundscape *s,
+                           const unsigned char __user *data,
+                           size_t size)
+{
+	unsigned long flags;
+	struct snd_dma_buffer dma;
+	int ret;
+
+	if (!get_dmabuf(&dma, PAGE_ALIGN(size)))
+		return -ENOMEM;
+
+	spin_lock_irqsave(&s->lock, flags);
+
+	/*
+	 * Reset the board ...
+	 */
+	sscape_write_unsafe(s->io_base, GA_HMCTL_REG, sscape_read_unsafe(s->io_base, GA_HMCTL_REG) & 0x3f);
+
+	/*
+	 * Enable the DMA channels and configure them ...
+	 */
+	sscape_write_unsafe(s->io_base, GA_DMACFG_REG, 0x50);
+	sscape_write_unsafe(s->io_base, GA_DMAA_REG, (s->chip->dma1 << 4) | DMA_8BIT);
+	sscape_write_unsafe(s->io_base, GA_DMAB_REG, 0x20);
+
+	/*
+	 * Take the board out of reset ...
+	 */
+	sscape_write_unsafe(s->io_base, GA_HMCTL_REG, sscape_read_unsafe(s->io_base, GA_HMCTL_REG) | 0x80);
+
+	/*
+	 * Upload the user's data (firmware?) to the SoundScape
+	 * board through the DMA channel ...
+	 */
+	while (size != 0) {
+		unsigned long len;
+
+		/*
+		 * Apparently, copying to/from userspace can sleep.
+		 * We are therefore forbidden from holding any
+		 * spinlocks while we copy ...
+		 */
+		spin_unlock_irqrestore(&s->lock, flags);
+
+		/*
+		 * Remember that the data that we want to DMA
+		 * comes from USERSPACE. We have already verified
+		 * the userspace pointer ...
+		 */
+		len = min(size, dma.bytes);
+		len -= __copy_from_user(dma.area, data, len);
+		data += len;
+		size -= len;
+
+		/*
+		 * Grab that spinlock again, now that we've
+		 * finished copying!
+		 */
+		spin_lock_irqsave(&s->lock, flags);
+
+		snd_dma_program(s->chip->dma1, dma.addr, len, DMA_MODE_WRITE);
+		sscape_start_dma_unsafe(s->io_base, GA_DMAA_REG);
+		if (!sscape_wait_dma_unsafe(s->io_base, GA_DMAA_REG, 5000)) {
+			/*
+			 * Don't forget to release this spinlock we're holding ...
+			 */
+			spin_unlock_irqrestore(&s->lock, flags);
+
+			snd_printk(KERN_ERR "sscape: DMA upload has timed out\n");
+			ret = -EAGAIN;
+			goto _release_dma;
+		}
+	} /* while */
+
+	set_host_mode_unsafe(s->io_base);
+
+	/*
+	 * Boot the board ... (I think)
+	 */
+	sscape_write_unsafe(s->io_base, GA_HMCTL_REG, sscape_read_unsafe(s->io_base, GA_HMCTL_REG) | 0x40);
+	spin_unlock_irqrestore(&s->lock, flags);
+
+	/*
+	 * If all has gone well, then the board should acknowledge
+	 * the new upload and tell us that it has rebooted OK. We
+	 * give it 5 seconds (max) ...
+	 */
+	ret = 0;
+	if (!obp_startup_ack(s, 5)) {
+		snd_printk(KERN_ERR "sscape: No response from on-board processor after upload\n");
+		ret = -EAGAIN;
+	} else if (!host_startup_ack(s, 5)) {
+		snd_printk(KERN_ERR "sscape: SoundScape failed to initialise\n");
+		ret = -EAGAIN;
+	}
+
+	_release_dma:
+	/*
+	 * NOTE!!! We are NOT holding any spinlocks at this point !!!
+	 */
+	sscape_write(s, GA_DMAA_REG, (s->ic_type == IC_ODIE ? 0x70 : 0x40));
+	free_dmabuf(&dma);
+
+	return ret;
+}
+
+/*
+ * Upload the bootblock(?) into the SoundScape. The only
+ * purpose of this block of code seems to be to tell
+ * us which version of the microcode we should be using.
+ *
+ * NOTE: The boot-block data resides in USER-SPACE!!!
+ *       However, we have already verified its memory
+ *       addresses by the time we get here.
+ */
+static int sscape_upload_bootblock(struct soundscape *sscape, struct sscape_bootblock __user *bb)
+{
+	unsigned long flags;
+	int data = 0;
+	int ret;
+
+	ret = upload_dma_data(sscape, bb->code, sizeof(bb->code));
+
+	spin_lock_irqsave(&sscape->lock, flags);
+	if (ret == 0) {
+		data = host_read_ctrl_unsafe(sscape->io_base, 100);
+	}
+	set_midi_mode_unsafe(sscape->io_base);
+	spin_unlock_irqrestore(&sscape->lock, flags);
+
+	if (ret == 0) {
+		if (data < 0) {
+			snd_printk(KERN_ERR "sscape: timeout reading firmware version\n");
+			ret = -EAGAIN;
+		}
+		else if (__copy_to_user(&bb->version, &data, sizeof(bb->version))) {
+			ret = -EFAULT;
+		}
+	}
+
+	return ret;
+}
+
+/*
+ * Upload the microcode into the SoundScape. The
+ * microcode is 64K of data, and if we try to copy
+ * it into a local variable then we will SMASH THE
+ * KERNEL'S STACK! We therefore leave it in USER
+ * SPACE, and save ourselves from copying it at all.
+ */
+static int sscape_upload_microcode(struct soundscape *sscape,
+                                   const struct sscape_microcode __user *mc)
+{
+	unsigned long flags;
+	char __user *code;
+	int err;
+
+	/*
+	 * We are going to have to copy this data into a special
+	 * DMA-able buffer before we can upload it. We shall therefore
+	 * just check that the data pointer is valid for now.
+	 *
+	 * NOTE: This buffer is 64K long! That's WAY too big to
+	 *       copy into a stack-temporary anyway.
+	 */
+	if ( get_user(code, &mc->code) ||
+	     !access_ok(VERIFY_READ, code, SSCAPE_MICROCODE_SIZE) )
+		return -EFAULT;
+
+	if ((err = upload_dma_data(sscape, code, SSCAPE_MICROCODE_SIZE)) == 0) {
+		snd_printk(KERN_INFO "sscape: MIDI firmware loaded\n");
+	}
+
+	spin_lock_irqsave(&sscape->lock, flags);
+	set_midi_mode_unsafe(sscape->io_base);
+	spin_unlock_irqrestore(&sscape->lock, flags);
+
+	initialise_mpu401(sscape->mpu);
+
+	return err;
+}
+
+/*
+ * Hardware-specific device functions, to implement special
+ * IOCTLs for the SoundScape card. This is how we upload
+ * the microcode into the card, for example, and so we
+ * must ensure that no two processes can open this device
+ * simultaneously, and that we can't open it at all if
+ * someone is using the MIDI device.
+ */
+static int sscape_hw_open(snd_hwdep_t * hw, struct file *file)
+{
+	register struct soundscape *sscape = get_hwdep_soundscape(hw);
+	unsigned long flags;
+	int err;
+
+	spin_lock_irqsave(&sscape->fwlock, flags);
+
+	if ((sscape->midi_usage != 0) || sscape->hw_in_use) {
+		err = -EBUSY;
+	} else {
+		sscape->hw_in_use = 1;
+		err = 0;
+	}
+
+	spin_unlock_irqrestore(&sscape->fwlock, flags);
+	return err;
+}
+
+static int sscape_hw_release(snd_hwdep_t * hw, struct file *file)
+{
+	register struct soundscape *sscape = get_hwdep_soundscape(hw);
+	unsigned long flags;
+
+	spin_lock_irqsave(&sscape->fwlock, flags);
+	sscape->hw_in_use = 0;
+	spin_unlock_irqrestore(&sscape->fwlock, flags);
+	return 0;
+}
+
+static int sscape_hw_ioctl(snd_hwdep_t * hw, struct file *file,
+                           unsigned int cmd, unsigned long arg)
+{
+	struct soundscape *sscape = get_hwdep_soundscape(hw);
+	int err = -EBUSY;
+
+	switch (cmd) {
+	case SND_SSCAPE_LOAD_BOOTB:
+		{
+			register struct sscape_bootblock __user *bb = (struct sscape_bootblock __user *) arg;
+
+			/*
+			 * We are going to have to copy this data into a special
+			 * DMA-able buffer before we can upload it. We shall therefore
+			 * just check that the data pointer is valid for now ...
+			 */
+			if ( !access_ok(VERIFY_READ, bb->code, sizeof(bb->code)) )
+				return -EFAULT;
+
+			/*
+			 * Now check that we can write the firmware version number too...
+			 */
+			if ( !access_ok(VERIFY_WRITE, &bb->version, sizeof(bb->version)) )
+				return -EFAULT;
+
+			err = sscape_upload_bootblock(sscape, bb);
+		}
+		break;
+
+	case SND_SSCAPE_LOAD_MCODE:
+		{
+			register const struct sscape_microcode __user *mc = (const struct sscape_microcode __user *) arg;
+
+			err = sscape_upload_microcode(sscape, mc);
+		}
+		break;
+
+	default:
+		err = -EINVAL;
+		break;
+	} /* switch */
+
+	return err;
+}
+
+
+/*
+ * Mixer control for the SoundScape's MIDI device.
+ */
+static int sscape_midi_info(snd_kcontrol_t * ctl,
+                            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 sscape_midi_get(snd_kcontrol_t * kctl,
+                           snd_ctl_elem_value_t * uctl)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kctl);
+	snd_card_t *card = chip->card;
+	register struct soundscape *s = get_card_soundscape(card);
+	unsigned long flags;
+
+	spin_lock_irqsave(&s->lock, flags);
+	set_host_mode_unsafe(s->io_base);
+
+	if (host_write_ctrl_unsafe(s->io_base, CMD_GET_MIDI_VOL, 100)) {
+		uctl->value.integer.value[0] = host_read_ctrl_unsafe(s->io_base, 100);
+	}
+
+	set_midi_mode_unsafe(s->io_base);
+	spin_unlock_irqrestore(&s->lock, flags);
+	return 0;
+}
+
+static int sscape_midi_put(snd_kcontrol_t * kctl,
+                           snd_ctl_elem_value_t * uctl)
+{
+	cs4231_t *chip = snd_kcontrol_chip(kctl);
+	snd_card_t *card = chip->card;
+	register struct soundscape *s = get_card_soundscape(card);
+	unsigned long flags;
+	int change;
+
+	spin_lock_irqsave(&s->lock, flags);
+
+	/*
+	 * We need to put the board into HOST mode before we
+	 * can send any volume-changing HOST commands ...
+	 */
+	set_host_mode_unsafe(s->io_base);
+
+	/*
+	 * To successfully change the MIDI volume setting, you seem to
+	 * have to write a volume command, write the new volume value,
+	 * and then perform another volume-related command. Perhaps the
+	 * first command is an "open" and the second command is a "close"?
+	 */
+	if (s->midi_vol == ((unsigned char) uctl->value.integer. value[0] & 127)) {
+		change = 0;
+		goto __skip_change;
+	}
+	change = (host_write_ctrl_unsafe(s->io_base, CMD_SET_MIDI_VOL, 100)
+	          && host_write_ctrl_unsafe(s->io_base, ((unsigned char) uctl->value.integer. value[0]) & 127, 100)
+	          && host_write_ctrl_unsafe(s->io_base, CMD_XXX_MIDI_VOL, 100));
+      __skip_change:
+
+	/*
+	 * Take the board out of HOST mode and back into MIDI mode ...
+	 */
+	set_midi_mode_unsafe(s->io_base);
+
+	spin_unlock_irqrestore(&s->lock, flags);
+	return change;
+}
+
+static snd_kcontrol_new_t midi_mixer_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "MIDI",
+	.info = sscape_midi_info,
+	.get = sscape_midi_get,
+	.put = sscape_midi_put
+};
+
+/*
+ * The SoundScape can use two IRQs from a possible set of four.
+ * These IRQs are encoded as bit patterns so that they can be
+ * written to the control registers.
+ */
+static unsigned __devinit get_irq_config(int irq)
+{
+	static const int valid_irq[] = { 9, 5, 7, 10 };
+	unsigned cfg;
+
+	for (cfg = 0; cfg < ARRAY_SIZE(valid_irq); ++cfg) {
+		if (irq == valid_irq[cfg])
+			return cfg;
+	} /* for */
+
+	return INVALID_IRQ;
+}
+
+
+/*
+ * Perform certain arcane port-checks to see whether there
+ * is a SoundScape board lurking behind the given ports.
+ */
+static int __devinit detect_sscape(struct soundscape *s)
+{
+	unsigned long flags;
+	unsigned d;
+	int retval = 0;
+
+	spin_lock_irqsave(&s->lock, flags);
+
+	/*
+	 * The following code is lifted from the original OSS driver,
+	 * and as I don't have a datasheet I cannot really comment
+	 * on what it is doing...
+	 */
+	if ((inb(HOST_CTRL_IO(s->io_base)) & 0x78) != 0)
+		goto _done;
+
+	d = inb(ODIE_ADDR_IO(s->io_base)) & 0xf0;
+	if ((d & 0x80) != 0)
+		goto _done;
+
+	if (d == 0) {
+		s->codec_type = 1;
+		s->ic_type = IC_ODIE;
+	} else if ((d & 0x60) != 0) {
+		s->codec_type = 2;
+		s->ic_type = IC_OPUS;
+	} else
+		goto _done;
+
+	outb(0xfa, ODIE_ADDR_IO(s->io_base));
+	if ((inb(ODIE_ADDR_IO(s->io_base)) & 0x9f) != 0x0a)
+		goto _done;
+
+	outb(0xfe, ODIE_ADDR_IO(s->io_base));
+	if ((inb(ODIE_ADDR_IO(s->io_base)) & 0x9f) != 0x0e)
+		goto _done;
+	if ((inb(ODIE_DATA_IO(s->io_base)) & 0x9f) != 0x0e)
+		goto _done;
+
+	/*
+	 * SoundScape successfully detected!
+	 */
+	retval = 1;
+
+	_done:
+	spin_unlock_irqrestore(&s->lock, flags);
+	return retval;
+}
+
+/*
+ * ALSA callback function, called when attempting to open the MIDI device.
+ * Check that the MIDI firmware has been loaded, because we don't want
+ * to crash the machine. Also check that someone isn't using the hardware
+ * IOCTL device.
+ */
+static int mpu401_open(mpu401_t * mpu)
+{
+	int err;
+
+	if (!verify_mpu401(mpu)) {
+		snd_printk(KERN_ERR "sscape: MIDI disabled, please load firmware\n");
+		err = -ENODEV;
+	} else {
+		register struct soundscape *sscape = get_mpu401_soundscape(mpu);
+		unsigned long flags;
+
+		spin_lock_irqsave(&sscape->fwlock, flags);
+
+		if (sscape->hw_in_use || (sscape->midi_usage == ULONG_MAX)) {
+			err = -EBUSY;
+		} else {
+			++(sscape->midi_usage);
+			err = 0;
+		}
+
+		spin_unlock_irqrestore(&sscape->fwlock, flags);
+	}
+
+	return err;
+}
+
+static void mpu401_close(mpu401_t * mpu)
+{
+	register struct soundscape *sscape = get_mpu401_soundscape(mpu);
+	unsigned long flags;
+
+	spin_lock_irqsave(&sscape->fwlock, flags);
+	--(sscape->midi_usage);
+	spin_unlock_irqrestore(&sscape->fwlock, flags);
+}
+
+/*
+ * Initialse an MPU-401 subdevice for MIDI support on the SoundScape.
+ */
+static int __devinit create_mpu401(snd_card_t * card, int devnum, unsigned long port, int irq)
+{
+	struct soundscape *sscape = get_card_soundscape(card);
+	snd_rawmidi_t *rawmidi;
+	int err;
+
+#define MPU401_SHARE_HARDWARE  1
+	if ((err = snd_mpu401_uart_new(card, devnum,
+	                               MPU401_HW_MPU401,
+	                               port, MPU401_SHARE_HARDWARE,
+	                               irq, SA_INTERRUPT,
+	                               &rawmidi)) == 0) {
+		mpu401_t *mpu = (mpu401_t *) rawmidi->private_data;
+		mpu->open_input = mpu401_open;
+		mpu->open_output = mpu401_open;
+		mpu->close_input = mpu401_close;
+		mpu->close_output = mpu401_close;
+		mpu->private_data = sscape;
+		sscape->mpu = mpu;
+
+		initialise_mpu401(mpu);
+	}
+
+	return err;
+}
+
+
+/*
+ * Override for the CS4231 playback format function.
+ * The AD1845 has much simpler format and rate selection.
+ */
+static void ad1845_playback_format(cs4231_t * chip, snd_pcm_hw_params_t * params, unsigned char format)
+{
+	unsigned long flags;
+	unsigned rate = params_rate(params);
+
+	/*
+	 * The AD1845 can't handle sample frequencies
+	 * outside of 4 kHZ to 50 kHZ
+	 */
+	if (rate > 50000)
+		rate = 50000;
+	else if (rate < 4000)
+		rate = 4000;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+
+	/*
+	 * Program the AD1845 correctly for the playback stream.
+	 * Note that we do NOT need to toggle the MCE bit because
+	 * the PLAYBACK_ENABLE bit of the Interface Configuration
+	 * register is set.
+	 * 
+	 * NOTE: We seem to need to write to the MSB before the LSB
+	 *       to get the correct sample frequency.
+	 */
+	snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, (format & 0xf0));
+	snd_cs4231_out(chip, AD1845_FREQ_SEL_MSB, (unsigned char) (rate >> 8));
+	snd_cs4231_out(chip, AD1845_FREQ_SEL_LSB, (unsigned char) rate);
+
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+/*
+ * Override for the CS4231 capture format function. 
+ * The AD1845 has much simpler format and rate selection.
+ */
+static void ad1845_capture_format(cs4231_t * chip, snd_pcm_hw_params_t * params, unsigned char format)
+{
+	unsigned long flags;
+	unsigned rate = params_rate(params);
+
+	/*
+	 * The AD1845 can't handle sample frequencies 
+	 * outside of 4 kHZ to 50 kHZ
+	 */
+	if (rate > 50000)
+		rate = 50000;
+	else if (rate < 4000)
+		rate = 4000;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+
+	/*
+	 * Program the AD1845 correctly for the playback stream.
+	 * Note that we do NOT need to toggle the MCE bit because
+	 * the CAPTURE_ENABLE bit of the Interface Configuration
+	 * register is set.
+	 *
+	 * NOTE: We seem to need to write to the MSB before the LSB
+	 *       to get the correct sample frequency.
+	 */
+	snd_cs4231_out(chip, CS4231_REC_FORMAT, (format & 0xf0));
+	snd_cs4231_out(chip, AD1845_FREQ_SEL_MSB, (unsigned char) (rate >> 8));
+	snd_cs4231_out(chip, AD1845_FREQ_SEL_LSB, (unsigned char) rate);
+
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+/*
+ * Create an AD1845 PCM subdevice on the SoundScape. The AD1845
+ * is very much like a CS4231, with a few extra bits. We will
+ * try to support at least some of the extra bits by overriding
+ * some of the CS4231 callback.
+ */
+static int __devinit create_ad1845(snd_card_t * card, unsigned port, int irq, int dma1)
+{
+	register struct soundscape *sscape = get_card_soundscape(card);
+	cs4231_t *chip;
+	int err;
+
+#define CS4231_SHARE_HARDWARE  (CS4231_HWSHARE_DMA1 | CS4231_HWSHARE_DMA2)
+	/*
+	 * The AD1845 PCM device is only half-duplex, and so
+	 * we only give it one DMA channel ...
+	 */
+	if ((err = snd_cs4231_create(card,
+				     port, -1, irq, dma1, dma1,
+				     CS4231_HW_DETECT,
+				     CS4231_HWSHARE_DMA1, &chip)) == 0) {
+		unsigned long flags;
+		snd_pcm_t *pcm;
+
+#define AD1845_FREQ_SEL_ENABLE  0x08
+
+#define AD1845_PWR_DOWN_CTRL   0x1b
+#define AD1845_CRYS_CLOCK_SEL  0x1d
+
+/*
+ * It turns out that the PLAYBACK_ENABLE bit is set
+ * by the lowlevel driver ...
+ *
+#define AD1845_IFACE_CONFIG  \
+           (CS4231_AUTOCALIB | CS4231_RECORD_ENABLE | CS4231_PLAYBACK_ENABLE)
+    snd_cs4231_mce_up(chip);
+    spin_lock_irqsave(&chip->reg_lock, flags);
+    snd_cs4231_out(chip, CS4231_IFACE_CTRL, AD1845_IFACE_CONFIG);
+    spin_unlock_irqrestore(&chip->reg_lock, flags);
+    snd_cs4231_mce_down(chip);
+ */
+
+		/*
+		 * The input clock frequency on the SoundScape must
+		 * be 14.31818 MHz, because we must set this register
+		 * to get the playback to sound correct ...
+		 */
+		snd_cs4231_mce_up(chip);
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		snd_cs4231_out(chip, AD1845_CRYS_CLOCK_SEL, 0x20);
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		snd_cs4231_mce_down(chip);
+
+		/*
+		 * More custom configuration:
+		 * a) select "mode 2", and provide a current drive of 8 mA
+		 * b) enable frequency selection (for capture/playback)
+		 */
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		snd_cs4231_out(chip, CS4231_MISC_INFO, (CS4231_MODE2 | 0x10));
+		snd_cs4231_out(chip, AD1845_PWR_DOWN_CTRL, snd_cs4231_in(chip, AD1845_PWR_DOWN_CTRL) | AD1845_FREQ_SEL_ENABLE);
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+		if ((err = snd_cs4231_pcm(chip, 0, &pcm)) < 0) {
+			snd_printk(KERN_ERR "sscape: No PCM device for AD1845 chip\n");
+			goto _error;
+		}
+
+		if ((err = snd_cs4231_mixer(chip)) < 0) {
+			snd_printk(KERN_ERR "sscape: No mixer device for AD1845 chip\n");
+			goto _error;
+		}
+
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&midi_mixer_ctl, chip))) < 0) {
+			snd_printk(KERN_ERR "sscape: Could not create MIDI mixer control\n");
+			goto _error;
+		}
+
+		strcpy(card->driver, "SoundScape");
+		strcpy(card->shortname, pcm->name);
+		snprintf(card->longname, sizeof(card->longname),
+		         "%s at 0x%lx, IRQ %d, DMA %d\n",
+		         pcm->name, chip->port, chip->irq, chip->dma1);
+		chip->set_playback_format = ad1845_playback_format;
+		chip->set_capture_format = ad1845_capture_format;
+		sscape->chip = chip;
+	}
+
+	_error:
+	return err;
+}
+
+
+struct params
+{
+	int index;
+	const char *id;
+	unsigned port;
+	int irq;
+	int mpu_irq;
+	int dma1;
+};
+
+
+static inline struct params*
+init_params(struct params *params,
+            int index,
+            const char *id,
+            unsigned port,
+            int irq,
+            int mpu_irq,
+            int dma1)
+{
+	params->index = index;
+	params->id = id;
+	params->port = port;
+	params->irq = irq;
+	params->mpu_irq = mpu_irq;  
+	params->dma1 = (dma1 & 0x03);
+
+	return params;
+}
+
+
+/*
+ * Create an ALSA soundcard entry for the SoundScape, using
+ * the given list of port, IRQ and DMA resources.
+ */
+static int __devinit create_sscape(const struct params *params, snd_card_t **rcardp)
+{
+	snd_card_t *card;
+	register struct soundscape *sscape;
+	register unsigned dma_cfg;
+	unsigned irq_cfg;
+	unsigned mpu_irq_cfg;
+	struct resource *io_res;
+	unsigned long flags;
+	int err;
+
+	/*
+	 * Check that the user didn't pass us garbage data ...
+	 */
+	irq_cfg = get_irq_config(params->irq);
+	if (irq_cfg == INVALID_IRQ) {
+		snd_printk(KERN_ERR "sscape: Invalid IRQ %d\n", params->irq);
+		return -ENXIO;
+	}
+
+	mpu_irq_cfg = get_irq_config(params->mpu_irq);
+	if (mpu_irq_cfg == INVALID_IRQ) {
+		printk(KERN_ERR "sscape: Invalid IRQ %d\n", params->mpu_irq);
+		return -ENXIO;
+	}
+
+	/*
+	 * Grab IO ports that we will need to probe so that we
+	 * can detect and control this hardware ...
+	 */
+	if ((io_res = request_region(params->port, 8, "SoundScape")) == NULL) {
+		snd_printk(KERN_ERR "sscape: can't grab port 0x%x\n", params->port);
+		return -EBUSY;
+	}
+
+	/*
+	 * Grab both DMA channels (OK, only one for now) ...
+	 */
+	if ((err = request_dma(params->dma1, "SoundScape")) < 0) {
+		snd_printk(KERN_ERR "sscape: can't grab DMA %d\n", params->dma1);
+		goto _release_region;
+	}
+
+	/*
+	 * Create a new ALSA sound card entry, in anticipation
+	 * of detecting our hardware ...
+	 */
+	if ((card = snd_card_new(params->index, params->id, THIS_MODULE, sizeof(struct soundscape))) == NULL) {
+		err = -ENOMEM;
+		goto _release_dma;
+	}
+
+	sscape = get_card_soundscape(card);
+	spin_lock_init(&sscape->lock);
+	spin_lock_init(&sscape->fwlock);
+	sscape->io_res = io_res;
+	sscape->io_base = params->port;
+
+	if (!detect_sscape(sscape)) {
+		printk(KERN_ERR "sscape: hardware not detected at 0x%x\n", sscape->io_base);
+		err = -ENODEV;
+		goto _release_card;
+	}
+
+	printk(KERN_INFO "sscape: hardware detected at 0x%x, using IRQ %d, DMA %d\n",
+	                 sscape->io_base, params->irq, params->dma1);
+
+	/*
+	 * Now create the hardware-specific device so that we can
+	 * load the microcode into the on-board processor.
+	 * We cannot use the MPU-401 MIDI system until this firmware
+	 * has been loaded into the card.
+	 */
+	if ((err = snd_hwdep_new(card, "MC68EC000", 0, &(sscape->hw))) < 0) {
+		printk(KERN_ERR "sscape: Failed to create firmware device\n");
+		goto _release_card;
+	}
+	strlcpy(sscape->hw->name, "SoundScape M68K", sizeof(sscape->hw->name));
+	sscape->hw->name[sizeof(sscape->hw->name) - 1] = '\0';
+	sscape->hw->iface = SNDRV_HWDEP_IFACE_SSCAPE;
+	sscape->hw->ops.open = sscape_hw_open;
+	sscape->hw->ops.release = sscape_hw_release;
+	sscape->hw->ops.ioctl = sscape_hw_ioctl;
+	sscape->hw->private_data = sscape;
+
+	/*
+	 * Tell the on-board devices where their resources are (I think -
+	 * I can't be sure without a datasheet ... So many magic values!)
+	 */
+	spin_lock_irqsave(&sscape->lock, flags);
+
+	activate_ad1845_unsafe(sscape->io_base);
+
+	sscape_write_unsafe(sscape->io_base, GA_INTENA_REG, 0x00); /* disable */
+	sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2e);
+	sscape_write_unsafe(sscape->io_base, GA_SMCFGB_REG, 0x00);
+
+	/*
+	 * Enable and configure the DMA channels ...
+	 */
+	sscape_write_unsafe(sscape->io_base, GA_DMACFG_REG, 0x50);
+	dma_cfg = (sscape->ic_type == IC_ODIE ? 0x70 : 0x40);
+	sscape_write_unsafe(sscape->io_base, GA_DMAA_REG, dma_cfg);
+	sscape_write_unsafe(sscape->io_base, GA_DMAB_REG, 0x20);
+
+	sscape_write_unsafe(sscape->io_base,
+	                    GA_INTCFG_REG, 0xf0 | (mpu_irq_cfg << 2) | mpu_irq_cfg);
+	sscape_write_unsafe(sscape->io_base,
+	                    GA_CDCFG_REG, 0x09 | DMA_8BIT | (params->dma1 << 4) | (irq_cfg << 1));
+
+	spin_unlock_irqrestore(&sscape->lock, flags);
+
+	/*
+	 * We have now enabled the codec chip, and so we should
+	 * detect the AD1845 device ...
+	 */
+	if ((err = create_ad1845(card, CODEC_IO(params->port), params->irq, params->dma1)) < 0) {
+		printk(KERN_ERR "sscape: No AD1845 device at 0x%x, IRQ %d\n",
+		                CODEC_IO(params->port), params->irq);
+		goto _release_card;
+	}
+#define MIDI_DEVNUM  0
+	if ((err = create_mpu401(card, MIDI_DEVNUM, MPU401_IO(params->port), params->mpu_irq)) < 0) {
+		printk(KERN_ERR "sscape: Failed to create MPU-401 device at 0x%x\n",
+		                MPU401_IO(params->port));
+		goto _release_card;
+	}
+
+	/*
+	 * Enable the master IRQ ...
+	 */
+	sscape_write(sscape, GA_INTENA_REG, 0x80);
+
+	if ((err = snd_card_register(card)) < 0) {
+		printk(KERN_ERR "sscape: Failed to register sound card\n");
+		goto _release_card;
+	}
+
+	/*
+	 * Initialize mixer
+	 */
+	sscape->midi_vol = 0;
+	host_write_ctrl_unsafe(sscape->io_base, CMD_SET_MIDI_VOL, 100);
+	host_write_ctrl_unsafe(sscape->io_base, 0, 100);
+	host_write_ctrl_unsafe(sscape->io_base, CMD_XXX_MIDI_VOL, 100);
+
+	/*
+	 * Now that we have successfully created this sound card,
+	 * it is safe to store the pointer.
+	 * NOTE: we only register the sound card's "destructor"
+	 *       function now that our "constructor" has completed.
+	 */
+	card->private_free = soundscape_free;
+	*rcardp = card;
+
+	return 0;
+
+	_release_card:
+	snd_card_free(card);
+
+	_release_dma:
+	free_dma(params->dma1);
+
+	_release_region:
+	release_resource(io_res);
+	kfree_nocheck(io_res);
+
+	return err;
+}
+
+
+static int sscape_cards __devinitdata;
+static struct params sscape_params[SNDRV_CARDS] __devinitdata;
+
+#ifdef CONFIG_PNP
+static inline int __devinit get_next_autoindex(int i)
+{
+	while ((i < SNDRV_CARDS) && (port[i] != SNDRV_AUTO_PORT)) {
+		++i;
+	} /* while */
+
+	return i;
+}
+
+
+static inline int __devinit is_port_known(unsigned io, struct params *params, int cards)
+{
+	while (--cards >= 0) {
+		if (params[cards].port == io)
+			return 1;
+	} /* while */
+
+	return 0;
+}
+
+static int __devinit sscape_pnp_detect(struct pnp_card_link *pcard,
+				       const struct pnp_card_device_id *pid)
+{
+	struct pnp_dev *dev;
+	static int idx = 0;
+	int ret;
+
+	/*
+	 * Allow this function to fail *quietly* if all the ISA PnP
+	 * devices were configured using module parameters instead.
+	 */
+	if ((idx = get_next_autoindex(idx)) >= SNDRV_CARDS) {
+		return -ENOSPC;
+	}
+
+	/*
+	 * We have found a candidate ISA PnP card. Now we
+	 * have to check that it has the devices that we
+	 * expect it to have.
+	 *
+	 * We will NOT try and autoconfigure all of the resources
+	 * needed and then activate the card as we are assuming that
+	 * has already been done at boot-time using /proc/isapnp.
+	 * We shall simply try to give each active card the resources
+	 * that it wants. This is a sensible strategy for a modular
+	 * system where unused modules are unloaded regularly.
+	 *
+	 * This strategy is utterly useless if we compile the driver
+	 * into the kernel, of course.
+	 */
+	// printk(KERN_INFO "sscape: %s\n", card->name);
+
+	/*
+	 * Check that we still have room for another sound card ...
+	 */
+	if (sscape_cards >= SNDRV_CARDS) {
+		printk(KERN_ERR "sscape: No room for another ALSA device\n");
+		return -ENOSPC;
+	}
+
+	ret = -ENODEV;
+
+	dev = pnp_request_card_device(pcard, pid->devs[0].id, NULL);
+	if (dev) {
+		struct params *this;
+		if (!pnp_is_active(dev)) {
+			if (pnp_activate_dev(dev) < 0) {
+				printk(KERN_INFO "sscape: device is inactive\n");
+				return -EBUSY;
+			}
+		}
+		/*
+		 * Read the correct parameters off the ISA PnP bus ...
+		 */
+		this = init_params(&sscape_params[sscape_cards],
+				   index[idx],
+				   id[idx],
+				   pnp_port_start(dev, 0),
+				   pnp_irq(dev, 0),
+				   pnp_irq(dev, 1),
+				   pnp_dma(dev, 0));
+
+		/*
+		 * Do we know about this sound card already?
+		 */
+		if ( !is_port_known(this->port, sscape_params, sscape_cards) ) {
+			snd_card_t *card;
+
+			ret = create_sscape(this, &card);
+			if (ret < 0)
+				return ret;
+			snd_card_set_dev(card, &pcard->card->dev);
+			pnp_set_card_drvdata(pcard, card);
+			++sscape_cards;
+			++idx;
+		}
+	}
+
+	return ret;
+}
+
+static void __devexit sscape_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_t *card = (snd_card_t *) pnp_get_card_drvdata(pcard);
+        
+	pnp_set_card_drvdata(pcard, NULL);
+	snd_card_disconnect(card);
+	snd_card_free_in_thread(card);
+}
+
+static struct pnp_card_driver sscape_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DO_NOT_CHANGE,
+	.name = "sscape",
+	.id_table = sscape_pnpids,
+	.probe = sscape_pnp_detect,
+	.remove = __devexit_p(sscape_pnp_remove),
+};
+
+#endif /* CONFIG_PNP */
+
+static int __init sscape_manual_probe(struct params *params)
+{
+	int ret;
+	unsigned i;
+	snd_card_t *card;
+
+	for (i = 0; i < SNDRV_CARDS; ++i) {
+		/*
+		 * We do NOT probe for ports.
+		 * If we're not given a port number for this
+		 * card then we completely ignore this line
+		 * of parameters.
+		 */
+		if (port[i] == SNDRV_AUTO_PORT)
+			continue;
+
+		/*
+		 * Make sure we were given ALL of the other parameters.
+		 */
+		if ( (irq[i] == SNDRV_AUTO_IRQ) ||
+		     (mpu_irq[i] == SNDRV_AUTO_IRQ) ||
+		     (dma[i] == SNDRV_AUTO_DMA) ) {
+			printk(KERN_INFO
+			       "sscape: insufficient parameters, need IO, IRQ, MPU-IRQ and DMA\n");
+			return -ENXIO;
+		}
+
+		/*
+		 * This cards looks OK ...
+		 */
+		init_params(params, index[i], id[i], port[i], irq[i], mpu_irq[i], dma[i]);
+
+		ret = create_sscape(params, &card);
+		if (ret < 0)
+			return ret;
+
+		sscape_card[sscape_cards] = card;
+		params++;
+		sscape_cards++;
+	} /* for */
+
+	return 0;
+}
+
+
+static void sscape_exit(void)
+{
+	unsigned i;
+
+#ifdef CONFIG_PNP
+	pnp_unregister_card_driver(&sscape_pnpc_driver);
+#endif
+	for (i = 0; i < ARRAY_SIZE(sscape_card); ++i) {
+		snd_card_free(sscape_card[i]);
+	} /* for */
+}
+
+
+static int __init sscape_init(void)
+{
+	int ret;
+
+	/*
+	 * First check whether we were passed any parameters.
+	 * These MUST take precedence over ANY automatic way
+	 * of allocating cards, because the operator is
+	 * S-P-E-L-L-I-N-G it out for us...
+	 */
+	ret = sscape_manual_probe(sscape_params);
+	if (ret < 0) {
+		int i;
+		for (i = 0; i < sscape_cards; ++i)
+			snd_card_free(sscape_card[i]);
+		return ret;
+	}
+
+#ifdef CONFIG_PNP
+	if (sscape_cards < SNDRV_CARDS) {
+		ret = pnp_register_card_driver(&sscape_pnpc_driver);
+		if (ret < 0) {
+			sscape_exit();
+			return ret;
+		}
+	}
+#endif
+
+	return 0;
+}
+
+module_init(sscape_init);
+module_exit(sscape_exit);
diff --git a/sound/isa/wavefront/Makefile b/sound/isa/wavefront/Makefile
new file mode 100644
index 0000000..b4cb284
--- /dev/null
+++ b/sound/isa/wavefront/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-wavefront-objs := wavefront.o wavefront_fx.o wavefront_synth.o wavefront_midi.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_WAVEFRONT) += snd-wavefront.o
diff --git a/sound/isa/wavefront/wavefront.c b/sound/isa/wavefront/wavefront.c
new file mode 100644
index 0000000..79b0220
--- /dev/null
+++ b/sound/isa/wavefront/wavefront.c
@@ -0,0 +1,716 @@
+/*
+ *  ALSA card-level driver for Turtle Beach Wavefront cards 
+ *                                              (Maui,Tropez,Tropez+)
+ *
+ *  Copyright (c) 1997-1999 by Paul Barton-Davis <pbd@op.net>
+ *
+ *  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/interrupt.h>
+#include <linux/slab.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/opl3.h>
+#include <sound/snd_wavefront.h>
+
+MODULE_AUTHOR("Paul Barton-Davis <pbd@op.net>");
+MODULE_DESCRIPTION("Turtle Beach Wavefront");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Turtle Beach,Maui/Tropez/Tropez+}}");
+
+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;	    /* Enable this card */
+static int isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+static long cs4232_pcm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static int cs4232_pcm_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,11,12,15 */
+static long cs4232_mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static int cs4232_mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 9,11,12,15 */
+static long ics2115_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static int ics2115_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;    /* 2,9,11,12,15 */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	    /* PnP setup */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	    /* 0,1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	    /* 0,1,3,5,6,7 */
+static int use_cs4232_midi[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 0}; 
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for WaveFront soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for WaveFront soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable WaveFront soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "ISA PnP detection for WaveFront soundcards.");
+#endif
+module_param_array(cs4232_pcm_port, long, NULL, 0444);
+MODULE_PARM_DESC(cs4232_pcm_port, "Port # for CS4232 PCM interface.");
+module_param_array(cs4232_pcm_irq, int, NULL, 0444);
+MODULE_PARM_DESC(cs4232_pcm_irq, "IRQ # for CS4232 PCM interface.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for CS4232 PCM interface.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for CS4232 PCM interface.");
+module_param_array(cs4232_mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(cs4232_mpu_port, "port # for CS4232 MPU-401 interface.");
+module_param_array(cs4232_mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(cs4232_mpu_irq, "IRQ # for CS4232 MPU-401 interface.");
+module_param_array(ics2115_irq, int, NULL, 0444);
+MODULE_PARM_DESC(ics2115_irq, "IRQ # for ICS2115.");
+module_param_array(ics2115_port, long, NULL, 0444);
+MODULE_PARM_DESC(ics2115_port, "Port # for ICS2115.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port #.");
+module_param_array(use_cs4232_midi, bool, NULL, 0444);
+MODULE_PARM_DESC(use_cs4232_midi, "Use CS4232 MPU-401 interface (inaccessibly located inside your computer)");
+
+static snd_card_t *snd_wavefront_legacy[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+#ifdef CONFIG_PNP
+
+static struct pnp_card_device_id snd_wavefront_pnpids[] = {
+	/* Tropez */
+	{ .id = "CSC7532", .devs = { { "CSC0000" }, { "CSC0010" }, { "PnPb006" }, { "CSC0004" } } },
+	/* Tropez+ */
+	{ .id = "CSC7632", .devs = { { "CSC0000" }, { "CSC0010" }, { "PnPb006" }, { "CSC0004" } } },
+	{ .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_wavefront_pnpids);
+
+static int __devinit
+snd_wavefront_pnp (int dev, snd_wavefront_card_t *acard, struct pnp_card_link *card,
+		   const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	struct pnp_resource_table *cfg = kmalloc(sizeof(*cfg), GFP_KERNEL);
+	int err;
+
+	if (!cfg)
+		return -ENOMEM;
+
+	/* Check for each logical device. */
+
+	/* CS4232 chip (aka "windows sound system") is logical device 0 */
+
+	acard->wss = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->wss == NULL) {
+		kfree(cfg);
+		return -EBUSY;
+	}
+
+	/* there is a game port at logical device 1, but we ignore it completely */
+
+	/* the control interface is logical device 2, but we ignore it
+	   completely. in fact, nobody even seems to know what it
+	   does.
+	*/
+
+	/* Only configure the CS4232 MIDI interface if its been
+	   specifically requested. It is logical device 3.
+	*/
+
+	if (use_cs4232_midi[dev]) {
+		acard->mpu = pnp_request_card_device(card, id->devs[2].id, NULL);
+		if (acard->mpu == NULL) {
+			kfree(cfg);
+			return -EBUSY;
+		}
+	}
+
+	/* The ICS2115 synth is logical device 4 */
+
+	acard->synth = pnp_request_card_device(card, id->devs[3].id, NULL);
+	if (acard->synth == NULL) {
+		kfree(cfg);
+		return -EBUSY;
+	}
+
+	/* PCM/FM initialization */
+
+	pdev = acard->wss;
+
+	pnp_init_resource_table(cfg);
+
+	/* An interesting note from the Tropez+ FAQ:
+
+	   Q. [Ports] Why is the base address of the WSS I/O ports off by 4?
+
+	   A. WSS I/O requires a block of 8 I/O addresses ("ports"). Of these, the first
+	   4 are used to identify and configure the board. With the advent of PnP,
+	   these first 4 addresses have become obsolete, and software applications
+	   only use the last 4 addresses to control the codec chip. Therefore, the
+	   base address setting "skips past" the 4 unused addresses.
+
+	*/
+
+	if (cs4232_pcm_port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[0], cs4232_pcm_port[dev], 4);
+	if (fm_port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[1], fm_port[dev], 4);
+	if (dma1[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[0], dma1[dev], 1);
+	if (dma2[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[1], dma2[dev], 1);
+	if (cs4232_pcm_irq[dev] != SNDRV_AUTO_IRQ)
+		pnp_resource_change(&cfg->irq_resource[0], cs4232_pcm_irq[dev], 1);
+
+	if (pnp_manual_config_dev(pdev, cfg, 0) < 0)
+		snd_printk(KERN_ERR "PnP WSS the requested resources are invalid, using auto config\n");
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "PnP WSS pnp configure failure\n");
+		kfree(cfg);
+		return err;
+	}
+
+	cs4232_pcm_port[dev] = pnp_port_start(pdev, 0);
+	fm_port[dev] = pnp_port_start(pdev, 1);
+	dma1[dev] = pnp_dma(pdev, 0);
+	dma2[dev] = pnp_dma(pdev, 1);
+	cs4232_pcm_irq[dev] = pnp_irq(pdev, 0);
+
+	/* Synth initialization */
+
+	pdev = acard->synth;
+	
+	pnp_init_resource_table(cfg);
+
+	if (ics2115_port[dev] != SNDRV_AUTO_PORT) {
+		pnp_resource_change(&cfg->port_resource[0], ics2115_port[dev], 16);
+	}
+		
+	if (ics2115_port[dev] != SNDRV_AUTO_IRQ) {
+		pnp_resource_change(&cfg->irq_resource[0], ics2115_irq[dev], 1);
+	}
+
+	if (pnp_manual_config_dev(pdev, cfg, 0) < 0)
+		snd_printk(KERN_ERR "PnP ICS2115 the requested resources are invalid, using auto config\n");
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "PnP ICS2115 pnp configure failure\n");
+		kfree(cfg);
+		return err;
+	}
+
+	ics2115_port[dev] = pnp_port_start(pdev, 0);
+	ics2115_irq[dev] = pnp_irq(pdev, 0);
+
+	/* CS4232 MPU initialization. Configure this only if
+	   explicitly requested, since its physically inaccessible and
+	   consumes another IRQ.
+	*/
+
+	if (use_cs4232_midi[dev]) {
+
+		pdev = acard->mpu;
+
+		pnp_init_resource_table(cfg);
+
+		if (cs4232_mpu_port[dev] != SNDRV_AUTO_PORT)
+			pnp_resource_change(&cfg->port_resource[0], cs4232_mpu_port[dev], 2);
+		if (cs4232_mpu_irq[dev] != SNDRV_AUTO_IRQ)
+			pnp_resource_change(&cfg->port_resource[0], cs4232_mpu_irq[dev], 1);
+
+		if (pnp_manual_config_dev(pdev, cfg, 0) < 0)
+			snd_printk(KERN_ERR "PnP MPU401 the requested resources are invalid, using auto config\n");
+		err = pnp_activate_dev(pdev);
+		if (err < 0) {
+			snd_printk(KERN_ERR "PnP MPU401 pnp configure failure\n");
+			cs4232_mpu_port[dev] = SNDRV_AUTO_PORT;
+		} else {
+			cs4232_mpu_port[dev] = pnp_port_start(pdev, 0);
+			cs4232_mpu_irq[dev] = pnp_irq(pdev, 0);
+		}
+
+		snd_printk ("CS4232 MPU: port=0x%lx, irq=%i\n", 
+			    cs4232_mpu_port[dev], 
+			    cs4232_mpu_irq[dev]);
+	}
+
+	snd_printdd ("CS4232: pcm port=0x%lx, fm port=0x%lx, dma1=%i, dma2=%i, irq=%i\nICS2115: port=0x%lx, irq=%i\n", 
+		    cs4232_pcm_port[dev], 
+		    fm_port[dev],
+		    dma1[dev], 
+		    dma2[dev], 
+		    cs4232_pcm_irq[dev],
+		    ics2115_port[dev], 
+		    ics2115_irq[dev]);
+	
+	kfree(cfg);
+	return 0;
+}
+
+#endif /* CONFIG_PNP */
+
+static irqreturn_t snd_wavefront_ics2115_interrupt(int irq, 
+					    void *dev_id, 
+					    struct pt_regs *regs)
+{
+	snd_wavefront_card_t *acard;
+
+	acard = (snd_wavefront_card_t *) dev_id;
+
+	if (acard == NULL) 
+		return IRQ_NONE;
+
+	if (acard->wavefront.interrupts_are_midi) {
+		snd_wavefront_midi_interrupt (acard);
+	} else {
+		snd_wavefront_internal_interrupt (acard);
+	}
+	return IRQ_HANDLED;
+}
+
+static snd_hwdep_t * __devinit
+snd_wavefront_new_synth (snd_card_t *card,
+			 int hw_dev,
+			 snd_wavefront_card_t *acard)
+{
+	snd_hwdep_t *wavefront_synth;
+
+	if (snd_wavefront_detect (acard) < 0) {
+		return NULL;
+	}
+
+	if (snd_wavefront_start (&acard->wavefront) < 0) {
+		return NULL;
+	}
+
+	if (snd_hwdep_new(card, "WaveFront", hw_dev, &wavefront_synth) < 0)
+		return NULL;
+	strcpy (wavefront_synth->name, 
+		"WaveFront (ICS2115) wavetable synthesizer");
+	wavefront_synth->ops.open = snd_wavefront_synth_open;
+	wavefront_synth->ops.release = snd_wavefront_synth_release;
+	wavefront_synth->ops.ioctl = snd_wavefront_synth_ioctl;
+
+	return wavefront_synth;
+}
+
+static snd_hwdep_t * __devinit
+snd_wavefront_new_fx (snd_card_t *card,
+		      int hw_dev,
+		      snd_wavefront_card_t *acard,
+		      unsigned long port)
+
+{
+	snd_hwdep_t *fx_processor;
+
+	if (snd_wavefront_fx_start (&acard->wavefront)) {
+		snd_printk ("cannot initialize YSS225 FX processor");
+		return NULL;
+	}
+
+	if (snd_hwdep_new (card, "YSS225", hw_dev, &fx_processor) < 0)
+		return NULL;
+	sprintf (fx_processor->name, "YSS225 FX Processor at 0x%lx", port);
+	fx_processor->ops.open = snd_wavefront_fx_open;
+	fx_processor->ops.release = snd_wavefront_fx_release;
+	fx_processor->ops.ioctl = snd_wavefront_fx_ioctl;
+	
+	return fx_processor;
+}
+
+static snd_wavefront_mpu_id internal_id = internal_mpu;
+static snd_wavefront_mpu_id external_id = external_mpu;
+
+static snd_rawmidi_t * __devinit
+snd_wavefront_new_midi (snd_card_t *card,
+			int midi_dev,
+			snd_wavefront_card_t *acard,
+			unsigned long port,
+			snd_wavefront_mpu_id mpu)
+
+{
+	snd_rawmidi_t *rmidi;
+	static int first = 1;
+
+	if (first) {
+		first = 0;
+		acard->wavefront.midi.base = port;
+		if (snd_wavefront_midi_start (acard)) {
+			snd_printk ("cannot initialize MIDI interface\n");
+			return NULL;
+		}
+	}
+
+	if (snd_rawmidi_new (card, "WaveFront MIDI", midi_dev, 1, 1, &rmidi) < 0)
+		return NULL;
+
+	if (mpu == internal_mpu) {
+		strcpy(rmidi->name, "WaveFront MIDI (Internal)");
+		rmidi->private_data = &internal_id;
+	} else {
+		strcpy(rmidi->name, "WaveFront MIDI (External)");
+		rmidi->private_data = &external_id;
+	}
+
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_wavefront_midi_output);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_wavefront_midi_input);
+
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT |
+	                     SNDRV_RAWMIDI_INFO_INPUT |
+	                     SNDRV_RAWMIDI_INFO_DUPLEX;
+
+	return rmidi;
+}
+
+static void
+snd_wavefront_free(snd_card_t *card)
+{
+	snd_wavefront_card_t *acard = (snd_wavefront_card_t *)card->private_data;
+	
+	if (acard) {
+		if (acard->wavefront.res_base != NULL) {
+			release_resource(acard->wavefront.res_base);
+			kfree_nocheck(acard->wavefront.res_base);
+		}
+		if (acard->wavefront.irq > 0)
+			free_irq(acard->wavefront.irq, (void *)acard);
+	}
+}
+
+static int __devinit
+snd_wavefront_probe (int dev, struct pnp_card_link *pcard,
+		     const struct pnp_card_device_id *pid)
+{
+	snd_card_t *card;
+	snd_wavefront_card_t *acard;
+	cs4231_t *chip;
+	snd_hwdep_t *wavefront_synth;
+	snd_rawmidi_t *ics2115_internal_rmidi = NULL;
+	snd_rawmidi_t *ics2115_external_rmidi = NULL;
+	snd_hwdep_t *fx_processor;
+	int hw_dev = 0, midi_dev = 0, err;
+
+#ifdef CONFIG_PNP
+	if (!isapnp[dev]) {
+#endif
+		if (cs4232_pcm_port[dev] == SNDRV_AUTO_PORT) {
+			snd_printk("specify CS4232 port\n");
+			return -EINVAL;
+		}
+		if (ics2115_port[dev] == SNDRV_AUTO_PORT) {
+			snd_printk("specify ICS2115 port\n");
+			return -ENODEV;
+		}
+#ifdef CONFIG_PNP
+	}
+#endif
+	card = snd_card_new (index[dev], 
+			     id[dev],
+			     THIS_MODULE,
+			     sizeof(snd_wavefront_card_t));
+
+	if (card == NULL) {
+		return -ENOMEM;
+	}
+	acard = (snd_wavefront_card_t *)card->private_data;
+	acard->wavefront.irq = -1;
+	spin_lock_init(&acard->wavefront.irq_lock);
+	init_waitqueue_head(&acard->wavefront.interrupt_sleeper);
+	spin_lock_init(&acard->wavefront.midi.open);
+	spin_lock_init(&acard->wavefront.midi.virtual);
+	card->private_free = snd_wavefront_free;
+
+#ifdef CONFIG_PNP
+	if (isapnp[dev]) {
+		if (snd_wavefront_pnp (dev, acard, pcard, pid) < 0) {
+			if (cs4232_pcm_port[dev] == SNDRV_AUTO_PORT) {
+				snd_printk ("isapnp detection failed\n");
+				snd_card_free (card);
+				return -ENODEV;
+			}
+		}
+		snd_card_set_dev(card, &pcard->card->dev);
+	}
+#endif /* CONFIG_PNP */
+
+	/* --------- PCM --------------- */
+
+	if ((err = snd_cs4231_create (card,
+				      cs4232_pcm_port[dev],
+				      -1,
+				      cs4232_pcm_irq[dev],
+				      dma1[dev],
+				      dma2[dev],
+				      CS4231_HW_DETECT, 0, &chip)) < 0) {
+		snd_card_free(card);
+		snd_printk ("can't allocate CS4231 device\n");
+		return err;
+	}
+
+	if ((err = snd_cs4231_pcm (chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_cs4231_timer (chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	/* ---------- OPL3 synth --------- */
+
+	if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+		opl3_t *opl3;
+
+	        if ((err = snd_opl3_create(card,
+					   fm_port[dev],
+					   fm_port[dev] + 2,
+					   OPL3_HW_OPL3_CS,
+					   0, &opl3)) < 0) {
+			snd_printk ("can't allocate or detect OPL3 synth\n");
+			snd_card_free(card);
+			return err;
+		}
+
+		if ((err = snd_opl3_hwdep_new(opl3, hw_dev, 1, NULL)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+		hw_dev++;
+	}
+
+	/* ------- ICS2115 Wavetable synth ------- */
+
+	if ((acard->wavefront.res_base = request_region(ics2115_port[dev], 16, "ICS2115")) == NULL) {
+		snd_printk("unable to grab ICS2115 i/o region 0x%lx-0x%lx\n", ics2115_port[dev], ics2115_port[dev] + 16 - 1);
+		snd_card_free(card);
+		return -EBUSY;
+	}
+	if (request_irq(ics2115_irq[dev], snd_wavefront_ics2115_interrupt, SA_INTERRUPT, "ICS2115", (void *)acard)) {
+		snd_printk("unable to use ICS2115 IRQ %d\n", ics2115_irq[dev]);
+		snd_card_free(card);
+		return -EBUSY;
+	}
+	
+	acard->wavefront.irq = ics2115_irq[dev];
+	acard->wavefront.base = ics2115_port[dev];
+
+	if ((wavefront_synth = snd_wavefront_new_synth (card, hw_dev, acard)) == NULL) {
+		snd_printk ("can't create WaveFront synth device\n");
+		snd_card_free(card);
+		return -ENOMEM;
+	}
+
+	strcpy (wavefront_synth->name, "ICS2115 Wavetable MIDI Synthesizer");
+	wavefront_synth->iface = SNDRV_HWDEP_IFACE_ICS2115;
+	hw_dev++;
+
+	/* --------- Mixer ------------ */
+
+	if ((err = snd_cs4231_mixer(chip)) < 0) {
+		snd_printk ("can't allocate mixer device\n");
+		snd_card_free(card);
+		return err;
+	}
+
+	/* -------- CS4232 MPU-401 interface -------- */
+
+	if (cs4232_mpu_port[dev] > 0 && cs4232_mpu_port[dev] != SNDRV_AUTO_PORT) {
+		if ((err = snd_mpu401_uart_new(card, midi_dev, MPU401_HW_CS4232,
+					       cs4232_mpu_port[dev], 0,
+					       cs4232_mpu_irq[dev],
+					       SA_INTERRUPT,
+					       NULL)) < 0) {
+			snd_printk ("can't allocate CS4232 MPU-401 device\n");
+			snd_card_free(card);
+			return err;
+		}
+		midi_dev++;
+	}
+
+	/* ------ ICS2115 internal MIDI ------------ */
+
+	if (ics2115_port[dev] > 0 && ics2115_port[dev] != SNDRV_AUTO_PORT) {
+		ics2115_internal_rmidi = 
+			snd_wavefront_new_midi (card, 
+						midi_dev,
+						acard,
+						ics2115_port[dev],
+						internal_mpu);
+		if (ics2115_internal_rmidi == NULL) {
+			snd_printk ("can't setup ICS2115 internal MIDI device\n");
+			snd_card_free(card);
+			return -ENOMEM;
+		}
+		midi_dev++;
+	}
+
+	/* ------ ICS2115 external MIDI ------------ */
+
+	if (ics2115_port[dev] > 0 && ics2115_port[dev] != SNDRV_AUTO_PORT) {
+		ics2115_external_rmidi = 
+			snd_wavefront_new_midi (card, 
+						midi_dev,
+						acard,
+						ics2115_port[dev],
+						external_mpu);
+		if (ics2115_external_rmidi == NULL) {
+			snd_printk ("can't setup ICS2115 external MIDI device\n");
+			snd_card_free(card);
+			return -ENOMEM;
+		}
+		midi_dev++;
+	}
+
+	/* FX processor for Tropez+ */
+
+	if (acard->wavefront.has_fx) {
+		fx_processor = snd_wavefront_new_fx (card,
+						     hw_dev,
+						     acard,
+						     ics2115_port[dev]);
+		if (fx_processor == NULL) {
+			snd_printk ("can't setup FX device\n");
+			snd_card_free(card);
+			return -ENOMEM;
+		}
+
+		hw_dev++;
+
+		strcpy(card->driver, "Tropez+");
+		strcpy(card->shortname, "Turtle Beach Tropez+");
+	} else {
+		/* Need a way to distinguish between Maui and Tropez */
+		strcpy(card->driver, "WaveFront");
+		strcpy(card->shortname, "Turtle Beach WaveFront");
+	}
+
+	/* ----- Register the card --------- */
+
+	/* Not safe to include "Turtle Beach" in longname, due to 
+	   length restrictions
+	*/
+
+	sprintf(card->longname, "%s PCM 0x%lx irq %d dma %d",
+		card->driver,
+		chip->port,
+		cs4232_pcm_irq[dev],
+		dma1[dev]);
+
+	if (dma2[dev] >= 0 && dma2[dev] < 8)
+		sprintf(card->longname + strlen(card->longname), "&%d", dma2[dev]);
+
+	if (cs4232_mpu_port[dev] > 0 && cs4232_mpu_port[dev] != SNDRV_AUTO_PORT) {
+		sprintf (card->longname + strlen (card->longname), 
+			 " MPU-401 0x%lx irq %d",
+			 cs4232_mpu_port[dev],
+			 cs4232_mpu_irq[dev]);
+	}
+
+	sprintf (card->longname + strlen (card->longname), 
+		 " SYNTH 0x%lx irq %d",
+		 ics2115_port[dev],
+		 ics2115_irq[dev]);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (pcard)
+		pnp_set_card_drvdata(pcard, card);
+	else
+		snd_wavefront_legacy[dev] = card;
+	return 0;
+}	
+
+#ifdef CONFIG_PNP
+
+static int __devinit snd_wavefront_pnp_detect(struct pnp_card_link *card,
+                                              const struct pnp_card_device_id *id)
+{
+        static int dev;
+        int res;
+
+        for ( ; dev < SNDRV_CARDS; dev++) {
+                if (!enable[dev] || !isapnp[dev])
+                        continue;
+                res = snd_wavefront_probe(dev, card, id);
+                if (res < 0)
+                        return res;
+                dev++;
+                return 0;
+        }
+
+        return -ENODEV;
+}
+
+static void __devexit snd_wavefront_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_t *card = (snd_card_t *) pnp_get_card_drvdata(pcard);
+
+	snd_card_disconnect(card);
+	snd_card_free_in_thread(card);
+}
+
+static struct pnp_card_driver wavefront_pnpc_driver = {
+	.flags		= PNP_DRIVER_RES_DISABLE,
+	.name		= "wavefront",
+	.id_table	= snd_wavefront_pnpids,
+	.probe		= snd_wavefront_pnp_detect,
+	.remove		= __devexit_p(snd_wavefront_pnp_remove),
+};
+
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_wavefront_init(void)
+{
+	int cards = 0;
+	int dev;
+	for (dev = 0; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev])
+			continue;
+#ifdef CONFIG_PNP
+		if (isapnp[dev])
+			continue;
+#endif
+		if (snd_wavefront_probe(dev, NULL, NULL) >= 0)
+			cards++;
+	}
+#ifdef CONFIG_PNP
+	cards += pnp_register_card_driver(&wavefront_pnpc_driver);
+#endif
+	if (!cards) {
+#ifdef CONFIG_PNP
+		pnp_unregister_card_driver(&wavefront_pnpc_driver);
+#endif
+#ifdef MODULE
+		printk (KERN_ERR "No WaveFront cards found or devices busy\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_wavefront_exit(void)
+{
+	int idx;
+
+#ifdef CONFIG_PNP
+	pnp_unregister_card_driver(&wavefront_pnpc_driver);
+#endif
+	for (idx = 0; idx < SNDRV_CARDS; idx++)
+		snd_card_free(snd_wavefront_legacy[idx]);
+}
+
+module_init(alsa_card_wavefront_init)
+module_exit(alsa_card_wavefront_exit)
diff --git a/sound/isa/wavefront/wavefront_fx.c b/sound/isa/wavefront/wavefront_fx.c
new file mode 100644
index 0000000..0e13623
--- /dev/null
+++ b/sound/isa/wavefront/wavefront_fx.c
@@ -0,0 +1,1019 @@
+/*
+ *  Copyright (c) 1998-2002 by Paul Davis <pbd@op.net>
+ *
+ *  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/time.h>
+#include <linux/wait.h>
+#include <sound/core.h>
+#include <sound/snd_wavefront.h>
+#include <sound/initval.h>
+
+/* Control bits for the Load Control Register
+ */
+
+#define FX_LSB_TRANSFER 0x01    /* transfer after DSP LSB byte written */
+#define FX_MSB_TRANSFER 0x02    /* transfer after DSP MSB byte written */
+#define FX_AUTO_INCR    0x04    /* auto-increment DSP address after transfer */
+
+/* weird stuff, derived from port I/O tracing with dosemu */
+
+unsigned char page_zero[] __initdata = {
+0x01, 0x7c, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf5, 0x00,
+0x11, 0x00, 0x20, 0x00, 0x32, 0x00, 0x40, 0x00, 0x13, 0x00, 0x00,
+0x00, 0x14, 0x02, 0x76, 0x00, 0x60, 0x00, 0x80, 0x02, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x19,
+0x01, 0x1a, 0x01, 0x20, 0x01, 0x40, 0x01, 0x17, 0x00, 0x00, 0x01,
+0x80, 0x01, 0x20, 0x00, 0x10, 0x01, 0xa0, 0x03, 0xd1, 0x00, 0x00,
+0x01, 0xf2, 0x02, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0xf4, 0x02,
+0xe0, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17,
+0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x50, 0x00, 0x00, 0x00,
+0x40, 0x00, 0x00, 0x00, 0x71, 0x02, 0x00, 0x00, 0x60, 0x00, 0x00,
+0x00, 0x92, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xb3, 0x02,
+0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0x40,
+0x00, 0x80, 0x00, 0xf5, 0x00, 0x20, 0x00, 0x70, 0x00, 0xa0, 0x02,
+0x11, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+0x02, 0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0x17, 0x00, 0x1b, 0x00,
+0x1d, 0x02, 0xdf
+};
+
+unsigned char page_one[] __initdata = {
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x19, 0x00,
+0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xd8, 0x00, 0x00,
+0x02, 0x20, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x01,
+0xc0, 0x01, 0xfa, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x02, 0x60,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xc0, 0x02, 0x80, 0x00,
+0x00, 0x02, 0xfb, 0x02, 0xa0, 0x00, 0x00, 0x00, 0x1b, 0x02, 0xd7,
+0x00, 0x00, 0x02, 0xf7, 0x03, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00,
+0x1c, 0x03, 0x3c, 0x00, 0x00, 0x03, 0x3f, 0x00, 0x00, 0x03, 0xc0,
+0x00, 0x00, 0x03, 0xdf, 0x00, 0x00, 0x00, 0x00, 0x03, 0x5d, 0x00,
+0x00, 0x03, 0xc0, 0x00, 0x00, 0x03, 0x7d, 0x00, 0x00, 0x03, 0xc0,
+0x00, 0x00, 0x03, 0x9e, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x03,
+0xbe, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+0xdb, 0x00, 0x00, 0x02, 0xdb, 0x00, 0x00, 0x02, 0xe0, 0x00, 0x00,
+0x02, 0xfb, 0x00, 0x00, 0x02, 0xc0, 0x02, 0x40, 0x02, 0xfb, 0x02,
+0x60, 0x00, 0x1b
+};
+
+unsigned char page_two[] __initdata = {
+0xc4, 0x00, 0x44, 0x07, 0x44, 0x00, 0x40, 0x25, 0x01, 0x06, 0xc4,
+0x07, 0x40, 0x25, 0x01, 0x00, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x07,
+0x05, 0x05, 0x05, 0x04, 0x07, 0x05, 0x04, 0x07, 0x05, 0x44, 0x46,
+0x44, 0x46, 0x46, 0x07, 0x05, 0x44, 0x46, 0x05, 0x46, 0x05, 0x46,
+0x05, 0x46, 0x05, 0x44, 0x46, 0x05, 0x07, 0x44, 0x46, 0x05, 0x07,
+0x44, 0x46, 0x05, 0x07, 0x44, 0x46, 0x05, 0x07, 0x44, 0x05, 0x05,
+0x05, 0x44, 0x05, 0x05, 0x05, 0x46, 0x05, 0x46, 0x05, 0x46, 0x05,
+0x46, 0x05, 0x46, 0x07, 0x46, 0x07, 0x44
+};
+
+unsigned char page_three[] __initdata = {
+0x07, 0x40, 0x00, 0x00, 0x00, 0x47, 0x00, 0x40, 0x00, 0x40, 0x06,
+0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80,
+0xc0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00,
+0x60, 0x00, 0x70, 0x00, 0x40, 0x00, 0x40, 0x00, 0x42, 0x00, 0x40,
+0x00, 0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
+0x00, 0x42, 0x00, 0x40, 0x00, 0x42, 0x00, 0x02, 0x00, 0x02, 0x00,
+0x02, 0x00, 0x42, 0x00, 0xc0, 0x00, 0x40
+};
+
+unsigned char page_four[] __initdata = {
+0x63, 0x03, 0x26, 0x02, 0x2c, 0x00, 0x24, 0x00, 0x2e, 0x02, 0x02,
+0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+0x20, 0x00, 0x60, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20,
+0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x60, 0x00,
+0x20, 0x00, 0x60, 0x00, 0x20, 0x00, 0x60, 0x00, 0x20, 0x00, 0x60,
+0x00, 0x20, 0x00, 0x60, 0x00, 0x20, 0x00, 0x60, 0x00, 0x20, 0x00,
+0x20, 0x00, 0x22, 0x02, 0x22, 0x02, 0x20, 0x00, 0x60, 0x00, 0x22,
+0x02, 0x62, 0x02, 0x20, 0x01, 0x21, 0x01
+};
+
+unsigned char page_six[] __initdata = {
+0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x04, 0x00, 0x00, 0x06, 0x00,
+0x00, 0x08, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0e,
+0x00, 0x00, 0x10, 0x00, 0x00, 0x12, 0x00, 0x00, 0x14, 0x00, 0x00,
+0x16, 0x00, 0x00, 0x18, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x1c, 0x00,
+0x00, 0x1e, 0x00, 0x00, 0x20, 0x00, 0x00, 0x22, 0x00, 0x00, 0x24,
+0x00, 0x00, 0x26, 0x00, 0x00, 0x28, 0x00, 0x00, 0x2a, 0x00, 0x00,
+0x2c, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x30, 0x00, 0x00, 0x32, 0x00,
+0x00, 0x34, 0x00, 0x00, 0x36, 0x00, 0x00, 0x38, 0x00, 0x00, 0x3a,
+0x00, 0x00, 0x3c, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x40, 0x00, 0x00,
+0x42, 0x03, 0x00, 0x44, 0x01, 0x00, 0x46, 0x0a, 0x21, 0x48, 0x0d,
+0x23, 0x4a, 0x23, 0x1b, 0x4c, 0x37, 0x8f, 0x4e, 0x45, 0x77, 0x50,
+0x52, 0xe2, 0x52, 0x1c, 0x92, 0x54, 0x1c, 0x52, 0x56, 0x07, 0x00,
+0x58, 0x2f, 0xc6, 0x5a, 0x0b, 0x00, 0x5c, 0x30, 0x06, 0x5e, 0x17,
+0x00, 0x60, 0x3d, 0xda, 0x62, 0x29, 0x00, 0x64, 0x3e, 0x41, 0x66,
+0x39, 0x00, 0x68, 0x4c, 0x48, 0x6a, 0x49, 0x00, 0x6c, 0x4c, 0x6c,
+0x6e, 0x11, 0xd2, 0x70, 0x16, 0x0c, 0x72, 0x00, 0x00, 0x74, 0x00,
+0x80, 0x76, 0x0f, 0x00, 0x78, 0x00, 0x80, 0x7a, 0x13, 0x00, 0x7c,
+0x80, 0x00, 0x7e, 0x80, 0x80
+};
+
+unsigned char page_seven[] __initdata = {
+0x0f, 0xff, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,
+0x08, 0x00, 0x00, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x0f,
+0xff, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff,
+0x0f, 0xff, 0x0f, 0xff, 0x02, 0xe9, 0x06, 0x8c, 0x06, 0x8c, 0x0f,
+0xff, 0x1a, 0x75, 0x0d, 0x8b, 0x04, 0xe9, 0x0b, 0x16, 0x1a, 0x38,
+0x0d, 0xc8, 0x04, 0x6f, 0x0b, 0x91, 0x0f, 0xff, 0x06, 0x40, 0x06,
+0x40, 0x02, 0x8f, 0x0f, 0xff, 0x06, 0x62, 0x06, 0x62, 0x02, 0x7b,
+0x0f, 0xff, 0x06, 0x97, 0x06, 0x97, 0x02, 0x52, 0x0f, 0xff, 0x06,
+0xf6, 0x06, 0xf6, 0x02, 0x19, 0x05, 0x55, 0x05, 0x55, 0x05, 0x55,
+0x05, 0x55, 0x05, 0x55, 0x05, 0x55, 0x05, 0x55, 0x05, 0x55, 0x14,
+0xda, 0x0d, 0x93, 0x04, 0xda, 0x05, 0x93, 0x14, 0xda, 0x0d, 0x93,
+0x04, 0xda, 0x05, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x02, 0x00
+};
+
+unsigned char page_zero_v2[] __initdata = {
+0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+unsigned char page_one_v2[] __initdata = {
+0x01, 0xc0, 0x01, 0xfa, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+unsigned char page_two_v2[] __initdata = {
+0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00
+};
+unsigned char page_three_v2[] __initdata = {
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00
+};
+unsigned char page_four_v2[] __initdata = {
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00
+};
+
+unsigned char page_seven_v2[] __initdata = {
+0x0f, 0xff, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+unsigned char mod_v2[] __initdata = {
+0x01, 0x00, 0x02, 0x00, 0x01, 0x01, 0x02, 0x00, 0x01, 0x02, 0x02,
+0x00, 0x01, 0x03, 0x02, 0x00, 0x01, 0x04, 0x02, 0x00, 0x01, 0x05,
+0x02, 0x00, 0x01, 0x06, 0x02, 0x00, 0x01, 0x07, 0x02, 0x00, 0xb0,
+0x20, 0xb1, 0x20, 0xb2, 0x20, 0xb3, 0x20, 0xb4, 0x20, 0xb5, 0x20,
+0xb6, 0x20, 0xb7, 0x20, 0xf0, 0x20, 0xf1, 0x20, 0xf2, 0x20, 0xf3,
+0x20, 0xf4, 0x20, 0xf5, 0x20, 0xf6, 0x20, 0xf7, 0x20, 0x10, 0xff,
+0x11, 0xff, 0x12, 0xff, 0x13, 0xff, 0x14, 0xff, 0x15, 0xff, 0x16,
+0xff, 0x17, 0xff, 0x20, 0xff, 0x21, 0xff, 0x22, 0xff, 0x23, 0xff,
+0x24, 0xff, 0x25, 0xff, 0x26, 0xff, 0x27, 0xff, 0x30, 0x00, 0x31,
+0x00, 0x32, 0x00, 0x33, 0x00, 0x34, 0x00, 0x35, 0x00, 0x36, 0x00,
+0x37, 0x00, 0x40, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x44,
+0x00, 0x45, 0x00, 0x46, 0x00, 0x47, 0x00, 0x50, 0x00, 0x51, 0x00,
+0x52, 0x00, 0x53, 0x00, 0x54, 0x00, 0x55, 0x00, 0x56, 0x00, 0x57,
+0x00, 0x60, 0x00, 0x61, 0x00, 0x62, 0x00, 0x63, 0x00, 0x64, 0x00,
+0x65, 0x00, 0x66, 0x00, 0x67, 0x00, 0x70, 0xc0, 0x71, 0xc0, 0x72,
+0xc0, 0x73, 0xc0, 0x74, 0xc0, 0x75, 0xc0, 0x76, 0xc0, 0x77, 0xc0,
+0x80, 0x00, 0x81, 0x00, 0x82, 0x00, 0x83, 0x00, 0x84, 0x00, 0x85,
+0x00, 0x86, 0x00, 0x87, 0x00, 0x90, 0x00, 0x91, 0x00, 0x92, 0x00,
+0x93, 0x00, 0x94, 0x00, 0x95, 0x00, 0x96, 0x00, 0x97, 0x00, 0xa0,
+0x00, 0xa1, 0x00, 0xa2, 0x00, 0xa3, 0x00, 0xa4, 0x00, 0xa5, 0x00,
+0xa6, 0x00, 0xa7, 0x00, 0xc0, 0x00, 0xc1, 0x00, 0xc2, 0x00, 0xc3,
+0x00, 0xc4, 0x00, 0xc5, 0x00, 0xc6, 0x00, 0xc7, 0x00, 0xd0, 0x00,
+0xd1, 0x00, 0xd2, 0x00, 0xd3, 0x00, 0xd4, 0x00, 0xd5, 0x00, 0xd6,
+0x00, 0xd7, 0x00, 0xe0, 0x00, 0xe1, 0x00, 0xe2, 0x00, 0xe3, 0x00,
+0xe4, 0x00, 0xe5, 0x00, 0xe6, 0x00, 0xe7, 0x00, 0x01, 0x00, 0x02,
+0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x03,
+0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x05, 0x02, 0x01, 0x01,
+0x06, 0x02, 0x01, 0x01, 0x07, 0x02, 0x01
+};
+unsigned char coefficients[] __initdata = {
+0x07, 0x46, 0x00, 0x00, 0x07, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x03,
+0x11, 0x00, 0x4d, 0x01, 0x32, 0x07, 0x46, 0x00, 0x00, 0x07, 0x49,
+0x00, 0x00, 0x07, 0x40, 0x00, 0x00, 0x07, 0x41, 0x00, 0x00, 0x01,
+0x40, 0x02, 0x40, 0x01, 0x41, 0x02, 0x60, 0x07, 0x40, 0x00, 0x00,
+0x07, 0x41, 0x00, 0x00, 0x07, 0x47, 0x00, 0x00, 0x07, 0x4a, 0x00,
+0x00, 0x00, 0x47, 0x01, 0x00, 0x00, 0x4a, 0x01, 0x20, 0x07, 0x47,
+0x00, 0x00, 0x07, 0x4a, 0x00, 0x00, 0x07, 0x7c, 0x00, 0x00, 0x07,
+0x7e, 0x00, 0x00, 0x00, 0x00, 0x01, 0x1c, 0x07, 0x7c, 0x00, 0x00,
+0x07, 0x7e, 0x00, 0x00, 0x07, 0x44, 0x00, 0x00, 0x00, 0x44, 0x01,
+0x00, 0x07, 0x44, 0x00, 0x00, 0x07, 0x42, 0x00, 0x00, 0x07, 0x43,
+0x00, 0x00, 0x00, 0x42, 0x01, 0x1a, 0x00, 0x43, 0x01, 0x20, 0x07,
+0x42, 0x00, 0x00, 0x07, 0x43, 0x00, 0x00, 0x07, 0x40, 0x00, 0x00,
+0x07, 0x41, 0x00, 0x00, 0x01, 0x40, 0x02, 0x40, 0x01, 0x41, 0x02,
+0x60, 0x07, 0x40, 0x00, 0x00, 0x07, 0x41, 0x00, 0x00, 0x07, 0x44,
+0x0f, 0xff, 0x07, 0x42, 0x00, 0x00, 0x07, 0x43, 0x00, 0x00, 0x07,
+0x40, 0x00, 0x00, 0x07, 0x41, 0x00, 0x00, 0x07, 0x51, 0x06, 0x40,
+0x07, 0x50, 0x06, 0x40, 0x07, 0x4f, 0x03, 0x81, 0x07, 0x53, 0x1a,
+0x76, 0x07, 0x54, 0x0d, 0x8b, 0x07, 0x55, 0x04, 0xe9, 0x07, 0x56,
+0x0b, 0x17, 0x07, 0x57, 0x1a, 0x38, 0x07, 0x58, 0x0d, 0xc9, 0x07,
+0x59, 0x04, 0x6f, 0x07, 0x5a, 0x0b, 0x91, 0x07, 0x73, 0x14, 0xda,
+0x07, 0x74, 0x0d, 0x93, 0x07, 0x75, 0x04, 0xd9, 0x07, 0x76, 0x05,
+0x93, 0x07, 0x77, 0x14, 0xda, 0x07, 0x78, 0x0d, 0x93, 0x07, 0x79,
+0x04, 0xd9, 0x07, 0x7a, 0x05, 0x93, 0x07, 0x5e, 0x03, 0x68, 0x07,
+0x5c, 0x04, 0x31, 0x07, 0x5d, 0x04, 0x31, 0x07, 0x62, 0x03, 0x52,
+0x07, 0x60, 0x04, 0x76, 0x07, 0x61, 0x04, 0x76, 0x07, 0x66, 0x03,
+0x2e, 0x07, 0x64, 0x04, 0xda, 0x07, 0x65, 0x04, 0xda, 0x07, 0x6a,
+0x02, 0xf6, 0x07, 0x68, 0x05, 0x62, 0x07, 0x69, 0x05, 0x62, 0x06,
+0x46, 0x0a, 0x22, 0x06, 0x48, 0x0d, 0x24, 0x06, 0x6e, 0x11, 0xd3,
+0x06, 0x70, 0x15, 0xcb, 0x06, 0x52, 0x20, 0x93, 0x06, 0x54, 0x20,
+0x54, 0x06, 0x4a, 0x27, 0x1d, 0x06, 0x58, 0x2f, 0xc8, 0x06, 0x5c,
+0x30, 0x07, 0x06, 0x4c, 0x37, 0x90, 0x06, 0x60, 0x3d, 0xdb, 0x06,
+0x64, 0x3e, 0x42, 0x06, 0x4e, 0x45, 0x78, 0x06, 0x68, 0x4c, 0x48,
+0x06, 0x6c, 0x4c, 0x6c, 0x06, 0x50, 0x52, 0xe2, 0x06, 0x42, 0x02,
+0xba
+};
+unsigned char coefficients2[] __initdata = {
+0x07, 0x46, 0x00, 0x00, 0x07, 0x49, 0x00, 0x00, 0x07, 0x45, 0x0f,
+0xff, 0x07, 0x48, 0x0f, 0xff, 0x07, 0x7b, 0x04, 0xcc, 0x07, 0x7d,
+0x04, 0xcc, 0x07, 0x7c, 0x00, 0x00, 0x07, 0x7e, 0x00, 0x00, 0x07,
+0x46, 0x00, 0x00, 0x07, 0x49, 0x00, 0x00, 0x07, 0x47, 0x00, 0x00,
+0x07, 0x4a, 0x00, 0x00, 0x07, 0x4c, 0x00, 0x00, 0x07, 0x4e, 0x00, 0x00
+};
+unsigned char coefficients3[] __initdata = {
+0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x28, 0x00, 0x51, 0x00,
+0x51, 0x00, 0x7a, 0x00, 0x7a, 0x00, 0xa3, 0x00, 0xa3, 0x00, 0xcc,
+0x00, 0xcc, 0x00, 0xf5, 0x00, 0xf5, 0x01, 0x1e, 0x01, 0x1e, 0x01,
+0x47, 0x01, 0x47, 0x01, 0x70, 0x01, 0x70, 0x01, 0x99, 0x01, 0x99,
+0x01, 0xc2, 0x01, 0xc2, 0x01, 0xeb, 0x01, 0xeb, 0x02, 0x14, 0x02,
+0x14, 0x02, 0x3d, 0x02, 0x3d, 0x02, 0x66, 0x02, 0x66, 0x02, 0x8f,
+0x02, 0x8f, 0x02, 0xb8, 0x02, 0xb8, 0x02, 0xe1, 0x02, 0xe1, 0x03,
+0x0a, 0x03, 0x0a, 0x03, 0x33, 0x03, 0x33, 0x03, 0x5c, 0x03, 0x5c,
+0x03, 0x85, 0x03, 0x85, 0x03, 0xae, 0x03, 0xae, 0x03, 0xd7, 0x03,
+0xd7, 0x04, 0x00, 0x04, 0x00, 0x04, 0x28, 0x04, 0x28, 0x04, 0x51,
+0x04, 0x51, 0x04, 0x7a, 0x04, 0x7a, 0x04, 0xa3, 0x04, 0xa3, 0x04,
+0xcc, 0x04, 0xcc, 0x04, 0xf5, 0x04, 0xf5, 0x05, 0x1e, 0x05, 0x1e,
+0x05, 0x47, 0x05, 0x47, 0x05, 0x70, 0x05, 0x70, 0x05, 0x99, 0x05,
+0x99, 0x05, 0xc2, 0x05, 0xc2, 0x05, 0xeb, 0x05, 0xeb, 0x06, 0x14,
+0x06, 0x14, 0x06, 0x3d, 0x06, 0x3d, 0x06, 0x66, 0x06, 0x66, 0x06,
+0x8f, 0x06, 0x8f, 0x06, 0xb8, 0x06, 0xb8, 0x06, 0xe1, 0x06, 0xe1,
+0x07, 0x0a, 0x07, 0x0a, 0x07, 0x33, 0x07, 0x33, 0x07, 0x5c, 0x07,
+0x5c, 0x07, 0x85, 0x07, 0x85, 0x07, 0xae, 0x07, 0xae, 0x07, 0xd7,
+0x07, 0xd7, 0x08, 0x00, 0x08, 0x00, 0x08, 0x28, 0x08, 0x28, 0x08,
+0x51, 0x08, 0x51, 0x08, 0x7a, 0x08, 0x7a, 0x08, 0xa3, 0x08, 0xa3,
+0x08, 0xcc, 0x08, 0xcc, 0x08, 0xf5, 0x08, 0xf5, 0x09, 0x1e, 0x09,
+0x1e, 0x09, 0x47, 0x09, 0x47, 0x09, 0x70, 0x09, 0x70, 0x09, 0x99,
+0x09, 0x99, 0x09, 0xc2, 0x09, 0xc2, 0x09, 0xeb, 0x09, 0xeb, 0x0a,
+0x14, 0x0a, 0x14, 0x0a, 0x3d, 0x0a, 0x3d, 0x0a, 0x66, 0x0a, 0x66,
+0x0a, 0x8f, 0x0a, 0x8f, 0x0a, 0xb8, 0x0a, 0xb8, 0x0a, 0xe1, 0x0a,
+0xe1, 0x0b, 0x0a, 0x0b, 0x0a, 0x0b, 0x33, 0x0b, 0x33, 0x0b, 0x5c,
+0x0b, 0x5c, 0x0b, 0x85, 0x0b, 0x85, 0x0b, 0xae, 0x0b, 0xae, 0x0b,
+0xd7, 0x0b, 0xd7, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x28, 0x0c, 0x28,
+0x0c, 0x51, 0x0c, 0x51, 0x0c, 0x7a, 0x0c, 0x7a, 0x0c, 0xa3, 0x0c,
+0xa3, 0x0c, 0xcc, 0x0c, 0xcc, 0x0c, 0xf5, 0x0c, 0xf5, 0x0d, 0x1e,
+0x0d, 0x1e, 0x0d, 0x47, 0x0d, 0x47, 0x0d, 0x70, 0x0d, 0x70, 0x0d,
+0x99, 0x0d, 0x99, 0x0d, 0xc2, 0x0d, 0xc2, 0x0d, 0xeb, 0x0d, 0xeb,
+0x0e, 0x14, 0x0e, 0x14, 0x0e, 0x3d, 0x0e, 0x3d, 0x0e, 0x66, 0x0e,
+0x66, 0x0e, 0x8f, 0x0e, 0x8f, 0x0e, 0xb8, 0x0e, 0xb8, 0x0e, 0xe1,
+0x0e, 0xe1, 0x0f, 0x0a, 0x0f, 0x0a, 0x0f, 0x33, 0x0f, 0x33, 0x0f,
+0x5c, 0x0f, 0x5c, 0x0f, 0x85, 0x0f, 0x85, 0x0f, 0xae, 0x0f, 0xae,
+0x0f, 0xd7, 0x0f, 0xd7, 0x0f, 0xff, 0x0f, 0xff
+};
+
+static int
+wavefront_fx_idle (snd_wavefront_t *dev)
+
+{
+	int i;
+	unsigned int x = 0x80;
+
+	for (i = 0; i < 1000; i++) {
+		x = inb (dev->fx_status);
+		if ((x & 0x80) == 0) {
+			break;
+		}
+	}
+
+	if (x & 0x80) {
+		snd_printk ("FX device never idle.\n");
+		return 0;
+	}
+
+	return (1);
+}
+
+static void
+wavefront_fx_mute (snd_wavefront_t *dev, int onoff)
+
+{
+	if (!wavefront_fx_idle(dev)) {
+		return;
+	}
+
+	outb (onoff ? 0x02 : 0x00, dev->fx_op);
+}
+
+static int
+wavefront_fx_memset (snd_wavefront_t *dev,
+		     int page,
+		     int addr,
+		     int cnt,
+		     unsigned short *data)
+{
+	if (page < 0 || page > 7) {
+		snd_printk ("FX memset: "
+			"page must be >= 0 and <= 7\n");
+		return -(EINVAL);
+	}
+
+	if (addr < 0 || addr > 0x7f) {
+		snd_printk ("FX memset: "
+			"addr must be >= 0 and <= 7f\n");
+		return -(EINVAL);
+	}
+
+	if (cnt == 1) {
+
+		outb (FX_LSB_TRANSFER, dev->fx_lcr);
+		outb (page, dev->fx_dsp_page);
+		outb (addr, dev->fx_dsp_addr);
+		outb ((data[0] >> 8), dev->fx_dsp_msb);
+		outb ((data[0] & 0xff), dev->fx_dsp_lsb);
+
+		snd_printk ("FX: addr %d:%x set to 0x%x\n",
+			page, addr, data[0]);
+
+	} else {
+		int i;
+
+		outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr);
+		outb (page, dev->fx_dsp_page);
+		outb (addr, dev->fx_dsp_addr);
+
+		for (i = 0; i < cnt; i++) {
+			outb ((data[i] >> 8), dev->fx_dsp_msb);
+			outb ((data[i] & 0xff), dev->fx_dsp_lsb);
+			if (!wavefront_fx_idle (dev)) {
+				break;
+			}
+		}
+
+		if (i != cnt) {
+			snd_printk ("FX memset "
+				    "(0x%x, 0x%x, 0x%lx, %d) incomplete\n",
+				    page, addr, (unsigned long) data, cnt);
+			return -(EIO);
+		}
+	}
+
+	return 0;
+}
+
+int
+snd_wavefront_fx_detect (snd_wavefront_t *dev)
+
+{
+	/* This is a crude check, but its the best one I have for now.
+	   Certainly on the Maui and the Tropez, wavefront_fx_idle() will
+	   report "never idle", which suggests that this test should
+	   work OK.
+	*/
+
+	if (inb (dev->fx_status) & 0x80) {
+		snd_printk ("Hmm, probably a Maui or Tropez.\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+int
+snd_wavefront_fx_open (snd_hwdep_t *hw, struct file *file)
+
+{
+	if (!try_module_get(hw->card->module))
+		return -EFAULT;
+	file->private_data = hw;
+	return 0;
+}
+
+int 
+snd_wavefront_fx_release (snd_hwdep_t *hw, struct file *file)
+
+{
+	module_put(hw->card->module);
+	return 0;
+}
+
+int
+snd_wavefront_fx_ioctl (snd_hwdep_t *sdev, struct file *file,
+			unsigned int cmd, unsigned long arg)
+
+{
+	snd_card_t *card;
+	snd_wavefront_card_t *acard;
+	snd_wavefront_t *dev;
+	wavefront_fx_info r;
+	unsigned short *page_data = NULL;
+	unsigned short *pd;
+	int err = 0;
+
+	snd_assert(sdev->card != NULL, return -ENODEV);
+	
+	card = sdev->card;
+
+	snd_assert(card->private_data != NULL, return -ENODEV);
+
+	acard = card->private_data;
+	dev = &acard->wavefront;
+
+	if (copy_from_user (&r, (void __user *)arg, sizeof (wavefront_fx_info)))
+		return -EFAULT;
+
+	switch (r.request) {
+	case WFFX_MUTE:
+		wavefront_fx_mute (dev, r.data[0]);
+		return -EIO;
+
+	case WFFX_MEMSET:
+		if (r.data[2] <= 0) {
+			snd_printk ("cannot write "
+				"<= 0 bytes to FX\n");
+			return -EIO;
+		} else if (r.data[2] == 1) {
+			pd = (unsigned short *) &r.data[3];
+		} else {
+			if (r.data[2] > 256) {
+				snd_printk ("cannot write "
+					    "> 512 bytes to FX\n");
+				return -EIO;
+			}
+			page_data = kmalloc(r.data[2] * sizeof(short), GFP_KERNEL);
+			if (!page_data)
+				return -ENOMEM;
+			if (copy_from_user (page_data,
+					    (unsigned char __user *) r.data[3],
+					    r.data[2] * sizeof(short))) {
+				kfree(page_data);
+				return -EFAULT;
+			}
+			pd = page_data;
+		}
+
+		err = wavefront_fx_memset (dev,
+			     r.data[0], /* page */
+			     r.data[1], /* addr */
+			     r.data[2], /* cnt */
+			     pd);
+		kfree(page_data);
+		break;
+
+	default:
+		snd_printk ("FX: ioctl %d not yet supported\n",
+			    r.request);
+		return -ENOTTY;
+	}
+	return err;
+}
+
+/* YSS225 initialization.
+
+   This code was developed using DOSEMU. The Turtle Beach SETUPSND
+   utility was run with I/O tracing in DOSEMU enabled, and a reconstruction
+   of the port I/O done, using the Yamaha faxback document as a guide
+   to add more logic to the code. Its really pretty weird.
+
+   There was an alternative approach of just dumping the whole I/O
+   sequence as a series of port/value pairs and a simple loop
+   that output it. However, I hope that eventually I'll get more
+   control over what this code does, and so I tried to stick with
+   a somewhat "algorithmic" approach.
+*/
+
+
+int __init
+snd_wavefront_fx_start (snd_wavefront_t *dev)
+
+{
+	unsigned int i, j;
+
+	/* Set all bits for all channels on the MOD unit to zero */
+	/* XXX But why do this twice ? */
+
+	for (j = 0; j < 2; j++) {
+		for (i = 0x10; i <= 0xff; i++) {
+	    
+			if (!wavefront_fx_idle (dev)) {
+				return (-1);
+			}
+	    
+			outb (i, dev->fx_mod_addr);
+			outb (0x0, dev->fx_mod_data);
+		}
+	}
+
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x02, dev->fx_op);                        /* mute on */
+
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x07, dev->fx_dsp_page);
+	outb (0x44, dev->fx_dsp_addr);
+	outb (0x00, dev->fx_dsp_msb);
+	outb (0x00, dev->fx_dsp_lsb);
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x07, dev->fx_dsp_page);
+	outb (0x42, dev->fx_dsp_addr);
+	outb (0x00, dev->fx_dsp_msb);
+	outb (0x00, dev->fx_dsp_lsb);
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x07, dev->fx_dsp_page);
+	outb (0x43, dev->fx_dsp_addr);
+	outb (0x00, dev->fx_dsp_msb);
+	outb (0x00, dev->fx_dsp_lsb);
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x07, dev->fx_dsp_page);
+	outb (0x7c, dev->fx_dsp_addr);
+	outb (0x00, dev->fx_dsp_msb);
+	outb (0x00, dev->fx_dsp_lsb);
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x07, dev->fx_dsp_page);
+	outb (0x7e, dev->fx_dsp_addr);
+	outb (0x00, dev->fx_dsp_msb);
+	outb (0x00, dev->fx_dsp_lsb);
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x07, dev->fx_dsp_page);
+	outb (0x46, dev->fx_dsp_addr);
+	outb (0x00, dev->fx_dsp_msb);
+	outb (0x00, dev->fx_dsp_lsb);
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x07, dev->fx_dsp_page);
+	outb (0x49, dev->fx_dsp_addr);
+	outb (0x00, dev->fx_dsp_msb);
+	outb (0x00, dev->fx_dsp_lsb);
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x07, dev->fx_dsp_page);
+	outb (0x47, dev->fx_dsp_addr);
+	outb (0x00, dev->fx_dsp_msb);
+	outb (0x00, dev->fx_dsp_lsb);
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x07, dev->fx_dsp_page);
+	outb (0x4a, dev->fx_dsp_addr);
+	outb (0x00, dev->fx_dsp_msb);
+	outb (0x00, dev->fx_dsp_lsb);
+
+	/* either because of stupidity by TB's programmers, or because it
+	   actually does something, rezero the MOD page.
+	*/
+	for (i = 0x10; i <= 0xff; i++) {
+	
+		if (!wavefront_fx_idle (dev)) {
+			return (-1);
+		}
+	
+		outb (i, dev->fx_mod_addr);
+		outb (0x0, dev->fx_mod_data);
+	}
+	/* load page zero */
+
+	outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr);
+	outb (0x00, dev->fx_dsp_page);
+	outb (0x00, dev->fx_dsp_addr);
+
+	for (i = 0; i < sizeof (page_zero); i += 2) {
+		outb (page_zero[i], dev->fx_dsp_msb);
+		outb (page_zero[i+1], dev->fx_dsp_lsb);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	/* Now load page one */
+
+	outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr);
+	outb (0x01, dev->fx_dsp_page);
+	outb (0x00, dev->fx_dsp_addr);
+
+	for (i = 0; i < sizeof (page_one); i += 2) {
+		outb (page_one[i], dev->fx_dsp_msb);
+		outb (page_one[i+1], dev->fx_dsp_lsb);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+    
+	outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr);
+	outb (0x02, dev->fx_dsp_page);
+	outb (0x00, dev->fx_dsp_addr);
+
+	for (i = 0; i < sizeof (page_two); i++) {
+		outb (page_two[i], dev->fx_dsp_lsb);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+    
+	outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr);
+	outb (0x03, dev->fx_dsp_page);
+	outb (0x00, dev->fx_dsp_addr);
+
+	for (i = 0; i < sizeof (page_three); i++) {
+		outb (page_three[i], dev->fx_dsp_lsb);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+    
+	outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr);
+	outb (0x04, dev->fx_dsp_page);
+	outb (0x00, dev->fx_dsp_addr);
+
+	for (i = 0; i < sizeof (page_four); i++) {
+		outb (page_four[i], dev->fx_dsp_lsb);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	/* Load memory area (page six) */
+    
+	outb (FX_LSB_TRANSFER, dev->fx_lcr); 
+	outb (0x06, dev->fx_dsp_page); 
+
+	for (i = 0; i < sizeof (page_six); i += 3) {
+		outb (page_six[i], dev->fx_dsp_addr);
+		outb (page_six[i+1], dev->fx_dsp_msb);
+		outb (page_six[i+2], dev->fx_dsp_lsb);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+    
+	outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr);
+	outb (0x07, dev->fx_dsp_page);
+	outb (0x00, dev->fx_dsp_addr);
+
+	for (i = 0; i < sizeof (page_seven); i += 2) {
+		outb (page_seven[i], dev->fx_dsp_msb);
+		outb (page_seven[i+1], dev->fx_dsp_lsb);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	/* Now setup the MOD area. We do this algorithmically in order to
+	   save a little data space. It could be done in the same fashion
+	   as the "pages".
+	*/
+
+	for (i = 0x00; i <= 0x0f; i++) {
+		outb (0x01, dev->fx_mod_addr);
+		outb (i, dev->fx_mod_data);
+		if (!wavefront_fx_idle (dev)) return (-1);
+		outb (0x02, dev->fx_mod_addr);
+		outb (0x00, dev->fx_mod_data);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	for (i = 0xb0; i <= 0xbf; i++) {
+		outb (i, dev->fx_mod_addr);
+		outb (0x20, dev->fx_mod_data);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	for (i = 0xf0; i <= 0xff; i++) {
+		outb (i, dev->fx_mod_addr);
+		outb (0x20, dev->fx_mod_data);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	for (i = 0x10; i <= 0x1d; i++) {
+		outb (i, dev->fx_mod_addr);
+		outb (0xff, dev->fx_mod_data);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	outb (0x1e, dev->fx_mod_addr);
+	outb (0x40, dev->fx_mod_data);
+	if (!wavefront_fx_idle (dev)) return (-1);
+
+	for (i = 0x1f; i <= 0x2d; i++) {
+		outb (i, dev->fx_mod_addr);
+		outb (0xff, dev->fx_mod_data);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	outb (0x2e, dev->fx_mod_addr);
+	outb (0x00, dev->fx_mod_data);
+	if (!wavefront_fx_idle (dev)) return (-1);
+
+	for (i = 0x2f; i <= 0x3e; i++) {
+		outb (i, dev->fx_mod_addr);
+		outb (0x00, dev->fx_mod_data);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	outb (0x3f, dev->fx_mod_addr);
+	outb (0x20, dev->fx_mod_data);
+	if (!wavefront_fx_idle (dev)) return (-1);
+
+	for (i = 0x40; i <= 0x4d; i++) {
+		outb (i, dev->fx_mod_addr);
+		outb (0x00, dev->fx_mod_data);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	outb (0x4e, dev->fx_mod_addr);
+	outb (0x0e, dev->fx_mod_data);
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x4f, dev->fx_mod_addr);
+	outb (0x0e, dev->fx_mod_data);
+	if (!wavefront_fx_idle (dev)) return (-1);
+
+
+	for (i = 0x50; i <= 0x6b; i++) {
+		outb (i, dev->fx_mod_addr);
+		outb (0x00, dev->fx_mod_data);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	outb (0x6c, dev->fx_mod_addr);
+	outb (0x40, dev->fx_mod_data);
+	if (!wavefront_fx_idle (dev)) return (-1);
+
+	outb (0x6d, dev->fx_mod_addr);
+	outb (0x00, dev->fx_mod_data);
+	if (!wavefront_fx_idle (dev)) return (-1);
+
+	outb (0x6e, dev->fx_mod_addr);
+	outb (0x40, dev->fx_mod_data);
+	if (!wavefront_fx_idle (dev)) return (-1);
+
+	outb (0x6f, dev->fx_mod_addr);
+	outb (0x40, dev->fx_mod_data);
+	if (!wavefront_fx_idle (dev)) return (-1);
+
+	for (i = 0x70; i <= 0x7f; i++) {
+		outb (i, dev->fx_mod_addr);
+		outb (0xc0, dev->fx_mod_data);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+    
+	for (i = 0x80; i <= 0xaf; i++) {
+		outb (i, dev->fx_mod_addr);
+		outb (0x00, dev->fx_mod_data);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	for (i = 0xc0; i <= 0xdd; i++) {
+		outb (i, dev->fx_mod_addr);
+		outb (0x00, dev->fx_mod_data);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	outb (0xde, dev->fx_mod_addr);
+	outb (0x10, dev->fx_mod_data);
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0xdf, dev->fx_mod_addr);
+	outb (0x10, dev->fx_mod_data);
+	if (!wavefront_fx_idle (dev)) return (-1);
+
+	for (i = 0xe0; i <= 0xef; i++) {
+		outb (i, dev->fx_mod_addr);
+		outb (0x00, dev->fx_mod_data);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	for (i = 0x00; i <= 0x0f; i++) {
+		outb (0x01, dev->fx_mod_addr);
+		outb (i, dev->fx_mod_data);
+		outb (0x02, dev->fx_mod_addr);
+		outb (0x01, dev->fx_mod_data);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	outb (0x02, dev->fx_op); /* mute on */
+
+	/* Now set the coefficients and so forth for the programs above */
+
+	for (i = 0; i < sizeof (coefficients); i += 4) {
+		outb (coefficients[i], dev->fx_dsp_page);
+		outb (coefficients[i+1], dev->fx_dsp_addr);
+		outb (coefficients[i+2], dev->fx_dsp_msb);
+		outb (coefficients[i+3], dev->fx_dsp_lsb);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	/* Some settings (?) that are too small to bundle into loops */
+
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x1e, dev->fx_mod_addr);
+	outb (0x14, dev->fx_mod_data);
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0xde, dev->fx_mod_addr);
+	outb (0x20, dev->fx_mod_data);
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0xdf, dev->fx_mod_addr);
+	outb (0x20, dev->fx_mod_data);
+    
+	/* some more coefficients */
+
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x06, dev->fx_dsp_page);
+	outb (0x78, dev->fx_dsp_addr);
+	outb (0x00, dev->fx_dsp_msb);
+	outb (0x40, dev->fx_dsp_lsb);
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x07, dev->fx_dsp_page);
+	outb (0x03, dev->fx_dsp_addr);
+	outb (0x0f, dev->fx_dsp_msb);
+	outb (0xff, dev->fx_dsp_lsb);
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x07, dev->fx_dsp_page);
+	outb (0x0b, dev->fx_dsp_addr);
+	outb (0x0f, dev->fx_dsp_msb);
+	outb (0xff, dev->fx_dsp_lsb);
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x07, dev->fx_dsp_page);
+	outb (0x02, dev->fx_dsp_addr);
+	outb (0x00, dev->fx_dsp_msb);
+	outb (0x00, dev->fx_dsp_lsb);
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x07, dev->fx_dsp_page);
+	outb (0x0a, dev->fx_dsp_addr);
+	outb (0x00, dev->fx_dsp_msb);
+	outb (0x00, dev->fx_dsp_lsb);
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x07, dev->fx_dsp_page);
+	outb (0x46, dev->fx_dsp_addr);
+	outb (0x00, dev->fx_dsp_msb);
+	outb (0x00, dev->fx_dsp_lsb);
+	if (!wavefront_fx_idle (dev)) return (-1);
+	outb (0x07, dev->fx_dsp_page);
+	outb (0x49, dev->fx_dsp_addr);
+	outb (0x00, dev->fx_dsp_msb);
+	outb (0x00, dev->fx_dsp_lsb);
+    
+	/* Now, for some strange reason, lets reload every page
+	   and all the coefficients over again. I have *NO* idea
+	   why this is done. I do know that no sound is produced
+	   is this phase is omitted.
+	*/
+
+	outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr);
+	outb (0x00, dev->fx_dsp_page);  
+	outb (0x10, dev->fx_dsp_addr);
+
+	for (i = 0; i < sizeof (page_zero_v2); i += 2) {
+		outb (page_zero_v2[i], dev->fx_dsp_msb);
+		outb (page_zero_v2[i+1], dev->fx_dsp_lsb);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+    
+	outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr);
+	outb (0x01, dev->fx_dsp_page);
+	outb (0x10, dev->fx_dsp_addr);
+
+	for (i = 0; i < sizeof (page_one_v2); i += 2) {
+		outb (page_one_v2[i], dev->fx_dsp_msb);
+		outb (page_one_v2[i+1], dev->fx_dsp_lsb);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+    
+	if (!wavefront_fx_idle (dev)) return (-1);
+	if (!wavefront_fx_idle (dev)) return (-1);
+    
+	outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr);
+	outb (0x02, dev->fx_dsp_page);
+	outb (0x10, dev->fx_dsp_addr);
+
+	for (i = 0; i < sizeof (page_two_v2); i++) {
+		outb (page_two_v2[i], dev->fx_dsp_lsb);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+	outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr);
+	outb (0x03, dev->fx_dsp_page);
+	outb (0x10, dev->fx_dsp_addr);
+
+	for (i = 0; i < sizeof (page_three_v2); i++) {
+		outb (page_three_v2[i], dev->fx_dsp_lsb);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+    
+	outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr);
+	outb (0x04, dev->fx_dsp_page);
+	outb (0x10, dev->fx_dsp_addr);
+
+	for (i = 0; i < sizeof (page_four_v2); i++) {
+		outb (page_four_v2[i], dev->fx_dsp_lsb);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+    
+	outb (FX_LSB_TRANSFER, dev->fx_lcr);
+	outb (0x06, dev->fx_dsp_page);
+
+	/* Page six v.2 is algorithmic */
+    
+	for (i = 0x10; i <= 0x3e; i += 2) {
+		outb (i, dev->fx_dsp_addr);
+		outb (0x00, dev->fx_dsp_msb);
+		outb (0x00, dev->fx_dsp_lsb);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr);
+	outb (0x07, dev->fx_dsp_page);
+	outb (0x10, dev->fx_dsp_addr);
+
+	for (i = 0; i < sizeof (page_seven_v2); i += 2) {
+		outb (page_seven_v2[i], dev->fx_dsp_msb);
+		outb (page_seven_v2[i+1], dev->fx_dsp_lsb);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	for (i = 0x00; i < sizeof(mod_v2); i += 2) {
+		outb (mod_v2[i], dev->fx_mod_addr);
+		outb (mod_v2[i+1], dev->fx_mod_data);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	for (i = 0; i < sizeof (coefficients2); i += 4) {
+		outb (coefficients2[i], dev->fx_dsp_page);
+		outb (coefficients2[i+1], dev->fx_dsp_addr);
+		outb (coefficients2[i+2], dev->fx_dsp_msb);
+		outb (coefficients2[i+3], dev->fx_dsp_lsb);
+		if (!wavefront_fx_idle (dev)) return (-1);
+	}
+
+	for (i = 0; i < sizeof (coefficients3); i += 2) {
+		int x;
+
+		outb (0x07, dev->fx_dsp_page);
+		x = (i % 4) ? 0x4e : 0x4c;
+		outb (x, dev->fx_dsp_addr);
+		outb (coefficients3[i], dev->fx_dsp_msb);
+		outb (coefficients3[i+1], dev->fx_dsp_lsb);
+	}
+
+	outb (0x00, dev->fx_op); /* mute off */
+	if (!wavefront_fx_idle (dev)) return (-1);
+
+	return (0);
+}
diff --git a/sound/isa/wavefront/wavefront_midi.c b/sound/isa/wavefront/wavefront_midi.c
new file mode 100644
index 0000000..6f51d64
--- /dev/null
+++ b/sound/isa/wavefront/wavefront_midi.c
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) by Paul Barton-Davis 1998-1999
+ *
+ * This file is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this
+ * software for more info.  
+ */
+
+/* The low level driver for the WaveFront ICS2115 MIDI interface(s)
+ *
+ * Note that there is also an MPU-401 emulation (actually, a UART-401
+ * emulation) on the CS4232 on the Tropez and Tropez Plus. This code
+ * has nothing to do with that interface at all.
+ *
+ * The interface is essentially just a UART-401, but is has the
+ * interesting property of supporting what Turtle Beach called
+ * "Virtual MIDI" mode. In this mode, there are effectively *two*
+ * MIDI buses accessible via the interface, one that is routed
+ * solely to/from the external WaveFront synthesizer and the other
+ * corresponding to the pin/socket connector used to link external
+ * MIDI devices to the board.
+ *
+ * This driver fully supports this mode, allowing two distinct MIDI
+ * busses to be used completely independently, giving 32 channels of
+ * MIDI routing, 16 to the WaveFront synth and 16 to the external MIDI
+ * bus. The devices are named /dev/snd/midiCnD0 and /dev/snd/midiCnD1,
+ * where `n' is the card number. Note that the device numbers may be
+ * something other than 0 and 1 if the CS4232 UART/MPU-401 interface
+ * is enabled.
+ *
+ * Switching between the two is accomplished externally by the driver
+ * using the two otherwise unused MIDI bytes. See the code for more details.
+ *
+ * NOTE: VIRTUAL MIDI MODE IS ON BY DEFAULT (see lowlevel/isa/wavefront.c)
+ *
+ * The main reason to turn off Virtual MIDI mode is when you want to
+ * tightly couple the WaveFront synth with an external MIDI
+ * device. You won't be able to distinguish the source of any MIDI
+ * data except via SysEx ID, but thats probably OK, since for the most
+ * part, the WaveFront won't be sending any MIDI data at all.
+ *  
+ * The main reason to turn on Virtual MIDI Mode is to provide two
+ * completely independent 16-channel MIDI buses, one to the
+ * WaveFront and one to any external MIDI devices. Given the 32
+ * voice nature of the WaveFront, its pretty easy to find a use
+ * for all 16 channels driving just that synth.
+ *  
+ */
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <sound/core.h>
+#include <sound/snd_wavefront.h>
+
+static inline int 
+wf_mpu_status (snd_wavefront_midi_t *midi)
+
+{
+	return inb (midi->mpu_status_port);
+}
+
+static inline int 
+input_avail (snd_wavefront_midi_t *midi)
+
+{
+	return !(wf_mpu_status(midi) & INPUT_AVAIL);
+}
+
+static inline int
+output_ready (snd_wavefront_midi_t *midi)
+
+{
+	return !(wf_mpu_status(midi) & OUTPUT_READY);
+}
+
+static inline int 
+read_data (snd_wavefront_midi_t *midi)
+
+{
+	return inb (midi->mpu_data_port);
+}
+
+static inline void 
+write_data (snd_wavefront_midi_t *midi, unsigned char byte)
+
+{
+	outb (byte, midi->mpu_data_port);
+}
+
+static snd_wavefront_midi_t *
+get_wavefront_midi (snd_rawmidi_substream_t *substream)
+
+{
+	snd_card_t *card;
+	snd_wavefront_card_t *acard;
+
+	if (substream == NULL || substream->rmidi == NULL) 
+	        return NULL;
+
+	card = substream->rmidi->card;
+
+	if (card == NULL) 
+	        return NULL;
+
+	if (card->private_data == NULL) 
+ 	        return NULL;
+
+	acard = card->private_data;
+
+	return &acard->wavefront.midi;
+}
+
+static void snd_wavefront_midi_output_write(snd_wavefront_card_t *card)
+{
+	snd_wavefront_midi_t *midi = &card->wavefront.midi;
+	snd_wavefront_mpu_id  mpu;
+	unsigned long flags;
+	unsigned char midi_byte;
+	int max = 256, mask = 1;
+	int timeout;
+
+	/* Its not OK to try to change the status of "virtuality" of
+	   the MIDI interface while we're outputting stuff.  See
+	   snd_wavefront_midi_{enable,disable}_virtual () for the
+	   other half of this.  
+
+	   The first loop attempts to flush any data from the
+	   current output device, and then the second 
+	   emits the switch byte (if necessary), and starts
+	   outputting data for the output device currently in use.
+	*/
+
+	if (midi->substream_output[midi->output_mpu] == NULL) {
+		goto __second;
+	}
+
+	while (max > 0) {
+
+		/* XXX fix me - no hard timing loops allowed! */
+
+		for (timeout = 30000; timeout > 0; timeout--) {
+			if (output_ready (midi))
+				break;
+		}
+	
+		spin_lock_irqsave (&midi->virtual, flags);
+		if ((midi->mode[midi->output_mpu] & MPU401_MODE_OUTPUT) == 0) {
+			spin_unlock_irqrestore (&midi->virtual, flags);
+			goto __second;
+		}
+		if (output_ready (midi)) {
+			if (snd_rawmidi_transmit(midi->substream_output[midi->output_mpu], &midi_byte, 1) == 1) {
+				if (!midi->isvirtual ||
+					(midi_byte != WF_INTERNAL_SWITCH &&
+					 midi_byte != WF_EXTERNAL_SWITCH))
+					write_data(midi, midi_byte);
+				max--;
+			} else {
+				if (midi->istimer) {
+					if (--midi->istimer <= 0)
+						del_timer(&midi->timer);
+				}
+				midi->mode[midi->output_mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER;
+				spin_unlock_irqrestore (&midi->virtual, flags);
+				goto __second;
+			}
+		} else {
+			spin_unlock_irqrestore (&midi->virtual, flags);
+			return;
+		}
+		spin_unlock_irqrestore (&midi->virtual, flags);
+	}
+
+      __second:
+
+	if (midi->substream_output[!midi->output_mpu] == NULL) {
+		return;
+	}
+
+	while (max > 0) {
+
+		/* XXX fix me - no hard timing loops allowed! */
+
+		for (timeout = 30000; timeout > 0; timeout--) {
+			if (output_ready (midi))
+				break;
+		}
+	
+		spin_lock_irqsave (&midi->virtual, flags);
+		if (!midi->isvirtual)
+			mask = 0;
+		mpu = midi->output_mpu ^ mask;
+		mask = 0;	/* don't invert the value from now */
+		if ((midi->mode[mpu] & MPU401_MODE_OUTPUT) == 0) {
+			spin_unlock_irqrestore (&midi->virtual, flags);
+			return;
+		}
+		if (snd_rawmidi_transmit_empty(midi->substream_output[mpu]))
+			goto __timer;
+		if (output_ready (midi)) {
+			if (mpu != midi->output_mpu) {
+				write_data(midi, mpu == internal_mpu ?
+							WF_INTERNAL_SWITCH :
+							WF_EXTERNAL_SWITCH);
+				midi->output_mpu = mpu;
+			} else if (snd_rawmidi_transmit(midi->substream_output[mpu], &midi_byte, 1) == 1) {
+				if (!midi->isvirtual ||
+					(midi_byte != WF_INTERNAL_SWITCH &&
+					 midi_byte != WF_EXTERNAL_SWITCH))
+					write_data(midi, midi_byte);
+				max--;
+			} else {
+			      __timer:
+				if (midi->istimer) {
+					if (--midi->istimer <= 0)
+						del_timer(&midi->timer);
+				}
+				midi->mode[mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER;
+				spin_unlock_irqrestore (&midi->virtual, flags);
+				return;
+			}
+		} else {
+			spin_unlock_irqrestore (&midi->virtual, flags);
+			return;
+		}
+		spin_unlock_irqrestore (&midi->virtual, flags);
+	}
+}
+
+static int snd_wavefront_midi_input_open(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	snd_wavefront_midi_t *midi;
+	snd_wavefront_mpu_id mpu;
+
+	snd_assert(substream != NULL && substream->rmidi != NULL, return -EIO);
+	snd_assert(substream->rmidi->private_data != NULL, return -EIO);
+
+	mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
+
+	if ((midi = get_wavefront_midi (substream)) == NULL)
+	        return -EIO;
+
+	spin_lock_irqsave (&midi->open, flags);
+	midi->mode[mpu] |= MPU401_MODE_INPUT;
+	midi->substream_input[mpu] = substream;
+	spin_unlock_irqrestore (&midi->open, flags);
+
+	return 0;
+}
+
+static int snd_wavefront_midi_output_open(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	snd_wavefront_midi_t *midi;
+	snd_wavefront_mpu_id mpu;
+
+	snd_assert(substream != NULL && substream->rmidi != NULL, return -EIO);
+	snd_assert(substream->rmidi->private_data != NULL, return -EIO);
+
+	mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
+
+	if ((midi = get_wavefront_midi (substream)) == NULL)
+	        return -EIO;
+
+	spin_lock_irqsave (&midi->open, flags);
+	midi->mode[mpu] |= MPU401_MODE_OUTPUT;
+	midi->substream_output[mpu] = substream;
+	spin_unlock_irqrestore (&midi->open, flags);
+
+	return 0;
+}
+
+static int snd_wavefront_midi_input_close(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	snd_wavefront_midi_t *midi;
+	snd_wavefront_mpu_id mpu;
+
+	snd_assert(substream != NULL && substream->rmidi != NULL, return -EIO);
+	snd_assert(substream->rmidi->private_data != NULL, return -EIO);
+
+	mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
+
+	if ((midi = get_wavefront_midi (substream)) == NULL)
+	        return -EIO;
+
+	spin_lock_irqsave (&midi->open, flags);
+	midi->mode[mpu] &= ~MPU401_MODE_INPUT;
+	spin_unlock_irqrestore (&midi->open, flags);
+
+	return 0;
+}
+
+static int snd_wavefront_midi_output_close(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	snd_wavefront_midi_t *midi;
+	snd_wavefront_mpu_id mpu;
+
+	snd_assert(substream != NULL && substream->rmidi != NULL, return -EIO);
+	snd_assert(substream->rmidi->private_data != NULL, return -EIO);
+
+	mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
+
+	if ((midi = get_wavefront_midi (substream)) == NULL)
+	        return -EIO;
+
+	spin_lock_irqsave (&midi->open, flags);
+	midi->mode[mpu] &= ~MPU401_MODE_OUTPUT;
+	spin_unlock_irqrestore (&midi->open, flags);
+	return 0;
+}
+
+static void snd_wavefront_midi_input_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+	unsigned long flags;
+	snd_wavefront_midi_t *midi;
+	snd_wavefront_mpu_id mpu;
+
+	if (substream == NULL || substream->rmidi == NULL) 
+	        return;
+
+	if (substream->rmidi->private_data == NULL)
+	        return;
+
+	mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
+
+	if ((midi = get_wavefront_midi (substream)) == NULL) {
+		return;
+	}
+
+	spin_lock_irqsave (&midi->virtual, flags);
+	if (up) {
+		midi->mode[mpu] |= MPU401_MODE_INPUT_TRIGGER;
+	} else {
+		midi->mode[mpu] &= ~MPU401_MODE_INPUT_TRIGGER;
+	}
+	spin_unlock_irqrestore (&midi->virtual, flags);
+}
+
+static void snd_wavefront_midi_output_timer(unsigned long data)
+{
+	snd_wavefront_card_t *card = (snd_wavefront_card_t *)data;
+	snd_wavefront_midi_t *midi = &card->wavefront.midi;
+	unsigned long flags;
+	
+	spin_lock_irqsave (&midi->virtual, flags);
+	midi->timer.expires = 1 + jiffies;
+	add_timer(&midi->timer);
+	spin_unlock_irqrestore (&midi->virtual, flags);
+	snd_wavefront_midi_output_write(card);
+}
+
+static void snd_wavefront_midi_output_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+	unsigned long flags;
+	snd_wavefront_midi_t *midi;
+	snd_wavefront_mpu_id mpu;
+
+	if (substream == NULL || substream->rmidi == NULL) 
+	        return;
+
+	if (substream->rmidi->private_data == NULL)
+	        return;
+
+	mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
+
+	if ((midi = get_wavefront_midi (substream)) == NULL) {
+		return;
+	}
+
+	spin_lock_irqsave (&midi->virtual, flags);
+	if (up) {
+		if ((midi->mode[mpu] & MPU401_MODE_OUTPUT_TRIGGER) == 0) {
+			if (!midi->istimer) {
+				init_timer(&midi->timer);
+				midi->timer.function = snd_wavefront_midi_output_timer;
+				midi->timer.data = (unsigned long) substream->rmidi->card->private_data;
+				midi->timer.expires = 1 + jiffies;
+				add_timer(&midi->timer);
+			}
+			midi->istimer++;
+			midi->mode[mpu] |= MPU401_MODE_OUTPUT_TRIGGER;
+		}
+	} else {
+		midi->mode[mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER;
+	}
+	spin_unlock_irqrestore (&midi->virtual, flags);
+
+	if (up)
+		snd_wavefront_midi_output_write((snd_wavefront_card_t *)substream->rmidi->card->private_data);
+}
+
+void
+snd_wavefront_midi_interrupt (snd_wavefront_card_t *card)
+
+{
+	unsigned long flags;
+	snd_wavefront_midi_t *midi;
+	static snd_rawmidi_substream_t *substream = NULL;
+	static int mpu = external_mpu; 
+	int max = 128;
+	unsigned char byte;
+
+	midi = &card->wavefront.midi;
+
+	if (!input_avail (midi)) { /* not for us */
+		snd_wavefront_midi_output_write(card);
+		return;
+	}
+
+	spin_lock_irqsave (&midi->virtual, flags);
+	while (--max) {
+
+		if (input_avail (midi)) {
+			byte = read_data (midi);
+
+			if (midi->isvirtual) {				
+				if (byte == WF_EXTERNAL_SWITCH) {
+					substream = midi->substream_input[external_mpu];
+					mpu = external_mpu;
+				} else if (byte == WF_INTERNAL_SWITCH) { 
+					substream = midi->substream_output[internal_mpu];
+					mpu = internal_mpu;
+				} /* else just leave it as it is */
+			} else {
+				substream = midi->substream_input[internal_mpu];
+				mpu = internal_mpu;
+			}
+
+			if (substream == NULL) {
+				continue;
+			}
+
+			if (midi->mode[mpu] & MPU401_MODE_INPUT_TRIGGER) {
+				snd_rawmidi_receive(substream, &byte, 1);
+			}
+		} else {
+			break;
+		}
+	} 
+	spin_unlock_irqrestore (&midi->virtual, flags);
+
+	snd_wavefront_midi_output_write(card);
+}
+
+void
+snd_wavefront_midi_enable_virtual (snd_wavefront_card_t *card)
+
+{
+	unsigned long flags;
+
+	spin_lock_irqsave (&card->wavefront.midi.virtual, flags);
+	card->wavefront.midi.isvirtual = 1;
+	card->wavefront.midi.output_mpu = internal_mpu;
+	card->wavefront.midi.input_mpu = internal_mpu;
+	spin_unlock_irqrestore (&card->wavefront.midi.virtual, flags);
+}
+
+void
+snd_wavefront_midi_disable_virtual (snd_wavefront_card_t *card)
+
+{
+	unsigned long flags;
+
+	spin_lock_irqsave (&card->wavefront.midi.virtual, flags);
+	// snd_wavefront_midi_input_close (card->ics2115_external_rmidi);
+	// snd_wavefront_midi_output_close (card->ics2115_external_rmidi);
+	card->wavefront.midi.isvirtual = 0;
+	spin_unlock_irqrestore (&card->wavefront.midi.virtual, flags);
+}
+
+int __init
+snd_wavefront_midi_start (snd_wavefront_card_t *card)
+
+{
+	int ok, i;
+	unsigned char rbuf[4], wbuf[4];
+	snd_wavefront_t *dev;
+	snd_wavefront_midi_t *midi;
+
+	dev = &card->wavefront;
+	midi = &dev->midi;
+
+	/* The ICS2115 MPU-401 interface doesn't do anything
+	   until its set into UART mode.
+	*/
+
+	/* XXX fix me - no hard timing loops allowed! */
+
+	for (i = 0; i < 30000 && !output_ready (midi); i++);
+
+	if (!output_ready (midi)) {
+		snd_printk ("MIDI interface not ready for command\n");
+		return -1;
+	}
+
+	/* Any interrupts received from now on
+	   are owned by the MIDI side of things.
+	*/
+
+	dev->interrupts_are_midi = 1;
+	
+	outb (UART_MODE_ON, midi->mpu_command_port);
+
+	for (ok = 0, i = 50000; i > 0 && !ok; i--) {
+		if (input_avail (midi)) {
+			if (read_data (midi) == MPU_ACK) {
+				ok = 1;
+				break;
+			}
+		}
+	}
+
+	if (!ok) {
+		snd_printk ("cannot set UART mode for MIDI interface");
+		dev->interrupts_are_midi = 0;
+		return -1;
+	}
+
+	/* Route external MIDI to WaveFront synth (by default) */
+    
+	if (snd_wavefront_cmd (dev, WFC_MISYNTH_ON, rbuf, wbuf)) {
+		snd_printk ("can't enable MIDI-IN-2-synth routing.\n");
+		/* XXX error ? */
+	}
+
+	/* Turn on Virtual MIDI, but first *always* turn it off,
+	   since otherwise consectutive reloads of the driver will
+	   never cause the hardware to generate the initial "internal" or 
+	   "external" source bytes in the MIDI data stream. This
+	   is pretty important, since the internal hardware generally will
+	   be used to generate none or very little MIDI output, and
+	   thus the only source of MIDI data is actually external. Without
+	   the switch bytes, the driver will think it all comes from
+	   the internal interface. Duh.
+	*/
+
+	if (snd_wavefront_cmd (dev, WFC_VMIDI_OFF, rbuf, wbuf)) { 
+		snd_printk ("virtual MIDI mode not disabled\n");
+		return 0; /* We're OK, but missing the external MIDI dev */
+	}
+
+	snd_wavefront_midi_enable_virtual (card);
+
+	if (snd_wavefront_cmd (dev, WFC_VMIDI_ON, rbuf, wbuf)) {
+		snd_printk ("cannot enable virtual MIDI mode.\n");
+		snd_wavefront_midi_disable_virtual (card);
+	} 
+	return 0;
+}
+
+snd_rawmidi_ops_t snd_wavefront_midi_output =
+{
+	.open =		snd_wavefront_midi_output_open,
+	.close =	snd_wavefront_midi_output_close,
+	.trigger =	snd_wavefront_midi_output_trigger,
+};
+
+snd_rawmidi_ops_t snd_wavefront_midi_input =
+{
+	.open =		snd_wavefront_midi_input_open,
+	.close =	snd_wavefront_midi_input_close,
+	.trigger =	snd_wavefront_midi_input_trigger,
+};
+
diff --git a/sound/isa/wavefront/wavefront_synth.c b/sound/isa/wavefront/wavefront_synth.c
new file mode 100644
index 0000000..0c3c951
--- /dev/null
+++ b/sound/isa/wavefront/wavefront_synth.c
@@ -0,0 +1,2243 @@
+/* Copyright (C) by Paul Barton-Davis 1998-1999
+ *
+ * Some portions of this file are taken from work that is
+ * copyright (C) by Hannu Savolainen 1993-1996
+ *
+ * This program is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.  
+ */
+
+/*  
+ * An ALSA lowlevel driver for Turtle Beach ICS2115 wavetable synth
+ *                                             (Maui, Tropez, Tropez Plus)
+ *
+ * This driver supports the onboard wavetable synthesizer (an ICS2115),
+ * including patch, sample and program loading and unloading, conversion
+ * of GUS patches during loading, and full user-level access to all
+ * WaveFront commands. It tries to provide semi-intelligent patch and
+ * sample management as well.
+ *
+ */
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/snd_wavefront.h>
+#include <sound/initval.h>
+
+static int wf_raw = 0; /* we normally check for "raw state" to firmware
+			  loading. if non-zero, then during driver loading, the
+			  state of the board is ignored, and we reset the
+			  board and load the firmware anyway.
+		       */
+		   
+static int fx_raw = 1; /* if this is zero, we'll leave the FX processor in
+			  whatever state it is when the driver is loaded.
+			  The default is to download the microprogram and
+			  associated coefficients to set it up for "default"
+			  operation, whatever that means.
+		       */
+
+static int debug_default = 0;  /* you can set this to control debugging
+				  during driver loading. it takes any combination
+				  of the WF_DEBUG_* flags defined in
+				  wavefront.h
+			       */
+
+/* XXX this needs to be made firmware and hardware version dependent */
+
+static char *ospath = "/etc/sound/wavefront.os"; /* where to find a processed
+						    version of the WaveFront OS
+						 */
+
+static int wait_usecs = 150; /* This magic number seems to give pretty optimal
+				throughput based on my limited experimentation.
+				If you want to play around with it and find a better
+				value, be my guest. Remember, the idea is to
+				get a number that causes us to just busy wait
+				for as many WaveFront commands as possible, without
+				coming up with a number so large that we hog the
+				whole CPU.
+
+				Specifically, with this number, out of about 134,000
+				status waits, only about 250 result in a sleep.
+			    */
+
+static int sleep_interval = 100;   /* HZ/sleep_interval seconds per sleep */
+static int sleep_tries = 50;       /* number of times we'll try to sleep */
+
+static int reset_time = 2;        /* hundreths of a second we wait after a HW
+				     reset for the expected interrupt.
+				  */
+
+static int ramcheck_time = 20;    /* time in seconds to wait while ROM code
+				     checks on-board RAM.
+				  */
+
+static int osrun_time = 10;       /* time in seconds we wait for the OS to
+				     start running.
+				  */
+module_param(wf_raw, int, 0444);
+MODULE_PARM_DESC(wf_raw, "if non-zero, assume that we need to boot the OS");
+module_param(fx_raw, int, 0444);
+MODULE_PARM_DESC(fx_raw, "if non-zero, assume that the FX process needs help");
+module_param(debug_default, int, 0444);
+MODULE_PARM_DESC(debug_default, "debug parameters for card initialization");
+module_param(wait_usecs, int, 0444);
+MODULE_PARM_DESC(wait_usecs, "how long to wait without sleeping, usecs");
+module_param(sleep_interval, int, 0444);
+MODULE_PARM_DESC(sleep_interval, "how long to sleep when waiting for reply");
+module_param(sleep_tries, int, 0444);
+MODULE_PARM_DESC(sleep_tries, "how many times to try sleeping during a wait");
+module_param(ospath, charp, 0444);
+MODULE_PARM_DESC(ospath, "full pathname to processed ICS2115 OS firmware");
+module_param(reset_time, int, 0444);
+MODULE_PARM_DESC(reset_time, "how long to wait for a reset to take effect");
+module_param(ramcheck_time, int, 0444);
+MODULE_PARM_DESC(ramcheck_time, "how many seconds to wait for the RAM test");
+module_param(osrun_time, int, 0444);
+MODULE_PARM_DESC(osrun_time, "how many seconds to wait for the ICS2115 OS");
+
+/* if WF_DEBUG not defined, no run-time debugging messages will
+   be available via the debug flag setting. Given the current
+   beta state of the driver, this will remain set until a future 
+   version.
+*/
+
+#define WF_DEBUG 1
+
+#ifdef WF_DEBUG
+
+#if defined(NEW_MACRO_VARARGS) || __GNUC__ >= 3
+#define DPRINT(cond, ...) \
+       if ((dev->debug & (cond)) == (cond)) { \
+	     snd_printk (__VA_ARGS__); \
+       }
+#else
+#define DPRINT(cond, args...) \
+       if ((dev->debug & (cond)) == (cond)) { \
+	     snd_printk (args); \
+       }
+#endif
+#else
+#define DPRINT(cond, args...)
+#endif /* WF_DEBUG */
+
+#define LOGNAME "WaveFront: "
+
+/* bitmasks for WaveFront status port value */
+
+#define STAT_RINTR_ENABLED	0x01
+#define STAT_CAN_READ		0x02
+#define STAT_INTR_READ		0x04
+#define STAT_WINTR_ENABLED	0x10
+#define STAT_CAN_WRITE		0x20
+#define STAT_INTR_WRITE		0x40
+
+static int wavefront_delete_sample (snd_wavefront_t *, int sampnum);
+static int wavefront_find_free_sample (snd_wavefront_t *);
+
+typedef struct {
+	int cmd;
+	char *action;
+	unsigned int read_cnt;
+	unsigned int write_cnt;
+	int need_ack;
+} wavefront_command;
+
+static struct {
+	int errno;
+	const char *errstr;
+} wavefront_errors[] = {
+	{ 0x01, "Bad sample number" },
+	{ 0x02, "Out of sample memory" },
+	{ 0x03, "Bad patch number" },
+	{ 0x04, "Error in number of voices" },
+	{ 0x06, "Sample load already in progress" },
+	{ 0x0B, "No sample load request pending" },
+	{ 0x0E, "Bad MIDI channel number" },
+	{ 0x10, "Download Record Error" },
+	{ 0x80, "Success" },
+	{ 0x0 }
+};
+
+#define NEEDS_ACK 1
+
+static wavefront_command wavefront_commands[] = {
+	{ WFC_SET_SYNTHVOL, "set synthesizer volume", 0, 1, NEEDS_ACK },
+	{ WFC_GET_SYNTHVOL, "get synthesizer volume", 1, 0, 0},
+	{ WFC_SET_NVOICES, "set number of voices", 0, 1, NEEDS_ACK },
+	{ WFC_GET_NVOICES, "get number of voices", 1, 0, 0 },
+	{ WFC_SET_TUNING, "set synthesizer tuning", 0, 2, NEEDS_ACK },
+	{ WFC_GET_TUNING, "get synthesizer tuning", 2, 0, 0 },
+	{ WFC_DISABLE_CHANNEL, "disable synth channel", 0, 1, NEEDS_ACK },
+	{ WFC_ENABLE_CHANNEL, "enable synth channel", 0, 1, NEEDS_ACK },
+	{ WFC_GET_CHANNEL_STATUS, "get synth channel status", 3, 0, 0 },
+	{ WFC_MISYNTH_OFF, "disable midi-in to synth", 0, 0, NEEDS_ACK },
+	{ WFC_MISYNTH_ON, "enable midi-in to synth", 0, 0, NEEDS_ACK },
+	{ WFC_VMIDI_ON, "enable virtual midi mode", 0, 0, NEEDS_ACK },
+	{ WFC_VMIDI_OFF, "disable virtual midi mode", 0, 0, NEEDS_ACK },
+	{ WFC_MIDI_STATUS, "report midi status", 1, 0, 0 },
+	{ WFC_FIRMWARE_VERSION, "report firmware version", 2, 0, 0 },
+	{ WFC_HARDWARE_VERSION, "report hardware version", 2, 0, 0 },
+	{ WFC_GET_NSAMPLES, "report number of samples", 2, 0, 0 },
+	{ WFC_INSTOUT_LEVELS, "report instantaneous output levels", 7, 0, 0 },
+	{ WFC_PEAKOUT_LEVELS, "report peak output levels", 7, 0, 0 },
+	{ WFC_DOWNLOAD_SAMPLE, "download sample",
+	  0, WF_SAMPLE_BYTES, NEEDS_ACK },
+	{ WFC_DOWNLOAD_BLOCK, "download block", 0, 0, NEEDS_ACK},
+	{ WFC_DOWNLOAD_SAMPLE_HEADER, "download sample header",
+	  0, WF_SAMPLE_HDR_BYTES, NEEDS_ACK },
+	{ WFC_UPLOAD_SAMPLE_HEADER, "upload sample header", 13, 2, 0 },
+
+	/* This command requires a variable number of bytes to be written.
+	   There is a hack in snd_wavefront_cmd() to support this. The actual
+	   count is passed in as the read buffer ptr, cast appropriately.
+	   Ugh.
+	*/
+
+	{ WFC_DOWNLOAD_MULTISAMPLE, "download multisample", 0, 0, NEEDS_ACK },
+
+	/* This one is a hack as well. We just read the first byte of the
+	   response, don't fetch an ACK, and leave the rest to the 
+	   calling function. Ugly, ugly, ugly.
+	*/
+
+	{ WFC_UPLOAD_MULTISAMPLE, "upload multisample", 2, 1, 0 },
+	{ WFC_DOWNLOAD_SAMPLE_ALIAS, "download sample alias",
+	  0, WF_ALIAS_BYTES, NEEDS_ACK },
+	{ WFC_UPLOAD_SAMPLE_ALIAS, "upload sample alias", WF_ALIAS_BYTES, 2, 0},
+	{ WFC_DELETE_SAMPLE, "delete sample", 0, 2, NEEDS_ACK },
+	{ WFC_IDENTIFY_SAMPLE_TYPE, "identify sample type", 5, 2, 0 },
+	{ WFC_UPLOAD_SAMPLE_PARAMS, "upload sample parameters" },
+	{ WFC_REPORT_FREE_MEMORY, "report free memory", 4, 0, 0 },
+	{ WFC_DOWNLOAD_PATCH, "download patch", 0, 134, NEEDS_ACK },
+	{ WFC_UPLOAD_PATCH, "upload patch", 132, 2, 0 },
+	{ WFC_DOWNLOAD_PROGRAM, "download program", 0, 33, NEEDS_ACK },
+	{ WFC_UPLOAD_PROGRAM, "upload program", 32, 1, 0 },
+	{ WFC_DOWNLOAD_EDRUM_PROGRAM, "download enhanced drum program", 0, 9,
+	  NEEDS_ACK},
+	{ WFC_UPLOAD_EDRUM_PROGRAM, "upload enhanced drum program", 8, 1, 0},
+	{ WFC_SET_EDRUM_CHANNEL, "set enhanced drum program channel",
+	  0, 1, NEEDS_ACK },
+	{ WFC_DISABLE_DRUM_PROGRAM, "disable drum program", 0, 1, NEEDS_ACK },
+	{ WFC_REPORT_CHANNEL_PROGRAMS, "report channel program numbers",
+	  32, 0, 0 },
+	{ WFC_NOOP, "the no-op command", 0, 0, NEEDS_ACK },
+	{ 0x00 }
+};
+
+static const char *
+wavefront_errorstr (int errnum)
+
+{
+	int i;
+
+	for (i = 0; wavefront_errors[i].errstr; i++) {
+		if (wavefront_errors[i].errno == errnum) {
+			return wavefront_errors[i].errstr;
+		}
+	}
+
+	return "Unknown WaveFront error";
+}
+
+static wavefront_command *
+wavefront_get_command (int cmd) 
+
+{
+	int i;
+
+	for (i = 0; wavefront_commands[i].cmd != 0; i++) {
+		if (cmd == wavefront_commands[i].cmd) {
+			return &wavefront_commands[i];
+		}
+	}
+
+	return (wavefront_command *) 0;
+}
+
+static inline int
+wavefront_status (snd_wavefront_t *dev) 
+
+{
+	return inb (dev->status_port);
+}
+
+static int
+wavefront_sleep (int limit)
+
+{
+	set_current_state(TASK_INTERRUPTIBLE);
+	schedule_timeout(limit);
+
+	return signal_pending(current);
+}
+
+static int
+wavefront_wait (snd_wavefront_t *dev, int mask)
+
+{
+	int             i;
+
+	/* Spin for a short period of time, because >99% of all
+	   requests to the WaveFront can be serviced inline like this.
+	*/
+
+	for (i = 0; i < wait_usecs; i += 5) {
+		if (wavefront_status (dev) & mask) {
+			return 1;
+		}
+		udelay(5);
+	}
+
+	for (i = 0; i < sleep_tries; i++) {
+
+		if (wavefront_status (dev) & mask) {
+			return 1;
+		}
+
+		if (wavefront_sleep (HZ/sleep_interval)) {
+			return (0);
+		}
+	}
+
+	return (0);
+}
+
+static int
+wavefront_read (snd_wavefront_t *dev)
+
+{
+	if (wavefront_wait (dev, STAT_CAN_READ))
+		return inb (dev->data_port);
+
+	DPRINT (WF_DEBUG_DATA, "read timeout.\n");
+
+	return -1;
+}
+
+static int
+wavefront_write (snd_wavefront_t *dev, unsigned char data)
+
+{
+	if (wavefront_wait (dev, STAT_CAN_WRITE)) {
+		outb (data, dev->data_port);
+		return 0;
+	}
+
+	DPRINT (WF_DEBUG_DATA, "write timeout.\n");
+
+	return -1;
+}
+
+int
+snd_wavefront_cmd (snd_wavefront_t *dev, 
+		   int cmd, unsigned char *rbuf, unsigned char *wbuf)
+
+{
+	int ack;
+	unsigned int i;
+	int c;
+	wavefront_command *wfcmd;
+
+	if ((wfcmd = wavefront_get_command (cmd)) == (wavefront_command *) 0) {
+		snd_printk ("command 0x%x not supported.\n",
+			cmd);
+		return 1;
+	}
+
+	/* Hack to handle the one variable-size write command. See
+	   wavefront_send_multisample() for the other half of this
+	   gross and ugly strategy.
+	*/
+
+	if (cmd == WFC_DOWNLOAD_MULTISAMPLE) {
+		wfcmd->write_cnt = (unsigned long) rbuf;
+		rbuf = NULL;
+	}
+
+	DPRINT (WF_DEBUG_CMD, "0x%x [%s] (%d,%d,%d)\n",
+			       cmd, wfcmd->action, wfcmd->read_cnt,
+			       wfcmd->write_cnt, wfcmd->need_ack);
+    
+	if (wavefront_write (dev, cmd)) { 
+		DPRINT ((WF_DEBUG_IO|WF_DEBUG_CMD), "cannot request "
+						     "0x%x [%s].\n",
+						     cmd, wfcmd->action);
+		return 1;
+	} 
+
+	if (wfcmd->write_cnt > 0) {
+		DPRINT (WF_DEBUG_DATA, "writing %d bytes "
+					"for 0x%x\n",
+					wfcmd->write_cnt, cmd);
+
+		for (i = 0; i < wfcmd->write_cnt; i++) {
+			if (wavefront_write (dev, wbuf[i])) {
+				DPRINT (WF_DEBUG_IO, "bad write for byte "
+						      "%d of 0x%x [%s].\n",
+						      i, cmd, wfcmd->action);
+				return 1;
+			}
+
+			DPRINT (WF_DEBUG_DATA, "write[%d] = 0x%x\n",
+						i, wbuf[i]);
+		}
+	}
+
+	if (wfcmd->read_cnt > 0) {
+		DPRINT (WF_DEBUG_DATA, "reading %d ints "
+					"for 0x%x\n",
+					wfcmd->read_cnt, cmd);
+
+		for (i = 0; i < wfcmd->read_cnt; i++) {
+
+			if ((c = wavefront_read (dev)) == -1) {
+				DPRINT (WF_DEBUG_IO, "bad read for byte "
+						      "%d of 0x%x [%s].\n",
+						      i, cmd, wfcmd->action);
+				return 1;
+			}
+
+			/* Now handle errors. Lots of special cases here */
+	    
+			if (c == 0xff) { 
+				if ((c = wavefront_read (dev)) == -1) {
+					DPRINT (WF_DEBUG_IO, "bad read for "
+							      "error byte at "
+							      "read byte %d "
+							      "of 0x%x [%s].\n",
+							      i, cmd,
+							      wfcmd->action);
+					return 1;
+				}
+
+				/* Can you believe this madness ? */
+
+				if (c == 1 &&
+				    wfcmd->cmd == WFC_IDENTIFY_SAMPLE_TYPE) {
+					rbuf[0] = WF_ST_EMPTY;
+					return (0);
+
+				} else if (c == 3 &&
+					   wfcmd->cmd == WFC_UPLOAD_PATCH) {
+
+					return 3;
+
+				} else if (c == 1 &&
+					   wfcmd->cmd == WFC_UPLOAD_PROGRAM) {
+
+					return 1;
+
+				} else {
+
+					DPRINT (WF_DEBUG_IO, "error %d (%s) "
+							      "during "
+							      "read for byte "
+							      "%d of 0x%x "
+							      "[%s].\n",
+							      c,
+							      wavefront_errorstr (c),
+							      i, cmd,
+							      wfcmd->action);
+					return 1;
+
+				}
+		
+		} else {
+				rbuf[i] = c;
+			}
+			
+			DPRINT (WF_DEBUG_DATA, "read[%d] = 0x%x\n",i, rbuf[i]);
+		}
+	}
+	
+	if ((wfcmd->read_cnt == 0 && wfcmd->write_cnt == 0) || wfcmd->need_ack) {
+
+		DPRINT (WF_DEBUG_CMD, "reading ACK for 0x%x\n", cmd);
+
+		/* Some commands need an ACK, but return zero instead
+		   of the standard value.
+		*/
+	    
+		if ((ack = wavefront_read (dev)) == 0) {
+			ack = WF_ACK;
+		}
+	
+		if (ack != WF_ACK) {
+			if (ack == -1) {
+				DPRINT (WF_DEBUG_IO, "cannot read ack for "
+						      "0x%x [%s].\n",
+						      cmd, wfcmd->action);
+				return 1;
+		
+			} else {
+				int err = -1; /* something unknown */
+
+				if (ack == 0xff) { /* explicit error */
+		    
+					if ((err = wavefront_read (dev)) == -1) {
+						DPRINT (WF_DEBUG_DATA,
+							"cannot read err "
+							"for 0x%x [%s].\n",
+							cmd, wfcmd->action);
+					}
+				}
+				
+				DPRINT (WF_DEBUG_IO, "0x%x [%s] "
+					"failed (0x%x, 0x%x, %s)\n",
+					cmd, wfcmd->action, ack, err,
+					wavefront_errorstr (err));
+				
+				return -err;
+			}
+		}
+		
+		DPRINT (WF_DEBUG_DATA, "ack received "
+					"for 0x%x [%s]\n",
+					cmd, wfcmd->action);
+	} else {
+
+		DPRINT (WF_DEBUG_CMD, "0x%x [%s] does not need "
+				       "ACK (%d,%d,%d)\n",
+				       cmd, wfcmd->action, wfcmd->read_cnt,
+				       wfcmd->write_cnt, wfcmd->need_ack);
+	}
+
+	return 0;
+	
+}
+
+/***********************************************************************
+WaveFront data munging   
+
+Things here are weird. All data written to the board cannot 
+have its most significant bit set. Any data item with values 
+potentially > 0x7F (127) must be split across multiple bytes.
+
+Sometimes, we need to munge numeric values that are represented on
+the x86 side as 8-32 bit values. Sometimes, we need to munge data
+that is represented on the x86 side as an array of bytes. The most
+efficient approach to handling both cases seems to be to use 2
+different functions for munging and 2 for de-munging. This avoids
+weird casting and worrying about bit-level offsets.
+
+**********************************************************************/
+
+static unsigned char *
+munge_int32 (unsigned int src,
+	     unsigned char *dst,
+	     unsigned int dst_size)
+{
+	unsigned int i;
+
+	for (i = 0; i < dst_size; i++) {
+		*dst = src & 0x7F;  /* Mask high bit of LSB */
+		src = src >> 7;     /* Rotate Right 7 bits  */
+	                            /* Note: we leave the upper bits in place */ 
+
+		dst++;
+ 	};
+	return dst;
+};
+
+static int 
+demunge_int32 (unsigned char* src, int src_size)
+
+{
+	int i;
+ 	int outval = 0;
+	
+ 	for (i = src_size - 1; i >= 0; i--) {
+		outval=(outval<<7)+src[i];
+	}
+
+	return outval;
+};
+
+static 
+unsigned char *
+munge_buf (unsigned char *src, unsigned char *dst, unsigned int dst_size)
+
+{
+	unsigned int i;
+	unsigned int last = dst_size / 2;
+
+	for (i = 0; i < last; i++) {
+		*dst++ = src[i] & 0x7f;
+		*dst++ = src[i] >> 7;
+	}
+	return dst;
+}
+
+static 
+unsigned char *
+demunge_buf (unsigned char *src, unsigned char *dst, unsigned int src_bytes)
+
+{
+	int i;
+	unsigned char *end = src + src_bytes;
+    
+	end = src + src_bytes;
+
+	/* NOTE: src and dst *CAN* point to the same address */
+
+	for (i = 0; src != end; i++) {
+		dst[i] = *src++;
+		dst[i] |= (*src++)<<7;
+	}
+
+	return dst;
+}
+
+/***********************************************************************
+WaveFront: sample, patch and program management.
+***********************************************************************/
+
+static int
+wavefront_delete_sample (snd_wavefront_t *dev, int sample_num)
+
+{
+	unsigned char wbuf[2];
+	int x;
+
+	wbuf[0] = sample_num & 0x7f;
+	wbuf[1] = sample_num >> 7;
+
+	if ((x = snd_wavefront_cmd (dev, WFC_DELETE_SAMPLE, NULL, wbuf)) == 0) {
+		dev->sample_status[sample_num] = WF_ST_EMPTY;
+	}
+
+	return x;
+}
+
+static int
+wavefront_get_sample_status (snd_wavefront_t *dev, int assume_rom)
+
+{
+	int i;
+	unsigned char rbuf[32], wbuf[32];
+	unsigned int    sc_real, sc_alias, sc_multi;
+
+	/* check sample status */
+    
+	if (snd_wavefront_cmd (dev, WFC_GET_NSAMPLES, rbuf, wbuf)) {
+		snd_printk ("cannot request sample count.\n");
+		return -1;
+	} 
+    
+	sc_real = sc_alias = sc_multi = dev->samples_used = 0;
+    
+	for (i = 0; i < WF_MAX_SAMPLE; i++) {
+	
+		wbuf[0] = i & 0x7f;
+		wbuf[1] = i >> 7;
+
+		if (snd_wavefront_cmd (dev, WFC_IDENTIFY_SAMPLE_TYPE, rbuf, wbuf)) {
+			snd_printk("cannot identify sample "
+				   "type of slot %d\n", i);
+			dev->sample_status[i] = WF_ST_EMPTY;
+			continue;
+		}
+
+		dev->sample_status[i] = (WF_SLOT_FILLED|rbuf[0]);
+
+		if (assume_rom) {
+			dev->sample_status[i] |= WF_SLOT_ROM;
+		}
+
+		switch (rbuf[0] & WF_ST_MASK) {
+		case WF_ST_SAMPLE:
+			sc_real++;
+			break;
+		case WF_ST_MULTISAMPLE:
+			sc_multi++;
+			break;
+		case WF_ST_ALIAS:
+			sc_alias++;
+			break;
+		case WF_ST_EMPTY:
+			break;
+
+		default:
+			snd_printk ("unknown sample type for "
+				    "slot %d (0x%x)\n", 
+				    i, rbuf[0]);
+		}
+
+		if (rbuf[0] != WF_ST_EMPTY) {
+			dev->samples_used++;
+		} 
+	}
+
+	snd_printk ("%d samples used (%d real, %d aliases, %d multi), "
+		    "%d empty\n", dev->samples_used, sc_real, sc_alias, sc_multi,
+		    WF_MAX_SAMPLE - dev->samples_used);
+
+
+	return (0);
+
+}
+
+static int
+wavefront_get_patch_status (snd_wavefront_t *dev)
+
+{
+	unsigned char patchbuf[WF_PATCH_BYTES];
+	unsigned char patchnum[2];
+	wavefront_patch *p;
+	int i, x, cnt, cnt2;
+
+	for (i = 0; i < WF_MAX_PATCH; i++) {
+		patchnum[0] = i & 0x7f;
+		patchnum[1] = i >> 7;
+
+		if ((x = snd_wavefront_cmd (dev, WFC_UPLOAD_PATCH, patchbuf,
+					patchnum)) == 0) {
+
+			dev->patch_status[i] |= WF_SLOT_FILLED;
+			p = (wavefront_patch *) patchbuf;
+			dev->sample_status
+				[p->sample_number|(p->sample_msb<<7)] |=
+				WF_SLOT_USED;
+	    
+		} else if (x == 3) { /* Bad patch number */
+			dev->patch_status[i] = 0;
+		} else {
+			snd_printk ("upload patch "
+				    "error 0x%x\n", x);
+			dev->patch_status[i] = 0;
+			return 1;
+		}
+	}
+
+	/* program status has already filled in slot_used bits */
+
+	for (i = 0, cnt = 0, cnt2 = 0; i < WF_MAX_PATCH; i++) {
+		if (dev->patch_status[i] & WF_SLOT_FILLED) {
+			cnt++;
+		}
+		if (dev->patch_status[i] & WF_SLOT_USED) {
+			cnt2++;
+		}
+	
+	}
+	snd_printk ("%d patch slots filled, %d in use\n", cnt, cnt2);
+
+	return (0);
+}
+
+static int
+wavefront_get_program_status (snd_wavefront_t *dev)
+
+{
+	unsigned char progbuf[WF_PROGRAM_BYTES];
+	wavefront_program prog;
+	unsigned char prognum;
+	int i, x, l, cnt;
+
+	for (i = 0; i < WF_MAX_PROGRAM; i++) {
+		prognum = i;
+
+		if ((x = snd_wavefront_cmd (dev, WFC_UPLOAD_PROGRAM, progbuf,
+					&prognum)) == 0) {
+
+			dev->prog_status[i] |= WF_SLOT_USED;
+
+			demunge_buf (progbuf, (unsigned char *) &prog,
+				     WF_PROGRAM_BYTES);
+
+			for (l = 0; l < WF_NUM_LAYERS; l++) {
+				if (prog.layer[l].mute) {
+					dev->patch_status
+						[prog.layer[l].patch_number] |=
+						WF_SLOT_USED;
+				}
+			}
+		} else if (x == 1) { /* Bad program number */
+			dev->prog_status[i] = 0;
+		} else {
+			snd_printk ("upload program "
+				    "error 0x%x\n", x);
+			dev->prog_status[i] = 0;
+		}
+	}
+
+	for (i = 0, cnt = 0; i < WF_MAX_PROGRAM; i++) {
+		if (dev->prog_status[i]) {
+			cnt++;
+		}
+	}
+
+	snd_printk ("%d programs slots in use\n", cnt);
+
+	return (0);
+}
+
+static int
+wavefront_send_patch (snd_wavefront_t *dev, wavefront_patch_info *header)
+
+{
+	unsigned char buf[WF_PATCH_BYTES+2];
+	unsigned char *bptr;
+
+	DPRINT (WF_DEBUG_LOAD_PATCH, "downloading patch %d\n",
+				      header->number);
+
+	dev->patch_status[header->number] |= WF_SLOT_FILLED;
+
+	bptr = buf;
+	bptr = munge_int32 (header->number, buf, 2);
+	munge_buf ((unsigned char *)&header->hdr.p, bptr, WF_PATCH_BYTES);
+    
+	if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_PATCH, NULL, buf)) {
+		snd_printk ("download patch failed\n");
+		return -(EIO);
+	}
+
+	return (0);
+}
+
+static int
+wavefront_send_program (snd_wavefront_t *dev, wavefront_patch_info *header)
+
+{
+	unsigned char buf[WF_PROGRAM_BYTES+1];
+	int i;
+
+	DPRINT (WF_DEBUG_LOAD_PATCH, "downloading program %d\n",
+		header->number);
+
+	dev->prog_status[header->number] = WF_SLOT_USED;
+
+	/* XXX need to zero existing SLOT_USED bit for program_status[i]
+	   where `i' is the program that's being (potentially) overwritten.
+	*/
+    
+	for (i = 0; i < WF_NUM_LAYERS; i++) {
+		if (header->hdr.pr.layer[i].mute) {
+			dev->patch_status[header->hdr.pr.layer[i].patch_number] |=
+				WF_SLOT_USED;
+
+			/* XXX need to mark SLOT_USED for sample used by
+			   patch_number, but this means we have to load it. Ick.
+			*/
+		}
+	}
+
+	buf[0] = header->number;
+	munge_buf ((unsigned char *)&header->hdr.pr, &buf[1], WF_PROGRAM_BYTES);
+    
+	if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_PROGRAM, NULL, buf)) {
+		snd_printk ("download patch failed\n");	
+		return -(EIO);
+	}
+
+	return (0);
+}
+
+static int
+wavefront_freemem (snd_wavefront_t *dev)
+
+{
+	char rbuf[8];
+
+	if (snd_wavefront_cmd (dev, WFC_REPORT_FREE_MEMORY, rbuf, NULL)) {
+		snd_printk ("can't get memory stats.\n");
+		return -1;
+	} else {
+		return demunge_int32 (rbuf, 4);
+	}
+}
+
+static int
+wavefront_send_sample (snd_wavefront_t *dev, 
+		       wavefront_patch_info *header,
+		       u16 __user *dataptr,
+		       int data_is_unsigned)
+
+{
+	/* samples are downloaded via a 16-bit wide i/o port
+	   (you could think of it as 2 adjacent 8-bit wide ports
+	   but its less efficient that way). therefore, all
+	   the blocksizes and so forth listed in the documentation,
+	   and used conventionally to refer to sample sizes,
+	   which are given in 8-bit units (bytes), need to be
+	   divided by 2.
+        */
+
+	u16 sample_short;
+	u32 length;
+	u16 __user *data_end = NULL;
+	unsigned int i;
+	const unsigned int max_blksize = 4096/2;
+	unsigned int written;
+	unsigned int blocksize;
+	int dma_ack;
+	int blocknum;
+	unsigned char sample_hdr[WF_SAMPLE_HDR_BYTES];
+	unsigned char *shptr;
+	int skip = 0;
+	int initial_skip = 0;
+
+	DPRINT (WF_DEBUG_LOAD_PATCH, "sample %sdownload for slot %d, "
+				      "type %d, %d bytes from 0x%lx\n",
+				      header->size ? "" : "header ", 
+				      header->number, header->subkey,
+				      header->size,
+				      (unsigned long) header->dataptr);
+
+	if (header->number == WAVEFRONT_FIND_FREE_SAMPLE_SLOT) {
+		int x;
+
+		if ((x = wavefront_find_free_sample (dev)) < 0) {
+			return -ENOMEM;
+		}
+		snd_printk ("unspecified sample => %d\n", x);
+		header->number = x;
+	}
+
+	if (header->size) {
+
+		/* XXX it's a debatable point whether or not RDONLY semantics
+		   on the ROM samples should cover just the sample data or
+		   the sample header. For now, it only covers the sample data,
+		   so anyone is free at all times to rewrite sample headers.
+
+		   My reason for this is that we have the sample headers
+		   available in the WFB file for General MIDI, and so these
+		   can always be reset if needed. The sample data, however,
+		   cannot be recovered without a complete reset and firmware
+		   reload of the ICS2115, which is a very expensive operation.
+
+		   So, doing things this way allows us to honor the notion of
+		   "RESETSAMPLES" reasonably cheaply. Note however, that this
+		   is done purely at user level: there is no WFB parser in
+		   this driver, and so a complete reset (back to General MIDI,
+		   or theoretically some other configuration) is the
+		   responsibility of the user level library. 
+
+		   To try to do this in the kernel would be a little
+		   crazy: we'd need 158K of kernel space just to hold
+		   a copy of the patch/program/sample header data.
+		*/
+
+		if (dev->rom_samples_rdonly) {
+			if (dev->sample_status[header->number] & WF_SLOT_ROM) {
+				snd_printk ("sample slot %d "
+					    "write protected\n",
+					    header->number);
+				return -EACCES;
+			}
+		}
+
+		wavefront_delete_sample (dev, header->number);
+	}
+
+	if (header->size) {
+		dev->freemem = wavefront_freemem (dev);
+
+		if (dev->freemem < (int)header->size) {
+			snd_printk ("insufficient memory to "
+				    "load %d byte sample.\n",
+				    header->size);
+			return -ENOMEM;
+		}
+	
+	}
+
+	skip = WF_GET_CHANNEL(&header->hdr.s);
+
+	if (skip > 0 && header->hdr.s.SampleResolution != LINEAR_16BIT) {
+		snd_printk ("channel selection only "
+			    "possible on 16-bit samples");
+		return -(EINVAL);
+	}
+
+	switch (skip) {
+	case 0:
+		initial_skip = 0;
+		skip = 1;
+		break;
+	case 1:
+		initial_skip = 0;
+		skip = 2;
+		break;
+	case 2:
+		initial_skip = 1;
+		skip = 2;
+		break;
+	case 3:
+		initial_skip = 2;
+		skip = 3;
+		break;
+	case 4:
+		initial_skip = 3;
+		skip = 4;
+		break;
+	case 5:
+		initial_skip = 4;
+		skip = 5;
+		break;
+	case 6:
+		initial_skip = 5;
+		skip = 6;
+		break;
+	}
+
+	DPRINT (WF_DEBUG_LOAD_PATCH, "channel selection: %d => "
+				      "initial skip = %d, skip = %d\n",
+				      WF_GET_CHANNEL (&header->hdr.s),
+				      initial_skip, skip);
+    
+	/* Be safe, and zero the "Unused" bits ... */
+
+	WF_SET_CHANNEL(&header->hdr.s, 0);
+
+	/* adjust size for 16 bit samples by dividing by two.  We always
+	   send 16 bits per write, even for 8 bit samples, so the length
+	   is always half the size of the sample data in bytes.
+	*/
+
+	length = header->size / 2;
+
+	/* the data we're sent has not been munged, and in fact, the
+	   header we have to send isn't just a munged copy either.
+	   so, build the sample header right here.
+	*/
+
+	shptr = &sample_hdr[0];
+
+	shptr = munge_int32 (header->number, shptr, 2);
+
+	if (header->size) {
+		shptr = munge_int32 (length, shptr, 4);
+	}
+
+	/* Yes, a 4 byte result doesn't contain all of the offset bits,
+	   but the offset only uses 24 bits.
+	*/
+
+	shptr = munge_int32 (*((u32 *) &header->hdr.s.sampleStartOffset),
+			     shptr, 4);
+	shptr = munge_int32 (*((u32 *) &header->hdr.s.loopStartOffset),
+			     shptr, 4);
+	shptr = munge_int32 (*((u32 *) &header->hdr.s.loopEndOffset),
+			     shptr, 4);
+	shptr = munge_int32 (*((u32 *) &header->hdr.s.sampleEndOffset),
+			     shptr, 4);
+	
+	/* This one is truly weird. What kind of weirdo decided that in
+	   a system dominated by 16 and 32 bit integers, they would use
+	   a just 12 bits ?
+	*/
+	
+	shptr = munge_int32 (header->hdr.s.FrequencyBias, shptr, 3);
+	
+	/* Why is this nybblified, when the MSB is *always* zero ? 
+	   Anyway, we can't take address of bitfield, so make a
+	   good-faith guess at where it starts.
+	*/
+	
+	shptr = munge_int32 (*(&header->hdr.s.FrequencyBias+1),
+			     shptr, 2);
+
+	if (snd_wavefront_cmd (dev, 
+			   header->size ?
+			   WFC_DOWNLOAD_SAMPLE : WFC_DOWNLOAD_SAMPLE_HEADER,
+			   NULL, sample_hdr)) {
+		snd_printk ("sample %sdownload refused.\n",
+			    header->size ? "" : "header ");
+		return -(EIO);
+	}
+
+	if (header->size == 0) {
+		goto sent; /* Sorry. Just had to have one somewhere */
+	}
+    
+	data_end = dataptr + length;
+
+	/* Do any initial skip over an unused channel's data */
+
+	dataptr += initial_skip;
+    
+	for (written = 0, blocknum = 0;
+	     written < length; written += max_blksize, blocknum++) {
+	
+		if ((length - written) > max_blksize) {
+			blocksize = max_blksize;
+		} else {
+			/* round to nearest 16-byte value */
+			blocksize = ((length-written+7)&~0x7);
+		}
+
+		if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_BLOCK, NULL, NULL)) {
+			snd_printk ("download block "
+				    "request refused.\n");
+			return -(EIO);
+		}
+
+		for (i = 0; i < blocksize; i++) {
+
+			if (dataptr < data_end) {
+		
+				__get_user (sample_short, dataptr);
+				dataptr += skip;
+		
+				if (data_is_unsigned) { /* GUS ? */
+
+					if (WF_SAMPLE_IS_8BIT(&header->hdr.s)) {
+			
+						/* 8 bit sample
+						 resolution, sign
+						 extend both bytes.
+						*/
+			
+						((unsigned char*)
+						 &sample_short)[0] += 0x7f;
+						((unsigned char*)
+						 &sample_short)[1] += 0x7f;
+			
+					} else {
+			
+						/* 16 bit sample
+						 resolution, sign
+						 extend the MSB.
+						*/
+			
+						sample_short += 0x7fff;
+					}
+				}
+
+			} else {
+
+				/* In padding section of final block:
+
+				   Don't fetch unsupplied data from
+				   user space, just continue with
+				   whatever the final value was.
+				*/
+			}
+	    
+			if (i < blocksize - 1) {
+				outw (sample_short, dev->block_port);
+			} else {
+				outw (sample_short, dev->last_block_port);
+			}
+		}
+
+		/* Get "DMA page acknowledge", even though its really
+		   nothing to do with DMA at all.
+		*/
+	
+		if ((dma_ack = wavefront_read (dev)) != WF_DMA_ACK) {
+			if (dma_ack == -1) {
+				snd_printk ("upload sample "
+					    "DMA ack timeout\n");
+				return -(EIO);
+			} else {
+				snd_printk ("upload sample "
+					    "DMA ack error 0x%x\n",
+					    dma_ack);
+				return -(EIO);
+			}
+		}
+	}
+
+	dev->sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_SAMPLE);
+
+	/* Note, label is here because sending the sample header shouldn't
+	   alter the sample_status info at all.
+	*/
+
+ sent:
+	return (0);
+}
+
+static int
+wavefront_send_alias (snd_wavefront_t *dev, wavefront_patch_info *header)
+
+{
+	unsigned char alias_hdr[WF_ALIAS_BYTES];
+
+	DPRINT (WF_DEBUG_LOAD_PATCH, "download alias, %d is "
+				      "alias for %d\n",
+				      header->number,
+				      header->hdr.a.OriginalSample);
+    
+	munge_int32 (header->number, &alias_hdr[0], 2);
+	munge_int32 (header->hdr.a.OriginalSample, &alias_hdr[2], 2);
+	munge_int32 (*((unsigned int *)&header->hdr.a.sampleStartOffset),
+		     &alias_hdr[4], 4);
+	munge_int32 (*((unsigned int *)&header->hdr.a.loopStartOffset),
+		     &alias_hdr[8], 4);
+	munge_int32 (*((unsigned int *)&header->hdr.a.loopEndOffset),
+		     &alias_hdr[12], 4);
+	munge_int32 (*((unsigned int *)&header->hdr.a.sampleEndOffset),
+		     &alias_hdr[16], 4);
+	munge_int32 (header->hdr.a.FrequencyBias, &alias_hdr[20], 3);
+	munge_int32 (*(&header->hdr.a.FrequencyBias+1), &alias_hdr[23], 2);
+
+	if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_SAMPLE_ALIAS, NULL, alias_hdr)) {
+		snd_printk ("download alias failed.\n");
+		return -(EIO);
+	}
+
+	dev->sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_ALIAS);
+
+	return (0);
+}
+
+static int
+wavefront_send_multisample (snd_wavefront_t *dev, wavefront_patch_info *header)
+{
+	int i;
+	int num_samples;
+	unsigned char *msample_hdr;
+
+	msample_hdr = kmalloc(sizeof(WF_MSAMPLE_BYTES), GFP_KERNEL);
+	if (! msample_hdr)
+		return -ENOMEM;
+
+	munge_int32 (header->number, &msample_hdr[0], 2);
+
+	/* You'll recall at this point that the "number of samples" value
+	   in a wavefront_multisample struct is actually the log2 of the
+	   real number of samples.
+	*/
+
+	num_samples = (1<<(header->hdr.ms.NumberOfSamples&7));
+	msample_hdr[2] = (unsigned char) header->hdr.ms.NumberOfSamples;
+
+	DPRINT (WF_DEBUG_LOAD_PATCH, "multi %d with %d=%d samples\n",
+				      header->number,
+				      header->hdr.ms.NumberOfSamples,
+				      num_samples);
+
+	for (i = 0; i < num_samples; i++) {
+		DPRINT(WF_DEBUG_LOAD_PATCH|WF_DEBUG_DATA, "sample[%d] = %d\n",
+		       i, header->hdr.ms.SampleNumber[i]);
+		munge_int32 (header->hdr.ms.SampleNumber[i],
+		     &msample_hdr[3+(i*2)], 2);
+	}
+    
+	/* Need a hack here to pass in the number of bytes
+	   to be written to the synth. This is ugly, and perhaps
+	   one day, I'll fix it.
+	*/
+
+	if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_MULTISAMPLE, 
+			   (unsigned char *) (long) ((num_samples*2)+3),
+			   msample_hdr)) {
+		snd_printk ("download of multisample failed.\n");
+		kfree(msample_hdr);
+		return -(EIO);
+	}
+
+	dev->sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_MULTISAMPLE);
+
+	kfree(msample_hdr);
+	return (0);
+}
+
+static int
+wavefront_fetch_multisample (snd_wavefront_t *dev, 
+			     wavefront_patch_info *header)
+{
+	int i;
+	unsigned char log_ns[1];
+	unsigned char number[2];
+	int num_samples;
+
+	munge_int32 (header->number, number, 2);
+    
+	if (snd_wavefront_cmd (dev, WFC_UPLOAD_MULTISAMPLE, log_ns, number)) {
+		snd_printk ("upload multisample failed.\n");
+		return -(EIO);
+	}
+    
+	DPRINT (WF_DEBUG_DATA, "msample %d has %d samples\n",
+				header->number, log_ns[0]);
+
+	header->hdr.ms.NumberOfSamples = log_ns[0];
+
+	/* get the number of samples ... */
+
+	num_samples = (1 << log_ns[0]);
+    
+	for (i = 0; i < num_samples; i++) {
+		char d[2];
+		int val;
+	
+		if ((val = wavefront_read (dev)) == -1) {
+			snd_printk ("upload multisample failed "
+				    "during sample loop.\n");
+			return -(EIO);
+		}
+		d[0] = val;
+
+		if ((val = wavefront_read (dev)) == -1) {
+			snd_printk ("upload multisample failed "
+				    "during sample loop.\n");
+			return -(EIO);
+		}
+		d[1] = val;
+	
+		header->hdr.ms.SampleNumber[i] =
+			demunge_int32 ((unsigned char *) d, 2);
+	
+		DPRINT (WF_DEBUG_DATA, "msample sample[%d] = %d\n",
+					i, header->hdr.ms.SampleNumber[i]);
+	}
+
+	return (0);
+}
+
+
+static int
+wavefront_send_drum (snd_wavefront_t *dev, wavefront_patch_info *header)
+
+{
+	unsigned char drumbuf[WF_DRUM_BYTES];
+	wavefront_drum *drum = &header->hdr.d;
+	int i;
+
+	DPRINT (WF_DEBUG_LOAD_PATCH, "downloading edrum for MIDI "
+		"note %d, patch = %d\n", 
+		header->number, drum->PatchNumber);
+
+	drumbuf[0] = header->number & 0x7f;
+
+	for (i = 0; i < 4; i++) {
+		munge_int32 (((unsigned char *)drum)[i], &drumbuf[1+(i*2)], 2);
+	}
+
+	if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_EDRUM_PROGRAM, NULL, drumbuf)) {
+		snd_printk ("download drum failed.\n");
+		return -(EIO);
+	}
+
+	return (0);
+}
+
+static int 
+wavefront_find_free_sample (snd_wavefront_t *dev)
+
+{
+	int i;
+
+	for (i = 0; i < WF_MAX_SAMPLE; i++) {
+		if (!(dev->sample_status[i] & WF_SLOT_FILLED)) {
+			return i;
+		}
+	}
+	snd_printk ("no free sample slots!\n");
+	return -1;
+}
+
+#if 0
+static int 
+wavefront_find_free_patch (snd_wavefront_t *dev)
+
+{
+	int i;
+
+	for (i = 0; i < WF_MAX_PATCH; i++) {
+		if (!(dev->patch_status[i] & WF_SLOT_FILLED)) {
+			return i;
+		}
+	}
+	snd_printk ("no free patch slots!\n");
+	return -1;
+}
+#endif
+
+static int
+wavefront_load_patch (snd_wavefront_t *dev, const char __user *addr)
+{
+	wavefront_patch_info *header;
+	int err;
+	
+	header = kmalloc(sizeof(*header), GFP_KERNEL);
+	if (! header)
+		return -ENOMEM;
+
+	if (copy_from_user (header, addr, sizeof(wavefront_patch_info) -
+			    sizeof(wavefront_any))) {
+		snd_printk ("bad address for load patch.\n");
+		err = -EFAULT;
+		goto __error;
+	}
+
+	DPRINT (WF_DEBUG_LOAD_PATCH, "download "
+				      "Sample type: %d "
+				      "Sample number: %d "
+				      "Sample size: %d\n",
+				      header->subkey,
+				      header->number,
+				      header->size);
+
+	switch (header->subkey) {
+	case WF_ST_SAMPLE:  /* sample or sample_header, based on patch->size */
+
+		if (copy_from_user (&header->hdr.s, header->hdrptr,
+				    sizeof (wavefront_sample))) {
+			err = -EFAULT;
+			break;
+		}
+
+		err = wavefront_send_sample (dev, header, header->dataptr, 0);
+		break;
+
+	case WF_ST_MULTISAMPLE:
+
+		if (copy_from_user (&header->hdr.s, header->hdrptr,
+				    sizeof (wavefront_multisample))) {
+			err = -EFAULT;
+			break;
+		}
+
+		err = wavefront_send_multisample (dev, header);
+		break;
+
+	case WF_ST_ALIAS:
+
+		if (copy_from_user (&header->hdr.a, header->hdrptr,
+				    sizeof (wavefront_alias))) {
+			err = -EFAULT;
+			break;
+		}
+
+		err = wavefront_send_alias (dev, header);
+		break;
+
+	case WF_ST_DRUM:
+		if (copy_from_user (&header->hdr.d, header->hdrptr,
+				    sizeof (wavefront_drum))) {
+			err = -EFAULT;
+			break;
+		}
+
+		err = wavefront_send_drum (dev, header);
+		break;
+
+	case WF_ST_PATCH:
+		if (copy_from_user (&header->hdr.p, header->hdrptr,
+				    sizeof (wavefront_patch))) {
+			err = -EFAULT;
+			break;
+		}
+		
+		err = wavefront_send_patch (dev, header);
+		break;
+
+	case WF_ST_PROGRAM:
+		if (copy_from_user (&header->hdr.pr, header->hdrptr,
+				    sizeof (wavefront_program))) {
+			err = -EFAULT;
+			break;
+		}
+
+		err = wavefront_send_program (dev, header);
+		break;
+
+	default:
+		snd_printk ("unknown patch type %d.\n",
+			    header->subkey);
+		err = -EINVAL;
+		break;
+	}
+
+ __error:
+	kfree(header);
+	return err;
+}
+
+/***********************************************************************
+WaveFront: hardware-dependent interface
+***********************************************************************/
+
+static void
+process_sample_hdr (u8 *buf)
+
+{
+	wavefront_sample s;
+	u8 *ptr;
+
+	ptr = buf;
+
+	/* The board doesn't send us an exact copy of a "wavefront_sample"
+	   in response to an Upload Sample Header command. Instead, we 
+	   have to convert the data format back into our data structure,
+	   just as in the Download Sample command, where we have to do
+	   something very similar in the reverse direction.
+	*/
+
+	*((u32 *) &s.sampleStartOffset) = demunge_int32 (ptr, 4); ptr += 4;
+	*((u32 *) &s.loopStartOffset) = demunge_int32 (ptr, 4); ptr += 4;
+	*((u32 *) &s.loopEndOffset) = demunge_int32 (ptr, 4); ptr += 4;
+	*((u32 *) &s.sampleEndOffset) = demunge_int32 (ptr, 4); ptr += 4;
+	*((u32 *) &s.FrequencyBias) = demunge_int32 (ptr, 3); ptr += 3;
+
+	s.SampleResolution = *ptr & 0x3;
+	s.Loop = *ptr & 0x8;
+	s.Bidirectional = *ptr & 0x10;
+	s.Reverse = *ptr & 0x40;
+
+	/* Now copy it back to where it came from */
+
+	memcpy (buf, (unsigned char *) &s, sizeof (wavefront_sample));
+}
+
+static int
+wavefront_synth_control (snd_wavefront_card_t *acard, 
+			 wavefront_control *wc)
+
+{
+	snd_wavefront_t *dev = &acard->wavefront;
+	unsigned char patchnumbuf[2];
+	int i;
+
+	DPRINT (WF_DEBUG_CMD, "synth control with "
+		"cmd 0x%x\n", wc->cmd);
+
+	/* Pre-handling of or for various commands */
+
+	switch (wc->cmd) {
+		
+	case WFC_DISABLE_INTERRUPTS:
+		snd_printk ("interrupts disabled.\n");
+		outb (0x80|0x20, dev->control_port);
+		dev->interrupts_are_midi = 1;
+		return 0;
+
+	case WFC_ENABLE_INTERRUPTS:
+		snd_printk ("interrupts enabled.\n");
+		outb (0x80|0x40|0x20, dev->control_port);
+		dev->interrupts_are_midi = 1;
+		return 0;
+
+	case WFC_INTERRUPT_STATUS:
+		wc->rbuf[0] = dev->interrupts_are_midi;
+		return 0;
+
+	case WFC_ROMSAMPLES_RDONLY:
+		dev->rom_samples_rdonly = wc->wbuf[0];
+		wc->status = 0;
+		return 0;
+
+	case WFC_IDENTIFY_SLOT_TYPE:
+		i = wc->wbuf[0] | (wc->wbuf[1] << 7);
+		if (i <0 || i >= WF_MAX_SAMPLE) {
+			snd_printk ("invalid slot ID %d\n",
+				i);
+			wc->status = EINVAL;
+			return -EINVAL;
+		}
+		wc->rbuf[0] = dev->sample_status[i];
+		wc->status = 0;
+		return 0;
+
+	case WFC_DEBUG_DRIVER:
+		dev->debug = wc->wbuf[0];
+		snd_printk ("debug = 0x%x\n", dev->debug);
+		return 0;
+
+	case WFC_UPLOAD_PATCH:
+		munge_int32 (*((u32 *) wc->wbuf), patchnumbuf, 2);
+		memcpy (wc->wbuf, patchnumbuf, 2);
+		break;
+
+	case WFC_UPLOAD_MULTISAMPLE:
+		/* multisamples have to be handled differently, and
+		   cannot be dealt with properly by snd_wavefront_cmd() alone.
+		*/
+		wc->status = wavefront_fetch_multisample
+			(dev, (wavefront_patch_info *) wc->rbuf);
+		return 0;
+
+	case WFC_UPLOAD_SAMPLE_ALIAS:
+		snd_printk ("support for sample alias upload "
+			"being considered.\n");
+		wc->status = EINVAL;
+		return -EINVAL;
+	}
+
+	wc->status = snd_wavefront_cmd (dev, wc->cmd, wc->rbuf, wc->wbuf);
+
+	/* Post-handling of certain commands.
+
+	   In particular, if the command was an upload, demunge the data
+	   so that the user-level doesn't have to think about it.
+	*/
+
+	if (wc->status == 0) {
+		switch (wc->cmd) {
+			/* intercept any freemem requests so that we know
+			   we are always current with the user-level view
+			   of things.
+			*/
+
+		case WFC_REPORT_FREE_MEMORY:
+			dev->freemem = demunge_int32 (wc->rbuf, 4);
+			break;
+
+		case WFC_UPLOAD_PATCH:
+			demunge_buf (wc->rbuf, wc->rbuf, WF_PATCH_BYTES);
+			break;
+
+		case WFC_UPLOAD_PROGRAM:
+			demunge_buf (wc->rbuf, wc->rbuf, WF_PROGRAM_BYTES);
+			break;
+
+		case WFC_UPLOAD_EDRUM_PROGRAM:
+			demunge_buf (wc->rbuf, wc->rbuf, WF_DRUM_BYTES - 1);
+			break;
+
+		case WFC_UPLOAD_SAMPLE_HEADER:
+			process_sample_hdr (wc->rbuf);
+			break;
+
+		case WFC_UPLOAD_SAMPLE_ALIAS:
+			snd_printk ("support for "
+				    "sample aliases still "
+				    "being considered.\n");
+			break;
+
+		case WFC_VMIDI_OFF:
+			snd_wavefront_midi_disable_virtual (acard);
+			break;
+
+		case WFC_VMIDI_ON:
+			snd_wavefront_midi_enable_virtual (acard);
+			break;
+		}
+	}
+
+	return 0;
+}
+
+int 
+snd_wavefront_synth_open (snd_hwdep_t *hw, struct file *file)
+
+{
+	if (!try_module_get(hw->card->module))
+		return -EFAULT;
+	file->private_data = hw;
+	return 0;
+}
+
+int 
+snd_wavefront_synth_release (snd_hwdep_t *hw, struct file *file)
+
+{
+	module_put(hw->card->module);
+	return 0;
+}
+
+int
+snd_wavefront_synth_ioctl (snd_hwdep_t *hw, struct file *file,
+			   unsigned int cmd, unsigned long arg)
+
+{
+	snd_card_t *card;
+	snd_wavefront_t *dev;
+	snd_wavefront_card_t *acard;
+	wavefront_control *wc;
+	void __user *argp = (void __user *)arg;
+	int err;
+
+	card = (snd_card_t *) hw->card;
+
+	snd_assert(card != NULL, return -ENODEV);
+
+	snd_assert(card->private_data != NULL, return -ENODEV);
+
+	acard = card->private_data;
+	dev = &acard->wavefront;
+	
+	switch (cmd) {
+	case WFCTL_LOAD_SPP:
+		if (wavefront_load_patch (dev, argp) != 0) {
+			return -EIO;
+		}
+		break;
+
+	case WFCTL_WFCMD:
+		wc = kmalloc(sizeof(*wc), GFP_KERNEL);
+		if (! wc)
+			return -ENOMEM;
+		if (copy_from_user (wc, argp, sizeof (*wc)))
+			err = -EFAULT;
+		else if (wavefront_synth_control (acard, wc) < 0)
+			err = -EIO;
+		else if (copy_to_user (argp, wc, sizeof (*wc)))
+			err = -EFAULT;
+		else
+			err = 0;
+		kfree(wc);
+		return err;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+
+/***********************************************************************/
+/*  WaveFront: interface for card-level wavefront module               */
+/***********************************************************************/
+
+void
+snd_wavefront_internal_interrupt (snd_wavefront_card_t *card)
+{
+	snd_wavefront_t *dev = &card->wavefront;
+
+	/*
+	   Some comments on interrupts. I attempted a version of this
+	   driver that used interrupts throughout the code instead of
+	   doing busy and/or sleep-waiting. Alas, it appears that once
+	   the Motorola firmware is downloaded, the card *never*
+	   generates an RX interrupt. These are successfully generated
+	   during firmware loading, and after that wavefront_status()
+	   reports that an interrupt is pending on the card from time
+	   to time, but it never seems to be delivered to this
+	   driver. Note also that wavefront_status() continues to
+	   report that RX interrupts are enabled, suggesting that I
+	   didn't goof up and disable them by mistake.
+
+	   Thus, I stepped back to a prior version of
+	   wavefront_wait(), the only place where this really
+	   matters. Its sad, but I've looked through the code to check
+	   on things, and I really feel certain that the Motorola
+	   firmware prevents RX-ready interrupts.
+	*/
+
+	if ((wavefront_status(dev) & (STAT_INTR_READ|STAT_INTR_WRITE)) == 0) {
+		return;
+	}
+
+	spin_lock(&dev->irq_lock);
+	dev->irq_ok = 1;
+	dev->irq_cnt++;
+	spin_unlock(&dev->irq_lock);
+	wake_up(&dev->interrupt_sleeper);
+}
+
+/* STATUS REGISTER 
+
+0 Host Rx Interrupt Enable (1=Enabled)
+1 Host Rx Register Full (1=Full)
+2 Host Rx Interrupt Pending (1=Interrupt)
+3 Unused
+4 Host Tx Interrupt (1=Enabled)
+5 Host Tx Register empty (1=Empty)
+6 Host Tx Interrupt Pending (1=Interrupt)
+7 Unused
+*/
+
+static int __init
+snd_wavefront_interrupt_bits (int irq)
+
+{
+	int bits;
+
+	switch (irq) {
+	case 9:
+		bits = 0x00;
+		break;
+	case 5:
+		bits = 0x08;
+		break;
+	case 12:
+		bits = 0x10;
+		break;
+	case 15:
+		bits = 0x18;
+		break;
+	
+	default:
+		snd_printk ("invalid IRQ %d\n", irq);
+		bits = -1;
+	}
+
+	return bits;
+}
+
+static void __init
+wavefront_should_cause_interrupt (snd_wavefront_t *dev, 
+				  int val, int port, int timeout)
+
+{
+	wait_queue_t wait;
+
+	init_waitqueue_entry(&wait, current);
+	spin_lock_irq(&dev->irq_lock);
+	add_wait_queue(&dev->interrupt_sleeper, &wait);
+	dev->irq_ok = 0;
+	outb (val,port);
+	spin_unlock_irq(&dev->irq_lock);
+	while (1) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		if ((timeout = schedule_timeout(timeout)) == 0)
+			return;
+		if (dev->irq_ok)
+			return;
+	}
+}
+
+static int __init
+wavefront_reset_to_cleanliness (snd_wavefront_t *dev)
+
+{
+	int bits;
+	int hwv[2];
+
+	/* IRQ already checked */
+
+	bits = snd_wavefront_interrupt_bits (dev->irq);
+
+	/* try reset of port */
+
+	outb (0x0, dev->control_port); 
+  
+	/* At this point, the board is in reset, and the H/W initialization
+	   register is accessed at the same address as the data port.
+     
+	   Bit 7 - Enable IRQ Driver	
+	   0 - Tri-state the Wave-Board drivers for the PC Bus IRQs
+	   1 - Enable IRQ selected by bits 5:3 to be driven onto the PC Bus.
+     
+	   Bit 6 - MIDI Interface Select
+
+	   0 - Use the MIDI Input from the 26-pin WaveBlaster
+	   compatible header as the serial MIDI source
+	   1 - Use the MIDI Input from the 9-pin D connector as the
+	   serial MIDI source.
+     
+	   Bits 5:3 - IRQ Selection
+	   0 0 0 - IRQ 2/9
+	   0 0 1 - IRQ 5
+	   0 1 0 - IRQ 12
+	   0 1 1 - IRQ 15
+	   1 0 0 - Reserved
+	   1 0 1 - Reserved
+	   1 1 0 - Reserved
+	   1 1 1 - Reserved
+     
+	   Bits 2:1 - Reserved
+	   Bit 0 - Disable Boot ROM
+	   0 - memory accesses to 03FC30-03FFFFH utilize the internal Boot ROM
+	   1 - memory accesses to 03FC30-03FFFFH are directed to external 
+	   storage.
+     
+	*/
+
+	/* configure hardware: IRQ, enable interrupts, 
+	   plus external 9-pin MIDI interface selected
+	*/
+
+	outb (0x80 | 0x40 | bits, dev->data_port);	
+  
+	/* CONTROL REGISTER
+
+	   0 Host Rx Interrupt Enable (1=Enabled)      0x1
+	   1 Unused                                    0x2
+	   2 Unused                                    0x4
+	   3 Unused                                    0x8
+	   4 Host Tx Interrupt Enable                 0x10
+	   5 Mute (0=Mute; 1=Play)                    0x20
+	   6 Master Interrupt Enable (1=Enabled)      0x40
+	   7 Master Reset (0=Reset; 1=Run)            0x80
+
+	   Take us out of reset, mute output, master + TX + RX interrupts on.
+	   
+	   We'll get an interrupt presumably to tell us that the TX
+	   register is clear.
+	*/
+
+	wavefront_should_cause_interrupt(dev, 0x80|0x40|0x10|0x1,
+					 dev->control_port,
+					 (reset_time*HZ)/100);
+
+	/* Note: data port is now the data port, not the h/w initialization
+	   port.
+	 */
+
+	if (!dev->irq_ok) {
+		snd_printk ("intr not received after h/w un-reset.\n");
+		goto gone_bad;
+	} 
+
+	/* Note: data port is now the data port, not the h/w initialization
+	   port.
+
+	   At this point, only "HW VERSION" or "DOWNLOAD OS" commands
+	   will work. So, issue one of them, and wait for TX
+	   interrupt. This can take a *long* time after a cold boot,
+	   while the ISC ROM does its RAM test. The SDK says up to 4
+	   seconds - with 12MB of RAM on a Tropez+, it takes a lot
+	   longer than that (~16secs). Note that the card understands
+	   the difference between a warm and a cold boot, so
+	   subsequent ISC2115 reboots (say, caused by module
+	   reloading) will get through this much faster.
+
+	   XXX Interesting question: why is no RX interrupt received first ?
+	*/
+
+	wavefront_should_cause_interrupt(dev, WFC_HARDWARE_VERSION, 
+					 dev->data_port, ramcheck_time*HZ);
+
+	if (!dev->irq_ok) {
+		snd_printk ("post-RAM-check interrupt not received.\n");
+		goto gone_bad;
+	} 
+
+	if (!wavefront_wait (dev, STAT_CAN_READ)) {
+		snd_printk ("no response to HW version cmd.\n");
+		goto gone_bad;
+	}
+	
+	if ((hwv[0] = wavefront_read (dev)) == -1) {
+		snd_printk ("board not responding correctly.\n");
+		goto gone_bad;
+	}
+
+	if (hwv[0] == 0xFF) { /* NAK */
+
+		/* Board's RAM test failed. Try to read error code,
+		   and tell us about it either way.
+		*/
+		
+		if ((hwv[0] = wavefront_read (dev)) == -1) {
+			snd_printk ("on-board RAM test failed "
+				    "(bad error code).\n");
+		} else {
+			snd_printk ("on-board RAM test failed "
+				    "(error code: 0x%x).\n",
+				hwv[0]);
+		}
+		goto gone_bad;
+	}
+
+	/* We're OK, just get the next byte of the HW version response */
+
+	if ((hwv[1] = wavefront_read (dev)) == -1) {
+		snd_printk ("incorrect h/w response.\n");
+		goto gone_bad;
+	}
+
+	snd_printk ("hardware version %d.%d\n",
+		    hwv[0], hwv[1]);
+
+	return 0;
+
+
+     gone_bad:
+	return (1);
+}
+
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/unistd.h>
+#include <linux/syscalls.h>
+#include <asm/uaccess.h>
+
+
+static int __init
+wavefront_download_firmware (snd_wavefront_t *dev, char *path)
+
+{
+	unsigned char section[WF_SECTION_MAX];
+	signed char section_length; /* yes, just a char; max value is WF_SECTION_MAX */
+	int section_cnt_downloaded = 0;
+	int fd;
+	int c;
+	int i;
+	mm_segment_t fs;
+
+	/* This tries to be a bit cleverer than the stuff Alan Cox did for
+	   the generic sound firmware, in that it actually knows
+	   something about the structure of the Motorola firmware. In
+	   particular, it uses a version that has been stripped of the
+	   20K of useless header information, and had section lengths
+	   added, making it possible to load the entire OS without any
+	   [kv]malloc() activity, since the longest entity we ever read is
+	   42 bytes (well, WF_SECTION_MAX) long.
+	*/
+
+	fs = get_fs();
+	set_fs (get_ds());
+
+	if ((fd = sys_open ((char __user *) path, 0, 0)) < 0) {
+		snd_printk ("Unable to load \"%s\".\n",
+			path);
+		return 1;
+	}
+
+	while (1) {
+		int x;
+
+		if ((x = sys_read (fd, (char __user *) &section_length, sizeof (section_length))) !=
+		    sizeof (section_length)) {
+			snd_printk ("firmware read error.\n");
+			goto failure;
+		}
+
+		if (section_length == 0) {
+			break;
+		}
+
+		if (section_length < 0 || section_length > WF_SECTION_MAX) {
+			snd_printk ("invalid firmware section length %d\n",
+				    section_length);
+			goto failure;
+		}
+
+		if (sys_read (fd, (char __user *) section, section_length) != section_length) {
+			snd_printk ("firmware section "
+				"read error.\n");
+			goto failure;
+		}
+
+		/* Send command */
+	
+		if (wavefront_write (dev, WFC_DOWNLOAD_OS)) {
+			goto failure;
+		}
+	
+		for (i = 0; i < section_length; i++) {
+			if (wavefront_write (dev, section[i])) {
+				goto failure;
+			}
+		}
+	
+		/* get ACK */
+	
+		if (wavefront_wait (dev, STAT_CAN_READ)) {
+
+			if ((c = inb (dev->data_port)) != WF_ACK) {
+
+				snd_printk ("download "
+					    "of section #%d not "
+					    "acknowledged, ack = 0x%x\n",
+					    section_cnt_downloaded + 1, c);
+				goto failure;
+		
+			}
+
+		} else {
+			snd_printk ("time out for firmware ACK.\n");
+			goto failure;
+		}
+
+	}
+
+	sys_close (fd);
+	set_fs (fs);
+	return 0;
+
+ failure:
+	sys_close (fd);
+	set_fs (fs);
+	snd_printk ("firmware download failed!!!\n");
+	return 1;
+}
+
+
+static int __init
+wavefront_do_reset (snd_wavefront_t *dev)
+
+{
+	char voices[1];
+
+	if (wavefront_reset_to_cleanliness (dev)) {
+		snd_printk ("hw reset failed.\n");
+		goto gone_bad;
+	}
+
+	if (dev->israw) {
+		if (wavefront_download_firmware (dev, ospath)) {
+			goto gone_bad;
+		}
+
+		dev->israw = 0;
+
+		/* Wait for the OS to get running. The protocol for
+		   this is non-obvious, and was determined by
+		   using port-IO tracing in DOSemu and some
+		   experimentation here.
+		   
+		   Rather than using timed waits, use interrupts creatively.
+		*/
+
+		wavefront_should_cause_interrupt (dev, WFC_NOOP,
+						  dev->data_port,
+						  (osrun_time*HZ));
+
+		if (!dev->irq_ok) {
+			snd_printk ("no post-OS interrupt.\n");
+			goto gone_bad;
+		}
+		
+		/* Now, do it again ! */
+		
+		wavefront_should_cause_interrupt (dev, WFC_NOOP,
+						  dev->data_port, (10*HZ));
+		
+		if (!dev->irq_ok) {
+			snd_printk ("no post-OS interrupt(2).\n");
+			goto gone_bad;
+		}
+
+		/* OK, no (RX/TX) interrupts any more, but leave mute
+		   in effect. 
+		*/
+		
+		outb (0x80|0x40, dev->control_port); 
+	}
+
+	/* SETUPSND.EXE asks for sample memory config here, but since i
+	   have no idea how to interpret the result, we'll forget
+	   about it.
+	*/
+	
+	if ((dev->freemem = wavefront_freemem (dev)) < 0) {
+		goto gone_bad;
+	}
+		
+	snd_printk ("available DRAM %dk\n", dev->freemem / 1024);
+
+	if (wavefront_write (dev, 0xf0) ||
+	    wavefront_write (dev, 1) ||
+	    (wavefront_read (dev) < 0)) {
+		dev->debug = 0;
+		snd_printk ("MPU emulation mode not set.\n");
+		goto gone_bad;
+	}
+
+	voices[0] = 32;
+
+	if (snd_wavefront_cmd (dev, WFC_SET_NVOICES, NULL, voices)) {
+		snd_printk ("cannot set number of voices to 32.\n");
+		goto gone_bad;
+	}
+
+
+	return 0;
+
+ gone_bad:
+	/* reset that sucker so that it doesn't bother us. */
+
+	outb (0x0, dev->control_port);
+	dev->interrupts_are_midi = 0;
+	return 1;
+}
+
+int __init
+snd_wavefront_start (snd_wavefront_t *dev)
+
+{
+	int samples_are_from_rom;
+
+	/* IMPORTANT: assumes that snd_wavefront_detect() and/or
+	   wavefront_reset_to_cleanliness() has already been called 
+	*/
+
+	if (dev->israw) {
+		samples_are_from_rom = 1;
+	} else {
+		/* XXX is this always true ? */
+		samples_are_from_rom = 0;
+	}
+
+	if (dev->israw || fx_raw) {
+		if (wavefront_do_reset (dev)) {
+			return -1;
+		}
+	}
+	/* Check for FX device, present only on Tropez+ */
+
+	dev->has_fx = (snd_wavefront_fx_detect (dev) == 0);
+
+	if (dev->has_fx && fx_raw) {
+		snd_wavefront_fx_start (dev);
+	}
+
+	wavefront_get_sample_status (dev, samples_are_from_rom);
+	wavefront_get_program_status (dev);
+	wavefront_get_patch_status (dev);
+
+	/* Start normal operation: unreset, master interrupt enabled, no mute
+	*/
+
+	outb (0x80|0x40|0x20, dev->control_port); 
+
+	return (0);
+}
+
+int __init
+snd_wavefront_detect (snd_wavefront_card_t *card)
+
+{
+	unsigned char   rbuf[4], wbuf[4];
+	snd_wavefront_t *dev = &card->wavefront;
+	
+	/* returns zero if a WaveFront card is successfully detected.
+	   negative otherwise.
+	*/
+
+	dev->israw = 0;
+	dev->has_fx = 0;
+	dev->debug = debug_default;
+	dev->interrupts_are_midi = 0;
+	dev->irq_cnt = 0;
+	dev->rom_samples_rdonly = 1;
+
+	if (snd_wavefront_cmd (dev, WFC_FIRMWARE_VERSION, rbuf, wbuf) == 0) {
+
+		dev->fw_version[0] = rbuf[0];
+		dev->fw_version[1] = rbuf[1];
+
+		snd_printk ("firmware %d.%d already loaded.\n",
+			    rbuf[0], rbuf[1]);
+
+		/* check that a command actually works */
+      
+		if (snd_wavefront_cmd (dev, WFC_HARDWARE_VERSION,
+				       rbuf, wbuf) == 0) {
+			dev->hw_version[0] = rbuf[0];
+			dev->hw_version[1] = rbuf[1];
+		} else {
+			snd_printk ("not raw, but no "
+				    "hardware version!\n");
+			return -1;
+		}
+
+		if (!wf_raw) {
+			return 0;
+		} else {
+			snd_printk ("reloading firmware as you requested.\n");
+			dev->israw = 1;
+		}
+
+	} else {
+
+		dev->israw = 1;
+		snd_printk ("no response to firmware probe, assume raw.\n");
+
+	}
+
+	return 0;
+}