v4.19.13 snapshot.
diff --git a/sound/isa/Kconfig b/sound/isa/Kconfig
new file mode 100644
index 0000000..d7db1ee
--- /dev/null
+++ b/sound/isa/Kconfig
@@ -0,0 +1,471 @@
+# ALSA ISA drivers
+
+config SND_WSS_LIB
+        tristate
+        select SND_PCM
+	select SND_TIMER
+
+config SND_SB_COMMON
+        tristate
+
+config SND_SB8_DSP
+        tristate
+        select SND_PCM
+        select SND_SB_COMMON
+
+config SND_SB16_DSP
+        tristate
+        select SND_PCM
+        select SND_SB_COMMON
+
+menuconfig SND_ISA
+	bool "ISA sound devices"
+	depends on ISA || COMPILE_TEST
+	depends on ISA_DMA_API
+	default y
+	help
+	  Support for sound devices connected via the ISA bus.
+
+if SND_ISA
+
+config SND_ADLIB
+	tristate "AdLib FM card"
+	select SND_OPL3_LIB
+	help
+	  Say Y here to include support for AdLib FM cards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-adlib.
+
+config SND_AD1816A
+	tristate "Analog Devices SoundPort AD1816A"
+	depends on PNP
+	select ISAPNP
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	select SND_TIMER
+	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"
+	select SND_WSS_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 or CS4232+
+	  drivers.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-ad1848.
+
+config SND_ALS100
+	tristate "Diamond Tech. DT-019x and Avance Logic ALSxxx"
+	depends on PNP
+	select ISAPNP
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_SB16_DSP
+	help
+	  Say Y here to include support for soundcards based on the
+	  Diamond Technologies DT-019X or Avance Logic chips: ALS007,
+	  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_AZT1605
+	tristate "Aztech AZT1605 Driver"
+	depends on SND
+	select SND_WSS_LIB
+	select SND_MPU401_UART
+	select SND_OPL3_LIB
+	help
+	  Say Y here to include support for Aztech Sound Galaxy cards
+	  based on the AZT1605 chipset.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-azt1605.
+
+config SND_AZT2316
+	tristate "Aztech AZT2316 Driver"
+	depends on SND
+	select SND_WSS_LIB
+	select SND_MPU401_UART
+	select SND_OPL3_LIB
+	help
+	  Say Y here to include support for Aztech Sound Galaxy cards
+	  based on the AZT2316 chipset.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-azt2316.
+
+config SND_AZT2320
+	tristate "Aztech Systems AZT2320"
+	depends on PNP
+	select ISAPNP
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_WSS_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_CMI8328
+	tristate "C-Media CMI8328"
+	select SND_WSS_LIB
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	help
+	  Say Y here to include support for soundcards based on the
+	  C-Media CMI8328 chip.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-cmi8328.
+
+config SND_CMI8330
+	tristate "C-Media CMI8330"
+	select SND_WSS_LIB
+	select SND_SB16_DSP
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	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_CS4231
+	tristate "Generic Cirrus Logic CS4231 driver"
+	select SND_MPU401_UART
+	select SND_WSS_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_CS4236
+	tristate "Generic Cirrus Logic CS4232/CS4236+ driver"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_WSS_LIB
+	help
+	  Say Y to include support for CS4232,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_ES1688
+	tristate "Generic ESS ES688/ES1688 and ES968 PnP driver"
+	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. Also, this module support cards with ES968 PnP chip.
+
+	  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"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	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_SC6000
+	tristate "Gallant SC-6000/6600/7000 and Audio Excel DSP 16"
+	depends on HAS_IOPORT_MAP
+	select SND_WSS_LIB
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	help
+	  Say Y here to include support for Gallant SC-6000, SC-6600, SC-7000
+	  cards and clones:
+	  Audio Excel DSP 16 and Zoltrix AV302.
+
+	  These cards are based on CompuMedia ASC-9308 or ASC-9408 chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-sc6000.
+
+config SND_GUSCLASSIC
+	tristate "Gravis UltraSound Classic"
+	select SND_RAWMIDI
+	select SND_PCM
+	select SND_TIMER
+	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"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	select SND_TIMER
+	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"
+	select SND_RAWMIDI
+	select SND_WSS_LIB
+	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 PNP
+	select SND_RAWMIDI
+	select SND_WSS_LIB
+	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 PNP
+	select SND_RAWMIDI
+	select SND_WSS_LIB
+	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_JAZZ16
+	tristate "Media Vision Jazz16 card and compatibles"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_SB8_DSP
+	help
+	  Say Y here to include support for soundcards based on the
+	  Media Vision Jazz16 chipset: digital chip MVD1216 (Jazz16),
+	  codec MVA416 (CS4216) and mixer MVA514 (ICS2514).
+	  Media Vision's Jazz16 cards were sold under names Pro Sonic 16,
+	  Premium 3-D and Pro 3-D. There were also OEMs cards with the
+	  Jazz16 chipset.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-jazz16.
+
+config SND_OPL3SA2
+	tristate "Yamaha OPL3-SA2/SA3"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_WSS_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_OPTI92X_AD1848
+	tristate "OPTi 82C92x - AD1848"
+	select SND_OPL3_LIB
+	select SND_OPL4_LIB
+	select SND_MPU401_UART
+	select SND_WSS_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"
+	select SND_OPL3_LIB
+	select SND_OPL4_LIB
+	select SND_MPU401_UART
+	select SND_WSS_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"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_WSS_LIB
+	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_MIRO
+	tristate "Miro miroSOUND PCM1pro/PCM12/PCM20radio driver"
+	select SND_OPL4_LIB
+	select SND_WSS_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  Say 'Y' or 'M' to include support for Miro miroSOUND PCM1 pro, 
+	  miroSOUND PCM12 and miroSOUND PCM20 Radio soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-miro.
+
+config SND_SB8
+	tristate "Sound Blaster 1.0/2.0/Pro (8-bit)"
+	select SND_OPL3_LIB
+	select SND_RAWMIDI
+	select SND_SB8_DSP
+	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)"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_SB16_DSP
+	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)"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_SB16_DSP
+	select SND_SEQ_DEVICE if SND_SEQUENCER != n
+	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.
+
+# select SEQ stuff to min(SND_SEQUENCER,SND_XXX)
+config SND_SBAWE_SEQ
+	def_tristate SND_SEQUENCER && SND_SBAWE
+	select SND_SEQ_MIDI_EMUL
+	select SND_SEQ_VIRMIDI
+	select SND_SYNTH_EMUX
+
+config SND_SB16_CSP
+	bool "Sound Blaster 16/AWE CSP support"
+	depends on (SND_SB16 || SND_SBAWE) && (BROKEN || !PPC)
+	select FW_LOADER
+	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_SSCAPE
+	tristate "Ensoniq SoundScape driver"
+	select SND_MPU401_UART
+	select SND_WSS_LIB
+	select FW_LOADER
+	help
+	  Say Y here to include support for Ensoniq SoundScape 
+	  and Ensoniq OEM soundcards.
+
+	  The PCM audio is supported on SoundScape Classic, Elite, PnP
+	  and VIVO cards. The supported OEM cards are SPEA Media FX and
+	  Reveal SC-600.
+	  The MIDI support is very experimental and requires binary
+	  firmware files called "scope.cod" and "sndscape.co?" where the
+	  ? is digit 0, 1, 2, 3 or 4. The firmware files can be found
+	  in DOS or Windows driver packages. One has to put the firmware
+	  files into the /lib/firmware directory.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-sscape.
+
+config SND_WAVEFRONT
+	tristate "Turtle Beach Maui,Tropez,Tropez+ (Wavefront)"
+	select FW_LOADER
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_WSS_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_MSND_PINNACLE
+	tristate "Turtle Beach MultiSound Pinnacle/Fiji driver"
+	depends on X86
+	select FW_LOADER
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  Say Y to include support for Turtle Beach MultiSound Pinnacle/
+	  Fiji soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-msnd-pinnacle.
+
+config SND_MSND_CLASSIC
+	tristate "Support for Turtle Beach MultiSound Classic, Tahiti, Monterey"
+	depends on X86
+	select FW_LOADER
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  Say M here if you have a Turtle Beach MultiSound Classic, Tahiti or
+	  Monterey (not for the Pinnacle or Fiji).
+
+	  See <file:Documentation/sound/cards/multisound.sh> for important information
+	  about this driver.  Note that it has been discontinued, but the
+	  Voyetra Turtle Beach knowledge base entry for it is still available
+	  at <http://www.turtlebeach.com/site/kb_ftp/790.asp>.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-msnd-classic.
+
+endif	# SND_ISA
+
diff --git a/sound/isa/Makefile b/sound/isa/Makefile
new file mode 100644
index 0000000..5eaddbf
--- /dev/null
+++ b/sound/isa/Makefile
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-adlib-objs := adlib.o
+snd-als100-objs := als100.o
+snd-azt2320-objs := azt2320.o
+snd-cmi8328-objs := cmi8328.o
+snd-cmi8330-objs := cmi8330.o
+snd-es18xx-objs := es18xx.o
+snd-opl3sa2-objs := opl3sa2.o
+snd-sc6000-objs := sc6000.o
+snd-sscape-objs := sscape.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_ADLIB) += snd-adlib.o
+obj-$(CONFIG_SND_ALS100) += snd-als100.o
+obj-$(CONFIG_SND_AZT2320) += snd-azt2320.o
+obj-$(CONFIG_SND_CMI8328) += snd-cmi8328.o
+obj-$(CONFIG_SND_CMI8330) += snd-cmi8330.o
+obj-$(CONFIG_SND_ES18XX) += snd-es18xx.o
+obj-$(CONFIG_SND_OPL3SA2) += snd-opl3sa2.o
+obj-$(CONFIG_SND_SC6000) += snd-sc6000.o
+obj-$(CONFIG_SND_SSCAPE) += snd-sscape.o
+
+obj-$(CONFIG_SND) += ad1816a/ ad1848/ cs423x/ es1688/ galaxy/ gus/ msnd/ opti9xx/ \
+		     sb/ wavefront/ wss/
diff --git a/sound/isa/ad1816a/Makefile b/sound/isa/ad1816a/Makefile
new file mode 100644
index 0000000..487ab23
--- /dev/null
+++ b/sound/isa/ad1816a/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-ad1816a-objs := ad1816a.o ad1816a_lib.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_AD1816A) += snd-ad1816a.o
diff --git a/sound/isa/ad1816a/ad1816a.c b/sound/isa/ad1816a/ad1816a.c
new file mode 100644
index 0000000..4be6c12
--- /dev/null
+++ b/sound/isa/ad1816a/ad1816a.c
@@ -0,0 +1,303 @@
+
+/*
+    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 <linux/init.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/pnp.h>
+#include <linux/module.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 bool 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 */
+static int clockfreq[SNDRV_CARDS];
+
+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(clockfreq, int, NULL, 0444);
+MODULE_PARM_DESC(clockfreq, "Clock frequency for ad1816a driver (default = 0).");
+
+static const struct pnp_card_device_id snd_ad1816a_pnpids[] = {
+	/* Analog Devices AD1815 */
+	{ .id = "ADS7150", .devs = { { .id = "ADS7150" }, { .id = "ADS7151" } } },
+	/* Analog Device AD1816? */
+	{ .id = "ADS7180", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+	/* 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 EWS64 S */
+	{ .id = "TER1112", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+	/* Analog Devices AD1816A - Terratec AudioSystem EWS64 S */
+	{ .id = "TER1112", .devs = { { .id = "TER1100" }, { .id = "TER1101" } } },
+	/* 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 snd_card_ad1816a_pnp(int dev, struct pnp_card_link *card,
+				const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	int err;
+
+	pdev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (pdev == NULL)
+		return -EBUSY;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		printk(KERN_ERR PFX "AUDIO PnP configure failure\n");
+		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 = pnp_request_card_device(card, id->devs[1].id, NULL);
+	if (pdev == NULL) {
+		mpu_port[dev] = -1;
+		snd_printk(KERN_WARNING PFX "MPU401 device busy, skipping.\n");
+		return 0;
+	}
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		printk(KERN_ERR PFX "MPU401 PnP configure failure\n");
+		mpu_port[dev] = -1;
+	} else {
+		mpu_port[dev] = pnp_port_start(pdev, 0);
+		mpu_irq[dev] = pnp_irq(pdev, 0);
+	}
+
+	return 0;
+}
+
+static int snd_card_ad1816a_probe(int dev, struct pnp_card_link *pcard,
+				  const struct pnp_card_device_id *pid)
+{
+	int error;
+	struct snd_card *card;
+	struct snd_ad1816a *chip;
+	struct snd_opl3 *opl3;
+
+	error = snd_card_new(&pcard->card->dev,
+			     index[dev], id[dev], THIS_MODULE,
+			     sizeof(struct snd_ad1816a), &card);
+	if (error < 0)
+		return error;
+	chip = card->private_data;
+
+	if ((error = snd_card_ad1816a_pnp(dev, pcard, pid))) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if ((error = snd_ad1816a_create(card, port[dev],
+					irq[dev],
+					dma1[dev],
+					dma2[dev],
+					chip)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	if (clockfreq[dev] >= 5000 && clockfreq[dev] <= 100000)
+		chip->clock_freq = clockfreq[dev];
+
+	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)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if ((error = snd_ad1816a_mixer(chip)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	error = snd_ad1816a_timer(chip, 0);
+	if (error < 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],
+					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 {
+			error = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+			if (error < 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 unsigned int ad1816a_devices;
+
+static int 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++;
+		ad1816a_devices++;
+		return 0;
+	}
+        return -ENODEV;
+}
+
+static void snd_ad1816a_pnp_remove(struct pnp_card_link *pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_ad1816a_pnp_suspend(struct pnp_card_link *pcard,
+				   pm_message_t state)
+{
+	struct snd_card *card = pnp_get_card_drvdata(pcard);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_ad1816a_suspend(card->private_data);
+	return 0;
+}
+
+static int snd_ad1816a_pnp_resume(struct pnp_card_link *pcard)
+{
+	struct snd_card *card = pnp_get_card_drvdata(pcard);
+
+	snd_ad1816a_resume(card->private_data);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+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		= snd_ad1816a_pnp_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_ad1816a_pnp_suspend,
+	.resume		= snd_ad1816a_pnp_resume,
+#endif
+};
+
+static int __init alsa_card_ad1816a_init(void)
+{
+	int err;
+
+	err = pnp_register_card_driver(&ad1816a_pnpc_driver);
+	if (err)
+		return err;
+
+	if (!ad1816a_devices) {
+		pnp_unregister_card_driver(&ad1816a_pnpc_driver);
+#ifdef MODULE
+		printk(KERN_ERR "no AD1816A based soundcards found.\n");
+#endif	/* MODULE */
+		return -ENODEV;
+	}
+	return 0;
+}
+
+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..fba6d22
--- /dev/null
+++ b/sound/isa/ad1816a/ad1816a_lib.c
@@ -0,0 +1,981 @@
+/*
+    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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+#include <sound/ad1816a.h>
+
+#include <asm/dma.h>
+
+static inline int snd_ad1816a_busy_wait(struct snd_ad1816a *chip)
+{
+	int timeout;
+
+	for (timeout = 1000; timeout-- > 0; udelay(10))
+		if (inb(AD1816A_REG(AD1816A_CHIP_STATUS)) & AD1816A_READY)
+			return 0;
+
+	snd_printk(KERN_WARNING "chip busy.\n");
+	return -EBUSY;
+}
+
+static inline unsigned char snd_ad1816a_in(struct snd_ad1816a *chip, unsigned char reg)
+{
+	snd_ad1816a_busy_wait(chip);
+	return inb(AD1816A_REG(reg));
+}
+
+static inline void snd_ad1816a_out(struct snd_ad1816a *chip, unsigned char reg,
+			    unsigned char value)
+{
+	snd_ad1816a_busy_wait(chip);
+	outb(value, AD1816A_REG(reg));
+}
+
+static inline void snd_ad1816a_out_mask(struct snd_ad1816a *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(struct snd_ad1816a *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(struct snd_ad1816a *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(struct snd_ad1816a *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(struct snd_ad1816a *chip,
+					    snd_pcm_format_t 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(struct snd_ad1816a *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(struct snd_ad1816a *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(struct snd_ad1816a *chip, unsigned char what,
+			       int channel, int cmd, int iscapture)
+{
+	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) */
+		/* That is not valid, because playback and capture enable
+		 * are the same bit pattern, just to different addresses
+		 */
+		if (! iscapture)
+			snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG,
+				AD1816A_PLAYBACK_ENABLE, cmd);
+		else
+			snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG,
+				AD1816A_CAPTURE_ENABLE, cmd);
+		spin_unlock(&chip->lock);
+		break;
+	default:
+		snd_printk(KERN_WARNING "invalid trigger mode 0x%x.\n", what);
+		error = -EINVAL;
+	}
+
+	return error;
+}
+
+static int snd_ad1816a_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+	return snd_ad1816a_trigger(chip, AD1816A_PLAYBACK_ENABLE,
+				   SNDRV_PCM_STREAM_PLAYBACK, cmd, 0);
+}
+
+static int snd_ad1816a_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+	return snd_ad1816a_trigger(chip, AD1816A_CAPTURE_ENABLE,
+				   SNDRV_PCM_STREAM_CAPTURE, cmd, 1);
+}
+
+static int snd_ad1816a_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_ad1816a_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_ad1816a_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+	unsigned long flags;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int size, rate;
+
+	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);
+
+	rate = runtime->rate;
+	if (chip->clock_freq)
+		rate = (rate * 33000) / chip->clock_freq;
+	snd_ad1816a_write(chip, AD1816A_PLAYBACK_SAMPLE_RATE, 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(struct snd_pcm_substream *substream)
+{
+	struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+	unsigned long flags;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int size, rate;
+
+	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);
+
+	rate = runtime->rate;
+	if (chip->clock_freq)
+		rate = (rate * 33000) / chip->clock_freq;
+	snd_ad1816a_write(chip, AD1816A_CAPTURE_SAMPLE_RATE, 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(struct snd_pcm_substream *substream)
+{
+	struct snd_ad1816a *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(struct snd_pcm_substream *substream)
+{
+	struct snd_ad1816a *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 snd_ad1816a *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 const struct snd_pcm_hardware 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 const struct snd_pcm_hardware 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,
+};
+
+static int snd_ad1816a_timer_close(struct snd_timer *timer)
+{
+	struct snd_ad1816a *chip = snd_timer_chip(timer);
+	snd_ad1816a_close(chip, AD1816A_MODE_TIMER);
+	return 0;
+}
+
+static int snd_ad1816a_timer_open(struct snd_timer *timer)
+{
+	struct snd_ad1816a *chip = snd_timer_chip(timer);
+	snd_ad1816a_open(chip, AD1816A_MODE_TIMER);
+	return 0;
+}
+
+static unsigned long snd_ad1816a_timer_resolution(struct snd_timer *timer)
+{
+	if (snd_BUG_ON(!timer))
+		return 0;
+
+	return 10000;
+}
+
+static int snd_ad1816a_timer_start(struct snd_timer *timer)
+{
+	unsigned short bits;
+	unsigned long flags;
+	struct snd_ad1816a *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(struct snd_timer *timer)
+{
+	unsigned long flags;
+	struct snd_ad1816a *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,
+};
+
+static int snd_ad1816a_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int error;
+
+	if ((error = snd_ad1816a_open(chip, AD1816A_MODE_PLAYBACK)) < 0)
+		return error;
+	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(struct snd_pcm_substream *substream)
+{
+	struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int error;
+
+	if ((error = snd_ad1816a_open(chip, AD1816A_MODE_CAPTURE)) < 0)
+		return error;
+	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(struct snd_pcm_substream *substream)
+{
+	struct snd_ad1816a *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(struct snd_pcm_substream *substream)
+{
+	struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+
+	chip->capture_substream = NULL;
+	snd_ad1816a_close(chip, AD1816A_MODE_CAPTURE);
+	return 0;
+}
+
+
+static void snd_ad1816a_init(struct snd_ad1816a *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);
+}
+
+#ifdef CONFIG_PM
+void snd_ad1816a_suspend(struct snd_ad1816a *chip)
+{
+	int reg;
+	unsigned long flags;
+
+	snd_pcm_suspend_all(chip->pcm);
+	spin_lock_irqsave(&chip->lock, flags);
+	for (reg = 0; reg < 48; reg++)
+		chip->image[reg] = snd_ad1816a_read(chip, reg);
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+void snd_ad1816a_resume(struct snd_ad1816a *chip)
+{
+	int reg;
+	unsigned long flags;
+
+	snd_ad1816a_init(chip);
+	spin_lock_irqsave(&chip->lock, flags);
+	for (reg = 0; reg < 48; reg++)
+		snd_ad1816a_write(chip, reg, chip->image[reg]);
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+#endif
+
+static int snd_ad1816a_probe(struct snd_ad1816a *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(struct snd_ad1816a *chip)
+{
+	release_and_free_resource(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);
+	}
+	return 0;
+}
+
+static int snd_ad1816a_dev_free(struct snd_device *device)
+{
+	struct snd_ad1816a *chip = device->device_data;
+	return snd_ad1816a_free(chip);
+}
+
+static const char *snd_ad1816a_chip_id(struct snd_ad1816a *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(KERN_WARNING "Unknown chip version %d:%d.\n",
+			chip->version, chip->hardware);
+		return "AD1816A - unknown";
+	}
+}
+
+int snd_ad1816a_create(struct snd_card *card,
+		       unsigned long port, int irq, int dma1, int dma2,
+		       struct snd_ad1816a *chip)
+{
+        static struct snd_device_ops ops = {
+		.dev_free =	snd_ad1816a_dev_free,
+	};
+	int error;
+
+	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, 0, "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;
+	}
+
+	return 0;
+}
+
+static const struct snd_pcm_ops 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 const struct snd_pcm_ops 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,
+};
+
+int snd_ad1816a_pcm(struct snd_ad1816a *chip, int device)
+{
+	int error;
+	struct snd_pcm *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->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;
+	return 0;
+}
+
+int snd_ad1816a_timer(struct snd_ad1816a *chip, int device)
+{
+	struct snd_timer *timer;
+	struct snd_timer_id 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;
+	chip->timer = timer;
+	timer->hw = snd_ad1816a_timer_table;
+	return 0;
+}
+
+/*
+ *
+ */
+
+static int snd_ad1816a_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts[8] = {
+		"Line", "Mix", "CD", "Synth", "Video",
+		"Mic", "Phone",
+	};
+
+	return snd_ctl_enum_info(uinfo, 2, 7, texts);
+}
+
+static int snd_ad1816a_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ad1816a *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ad1816a *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_TLV(xname, reg, shift, mask, invert, xtlv)	\
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .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), \
+  .tlv = { .p = (xtlv) } }
+#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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ad1816a *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ad1816a *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_TLV(xname, reg, shift_left, shift_right, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .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), \
+  .tlv = { .p = (xtlv) } }
+
+#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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ad1816a *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ad1816a *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 const DECLARE_TLV_DB_SCALE(db_scale_4bit, -4500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit, -4650, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_6bit, -9450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_rec_gain, 0, 150, 0);
+
+static struct snd_kcontrol_new snd_ad1816a_controls[] = {
+AD1816A_DOUBLE("Master Playback Switch", AD1816A_MASTER_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("Master Playback Volume", AD1816A_MASTER_ATT, 8, 0, 31, 1,
+		   db_scale_5bit),
+AD1816A_DOUBLE("PCM Playback Switch", AD1816A_VOICE_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("PCM Playback Volume", AD1816A_VOICE_ATT, 8, 0, 63, 1,
+		   db_scale_6bit),
+AD1816A_DOUBLE("Line Playback Switch", AD1816A_LINE_GAIN_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("Line Playback Volume", AD1816A_LINE_GAIN_ATT, 8, 0, 31, 1,
+		   db_scale_5bit_12db_max),
+AD1816A_DOUBLE("CD Playback Switch", AD1816A_CD_GAIN_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("CD Playback Volume", AD1816A_CD_GAIN_ATT, 8, 0, 31, 1,
+		   db_scale_5bit_12db_max),
+AD1816A_DOUBLE("Synth Playback Switch", AD1816A_SYNTH_GAIN_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("Synth Playback Volume", AD1816A_SYNTH_GAIN_ATT, 8, 0, 31, 1,
+		   db_scale_5bit_12db_max),
+AD1816A_DOUBLE("FM Playback Switch", AD1816A_FM_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("FM Playback Volume", AD1816A_FM_ATT, 8, 0, 63, 1,
+		   db_scale_6bit),
+AD1816A_SINGLE("Mic Playback Switch", AD1816A_MIC_GAIN_ATT, 15, 1, 1),
+AD1816A_SINGLE_TLV("Mic Playback Volume", AD1816A_MIC_GAIN_ATT, 8, 31, 1,
+		   db_scale_5bit_12db_max),
+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_TLV("Video Playback Volume", AD1816A_VID_GAIN_ATT, 8, 0, 31, 1,
+		   db_scale_5bit_12db_max),
+AD1816A_SINGLE("Phone Capture Switch", AD1816A_PHONE_IN_GAIN_ATT, 15, 1, 1),
+AD1816A_SINGLE_TLV("Phone Capture Volume", AD1816A_PHONE_IN_GAIN_ATT, 0, 15, 1,
+		   db_scale_4bit),
+AD1816A_SINGLE("Phone Playback Switch", AD1816A_PHONE_OUT_ATT, 7, 1, 1),
+AD1816A_SINGLE_TLV("Phone Playback Volume", AD1816A_PHONE_OUT_ATT, 0, 31, 1,
+		   db_scale_5bit),
+{
+	.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_TLV("Capture Volume", AD1816A_ADC_PGA, 8, 0, 15, 0,
+		   db_scale_rec_gain),
+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(struct snd_ad1816a *chip)
+{
+	struct snd_card *card;
+	unsigned int idx;
+	int err;
+
+	if (snd_BUG_ON(!chip || !chip->card))
+		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;
+}
diff --git a/sound/isa/ad1848/Makefile b/sound/isa/ad1848/Makefile
new file mode 100644
index 0000000..3d6dea3
--- /dev/null
+++ b/sound/isa/ad1848/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-ad1848-objs := ad1848.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_AD1848) += snd-ad1848.o
+
diff --git a/sound/isa/ad1848/ad1848.c b/sound/isa/ad1848/ad1848.c
new file mode 100644
index 0000000..7c8e92f
--- /dev/null
+++ b/sound/isa/ad1848/ad1848.c
@@ -0,0 +1,177 @@
+/*
+ *  Generic driver for AD1848/AD1847/CS4248 chips (0.1 Alpha)
+ *  Copyright (c) by Tugrul Galatali <galatalt@stuy.edu>,
+ *                   Jaroslav Kysela <perex@perex.cz>
+ *  Based on card-4232.c by Jaroslav Kysela <perex@perex.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 <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/initval.h>
+
+#define CRD_NAME "Generic AD1848/AD1847/CS4248"
+#define DEV_NAME "ad1848"
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Tugrul Galatali <galatalt@stuy.edu>, Jaroslav Kysela <perex@perex.cz>");
+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 bool 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 bool thinkpad[SNDRV_CARDS];			/* Thinkpad special case */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for " CRD_NAME " 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 int snd_ad1848_match(struct device *dev, unsigned int n)
+{
+	if (!enable[n])
+		return 0;
+
+	if (port[n] == SNDRV_AUTO_PORT) {
+		dev_err(dev, "please specify port\n");
+		return 0;
+	}
+	if (irq[n] == SNDRV_AUTO_IRQ) {
+		dev_err(dev, "please specify irq\n");
+		return 0;	
+	}
+	if (dma1[n] == SNDRV_AUTO_DMA) {
+		dev_err(dev, "please specify dma1\n");
+		return 0;
+	}
+	return 1;
+}
+
+static int snd_ad1848_probe(struct device *dev, unsigned int n)
+{
+	struct snd_card *card;
+	struct snd_wss *chip;
+	int error;
+
+	error = snd_card_new(dev, index[n], id[n], THIS_MODULE, 0, &card);
+	if (error < 0)
+		return error;
+
+	error = snd_wss_create(card, port[n], -1, irq[n], dma1[n], -1,
+			thinkpad[n] ? WSS_HW_THINKPAD : WSS_HW_DETECT,
+			0, &chip);
+	if (error < 0)
+		goto out;
+
+	card->private_data = chip;
+
+	error = snd_wss_pcm(chip, 0);
+	if (error < 0)
+		goto out;
+
+	error = snd_wss_mixer(chip);
+	if (error < 0)
+		goto out;
+
+	strlcpy(card->driver, "AD1848", sizeof(card->driver));
+	strlcpy(card->shortname, chip->pcm->name, sizeof(card->shortname));
+
+	if (!thinkpad[n])
+		snprintf(card->longname, sizeof(card->longname),
+			 "%s at 0x%lx, irq %d, dma %d",
+			 chip->pcm->name, chip->port, irq[n], dma1[n]);
+	else
+		snprintf(card->longname, sizeof(card->longname),
+			 "%s at 0x%lx, irq %d, dma %d [Thinkpad]",
+			 chip->pcm->name, chip->port, irq[n], dma1[n]);
+
+	error = snd_card_register(card);
+	if (error < 0)
+		goto out;
+
+	dev_set_drvdata(dev, card);
+	return 0;
+
+out:	snd_card_free(card);
+	return error;
+}
+
+static int snd_ad1848_remove(struct device *dev, unsigned int n)
+{
+	snd_card_free(dev_get_drvdata(dev));
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_ad1848_suspend(struct device *dev, unsigned int n, pm_message_t state)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	struct snd_wss *chip = card->private_data;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	chip->suspend(chip);
+	return 0;
+}
+
+static int snd_ad1848_resume(struct device *dev, unsigned int n)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	struct snd_wss *chip = card->private_data;
+
+	chip->resume(chip);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+static struct isa_driver snd_ad1848_driver = {
+	.match		= snd_ad1848_match,
+	.probe		= snd_ad1848_probe,
+	.remove		= snd_ad1848_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_ad1848_suspend,
+	.resume		= snd_ad1848_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	}
+};
+
+module_isa_driver(snd_ad1848_driver, SNDRV_CARDS);
diff --git a/sound/isa/adlib.c b/sound/isa/adlib.c
new file mode 100644
index 0000000..5fb619e
--- /dev/null
+++ b/sound/isa/adlib.c
@@ -0,0 +1,115 @@
+/*
+ * AdLib FM card driver.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/isa.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/opl3.h>
+
+#define CRD_NAME "AdLib FM"
+#define DEV_NAME "adlib"
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Rene Herman");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+
+static int snd_adlib_match(struct device *dev, unsigned int n)
+{
+	if (!enable[n])
+		return 0;
+
+	if (port[n] == SNDRV_AUTO_PORT) {
+		dev_err(dev, "please specify port\n");
+		return 0;
+	}
+	return 1;
+}
+
+static void snd_adlib_free(struct snd_card *card)
+{
+	release_and_free_resource(card->private_data);
+}
+
+static int snd_adlib_probe(struct device *dev, unsigned int n)
+{
+	struct snd_card *card;
+	struct snd_opl3 *opl3;
+	int error;
+
+	error = snd_card_new(dev, index[n], id[n], THIS_MODULE, 0, &card);
+	if (error < 0) {
+		dev_err(dev, "could not create card\n");
+		return error;
+	}
+
+	card->private_data = request_region(port[n], 4, CRD_NAME);
+	if (!card->private_data) {
+		dev_err(dev, "could not grab ports\n");
+		error = -EBUSY;
+		goto out;
+	}
+	card->private_free = snd_adlib_free;
+
+	strcpy(card->driver, DEV_NAME);
+	strcpy(card->shortname, CRD_NAME);
+	sprintf(card->longname, CRD_NAME " at %#lx", port[n]);
+
+	error = snd_opl3_create(card, port[n], port[n] + 2, OPL3_HW_AUTO, 1, &opl3);
+	if (error < 0) {
+		dev_err(dev, "could not create OPL\n");
+		goto out;
+	}
+
+	error = snd_opl3_hwdep_new(opl3, 0, 0, NULL);
+	if (error < 0) {
+		dev_err(dev, "could not create FM\n");
+		goto out;
+	}
+
+	error = snd_card_register(card);
+	if (error < 0) {
+		dev_err(dev, "could not register card\n");
+		goto out;
+	}
+
+	dev_set_drvdata(dev, card);
+	return 0;
+
+out:	snd_card_free(card);
+	return error;
+}
+
+static int snd_adlib_remove(struct device *dev, unsigned int n)
+{
+	snd_card_free(dev_get_drvdata(dev));
+	return 0;
+}
+
+static struct isa_driver snd_adlib_driver = {
+	.match		= snd_adlib_match,
+	.probe		= snd_adlib_probe,
+	.remove		= snd_adlib_remove,
+
+	.driver		= {
+		.name	= DEV_NAME
+	}
+};
+
+module_isa_driver(snd_adlib_driver, SNDRV_CARDS);
diff --git a/sound/isa/als100.c b/sound/isa/als100.c
new file mode 100644
index 0000000..f63142e
--- /dev/null
+++ b/sound/isa/als100.c
@@ -0,0 +1,379 @@
+
+/*
+    card-als100.c - driver for Avance Logic ALS100 based soundcards.
+    Copyright (C) 1999-2000 by Massimo Piccioni <dafastidio@libero.it>
+    Copyright (C) 1999-2002 by Massimo Piccioni <dafastidio@libero.it>
+
+    Thanks to Pierfrancesco 'qM2' Passerini.
+
+    Generalised for soundcards based on DT-0196 and ALS-007 chips
+    by Jonathan Woithe <jwoithe@just42.net>: 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 <linux/init.h>
+#include <linux/wait.h>
+#include <linux/time.h>
+#include <linux/pnp.h>
+#include <linux/module.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_DESCRIPTION("Avance Logic ALS007/ALS1X0");
+MODULE_SUPPORTED_DEVICE("{{Diamond Technologies DT-019X},"
+		"{Avance Logic ALS-007}}"
+		"{{Avance Logic,ALS100 - PRO16PNP},"
+	        "{Avance Logic,ALS110},"
+	        "{Avance Logic,ALS120},"
+	        "{Avance Logic,ALS200},"
+	        "{3D Melody,MF1000},"
+	        "{Digimate,3D Sound},"
+	        "{Avance Logic,ALS120},"
+	        "{RTL,RTL3000}}");
+
+MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static bool 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 */
+static int dma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* PnP setup */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Avance Logic based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Avance Logic based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Avance Logic based soundcard.");
+
+MODULE_ALIAS("snd-dt019x");
+
+struct snd_card_als100 {
+	struct pnp_dev *dev;
+	struct pnp_dev *devmpu;
+	struct pnp_dev *devopl;
+	struct snd_sb *chip;
+};
+
+static const struct pnp_card_device_id snd_als100_pnpids[] = {
+	/* DT197A30 */
+	{ .id = "RWB1688",
+	  .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" } },
+	  .driver_data = SB_HW_DT019X },
+	/* DT0196 / ALS-007 */
+	{ .id = "ALS0007",
+	  .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" } },
+	  .driver_data = SB_HW_DT019X },
+	/* ALS100 - PRO16PNP */
+	{ .id = "ALS0001",
+	  .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" } },
+	  .driver_data = SB_HW_ALS100 },
+	/* ALS110 - MF1000 - Digimate 3D Sound */
+	{ .id = "ALS0110",
+	  .devs = { { "@@@1001" }, { "@X@1001" }, { "@H@1001" } },
+	  .driver_data = SB_HW_ALS100 },
+	/* ALS120 */
+	{ .id = "ALS0120",
+	  .devs = { { "@@@2001" }, { "@X@2001" }, { "@H@2001" } },
+	  .driver_data = SB_HW_ALS100 },
+	/* ALS200 */
+	{ .id = "ALS0200",
+	  .devs = { { "@@@0020" }, { "@X@0020" }, { "@H@0001" } },
+	  .driver_data = SB_HW_ALS100 },
+	/* ALS200 OEM */
+	{ .id = "ALS0200",
+	  .devs = { { "@@@0020" }, { "@X@0020" }, { "@H@0020" } },
+	  .driver_data = SB_HW_ALS100 },
+	/* RTL3000 */
+	{ .id = "RTL3000",
+	  .devs = { { "@@@2001" }, { "@X@2001" }, { "@H@2001" } },
+	  .driver_data = SB_HW_ALS100 },
+	{ .id = "" } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_als100_pnpids);
+
+static int 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;
+	int err;
+
+	acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->dev == NULL)
+		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;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n");
+		return err;
+	}
+	port[dev] = pnp_port_start(pdev, 0);
+	if (id->driver_data == SB_HW_DT019X)
+		dma8[dev] = pnp_dma(pdev, 0);
+	else {
+		dma8[dev] = pnp_dma(pdev, 1);
+		dma16[dev] = pnp_dma(pdev, 0);
+	}
+	irq[dev] = pnp_irq(pdev, 0);
+
+	pdev = acard->devmpu;
+	if (pdev != NULL) {
+		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) {
+		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;
+	}
+
+	return 0;
+}
+
+static int snd_card_als100_probe(int dev,
+				 struct pnp_card_link *pcard,
+				 const struct pnp_card_device_id *pid)
+{
+	int error;
+	struct snd_sb *chip;
+	struct snd_card *card;
+	struct snd_card_als100 *acard;
+	struct snd_opl3 *opl3;
+
+	error = snd_card_new(&pcard->card->dev,
+			     index[dev], id[dev], THIS_MODULE,
+			     sizeof(struct snd_card_als100), &card);
+	if (error < 0)
+		return error;
+	acard = card->private_data;
+
+	if ((error = snd_card_als100_pnp(dev, acard, pcard, pid))) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if (pid->driver_data == SB_HW_DT019X)
+		dma16[dev] = -1;
+
+	error = snd_sbdsp_create(card, port[dev], irq[dev],
+				  snd_sb16dsp_interrupt,
+				  dma8[dev], dma16[dev],
+				  pid->driver_data,
+				  &chip);
+	if (error < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	acard->chip = chip;
+
+	if (pid->driver_data == SB_HW_DT019X) {
+		strcpy(card->driver, "DT-019X");
+		strcpy(card->shortname, "Diamond Tech. DT-019X");
+		snprintf(card->longname, sizeof(card->longname),
+			 "Diamond Tech. DT-019X, %s at 0x%lx, irq %d, dma %d",
+			 chip->name, chip->port, irq[dev], dma8[dev]);
+	} else {
+		strcpy(card->driver, "ALS100");
+		strcpy(card->shortname, "Avance Logic ALS100");
+		snprintf(card->longname, sizeof(card->longname),
+			 "Avance Logic ALS100, %s at 0x%lx, irq %d, dma %d&%d",
+			 chip->name, chip->port, irq[dev], dma8[dev],
+			 dma16[dev]);
+	}
+
+	if ((error = snd_sb16dsp_pcm(chip, 0)) < 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) {
+		int mpu_type = MPU401_HW_ALS100;
+
+		if (mpu_irq[dev] == SNDRV_AUTO_IRQ)
+			mpu_irq[dev] = -1;
+
+		if (pid->driver_data == SB_HW_DT019X)
+			mpu_type = MPU401_HW_MPU401;
+
+		if (snd_mpu401_uart_new(card, 0,
+					mpu_type,
+					mpu_port[dev], 0, 
+					mpu_irq[dev],
+					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 unsigned int als100_devices;
+
+static int 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++;
+		als100_devices++;
+		return 0;
+	}
+	return -ENODEV;
+}
+
+static void snd_als100_pnp_remove(struct pnp_card_link *pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_als100_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+	struct snd_card *card = pnp_get_card_drvdata(pcard);
+	struct snd_card_als100 *acard = card->private_data;
+	struct snd_sb *chip = acard->chip;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	snd_sbmixer_suspend(chip);
+	return 0;
+}
+
+static int snd_als100_pnp_resume(struct pnp_card_link *pcard)
+{
+	struct snd_card *card = pnp_get_card_drvdata(pcard);
+	struct snd_card_als100 *acard = card->private_data;
+	struct snd_sb *chip = acard->chip;
+
+	snd_sbdsp_reset(chip);
+	snd_sbmixer_resume(chip);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+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		= snd_als100_pnp_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_als100_pnp_suspend,
+	.resume		= snd_als100_pnp_resume,
+#endif
+};
+
+static int __init alsa_card_als100_init(void)
+{
+	int err;
+
+	err = pnp_register_card_driver(&als100_pnpc_driver);
+	if (err)
+		return err;
+
+	if (!als100_devices) {
+		pnp_unregister_card_driver(&als100_pnpc_driver);
+#ifdef MODULE
+		snd_printk(KERN_ERR "no Avance Logic based soundcards found\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+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..4e6fad4
--- /dev/null
+++ b/sound/isa/azt2320.c
@@ -0,0 +1,354 @@
+/*
+    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 <linux/io.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/pnp.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/wss.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 bool 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.");
+
+struct snd_card_azt2320 {
+	int dev_no;
+	struct pnp_dev *dev;
+	struct pnp_dev *devmpu;
+	struct snd_wss *chip;
+};
+
+static const 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 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;
+	int err;
+
+	acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->dev == NULL)
+		return -ENODEV;
+
+	acard->devmpu = pnp_request_card_device(card, id->devs[1].id, NULL);
+
+	pdev = acard->dev;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n");
+		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) {
+		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;
+	}
+
+	return 0;
+}
+
+/* same of snd_sbdsp_command by Jaroslav Kysela */
+static int 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 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 snd_card_azt2320_probe(int dev,
+				  struct pnp_card_link *pcard,
+				  const struct pnp_card_device_id *pid)
+{
+	int error;
+	struct snd_card *card;
+	struct snd_card_azt2320 *acard;
+	struct snd_wss *chip;
+	struct snd_opl3 *opl3;
+
+	error = snd_card_new(&pcard->card->dev,
+			     index[dev], id[dev], THIS_MODULE,
+			     sizeof(struct snd_card_azt2320), &card);
+	if (error < 0)
+		return error;
+	acard = card->private_data;
+
+	if ((error = snd_card_azt2320_pnp(dev, acard, pcard, pid))) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if ((error = snd_card_azt2320_enable_wss(port[dev]))) {
+		snd_card_free(card);
+		return error;
+	}
+
+	error = snd_wss_create(card, wss_port[dev], -1,
+			       irq[dev],
+			       dma1[dev], dma2[dev],
+			       WSS_HW_DETECT, 0, &chip);
+	if (error < 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]);
+
+	error = snd_wss_pcm(chip, 0);
+	if (error < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	error = snd_wss_mixer(chip);
+	if (error < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	error = snd_wss_timer(chip, 0);
+	if (error < 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], 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 unsigned int azt2320_devices;
+
+static int 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++;
+		azt2320_devices++;
+		return 0;
+	}
+        return -ENODEV;
+}
+
+static void snd_azt2320_pnp_remove(struct pnp_card_link *pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_azt2320_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+	struct snd_card *card = pnp_get_card_drvdata(pcard);
+	struct snd_card_azt2320 *acard = card->private_data;
+	struct snd_wss *chip = acard->chip;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	chip->suspend(chip);
+	return 0;
+}
+
+static int snd_azt2320_pnp_resume(struct pnp_card_link *pcard)
+{
+	struct snd_card *card = pnp_get_card_drvdata(pcard);
+	struct snd_card_azt2320 *acard = card->private_data;
+	struct snd_wss *chip = acard->chip;
+
+	chip->resume(chip);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+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         = snd_azt2320_pnp_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_azt2320_pnp_suspend,
+	.resume		= snd_azt2320_pnp_resume,
+#endif
+};
+
+static int __init alsa_card_azt2320_init(void)
+{
+	int err;
+
+	err = pnp_register_card_driver(&azt2320_pnpc_driver);
+	if (err)
+		return err;
+
+	if (!azt2320_devices) {
+		pnp_unregister_card_driver(&azt2320_pnpc_driver);
+#ifdef MODULE
+		snd_printk(KERN_ERR "no AZT2320 based soundcards found\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+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/cmi8328.c b/sound/isa/cmi8328.c
new file mode 100644
index 0000000..de6ef1b
--- /dev/null
+++ b/sound/isa/cmi8328.c
@@ -0,0 +1,472 @@
+/*
+ * Driver for C-Media CMI8328-based soundcards, such as AudioExcel AV500
+ * Copyright (c) 2012 Ondrej Zary
+ *
+ * AudioExcel AV500 card consists of:
+ *  - CMI8328 - main chip (SB Pro emulation, gameport, OPL3, MPU401, CD-ROM)
+ *  - CS4231A - WSS codec
+ *  - Dream SAM9233+GMS950400+RAM+ROM: Wavetable MIDI, connected to MPU401
+ */
+
+#include <linux/init.h>
+#include <linux/isa.h>
+#include <linux/module.h>
+#include <linux/gameport.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/opl3.h>
+#include <sound/mpu401.h>
+#define SNDRV_LEGACY_FIND_FREE_IOPORT
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Ondrej Zary <linux@rainbow-software.org>");
+MODULE_DESCRIPTION("C-Media CMI8328");
+MODULE_LICENSE("GPL");
+
+#if IS_ENABLED(CONFIG_GAMEPORT)
+#define SUPPORT_JOYSTICK 1
+#endif
+
+/* I/O port is configured by jumpers on the card to one of these */
+static int cmi8328_ports[] = { 0x530, 0xe80, 0xf40, 0x604 };
+#define CMI8328_MAX	ARRAY_SIZE(cmi8328_ports)
+
+static int index[CMI8328_MAX] =     {[0 ... (CMI8328_MAX-1)] = -1};
+static char *id[CMI8328_MAX] =      {[0 ... (CMI8328_MAX-1)] = NULL};
+static long port[CMI8328_MAX] =     {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_PORT};
+static int irq[CMI8328_MAX] =       {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_IRQ};
+static int dma1[CMI8328_MAX] =      {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_DMA};
+static int dma2[CMI8328_MAX] =      {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_DMA};
+static long mpuport[CMI8328_MAX] =  {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_PORT};
+static int mpuirq[CMI8328_MAX] =    {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_IRQ};
+#ifdef SUPPORT_JOYSTICK
+static bool gameport[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = true};
+#endif
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for CMI8328 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for CMI8328 soundcard.");
+
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for CMI8328 driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for CMI8328 driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 for CMI8328 driver.");
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 for CMI8328 driver.");
+
+module_param_hw_array(mpuport, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpuport, "MPU-401 port # for CMI8328 driver.");
+module_param_hw_array(mpuirq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpuirq, "IRQ # for CMI8328 MPU-401 port.");
+#ifdef SUPPORT_JOYSTICK
+module_param_array(gameport, bool, NULL, 0444);
+MODULE_PARM_DESC(gameport, "Enable gameport.");
+#endif
+
+struct snd_cmi8328 {
+	u16 port;
+	u8 cfg[3];
+	u8 wss_cfg;
+	struct snd_card *card;
+	struct snd_wss *wss;
+#ifdef SUPPORT_JOYSTICK
+	struct gameport *gameport;
+#endif
+};
+
+/* CMI8328 configuration registers */
+#define CFG1 0x61
+#define CFG1_SB_DISABLE	(1 << 0)
+#define CFG1_GAMEPORT	(1 << 1)
+/*
+ * bit 0:    SB: 0=enabled, 1=disabled
+ * bit 1:    gameport: 0=disabled, 1=enabled
+ * bits 2-4: SB IRQ: 001=3, 010=5, 011=7, 100=9, 101=10, 110=11
+ * bits 5-6: SB DMA: 00=disabled (when SB disabled), 01=DMA0, 10=DMA1, 11=DMA3
+ * bit 7:    SB port: 0=0x220, 1=0x240
+ */
+#define CFG2 0x62
+#define CFG2_MPU_ENABLE (1 << 2)
+/*
+ * bits 0-1: CD-ROM mode: 00=disabled, 01=Panasonic, 10=Sony/Mitsumi/Wearnes,
+			  11=IDE
+ * bit 2:    MPU401: 0=disabled, 1=enabled
+ * bits 3-4: MPU401 IRQ: 00=3, 01=5, 10=7, 11=9,
+ * bits 5-7: MPU401 port: 000=0x300, 001=0x310, 010=0x320, 011=0x330, 100=0x332,
+			  101=0x334, 110=0x336
+ */
+#define CFG3 0x63
+/*
+ * bits 0-2: CD-ROM IRQ: 000=disabled, 001=3, 010=5, 011=7, 100=9, 101=10,
+			 110=11
+ * bits 3-4: CD-ROM DMA: 00=disabled, 01=DMA0, 10=DMA1, 11=DMA3
+ * bits 5-7: CD-ROM port: 000=0x300, 001=0x310, 010=0x320, 011=0x330, 100=0x340,
+			  101=0x350, 110=0x360, 111=0x370
+ */
+
+static u8 snd_cmi8328_cfg_read(u16 port, u8 reg)
+{
+	outb(0x43, port + 3);
+	outb(0x21, port + 3);
+	outb(reg, port + 3);
+	return inb(port);
+}
+
+static void snd_cmi8328_cfg_write(u16 port, u8 reg, u8 val)
+{
+	outb(0x43, port + 3);
+	outb(0x21, port + 3);
+	outb(reg, port + 3);
+	outb(val, port + 3);	/* yes, value goes to the same port as index */
+}
+
+#ifdef CONFIG_PM
+static void snd_cmi8328_cfg_save(u16 port, u8 cfg[])
+{
+	cfg[0] = snd_cmi8328_cfg_read(port, CFG1);
+	cfg[1] = snd_cmi8328_cfg_read(port, CFG2);
+	cfg[2] = snd_cmi8328_cfg_read(port, CFG3);
+}
+
+static void snd_cmi8328_cfg_restore(u16 port, u8 cfg[])
+{
+	snd_cmi8328_cfg_write(port, CFG1, cfg[0]);
+	snd_cmi8328_cfg_write(port, CFG2, cfg[1]);
+	snd_cmi8328_cfg_write(port, CFG3, cfg[2]);
+}
+#endif /* CONFIG_PM */
+
+static int snd_cmi8328_mixer(struct snd_wss *chip)
+{
+	struct snd_card *card;
+	struct snd_ctl_elem_id id1, id2;
+	int err;
+
+	card = chip->card;
+
+	memset(&id1, 0, sizeof(id1));
+	memset(&id2, 0, sizeof(id2));
+	id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	/* rename AUX0 switch to CD */
+	strcpy(id1.name, "Aux Playback Switch");
+	strcpy(id2.name, "CD Playback Switch");
+	err = snd_ctl_rename_id(card, &id1, &id2);
+	if (err < 0) {
+		snd_printk(KERN_ERR "error renaming control\n");
+		return err;
+	}
+	/* rename AUX0 volume to CD */
+	strcpy(id1.name, "Aux Playback Volume");
+	strcpy(id2.name, "CD Playback Volume");
+	err = snd_ctl_rename_id(card, &id1, &id2);
+	if (err < 0) {
+		snd_printk(KERN_ERR "error renaming control\n");
+		return err;
+	}
+	/* rename AUX1 switch to Synth */
+	strcpy(id1.name, "Aux Playback Switch");
+	id1.index = 1;
+	strcpy(id2.name, "Synth Playback Switch");
+	err = snd_ctl_rename_id(card, &id1, &id2);
+	if (err < 0) {
+		snd_printk(KERN_ERR "error renaming control\n");
+		return err;
+	}
+	/* rename AUX1 volume to Synth */
+	strcpy(id1.name, "Aux Playback Volume");
+	id1.index = 1;
+	strcpy(id2.name, "Synth Playback Volume");
+	err = snd_ctl_rename_id(card, &id1, &id2);
+	if (err < 0) {
+		snd_printk(KERN_ERR "error renaming control\n");
+		return err;
+	}
+
+	return 0;
+}
+
+/* find index of an item in "-1"-ended array */
+static int array_find(int array[], int item)
+{
+	int i;
+
+	for (i = 0; array[i] != -1; i++)
+		if (array[i] == item)
+			return i;
+
+	return -1;
+}
+/* the same for long */
+static int array_find_l(long array[], long item)
+{
+	int i;
+
+	for (i = 0; array[i] != -1; i++)
+		if (array[i] == item)
+			return i;
+
+	return -1;
+}
+
+static int snd_cmi8328_probe(struct device *pdev, unsigned int ndev)
+{
+	struct snd_card *card;
+	struct snd_opl3 *opl3;
+	struct snd_cmi8328 *cmi;
+#ifdef SUPPORT_JOYSTICK
+	struct resource *res;
+#endif
+	int err, pos;
+	static long mpu_ports[] = { 0x330, 0x300, 0x310, 0x320, 0x332, 0x334,
+				   0x336, -1 };
+	static u8 mpu_port_bits[] = { 3, 0, 1, 2, 4, 5, 6 };
+	static int mpu_irqs[] = { 9, 7, 5, 3, -1 };
+	static u8 mpu_irq_bits[] = { 3, 2, 1, 0 };
+	static int irqs[] = { 9, 10, 11, 7, -1 };
+	static u8 irq_bits[] = { 2, 3, 4, 1 };
+	static int dma1s[] = { 3, 1, 0, -1 };
+	static u8 dma_bits[] = { 3, 2, 1 };
+	static int dma2s[][2] = { {1, -1}, {0, -1}, {-1, -1}, {0, -1} };
+	u16 port = cmi8328_ports[ndev];
+	u8 val;
+
+	/* 0xff is invalid configuration (but settable - hope it isn't set) */
+	if (snd_cmi8328_cfg_read(port, CFG1) == 0xff)
+		return -ENODEV;
+	/* the SB disable bit must NEVER EVER be cleared or the WSS dies */
+	snd_cmi8328_cfg_write(port, CFG1, CFG1_SB_DISABLE);
+	if (snd_cmi8328_cfg_read(port, CFG1) != CFG1_SB_DISABLE)
+		return -ENODEV;
+	/* disable everything first */
+	snd_cmi8328_cfg_write(port, CFG2, 0);	/* disable CDROM and MPU401 */
+	snd_cmi8328_cfg_write(port, CFG3, 0);	/* disable CDROM IRQ and DMA */
+
+	if (irq[ndev] == SNDRV_AUTO_IRQ) {
+		irq[ndev] = snd_legacy_find_free_irq(irqs);
+		if (irq[ndev] < 0) {
+			snd_printk(KERN_ERR "unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (dma1[ndev] == SNDRV_AUTO_DMA) {
+		dma1[ndev] = snd_legacy_find_free_dma(dma1s);
+		if (dma1[ndev] < 0) {
+			snd_printk(KERN_ERR "unable to find a free DMA1\n");
+			return -EBUSY;
+		}
+	}
+	if (dma2[ndev] == SNDRV_AUTO_DMA) {
+		dma2[ndev] = snd_legacy_find_free_dma(dma2s[dma1[ndev] % 4]);
+		if (dma2[ndev] < 0) {
+			snd_printk(KERN_WARNING "unable to find a free DMA2, full-duplex will not work\n");
+			dma2[ndev] = -1;
+		}
+	}
+	/* configure WSS IRQ... */
+	pos = array_find(irqs, irq[ndev]);
+	if (pos < 0) {
+		snd_printk(KERN_ERR "invalid IRQ %d\n", irq[ndev]);
+		return -EINVAL;
+	}
+	val = irq_bits[pos] << 3;
+	/* ...and DMA... */
+	pos = array_find(dma1s, dma1[ndev]);
+	if (pos < 0) {
+		snd_printk(KERN_ERR "invalid DMA1 %d\n", dma1[ndev]);
+		return -EINVAL;
+	}
+	val |= dma_bits[pos];
+	/* ...and DMA2 */
+	if (dma2[ndev] >= 0 && dma1[ndev] != dma2[ndev]) {
+		pos = array_find(dma2s[dma1[ndev]], dma2[ndev]);
+		if (pos < 0) {
+			snd_printk(KERN_ERR "invalid DMA2 %d\n", dma2[ndev]);
+			return -EINVAL;
+		}
+		val |= 0x04; /* enable separate capture DMA */
+	}
+	outb(val, port);
+
+	err = snd_card_new(pdev, index[ndev], id[ndev], THIS_MODULE,
+			   sizeof(struct snd_cmi8328), &card);
+	if (err < 0)
+		return err;
+	cmi = card->private_data;
+	cmi->card = card;
+	cmi->port = port;
+	cmi->wss_cfg = val;
+
+	err = snd_wss_create(card, port + 4, -1, irq[ndev], dma1[ndev],
+			dma2[ndev], WSS_HW_DETECT, 0, &cmi->wss);
+	if (err < 0)
+		goto error;
+
+	err = snd_wss_pcm(cmi->wss, 0);
+	if (err < 0)
+		goto error;
+
+	err = snd_wss_mixer(cmi->wss);
+	if (err < 0)
+		goto error;
+	err = snd_cmi8328_mixer(cmi->wss);
+	if (err < 0)
+		goto error;
+
+	if (snd_wss_timer(cmi->wss, 0) < 0)
+		snd_printk(KERN_WARNING "error initializing WSS timer\n");
+
+	if (mpuport[ndev] == SNDRV_AUTO_PORT) {
+		mpuport[ndev] = snd_legacy_find_free_ioport(mpu_ports, 2);
+		if (mpuport[ndev] < 0)
+			snd_printk(KERN_ERR "unable to find a free MPU401 port\n");
+	}
+	if (mpuirq[ndev] == SNDRV_AUTO_IRQ) {
+		mpuirq[ndev] = snd_legacy_find_free_irq(mpu_irqs);
+		if (mpuirq[ndev] < 0)
+			snd_printk(KERN_ERR "unable to find a free MPU401 IRQ\n");
+	}
+	/* enable and configure MPU401 */
+	if (mpuport[ndev] > 0 && mpuirq[ndev] > 0) {
+		val = CFG2_MPU_ENABLE;
+		pos = array_find_l(mpu_ports, mpuport[ndev]);
+		if (pos < 0)
+			snd_printk(KERN_WARNING "invalid MPU401 port 0x%lx\n",
+								mpuport[ndev]);
+		else {
+			val |= mpu_port_bits[pos] << 5;
+			pos = array_find(mpu_irqs, mpuirq[ndev]);
+			if (pos < 0)
+				snd_printk(KERN_WARNING "invalid MPU401 IRQ %d\n",
+								mpuirq[ndev]);
+			else {
+				val |= mpu_irq_bits[pos] << 3;
+				snd_cmi8328_cfg_write(port, CFG2, val);
+				if (snd_mpu401_uart_new(card, 0,
+						MPU401_HW_MPU401, mpuport[ndev],
+						0, mpuirq[ndev], NULL) < 0)
+					snd_printk(KERN_ERR "error initializing MPU401\n");
+			}
+		}
+	}
+	/* OPL3 is hardwired to 0x388 and cannot be disabled */
+	if (snd_opl3_create(card, 0x388, 0x38a, OPL3_HW_AUTO, 0, &opl3) < 0)
+		snd_printk(KERN_ERR "error initializing OPL3\n");
+	else
+		if (snd_opl3_hwdep_new(opl3, 0, 1, NULL) < 0)
+			snd_printk(KERN_WARNING "error initializing OPL3 hwdep\n");
+
+	strcpy(card->driver, "CMI8328");
+	strcpy(card->shortname, "C-Media CMI8328");
+	sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d,%d",
+		card->shortname, cmi->wss->port, irq[ndev], dma1[ndev],
+		(dma2[ndev] >= 0) ? dma2[ndev] : dma1[ndev]);
+
+	dev_set_drvdata(pdev, card);
+	err = snd_card_register(card);
+	if (err < 0)
+		goto error;
+#ifdef SUPPORT_JOYSTICK
+	if (!gameport[ndev])
+		return 0;
+	/* gameport is hardwired to 0x200 */
+	res = request_region(0x200, 8, "CMI8328 gameport");
+	if (!res)
+		snd_printk(KERN_WARNING "unable to allocate gameport I/O port\n");
+	else {
+		struct gameport *gp = cmi->gameport = gameport_allocate_port();
+		if (!cmi->gameport)
+			release_and_free_resource(res);
+		else {
+			gameport_set_name(gp, "CMI8328 Gameport");
+			gameport_set_phys(gp, "%s/gameport0", dev_name(pdev));
+			gameport_set_dev_parent(gp, pdev);
+			gp->io = 0x200;
+			gameport_set_port_data(gp, res);
+			/* Enable gameport */
+			snd_cmi8328_cfg_write(port, CFG1,
+					CFG1_SB_DISABLE | CFG1_GAMEPORT);
+			gameport_register_port(gp);
+		}
+	}
+#endif
+	return 0;
+error:
+	snd_card_free(card);
+
+	return err;
+}
+
+static int snd_cmi8328_remove(struct device *pdev, unsigned int dev)
+{
+	struct snd_card *card = dev_get_drvdata(pdev);
+	struct snd_cmi8328 *cmi = card->private_data;
+
+#ifdef SUPPORT_JOYSTICK
+	if (cmi->gameport) {
+		struct resource *res = gameport_get_port_data(cmi->gameport);
+		gameport_unregister_port(cmi->gameport);
+		release_and_free_resource(res);
+	}
+#endif
+	/* disable everything */
+	snd_cmi8328_cfg_write(cmi->port, CFG1, CFG1_SB_DISABLE);
+	snd_cmi8328_cfg_write(cmi->port, CFG2, 0);
+	snd_cmi8328_cfg_write(cmi->port, CFG3, 0);
+	snd_card_free(card);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_cmi8328_suspend(struct device *pdev, unsigned int n,
+				pm_message_t state)
+{
+	struct snd_card *card = dev_get_drvdata(pdev);
+	struct snd_cmi8328 *cmi;
+
+	if (!card)	/* ignore absent devices */
+		return 0;
+	cmi = card->private_data;
+	snd_cmi8328_cfg_save(cmi->port, cmi->cfg);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(cmi->wss->pcm);
+	cmi->wss->suspend(cmi->wss);
+
+	return 0;
+}
+
+static int snd_cmi8328_resume(struct device *pdev, unsigned int n)
+{
+	struct snd_card *card = dev_get_drvdata(pdev);
+	struct snd_cmi8328 *cmi;
+
+	if (!card)	/* ignore absent devices */
+		return 0;
+	cmi = card->private_data;
+	snd_cmi8328_cfg_restore(cmi->port, cmi->cfg);
+	outb(cmi->wss_cfg, cmi->port);
+	cmi->wss->resume(cmi->wss);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+
+	return 0;
+}
+#endif
+
+static struct isa_driver snd_cmi8328_driver = {
+	.probe		= snd_cmi8328_probe,
+	.remove		= snd_cmi8328_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_cmi8328_suspend,
+	.resume		= snd_cmi8328_resume,
+#endif
+	.driver		= {
+		.name	= "cmi8328"
+	},
+};
+
+module_isa_driver(snd_cmi8328_driver, CMI8328_MAX);
diff --git a/sound/isa/cmi8330.c b/sound/isa/cmi8330.c
new file mode 100644
index 0000000..6b8c469
--- /dev/null
+++ b/sound/isa/cmi8330.c
@@ -0,0 +1,780 @@
+/*
+ *  Driver for C-Media's CMI8330 and CMI8329 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 fmport=0x388
+ *
+ *  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 <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/pnp.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/opl3.h>
+#include <sound/mpu401.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/CMI8329");
+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 bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP;
+#ifdef CONFIG_PNP
+static bool 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;
+static long fmport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static long mpuport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int mpuirq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for CMI8330/CMI8329 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string  for CMI8330/CMI8329 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable CMI8330/CMI8329 soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+
+module_param_hw_array(sbport, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(sbport, "Port # for CMI8330/CMI8329 SB driver.");
+module_param_hw_array(sbirq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(sbirq, "IRQ # for CMI8330/CMI8329 SB driver.");
+module_param_hw_array(sbdma8, int, dma, NULL, 0444);
+MODULE_PARM_DESC(sbdma8, "DMA8 for CMI8330/CMI8329 SB driver.");
+module_param_hw_array(sbdma16, int, dma, NULL, 0444);
+MODULE_PARM_DESC(sbdma16, "DMA16 for CMI8330/CMI8329 SB driver.");
+
+module_param_hw_array(wssport, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(wssport, "Port # for CMI8330/CMI8329 WSS driver.");
+module_param_hw_array(wssirq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(wssirq, "IRQ # for CMI8330/CMI8329 WSS driver.");
+module_param_hw_array(wssdma, int, dma, NULL, 0444);
+MODULE_PARM_DESC(wssdma, "DMA for CMI8330/CMI8329 WSS driver.");
+
+module_param_hw_array(fmport, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(fmport, "FM port # for CMI8330/CMI8329 driver.");
+module_param_hw_array(mpuport, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpuport, "MPU-401 port # for CMI8330/CMI8329 driver.");
+module_param_hw_array(mpuirq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpuirq, "IRQ # for CMI8330/CMI8329 MPU-401 port.");
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+#endif
+
+#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)(struct snd_pcm_substream *);
+
+enum card_type {
+	CMI8330,
+	CMI8329
+};
+
+struct snd_cmi8330 {
+#ifdef CONFIG_PNP
+	struct pnp_dev *cap;
+	struct pnp_dev *play;
+	struct pnp_dev *mpu;
+#endif
+	struct snd_card *card;
+	struct snd_wss *wss;
+	struct snd_sb *sb;
+
+	struct snd_pcm *pcm;
+	struct snd_cmi8330_stream {
+		struct snd_pcm_ops ops;
+		snd_pcm_open_callback_t open;
+		void *private_data; /* sb or wss */
+	} streams[2];
+
+	enum card_type type;
+};
+
+#ifdef CONFIG_PNP
+
+static const struct pnp_card_device_id snd_cmi8330_pnpids[] = {
+	{ .id = "CMI0001", .devs = { { "@X@0001" }, { "@@@0001" }, { "@H@0001" }, { "A@@0001" } } },
+	{ .id = "CMI0001", .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" } } },
+	{ .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_cmi8330_pnpids);
+
+#endif
+
+
+static struct snd_kcontrol_new snd_cmi8330_controls[] = {
+WSS_DOUBLE("Master Playback Volume", 0,
+		CMI8330_MASTVOL, CMI8330_MASTVOL, 4, 0, 15, 0),
+WSS_SINGLE("Loud Playback Switch", 0,
+		CMI8330_MUTEMUX, 6, 1, 1),
+WSS_DOUBLE("PCM Playback Switch", 0,
+		CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
+WSS_DOUBLE("PCM Playback Volume", 0,
+		CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1),
+WSS_DOUBLE("Line Playback Switch", 0,
+		CMI8330_MUTEMUX, CMI8330_MUTEMUX, 4, 3, 1, 0),
+WSS_DOUBLE("Line Playback Volume", 0,
+		CMI8330_LINVOL, CMI8330_LINVOL, 4, 0, 15, 0),
+WSS_DOUBLE("Line Capture Switch", 0,
+		CMI8330_RMUX3D, CMI8330_RMUX3D, 2, 1, 1, 0),
+WSS_DOUBLE("Line Capture Volume", 0,
+		CMI8330_LINGAIN, CMI8330_LINGAIN, 4, 0, 15, 0),
+WSS_DOUBLE("CD Playback Switch", 0,
+		CMI8330_MUTEMUX, CMI8330_MUTEMUX, 2, 1, 1, 0),
+WSS_DOUBLE("CD Capture Switch", 0,
+		CMI8330_RMUX3D, CMI8330_RMUX3D, 4, 3, 1, 0),
+WSS_DOUBLE("CD Playback Volume", 0,
+		CMI8330_CDINVOL, CMI8330_CDINVOL, 4, 0, 15, 0),
+WSS_DOUBLE("CD Capture Volume", 0,
+		CMI8330_CDINGAIN, CMI8330_CDINGAIN, 4, 0, 15, 0),
+WSS_SINGLE("Mic Playback Switch", 0,
+		CMI8330_MUTEMUX, 0, 1, 0),
+WSS_SINGLE("Mic Playback Volume", 0,
+		CMI8330_OUTPUTVOL, 0, 7, 0),
+WSS_SINGLE("Mic Capture Switch", 0,
+		CMI8330_RMUX3D, 0, 1, 0),
+WSS_SINGLE("Mic Capture Volume", 0,
+		CMI8330_OUTPUTVOL, 5, 7, 0),
+WSS_DOUBLE("Wavetable Playback Switch", 0,
+		CMI8330_RECMUX, CMI8330_RECMUX, 1, 0, 1, 0),
+WSS_DOUBLE("Wavetable Playback Volume", 0,
+		CMI8330_WAVVOL, CMI8330_WAVVOL, 4, 0, 15, 0),
+WSS_DOUBLE("Wavetable Capture Switch", 0,
+		CMI8330_RECMUX, CMI8330_RECMUX, 5, 4, 1, 0),
+WSS_DOUBLE("Wavetable Capture Volume", 0,
+		CMI8330_WAVGAIN, CMI8330_WAVGAIN, 4, 0, 15, 0),
+WSS_SINGLE("3D Control - Switch", 0,
+		CMI8330_RMUX3D, 5, 1, 1),
+WSS_SINGLE("Beep Playback Volume", 0,
+		CMI8330_OUTPUTVOL, 3, 3, 0),
+WSS_DOUBLE("FM Playback Switch", 0,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE("FM Playback Volume", 0,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1),
+WSS_SINGLE(SNDRV_CTL_NAME_IEC958("Input ", CAPTURE, SWITCH), 0,
+		CMI8330_RMUX3D, 7, 1, 1),
+WSS_SINGLE(SNDRV_CTL_NAME_IEC958("Input ", PLAYBACK, SWITCH), 0,
+		CMI8330_MUTEMUX, 7, 1, 1),
+};
+
+#ifdef ENABLE_SB_MIXER
+static struct sbmix_elem cmi8330_sb_mixers[] = {
+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 Beep 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] = {
+	{ 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 cmi8330_add_sb_mixers(struct snd_sb *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 snd_cmi8330_mixer(struct snd_card *card, struct snd_cmi8330 *acard)
+{
+	unsigned int idx;
+	int err;
+
+	strcpy(card->mixername, (acard->type == CMI8329) ? "CMI8329" : "CMI8330/C3D");
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_cmi8330_controls); idx++) {
+		err = snd_ctl_add(card,
+				snd_ctl_new1(&snd_cmi8330_controls[idx],
+					     acard->wss));
+		if (err < 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 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;
+	int err;
+
+	/* CMI8329 has a device with ID A@@0001, CMI8330 does not */
+	acard->type = (id->devs[3].id[0]) ? CMI8329 : CMI8330;
+
+	acard->cap = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->cap == NULL)
+		return -EBUSY;
+
+	acard->play = pnp_request_card_device(card, id->devs[1].id, NULL);
+	if (acard->play == NULL)
+		return -EBUSY;
+
+	acard->mpu = pnp_request_card_device(card, id->devs[2].id, NULL);
+	if (acard->mpu == NULL)
+		return -EBUSY;
+
+	pdev = acard->cap;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "AD1848 PnP configure failure\n");
+		return -EBUSY;
+	}
+	wssport[dev] = pnp_port_start(pdev, 0);
+	wssdma[dev] = pnp_dma(pdev, 0);
+	wssirq[dev] = pnp_irq(pdev, 0);
+	if (pnp_port_start(pdev, 1))
+		fmport[dev] = pnp_port_start(pdev, 1);
+
+	/* allocate SB16 resources */
+	pdev = acard->play;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "SB16 PnP configure failure\n");
+		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);
+	/* On CMI8239, the OPL3 port might be present in SB16 PnP resources */
+	if (fmport[dev] == SNDRV_AUTO_PORT) {
+		if (pnp_port_start(pdev, 1))
+			fmport[dev] = pnp_port_start(pdev, 1);
+		else
+			fmport[dev] = 0x388;	/* Or hardwired */
+	}
+
+	/* allocate MPU-401 resources */
+	pdev = acard->mpu;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0)
+		snd_printk(KERN_ERR "MPU-401 PnP configure failure: will be disabled\n");
+	else {
+		mpuport[dev] = pnp_port_start(pdev, 0);
+		mpuirq[dev] = pnp_irq(pdev, 0);
+	}
+	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(struct snd_pcm_substream *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(struct snd_pcm_substream *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 int snd_cmi8330_pcm(struct snd_card *card, struct snd_cmi8330 *chip)
+{
+	struct snd_pcm *pcm;
+	const struct snd_pcm_ops *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, (chip->type == CMI8329) ? "CMI8329" : "CMI8330", 0, 1, 1, &pcm)) < 0)
+		return err;
+	strcpy(pcm->name, (chip->type == CMI8329) ? "CMI8329" : "CMI8330");
+	pcm->private_data = chip;
+	
+	/* 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_wss_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;
+}
+
+
+#ifdef CONFIG_PM
+static int snd_cmi8330_suspend(struct snd_card *card)
+{
+	struct snd_cmi8330 *acard = card->private_data;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(acard->pcm);
+	acard->wss->suspend(acard->wss);
+	snd_sbmixer_suspend(acard->sb);
+	return 0;
+}
+
+static int snd_cmi8330_resume(struct snd_card *card)
+{
+	struct snd_cmi8330 *acard = card->private_data;
+
+	snd_sbdsp_reset(acard->sb);
+	snd_sbmixer_suspend(acard->sb);
+	acard->wss->resume(acard->wss);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+
+/*
+ */
+
+#ifdef CONFIG_PNP
+#define is_isapnp_selected(dev)		isapnp[dev]
+#else
+#define is_isapnp_selected(dev)		0
+#endif
+
+#define PFX	"cmi8330: "
+
+static int snd_cmi8330_card_new(struct device *pdev, int dev,
+				struct snd_card **cardp)
+{
+	struct snd_card *card;
+	struct snd_cmi8330 *acard;
+	int err;
+
+	err = snd_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+			   sizeof(struct snd_cmi8330), &card);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "could not get a new card\n");
+		return err;
+	}
+	acard = card->private_data;
+	acard->card = card;
+	*cardp = card;
+	return 0;
+}
+
+static int snd_cmi8330_probe(struct snd_card *card, int dev)
+{
+	struct snd_cmi8330 *acard;
+	int i, err;
+	struct snd_opl3 *opl3;
+
+	acard = card->private_data;
+	err = snd_wss_create(card, wssport[dev] + 4, -1,
+			     wssirq[dev],
+			     wssdma[dev], -1,
+			     WSS_HW_DETECT, 0, &acard->wss);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "AD1848 device busy??\n");
+		return err;
+	}
+	if (acard->wss->hardware != WSS_HW_CMI8330) {
+		snd_printk(KERN_ERR PFX "AD1848 not found during probe\n");
+		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(KERN_ERR PFX "SB16 device busy??\n");
+		return err;
+	}
+	if (acard->sb->hardware != SB_HW_16) {
+		snd_printk(KERN_ERR PFX "SB16 not found during probe\n");
+		return err;
+	}
+
+	snd_wss_out(acard->wss, CS4231_MISC_INFO, 0x40); /* switch on MODE2 */
+	for (i = CMI8330_RMUX3D; i <= CMI8330_CDINGAIN; i++)
+		snd_wss_out(acard->wss, i,
+			    snd_cmi8330_image[i - CMI8330_RMUX3D]);
+
+	if ((err = snd_cmi8330_mixer(card, acard)) < 0) {
+		snd_printk(KERN_ERR PFX "failed to create mixers\n");
+		return err;
+	}
+
+	if ((err = snd_cmi8330_pcm(card, acard)) < 0) {
+		snd_printk(KERN_ERR PFX "failed to create pcms\n");
+		return err;
+	}
+	if (fmport[dev] != SNDRV_AUTO_PORT) {
+		if (snd_opl3_create(card,
+				    fmport[dev], fmport[dev] + 2,
+				    OPL3_HW_AUTO, 0, &opl3) < 0) {
+			snd_printk(KERN_ERR PFX
+				   "no OPL device at 0x%lx-0x%lx ?\n",
+				   fmport[dev], fmport[dev] + 2);
+		} else {
+			err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+			if (err < 0)
+				return err;
+		}
+	}
+
+	if (mpuport[dev] != SNDRV_AUTO_PORT) {
+		if (snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+					mpuport[dev], 0, mpuirq[dev],
+					NULL) < 0)
+			printk(KERN_ERR PFX "no MPU-401 device at 0x%lx.\n",
+				mpuport[dev]);
+	}
+
+	strcpy(card->driver, (acard->type == CMI8329) ? "CMI8329" : "CMI8330/C3D");
+	strcpy(card->shortname, (acard->type == CMI8329) ? "C-Media CMI8329" : "C-Media CMI8330/C3D");
+	sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+		card->shortname,
+		acard->wss->port,
+		wssirq[dev],
+		wssdma[dev]);
+
+	return snd_card_register(card);
+}
+
+static int snd_cmi8330_isa_match(struct device *pdev,
+				 unsigned int dev)
+{
+	if (!enable[dev] || is_isapnp_selected(dev))
+		return 0;
+	if (wssport[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR PFX "specify wssport\n");
+		return 0;
+	}
+	if (sbport[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR PFX "specify sbport\n");
+		return 0;
+	}
+	return 1;
+}
+
+static int snd_cmi8330_isa_probe(struct device *pdev,
+				 unsigned int dev)
+{
+	struct snd_card *card;
+	int err;
+
+	err = snd_cmi8330_card_new(pdev, dev, &card);
+	if (err < 0)
+		return err;
+	if ((err = snd_cmi8330_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	dev_set_drvdata(pdev, card);
+	return 0;
+}
+
+static int snd_cmi8330_isa_remove(struct device *devptr,
+				  unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(devptr));
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_cmi8330_isa_suspend(struct device *dev, unsigned int n,
+				   pm_message_t state)
+{
+	return snd_cmi8330_suspend(dev_get_drvdata(dev));
+}
+
+static int snd_cmi8330_isa_resume(struct device *dev, unsigned int n)
+{
+	return snd_cmi8330_resume(dev_get_drvdata(dev));
+}
+#endif
+
+#define DEV_NAME	"cmi8330"
+
+static struct isa_driver snd_cmi8330_driver = {
+	.match		= snd_cmi8330_isa_match,
+	.probe		= snd_cmi8330_isa_probe,
+	.remove		= snd_cmi8330_isa_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_cmi8330_isa_suspend,
+	.resume		= snd_cmi8330_isa_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+
+#ifdef CONFIG_PNP
+static int snd_cmi8330_pnp_detect(struct pnp_card_link *pcard,
+				  const struct pnp_card_device_id *pid)
+{
+	static int dev;
+	struct snd_card *card;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+			       
+	res = snd_cmi8330_card_new(&pcard->card->dev, dev, &card);
+	if (res < 0)
+		return res;
+	if ((res = snd_cmi8330_pnp(dev, card->private_data, pcard, pid)) < 0) {
+		snd_printk(KERN_ERR PFX "PnP detection failed\n");
+		snd_card_free(card);
+		return res;
+	}
+	if ((res = snd_cmi8330_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return res;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	dev++;
+	return 0;
+}
+
+static void snd_cmi8330_pnp_remove(struct pnp_card_link *pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_cmi8330_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+	return snd_cmi8330_suspend(pnp_get_card_drvdata(pcard));
+}
+
+static int snd_cmi8330_pnp_resume(struct pnp_card_link *pcard)
+{
+	return snd_cmi8330_resume(pnp_get_card_drvdata(pcard));
+}
+#endif
+
+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 = snd_cmi8330_pnp_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_cmi8330_pnp_suspend,
+	.resume		= snd_cmi8330_pnp_resume,
+#endif
+};
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_cmi8330_init(void)
+{
+	int err;
+
+	err = isa_register_driver(&snd_cmi8330_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+	if (!err)
+		isa_registered = 1;
+
+	err = pnp_register_card_driver(&cmi8330_pnpc_driver);
+	if (!err)
+		pnp_registered = 1;
+
+	if (isa_registered)
+		err = 0;
+#endif
+	return err;
+}
+
+static void __exit alsa_card_cmi8330_exit(void)
+{
+#ifdef CONFIG_PNP
+	if (pnp_registered)
+		pnp_unregister_card_driver(&cmi8330_pnpc_driver);
+
+	if (isa_registered)
+#endif
+		isa_unregister_driver(&snd_cmi8330_driver);
+}
+
+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..6d397e8
--- /dev/null
+++ b/sound/isa/cs423x/Makefile
@@ -0,0 +1,13 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-cs4231-objs := cs4231.o
+snd-cs4236-objs := cs4236.o cs4236_lib.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_CS4231) += snd-cs4231.o
+obj-$(CONFIG_SND_CS4236) += snd-cs4236.o
+
+
diff --git a/sound/isa/cs423x/cs4231.c b/sound/isa/cs423x/cs4231.c
new file mode 100644
index 0000000..d90ab95
--- /dev/null
+++ b/sound/isa/cs423x/cs4231.c
@@ -0,0 +1,193 @@
+/*
+ *  Generic driver for CS4231 chips
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/initval.h>
+
+#define CRD_NAME "Generic CS4231"
+#define DEV_NAME "cs4231"
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+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 bool 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 " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+module_param_hw_array(mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for " CRD_NAME " driver.");
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for " CRD_NAME " driver.");
+
+static int snd_cs4231_match(struct device *dev, unsigned int n)
+{
+	if (!enable[n])
+		return 0;
+
+	if (port[n] == SNDRV_AUTO_PORT) {
+		dev_err(dev, "please specify port\n");
+		return 0;
+	}
+	if (irq[n] == SNDRV_AUTO_IRQ) {
+		dev_err(dev, "please specify irq\n");
+		return 0;
+	}
+	if (dma1[n] == SNDRV_AUTO_DMA) {
+		dev_err(dev, "please specify dma1\n");
+		return 0;
+	}
+	return 1;
+}
+
+static int snd_cs4231_probe(struct device *dev, unsigned int n)
+{
+	struct snd_card *card;
+	struct snd_wss *chip;
+	int error;
+
+	error = snd_card_new(dev, index[n], id[n], THIS_MODULE, 0, &card);
+	if (error < 0)
+		return error;
+
+	error = snd_wss_create(card, port[n], -1, irq[n], dma1[n], dma2[n],
+			WSS_HW_DETECT, 0, &chip);
+	if (error < 0)
+		goto out;
+
+	card->private_data = chip;
+
+	error = snd_wss_pcm(chip, 0);
+	if (error < 0)
+		goto out;
+
+	strlcpy(card->driver, "CS4231", sizeof(card->driver));
+	strlcpy(card->shortname, chip->pcm->name, sizeof(card->shortname));
+
+	if (dma2[n] < 0)
+		snprintf(card->longname, sizeof(card->longname),
+			 "%s at 0x%lx, irq %d, dma %d",
+			 chip->pcm->name, chip->port, irq[n], dma1[n]);
+	else
+		snprintf(card->longname, sizeof(card->longname),
+			 "%s at 0x%lx, irq %d, dma %d&%d",
+			 chip->pcm->name, chip->port, irq[n], dma1[n], dma2[n]);
+
+	error = snd_wss_mixer(chip);
+	if (error < 0)
+		goto out;
+
+	error = snd_wss_timer(chip, 0);
+	if (error < 0)
+		goto out;
+
+	if (mpu_port[n] > 0 && mpu_port[n] != SNDRV_AUTO_PORT) {
+		if (mpu_irq[n] == SNDRV_AUTO_IRQ)
+			mpu_irq[n] = -1;
+		if (snd_mpu401_uart_new(card, 0, MPU401_HW_CS4232,
+					mpu_port[n], 0, mpu_irq[n],
+					NULL) < 0)
+			dev_warn(dev, "MPU401 not detected\n");
+	}
+
+	error = snd_card_register(card);
+	if (error < 0)
+		goto out;
+
+	dev_set_drvdata(dev, card);
+	return 0;
+
+out:	snd_card_free(card);
+	return error;
+}
+
+static int snd_cs4231_remove(struct device *dev, unsigned int n)
+{
+	snd_card_free(dev_get_drvdata(dev));
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_cs4231_suspend(struct device *dev, unsigned int n, pm_message_t state)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	struct snd_wss *chip = card->private_data;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	chip->suspend(chip);
+	return 0;
+}
+
+static int snd_cs4231_resume(struct device *dev, unsigned int n)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	struct snd_wss *chip = card->private_data;
+
+	chip->resume(chip);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+static struct isa_driver snd_cs4231_driver = {
+	.match		= snd_cs4231_match,
+	.probe		= snd_cs4231_probe,
+	.remove		= snd_cs4231_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_cs4231_suspend,
+	.resume		= snd_cs4231_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	}
+};
+
+module_isa_driver(snd_cs4231_driver, SNDRV_CARDS);
diff --git a/sound/isa/cs423x/cs4236.c b/sound/isa/cs423x/cs4236.c
new file mode 100644
index 0000000..70559e5
--- /dev/null
+++ b/sound/isa/cs423x/cs4236.c
@@ -0,0 +1,725 @@
+/*
+ *  Driver for generic CS4232/CS4235/CS4236/CS4236B/CS4237B/CS4238B/CS4239 chips
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/pnp.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Cirrus Logic CS4232-9");
+MODULE_SUPPORTED_DEVICE("{{Turtle Beach,TBS-2000},"
+		"{Turtle Beach,Tropez Plus},"
+		"{SIC CrystalWave 32},"
+		"{Hewlett Packard,Omnibook 5500},"
+		"{TerraTec,Maestro 32/96},"
+		"{Philips,PCA70PS}},"
+		"{{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}}");
+
+MODULE_ALIAS("snd_cs4232");
+
+#define IDENT "CS4232+"
+#define DEV_NAME "cs4232+"
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static bool 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_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " IDENT " driver.");
+module_param_hw_array(cport, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(cport, "Control port # for " IDENT " driver.");
+module_param_hw_array(mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " IDENT " driver.");
+module_param_hw_array(fm_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for " IDENT " driver.");
+module_param_hw_array(sb_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(sb_port, "SB port # for " IDENT " driver (optional).");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " IDENT " driver.");
+module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " IDENT " driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for " IDENT " driver.");
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for " IDENT " driver.");
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnpc_registered;
+static int pnp_registered;
+#endif /* CONFIG_PNP */
+
+struct snd_card_cs4236 {
+	struct snd_wss *chip;
+	struct resource *res_sb_port;
+#ifdef CONFIG_PNP
+	struct pnp_dev *wss;
+	struct pnp_dev *ctrl;
+	struct pnp_dev *mpu;
+#endif
+};
+
+#ifdef CONFIG_PNP
+
+/*
+ * PNP BIOS
+ */
+static const struct pnp_device_id snd_cs423x_pnpbiosids[] = {
+	{ .id = "CSC0100" },
+	{ .id = "CSC0000" },
+	/* Guillemot Turtlebeach something appears to be cs4232 compatible
+	 * (untested) */
+	{ .id = "GIM0100" },
+	{ .id = "" }
+};
+MODULE_DEVICE_TABLE(pnp, snd_cs423x_pnpbiosids);
+
+#define CS423X_ISAPNP_DRIVER	"cs4232_isapnp"
+static const 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" } } },
+	/* Netfinity 3000 on-board soundcard */
+	{ .id = "CSCe825", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC010f" } } },
+	/* 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 unknown 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" } } },
+	/* Techmakers MF-4236PW */
+	{ .id = "CSCa736", .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 */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_cs423x_pnpids);
+
+/* WSS initialization */
+static int snd_cs423x_pnp_init_wss(int dev, struct pnp_dev *pdev)
+{
+	if (pnp_activate_dev(pdev) < 0) {
+		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]);
+	return 0;
+}
+
+/* CTRL initialization */
+static int snd_cs423x_pnp_init_ctrl(int dev, struct pnp_dev *pdev)
+{
+	if (pnp_activate_dev(pdev) < 0) {
+		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]);
+	return 0;
+}
+
+/* MPU initialization */
+static int snd_cs423x_pnp_init_mpu(int dev, struct pnp_dev *pdev)
+{
+	if (pnp_activate_dev(pdev) < 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]);
+	return 0;
+}
+
+static int snd_card_cs423x_pnp(int dev, struct snd_card_cs4236 *acard,
+			       struct pnp_dev *pdev,
+			       struct pnp_dev *cdev)
+{
+	acard->wss = pdev;
+	if (snd_cs423x_pnp_init_wss(dev, acard->wss) < 0)
+		return -EBUSY;
+	if (cdev)
+		cport[dev] = pnp_port_start(cdev, 0);
+	else
+		cport[dev] = -1;
+	return 0;
+}
+
+static int snd_card_cs423x_pnpc(int dev, struct snd_card_cs4236 *acard,
+				struct pnp_card_link *card,
+				const struct pnp_card_device_id *id)
+{
+	acard->wss = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->wss == NULL)
+		return -EBUSY;
+	acard->ctrl = pnp_request_card_device(card, id->devs[1].id, NULL);
+	if (acard->ctrl == NULL)
+		return -EBUSY;
+	if (id->devs[2].id[0]) {
+		acard->mpu = pnp_request_card_device(card, id->devs[2].id, NULL);
+		if (acard->mpu == NULL)
+			return -EBUSY;
+	}
+
+	/* WSS initialization */
+	if (snd_cs423x_pnp_init_wss(dev, acard->wss) < 0)
+		return -EBUSY;
+
+	/* CTRL initialization */
+	if (acard->ctrl && cport[dev] > 0) {
+		if (snd_cs423x_pnp_init_ctrl(dev, acard->ctrl) < 0)
+			return -EBUSY;
+	}
+	/* MPU initialization */
+	if (acard->mpu && mpu_port[dev] > 0) {
+		if (snd_cs423x_pnp_init_mpu(dev, acard->mpu) < 0)
+			return -EBUSY;
+	}
+	return 0;
+}
+#endif /* CONFIG_PNP */
+
+#ifdef CONFIG_PNP
+#define is_isapnp_selected(dev)		isapnp[dev]
+#else
+#define is_isapnp_selected(dev)		0
+#endif
+
+static void snd_card_cs4236_free(struct snd_card *card)
+{
+	struct snd_card_cs4236 *acard = card->private_data;
+
+	release_and_free_resource(acard->res_sb_port);
+}
+
+static int snd_cs423x_card_new(struct device *pdev, int dev,
+			       struct snd_card **cardp)
+{
+	struct snd_card *card;
+	int err;
+
+	err = snd_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+			   sizeof(struct snd_card_cs4236), &card);
+	if (err < 0)
+		return err;
+	card->private_free = snd_card_cs4236_free;
+	*cardp = card;
+	return 0;
+}
+
+static int snd_cs423x_probe(struct snd_card *card, int dev)
+{
+	struct snd_card_cs4236 *acard;
+	struct snd_wss *chip;
+	struct snd_opl3 *opl3;
+	int err;
+
+	acard = card->private_data;
+	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]);
+			return -EBUSY;
+		}
+
+	err = snd_cs4236_create(card, port[dev], cport[dev],
+			     irq[dev],
+			     dma1[dev], dma2[dev],
+			     WSS_HW_DETECT3, 0, &chip);
+	if (err < 0)
+		return err;
+
+	acard->chip = chip;
+	if (chip->hardware & WSS_HW_CS4236B_MASK) {
+
+		err = snd_cs4236_pcm(chip, 0);
+		if (err < 0)
+			return err;
+
+		err = snd_cs4236_mixer(chip);
+		if (err < 0)
+			return err;
+	} else {
+		err = snd_wss_pcm(chip, 0);
+		if (err < 0)
+			return err;
+
+		err = snd_wss_mixer(chip);
+		if (err < 0)
+			return err;
+	}
+	strlcpy(card->driver, chip->pcm->name, sizeof(card->driver));
+	strlcpy(card->shortname, chip->pcm->name, sizeof(card->shortname));
+	if (dma2[dev] < 0)
+		snprintf(card->longname, sizeof(card->longname),
+			 "%s at 0x%lx, irq %i, dma %i",
+			 chip->pcm->name, chip->port, irq[dev], dma1[dev]);
+	else
+		snprintf(card->longname, sizeof(card->longname),
+			 "%s at 0x%lx, irq %i, dma %i&%d",
+			 chip->pcm->name, chip->port, irq[dev], dma1[dev],
+			 dma2[dev]);
+
+	err = snd_wss_timer(chip, 0);
+	if (err < 0)
+		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_WARNING IDENT ": OPL3 not detected\n");
+		} else {
+			if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0)
+				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], NULL) < 0)
+			printk(KERN_WARNING IDENT ": MPU401 not detected\n");
+	}
+
+	return snd_card_register(card);
+}
+
+static int snd_cs423x_isa_match(struct device *pdev,
+				unsigned int dev)
+{
+	if (!enable[dev] || is_isapnp_selected(dev))
+		return 0;
+
+	if (port[dev] == SNDRV_AUTO_PORT) {
+		dev_err(pdev, "please specify port\n");
+		return 0;
+	}
+	if (cport[dev] == SNDRV_AUTO_PORT) {
+		dev_err(pdev, "please specify cport\n");
+		return 0;
+	}
+	if (irq[dev] == SNDRV_AUTO_IRQ) {
+		dev_err(pdev, "please specify irq\n");
+		return 0;
+	}
+	if (dma1[dev] == SNDRV_AUTO_DMA) {
+		dev_err(pdev, "please specify dma1\n");
+		return 0;
+	}
+	return 1;
+}
+
+static int snd_cs423x_isa_probe(struct device *pdev,
+				unsigned int dev)
+{
+	struct snd_card *card;
+	int err;
+
+	err = snd_cs423x_card_new(pdev, dev, &card);
+	if (err < 0)
+		return err;
+	if ((err = snd_cs423x_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	dev_set_drvdata(pdev, card);
+	return 0;
+}
+
+static int snd_cs423x_isa_remove(struct device *pdev,
+				 unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(pdev));
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_cs423x_suspend(struct snd_card *card)
+{
+	struct snd_card_cs4236 *acard = card->private_data;
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	acard->chip->suspend(acard->chip);
+	return 0;
+}
+
+static int snd_cs423x_resume(struct snd_card *card)
+{
+	struct snd_card_cs4236 *acard = card->private_data;
+	acard->chip->resume(acard->chip);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+
+static int snd_cs423x_isa_suspend(struct device *dev, unsigned int n,
+				  pm_message_t state)
+{
+	return snd_cs423x_suspend(dev_get_drvdata(dev));
+}
+
+static int snd_cs423x_isa_resume(struct device *dev, unsigned int n)
+{
+	return snd_cs423x_resume(dev_get_drvdata(dev));
+}
+#endif
+
+static struct isa_driver cs423x_isa_driver = {
+	.match		= snd_cs423x_isa_match,
+	.probe		= snd_cs423x_isa_probe,
+	.remove		= snd_cs423x_isa_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_cs423x_isa_suspend,
+	.resume		= snd_cs423x_isa_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+
+#ifdef CONFIG_PNP
+static int snd_cs423x_pnpbios_detect(struct pnp_dev *pdev,
+				     const struct pnp_device_id *id)
+{
+	static int dev;
+	int err;
+	struct snd_card *card;
+	struct pnp_dev *cdev;
+	char cid[PNP_ID_LEN];
+
+	if (pnp_device_is_isapnp(pdev))
+		return -ENOENT;	/* we have another procedure - card */
+	for (; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+
+	/* prepare second id */
+	strcpy(cid, pdev->id[0].id);
+	cid[5] = '1';
+	cdev = NULL;
+	list_for_each_entry(cdev, &(pdev->protocol->devices), protocol_list) {
+		if (!strcmp(cdev->id[0].id, cid))
+			break;
+	}
+	err = snd_cs423x_card_new(&pdev->dev, dev, &card);
+	if (err < 0)
+		return err;
+	err = snd_card_cs423x_pnp(dev, card->private_data, pdev, cdev);
+	if (err < 0) {
+		printk(KERN_ERR "PnP BIOS detection failed for " IDENT "\n");
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_cs423x_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pnp_set_drvdata(pdev, card);
+	dev++;
+	return 0;
+}
+
+static void snd_cs423x_pnp_remove(struct pnp_dev *pdev)
+{
+	snd_card_free(pnp_get_drvdata(pdev));
+}
+
+#ifdef CONFIG_PM
+static int snd_cs423x_pnp_suspend(struct pnp_dev *pdev, pm_message_t state)
+{
+	return snd_cs423x_suspend(pnp_get_drvdata(pdev));
+}
+
+static int snd_cs423x_pnp_resume(struct pnp_dev *pdev)
+{
+	return snd_cs423x_resume(pnp_get_drvdata(pdev));
+}
+#endif
+
+static struct pnp_driver cs423x_pnp_driver = {
+	.name = "cs423x-pnpbios",
+	.id_table = snd_cs423x_pnpbiosids,
+	.probe = snd_cs423x_pnpbios_detect,
+	.remove = snd_cs423x_pnp_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_cs423x_pnp_suspend,
+	.resume		= snd_cs423x_pnp_resume,
+#endif
+};
+
+static int snd_cs423x_pnpc_detect(struct pnp_card_link *pcard,
+				  const struct pnp_card_device_id *pid)
+{
+	static int dev;
+	struct snd_card *card;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+
+	res = snd_cs423x_card_new(&pcard->card->dev, dev, &card);
+	if (res < 0)
+		return res;
+	if ((res = snd_card_cs423x_pnpc(dev, card->private_data, pcard, pid)) < 0) {
+		printk(KERN_ERR "isapnp detection failed and probing for " IDENT
+		       " is not supported\n");
+		snd_card_free(card);
+		return res;
+	}
+	if ((res = snd_cs423x_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return res;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	dev++;
+	return 0;
+}
+
+static void snd_cs423x_pnpc_remove(struct pnp_card_link *pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_cs423x_pnpc_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+	return snd_cs423x_suspend(pnp_get_card_drvdata(pcard));
+}
+
+static int snd_cs423x_pnpc_resume(struct pnp_card_link *pcard)
+{
+	return snd_cs423x_resume(pnp_get_card_drvdata(pcard));
+}
+#endif
+
+static struct pnp_card_driver cs423x_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DISABLE,
+	.name = CS423X_ISAPNP_DRIVER,
+	.id_table = snd_cs423x_pnpids,
+	.probe = snd_cs423x_pnpc_detect,
+	.remove = snd_cs423x_pnpc_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_cs423x_pnpc_suspend,
+	.resume		= snd_cs423x_pnpc_resume,
+#endif
+};
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_cs423x_init(void)
+{
+	int err;
+
+	err = isa_register_driver(&cs423x_isa_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+	if (!err)
+		isa_registered = 1;
+	err = pnp_register_driver(&cs423x_pnp_driver);
+	if (!err)
+		pnp_registered = 1;
+	err = pnp_register_card_driver(&cs423x_pnpc_driver);
+	if (!err)
+		pnpc_registered = 1;
+	if (pnp_registered)
+		err = 0;
+	if (isa_registered)
+		err = 0;
+#endif
+	return err;
+}
+
+static void __exit alsa_card_cs423x_exit(void)
+{
+#ifdef CONFIG_PNP
+	if (pnpc_registered)
+		pnp_unregister_card_driver(&cs423x_pnpc_driver);
+	if (pnp_registered)
+		pnp_unregister_driver(&cs423x_pnp_driver);
+	if (isa_registered)
+#endif
+		isa_unregister_driver(&cs423x_isa_driver);
+}
+
+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..2012936
--- /dev/null
+++ b/sound/isa/cs423x/cs4236_lib.c
@@ -0,0 +1,1086 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/io.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+/*
+ *
+ */
+
+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(struct snd_wss *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(struct snd_wss *chip, unsigned char reg)
+{
+	outb(reg, chip->cport + 3);
+	return inb(chip->cport + 4);
+}
+
+/*
+ *  PCM
+ */
+
+#define CLOCKS 8
+
+static const struct snd_ratnum 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 const struct snd_pcm_hw_constraint_ratnums hw_constraints_clocks = {
+	.nrats = CLOCKS,
+	.rats = clocks,
+};
+
+static int snd_cs4236_xrate(struct snd_pcm_runtime *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:
+		if (divisor < 21 || divisor > 192) {
+			snd_BUG();
+			return 192;
+		}
+		return divisor;
+	}
+}
+
+static void snd_cs4236_playback_format(struct snd_wss *chip,
+				       struct snd_pcm_hw_params *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_wss_out(chip, CS4231_ALT_FEATURE_1,
+		    chip->image[CS4231_ALT_FEATURE_1] | 0x10);
+	snd_wss_out(chip, CS4231_PLAYBK_FORMAT, pdfr & 0xf0);
+	snd_wss_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(struct snd_wss *chip,
+				      struct snd_pcm_hw_params *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_wss_out(chip, CS4231_ALT_FEATURE_1,
+		    chip->image[CS4231_ALT_FEATURE_1] | 0x20);
+	snd_wss_out(chip, CS4231_REC_FORMAT, cdfr & 0xf0);
+	snd_wss_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(struct snd_wss *chip)
+{
+	int reg;
+	unsigned long flags;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	for (reg = 0; reg < 32; reg++)
+		chip->image[reg] = snd_wss_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(struct snd_wss *chip)
+{
+	int reg;
+	unsigned long flags;
+	
+	snd_wss_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_wss_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_wss_mce_down(chip);
+}
+
+#endif /* CONFIG_PM */
+/*
+ * This function does no fail if the chip is not CS4236B or compatible.
+ * It just an equivalent to the snd_wss_create() then.
+ */
+int snd_cs4236_create(struct snd_card *card,
+		      unsigned long port,
+		      unsigned long cport,
+		      int irq, int dma1, int dma2,
+		      unsigned short hardware,
+		      unsigned short hwshare,
+		      struct snd_wss **rchip)
+{
+	struct snd_wss *chip;
+	unsigned char ver1, ver2;
+	unsigned int reg;
+	int err;
+
+	*rchip = NULL;
+	if (hardware == WSS_HW_DETECT)
+		hardware = WSS_HW_DETECT3;
+
+	err = snd_wss_create(card, port, cport,
+			     irq, dma1, dma2, hardware, hwshare, &chip);
+	if (err < 0)
+		return err;
+
+	if ((chip->hardware & WSS_HW_CS4236B_MASK) == 0) {
+		snd_printd("chip is not CS4236+, hardware=0x%x\n",
+			   chip->hardware);
+		*rchip = chip;
+		return 0;
+	}
+#if 0
+	{
+		int idx;
+		for (idx = 0; idx < 8; idx++)
+			snd_printk(KERN_DEBUG "CD%i = 0x%x\n",
+				   idx, inb(chip->cport + idx));
+		for (idx = 0; idx < 9; idx++)
+			snd_printk(KERN_DEBUG "C%i = 0x%x\n",
+				   idx, snd_cs4236_ctrl_in(chip, idx));
+	}
+#endif
+	if (cport < 0x100 || cport == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR "please, specify control port "
+			   "for CS4236+ chips\n");
+		snd_device_free(card, chip);
+		return -ENODEV;
+	}
+	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(KERN_ERR "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);
+	reg = ((IEC958_AES1_CON_PCM_CODER & 3) << 6) |
+	      IEC958_AES0_CON_EMPHASIS_NONE;
+	snd_cs4236_ctrl_out(chip, 5, reg);
+	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_wss_out(chip, CS4231_LEFT_INPUT, 0x40);
+	snd_wss_out(chip, CS4231_RIGHT_INPUT, 0x40);
+	snd_wss_out(chip, CS4231_AUX1_LEFT_INPUT, 0xff);
+	snd_wss_out(chip, CS4231_AUX1_RIGHT_INPUT, 0xff);
+	snd_wss_out(chip, CS4231_AUX2_LEFT_INPUT, 0xdf);
+	snd_wss_out(chip, CS4231_AUX2_RIGHT_INPUT, 0xdf);
+	snd_wss_out(chip, CS4231_RIGHT_LINE_IN, 0xff);
+	snd_wss_out(chip, CS4231_LEFT_LINE_IN, 0xff);
+	snd_wss_out(chip, CS4231_RIGHT_LINE_IN, 0xff);
+	switch (chip->hardware) {
+	case WSS_HW_CS4235:
+	case WSS_HW_CS4239:
+		snd_wss_out(chip, CS4235_LEFT_MASTER, 0xff);
+		snd_wss_out(chip, CS4235_RIGHT_MASTER, 0xff);
+		break;
+	}
+
+	*rchip = chip;
+	return 0;
+}
+
+int snd_cs4236_pcm(struct snd_wss *chip, int device)
+{
+	int err;
+	
+	err = snd_wss_pcm(chip, device);
+	if (err < 0)
+		return err;
+	chip->pcm->info_flags &= ~SNDRV_PCM_INFO_JOINT_DUPLEX;
+	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) }
+
+#define CS4236_SINGLE_TLV(xname, xindex, reg, shift, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .info = snd_cs4236_info_single, \
+  .get = snd_cs4236_get_single, .put = snd_cs4236_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24), \
+  .tlv = { .p = (xtlv) } }
+
+static int snd_cs4236_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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) }
+
+#define CS4236_DOUBLE_TLV(xname, xindex, left_reg, right_reg, shift_left, \
+			  shift_right, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .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), \
+  .tlv = { .p = (xtlv) } }
+
+static int snd_cs4236_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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) }
+
+#define CS4236_DOUBLE1_TLV(xname, xindex, left_reg, right_reg, shift_left, \
+			   shift_right, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .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), \
+  .tlv = { .p = (xtlv) } }
+
+static int snd_cs4236_get_double1(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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_wss_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, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .info = snd_cs4236_info_double, \
+  .get = snd_cs4236_get_master_digital, .put = snd_cs4236_put_master_digital, \
+  .private_value = 71 << 24, \
+  .tlv = { .p = (xtlv) } }
+
+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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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, val2);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+#define CS4235_OUTPUT_ACCU(xname, xindex, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .info = snd_cs4236_info_double, \
+  .get = snd_cs4235_get_output_accu, .put = snd_cs4235_put_output_accu, \
+  .private_value = 3 << 24, \
+  .tlv = { .p = (xtlv) } }
+
+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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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_wss_out(chip, CS4235_LEFT_MASTER, val1);
+	snd_wss_out(chip, CS4235_RIGHT_MASTER, val2);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_7bit, -9450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_6bit, -9450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_6bit_12db_max, -8250, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_22db_max, -2400, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_4bit, -4500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_2bit, -1800, 600, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_rec_gain, 0, 150, 0);
+
+static struct snd_kcontrol_new 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, db_scale_7bit),
+
+CS4236_DOUBLE_TLV("Capture Boost Volume", 0,
+		  CS4236_LEFT_MIX_CTRL, CS4236_RIGHT_MIX_CTRL, 5, 5, 3, 1,
+		  db_scale_2bit),
+
+WSS_DOUBLE("PCM Playback Switch", 0,
+		CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("PCM Playback Volume", 0,
+		CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1,
+		db_scale_6bit),
+
+CS4236_DOUBLE("DSP Playback Switch", 0,
+		CS4236_LEFT_DSP, CS4236_RIGHT_DSP, 7, 7, 1, 1),
+CS4236_DOUBLE_TLV("DSP Playback Volume", 0,
+		  CS4236_LEFT_DSP, CS4236_RIGHT_DSP, 0, 0, 63, 1,
+		  db_scale_6bit),
+
+CS4236_DOUBLE("FM Playback Switch", 0,
+		CS4236_LEFT_FM, CS4236_RIGHT_FM, 7, 7, 1, 1),
+CS4236_DOUBLE_TLV("FM Playback Volume", 0,
+		  CS4236_LEFT_FM, CS4236_RIGHT_FM, 0, 0, 63, 1,
+		  db_scale_6bit),
+
+CS4236_DOUBLE("Wavetable Playback Switch", 0,
+		CS4236_LEFT_WAVE, CS4236_RIGHT_WAVE, 7, 7, 1, 1),
+CS4236_DOUBLE_TLV("Wavetable Playback Volume", 0,
+		  CS4236_LEFT_WAVE, CS4236_RIGHT_WAVE, 0, 0, 63, 1,
+		  db_scale_6bit_12db_max),
+
+WSS_DOUBLE("Synth Playback Switch", 0,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Synth Volume", 0,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1,
+		db_scale_5bit_12db_max),
+WSS_DOUBLE("Synth Capture Switch", 0,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 6, 6, 1, 1),
+WSS_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_TLV("Mic Volume", 0, CS4236_LEFT_MIC, CS4236_RIGHT_MIC,
+		  0, 0, 31, 1, db_scale_5bit_22db_max),
+CS4236_DOUBLE("Mic Playback Boost (+20dB)", 0,
+		CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 5, 5, 1, 0),
+
+WSS_DOUBLE("Line Playback Switch", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Line Volume", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1,
+		db_scale_5bit_12db_max),
+WSS_DOUBLE("Line Capture Switch", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 6, 6, 1, 1),
+WSS_DOUBLE("Line Capture Bypass", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 5, 5, 1, 1),
+
+WSS_DOUBLE("CD Playback Switch", 0,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("CD Volume", 0,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1,
+		db_scale_5bit_12db_max),
+WSS_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("Beep Playback Switch", 0,
+		CS4231_MONO_CTRL, CS4236_LEFT_MIX_CTRL, 7, 7, 1, 1),
+WSS_SINGLE_TLV("Beep Playback Volume", 0, CS4231_MONO_CTRL, 0, 15, 1,
+		db_scale_4bit),
+WSS_SINGLE("Beep Bypass Playback Switch", 0, CS4231_MONO_CTRL, 5, 1, 0),
+
+WSS_DOUBLE_TLV("Capture Volume", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT,
+		0, 0, 15, 0, db_scale_rec_gain),
+WSS_DOUBLE("Analog Loopback Capture Switch", 0,
+		CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 7, 7, 1, 0),
+
+WSS_SINGLE("Loopback Digital Playback Switch", 0, CS4231_LOOPBACK, 0, 1, 0),
+CS4236_DOUBLE1_TLV("Loopback Digital Playback Volume", 0,
+		   CS4231_LOOPBACK, CS4236_RIGHT_LOOPBACK, 2, 0, 63, 1,
+		   db_scale_6bit),
+};
+
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_6db_max, -5600, 200, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_2bit_16db_max, -2400, 800, 0);
+
+static struct snd_kcontrol_new snd_cs4235_controls[] = {
+
+WSS_DOUBLE("Master Playback Switch", 0,
+		CS4235_LEFT_MASTER, CS4235_RIGHT_MASTER, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Master Playback Volume", 0,
+		CS4235_LEFT_MASTER, CS4235_RIGHT_MASTER, 0, 0, 31, 1,
+		db_scale_5bit_6db_max),
+
+CS4235_OUTPUT_ACCU("Playback Volume", 0, db_scale_2bit_16db_max),
+
+WSS_DOUBLE("Synth Playback Switch", 1,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1),
+WSS_DOUBLE("Synth Capture Switch", 1,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 6, 6, 1, 1),
+WSS_DOUBLE_TLV("Synth Volume", 1,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1,
+		db_scale_5bit_12db_max),
+
+CS4236_DOUBLE_TLV("Capture Volume", 0,
+		  CS4236_LEFT_MIX_CTRL, CS4236_RIGHT_MIX_CTRL, 5, 5, 3, 1,
+		  db_scale_2bit),
+
+WSS_DOUBLE("PCM Playback Switch", 0,
+		CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
+WSS_DOUBLE("PCM Capture Switch", 0,
+		CS4236_DAC_MUTE, CS4236_DAC_MUTE, 7, 6, 1, 1),
+WSS_DOUBLE_TLV("PCM Volume", 0,
+		CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1,
+		db_scale_6bit),
+
+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_TLV("Mic Volume", 0, CS4236_LEFT_MIC, 0, 31, 1,
+		  db_scale_5bit_22db_max),
+CS4236_SINGLE("Mic Boost (+20dB)", 0, CS4236_LEFT_MIC, 5, 1, 0),
+
+WSS_DOUBLE("Line Playback Switch", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE("Line Capture Switch", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 6, 6, 1, 1),
+WSS_DOUBLE_TLV("Line Volume", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1,
+		db_scale_5bit_12db_max),
+
+WSS_DOUBLE("CD Playback Switch", 1,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE("CD Capture Switch", 1,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 6, 6, 1, 1),
+WSS_DOUBLE_TLV("CD Volume", 1,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1,
+		db_scale_5bit_12db_max),
+
+CS4236_DOUBLE1("Beep Playback Switch", 0,
+		CS4231_MONO_CTRL, CS4236_LEFT_MIX_CTRL, 7, 7, 1, 1),
+WSS_SINGLE("Beep Playback Volume", 0, CS4231_MONO_CTRL, 0, 15, 1),
+
+WSS_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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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(KERN_DEBUG "get valid: ALT = 0x%x, C3 = 0x%x, C4 = 0x%x, "
+	       "C5 = 0x%x, C6 = 0x%x, C8 = 0x%x\n",
+			snd_wss_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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned short enable, val;
+	
+	enable = ucontrol->value.integer.value[0] & 1;
+
+	mutex_lock(&chip->mce_mutex);
+	snd_wss_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_wss_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_wss_mce_down(chip);
+	mutex_unlock(&chip->mce_mutex);
+
+#if 0
+	printk(KERN_DEBUG "set valid: ALT = 0x%x, C3 = 0x%x, C4 = 0x%x, "
+	       "C5 = 0x%x, C6 = 0x%x, C8 = 0x%x\n",
+			snd_wss_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 struct snd_kcontrol_new 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 struct snd_kcontrol_new 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 struct snd_kcontrol_new 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 struct snd_kcontrol_new 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(struct snd_wss *chip)
+{
+	struct snd_card *card;
+	unsigned int idx, count;
+	int err;
+	struct snd_kcontrol_new *kcontrol;
+
+	if (snd_BUG_ON(!chip || !chip->card))
+		return -EINVAL;
+	card = chip->card;
+	strcpy(card->mixername, snd_wss_chip_id(chip));
+
+	if (chip->hardware == WSS_HW_CS4235 ||
+	    chip->hardware == WSS_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 WSS_HW_CS4235:
+	case WSS_HW_CS4239:
+		count = ARRAY_SIZE(snd_cs4236_3d_controls_cs4235);
+		kcontrol = snd_cs4236_3d_controls_cs4235;
+		break;
+	case WSS_HW_CS4237B:
+		count = ARRAY_SIZE(snd_cs4236_3d_controls_cs4237);
+		kcontrol = snd_cs4236_3d_controls_cs4237;
+		break;
+	case WSS_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 == WSS_HW_CS4237B ||
+	    chip->hardware == WSS_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;
+}
diff --git a/sound/isa/es1688/Makefile b/sound/isa/es1688/Makefile
new file mode 100644
index 0000000..aee1e4d
--- /dev/null
+++ b/sound/isa/es1688/Makefile
@@ -0,0 +1,11 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.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..3dfe7e5
--- /dev/null
+++ b/sound/isa/es1688/es1688.c
@@ -0,0 +1,366 @@
+/*
+ *  Driver for generic ESS AudioDrive ESx688 soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/isapnp.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/module.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/es1688.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+#define CRD_NAME "Generic ESS ES1688/ES688 AudioDrive"
+#define DEV_NAME "es1688"
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+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}}");
+
+MODULE_ALIAS("snd_es968");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+#ifdef CONFIG_PNP
+static bool isapnp[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP;
+#endif
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x240,0x260 */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* Usually 0x388 */
+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 " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+module_param_hw_array(mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+module_param_hw_array(fm_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for ES1688 driver.");
+MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(dma8, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for " CRD_NAME " driver.");
+
+#ifdef CONFIG_PNP
+#define is_isapnp_selected(dev)		isapnp[dev]
+#else
+#define is_isapnp_selected(dev)		0
+#endif
+
+static int snd_es1688_match(struct device *dev, unsigned int n)
+{
+	return enable[n] && !is_isapnp_selected(n);
+}
+
+static int snd_es1688_legacy_create(struct snd_card *card,
+				    struct device *dev, unsigned int n)
+{
+	struct snd_es1688 *chip = card->private_data;
+	static long possible_ports[] = {0x220, 0x240, 0x260};
+	static int possible_irqs[] = {5, 9, 10, 7, -1};
+	static int possible_dmas[] = {1, 3, 0, -1};
+
+	int i, error;
+
+	if (irq[n] == SNDRV_AUTO_IRQ) {
+		irq[n] = snd_legacy_find_free_irq(possible_irqs);
+		if (irq[n] < 0) {
+			dev_err(dev, "unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (dma8[n] == SNDRV_AUTO_DMA) {
+		dma8[n] = snd_legacy_find_free_dma(possible_dmas);
+		if (dma8[n] < 0) {
+			dev_err(dev, "unable to find a free DMA\n");
+			return -EBUSY;
+		}
+	}
+
+	if (port[n] != SNDRV_AUTO_PORT)
+		return snd_es1688_create(card, chip, port[n], mpu_port[n],
+				irq[n], mpu_irq[n], dma8[n], ES1688_HW_AUTO);
+
+	i = 0;
+	do {
+		port[n] = possible_ports[i];
+		error = snd_es1688_create(card, chip, port[n], mpu_port[n],
+				irq[n], mpu_irq[n], dma8[n], ES1688_HW_AUTO);
+	} while (error < 0 && ++i < ARRAY_SIZE(possible_ports));
+
+	return error;
+}
+
+static int snd_es1688_probe(struct snd_card *card, unsigned int n)
+{
+	struct snd_es1688 *chip = card->private_data;
+	struct snd_opl3 *opl3;
+	int error;
+
+	error = snd_es1688_pcm(card, chip, 0);
+	if (error < 0)
+		return error;
+
+	error = snd_es1688_mixer(card, chip);
+	if (error < 0)
+		return error;
+
+	strlcpy(card->driver, "ES1688", sizeof(card->driver));
+	strlcpy(card->shortname, chip->pcm->name, sizeof(card->shortname));
+	snprintf(card->longname, sizeof(card->longname),
+		"%s at 0x%lx, irq %i, dma %i", chip->pcm->name, chip->port,
+		 chip->irq, chip->dma8);
+
+	if (fm_port[n] == SNDRV_AUTO_PORT)
+		fm_port[n] = port[n];	/* share the same port */
+
+	if (fm_port[n] > 0) {
+		if (snd_opl3_create(card, fm_port[n], fm_port[n] + 2,
+				OPL3_HW_OPL3, 0, &opl3) < 0)
+			dev_warn(card->dev,
+				 "opl3 not detected at 0x%lx\n", fm_port[n]);
+		else {
+			error =	snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+			if (error < 0)
+				return error;
+		}
+	}
+
+	if (mpu_irq[n] >= 0 && mpu_irq[n] != SNDRV_AUTO_IRQ &&
+			chip->mpu_port > 0) {
+		error = snd_mpu401_uart_new(card, 0, MPU401_HW_ES1688,
+				chip->mpu_port, 0,
+				mpu_irq[n], NULL);
+		if (error < 0)
+			return error;
+	}
+
+	return snd_card_register(card);
+}
+
+static int snd_es1688_isa_probe(struct device *dev, unsigned int n)
+{
+	struct snd_card *card;
+	int error;
+
+	error = snd_card_new(dev, index[n], id[n], THIS_MODULE,
+			     sizeof(struct snd_es1688), &card);
+	if (error < 0)
+		return error;
+
+	error = snd_es1688_legacy_create(card, dev, n);
+	if (error < 0)
+		goto out;
+
+	error = snd_es1688_probe(card, n);
+	if (error < 0)
+		goto out;
+
+	dev_set_drvdata(dev, card);
+
+	return 0;
+out:
+	snd_card_free(card);
+	return error;
+}
+
+static int snd_es1688_isa_remove(struct device *dev, unsigned int n)
+{
+	snd_card_free(dev_get_drvdata(dev));
+	return 0;
+}
+
+static struct isa_driver snd_es1688_driver = {
+	.match		= snd_es1688_match,
+	.probe		= snd_es1688_isa_probe,
+	.remove		= snd_es1688_isa_remove,
+#if 0	/* FIXME */
+	.suspend	= snd_es1688_suspend,
+	.resume		= snd_es1688_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	}
+};
+
+static int snd_es968_pnp_is_probed;
+
+#ifdef CONFIG_PNP
+static int snd_card_es968_pnp(struct snd_card *card, unsigned int n,
+			      struct pnp_card_link *pcard,
+			      const struct pnp_card_device_id *pid)
+{
+	struct snd_es1688 *chip = card->private_data;
+	struct pnp_dev *pdev;
+	int error;
+
+	pdev = pnp_request_card_device(pcard, pid->devs[0].id, NULL);
+	if (pdev == NULL)
+		return -ENODEV;
+
+	error = pnp_activate_dev(pdev);
+	if (error < 0) {
+		snd_printk(KERN_ERR "ES968 pnp configure failure\n");
+		return error;
+	}
+	port[n] = pnp_port_start(pdev, 0);
+	dma8[n] = pnp_dma(pdev, 0);
+	irq[n] = pnp_irq(pdev, 0);
+
+	return snd_es1688_create(card, chip, port[n], mpu_port[n], irq[n],
+				 mpu_irq[n], dma8[n], ES1688_HW_AUTO);
+}
+
+static int snd_es968_pnp_detect(struct pnp_card_link *pcard,
+				const struct pnp_card_device_id *pid)
+{
+	struct snd_card *card;
+	static unsigned int dev;
+	int error;
+
+	if (snd_es968_pnp_is_probed)
+		return -EBUSY;
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev == SNDRV_CARDS)
+		return -ENODEV;
+
+	error = snd_card_new(&pcard->card->dev,
+			     index[dev], id[dev], THIS_MODULE,
+			     sizeof(struct snd_es1688), &card);
+	if (error < 0)
+		return error;
+
+	error = snd_card_es968_pnp(card, dev, pcard, pid);
+	if (error < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	error = snd_es1688_probe(card, dev);
+	if (error < 0)
+		return error;
+	pnp_set_card_drvdata(pcard, card);
+	snd_es968_pnp_is_probed = 1;
+	return 0;
+}
+
+static void snd_es968_pnp_remove(struct pnp_card_link *pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+	snd_es968_pnp_is_probed = 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_es968_pnp_suspend(struct pnp_card_link *pcard,
+				 pm_message_t state)
+{
+	struct snd_card *card = pnp_get_card_drvdata(pcard);
+	struct snd_es1688 *chip = card->private_data;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	return 0;
+}
+
+static int snd_es968_pnp_resume(struct pnp_card_link *pcard)
+{
+	struct snd_card *card = pnp_get_card_drvdata(pcard);
+	struct snd_es1688 *chip = card->private_data;
+
+	snd_es1688_reset(chip);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+static const struct pnp_card_device_id snd_es968_pnpids[] = {
+	{ .id = "ESS0968", .devs = { { "@@@0968" }, } },
+	{ .id = "ESS0968", .devs = { { "ESS0968" }, } },
+	{ .id = "", } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_es968_pnpids);
+
+static struct pnp_card_driver es968_pnpc_driver = {
+	.flags		= PNP_DRIVER_RES_DISABLE,
+	.name		= DEV_NAME " PnP",
+	.id_table	= snd_es968_pnpids,
+	.probe		= snd_es968_pnp_detect,
+	.remove		= snd_es968_pnp_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_es968_pnp_suspend,
+	.resume		= snd_es968_pnp_resume,
+#endif
+};
+#endif
+
+static int __init alsa_card_es1688_init(void)
+{
+#ifdef CONFIG_PNP
+	pnp_register_card_driver(&es968_pnpc_driver);
+	if (snd_es968_pnp_is_probed)
+		return 0;
+	pnp_unregister_card_driver(&es968_pnpc_driver);
+#endif
+	return isa_register_driver(&snd_es1688_driver, SNDRV_CARDS);
+}
+
+static void __exit alsa_card_es1688_exit(void)
+{
+	if (!snd_es968_pnp_is_probed) {
+		isa_unregister_driver(&snd_es1688_driver);
+		return;
+	}
+#ifdef CONFIG_PNP
+	pnp_unregister_card_driver(&es968_pnpc_driver);
+#endif
+}
+
+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..50cdce0
--- /dev/null
+++ b/sound/isa/es1688/es1688_lib.c
@@ -0,0 +1,1031 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/es1688.h>
+#include <sound/initval.h>
+
+#include <asm/dma.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("ESS ESx688 lowlevel module");
+MODULE_LICENSE("GPL");
+
+static int snd_es1688_dsp_command(struct snd_es1688 *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(KERN_DEBUG "snd_es1688_dsp_command: timeout (0x%x)\n", val);
+#endif
+	return 0;
+}
+
+static int snd_es1688_dsp_get_byte(struct snd_es1688 *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(struct snd_es1688 *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(struct snd_es1688 *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(struct snd_es1688 *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(struct snd_es1688 *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;
+}
+
+int snd_es1688_reset(struct snd_es1688 *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;
+}
+EXPORT_SYMBOL(snd_es1688_reset);
+
+static int snd_es1688_probe(struct snd_es1688 *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(KERN_ERR "[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(KERN_ERR "[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(struct snd_es1688 * 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(KERN_DEBUG "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(KERN_ERR "[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(KERN_ERR "[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 const struct snd_ratnum 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 const struct snd_pcm_hw_constraint_ratnums hw_constraints_clocks  = {
+	.nrats = 2,
+	.rats = clocks,
+};
+
+static void snd_es1688_set_rate(struct snd_es1688 *chip, struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *substream,
+			    unsigned int cmd, void *arg)
+{
+	return snd_pcm_lib_ioctl(substream, cmd, arg);
+}
+
+static int snd_es1688_trigger(struct snd_es1688 *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(KERN_DEBUG "trigger: val = 0x%x, value = 0x%x\n", val, value);
+	printk(KERN_DEBUG "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(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_es1688_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_es1688_playback_prepare(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *substream,
+				       int cmd)
+{
+	struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+	return snd_es1688_trigger(chip, cmd, 0x05);
+}
+
+static int snd_es1688_capture_prepare(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *substream,
+				      int cmd)
+{
+	struct snd_es1688 *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 snd_es1688 *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(struct snd_pcm_substream *substream)
+{
+	struct snd_es1688 *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(struct snd_pcm_substream *substream)
+{
+	struct snd_es1688 *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 const struct snd_pcm_hardware 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 const struct snd_pcm_hardware 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(struct snd_pcm_substream *substream)
+{
+	struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *substream)
+{
+	struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *substream)
+{
+	struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+
+	chip->playback_substream = NULL;
+	return 0;
+}
+
+static int snd_es1688_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+
+	chip->capture_substream = NULL;
+	return 0;
+}
+
+static int snd_es1688_free(struct snd_es1688 *chip)
+{
+	if (chip->hardware != ES1688_HW_UNDEF)
+		snd_es1688_init(chip, 0);
+	release_and_free_resource(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);
+	}
+	return 0;
+}
+
+static int snd_es1688_dev_free(struct snd_device *device)
+{
+	struct snd_es1688 *chip = device->device_data;
+	return snd_es1688_free(chip);
+}
+
+static const char *snd_es1688_chip_id(struct snd_es1688 *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(struct snd_card *card,
+		      struct snd_es1688 *chip,
+		      unsigned long port,
+		      unsigned long mpu_port,
+		      int irq,
+		      int mpu_irq,
+		      int dma8,
+		      unsigned short hardware)
+{
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_es1688_dev_free,
+	};
+                                
+	int err;
+
+	if (chip == NULL)
+		return -ENOMEM;
+	chip->irq = -1;
+	chip->dma8 = -1;
+	chip->hardware = ES1688_HW_UNDEF;
+	
+	chip->res_port = request_region(port + 4, 12, "ES1688");
+	if (chip->res_port == NULL) {
+		snd_printk(KERN_ERR "es1688: can't grab port 0x%lx\n", port + 4);
+		err = -EBUSY;
+		goto exit;
+	}
+
+	err = request_irq(irq, snd_es1688_interrupt, 0, "ES1688", (void *) chip);
+	if (err < 0) {
+		snd_printk(KERN_ERR "es1688: can't grab IRQ %d\n", irq);
+		goto exit;
+	}
+
+	chip->irq = irq;
+	err = request_dma(dma8, "ES1688");
+
+	if (err < 0) {
+		snd_printk(KERN_ERR "es1688: can't grab DMA8 %d\n", dma8);
+		goto exit;
+	}
+	chip->dma8 = dma8;
+
+	spin_lock_init(&chip->reg_lock);
+	spin_lock_init(&chip->mixer_lock);
+	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;
+
+	err = snd_es1688_probe(chip);
+	if (err < 0)
+		goto exit;
+
+	err = snd_es1688_init(chip, 1);
+	if (err < 0)
+		goto exit;
+
+	/* Register device */
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+exit:
+	if (err)
+		snd_es1688_free(chip);
+	return err;
+}
+
+static const struct snd_pcm_ops 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 const struct snd_pcm_ops 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,
+};
+
+int snd_es1688_pcm(struct snd_card *card, struct snd_es1688 *chip, int device)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(card, "ESx688", device, 1, 1, &pcm);
+	if (err < 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->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+	strcpy(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);
+	return 0;
+}
+
+/*
+ *  MIXER part
+ */
+
+static int snd_es1688_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts[8] = {
+		"Mic", "Mic Master", "CD", "AOUT",
+		"Mic1", "Mix", "Line", "Master"
+	};
+
+	return snd_ctl_enum_info(uinfo, 1, 8, texts);
+}
+
+static int snd_es1688_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es1688 *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es1688 *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es1688 *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es1688 *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es1688 *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es1688 *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 struct snd_kcontrol_new 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("Beep 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(struct snd_card *card, struct snd_es1688 *chip)
+{
+	unsigned int idx;
+	int err;
+	unsigned char reg, val;
+
+	if (snd_BUG_ON(!chip || !card))
+		return -EINVAL;
+
+	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);
diff --git a/sound/isa/es18xx.c b/sound/isa/es18xx.c
new file mode 100644
index 0000000..0d103d6
--- /dev/null
+++ b/sound/isa/es18xx.c
@@ -0,0 +1,2441 @@
+/*
+ *  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.
+ *
+ * - Zoom Video is implemented by sharing the FM DAC, thus the user can
+ *   have either FM playback or Video playback but not both simultaneously.
+ *   The Video Playback Switch mixer control toggles this choice.
+ *
+ * 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.
+ *
+ */
+
+/*
+ * ES1879 NOTES:
+ * - When Zoom Video is enabled (reg 0x71 bit 6 toggled on) the PCM playback
+ *   seems to be effected (speaker_test plays a lower frequency). Can't find
+ *   anything in the datasheet to account for this, so a Video Playback Switch
+ *   control has been included to allow ZV to be enabled only when necessary.
+ *   Then again on at least one test system the 0x71 bit 6 enable bit is not 
+ *   needed for ZV, so maybe the datasheet is entirely wrong here.
+ */
+ 
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/pnp.h>
+#include <linux/isapnp.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+
+#include <asm/dma.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_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 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_shift;
+	unsigned int dma2_shift;
+
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *playback_a_substream;
+	struct snd_pcm_substream *capture_a_substream;
+	struct snd_pcm_substream *playback_b_substream;
+
+	struct snd_rawmidi *rmidi;
+
+	struct snd_kcontrol *hw_volume;
+	struct snd_kcontrol *hw_switch;
+	struct snd_kcontrol *master_volume;
+	struct snd_kcontrol *master_switch;
+
+	spinlock_t reg_lock;
+	spinlock_t mixer_lock;
+#ifdef CONFIG_PM
+	unsigned char pm_reg;
+#endif
+#ifdef CONFIG_PNP
+	struct pnp_dev *dev;
+	struct pnp_dev *devc;
+#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 separate hardware volume mixer controls*/
+#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 */
+#define ES18XX_GPO_2BIT	0x1000	/* GPO0,1 controlled by PM port */
+
+/* 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
+
+/* Lowlevel */
+
+#define DAC1 0x01
+#define ADC1 0x02
+#define DAC2 0x04
+#define MILLISECOND 10000
+
+static int snd_es18xx_dsp_command(struct snd_es18xx *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(KERN_ERR "dsp_command: timeout (0x%x)\n", val);
+        return -EINVAL;
+}
+
+static int snd_es18xx_dsp_get_byte(struct snd_es18xx *chip)
+{
+        int i;
+
+        for(i = MILLISECOND/10; i; i--)
+                if (inb(chip->port + 0x0C) & 0x40)
+                        return inb(chip->port + 0x0A);
+	snd_printk(KERN_ERR "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(struct snd_es18xx *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(KERN_DEBUG "Reg %02x set to %02x\n", reg, data);
+#endif
+	return ret;
+}
+
+static int snd_es18xx_read(struct snd_es18xx *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(KERN_DEBUG "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(struct snd_es18xx *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(KERN_DEBUG "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(struct snd_es18xx *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(KERN_DEBUG "Mixer reg %02x set to %02x\n", reg, data);
+#endif
+}
+
+static inline int snd_es18xx_mixer_read(struct snd_es18xx *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(KERN_DEBUG "Mixer reg %02x now is %02x\n", reg, data);
+#endif
+        return data;
+}
+
+/* Return old value */
+static inline int snd_es18xx_mixer_bits(struct snd_es18xx *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(KERN_DEBUG "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(struct snd_es18xx *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(KERN_DEBUG "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(struct snd_es18xx *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(struct snd_es18xx *chip)
+{
+        outb(0x02, chip->port + 0x06);
+        inb(chip->port + 0x06);
+        outb(0x00, chip->port + 0x06);
+	return 0;
+}
+
+static const struct snd_ratnum 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 const struct snd_pcm_hw_constraint_ratnums new_hw_constraints_clocks = {
+	.nrats = 2,
+	.rats = new_clocks,
+};
+
+static const struct snd_ratnum 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 const struct snd_pcm_hw_constraint_ratnums old_hw_constraints_clocks  = {
+	.nrats = 2,
+	.rats = old_clocks,
+};
+
+
+static void snd_es18xx_rate_set(struct snd_es18xx *chip, 
+				struct snd_pcm_substream *substream,
+				int mode)
+{
+	unsigned int bits, div0;
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *substream,
+					 struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_es18xx *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(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_es18xx_playback1_prepare(struct snd_es18xx *chip,
+					struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+        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(struct snd_es18xx *chip,
+					struct snd_pcm_substream *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 */
+		mdelay(100);
+		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
+		mdelay(25);
+		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(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_es18xx *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(struct snd_pcm_substream *substream)
+{
+        struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	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
+	mdelay(100);
+#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 controller */
+        snd_dma_program(chip->dma1, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
+
+	return 0;
+}
+
+static int snd_es18xx_capture_trigger(struct snd_pcm_substream *substream,
+				      int cmd)
+{
+        struct snd_es18xx *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(struct snd_es18xx *chip,
+					struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	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 controller */
+        snd_dma_program(chip->dma1, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
+
+	return 0;
+}
+
+static int snd_es18xx_playback2_trigger(struct snd_es18xx *chip,
+					struct snd_pcm_substream *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 */
+		mdelay(100);
+                /* 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 */
+		mdelay(25);
+                /* Disable Audio 1 */
+                snd_es18xx_dsp_command(chip, 0xD3);
+#endif
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int snd_es18xx_playback_prepare(struct snd_pcm_substream *substream)
+{
+        struct snd_es18xx *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(struct snd_pcm_substream *substream,
+				       int cmd)
+{
+        struct snd_es18xx *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 snd_card *card = dev_id;
+	struct snd_es18xx *chip = card->private_data;
+	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);
+
+	/* Hardware volume */
+	if (status & HWV_IRQ) {
+		int split = 0;
+		if (chip->caps & ES18XX_HWV) {
+			split = snd_es18xx_mixer_read(chip, 0x64) & 0x80;
+			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+					&chip->hw_switch->id);
+			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+					&chip->hw_volume->id);
+		}
+		if (!split) {
+			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+					&chip->master_switch->id);
+			snd_ctl_notify(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(struct snd_pcm_substream *substream)
+{
+        struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	int pos;
+
+	if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) {
+		if (!(chip->active & DAC2))
+			return 0;
+		pos = snd_dma_pointer(chip->dma2, size);
+		return pos >> chip->dma2_shift;
+	} else {
+		if (!(chip->active & DAC1))
+			return 0;
+		pos = snd_dma_pointer(chip->dma1, size);
+		return pos >> chip->dma1_shift;
+	}
+}
+
+static snd_pcm_uframes_t snd_es18xx_capture_pointer(struct snd_pcm_substream *substream)
+{
+        struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	int pos;
+
+        if (!(chip->active & ADC1))
+                return 0;
+	pos = snd_dma_pointer(chip->dma1, size);
+	return pos >> chip->dma1_shift;
+}
+
+static const struct snd_pcm_hardware 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 const struct snd_pcm_hardware 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(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+        struct snd_es18xx *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(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+        struct snd_es18xx *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(struct snd_pcm_substream *substream)
+{
+        struct snd_es18xx *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(struct snd_pcm_substream *substream)
+{
+        struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+
+        chip->capture_a_substream = NULL;
+	snd_pcm_lib_free_pages(substream);
+        return 0;
+}
+
+/*
+ *  MIXER part
+ */
+
+/* Record source mux routines:
+ * Depending on the chipset this mux switches between 4, 5, or 8 possible inputs.
+ * bit table for the 4/5 source mux:
+ * reg 1C:
+ *  b2 b1 b0   muxSource
+ *   x  0  x   microphone
+ *   0  1  x   CD
+ *   1  1  0   line
+ *   1  1  1   mixer
+ * if it's "mixer" and it's a 5 source mux chipset then reg 7A bit 3 determines
+ * either the play mixer or the capture mixer.
+ *
+ * "map4Source" translates from source number to reg bit pattern
+ * "invMap4Source" translates from reg bit pattern to source number
+ */
+
+static int snd_es18xx_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts5Source[5] = {
+		"Mic", "CD", "Line", "Master", "Mix"
+	};
+	static const char * const texts8Source[8] = {
+		"Mic", "Mic Master", "CD", "AOUT",
+		"Mic1", "Mix", "Line", "Master"
+	};
+	struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+
+	switch (chip->version) {
+	case 0x1868:
+	case 0x1878:
+		return snd_ctl_enum_info(uinfo, 1, 4, texts5Source);
+	case 0x1887:
+	case 0x1888:
+		return snd_ctl_enum_info(uinfo, 1, 5, texts5Source);
+	case 0x1869: /* DS somewhat contradictory for 1869: could be be 5 or 8 */
+	case 0x1879:
+		return snd_ctl_enum_info(uinfo, 1, 8, texts8Source);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int snd_es18xx_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	static unsigned char invMap4Source[8] = {0, 0, 1, 1, 0, 0, 2, 3};
+	struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+	int muxSource = snd_es18xx_mixer_read(chip, 0x1c) & 0x07;
+	if (!(chip->version == 0x1869 || chip->version == 0x1879)) {
+		muxSource = invMap4Source[muxSource];
+		if (muxSource==3 && 
+		    (chip->version == 0x1887 || chip->version == 0x1888) &&
+		    (snd_es18xx_mixer_read(chip, 0x7a) & 0x08)
+		) 
+			muxSource = 4;
+	}
+	ucontrol->value.enumerated.item[0] = muxSource;
+	return 0;
+}
+
+static int snd_es18xx_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	static unsigned char map4Source[4] = {0, 2, 6, 7};
+	struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+	unsigned char val = ucontrol->value.enumerated.item[0];
+	unsigned char retVal = 0;
+
+	switch (chip->version) {
+ /* 5 source chips */
+	case 0x1887:
+	case 0x1888:
+		if (val > 4)
+			return -EINVAL;
+		if (val == 4) {
+			retVal = snd_es18xx_mixer_bits(chip, 0x7a, 0x08, 0x08) != 0x08;
+			val = 3;
+		} else
+			retVal = snd_es18xx_mixer_bits(chip, 0x7a, 0x08, 0x00) != 0x00;
+		/* fall through */
+ /* 4 source chips */
+	case 0x1868:
+	case 0x1878:
+		if (val > 3)
+			return -EINVAL;
+		val = map4Source[val];
+		break;
+ /* 8 source chips */
+	case 0x1869:
+	case 0x1879:
+		if (val > 7)
+			return -EINVAL;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return (snd_es18xx_mixer_bits(chip, 0x1c, 0x07, val) != val) || retVal;
+}
+
+#define snd_es18xx_info_spatializer_enable	snd_ctl_boolean_mono_info
+
+static int snd_es18xx_get_spatializer_enable(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es18xx *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es18xx *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es18xx *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;
+}
+
+#define snd_es18xx_info_hw_switch	snd_ctl_boolean_stereo_info
+
+static int snd_es18xx_get_hw_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es18xx *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(struct snd_kcontrol *kcontrol)
+{
+	struct snd_es18xx *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(struct snd_es18xx *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(struct snd_es18xx *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, flags) \
+{ .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) | (flags << 24) }
+
+#define ES18XX_FL_INVERT	(1 << 0)
+#define ES18XX_FL_PMPORT	(1 << 1)
+
+static int snd_es18xx_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es18xx *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) & ES18XX_FL_INVERT;
+	int pm_port = (kcontrol->private_value >> 24) & ES18XX_FL_PMPORT;
+	int val;
+
+	if (pm_port)
+		val = inb(chip->port + ES18XX_PM);
+	else
+		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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es18xx *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) & ES18XX_FL_INVERT;
+	int pm_port = (kcontrol->private_value >> 24) & ES18XX_FL_PMPORT;
+	unsigned char val;
+	
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	mask <<= shift;
+	val <<= shift;
+	if (pm_port) {
+		unsigned char cur = inb(chip->port + ES18XX_PM);
+
+		if ((cur & mask) == val)
+			return 0;
+		outb((cur & ~mask) | val, chip->port + ES18XX_PM);
+		return 1;
+	}
+
+	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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es18xx *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es18xx *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;
+}
+
+/* Mixer controls
+ * These arrays contain setup data for mixer controls.
+ * 
+ * The controls that are universal to all chipsets are fully initialized
+ * here.
+ */
+static struct snd_kcontrol_new 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("Mic Playback Volume", 0, 0x1a, 0x1a, 4, 0, 15, 0),
+ES18XX_DOUBLE("Aux Playback Volume", 0, 0x3a, 0x3a, 4, 0, 15, 0),
+ES18XX_SINGLE("Record Monitor", 0, 0xa8, 3, 1, 0),
+ES18XX_DOUBLE("Capture Volume", 0, 0xb4, 0xb4, 4, 0, 15, 0),
+{
+	.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 struct snd_kcontrol_new 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("CD Capture Volume", 0, 0x6a, 0x6a, 4, 0, 15, 0),
+ES18XX_DOUBLE("Aux Capture Volume", 0, 0x6c, 0x6c, 4, 0, 15, 0)
+};
+
+/*
+ * The chipset specific mixer controls
+ */
+static struct snd_kcontrol_new snd_es18xx_opt_speaker =
+	ES18XX_SINGLE("Beep Playback Volume", 0, 0x3c, 0, 7, 0);
+
+static struct snd_kcontrol_new snd_es18xx_opt_1869[] = {
+ES18XX_SINGLE("Capture Switch", 0, 0x1c, 4, 1, ES18XX_FL_INVERT),
+ES18XX_SINGLE("Video Playback Switch", 0, 0x7f, 0, 1, 0),
+ES18XX_DOUBLE("Mono Playback Volume", 0, 0x6d, 0x6d, 4, 0, 15, 0),
+ES18XX_DOUBLE("Mono Capture Volume", 0, 0x6f, 0x6f, 4, 0, 15, 0)
+};
+
+static struct snd_kcontrol_new snd_es18xx_opt_1878 =
+	ES18XX_DOUBLE("Video Playback Volume", 0, 0x68, 0x68, 4, 0, 15, 0);
+
+static struct snd_kcontrol_new snd_es18xx_opt_1879[] = {
+ES18XX_SINGLE("Video Playback Switch", 0, 0x71, 6, 1, 0),
+ES18XX_DOUBLE("Video Playback Volume", 0, 0x6d, 0x6d, 4, 0, 15, 0),
+ES18XX_DOUBLE("Video Capture Volume", 0, 0x6f, 0x6f, 4, 0, 15, 0)
+};
+
+static struct snd_kcontrol_new snd_es18xx_pcm1_controls[] = {
+ES18XX_DOUBLE("PCM Playback Volume", 0, 0x14, 0x14, 4, 0, 15, 0),
+};
+
+static struct snd_kcontrol_new 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 struct snd_kcontrol_new 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 struct snd_kcontrol_new snd_es18xx_micpre1_control = 
+ES18XX_SINGLE("Mic Boost (+26dB)", 0, 0xa9, 2, 1, 0);
+
+static struct snd_kcontrol_new snd_es18xx_micpre2_control =
+ES18XX_SINGLE("Mic Boost (+26dB)", 0, 0x7d, 3, 1, 0);
+
+static struct snd_kcontrol_new 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),
+};
+
+static struct snd_kcontrol_new snd_es18xx_opt_gpo_2bit[] = {
+ES18XX_SINGLE("GPO0 Switch", 0, ES18XX_PM, 0, 1, ES18XX_FL_PMPORT),
+ES18XX_SINGLE("GPO1 Switch", 0, ES18XX_PM, 1, 1, ES18XX_FL_PMPORT),
+};
+
+static int snd_es18xx_config_read(struct snd_es18xx *chip, unsigned char reg)
+{
+	int data;
+
+	outb(reg, chip->ctrl_port);
+	data = inb(chip->ctrl_port + 1);
+	return data;
+}
+
+static void snd_es18xx_config_write(struct snd_es18xx *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(KERN_DEBUG "Config reg %02x set to %02x\n", reg, data);
+#endif
+}
+
+static int snd_es18xx_initialize(struct snd_es18xx *chip,
+				 unsigned long mpu_port,
+				 unsigned long fm_port)
+{
+	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 (fm_port > 0 && fm_port != SNDRV_AUTO_PORT) {
+			/* FM I/O */
+			snd_es18xx_config_write(chip, 0x62, fm_port >> 8);
+			snd_es18xx_config_write(chip, 0x63, fm_port & 0xff);
+		}
+		if (mpu_port > 0 && mpu_port != SNDRV_AUTO_PORT) {
+			/* MPU-401 I/O */
+			snd_es18xx_config_write(chip, 0x64, mpu_port >> 8);
+			snd_es18xx_config_write(chip, 0x65, 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);
+		/* Enable ESS wavetable input */
+		snd_es18xx_mixer_bits(chip, 0x48, 0x10, 0x10);
+	}
+	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(KERN_ERR "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(KERN_ERR "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(KERN_ERR "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 (mpu_port > 0 && mpu_port != SNDRV_AUTO_PORT) {
+			/* MPU401 share irq with audio
+			   Joystick enabled
+			   FM enabled */
+			snd_es18xx_mixer_write(chip, 0x40,
+					       0x43 | (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);
+	}
+	/* Flip the "enable I2S" bits for those chipsets that need it */
+	switch (chip->version) {
+	case 0x1879:
+		//Leaving I2S enabled on the 1879 screws up the PCM playback (rate effected somehow)
+		//so a Switch control has been added to toggle this 0x71 bit on/off:
+		//snd_es18xx_mixer_bits(chip, 0x71, 0x40, 0x40);
+		/* Note: we fall through on purpose here. */
+	case 0x1878:
+		snd_es18xx_config_write(chip, 0x29, snd_es18xx_config_read(chip, 0x29) | 0x40);
+		break;
+	}
+	/* 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 snd_es18xx_identify(struct snd_es18xx *chip)
+{
+	int hi,lo;
+
+	/* reset */
+	if (snd_es18xx_reset(chip) < 0) {
+		snd_printk(KERN_ERR "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);
+	udelay(10);
+	hi = inb(chip->port + 0x05);
+	udelay(10);
+	lo = inb(chip->port + 0x05);
+	if (hi != lo) {
+		chip->version = hi << 8 | lo;
+		chip->ctrl_port = inb(chip->port + 0x05) << 8;
+		udelay(10);
+		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 snd_es18xx_probe(struct snd_es18xx *chip,
+			    unsigned long mpu_port,
+			    unsigned long fm_port)
+{
+	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_GPO_2BIT;
+		break;
+	case 0x1869:
+		chip->caps = ES18XX_PCM2 | ES18XX_SPATIALIZER | ES18XX_RECMIX | ES18XX_NEW_RATE | ES18XX_AUXB | ES18XX_MONO | ES18XX_MUTEREC | ES18XX_CONTROL | ES18XX_HWV | ES18XX_GPO_2BIT;
+		break;
+	case 0x1878:
+		chip->caps = ES18XX_DUPLEX_MONO | ES18XX_DUPLEX_SAME | ES18XX_I2S | ES18XX_CONTROL;
+		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:
+	case 0x1888:
+		chip->caps = ES18XX_PCM2 | ES18XX_RECMIX | ES18XX_AUXB | ES18XX_DUPLEX_SAME | ES18XX_GPO_2BIT;
+		break;
+	default:
+		snd_printk(KERN_ERR "[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, mpu_port, fm_port);
+}
+
+static const struct snd_pcm_ops 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 const struct snd_pcm_ops 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 int snd_es18xx_pcm(struct snd_card *card, int device)
+{
+	struct snd_es18xx *chip = card->private_data;
+        struct snd_pcm *pcm;
+	char str[16];
+	int err;
+
+	sprintf(str, "ES%x", chip->version);
+	if (chip->caps & ES18XX_PCM2)
+		err = snd_pcm_new(card, str, device, 2, 1, &pcm);
+	else
+		err = snd_pcm_new(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->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);
+	return 0;
+}
+
+/* Power Management support functions */
+#ifdef CONFIG_PM
+static int snd_es18xx_suspend(struct snd_card *card, pm_message_t state)
+{
+	struct snd_es18xx *chip = card->private_data;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+
+	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(struct snd_card *card)
+{
+	struct snd_es18xx *chip = card->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);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+static int snd_es18xx_free(struct snd_card *card)
+{
+	struct snd_es18xx *chip = card->private_data;
+
+	release_and_free_resource(chip->res_port);
+	release_and_free_resource(chip->res_ctrl_port);
+	release_and_free_resource(chip->res_mpu_port);
+	if (chip->irq >= 0)
+		free_irq(chip->irq, (void *) card);
+	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);
+	}
+	return 0;
+}
+
+static int snd_es18xx_dev_free(struct snd_device *device)
+{
+	return snd_es18xx_free(device->card);
+}
+
+static int snd_es18xx_new_device(struct snd_card *card,
+				 unsigned long port,
+				 unsigned long mpu_port,
+				 unsigned long fm_port,
+				 int irq, int dma1, int dma2)
+{
+	struct snd_es18xx *chip = card->private_data;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_es18xx_dev_free,
+        };
+	int err;
+
+	spin_lock_init(&chip->reg_lock);
+ 	spin_lock_init(&chip->mixer_lock);
+        chip->port = port;
+        chip->irq = -1;
+        chip->dma1 = -1;
+        chip->dma2 = -1;
+        chip->audio2_vol = 0x00;
+	chip->active = 0;
+
+	chip->res_port = request_region(port, 16, "ES18xx");
+	if (chip->res_port == NULL) {
+		snd_es18xx_free(card);
+		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, 0, "ES18xx",
+			(void *) card)) {
+		snd_es18xx_free(card);
+		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(card);
+		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(card);
+		snd_printk(KERN_ERR PFX "unable to grap DMA2 %d\n", dma2);
+		return -EBUSY;
+	}
+	chip->dma2 = dma2;
+
+	if (snd_es18xx_probe(chip, mpu_port, fm_port) < 0) {
+		snd_es18xx_free(card);
+		return -ENODEV;
+	}
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err < 0) {
+		snd_es18xx_free(card);
+		return err;
+	}
+        return 0;
+}
+
+static int snd_es18xx_mixer(struct snd_card *card)
+{
+	struct snd_es18xx *chip = card->private_data;
+	int err;
+	unsigned int idx;
+
+	strcpy(card->mixername, chip->pcm->name);
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_base_controls); idx++) {
+		struct snd_kcontrol *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_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++) {
+			struct snd_kcontrol *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;
+			
+		}
+	}
+	/* finish initializing other chipset specific controls
+	 */
+	if (chip->version != 0x1868) {
+		err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_opt_speaker,
+						     chip));
+		if (err < 0)
+			return err;
+	}
+	if (chip->version == 0x1869) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_opt_1869); idx++) {
+			err = snd_ctl_add(card,
+					  snd_ctl_new1(&snd_es18xx_opt_1869[idx],
+						       chip));
+			if (err < 0)
+				return err;
+		}
+	} else if (chip->version == 0x1878) {
+		err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_opt_1878,
+						     chip));
+		if (err < 0)
+			return err;
+	} else if (chip->version == 0x1879) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_opt_1879); idx++) {
+			err = snd_ctl_add(card,
+					  snd_ctl_new1(&snd_es18xx_opt_1879[idx],
+						       chip));
+			if (err < 0)
+				return err;
+		}
+	}
+	if (chip->caps & ES18XX_GPO_2BIT) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_opt_gpo_2bit); idx++) {
+			err = snd_ctl_add(card,
+					  snd_ctl_new1(&snd_es18xx_opt_gpo_2bit[idx],
+						       chip));
+			if (err < 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 bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static bool isapnp[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP;
+#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_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for ES18xx driver.");
+module_param_hw_array(mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for ES18xx driver.");
+module_param_hw_array(fm_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for ES18xx driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for ES18xx driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA 1 # for ES18xx driver.");
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA 2 # for ES18xx driver.");
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+static int pnpc_registered;
+
+static const struct pnp_device_id snd_audiodrive_pnpbiosids[] = {
+	{ .id = "ESS1869" },
+	{ .id = "ESS1879" },
+	{ .id = "" }		/* end */
+};
+
+MODULE_DEVICE_TABLE(pnp, snd_audiodrive_pnpbiosids);
+
+/* PnP main device initialization */
+static int snd_audiodrive_pnp_init_main(int dev, struct pnp_dev *pdev)
+{
+	if (pnp_activate_dev(pdev) < 0) {
+		snd_printk(KERN_ERR PFX "PnP configure failure (out of resources?)\n");
+		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();
+	}
+	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]);
+	return 0;
+}
+
+static int snd_audiodrive_pnp(int dev, struct snd_es18xx *chip,
+			      struct pnp_dev *pdev)
+{
+	chip->dev = pdev;
+	if (snd_audiodrive_pnp_init_main(dev, chip->dev) < 0)
+		return -EBUSY;
+	return 0;
+}
+
+static const 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 snd_audiodrive_pnpc(int dev, struct snd_es18xx *chip,
+			       struct pnp_card_link *card,
+			       const struct pnp_card_device_id *id)
+{
+	chip->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (chip->dev == NULL)
+		return -EBUSY;
+
+	chip->devc = pnp_request_card_device(card, id->devs[1].id, NULL);
+	if (chip->devc == NULL)
+		return -EBUSY;
+
+	/* Control port initialization */
+	if (pnp_activate_dev(chip->devc) < 0) {
+		snd_printk(KERN_ERR PFX "PnP control configure failure (out of resources?)\n");
+		return -EAGAIN;
+	}
+	snd_printdd("pnp: port=0x%llx\n",
+			(unsigned long long)pnp_port_start(chip->devc, 0));
+	if (snd_audiodrive_pnp_init_main(dev, chip->dev) < 0)
+		return -EBUSY;
+
+	return 0;
+}
+#endif /* CONFIG_PNP */
+
+#ifdef CONFIG_PNP
+#define is_isapnp_selected(dev)		isapnp[dev]
+#else
+#define is_isapnp_selected(dev)		0
+#endif
+
+static int snd_es18xx_card_new(struct device *pdev, int dev,
+			       struct snd_card **cardp)
+{
+	return snd_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+			    sizeof(struct snd_es18xx), cardp);
+}
+
+static int snd_audiodrive_probe(struct snd_card *card, int dev)
+{
+	struct snd_es18xx *chip = card->private_data;
+	struct snd_opl3 *opl3;
+	int err;
+
+	err = snd_es18xx_new_device(card,
+				    port[dev], mpu_port[dev], fm_port[dev],
+				    irq[dev], dma1[dev], dma2[dev]);
+	if (err < 0)
+		return err;
+
+	sprintf(card->driver, "ES%x", chip->version);
+	
+	sprintf(card->shortname, "ESS AudioDrive ES%x", chip->version);
+	if (dma1[dev] != dma2[dev])
+		sprintf(card->longname, "%s at 0x%lx, irq %d, dma1 %d, dma2 %d",
+			card->shortname,
+			chip->port,
+			irq[dev], dma1[dev], dma2[dev]);
+	else
+		sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+			card->shortname,
+			chip->port,
+			irq[dev], dma1[dev]);
+
+	err = snd_es18xx_pcm(card, 0);
+	if (err < 0)
+		return err;
+
+	err = snd_es18xx_mixer(card);
+	if (err < 0)
+		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, 0, &opl3) < 0) {
+			snd_printk(KERN_WARNING PFX
+				   "opl3 not detected at 0x%lx\n",
+				   fm_port[dev]);
+		} else {
+			err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+			if (err < 0)
+				return err;
+		}
+	}
+
+	if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) {
+		err = snd_mpu401_uart_new(card, 0, MPU401_HW_ES18XX,
+					  mpu_port[dev], MPU401_INFO_IRQ_HOOK,
+					  -1, &chip->rmidi);
+		if (err < 0)
+			return err;
+	}
+
+	return snd_card_register(card);
+}
+
+static int snd_es18xx_isa_match(struct device *pdev, unsigned int dev)
+{
+	return enable[dev] && !is_isapnp_selected(dev);
+}
+
+static int snd_es18xx_isa_probe1(int dev, struct device *devptr)
+{
+	struct snd_card *card;
+	int err;
+
+	err = snd_es18xx_card_new(devptr, dev, &card);
+	if (err < 0)
+		return err;
+	if ((err = snd_audiodrive_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	dev_set_drvdata(devptr, card);
+	return 0;
+}
+
+static int snd_es18xx_isa_probe(struct device *pdev, unsigned int dev)
+{
+	int err;
+	static int possible_irqs[] = {5, 9, 10, 7, 11, 12, -1};
+	static int possible_dmas[] = {1, 0, 3, 5, -1};
+
+	if (irq[dev] == SNDRV_AUTO_IRQ) {
+		if ((irq[dev] = snd_legacy_find_free_irq(possible_irqs)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (dma1[dev] == SNDRV_AUTO_DMA) {
+		if ((dma1[dev] = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free DMA1\n");
+			return -EBUSY;
+		}
+	}
+	if (dma2[dev] == SNDRV_AUTO_DMA) {
+		if ((dma2[dev] = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free DMA2\n");
+			return -EBUSY;
+		}
+	}
+
+	if (port[dev] != SNDRV_AUTO_PORT) {
+		return snd_es18xx_isa_probe1(dev, pdev);
+	} else {
+		static unsigned long possible_ports[] = {0x220, 0x240, 0x260, 0x280};
+		int i;
+		for (i = 0; i < ARRAY_SIZE(possible_ports); i++) {
+			port[dev] = possible_ports[i];
+			err = snd_es18xx_isa_probe1(dev, pdev);
+			if (! err)
+				return 0;
+		}
+		return err;
+	}
+}
+
+static int snd_es18xx_isa_remove(struct device *devptr,
+				 unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(devptr));
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_es18xx_isa_suspend(struct device *dev, unsigned int n,
+				  pm_message_t state)
+{
+	return snd_es18xx_suspend(dev_get_drvdata(dev), state);
+}
+
+static int snd_es18xx_isa_resume(struct device *dev, unsigned int n)
+{
+	return snd_es18xx_resume(dev_get_drvdata(dev));
+}
+#endif
+
+#define DEV_NAME "es18xx"
+
+static struct isa_driver snd_es18xx_isa_driver = {
+	.match		= snd_es18xx_isa_match,
+	.probe		= snd_es18xx_isa_probe,
+	.remove		= snd_es18xx_isa_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_es18xx_isa_suspend,
+	.resume		= snd_es18xx_isa_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+
+#ifdef CONFIG_PNP
+static int snd_audiodrive_pnp_detect(struct pnp_dev *pdev,
+				     const struct pnp_device_id *id)
+{
+	static int dev;
+	int err;
+	struct snd_card *card;
+
+	if (pnp_device_is_isapnp(pdev))
+		return -ENOENT;	/* we have another procedure - card */
+	for (; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+
+	err = snd_es18xx_card_new(&pdev->dev, dev, &card);
+	if (err < 0)
+		return err;
+	if ((err = snd_audiodrive_pnp(dev, card->private_data, pdev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_audiodrive_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pnp_set_drvdata(pdev, card);
+	dev++;
+	return 0;
+}
+
+static void snd_audiodrive_pnp_remove(struct pnp_dev *pdev)
+{
+	snd_card_free(pnp_get_drvdata(pdev));
+}
+
+#ifdef CONFIG_PM
+static int snd_audiodrive_pnp_suspend(struct pnp_dev *pdev, pm_message_t state)
+{
+	return snd_es18xx_suspend(pnp_get_drvdata(pdev), state);
+}
+static int snd_audiodrive_pnp_resume(struct pnp_dev *pdev)
+{
+	return snd_es18xx_resume(pnp_get_drvdata(pdev));
+}
+#endif
+
+static struct pnp_driver es18xx_pnp_driver = {
+	.name = "es18xx-pnpbios",
+	.id_table = snd_audiodrive_pnpbiosids,
+	.probe = snd_audiodrive_pnp_detect,
+	.remove = snd_audiodrive_pnp_remove,
+#ifdef CONFIG_PM
+	.suspend = snd_audiodrive_pnp_suspend,
+	.resume = snd_audiodrive_pnp_resume,
+#endif
+};
+
+static int snd_audiodrive_pnpc_detect(struct pnp_card_link *pcard,
+				      const struct pnp_card_device_id *pid)
+{
+	static int dev;
+	struct snd_card *card;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+
+	res = snd_es18xx_card_new(&pcard->card->dev, dev, &card);
+	if (res < 0)
+		return res;
+
+	if ((res = snd_audiodrive_pnpc(dev, card->private_data, pcard, pid)) < 0) {
+		snd_card_free(card);
+		return res;
+	}
+	if ((res = snd_audiodrive_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return res;
+	}
+
+	pnp_set_card_drvdata(pcard, card);
+	dev++;
+	return 0;
+}
+
+static void snd_audiodrive_pnpc_remove(struct pnp_card_link *pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_audiodrive_pnpc_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+	return snd_es18xx_suspend(pnp_get_card_drvdata(pcard), state);
+}
+
+static int snd_audiodrive_pnpc_resume(struct pnp_card_link *pcard)
+{
+	return snd_es18xx_resume(pnp_get_card_drvdata(pcard));
+}
+
+#endif
+
+static struct pnp_card_driver es18xx_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DISABLE,
+	.name = "es18xx",
+	.id_table = snd_audiodrive_pnpids,
+	.probe = snd_audiodrive_pnpc_detect,
+	.remove = snd_audiodrive_pnpc_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_audiodrive_pnpc_suspend,
+	.resume		= snd_audiodrive_pnpc_resume,
+#endif
+};
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_es18xx_init(void)
+{
+	int err;
+
+	err = isa_register_driver(&snd_es18xx_isa_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+	if (!err)
+		isa_registered = 1;
+
+	err = pnp_register_driver(&es18xx_pnp_driver);
+	if (!err)
+		pnp_registered = 1;
+
+	err = pnp_register_card_driver(&es18xx_pnpc_driver);
+	if (!err)
+		pnpc_registered = 1;
+
+	if (isa_registered || pnp_registered)
+		err = 0;
+#endif
+	return err;
+}
+
+static void __exit alsa_card_es18xx_exit(void)
+{
+#ifdef CONFIG_PNP
+	if (pnpc_registered)
+		pnp_unregister_card_driver(&es18xx_pnpc_driver);
+	if (pnp_registered)
+		pnp_unregister_driver(&es18xx_pnp_driver);
+	if (isa_registered)
+#endif
+		isa_unregister_driver(&snd_es18xx_isa_driver);
+}
+
+module_init(alsa_card_es18xx_init)
+module_exit(alsa_card_es18xx_exit)
diff --git a/sound/isa/galaxy/Makefile b/sound/isa/galaxy/Makefile
new file mode 100644
index 0000000..e307066
--- /dev/null
+++ b/sound/isa/galaxy/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-azt1605-objs := azt1605.o
+snd-azt2316-objs := azt2316.o
+
+obj-$(CONFIG_SND_AZT1605) += snd-azt1605.o
+obj-$(CONFIG_SND_AZT2316) += snd-azt2316.o
diff --git a/sound/isa/galaxy/azt1605.c b/sound/isa/galaxy/azt1605.c
new file mode 100644
index 0000000..9a97643
--- /dev/null
+++ b/sound/isa/galaxy/azt1605.c
@@ -0,0 +1,91 @@
+/*
+ * Aztech AZT1605 Driver
+ * Copyright (C) 2007,2010  Rene Herman
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#define AZT1605
+
+#define CRD_NAME "Aztech AZT1605"
+#define DRV_NAME "AZT1605"
+#define DEV_NAME "azt1605"
+
+#define GALAXY_DSP_MAJOR		2
+#define GALAXY_DSP_MINOR		1
+
+#define GALAXY_CONFIG_SIZE		3
+
+/*
+ * 24-bit config register
+ */
+
+#define GALAXY_CONFIG_SBA_220		(0 << 0)
+#define GALAXY_CONFIG_SBA_240		(1 << 0)
+#define GALAXY_CONFIG_SBA_260		(2 << 0)
+#define GALAXY_CONFIG_SBA_280		(3 << 0)
+#define GALAXY_CONFIG_SBA_MASK		GALAXY_CONFIG_SBA_280
+
+#define GALAXY_CONFIG_MPUA_300		(0 << 2)
+#define GALAXY_CONFIG_MPUA_330		(1 << 2)
+
+#define GALAXY_CONFIG_MPU_ENABLE	(1 << 3)
+
+#define GALAXY_CONFIG_GAME_ENABLE	(1 << 4)
+
+#define GALAXY_CONFIG_CD_PANASONIC	(1 << 5)
+#define GALAXY_CONFIG_CD_MITSUMI	(1 << 6)
+#define GALAXY_CONFIG_CD_MASK		(\
+	GALAXY_CONFIG_CD_PANASONIC | GALAXY_CONFIG_CD_MITSUMI)
+
+#define GALAXY_CONFIG_UNUSED		(1 << 7)
+#define GALAXY_CONFIG_UNUSED_MASK	GALAXY_CONFIG_UNUSED
+
+#define GALAXY_CONFIG_SBIRQ_2		(1 << 8)
+#define GALAXY_CONFIG_SBIRQ_3		(1 << 9)
+#define GALAXY_CONFIG_SBIRQ_5		(1 << 10)
+#define GALAXY_CONFIG_SBIRQ_7		(1 << 11)
+
+#define GALAXY_CONFIG_MPUIRQ_2		(1 << 12)
+#define GALAXY_CONFIG_MPUIRQ_3		(1 << 13)
+#define GALAXY_CONFIG_MPUIRQ_5		(1 << 14)
+#define GALAXY_CONFIG_MPUIRQ_7		(1 << 15)
+
+#define GALAXY_CONFIG_WSSA_530		(0 << 16)
+#define GALAXY_CONFIG_WSSA_604		(1 << 16)
+#define GALAXY_CONFIG_WSSA_E80		(2 << 16)
+#define GALAXY_CONFIG_WSSA_F40		(3 << 16)
+
+#define GALAXY_CONFIG_WSS_ENABLE	(1 << 18)
+
+#define GALAXY_CONFIG_CDIRQ_11		(1 << 19)
+#define GALAXY_CONFIG_CDIRQ_12		(1 << 20)
+#define GALAXY_CONFIG_CDIRQ_15		(1 << 21)
+#define GALAXY_CONFIG_CDIRQ_MASK	(\
+	GALAXY_CONFIG_CDIRQ_11 | GALAXY_CONFIG_CDIRQ_12 |\
+	GALAXY_CONFIG_CDIRQ_15)
+
+#define GALAXY_CONFIG_CDDMA_DISABLE	(0 << 22)
+#define GALAXY_CONFIG_CDDMA_0		(1 << 22)
+#define GALAXY_CONFIG_CDDMA_1		(2 << 22)
+#define GALAXY_CONFIG_CDDMA_3		(3 << 22)
+#define GALAXY_CONFIG_CDDMA_MASK	GALAXY_CONFIG_CDDMA_3
+
+#define GALAXY_CONFIG_MASK		(\
+	GALAXY_CONFIG_SBA_MASK | GALAXY_CONFIG_CD_MASK |\
+	GALAXY_CONFIG_UNUSED_MASK | GALAXY_CONFIG_CDIRQ_MASK |\
+	GALAXY_CONFIG_CDDMA_MASK)
+
+#include "galaxy.c"
diff --git a/sound/isa/galaxy/azt2316.c b/sound/isa/galaxy/azt2316.c
new file mode 100644
index 0000000..1894411
--- /dev/null
+++ b/sound/isa/galaxy/azt2316.c
@@ -0,0 +1,111 @@
+/*
+ * Aztech AZT2316 Driver
+ * Copyright (C) 2007,2010  Rene Herman
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#define AZT2316
+
+#define CRD_NAME "Aztech AZT2316"
+#define DRV_NAME "AZT2316"
+#define DEV_NAME "azt2316"
+
+#define GALAXY_DSP_MAJOR		3
+#define GALAXY_DSP_MINOR		1
+
+#define GALAXY_CONFIG_SIZE		4
+
+/*
+ * 32-bit config register
+ */
+
+#define GALAXY_CONFIG_SBA_220		(0 << 0)
+#define GALAXY_CONFIG_SBA_240		(1 << 0)
+#define GALAXY_CONFIG_SBA_260		(2 << 0)
+#define GALAXY_CONFIG_SBA_280		(3 << 0)
+#define GALAXY_CONFIG_SBA_MASK		GALAXY_CONFIG_SBA_280
+
+#define GALAXY_CONFIG_SBIRQ_2		(1 << 2)
+#define GALAXY_CONFIG_SBIRQ_5		(1 << 3)
+#define GALAXY_CONFIG_SBIRQ_7		(1 << 4)
+#define GALAXY_CONFIG_SBIRQ_10		(1 << 5)
+
+#define GALAXY_CONFIG_SBDMA_DISABLE	(0 << 6)
+#define GALAXY_CONFIG_SBDMA_0		(1 << 6)
+#define GALAXY_CONFIG_SBDMA_1		(2 << 6)
+#define GALAXY_CONFIG_SBDMA_3		(3 << 6)
+
+#define GALAXY_CONFIG_WSSA_530		(0 << 8)
+#define GALAXY_CONFIG_WSSA_604		(1 << 8)
+#define GALAXY_CONFIG_WSSA_E80		(2 << 8)
+#define GALAXY_CONFIG_WSSA_F40		(3 << 8)
+
+#define GALAXY_CONFIG_WSS_ENABLE	(1 << 10)
+
+#define GALAXY_CONFIG_GAME_ENABLE	(1 << 11)
+
+#define GALAXY_CONFIG_MPUA_300		(0 << 12)
+#define GALAXY_CONFIG_MPUA_330		(1 << 12)
+
+#define GALAXY_CONFIG_MPU_ENABLE	(1 << 13)
+
+#define GALAXY_CONFIG_CDA_310		(0 << 14)
+#define GALAXY_CONFIG_CDA_320		(1 << 14)
+#define GALAXY_CONFIG_CDA_340		(2 << 14)
+#define GALAXY_CONFIG_CDA_350		(3 << 14)
+#define GALAXY_CONFIG_CDA_MASK		GALAXY_CONFIG_CDA_350
+
+#define GALAXY_CONFIG_CD_DISABLE	(0 << 16)
+#define GALAXY_CONFIG_CD_PANASONIC	(1 << 16)
+#define GALAXY_CONFIG_CD_SONY		(2 << 16)
+#define GALAXY_CONFIG_CD_MITSUMI	(3 << 16)
+#define GALAXY_CONFIG_CD_AZTECH		(4 << 16)
+#define GALAXY_CONFIG_CD_UNUSED_5	(5 << 16)
+#define GALAXY_CONFIG_CD_UNUSED_6	(6 << 16)
+#define GALAXY_CONFIG_CD_UNUSED_7	(7 << 16)
+#define GALAXY_CONFIG_CD_MASK		GALAXY_CONFIG_CD_UNUSED_7
+
+#define GALAXY_CONFIG_CDDMA8_DISABLE	(0 << 20)
+#define GALAXY_CONFIG_CDDMA8_0		(1 << 20)
+#define GALAXY_CONFIG_CDDMA8_1		(2 << 20)
+#define GALAXY_CONFIG_CDDMA8_3		(3 << 20)
+#define GALAXY_CONFIG_CDDMA8_MASK	GALAXY_CONFIG_CDDMA8_3
+
+#define GALAXY_CONFIG_CDDMA16_DISABLE	(0 << 22)
+#define GALAXY_CONFIG_CDDMA16_5		(1 << 22)
+#define GALAXY_CONFIG_CDDMA16_6		(2 << 22)
+#define GALAXY_CONFIG_CDDMA16_7		(3 << 22)
+#define GALAXY_CONFIG_CDDMA16_MASK	GALAXY_CONFIG_CDDMA16_7
+
+#define GALAXY_CONFIG_MPUIRQ_2		(1 << 24)
+#define GALAXY_CONFIG_MPUIRQ_5		(1 << 25)
+#define GALAXY_CONFIG_MPUIRQ_7		(1 << 26)
+#define GALAXY_CONFIG_MPUIRQ_10		(1 << 27)
+
+#define GALAXY_CONFIG_CDIRQ_5		(1 << 28)
+#define GALAXY_CONFIG_CDIRQ_11		(1 << 29)
+#define GALAXY_CONFIG_CDIRQ_12		(1 << 30)
+#define GALAXY_CONFIG_CDIRQ_15		(1 << 31)
+#define GALAXY_CONFIG_CDIRQ_MASK	(\
+	GALAXY_CONFIG_CDIRQ_5 | GALAXY_CONFIG_CDIRQ_11 |\
+	GALAXY_CONFIG_CDIRQ_12 | GALAXY_CONFIG_CDIRQ_15)
+
+#define GALAXY_CONFIG_MASK		(\
+	GALAXY_CONFIG_SBA_MASK | GALAXY_CONFIG_CDA_MASK |\
+	GALAXY_CONFIG_CD_MASK | GALAXY_CONFIG_CDDMA16_MASK |\
+	GALAXY_CONFIG_CDDMA8_MASK | GALAXY_CONFIG_CDIRQ_MASK)
+
+#include "galaxy.c"
diff --git a/sound/isa/galaxy/galaxy.c b/sound/isa/galaxy/galaxy.c
new file mode 100644
index 0000000..af9eea4
--- /dev/null
+++ b/sound/isa/galaxy/galaxy.c
@@ -0,0 +1,640 @@
+/*
+ * Aztech AZT1605/AZT2316 Driver
+ * Copyright (C) 2007,2010  Rene Herman
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <asm/processor.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Rene Herman");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+module_param_hw_array(wss_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(wss_port, "WSS port # for " CRD_NAME " driver.");
+module_param_hw_array(mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver.");
+module_param_hw_array(fm_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for " CRD_NAME " driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "Playback DMA # for " CRD_NAME " driver.");
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "Capture DMA # for " CRD_NAME " driver.");
+
+/*
+ * Generic SB DSP support routines
+ */
+
+#define DSP_PORT_RESET		0x6
+#define DSP_PORT_READ		0xa
+#define DSP_PORT_COMMAND	0xc
+#define DSP_PORT_STATUS		0xc
+#define DSP_PORT_DATA_AVAIL	0xe
+
+#define DSP_SIGNATURE		0xaa
+
+#define DSP_COMMAND_GET_VERSION	0xe1
+
+static int dsp_get_byte(void __iomem *port, u8 *val)
+{
+	int loops = 1000;
+
+	while (!(ioread8(port + DSP_PORT_DATA_AVAIL) & 0x80)) {
+		if (!loops--)
+			return -EIO;
+		cpu_relax();
+	}
+	*val = ioread8(port + DSP_PORT_READ);
+	return 0;
+}
+
+static int dsp_reset(void __iomem *port)
+{
+	u8 val;
+
+	iowrite8(1, port + DSP_PORT_RESET);
+	udelay(10);
+	iowrite8(0, port + DSP_PORT_RESET);
+
+	if (dsp_get_byte(port, &val) < 0 || val != DSP_SIGNATURE)
+		return -ENODEV;
+
+	return 0;
+}
+
+static int dsp_command(void __iomem *port, u8 cmd)
+{
+	int loops = 1000;
+
+	while (ioread8(port + DSP_PORT_STATUS) & 0x80) {
+		if (!loops--)
+			return -EIO;
+		cpu_relax();
+	}
+	iowrite8(cmd, port + DSP_PORT_COMMAND);
+	return 0;
+}
+
+static int dsp_get_version(void __iomem *port, u8 *major, u8 *minor)
+{
+	int err;
+
+	err = dsp_command(port, DSP_COMMAND_GET_VERSION);
+	if (err < 0)
+		return err;
+
+	err = dsp_get_byte(port, major);
+	if (err < 0)
+		return err;
+
+	err = dsp_get_byte(port, minor);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+/*
+ * Generic WSS support routines
+ */
+
+#define WSS_CONFIG_DMA_0	(1 << 0)
+#define WSS_CONFIG_DMA_1	(2 << 0)
+#define WSS_CONFIG_DMA_3	(3 << 0)
+#define WSS_CONFIG_DUPLEX	(1 << 2)
+#define WSS_CONFIG_IRQ_7	(1 << 3)
+#define WSS_CONFIG_IRQ_9	(2 << 3)
+#define WSS_CONFIG_IRQ_10	(3 << 3)
+#define WSS_CONFIG_IRQ_11	(4 << 3)
+
+#define WSS_PORT_CONFIG		0
+#define WSS_PORT_SIGNATURE	3
+
+#define WSS_SIGNATURE		4
+
+static int wss_detect(void __iomem *wss_port)
+{
+	if ((ioread8(wss_port + WSS_PORT_SIGNATURE) & 0x3f) != WSS_SIGNATURE)
+		return -ENODEV;
+
+	return 0;
+}
+
+static void wss_set_config(void __iomem *wss_port, u8 wss_config)
+{
+	iowrite8(wss_config, wss_port + WSS_PORT_CONFIG);
+}
+
+/*
+ * Aztech Sound Galaxy specifics
+ */
+
+#define GALAXY_PORT_CONFIG	1024
+#define CONFIG_PORT_SET		4
+
+#define DSP_COMMAND_GALAXY_8	8
+#define GALAXY_COMMAND_GET_TYPE	5
+
+#define DSP_COMMAND_GALAXY_9	9
+#define GALAXY_COMMAND_WSSMODE	0
+#define GALAXY_COMMAND_SB8MODE	1
+
+#define GALAXY_MODE_WSS		GALAXY_COMMAND_WSSMODE
+#define GALAXY_MODE_SB8		GALAXY_COMMAND_SB8MODE
+
+struct snd_galaxy {
+	void __iomem *port;
+	void __iomem *config_port;
+	void __iomem *wss_port;
+	u32 config;
+	struct resource *res_port;
+	struct resource *res_config_port;
+	struct resource *res_wss_port;
+};
+
+static u32 config[SNDRV_CARDS];
+static u8 wss_config[SNDRV_CARDS];
+
+static int snd_galaxy_match(struct device *dev, unsigned int n)
+{
+	if (!enable[n])
+		return 0;
+
+	switch (port[n]) {
+	case SNDRV_AUTO_PORT:
+		dev_err(dev, "please specify port\n");
+		return 0;
+	case 0x220:
+		config[n] |= GALAXY_CONFIG_SBA_220;
+		break;
+	case 0x240:
+		config[n] |= GALAXY_CONFIG_SBA_240;
+		break;
+	case 0x260:
+		config[n] |= GALAXY_CONFIG_SBA_260;
+		break;
+	case 0x280:
+		config[n] |= GALAXY_CONFIG_SBA_280;
+		break;
+	default:
+		dev_err(dev, "invalid port %#lx\n", port[n]);
+		return 0;
+	}
+
+	switch (wss_port[n]) {
+	case SNDRV_AUTO_PORT:
+		dev_err(dev,  "please specify wss_port\n");
+		return 0;
+	case 0x530:
+		config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_530;
+		break;
+	case 0x604:
+		config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_604;
+		break;
+	case 0xe80:
+		config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_E80;
+		break;
+	case 0xf40:
+		config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_F40;
+		break;
+	default:
+		dev_err(dev, "invalid WSS port %#lx\n", wss_port[n]);
+		return 0;
+	}
+
+	switch (irq[n]) {
+	case SNDRV_AUTO_IRQ:
+		dev_err(dev,  "please specify irq\n");
+		return 0;
+	case 7:
+		wss_config[n] |= WSS_CONFIG_IRQ_7;
+		break;
+	case 2:
+		irq[n] = 9;
+		/* Fall through */
+	case 9:
+		wss_config[n] |= WSS_CONFIG_IRQ_9;
+		break;
+	case 10:
+		wss_config[n] |= WSS_CONFIG_IRQ_10;
+		break;
+	case 11:
+		wss_config[n] |= WSS_CONFIG_IRQ_11;
+		break;
+	default:
+		dev_err(dev, "invalid IRQ %d\n", irq[n]);
+		return 0;
+	}
+
+	switch (dma1[n]) {
+	case SNDRV_AUTO_DMA:
+		dev_err(dev,  "please specify dma1\n");
+		return 0;
+	case 0:
+		wss_config[n] |= WSS_CONFIG_DMA_0;
+		break;
+	case 1:
+		wss_config[n] |= WSS_CONFIG_DMA_1;
+		break;
+	case 3:
+		wss_config[n] |= WSS_CONFIG_DMA_3;
+		break;
+	default:
+		dev_err(dev, "invalid playback DMA %d\n", dma1[n]);
+		return 0;
+	}
+
+	if (dma2[n] == SNDRV_AUTO_DMA || dma2[n] == dma1[n]) {
+		dma2[n] = -1;
+		goto mpu;
+	}
+
+	wss_config[n] |= WSS_CONFIG_DUPLEX;
+	switch (dma2[n]) {
+	case 0:
+		break;
+	case 1:
+		if (dma1[n] == 0)
+			break;
+		/* Fall through */
+	default:
+		dev_err(dev, "invalid capture DMA %d\n", dma2[n]);
+		return 0;
+	}
+
+mpu:
+	switch (mpu_port[n]) {
+	case SNDRV_AUTO_PORT:
+		dev_warn(dev, "mpu_port not specified; not using MPU-401\n");
+		mpu_port[n] = -1;
+		goto fm;
+	case 0x300:
+		config[n] |= GALAXY_CONFIG_MPU_ENABLE | GALAXY_CONFIG_MPUA_300;
+		break;
+	case 0x330:
+		config[n] |= GALAXY_CONFIG_MPU_ENABLE | GALAXY_CONFIG_MPUA_330;
+		break;
+	default:
+		dev_err(dev, "invalid MPU port %#lx\n", mpu_port[n]);
+		return 0;
+	}
+
+	switch (mpu_irq[n]) {
+	case SNDRV_AUTO_IRQ:
+		dev_warn(dev, "mpu_irq not specified: using polling mode\n");
+		mpu_irq[n] = -1;
+		break;
+	case 2:
+		mpu_irq[n] = 9;
+		/* Fall through */
+	case 9:
+		config[n] |= GALAXY_CONFIG_MPUIRQ_2;
+		break;
+#ifdef AZT1605
+	case 3:
+		config[n] |= GALAXY_CONFIG_MPUIRQ_3;
+		break;
+#endif
+	case 5:
+		config[n] |= GALAXY_CONFIG_MPUIRQ_5;
+		break;
+	case 7:
+		config[n] |= GALAXY_CONFIG_MPUIRQ_7;
+		break;
+#ifdef AZT2316
+	case 10:
+		config[n] |= GALAXY_CONFIG_MPUIRQ_10;
+		break;
+#endif
+	default:
+		dev_err(dev, "invalid MPU IRQ %d\n", mpu_irq[n]);
+		return 0;
+	}
+
+	if (mpu_irq[n] == irq[n]) {
+		dev_err(dev, "cannot share IRQ between WSS and MPU-401\n");
+		return 0;
+	}
+
+fm:
+	switch (fm_port[n]) {
+	case SNDRV_AUTO_PORT:
+		dev_warn(dev, "fm_port not specified: not using OPL3\n");
+		fm_port[n] = -1;
+		break;
+	case 0x388:
+		break;
+	default:
+		dev_err(dev, "illegal FM port %#lx\n", fm_port[n]);
+		return 0;
+	}
+
+	config[n] |= GALAXY_CONFIG_GAME_ENABLE;
+	return 1;
+}
+
+static int galaxy_init(struct snd_galaxy *galaxy, u8 *type)
+{
+	u8 major;
+	u8 minor;
+	int err;
+
+	err = dsp_reset(galaxy->port);
+	if (err < 0)
+		return err;
+
+	err = dsp_get_version(galaxy->port, &major, &minor);
+	if (err < 0)
+		return err;
+
+	if (major != GALAXY_DSP_MAJOR || minor != GALAXY_DSP_MINOR)
+		return -ENODEV;
+
+	err = dsp_command(galaxy->port, DSP_COMMAND_GALAXY_8);
+	if (err < 0)
+		return err;
+
+	err = dsp_command(galaxy->port, GALAXY_COMMAND_GET_TYPE);
+	if (err < 0)
+		return err;
+
+	err = dsp_get_byte(galaxy->port, type);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int galaxy_set_mode(struct snd_galaxy *galaxy, u8 mode)
+{
+	int err;
+
+	err = dsp_command(galaxy->port, DSP_COMMAND_GALAXY_9);
+	if (err < 0)
+		return err;
+
+	err = dsp_command(galaxy->port, mode);
+	if (err < 0)
+		return err;
+
+#ifdef AZT1605
+	/*
+	 * Needed for MPU IRQ on AZT1605, but AZT2316 loses WSS again
+	 */
+	err = dsp_reset(galaxy->port);
+	if (err < 0)
+		return err;
+#endif
+
+	return 0;
+}
+
+static void galaxy_set_config(struct snd_galaxy *galaxy, u32 config)
+{
+	u8 tmp = ioread8(galaxy->config_port + CONFIG_PORT_SET);
+	int i;
+
+	iowrite8(tmp | 0x80, galaxy->config_port + CONFIG_PORT_SET);
+	for (i = 0; i < GALAXY_CONFIG_SIZE; i++) {
+		iowrite8(config, galaxy->config_port + i);
+		config >>= 8;
+	}
+	iowrite8(tmp & 0x7f, galaxy->config_port + CONFIG_PORT_SET);
+	msleep(10);
+}
+
+static void galaxy_config(struct snd_galaxy *galaxy, u32 config)
+{
+	int i;
+
+	for (i = GALAXY_CONFIG_SIZE; i; i--) {
+		u8 tmp = ioread8(galaxy->config_port + i - 1);
+		galaxy->config = (galaxy->config << 8) | tmp;
+	}
+	config |= galaxy->config & GALAXY_CONFIG_MASK;
+	galaxy_set_config(galaxy, config);
+}
+
+static int galaxy_wss_config(struct snd_galaxy *galaxy, u8 wss_config)
+{
+	int err;
+
+	err = wss_detect(galaxy->wss_port);
+	if (err < 0)
+		return err;
+
+	wss_set_config(galaxy->wss_port, wss_config);
+
+	err = galaxy_set_mode(galaxy, GALAXY_MODE_WSS);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static void snd_galaxy_free(struct snd_card *card)
+{
+	struct snd_galaxy *galaxy = card->private_data;
+
+	if (galaxy->wss_port) {
+		wss_set_config(galaxy->wss_port, 0);
+		ioport_unmap(galaxy->wss_port);
+		release_and_free_resource(galaxy->res_wss_port);
+	}
+	if (galaxy->config_port) {
+		galaxy_set_config(galaxy, galaxy->config);
+		ioport_unmap(galaxy->config_port);
+		release_and_free_resource(galaxy->res_config_port);
+	}
+	if (galaxy->port) {
+		ioport_unmap(galaxy->port);
+		release_and_free_resource(galaxy->res_port);
+	}
+}
+
+static int snd_galaxy_probe(struct device *dev, unsigned int n)
+{
+	struct snd_galaxy *galaxy;
+	struct snd_wss *chip;
+	struct snd_card *card;
+	u8 type;
+	int err;
+
+	err = snd_card_new(dev, index[n], id[n], THIS_MODULE,
+			   sizeof(*galaxy), &card);
+	if (err < 0)
+		return err;
+
+	card->private_free = snd_galaxy_free;
+	galaxy = card->private_data;
+
+	galaxy->res_port = request_region(port[n], 16, DRV_NAME);
+	if (!galaxy->res_port) {
+		dev_err(dev, "could not grab ports %#lx-%#lx\n", port[n],
+			port[n] + 15);
+		err = -EBUSY;
+		goto error;
+	}
+	galaxy->port = ioport_map(port[n], 16);
+
+	err = galaxy_init(galaxy, &type);
+	if (err < 0) {
+		dev_err(dev, "did not find a Sound Galaxy at %#lx\n", port[n]);
+		goto error;
+	}
+	dev_info(dev, "Sound Galaxy (type %d) found at %#lx\n", type, port[n]);
+
+	galaxy->res_config_port = request_region(port[n] + GALAXY_PORT_CONFIG,
+						 16, DRV_NAME);
+	if (!galaxy->res_config_port) {
+		dev_err(dev, "could not grab ports %#lx-%#lx\n",
+			port[n] + GALAXY_PORT_CONFIG,
+			port[n] + GALAXY_PORT_CONFIG + 15);
+		err = -EBUSY;
+		goto error;
+	}
+	galaxy->config_port = ioport_map(port[n] + GALAXY_PORT_CONFIG, 16);
+
+	galaxy_config(galaxy, config[n]);
+
+	galaxy->res_wss_port = request_region(wss_port[n], 4, DRV_NAME);
+	if (!galaxy->res_wss_port)  {
+		dev_err(dev, "could not grab ports %#lx-%#lx\n", wss_port[n],
+			wss_port[n] + 3);
+		err = -EBUSY;
+		goto error;
+	}
+	galaxy->wss_port = ioport_map(wss_port[n], 4);
+
+	err = galaxy_wss_config(galaxy, wss_config[n]);
+	if (err < 0) {
+		dev_err(dev, "could not configure WSS\n");
+		goto error;
+	}
+
+	strcpy(card->driver, DRV_NAME);
+	strcpy(card->shortname, DRV_NAME);
+	sprintf(card->longname, "%s at %#lx/%#lx, irq %d, dma %d/%d",
+		card->shortname, port[n], wss_port[n], irq[n], dma1[n],
+		dma2[n]);
+
+	err = snd_wss_create(card, wss_port[n] + 4, -1, irq[n], dma1[n],
+			     dma2[n], WSS_HW_DETECT, 0, &chip);
+	if (err < 0)
+		goto error;
+
+	err = snd_wss_pcm(chip, 0);
+	if (err < 0)
+		goto error;
+
+	err = snd_wss_mixer(chip);
+	if (err < 0)
+		goto error;
+
+	err = snd_wss_timer(chip, 0);
+	if (err < 0)
+		goto error;
+
+	if (mpu_port[n] >= 0) {
+		err = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+					  mpu_port[n], 0, mpu_irq[n], NULL);
+		if (err < 0)
+			goto error;
+	}
+
+	if (fm_port[n] >= 0) {
+		struct snd_opl3 *opl3;
+
+		err = snd_opl3_create(card, fm_port[n], fm_port[n] + 2,
+				      OPL3_HW_AUTO, 0, &opl3);
+		if (err < 0) {
+			dev_err(dev, "no OPL device at %#lx\n", fm_port[n]);
+			goto error;
+		}
+		err = snd_opl3_timer_new(opl3, 1, 2);
+		if (err < 0)
+			goto error;
+
+		err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+		if (err < 0)
+			goto error;
+	}
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto error;
+
+	dev_set_drvdata(dev, card);
+	return 0;
+
+error:
+	snd_card_free(card);
+	return err;
+}
+
+static int snd_galaxy_remove(struct device *dev, unsigned int n)
+{
+	snd_card_free(dev_get_drvdata(dev));
+	return 0;
+}
+
+static struct isa_driver snd_galaxy_driver = {
+	.match		= snd_galaxy_match,
+	.probe		= snd_galaxy_probe,
+	.remove		= snd_galaxy_remove,
+
+	.driver		= {
+		.name	= DEV_NAME
+	}
+};
+
+module_isa_driver(snd_galaxy_driver, SNDRV_CARDS);
diff --git a/sound/isa/gus/Makefile b/sound/isa/gus/Makefile
new file mode 100644
index 0000000..c6f32ff
--- /dev/null
+++ b/sound/isa/gus/Makefile
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.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-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
+
+# 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
diff --git a/sound/isa/gus/gus_dma.c b/sound/isa/gus/gus_dma.c
new file mode 100644
index 0000000..7f95f45
--- /dev/null
+++ b/sound/isa/gus/gus_dma.c
@@ -0,0 +1,249 @@
+/*
+ *  Routines for GF1 DMA control
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <asm/dma.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+static void snd_gf1_dma_ack(struct snd_gus_card * 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(struct snd_gus_card * 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_printdd("dma_transfer: addr=0x%x, buf=0x%lx, count=0x%x\n",
+		    addr, buf_addr, 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(KERN_DEBUG "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 struct snd_gf1_dma_block *snd_gf1_dma_next_block(struct snd_gus_card * gus)
+{
+	struct snd_gf1_dma_block *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(struct snd_gus_card * gus)
+{
+	struct snd_gf1_dma_block *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
+	snd_printd(KERN_DEBUG "program dma (IRQ) - "
+		   "addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n",
+		   block->addr, block->buf_addr, block->count, block->cmd);
+#endif
+}
+
+int snd_gf1_dma_init(struct snd_gus_card * gus)
+{
+	mutex_lock(&gus->dma_mutex);
+	gus->gf1.dma_shared++;
+	if (gus->gf1.dma_shared > 1) {
+		mutex_unlock(&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;
+	mutex_unlock(&gus->dma_mutex);
+	return 0;
+}
+
+int snd_gf1_dma_done(struct snd_gus_card * gus)
+{
+	struct snd_gf1_dma_block *block;
+
+	mutex_lock(&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;
+	}
+	mutex_unlock(&gus->dma_mutex);
+	return 0;
+}
+
+int snd_gf1_dma_transfer_block(struct snd_gus_card * gus,
+			       struct snd_gf1_dma_block * __block,
+			       int atomic,
+			       int synth)
+{
+	unsigned long flags;
+	struct snd_gf1_dma_block *block;
+
+	block = kmalloc(sizeof(*block), atomic ? GFP_ATOMIC : GFP_KERNEL);
+	if (!block)
+		return -ENOMEM;
+
+	*block = *__block;
+	block->next = NULL;
+
+	snd_printdd("addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n",
+		    block->addr, (long) block->buffer, block->count,
+		    block->cmd);
+
+	snd_printdd("gus->gf1.dma_data_pcm_last = 0x%lx\n",
+		    (long)gus->gf1.dma_data_pcm_last);
+	snd_printdd("gus->gf1.dma_data_pcm = 0x%lx\n",
+		    (long)gus->gf1.dma_data_pcm);
+
+	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..fd2e2e2
--- /dev/null
+++ b/sound/isa/gus/gus_dram.c
@@ -0,0 +1,102 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/info.h>
+
+
+static int snd_gus_dram_poke(struct snd_gus_card *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(struct snd_gus_card *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(struct snd_gus_card *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(struct snd_gus_card *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_io.c b/sound/isa/gus/gus_io.c
new file mode 100644
index 0000000..2fd32ef
--- /dev/null
+++ b/sound/isa/gus/gus_io.c
@@ -0,0 +1,540 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/delay.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+void snd_gf1_delay(struct snd_gus_card * 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(struct snd_gus_card * 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(struct snd_gus_card * 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(struct snd_gus_card * gus,
+					    unsigned char reg)
+{
+	outb(reg, gus->gf1.reg_regsel);
+	mb();
+	return inb(gus->gf1.reg_data8);
+}
+
+static inline void __snd_gf1_write16(struct snd_gus_card * 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(struct snd_gus_card * gus,
+					      unsigned char reg)
+{
+	outb(reg, gus->gf1.reg_regsel);
+	mb();
+	return inw(gus->gf1.reg_data16);
+}
+
+static inline void __snd_gf1_adlib_write(struct snd_gus_card * 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(struct snd_gus_card * 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(struct snd_gus_card * 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(struct snd_gus_card * gus, unsigned char reg)
+{
+	__snd_gf1_ctrl_stop(gus, reg);
+}
+
+void snd_gf1_write8(struct snd_gus_card * gus,
+		    unsigned char reg,
+		    unsigned char data)
+{
+	__snd_gf1_write8(gus, reg, data);
+}
+
+unsigned char snd_gf1_look8(struct snd_gus_card * gus, unsigned char reg)
+{
+	return __snd_gf1_look8(gus, reg);
+}
+
+void snd_gf1_write16(struct snd_gus_card * gus,
+		     unsigned char reg,
+		     unsigned int data)
+{
+	__snd_gf1_write16(gus, reg, data);
+}
+
+unsigned short snd_gf1_look16(struct snd_gus_card * gus, unsigned char reg)
+{
+	return __snd_gf1_look16(gus, reg);
+}
+
+void snd_gf1_adlib_write(struct snd_gus_card * gus,
+                         unsigned char reg,
+                         unsigned char data)
+{
+	__snd_gf1_adlib_write(gus, reg, data);
+}
+
+void snd_gf1_write_addr(struct snd_gus_card * 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(struct snd_gus_card * gus,
+                               unsigned char reg,
+                               short w_16bit)
+{
+	return __snd_gf1_read_addr(gus, reg, w_16bit);
+}
+
+/*
+
+ */
+
+void snd_gf1_i_ctrl_stop(struct snd_gus_card * 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(struct snd_gus_card * 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(struct snd_gus_card * 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(struct snd_gus_card * 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(struct snd_gus_card * 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;
+}
+
+#if 0
+
+void snd_gf1_i_adlib_write(struct snd_gus_card * 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(struct snd_gus_card * 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);
+}
+
+#endif  /*  0  */
+
+#ifdef CONFIG_SND_DEBUG
+static unsigned int snd_gf1_i_read_addr(struct snd_gus_card * 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;
+}
+#endif
+
+/*
+
+ */
+
+void snd_gf1_dram_addr(struct snd_gus_card * 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(struct snd_gus_card * 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(struct snd_gus_card * 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;
+}
+
+#if 0
+
+void snd_gf1_pokew(struct snd_gus_card * gus, unsigned int addr, unsigned short data)
+{
+	unsigned long flags;
+
+#ifdef CONFIG_SND_DEBUG
+	if (!gus->interwave)
+		snd_printk(KERN_DEBUG "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(struct snd_gus_card * gus, unsigned int addr)
+{
+	unsigned long flags;
+	unsigned short res;
+
+#ifdef CONFIG_SND_DEBUG
+	if (!gus->interwave)
+		snd_printk(KERN_DEBUG "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(struct snd_gus_card * 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(KERN_DEBUG "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);
+}
+
+#endif  /*  0  */
+
+void snd_gf1_select_active_voices(struct snd_gus_card * 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(struct snd_gus_card * gus)
+{
+	unsigned char mode;
+	int voice, ctrl;
+
+	voice = gus->gf1.active_voice;
+	printk(KERN_INFO " -%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(KERN_INFO " -%i- GF1  frequency              = 0x%x\n", voice, snd_gf1_i_read16(gus, 1));
+	printk(KERN_INFO " -%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(KERN_INFO " -%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(KERN_INFO" -%i- GF1  volume                 = 0x%x\n", voice, snd_gf1_i_read16(gus, 9));
+	printk(KERN_INFO " -%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(KERN_INFO " -%i- GFA1 mode                   = 0x%x\n", voice, mode);
+		if (mode & 0x01) {	/* Effect processor */
+			printk(KERN_INFO " -%i- GFA1 effect address         = 0x%x\n", voice, snd_gf1_i_read_addr(gus, 0x11, ctrl & 4));
+			printk(KERN_INFO " -%i- GFA1 effect volume          = 0x%x\n", voice, snd_gf1_i_read16(gus, 0x16));
+			printk(KERN_INFO " -%i- GFA1 effect volume final    = 0x%x\n", voice, snd_gf1_i_read16(gus, 0x1d));
+			printk(KERN_INFO " -%i- GFA1 effect accumulator     = 0x%x\n", voice, snd_gf1_i_read8(gus, 0x14));
+		}
+		if (mode & 0x20) {
+			printk(KERN_INFO " -%i- GFA1 left offset            = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x13), snd_gf1_i_read16(gus, 0x13) >> 4);
+			printk(KERN_INFO " -%i- GFA1 left offset final      = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x1c), snd_gf1_i_read16(gus, 0x1c) >> 4);
+			printk(KERN_INFO " -%i- GFA1 right offset           = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x0c), snd_gf1_i_read16(gus, 0x0c) >> 4);
+			printk(KERN_INFO " -%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(KERN_INFO " -%i- GF1  pan                    = 0x%x\n", voice, snd_gf1_i_read8(gus, 0x0c));
+	} else
+		printk(KERN_INFO " -%i- GF1  pan                    = 0x%x\n", voice, snd_gf1_i_read8(gus, 0x0c));
+}
+
+#if 0
+
+void snd_gf1_print_global_registers(struct snd_gus_card * gus)
+{
+	unsigned char global_mode = 0x00;
+
+	printk(KERN_INFO " -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(KERN_INFO " -G- GF1 global mode              = 0x%x\n", global_mode);
+	}
+	if (global_mode & 0x02)	/* LFO enabled? */
+		printk(KERN_INFO " -G- GF1 LFO base                 = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_LFO_BASE));
+	printk(KERN_INFO " -G- GF1 voices IRQ read          = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_VOICES_IRQ_READ));
+	printk(KERN_INFO " -G- GF1 DRAM DMA control         = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL));
+	printk(KERN_INFO " -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(KERN_INFO " -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(KERN_INFO " -G- GF1 record DMA control       = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL));
+	printk(KERN_INFO " -G- GF1 DRAM IO 16               = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_DRAM_IO16));
+	if (gus->gf1.enh_mode) {
+		printk(KERN_INFO " -G- GFA1 memory config           = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG));
+		printk(KERN_INFO " -G- GFA1 memory control          = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_MEMORY_CONTROL));
+		printk(KERN_INFO " -G- GFA1 FIFO record base        = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_FIFO_RECORD_BASE_ADDR));
+		printk(KERN_INFO " -G- GFA1 FIFO playback base      = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_FIFO_PLAY_BASE_ADDR));
+		printk(KERN_INFO " -G- GFA1 interleave control      = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_INTERLEAVE));
+	}
+}
+
+void snd_gf1_print_setup_registers(struct snd_gus_card * gus)
+{
+	printk(KERN_INFO " -S- mix control                  = 0x%x\n", inb(GUSP(gus, MIXCNTRLREG)));
+	printk(KERN_INFO " -S- IRQ status                   = 0x%x\n", inb(GUSP(gus, IRQSTAT)));
+	printk(KERN_INFO " -S- timer control                = 0x%x\n", inb(GUSP(gus, TIMERCNTRL)));
+	printk(KERN_INFO " -S- timer data                   = 0x%x\n", inb(GUSP(gus, TIMERDATA)));
+	printk(KERN_INFO " -S- status read                  = 0x%x\n", inb(GUSP(gus, REGCNTRLS)));
+	printk(KERN_INFO " -S- Sound Blaster control        = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL));
+	printk(KERN_INFO " -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(KERN_INFO " -S- reset                        = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET));
+	if (gus->interwave) {
+		printk(KERN_INFO " -S- compatibility                = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_COMPATIBILITY));
+		printk(KERN_INFO " -S- decode control               = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DECODE_CONTROL));
+		printk(KERN_INFO " -S- version number               = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_VERSION_NUMBER));
+		printk(KERN_INFO " -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(KERN_INFO " -S- emulation IRQ                = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_EMULATION_IRQ));
+	}
+}
+
+void snd_gf1_peek_print_block(struct snd_gus_card * 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  /*  0  */
+
+#endif
diff --git a/sound/isa/gus/gus_irq.c b/sound/isa/gus/gus_irq.c
new file mode 100644
index 0000000..2055aff
--- /dev/null
+++ b/sound/isa/gus/gus_irq.c
@@ -0,0 +1,149 @@
+/*
+ *  Routine for IRQ handling from GF1/InterWave chip
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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/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 snd_gus_card * 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(KERN_DEBUG "IRQ: status = 0x%x\n", status); */
+	if (status & 0x02) {
+		STAT_ADD(gus->gf1.interrupt_stat_midi_in);
+		if (gus->gf1.interrupt_handler_midi_in)
+			gus->gf1.interrupt_handler_midi_in(gus);
+	}
+	if (status & 0x01) {
+		STAT_ADD(gus->gf1.interrupt_stat_midi_out);
+		if (gus->gf1.interrupt_handler_midi_out)
+			gus->gf1.interrupt_handler_midi_out(gus);
+	}
+	if (status & (0x20 | 0x40)) {
+		unsigned int already, _current_;
+		unsigned char voice_status, voice;
+		struct snd_gus_voice *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(KERN_DEBUG "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);
+		if (gus->gf1.interrupt_handler_timer1)
+			gus->gf1.interrupt_handler_timer1(gus);
+	}
+	if (status & 0x08) {
+		STAT_ADD(gus->gf1.interrupt_stat_timer2);
+		if (gus->gf1.interrupt_handler_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);
+			if (gus->gf1.interrupt_handler_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);
+			if (gus->gf1.interrupt_handler_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(struct snd_info_entry *entry, 
+				  struct snd_info_buffer *buffer)
+{
+	struct snd_gus_card *gus;
+	struct snd_gus_voice *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(struct snd_gus_card *gus)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(gus->card, "gusirq", &entry))
+		snd_info_set_text_ops(entry, gus, 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..3b8a0c8
--- /dev/null
+++ b/sound/isa/gus/gus_main.c
@@ -0,0 +1,467 @@
+/*
+ *  Routines for Gravis UltraSound soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/control.h>
+
+#include <asm/dma.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Routines for Gravis UltraSound soundcards");
+MODULE_LICENSE("GPL");
+
+static int snd_gus_init_dma_irq(struct snd_gus_card * gus, int latches);
+
+int snd_gus_use_inc(struct snd_gus_card * gus)
+{
+	if (!try_module_get(gus->card->module))
+		return 0;
+	return 1;
+}
+
+void snd_gus_use_dec(struct snd_gus_card * gus)
+{
+	module_put(gus->card->module);
+}
+
+static int snd_gus_joystick_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = gus->joystick_dac & 31;
+	return 0;
+}
+
+static int snd_gus_joystick_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_gus_card *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 const struct snd_kcontrol_new 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(struct snd_gus_card *gus)
+{
+	if (!gus->ace_flag)
+		snd_ctl_add(gus->card, snd_ctl_new1(&snd_gus_joystick_control, gus));
+}
+
+/*
+ *
+ */
+
+static int snd_gus_free(struct snd_gus_card *gus)
+{
+	if (gus->gf1.res_port2 == NULL)
+		goto __hw_end;
+	snd_gf1_stop(gus);
+	snd_gus_init_dma_irq(gus, 0);
+      __hw_end:
+	release_and_free_resource(gus->gf1.res_port1);
+	release_and_free_resource(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(struct snd_device *device)
+{
+	struct snd_gus_card *gus = device->device_data;
+	return snd_gus_free(gus);
+}
+
+int snd_gus_create(struct snd_card *card,
+		   unsigned long port,
+		   int irq, int dma1, int dma2,
+		   int timer_dev,
+		   int voices,
+		   int pcm_channels,
+		   int effect,
+		   struct snd_gus_card **rgus)
+{
+	struct snd_gus_card *gus;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_gus_dev_free,
+	};
+
+	*rgus = NULL;
+	gus = kzalloc(sizeof(*gus), GFP_KERNEL);
+	if (gus == NULL)
+		return -ENOMEM;
+	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);
+	mutex_init(&gus->dma_mutex);
+	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, 0, "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;
+	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(struct snd_gus_card * 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(KERN_ERR "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(struct snd_gus_card * gus, int latches)
+{
+	struct snd_card *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};
+
+	if (snd_BUG_ON(!gus))
+		return -EINVAL;
+	card = gus->card;
+	if (snd_BUG_ON(!card))
+		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 = abs(dma1);
+	dma1 = dmas[dma1 & 7];
+	dma2 = gus->gf1.dma2;
+	dma2 = abs(dma2);
+	dma2 = dmas[dma2 & 7];
+	dma1 |= gus->equal_dma ? 0x40 : (dma2 << 3);
+
+	if ((dma1 & 7) == 0 || (dma2 & 7) == 0) {
+		snd_printk(KERN_ERR "Error! DMA isn't defined.\n");
+		return -EINVAL;
+	}
+	irq = gus->gf1.irq;
+	irq = abs(irq);
+	irq = irqs[irq & 0x0f];
+	if (irq == 0) {
+		snd_printk(KERN_ERR "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(struct snd_gus_card * gus)
+{
+	unsigned long flags;
+	unsigned char val, rev;
+	struct snd_card *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(KERN_ERR "unknown GF1 revision number at 0x%lx - 0x%x (0x%x)\n", gus->gf1.port, rev, val);
+				snd_printk(KERN_ERR "  please - report to <perex@perex.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;
+}
+
+int snd_gus_initialize(struct snd_gus_card *gus)
+{
+	int err;
+
+	if (!gus->interwave) {
+		if ((err = snd_gus_check_version(gus)) < 0) {
+			snd_printk(KERN_ERR "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;
+	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_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);
+  /* 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);
diff --git a/sound/isa/gus/gus_mem.c b/sound/isa/gus/gus_mem.c
new file mode 100644
index 0000000..af888a0
--- /dev/null
+++ b/sound/isa/gus/gus_mem.c
@@ -0,0 +1,351 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/slab.h>
+#include <linux/string.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/info.h>
+
+#ifdef CONFIG_SND_DEBUG
+static void snd_gf1_mem_info_read(struct snd_info_entry *entry, 
+				  struct snd_info_buffer *buffer);
+#endif
+
+void snd_gf1_mem_lock(struct snd_gf1_mem * alloc, int xup)
+{
+	if (!xup) {
+		mutex_lock(&alloc->memory_mutex);
+	} else {
+		mutex_unlock(&alloc->memory_mutex);
+	}
+}
+
+static struct snd_gf1_mem_block *snd_gf1_mem_xalloc(struct snd_gf1_mem * alloc,
+					       struct snd_gf1_mem_block * block)
+{
+	struct snd_gf1_mem_block *pblock, *nblock;
+
+	nblock = kmalloc(sizeof(struct snd_gf1_mem_block), 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;
+			mutex_unlock(&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(struct snd_gf1_mem * alloc, struct snd_gf1_mem_block * block)
+{
+	if (block->share) {	/* ok.. shared block */
+		block->share--;
+		mutex_unlock(&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;
+}
+
+static struct snd_gf1_mem_block *snd_gf1_mem_look(struct snd_gf1_mem * alloc,
+					     unsigned int address)
+{
+	struct snd_gf1_mem_block *block;
+
+	for (block = alloc->first; block; block = block->next) {
+		if (block->ptr == address) {
+			return block;
+		}
+	}
+	return NULL;
+}
+
+static struct snd_gf1_mem_block *snd_gf1_mem_share(struct snd_gf1_mem * alloc,
+					      unsigned int *share_id)
+{
+	struct snd_gf1_mem_block *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(block->share_id)))
+			return block;
+	return NULL;
+}
+
+static int snd_gf1_mem_find(struct snd_gf1_mem * alloc,
+			    struct snd_gf1_mem_block * block,
+			    unsigned int size, int w_16, int align)
+{
+	struct snd_gf1_bank_info *info = w_16 ? alloc->banks_16 : alloc->banks_8;
+	unsigned int idx, boundary;
+	int size1;
+	struct snd_gf1_mem_block *pblock;
+	unsigned int ptr1, ptr2;
+
+	if (w_16 && align < 2)
+		align = 2;
+	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 = ALIGN(pblock->ptr + pblock->size, 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;
+}
+
+struct snd_gf1_mem_block *snd_gf1_mem_alloc(struct snd_gf1_mem * alloc, int owner,
+				       char *name, int size, int w_16, int align,
+				       unsigned int *share_id)
+{
+	struct snd_gf1_mem_block 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(KERN_ERR "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 = kstrdup(name, GFP_KERNEL);
+	nblock = snd_gf1_mem_xalloc(alloc, &block);
+	snd_gf1_mem_lock(alloc, 1);
+	return nblock;
+}
+
+int snd_gf1_mem_free(struct snd_gf1_mem * alloc, unsigned int address)
+{
+	int result;
+	struct snd_gf1_mem_block *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(struct snd_gus_card * gus)
+{
+	struct snd_gf1_mem *alloc;
+	struct snd_gf1_mem_block block;
+#ifdef CONFIG_SND_DEBUG
+	struct snd_info_entry *entry;
+#endif
+
+	alloc = &gus->gf1.mem_alloc;
+	mutex_init(&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 = kstrdup("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 = kstrdup("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, snd_gf1_mem_info_read);
+#endif
+	return 0;
+}
+
+int snd_gf1_mem_done(struct snd_gus_card * gus)
+{
+	struct snd_gf1_mem *alloc;
+	struct snd_gf1_mem_block *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(struct snd_info_entry *entry, 
+				  struct snd_info_buffer *buffer)
+{
+	struct snd_gus_card *gus;
+	struct snd_gf1_mem *alloc;
+	struct snd_gf1_mem_block *block;
+	unsigned int total, used;
+	int i;
+
+	gus = entry->private_data;
+	alloc = &gus->gf1.mem_alloc;
+	mutex_lock(&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);
+	mutex_unlock(&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..2ccb3fa
--- /dev/null
+++ b/sound/isa/gus/gus_mem_proc.c
@@ -0,0 +1,102 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/slab.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/info.h>
+
+struct gus_proc_private {
+	int rom;		/* data are in ROM */
+	unsigned int address;
+	unsigned int size;
+	struct snd_gus_card * gus;
+};
+
+static ssize_t snd_gf1_mem_proc_dump(struct snd_info_entry *entry,
+				     void *file_private_data,
+				     struct file *file, char __user *buf,
+				     size_t count, loff_t pos)
+{
+	struct gus_proc_private *priv = entry->private_data;
+	struct snd_gus_card *gus = priv->gus;
+	int err;
+
+	err = snd_gus_dram_read(gus, buf, pos, count, priv->rom);
+	if (err < 0)
+		return err;
+	return count;
+}			
+
+static void snd_gf1_mem_proc_free(struct snd_info_entry *entry)
+{
+	struct gus_proc_private *priv = entry->private_data;
+	kfree(priv);
+}
+
+static struct snd_info_entry_ops snd_gf1_mem_proc_ops = {
+	.read = snd_gf1_mem_proc_dump,
+};
+
+int snd_gf1_mem_proc_init(struct snd_gus_card * gus)
+{
+	int idx;
+	char name[16];
+	struct gus_proc_private *priv;
+	struct snd_info_entry *entry;
+
+	for (idx = 0; idx < 4; idx++) {
+		if (gus->gf1.mem_alloc.banks_8[idx].size > 0) {
+			priv = kzalloc(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 = kzalloc(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..3b5d9a7
--- /dev/null
+++ b/sound/isa/gus/gus_mixer.c
@@ -0,0 +1,190 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <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) }
+
+#define snd_gf1_info_single	snd_ctl_boolean_mono_info
+
+static int snd_gf1_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_gus_card *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_gus_card *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_gus_card *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int addr = kcontrol->private_value & 0xff;
+	int change;
+	unsigned char val1, val2, oval1, oval2;
+	
+	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))
+		swap(val1, val2);
+	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 struct snd_kcontrol_new 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 struct snd_kcontrol_new 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(struct snd_gus_card * gus)
+{
+	struct snd_card *card;
+	unsigned int idx, max;
+	int err;
+
+	if (snd_BUG_ON(!gus))
+		return -EINVAL;
+	card = gus->card;
+	if (snd_BUG_ON(!card))
+		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..131b289
--- /dev/null
+++ b/sound/isa/gus/gus_pcm.c
@@ -0,0 +1,924 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <asm/dma.h>
+#include <linux/slab.h>
+#include <linux/sched/signal.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)
+
+struct gus_pcm_private {
+	struct snd_gus_card * gus;
+	struct snd_pcm_substream *substream;
+	spinlock_t lock;
+	unsigned int voices;
+	struct snd_gus_voice *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;
+};
+
+static void snd_gf1_pcm_block_change_ack(struct snd_gus_card * gus, void *private_data)
+{
+	struct gus_pcm_private *pcmp = private_data;
+
+	if (pcmp) {
+		atomic_dec(&pcmp->dma_count);
+		wake_up(&pcmp->sleep);
+	}
+}
+
+static int snd_gf1_pcm_block_change(struct snd_pcm_substream *substream,
+				    unsigned int offset,
+				    unsigned int addr,
+				    unsigned int count)
+{
+	struct snd_gf1_dma_block block;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *pcmp = runtime->private_data;
+
+	count += offset & 31;
+	offset &= ~31;
+	/*
+	snd_printk(KERN_DEBUG "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(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *pcmp = runtime->private_data;
+	struct snd_gus_card * 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;
+
+	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(KERN_DEBUG "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(struct snd_gus_card * gus,
+				       struct snd_gus_voice *pvoice)
+{
+	struct gus_pcm_private * pcmp;
+	struct snd_pcm_runtime *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(KERN_DEBUG "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(KERN_DEBUG "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(struct snd_gus_card * gus,
+					 struct snd_gus_voice * pvoice)
+{
+	unsigned short vol;
+	int cvoice;
+	struct gus_pcm_private *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(struct snd_gus_card * gus)
+{
+}
+
+static int snd_gf1_pcm_poke_block(struct snd_gus_card *gus, unsigned char *buf,
+				  unsigned int pos, unsigned int count,
+				  int w16, int invert)
+{
+	unsigned int len;
+	unsigned long flags;
+
+	/*
+	printk(KERN_DEBUG
+	       "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()) {
+			schedule_timeout_interruptible(1);
+			if (signal_pending(current))
+				return -EAGAIN;
+		}
+	}
+	return 0;
+}
+
+static int get_bpos(struct gus_pcm_private *pcmp, int voice, unsigned int pos,
+		    unsigned int len)
+{
+	unsigned int bpos = pos + (voice * (pcmp->dma_size / 2));
+	if (snd_BUG_ON(bpos > pcmp->dma_size))
+		return -EIO;
+	if (snd_BUG_ON(bpos + len > pcmp->dma_size))
+		return -EIO;
+	return bpos;
+}
+
+static int playback_copy_ack(struct snd_pcm_substream *substream,
+			     unsigned int bpos, unsigned int len)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *pcmp = runtime->private_data;
+	struct snd_gus_card *gus = pcmp->gus;
+	int w16, invert;
+
+	if (len > 32)
+		return snd_gf1_pcm_block_change(substream, bpos,
+						pcmp->memory + bpos, len);
+
+	w16 = (snd_pcm_format_width(runtime->format) == 16);
+	invert = snd_pcm_format_unsigned(runtime->format);
+	return snd_gf1_pcm_poke_block(gus, runtime->dma_area + bpos,
+				      pcmp->memory + bpos, len, w16, invert);
+}
+
+static int snd_gf1_pcm_playback_copy(struct snd_pcm_substream *substream,
+				     int voice, unsigned long pos,
+				     void __user *src, unsigned long count)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *pcmp = runtime->private_data;
+	unsigned int len = count;
+	int bpos;
+
+	bpos = get_bpos(pcmp, voice, pos, len);
+	if (bpos < 0)
+		return pos;
+	if (copy_from_user(runtime->dma_area + bpos, src, len))
+		return -EFAULT;
+	return playback_copy_ack(substream, bpos, len);
+}
+
+static int snd_gf1_pcm_playback_copy_kernel(struct snd_pcm_substream *substream,
+					    int voice, unsigned long pos,
+					    void *src, unsigned long count)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *pcmp = runtime->private_data;
+	unsigned int len = count;
+	int bpos;
+
+	bpos = get_bpos(pcmp, voice, pos, len);
+	if (bpos < 0)
+		return pos;
+	memcpy(runtime->dma_area + bpos, src, len);
+	return playback_copy_ack(substream, bpos, len);
+}
+
+static int snd_gf1_pcm_playback_silence(struct snd_pcm_substream *substream,
+					int voice, unsigned long pos,
+					unsigned long count)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *pcmp = runtime->private_data;
+	unsigned int len = count;
+	int bpos;
+	
+	bpos = get_bpos(pcmp, voice, pos, len);
+	if (bpos < 0)
+		return pos;
+	snd_pcm_format_set_silence(runtime->format, runtime->dma_area + bpos,
+				   bytes_to_samples(runtime, count));
+	return playback_copy_ack(substream, bpos, len);
+}
+
+static int snd_gf1_pcm_playback_hw_params(struct snd_pcm_substream *substream,
+					  struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *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 */
+		struct snd_gf1_mem_block *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(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *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(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *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(struct snd_pcm_substream *substream,
+					int cmd)
+{
+	struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *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(struct snd_pcm_substream *substream)
+{
+	struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *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 const struct snd_ratnum clock = {
+	.num = 9878400/16,
+	.den_min = 2,
+	.den_max = 257,
+	.den_step = 1,
+};
+
+static const struct snd_pcm_hw_constraint_ratnums hw_constraints_clocks  = {
+	.nrats = 1,
+	.rats = &clock,
+};
+
+static int snd_gf1_pcm_capture_hw_params(struct snd_pcm_substream *substream,
+					 struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_gus_card *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(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_gf1_pcm_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *substream,
+				       int cmd)
+{
+	struct snd_gus_card *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(struct snd_pcm_substream *substream)
+{
+	struct snd_gus_card *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(struct snd_gus_card * 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 const struct snd_pcm_hardware 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 const struct snd_pcm_hardware 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(struct snd_pcm_runtime *runtime)
+{
+	kfree(runtime->private_data);
+}
+
+static int snd_gf1_pcm_playback_open(struct snd_pcm_substream *substream)
+{
+	struct gus_pcm_private *pcmp;
+	struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	pcmp = kzalloc(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(KERN_DEBUG "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(struct snd_pcm_substream *substream)
+{
+	struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *pcmp = runtime->private_data;
+	
+	if (!wait_event_timeout(pcmp->sleep, (atomic_read(&pcmp->dma_count) <= 0), 2*HZ))
+		snd_printk(KERN_ERR "gf1 pcm - serious DMA problem\n");
+
+	snd_gf1_dma_done(gus);	
+	return 0;
+}
+
+static int snd_gf1_pcm_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_gus_card *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(struct snd_pcm_substream *substream)
+{
+	struct snd_gus_card *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 int snd_gf1_pcm_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_gus_card *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned int idx;
+	unsigned short val1, val2, vol;
+	struct gus_pcm_private *pcmp;
+	struct snd_gus_voice *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(&gus->reg_lock);
+		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(&gus->reg_lock);
+	}
+	spin_unlock_irqrestore(&gus->voice_alloc, flags);
+	return change;
+}
+
+static const struct snd_kcontrol_new 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 const struct snd_kcontrol_new 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 const struct snd_pcm_ops 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_user =	snd_gf1_pcm_playback_copy,
+	.copy_kernel =	snd_gf1_pcm_playback_copy_kernel,
+	.fill_silence =	snd_gf1_pcm_playback_silence,
+};
+
+static const struct snd_pcm_ops 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(struct snd_gus_card *gus, int pcm_dev, int control_index)
+{
+	struct snd_card *card;
+	struct snd_kcontrol *kctl;
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *substream;
+	int capture, err;
+
+	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;
+	/* 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;
+
+	return 0;
+}
+
diff --git a/sound/isa/gus/gus_reset.c b/sound/isa/gus/gus_reset.c
new file mode 100644
index 0000000..59b3f68
--- /dev/null
+++ b/sound/isa/gus/gus_reset.c
@@ -0,0 +1,411 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <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(struct snd_gus_card * gus);
+extern void snd_gf1_timers_done(struct snd_gus_card * gus);
+extern int snd_gf1_synth_init(struct snd_gus_card * gus);
+extern void snd_gf1_synth_done(struct snd_gus_card * gus);
+
+/*
+ *  ok.. default interrupt handlers...
+ */
+
+static void snd_gf1_default_interrupt_handler_midi_out(struct snd_gus_card * gus)
+{
+	snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd &= ~0x20);
+}
+
+static void snd_gf1_default_interrupt_handler_midi_in(struct snd_gus_card * gus)
+{
+	snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd &= ~0x80);
+}
+
+static void snd_gf1_default_interrupt_handler_timer1(struct snd_gus_card * 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(struct snd_gus_card * 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(struct snd_gus_card * gus, struct snd_gus_voice * voice)
+{
+	snd_gf1_i_ctrl_stop(gus, 0x00);
+	snd_gf1_i_ctrl_stop(gus, 0x0d);
+}
+
+static void snd_gf1_default_interrupt_handler_dma_write(struct snd_gus_card * gus)
+{
+	snd_gf1_i_write8(gus, 0x41, 0x00);
+}
+
+static void snd_gf1_default_interrupt_handler_dma_read(struct snd_gus_card * gus)
+{
+	snd_gf1_i_write8(gus, 0x49, 0x00);
+}
+
+void snd_gf1_set_default_handlers(struct snd_gus_card * 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) {
+		struct snd_gus_voice *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(struct snd_gus_card * 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(struct snd_gus_card * 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(struct snd_gus_card * gus, unsigned short voice)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	snd_gf1_select_voice(gus, voice);
+#if 0
+	printk(KERN_DEBUG " -%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(struct snd_gus_card * gus, unsigned short voice)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	snd_gf1_select_voice(gus, voice);
+#if 0
+	printk(KERN_DEBUG " -%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
+}
+
+static void snd_gf1_clear_voices(struct snd_gus_card * 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(struct snd_gus_card * 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(struct snd_gus_card * gus, 
+				    struct snd_gus_voice * 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;
+	}
+}
+
+struct snd_gus_voice *snd_gf1_alloc_voice(struct snd_gus_card * gus, int type, int client, int port)
+{
+	struct snd_gus_voice *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(struct snd_gus_card * gus, struct snd_gus_voice *voice)
+{
+	unsigned long flags;
+	void (*private_free)(struct snd_gus_voice *voice);
+
+	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;
+	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(struct snd_gus_card * 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(struct snd_gus_card * 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_tables.h b/sound/isa/gus/gus_tables.h
new file mode 100644
index 0000000..42a4ca0
--- /dev/null
+++ b/sound/isa/gus/gus_tables.h
@@ -0,0 +1,90 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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__
+
+#if 0
+
+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
+};
+
+#endif  /*  0  */
+
+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..c537271
--- /dev/null
+++ b/sound/isa/gus/gus_timer.c
@@ -0,0 +1,203 @@
+/*
+ *  Routines for Gravis UltraSound soundcards - Timers
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+/*
+ *  Timer 1 - 80us
+ */
+
+static int snd_gf1_timer1_start(struct snd_timer * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	unsigned int ticks;
+	struct snd_gus_card *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(struct snd_timer * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	struct snd_gus_card *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(struct snd_timer * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	unsigned int ticks;
+	struct snd_gus_card *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(struct snd_timer * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	struct snd_gus_card *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(struct snd_gus_card * gus)
+{
+	struct snd_timer *timer = gus->gf1.timer1;
+
+	if (timer == NULL)
+		return;
+	snd_timer_interrupt(timer, timer->sticks);
+}
+
+static void snd_gf1_interrupt_timer2(struct snd_gus_card * gus)
+{
+	struct snd_timer *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(struct snd_timer *timer)
+{
+	struct snd_gus_card *gus = timer->private_data;
+	gus->gf1.timer1 = NULL;
+}
+
+static void snd_gf1_timer2_free(struct snd_timer *timer)
+{
+	struct snd_gus_card *gus = timer->private_data;
+	gus->gf1.timer2 = NULL;
+}
+
+void snd_gf1_timers_init(struct snd_gus_card * gus)
+{
+	struct snd_timer *timer;
+	struct snd_timer_id 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(struct snd_gus_card * 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..ac5f568
--- /dev/null
+++ b/sound/isa/gus/gus_uart.c
@@ -0,0 +1,258 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <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(struct snd_gus_card * 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(struct snd_gus_card * 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(struct snd_gus_card * 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(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_gus_card *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(KERN_DEBUG "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(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_gus_card *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(KERN_ERR "gus midi uart init read - cleanup error\n");
+	}
+	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+#if 0
+	snd_printk(KERN_DEBUG
+		   "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(KERN_DEBUG
+		   "[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(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_gus_card *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(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_gus_card *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(struct snd_rawmidi_substream *substream, int up)
+{
+	struct snd_gus_card *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(struct snd_rawmidi_substream *substream, int up)
+{
+	unsigned long flags;
+	struct snd_gus_card *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 const struct snd_rawmidi_ops snd_gf1_uart_output =
+{
+	.open =		snd_gf1_uart_output_open,
+	.close =	snd_gf1_uart_output_close,
+	.trigger =	snd_gf1_uart_output_trigger,
+};
+
+static const struct snd_rawmidi_ops 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(struct snd_gus_card *gus, int device)
+{
+	struct snd_rawmidi *rmidi;
+	int err;
+
+	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;
+	return err;
+}
diff --git a/sound/isa/gus/gus_volume.c b/sound/isa/gus/gus_volume.c
new file mode 100644
index 0000000..3dd841a
--- /dev/null
+++ b/sound/isa/gus/gus_volume.c
@@ -0,0 +1,218 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/time.h>
+#include <linux/export.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;
+}
+
+#if 0
+
+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(struct snd_gus_card * 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);
+}
+
+#endif  /*  0  */
+
+unsigned short snd_gf1_translate_freq(struct snd_gus_card * gus, unsigned int freq16)
+{
+	freq16 >>= 3;
+	if (freq16 < 50)
+		freq16 = 50;
+	if (freq16 & 0xf8000000) {
+		freq16 = ~0xf8000000;
+		snd_printk(KERN_ERR "snd_gf1_translate_freq: overflow - freq = 0x%x\n", freq16);
+	}
+	return ((freq16 << 9) + (gus->gf1.playback_freq >> 1)) / gus->gf1.playback_freq;
+}
+
+#if 0
+
+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(KERN_ERR "patch: (1) fc frequency overflow - %u\n", fc);
+	}
+	fc = (fc * 44100UL) / mix_rate;
+	while (scale--)
+		fc <<= 1;
+	if (fc > 65535L) {
+		fc = 65535;
+		snd_printk(KERN_ERR "patch: (2) fc frequency overflow - %u\n", fc);
+	}
+	return (unsigned short) fc;
+}
+
+#endif  /*  0  */
diff --git a/sound/isa/gus/gusclassic.c b/sound/isa/gus/gusclassic.c
new file mode 100644
index 0000000..92a997a
--- /dev/null
+++ b/sound/isa/gus/gusclassic.c
@@ -0,0 +1,232 @@
+/*
+ *  Driver for Gravis UltraSound Classic soundcard
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/module.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+#define CRD_NAME "Gravis UltraSound Classic"
+#define DEV_NAME "gusclassic"
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+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 bool 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 " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for " CRD_NAME " driver.");
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for " CRD_NAME " 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 " CRD_NAME " driver.");
+module_param_array(channels, int, NULL, 0444);
+MODULE_PARM_DESC(channels, "GF1 channels for " CRD_NAME " driver.");
+module_param_array(pcm_channels, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for " CRD_NAME " driver.");
+
+static int snd_gusclassic_match(struct device *dev, unsigned int n)
+{
+	return enable[n];
+}
+
+static int snd_gusclassic_create(struct snd_card *card,
+				 struct device *dev, unsigned int n,
+				 struct snd_gus_card **rgus)
+{
+	static long possible_ports[] = {0x220, 0x230, 0x240, 0x250, 0x260};
+	static int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, 4, -1};
+	static int possible_dmas[] = {5, 6, 7, 1, 3, -1};
+
+	int i, error;
+
+	if (irq[n] == SNDRV_AUTO_IRQ) {
+		irq[n] = snd_legacy_find_free_irq(possible_irqs);
+		if (irq[n] < 0) {
+			dev_err(dev, "unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (dma1[n] == SNDRV_AUTO_DMA) {
+		dma1[n] = snd_legacy_find_free_dma(possible_dmas);
+		if (dma1[n] < 0) {
+			dev_err(dev, "unable to find a free DMA1\n");
+			return -EBUSY;
+		}
+	}
+	if (dma2[n] == SNDRV_AUTO_DMA) {
+		dma2[n] = snd_legacy_find_free_dma(possible_dmas);
+		if (dma2[n] < 0) {
+			dev_err(dev, "unable to find a free DMA2\n");
+			return -EBUSY;
+		}
+	}
+
+	if (port[n] != SNDRV_AUTO_PORT)
+		return snd_gus_create(card, port[n], irq[n], dma1[n], dma2[n],
+				0, channels[n], pcm_channels[n], 0, rgus);
+
+	i = 0;
+	do {
+		port[n] = possible_ports[i];
+		error = snd_gus_create(card, port[n], irq[n], dma1[n], dma2[n],
+				0, channels[n], pcm_channels[n], 0, rgus);
+	} while (error < 0 && ++i < ARRAY_SIZE(possible_ports));
+
+	return error;
+}
+
+static int snd_gusclassic_detect(struct snd_gus_card *gus)
+{
+	unsigned char d;
+
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0);	/* reset GF1 */
+	if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) {
+		snd_printdd("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d);
+		return -ENODEV;
+	}
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);	/* release reset */
+	udelay(160);
+	if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) {
+		snd_printdd("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static int snd_gusclassic_probe(struct device *dev, unsigned int n)
+{
+	struct snd_card *card;
+	struct snd_gus_card *gus;
+	int error;
+
+	error = snd_card_new(dev, index[n], id[n], THIS_MODULE, 0, &card);
+	if (error < 0)
+		return error;
+
+	if (pcm_channels[n] < 2)
+		pcm_channels[n] = 2;
+
+	error = snd_gusclassic_create(card, dev, n, &gus);
+	if (error < 0)
+		goto out;
+
+	error = snd_gusclassic_detect(gus);
+	if (error < 0)
+		goto out;
+
+	gus->joystick_dac = joystick_dac[n];
+
+	error = snd_gus_initialize(gus);
+	if (error < 0)
+		goto out;
+
+	error = -ENODEV;
+	if (gus->max_flag || gus->ess_flag) {
+		dev_err(dev, "GUS Classic or ACE soundcard was "
+			"not detected at 0x%lx\n", gus->gf1.port);
+		goto out;
+	}
+
+	error = snd_gf1_new_mixer(gus);
+	if (error < 0)
+		goto out;
+
+	error = snd_gf1_pcm_new(gus, 0, 0);
+	if (error < 0)
+		goto out;
+
+	if (!gus->ace_flag) {
+		error = snd_gf1_rawmidi_new(gus, 0);
+		if (error < 0)
+			goto out;
+	}
+
+	sprintf(card->longname + strlen(card->longname),
+		" at 0x%lx, irq %d, dma %d",
+		gus->gf1.port, gus->gf1.irq, gus->gf1.dma1);
+
+	if (gus->gf1.dma2 >= 0)
+		sprintf(card->longname + strlen(card->longname),
+			"&%d", gus->gf1.dma2);
+
+	error = snd_card_register(card);
+	if (error < 0)
+		goto out;
+
+	dev_set_drvdata(dev, card);
+	return 0;
+
+out:	snd_card_free(card);
+	return error;
+}
+
+static int snd_gusclassic_remove(struct device *dev, unsigned int n)
+{
+	snd_card_free(dev_get_drvdata(dev));
+	return 0;
+}
+
+static struct isa_driver snd_gusclassic_driver = {
+	.match		= snd_gusclassic_match,
+	.probe		= snd_gusclassic_probe,
+	.remove		= snd_gusclassic_remove,
+#if 0	/* FIXME */
+	.suspend	= snd_gusclassic_suspend,
+	.remove		= snd_gusclassic_remove,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	}
+};
+
+module_isa_driver(snd_gusclassic_driver, SNDRV_CARDS);
diff --git a/sound/isa/gus/gusextreme.c b/sound/isa/gus/gusextreme.c
new file mode 100644
index 0000000..beb52c0
--- /dev/null
+++ b/sound/isa/gus/gusextreme.c
@@ -0,0 +1,361 @@
+/*
+ *  Driver for Gravis UltraSound Extreme soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/module.h>
+#include <asm/dma.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>
+
+#define CRD_NAME "Gravis UltraSound Extreme"
+#define DEV_NAME "gusextreme"
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+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 bool 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 " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+module_param_hw_array(gf1_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(gf1_port, "GF1 port # for " CRD_NAME " driver (optional).");
+module_param_hw_array(mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(gf1_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(gf1_irq, "GF1 IRQ # for " CRD_NAME " driver.");
+module_param_hw_array(dma8, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for " CRD_NAME " driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "GF1 DMA # for " CRD_NAME " 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 " CRD_NAME " driver.");
+module_param_array(channels, int, NULL, 0444);
+MODULE_PARM_DESC(channels, "GF1 channels for " CRD_NAME " driver.");
+module_param_array(pcm_channels, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for " CRD_NAME " driver.");
+
+static int snd_gusextreme_match(struct device *dev, unsigned int n)
+{
+	return enable[n];
+}
+
+static int snd_gusextreme_es1688_create(struct snd_card *card,
+					struct snd_es1688 *chip,
+					struct device *dev, unsigned int n)
+{
+	static long possible_ports[] = {0x220, 0x240, 0x260};
+	static int possible_irqs[] = {5, 9, 10, 7, -1};
+	static int possible_dmas[] = {1, 3, 0, -1};
+
+	int i, error;
+
+	if (irq[n] == SNDRV_AUTO_IRQ) {
+		irq[n] = snd_legacy_find_free_irq(possible_irqs);
+		if (irq[n] < 0) {
+			dev_err(dev, "unable to find a free IRQ for ES1688\n");
+			return -EBUSY;
+		}
+	}
+	if (dma8[n] == SNDRV_AUTO_DMA) {
+		dma8[n] = snd_legacy_find_free_dma(possible_dmas);
+		if (dma8[n] < 0) {
+			dev_err(dev, "unable to find a free DMA for ES1688\n");
+			return -EBUSY;
+		}
+	}
+
+	if (port[n] != SNDRV_AUTO_PORT)
+		return snd_es1688_create(card, chip, port[n], mpu_port[n],
+				irq[n], mpu_irq[n], dma8[n], ES1688_HW_1688);
+
+	i = 0;
+	do {
+		port[n] = possible_ports[i];
+		error = snd_es1688_create(card, chip, port[n], mpu_port[n],
+				irq[n], mpu_irq[n], dma8[n], ES1688_HW_1688);
+	} while (error < 0 && ++i < ARRAY_SIZE(possible_ports));
+
+	return error;
+}
+
+static int snd_gusextreme_gus_card_create(struct snd_card *card,
+					  struct device *dev, unsigned int n,
+					  struct snd_gus_card **rgus)
+{
+	static int possible_irqs[] = {11, 12, 15, 9, 5, 7, 3, -1};
+	static int possible_dmas[] = {5, 6, 7, 3, 1, -1};
+
+	if (gf1_irq[n] == SNDRV_AUTO_IRQ) {
+		gf1_irq[n] = snd_legacy_find_free_irq(possible_irqs);
+		if (gf1_irq[n] < 0) {
+			dev_err(dev, "unable to find a free IRQ for GF1\n");
+			return -EBUSY;
+		}
+	}
+	if (dma1[n] == SNDRV_AUTO_DMA) {
+		dma1[n] = snd_legacy_find_free_dma(possible_dmas);
+		if (dma1[n] < 0) {
+			dev_err(dev, "unable to find a free DMA for GF1\n");
+			return -EBUSY;
+		}
+	}
+	return snd_gus_create(card, gf1_port[n], gf1_irq[n], dma1[n], -1,
+			0, channels[n], pcm_channels[n], 0, rgus);
+}
+
+static int snd_gusextreme_detect(struct snd_gus_card *gus,
+				 struct snd_es1688 *es1688)
+{
+	unsigned long flags;
+	unsigned char d;
+
+	/*
+	 * 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(gus->gf1.port & 0x040 ? 2 : 0, ES1688P(es1688, INIT1));
+	outb(0, 0x201);
+	outb(gus->gf1.port & 0x020 ? 2 : 0, ES1688P(es1688, INIT1));
+	outb(0, 0x201);
+	outb(gus->gf1.port & 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 */
+	if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) {
+		snd_printdd("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d);
+		return -EIO;
+	}
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);	/* release reset */
+	udelay(160);
+	if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) {
+		snd_printdd("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int snd_gusextreme_mixer(struct snd_card *card)
+{
+	struct snd_ctl_elem_id id1, id2;
+	int error;
+
+	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");
+	error = snd_ctl_rename_id(card, &id1, &id2);
+	if (error < 0)
+		return error;
+
+	/* reassign Master Playback Switch to Synth Playback Switch */
+	strcpy(id1.name, "Master Playback Switch");
+	strcpy(id2.name, "Synth Playback Switch");
+	error = snd_ctl_rename_id(card, &id1, &id2);
+	if (error < 0)
+		return error;
+
+	return 0;
+}
+
+static int snd_gusextreme_probe(struct device *dev, unsigned int n)
+{
+	struct snd_card *card;
+	struct snd_gus_card *gus;
+	struct snd_es1688 *es1688;
+	struct snd_opl3 *opl3;
+	int error;
+
+	error = snd_card_new(dev, index[n], id[n], THIS_MODULE,
+			     sizeof(struct snd_es1688), &card);
+	if (error < 0)
+		return error;
+
+	es1688 = card->private_data;
+
+	if (mpu_port[n] == SNDRV_AUTO_PORT)
+		mpu_port[n] = 0;
+
+	if (mpu_irq[n] > 15)
+		mpu_irq[n] = -1;
+
+	error = snd_gusextreme_es1688_create(card, es1688, dev, n);
+	if (error < 0)
+		goto out;
+
+	if (gf1_port[n] < 0)
+		gf1_port[n] = es1688->port + 0x20;
+
+	error = snd_gusextreme_gus_card_create(card, dev, n, &gus);
+	if (error < 0)
+		goto out;
+
+	error = snd_gusextreme_detect(gus, es1688);
+	if (error < 0)
+		goto out;
+
+	gus->joystick_dac = joystick_dac[n];
+
+	error = snd_gus_initialize(gus);
+	if (error < 0)
+		goto out;
+
+	error = -ENODEV;
+	if (!gus->ess_flag) {
+		dev_err(dev, "GUS Extreme soundcard was not "
+			"detected at 0x%lx\n", gus->gf1.port);
+		goto out;
+	}
+	gus->codec_flag = 1;
+
+	error = snd_es1688_pcm(card, es1688, 0);
+	if (error < 0)
+		goto out;
+
+	error = snd_es1688_mixer(card, es1688);
+	if (error < 0)
+		goto out;
+
+	snd_component_add(card, "ES1688");
+
+	if (pcm_channels[n] > 0) {
+		error = snd_gf1_pcm_new(gus, 1, 1);
+		if (error < 0)
+			goto out;
+	}
+
+	error = snd_gf1_new_mixer(gus);
+	if (error < 0)
+		goto out;
+
+	error = snd_gusextreme_mixer(card);
+	if (error < 0)
+		goto out;
+
+	if (snd_opl3_create(card, es1688->port, es1688->port + 2,
+			OPL3_HW_OPL3, 0, &opl3) < 0)
+		dev_warn(dev, "opl3 not detected at 0x%lx\n", es1688->port);
+	else {
+		error = snd_opl3_hwdep_new(opl3, 0, 2, NULL);
+		if (error < 0)
+			goto out;
+	}
+
+	if (es1688->mpu_port >= 0x300) {
+		error = snd_mpu401_uart_new(card, 0, MPU401_HW_ES1688,
+				es1688->mpu_port, 0, mpu_irq[n], NULL);
+		if (error < 0)
+			goto out;
+	}
+
+	sprintf(card->longname, "Gravis UltraSound Extreme at 0x%lx, "
+		"irq %i&%i, dma %i&%i", es1688->port,
+		gus->gf1.irq, es1688->irq, gus->gf1.dma1, es1688->dma8);
+
+	error = snd_card_register(card);
+	if (error < 0)
+		goto out;
+
+	dev_set_drvdata(dev, card);
+	return 0;
+
+out:	snd_card_free(card);
+	return error;
+}
+
+static int snd_gusextreme_remove(struct device *dev, unsigned int n)
+{
+	snd_card_free(dev_get_drvdata(dev));
+	return 0;
+}
+
+static struct isa_driver snd_gusextreme_driver = {
+	.match		= snd_gusextreme_match,
+	.probe		= snd_gusextreme_probe,
+	.remove		= snd_gusextreme_remove,
+#if 0	/* FIXME */
+	.suspend	= snd_gusextreme_suspend,
+	.resume		= snd_gusextreme_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	}
+};
+
+module_isa_driver(snd_gusextreme_driver, SNDRV_CARDS);
diff --git a/sound/isa/gus/gusmax.c b/sound/isa/gus/gusmax.c
new file mode 100644
index 0000000..63309a4
--- /dev/null
+++ b/sound/isa/gus/gusmax.c
@@ -0,0 +1,373 @@
+/*
+ *  Driver for Gravis UltraSound MAX soundcard
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/module.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/wss.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.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 bool 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_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for GUS MAX driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for GUS MAX driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for GUS MAX driver.");
+module_param_hw_array(dma2, int, dma, 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;
+	struct snd_card *card;
+	struct snd_gus_card *gus;
+	struct snd_wss *wss;
+	unsigned short gus_status_reg;
+	unsigned short pcm_status_reg;
+};
+
+#define PFX	"gusmax: "
+
+static int snd_gusmax_detect(struct snd_gus_card *gus)
+{
+	unsigned char d;
+
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0);	/* reset GF1 */
+	if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) {
+		snd_printdd("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d);
+		return -ENODEV;
+	}
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);	/* release reset */
+	udelay(160);
+	if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) {
+		snd_printdd("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static irqreturn_t snd_gusmax_interrupt(int irq, void *dev_id)
+{
+	struct snd_gusmax *maxcard = 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);
+			loop++;
+		}
+		if (inb(maxcard->pcm_status_reg) & 0x01) { /* IRQ bit is set? */
+			handled = 1;
+			snd_wss_interrupt(irq, maxcard->wss);
+			loop++;
+		}
+	} while (loop && --max > 0);
+	return IRQ_RETVAL(handled);
+}
+
+static void snd_gusmax_init(int dev, struct snd_card *card,
+			    struct snd_gus_card *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));
+}
+
+static int snd_gusmax_mixer(struct snd_wss *chip)
+{
+	struct snd_card *card = chip->card;
+	struct snd_ctl_elem_id 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(struct snd_card *card)
+{
+	struct snd_gusmax *maxcard = card->private_data;
+	
+	if (maxcard == NULL)
+		return;
+	if (maxcard->irq >= 0)
+		free_irq(maxcard->irq, (void *)maxcard);
+}
+
+static int snd_gusmax_match(struct device *pdev, unsigned int dev)
+{
+	return enable[dev];
+}
+
+static int snd_gusmax_probe(struct device *pdev, unsigned 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;
+	struct snd_card *card;
+	struct snd_gus_card *gus = NULL;
+	struct snd_wss *wss;
+	struct snd_gusmax *maxcard;
+
+	err = snd_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+			   sizeof(struct snd_gusmax), &card);
+	if (err < 0)
+		return err;
+	card->private_free = snd_gusmax_free;
+	maxcard = 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_printk(KERN_ERR PFX "unable to find a free IRQ\n");
+			err = -EBUSY;
+			goto _err;
+		}
+	}
+	xdma1 = dma1[dev];
+	if (xdma1 == SNDRV_AUTO_DMA) {
+		if ((xdma1 = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free DMA1\n");
+			err = -EBUSY;
+			goto _err;
+		}
+	}
+	xdma2 = dma2[dev];
+	if (xdma2 == SNDRV_AUTO_DMA) {
+		if ((xdma2 = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free DMA2\n");
+			err = -EBUSY;
+			goto _err;
+		}
+	}
+
+	if (port[dev] != SNDRV_AUTO_PORT) {
+		err = snd_gus_create(card,
+				     port[dev],
+				     -xirq, xdma1, xdma2,
+				     0, channels[dev],
+				     pcm_channels[dev],
+				     0, &gus);
+	} else {
+		static unsigned long possible_ports[] = {
+			0x220, 0x230, 0x240, 0x250, 0x260
+		};
+		int i;
+		for (i = 0; i < ARRAY_SIZE(possible_ports); i++) {
+			err = snd_gus_create(card,
+					     possible_ports[i],
+					     -xirq, xdma1, xdma2,
+					     0, channels[dev],
+					     pcm_channels[dev],
+					     0, &gus);
+			if (err >= 0) {
+				port[dev] = possible_ports[i];
+				break;
+			}
+		}
+	}
+	if (err < 0)
+		goto _err;
+
+	if ((err = snd_gusmax_detect(gus)) < 0)
+		goto _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)
+		goto _err;
+
+	if (!gus->max_flag) {
+		snd_printk(KERN_ERR PFX "GUS MAX soundcard was not detected at 0x%lx\n", gus->gf1.port);
+		err = -ENODEV;
+		goto _err;
+	}
+
+	if (request_irq(xirq, snd_gusmax_interrupt, 0, "GUS MAX", (void *)maxcard)) {
+		snd_printk(KERN_ERR PFX "unable to grab IRQ %d\n", xirq);
+		err = -EBUSY;
+		goto _err;
+	}
+	maxcard->irq = xirq;
+	
+	err = snd_wss_create(card,
+			     gus->gf1.port + 0x10c, -1, xirq,
+			     xdma2 < 0 ? xdma1 : xdma2, xdma1,
+			     WSS_HW_DETECT,
+			     WSS_HWSHARE_IRQ |
+			     WSS_HWSHARE_DMA1 |
+			     WSS_HWSHARE_DMA2,
+			     &wss);
+	if (err < 0)
+		goto _err;
+
+	err = snd_wss_pcm(wss, 0);
+	if (err < 0)
+		goto _err;
+
+	err = snd_wss_mixer(wss);
+	if (err < 0)
+		goto _err;
+
+	err = snd_wss_timer(wss, 2);
+	if (err < 0)
+		goto _err;
+
+	if (pcm_channels[dev] > 0) {
+		if ((err = snd_gf1_pcm_new(gus, 1, 1)) < 0)
+			goto _err;
+	}
+	err = snd_gusmax_mixer(wss);
+	if (err < 0)
+		goto _err;
+
+	err = snd_gf1_rawmidi_new(gus, 0);
+	if (err < 0)
+		goto _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);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto _err;
+		
+	maxcard->gus = gus;
+	maxcard->wss = wss;
+
+	dev_set_drvdata(pdev, card);
+	return 0;
+
+ _err:
+	snd_card_free(card);
+	return err;
+}
+
+static int snd_gusmax_remove(struct device *devptr, unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(devptr));
+	return 0;
+}
+
+#define DEV_NAME "gusmax"
+
+static struct isa_driver snd_gusmax_driver = {
+	.match		= snd_gusmax_match,
+	.probe		= snd_gusmax_probe,
+	.remove		= snd_gusmax_remove,
+	/* FIXME: suspend/resume */
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+module_isa_driver(snd_gusmax_driver, SNDRV_CARDS);
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..a6fc26b
--- /dev/null
+++ b/sound/isa/gus/interwave.c
@@ -0,0 +1,937 @@
+/*
+ *  Driver for AMD InterWave soundcard
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/pnp.h>
+#include <linux/module.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/wss.h>
+#ifdef SNDRV_STB
+#include <sound/tea6330t.h>
+#endif
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.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 bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static bool 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];
+static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+static int effect[SNDRV_CARDS];
+
+#ifdef SNDRV_STB
+#define PFX "interwave-stb: "
+#define INTERWAVE_DRIVER	"snd_interwave_stb"
+#define INTERWAVE_PNP_DRIVER	"interwave-stb"
+#else
+#define PFX "interwave: "
+#define INTERWAVE_DRIVER	"snd_interwave"
+#define INTERWAVE_PNP_DRIVER	"interwave"
+#endif
+
+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_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for InterWave driver.");
+#ifdef SNDRV_STB
+module_param_hw_array(port_tc, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port_tc, "Tone control (TEA6330T - i2c bus) port # for InterWave driver.");
+#endif
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for InterWave driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for InterWave driver.");
+module_param_hw_array(dma2, int, dma, 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;
+	struct snd_card *card;
+	struct snd_gus_card *gus;
+	struct snd_wss *wss;
+#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
+};
+
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+
+static const 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(struct snd_i2c_bus *bus, int ctrl, int data)
+{
+	unsigned long port = bus->private_value;
+
+#if 0
+	printk(KERN_DEBUG "i2c_setlines - 0x%lx <- %i,%i\n", port, ctrl, data);
+#endif
+	outb((data << 1) | ctrl, port);
+	udelay(10);
+}
+
+static int snd_interwave_i2c_getclockline(struct snd_i2c_bus *bus)
+{
+	unsigned long port = bus->private_value;
+	unsigned char res;
+
+	res = inb(port) & 1;
+#if 0
+	printk(KERN_DEBUG "i2c_getclockline - 0x%lx -> %i\n", port, res);
+#endif
+	return res;
+}
+
+static int snd_interwave_i2c_getdataline(struct snd_i2c_bus *bus, int ack)
+{
+	unsigned long port = bus->private_value;
+	unsigned char res;
+
+	if (ack)
+		udelay(10);
+	res = (inb(port) & 2) >> 1;
+#if 0
+	printk(KERN_DEBUG "i2c_getdataline - 0x%lx -> %i\n", port, res);
+#endif
+	return res;
+}
+
+static struct snd_i2c_bit_ops snd_interwave_i2c_bit_ops = {
+	.setlines = snd_interwave_i2c_setlines,
+	.getclock = snd_interwave_i2c_getclockline,
+	.getdata  = snd_interwave_i2c_getdataline,
+};
+
+static int snd_interwave_detect_stb(struct snd_interwave *iwcard,
+				    struct snd_gus_card *gus, int dev,
+				    struct snd_i2c_bus **rbus)
+{
+	unsigned long port;
+	struct snd_i2c_bus *bus;
+	struct snd_card *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 snd_interwave_detect(struct snd_interwave *iwcard,
+				struct snd_gus_card *gus,
+				int dev
+#ifdef SNDRV_STB
+				, struct snd_i2c_bus **rbus
+#endif
+				          )
+{
+	unsigned long flags;
+	unsigned char rev1, rev2;
+	int d;
+
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0);	/* reset GF1 */
+	if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) {
+		snd_printdd("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d);
+		return -ENODEV;
+	}
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);	/* release reset */
+	udelay(160);
+	if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) {
+		snd_printdd("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d);
+		return -ENODEV;
+	}
+	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 snd_interwave *iwcard = 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);
+			loop++;
+		}
+		if (inb(iwcard->pcm_status_reg) & 0x01) {	/* IRQ bit is set? */
+			handled = 1;
+			snd_wss_interrupt(irq, iwcard->wss);
+			loop++;
+		}
+	} while (loop && --max > 0);
+	return IRQ_RETVAL(handled);
+}
+
+static void snd_interwave_reset(struct snd_gus_card *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 snd_interwave_bank_sizes(struct snd_gus_card *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(KERN_DEBUG "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(KERN_DEBUG "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 snd_interwave_detect_memory(struct snd_gus_card *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(KERN_DEBUG "lmct = 0x%08x\n", lmct);
+#endif
+		for (i = 0; i < ARRAY_SIZE(lmc); i++)
+			if (lmct == lmc[i]) {
+#if 0
+				printk(KERN_DEBUG "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);
+		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);
+		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 snd_interwave_init(int dev, struct snd_gus_card *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 struct snd_kcontrol_new snd_interwave_controls[] = {
+WSS_DOUBLE("Master Playback Switch", 0,
+		CS4231_LINE_LEFT_OUTPUT, CS4231_LINE_RIGHT_OUTPUT, 7, 7, 1, 1),
+WSS_DOUBLE("Master Playback Volume", 0,
+		CS4231_LINE_LEFT_OUTPUT, CS4231_LINE_RIGHT_OUTPUT, 0, 0, 31, 1),
+WSS_DOUBLE("Mic Playback Switch", 0,
+		CS4231_LEFT_MIC_INPUT, CS4231_RIGHT_MIC_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE("Mic Playback Volume", 0,
+		CS4231_LEFT_MIC_INPUT, CS4231_RIGHT_MIC_INPUT, 0, 0, 31, 1)
+};
+
+static int snd_interwave_mixer(struct snd_wss *chip)
+{
+	struct snd_card *card = chip->card;
+	struct snd_ctl_elem_id 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_wss_out(chip, CS4231_LINE_LEFT_OUTPUT, 0x9f);
+	snd_wss_out(chip, CS4231_LINE_RIGHT_OUTPUT, 0x9f);
+	snd_wss_out(chip, CS4231_LEFT_MIC_INPUT, 0x9f);
+	snd_wss_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 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;
+	int err;
+
+	iwcard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (iwcard->dev == NULL)
+		return -EBUSY;
+
+#ifdef SNDRV_STB
+	iwcard->devtc = pnp_request_card_device(card, id->devs[1].id, NULL);
+	if (iwcard->devtc == NULL)
+		return -EBUSY;
+#endif
+	/* Synth & Codec initialization */
+	pdev = iwcard->dev;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		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)) {
+		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%llx, gf1 port=0x%llx, codec port=0x%llx\n",
+			(unsigned long long)pnp_port_start(pdev, 0),
+			(unsigned long long)pnp_port_start(pdev, 1),
+			(unsigned long long)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;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		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
+	return 0;
+}
+#endif /* CONFIG_PNP */
+
+static void snd_interwave_free(struct snd_card *card)
+{
+	struct snd_interwave *iwcard = card->private_data;
+
+	if (iwcard == NULL)
+		return;
+#ifdef SNDRV_STB
+	release_and_free_resource(iwcard->i2c_res);
+#endif
+	if (iwcard->irq >= 0)
+		free_irq(iwcard->irq, (void *)iwcard);
+}
+
+static int snd_interwave_card_new(struct device *pdev, int dev,
+				  struct snd_card **cardp)
+{
+	struct snd_card *card;
+	struct snd_interwave *iwcard;
+	int err;
+
+	err = snd_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+			   sizeof(struct snd_interwave), &card);
+	if (err < 0)
+		return err;
+	iwcard = card->private_data;
+	iwcard->card = card;
+	iwcard->irq = -1;
+	card->private_free = snd_interwave_free;
+	*cardp = card;
+	return 0;
+}
+
+static int snd_interwave_probe(struct snd_card *card, int dev)
+{
+	int xirq, xdma1, xdma2;
+	struct snd_interwave *iwcard = card->private_data;
+	struct snd_wss *wss;
+	struct snd_gus_card *gus;
+#ifdef SNDRV_STB
+	struct snd_i2c_bus *i2c_bus;
+#endif
+	char *str;
+	int err;
+
+	xirq = irq[dev];
+	xdma1 = dma1[dev];
+	xdma2 = dma2[dev];
+
+	if ((err = snd_gus_create(card,
+				  port[dev],
+				  -xirq, xdma1, xdma2,
+				  0, 32,
+				  pcm_channels[dev], effect[dev], &gus)) < 0)
+		return err;
+
+	if ((err = snd_interwave_detect(iwcard, gus, dev
+#ifdef SNDRV_STB
+            , &i2c_bus
+#endif
+	    )) < 0)
+		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)
+		return err;
+
+	if (request_irq(xirq, snd_interwave_interrupt, 0,
+			"InterWave", iwcard)) {
+		snd_printk(KERN_ERR PFX "unable to grab IRQ %d\n", xirq);
+		return -EBUSY;
+	}
+	iwcard->irq = xirq;
+
+	err = snd_wss_create(card,
+			     gus->gf1.port + 0x10c, -1, xirq,
+			     xdma2 < 0 ? xdma1 : xdma2, xdma1,
+			     WSS_HW_INTERWAVE,
+			     WSS_HWSHARE_IRQ |
+			     WSS_HWSHARE_DMA1 |
+			     WSS_HWSHARE_DMA2,
+			     &wss);
+	if (err < 0)
+		return err;
+
+	err = snd_wss_pcm(wss, 0);
+	if (err < 0)
+		return err;
+
+	sprintf(wss->pcm->name + strlen(wss->pcm->name), " rev %c",
+		gus->revision + 'A');
+	strcat(wss->pcm->name, " (codec)");
+
+	err = snd_wss_timer(wss, 2);
+	if (err < 0)
+		return err;
+
+	err = snd_wss_mixer(wss);
+	if (err < 0)
+		return err;
+
+	if (pcm_channels[dev] > 0) {
+		err = snd_gf1_pcm_new(gus, 1, 1);
+		if (err < 0)
+			return err;
+	}
+	err = snd_interwave_mixer(wss);
+	if (err < 0)
+		return err;
+
+#ifdef SNDRV_STB
+	{
+		struct snd_ctl_elem_id 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)
+			return err;
+		strcpy(id1.name, "Master Playback Volume");
+		strcpy(id2.name, id1.name);
+		if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+			return err;
+		if ((err = snd_tea6330t_update_mixer(card, i2c_bus, 0, 1)) < 0)
+			return err;
+	}
+#endif
+
+	gus->uart_enable = midi[dev];
+	if ((err = snd_gf1_rawmidi_new(gus, 0)) < 0)
+		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);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		return err;
+	
+	iwcard->wss = wss;
+	iwcard->gus = gus;
+	return 0;
+}
+
+static int snd_interwave_isa_probe1(int dev, struct device *devptr)
+{
+	struct snd_card *card;
+	int err;
+
+	err = snd_interwave_card_new(devptr, dev, &card);
+	if (err < 0)
+		return err;
+
+	if ((err = snd_interwave_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	dev_set_drvdata(devptr, card);
+	return 0;
+}
+
+static int snd_interwave_isa_match(struct device *pdev,
+				   unsigned int dev)
+{
+	if (!enable[dev])
+		return 0;
+#ifdef CONFIG_PNP
+	if (isapnp[dev])
+		return 0;
+#endif
+	return 1;
+}
+
+static int snd_interwave_isa_probe(struct device *pdev,
+				   unsigned int dev)
+{
+	int err;
+	static int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, -1};
+	static int possible_dmas[] = {0, 1, 3, 5, 6, 7, -1};
+
+	if (irq[dev] == SNDRV_AUTO_IRQ) {
+		if ((irq[dev] = snd_legacy_find_free_irq(possible_irqs)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (dma1[dev] == SNDRV_AUTO_DMA) {
+		if ((dma1[dev] = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free DMA1\n");
+			return -EBUSY;
+		}
+	}
+	if (dma2[dev] == SNDRV_AUTO_DMA) {
+		if ((dma2[dev] = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free DMA2\n");
+			return -EBUSY;
+		}
+	}
+
+	if (port[dev] != SNDRV_AUTO_PORT)
+		return snd_interwave_isa_probe1(dev, pdev);
+	else {
+		static long possible_ports[] = {0x210, 0x220, 0x230, 0x240, 0x250, 0x260};
+		int i;
+		for (i = 0; i < ARRAY_SIZE(possible_ports); i++) {
+			port[dev] = possible_ports[i];
+			err = snd_interwave_isa_probe1(dev, pdev);
+			if (! err)
+				return 0;
+		}
+		return err;
+	}
+}
+
+static int snd_interwave_isa_remove(struct device *devptr, unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(devptr));
+	return 0;
+}
+
+static struct isa_driver snd_interwave_driver = {
+	.match		= snd_interwave_isa_match,
+	.probe		= snd_interwave_isa_probe,
+	.remove		= snd_interwave_isa_remove,
+	/* FIXME: suspend,resume */
+	.driver		= {
+		.name	= INTERWAVE_DRIVER
+	},
+};
+
+#ifdef CONFIG_PNP
+static int snd_interwave_pnp_detect(struct pnp_card_link *pcard,
+				    const struct pnp_card_device_id *pid)
+{
+	static int dev;
+	struct snd_card *card;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+				
+	res = snd_interwave_card_new(&pcard->card->dev, dev, &card);
+	if (res < 0)
+		return res;
+
+	if ((res = snd_interwave_pnp(dev, card->private_data, pcard, pid)) < 0) {
+		snd_card_free(card);
+		return res;
+	}
+	if ((res = snd_interwave_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return res;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	dev++;
+	return 0;
+}
+
+static void snd_interwave_pnp_remove(struct pnp_card_link *pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+static struct pnp_card_driver interwave_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DISABLE,
+	.name = INTERWAVE_PNP_DRIVER,
+	.id_table = snd_interwave_pnpids,
+	.probe = snd_interwave_pnp_detect,
+	.remove = snd_interwave_pnp_remove,
+	/* FIXME: suspend,resume */
+};
+
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_interwave_init(void)
+{
+	int err;
+
+	err = isa_register_driver(&snd_interwave_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+	if (!err)
+		isa_registered = 1;
+
+	err = pnp_register_card_driver(&interwave_pnpc_driver);
+	if (!err)
+		pnp_registered = 1;
+
+	if (isa_registered)
+		err = 0;
+#endif
+	return err;
+}
+
+static void __exit alsa_card_interwave_exit(void)
+{
+#ifdef CONFIG_PNP
+	if (pnp_registered)
+		pnp_unregister_card_driver(&interwave_pnpc_driver);
+	if (isa_registered)
+#endif
+		isa_unregister_driver(&snd_interwave_driver);
+}
+
+module_init(alsa_card_interwave_init)
+module_exit(alsa_card_interwave_exit)
diff --git a/sound/isa/msnd/Makefile b/sound/isa/msnd/Makefile
new file mode 100644
index 0000000..ec231a7
--- /dev/null
+++ b/sound/isa/msnd/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+
+snd-msnd-lib-objs := msnd.o msnd_midi.o msnd_pinnacle_mixer.o
+snd-msnd-pinnacle-objs := msnd_pinnacle.o
+snd-msnd-classic-objs := msnd_classic.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_MSND_PINNACLE) += snd-msnd-pinnacle.o snd-msnd-lib.o
+obj-$(CONFIG_SND_MSND_CLASSIC) += snd-msnd-classic.o snd-msnd-lib.o
+
diff --git a/sound/isa/msnd/msnd.c b/sound/isa/msnd/msnd.c
new file mode 100644
index 0000000..7c3203f
--- /dev/null
+++ b/sound/isa/msnd/msnd.c
@@ -0,0 +1,705 @@
+/*********************************************************************
+ *
+ * 2002/06/30 Karsten Wiese:
+ *	removed kernel-version dependencies.
+ *	ripped from linux kernel 2.4.18 (OSS Implementation) by me.
+ *	In the OSS Version, this file is compiled to a separate MODULE,
+ *	that is used by the pinnacle and the classic driver.
+ *	since there is no classic driver for alsa yet (i dont have a classic
+ *	& writing one blindfold is difficult) this file's object is statically
+ *	linked into the pinnacle-driver-module for now.	look for the string
+ *		"uncomment this to make this a module again"
+ *	to do guess what.
+ *
+ * the following is a copy of the 2.4.18 OSS FREE file-heading comment:
+ *
+ * msnd.c - Driver Base
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ********************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/sched/signal.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "msnd.h"
+
+#define LOGNAME			"msnd"
+
+
+void snd_msnd_init_queue(void __iomem *base, int start, int size)
+{
+	writew(PCTODSP_BASED(start), base + JQS_wStart);
+	writew(PCTODSP_OFFSET(size) - 1, base + JQS_wSize);
+	writew(0, base + JQS_wHead);
+	writew(0, base + JQS_wTail);
+}
+EXPORT_SYMBOL(snd_msnd_init_queue);
+
+static int snd_msnd_wait_TXDE(struct snd_msnd *dev)
+{
+	unsigned int io = dev->io;
+	int timeout = 1000;
+
+	while (timeout-- > 0)
+		if (inb(io + HP_ISR) & HPISR_TXDE)
+			return 0;
+
+	return -EIO;
+}
+
+static int snd_msnd_wait_HC0(struct snd_msnd *dev)
+{
+	unsigned int io = dev->io;
+	int timeout = 1000;
+
+	while (timeout-- > 0)
+		if (!(inb(io + HP_CVR) & HPCVR_HC))
+			return 0;
+
+	return -EIO;
+}
+
+int snd_msnd_send_dsp_cmd(struct snd_msnd *dev, u8 cmd)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (snd_msnd_wait_HC0(dev) == 0) {
+		outb(cmd, dev->io + HP_CVR);
+		spin_unlock_irqrestore(&dev->lock, flags);
+		return 0;
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	snd_printd(KERN_ERR LOGNAME ": Send DSP command timeout\n");
+
+	return -EIO;
+}
+EXPORT_SYMBOL(snd_msnd_send_dsp_cmd);
+
+int snd_msnd_send_word(struct snd_msnd *dev, unsigned char high,
+		   unsigned char mid, unsigned char low)
+{
+	unsigned int io = dev->io;
+
+	if (snd_msnd_wait_TXDE(dev) == 0) {
+		outb(high, io + HP_TXH);
+		outb(mid, io + HP_TXM);
+		outb(low, io + HP_TXL);
+		return 0;
+	}
+
+	snd_printd(KERN_ERR LOGNAME ": Send host word timeout\n");
+
+	return -EIO;
+}
+EXPORT_SYMBOL(snd_msnd_send_word);
+
+int snd_msnd_upload_host(struct snd_msnd *dev, const u8 *bin, int len)
+{
+	int i;
+
+	if (len % 3 != 0) {
+		snd_printk(KERN_ERR LOGNAME
+			   ": Upload host data not multiple of 3!\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < len; i += 3)
+		if (snd_msnd_send_word(dev, bin[i], bin[i + 1], bin[i + 2]))
+			return -EIO;
+
+	inb(dev->io + HP_RXL);
+	inb(dev->io + HP_CVR);
+
+	return 0;
+}
+EXPORT_SYMBOL(snd_msnd_upload_host);
+
+int snd_msnd_enable_irq(struct snd_msnd *dev)
+{
+	unsigned long flags;
+
+	if (dev->irq_ref++)
+		return 0;
+
+	snd_printdd(LOGNAME ": Enabling IRQ\n");
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (snd_msnd_wait_TXDE(dev) == 0) {
+		outb(inb(dev->io + HP_ICR) | HPICR_TREQ, dev->io + HP_ICR);
+		if (dev->type == msndClassic)
+			outb(dev->irqid, dev->io + HP_IRQM);
+
+		outb(inb(dev->io + HP_ICR) & ~HPICR_TREQ, dev->io + HP_ICR);
+		outb(inb(dev->io + HP_ICR) | HPICR_RREQ, dev->io + HP_ICR);
+		enable_irq(dev->irq);
+		snd_msnd_init_queue(dev->DSPQ, dev->dspq_data_buff,
+				    dev->dspq_buff_size);
+		spin_unlock_irqrestore(&dev->lock, flags);
+		return 0;
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	snd_printd(KERN_ERR LOGNAME ": Enable IRQ failed\n");
+
+	return -EIO;
+}
+EXPORT_SYMBOL(snd_msnd_enable_irq);
+
+int snd_msnd_disable_irq(struct snd_msnd *dev)
+{
+	unsigned long flags;
+
+	if (--dev->irq_ref > 0)
+		return 0;
+
+	if (dev->irq_ref < 0)
+		snd_printd(KERN_WARNING LOGNAME ": IRQ ref count is %d\n",
+			   dev->irq_ref);
+
+	snd_printdd(LOGNAME ": Disabling IRQ\n");
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (snd_msnd_wait_TXDE(dev) == 0) {
+		outb(inb(dev->io + HP_ICR) & ~HPICR_RREQ, dev->io + HP_ICR);
+		if (dev->type == msndClassic)
+			outb(HPIRQ_NONE, dev->io + HP_IRQM);
+		disable_irq(dev->irq);
+		spin_unlock_irqrestore(&dev->lock, flags);
+		return 0;
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	snd_printd(KERN_ERR LOGNAME ": Disable IRQ failed\n");
+
+	return -EIO;
+}
+EXPORT_SYMBOL(snd_msnd_disable_irq);
+
+static inline long get_play_delay_jiffies(struct snd_msnd *chip, long size)
+{
+	long tmp = (size * HZ * chip->play_sample_size) / 8;
+	return tmp / (chip->play_sample_rate * chip->play_channels);
+}
+
+static void snd_msnd_dsp_write_flush(struct snd_msnd *chip)
+{
+	if (!(chip->mode & FMODE_WRITE) || !test_bit(F_WRITING, &chip->flags))
+		return;
+	set_bit(F_WRITEFLUSH, &chip->flags);
+/*	interruptible_sleep_on_timeout(
+		&chip->writeflush,
+		get_play_delay_jiffies(&chip, chip->DAPF.len));*/
+	clear_bit(F_WRITEFLUSH, &chip->flags);
+	if (!signal_pending(current))
+		schedule_timeout_interruptible(
+			get_play_delay_jiffies(chip, chip->play_period_bytes));
+	clear_bit(F_WRITING, &chip->flags);
+}
+
+void snd_msnd_dsp_halt(struct snd_msnd *chip, struct file *file)
+{
+	if ((file ? file->f_mode : chip->mode) & FMODE_READ) {
+		clear_bit(F_READING, &chip->flags);
+		snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_STOP);
+		snd_msnd_disable_irq(chip);
+		if (file) {
+			snd_printd(KERN_INFO LOGNAME
+				   ": Stopping read for %p\n", file);
+			chip->mode &= ~FMODE_READ;
+		}
+		clear_bit(F_AUDIO_READ_INUSE, &chip->flags);
+	}
+	if ((file ? file->f_mode : chip->mode) & FMODE_WRITE) {
+		if (test_bit(F_WRITING, &chip->flags)) {
+			snd_msnd_dsp_write_flush(chip);
+			snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_STOP);
+		}
+		snd_msnd_disable_irq(chip);
+		if (file) {
+			snd_printd(KERN_INFO
+				   LOGNAME ": Stopping write for %p\n", file);
+			chip->mode &= ~FMODE_WRITE;
+		}
+		clear_bit(F_AUDIO_WRITE_INUSE, &chip->flags);
+	}
+}
+EXPORT_SYMBOL(snd_msnd_dsp_halt);
+
+
+int snd_msnd_DARQ(struct snd_msnd *chip, int bank)
+{
+	int /*size, n,*/ timeout = 3;
+	u16 wTmp;
+	/* void *DAQD; */
+
+	/* Increment the tail and check for queue wrap */
+	wTmp = readw(chip->DARQ + JQS_wTail) + PCTODSP_OFFSET(DAQDS__size);
+	if (wTmp > readw(chip->DARQ + JQS_wSize))
+		wTmp = 0;
+	while (wTmp == readw(chip->DARQ + JQS_wHead) && timeout--)
+		udelay(1);
+
+	if (chip->capturePeriods == 2) {
+		void __iomem *pDAQ = chip->mappedbase + DARQ_DATA_BUFF +
+			     bank * DAQDS__size + DAQDS_wStart;
+		unsigned short offset = 0x3000 + chip->capturePeriodBytes;
+
+		if (readw(pDAQ) != PCTODSP_BASED(0x3000))
+			offset = 0x3000;
+		writew(PCTODSP_BASED(offset), pDAQ);
+	}
+
+	writew(wTmp, chip->DARQ + JQS_wTail);
+
+#if 0
+	/* Get our digital audio queue struct */
+	DAQD = bank * DAQDS__size + chip->mappedbase + DARQ_DATA_BUFF;
+
+	/* Get length of data */
+	size = readw(DAQD + DAQDS_wSize);
+
+	/* Read data from the head (unprotected bank 1 access okay
+	   since this is only called inside an interrupt) */
+	outb(HPBLKSEL_1, chip->io + HP_BLKS);
+	n = msnd_fifo_write(&chip->DARF,
+			    (char *)(chip->base + bank * DAR_BUFF_SIZE),
+			    size, 0);
+	if (n <= 0) {
+		outb(HPBLKSEL_0, chip->io + HP_BLKS);
+		return n;
+	}
+	outb(HPBLKSEL_0, chip->io + HP_BLKS);
+#endif
+
+	return 1;
+}
+EXPORT_SYMBOL(snd_msnd_DARQ);
+
+int snd_msnd_DAPQ(struct snd_msnd *chip, int start)
+{
+	u16	DAPQ_tail;
+	int	protect = start, nbanks = 0;
+	void	__iomem *DAQD;
+	static int play_banks_submitted;
+	/* unsigned long flags;
+	spin_lock_irqsave(&chip->lock, flags); not necessary */
+
+	DAPQ_tail = readw(chip->DAPQ + JQS_wTail);
+	while (DAPQ_tail != readw(chip->DAPQ + JQS_wHead) || start) {
+		int bank_num = DAPQ_tail / PCTODSP_OFFSET(DAQDS__size);
+
+		if (start) {
+			start = 0;
+			play_banks_submitted = 0;
+		}
+
+		/* Get our digital audio queue struct */
+		DAQD = bank_num * DAQDS__size + chip->mappedbase +
+			DAPQ_DATA_BUFF;
+
+		/* Write size of this bank */
+		writew(chip->play_period_bytes, DAQD + DAQDS_wSize);
+		if (play_banks_submitted < 3)
+			++play_banks_submitted;
+		else if (chip->playPeriods == 2) {
+			unsigned short offset = chip->play_period_bytes;
+
+			if (readw(DAQD + DAQDS_wStart) != PCTODSP_BASED(0x0))
+				offset = 0;
+
+			writew(PCTODSP_BASED(offset), DAQD + DAQDS_wStart);
+		}
+		++nbanks;
+
+		/* Then advance the tail */
+		/*
+		if (protect)
+			snd_printd(KERN_INFO "B %X %lX\n",
+				   bank_num, xtime.tv_usec);
+		*/
+
+		DAPQ_tail = (++bank_num % 3) * PCTODSP_OFFSET(DAQDS__size);
+		writew(DAPQ_tail, chip->DAPQ + JQS_wTail);
+		/* Tell the DSP to play the bank */
+		snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_START);
+		if (protect)
+			if (2 == bank_num)
+				break;
+	}
+	/*
+	if (protect)
+		snd_printd(KERN_INFO "%lX\n", xtime.tv_usec);
+	*/
+	/* spin_unlock_irqrestore(&chip->lock, flags); not necessary */
+	return nbanks;
+}
+EXPORT_SYMBOL(snd_msnd_DAPQ);
+
+static void snd_msnd_play_reset_queue(struct snd_msnd *chip,
+				      unsigned int pcm_periods,
+				      unsigned int pcm_count)
+{
+	int	n;
+	void	__iomem *pDAQ = chip->mappedbase + DAPQ_DATA_BUFF;
+
+	chip->last_playbank = -1;
+	chip->playLimit = pcm_count * (pcm_periods - 1);
+	chip->playPeriods = pcm_periods;
+	writew(PCTODSP_OFFSET(0 * DAQDS__size), chip->DAPQ + JQS_wHead);
+	writew(PCTODSP_OFFSET(0 * DAQDS__size), chip->DAPQ + JQS_wTail);
+
+	chip->play_period_bytes = pcm_count;
+
+	for (n = 0; n < pcm_periods; ++n, pDAQ += DAQDS__size) {
+		writew(PCTODSP_BASED((u32)(pcm_count * n)),
+			pDAQ + DAQDS_wStart);
+		writew(0, pDAQ + DAQDS_wSize);
+		writew(1, pDAQ + DAQDS_wFormat);
+		writew(chip->play_sample_size, pDAQ + DAQDS_wSampleSize);
+		writew(chip->play_channels, pDAQ + DAQDS_wChannels);
+		writew(chip->play_sample_rate, pDAQ + DAQDS_wSampleRate);
+		writew(HIMT_PLAY_DONE * 0x100 + n, pDAQ + DAQDS_wIntMsg);
+		writew(n, pDAQ + DAQDS_wFlags);
+	}
+}
+
+static void snd_msnd_capture_reset_queue(struct snd_msnd *chip,
+					 unsigned int pcm_periods,
+					 unsigned int pcm_count)
+{
+	int		n;
+	void		__iomem *pDAQ;
+	/* unsigned long	flags; */
+
+	/* snd_msnd_init_queue(chip->DARQ, DARQ_DATA_BUFF, DARQ_BUFF_SIZE); */
+
+	chip->last_recbank = 2;
+	chip->captureLimit = pcm_count * (pcm_periods - 1);
+	chip->capturePeriods = pcm_periods;
+	writew(PCTODSP_OFFSET(0 * DAQDS__size), chip->DARQ + JQS_wHead);
+	writew(PCTODSP_OFFSET(chip->last_recbank * DAQDS__size),
+		chip->DARQ + JQS_wTail);
+
+#if 0 /* Critical section: bank 1 access. this is how the OSS driver does it:*/
+	spin_lock_irqsave(&chip->lock, flags);
+	outb(HPBLKSEL_1, chip->io + HP_BLKS);
+	memset_io(chip->mappedbase, 0, DAR_BUFF_SIZE * 3);
+	outb(HPBLKSEL_0, chip->io + HP_BLKS);
+	spin_unlock_irqrestore(&chip->lock, flags);
+#endif
+
+	chip->capturePeriodBytes = pcm_count;
+	snd_printdd("snd_msnd_capture_reset_queue() %i\n", pcm_count);
+
+	pDAQ = chip->mappedbase + DARQ_DATA_BUFF;
+
+	for (n = 0; n < pcm_periods; ++n, pDAQ += DAQDS__size) {
+		u32 tmp = pcm_count * n;
+
+		writew(PCTODSP_BASED(tmp + 0x3000), pDAQ + DAQDS_wStart);
+		writew(pcm_count, pDAQ + DAQDS_wSize);
+		writew(1, pDAQ + DAQDS_wFormat);
+		writew(chip->capture_sample_size, pDAQ + DAQDS_wSampleSize);
+		writew(chip->capture_channels, pDAQ + DAQDS_wChannels);
+		writew(chip->capture_sample_rate, pDAQ + DAQDS_wSampleRate);
+		writew(HIMT_RECORD_DONE * 0x100 + n, pDAQ + DAQDS_wIntMsg);
+		writew(n, pDAQ + DAQDS_wFlags);
+	}
+}
+
+static const struct snd_pcm_hardware snd_msnd_playback = {
+	.info =			SNDRV_PCM_INFO_MMAP |
+				SNDRV_PCM_INFO_INTERLEAVED |
+				SNDRV_PCM_INFO_MMAP_VALID |
+				SNDRV_PCM_INFO_BATCH,
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		8000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	0x3000,
+	.period_bytes_min =	0x40,
+	.period_bytes_max =	0x1800,
+	.periods_min =		2,
+	.periods_max =		3,
+	.fifo_size =		0,
+};
+
+static const struct snd_pcm_hardware snd_msnd_capture = {
+	.info =			SNDRV_PCM_INFO_MMAP |
+				SNDRV_PCM_INFO_INTERLEAVED |
+				SNDRV_PCM_INFO_MMAP_VALID |
+				SNDRV_PCM_INFO_BATCH,
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		8000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	0x3000,
+	.period_bytes_min =	0x40,
+	.period_bytes_max =	0x1800,
+	.periods_min =		2,
+	.periods_max =		3,
+	.fifo_size =		0,
+};
+
+
+static int snd_msnd_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+	set_bit(F_AUDIO_WRITE_INUSE, &chip->flags);
+	clear_bit(F_WRITING, &chip->flags);
+	snd_msnd_enable_irq(chip);
+
+	runtime->dma_area = (__force void *)chip->mappedbase;
+	runtime->dma_bytes = 0x3000;
+
+	chip->playback_substream = substream;
+	runtime->hw = snd_msnd_playback;
+	return 0;
+}
+
+static int snd_msnd_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+	snd_msnd_disable_irq(chip);
+	clear_bit(F_AUDIO_WRITE_INUSE, &chip->flags);
+	return 0;
+}
+
+
+static int snd_msnd_playback_hw_params(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *params)
+{
+	int	i;
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+	void	__iomem *pDAQ =	chip->mappedbase + DAPQ_DATA_BUFF;
+
+	chip->play_sample_size = snd_pcm_format_width(params_format(params));
+	chip->play_channels = params_channels(params);
+	chip->play_sample_rate = params_rate(params);
+
+	for (i = 0; i < 3; ++i, pDAQ += DAQDS__size) {
+		writew(chip->play_sample_size, pDAQ + DAQDS_wSampleSize);
+		writew(chip->play_channels, pDAQ + DAQDS_wChannels);
+		writew(chip->play_sample_rate, pDAQ + DAQDS_wSampleRate);
+	}
+	/* dont do this here:
+	 * snd_msnd_calibrate_adc(chip->play_sample_rate);
+	 */
+
+	return 0;
+}
+
+static int snd_msnd_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+	unsigned int pcm_size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int pcm_count = snd_pcm_lib_period_bytes(substream);
+	unsigned int pcm_periods = pcm_size / pcm_count;
+
+	snd_msnd_play_reset_queue(chip, pcm_periods, pcm_count);
+	chip->playDMAPos = 0;
+	return 0;
+}
+
+static int snd_msnd_playback_trigger(struct snd_pcm_substream *substream,
+				     int cmd)
+{
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+	int	result = 0;
+
+	if (cmd == SNDRV_PCM_TRIGGER_START) {
+		snd_printdd("snd_msnd_playback_trigger(START)\n");
+		chip->banksPlayed = 0;
+		set_bit(F_WRITING, &chip->flags);
+		snd_msnd_DAPQ(chip, 1);
+	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		snd_printdd("snd_msnd_playback_trigger(STop)\n");
+		/* interrupt diagnostic, comment this out later */
+		clear_bit(F_WRITING, &chip->flags);
+		snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_STOP);
+	} else {
+		snd_printd(KERN_ERR "snd_msnd_playback_trigger(?????)\n");
+		result = -EINVAL;
+	}
+
+	snd_printdd("snd_msnd_playback_trigger() ENDE\n");
+	return result;
+}
+
+static snd_pcm_uframes_t
+snd_msnd_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+	return bytes_to_frames(substream->runtime, chip->playDMAPos);
+}
+
+
+static const struct snd_pcm_ops snd_msnd_playback_ops = {
+	.open =		snd_msnd_playback_open,
+	.close =	snd_msnd_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_msnd_playback_hw_params,
+	.prepare =	snd_msnd_playback_prepare,
+	.trigger =	snd_msnd_playback_trigger,
+	.pointer =	snd_msnd_playback_pointer,
+};
+
+static int snd_msnd_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+	set_bit(F_AUDIO_READ_INUSE, &chip->flags);
+	snd_msnd_enable_irq(chip);
+	runtime->dma_area = (__force void *)chip->mappedbase + 0x3000;
+	runtime->dma_bytes = 0x3000;
+	memset(runtime->dma_area, 0, runtime->dma_bytes);
+	chip->capture_substream = substream;
+	runtime->hw = snd_msnd_capture;
+	return 0;
+}
+
+static int snd_msnd_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+	snd_msnd_disable_irq(chip);
+	clear_bit(F_AUDIO_READ_INUSE, &chip->flags);
+	return 0;
+}
+
+static int snd_msnd_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+	unsigned int pcm_size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int pcm_count = snd_pcm_lib_period_bytes(substream);
+	unsigned int pcm_periods = pcm_size / pcm_count;
+
+	snd_msnd_capture_reset_queue(chip, pcm_periods, pcm_count);
+	chip->captureDMAPos = 0;
+	return 0;
+}
+
+static int snd_msnd_capture_trigger(struct snd_pcm_substream *substream,
+				    int cmd)
+{
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+	if (cmd == SNDRV_PCM_TRIGGER_START) {
+		chip->last_recbank = -1;
+		set_bit(F_READING, &chip->flags);
+		if (snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_START) == 0)
+			return 0;
+
+		clear_bit(F_READING, &chip->flags);
+	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		clear_bit(F_READING, &chip->flags);
+		snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_STOP);
+		return 0;
+	}
+	return -EINVAL;
+}
+
+
+static snd_pcm_uframes_t
+snd_msnd_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+	return bytes_to_frames(runtime, chip->captureDMAPos);
+}
+
+
+static int snd_msnd_capture_hw_params(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *params)
+{
+	int		i;
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+	void		__iomem *pDAQ = chip->mappedbase + DARQ_DATA_BUFF;
+
+	chip->capture_sample_size = snd_pcm_format_width(params_format(params));
+	chip->capture_channels = params_channels(params);
+	chip->capture_sample_rate = params_rate(params);
+
+	for (i = 0; i < 3; ++i, pDAQ += DAQDS__size) {
+		writew(chip->capture_sample_size, pDAQ + DAQDS_wSampleSize);
+		writew(chip->capture_channels, pDAQ + DAQDS_wChannels);
+		writew(chip->capture_sample_rate, pDAQ + DAQDS_wSampleRate);
+	}
+	return 0;
+}
+
+
+static const struct snd_pcm_ops snd_msnd_capture_ops = {
+	.open =		snd_msnd_capture_open,
+	.close =	snd_msnd_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_msnd_capture_hw_params,
+	.prepare =	snd_msnd_capture_prepare,
+	.trigger =	snd_msnd_capture_trigger,
+	.pointer =	snd_msnd_capture_pointer,
+};
+
+
+int snd_msnd_pcm(struct snd_card *card, int device)
+{
+	struct snd_msnd *chip = card->private_data;
+	struct snd_pcm	*pcm;
+	int err;
+
+	err = snd_pcm_new(card, "MSNDPINNACLE", device, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_msnd_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_msnd_capture_ops);
+
+	pcm->private_data = chip;
+	strcpy(pcm->name, "Hurricane");
+
+	return 0;
+}
+EXPORT_SYMBOL(snd_msnd_pcm);
+
+MODULE_DESCRIPTION("Common routines for Turtle Beach Multisound drivers");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/isa/msnd/msnd.h b/sound/isa/msnd/msnd.h
new file mode 100644
index 0000000..80c7187
--- /dev/null
+++ b/sound/isa/msnd/msnd.h
@@ -0,0 +1,308 @@
+/*********************************************************************
+ *
+ * msnd.h
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ *
+ * Some parts of this header file were derived from the Turtle Beach
+ * MultiSound Driver Development Kit.
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ * Copyright (C) 1993 Turtle Beach Systems, Inc.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ********************************************************************/
+#ifndef __MSND_H
+#define __MSND_H
+
+#define DEFSAMPLERATE		44100
+#define DEFSAMPLESIZE		SNDRV_PCM_FORMAT_S16
+#define DEFCHANNELS		1
+
+#define SRAM_BANK_SIZE		0x8000
+#define SRAM_CNTL_START		0x7F00
+#define SMA_STRUCT_START	0x7F40
+
+#define DSP_BASE_ADDR		0x4000
+#define DSP_BANK_BASE		0x4000
+
+#define AGND			0x01
+#define SIGNAL			0x02
+
+#define EXT_DSP_BIT_DCAL	0x0001
+#define EXT_DSP_BIT_MIDI_CON	0x0002
+
+#define BUFFSIZE		0x8000
+#define HOSTQ_SIZE		0x40
+
+#define DAP_BUFF_SIZE		0x2400
+
+#define DAPQ_STRUCT_SIZE	0x10
+#define DARQ_STRUCT_SIZE	0x10
+#define DAPQ_BUFF_SIZE		(3 * 0x10)
+#define DARQ_BUFF_SIZE		(3 * 0x10)
+#define MODQ_BUFF_SIZE		0x400
+
+#define DAPQ_DATA_BUFF		0x6C00
+#define DARQ_DATA_BUFF		0x6C30
+#define MODQ_DATA_BUFF		0x6C60
+#define MIDQ_DATA_BUFF		0x7060
+
+#define DAPQ_OFFSET		SRAM_CNTL_START
+#define DARQ_OFFSET		(SRAM_CNTL_START + 0x08)
+#define MODQ_OFFSET		(SRAM_CNTL_START + 0x10)
+#define MIDQ_OFFSET		(SRAM_CNTL_START + 0x18)
+#define DSPQ_OFFSET		(SRAM_CNTL_START + 0x20)
+
+#define	HP_ICR			0x00
+#define	HP_CVR			0x01
+#define	HP_ISR			0x02
+#define	HP_IVR			0x03
+#define HP_NU			0x04
+#define HP_INFO			0x04
+#define	HP_TXH			0x05
+#define	HP_RXH			0x05
+#define	HP_TXM			0x06
+#define	HP_RXM			0x06
+#define	HP_TXL			0x07
+#define	HP_RXL			0x07
+
+#define HP_ICR_DEF		0x00
+#define HP_CVR_DEF		0x12
+#define HP_ISR_DEF		0x06
+#define HP_IVR_DEF		0x0f
+#define HP_NU_DEF		0x00
+
+#define	HP_IRQM			0x09
+
+#define	HPR_BLRC		0x08
+#define	HPR_SPR1		0x09
+#define	HPR_SPR2		0x0A
+#define	HPR_TCL0		0x0B
+#define	HPR_TCL1		0x0C
+#define	HPR_TCL2		0x0D
+#define	HPR_TCL3		0x0E
+#define	HPR_TCL4		0x0F
+
+#define	HPICR_INIT		0x80
+#define HPICR_HM1		0x40
+#define HPICR_HM0		0x20
+#define HPICR_HF1		0x10
+#define HPICR_HF0		0x08
+#define	HPICR_TREQ		0x02
+#define	HPICR_RREQ		0x01
+
+#define HPCVR_HC		0x80
+
+#define	HPISR_HREQ		0x80
+#define HPISR_DMA		0x40
+#define HPISR_HF3		0x10
+#define HPISR_HF2		0x08
+#define	HPISR_TRDY		0x04
+#define	HPISR_TXDE		0x02
+#define	HPISR_RXDF		0x01
+
+#define	HPIO_290		0
+#define	HPIO_260		1
+#define	HPIO_250		2
+#define	HPIO_240		3
+#define	HPIO_230		4
+#define	HPIO_220		5
+#define	HPIO_210		6
+#define	HPIO_3E0		7
+
+#define	HPMEM_NONE		0
+#define	HPMEM_B000		1
+#define	HPMEM_C800		2
+#define	HPMEM_D000		3
+#define	HPMEM_D400		4
+#define	HPMEM_D800		5
+#define	HPMEM_E000		6
+#define	HPMEM_E800		7
+
+#define	HPIRQ_NONE		0
+#define HPIRQ_5			1
+#define HPIRQ_7			2
+#define HPIRQ_9			3
+#define HPIRQ_10		4
+#define HPIRQ_11		5
+#define HPIRQ_12		6
+#define HPIRQ_15		7
+
+#define	HIMT_PLAY_DONE		0x00
+#define	HIMT_RECORD_DONE	0x01
+#define	HIMT_MIDI_EOS		0x02
+#define	HIMT_MIDI_OUT		0x03
+
+#define	HIMT_MIDI_IN_UCHAR	0x0E
+#define	HIMT_DSP		0x0F
+
+#define	HDEX_BASE	       	0x92
+#define	HDEX_PLAY_START		(0 + HDEX_BASE)
+#define	HDEX_PLAY_STOP		(1 + HDEX_BASE)
+#define	HDEX_PLAY_PAUSE		(2 + HDEX_BASE)
+#define	HDEX_PLAY_RESUME	(3 + HDEX_BASE)
+#define	HDEX_RECORD_START	(4 + HDEX_BASE)
+#define	HDEX_RECORD_STOP	(5 + HDEX_BASE)
+#define	HDEX_MIDI_IN_START 	(6 + HDEX_BASE)
+#define	HDEX_MIDI_IN_STOP	(7 + HDEX_BASE)
+#define	HDEX_MIDI_OUT_START	(8 + HDEX_BASE)
+#define	HDEX_MIDI_OUT_STOP	(9 + HDEX_BASE)
+#define	HDEX_AUX_REQ		(10 + HDEX_BASE)
+
+#define	HDEXAR_CLEAR_PEAKS	1
+#define	HDEXAR_IN_SET_POTS	2
+#define	HDEXAR_AUX_SET_POTS	3
+#define	HDEXAR_CAL_A_TO_D	4
+#define	HDEXAR_RD_EXT_DSP_BITS	5
+
+/* Pinnacle only HDEXAR defs */
+#define	HDEXAR_SET_ANA_IN	0
+#define	HDEXAR_SET_SYNTH_IN	4
+#define	HDEXAR_READ_DAT_IN	5
+#define	HDEXAR_MIC_SET_POTS	6
+#define	HDEXAR_SET_DAT_IN	7
+
+#define HDEXAR_SET_SYNTH_48	8
+#define HDEXAR_SET_SYNTH_44	9
+
+#define HIWORD(l)		((u16)((((u32)(l)) >> 16) & 0xFFFF))
+#define LOWORD(l)		((u16)(u32)(l))
+#define HIBYTE(w)		((u8)(((u16)(w) >> 8) & 0xFF))
+#define LOBYTE(w)		((u8)(w))
+#define MAKELONG(low, hi)	((long)(((u16)(low))|(((u32)((u16)(hi)))<<16)))
+#define MAKEWORD(low, hi)	((u16)(((u8)(low))|(((u16)((u8)(hi)))<<8)))
+
+#define PCTODSP_OFFSET(w)	(u16)((w)/2)
+#define PCTODSP_BASED(w)	(u16)(((w)/2) + DSP_BASE_ADDR)
+#define DSPTOPC_BASED(w)	(((w) - DSP_BASE_ADDR) * 2)
+
+#ifdef SLOWIO
+#  undef outb
+#  undef inb
+#  define outb			outb_p
+#  define inb			inb_p
+#endif
+
+/* JobQueueStruct */
+#define JQS_wStart		0x00
+#define JQS_wSize		0x02
+#define JQS_wHead		0x04
+#define JQS_wTail		0x06
+#define JQS__size		0x08
+
+/* DAQueueDataStruct */
+#define DAQDS_wStart		0x00
+#define DAQDS_wSize		0x02
+#define DAQDS_wFormat		0x04
+#define DAQDS_wSampleSize	0x06
+#define DAQDS_wChannels		0x08
+#define DAQDS_wSampleRate	0x0A
+#define DAQDS_wIntMsg		0x0C
+#define DAQDS_wFlags		0x0E
+#define DAQDS__size		0x10
+
+#include <sound/pcm.h>
+
+struct snd_msnd {
+	void __iomem		*mappedbase;
+	int			play_period_bytes;
+	int			playLimit;
+	int			playPeriods;
+	int 			playDMAPos;
+	int			banksPlayed;
+	int 			captureDMAPos;
+	int			capturePeriodBytes;
+	int			captureLimit;
+	int			capturePeriods;
+	struct snd_card		*card;
+	void			*msndmidi_mpu;
+	struct snd_rawmidi	*rmidi;
+
+	/* Hardware resources */
+	long io;
+	int memid, irqid;
+	int irq, irq_ref;
+	unsigned long base;
+
+	/* Motorola 56k DSP SMA */
+	void __iomem	*SMA;
+	void __iomem	*DAPQ;
+	void __iomem	*DARQ;
+	void __iomem	*MODQ;
+	void __iomem	*MIDQ;
+	void __iomem	*DSPQ;
+	int dspq_data_buff, dspq_buff_size;
+
+	/* State variables */
+	enum { msndClassic, msndPinnacle } type;
+	fmode_t mode;
+	unsigned long flags;
+#define F_RESETTING			0
+#define F_HAVEDIGITAL			1
+#define F_AUDIO_WRITE_INUSE		2
+#define F_WRITING			3
+#define F_WRITEBLOCK			4
+#define F_WRITEFLUSH			5
+#define F_AUDIO_READ_INUSE		6
+#define F_READING			7
+#define F_READBLOCK			8
+#define F_EXT_MIDI_INUSE		9
+#define F_HDR_MIDI_INUSE		10
+#define F_DISABLE_WRITE_NDELAY		11
+	spinlock_t lock;
+	spinlock_t mixer_lock;
+	int nresets;
+	unsigned recsrc;
+#define LEVEL_ENTRIES 32
+	int left_levels[LEVEL_ENTRIES];
+	int right_levels[LEVEL_ENTRIES];
+	int calibrate_signal;
+	int play_sample_size, play_sample_rate, play_channels;
+	int play_ndelay;
+	int capture_sample_size, capture_sample_rate, capture_channels;
+	int capture_ndelay;
+	u8 bCurrentMidiPatch;
+
+	int last_playbank, last_recbank;
+	struct snd_pcm_substream *playback_substream;
+	struct snd_pcm_substream *capture_substream;
+
+};
+
+void snd_msnd_init_queue(void __iomem *base, int start, int size);
+
+int snd_msnd_send_dsp_cmd(struct snd_msnd *chip, u8 cmd);
+int snd_msnd_send_word(struct snd_msnd *chip,
+			   unsigned char high,
+			   unsigned char mid,
+			   unsigned char low);
+int snd_msnd_upload_host(struct snd_msnd *chip,
+			     const u8 *bin, int len);
+int snd_msnd_enable_irq(struct snd_msnd *chip);
+int snd_msnd_disable_irq(struct snd_msnd *chip);
+void snd_msnd_dsp_halt(struct snd_msnd *chip, struct file *file);
+int snd_msnd_DAPQ(struct snd_msnd *chip, int start);
+int snd_msnd_DARQ(struct snd_msnd *chip, int start);
+int snd_msnd_pcm(struct snd_card *card, int device);
+
+int snd_msndmidi_new(struct snd_card *card, int device);
+void snd_msndmidi_input_read(void *mpu);
+
+void snd_msndmix_setup(struct snd_msnd *chip);
+int snd_msndmix_new(struct snd_card *card);
+int snd_msndmix_force_recsrc(struct snd_msnd *chip, int recsrc);
+#endif /* __MSND_H */
diff --git a/sound/isa/msnd/msnd_classic.c b/sound/isa/msnd/msnd_classic.c
new file mode 100644
index 0000000..3b23a09
--- /dev/null
+++ b/sound/isa/msnd/msnd_classic.c
@@ -0,0 +1,3 @@
+/* The work is in msnd_pinnacle.c, just define MSND_CLASSIC before it. */
+#define MSND_CLASSIC
+#include "msnd_pinnacle.c"
diff --git a/sound/isa/msnd/msnd_classic.h b/sound/isa/msnd/msnd_classic.h
new file mode 100644
index 0000000..f18d5fa
--- /dev/null
+++ b/sound/isa/msnd/msnd_classic.h
@@ -0,0 +1,129 @@
+/*********************************************************************
+ *
+ * msnd_classic.h
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ *
+ * Some parts of this header file were derived from the Turtle Beach
+ * MultiSound Driver Development Kit.
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ * Copyright (C) 1993 Turtle Beach Systems, Inc.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ********************************************************************/
+#ifndef __MSND_CLASSIC_H
+#define __MSND_CLASSIC_H
+
+#define DSP_NUMIO				0x10
+
+#define	HP_MEMM					0x08
+
+#define	HP_BITM					0x0E
+#define	HP_WAIT					0x0D
+#define	HP_DSPR					0x0A
+#define	HP_PROR					0x0B
+#define	HP_BLKS					0x0C
+
+#define	HPPRORESET_OFF				0
+#define HPPRORESET_ON				1
+
+#define HPDSPRESET_OFF				0
+#define HPDSPRESET_ON				1
+
+#define HPBLKSEL_0				0
+#define HPBLKSEL_1				1
+
+#define HPWAITSTATE_0				0
+#define HPWAITSTATE_1				1
+
+#define HPBITMODE_16				0
+#define HPBITMODE_8				1
+
+#define	HIDSP_INT_PLAY_UNDER			0x00
+#define	HIDSP_INT_RECORD_OVER			0x01
+#define	HIDSP_INPUT_CLIPPING			0x02
+#define	HIDSP_MIDI_IN_OVER			0x10
+#define	HIDSP_MIDI_OVERRUN_ERR  0x13
+
+#define TIME_PRO_RESET_DONE			0x028A
+#define TIME_PRO_SYSEX				0x0040
+#define TIME_PRO_RESET				0x0032
+
+#define DAR_BUFF_SIZE				0x2000
+
+#define MIDQ_BUFF_SIZE				0x200
+#define DSPQ_BUFF_SIZE				0x40
+
+#define DSPQ_DATA_BUFF				0x7260
+
+#define MOP_SYNTH				0x10
+#define MOP_EXTOUT				0x32
+#define MOP_EXTTHRU				0x02
+#define MOP_OUTMASK				0x01
+
+#define MIP_EXTIN				0x01
+#define MIP_SYNTH				0x00
+#define MIP_INMASK				0x32
+
+/* Classic SMA Common Data */
+#define SMA_wCurrPlayBytes			0x0000
+#define SMA_wCurrRecordBytes			0x0002
+#define SMA_wCurrPlayVolLeft			0x0004
+#define SMA_wCurrPlayVolRight			0x0006
+#define SMA_wCurrInVolLeft			0x0008
+#define SMA_wCurrInVolRight			0x000a
+#define SMA_wUser_3				0x000c
+#define SMA_wUser_4				0x000e
+#define SMA_dwUser_5				0x0010
+#define SMA_dwUser_6				0x0014
+#define SMA_wUser_7				0x0018
+#define SMA_wReserved_A				0x001a
+#define SMA_wReserved_B				0x001c
+#define SMA_wReserved_C				0x001e
+#define SMA_wReserved_D				0x0020
+#define SMA_wReserved_E				0x0022
+#define SMA_wReserved_F				0x0024
+#define SMA_wReserved_G				0x0026
+#define SMA_wReserved_H				0x0028
+#define SMA_wCurrDSPStatusFlags			0x002a
+#define SMA_wCurrHostStatusFlags		0x002c
+#define SMA_wCurrInputTagBits			0x002e
+#define SMA_wCurrLeftPeak			0x0030
+#define SMA_wCurrRightPeak			0x0032
+#define SMA_wExtDSPbits				0x0034
+#define SMA_bExtHostbits			0x0036
+#define SMA_bBoardLevel				0x0037
+#define SMA_bInPotPosRight			0x0038
+#define SMA_bInPotPosLeft			0x0039
+#define SMA_bAuxPotPosRight			0x003a
+#define SMA_bAuxPotPosLeft			0x003b
+#define SMA_wCurrMastVolLeft			0x003c
+#define SMA_wCurrMastVolRight			0x003e
+#define SMA_bUser_12				0x0040
+#define SMA_bUser_13				0x0041
+#define SMA_wUser_14				0x0042
+#define SMA_wUser_15				0x0044
+#define SMA_wCalFreqAtoD			0x0046
+#define SMA_wUser_16				0x0048
+#define SMA_wUser_17				0x004a
+#define SMA__size				0x004c
+
+#define INITCODEFILE		"turtlebeach/msndinit.bin"
+#define PERMCODEFILE		"turtlebeach/msndperm.bin"
+#define LONGNAME		"MultiSound (Classic/Monterey/Tahiti)"
+
+#endif /* __MSND_CLASSIC_H */
diff --git a/sound/isa/msnd/msnd_midi.c b/sound/isa/msnd/msnd_midi.c
new file mode 100644
index 0000000..42876b0
--- /dev/null
+++ b/sound/isa/msnd/msnd_midi.c
@@ -0,0 +1,182 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Copyright (c) 2009 by Krzysztof Helt
+ *  Routines for control of MPU-401 in UART mode
+ *
+ *  MPU-401 supports UART mode which is not capable generate transmit
+ *  interrupts thus output is done via polling. Also, if irq < 0, then
+ *  input is done also via polling. Do not expect good performance.
+ *
+ *
+ *   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 <linux/io.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include <sound/rawmidi.h>
+
+#include "msnd.h"
+
+#define MSNDMIDI_MODE_BIT_INPUT		0
+#define MSNDMIDI_MODE_BIT_OUTPUT		1
+#define MSNDMIDI_MODE_BIT_INPUT_TRIGGER	2
+#define MSNDMIDI_MODE_BIT_OUTPUT_TRIGGER	3
+
+struct snd_msndmidi {
+	struct snd_msnd *dev;
+
+	unsigned long mode;		/* MSNDMIDI_MODE_XXXX */
+
+	struct snd_rawmidi_substream *substream_input;
+
+	spinlock_t input_lock;
+};
+
+/*
+ * input/output open/close - protected by open_mutex in rawmidi.c
+ */
+static int snd_msndmidi_input_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_msndmidi *mpu;
+
+	snd_printdd("snd_msndmidi_input_open()\n");
+
+	mpu = substream->rmidi->private_data;
+
+	mpu->substream_input = substream;
+
+	snd_msnd_enable_irq(mpu->dev);
+
+	snd_msnd_send_dsp_cmd(mpu->dev, HDEX_MIDI_IN_START);
+	set_bit(MSNDMIDI_MODE_BIT_INPUT, &mpu->mode);
+	return 0;
+}
+
+static int snd_msndmidi_input_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_msndmidi *mpu;
+
+	mpu = substream->rmidi->private_data;
+	snd_msnd_send_dsp_cmd(mpu->dev, HDEX_MIDI_IN_STOP);
+	clear_bit(MSNDMIDI_MODE_BIT_INPUT, &mpu->mode);
+	mpu->substream_input = NULL;
+	snd_msnd_disable_irq(mpu->dev);
+	return 0;
+}
+
+static void snd_msndmidi_input_drop(struct snd_msndmidi *mpu)
+{
+	u16 tail;
+
+	tail = readw(mpu->dev->MIDQ + JQS_wTail);
+	writew(tail, mpu->dev->MIDQ + JQS_wHead);
+}
+
+/*
+ * trigger input
+ */
+static void snd_msndmidi_input_trigger(struct snd_rawmidi_substream *substream,
+					int up)
+{
+	unsigned long flags;
+	struct snd_msndmidi *mpu;
+
+	snd_printdd("snd_msndmidi_input_trigger(, %i)\n", up);
+
+	mpu = substream->rmidi->private_data;
+	spin_lock_irqsave(&mpu->input_lock, flags);
+	if (up) {
+		if (!test_and_set_bit(MSNDMIDI_MODE_BIT_INPUT_TRIGGER,
+				      &mpu->mode))
+			snd_msndmidi_input_drop(mpu);
+	} else {
+		clear_bit(MSNDMIDI_MODE_BIT_INPUT_TRIGGER, &mpu->mode);
+	}
+	spin_unlock_irqrestore(&mpu->input_lock, flags);
+	if (up)
+		snd_msndmidi_input_read(mpu);
+}
+
+void snd_msndmidi_input_read(void *mpuv)
+{
+	unsigned long flags;
+	struct snd_msndmidi *mpu = mpuv;
+	void __iomem *pwMIDQData = mpu->dev->mappedbase + MIDQ_DATA_BUFF;
+	u16 head, tail, size;
+
+	spin_lock_irqsave(&mpu->input_lock, flags);
+	head = readw(mpu->dev->MIDQ + JQS_wHead);
+	tail = readw(mpu->dev->MIDQ + JQS_wTail);
+	size = readw(mpu->dev->MIDQ + JQS_wSize);
+	if (head > size || tail > size)
+		goto out;
+	while (head != tail) {
+		unsigned char val = readw(pwMIDQData + 2 * head);
+
+		if (test_bit(MSNDMIDI_MODE_BIT_INPUT_TRIGGER, &mpu->mode))
+			snd_rawmidi_receive(mpu->substream_input, &val, 1);
+		if (++head > size)
+			head = 0;
+		writew(head, mpu->dev->MIDQ + JQS_wHead);
+	}
+ out:
+	spin_unlock_irqrestore(&mpu->input_lock, flags);
+}
+EXPORT_SYMBOL(snd_msndmidi_input_read);
+
+static const struct snd_rawmidi_ops snd_msndmidi_input = {
+	.open =		snd_msndmidi_input_open,
+	.close =	snd_msndmidi_input_close,
+	.trigger =	snd_msndmidi_input_trigger,
+};
+
+static void snd_msndmidi_free(struct snd_rawmidi *rmidi)
+{
+	struct snd_msndmidi *mpu = rmidi->private_data;
+	kfree(mpu);
+}
+
+int snd_msndmidi_new(struct snd_card *card, int device)
+{
+	struct snd_msnd *chip = card->private_data;
+	struct snd_msndmidi *mpu;
+	struct snd_rawmidi *rmidi;
+	int err;
+
+	err = snd_rawmidi_new(card, "MSND-MIDI", device, 1, 1, &rmidi);
+	if (err < 0)
+		return err;
+	mpu = kzalloc(sizeof(*mpu), GFP_KERNEL);
+	if (mpu == NULL) {
+		snd_device_free(card, rmidi);
+		return -ENOMEM;
+	}
+	mpu->dev = chip;
+	chip->msndmidi_mpu = mpu;
+	rmidi->private_data = mpu;
+	rmidi->private_free = snd_msndmidi_free;
+	spin_lock_init(&mpu->input_lock);
+	strcpy(rmidi->name, "MSND MIDI");
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+			    &snd_msndmidi_input);
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+	return 0;
+}
diff --git a/sound/isa/msnd/msnd_pinnacle.c b/sound/isa/msnd/msnd_pinnacle.c
new file mode 100644
index 0000000..11af9c4
--- /dev/null
+++ b/sound/isa/msnd/msnd_pinnacle.c
@@ -0,0 +1,1243 @@
+/*********************************************************************
+ *
+ * Linux multisound pinnacle/fiji driver for ALSA.
+ *
+ * 2002/06/30 Karsten Wiese:
+ *	for now this is only used to build a pinnacle / fiji driver.
+ *	the OSS parent of this code is designed to also support
+ *	the multisound classic via the file msnd_classic.c.
+ *	to make it easier for some brave heart to implemt classic
+ *	support in alsa, i left all the MSND_CLASSIC tokens in this file.
+ *	but for now this untested & undone.
+ *
+ *
+ * ripped from linux kernel 2.4.18 by Karsten Wiese.
+ *
+ * the following is a copy of the 2.4.18 OSS FREE file-heading comment:
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ * msnd_pinnacle.c / msnd_classic.c
+ *
+ * -- If MSND_CLASSIC is defined:
+ *
+ *     -> driver for Turtle Beach Classic/Monterey/Tahiti
+ *
+ * -- Else
+ *
+ *     -> driver for Turtle Beach Pinnacle/Fiji
+ *
+ * 12-3-2000  Modified IO port validation  Steve Sycamore
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ********************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/firmware.h>
+#include <linux/isa.h>
+#include <linux/isapnp.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/asound.h>
+#include <sound/pcm.h>
+#include <sound/mpu401.h>
+
+#ifdef MSND_CLASSIC
+# ifndef __alpha__
+#  define SLOWIO
+# endif
+#endif
+#include "msnd.h"
+#ifdef MSND_CLASSIC
+#  include "msnd_classic.h"
+#  define LOGNAME			"msnd_classic"
+#  define DEV_NAME			"msnd-classic"
+#else
+#  include "msnd_pinnacle.h"
+#  define LOGNAME			"snd_msnd_pinnacle"
+#  define DEV_NAME			"msnd-pinnacle"
+#endif
+
+static void set_default_audio_parameters(struct snd_msnd *chip)
+{
+	chip->play_sample_size = snd_pcm_format_width(DEFSAMPLESIZE);
+	chip->play_sample_rate = DEFSAMPLERATE;
+	chip->play_channels = DEFCHANNELS;
+	chip->capture_sample_size = snd_pcm_format_width(DEFSAMPLESIZE);
+	chip->capture_sample_rate = DEFSAMPLERATE;
+	chip->capture_channels = DEFCHANNELS;
+}
+
+static void snd_msnd_eval_dsp_msg(struct snd_msnd *chip, u16 wMessage)
+{
+	switch (HIBYTE(wMessage)) {
+	case HIMT_PLAY_DONE: {
+		if (chip->banksPlayed < 3)
+			snd_printdd("%08X: HIMT_PLAY_DONE: %i\n",
+				(unsigned)jiffies, LOBYTE(wMessage));
+
+		if (chip->last_playbank == LOBYTE(wMessage)) {
+			snd_printdd("chip.last_playbank == LOBYTE(wMessage)\n");
+			break;
+		}
+		chip->banksPlayed++;
+
+		if (test_bit(F_WRITING, &chip->flags))
+			snd_msnd_DAPQ(chip, 0);
+
+		chip->last_playbank = LOBYTE(wMessage);
+		chip->playDMAPos += chip->play_period_bytes;
+		if (chip->playDMAPos > chip->playLimit)
+			chip->playDMAPos = 0;
+		snd_pcm_period_elapsed(chip->playback_substream);
+
+		break;
+	}
+	case HIMT_RECORD_DONE:
+		if (chip->last_recbank == LOBYTE(wMessage))
+			break;
+		chip->last_recbank = LOBYTE(wMessage);
+		chip->captureDMAPos += chip->capturePeriodBytes;
+		if (chip->captureDMAPos > (chip->captureLimit))
+			chip->captureDMAPos = 0;
+
+		if (test_bit(F_READING, &chip->flags))
+			snd_msnd_DARQ(chip, chip->last_recbank);
+
+		snd_pcm_period_elapsed(chip->capture_substream);
+		break;
+
+	case HIMT_DSP:
+		switch (LOBYTE(wMessage)) {
+#ifndef MSND_CLASSIC
+		case HIDSP_PLAY_UNDER:
+#endif
+		case HIDSP_INT_PLAY_UNDER:
+			snd_printd(KERN_WARNING LOGNAME ": Play underflow %i\n",
+				chip->banksPlayed);
+			if (chip->banksPlayed > 2)
+				clear_bit(F_WRITING, &chip->flags);
+			break;
+
+		case HIDSP_INT_RECORD_OVER:
+			snd_printd(KERN_WARNING LOGNAME ": Record overflow\n");
+			clear_bit(F_READING, &chip->flags);
+			break;
+
+		default:
+			snd_printd(KERN_WARNING LOGNAME
+				   ": DSP message %d 0x%02x\n",
+				   LOBYTE(wMessage), LOBYTE(wMessage));
+			break;
+		}
+		break;
+
+	case HIMT_MIDI_IN_UCHAR:
+		if (chip->msndmidi_mpu)
+			snd_msndmidi_input_read(chip->msndmidi_mpu);
+		break;
+
+	default:
+		snd_printd(KERN_WARNING LOGNAME ": HIMT message %d 0x%02x\n",
+			   HIBYTE(wMessage), HIBYTE(wMessage));
+		break;
+	}
+}
+
+static irqreturn_t snd_msnd_interrupt(int irq, void *dev_id)
+{
+	struct snd_msnd *chip = dev_id;
+	void __iomem *pwDSPQData = chip->mappedbase + DSPQ_DATA_BUFF;
+	u16 head, tail, size;
+
+	/* Send ack to DSP */
+	/* inb(chip->io + HP_RXL); */
+
+	/* Evaluate queued DSP messages */
+	head = readw(chip->DSPQ + JQS_wHead);
+	tail = readw(chip->DSPQ + JQS_wTail);
+	size = readw(chip->DSPQ + JQS_wSize);
+	if (head > size || tail > size)
+		goto out;
+	while (head != tail) {
+		snd_msnd_eval_dsp_msg(chip, readw(pwDSPQData + 2 * head));
+		if (++head > size)
+			head = 0;
+		writew(head, chip->DSPQ + JQS_wHead);
+	}
+ out:
+	/* Send ack to DSP */
+	inb(chip->io + HP_RXL);
+	return IRQ_HANDLED;
+}
+
+
+static int snd_msnd_reset_dsp(long io, unsigned char *info)
+{
+	int timeout = 100;
+
+	outb(HPDSPRESET_ON, io + HP_DSPR);
+	msleep(1);
+#ifndef MSND_CLASSIC
+	if (info)
+		*info = inb(io + HP_INFO);
+#endif
+	outb(HPDSPRESET_OFF, io + HP_DSPR);
+	msleep(1);
+	while (timeout-- > 0) {
+		if (inb(io + HP_CVR) == HP_CVR_DEF)
+			return 0;
+		msleep(1);
+	}
+	snd_printk(KERN_ERR LOGNAME ": Cannot reset DSP\n");
+
+	return -EIO;
+}
+
+static int snd_msnd_probe(struct snd_card *card)
+{
+	struct snd_msnd *chip = card->private_data;
+	unsigned char info;
+#ifndef MSND_CLASSIC
+	char *xv, *rev = NULL;
+	char *pin = "TB Pinnacle", *fiji = "TB Fiji";
+	char *pinfiji = "TB Pinnacle/Fiji";
+#endif
+
+	if (!request_region(chip->io, DSP_NUMIO, "probing")) {
+		snd_printk(KERN_ERR LOGNAME ": I/O port conflict\n");
+		return -ENODEV;
+	}
+
+	if (snd_msnd_reset_dsp(chip->io, &info) < 0) {
+		release_region(chip->io, DSP_NUMIO);
+		return -ENODEV;
+	}
+
+#ifdef MSND_CLASSIC
+	strcpy(card->shortname, "Classic/Tahiti/Monterey");
+	strcpy(card->longname, "Turtle Beach Multisound");
+	printk(KERN_INFO LOGNAME ": %s, "
+	       "I/O 0x%lx-0x%lx, IRQ %d, memory mapped to 0x%lX-0x%lX\n",
+	       card->shortname,
+	       chip->io, chip->io + DSP_NUMIO - 1,
+	       chip->irq,
+	       chip->base, chip->base + 0x7fff);
+#else
+	switch (info >> 4) {
+	case 0xf:
+		xv = "<= 1.15";
+		break;
+	case 0x1:
+		xv = "1.18/1.2";
+		break;
+	case 0x2:
+		xv = "1.3";
+		break;
+	case 0x3:
+		xv = "1.4";
+		break;
+	default:
+		xv = "unknown";
+		break;
+	}
+
+	switch (info & 0x7) {
+	case 0x0:
+		rev = "I";
+		strcpy(card->shortname, pin);
+		break;
+	case 0x1:
+		rev = "F";
+		strcpy(card->shortname, pin);
+		break;
+	case 0x2:
+		rev = "G";
+		strcpy(card->shortname, pin);
+		break;
+	case 0x3:
+		rev = "H";
+		strcpy(card->shortname, pin);
+		break;
+	case 0x4:
+		rev = "E";
+		strcpy(card->shortname, fiji);
+		break;
+	case 0x5:
+		rev = "C";
+		strcpy(card->shortname, fiji);
+		break;
+	case 0x6:
+		rev = "D";
+		strcpy(card->shortname, fiji);
+		break;
+	case 0x7:
+		rev = "A-B (Fiji) or A-E (Pinnacle)";
+		strcpy(card->shortname, pinfiji);
+		break;
+	}
+	strcpy(card->longname, "Turtle Beach Multisound Pinnacle");
+	printk(KERN_INFO LOGNAME ": %s revision %s, Xilinx version %s, "
+	       "I/O 0x%lx-0x%lx, IRQ %d, memory mapped to 0x%lX-0x%lX\n",
+	       card->shortname,
+	       rev, xv,
+	       chip->io, chip->io + DSP_NUMIO - 1,
+	       chip->irq,
+	       chip->base, chip->base + 0x7fff);
+#endif
+
+	release_region(chip->io, DSP_NUMIO);
+	return 0;
+}
+
+static int snd_msnd_init_sma(struct snd_msnd *chip)
+{
+	static int initted;
+	u16 mastVolLeft, mastVolRight;
+	unsigned long flags;
+
+#ifdef MSND_CLASSIC
+	outb(chip->memid, chip->io + HP_MEMM);
+#endif
+	outb(HPBLKSEL_0, chip->io + HP_BLKS);
+	/* Motorola 56k shared memory base */
+	chip->SMA = chip->mappedbase + SMA_STRUCT_START;
+
+	if (initted) {
+		mastVolLeft = readw(chip->SMA + SMA_wCurrMastVolLeft);
+		mastVolRight = readw(chip->SMA + SMA_wCurrMastVolRight);
+	} else
+		mastVolLeft = mastVolRight = 0;
+	memset_io(chip->mappedbase, 0, 0x8000);
+
+	/* Critical section: bank 1 access */
+	spin_lock_irqsave(&chip->lock, flags);
+	outb(HPBLKSEL_1, chip->io + HP_BLKS);
+	memset_io(chip->mappedbase, 0, 0x8000);
+	outb(HPBLKSEL_0, chip->io + HP_BLKS);
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	/* Digital audio play queue */
+	chip->DAPQ = chip->mappedbase + DAPQ_OFFSET;
+	snd_msnd_init_queue(chip->DAPQ, DAPQ_DATA_BUFF, DAPQ_BUFF_SIZE);
+
+	/* Digital audio record queue */
+	chip->DARQ = chip->mappedbase + DARQ_OFFSET;
+	snd_msnd_init_queue(chip->DARQ, DARQ_DATA_BUFF, DARQ_BUFF_SIZE);
+
+	/* MIDI out queue */
+	chip->MODQ = chip->mappedbase + MODQ_OFFSET;
+	snd_msnd_init_queue(chip->MODQ, MODQ_DATA_BUFF, MODQ_BUFF_SIZE);
+
+	/* MIDI in queue */
+	chip->MIDQ = chip->mappedbase + MIDQ_OFFSET;
+	snd_msnd_init_queue(chip->MIDQ, MIDQ_DATA_BUFF, MIDQ_BUFF_SIZE);
+
+	/* DSP -> host message queue */
+	chip->DSPQ = chip->mappedbase + DSPQ_OFFSET;
+	snd_msnd_init_queue(chip->DSPQ, DSPQ_DATA_BUFF, DSPQ_BUFF_SIZE);
+
+	/* Setup some DSP values */
+#ifndef MSND_CLASSIC
+	writew(1, chip->SMA + SMA_wCurrPlayFormat);
+	writew(chip->play_sample_size, chip->SMA + SMA_wCurrPlaySampleSize);
+	writew(chip->play_channels, chip->SMA + SMA_wCurrPlayChannels);
+	writew(chip->play_sample_rate, chip->SMA + SMA_wCurrPlaySampleRate);
+#endif
+	writew(chip->play_sample_rate, chip->SMA + SMA_wCalFreqAtoD);
+	writew(mastVolLeft, chip->SMA + SMA_wCurrMastVolLeft);
+	writew(mastVolRight, chip->SMA + SMA_wCurrMastVolRight);
+#ifndef MSND_CLASSIC
+	writel(0x00010000, chip->SMA + SMA_dwCurrPlayPitch);
+	writel(0x00000001, chip->SMA + SMA_dwCurrPlayRate);
+#endif
+	writew(0x303, chip->SMA + SMA_wCurrInputTagBits);
+
+	initted = 1;
+
+	return 0;
+}
+
+
+static int upload_dsp_code(struct snd_card *card)
+{
+	struct snd_msnd *chip = card->private_data;
+	const struct firmware *init_fw = NULL, *perm_fw = NULL;
+	int err;
+
+	outb(HPBLKSEL_0, chip->io + HP_BLKS);
+
+	err = request_firmware(&init_fw, INITCODEFILE, card->dev);
+	if (err < 0) {
+		printk(KERN_ERR LOGNAME ": Error loading " INITCODEFILE);
+		goto cleanup1;
+	}
+	err = request_firmware(&perm_fw, PERMCODEFILE, card->dev);
+	if (err < 0) {
+		printk(KERN_ERR LOGNAME ": Error loading " PERMCODEFILE);
+		goto cleanup;
+	}
+
+	memcpy_toio(chip->mappedbase, perm_fw->data, perm_fw->size);
+	if (snd_msnd_upload_host(chip, init_fw->data, init_fw->size) < 0) {
+		printk(KERN_WARNING LOGNAME ": Error uploading to DSP\n");
+		err = -ENODEV;
+		goto cleanup;
+	}
+	printk(KERN_INFO LOGNAME ": DSP firmware uploaded\n");
+	err = 0;
+
+cleanup:
+	release_firmware(perm_fw);
+cleanup1:
+	release_firmware(init_fw);
+	return err;
+}
+
+#ifdef MSND_CLASSIC
+static void reset_proteus(struct snd_msnd *chip)
+{
+	outb(HPPRORESET_ON, chip->io + HP_PROR);
+	msleep(TIME_PRO_RESET);
+	outb(HPPRORESET_OFF, chip->io + HP_PROR);
+	msleep(TIME_PRO_RESET_DONE);
+}
+#endif
+
+static int snd_msnd_initialize(struct snd_card *card)
+{
+	struct snd_msnd *chip = card->private_data;
+	int err, timeout;
+
+#ifdef MSND_CLASSIC
+	outb(HPWAITSTATE_0, chip->io + HP_WAIT);
+	outb(HPBITMODE_16, chip->io + HP_BITM);
+
+	reset_proteus(chip);
+#endif
+	err = snd_msnd_init_sma(chip);
+	if (err < 0) {
+		printk(KERN_WARNING LOGNAME ": Cannot initialize SMA\n");
+		return err;
+	}
+
+	err = snd_msnd_reset_dsp(chip->io, NULL);
+	if (err < 0)
+		return err;
+
+	err = upload_dsp_code(card);
+	if (err < 0) {
+		printk(KERN_WARNING LOGNAME ": Cannot upload DSP code\n");
+		return err;
+	}
+
+	timeout = 200;
+
+	while (readw(chip->mappedbase)) {
+		msleep(1);
+		if (!timeout--) {
+			snd_printd(KERN_ERR LOGNAME ": DSP reset timeout\n");
+			return -EIO;
+		}
+	}
+
+	snd_msndmix_setup(chip);
+	return 0;
+}
+
+static int snd_msnd_dsp_full_reset(struct snd_card *card)
+{
+	struct snd_msnd *chip = card->private_data;
+	int rv;
+
+	if (test_bit(F_RESETTING, &chip->flags) || ++chip->nresets > 10)
+		return 0;
+
+	set_bit(F_RESETTING, &chip->flags);
+	snd_msnd_dsp_halt(chip, NULL);	/* Unconditionally halt */
+
+	rv = snd_msnd_initialize(card);
+	if (rv)
+		printk(KERN_WARNING LOGNAME ": DSP reset failed\n");
+	snd_msndmix_force_recsrc(chip, 0);
+	clear_bit(F_RESETTING, &chip->flags);
+	return rv;
+}
+
+static int snd_msnd_dev_free(struct snd_device *device)
+{
+	snd_printdd("snd_msnd_chip_free()\n");
+	return 0;
+}
+
+static int snd_msnd_send_dsp_cmd_chk(struct snd_msnd *chip, u8 cmd)
+{
+	if (snd_msnd_send_dsp_cmd(chip, cmd) == 0)
+		return 0;
+	snd_msnd_dsp_full_reset(chip->card);
+	return snd_msnd_send_dsp_cmd(chip, cmd);
+}
+
+static int snd_msnd_calibrate_adc(struct snd_msnd *chip, u16 srate)
+{
+	snd_printdd("snd_msnd_calibrate_adc(%i)\n", srate);
+	writew(srate, chip->SMA + SMA_wCalFreqAtoD);
+	if (chip->calibrate_signal == 0)
+		writew(readw(chip->SMA + SMA_wCurrHostStatusFlags)
+		       | 0x0001, chip->SMA + SMA_wCurrHostStatusFlags);
+	else
+		writew(readw(chip->SMA + SMA_wCurrHostStatusFlags)
+		       & ~0x0001, chip->SMA + SMA_wCurrHostStatusFlags);
+	if (snd_msnd_send_word(chip, 0, 0, HDEXAR_CAL_A_TO_D) == 0 &&
+	    snd_msnd_send_dsp_cmd_chk(chip, HDEX_AUX_REQ) == 0) {
+		schedule_timeout_interruptible(msecs_to_jiffies(333));
+		return 0;
+	}
+	printk(KERN_WARNING LOGNAME ": ADC calibration failed\n");
+	return -EIO;
+}
+
+/*
+ * ALSA callback function, called when attempting to open the MIDI device.
+ */
+static int snd_msnd_mpu401_open(struct snd_mpu401 *mpu)
+{
+	snd_msnd_enable_irq(mpu->private_data);
+	snd_msnd_send_dsp_cmd(mpu->private_data, HDEX_MIDI_IN_START);
+	return 0;
+}
+
+static void snd_msnd_mpu401_close(struct snd_mpu401 *mpu)
+{
+	snd_msnd_send_dsp_cmd(mpu->private_data, HDEX_MIDI_IN_STOP);
+	snd_msnd_disable_irq(mpu->private_data);
+}
+
+static long mpu_io[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+
+static int snd_msnd_attach(struct snd_card *card)
+{
+	struct snd_msnd *chip = card->private_data;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =      snd_msnd_dev_free,
+		};
+
+	err = request_irq(chip->irq, snd_msnd_interrupt, 0, card->shortname,
+			  chip);
+	if (err < 0) {
+		printk(KERN_ERR LOGNAME ": Couldn't grab IRQ %d\n", chip->irq);
+		return err;
+	}
+	if (request_region(chip->io, DSP_NUMIO, card->shortname) == NULL) {
+		free_irq(chip->irq, chip);
+		return -EBUSY;
+	}
+
+	if (!request_mem_region(chip->base, BUFFSIZE, card->shortname)) {
+		printk(KERN_ERR LOGNAME
+			": unable to grab memory region 0x%lx-0x%lx\n",
+			chip->base, chip->base + BUFFSIZE - 1);
+		release_region(chip->io, DSP_NUMIO);
+		free_irq(chip->irq, chip);
+		return -EBUSY;
+	}
+	chip->mappedbase = ioremap_nocache(chip->base, 0x8000);
+	if (!chip->mappedbase) {
+		printk(KERN_ERR LOGNAME
+			": unable to map memory region 0x%lx-0x%lx\n",
+			chip->base, chip->base + BUFFSIZE - 1);
+		err = -EIO;
+		goto err_release_region;
+	}
+
+	err = snd_msnd_dsp_full_reset(card);
+	if (err < 0)
+		goto err_release_region;
+
+	/* Register device */
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err < 0)
+		goto err_release_region;
+
+	err = snd_msnd_pcm(card, 0);
+	if (err < 0) {
+		printk(KERN_ERR LOGNAME ": error creating new PCM device\n");
+		goto err_release_region;
+	}
+
+	err = snd_msndmix_new(card);
+	if (err < 0) {
+		printk(KERN_ERR LOGNAME ": error creating new Mixer device\n");
+		goto err_release_region;
+	}
+
+
+	if (mpu_io[0] != SNDRV_AUTO_PORT) {
+		struct snd_mpu401 *mpu;
+
+		err = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+					  mpu_io[0],
+					  MPU401_MODE_INPUT |
+					  MPU401_MODE_OUTPUT,
+					  mpu_irq[0],
+					  &chip->rmidi);
+		if (err < 0) {
+			printk(KERN_ERR LOGNAME
+				": error creating new Midi device\n");
+			goto err_release_region;
+		}
+		mpu = chip->rmidi->private_data;
+
+		mpu->open_input = snd_msnd_mpu401_open;
+		mpu->close_input = snd_msnd_mpu401_close;
+		mpu->private_data = chip;
+	}
+
+	disable_irq(chip->irq);
+	snd_msnd_calibrate_adc(chip, chip->play_sample_rate);
+	snd_msndmix_force_recsrc(chip, 0);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto err_release_region;
+
+	return 0;
+
+err_release_region:
+	iounmap(chip->mappedbase);
+	release_mem_region(chip->base, BUFFSIZE);
+	release_region(chip->io, DSP_NUMIO);
+	free_irq(chip->irq, chip);
+	return err;
+}
+
+
+static void snd_msnd_unload(struct snd_card *card)
+{
+	struct snd_msnd *chip = card->private_data;
+
+	iounmap(chip->mappedbase);
+	release_mem_region(chip->base, BUFFSIZE);
+	release_region(chip->io, DSP_NUMIO);
+	free_irq(chip->irq, chip);
+	snd_card_free(card);
+}
+
+#ifndef MSND_CLASSIC
+
+/* Pinnacle/Fiji Logical Device Configuration */
+
+static int snd_msnd_write_cfg(int cfg, int reg, int value)
+{
+	outb(reg, cfg);
+	outb(value, cfg + 1);
+	if (value != inb(cfg + 1)) {
+		printk(KERN_ERR LOGNAME ": snd_msnd_write_cfg: I/O error\n");
+		return -EIO;
+	}
+	return 0;
+}
+
+static int snd_msnd_write_cfg_io0(int cfg, int num, u16 io)
+{
+	if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+		return -EIO;
+	if (snd_msnd_write_cfg(cfg, IREG_IO0_BASEHI, HIBYTE(io)))
+		return -EIO;
+	if (snd_msnd_write_cfg(cfg, IREG_IO0_BASELO, LOBYTE(io)))
+		return -EIO;
+	return 0;
+}
+
+static int snd_msnd_write_cfg_io1(int cfg, int num, u16 io)
+{
+	if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+		return -EIO;
+	if (snd_msnd_write_cfg(cfg, IREG_IO1_BASEHI, HIBYTE(io)))
+		return -EIO;
+	if (snd_msnd_write_cfg(cfg, IREG_IO1_BASELO, LOBYTE(io)))
+		return -EIO;
+	return 0;
+}
+
+static int snd_msnd_write_cfg_irq(int cfg, int num, u16 irq)
+{
+	if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+		return -EIO;
+	if (snd_msnd_write_cfg(cfg, IREG_IRQ_NUMBER, LOBYTE(irq)))
+		return -EIO;
+	if (snd_msnd_write_cfg(cfg, IREG_IRQ_TYPE, IRQTYPE_EDGE))
+		return -EIO;
+	return 0;
+}
+
+static int snd_msnd_write_cfg_mem(int cfg, int num, int mem)
+{
+	u16 wmem;
+
+	mem >>= 8;
+	wmem = (u16)(mem & 0xfff);
+	if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+		return -EIO;
+	if (snd_msnd_write_cfg(cfg, IREG_MEMBASEHI, HIBYTE(wmem)))
+		return -EIO;
+	if (snd_msnd_write_cfg(cfg, IREG_MEMBASELO, LOBYTE(wmem)))
+		return -EIO;
+	if (wmem && snd_msnd_write_cfg(cfg, IREG_MEMCONTROL,
+				       MEMTYPE_HIADDR | MEMTYPE_16BIT))
+		return -EIO;
+	return 0;
+}
+
+static int snd_msnd_activate_logical(int cfg, int num)
+{
+	if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+		return -EIO;
+	if (snd_msnd_write_cfg(cfg, IREG_ACTIVATE, LD_ACTIVATE))
+		return -EIO;
+	return 0;
+}
+
+static int snd_msnd_write_cfg_logical(int cfg, int num, u16 io0,
+				      u16 io1, u16 irq, int mem)
+{
+	if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+		return -EIO;
+	if (snd_msnd_write_cfg_io0(cfg, num, io0))
+		return -EIO;
+	if (snd_msnd_write_cfg_io1(cfg, num, io1))
+		return -EIO;
+	if (snd_msnd_write_cfg_irq(cfg, num, irq))
+		return -EIO;
+	if (snd_msnd_write_cfg_mem(cfg, num, mem))
+		return -EIO;
+	if (snd_msnd_activate_logical(cfg, num))
+		return -EIO;
+	return 0;
+}
+
+static int snd_msnd_pinnacle_cfg_reset(int cfg)
+{
+	int i;
+
+	/* Reset devices if told to */
+	printk(KERN_INFO LOGNAME ": Resetting all devices\n");
+	for (i = 0; i < 4; ++i)
+		if (snd_msnd_write_cfg_logical(cfg, i, 0, 0, 0, 0))
+			return -EIO;
+
+	return 0;
+}
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for msnd_pinnacle soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for msnd_pinnacle soundcard.");
+
+static long io[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static long mem[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+
+#ifndef MSND_CLASSIC
+static long cfg[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+
+/* Extra Peripheral Configuration (Default: Disable) */
+static long ide_io0[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static long ide_io1[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int ide_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+
+static long joystick_io[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+/* If we have the digital daugherboard... */
+static int digital[SNDRV_CARDS];
+
+/* Extra Peripheral Configuration */
+static int reset[SNDRV_CARDS];
+#endif
+
+static int write_ndelay[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = 1 };
+
+static int calibrate_signal;
+
+#ifdef CONFIG_PNP
+static bool isapnp[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "ISA PnP detection for specified soundcard.");
+#define has_isapnp(x) isapnp[x]
+#else
+#define has_isapnp(x) 0
+#endif
+
+MODULE_AUTHOR("Karsten Wiese <annabellesgarden@yahoo.de>");
+MODULE_DESCRIPTION("Turtle Beach " LONGNAME " Linux Driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(INITCODEFILE);
+MODULE_FIRMWARE(PERMCODEFILE);
+
+module_param_hw_array(io, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(io, "IO port #");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+module_param_hw_array(mem, long, iomem, NULL, 0444);
+module_param_array(write_ndelay, int, NULL, 0444);
+module_param(calibrate_signal, int, 0444);
+#ifndef MSND_CLASSIC
+module_param_array(digital, int, NULL, 0444);
+module_param_hw_array(cfg, long, ioport, NULL, 0444);
+module_param_array(reset, int, NULL, 0444);
+module_param_hw_array(mpu_io, long, ioport, NULL, 0444);
+module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
+module_param_hw_array(ide_io0, long, ioport, NULL, 0444);
+module_param_hw_array(ide_io1, long, ioport, NULL, 0444);
+module_param_hw_array(ide_irq, int, irq, NULL, 0444);
+module_param_hw_array(joystick_io, long, ioport, NULL, 0444);
+#endif
+
+
+static int snd_msnd_isa_match(struct device *pdev, unsigned int i)
+{
+	if (io[i] == SNDRV_AUTO_PORT)
+		return 0;
+
+	if (irq[i] == SNDRV_AUTO_PORT || mem[i] == SNDRV_AUTO_PORT) {
+		printk(KERN_WARNING LOGNAME ": io, irq and mem must be set\n");
+		return 0;
+	}
+
+#ifdef MSND_CLASSIC
+	if (!(io[i] == 0x290 ||
+	      io[i] == 0x260 ||
+	      io[i] == 0x250 ||
+	      io[i] == 0x240 ||
+	      io[i] == 0x230 ||
+	      io[i] == 0x220 ||
+	      io[i] == 0x210 ||
+	      io[i] == 0x3e0)) {
+		printk(KERN_ERR LOGNAME ": \"io\" - DSP I/O base must be set "
+			" to 0x210, 0x220, 0x230, 0x240, 0x250, 0x260, 0x290, "
+			"or 0x3E0\n");
+		return 0;
+	}
+#else
+	if (io[i] < 0x100 || io[i] > 0x3e0 || (io[i] % 0x10) != 0) {
+		printk(KERN_ERR LOGNAME
+			": \"io\" - DSP I/O base must within the range 0x100 "
+			"to 0x3E0 and must be evenly divisible by 0x10\n");
+		return 0;
+	}
+#endif /* MSND_CLASSIC */
+
+	if (!(irq[i] == 5 ||
+	      irq[i] == 7 ||
+	      irq[i] == 9 ||
+	      irq[i] == 10 ||
+	      irq[i] == 11 ||
+	      irq[i] == 12)) {
+		printk(KERN_ERR LOGNAME
+			": \"irq\" - must be set to 5, 7, 9, 10, 11 or 12\n");
+		return 0;
+	}
+
+	if (!(mem[i] == 0xb0000 ||
+	      mem[i] == 0xc8000 ||
+	      mem[i] == 0xd0000 ||
+	      mem[i] == 0xd8000 ||
+	      mem[i] == 0xe0000 ||
+	      mem[i] == 0xe8000)) {
+		printk(KERN_ERR LOGNAME ": \"mem\" - must be set to "
+		       "0xb0000, 0xc8000, 0xd0000, 0xd8000, 0xe0000 or "
+		       "0xe8000\n");
+		return 0;
+	}
+
+#ifndef MSND_CLASSIC
+	if (cfg[i] == SNDRV_AUTO_PORT) {
+		printk(KERN_INFO LOGNAME ": Assuming PnP mode\n");
+	} else if (cfg[i] != 0x250 && cfg[i] != 0x260 && cfg[i] != 0x270) {
+		printk(KERN_INFO LOGNAME
+			": Config port must be 0x250, 0x260 or 0x270 "
+			"(or unspecified for PnP mode)\n");
+		return 0;
+	}
+#endif /* MSND_CLASSIC */
+
+	return 1;
+}
+
+static int snd_msnd_isa_probe(struct device *pdev, unsigned int idx)
+{
+	int err;
+	struct snd_card *card;
+	struct snd_msnd *chip;
+
+	if (has_isapnp(idx)
+#ifndef MSND_CLASSIC
+	    || cfg[idx] == SNDRV_AUTO_PORT
+#endif
+	    ) {
+		printk(KERN_INFO LOGNAME ": Assuming PnP mode\n");
+		return -ENODEV;
+	}
+
+	err = snd_card_new(pdev, index[idx], id[idx], THIS_MODULE,
+			   sizeof(struct snd_msnd), &card);
+	if (err < 0)
+		return err;
+
+	chip = card->private_data;
+	chip->card = card;
+
+#ifdef MSND_CLASSIC
+	switch (irq[idx]) {
+	case 5:
+		chip->irqid = HPIRQ_5; break;
+	case 7:
+		chip->irqid = HPIRQ_7; break;
+	case 9:
+		chip->irqid = HPIRQ_9; break;
+	case 10:
+		chip->irqid = HPIRQ_10; break;
+	case 11:
+		chip->irqid = HPIRQ_11; break;
+	case 12:
+		chip->irqid = HPIRQ_12; break;
+	}
+
+	switch (mem[idx]) {
+	case 0xb0000:
+		chip->memid = HPMEM_B000; break;
+	case 0xc8000:
+		chip->memid = HPMEM_C800; break;
+	case 0xd0000:
+		chip->memid = HPMEM_D000; break;
+	case 0xd8000:
+		chip->memid = HPMEM_D800; break;
+	case 0xe0000:
+		chip->memid = HPMEM_E000; break;
+	case 0xe8000:
+		chip->memid = HPMEM_E800; break;
+	}
+#else
+	printk(KERN_INFO LOGNAME ": Non-PnP mode: configuring at port 0x%lx\n",
+			cfg[idx]);
+
+	if (!request_region(cfg[idx], 2, "Pinnacle/Fiji Config")) {
+		printk(KERN_ERR LOGNAME ": Config port 0x%lx conflict\n",
+			   cfg[idx]);
+		snd_card_free(card);
+		return -EIO;
+	}
+	if (reset[idx])
+		if (snd_msnd_pinnacle_cfg_reset(cfg[idx])) {
+			err = -EIO;
+			goto cfg_error;
+		}
+
+	/* DSP */
+	err = snd_msnd_write_cfg_logical(cfg[idx], 0,
+					 io[idx], 0,
+					 irq[idx], mem[idx]);
+
+	if (err)
+		goto cfg_error;
+
+	/* The following are Pinnacle specific */
+
+	/* MPU */
+	if (mpu_io[idx] != SNDRV_AUTO_PORT
+	    && mpu_irq[idx] != SNDRV_AUTO_IRQ) {
+		printk(KERN_INFO LOGNAME
+		       ": Configuring MPU to I/O 0x%lx IRQ %d\n",
+		       mpu_io[idx], mpu_irq[idx]);
+		err = snd_msnd_write_cfg_logical(cfg[idx], 1,
+						 mpu_io[idx], 0,
+						 mpu_irq[idx], 0);
+
+		if (err)
+			goto cfg_error;
+	}
+
+	/* IDE */
+	if (ide_io0[idx] != SNDRV_AUTO_PORT
+	    && ide_io1[idx] != SNDRV_AUTO_PORT
+	    && ide_irq[idx] != SNDRV_AUTO_IRQ) {
+		printk(KERN_INFO LOGNAME
+		       ": Configuring IDE to I/O 0x%lx, 0x%lx IRQ %d\n",
+		       ide_io0[idx], ide_io1[idx], ide_irq[idx]);
+		err = snd_msnd_write_cfg_logical(cfg[idx], 2,
+						 ide_io0[idx], ide_io1[idx],
+						 ide_irq[idx], 0);
+
+		if (err)
+			goto cfg_error;
+	}
+
+	/* Joystick */
+	if (joystick_io[idx] != SNDRV_AUTO_PORT) {
+		printk(KERN_INFO LOGNAME
+		       ": Configuring joystick to I/O 0x%lx\n",
+		       joystick_io[idx]);
+		err = snd_msnd_write_cfg_logical(cfg[idx], 3,
+						 joystick_io[idx], 0,
+						 0, 0);
+
+		if (err)
+			goto cfg_error;
+	}
+	release_region(cfg[idx], 2);
+
+#endif /* MSND_CLASSIC */
+
+	set_default_audio_parameters(chip);
+#ifdef MSND_CLASSIC
+	chip->type = msndClassic;
+#else
+	chip->type = msndPinnacle;
+#endif
+	chip->io = io[idx];
+	chip->irq = irq[idx];
+	chip->base = mem[idx];
+
+	chip->calibrate_signal = calibrate_signal ? 1 : 0;
+	chip->recsrc = 0;
+	chip->dspq_data_buff = DSPQ_DATA_BUFF;
+	chip->dspq_buff_size = DSPQ_BUFF_SIZE;
+	if (write_ndelay[idx])
+		clear_bit(F_DISABLE_WRITE_NDELAY, &chip->flags);
+	else
+		set_bit(F_DISABLE_WRITE_NDELAY, &chip->flags);
+#ifndef MSND_CLASSIC
+	if (digital[idx])
+		set_bit(F_HAVEDIGITAL, &chip->flags);
+#endif
+	spin_lock_init(&chip->lock);
+	err = snd_msnd_probe(card);
+	if (err < 0) {
+		printk(KERN_ERR LOGNAME ": Probe failed\n");
+		snd_card_free(card);
+		return err;
+	}
+
+	err = snd_msnd_attach(card);
+	if (err < 0) {
+		printk(KERN_ERR LOGNAME ": Attach failed\n");
+		snd_card_free(card);
+		return err;
+	}
+	dev_set_drvdata(pdev, card);
+
+	return 0;
+
+#ifndef MSND_CLASSIC
+cfg_error:
+	release_region(cfg[idx], 2);
+	snd_card_free(card);
+	return err;
+#endif
+}
+
+static int snd_msnd_isa_remove(struct device *pdev, unsigned int dev)
+{
+	snd_msnd_unload(dev_get_drvdata(pdev));
+	return 0;
+}
+
+static struct isa_driver snd_msnd_driver = {
+	.match		= snd_msnd_isa_match,
+	.probe		= snd_msnd_isa_probe,
+	.remove		= snd_msnd_isa_remove,
+	/* FIXME: suspend, resume */
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+#ifdef CONFIG_PNP
+static int snd_msnd_pnp_detect(struct pnp_card_link *pcard,
+			       const struct pnp_card_device_id *pid)
+{
+	static int idx;
+	struct pnp_dev *pnp_dev;
+	struct pnp_dev *mpu_dev;
+	struct snd_card *card;
+	struct snd_msnd *chip;
+	int ret;
+
+	for ( ; idx < SNDRV_CARDS; idx++) {
+		if (has_isapnp(idx))
+			break;
+	}
+	if (idx >= SNDRV_CARDS)
+		return -ENODEV;
+
+	/*
+	 * Check that we still have room for another sound card ...
+	 */
+	pnp_dev = pnp_request_card_device(pcard, pid->devs[0].id, NULL);
+	if (!pnp_dev)
+		return -ENODEV;
+
+	mpu_dev = pnp_request_card_device(pcard, pid->devs[1].id, NULL);
+	if (!mpu_dev)
+		return -ENODEV;
+
+	if (!pnp_is_active(pnp_dev) && pnp_activate_dev(pnp_dev) < 0) {
+		printk(KERN_INFO "msnd_pinnacle: device is inactive\n");
+		return -EBUSY;
+	}
+
+	if (!pnp_is_active(mpu_dev) && pnp_activate_dev(mpu_dev) < 0) {
+		printk(KERN_INFO "msnd_pinnacle: MPU device is inactive\n");
+		return -EBUSY;
+	}
+
+	/*
+	 * Create a new ALSA sound card entry, in anticipation
+	 * of detecting our hardware ...
+	 */
+	ret = snd_card_new(&pcard->card->dev,
+			   index[idx], id[idx], THIS_MODULE,
+			   sizeof(struct snd_msnd), &card);
+	if (ret < 0)
+		return ret;
+
+	chip = card->private_data;
+	chip->card = card;
+
+	/*
+	 * Read the correct parameters off the ISA PnP bus ...
+	 */
+	io[idx] = pnp_port_start(pnp_dev, 0);
+	irq[idx] = pnp_irq(pnp_dev, 0);
+	mem[idx] = pnp_mem_start(pnp_dev, 0);
+	mpu_io[idx] = pnp_port_start(mpu_dev, 0);
+	mpu_irq[idx] = pnp_irq(mpu_dev, 0);
+
+	set_default_audio_parameters(chip);
+#ifdef MSND_CLASSIC
+	chip->type = msndClassic;
+#else
+	chip->type = msndPinnacle;
+#endif
+	chip->io = io[idx];
+	chip->irq = irq[idx];
+	chip->base = mem[idx];
+
+	chip->calibrate_signal = calibrate_signal ? 1 : 0;
+	chip->recsrc = 0;
+	chip->dspq_data_buff = DSPQ_DATA_BUFF;
+	chip->dspq_buff_size = DSPQ_BUFF_SIZE;
+	if (write_ndelay[idx])
+		clear_bit(F_DISABLE_WRITE_NDELAY, &chip->flags);
+	else
+		set_bit(F_DISABLE_WRITE_NDELAY, &chip->flags);
+#ifndef MSND_CLASSIC
+	if (digital[idx])
+		set_bit(F_HAVEDIGITAL, &chip->flags);
+#endif
+	spin_lock_init(&chip->lock);
+	ret = snd_msnd_probe(card);
+	if (ret < 0) {
+		printk(KERN_ERR LOGNAME ": Probe failed\n");
+		goto _release_card;
+	}
+
+	ret = snd_msnd_attach(card);
+	if (ret < 0) {
+		printk(KERN_ERR LOGNAME ": Attach failed\n");
+		goto _release_card;
+	}
+
+	pnp_set_card_drvdata(pcard, card);
+	++idx;
+	return 0;
+
+_release_card:
+	snd_card_free(card);
+	return ret;
+}
+
+static void snd_msnd_pnp_remove(struct pnp_card_link *pcard)
+{
+	snd_msnd_unload(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+static int isa_registered;
+static int pnp_registered;
+
+static const struct pnp_card_device_id msnd_pnpids[] = {
+	/* Pinnacle PnP */
+	{ .id = "BVJ0440", .devs = { { "TBS0000" }, { "TBS0001" } } },
+	{ .id = "" }	/* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, msnd_pnpids);
+
+static struct pnp_card_driver msnd_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DO_NOT_CHANGE,
+	.name = "msnd_pinnacle",
+	.id_table = msnd_pnpids,
+	.probe = snd_msnd_pnp_detect,
+	.remove = snd_msnd_pnp_remove,
+};
+#endif /* CONFIG_PNP */
+
+static int __init snd_msnd_init(void)
+{
+	int err;
+
+	err = isa_register_driver(&snd_msnd_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+	if (!err)
+		isa_registered = 1;
+
+	err = pnp_register_card_driver(&msnd_pnpc_driver);
+	if (!err)
+		pnp_registered = 1;
+
+	if (isa_registered)
+		err = 0;
+#endif
+	return err;
+}
+
+static void __exit snd_msnd_exit(void)
+{
+#ifdef CONFIG_PNP
+	if (pnp_registered)
+		pnp_unregister_card_driver(&msnd_pnpc_driver);
+	if (isa_registered)
+#endif
+		isa_unregister_driver(&snd_msnd_driver);
+}
+
+module_init(snd_msnd_init);
+module_exit(snd_msnd_exit);
+
diff --git a/sound/isa/msnd/msnd_pinnacle.h b/sound/isa/msnd/msnd_pinnacle.h
new file mode 100644
index 0000000..48318d1
--- /dev/null
+++ b/sound/isa/msnd/msnd_pinnacle.h
@@ -0,0 +1,181 @@
+/*********************************************************************
+ *
+ * msnd_pinnacle.h
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ *
+ * Some parts of this header file were derived from the Turtle Beach
+ * MultiSound Driver Development Kit.
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ * Copyright (C) 1993 Turtle Beach Systems, Inc.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ********************************************************************/
+#ifndef __MSND_PINNACLE_H
+#define __MSND_PINNACLE_H
+
+#define DSP_NUMIO				0x08
+
+#define IREG_LOGDEVICE				0x07
+#define IREG_ACTIVATE				0x30
+#define LD_ACTIVATE				0x01
+#define LD_DISACTIVATE				0x00
+#define IREG_EECONTROL				0x3F
+#define IREG_MEMBASEHI				0x40
+#define IREG_MEMBASELO				0x41
+#define IREG_MEMCONTROL				0x42
+#define IREG_MEMRANGEHI				0x43
+#define IREG_MEMRANGELO				0x44
+#define MEMTYPE_8BIT				0x00
+#define MEMTYPE_16BIT				0x02
+#define MEMTYPE_RANGE				0x00
+#define MEMTYPE_HIADDR				0x01
+#define IREG_IO0_BASEHI				0x60
+#define IREG_IO0_BASELO				0x61
+#define IREG_IO1_BASEHI				0x62
+#define IREG_IO1_BASELO				0x63
+#define IREG_IRQ_NUMBER				0x70
+#define IREG_IRQ_TYPE				0x71
+#define IRQTYPE_HIGH				0x02
+#define IRQTYPE_LOW				0x00
+#define IRQTYPE_LEVEL				0x01
+#define IRQTYPE_EDGE				0x00
+
+#define	HP_DSPR					0x04
+#define	HP_BLKS					0x04
+
+#define HPDSPRESET_OFF				2
+#define HPDSPRESET_ON				0
+
+#define HPBLKSEL_0				2
+#define HPBLKSEL_1				3
+
+#define	HIMT_DAT_OFF				0x03
+
+#define	HIDSP_PLAY_UNDER			0x00
+#define	HIDSP_INT_PLAY_UNDER			0x01
+#define	HIDSP_SSI_TX_UNDER  			0x02
+#define HIDSP_RECQ_OVERFLOW			0x08
+#define HIDSP_INT_RECORD_OVER			0x09
+#define HIDSP_SSI_RX_OVERFLOW			0x0a
+
+#define	HIDSP_MIDI_IN_OVER			0x10
+
+#define	HIDSP_MIDI_FRAME_ERR			0x11
+#define	HIDSP_MIDI_PARITY_ERR			0x12
+#define	HIDSP_MIDI_OVERRUN_ERR			0x13
+
+#define HIDSP_INPUT_CLIPPING			0x20
+#define	HIDSP_MIX_CLIPPING			0x30
+#define HIDSP_DAT_IN_OFF			0x21
+
+#define TIME_PRO_RESET_DONE			0x028A
+#define TIME_PRO_SYSEX				0x001E
+#define TIME_PRO_RESET				0x0032
+
+#define DAR_BUFF_SIZE				0x1000
+
+#define MIDQ_BUFF_SIZE				0x800
+#define DSPQ_BUFF_SIZE				0x5A0
+
+#define DSPQ_DATA_BUFF				0x7860
+
+#define MOP_WAVEHDR				0
+#define MOP_EXTOUT				1
+#define MOP_HWINIT				0xfe
+#define MOP_NONE				0xff
+#define MOP_MAX					1
+
+#define MIP_EXTIN				0
+#define MIP_WAVEHDR				1
+#define MIP_HWINIT				0xfe
+#define MIP_MAX					1
+
+/* Pinnacle/Fiji SMA Common Data */
+#define SMA_wCurrPlayBytes			0x0000
+#define SMA_wCurrRecordBytes			0x0002
+#define SMA_wCurrPlayVolLeft			0x0004
+#define SMA_wCurrPlayVolRight			0x0006
+#define SMA_wCurrInVolLeft			0x0008
+#define SMA_wCurrInVolRight			0x000a
+#define SMA_wCurrMHdrVolLeft			0x000c
+#define SMA_wCurrMHdrVolRight			0x000e
+#define SMA_dwCurrPlayPitch			0x0010
+#define SMA_dwCurrPlayRate			0x0014
+#define SMA_wCurrMIDIIOPatch			0x0018
+#define SMA_wCurrPlayFormat			0x001a
+#define SMA_wCurrPlaySampleSize			0x001c
+#define SMA_wCurrPlayChannels			0x001e
+#define SMA_wCurrPlaySampleRate			0x0020
+#define SMA_wCurrRecordFormat			0x0022
+#define SMA_wCurrRecordSampleSize		0x0024
+#define SMA_wCurrRecordChannels			0x0026
+#define SMA_wCurrRecordSampleRate		0x0028
+#define SMA_wCurrDSPStatusFlags			0x002a
+#define SMA_wCurrHostStatusFlags		0x002c
+#define SMA_wCurrInputTagBits			0x002e
+#define SMA_wCurrLeftPeak			0x0030
+#define SMA_wCurrRightPeak			0x0032
+#define SMA_bMicPotPosLeft			0x0034
+#define SMA_bMicPotPosRight			0x0035
+#define SMA_bMicPotMaxLeft			0x0036
+#define SMA_bMicPotMaxRight			0x0037
+#define SMA_bInPotPosLeft			0x0038
+#define SMA_bInPotPosRight			0x0039
+#define SMA_bAuxPotPosLeft			0x003a
+#define SMA_bAuxPotPosRight			0x003b
+#define SMA_bInPotMaxLeft			0x003c
+#define SMA_bInPotMaxRight			0x003d
+#define SMA_bAuxPotMaxLeft			0x003e
+#define SMA_bAuxPotMaxRight			0x003f
+#define SMA_bInPotMaxMethod			0x0040
+#define SMA_bAuxPotMaxMethod			0x0041
+#define SMA_wCurrMastVolLeft			0x0042
+#define SMA_wCurrMastVolRight			0x0044
+#define SMA_wCalFreqAtoD			0x0046
+#define SMA_wCurrAuxVolLeft			0x0048
+#define SMA_wCurrAuxVolRight			0x004a
+#define SMA_wCurrPlay1VolLeft			0x004c
+#define SMA_wCurrPlay1VolRight			0x004e
+#define SMA_wCurrPlay2VolLeft			0x0050
+#define SMA_wCurrPlay2VolRight			0x0052
+#define SMA_wCurrPlay3VolLeft			0x0054
+#define SMA_wCurrPlay3VolRight			0x0056
+#define SMA_wCurrPlay4VolLeft			0x0058
+#define SMA_wCurrPlay4VolRight			0x005a
+#define SMA_wCurrPlay1PeakLeft			0x005c
+#define SMA_wCurrPlay1PeakRight			0x005e
+#define SMA_wCurrPlay2PeakLeft			0x0060
+#define SMA_wCurrPlay2PeakRight			0x0062
+#define SMA_wCurrPlay3PeakLeft			0x0064
+#define SMA_wCurrPlay3PeakRight			0x0066
+#define SMA_wCurrPlay4PeakLeft			0x0068
+#define SMA_wCurrPlay4PeakRight			0x006a
+#define SMA_wCurrPlayPeakLeft			0x006c
+#define SMA_wCurrPlayPeakRight			0x006e
+#define SMA_wCurrDATSR				0x0070
+#define SMA_wCurrDATRXCHNL			0x0072
+#define SMA_wCurrDATTXCHNL			0x0074
+#define SMA_wCurrDATRXRate			0x0076
+#define SMA_dwDSPPlayCount			0x0078
+#define SMA__size				0x007c
+
+#define INITCODEFILE		"turtlebeach/pndspini.bin"
+#define PERMCODEFILE		"turtlebeach/pndsperm.bin"
+#define LONGNAME		"MultiSound (Pinnacle/Fiji)"
+
+#endif /* __MSND_PINNACLE_H */
diff --git a/sound/isa/msnd/msnd_pinnacle_mixer.c b/sound/isa/msnd/msnd_pinnacle_mixer.c
new file mode 100644
index 0000000..b408540
--- /dev/null
+++ b/sound/isa/msnd/msnd_pinnacle_mixer.c
@@ -0,0 +1,338 @@
+/***************************************************************************
+			  msnd_pinnacle_mixer.c  -  description
+			     -------------------
+    begin		: Fre Jun 7 2002
+    copyright 		: (C) 2002 by karsten wiese
+    email		: annabellesgarden@yahoo.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.				   *
+ *									   *
+ ***************************************************************************/
+
+#include <linux/io.h>
+#include <linux/export.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include "msnd.h"
+#include "msnd_pinnacle.h"
+
+
+#define MSND_MIXER_VOLUME	0
+#define MSND_MIXER_PCM		1
+#define MSND_MIXER_AUX		2	/* Input source 1  (aux1) */
+#define MSND_MIXER_IMIX		3	/*  Recording monitor  */
+#define MSND_MIXER_SYNTH	4
+#define MSND_MIXER_SPEAKER	5
+#define MSND_MIXER_LINE		6
+#define MSND_MIXER_MIC		7
+#define MSND_MIXER_RECLEV	11	/* Recording level */
+#define MSND_MIXER_IGAIN	12	/* Input gain */
+#define MSND_MIXER_OGAIN	13	/* Output gain */
+#define MSND_MIXER_DIGITAL	17	/* Digital (input) 1 */
+
+/*	Device mask bits	*/
+
+#define MSND_MASK_VOLUME	(1 << MSND_MIXER_VOLUME)
+#define MSND_MASK_SYNTH		(1 << MSND_MIXER_SYNTH)
+#define MSND_MASK_PCM		(1 << MSND_MIXER_PCM)
+#define MSND_MASK_SPEAKER	(1 << MSND_MIXER_SPEAKER)
+#define MSND_MASK_LINE		(1 << MSND_MIXER_LINE)
+#define MSND_MASK_MIC		(1 << MSND_MIXER_MIC)
+#define MSND_MASK_IMIX		(1 << MSND_MIXER_IMIX)
+#define MSND_MASK_RECLEV	(1 << MSND_MIXER_RECLEV)
+#define MSND_MASK_IGAIN		(1 << MSND_MIXER_IGAIN)
+#define MSND_MASK_OGAIN		(1 << MSND_MIXER_OGAIN)
+#define MSND_MASK_AUX		(1 << MSND_MIXER_AUX)
+#define MSND_MASK_DIGITAL	(1 << MSND_MIXER_DIGITAL)
+
+static int snd_msndmix_info_mux(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts[3] = {
+		"Analog", "MASS", "SPDIF",
+	};
+	struct snd_msnd *chip = snd_kcontrol_chip(kcontrol);
+	unsigned items = test_bit(F_HAVEDIGITAL, &chip->flags) ? 3 : 2;
+
+	return snd_ctl_enum_info(uinfo, 1, items, texts);
+}
+
+static int snd_msndmix_get_mux(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_msnd *chip = snd_kcontrol_chip(kcontrol);
+	/* MSND_MASK_IMIX is the default */
+	ucontrol->value.enumerated.item[0] = 0;
+
+	if (chip->recsrc & MSND_MASK_SYNTH) {
+		ucontrol->value.enumerated.item[0] = 1;
+	} else if ((chip->recsrc & MSND_MASK_DIGITAL) &&
+		 test_bit(F_HAVEDIGITAL, &chip->flags)) {
+		ucontrol->value.enumerated.item[0] = 2;
+	}
+
+
+	return 0;
+}
+
+static int snd_msndmix_set_mux(struct snd_msnd *chip, int val)
+{
+	unsigned newrecsrc;
+	int change;
+	unsigned char msndbyte;
+
+	switch (val) {
+	case 0:
+		newrecsrc = MSND_MASK_IMIX;
+		msndbyte = HDEXAR_SET_ANA_IN;
+		break;
+	case 1:
+		newrecsrc = MSND_MASK_SYNTH;
+		msndbyte = HDEXAR_SET_SYNTH_IN;
+		break;
+	case 2:
+		newrecsrc = MSND_MASK_DIGITAL;
+		msndbyte = HDEXAR_SET_DAT_IN;
+		break;
+	default:
+		return -EINVAL;
+	}
+	change  = newrecsrc != chip->recsrc;
+	if (change) {
+		change = 0;
+		if (!snd_msnd_send_word(chip, 0, 0, msndbyte))
+			if (!snd_msnd_send_dsp_cmd(chip, HDEX_AUX_REQ)) {
+				chip->recsrc = newrecsrc;
+				change = 1;
+			}
+	}
+	return change;
+}
+
+static int snd_msndmix_put_mux(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_msnd *msnd = snd_kcontrol_chip(kcontrol);
+	return snd_msndmix_set_mux(msnd, ucontrol->value.enumerated.item[0]);
+}
+
+
+static int snd_msndmix_volume_info(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 100;
+	return 0;
+}
+
+static int snd_msndmix_volume_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_msnd *msnd = snd_kcontrol_chip(kcontrol);
+	int addr = kcontrol->private_value;
+	unsigned long flags;
+
+	spin_lock_irqsave(&msnd->mixer_lock, flags);
+	ucontrol->value.integer.value[0] = msnd->left_levels[addr] * 100;
+	ucontrol->value.integer.value[0] /= 0xFFFF;
+	ucontrol->value.integer.value[1] = msnd->right_levels[addr] * 100;
+	ucontrol->value.integer.value[1] /= 0xFFFF;
+	spin_unlock_irqrestore(&msnd->mixer_lock, flags);
+	return 0;
+}
+
+#define update_volm(a, b)						\
+	do {								\
+		writew((dev->left_levels[a] >> 1) *			\
+		       readw(dev->SMA + SMA_wCurrMastVolLeft) / 0xffff,	\
+		       dev->SMA + SMA_##b##Left);			\
+		writew((dev->right_levels[a] >> 1)  *			\
+		       readw(dev->SMA + SMA_wCurrMastVolRight) / 0xffff, \
+		       dev->SMA + SMA_##b##Right);			\
+	} while (0);
+
+#define update_potm(d, s, ar)						\
+	do {								\
+		writeb((dev->left_levels[d] >> 8) *			\
+		       readw(dev->SMA + SMA_wCurrMastVolLeft) / 0xffff, \
+		       dev->SMA + SMA_##s##Left);			\
+		writeb((dev->right_levels[d] >> 8) *			\
+		       readw(dev->SMA + SMA_wCurrMastVolRight) / 0xffff, \
+		       dev->SMA + SMA_##s##Right);			\
+		if (snd_msnd_send_word(dev, 0, 0, ar) == 0)		\
+			snd_msnd_send_dsp_cmd(dev, HDEX_AUX_REQ);	\
+	} while (0);
+
+#define update_pot(d, s, ar)						\
+	do {								\
+		writeb(dev->left_levels[d] >> 8,			\
+		       dev->SMA + SMA_##s##Left);			\
+		writeb(dev->right_levels[d] >> 8,			\
+		       dev->SMA + SMA_##s##Right);			\
+		if (snd_msnd_send_word(dev, 0, 0, ar) == 0)		\
+			snd_msnd_send_dsp_cmd(dev, HDEX_AUX_REQ);	\
+	} while (0);
+
+
+static int snd_msndmix_set(struct snd_msnd *dev, int d, int left, int right)
+{
+	int bLeft, bRight;
+	int wLeft, wRight;
+	int updatemaster = 0;
+
+	if (d >= LEVEL_ENTRIES)
+		return -EINVAL;
+
+	bLeft = left * 0xff / 100;
+	wLeft = left * 0xffff / 100;
+
+	bRight = right * 0xff / 100;
+	wRight = right * 0xffff / 100;
+
+	dev->left_levels[d] = wLeft;
+	dev->right_levels[d] = wRight;
+
+	switch (d) {
+		/* master volume unscaled controls */
+	case MSND_MIXER_LINE:			/* line pot control */
+		/* scaled by IMIX in digital mix */
+		writeb(bLeft, dev->SMA + SMA_bInPotPosLeft);
+		writeb(bRight, dev->SMA + SMA_bInPotPosRight);
+		if (snd_msnd_send_word(dev, 0, 0, HDEXAR_IN_SET_POTS) == 0)
+			snd_msnd_send_dsp_cmd(dev, HDEX_AUX_REQ);
+		break;
+	case MSND_MIXER_MIC:			/* mic pot control */
+		if (dev->type == msndClassic)
+			return -EINVAL;
+		/* scaled by IMIX in digital mix */
+		writeb(bLeft, dev->SMA + SMA_bMicPotPosLeft);
+		writeb(bRight, dev->SMA + SMA_bMicPotPosRight);
+		if (snd_msnd_send_word(dev, 0, 0, HDEXAR_MIC_SET_POTS) == 0)
+			snd_msnd_send_dsp_cmd(dev, HDEX_AUX_REQ);
+		break;
+	case MSND_MIXER_VOLUME:		/* master volume */
+		writew(wLeft, dev->SMA + SMA_wCurrMastVolLeft);
+		writew(wRight, dev->SMA + SMA_wCurrMastVolRight);
+		/* fall through */
+
+	case MSND_MIXER_AUX:			/* aux pot control */
+		/* scaled by master volume */
+		/* fall through */
+
+		/* digital controls */
+	case MSND_MIXER_SYNTH:			/* synth vol (dsp mix) */
+	case MSND_MIXER_PCM:			/* pcm vol (dsp mix) */
+	case MSND_MIXER_IMIX:			/* input monitor (dsp mix) */
+		/* scaled by master volume */
+		updatemaster = 1;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	if (updatemaster) {
+		/* update master volume scaled controls */
+		update_volm(MSND_MIXER_PCM, wCurrPlayVol);
+		update_volm(MSND_MIXER_IMIX, wCurrInVol);
+		if (dev->type == msndPinnacle)
+			update_volm(MSND_MIXER_SYNTH, wCurrMHdrVol);
+		update_potm(MSND_MIXER_AUX, bAuxPotPos, HDEXAR_AUX_SET_POTS);
+	}
+
+	return 0;
+}
+
+static int snd_msndmix_volume_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_msnd *msnd = snd_kcontrol_chip(kcontrol);
+	int change, addr = kcontrol->private_value;
+	int left, right;
+	unsigned long flags;
+
+	left = ucontrol->value.integer.value[0] % 101;
+	right = ucontrol->value.integer.value[1] % 101;
+	spin_lock_irqsave(&msnd->mixer_lock, flags);
+	change = msnd->left_levels[addr] != left
+		|| msnd->right_levels[addr] != right;
+	snd_msndmix_set(msnd, addr, left, right);
+	spin_unlock_irqrestore(&msnd->mixer_lock, flags);
+	return change;
+}
+
+
+#define DUMMY_VOLUME(xname, xindex, addr) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_msndmix_volume_info, \
+  .get = snd_msndmix_volume_get, .put = snd_msndmix_volume_put, \
+  .private_value = addr }
+
+
+static struct snd_kcontrol_new snd_msnd_controls[] = {
+DUMMY_VOLUME("Master Volume", 0, MSND_MIXER_VOLUME),
+DUMMY_VOLUME("PCM Volume", 0, MSND_MIXER_PCM),
+DUMMY_VOLUME("Aux Volume", 0, MSND_MIXER_AUX),
+DUMMY_VOLUME("Line Volume", 0, MSND_MIXER_LINE),
+DUMMY_VOLUME("Mic Volume", 0, MSND_MIXER_MIC),
+DUMMY_VOLUME("Monitor",	0, MSND_MIXER_IMIX),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Capture Source",
+	.info = snd_msndmix_info_mux,
+	.get = snd_msndmix_get_mux,
+	.put = snd_msndmix_put_mux,
+}
+};
+
+
+int snd_msndmix_new(struct snd_card *card)
+{
+	struct snd_msnd *chip = card->private_data;
+	unsigned int idx;
+	int err;
+
+	if (snd_BUG_ON(!chip))
+		return -EINVAL;
+	spin_lock_init(&chip->mixer_lock);
+	strcpy(card->mixername, "MSND Pinnacle Mixer");
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_msnd_controls); idx++) {
+		err = snd_ctl_add(card,
+				  snd_ctl_new1(snd_msnd_controls + idx, chip));
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(snd_msndmix_new);
+
+void snd_msndmix_setup(struct snd_msnd *dev)
+{
+	update_pot(MSND_MIXER_LINE, bInPotPos, HDEXAR_IN_SET_POTS);
+	update_potm(MSND_MIXER_AUX, bAuxPotPos, HDEXAR_AUX_SET_POTS);
+	update_volm(MSND_MIXER_PCM, wCurrPlayVol);
+	update_volm(MSND_MIXER_IMIX, wCurrInVol);
+	if (dev->type == msndPinnacle) {
+		update_pot(MSND_MIXER_MIC, bMicPotPos, HDEXAR_MIC_SET_POTS);
+		update_volm(MSND_MIXER_SYNTH, wCurrMHdrVol);
+	}
+}
+EXPORT_SYMBOL(snd_msndmix_setup);
+
+int snd_msndmix_force_recsrc(struct snd_msnd *dev, int recsrc)
+{
+	dev->recsrc = -1;
+	return snd_msndmix_set_mux(dev, recsrc);
+}
+EXPORT_SYMBOL(snd_msndmix_force_recsrc);
diff --git a/sound/isa/opl3sa2.c b/sound/isa/opl3sa2.c
new file mode 100644
index 0000000..7cce4cd
--- /dev/null
+++ b/sound/isa/opl3sa2.c
@@ -0,0 +1,966 @@
+/*
+ *  Driver for Yamaha OPL3-SA[2,3] soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/interrupt.h>
+#include <linux/pm.h>
+#include <linux/pnp.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.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 bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static bool 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,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_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for OPL3-SA driver.");
+module_param_hw_array(sb_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(sb_port, "SB port # for OPL3-SA driver.");
+module_param_hw_array(wss_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(wss_port, "WSS port # for OPL3-SA driver.");
+module_param_hw_array(fm_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for OPL3-SA driver.");
+module_param_hw_array(midi_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(midi_port, "MIDI port # for OPL3-SA driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for OPL3-SA driver.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for OPL3-SA driver.");
+module_param_hw_array(dma2, int, dma, 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.");
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+static int pnpc_registered;
+#endif
+
+/* 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)
+
+struct snd_opl3sa2 {
+	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;
+	struct snd_hwdep *synth;
+	struct snd_rawmidi *rmidi;
+	struct snd_wss *wss;
+	unsigned char ctlregs[0x20];
+	int ymode;		/* SL added */
+	struct snd_kcontrol *master_switch;
+	struct snd_kcontrol *master_volume;
+};
+
+#define PFX	"opl3sa2: "
+
+#ifdef CONFIG_PNP
+
+static const struct pnp_device_id snd_opl3sa2_pnpbiosids[] = {
+	{ .id = "YMH0021" },
+	{ .id = "NMX2210" },	/* Gateway Solo 2500 */
+	{ .id = "" }		/* end */
+};
+
+MODULE_DEVICE_TABLE(pnp, snd_opl3sa2_pnpbiosids);
+
+static const 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" } } },
+	/* NeoMagic MagicWave 3D */
+	{ .id = "NMX2200", .devs = { { "NMX2210" } } },
+	/* --- */
+	{ .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(struct snd_opl3sa2 *chip, unsigned char reg)
+{
+	unsigned char result;
+#if 0
+	outb(0x1d, port);	/* password */
+	printk(KERN_DEBUG "read [0x%lx] = 0x%x\n", port, inb(port));
+#endif
+	outb(reg, chip->port);	/* register */
+	result = inb(chip->port + 1);
+#if 0
+	printk(KERN_DEBUG "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(struct snd_opl3sa2 *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(struct snd_opl3sa2 *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(struct snd_opl3sa2 *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 snd_opl3sa2_detect(struct snd_card *card)
+{
+	struct snd_opl3sa2 *chip = card->private_data;
+	unsigned long port;
+	unsigned char tmp, tmp1;
+	char str[2];
+
+	port = chip->port;
+	if ((chip->res_port = request_region(port, 2, "OPL3-SA control")) == NULL) {
+		snd_printk(KERN_ERR PFX "can't grab port 0x%lx\n", port);
+		return -EBUSY;
+	}
+	/*
+	snd_printk(KERN_DEBUG "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 */
+		/* 0x07 - unknown - Neomagic MagicWave 3D */
+		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 accessible */
+	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)
+{
+	unsigned short status;
+	struct snd_card *card = dev_id;
+	struct snd_opl3sa2 *chip;
+	int handled = 0;
+
+	if (card == NULL)
+		return IRQ_NONE;
+
+	chip = card->private_data;
+	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);
+	}
+
+	if (status & 0x07) {	/* TI,CI,PI */
+		handled = 1;
+		snd_wss_interrupt(irq, chip->wss);
+	}
+
+	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(card, SNDRV_CTL_EVENT_MASK_VALUE,
+					&chip->master_switch->id);
+			snd_ctl_notify(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_wss_info_single, \
+  .get = snd_opl3sa2_get_single, .put = snd_opl3sa2_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+#define OPL3SA2_SINGLE_TLV(xname, xindex, reg, shift, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .name = xname, .index = xindex, \
+  .info = snd_wss_info_single, \
+  .get = snd_opl3sa2_get_single, .put = snd_opl3sa2_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24), \
+  .tlv = { .p = (xtlv) } }
+
+static int snd_opl3sa2_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_opl3sa2 *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_opl3sa2 *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_wss_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) }
+#define OPL3SA2_DOUBLE_TLV(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .name = xname, .index = xindex, \
+  .info = snd_wss_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), \
+  .tlv = { .p = (xtlv) } }
+
+static int snd_opl3sa2_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_opl3sa2 *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_opl3sa2 *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 const DECLARE_TLV_DB_SCALE(db_scale_master, -3000, 200, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0);
+
+static struct snd_kcontrol_new snd_opl3sa2_controls[] = {
+OPL3SA2_DOUBLE("Master Playback Switch", 0, 0x07, 0x08, 7, 7, 1, 1),
+OPL3SA2_DOUBLE_TLV("Master Playback Volume", 0, 0x07, 0x08, 0, 0, 15, 1,
+		   db_scale_master),
+OPL3SA2_SINGLE("Mic Playback Switch", 0, 0x09, 7, 1, 1),
+OPL3SA2_SINGLE_TLV("Mic Playback Volume", 0, 0x09, 0, 31, 1,
+		   db_scale_5bit_12db_max),
+OPL3SA2_SINGLE("ZV Port Switch", 0, 0x02, 0, 1, 0),
+};
+
+static struct snd_kcontrol_new 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(struct snd_kcontrol *kcontrol)
+{
+	struct snd_opl3sa2 *chip = snd_kcontrol_chip(kcontrol);
+	chip->master_switch = NULL;
+	chip->master_volume = NULL;
+}
+
+static int snd_opl3sa2_mixer(struct snd_card *card)
+{
+	struct snd_opl3sa2 *chip = card->private_data;
+	struct snd_ctl_elem_id id1, id2;
+	struct snd_kcontrol *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) {
+		snd_printk(KERN_ERR "Cannot rename opl3sa2 control\n");
+                return err;
+	}
+        strcpy(id1.name, "Aux Playback Volume");
+        strcpy(id2.name, "CD Playback Volume");
+        if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) {
+		snd_printk(KERN_ERR "Cannot rename opl3sa2 control\n");
+                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) {
+		snd_printk(KERN_ERR "Cannot rename opl3sa2 control\n");
+                return err;
+	}
+        strcpy(id1.name, "Aux Playback Volume");
+        strcpy(id2.name, "FM Playback Volume");
+        if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) {
+		snd_printk(KERN_ERR "Cannot rename opl3sa2 control\n");
+                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(struct snd_card *card, pm_message_t state)
+{
+	if (card) {
+		struct snd_opl3sa2 *chip = card->private_data;
+
+		snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+		chip->wss->suspend(chip->wss);
+		/* power down */
+		snd_opl3sa2_write(chip, OPL3SA2_PM_CTRL, OPL3SA2_PM_D3);
+	}
+
+	return 0;
+}
+
+static int snd_opl3sa2_resume(struct snd_card *card)
+{
+	struct snd_opl3sa2 *chip;
+	int i;
+
+	if (!card)
+		return 0;
+
+	chip = card->private_data;
+	/* 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 wss */
+	chip->wss->resume(chip->wss);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+#ifdef CONFIG_PNP
+static int snd_opl3sa2_pnp(int dev, struct snd_opl3sa2 *chip,
+			   struct pnp_dev *pdev)
+{
+	if (pnp_activate_dev(pdev) < 0) {
+		snd_printk(KERN_ERR "PnP configure failure (out of resources?)\n");
+		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("%sPnP OPL3-SA: sb port=0x%lx, wss port=0x%lx, fm port=0x%lx, midi port=0x%lx\n",
+		pnp_device_is_pnpbios(pdev) ? "BIOS" : "ISA", sb_port[dev], wss_port[dev], fm_port[dev], midi_port[dev]);
+	snd_printdd("%sPnP OPL3-SA: control port=0x%lx, dma1=%i, dma2=%i, irq=%i\n",
+		pnp_device_is_pnpbios(pdev) ? "BIOS" : "ISA", port[dev], dma1[dev], dma2[dev], irq[dev]);
+	return 0;
+}
+#endif /* CONFIG_PNP */
+
+static void snd_opl3sa2_free(struct snd_card *card)
+{
+	struct snd_opl3sa2 *chip = card->private_data;
+	if (chip->irq >= 0)
+		free_irq(chip->irq, card);
+	release_and_free_resource(chip->res_port);
+}
+
+static int snd_opl3sa2_card_new(struct device *pdev, int dev,
+				struct snd_card **cardp)
+{
+	struct snd_card *card;
+	struct snd_opl3sa2 *chip;
+	int err;
+
+	err = snd_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+			   sizeof(struct snd_opl3sa2), &card);
+	if (err < 0)
+		return err;
+	strcpy(card->driver, "OPL3SA2");
+	strcpy(card->shortname, "Yamaha OPL3-SA");
+	chip = card->private_data;
+	spin_lock_init(&chip->reg_lock);
+	chip->irq = -1;
+	card->private_free = snd_opl3sa2_free;
+	*cardp = card;
+	return 0;
+}
+
+static int snd_opl3sa2_probe(struct snd_card *card, int dev)
+{
+	int xirq, xdma1, xdma2;
+	struct snd_opl3sa2 *chip;
+	struct snd_wss *wss;
+	struct snd_opl3 *opl3;
+	int err;
+
+	/* initialise this card from supplied (or default) parameter*/ 
+	chip = card->private_data;
+	chip->ymode = opl3sa3_ymode[dev] & 0x03 ;
+	chip->port = port[dev];
+	xirq = irq[dev];
+	xdma1 = dma1[dev];
+	xdma2 = dma2[dev];
+	if (xdma2 < 0)
+		chip->single_dma = 1;
+	err = snd_opl3sa2_detect(card);
+	if (err < 0)
+		return err;
+	err = request_irq(xirq, snd_opl3sa2_interrupt, 0,
+			  "OPL3-SA2", card);
+	if (err) {
+		snd_printk(KERN_ERR PFX "can't grab IRQ %d\n", xirq);
+		return -ENODEV;
+	}
+	chip->irq = xirq;
+	err = snd_wss_create(card,
+			     wss_port[dev] + 4, -1,
+			     xirq, xdma1, xdma2,
+			     WSS_HW_OPL3SA2, WSS_HWSHARE_IRQ, &wss);
+	if (err < 0) {
+		snd_printd("Oops, WSS not detected at 0x%lx\n", wss_port[dev] + 4);
+		return err;
+	}
+	chip->wss = wss;
+	err = snd_wss_pcm(wss, 0);
+	if (err < 0)
+		return err;
+	err = snd_wss_mixer(wss);
+	if (err < 0)
+		return err;
+	err = snd_opl3sa2_mixer(card);
+	if (err < 0)
+		return err;
+	err = snd_wss_timer(wss, 0);
+	if (err < 0)
+		return err;
+	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)
+			return err;
+		if ((err = snd_opl3_timer_new(opl3, 1, 2)) < 0)
+			return err;
+		if ((err = snd_opl3_hwdep_new(opl3, 0, 1, &chip->synth)) < 0)
+			return err;
+	}
+	if (midi_port[dev] >= 0x300 && midi_port[dev] < 0x340) {
+		if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_OPL3SA2,
+					       midi_port[dev],
+					       MPU401_INFO_IRQ_HOOK, -1,
+					       &chip->rmidi)) < 0)
+			return err;
+	}
+	sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+		card->shortname, chip->port, xirq, xdma1);
+	if (xdma2 >= 0)
+		sprintf(card->longname + strlen(card->longname), "&%d", xdma2);
+
+	return snd_card_register(card);
+}
+
+#ifdef CONFIG_PNP
+static int snd_opl3sa2_pnp_detect(struct pnp_dev *pdev,
+				  const struct pnp_device_id *id)
+{
+	static int dev;
+	int err;
+	struct snd_card *card;
+
+	if (pnp_device_is_isapnp(pdev))
+		return -ENOENT;	/* we have another procedure - card */
+	for (; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+
+	err = snd_opl3sa2_card_new(&pdev->dev, dev, &card);
+	if (err < 0)
+		return err;
+	if ((err = snd_opl3sa2_pnp(dev, card->private_data, pdev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_opl3sa2_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pnp_set_drvdata(pdev, card);
+	dev++;
+	return 0;
+}
+
+static void snd_opl3sa2_pnp_remove(struct pnp_dev *pdev)
+{
+	snd_card_free(pnp_get_drvdata(pdev));
+}
+
+#ifdef CONFIG_PM
+static int snd_opl3sa2_pnp_suspend(struct pnp_dev *pdev, pm_message_t state)
+{
+	return snd_opl3sa2_suspend(pnp_get_drvdata(pdev), state);
+}
+static int snd_opl3sa2_pnp_resume(struct pnp_dev *pdev)
+{
+	return snd_opl3sa2_resume(pnp_get_drvdata(pdev));
+}
+#endif
+
+static struct pnp_driver opl3sa2_pnp_driver = {
+	.name = "snd-opl3sa2-pnpbios",
+	.id_table = snd_opl3sa2_pnpbiosids,
+	.probe = snd_opl3sa2_pnp_detect,
+	.remove = snd_opl3sa2_pnp_remove,
+#ifdef CONFIG_PM
+	.suspend = snd_opl3sa2_pnp_suspend,
+	.resume = snd_opl3sa2_pnp_resume,
+#endif
+};
+
+static int snd_opl3sa2_pnp_cdetect(struct pnp_card_link *pcard,
+				   const struct pnp_card_device_id *id)
+{
+	static int dev;
+	struct pnp_dev *pdev;
+	int err;
+	struct snd_card *card;
+
+	pdev = pnp_request_card_device(pcard, id->devs[0].id, NULL);
+	if (pdev == NULL) {
+		snd_printk(KERN_ERR PFX "can't get pnp device from id '%s'\n",
+			   id->devs[0].id);
+		return -EBUSY;
+	}
+	for (; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+
+	err = snd_opl3sa2_card_new(&pdev->dev, dev, &card);
+	if (err < 0)
+		return err;
+	if ((err = snd_opl3sa2_pnp(dev, card->private_data, pdev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_opl3sa2_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	dev++;
+	return 0;
+}
+
+static void snd_opl3sa2_pnp_cremove(struct pnp_card_link *pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_opl3sa2_pnp_csuspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+	return snd_opl3sa2_suspend(pnp_get_card_drvdata(pcard), state);
+}
+static int snd_opl3sa2_pnp_cresume(struct pnp_card_link *pcard)
+{
+	return snd_opl3sa2_resume(pnp_get_card_drvdata(pcard));
+}
+#endif
+
+static struct pnp_card_driver opl3sa2_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DISABLE,
+	.name = "snd-opl3sa2-cpnp",
+	.id_table = snd_opl3sa2_pnpids,
+	.probe = snd_opl3sa2_pnp_cdetect,
+	.remove = snd_opl3sa2_pnp_cremove,
+#ifdef CONFIG_PM
+	.suspend = snd_opl3sa2_pnp_csuspend,
+	.resume = snd_opl3sa2_pnp_cresume,
+#endif
+};
+#endif /* CONFIG_PNP */
+
+static int snd_opl3sa2_isa_match(struct device *pdev,
+				 unsigned int dev)
+{
+	if (!enable[dev])
+		return 0;
+#ifdef CONFIG_PNP
+	if (isapnp[dev])
+		return 0;
+#endif
+	if (port[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR PFX "specify port\n");
+		return 0;
+	}
+	if (wss_port[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR PFX "specify wss_port\n");
+		return 0;
+	}
+	if (fm_port[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR PFX "specify fm_port\n");
+		return 0;
+	}
+	if (midi_port[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR PFX "specify midi_port\n");
+		return 0;
+	}
+	return 1;
+}
+
+static int snd_opl3sa2_isa_probe(struct device *pdev,
+				 unsigned int dev)
+{
+	struct snd_card *card;
+	int err;
+
+	err = snd_opl3sa2_card_new(pdev, dev, &card);
+	if (err < 0)
+		return err;
+	if ((err = snd_opl3sa2_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	dev_set_drvdata(pdev, card);
+	return 0;
+}
+
+static int snd_opl3sa2_isa_remove(struct device *devptr,
+				  unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(devptr));
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_opl3sa2_isa_suspend(struct device *dev, unsigned int n,
+				   pm_message_t state)
+{
+	return snd_opl3sa2_suspend(dev_get_drvdata(dev), state);
+}
+
+static int snd_opl3sa2_isa_resume(struct device *dev, unsigned int n)
+{
+	return snd_opl3sa2_resume(dev_get_drvdata(dev));
+}
+#endif
+
+#define DEV_NAME "opl3sa2"
+
+static struct isa_driver snd_opl3sa2_isa_driver = {
+	.match		= snd_opl3sa2_isa_match,
+	.probe		= snd_opl3sa2_isa_probe,
+	.remove		= snd_opl3sa2_isa_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_opl3sa2_isa_suspend,
+	.resume		= snd_opl3sa2_isa_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+static int __init alsa_card_opl3sa2_init(void)
+{
+	int err;
+
+	err = isa_register_driver(&snd_opl3sa2_isa_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+	if (!err)
+		isa_registered = 1;
+
+	err = pnp_register_driver(&opl3sa2_pnp_driver);
+	if (!err)
+		pnp_registered = 1;
+
+	err = pnp_register_card_driver(&opl3sa2_pnpc_driver);
+	if (!err)
+		pnpc_registered = 1;
+
+	if (isa_registered || pnp_registered)
+		err = 0;
+#endif
+	return err;
+}
+
+static void __exit alsa_card_opl3sa2_exit(void)
+{
+#ifdef CONFIG_PNP
+	if (pnpc_registered)
+		pnp_unregister_card_driver(&opl3sa2_pnpc_driver);
+	if (pnp_registered)
+		pnp_unregister_driver(&opl3sa2_pnp_driver);
+	if (isa_registered)
+#endif
+		isa_unregister_driver(&snd_opl3sa2_isa_driver);
+}
+
+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..a9dcdeb
--- /dev/null
+++ b/sound/isa/opti9xx/Makefile
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-opti92x-ad1848-objs := opti92x-ad1848.o
+snd-opti92x-cs4231-objs := opti92x-cs4231.o
+snd-opti93x-objs := opti93x.o
+snd-miro-objs := miro.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
+obj-$(CONFIG_SND_MIRO) += snd-miro.o
diff --git a/sound/isa/opti9xx/miro.c b/sound/isa/opti9xx/miro.c
new file mode 100644
index 0000000..c6136c6
--- /dev/null
+++ b/sound/isa/opti9xx/miro.c
@@ -0,0 +1,1666 @@
+/*
+ *   ALSA soundcard driver for Miro miroSOUND PCM1 pro
+ *                                  miroSOUND PCM12
+ *                                  miroSOUND PCM20 Radio
+ *
+ *   Copyright (C) 2004-2005 Martin Langer <martin-langer@gmx.de>
+ *
+ *   Based on OSS ACI and ALSA OPTi9xx drivers
+ *
+ *   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 <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/pnp.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/opl4.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#define SNDRV_LEGACY_FIND_FREE_IOPORT
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+#include <sound/aci.h>
+
+MODULE_AUTHOR("Martin Langer <martin-langer@gmx.de>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Miro miroSOUND PCM1 pro, PCM12, PCM20 Radio");
+MODULE_SUPPORTED_DEVICE("{{Miro,miroSOUND PCM1 pro}, "
+			"{Miro,miroSOUND PCM12}, "
+			"{Miro,miroSOUND PCM20 Radio}}");
+
+static int index = SNDRV_DEFAULT_IDX1;		/* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1;		/* ID for this card */
+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 */
+static int dma2 = SNDRV_DEFAULT_DMA1;		/* 0,1,3 */
+static int wss;
+static int ide;
+#ifdef CONFIG_PNP
+static bool isapnp = 1;				/* Enable ISA PnP detection */
+#endif
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for miro soundcard.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for miro soundcard.");
+module_param_hw(port, long, ioport, 0444);
+MODULE_PARM_DESC(port, "WSS port # for miro driver.");
+module_param_hw(mpu_port, long, ioport, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for miro driver.");
+module_param_hw(fm_port, long, ioport, 0444);
+MODULE_PARM_DESC(fm_port, "FM Port # for miro driver.");
+module_param_hw(irq, int, irq, 0444);
+MODULE_PARM_DESC(irq, "WSS irq # for miro driver.");
+module_param_hw(mpu_irq, int, irq, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 irq # for miro driver.");
+module_param_hw(dma1, int, dma, 0444);
+MODULE_PARM_DESC(dma1, "1st dma # for miro driver.");
+module_param_hw(dma2, int, dma, 0444);
+MODULE_PARM_DESC(dma2, "2nd dma # for miro driver.");
+module_param(wss, int, 0444);
+MODULE_PARM_DESC(wss, "wss mode");
+module_param(ide, int, 0444);
+MODULE_PARM_DESC(ide, "enable ide port");
+#ifdef CONFIG_PNP
+module_param(isapnp, bool, 0444);
+MODULE_PARM_DESC(isapnp, "Enable ISA PnP detection for specified soundcard.");
+#endif
+
+#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
+
+struct snd_miro {
+	unsigned short hardware;
+	unsigned char password;
+	char name[7];
+
+	struct resource *res_mc_base;
+	struct resource *res_aci_port;
+
+	unsigned long mc_base;
+	unsigned long mc_base_size;
+	unsigned long pwd_reg;
+
+	spinlock_t lock;
+	struct snd_pcm *pcm;
+
+	long wss_base;
+	int irq;
+	int dma1;
+	int dma2;
+
+	long mpu_port;
+	int mpu_irq;
+
+	struct snd_miro_aci *aci;
+};
+
+static struct snd_miro_aci aci_device;
+
+static char * snd_opti9xx_names[] = {
+	"unknown",
+	"82C928", "82C929",
+	"82C924", "82C925",
+	"82C930", "82C931", "82C933"
+};
+
+static int snd_miro_pnp_is_probed;
+
+#ifdef CONFIG_PNP
+
+static const struct pnp_card_device_id snd_miro_pnpids[] = {
+	/* PCM20 and PCM12 in PnP mode */
+	{ .id = "MIR0924",
+	  .devs = { { "MIR0000" }, { "MIR0002" }, { "MIR0005" } }, },
+	{ .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_miro_pnpids);
+
+#endif	/* CONFIG_PNP */
+
+/* 
+ *  ACI control
+ */
+
+static int aci_busy_wait(struct snd_miro_aci *aci)
+{
+	long timeout;
+	unsigned char byte;
+
+	for (timeout = 1; timeout <= ACI_MINTIME + 30; timeout++) {
+		byte = inb(aci->aci_port + ACI_REG_BUSY);
+		if ((byte & 1) == 0) {
+			if (timeout >= ACI_MINTIME)
+				snd_printd("aci ready in round %ld.\n",
+					   timeout-ACI_MINTIME);
+			return byte;
+		}
+		if (timeout >= ACI_MINTIME) {
+			long out=10*HZ;
+			switch (timeout-ACI_MINTIME) {
+			case 0 ... 9:
+				out /= 10;
+				/* fall through */
+			case 10 ... 19:
+				out /= 10;
+				/* fall through */
+			case 20 ... 30:
+				out /= 10;
+				/* fall through */
+			default:
+				set_current_state(TASK_UNINTERRUPTIBLE);
+				schedule_timeout(out);
+				break;
+			}
+		}
+	}
+	snd_printk(KERN_ERR "aci_busy_wait() time out\n");
+	return -EBUSY;
+}
+
+static inline int aci_write(struct snd_miro_aci *aci, unsigned char byte)
+{
+	if (aci_busy_wait(aci) >= 0) {
+		outb(byte, aci->aci_port + ACI_REG_COMMAND);
+		return 0;
+	} else {
+		snd_printk(KERN_ERR "aci busy, aci_write(0x%x) stopped.\n", byte);
+		return -EBUSY;
+	}
+}
+
+static inline int aci_read(struct snd_miro_aci *aci)
+{
+	unsigned char byte;
+
+	if (aci_busy_wait(aci) >= 0) {
+		byte = inb(aci->aci_port + ACI_REG_STATUS);
+		return byte;
+	} else {
+		snd_printk(KERN_ERR "aci busy, aci_read() stopped.\n");
+		return -EBUSY;
+	}
+}
+
+int snd_aci_cmd(struct snd_miro_aci *aci, int write1, int write2, int write3)
+{
+	int write[] = {write1, write2, write3};
+	int value, i;
+
+	if (mutex_lock_interruptible(&aci->aci_mutex))
+		return -EINTR;
+
+	for (i=0; i<3; i++) {
+		if (write[i]< 0 || write[i] > 255)
+			break;
+		else {
+			value = aci_write(aci, write[i]);
+			if (value < 0)
+				goto out;
+		}
+	}
+
+	value = aci_read(aci);
+
+out:	mutex_unlock(&aci->aci_mutex);
+	return value;
+}
+EXPORT_SYMBOL(snd_aci_cmd);
+
+static int aci_getvalue(struct snd_miro_aci *aci, unsigned char index)
+{
+	return snd_aci_cmd(aci, ACI_STATUS, index, -1);
+}
+
+static int aci_setvalue(struct snd_miro_aci *aci, unsigned char index,
+			int value)
+{
+	return snd_aci_cmd(aci, index, value, -1);
+}
+
+struct snd_miro_aci *snd_aci_get_aci(void)
+{
+	if (aci_device.aci_port == 0)
+		return NULL;
+	return &aci_device;
+}
+EXPORT_SYMBOL(snd_aci_get_aci);
+
+/*
+ *  MIXER part
+ */
+
+#define snd_miro_info_capture	snd_ctl_boolean_mono_info
+
+static int snd_miro_get_capture(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+	int value;
+
+	value = aci_getvalue(miro->aci, ACI_S_GENERAL);
+	if (value < 0) {
+		snd_printk(KERN_ERR "snd_miro_get_capture() failed: %d\n",
+			   value);
+		return value;
+	}
+
+	ucontrol->value.integer.value[0] = value & 0x20;
+
+	return 0;
+}
+
+static int snd_miro_put_capture(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+	int change, value, error;
+
+	value = !(ucontrol->value.integer.value[0]);
+
+	error = aci_setvalue(miro->aci, ACI_SET_SOLOMODE, value);
+	if (error < 0) {
+		snd_printk(KERN_ERR "snd_miro_put_capture() failed: %d\n",
+			   error);
+		return error;
+	}
+
+	change = (value != miro->aci->aci_solomode);
+	miro->aci->aci_solomode = value;
+	
+	return change;
+}
+
+static int snd_miro_info_preamp(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 3;
+
+	return 0;
+}
+
+static int snd_miro_get_preamp(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+	int value;
+
+	if (miro->aci->aci_version <= 176) {
+
+		/* 
+		   OSS says it's not readable with versions < 176.
+		   But it doesn't work on my card,
+		   which is a PCM12 with aci_version = 176.
+		*/
+
+		ucontrol->value.integer.value[0] = miro->aci->aci_preamp;
+		return 0;
+	}
+
+	value = aci_getvalue(miro->aci, ACI_GET_PREAMP);
+	if (value < 0) {
+		snd_printk(KERN_ERR "snd_miro_get_preamp() failed: %d\n",
+			   value);
+		return value;
+	}
+	
+	ucontrol->value.integer.value[0] = value;
+
+	return 0;
+}
+
+static int snd_miro_put_preamp(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+	int error, value, change;
+
+	value = ucontrol->value.integer.value[0];
+
+	error = aci_setvalue(miro->aci, ACI_SET_PREAMP, value);
+	if (error < 0) {
+		snd_printk(KERN_ERR "snd_miro_put_preamp() failed: %d\n",
+			   error);
+		return error;
+	}
+
+	change = (value != miro->aci->aci_preamp);
+	miro->aci->aci_preamp = value;
+
+	return change;
+}
+
+#define snd_miro_info_amp	snd_ctl_boolean_mono_info
+
+static int snd_miro_get_amp(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = miro->aci->aci_amp;
+
+	return 0;
+}
+
+static int snd_miro_put_amp(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+	int error, value, change;
+
+	value = ucontrol->value.integer.value[0];
+
+	error = aci_setvalue(miro->aci, ACI_SET_POWERAMP, value);
+	if (error < 0) {
+		snd_printk(KERN_ERR "snd_miro_put_amp() to %d failed: %d\n", value, error);
+		return error;
+	}
+
+	change = (value != miro->aci->aci_amp);
+	miro->aci->aci_amp = value;
+
+	return change;
+}
+
+#define MIRO_DOUBLE(ctl_name, ctl_index, get_right_reg, set_right_reg) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = ctl_name, \
+  .index = ctl_index, \
+  .info = snd_miro_info_double, \
+  .get = snd_miro_get_double, \
+  .put = snd_miro_put_double, \
+  .private_value = get_right_reg | (set_right_reg << 8) \
+}
+
+static int snd_miro_info_double(struct snd_kcontrol *kcontrol, 
+				struct snd_ctl_elem_info *uinfo)
+{
+	int reg = kcontrol->private_value & 0xff;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+
+	if ((reg >= ACI_GET_EQ1) && (reg <= ACI_GET_EQ7)) {
+
+		/* equalizer elements */
+
+		uinfo->value.integer.min = - 0x7f;
+		uinfo->value.integer.max = 0x7f;
+	} else {
+
+		/* non-equalizer elements */
+
+		uinfo->value.integer.min = 0;
+		uinfo->value.integer.max = 0x20;
+	}
+
+	return 0;
+}
+
+static int snd_miro_get_double(struct snd_kcontrol *kcontrol, 
+			       struct snd_ctl_elem_value *uinfo)
+{
+	struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+	int left_val, right_val;
+
+	int right_reg = kcontrol->private_value & 0xff;
+	int left_reg = right_reg + 1;
+
+	right_val = aci_getvalue(miro->aci, right_reg);
+	if (right_val < 0) {
+		snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", right_reg, right_val);
+		return right_val;
+	}
+
+	left_val = aci_getvalue(miro->aci, left_reg);
+	if (left_val < 0) {
+		snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", left_reg, left_val);
+		return left_val;
+	}
+
+	if ((right_reg >= ACI_GET_EQ1) && (right_reg <= ACI_GET_EQ7)) {
+
+		/* equalizer elements */
+
+		if (left_val < 0x80) {
+			uinfo->value.integer.value[0] = left_val;
+		} else {
+			uinfo->value.integer.value[0] = 0x80 - left_val;
+		}
+
+		if (right_val < 0x80) {
+			uinfo->value.integer.value[1] = right_val;
+		} else {
+			uinfo->value.integer.value[1] = 0x80 - right_val;
+		}
+
+	} else {
+
+		/* non-equalizer elements */
+
+		uinfo->value.integer.value[0] = 0x20 - left_val;
+		uinfo->value.integer.value[1] = 0x20 - right_val;
+	}
+
+	return 0;
+}
+
+static int snd_miro_put_double(struct snd_kcontrol *kcontrol, 
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+	struct snd_miro_aci *aci = miro->aci;
+	int left, right, left_old, right_old;
+	int setreg_left, setreg_right, getreg_left, getreg_right;
+	int change, error;
+
+	left = ucontrol->value.integer.value[0];
+	right = ucontrol->value.integer.value[1];
+
+	setreg_right = (kcontrol->private_value >> 8) & 0xff;
+	setreg_left = setreg_right + 8;
+	if (setreg_right == ACI_SET_MASTER)
+		setreg_left -= 7;
+
+	getreg_right = kcontrol->private_value & 0xff;
+	getreg_left = getreg_right + 1;
+
+	left_old = aci_getvalue(aci, getreg_left);
+	if (left_old < 0) {
+		snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", getreg_left, left_old);
+		return left_old;
+	}
+
+	right_old = aci_getvalue(aci, getreg_right);
+	if (right_old < 0) {
+		snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", getreg_right, right_old);
+		return right_old;
+	}
+
+	if ((getreg_right >= ACI_GET_EQ1) && (getreg_right <= ACI_GET_EQ7)) {
+
+		/* equalizer elements */
+
+		if (left < -0x7f || left > 0x7f ||
+		    right < -0x7f || right > 0x7f)
+			return -EINVAL;
+
+		if (left_old > 0x80) 
+			left_old = 0x80 - left_old;
+		if (right_old > 0x80) 
+			right_old = 0x80 - right_old;
+
+		if (left >= 0) {
+			error = aci_setvalue(aci, setreg_left, left);
+			if (error < 0) {
+				snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+					   left, error);
+				return error;
+			}
+		} else {
+			error = aci_setvalue(aci, setreg_left, 0x80 - left);
+			if (error < 0) {
+				snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+					   0x80 - left, error);
+				return error;
+			}
+		}
+
+		if (right >= 0) {
+			error = aci_setvalue(aci, setreg_right, right);
+			if (error < 0) {
+				snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+					   right, error);
+				return error;
+			}
+		} else {
+			error = aci_setvalue(aci, setreg_right, 0x80 - right);
+			if (error < 0) {
+				snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+					   0x80 - right, error);
+				return error;
+			}
+		}
+
+	} else {
+
+		/* non-equalizer elements */
+
+		if (left < 0 || left > 0x20 ||
+		    right < 0 || right > 0x20)
+			return -EINVAL;
+
+		left_old = 0x20 - left_old;
+		right_old = 0x20 - right_old;
+
+		error = aci_setvalue(aci, setreg_left, 0x20 - left);
+		if (error < 0) {
+			snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+				   0x20 - left, error);
+			return error;
+		}
+		error = aci_setvalue(aci, setreg_right, 0x20 - right);
+		if (error < 0) {
+			snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+				   0x20 - right, error);
+			return error;
+		}
+	}
+
+	change = (left != left_old) || (right != right_old);
+
+	return change;
+}
+
+static struct snd_kcontrol_new snd_miro_controls[] = {
+MIRO_DOUBLE("Master Playback Volume", 0, ACI_GET_MASTER, ACI_SET_MASTER),
+MIRO_DOUBLE("Mic Playback Volume", 1, ACI_GET_MIC, ACI_SET_MIC),
+MIRO_DOUBLE("Line Playback Volume", 1, ACI_GET_LINE, ACI_SET_LINE),
+MIRO_DOUBLE("CD Playback Volume", 0, ACI_GET_CD, ACI_SET_CD),
+MIRO_DOUBLE("Synth Playback Volume", 0, ACI_GET_SYNTH, ACI_SET_SYNTH),
+MIRO_DOUBLE("PCM Playback Volume", 1, ACI_GET_PCM, ACI_SET_PCM),
+MIRO_DOUBLE("Aux Playback Volume", 2, ACI_GET_LINE2, ACI_SET_LINE2),
+};
+
+/* Equalizer with seven bands (only PCM20) 
+   from -12dB up to +12dB on each band */
+static struct snd_kcontrol_new snd_miro_eq_controls[] = {
+MIRO_DOUBLE("Tone Control - 28 Hz", 0, ACI_GET_EQ1, ACI_SET_EQ1),
+MIRO_DOUBLE("Tone Control - 160 Hz", 0, ACI_GET_EQ2, ACI_SET_EQ2),
+MIRO_DOUBLE("Tone Control - 400 Hz", 0, ACI_GET_EQ3, ACI_SET_EQ3),
+MIRO_DOUBLE("Tone Control - 1 kHz", 0, ACI_GET_EQ4, ACI_SET_EQ4),
+MIRO_DOUBLE("Tone Control - 2.5 kHz", 0, ACI_GET_EQ5, ACI_SET_EQ5),
+MIRO_DOUBLE("Tone Control - 6.3 kHz", 0, ACI_GET_EQ6, ACI_SET_EQ6),
+MIRO_DOUBLE("Tone Control - 16 kHz", 0, ACI_GET_EQ7, ACI_SET_EQ7),
+};
+
+static struct snd_kcontrol_new snd_miro_radio_control[] = {
+MIRO_DOUBLE("Radio Playback Volume", 0, ACI_GET_LINE1, ACI_SET_LINE1),
+};
+
+static struct snd_kcontrol_new snd_miro_line_control[] = {
+MIRO_DOUBLE("Line Playback Volume", 2, ACI_GET_LINE1, ACI_SET_LINE1),
+};
+
+static struct snd_kcontrol_new snd_miro_preamp_control[] = {
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Mic Boost",
+	.index = 1,
+	.info = snd_miro_info_preamp,
+	.get = snd_miro_get_preamp,
+	.put = snd_miro_put_preamp,
+}};
+
+static struct snd_kcontrol_new snd_miro_amp_control[] = {
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Line Boost",
+	.index = 0,
+	.info = snd_miro_info_amp,
+	.get = snd_miro_get_amp,
+	.put = snd_miro_put_amp,
+}};
+
+static struct snd_kcontrol_new snd_miro_capture_control[] = {
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "PCM Capture Switch",
+	.index = 0,
+	.info = snd_miro_info_capture,
+	.get = snd_miro_get_capture,
+	.put = snd_miro_put_capture,
+}};
+
+static unsigned char aci_init_values[][2] = {
+	{ ACI_SET_MUTE, 0x00 },
+	{ ACI_SET_POWERAMP, 0x00 },
+	{ ACI_SET_PREAMP, 0x00 },
+	{ ACI_SET_SOLOMODE, 0x00 },
+	{ ACI_SET_MIC + 0, 0x20 },
+	{ ACI_SET_MIC + 8, 0x20 },
+	{ ACI_SET_LINE + 0, 0x20 },
+	{ ACI_SET_LINE + 8, 0x20 },
+	{ ACI_SET_CD + 0, 0x20 },
+	{ ACI_SET_CD + 8, 0x20 },
+	{ ACI_SET_PCM + 0, 0x20 },
+	{ ACI_SET_PCM + 8, 0x20 },
+	{ ACI_SET_LINE1 + 0, 0x20 },
+	{ ACI_SET_LINE1 + 8, 0x20 },
+	{ ACI_SET_LINE2 + 0, 0x20 },
+	{ ACI_SET_LINE2 + 8, 0x20 },
+	{ ACI_SET_SYNTH + 0, 0x20 },
+	{ ACI_SET_SYNTH + 8, 0x20 },
+	{ ACI_SET_MASTER + 0, 0x20 },
+	{ ACI_SET_MASTER + 1, 0x20 },
+};
+
+static int snd_set_aci_init_values(struct snd_miro *miro)
+{
+	int idx, error;
+	struct snd_miro_aci *aci = miro->aci;
+
+	/* enable WSS on PCM1 */
+
+	if ((aci->aci_product == 'A') && wss) {
+		error = aci_setvalue(aci, ACI_SET_WSS, wss);
+		if (error < 0) {
+			snd_printk(KERN_ERR "enabling WSS mode failed\n");
+			return error;
+		}
+	}
+
+	/* enable IDE port */
+
+	if (ide) {
+		error = aci_setvalue(aci, ACI_SET_IDE, ide);
+		if (error < 0) {
+			snd_printk(KERN_ERR "enabling IDE port failed\n");
+			return error;
+		}
+	}
+
+	/* set common aci values */
+
+	for (idx = 0; idx < ARRAY_SIZE(aci_init_values); idx++) {
+		error = aci_setvalue(aci, aci_init_values[idx][0],
+				     aci_init_values[idx][1]);
+		if (error < 0) {
+			snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", 
+				   aci_init_values[idx][0], error);
+                        return error;
+                }
+	}
+	aci->aci_amp = 0;
+	aci->aci_preamp = 0;
+	aci->aci_solomode = 1;
+
+	return 0;
+}
+
+static int snd_miro_mixer(struct snd_card *card,
+			  struct snd_miro *miro)
+{
+	unsigned int idx;
+	int err;
+
+	if (snd_BUG_ON(!miro || !card))
+		return -EINVAL;
+
+	switch (miro->hardware) {
+	case OPTi9XX_HW_82C924:
+		strcpy(card->mixername, "ACI & OPTi924");
+		break;
+	case OPTi9XX_HW_82C929:
+		strcpy(card->mixername, "ACI & OPTi929");
+		break;
+	default:
+		snd_BUG();
+		break;
+	}
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_miro_controls); idx++) {
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_controls[idx], miro))) < 0)
+			return err;
+	}
+
+	if ((miro->aci->aci_product == 'A') ||
+	    (miro->aci->aci_product == 'B')) {
+		/* PCM1/PCM12 with power-amp and Line 2 */
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_line_control[0], miro))) < 0)
+			return err;
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_amp_control[0], miro))) < 0)
+			return err;
+	}
+
+	if ((miro->aci->aci_product == 'B') ||
+	    (miro->aci->aci_product == 'C')) {
+		/* PCM12/PCM20 with mic-preamp */
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_preamp_control[0], miro))) < 0)
+			return err;
+		if (miro->aci->aci_version >= 176)
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_capture_control[0], miro))) < 0)
+				return err;
+	}
+
+	if (miro->aci->aci_product == 'C') {
+		/* PCM20 with radio and 7 band equalizer */
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_radio_control[0], miro))) < 0)
+			return err;
+		for (idx = 0; idx < ARRAY_SIZE(snd_miro_eq_controls); idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_eq_controls[idx], miro))) < 0)
+				return err;
+		}
+	}
+
+	return 0;
+}
+
+static int snd_miro_init(struct snd_miro *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;
+	chip->dma2 = -1;
+	chip->mpu_port = -1;
+	chip->mpu_irq = -1;
+
+	chip->pwd_reg = 3;
+
+#ifdef CONFIG_PNP
+	if (isapnp && chip->mc_base)
+		/* PnP resource gives the least 10 bits */
+		chip->mc_base |= 0xc00;
+	else
+#endif
+		chip->mc_base = 0xf8c;
+
+	switch (hardware) {
+	case OPTi9XX_HW_82C929:
+		chip->password = 0xe3;
+		break;
+
+	case OPTi9XX_HW_82C924:
+		chip->password = 0xe5;
+		break;
+
+	default:
+		snd_printk(KERN_ERR "sorry, no support for %d\n", hardware);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static unsigned char snd_miro_read(struct snd_miro *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) {
+	case OPTi9XX_HW_82C924:
+		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;
+		}
+		/* fall through */
+
+	case OPTi9XX_HW_82C929:
+		retval = inb(chip->mc_base + reg);
+		break;
+
+	default:
+		snd_printk(KERN_ERR "sorry, no support for %d\n", chip->hardware);
+	}
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return retval;
+}
+
+static void snd_miro_write(struct snd_miro *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) {
+	case OPTi9XX_HW_82C924:
+		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;
+		}
+		/* fall through */
+
+	case OPTi9XX_HW_82C929:
+		outb(value, chip->mc_base + reg);
+		break;
+
+	default:
+		snd_printk(KERN_ERR "sorry, no support for %d\n", chip->hardware);
+	}
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+
+#define snd_miro_write_mask(chip, reg, value, mask)	\
+	snd_miro_write(chip, reg,			\
+		(snd_miro_read(chip, reg) & ~(mask)) | ((value) & (mask)))
+
+/*
+ *  Proc Interface
+ */
+
+static void snd_miro_proc_read(struct snd_info_entry * entry, 
+			       struct snd_info_buffer *buffer)
+{
+	struct snd_miro *miro = (struct snd_miro *) entry->private_data;
+	struct snd_miro_aci *aci = miro->aci;
+	char* model = "unknown";
+
+	/* miroSOUND PCM1 pro, early PCM12 */
+
+	if ((miro->hardware == OPTi9XX_HW_82C929) &&
+	    (aci->aci_vendor == 'm') &&
+	    (aci->aci_product == 'A')) {
+		switch (aci->aci_version) {
+		case 3:
+			model = "miroSOUND PCM1 pro";
+			break;
+		default:
+			model = "miroSOUND PCM1 pro / (early) PCM12";
+			break;
+		}
+	}
+
+	/* miroSOUND PCM12, PCM12 (Rev. E), PCM12 pnp */
+
+	if ((miro->hardware == OPTi9XX_HW_82C924) &&
+	    (aci->aci_vendor == 'm') &&
+	    (aci->aci_product == 'B')) {
+		switch (aci->aci_version) {
+		case 4:
+			model = "miroSOUND PCM12";
+			break;
+		case 176:
+			model = "miroSOUND PCM12 (Rev. E)";
+			break;
+		default:
+			model = "miroSOUND PCM12 / PCM12 pnp";
+			break;
+		}
+	}
+
+	/* miroSOUND PCM20 radio */
+
+	if ((miro->hardware == OPTi9XX_HW_82C924) &&
+	    (aci->aci_vendor == 'm') &&
+	    (aci->aci_product == 'C')) {
+		switch (aci->aci_version) {
+		case 7:
+			model = "miroSOUND PCM20 radio (Rev. E)";
+			break;
+		default:
+			model = "miroSOUND PCM20 radio";
+			break;
+		}
+	}
+
+	snd_iprintf(buffer, "\nGeneral information:\n");
+	snd_iprintf(buffer, "  model   : %s\n", model);
+	snd_iprintf(buffer, "  opti    : %s\n", miro->name);
+	snd_iprintf(buffer, "  codec   : %s\n", miro->pcm->name);
+	snd_iprintf(buffer, "  port    : 0x%lx\n", miro->wss_base);
+	snd_iprintf(buffer, "  irq     : %d\n", miro->irq);
+	snd_iprintf(buffer, "  dma     : %d,%d\n\n", miro->dma1, miro->dma2);
+
+	snd_iprintf(buffer, "MPU-401:\n");
+	snd_iprintf(buffer, "  port    : 0x%lx\n", miro->mpu_port);
+	snd_iprintf(buffer, "  irq     : %d\n\n", miro->mpu_irq);
+
+	snd_iprintf(buffer, "ACI information:\n");
+	snd_iprintf(buffer, "  vendor  : ");
+	switch (aci->aci_vendor) {
+	case 'm':
+		snd_iprintf(buffer, "Miro\n");
+		break;
+	default:
+		snd_iprintf(buffer, "unknown (0x%x)\n", aci->aci_vendor);
+		break;
+	}
+
+	snd_iprintf(buffer, "  product : ");
+	switch (aci->aci_product) {
+	case 'A':
+		snd_iprintf(buffer, "miroSOUND PCM1 pro / (early) PCM12\n");
+		break;
+	case 'B':
+		snd_iprintf(buffer, "miroSOUND PCM12\n");
+		break;
+	case 'C':
+		snd_iprintf(buffer, "miroSOUND PCM20 radio\n");
+		break;
+	default:
+		snd_iprintf(buffer, "unknown (0x%x)\n", aci->aci_product);
+		break;
+	}
+
+	snd_iprintf(buffer, "  firmware: %d (0x%x)\n",
+		    aci->aci_version, aci->aci_version);
+	snd_iprintf(buffer, "  port    : 0x%lx-0x%lx\n", 
+		    aci->aci_port, aci->aci_port+2);
+	snd_iprintf(buffer, "  wss     : 0x%x\n", wss);
+	snd_iprintf(buffer, "  ide     : 0x%x\n", ide);
+	snd_iprintf(buffer, "  solomode: 0x%x\n", aci->aci_solomode);
+	snd_iprintf(buffer, "  amp     : 0x%x\n", aci->aci_amp);
+	snd_iprintf(buffer, "  preamp  : 0x%x\n", aci->aci_preamp);
+}
+
+static void snd_miro_proc_init(struct snd_card *card,
+			       struct snd_miro *miro)
+{
+	struct snd_info_entry *entry;
+
+	if (!snd_card_proc_new(card, "miro", &entry))
+		snd_info_set_text_ops(entry, miro, snd_miro_proc_read);
+}
+
+/*
+ *  Init
+ */
+
+static int snd_miro_configure(struct snd_miro *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;
+	unsigned long flags;
+
+	snd_miro_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80);
+	snd_miro_write_mask(chip, OPTi9XX_MC_REG(2), 0x20, 0x20); /* OPL4 */
+	snd_miro_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02);
+
+	switch (chip->hardware) {
+	case OPTi9XX_HW_82C924:
+		snd_miro_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x02);
+		snd_miro_write_mask(chip, OPTi9XX_MC_REG(3), 0xf0, 0xff);
+		break;
+	case OPTi9XX_HW_82C929:
+		/* untested init commands for OPTi929 */
+		snd_miro_write_mask(chip, OPTi9XX_MC_REG(4), 0x00, 0x0c);
+		break;
+	default:
+		snd_printk(KERN_ERR "chip %d not supported\n", chip->hardware);
+		return -EINVAL;
+	}
+
+	/* PnP resource says it decodes only 10 bits of address */
+	switch (chip->wss_base & 0x3ff) {
+	case 0x130:
+		chip->wss_base = 0x530;
+		wss_base_bits = 0x00;
+		break;
+	case 0x204:
+		chip->wss_base = 0x604;
+		wss_base_bits = 0x03;
+		break;
+	case 0x280:
+		chip->wss_base = 0xe80;
+		wss_base_bits = 0x01;
+		break;
+	case 0x340:
+		chip->wss_base = 0xf40;
+		wss_base_bits = 0x02;
+		break;
+	default:
+		snd_printk(KERN_ERR "WSS port 0x%lx not valid\n", chip->wss_base);
+		goto __skip_base;
+	}
+	snd_miro_write_mask(chip, OPTi9XX_MC_REG(1), wss_base_bits << 4, 0x30);
+
+__skip_base:
+	switch (chip->irq) {
+	case 5:
+		irq_bits = 0x05;
+		break;
+	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(KERN_ERR "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(KERN_ERR "WSS dma1 # %d not valid\n", chip->dma1);
+		goto __skip_resources;
+	}
+
+	if (chip->dma1 == chip->dma2) {
+		snd_printk(KERN_ERR "don't want to share dmas\n");
+		return -EBUSY;
+	}
+
+	switch (chip->dma2) {
+	case 0:
+	case 1:
+		break;
+	default:
+		snd_printk(KERN_ERR "WSS dma2 # %d not valid\n", chip->dma2);
+		goto __skip_resources;
+	}
+	dma_bits |= 0x04;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	outb(irq_bits << 3 | dma_bits, chip->wss_base);
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+__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(KERN_ERR "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(KERN_ERR "MPU-401 irq # %d not valid\n",
+				   chip->mpu_irq);
+			goto __skip_mpu;
+		}
+
+		snd_miro_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;
+}
+
+static int snd_miro_opti_check(struct snd_miro *chip)
+{
+	unsigned char value;
+
+	chip->res_mc_base = request_region(chip->mc_base, chip->mc_base_size,
+					   "OPTi9xx MC");
+	if (chip->res_mc_base == NULL)
+		return -ENOMEM;
+
+	value = snd_miro_read(chip, OPTi9XX_MC_REG(1));
+	if (value != 0xff && value != inb(chip->mc_base + OPTi9XX_MC_REG(1)))
+		if (value == snd_miro_read(chip, OPTi9XX_MC_REG(1)))
+			return 0;
+
+	release_and_free_resource(chip->res_mc_base);
+	chip->res_mc_base = NULL;
+
+	return -ENODEV;
+}
+
+static int snd_card_miro_detect(struct snd_card *card,
+				struct snd_miro *chip)
+{
+	int i, err;
+
+	for (i = OPTi9XX_HW_82C929; i <= OPTi9XX_HW_82C924; i++) {
+
+		if ((err = snd_miro_init(chip, i)) < 0)
+			return err;
+
+		err = snd_miro_opti_check(chip);
+		if (err == 0)
+			return 1;
+	}
+
+	return -ENODEV;
+}
+
+static int snd_card_miro_aci_detect(struct snd_card *card,
+				    struct snd_miro *miro)
+{
+	unsigned char regval;
+	int i;
+	struct snd_miro_aci *aci = &aci_device;
+
+	miro->aci = aci;
+
+	mutex_init(&aci->aci_mutex);
+
+	/* get ACI port from OPTi9xx MC 4 */
+
+	regval=inb(miro->mc_base + 4);
+	aci->aci_port = (regval & 0x10) ? 0x344 : 0x354;
+
+	miro->res_aci_port = request_region(aci->aci_port, 3, "miro aci");
+	if (miro->res_aci_port == NULL) {
+		snd_printk(KERN_ERR "aci i/o area 0x%lx-0x%lx already used.\n", 
+			   aci->aci_port, aci->aci_port+2);
+		return -ENOMEM;
+	}
+
+        /* force ACI into a known state */
+	for (i = 0; i < 3; i++)
+		if (snd_aci_cmd(aci, ACI_ERROR_OP, -1, -1) < 0) {
+			snd_printk(KERN_ERR "can't force aci into known state.\n");
+			return -ENXIO;
+		}
+
+	aci->aci_vendor = snd_aci_cmd(aci, ACI_READ_IDCODE, -1, -1);
+	aci->aci_product = snd_aci_cmd(aci, ACI_READ_IDCODE, -1, -1);
+	if (aci->aci_vendor < 0 || aci->aci_product < 0) {
+		snd_printk(KERN_ERR "can't read aci id on 0x%lx.\n",
+			   aci->aci_port);
+		return -ENXIO;
+	}
+
+	aci->aci_version = snd_aci_cmd(aci, ACI_READ_VERSION, -1, -1);
+	if (aci->aci_version < 0) {
+		snd_printk(KERN_ERR "can't read aci version on 0x%lx.\n", 
+			   aci->aci_port);
+		return -ENXIO;
+	}
+
+	if (snd_aci_cmd(aci, ACI_INIT, -1, -1) < 0 ||
+	    snd_aci_cmd(aci, ACI_ERROR_OP, ACI_ERROR_OP, ACI_ERROR_OP) < 0 ||
+	    snd_aci_cmd(aci, ACI_ERROR_OP, ACI_ERROR_OP, ACI_ERROR_OP) < 0) {
+		snd_printk(KERN_ERR "can't initialize aci.\n"); 
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
+static void snd_card_miro_free(struct snd_card *card)
+{
+	struct snd_miro *miro = card->private_data;
+
+	release_and_free_resource(miro->res_aci_port);
+	if (miro->aci)
+		miro->aci->aci_port = 0;
+	release_and_free_resource(miro->res_mc_base);
+}
+
+static int snd_miro_probe(struct snd_card *card)
+{
+	int error;
+	struct snd_miro *miro = card->private_data;
+	struct snd_wss *codec;
+	struct snd_rawmidi *rmidi;
+
+	if (!miro->res_mc_base) {
+		miro->res_mc_base = request_region(miro->mc_base,
+						miro->mc_base_size,
+						"miro (OPTi9xx MC)");
+		if (miro->res_mc_base == NULL) {
+			snd_printk(KERN_ERR "request for OPTI9xx MC failed\n");
+			return -ENOMEM;
+		}
+	}
+
+	error = snd_card_miro_aci_detect(card, miro);
+	if (error < 0) {
+		snd_printk(KERN_ERR "unable to detect aci chip\n");
+		return -ENODEV;
+	}
+
+	miro->wss_base = port;
+	miro->mpu_port = mpu_port;
+	miro->irq = irq;
+	miro->mpu_irq = mpu_irq;
+	miro->dma1 = dma1;
+	miro->dma2 = dma2;
+
+	/* init proc interface */
+	snd_miro_proc_init(card, miro);
+
+	error = snd_miro_configure(miro);
+	if (error)
+		return error;
+
+	error = snd_wss_create(card, miro->wss_base + 4, -1,
+			       miro->irq, miro->dma1, miro->dma2,
+			       WSS_HW_DETECT, 0, &codec);
+	if (error < 0)
+		return error;
+
+	error = snd_wss_pcm(codec, 0);
+	if (error < 0)
+		return error;
+
+	error = snd_wss_mixer(codec);
+	if (error < 0)
+		return error;
+
+	error = snd_wss_timer(codec, 0);
+	if (error < 0)
+		return error;
+
+	miro->pcm = codec->pcm;
+
+	error = snd_miro_mixer(card, miro);
+	if (error < 0)
+		return error;
+
+	if (miro->aci->aci_vendor == 'm') {
+		/* It looks like a miro sound card. */
+		switch (miro->aci->aci_product) {
+		case 'A':
+			sprintf(card->shortname, 
+				"miroSOUND PCM1 pro / PCM12");
+			break;
+		case 'B':
+			sprintf(card->shortname, 
+				"miroSOUND PCM12");
+			break;
+		case 'C':
+			sprintf(card->shortname, 
+				"miroSOUND PCM20 radio");
+			break;
+		default:
+			sprintf(card->shortname, 
+				"unknown miro");
+			snd_printk(KERN_INFO "unknown miro aci id\n");
+			break;
+		}
+	} else {
+		snd_printk(KERN_INFO "found unsupported aci card\n");
+		sprintf(card->shortname, "unknown Cardinal Technologies");
+	}
+
+	strcpy(card->driver, "miro");
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s: OPTi%s, %s at 0x%lx, irq %d, dma %d&%d",
+		 card->shortname, miro->name, codec->pcm->name,
+		 miro->wss_base + 4, miro->irq, miro->dma1, miro->dma2);
+
+	if (mpu_port <= 0 || mpu_port == SNDRV_AUTO_PORT)
+		rmidi = NULL;
+	else {
+		error = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+				mpu_port, 0, miro->mpu_irq, &rmidi);
+		if (error < 0)
+			snd_printk(KERN_WARNING "no MPU-401 device at 0x%lx?\n",
+				   mpu_port);
+	}
+
+	if (fm_port > 0 && fm_port != SNDRV_AUTO_PORT) {
+		struct snd_opl3 *opl3 = NULL;
+		struct snd_opl4 *opl4;
+
+		if (snd_opl4_create(card, fm_port, fm_port - 8,
+				    2, &opl3, &opl4) < 0)
+			snd_printk(KERN_WARNING "no OPL4 device at 0x%lx\n",
+				   fm_port);
+	}
+
+	error = snd_set_aci_init_values(miro);
+	if (error < 0)
+                return error;
+
+	return snd_card_register(card);
+}
+
+static int snd_miro_isa_match(struct device *devptr, unsigned int n)
+{
+#ifdef CONFIG_PNP
+	if (snd_miro_pnp_is_probed)
+		return 0;
+	if (isapnp)
+		return 0;
+#endif
+	return 1;
+}
+
+static int snd_miro_isa_probe(struct device *devptr, unsigned int n)
+{
+	static long possible_ports[] = {0x530, 0xe80, 0xf40, 0x604, -1};
+	static long possible_mpu_ports[] = {0x330, 0x300, 0x310, 0x320, -1};
+	static int possible_irqs[] = {11, 9, 10, 7, -1};
+	static int possible_mpu_irqs[] = {10, 5, 9, 7, -1};
+	static int possible_dma1s[] = {3, 1, 0, -1};
+	static int possible_dma2s[][2] = { {1, -1}, {0, -1}, {-1, -1},
+					   {0, -1} };
+
+	int error;
+	struct snd_miro *miro;
+	struct snd_card *card;
+
+	error = snd_card_new(devptr, index, id, THIS_MODULE,
+			     sizeof(struct snd_miro), &card);
+	if (error < 0)
+		return error;
+
+	card->private_free = snd_card_miro_free;
+	miro = card->private_data;
+
+	error = snd_card_miro_detect(card, miro);
+	if (error < 0) {
+		snd_card_free(card);
+		snd_printk(KERN_ERR "unable to detect OPTi9xx chip\n");
+		return -ENODEV;
+	}
+
+	if (port == SNDRV_AUTO_PORT) {
+		port = snd_legacy_find_free_ioport(possible_ports, 4);
+		if (port < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR "unable to find a free WSS port\n");
+			return -EBUSY;
+		}
+	}
+
+	if (mpu_port == SNDRV_AUTO_PORT) {
+		mpu_port = snd_legacy_find_free_ioport(possible_mpu_ports, 2);
+		if (mpu_port < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR
+				   "unable to find a free MPU401 port\n");
+			return -EBUSY;
+		}
+	}
+
+	if (irq == SNDRV_AUTO_IRQ) {
+		irq = snd_legacy_find_free_irq(possible_irqs);
+		if (irq < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR "unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (mpu_irq == SNDRV_AUTO_IRQ) {
+		mpu_irq = snd_legacy_find_free_irq(possible_mpu_irqs);
+		if (mpu_irq < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR
+				   "unable to find a free MPU401 IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (dma1 == SNDRV_AUTO_DMA) {
+		dma1 = snd_legacy_find_free_dma(possible_dma1s);
+		if (dma1 < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR "unable to find a free DMA1\n");
+			return -EBUSY;
+		}
+	}
+	if (dma2 == SNDRV_AUTO_DMA) {
+		dma2 = snd_legacy_find_free_dma(possible_dma2s[dma1 % 4]);
+		if (dma2 < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR "unable to find a free DMA2\n");
+			return -EBUSY;
+		}
+	}
+
+	error = snd_miro_probe(card);
+	if (error < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	dev_set_drvdata(devptr, card);
+	return 0;
+}
+
+static int snd_miro_isa_remove(struct device *devptr,
+			       unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(devptr));
+	return 0;
+}
+
+#define DEV_NAME "miro"
+
+static struct isa_driver snd_miro_driver = {
+	.match		= snd_miro_isa_match,
+	.probe		= snd_miro_isa_probe,
+	.remove		= snd_miro_isa_remove,
+	/* FIXME: suspend/resume */
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+#ifdef CONFIG_PNP
+
+static int snd_card_miro_pnp(struct snd_miro *chip,
+			     struct pnp_card_link *card,
+			     const struct pnp_card_device_id *pid)
+{
+	struct pnp_dev *pdev;
+	int err;
+	struct pnp_dev *devmpu;
+	struct pnp_dev *devmc;
+
+	pdev = pnp_request_card_device(card, pid->devs[0].id, NULL);
+	if (pdev == NULL)
+		return -EBUSY;
+
+	devmpu = pnp_request_card_device(card, pid->devs[1].id, NULL);
+	if (devmpu == NULL)
+		return -EBUSY;
+
+	devmc = pnp_request_card_device(card, pid->devs[2].id, NULL);
+	if (devmc == NULL)
+		return -EBUSY;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "AUDIO pnp configure failure: %d\n", err);
+		return err;
+	}
+
+	err = pnp_activate_dev(devmc);
+	if (err < 0) {
+		snd_printk(KERN_ERR "MC pnp configure failure: %d\n",
+				    err);
+		return err;
+	}
+
+	port = pnp_port_start(pdev, 1);
+	fm_port = pnp_port_start(pdev, 2) + 8;
+
+	/*
+	 * The MC(0) is never accessed and the miroSOUND PCM20 card does not
+	 * include it in the PnP resource range. OPTI93x include it.
+	 */
+	chip->mc_base = pnp_port_start(devmc, 0) - 1;
+	chip->mc_base_size = pnp_port_len(devmc, 0) + 1;
+
+	irq = pnp_irq(pdev, 0);
+	dma1 = pnp_dma(pdev, 0);
+	dma2 = pnp_dma(pdev, 1);
+
+	if (mpu_port > 0) {
+		err = pnp_activate_dev(devmpu);
+		if (err < 0) {
+			snd_printk(KERN_ERR "MPU401 pnp configure failure\n");
+			mpu_port = -1;
+			return err;
+		}
+		mpu_port = pnp_port_start(devmpu, 0);
+		mpu_irq = pnp_irq(devmpu, 0);
+	}
+	return 0;
+}
+
+static int snd_miro_pnp_probe(struct pnp_card_link *pcard,
+			      const struct pnp_card_device_id *pid)
+{
+	struct snd_card *card;
+	int err;
+	struct snd_miro *miro;
+
+	if (snd_miro_pnp_is_probed)
+		return -EBUSY;
+	if (!isapnp)
+		return -ENODEV;
+	err = snd_card_new(&pcard->card->dev, index, id, THIS_MODULE,
+			   sizeof(struct snd_miro), &card);
+	if (err < 0)
+		return err;
+
+	card->private_free = snd_card_miro_free;
+	miro = card->private_data;
+
+	err = snd_card_miro_pnp(miro, pcard, pid);
+	if (err) {
+		snd_card_free(card);
+		return err;
+	}
+
+	/* only miroSOUND PCM20 and PCM12 == OPTi924 */
+	err = snd_miro_init(miro, OPTi9XX_HW_82C924);
+	if (err) {
+		snd_card_free(card);
+		return err;
+	}
+
+	err = snd_miro_opti_check(miro);
+	if (err) {
+		snd_printk(KERN_ERR "OPTI chip not found\n");
+		snd_card_free(card);
+		return err;
+	}
+
+	err = snd_miro_probe(card);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	snd_miro_pnp_is_probed = 1;
+	return 0;
+}
+
+static void snd_miro_pnp_remove(struct pnp_card_link *pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+	snd_miro_pnp_is_probed = 0;
+}
+
+static struct pnp_card_driver miro_pnpc_driver = {
+	.flags		= PNP_DRIVER_RES_DISABLE,
+	.name		= "miro",
+	.id_table	= snd_miro_pnpids,
+	.probe		= snd_miro_pnp_probe,
+	.remove		= snd_miro_pnp_remove,
+};
+#endif
+
+static int __init alsa_card_miro_init(void)
+{
+#ifdef CONFIG_PNP
+	pnp_register_card_driver(&miro_pnpc_driver);
+	if (snd_miro_pnp_is_probed)
+		return 0;
+	pnp_unregister_card_driver(&miro_pnpc_driver);
+#endif
+	return isa_register_driver(&snd_miro_driver, 1);
+}
+
+static void __exit alsa_card_miro_exit(void)
+{
+	if (!snd_miro_pnp_is_probed) {
+		isa_unregister_driver(&snd_miro_driver);
+		return;
+	}
+#ifdef CONFIG_PNP
+	pnp_unregister_card_driver(&miro_pnpc_driver);
+#endif
+}
+
+module_init(alsa_card_miro_init)
+module_exit(alsa_card_miro_exit)
diff --git a/sound/isa/opti9xx/opti92x-ad1848.c b/sound/isa/opti9xx/opti92x-ad1848.c
new file mode 100644
index 0000000..ac0ab6e
--- /dev/null
+++ b/sound/isa/opti9xx/opti92x-ad1848.c
@@ -0,0 +1,1206 @@
+/*
+    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 <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/pnp.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#ifndef OPTi93X
+#include <sound/opl4.h>
+#endif
+#define SNDRV_LEGACY_FIND_FREE_IOPORT
+#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 bool enable = SNDRV_DEFAULT_ENABLE1;	/* Enable this card */
+#ifdef CONFIG_PNP
+static bool isapnp = true;			/* Enable ISA PnP detection */
+#endif
+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.");
+#ifdef CONFIG_PNP
+module_param(isapnp, bool, 0444);
+MODULE_PARM_DESC(isapnp, "Enable ISA PnP detection for specified soundcard.");
+#endif
+module_param_hw(port, long, ioport, 0444);
+MODULE_PARM_DESC(port, "WSS port # for opti9xx driver.");
+module_param_hw(mpu_port, long, ioport, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for opti9xx driver.");
+module_param_hw(fm_port, long, ioport, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for opti9xx driver.");
+module_param_hw(irq, int, irq, 0444);
+MODULE_PARM_DESC(irq, "WSS irq # for opti9xx driver.");
+module_param_hw(mpu_irq, int, irq, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 irq # for opti9xx driver.");
+module_param_hw(dma1, int, dma, 0444);
+MODULE_PARM_DESC(dma1, "1st dma # for opti9xx driver.");
+#if defined(CS4231) || defined(OPTi93X)
+module_param_hw(dma2, int, dma, 0444);
+MODULE_PARM_DESC(dma2, "2nd dma # for opti9xx driver.");
+#endif	/* CS4231 || OPTi93X */
+
+#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
+
+#ifdef OPTi93X
+
+#define OPTi93X_STATUS			0x02
+#define OPTi93X_PORT(chip, r)		((chip)->port + OPTi93X_##r)
+
+#define OPTi93X_IRQ_PLAYBACK		0x04
+#define OPTi93X_IRQ_CAPTURE		0x08
+
+#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;
+	struct resource *res_mc_indir;
+#endif	/* OPTi93X */
+	struct snd_wss *codec;
+	unsigned long pwd_reg;
+
+	spinlock_t lock;
+
+	long wss_base;
+	int irq;
+};
+
+static int snd_opti9xx_pnp_is_probed;
+
+#ifdef CONFIG_PNP
+
+static const struct pnp_card_device_id snd_opti9xx_pnpids[] = {
+#ifndef OPTi93X
+	/* OPTi 82C924 */
+	{ .id = "OPT0924",
+	  .devs = { { "OPT0000" }, { "OPT0002" }, { "OPT0005" } },
+	  .driver_data = 0x0924 },
+	/* OPTi 82C925 */
+	{ .id = "OPT0925",
+	  .devs = { { "OPT9250" }, { "OPT0002" }, { "OPT0005" } },
+	  .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 */
+
+#define DEV_NAME KBUILD_MODNAME
+
+static char * snd_opti9xx_names[] = {
+	"unknown",
+	"82C928",	"82C929",
+	"82C924",	"82C925",
+	"82C930",	"82C931",	"82C933"
+};
+
+static int snd_opti9xx_init(struct snd_opti9xx *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]);
+
+	spin_lock_init(&chip->lock);
+
+	chip->irq = -1;
+
+#ifndef OPTi93X
+#ifdef CONFIG_PNP
+	if (isapnp && chip->mc_base)
+		/* PnP resource gives the least 10 bits */
+		chip->mc_base |= 0xc00;
+	else
+#endif	/* CONFIG_PNP */
+	{
+		chip->mc_base = 0xf8c;
+		chip->mc_base_size = opti9xx_mc_size[hardware];
+	}
+#else
+		chip->mc_base_size = opti9xx_mc_size[hardware];
+#endif
+
+	switch (hardware) {
+#ifndef OPTi93X
+	case OPTi9XX_HW_82C928:
+	case OPTi9XX_HW_82C929:
+		chip->password = (hardware == OPTi9XX_HW_82C928) ? 0xe2 : 0xe3;
+		chip->pwd_reg = 3;
+		break;
+
+	case OPTi9XX_HW_82C924:
+	case OPTi9XX_HW_82C925:
+		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;
+		if (!chip->mc_indir_index)
+			chip->mc_indir_index = 0xe0e;
+		chip->password = 0xe4;
+		chip->pwd_reg = 0;
+		break;
+#endif	/* OPTi93X */
+
+	default:
+		snd_printk(KERN_ERR "chip %d not supported\n", hardware);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static unsigned char snd_opti9xx_read(struct snd_opti9xx *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;
+		}
+		/* Fall through */
+
+	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(KERN_ERR "chip %d not supported\n", chip->hardware);
+	}
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return retval;
+}
+
+static void snd_opti9xx_write(struct snd_opti9xx *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;
+		}
+		/* Fall through */
+
+	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(KERN_ERR "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 snd_opti9xx_configure(struct snd_opti9xx *chip,
+					   long port,
+					   int irq, int dma1, int dma2,
+					   long mpu_port, int mpu_irq)
+{
+	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:
+		/* opti 929 mode (?), OPL3 clock output, audio enable */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0xf0, 0xfc);
+		/* enable wave audio */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x02);
+		/* Fall through */
+
+	case OPTi9XX_HW_82C925:
+		/* enable WSS mode */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80);
+		/* OPL3 FM synthesis */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), 0x00, 0x20);
+		/* disable Sound Blaster IRQ and DMA */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0xf0, 0xff);
+#ifdef CS4231
+		/* cs4231/4248 fix enabled */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02);
+#else
+		/* cs4231/4248 fix disabled */
+		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_82C931:
+		/* disable 3D sound (set GPIO1 as output, low) */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(20), 0x04, 0x0c);
+	case OPTi9XX_HW_82C933: /* FALL THROUGH */
+		/*
+		 * The BTC 1817DW has QS1000 wavetable which is connected
+		 * to the serial digital input of the OPTI931.
+		 */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(21), 0x82, 0xff);
+		/* 
+		 * This bit sets OPTI931 to automaticaly select FM
+		 * or digital input signal.
+		 */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(26), 0x01, 0x01);
+	case OPTi9XX_HW_82C930: /* FALL THROUGH */
+		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(KERN_ERR "chip %d not supported\n", chip->hardware);
+		return -EINVAL;
+	}
+
+	/* PnP resource says it decodes only 10 bits of address */
+	switch (port & 0x3ff) {
+	case 0x130:
+		chip->wss_base = 0x530;
+		wss_base_bits = 0x00;
+		break;
+	case 0x204:
+		chip->wss_base = 0x604;
+		wss_base_bits = 0x03;
+		break;
+	case 0x280:
+		chip->wss_base = 0xe80;
+		wss_base_bits = 0x01;
+		break;
+	case 0x340:
+		chip->wss_base = 0xf40;
+		wss_base_bits = 0x02;
+		break;
+	default:
+		snd_printk(KERN_WARNING "WSS port 0x%lx not valid\n", port);
+		goto __skip_base;
+	}
+	snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), wss_base_bits << 4, 0x30);
+
+__skip_base:
+	switch (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(KERN_WARNING "WSS irq # %d not valid\n", irq);
+		goto __skip_resources;
+	}
+
+	switch (dma1) {
+	case 0:
+		dma_bits = 0x01;
+		break;
+	case 1:
+		dma_bits = 0x02;
+		break;
+	case 3:
+		dma_bits = 0x03;
+		break;
+	default:
+		snd_printk(KERN_WARNING "WSS dma1 # %d not valid\n", dma1);
+		goto __skip_resources;
+	}
+
+#if defined(CS4231) || defined(OPTi93X)
+	if (dma1 == dma2) {
+		snd_printk(KERN_ERR "don't want to share dmas\n");
+		return -EBUSY;
+	}
+
+	switch (dma2) {
+	case 0:
+	case 1:
+		break;
+	default:
+		snd_printk(KERN_WARNING "WSS dma2 # %d not valid\n", 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 (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(KERN_WARNING
+				   "MPU-401 port 0x%lx not valid\n", mpu_port);
+			goto __skip_mpu;
+		}
+
+		switch (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(KERN_WARNING "MPU-401 irq # %d not valid\n",
+				mpu_irq);
+			goto __skip_mpu;
+		}
+
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6),
+			(mpu_port <= 0) ? 0x00 :
+				0x80 | mpu_port_bits << 5 | mpu_irq_bits << 3,
+			0xf8);
+	}
+__skip_mpu:
+
+	return 0;
+}
+
+#ifdef OPTi93X
+
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_3db_step, -9300, 300, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit, -4650, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_4bit_12db_max, -3300, 300, 0);
+
+static struct snd_kcontrol_new snd_opti93x_controls[] = {
+WSS_DOUBLE("Master Playback Switch", 0,
+		OPTi93X_OUT_LEFT, OPTi93X_OUT_RIGHT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Master Playback Volume", 0,
+		OPTi93X_OUT_LEFT, OPTi93X_OUT_RIGHT, 1, 1, 31, 1,
+		db_scale_5bit_3db_step),
+WSS_DOUBLE_TLV("PCM Playback Volume", 0,
+		CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 31, 1,
+		db_scale_5bit),
+WSS_DOUBLE_TLV("FM Playback Volume", 0,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 1, 1, 15, 1,
+		db_scale_4bit_12db_max),
+WSS_DOUBLE("Line Playback Switch", 0,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Line Playback Volume", 0,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 15, 1,
+		db_scale_4bit_12db_max),
+WSS_DOUBLE("Mic Playback Switch", 0,
+		OPTi93X_MIC_LEFT_INPUT, OPTi93X_MIC_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Mic Playback Volume", 0,
+		OPTi93X_MIC_LEFT_INPUT, OPTi93X_MIC_RIGHT_INPUT, 1, 1, 15, 1,
+		db_scale_4bit_12db_max),
+WSS_DOUBLE_TLV("CD Playback Volume", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 1, 1, 15, 1,
+		db_scale_4bit_12db_max),
+WSS_DOUBLE("Aux Playback Switch", 0,
+		OPTi931_AUX_LEFT_INPUT, OPTi931_AUX_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Aux Playback Volume", 0,
+		OPTi931_AUX_LEFT_INPUT, OPTi931_AUX_RIGHT_INPUT, 1, 1, 15, 1,
+		db_scale_4bit_12db_max),
+};
+
+static int snd_opti93x_mixer(struct snd_wss *chip)
+{
+	struct snd_card *card;
+	unsigned int idx;
+	struct snd_ctl_elem_id id1, id2;
+	int err;
+
+	if (snd_BUG_ON(!chip || !chip->pcm))
+		return -EINVAL;
+
+	card = chip->card;
+
+	strcpy(card->mixername, chip->pcm->name);
+
+	memset(&id1, 0, sizeof(id1));
+	memset(&id2, 0, sizeof(id2));
+	id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	/* reassign AUX0 switch to CD */
+	strcpy(id1.name, "Aux Playback Switch");
+	strcpy(id2.name, "CD Playback Switch");
+	err = snd_ctl_rename_id(card, &id1, &id2);
+	if (err < 0) {
+		snd_printk(KERN_ERR "Cannot rename opti93x control\n");
+		return err;
+	}
+	/* reassign AUX1 switch to FM */
+	strcpy(id1.name, "Aux Playback Switch"); id1.index = 1;
+	strcpy(id2.name, "FM Playback Switch");
+	err = snd_ctl_rename_id(card, &id1, &id2);
+	if (err < 0) {
+		snd_printk(KERN_ERR "Cannot rename opti93x control\n");
+		return err;
+	}
+	/* remove AUX1 volume */
+	strcpy(id1.name, "Aux Playback Volume"); id1.index = 1;
+	snd_ctl_remove_id(card, &id1);
+
+	/* Replace WSS volume controls with OPTi93x volume controls */
+	id1.index = 0;
+	for (idx = 0; idx < ARRAY_SIZE(snd_opti93x_controls); idx++) {
+		strcpy(id1.name, snd_opti93x_controls[idx].name);
+		snd_ctl_remove_id(card, &id1);
+
+		err = snd_ctl_add(card,
+				snd_ctl_new1(&snd_opti93x_controls[idx], chip));
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static irqreturn_t snd_opti93x_interrupt(int irq, void *dev_id)
+{
+	struct snd_opti9xx *chip = dev_id;
+	struct snd_wss *codec = chip->codec;
+	unsigned char status;
+
+	if (!codec)
+		return IRQ_HANDLED;
+
+	status = snd_opti9xx_read(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_wss_overrange(codec);
+		snd_pcm_period_elapsed(codec->capture_substream);
+	}
+	outb(0x00, OPTi93X_PORT(codec, STATUS));
+	return IRQ_HANDLED;
+}
+
+#endif /* OPTi93X */
+
+static int snd_opti9xx_read_check(struct snd_opti9xx *chip)
+{
+	unsigned char value;
+#ifdef OPTi93X
+	unsigned long flags;
+#endif
+
+	chip->res_mc_base = request_region(chip->mc_base, chip->mc_base_size,
+					   "OPTi9xx MC");
+	if (chip->res_mc_base == NULL)
+		return -EBUSY;
+#ifndef OPTi93X
+	value = snd_opti9xx_read(chip, OPTi9XX_MC_REG(1));
+	if (value != 0xff && value != inb(chip->mc_base + OPTi9XX_MC_REG(1)))
+		if (value == snd_opti9xx_read(chip, OPTi9XX_MC_REG(1)))
+			return 0;
+#else	/* OPTi93X */
+	chip->res_mc_indir = request_region(chip->mc_indir_index, 2,
+					    "OPTi93x MC");
+	if (chip->res_mc_indir == NULL)
+		return -EBUSY;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	outb(chip->password, chip->mc_base + chip->pwd_reg);
+	outb(((chip->mc_indir_index & 0x1f0) >> 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 0;
+
+	release_and_free_resource(chip->res_mc_indir);
+	chip->res_mc_indir = NULL;
+#endif	/* OPTi93X */
+	release_and_free_resource(chip->res_mc_base);
+	chip->res_mc_base = NULL;
+
+	return -ENODEV;
+}
+
+static int snd_card_opti9xx_detect(struct snd_card *card,
+				   struct snd_opti9xx *chip)
+{
+	int i, err;
+
+#ifndef OPTi93X
+	for (i = OPTi9XX_HW_82C928; i < OPTi9XX_HW_82C930; i++) {
+#else
+	for (i = OPTi9XX_HW_82C931; i >= OPTi9XX_HW_82C930; i--) {
+#endif
+		err = snd_opti9xx_init(chip, i);
+		if (err < 0)
+			return err;
+
+		err = snd_opti9xx_read_check(chip);
+		if (err == 0)
+			return 1;
+#ifdef OPTi93X
+		chip->mc_indir_index = 0;
+#endif
+	}
+	return -ENODEV;
+}
+
+#ifdef CONFIG_PNP
+static int snd_card_opti9xx_pnp(struct snd_opti9xx *chip,
+				struct pnp_card_link *card,
+				const struct pnp_card_device_id *pid)
+{
+	struct pnp_dev *pdev;
+	int err;
+	struct pnp_dev *devmpu;
+#ifndef OPTi93X
+	struct pnp_dev *devmc;
+#endif
+
+	pdev = pnp_request_card_device(card, pid->devs[0].id, NULL);
+	if (pdev == NULL)
+		return -EBUSY;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "AUDIO pnp configure failure: %d\n", err);
+		return err;
+	}
+
+#ifdef OPTi93X
+	port = pnp_port_start(pdev, 0) - 4;
+	fm_port = pnp_port_start(pdev, 1) + 8;
+	/* adjust mc_indir_index - some cards report it at 0xe?d,
+	   other at 0xe?c but it really is always at 0xe?e */
+	chip->mc_indir_index = (pnp_port_start(pdev, 3) & ~0xf) | 0xe;
+#else
+	devmc = pnp_request_card_device(card, pid->devs[2].id, NULL);
+	if (devmc == NULL)
+		return -EBUSY;
+
+	err = pnp_activate_dev(devmc);
+	if (err < 0) {
+		snd_printk(KERN_ERR "MC pnp configure failure: %d\n", err);
+		return err;
+	}
+
+	port = pnp_port_start(pdev, 1);
+	fm_port = pnp_port_start(pdev, 2) + 8;
+	/*
+	 * The MC(0) is never accessed and card does not
+	 * include it in the PnP resource range. OPTI93x include it.
+	 */
+	chip->mc_base = pnp_port_start(devmc, 0) - 1;
+	chip->mc_base_size = pnp_port_len(devmc, 0) + 1;
+#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 */
+
+	devmpu = pnp_request_card_device(card, pid->devs[1].id, NULL);
+
+	if (devmpu && mpu_port > 0) {
+		err = pnp_activate_dev(devmpu);
+		if (err < 0) {
+			snd_printk(KERN_ERR "MPU401 pnp configure failure\n");
+			mpu_port = -1;
+		} else {
+			mpu_port = pnp_port_start(devmpu, 0);
+			mpu_irq = pnp_irq(devmpu, 0);
+		}
+	}
+	return pid->driver_data;
+}
+#endif	/* CONFIG_PNP */
+
+static void snd_card_opti9xx_free(struct snd_card *card)
+{
+	struct snd_opti9xx *chip = card->private_data;
+
+	if (chip) {
+#ifdef OPTi93X
+		if (chip->irq > 0) {
+			disable_irq(chip->irq);
+			free_irq(chip->irq, chip);
+		}
+		release_and_free_resource(chip->res_mc_indir);
+#endif
+		release_and_free_resource(chip->res_mc_base);
+	}
+}
+
+static int snd_opti9xx_probe(struct snd_card *card)
+{
+	static long possible_ports[] = {0x530, 0xe80, 0xf40, 0x604, -1};
+	int error;
+	int xdma2;
+	struct snd_opti9xx *chip = card->private_data;
+	struct snd_wss *codec;
+	struct snd_rawmidi *rmidi;
+	struct snd_hwdep *synth;
+
+#if defined(CS4231) || defined(OPTi93X)
+	xdma2 = dma2;
+#else
+	xdma2 = -1;
+#endif
+
+	if (port == SNDRV_AUTO_PORT) {
+		port = snd_legacy_find_free_ioport(possible_ports, 4);
+		if (port < 0) {
+			snd_printk(KERN_ERR "unable to find a free WSS port\n");
+			return -EBUSY;
+		}
+	}
+	error = snd_opti9xx_configure(chip, port, irq, dma1, xdma2,
+				      mpu_port, mpu_irq);
+	if (error)
+		return error;
+
+	error = snd_wss_create(card, chip->wss_base + 4, -1, irq, dma1, xdma2,
+#ifdef OPTi93X
+			       WSS_HW_OPTI93X, WSS_HWSHARE_IRQ,
+#else
+			       WSS_HW_DETECT, 0,
+#endif
+			       &codec);
+	if (error < 0)
+		return error;
+	chip->codec = codec;
+	error = snd_wss_pcm(codec, 0);
+	if (error < 0)
+		return error;
+	error = snd_wss_mixer(codec);
+	if (error < 0)
+		return error;
+#ifdef OPTi93X
+	error = snd_opti93x_mixer(codec);
+	if (error < 0)
+		return error;
+#endif
+#ifdef CS4231
+	error = snd_wss_timer(codec, 0);
+	if (error < 0)
+		return error;
+#endif
+#ifdef OPTi93X
+	error = request_irq(irq, snd_opti93x_interrupt,
+			    0, DEV_NAME" - WSS", chip);
+	if (error < 0) {
+		snd_printk(KERN_ERR "opti9xx: can't grab IRQ %d\n", irq);
+		return error;
+	}
+#endif
+	chip->irq = irq;
+	strcpy(card->driver, chip->name);
+	sprintf(card->shortname, "OPTi %s", card->driver);
+#if defined(CS4231) || defined(OPTi93X)
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s, %s at 0x%lx, irq %d, dma %d&%d",
+		 card->shortname, codec->pcm->name,
+		 chip->wss_base + 4, irq, dma1, xdma2);
+#else
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s, %s at 0x%lx, irq %d, dma %d",
+		 card->shortname, codec->pcm->name, chip->wss_base + 4, irq,
+		 dma1);
+#endif	/* CS4231 || OPTi93X */
+
+	if (mpu_port <= 0 || mpu_port == SNDRV_AUTO_PORT)
+		rmidi = NULL;
+	else {
+		error = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+				mpu_port, 0, mpu_irq, &rmidi);
+		if (error)
+			snd_printk(KERN_WARNING "no MPU-401 device at 0x%lx?\n",
+				   mpu_port);
+	}
+
+	if (fm_port > 0 && fm_port != SNDRV_AUTO_PORT) {
+		struct snd_opl3 *opl3 = NULL;
+#ifndef OPTi93X
+		if (chip->hardware == OPTi9XX_HW_82C928 ||
+		    chip->hardware == OPTi9XX_HW_82C929 ||
+		    chip->hardware == OPTi9XX_HW_82C924) {
+			struct snd_opl4 *opl4;
+			/* assume we have an OPL4 */
+			snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2),
+					       0x20, 0x20);
+			if (snd_opl4_create(card, fm_port, 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, fm_port, fm_port + 2,
+					     OPL3_HW_AUTO, 0, &opl3) < 0) {
+			snd_printk(KERN_WARNING "no OPL device at 0x%lx-0x%lx\n",
+				   fm_port, fm_port + 4 - 1);
+		}
+		if (opl3) {
+			error = snd_opl3_hwdep_new(opl3, 0, 1, &synth);
+			if (error < 0)
+				return error;
+		}
+	}
+
+	return snd_card_register(card);
+}
+
+static int snd_opti9xx_card_new(struct device *pdev, struct snd_card **cardp)
+{
+	struct snd_card *card;
+	int err;
+
+	err = snd_card_new(pdev, index, id, THIS_MODULE,
+			   sizeof(struct snd_opti9xx), &card);
+	if (err < 0)
+		return err;
+	card->private_free = snd_card_opti9xx_free;
+	*cardp = card;
+	return 0;
+}
+
+static int snd_opti9xx_isa_match(struct device *devptr,
+				 unsigned int dev)
+{
+#ifdef CONFIG_PNP
+	if (snd_opti9xx_pnp_is_probed)
+		return 0;
+	if (isapnp)
+		return 0;
+#endif
+	return 1;
+}
+
+static int snd_opti9xx_isa_probe(struct device *devptr,
+				 unsigned int dev)
+{
+	struct snd_card *card;
+	int error;
+	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 */
+
+	if (mpu_port == SNDRV_AUTO_PORT) {
+		if ((mpu_port = snd_legacy_find_free_ioport(possible_mpu_ports, 2)) < 0) {
+			snd_printk(KERN_ERR "unable to find a free MPU401 port\n");
+			return -EBUSY;
+		}
+	}
+	if (irq == SNDRV_AUTO_IRQ) {
+		if ((irq = snd_legacy_find_free_irq(possible_irqs)) < 0) {
+			snd_printk(KERN_ERR "unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (mpu_irq == SNDRV_AUTO_IRQ) {
+		if ((mpu_irq = snd_legacy_find_free_irq(possible_mpu_irqs)) < 0) {
+			snd_printk(KERN_ERR "unable to find a free MPU401 IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (dma1 == SNDRV_AUTO_DMA) {
+		if ((dma1 = snd_legacy_find_free_dma(possible_dma1s)) < 0) {
+			snd_printk(KERN_ERR "unable to find a free DMA1\n");
+			return -EBUSY;
+		}
+	}
+#if defined(CS4231) || defined(OPTi93X)
+	if (dma2 == SNDRV_AUTO_DMA) {
+		if ((dma2 = snd_legacy_find_free_dma(possible_dma2s[dma1 % 4])) < 0) {
+			snd_printk(KERN_ERR "unable to find a free DMA2\n");
+			return -EBUSY;
+		}
+	}
+#endif
+
+	error = snd_opti9xx_card_new(devptr, &card);
+	if (error < 0)
+		return error;
+
+	if ((error = snd_card_opti9xx_detect(card, card->private_data)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	if ((error = snd_opti9xx_probe(card)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	dev_set_drvdata(devptr, card);
+	return 0;
+}
+
+static int snd_opti9xx_isa_remove(struct device *devptr,
+				  unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(devptr));
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_opti9xx_suspend(struct snd_card *card)
+{
+	struct snd_opti9xx *chip = card->private_data;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	chip->codec->suspend(chip->codec);
+	return 0;
+}
+
+static int snd_opti9xx_resume(struct snd_card *card)
+{
+	struct snd_opti9xx *chip = card->private_data;
+	int error, xdma2;
+#if defined(CS4231) || defined(OPTi93X)
+	xdma2 = dma2;
+#else
+	xdma2 = -1;
+#endif
+
+	error = snd_opti9xx_configure(chip, port, irq, dma1, xdma2,
+				      mpu_port, mpu_irq);
+	if (error)
+		return error;
+	chip->codec->resume(chip->codec);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+
+static int snd_opti9xx_isa_suspend(struct device *dev, unsigned int n,
+				   pm_message_t state)
+{
+	return snd_opti9xx_suspend(dev_get_drvdata(dev));
+}
+
+static int snd_opti9xx_isa_resume(struct device *dev, unsigned int n)
+{
+	return snd_opti9xx_resume(dev_get_drvdata(dev));
+}
+#endif
+
+static struct isa_driver snd_opti9xx_driver = {
+	.match		= snd_opti9xx_isa_match,
+	.probe		= snd_opti9xx_isa_probe,
+	.remove		= snd_opti9xx_isa_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_opti9xx_isa_suspend,
+	.resume		= snd_opti9xx_isa_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+#ifdef CONFIG_PNP
+static int snd_opti9xx_pnp_probe(struct pnp_card_link *pcard,
+				 const struct pnp_card_device_id *pid)
+{
+	struct snd_card *card;
+	int error, hw;
+	struct snd_opti9xx *chip;
+
+	if (snd_opti9xx_pnp_is_probed)
+		return -EBUSY;
+	if (! isapnp)
+		return -ENODEV;
+	error = snd_opti9xx_card_new(&pcard->card->dev, &card);
+	if (error < 0)
+		return error;
+	chip = card->private_data;
+
+	hw = snd_card_opti9xx_pnp(chip, pcard, pid);
+	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;
+	}
+	error = snd_opti9xx_read_check(chip);
+	if (error) {
+		snd_printk(KERN_ERR "OPTI chip not found\n");
+		snd_card_free(card);
+		return error;
+	}
+	if ((error = snd_opti9xx_probe(card)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	snd_opti9xx_pnp_is_probed = 1;
+	return 0;
+}
+
+static void snd_opti9xx_pnp_remove(struct pnp_card_link *pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+	snd_opti9xx_pnp_is_probed = 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_opti9xx_pnp_suspend(struct pnp_card_link *pcard,
+				   pm_message_t state)
+{
+	return snd_opti9xx_suspend(pnp_get_card_drvdata(pcard));
+}
+
+static int snd_opti9xx_pnp_resume(struct pnp_card_link *pcard)
+{
+	return snd_opti9xx_resume(pnp_get_card_drvdata(pcard));
+}
+#endif
+
+static struct pnp_card_driver opti9xx_pnpc_driver = {
+	.flags		= PNP_DRIVER_RES_DISABLE,
+	.name		= DEV_NAME,
+	.id_table	= snd_opti9xx_pnpids,
+	.probe		= snd_opti9xx_pnp_probe,
+	.remove		= snd_opti9xx_pnp_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_opti9xx_pnp_suspend,
+	.resume		= snd_opti9xx_pnp_resume,
+#endif
+};
+#endif
+
+#ifdef OPTi93X
+#define CHIP_NAME	"82C93x"
+#else
+#define CHIP_NAME	"82C92x"
+#endif
+
+static int __init alsa_card_opti9xx_init(void)
+{
+#ifdef CONFIG_PNP
+	pnp_register_card_driver(&opti9xx_pnpc_driver);
+	if (snd_opti9xx_pnp_is_probed)
+		return 0;
+	pnp_unregister_card_driver(&opti9xx_pnpc_driver);
+#endif
+	return isa_register_driver(&snd_opti9xx_driver, 1);
+}
+
+static void __exit alsa_card_opti9xx_exit(void)
+{
+	if (!snd_opti9xx_pnp_is_probed) {
+		isa_unregister_driver(&snd_opti9xx_driver);
+		return;
+	}
+#ifdef CONFIG_PNP
+	pnp_unregister_card_driver(&opti9xx_pnpc_driver);
+#endif
+}
+
+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..f174a5b
--- /dev/null
+++ b/sound/isa/sb/Makefile
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.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-jazz16-objs := jazz16.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_SB_COMMON) += snd-sb-common.o
+obj-$(CONFIG_SND_SB16_DSP) += snd-sb16-dsp.o
+obj-$(CONFIG_SND_SB8_DSP) += snd-sb8-dsp.o
+obj-$(CONFIG_SND_SB8) += snd-sb8.o
+obj-$(CONFIG_SND_SB16) += snd-sb16.o
+obj-$(CONFIG_SND_SBAWE) += snd-sbawe.o
+obj-$(CONFIG_SND_JAZZ16) += snd-jazz16.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-$(CONFIG_SND_SBAWE_SEQ) += snd-emu8000-synth.o
diff --git a/sound/isa/sb/emu8000.c b/sound/isa/sb/emu8000.c
new file mode 100644
index 0000000..d56973b
--- /dev/null
+++ b/sound/isa/sb/emu8000.c
@@ -0,0 +1,1171 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/wait.h>
+#include <linux/sched/signal.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/export.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/emu8000.h>
+#include <sound/emu8000_reg.h>
+#include <linux/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(struct snd_emu8000 *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(struct snd_emu8000 *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(struct snd_emu8000 *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(struct snd_emu8000 *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(struct snd_emu8000 *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
+snd_emu8000_read_wait(struct snd_emu8000 *emu)
+{
+	while ((EMU8000_SMALR_READ(emu) & 0x80000000) != 0) {
+		schedule_timeout_interruptible(1);
+		if (signal_pending(current))
+			break;
+	}
+}
+
+/*
+ */
+static void
+snd_emu8000_write_wait(struct snd_emu8000 *emu)
+{
+	while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
+		schedule_timeout_interruptible(1);
+		if (signal_pending(current))
+			break;
+	}
+}
+
+/*
+ * detect a card at the given port
+ */
+static int
+snd_emu8000_detect(struct snd_emu8000 *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_audio(struct snd_emu8000 *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_dma(struct snd_emu8000 *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] = {
+	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] = {
+	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] = {
+	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] = {
+	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
+send_array(struct snd_emu8000 *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_arrays(struct snd_emu8000 *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 arbitrary 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
+size_dram(struct snd_emu8000 *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 */
+	snd_emu8000_write_wait(emu);
+
+	/*
+	 * Detect first 512 KiB.  If a write succeeds at the beginning of a
+	 * 512 KiB page we assume that the whole page is there.
+	 */
+	EMU8000_SMALR_WRITE(emu, EMU8000_DRAM_OFFSET);
+	EMU8000_SMLD_READ(emu); /* discard stale data  */
+	if (EMU8000_SMLD_READ(emu) != UNIQUE_ID1)
+		goto skip_detect;   /* No RAM */
+	snd_emu8000_read_wait(emu);
+
+	for (size = 512 * 1024; size < EMU8000_MAX_DRAM; size += 512 * 1024) {
+
+		/* 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; /* no memory at this address */
+		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);
+
+		/* Otherwise, it's valid memory. */
+	}
+
+skip_detect:
+	/* wait until FULL bit in SMAxW register is false */
+	for (i = 0; i < 10000; i++) {
+		if ((EMU8000_SMALW_READ(emu) & 0x80000000) == 0)
+			break;
+		schedule_timeout_interruptible(1);
+		if (signal_pending(current))
+			break;
+	}
+	snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_CLOSE);
+	snd_emu8000_dma_chan(emu, 1, EMU8000_RAM_CLOSE);
+
+	pr_info("EMU8000 [0x%lx]: %d KiB on-board DRAM 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(struct snd_emu8000 *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
+snd_emu8000_init_hw(struct snd_emu8000 *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(struct snd_emu8000 *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
+
+struct soundfont_chorus_fx {
+	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) */
+};
+
+/* 5 parameters for each chorus mode; 3 x 16bit, 2 x 32bit */
+static char chorus_defined[SNDRV_EMU8000_CHORUS_NUMBERS];
+static struct soundfont_chorus_fx 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(struct snd_emu8000 *emu, int mode, const void __user *buf, long len)
+{
+	struct soundfont_chorus_fx 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(struct snd_emu8000 *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
+
+struct soundfont_reverb_fx {
+	unsigned short parms[28];
+};
+
+/* 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 struct soundfont_reverb_fx 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(struct snd_emu8000 *emu, int mode, const void __user *buf, long len)
+{
+	struct soundfont_reverb_fx 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(struct snd_emu8000 *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu8000 *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu8000 *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 struct snd_kcontrol_new 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 struct snd_kcontrol_new 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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu8000 *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu8000 *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 struct snd_kcontrol_new 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 struct snd_kcontrol_new 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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu8000 *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu8000 *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 struct snd_kcontrol_new 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 struct snd_kcontrol_new 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 struct snd_kcontrol_new *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
+snd_emu8000_create_mixer(struct snd_card *card, struct snd_emu8000 *emu)
+{
+	int i, err = 0;
+
+	if (snd_BUG_ON(!emu || !card))
+		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(struct snd_emu8000 *hw)
+{
+	release_and_free_resource(hw->res_port1);
+	release_and_free_resource(hw->res_port2);
+	release_and_free_resource(hw->res_port3);
+	kfree(hw);
+	return 0;
+}
+
+/*
+ */
+static int snd_emu8000_dev_free(struct snd_device *device)
+{
+	struct snd_emu8000 *hw = device->device_data;
+	return snd_emu8000_free(hw);
+}
+
+/*
+ * initialize and register emu8000 synth device.
+ */
+int
+snd_emu8000_new(struct snd_card *card, int index, long port, int seq_ports,
+		struct snd_seq_device **awe_ret)
+{
+	struct snd_seq_device *awe;
+	struct snd_emu8000 *hw;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_emu8000_dev_free,
+	};
+
+	if (awe_ret)
+		*awe_ret = NULL;
+
+	if (seq_ports <= 0)
+		return 0;
+
+	hw = kzalloc(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 IS_ENABLED(CONFIG_SND_SEQUENCER)
+	if (snd_seq_device_new(card, index, SNDRV_SEQ_DEV_ID_EMU8000,
+			       sizeof(struct snd_emu8000*), &awe) >= 0) {
+		strcpy(awe->name, "EMU-8000");
+		*(struct snd_emu8000 **)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..5a48550
--- /dev/null
+++ b/sound/isa/sb/emu8000_callback.c
@@ -0,0 +1,547 @@
+/*
+ *  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 <linux/export.h>
+#include <sound/asoundef.h>
+
+/*
+ * prototypes
+ */
+static struct snd_emux_voice *get_voice(struct snd_emux *emu,
+					struct snd_emux_port *port);
+static int start_voice(struct snd_emux_voice *vp);
+static void trigger_voice(struct snd_emux_voice *vp);
+static void release_voice(struct snd_emux_voice *vp);
+static void update_voice(struct snd_emux_voice *vp, int update);
+static void reset_voice(struct snd_emux *emu, int ch);
+static void terminate_voice(struct snd_emux_voice *vp);
+static void sysex(struct snd_emux *emu, char *buf, int len, int parsed,
+		  struct snd_midi_channel_set *chset);
+#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS)
+static int oss_ioctl(struct snd_emux *emu, int cmd, int p1, int p2);
+#endif
+static int load_fx(struct snd_emux *emu, int type, int mode,
+		   const void __user *buf, long len);
+
+static void set_pitch(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void set_volume(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void set_pan(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void set_fmmod(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void set_tremfreq(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void set_fm2frq2(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void set_filterQ(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void snd_emu8000_tweak_voice(struct snd_emu8000 *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 const struct snd_emux_operators 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,
+#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS)
+	.oss_ioctl =	oss_ioctl,
+#endif
+};
+
+void
+snd_emu8000_ops_setup(struct snd_emu8000 *hw)
+{
+	hw->emu->ops = emu8000_ops;
+}
+
+
+
+/*
+ * Terminate a voice
+ */
+static void
+release_voice(struct snd_emux_voice *vp)
+{
+	int dcysusv;
+	struct snd_emu8000 *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(struct snd_emux_voice *vp)
+{
+	struct snd_emu8000 *hw; 
+
+	hw = vp->hw;
+	EMU8000_DCYSUSV_WRITE(hw, vp->ch, 0x807F);
+}
+
+
+/*
+ */
+static void
+update_voice(struct snd_emux_voice *vp, int update)
+{
+	struct snd_emu8000 *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 struct snd_emux_voice *
+get_voice(struct snd_emux *emu, struct snd_emux_port *port)
+{
+	int  i;
+	struct snd_emux_voice *vp;
+	struct snd_emu8000 *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(struct snd_emux_voice *vp)
+{
+	unsigned int temp;
+	int ch;
+	int addr;
+	struct snd_midi_channel *chan;
+	struct snd_emu8000 *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(struct snd_emux_voice *vp)
+{
+	int ch = vp->ch;
+	unsigned int temp;
+	struct snd_emu8000 *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(struct snd_emux *emu, int ch)
+{
+	struct snd_emu8000 *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(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
+{
+	EMU8000_IP_WRITE(hw, vp->ch, vp->apitch);
+}
+
+/*
+ * Set the volume of a possibly already playing note
+ */
+static void
+set_volume(struct snd_emu8000 *hw, struct snd_emux_voice *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(struct snd_emu8000 *hw, struct snd_emux_voice *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(struct snd_emu8000 *hw, struct snd_emux_voice *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(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
+{
+	EMU8000_TREMFRQ_WRITE(hw, vp->ch, vp->reg.parm.tremfrq);
+}
+
+/* set lfo2 pitch & frequency */
+static void
+set_fm2frq2(struct snd_emu8000 *hw, struct snd_emux_voice *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(struct snd_emu8000 *hw, struct snd_emux_voice *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(struct snd_emu8000 *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(struct snd_emux *emu, char *buf, int len, int parsed, struct snd_midi_channel_set *chset)
+{
+	struct snd_emu8000 *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;
+	}
+}
+
+
+#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS)
+/*
+ * OSS ioctl callback
+ */
+static int
+oss_ioctl(struct snd_emux *emu, int cmd, int p1, int p2)
+{
+	struct snd_emu8000 *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(struct snd_emux *emu, int type, int mode, const void __user *buf, long len)
+{
+	struct snd_emu8000 *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..7e87c34
--- /dev/null
+++ b/sound/isa/sb/emu8000_local.h
@@ -0,0 +1,45 @@
+#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 <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(struct snd_emux *rec, struct snd_sf_sample *sp,
+			   struct snd_util_memhdr *hdr,
+			   const void __user *data, long count);
+int snd_emu8000_sample_free(struct snd_emux *rec, struct snd_sf_sample *sp,
+			    struct snd_util_memhdr *hdr);
+void snd_emu8000_sample_reset(struct snd_emux *rec);
+
+/* emu8000_callback.c */
+void snd_emu8000_ops_setup(struct snd_emu8000 *emu);
+
+/* emu8000_pcm.c */
+int snd_emu8000_pcm_new(struct snd_card *card, struct snd_emu8000 *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..d45a6b9
--- /dev/null
+++ b/sound/isa/sb/emu8000_patch.c
@@ -0,0 +1,304 @@
+/*
+ *  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 <linux/sched/signal.h>
+#include <linux/uaccess.h>
+#include <linux/moduleparam.h>
+
+static int emu8000_reset_addr;
+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(struct snd_emu8000 *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(struct snd_emu8000 *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(struct snd_emu8000 *emu)
+{
+	while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
+		schedule_timeout_interruptible(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.
+ */
+static inline void
+write_word(struct snd_emu8000 *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(struct snd_emux *rec, struct snd_sf_sample *sp,
+		       struct snd_util_memhdr *hdr,
+		       const void __user *data, long count)
+{
+	int  i;
+	int  rc;
+	int  offset;
+	int  truesize;
+	int  dram_offset, dram_start;
+	struct snd_emu8000 *emu;
+
+	emu = rec->hw;
+	if (snd_BUG_ON(!sp))
+		return -EINVAL;
+
+	if (sp->v.size == 0)
+		return 0;
+
+	/* be sure loop points start < end */
+	if (sp->v.loopstart > sp->v.loopend)
+		swap(sp->v.loopstart, sp->v.loopend);
+
+	/* 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(struct snd_emux *rec, struct snd_sf_sample *sp,
+			struct snd_util_memhdr *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(struct snd_emux *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..f46f6ec
--- /dev/null
+++ b/sound/isa/sb/emu8000_pcm.c
@@ -0,0 +1,709 @@
+/*
+ * 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/sched/signal.h>
+#include <linux/init.h>
+#include <linux/slab.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...
+ */
+
+
+struct snd_emu8k_pcm {
+	struct snd_emu8000 *emu;
+	struct snd_pcm_substream *substream;
+
+	unsigned int allocated_bytes;
+	struct snd_util_memblk *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(struct snd_emu8000 *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(struct snd_emu8000 *emu, int can_schedule)
+{
+	while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
+		if (can_schedule) {
+			schedule_timeout_interruptible(1);
+			if (signal_pending(current))
+				break;
+		}
+	}
+}
+
+/*
+ * close all channels
+ */
+static void
+emu8k_close_dram(struct snd_emu8000 *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 const struct snd_pcm_hardware 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(struct snd_emu8k_pcm *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(struct timer_list *t)
+{
+	struct snd_emu8k_pcm *rec = from_timer(rec, t, timer);
+	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 */
+	mod_timer(&rec->timer, jiffies + 1);
+
+	/* 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(struct snd_pcm_substream *subs)
+{
+	struct snd_emu8000 *emu = snd_pcm_substream_chip(subs);
+	struct snd_emu8k_pcm *rec;
+	struct snd_pcm_runtime *runtime = subs->runtime;
+
+	rec = kzalloc(sizeof(*rec), GFP_KERNEL);
+	if (! rec)
+		return -ENOMEM;
+
+	rec->emu = emu;
+	rec->substream = subs;
+	runtime->private_data = rec;
+
+	spin_lock_init(&rec->timer_lock);
+	timer_setup(&rec->timer, emu8k_pcm_timer_func, 0);
+
+	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(struct snd_pcm_substream *subs)
+{
+	struct snd_emu8k_pcm *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(struct snd_emu8k_pcm *rec, int ch)
+{
+	struct snd_emu8000 *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(struct snd_emu8k_pcm *rec, int ch)
+{
+	unsigned long flags;
+	struct snd_emu8000 *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) {
+		mod_timer(&rec->timer, jiffies + 1);
+		rec->timer_running = 1;
+	}
+	spin_unlock_irqrestore(&rec->timer_lock, flags);
+}
+
+/*
+ * stop the voice immediately
+ */
+static void stop_voice(struct snd_emu8k_pcm *rec, int ch)
+{
+	unsigned long flags;
+	struct snd_emu8000 *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(struct snd_pcm_substream *subs, int cmd)
+{
+	struct snd_emu8k_pcm *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)
+
+enum {
+	COPY_USER, COPY_KERNEL, FILL_SILENCE,
+};
+
+#define GET_VAL(sval, buf, mode)					\
+	do {								\
+		switch (mode) {						\
+		case FILL_SILENCE:					\
+			sval = 0;					\
+			break;						\
+		case COPY_KERNEL:					\
+			sval = *buf++;					\
+			break;						\
+		default:						\
+			if (get_user(sval, (unsigned short __user *)buf)) \
+				return -EFAULT;				\
+			buf++;						\
+			break;						\
+		}							\
+	} while (0)
+
+#ifdef USE_NONINTERLEAVE
+
+#define LOOP_WRITE(rec, offset, _buf, count, mode)		\
+	do {							\
+		struct snd_emu8000 *emu = (rec)->emu;		\
+		unsigned short *buf = (unsigned short *)(_buf); \
+		snd_emu8000_write_wait(emu, 1);			\
+		EMU8000_SMALW_WRITE(emu, offset);		\
+		while (count > 0) {				\
+			unsigned short sval;			\
+			CHECK_SCHEDULER();			\
+			GET_VAL(sval, buf, mode);		\
+			EMU8000_SMLD_WRITE(emu, sval);		\
+			count--;				\
+		}						\
+	} while (0)
+
+/* copy one channel block */
+static int emu8k_pcm_copy(struct snd_pcm_substream *subs,
+			  int voice, unsigned long pos,
+			  void __user *src, unsigned long count)
+{
+	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+
+	/* convert to word unit */
+	pos = (pos << 1) + rec->loop_start[voice];
+	count <<= 1;
+	LOOP_WRITE(rec, pos, src, count, COPY_USER);
+	return 0;
+}
+
+static int emu8k_pcm_copy_kernel(struct snd_pcm_substream *subs,
+				 int voice, unsigned long pos,
+				 void *src, unsigned long count)
+{
+	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+
+	/* convert to word unit */
+	pos = (pos << 1) + rec->loop_start[voice];
+	count <<= 1;
+	LOOP_WRITE(rec, pos, src, count, COPY_KERNEL);
+	return 0;
+}
+
+/* make a channel block silence */
+static int emu8k_pcm_silence(struct snd_pcm_substream *subs,
+			     int voice, unsigned long pos, unsigned long count)
+{
+	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+
+	/* convert to word unit */
+	pos = (pos << 1) + rec->loop_start[voice];
+	count <<= 1;
+	LOOP_WRITE(rec, pos, NULL, count, FILL_SILENCE);
+	return 0;
+}
+
+#else /* interleave */
+
+#define LOOP_WRITE(rec, pos, _buf, count, mode)				\
+	do {								\
+		struct snd_emu8000 *emu = rec->emu;			\
+		unsigned short *buf = (unsigned short *)(_buf);		\
+		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_VAL(sval, buf, mode);			\
+			EMU8000_SMLD_WRITE(emu, sval);			\
+			if (rec->voices > 1) {				\
+				CHECK_SCHEDULER();			\
+				GET_VAL(sval, buf, mode);		\
+				EMU8000_SMRD_WRITE(emu, sval);		\
+			}						\
+			count--;					\
+		}							\
+	} while (0)
+
+
+/*
+ * copy the interleaved data can be done easily by using
+ * DMA "left" and "right" channels on emu8k engine.
+ */
+static int emu8k_pcm_copy(struct snd_pcm_substream *subs,
+			  int voice, unsigned long pos,
+			  void __user *src, unsigned long count)
+{
+	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+
+	/* convert to frames */
+	pos = bytes_to_frames(subs->runtime, pos);
+	count = bytes_to_frames(subs->runtime, count);
+	LOOP_WRITE(rec, pos, src, count, COPY_USER);
+	return 0;
+}
+
+static int emu8k_pcm_copy_kernel(struct snd_pcm_substream *subs,
+				 int voice, unsigned long pos,
+				 void *src, unsigned long count)
+{
+	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+
+	/* convert to frames */
+	pos = bytes_to_frames(subs->runtime, pos);
+	count = bytes_to_frames(subs->runtime, count);
+	LOOP_WRITE(rec, pos, src, count, COPY_KERNEL);
+	return 0;
+}
+
+static int emu8k_pcm_silence(struct snd_pcm_substream *subs,
+			     int voice, unsigned long pos, unsigned long count)
+{
+	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+
+	/* convert to frames */
+	pos = bytes_to_frames(subs->runtime, pos);
+	count = bytes_to_frames(subs->runtime, count);
+	LOOP_WRITE(rec, pos, NULL, count, FILL_SILENCE);
+	return 0;
+}
+#endif
+
+
+/*
+ * allocate a memory block
+ */
+static int emu8k_pcm_hw_params(struct snd_pcm_substream *subs,
+			       struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_emu8k_pcm *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(struct snd_pcm_substream *subs)
+{
+	struct snd_emu8k_pcm *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(struct snd_pcm_substream *subs)
+{
+	struct snd_emu8k_pcm *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(struct snd_pcm_substream *subs)
+{
+	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+	if (rec->running)
+		return emu8k_get_curpos(rec, 0);
+	return 0;
+}
+
+
+static const struct snd_pcm_ops 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_user =	emu8k_pcm_copy,
+	.copy_kernel =	emu8k_pcm_copy_kernel,
+	.fill_silence =	emu8k_pcm_silence,
+};
+
+
+static void snd_emu8000_pcm_free(struct snd_pcm *pcm)
+{
+	struct snd_emu8000 *emu = pcm->private_data;
+	emu->pcm = NULL;
+}
+
+int snd_emu8000_pcm_new(struct snd_card *card, struct snd_emu8000 *emu, int index)
+{
+	struct snd_pcm *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..4aa719c
--- /dev/null
+++ b/sound/isa/sb/emu8000_synth.c
@@ -0,0 +1,129 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/module.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_probe(struct device *_dev)
+{
+	struct snd_seq_device *dev = to_seq_dev(_dev);
+	struct snd_emu8000 *hw;
+	struct snd_emux *emu;
+
+	hw = *(struct snd_emu8000**)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(KERN_ERR "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_remove(struct device *_dev)
+{
+	struct snd_seq_device *dev = to_seq_dev(_dev);
+	struct snd_emu8000 *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);
+	snd_emux_free(hw->emu);
+	snd_util_memhdr_free(hw->memhdr);
+	hw->emu = NULL;
+	hw->memhdr = NULL;
+	return 0;
+}
+
+/*
+ *  INIT part
+ */
+
+static struct snd_seq_driver emu8000_driver = {
+	.driver = {
+		.name = KBUILD_MODNAME,
+		.probe = snd_emu8000_probe,
+		.remove = snd_emu8000_remove,
+	},
+	.id = SNDRV_SEQ_DEV_ID_EMU8000,
+	.argsize = sizeof(struct snd_emu8000 *),
+};
+
+module_snd_seq_driver(emu8000_driver);
diff --git a/sound/isa/sb/jazz16.c b/sound/isa/sb/jazz16.c
new file mode 100644
index 0000000..bfa0055
--- /dev/null
+++ b/sound/isa/sb/jazz16.c
@@ -0,0 +1,390 @@
+
+/*
+ * jazz16.c - driver for Media Vision Jazz16 based soundcards.
+ * Copyright (C) 2009 Krzysztof Helt <krzysztof.h1@wp.pl>
+ * Based on patches posted by Rask Ingemann Lambertsen and Rene Herman.
+ * Based on OSS Sound Blaster driver.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file COPYING in the main directory of this archive for
+ * more details.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <asm/dma.h>
+#include <linux/isa.h>
+#include <sound/core.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/sb.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+#define PFX "jazz16: "
+
+MODULE_DESCRIPTION("Media Vision Jazz16");
+MODULE_SUPPORTED_DEVICE("{{Media Vision ??? },"
+		"{RTL,RTL3000}}");
+
+MODULE_AUTHOR("Krzysztof Helt <krzysztof.h1@wp.pl>");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
+static unsigned long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static unsigned long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static int dma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Media Vision Jazz16 based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Media Vision Jazz16 based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Media Vision Jazz16 based soundcard.");
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for jazz16 driver.");
+module_param_hw_array(mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for jazz16 driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for jazz16 driver.");
+module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for jazz16 driver.");
+module_param_hw_array(dma8, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma8, "DMA8 # for jazz16 driver.");
+module_param_hw_array(dma16, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma16, "DMA16 # for jazz16 driver.");
+
+#define SB_JAZZ16_WAKEUP	0xaf
+#define SB_JAZZ16_SET_PORTS	0x50
+#define SB_DSP_GET_JAZZ_BRD_REV	0xfa
+#define SB_JAZZ16_SET_DMAINTR	0xfb
+#define SB_DSP_GET_JAZZ_MODEL	0xfe
+
+struct snd_card_jazz16 {
+	struct snd_sb *chip;
+};
+
+static irqreturn_t jazz16_interrupt(int irq, void *chip)
+{
+	return snd_sb8dsp_interrupt(chip);
+}
+
+static int jazz16_configure_ports(unsigned long port,
+				  unsigned long mpu_port, int idx)
+{
+	unsigned char val;
+
+	if (!request_region(0x201, 1, "jazz16 config")) {
+		snd_printk(KERN_ERR "config port region is already in use.\n");
+		return -EBUSY;
+	}
+	outb(SB_JAZZ16_WAKEUP - idx, 0x201);
+	udelay(100);
+	outb(SB_JAZZ16_SET_PORTS + idx, 0x201);
+	udelay(100);
+	val = port & 0x70;
+	val |= (mpu_port & 0x30) >> 4;
+	outb(val, 0x201);
+
+	release_region(0x201, 1);
+	return 0;
+}
+
+static int jazz16_detect_board(unsigned long port,
+			       unsigned long mpu_port)
+{
+	int err;
+	int val;
+	struct snd_sb chip;
+
+	if (!request_region(port, 0x10, "jazz16")) {
+		snd_printk(KERN_ERR "I/O port region is already in use.\n");
+		return -EBUSY;
+	}
+	/* just to call snd_sbdsp_command/reset/get_byte() */
+	chip.port = port;
+
+	err = snd_sbdsp_reset(&chip);
+	if (err < 0)
+		for (val = 0; val < 4; val++) {
+			err = jazz16_configure_ports(port, mpu_port, val);
+			if (err < 0)
+				break;
+
+			err = snd_sbdsp_reset(&chip);
+			if (!err)
+				break;
+		}
+	if (err < 0) {
+		err = -ENODEV;
+		goto err_unmap;
+	}
+	if (!snd_sbdsp_command(&chip, SB_DSP_GET_JAZZ_BRD_REV)) {
+		err = -EBUSY;
+		goto err_unmap;
+	}
+	val = snd_sbdsp_get_byte(&chip);
+	if (val >= 0x30)
+		snd_sbdsp_get_byte(&chip);
+
+	if ((val & 0xf0) != 0x10) {
+		err = -ENODEV;
+		goto err_unmap;
+	}
+	if (!snd_sbdsp_command(&chip, SB_DSP_GET_JAZZ_MODEL)) {
+		err = -EBUSY;
+		goto err_unmap;
+	}
+	snd_sbdsp_get_byte(&chip);
+	err = snd_sbdsp_get_byte(&chip);
+	snd_printd("Media Vision Jazz16 board detected: rev 0x%x, model 0x%x\n",
+		   val, err);
+
+	err = 0;
+
+err_unmap:
+	release_region(port, 0x10);
+	return err;
+}
+
+static int jazz16_configure_board(struct snd_sb *chip, int mpu_irq)
+{
+	static unsigned char jazz_irq_bits[] = { 0, 0, 2, 3, 0, 1, 0, 4,
+						 0, 2, 5, 0, 0, 0, 0, 6 };
+	static unsigned char jazz_dma_bits[] = { 0, 1, 0, 2, 0, 3, 0, 4 };
+
+	if (jazz_dma_bits[chip->dma8] == 0 ||
+	    jazz_dma_bits[chip->dma16] == 0 ||
+	    jazz_irq_bits[chip->irq] == 0)
+		return -EINVAL;
+
+	if (!snd_sbdsp_command(chip, SB_JAZZ16_SET_DMAINTR))
+		return -EBUSY;
+
+	if (!snd_sbdsp_command(chip,
+			       jazz_dma_bits[chip->dma8] |
+			       (jazz_dma_bits[chip->dma16] << 4)))
+		return -EBUSY;
+
+	if (!snd_sbdsp_command(chip,
+			       jazz_irq_bits[chip->irq] |
+			       (jazz_irq_bits[mpu_irq] << 4)))
+		return -EBUSY;
+
+	return 0;
+}
+
+static int snd_jazz16_match(struct device *devptr, unsigned int dev)
+{
+	if (!enable[dev])
+		return 0;
+	if (port[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR "please specify port\n");
+		return 0;
+	} else if (port[dev] == 0x200 || (port[dev] & ~0x270)) {
+		snd_printk(KERN_ERR "incorrect port specified\n");
+		return 0;
+	}
+	if (dma8[dev] != SNDRV_AUTO_DMA &&
+	    dma8[dev] != 1 && dma8[dev] != 3) {
+		snd_printk(KERN_ERR "dma8 must be 1 or 3\n");
+		return 0;
+	}
+	if (dma16[dev] != SNDRV_AUTO_DMA &&
+	    dma16[dev] != 5 && dma16[dev] != 7) {
+		snd_printk(KERN_ERR "dma16 must be 5 or 7\n");
+		return 0;
+	}
+	if (mpu_port[dev] != SNDRV_AUTO_PORT &&
+	    (mpu_port[dev] & ~0x030) != 0x300) {
+		snd_printk(KERN_ERR "incorrect mpu_port specified\n");
+		return 0;
+	}
+	if (mpu_irq[dev] != SNDRV_AUTO_DMA &&
+	    mpu_irq[dev] != 2 && mpu_irq[dev] != 3 &&
+	    mpu_irq[dev] != 5 && mpu_irq[dev] != 7) {
+		snd_printk(KERN_ERR "mpu_irq must be 2, 3, 5 or 7\n");
+		return 0;
+	}
+	return 1;
+}
+
+static int snd_jazz16_probe(struct device *devptr, unsigned int dev)
+{
+	struct snd_card *card;
+	struct snd_card_jazz16 *jazz16;
+	struct snd_sb *chip;
+	struct snd_opl3 *opl3;
+	static int possible_irqs[] = {2, 3, 5, 7, 9, 10, 15, -1};
+	static int possible_dmas8[] = {1, 3, -1};
+	static int possible_dmas16[] = {5, 7, -1};
+	int err, xirq, xdma8, xdma16, xmpu_port, xmpu_irq;
+
+	err = snd_card_new(devptr, index[dev], id[dev], THIS_MODULE,
+			   sizeof(struct snd_card_jazz16), &card);
+	if (err < 0)
+		return err;
+
+	jazz16 = card->private_data;
+
+	xirq = irq[dev];
+	if (xirq == SNDRV_AUTO_IRQ) {
+		xirq = snd_legacy_find_free_irq(possible_irqs);
+		if (xirq < 0) {
+			snd_printk(KERN_ERR "unable to find a free IRQ\n");
+			err = -EBUSY;
+			goto err_free;
+		}
+	}
+	xdma8 = dma8[dev];
+	if (xdma8 == SNDRV_AUTO_DMA) {
+		xdma8 = snd_legacy_find_free_dma(possible_dmas8);
+		if (xdma8 < 0) {
+			snd_printk(KERN_ERR "unable to find a free DMA8\n");
+			err = -EBUSY;
+			goto err_free;
+		}
+	}
+	xdma16 = dma16[dev];
+	if (xdma16 == SNDRV_AUTO_DMA) {
+		xdma16 = snd_legacy_find_free_dma(possible_dmas16);
+		if (xdma16 < 0) {
+			snd_printk(KERN_ERR "unable to find a free DMA16\n");
+			err = -EBUSY;
+			goto err_free;
+		}
+	}
+
+	xmpu_port = mpu_port[dev];
+	if (xmpu_port == SNDRV_AUTO_PORT)
+		xmpu_port = 0;
+	err = jazz16_detect_board(port[dev], xmpu_port);
+	if (err < 0) {
+		printk(KERN_ERR "Media Vision Jazz16 board not detected\n");
+		goto err_free;
+	}
+	err = snd_sbdsp_create(card, port[dev], irq[dev],
+			       jazz16_interrupt,
+			       dma8[dev], dma16[dev],
+			       SB_HW_JAZZ16,
+			       &chip);
+	if (err < 0)
+		goto err_free;
+
+	xmpu_irq = mpu_irq[dev];
+	if (xmpu_irq == SNDRV_AUTO_IRQ || mpu_port[dev] == SNDRV_AUTO_PORT)
+		xmpu_irq = 0;
+	err = jazz16_configure_board(chip, xmpu_irq);
+	if (err < 0) {
+		printk(KERN_ERR "Media Vision Jazz16 configuration failed\n");
+		goto err_free;
+	}
+
+	jazz16->chip = chip;
+
+	strcpy(card->driver, "jazz16");
+	strcpy(card->shortname, "Media Vision Jazz16");
+	sprintf(card->longname,
+		"Media Vision Jazz16 at 0x%lx, irq %d, dma8 %d, dma16 %d",
+		port[dev], xirq, xdma8, xdma16);
+
+	err = snd_sb8dsp_pcm(chip, 0);
+	if (err < 0)
+		goto err_free;
+	err = snd_sbmixer_new(chip);
+	if (err < 0)
+		goto err_free;
+
+	err = snd_opl3_create(card, chip->port, chip->port + 2,
+			      OPL3_HW_AUTO, 1, &opl3);
+	if (err < 0)
+		snd_printk(KERN_WARNING "no OPL device at 0x%lx-0x%lx\n",
+			   chip->port, chip->port + 2);
+	else {
+		err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+		if (err < 0)
+			goto err_free;
+	}
+	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_MPU401,
+					mpu_port[dev], 0,
+					mpu_irq[dev],
+					NULL) < 0)
+			snd_printk(KERN_ERR "no MPU-401 device at 0x%lx\n",
+					mpu_port[dev]);
+	}
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto err_free;
+
+	dev_set_drvdata(devptr, card);
+	return 0;
+
+err_free:
+	snd_card_free(card);
+	return err;
+}
+
+static int snd_jazz16_remove(struct device *devptr, unsigned int dev)
+{
+	struct snd_card *card = dev_get_drvdata(devptr);
+
+	snd_card_free(card);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_jazz16_suspend(struct device *pdev, unsigned int n,
+			       pm_message_t state)
+{
+	struct snd_card *card = dev_get_drvdata(pdev);
+	struct snd_card_jazz16 *acard = card->private_data;
+	struct snd_sb *chip = acard->chip;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	snd_sbmixer_suspend(chip);
+	return 0;
+}
+
+static int snd_jazz16_resume(struct device *pdev, unsigned int n)
+{
+	struct snd_card *card = dev_get_drvdata(pdev);
+	struct snd_card_jazz16 *acard = card->private_data;
+	struct snd_sb *chip = acard->chip;
+
+	snd_sbdsp_reset(chip);
+	snd_sbmixer_resume(chip);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+static struct isa_driver snd_jazz16_driver = {
+	.match		= snd_jazz16_match,
+	.probe		= snd_jazz16_probe,
+	.remove		= snd_jazz16_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_jazz16_suspend,
+	.resume		= snd_jazz16_resume,
+#endif
+	.driver		= {
+		.name	= "jazz16"
+	},
+};
+
+module_isa_driver(snd_jazz16_driver, SNDRV_CARDS);
diff --git a/sound/isa/sb/sb16.c b/sound/isa/sb/sb16.c
new file mode 100644
index 0000000..8f9ebeb
--- /dev/null
+++ b/sound/isa/sb/sb16.c
@@ -0,0 +1,697 @@
+/*
+ *  Driver for SoundBlaster 16/AWE32/AWE64 soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <asm/dma.h>
+#include <linux/init.h>
+#include <linux/pnp.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/module.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_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@perex.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) && IS_ENABLED(CONFIG_SND_SEQUENCER)
+#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 bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static bool 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];
+#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_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for SB16 driver.");
+module_param_hw_array(mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for SB16 driver.");
+module_param_hw_array(fm_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for SB16 PnP driver.");
+#ifdef SNDRV_SBAWE_EMU8000
+module_param_hw_array(awe_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(awe_port, "AWE port # for SB16 PnP driver.");
+#endif
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for SB16 driver.");
+module_param_hw_array(dma8, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for SB16 driver.");
+module_param_hw_array(dma16, int, dma, 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
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+#endif
+
+struct snd_card_sb16 {
+	struct resource *fm_res;	/* used to block FM i/o region for legacy cards */
+	struct snd_sb *chip;
+#ifdef CONFIG_PNP
+	int dev_no;
+	struct pnp_dev *dev;
+#ifdef SNDRV_SBAWE_EMU8000
+	struct pnp_dev *devwt;
+#endif
+#endif
+};
+
+#ifdef CONFIG_PNP
+
+static const 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" } } },
+	/* Sound Blaster 16 (Virtual PC 2004) */
+	{ .id = "tBA03b0", .devs = { {.id="PNPb003" } } },
+#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 */
+	{ .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 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;
+	int err;
+
+	acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->dev == NULL)
+		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;
+
+	err = pnp_activate_dev(pdev); 
+	if (err < 0) { 
+		snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n"); 
+		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) {
+		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%llx\n",
+				(unsigned long long)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
+	return 0;
+}
+
+#endif /* CONFIG_PNP */
+
+static void snd_sb16_free(struct snd_card *card)
+{
+	struct snd_card_sb16 *acard = card->private_data;
+        
+	if (acard == NULL)
+		return;
+	release_and_free_resource(acard->fm_res);
+}
+
+#ifdef CONFIG_PNP
+#define is_isapnp_selected(dev)		isapnp[dev]
+#else
+#define is_isapnp_selected(dev)		0
+#endif
+
+static int snd_sb16_card_new(struct device *devptr, int dev,
+			     struct snd_card **cardp)
+{
+	struct snd_card *card;
+	int err;
+
+	err = snd_card_new(devptr, index[dev], id[dev], THIS_MODULE,
+			   sizeof(struct snd_card_sb16), &card);
+	if (err < 0)
+		return err;
+	card->private_free = snd_sb16_free;
+	*cardp = card;
+	return 0;
+}
+
+static int snd_sb16_probe(struct snd_card *card, int dev)
+{
+	int xirq, xdma8, xdma16;
+	struct snd_sb *chip;
+	struct snd_card_sb16 *acard = card->private_data;
+	struct snd_opl3 *opl3;
+	struct snd_hwdep *synth = NULL;
+#ifdef CONFIG_SND_SB16_CSP
+	struct snd_hwdep *xcsp = NULL;
+#endif
+	unsigned long flags;
+	int err;
+
+	xirq = irq[dev];
+	xdma8 = dma8[dev];
+	xdma16 = dma16[dev];
+
+	if ((err = snd_sbdsp_create(card,
+				    port[dev],
+				    xirq,
+				    snd_sb16dsp_interrupt,
+				    xdma8,
+				    xdma16,
+				    SB_HW_AUTO,
+				    &chip)) < 0)
+		return err;
+
+	acard->chip = chip;
+	if (chip->hardware != SB_HW_16) {
+		snd_printk(KERN_ERR PFX "SB 16 chip was not detected at 0x%lx\n", port[dev]);
+		return -ENODEV;
+	}
+	chip->mpu_port = mpu_port[dev];
+	if (! is_isapnp_selected(dev) && (err = snd_sb16dsp_configure(chip)) < 0)
+		return err;
+
+	if ((err = snd_sb16dsp_pcm(chip, 0)) < 0)
+		return err;
+
+	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,
+					       MPU401_INFO_IRQ_HOOK, -1,
+					       &chip->rmidi)) < 0)
+			return err;
+		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)
+				return err;
+		}
+	}
+
+	if ((err = snd_sbmixer_new(chip)) < 0)
+		return err;
+
+#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 ((err = 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]);
+
+			return err;
+		}
+	}
+#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)
+		return err;
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_sb16_suspend(struct snd_card *card, pm_message_t state)
+{
+	struct snd_card_sb16 *acard = card->private_data;
+	struct snd_sb *chip = acard->chip;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	snd_sbmixer_suspend(chip);
+	return 0;
+}
+
+static int snd_sb16_resume(struct snd_card *card)
+{
+	struct snd_card_sb16 *acard = card->private_data;
+	struct snd_sb *chip = acard->chip;
+
+	snd_sbdsp_reset(chip);
+	snd_sbmixer_resume(chip);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+static int snd_sb16_isa_probe1(int dev, struct device *pdev)
+{
+	struct snd_card_sb16 *acard;
+	struct snd_card *card;
+	int err;
+
+	err = snd_sb16_card_new(pdev, dev, &card);
+	if (err < 0)
+		return err;
+
+	acard = card->private_data;
+	/* 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
+
+	if ((err = snd_sb16_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	dev_set_drvdata(pdev, card);
+	return 0;
+}
+
+
+static int snd_sb16_isa_match(struct device *pdev, unsigned int dev)
+{
+	return enable[dev] && !is_isapnp_selected(dev);
+}
+
+static int snd_sb16_isa_probe(struct device *pdev, unsigned int dev)
+{
+	int err;
+	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};
+
+	if (irq[dev] == SNDRV_AUTO_IRQ) {
+		if ((irq[dev] = snd_legacy_find_free_irq(possible_irqs)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (dma8[dev] == SNDRV_AUTO_DMA) {
+		if ((dma8[dev] = snd_legacy_find_free_dma(possible_dmas8)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free 8-bit DMA\n");
+			return -EBUSY;
+		}
+	}
+	if (dma16[dev] == SNDRV_AUTO_DMA) {
+		if ((dma16[dev] = snd_legacy_find_free_dma(possible_dmas16)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free 16-bit DMA\n");
+			return -EBUSY;
+		}
+	}
+
+	if (port[dev] != SNDRV_AUTO_PORT)
+		return snd_sb16_isa_probe1(dev, pdev);
+	else {
+		static int possible_ports[] = {0x220, 0x240, 0x260, 0x280};
+		int i;
+		for (i = 0; i < ARRAY_SIZE(possible_ports); i++) {
+			port[dev] = possible_ports[i];
+			err = snd_sb16_isa_probe1(dev, pdev);
+			if (! err)
+				return 0;
+		}
+		return err;
+	}
+}
+
+static int snd_sb16_isa_remove(struct device *pdev, unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(pdev));
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_sb16_isa_suspend(struct device *dev, unsigned int n,
+				pm_message_t state)
+{
+	return snd_sb16_suspend(dev_get_drvdata(dev), state);
+}
+
+static int snd_sb16_isa_resume(struct device *dev, unsigned int n)
+{
+	return snd_sb16_resume(dev_get_drvdata(dev));
+}
+#endif
+
+#ifdef SNDRV_SBAWE
+#define DEV_NAME "sbawe"
+#else
+#define DEV_NAME "sb16"
+#endif
+
+static struct isa_driver snd_sb16_isa_driver = {
+	.match		= snd_sb16_isa_match,
+	.probe		= snd_sb16_isa_probe,
+	.remove		= snd_sb16_isa_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_sb16_isa_suspend,
+	.resume		= snd_sb16_isa_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+
+#ifdef CONFIG_PNP
+static int snd_sb16_pnp_detect(struct pnp_card_link *pcard,
+			       const struct pnp_card_device_id *pid)
+{
+	static int dev;
+	struct snd_card *card;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || !isapnp[dev])
+			continue;
+		res = snd_sb16_card_new(&pcard->card->dev, dev, &card);
+		if (res < 0)
+			return res;
+		if ((res = snd_card_sb16_pnp(dev, card->private_data, pcard, pid)) < 0 ||
+		    (res = snd_sb16_probe(card, dev)) < 0) {
+			snd_card_free(card);
+			return res;
+		}
+		pnp_set_card_drvdata(pcard, card);
+		dev++;
+		return 0;
+	}
+
+	return -ENODEV;
+}
+
+static void snd_sb16_pnp_remove(struct pnp_card_link *pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_sb16_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+	return snd_sb16_suspend(pnp_get_card_drvdata(pcard), state);
+}
+static int snd_sb16_pnp_resume(struct pnp_card_link *pcard)
+{
+	return snd_sb16_resume(pnp_get_card_drvdata(pcard));
+}
+#endif
+
+static struct pnp_card_driver sb16_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DISABLE,
+#ifdef SNDRV_SBAWE
+	.name = "sbawe",
+#else
+	.name = "sb16",
+#endif
+	.id_table = snd_sb16_pnpids,
+	.probe = snd_sb16_pnp_detect,
+	.remove = snd_sb16_pnp_remove,
+#ifdef CONFIG_PM
+	.suspend = snd_sb16_pnp_suspend,
+	.resume = snd_sb16_pnp_resume,
+#endif
+};
+
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_sb16_init(void)
+{
+	int err;
+
+	err = isa_register_driver(&snd_sb16_isa_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+	if (!err)
+		isa_registered = 1;
+
+	err = pnp_register_card_driver(&sb16_pnpc_driver);
+	if (!err)
+		pnp_registered = 1;
+
+	if (isa_registered)
+		err = 0;
+#endif
+	return err;
+}
+
+static void __exit alsa_card_sb16_exit(void)
+{
+#ifdef CONFIG_PNP
+	if (pnp_registered)
+		pnp_unregister_card_driver(&sb16_pnpc_driver);
+	if (isa_registered)
+#endif
+		isa_unregister_driver(&snd_sb16_isa_driver);
+}
+
+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..bf3db0d
--- /dev/null
+++ b/sound/isa/sb/sb16_csp.c
@@ -0,0 +1,1187 @@
+/*
+ *  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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.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");
+MODULE_FIRMWARE("sb16/mulaw_main.csp");
+MODULE_FIRMWARE("sb16/alaw_main.csp");
+MODULE_FIRMWARE("sb16/ima_adpcm_init.csp");
+MODULE_FIRMWARE("sb16/ima_adpcm_playback.csp");
+MODULE_FIRMWARE("sb16/ima_adpcm_capture.csp");
+
+#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 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
+ */
+struct riff_header {
+	__le32 name;
+	__le32 len;
+};
+
+struct desc_header {
+	struct riff_header info;
+	__le16 func_nr;
+	__le16 VOC_type;
+	__le16 flags_play_rec;
+	__le16 flags_16bit_8bit;
+	__le16 flags_stereo_mono;
+	__le16 flags_rates;
+};
+
+/*
+ * prototypes
+ */
+static void snd_sb_csp_free(struct snd_hwdep *hw);
+static int snd_sb_csp_open(struct snd_hwdep * hw, struct file *file);
+static int snd_sb_csp_ioctl(struct snd_hwdep * hw, struct file *file, unsigned int cmd, unsigned long arg);
+static int snd_sb_csp_release(struct snd_hwdep * hw, struct file *file);
+
+static int csp_detect(struct snd_sb *chip, int *version);
+static int set_codec_parameter(struct snd_sb *chip, unsigned char par, unsigned char val);
+static int set_register(struct snd_sb *chip, unsigned char reg, unsigned char val);
+static int read_register(struct snd_sb *chip, unsigned char reg);
+static int set_mode_register(struct snd_sb *chip, unsigned char mode);
+static int get_version(struct snd_sb *chip);
+
+static int snd_sb_csp_riff_load(struct snd_sb_csp * p,
+				struct snd_sb_csp_microcode __user * code);
+static int snd_sb_csp_unload(struct snd_sb_csp * p);
+static int snd_sb_csp_load_user(struct snd_sb_csp * p, const unsigned char __user *buf, int size, int load_flags);
+static int snd_sb_csp_autoload(struct snd_sb_csp * p, snd_pcm_format_t pcm_sfmt, int play_rec_mode);
+static int snd_sb_csp_check_version(struct snd_sb_csp * p);
+
+static int snd_sb_csp_use(struct snd_sb_csp * p);
+static int snd_sb_csp_unuse(struct snd_sb_csp * p);
+static int snd_sb_csp_start(struct snd_sb_csp * p, int sample_width, int channels);
+static int snd_sb_csp_stop(struct snd_sb_csp * p);
+static int snd_sb_csp_pause(struct snd_sb_csp * p);
+static int snd_sb_csp_restart(struct snd_sb_csp * p);
+
+static int snd_sb_qsound_build(struct snd_sb_csp * p);
+static void snd_sb_qsound_destroy(struct snd_sb_csp * p);
+static int snd_sb_csp_qsound_transfer(struct snd_sb_csp * p);
+
+static int init_proc_entry(struct snd_sb_csp * p, int device);
+static void info_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer);
+
+/*
+ * Detect CSP chip and create a new instance
+ */
+int snd_sb_csp_new(struct snd_sb *chip, int device, struct snd_hwdep ** rhwdep)
+{
+	struct snd_sb_csp *p;
+	int uninitialized_var(version);
+	int err;
+	struct snd_hwdep *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 = kzalloc(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;
+
+	mutex_init(&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(struct snd_hwdep *hwdep)
+{
+	int i;
+	struct snd_sb_csp *p = hwdep->private_data;
+	if (p) {
+		if (p->running & SNDRV_SB_CSP_ST_RUNNING)
+			snd_sb_csp_stop(p);
+		for (i = 0; i < ARRAY_SIZE(p->csp_programs); ++i)
+			release_firmware(p->csp_programs[i]);
+		kfree(p);
+	}
+}
+
+/* ------------------------------ */
+
+/*
+ * open the device exclusively
+ */
+static int snd_sb_csp_open(struct snd_hwdep * hw, struct file *file)
+{
+	struct snd_sb_csp *p = hw->private_data;
+	return (snd_sb_csp_use(p));
+}
+
+/*
+ * ioctl for hwdep device:
+ */
+static int snd_sb_csp_ioctl(struct snd_hwdep * hw, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct snd_sb_csp *p = hw->private_data;
+	struct snd_sb_csp_info info;
+	struct snd_sb_csp_start start_info;
+	int err;
+
+	if (snd_BUG_ON(!p))
+		return -EINVAL;
+
+	if (snd_sb_csp_check_version(p))
+		return -ENODEV;
+
+	switch (cmd) {
+		/* get information */
+	case SNDRV_SB_CSP_IOCTL_INFO:
+		memset(&info, 0, sizeof(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, (struct snd_sb_csp_microcode __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(struct snd_hwdep * hw, struct file *file)
+{
+	struct snd_sb_csp *p = hw->private_data;
+	return (snd_sb_csp_unuse(p));
+}
+
+/* ------------------------------ */
+
+/*
+ * acquire device
+ */
+static int snd_sb_csp_use(struct snd_sb_csp * p)
+{
+	mutex_lock(&p->access_mutex);
+	if (p->used) {
+		mutex_unlock(&p->access_mutex);
+		return -EAGAIN;
+	}
+	p->used++;
+	mutex_unlock(&p->access_mutex);
+
+	return 0;
+
+}
+
+/*
+ * release device
+ */
+static int snd_sb_csp_unuse(struct snd_sb_csp * p)
+{
+	mutex_lock(&p->access_mutex);
+	p->used--;
+	mutex_unlock(&p->access_mutex);
+
+	return 0;
+}
+
+/*
+ * load microcode via ioctl: 
+ * code is user-space pointer
+ */
+static int snd_sb_csp_riff_load(struct snd_sb_csp * p,
+				struct snd_sb_csp_microcode __user * mcode)
+{
+	struct snd_sb_csp_mc_header info;
+
+	unsigned char __user *data_ptr;
+	unsigned char __user *data_end;
+	unsigned short func_nr = 0;
+
+	struct riff_header file_h, item_h, code_h;
+	__le32 item_type;
+	struct desc_header 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 ((le32_to_cpu(file_h.name) != RIFF_HEADER) ||
+	    (le32_to_cpu(file_h.len) >= SNDRV_SB_CSP_MAX_MICROCODE_FILE_SIZE - sizeof(file_h))) {
+		snd_printd("%s: Invalid RIFF header\n", __func__);
+		return -EINVAL;
+	}
+	data_ptr += sizeof(file_h);
+	data_end = data_ptr + le32_to_cpu(file_h.len);
+
+	if (copy_from_user(&item_type, data_ptr, sizeof(item_type)))
+		return -EFAULT;
+	if (le32_to_cpu(item_type) != CSP__HEADER) {
+		snd_printd("%s: Invalid RIFF file type\n", __func__);
+		return -EINVAL;
+	}
+	data_ptr += sizeof (item_type);
+
+	for (; data_ptr < data_end; data_ptr += le32_to_cpu(item_h.len)) {
+		if (copy_from_user(&item_h, data_ptr, sizeof(item_h)))
+			return -EFAULT;
+		data_ptr += sizeof(item_h);
+		if (le32_to_cpu(item_h.name) != LIST_HEADER)
+			continue;
+
+		if (copy_from_user(&item_type, data_ptr, sizeof(item_type)))
+			 return -EFAULT;
+		switch (le32_to_cpu(item_type)) {
+		case FUNC_HEADER:
+			if (copy_from_user(&funcdesc_h, data_ptr + sizeof(item_type), sizeof(funcdesc_h)))
+				return -EFAULT;
+			func_nr = le16_to_cpu(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 (le32_to_cpu(code_h.name) != INIT_HEADER)
+					break;
+				data_ptr += sizeof(code_h);
+				err = snd_sb_csp_load_user(p, data_ptr, le32_to_cpu(code_h.len),
+						      SNDRV_SB_CSP_LOAD_INITBLOCK);
+				if (err)
+					return err;
+				data_ptr += le32_to_cpu(code_h.len);
+			}
+			/* main microcode block */
+			if (copy_from_user(&code_h, data_ptr, sizeof(code_h)))
+				return -EFAULT;
+
+			if (le32_to_cpu(code_h.name) != MAIN_HEADER) {
+				snd_printd("%s: Missing 'main' microcode\n", __func__);
+				return -EINVAL;
+			}
+			data_ptr += sizeof(code_h);
+			err = snd_sb_csp_load_user(p, data_ptr,
+						   le32_to_cpu(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 = le16_to_cpu(funcdesc_h.flags_play_rec);
+			switch (le16_to_cpu(funcdesc_h.VOC_type)) {
+			case 0x0001:	/* QSound decoder */
+				if (le16_to_cpu(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",
+					   __func__,
+					   le16_to_cpu(funcdesc_h.VOC_type));
+				return -EINVAL;
+			}
+			p->acc_channels = le16_to_cpu(funcdesc_h.flags_stereo_mono);
+			p->acc_width = le16_to_cpu(funcdesc_h.flags_16bit_8bit);
+			p->acc_rates = le16_to_cpu(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", __func__, info.func_req);
+	return -EINVAL;
+}
+
+/*
+ * unload CSP microcode
+ */
+static int snd_sb_csp_unload(struct snd_sb_csp * 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(struct snd_sb *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(struct snd_sb *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(struct snd_sb *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(struct snd_sb *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(struct snd_sb *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(struct snd_sb *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(struct snd_sb *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(struct snd_sb_csp * p)
+{
+	if (p->version < 0x10 || p->version > 0x1f) {
+		snd_printd("%s: Invalid CSP version: 0x%x\n", __func__, p->version);
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ * download microcode to CSP (microcode should have one "main" block).
+ */
+static int snd_sb_csp_load(struct snd_sb_csp * 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", __func__);
+		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", __func__);
+			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(struct snd_sb_csp * p, const unsigned char __user *buf, int size, int load_flags)
+{
+	int err;
+	unsigned char *kbuf;
+
+	kbuf = memdup_user(buf, size);
+	if (IS_ERR(kbuf))
+		return PTR_ERR(kbuf);
+
+	err = snd_sb_csp_load(p, kbuf, size, load_flags);
+
+	kfree(kbuf);
+	return err;
+}
+
+static int snd_sb_csp_firmware_load(struct snd_sb_csp *p, int index, int flags)
+{
+	static const char *const names[] = {
+		"sb16/mulaw_main.csp",
+		"sb16/alaw_main.csp",
+		"sb16/ima_adpcm_init.csp",
+		"sb16/ima_adpcm_playback.csp",
+		"sb16/ima_adpcm_capture.csp",
+	};
+	const struct firmware *program;
+
+	BUILD_BUG_ON(ARRAY_SIZE(names) != CSP_PROGRAM_COUNT);
+	program = p->csp_programs[index];
+	if (!program) {
+		int err = request_firmware(&program, names[index],
+				       p->chip->card->dev);
+		if (err < 0)
+			return err;
+		p->csp_programs[index] = program;
+	}
+	return snd_sb_csp_load(p, program->data, program->size, flags);
+}
+
+/*
+ * autoload hardware codec if necessary
+ * return 0 if CSP is loaded and ready to run (p->running != 0)
+ */
+static int snd_sb_csp_autoload(struct snd_sb_csp * p, snd_pcm_format_t 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 (((1U << (__force int)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_firmware_load(p, CSP_PROGRAM_MULAW, 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_firmware_load(p, CSP_PROGRAM_ALAW, 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_firmware_load(p, CSP_PROGRAM_ADPCM_INIT,
+						       SNDRV_SB_CSP_LOAD_INITBLOCK);
+			if (err)
+				break;
+			if (play_rec_mode == SNDRV_SB_CSP_MODE_DSP_WRITE) {
+				err = snd_sb_csp_firmware_load
+					(p, CSP_PROGRAM_ADPCM_PLAYBACK, 0);
+				p->mode = SNDRV_SB_CSP_MODE_DSP_WRITE;
+			} else {
+				err = snd_sb_csp_firmware_load
+					(p, CSP_PROGRAM_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(struct snd_sb_csp * 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", __func__);
+		return -ENXIO;
+	}
+	if (p->running & SNDRV_SB_CSP_ST_RUNNING) {
+		snd_printd("%s: CSP already running\n", __func__);
+		return -EBUSY;
+	}
+	if (!(sample_width & p->acc_width)) {
+		snd_printd("%s: Unsupported PCM sample width\n", __func__);
+		return -EINVAL;
+	}
+	if (!(channels & p->acc_channels)) {
+		snd_printd("%s: Invalid number of channels\n", __func__);
+		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", __func__);
+		goto __fail;
+	}
+	if (set_codec_parameter(p->chip, 0x80, 0x00)) {
+		snd_printd("%s: Codec start command failed\n", __func__);
+		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(struct snd_sb_csp * 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(struct snd_sb_csp * 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(struct snd_sb_csp * 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
+ */
+
+#define snd_sb_qsound_switch_info	snd_ctl_boolean_mono_info
+
+static int snd_sb_qsound_switch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb_csp *p = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = p->q_enabled ? 1 : 0;
+	return 0;
+}
+
+static int snd_sb_qsound_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb_csp *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb_csp *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb_csp *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 const struct snd_kcontrol_new 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 const struct snd_kcontrol_new 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(struct snd_sb_csp * p)
+{
+	struct snd_card *card;
+	int err;
+
+	if (snd_BUG_ON(!p))
+		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(struct snd_sb_csp * p)
+{
+	struct snd_card *card;
+	unsigned long flags;
+
+	if (snd_BUG_ON(!p))
+		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(struct snd_sb_csp * 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(struct snd_sb_csp * p, int device)
+{
+	char name[16];
+	struct snd_info_entry *entry;
+	sprintf(name, "cspD%d", device);
+	if (! snd_card_proc_new(p->chip->card, name, &entry))
+		snd_info_set_text_ops(entry, p, info_read);
+	return 0;
+}
+
+static void info_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct snd_sb_csp *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);
diff --git a/sound/isa/sb/sb16_main.c b/sound/isa/sb/sb16_main.c
new file mode 100644
index 0000000..37e6ce7
--- /dev/null
+++ b/sound/isa/sb/sb16_main.c
@@ -0,0 +1,902 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/io.h>
+#include <asm/dma.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/module.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@perex.cz>");
+MODULE_DESCRIPTION("Routines for control of 16-bit SoundBlaster cards and clones");
+MODULE_LICENSE("GPL");
+
+#define runtime_format_bits(runtime) \
+	((unsigned int)pcm_format_to_bits((runtime)->format))
+
+#ifdef CONFIG_SND_SB16_CSP
+static void snd_sb16_csp_playback_prepare(struct snd_sb *chip, struct snd_pcm_runtime *runtime)
+{
+	if (chip->hardware == SB_HW_16CSP) {
+		struct snd_sb_csp *csp = chip->csp;
+
+		if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
+			/* manually loaded codec */
+			if ((csp->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) &&
+			    (runtime_format_bits(runtime) == 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 (runtime_format_bits(runtime) & (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(struct snd_sb *chip, struct snd_pcm_runtime *runtime)
+{
+	if (chip->hardware == SB_HW_16CSP) {
+		struct snd_sb_csp *csp = chip->csp;
+
+		if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
+			/* manually loaded codec */
+			if ((csp->mode & SNDRV_SB_CSP_MODE_DSP_READ) &&
+			    (runtime_format_bits(runtime) == 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(struct snd_sb *chip)
+{
+	if (chip->hardware == SB_HW_16CSP) {
+		struct snd_sb_csp *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(struct snd_sb *chip, struct snd_pcm_runtime *runtime)
+{
+	/* CSP decoders (QSound excluded) support only 16bit transfers */
+	if (chip->hardware == SB_HW_16CSP) {
+		struct snd_sb_csp *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(struct snd_sb *chip)
+{
+	if ((chip->hardware == SB_HW_16CSP) && (chip->open == SNDRV_SB_CSP_MODE_DSP_WRITE)) {
+		struct snd_sb_csp *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(struct snd_sb *chip, struct snd_pcm_runtime *runtime)
+{
+	/* CSP coders support only 16bit transfers */
+	if (chip->hardware == SB_HW_16CSP) {
+		struct snd_sb_csp *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(struct snd_sb *chip)
+{
+	if ((chip->hardware == SB_HW_16CSP) && (chip->open == SNDRV_SB_CSP_MODE_DSP_READ)) {
+		struct snd_sb_csp *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(struct snd_sb *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(struct snd_pcm_substream *substream,
+			      struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_sb16_hw_free(struct snd_pcm_substream *substream)
+{
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int snd_sb16_playback_prepare(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *substream,
+				     int cmd)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	int result = 0;
+
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		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:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		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(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *substream,
+				    int cmd)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	int result = 0;
+
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		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:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		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 snd_sb *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);
+	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(struct snd_pcm_substream *substream)
+{
+	struct snd_sb *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(struct snd_pcm_substream *substream)
+{
+	struct snd_sb *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 const struct snd_pcm_hardware 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 const struct snd_pcm_hardware 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(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *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->hardware == SB_HW_CS5530) {
+		runtime->hw.buffer_bytes_max = 32 * 1024;
+		runtime->hw.periods_min = 2;
+		runtime->hw.rate_min = 44100;
+	}
+	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(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *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(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *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->hardware == SB_HW_CS5530) {
+		runtime->hw.buffer_bytes_max = 32 * 1024;
+		runtime->hw.periods_min = 2;
+		runtime->hw.rate_min = 44100;
+	}
+	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(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *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(struct snd_sb *chip, int what)
+{
+	if (chip->dma8 < 0 || chip->dma16 < 0) {
+		if (snd_BUG_ON(what))
+			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(struct snd_sb *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts[3] = {
+		"Auto", "Playback", "Capture"
+	};
+
+	return snd_ctl_enum_info(uinfo, 1, 3, texts);
+}
+
+static int snd_sb16_dma_control_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *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 const struct snd_kcontrol_new snd_sb16_dma_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
+	.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(struct snd_sb * 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(KERN_DEBUG "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(KERN_ERR "SB16 [0x%lx]: unable to set DMA & IRQ (PnP device?)\n", chip->port);
+		snd_printk(KERN_ERR "SB16 [0x%lx]: wanted: irqreg=0x%x, dmareg=0x%x, mpureg = 0x%x\n", chip->port, realirq, realdma, realmpureg);
+		snd_printk(KERN_ERR "SB16 [0x%lx]:    got: irqreg=0x%x, dmareg=0x%x, mpureg = 0x%x\n", chip->port, irqreg, dmareg, mpureg);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static const struct snd_pcm_ops 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 const struct snd_pcm_ops 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,
+};
+
+int snd_sb16dsp_pcm(struct snd_sb *chip, int device)
+{
+	struct snd_card *card = chip->card;
+	struct snd_pcm *pcm;
+	int err;
+
+	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;
+	chip->pcm = pcm;
+
+	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);
+	return 0;
+}
+
+const struct snd_pcm_ops *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);
diff --git a/sound/isa/sb/sb8.c b/sound/isa/sb/sb8.c
new file mode 100644
index 0000000..d77dcba
--- /dev/null
+++ b/sound/isa/sb/sb8.c
@@ -0,0 +1,254 @@
+/*
+ *  Driver for SoundBlaster 1.0/2.0/Pro soundcards and compatible
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.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 bool 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_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for SB8 driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for SB8 driver.");
+module_param_hw_array(dma8, int, dma, 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 */
+	struct snd_sb *chip;
+};
+
+static irqreturn_t snd_sb8_interrupt(int irq, void *dev_id)
+{
+	struct snd_sb *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(struct snd_card *card)
+{
+	struct snd_sb8 *acard = card->private_data;
+
+	if (acard == NULL)
+		return;
+	release_and_free_resource(acard->fm_res);
+}
+
+static int snd_sb8_match(struct device *pdev, unsigned int dev)
+{
+	if (!enable[dev])
+		return 0;
+	if (irq[dev] == SNDRV_AUTO_IRQ) {
+		dev_err(pdev, "please specify irq\n");
+		return 0;
+	}
+	if (dma8[dev] == SNDRV_AUTO_DMA) {
+		dev_err(pdev, "please specify dma8\n");
+		return 0;
+	}
+	return 1;
+}
+
+static int snd_sb8_probe(struct device *pdev, unsigned int dev)
+{
+	struct snd_sb *chip;
+	struct snd_card *card;
+	struct snd_sb8 *acard;
+	struct snd_opl3 *opl3;
+	int err;
+
+	err = snd_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+			   sizeof(struct snd_sb8), &card);
+	if (err < 0)
+		return err;
+	acard = 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 (port[dev] != SNDRV_AUTO_PORT) {
+		if ((err = snd_sbdsp_create(card, port[dev], irq[dev],
+					    snd_sb8_interrupt,
+					    dma8[dev],
+					    -1,
+					    SB_HW_AUTO,
+					    &chip)) < 0)
+			goto _err;
+	} else {
+		/* auto-probe legacy ports */
+		static unsigned long possible_ports[] = {
+			0x220, 0x240, 0x260,
+		};
+		int i;
+		for (i = 0; i < ARRAY_SIZE(possible_ports); i++) {
+			err = snd_sbdsp_create(card, possible_ports[i],
+					       irq[dev],
+					       snd_sb8_interrupt,
+					       dma8[dev],
+					       -1,
+					       SB_HW_AUTO,
+					       &chip);
+			if (err >= 0) {
+				port[dev] = possible_ports[i];
+				break;
+			}
+		}
+		if (i >= ARRAY_SIZE(possible_ports)) {
+			err = -EINVAL;
+			goto _err;
+		}
+	}
+	acard->chip = chip;
+			
+	if (chip->hardware >= SB_HW_16) {
+		if (chip->hardware == SB_HW_ALS100)
+			snd_printk(KERN_WARNING "ALS100 chip detected at 0x%lx, try snd-als100 module\n",
+				    port[dev]);
+		else
+			snd_printk(KERN_WARNING "SB 16 chip detected at 0x%lx, try snd-sb16 module\n",
+				   port[dev]);
+		err = -ENODEV;
+		goto _err;
+	}
+
+	if ((err = snd_sb8dsp_pcm(chip, 0)) < 0)
+		goto _err;
+
+	if ((err = snd_sbmixer_new(chip)) < 0)
+		goto _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_WARNING "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_WARNING "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)
+			goto _err;
+	}
+
+	if ((err = snd_sb8dsp_midi(chip, 0)) < 0)
+		goto _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)
+		goto _err;
+
+	dev_set_drvdata(pdev, card);
+	return 0;
+
+ _err:
+	snd_card_free(card);
+	return err;
+}
+
+static int snd_sb8_remove(struct device *pdev, unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(pdev));
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_sb8_suspend(struct device *dev, unsigned int n,
+			   pm_message_t state)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	struct snd_sb8 *acard = card->private_data;
+	struct snd_sb *chip = acard->chip;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	snd_sbmixer_suspend(chip);
+	return 0;
+}
+
+static int snd_sb8_resume(struct device *dev, unsigned int n)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	struct snd_sb8 *acard = card->private_data;
+	struct snd_sb *chip = acard->chip;
+
+	snd_sbdsp_reset(chip);
+	snd_sbmixer_resume(chip);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+#define DEV_NAME "sb8"
+
+static struct isa_driver snd_sb8_driver = {
+	.match		= snd_sb8_match,
+	.probe		= snd_sb8_probe,
+	.remove		= snd_sb8_remove,
+#ifdef CONFIG_PM
+	.suspend	= snd_sb8_suspend,
+	.resume		= snd_sb8_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME 
+	},
+};
+
+module_isa_driver(snd_sb8_driver, SNDRV_CARDS);
diff --git a/sound/isa/sb/sb8_main.c b/sound/isa/sb/sb8_main.c
new file mode 100644
index 0000000..4817977
--- /dev/null
+++ b/sound/isa/sb/sb8_main.c
@@ -0,0 +1,623 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/io.h>
+#include <asm/dma.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.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 const struct snd_ratnum clock = {
+	.num = SB8_CLOCK,
+	.den_min = 1,
+	.den_max = 256,
+	.den_step = 1,
+};
+
+static const struct snd_pcm_hw_constraint_ratnums hw_constraints_clock = {
+	.nrats = 1,
+	.rats = &clock,
+};
+
+static const struct snd_ratnum 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(struct snd_pcm_hw_params *params,
+					       struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *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(struct snd_pcm_hw_params *params,
+					       struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	if (r->min > SB8_RATE(22050) || r->max <= SB8_RATE(11025)) {
+		struct snd_interval 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(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int mixreg, rate, size, count;
+	unsigned char format;
+	unsigned char stereo = runtime->channels > 1;
+	int dma;
+
+	rate = runtime->rate;
+	switch (chip->hardware) {
+	case SB_HW_JAZZ16:
+		if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) {
+			if (chip->mode & SB_MODE_CAPTURE_16)
+				return -EBUSY;
+			else
+				chip->mode |= SB_MODE_PLAYBACK_16;
+		}
+		chip->playback_format = SB_DSP_LO_OUTPUT_AUTO;
+		break;
+	case SB_HW_PRO:
+		if (runtime->channels > 1) {
+			if (snd_BUG_ON(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;
+	}
+	if (chip->mode & SB_MODE_PLAYBACK_16) {
+		format = stereo ? SB_DSP_STEREO_16BIT : SB_DSP_MONO_16BIT;
+		dma = chip->dma16;
+	} else {
+		format = stereo ? SB_DSP_STEREO_8BIT : SB_DSP_MONO_8BIT;
+		chip->mode |= SB_MODE_PLAYBACK_8;
+		dma = chip->dma8;
+	}
+	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 (chip->hardware == SB_HW_JAZZ16)
+		snd_sbdsp_command(chip, format);
+	else if (stereo) {
+		/* 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(dma, runtime->dma_addr, 1, DMA_MODE_WRITE);
+		/* force interrupt */
+		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 (stereo) {
+		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) {
+		if (chip->mode & SB_MODE_PLAYBACK_16)
+			count /= 2;
+		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(dma, runtime->dma_addr,
+			size, DMA_MODE_WRITE | DMA_AUTOINIT);
+	return 0;
+}
+
+static int snd_sb8_playback_trigger(struct snd_pcm_substream *substream,
+				    int cmd)
+{
+	unsigned long flags;
+	struct snd_sb *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) {
+			struct snd_pcm_runtime *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);
+	return 0;
+}
+
+static int snd_sb8_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_sb8_hw_free(struct snd_pcm_substream *substream)
+{
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int snd_sb8_capture_prepare(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int mixreg, rate, size, count;
+	unsigned char format;
+	unsigned char stereo = runtime->channels > 1;
+	int dma;
+
+	rate = runtime->rate;
+	switch (chip->hardware) {
+	case SB_HW_JAZZ16:
+		if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) {
+			if (chip->mode & SB_MODE_PLAYBACK_16)
+				return -EBUSY;
+			else
+				chip->mode |= SB_MODE_CAPTURE_16;
+		}
+		chip->capture_format = SB_DSP_LO_INPUT_AUTO;
+		break;
+	case SB_HW_PRO:
+		if (runtime->channels > 1) {
+			if (snd_BUG_ON(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;
+	}
+	if (chip->mode & SB_MODE_CAPTURE_16) {
+		format = stereo ? SB_DSP_STEREO_16BIT : SB_DSP_MONO_16BIT;
+		dma = chip->dma16;
+	} else {
+		format = stereo ? SB_DSP_STEREO_8BIT : SB_DSP_MONO_8BIT;
+		chip->mode |= SB_MODE_CAPTURE_8;
+		dma = chip->dma8;
+	}
+	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 (chip->hardware == SB_HW_JAZZ16)
+		snd_sbdsp_command(chip, format);
+	else if (stereo)
+		snd_sbdsp_command(chip, SB_DSP_STEREO_8BIT);
+	snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE);
+	if (stereo) {
+		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_INPUT) {
+		if (chip->mode & SB_MODE_PLAYBACK_16)
+			count /= 2;
+		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(dma, runtime->dma_addr,
+			size, DMA_MODE_READ | DMA_AUTOINIT);
+	return 0;
+}
+
+static int snd_sb8_capture_trigger(struct snd_pcm_substream *substream,
+				   int cmd)
+{
+	unsigned long flags;
+	struct snd_sb *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) {
+			struct snd_pcm_runtime *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);
+	return 0;
+}
+
+irqreturn_t snd_sb8dsp_interrupt(struct snd_sb *chip)
+{
+	struct snd_pcm_substream *substream;
+
+	snd_sb_ack_8bit(chip);
+	switch (chip->mode) {
+	case SB_MODE_PLAYBACK_16:	/* ok.. playback is active */
+		if (chip->hardware != SB_HW_JAZZ16)
+			break;
+		/* fallthru */
+	case SB_MODE_PLAYBACK_8:
+		substream = chip->playback_substream;
+		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_16:
+		if (chip->hardware != SB_HW_JAZZ16)
+			break;
+		/* fallthru */
+	case SB_MODE_CAPTURE_8:
+		substream = chip->capture_substream;
+		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(struct snd_pcm_substream *substream)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	int dma;
+
+	if (chip->mode & SB_MODE_PLAYBACK_8)
+		dma = chip->dma8;
+	else if (chip->mode & SB_MODE_PLAYBACK_16)
+		dma = chip->dma16;
+	else
+		return 0;
+	ptr = snd_dma_pointer(dma, chip->p_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_sb8_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	int dma;
+
+	if (chip->mode & SB_MODE_CAPTURE_8)
+		dma = chip->dma8;
+	else if (chip->mode & SB_MODE_CAPTURE_16)
+		dma = chip->dma16;
+	else
+		return 0;
+	ptr = snd_dma_pointer(dma, chip->c_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+/*
+
+ */
+
+static const struct snd_pcm_hardware 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 const struct snd_pcm_hardware 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(struct snd_pcm_substream *substream)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *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_JAZZ16:
+		if (chip->dma16 == 5 || chip->dma16 == 7)
+			runtime->hw.formats |= SNDRV_PCM_FMTBIT_S16_LE;
+		runtime->hw.rates |= SNDRV_PCM_RATE_8000_48000;
+		runtime->hw.rate_min = 4000;
+		runtime->hw.rate_max = 50000;
+		runtime->hw.channels_max = 2;
+		break;
+	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);
+	if (chip->dma8 > 3 || chip->dma16 >= 0) {
+		snd_pcm_hw_constraint_step(runtime, 0,
+					   SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 2);
+		snd_pcm_hw_constraint_step(runtime, 0,
+					   SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 2);
+		runtime->hw.buffer_bytes_max = 128 * 1024 * 1024;
+		runtime->hw.period_bytes_max = 128 * 1024 * 1024;
+	}
+	return 0;	
+}
+
+static int snd_sb8_close(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *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;
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		chip->mode &= ~SB_MODE_PLAYBACK;
+	else
+		chip->mode &= ~SB_MODE_CAPTURE;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return 0;
+}
+
+/*
+ *  Initialization part
+ */
+ 
+static const struct snd_pcm_ops 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 const struct snd_pcm_ops 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,
+};
+
+int snd_sb8dsp_pcm(struct snd_sb *chip, int device)
+{
+	struct snd_card *card = chip->card;
+	struct snd_pcm *pcm;
+	int err;
+	size_t max_prealloc = 64 * 1024;
+
+	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;
+
+	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);
+
+	if (chip->dma8 > 3 || chip->dma16 >= 0)
+		max_prealloc = 128 * 1024;
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, max_prealloc);
+
+	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);
diff --git a/sound/isa/sb/sb8_midi.c b/sound/isa/sb/sb8_midi.c
new file mode 100644
index 0000000..4affdcb
--- /dev/null
+++ b/sound/isa/sb/sb8_midi.c
@@ -0,0 +1,279 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/io.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+
+
+irqreturn_t snd_sb8dsp_midi_interrupt(struct snd_sb *chip)
+{
+	struct snd_rawmidi *rmidi;
+	int max = 64;
+	char byte;
+
+	if (!chip)
+		return IRQ_NONE;
+	
+	rmidi = chip->rmidi;
+	if (!rmidi) {
+		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(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *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(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *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(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *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(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip;
+
+	chip = substream->rmidi->private_data;
+	del_timer_sync(&chip->midi_timer);
+	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(struct snd_rawmidi_substream *substream, int up)
+{
+	unsigned long flags;
+	struct snd_sb *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(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *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(struct timer_list *t)
+{
+	struct snd_sb *chip = from_timer(chip, t, midi_timer);
+	struct snd_rawmidi_substream *substream = chip->midi_substream_output;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->open_lock, flags);
+	mod_timer(&chip->midi_timer, 1 + jiffies);
+	spin_unlock_irqrestore(&chip->open_lock, flags);	
+	snd_sb8dsp_midi_output_write(substream);
+}
+
+static void snd_sb8dsp_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	unsigned long flags;
+	struct snd_sb *chip;
+
+	chip = substream->rmidi->private_data;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (up) {
+		if (!(chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER)) {
+			mod_timer(&chip->midi_timer, 1 + jiffies);
+			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 const struct snd_rawmidi_ops snd_sb8dsp_midi_output =
+{
+	.open =		snd_sb8dsp_midi_output_open,
+	.close =	snd_sb8dsp_midi_output_close,
+	.trigger =	snd_sb8dsp_midi_output_trigger,
+};
+
+static const struct snd_rawmidi_ops 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(struct snd_sb *chip, int device)
+{
+	struct snd_rawmidi *rmidi;
+	int err;
+
+	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;
+	timer_setup(&chip->midi_timer, snd_sb8dsp_midi_output_timer, 0);
+	chip->rmidi = 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..90b254a
--- /dev/null
+++ b/sound/isa/sb/sb_common.c
@@ -0,0 +1,307 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/initval.h>
+
+#include <asm/dma.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("ALSA lowlevel driver for Sound Blaster cards");
+MODULE_LICENSE("GPL");
+
+#define BUSY_LOOPS 100000
+
+#undef IO_DEBUG
+
+int snd_sbdsp_command(struct snd_sb *chip, unsigned char val)
+{
+	int i;
+#ifdef IO_DEBUG
+	snd_printk(KERN_DEBUG "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", __func__, chip->port, val);
+	return 0;
+}
+
+int snd_sbdsp_get_byte(struct snd_sb *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(KERN_DEBUG "get_byte 0x%x\n", val);
+#endif
+			return val;
+		}
+	}
+	snd_printd("%s [0x%lx]: timeout\n", __func__, chip->port);
+	return -ENODEV;
+}
+
+int snd_sbdsp_reset(struct snd_sb *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", __func__, chip->port);
+	return -ENODEV;
+}
+
+static int snd_sbdsp_version(struct snd_sb * 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(struct snd_sb * 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(KERN_INFO "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;
+	case SB_HW_CS5530:
+		str = "16 (CS5530)";
+		break;
+	case SB_HW_JAZZ16:
+		str = "Pro (Jazz16)";
+		break;
+	default:
+		return -ENODEV;
+	}
+	sprintf(chip->name, "Sound Blaster %s", str);
+	chip->version = (major << 8) | minor;
+	return 0;
+}
+
+static int snd_sbdsp_free(struct snd_sb *chip)
+{
+	release_and_free_resource(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(struct snd_device *device)
+{
+	struct snd_sb *chip = device->device_data;
+	return snd_sbdsp_free(chip);
+}
+
+int snd_sbdsp_create(struct snd_card *card,
+		     unsigned long port,
+		     int irq,
+		     irq_handler_t irq_handler,
+		     int dma8,
+		     int dma16,
+		     unsigned short hardware,
+		     struct snd_sb **r_chip)
+{
+	struct snd_sb *chip;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_sbdsp_dev_free,
+	};
+
+	if (snd_BUG_ON(!r_chip))
+		return -EINVAL;
+	*r_chip = NULL;
+	chip = kzalloc(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 ||
+			 hardware == SB_HW_CS5530) ?
+			IRQF_SHARED : 0,
+			"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);
+#ifdef CONFIG_PM
+EXPORT_SYMBOL(snd_sbmixer_suspend);
+EXPORT_SYMBOL(snd_sbmixer_resume);
+#endif
diff --git a/sound/isa/sb/sb_mixer.c b/sound/isa/sb/sb_mixer.c
new file mode 100644
index 0000000..add1d3f
--- /dev/null
+++ b/sound/isa/sb/sb_mixer.c
@@ -0,0 +1,961 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/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(struct snd_sb *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(KERN_DEBUG "mixer_write 0x%x 0x%x\n", reg, data);
+#endif
+}
+
+unsigned char snd_sbmixer_read(struct snd_sb *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(KERN_DEBUG "mixer_read 0x%x 0x%x\n", reg, result);
+#endif
+	return result;
+}
+
+/*
+ * Single channel mixer element
+ */
+
+static int snd_sbmixer_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts[5] = {
+		"CD", "Mic", "Line", "Synth", "Master"
+	};
+
+	return snd_ctl_enum_info(uinfo, 1, 5, texts);
+}
+
+static int snd_dt019x_input_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *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;
+}
+
+/*
+ * ALS4000 mono recording control switch
+ */
+
+static int snd_als4k_mono_capture_route_info(struct snd_kcontrol *kcontrol,
+					     struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts[3] = {
+		"L chan only", "R chan only", "L ch/2 + R ch/2"
+	};
+
+	return snd_ctl_enum_info(uinfo, 1, 3, texts);
+}
+
+static int snd_als4k_mono_capture_route_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned char oval;
+
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, SB_ALS4000_MONO_IO_CTRL);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	oval >>= 6;
+	if (oval > 2)
+		oval = 2;
+
+	ucontrol->value.enumerated.item[0] = oval;
+	return 0;
+}
+
+static int snd_als4k_mono_capture_route_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned char nval, oval;
+
+	if (ucontrol->value.enumerated.item[0] > 2)
+		return -EINVAL;
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, SB_ALS4000_MONO_IO_CTRL);
+
+	nval = (oval & ~(3 << 6))
+	     | (ucontrol->value.enumerated.item[0] << 6);
+	change = nval != oval;
+	if (change)
+		snd_sbmixer_write(sb, SB_ALS4000_MONO_IO_CTRL, nval);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	return change;
+}
+
+/*
+ * SBPRO input multiplexer
+ */
+
+static int snd_sb8mixer_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts[3] = {
+		"Mic", "CD", "Line"
+	};
+
+	return snd_ctl_enum_info(uinfo, 1, 3, texts);
+}
+
+
+static int snd_sb8mixer_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *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(struct snd_sb *chip, const char *name, int index, int type, unsigned long value)
+{
+	static struct snd_kcontrol_new 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,
+		},
+		[SB_MIX_MONO_CAPTURE_ALS4K] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_als4k_mono_capture_route_info,
+			.get = snd_als4k_mono_capture_route_get,
+			.put = snd_als4k_mono_capture_route_put,
+		},
+	};
+	struct snd_kcontrol *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)
+		return err;
+	return 0;
+}
+
+/*
+ * SB 2.0 specific mixer elements
+ */
+
+static struct sbmix_elem snd_sb20_controls[] = {
+	SB_SINGLE("Master Playback Volume", SB_DSP20_MASTER_DEV, 1, 7),
+	SB_SINGLE("PCM Playback Volume", SB_DSP20_PCM_DEV, 1, 3),
+	SB_SINGLE("Synth Playback Volume", SB_DSP20_FM_DEV, 1, 7),
+	SB_SINGLE("CD Playback Volume", SB_DSP20_CD_DEV, 1, 7)
+};
+
+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_controls[] = {
+	SB_DOUBLE("Master Playback Volume",
+		  SB_DSP_MASTER_DEV, SB_DSP_MASTER_DEV, 5, 1, 7),
+	SB_DOUBLE("PCM Playback Volume",
+		  SB_DSP_PCM_DEV, SB_DSP_PCM_DEV, 5, 1, 7),
+	SB_SINGLE("PCM Playback Filter", SB_DSP_PLAYBACK_FILT, 5, 1),
+	SB_DOUBLE("Synth Playback Volume",
+		  SB_DSP_FM_DEV, SB_DSP_FM_DEV, 5, 1, 7),
+	SB_DOUBLE("CD Playback Volume", SB_DSP_CD_DEV, SB_DSP_CD_DEV, 5, 1, 7),
+	SB_DOUBLE("Line Playback Volume",
+		  SB_DSP_LINE_DEV, SB_DSP_LINE_DEV, 5, 1, 7),
+	SB_SINGLE("Mic Playback Volume", SB_DSP_MIC_DEV, 1, 3),
+	{
+		.name = "Capture Source",
+		.type = SB_MIX_CAPTURE_PRO
+	},
+	SB_SINGLE("Capture Filter", SB_DSP_CAPTURE_FILT, 5, 1),
+	SB_SINGLE("Capture Low-Pass Filter", SB_DSP_CAPTURE_FILT, 3, 1)
+};
+
+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_controls[] = {
+	SB_DOUBLE("Master Playback Volume",
+		  SB_DSP4_MASTER_DEV, (SB_DSP4_MASTER_DEV + 1), 3, 3, 31),
+	SB_DOUBLE("PCM Playback Volume",
+		  SB_DSP4_PCM_DEV, (SB_DSP4_PCM_DEV + 1), 3, 3, 31),
+	SB16_INPUT_SW("Synth Capture Route",
+		      SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 6, 5),
+	SB_DOUBLE("Synth Playback Volume",
+		  SB_DSP4_SYNTH_DEV, (SB_DSP4_SYNTH_DEV + 1), 3, 3, 31),
+	SB16_INPUT_SW("CD Capture Route",
+		      SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 2, 1),
+	SB_DOUBLE("CD Playback Switch",
+		  SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 2, 1, 1),
+	SB_DOUBLE("CD Playback Volume",
+		  SB_DSP4_CD_DEV, (SB_DSP4_CD_DEV + 1), 3, 3, 31),
+	SB16_INPUT_SW("Mic Capture Route",
+		      SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 0, 0),
+	SB_SINGLE("Mic Playback Switch", SB_DSP4_OUTPUT_SW, 0, 1),
+	SB_SINGLE("Mic Playback Volume", SB_DSP4_MIC_DEV, 3, 31),
+	SB_SINGLE("Beep Volume", SB_DSP4_SPEAKER_DEV, 6, 3),
+	SB_DOUBLE("Capture Volume",
+		  SB_DSP4_IGAIN_DEV, (SB_DSP4_IGAIN_DEV + 1), 6, 6, 3),
+	SB_DOUBLE("Playback Volume",
+		  SB_DSP4_OGAIN_DEV, (SB_DSP4_OGAIN_DEV + 1), 6, 6, 3),
+	SB16_INPUT_SW("Line Capture Route",
+		      SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 4, 3),
+	SB_DOUBLE("Line Playback Switch",
+		  SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 4, 3, 1),
+	SB_DOUBLE("Line Playback Volume",
+		  SB_DSP4_LINE_DEV, (SB_DSP4_LINE_DEV + 1), 3, 3, 31),
+	SB_SINGLE("Mic Auto Gain", SB_DSP4_MIC_AGC, 0, 1),
+	SB_SINGLE("3D Enhancement Switch", SB_DSP4_3DSE, 0, 1),
+	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)
+};
+
+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_controls[] = {
+	/* ALS4000 below has some parts which we might be lacking,
+	 * e.g. snd_als4000_ctl_mono_playback_switch - check it! */
+	SB_DOUBLE("Master Playback Volume",
+		  SB_DT019X_MASTER_DEV, SB_DT019X_MASTER_DEV, 4, 0, 15),
+	SB_DOUBLE("PCM Playback Switch",
+		  SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 2, 1, 1),
+	SB_DOUBLE("PCM Playback Volume",
+		  SB_DT019X_PCM_DEV, SB_DT019X_PCM_DEV, 4, 0, 15),
+	SB_DOUBLE("Synth Playback Switch",
+		  SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 4, 3, 1),
+	SB_DOUBLE("Synth Playback Volume",
+		  SB_DT019X_SYNTH_DEV, SB_DT019X_SYNTH_DEV, 4, 0, 15),
+	SB_DOUBLE("CD Playback Switch",
+		  SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 2, 1, 1),
+	SB_DOUBLE("CD Playback Volume",
+		  SB_DT019X_CD_DEV, SB_DT019X_CD_DEV, 4, 0, 15),
+	SB_SINGLE("Mic Playback Switch", SB_DSP4_OUTPUT_SW, 0, 1),
+	SB_SINGLE("Mic Playback Volume", SB_DT019X_MIC_DEV, 4, 7),
+	SB_SINGLE("Beep Volume", SB_DT019X_SPKR_DEV, 0,  7),
+	SB_DOUBLE("Line Playback Switch",
+		  SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 4, 3, 1),
+	SB_DOUBLE("Line Playback Volume",
+		  SB_DT019X_LINE_DEV, SB_DT019X_LINE_DEV, 4, 0, 15),
+	{
+		.name = "Capture Source",
+		.type = SB_MIX_CAPTURE_DT019X
+	}
+};
+
+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
+ */
+static struct sbmix_elem snd_als4000_controls[] = {
+	SB_DOUBLE("PCM Playback Switch",
+		  SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 2, 1, 1),
+	SB_DOUBLE("Synth Playback Switch",
+		  SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 4, 3, 1),
+	SB_SINGLE("Mic Boost (+20dB)", SB_ALS4000_MIC_IN_GAIN, 0, 0x03),
+	SB_SINGLE("Master Mono Playback Switch", SB_ALS4000_MONO_IO_CTRL, 5, 1),
+	{
+		.name = "Master Mono Capture Route",
+		.type = SB_MIX_MONO_CAPTURE_ALS4K
+	},
+	SB_SINGLE("Mono Playback Switch", SB_DT019X_OUTPUT_SW2, 0, 1),
+	SB_SINGLE("Analog Loopback Switch", SB_ALS4000_MIC_IN_GAIN, 7, 0x01),
+	SB_SINGLE("3D Control - Switch", SB_ALS4000_3D_SND_FX, 6, 0x01),
+	SB_SINGLE("Digital Loopback Switch",
+		  SB_ALS4000_CR3_CONFIGURATION, 7, 0x01),
+	/* FIXME: functionality of 3D controls might be swapped, I didn't find
+	 * a description of how to identify what is supposed to be what */
+	SB_SINGLE("3D Control - Level", SB_ALS4000_3D_SND_FX, 0, 0x07),
+	/* FIXME: maybe there's actually some standard 3D ctrl name for it?? */
+	SB_SINGLE("3D Control - Freq", SB_ALS4000_3D_SND_FX, 4, 0x03),
+	/* FIXME: ALS4000a.pdf mentions BBD (Bucket Brigade Device) time delay,
+	 * but what ALSA 3D attribute is that actually? "Center", "Depth",
+	 * "Wide" or "Space" or even "Level"? Assuming "Wide" for now... */
+	SB_SINGLE("3D Control - Wide", SB_ALS4000_3D_TIME_DELAY, 0, 0x0f),
+	SB_SINGLE("3D PowerOff Switch", SB_ALS4000_3D_TIME_DELAY, 4, 0x01),
+	SB_SINGLE("Master Playback 8kHz / 20kHz LPF Switch",
+		  SB_ALS4000_FMDAC, 5, 0x01),
+#ifdef NOT_AVAILABLE
+	SB_SINGLE("FMDAC Switch (Option ?)", SB_ALS4000_FMDAC, 0, 0x01),
+	SB_SINGLE("QSound Mode", SB_ALS4000_QSOUND, 1, 0x1f),
+#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(struct snd_sb *chip,
+			    struct sbmix_elem *controls,
+			    int controls_count,
+			    unsigned char map[][2],
+			    int map_count,
+			    char *name)
+{
+	unsigned long flags;
+	struct snd_card *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++) {
+		err = snd_sbmixer_add_ctl_elem(chip, &controls[idx]);
+		if (err < 0)
+			return err;
+	}
+	snd_component_add(card, name);
+	strcpy(card->mixername, name);
+	return 0;
+}
+
+int snd_sbmixer_new(struct snd_sb *chip)
+{
+	struct snd_card *card;
+	int err;
+
+	if (snd_BUG_ON(!chip || !chip->card))
+		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:
+	case SB_HW_JAZZ16:
+		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:
+	case SB_HW_CS5530:
+		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:
+		/* use only the first 16 controls from SB16 */
+		err = snd_sbmixer_init(chip,
+					snd_sb16_controls,
+					16,
+					snd_sb16_init_values,
+					ARRAY_SIZE(snd_sb16_init_values),
+					"ALS4000");
+		if (err < 0)
+			return err;
+		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:
+		err = snd_sbmixer_init(chip,
+				       snd_dt019x_controls,
+				       ARRAY_SIZE(snd_dt019x_controls),
+				       snd_dt019x_init_values,
+				       ARRAY_SIZE(snd_dt019x_init_values),
+				       "DT019X");
+		if (err < 0)
+			return err;
+		break;
+	default:
+		strcpy(card->mixername, "???");
+	}
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static unsigned char sb20_saved_regs[] = {
+	SB_DSP20_MASTER_DEV,
+	SB_DSP20_PCM_DEV,
+	SB_DSP20_FM_DEV,
+	SB_DSP20_CD_DEV,
+};
+
+static unsigned char sbpro_saved_regs[] = {
+	SB_DSP_MASTER_DEV,
+	SB_DSP_PCM_DEV,
+	SB_DSP_PLAYBACK_FILT,
+	SB_DSP_FM_DEV,
+	SB_DSP_CD_DEV,
+	SB_DSP_LINE_DEV,
+	SB_DSP_MIC_DEV,
+	SB_DSP_CAPTURE_SOURCE,
+	SB_DSP_CAPTURE_FILT,
+};
+
+static unsigned char sb16_saved_regs[] = {
+	SB_DSP4_MASTER_DEV, SB_DSP4_MASTER_DEV + 1,
+	SB_DSP4_3DSE,
+	SB_DSP4_BASS_DEV, SB_DSP4_BASS_DEV + 1,
+	SB_DSP4_TREBLE_DEV, SB_DSP4_TREBLE_DEV + 1,
+	SB_DSP4_PCM_DEV, SB_DSP4_PCM_DEV + 1,
+	SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT,
+	SB_DSP4_SYNTH_DEV, SB_DSP4_SYNTH_DEV + 1,
+	SB_DSP4_OUTPUT_SW,
+	SB_DSP4_CD_DEV, SB_DSP4_CD_DEV + 1,
+	SB_DSP4_LINE_DEV, SB_DSP4_LINE_DEV + 1,
+	SB_DSP4_MIC_DEV,
+	SB_DSP4_SPEAKER_DEV,
+	SB_DSP4_IGAIN_DEV, SB_DSP4_IGAIN_DEV + 1,
+	SB_DSP4_OGAIN_DEV, SB_DSP4_OGAIN_DEV + 1,
+	SB_DSP4_MIC_AGC
+};
+
+static unsigned char dt019x_saved_regs[] = {
+	SB_DT019X_MASTER_DEV,
+	SB_DT019X_PCM_DEV,
+	SB_DT019X_SYNTH_DEV,
+	SB_DT019X_CD_DEV,
+	SB_DT019X_MIC_DEV,
+	SB_DT019X_SPKR_DEV,
+	SB_DT019X_LINE_DEV,
+	SB_DSP4_OUTPUT_SW,
+	SB_DT019X_OUTPUT_SW2,
+	SB_DT019X_CAPTURE_SW,
+};
+
+static unsigned char als4000_saved_regs[] = {
+	/* please verify in dsheet whether regs to be added
+	   are actually real H/W or just dummy */
+	SB_DSP4_MASTER_DEV, SB_DSP4_MASTER_DEV + 1,
+	SB_DSP4_OUTPUT_SW,
+	SB_DSP4_PCM_DEV, SB_DSP4_PCM_DEV + 1,
+	SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT,
+	SB_DSP4_SYNTH_DEV, SB_DSP4_SYNTH_DEV + 1,
+	SB_DSP4_CD_DEV, SB_DSP4_CD_DEV + 1,
+	SB_DSP4_MIC_DEV,
+	SB_DSP4_SPEAKER_DEV,
+	SB_DSP4_IGAIN_DEV, SB_DSP4_IGAIN_DEV + 1,
+	SB_DSP4_OGAIN_DEV, SB_DSP4_OGAIN_DEV + 1,
+	SB_DT019X_OUTPUT_SW2,
+	SB_ALS4000_MONO_IO_CTRL,
+	SB_ALS4000_MIC_IN_GAIN,
+	SB_ALS4000_FMDAC,
+	SB_ALS4000_3D_SND_FX,
+	SB_ALS4000_3D_TIME_DELAY,
+	SB_ALS4000_CR3_CONFIGURATION,
+};
+
+static void save_mixer(struct snd_sb *chip, unsigned char *regs, int num_regs)
+{
+	unsigned char *val = chip->saved_regs;
+	if (snd_BUG_ON(num_regs > ARRAY_SIZE(chip->saved_regs)))
+		return;
+	for (; num_regs; num_regs--)
+		*val++ = snd_sbmixer_read(chip, *regs++);
+}
+
+static void restore_mixer(struct snd_sb *chip, unsigned char *regs, int num_regs)
+{
+	unsigned char *val = chip->saved_regs;
+	if (snd_BUG_ON(num_regs > ARRAY_SIZE(chip->saved_regs)))
+		return;
+	for (; num_regs; num_regs--)
+		snd_sbmixer_write(chip, *regs++, *val++);
+}
+
+void snd_sbmixer_suspend(struct snd_sb *chip)
+{
+	switch (chip->hardware) {
+	case SB_HW_20:
+	case SB_HW_201:
+		save_mixer(chip, sb20_saved_regs, ARRAY_SIZE(sb20_saved_regs));
+		break;
+	case SB_HW_PRO:
+	case SB_HW_JAZZ16:
+		save_mixer(chip, sbpro_saved_regs, ARRAY_SIZE(sbpro_saved_regs));
+		break;
+	case SB_HW_16:
+	case SB_HW_ALS100:
+	case SB_HW_CS5530:
+		save_mixer(chip, sb16_saved_regs, ARRAY_SIZE(sb16_saved_regs));
+		break;
+	case SB_HW_ALS4000:
+		save_mixer(chip, als4000_saved_regs, ARRAY_SIZE(als4000_saved_regs));
+		break;
+	case SB_HW_DT019X:
+		save_mixer(chip, dt019x_saved_regs, ARRAY_SIZE(dt019x_saved_regs));
+		break;
+	default:
+		break;
+	}
+}
+
+void snd_sbmixer_resume(struct snd_sb *chip)
+{
+	switch (chip->hardware) {
+	case SB_HW_20:
+	case SB_HW_201:
+		restore_mixer(chip, sb20_saved_regs, ARRAY_SIZE(sb20_saved_regs));
+		break;
+	case SB_HW_PRO:
+	case SB_HW_JAZZ16:
+		restore_mixer(chip, sbpro_saved_regs, ARRAY_SIZE(sbpro_saved_regs));
+		break;
+	case SB_HW_16:
+	case SB_HW_ALS100:
+	case SB_HW_CS5530:
+		restore_mixer(chip, sb16_saved_regs, ARRAY_SIZE(sb16_saved_regs));
+		break;
+	case SB_HW_ALS4000:
+		restore_mixer(chip, als4000_saved_regs, ARRAY_SIZE(als4000_saved_regs));
+		break;
+	case SB_HW_DT019X:
+		restore_mixer(chip, dt019x_saved_regs, ARRAY_SIZE(dt019x_saved_regs));
+		break;
+	default:
+		break;
+	}
+}
+#endif
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/sc6000.c b/sound/isa/sc6000.c
new file mode 100644
index 0000000..a985e91
--- /dev/null
+++ b/sound/isa/sc6000.c
@@ -0,0 +1,714 @@
+/*
+ *  Driver for Gallant SC-6000 soundcard. This card is also known as
+ *  Audio Excel DSP 16 or Zoltrix AV302.
+ *  These cards use CompuMedia ASC-9308 chip + AD1848 codec.
+ *  SC-6600 and SC-7000 cards are also supported. They are based on
+ *  CompuMedia ASC-9408 chip and CS4231 codec.
+ *
+ *  Copyright (C) 2007 Krzysztof Helt <krzysztof.h1@wp.pl>
+ *
+ *  I don't have documentation for this card. I used the driver
+ *  for OSS/Free included in the kernel source as reference.
+ *
+ *  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 <linux/module.h>
+#include <linux/delay.h>
+#include <linux/isa.h>
+#include <linux/io.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/opl3.h>
+#include <sound/mpu401.h>
+#include <sound/control.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Krzysztof Helt");
+MODULE_DESCRIPTION("Gallant SC-6000");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Gallant, SC-6000},"
+			"{AudioExcel, Audio Excel DSP 16},"
+			"{Zoltrix, AV302}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220, 0x240 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5, 7, 9, 10, 11 */
+static long mss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x530, 0xe80 */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+						/* 0x300, 0x310, 0x320, 0x330 */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5, 7, 9, 10, 0 */
+static int dma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0, 1, 3 */
+static bool joystick[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = false };
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for sc-6000 based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for sc-6000 based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable sc-6000 based soundcard.");
+module_param_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for sc-6000 driver.");
+module_param_hw_array(mss_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mss_port, "MSS Port # for sc-6000 driver.");
+module_param_hw_array(mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for sc-6000 driver.");
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for sc-6000 driver.");
+module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for sc-6000 driver.");
+module_param_hw_array(dma, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma, "DMA # for sc-6000 driver.");
+module_param_array(joystick, bool, NULL, 0444);
+MODULE_PARM_DESC(joystick, "Enable gameport.");
+
+/*
+ * Commands of SC6000's DSP (SBPRO+special).
+ * Some of them are COMMAND_xx, in the future they may change.
+ */
+#define WRITE_MDIRQ_CFG	0x50	/* Set M&I&DRQ mask (the real config)	*/
+#define COMMAND_52	0x52	/*					*/
+#define READ_HARD_CFG	0x58	/* Read Hardware Config (I/O base etc)	*/
+#define COMMAND_5C	0x5c	/*					*/
+#define COMMAND_60	0x60	/*					*/
+#define COMMAND_66	0x66	/*					*/
+#define COMMAND_6C	0x6c	/*					*/
+#define COMMAND_6E	0x6e	/*					*/
+#define COMMAND_88	0x88	/* Unknown command 			*/
+#define DSP_INIT_MSS	0x8c	/* Enable Microsoft Sound System mode	*/
+#define COMMAND_C5	0xc5	/*					*/
+#define GET_DSP_VERSION	0xe1	/* Get DSP Version			*/
+#define GET_DSP_COPYRIGHT 0xe3	/* Get DSP Copyright			*/
+
+/*
+ * Offsets of SC6000 DSP I/O ports. The offset is added to base I/O port
+ * to have the actual I/O port.
+ * Register permissions are:
+ * (wo) == Write Only
+ * (ro) == Read  Only
+ * (w-) == Write
+ * (r-) == Read
+ */
+#define DSP_RESET	0x06	/* offset of DSP RESET		(wo) */
+#define DSP_READ	0x0a	/* offset of DSP READ		(ro) */
+#define DSP_WRITE	0x0c	/* offset of DSP WRITE		(w-) */
+#define DSP_COMMAND	0x0c	/* offset of DSP COMMAND	(w-) */
+#define DSP_STATUS	0x0c	/* offset of DSP STATUS		(r-) */
+#define DSP_DATAVAIL	0x0e	/* offset of DSP DATA AVAILABLE	(ro) */
+
+#define PFX "sc6000: "
+#define DRV_NAME "SC-6000"
+
+/* hardware dependent functions */
+
+/*
+ * sc6000_irq_to_softcfg - Decode irq number into cfg code.
+ */
+static unsigned char sc6000_irq_to_softcfg(int irq)
+{
+	unsigned char val = 0;
+
+	switch (irq) {
+	case 5:
+		val = 0x28;
+		break;
+	case 7:
+		val = 0x8;
+		break;
+	case 9:
+		val = 0x10;
+		break;
+	case 10:
+		val = 0x18;
+		break;
+	case 11:
+		val = 0x20;
+		break;
+	default:
+		break;
+	}
+	return val;
+}
+
+/*
+ * sc6000_dma_to_softcfg - Decode dma number into cfg code.
+ */
+static unsigned char sc6000_dma_to_softcfg(int dma)
+{
+	unsigned char val = 0;
+
+	switch (dma) {
+	case 0:
+		val = 1;
+		break;
+	case 1:
+		val = 2;
+		break;
+	case 3:
+		val = 3;
+		break;
+	default:
+		break;
+	}
+	return val;
+}
+
+/*
+ * sc6000_mpu_irq_to_softcfg - Decode MPU-401 irq number into cfg code.
+ */
+static unsigned char sc6000_mpu_irq_to_softcfg(int mpu_irq)
+{
+	unsigned char val = 0;
+
+	switch (mpu_irq) {
+	case 5:
+		val = 4;
+		break;
+	case 7:
+		val = 0x44;
+		break;
+	case 9:
+		val = 0x84;
+		break;
+	case 10:
+		val = 0xc4;
+		break;
+	default:
+		break;
+	}
+	return val;
+}
+
+static int sc6000_wait_data(char __iomem *vport)
+{
+	int loop = 1000;
+	unsigned char val = 0;
+
+	do {
+		val = ioread8(vport + DSP_DATAVAIL);
+		if (val & 0x80)
+			return 0;
+		cpu_relax();
+	} while (loop--);
+
+	return -EAGAIN;
+}
+
+static int sc6000_read(char __iomem *vport)
+{
+	if (sc6000_wait_data(vport))
+		return -EBUSY;
+
+	return ioread8(vport + DSP_READ);
+
+}
+
+static int sc6000_write(char __iomem *vport, int cmd)
+{
+	unsigned char val;
+	int loop = 500000;
+
+	do {
+		val = ioread8(vport + DSP_STATUS);
+		/*
+		 * DSP ready to receive data if bit 7 of val == 0
+		 */
+		if (!(val & 0x80)) {
+			iowrite8(cmd, vport + DSP_COMMAND);
+			return 0;
+		}
+		cpu_relax();
+	} while (loop--);
+
+	snd_printk(KERN_ERR "DSP Command (0x%x) timeout.\n", cmd);
+
+	return -EIO;
+}
+
+static int sc6000_dsp_get_answer(char __iomem *vport, int command,
+				 char *data, int data_len)
+{
+	int len = 0;
+
+	if (sc6000_write(vport, command)) {
+		snd_printk(KERN_ERR "CMD 0x%x: failed!\n", command);
+		return -EIO;
+	}
+
+	do {
+		int val = sc6000_read(vport);
+
+		if (val < 0)
+			break;
+
+		data[len++] = val;
+
+	} while (len < data_len);
+
+	/*
+	 * If no more data available, return to the caller, no error if len>0.
+	 * We have no other way to know when the string is finished.
+	 */
+	return len ? len : -EIO;
+}
+
+static int sc6000_dsp_reset(char __iomem *vport)
+{
+	iowrite8(1, vport + DSP_RESET);
+	udelay(10);
+	iowrite8(0, vport + DSP_RESET);
+	udelay(20);
+	if (sc6000_read(vport) == 0xaa)
+		return 0;
+	return -ENODEV;
+}
+
+/* detection and initialization */
+static int sc6000_hw_cfg_write(char __iomem *vport, const int *cfg)
+{
+	if (sc6000_write(vport, COMMAND_6C) < 0) {
+		snd_printk(KERN_WARNING "CMD 0x%x: failed!\n", COMMAND_6C);
+		return -EIO;
+	}
+	if (sc6000_write(vport, COMMAND_5C) < 0) {
+		snd_printk(KERN_ERR "CMD 0x%x: failed!\n", COMMAND_5C);
+		return -EIO;
+	}
+	if (sc6000_write(vport, cfg[0]) < 0) {
+		snd_printk(KERN_ERR "DATA 0x%x: failed!\n", cfg[0]);
+		return -EIO;
+	}
+	if (sc6000_write(vport, cfg[1]) < 0) {
+		snd_printk(KERN_ERR "DATA 0x%x: failed!\n", cfg[1]);
+		return -EIO;
+	}
+	if (sc6000_write(vport, COMMAND_C5) < 0) {
+		snd_printk(KERN_ERR "CMD 0x%x: failed!\n", COMMAND_C5);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int sc6000_cfg_write(char __iomem *vport, unsigned char softcfg)
+{
+
+	if (sc6000_write(vport, WRITE_MDIRQ_CFG)) {
+		snd_printk(KERN_ERR "CMD 0x%x: failed!\n", WRITE_MDIRQ_CFG);
+		return -EIO;
+	}
+	if (sc6000_write(vport, softcfg)) {
+		snd_printk(KERN_ERR "sc6000_cfg_write: failed!\n");
+		return -EIO;
+	}
+	return 0;
+}
+
+static int sc6000_setup_board(char __iomem *vport, int config)
+{
+	int loop = 10;
+
+	do {
+		if (sc6000_write(vport, COMMAND_88)) {
+			snd_printk(KERN_ERR "CMD 0x%x: failed!\n",
+				   COMMAND_88);
+			return -EIO;
+		}
+	} while ((sc6000_wait_data(vport) < 0) && loop--);
+
+	if (sc6000_read(vport) < 0) {
+		snd_printk(KERN_ERR "sc6000_read after CMD 0x%x: failed\n",
+			   COMMAND_88);
+		return -EIO;
+	}
+
+	if (sc6000_cfg_write(vport, config))
+		return -ENODEV;
+
+	return 0;
+}
+
+static int sc6000_init_mss(char __iomem *vport, int config,
+			   char __iomem *vmss_port, int mss_config)
+{
+	if (sc6000_write(vport, DSP_INIT_MSS)) {
+		snd_printk(KERN_ERR "sc6000_init_mss [0x%x]: failed!\n",
+			   DSP_INIT_MSS);
+		return -EIO;
+	}
+
+	msleep(10);
+
+	if (sc6000_cfg_write(vport, config))
+		return -EIO;
+
+	iowrite8(mss_config, vmss_port);
+
+	return 0;
+}
+
+static void sc6000_hw_cfg_encode(char __iomem *vport, int *cfg,
+				 long xport, long xmpu,
+				 long xmss_port, int joystick)
+{
+	cfg[0] = 0;
+	cfg[1] = 0;
+	if (xport == 0x240)
+		cfg[0] |= 1;
+	if (xmpu != SNDRV_AUTO_PORT) {
+		cfg[0] |= (xmpu & 0x30) >> 2;
+		cfg[1] |= 0x20;
+	}
+	if (xmss_port == 0xe80)
+		cfg[0] |= 0x10;
+	cfg[0] |= 0x40;		/* always set */
+	if (!joystick)
+		cfg[0] |= 0x02;
+	cfg[1] |= 0x80;		/* enable WSS system */
+	cfg[1] &= ~0x40;	/* disable IDE */
+	snd_printd("hw cfg %x, %x\n", cfg[0], cfg[1]);
+}
+
+static int sc6000_init_board(char __iomem *vport,
+			     char __iomem *vmss_port, int dev)
+{
+	char answer[15];
+	char version[2];
+	int mss_config = sc6000_irq_to_softcfg(irq[dev]) |
+			 sc6000_dma_to_softcfg(dma[dev]);
+	int config = mss_config |
+		     sc6000_mpu_irq_to_softcfg(mpu_irq[dev]);
+	int err;
+	int old = 0;
+
+	err = sc6000_dsp_reset(vport);
+	if (err < 0) {
+		snd_printk(KERN_ERR "sc6000_dsp_reset: failed!\n");
+		return err;
+	}
+
+	memset(answer, 0, sizeof(answer));
+	err = sc6000_dsp_get_answer(vport, GET_DSP_COPYRIGHT, answer, 15);
+	if (err <= 0) {
+		snd_printk(KERN_ERR "sc6000_dsp_copyright: failed!\n");
+		return -ENODEV;
+	}
+	/*
+	 * My SC-6000 card return "SC-6000" in DSPCopyright, so
+	 * if we have something different, we have to be warned.
+	 */
+	if (strncmp("SC-6000", answer, 7))
+		snd_printk(KERN_WARNING "Warning: non SC-6000 audio card!\n");
+
+	if (sc6000_dsp_get_answer(vport, GET_DSP_VERSION, version, 2) < 2) {
+		snd_printk(KERN_ERR "sc6000_dsp_version: failed!\n");
+		return -ENODEV;
+	}
+	printk(KERN_INFO PFX "Detected model: %s, DSP version %d.%d\n",
+		answer, version[0], version[1]);
+
+	/* set configuration */
+	sc6000_write(vport, COMMAND_5C);
+	if (sc6000_read(vport) < 0)
+		old = 1;
+
+	if (!old) {
+		int cfg[2];
+		sc6000_hw_cfg_encode(vport, &cfg[0], port[dev], mpu_port[dev],
+				     mss_port[dev], joystick[dev]);
+		if (sc6000_hw_cfg_write(vport, cfg) < 0) {
+			snd_printk(KERN_ERR "sc6000_hw_cfg_write: failed!\n");
+			return -EIO;
+		}
+	}
+	err = sc6000_setup_board(vport, config);
+	if (err < 0) {
+		snd_printk(KERN_ERR "sc6000_setup_board: failed!\n");
+		return -ENODEV;
+	}
+
+	sc6000_dsp_reset(vport);
+
+	if (!old) {
+		sc6000_write(vport, COMMAND_60);
+		sc6000_write(vport, 0x02);
+		sc6000_dsp_reset(vport);
+	}
+
+	err = sc6000_setup_board(vport, config);
+	if (err < 0) {
+		snd_printk(KERN_ERR "sc6000_setup_board: failed!\n");
+		return -ENODEV;
+	}
+	err = sc6000_init_mss(vport, config, vmss_port, mss_config);
+	if (err < 0) {
+		snd_printk(KERN_ERR "Cannot initialize "
+			   "Microsoft Sound System mode.\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int snd_sc6000_mixer(struct snd_wss *chip)
+{
+	struct snd_card *card = chip->card;
+	struct snd_ctl_elem_id id1, id2;
+	int err;
+
+	memset(&id1, 0, sizeof(id1));
+	memset(&id2, 0, sizeof(id2));
+	id1.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	/* reassign AUX0 to FM */
+	strcpy(id1.name, "Aux Playback Switch");
+	strcpy(id2.name, "FM Playback Switch");
+	err = snd_ctl_rename_id(card, &id1, &id2);
+	if (err < 0)
+		return err;
+	strcpy(id1.name, "Aux Playback Volume");
+	strcpy(id2.name, "FM Playback Volume");
+	err = snd_ctl_rename_id(card, &id1, &id2);
+	if (err < 0)
+		return err;
+	/* reassign AUX1 to CD */
+	strcpy(id1.name, "Aux Playback Switch"); id1.index = 1;
+	strcpy(id2.name, "CD Playback Switch");
+	err = snd_ctl_rename_id(card, &id1, &id2);
+	if (err < 0)
+		return err;
+	strcpy(id1.name, "Aux Playback Volume");
+	strcpy(id2.name, "CD Playback Volume");
+	err = snd_ctl_rename_id(card, &id1, &id2);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_sc6000_match(struct device *devptr, unsigned int dev)
+{
+	if (!enable[dev])
+		return 0;
+	if (port[dev] == SNDRV_AUTO_PORT) {
+		printk(KERN_ERR PFX "specify IO port\n");
+		return 0;
+	}
+	if (mss_port[dev] == SNDRV_AUTO_PORT) {
+		printk(KERN_ERR PFX "specify MSS port\n");
+		return 0;
+	}
+	if (port[dev] != 0x220 && port[dev] != 0x240) {
+		printk(KERN_ERR PFX "Port must be 0x220 or 0x240\n");
+		return 0;
+	}
+	if (mss_port[dev] != 0x530 && mss_port[dev] != 0xe80) {
+		printk(KERN_ERR PFX "MSS port must be 0x530 or 0xe80\n");
+		return 0;
+	}
+	if (irq[dev] != SNDRV_AUTO_IRQ && !sc6000_irq_to_softcfg(irq[dev])) {
+		printk(KERN_ERR PFX "invalid IRQ %d\n", irq[dev]);
+		return 0;
+	}
+	if (dma[dev] != SNDRV_AUTO_DMA && !sc6000_dma_to_softcfg(dma[dev])) {
+		printk(KERN_ERR PFX "invalid DMA %d\n", dma[dev]);
+		return 0;
+	}
+	if (mpu_port[dev] != SNDRV_AUTO_PORT &&
+	    (mpu_port[dev] & ~0x30L) != 0x300) {
+		printk(KERN_ERR PFX "invalid MPU-401 port %lx\n",
+			mpu_port[dev]);
+		return 0;
+	}
+	if (mpu_port[dev] != SNDRV_AUTO_PORT &&
+	    mpu_irq[dev] != SNDRV_AUTO_IRQ && mpu_irq[dev] != 0 &&
+	    !sc6000_mpu_irq_to_softcfg(mpu_irq[dev])) {
+		printk(KERN_ERR PFX "invalid MPU-401 IRQ %d\n", mpu_irq[dev]);
+		return 0;
+	}
+	return 1;
+}
+
+static int snd_sc6000_probe(struct device *devptr, unsigned int dev)
+{
+	static int possible_irqs[] = { 5, 7, 9, 10, 11, -1 };
+	static int possible_dmas[] = { 1, 3, 0, -1 };
+	int err;
+	int xirq = irq[dev];
+	int xdma = dma[dev];
+	struct snd_card *card;
+	struct snd_wss *chip;
+	struct snd_opl3 *opl3;
+	char __iomem **vport;
+	char __iomem *vmss_port;
+
+
+	err = snd_card_new(devptr, index[dev], id[dev], THIS_MODULE,
+			   sizeof(vport), &card);
+	if (err < 0)
+		return err;
+
+	vport = card->private_data;
+	if (xirq == SNDRV_AUTO_IRQ) {
+		xirq = snd_legacy_find_free_irq(possible_irqs);
+		if (xirq < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free IRQ\n");
+			err = -EBUSY;
+			goto err_exit;
+		}
+	}
+
+	if (xdma == SNDRV_AUTO_DMA) {
+		xdma = snd_legacy_find_free_dma(possible_dmas);
+		if (xdma < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free DMA\n");
+			err = -EBUSY;
+			goto err_exit;
+		}
+	}
+
+	if (!request_region(port[dev], 0x10, DRV_NAME)) {
+		snd_printk(KERN_ERR PFX
+			   "I/O port region is already in use.\n");
+		err = -EBUSY;
+		goto err_exit;
+	}
+	*vport = devm_ioport_map(devptr, port[dev], 0x10);
+	if (*vport == NULL) {
+		snd_printk(KERN_ERR PFX
+			   "I/O port cannot be iomapped.\n");
+		err = -EBUSY;
+		goto err_unmap1;
+	}
+
+	/* to make it marked as used */
+	if (!request_region(mss_port[dev], 4, DRV_NAME)) {
+		snd_printk(KERN_ERR PFX
+			   "SC-6000 port I/O port region is already in use.\n");
+		err = -EBUSY;
+		goto err_unmap1;
+	}
+	vmss_port = devm_ioport_map(devptr, mss_port[dev], 4);
+	if (!vmss_port) {
+		snd_printk(KERN_ERR PFX
+			   "MSS port I/O cannot be iomapped.\n");
+		err = -EBUSY;
+		goto err_unmap2;
+	}
+
+	snd_printd("Initializing BASE[0x%lx] IRQ[%d] DMA[%d] MIRQ[%d]\n",
+		   port[dev], xirq, xdma,
+		   mpu_irq[dev] == SNDRV_AUTO_IRQ ? 0 : mpu_irq[dev]);
+
+	err = sc6000_init_board(*vport, vmss_port, dev);
+	if (err < 0)
+		goto err_unmap2;
+
+	err = snd_wss_create(card, mss_port[dev] + 4,  -1, xirq, xdma, -1,
+			     WSS_HW_DETECT, 0, &chip);
+	if (err < 0)
+		goto err_unmap2;
+
+	err = snd_wss_pcm(chip, 0);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX
+			   "error creating new WSS PCM device\n");
+		goto err_unmap2;
+	}
+	err = snd_wss_mixer(chip);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "error creating new WSS mixer\n");
+		goto err_unmap2;
+	}
+	err = snd_sc6000_mixer(chip);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "the mixer rewrite failed\n");
+		goto err_unmap2;
+	}
+	if (snd_opl3_create(card,
+			    0x388, 0x388 + 2,
+			    OPL3_HW_AUTO, 0, &opl3) < 0) {
+		snd_printk(KERN_ERR PFX "no OPL device at 0x%x-0x%x ?\n",
+			   0x388, 0x388 + 2);
+	} else {
+		err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+		if (err < 0)
+			goto err_unmap2;
+	}
+
+	if (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_MPU401,
+					mpu_port[dev], 0,
+					mpu_irq[dev], NULL) < 0)
+			snd_printk(KERN_ERR "no MPU-401 device at 0x%lx ?\n",
+					mpu_port[dev]);
+	}
+
+	strcpy(card->driver, DRV_NAME);
+	strcpy(card->shortname, "SC-6000");
+	sprintf(card->longname, "Gallant SC-6000 at 0x%lx, irq %d, dma %d",
+		mss_port[dev], xirq, xdma);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto err_unmap2;
+
+	dev_set_drvdata(devptr, card);
+	return 0;
+
+err_unmap2:
+	sc6000_setup_board(*vport, 0);
+	release_region(mss_port[dev], 4);
+err_unmap1:
+	release_region(port[dev], 0x10);
+err_exit:
+	snd_card_free(card);
+	return err;
+}
+
+static int snd_sc6000_remove(struct device *devptr, unsigned int dev)
+{
+	struct snd_card *card = dev_get_drvdata(devptr);
+	char __iomem **vport = card->private_data;
+
+	if (sc6000_setup_board(*vport, 0) < 0)
+		snd_printk(KERN_WARNING "sc6000_setup_board failed on exit!\n");
+
+	release_region(port[dev], 0x10);
+	release_region(mss_port[dev], 4);
+
+	snd_card_free(card);
+	return 0;
+}
+
+static struct isa_driver snd_sc6000_driver = {
+	.match		= snd_sc6000_match,
+	.probe		= snd_sc6000_probe,
+	.remove		= snd_sc6000_remove,
+	/* FIXME: suspend/resume */
+	.driver		= {
+		.name	= DRV_NAME,
+	},
+};
+
+
+module_isa_driver(snd_sc6000_driver, SNDRV_CARDS);
diff --git a/sound/isa/sscape.c b/sound/isa/sscape.c
new file mode 100644
index 0000000..733adee
--- /dev/null
+++ b/sound/isa/sscape.c
@@ -0,0 +1,1357 @@
+/*
+ *   Low-level ALSA driver for the ENSONIQ SoundScape
+ *   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 <linux/init.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/pnp.h>
+#include <linux/spinlock.h>
+#include <linux/module.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/initval.h>
+
+
+MODULE_AUTHOR("Chris Rankin");
+MODULE_DESCRIPTION("ENSONIQ SoundScape driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE("sndscape.co0");
+MODULE_FIRMWARE("sndscape.co1");
+MODULE_FIRMWARE("sndscape.co2");
+MODULE_FIRMWARE("sndscape.co3");
+MODULE_FIRMWARE("sndscape.co4");
+MODULE_FIRMWARE("scope.cod");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int dma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static bool joystick[SNDRV_CARDS];
+
+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_hw_array(port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for SoundScape driver.");
+
+module_param_hw_array(wss_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(wss_port, "WSS Port # for SoundScape driver.");
+
+module_param_hw_array(irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for SoundScape driver.");
+
+module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU401 IRQ # for SoundScape driver.");
+
+module_param_hw_array(dma, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma, "DMA # for SoundScape driver.");
+
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for SoundScape driver.");
+
+module_param_array(joystick, bool, NULL, 0444);
+MODULE_PARM_DESC(joystick, "Enable gameport.");
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+
+static const struct pnp_card_device_id sscape_pnpids[] = {
+	{ .id = "ENS3081", .devs = { { "ENS0000" } } }, /* Soundscape PnP */
+	{ .id = "ENS4081", .devs = { { "ENS1011" } } },	/* VIVO90 */
+	{ .id = "" }	/* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, sscape_pnpids);
+#endif
+
+
+#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
+
+
+enum card_type {
+	MEDIA_FX,	/* Sequoia S-1000 */
+	SSCAPE,		/* Sequoia S-2000 */
+	SSCAPE_PNP,
+	SSCAPE_VIVO,
+};
+
+struct soundscape {
+	spinlock_t lock;
+	unsigned io_base;
+	int ic_type;
+	enum card_type type;
+	struct resource *io_res;
+	struct resource *wss_res;
+	struct snd_wss *chip;
+
+	unsigned char midi_vol;
+};
+
+#define INVALID_IRQ  ((unsigned)-1)
+
+
+static inline struct soundscape *get_card_soundscape(struct snd_card *c)
+{
+	return (struct soundscape *) (c->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 struct snd_mpu401 *mpu)
+{
+	return ((inb(MPU401C(mpu)) & 0xc0) == 0x80);
+}
+
+/*
+ * This is apparently the standard way to initailise an MPU-401
+ */
+static inline void initialise_mpu401(const struct snd_mpu401 *mpu)
+{
+	outb(0, MPU401D(mpu));
+}
+
+/*
+ * 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 void activate_ad1845_unsafe(unsigned io_base)
+{
+	unsigned char val = sscape_read_unsafe(io_base, GA_HMCTL_REG);
+	sscape_write_unsafe(io_base, GA_HMCTL_REG, (val & 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(struct snd_card *c)
+{
+	struct soundscape *sscape = get_card_soundscape(c);
+	release_and_free_resource(sscape->io_res);
+	release_and_free_resource(sscape->wss_res);
+	free_dma(sscape->chip->dma1);
+}
+
+/*
+ * Tell the SoundScape to begin a DMA tranfer using the given channel.
+ * All locking issues are left to the caller.
+ */
+static 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)
+{
+	unsigned long end_time = jiffies + msecs_to_jiffies(timeout);
+
+	do {
+		unsigned long flags;
+		int x;
+
+		spin_lock_irqsave(&s->lock, flags);
+		x = host_read_unsafe(s->io_base);
+		spin_unlock_irqrestore(&s->lock, flags);
+		if (x == 0xfe || x == 0xff)
+			return 1;
+
+		msleep(10);
+	} while (time_before(jiffies, end_time));
+
+	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)
+{
+	unsigned long end_time = jiffies + msecs_to_jiffies(timeout);
+
+	do {
+		unsigned long flags;
+		int x;
+
+		spin_lock_irqsave(&s->lock, flags);
+		x = host_read_unsafe(s->io_base);
+		spin_unlock_irqrestore(&s->lock, flags);
+		if (x == 0xfe)
+			return 1;
+
+		msleep(10);
+	} while (time_before(jiffies, end_time));
+
+	return 0;
+}
+
+/*
+ * Upload a byte-stream into the SoundScape using DMA channel A.
+ */
+static int upload_dma_data(struct soundscape *s, const unsigned char *data,
+			   size_t size)
+{
+	unsigned long flags;
+	struct snd_dma_buffer dma;
+	int ret;
+	unsigned char val;
+
+	if (!get_dmabuf(&dma, PAGE_ALIGN(32 * 1024)))
+		return -ENOMEM;
+
+	spin_lock_irqsave(&s->lock, flags);
+
+	/*
+	 * Reset the board ...
+	 */
+	val = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
+	sscape_write_unsafe(s->io_base, GA_HMCTL_REG, val & 0x3f);
+
+	/*
+	 * Enable the DMA channels and configure them ...
+	 */
+	val = (s->chip->dma1 << 4) | DMA_8BIT;
+	sscape_write_unsafe(s->io_base, GA_DMAA_REG, val);
+	sscape_write_unsafe(s->io_base, GA_DMAB_REG, 0x20);
+
+	/*
+	 * Take the board out of reset ...
+	 */
+	val = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
+	sscape_write_unsafe(s->io_base, GA_HMCTL_REG, val | 0x80);
+
+	/*
+	 * Upload the firmware to the SoundScape
+	 * board through the DMA channel ...
+	 */
+	while (size != 0) {
+		unsigned long len;
+
+		len = min(size, dma.bytes);
+		memcpy(dma.area, data, len);
+		data += len;
+		size -= len;
+
+		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);
+	outb(0x0, s->io_base);
+
+	/*
+	 * Boot the board ... (I think)
+	 */
+	val = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
+	sscape_write_unsafe(s->io_base, GA_HMCTL_REG, val | 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, 5000)) {
+		snd_printk(KERN_ERR "sscape: No response "
+				    "from on-board processor after upload\n");
+		ret = -EAGAIN;
+	} else if (!host_startup_ack(s, 5000)) {
+		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_OPUS ? 0x40 : 0x70));
+	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.
+ */
+static int sscape_upload_bootblock(struct snd_card *card)
+{
+	struct soundscape *sscape = get_card_soundscape(card);
+	unsigned long flags;
+	const struct firmware *init_fw = NULL;
+	int data = 0;
+	int ret;
+
+	ret = request_firmware(&init_fw, "scope.cod", card->dev);
+	if (ret < 0) {
+		snd_printk(KERN_ERR "sscape: Error loading scope.cod");
+		return ret;
+	}
+	ret = upload_dma_data(sscape, init_fw->data, init_fw->size);
+
+	release_firmware(init_fw);
+
+	spin_lock_irqsave(&sscape->lock, flags);
+	if (ret == 0)
+		data = host_read_ctrl_unsafe(sscape->io_base, 100);
+
+	if (data & 0x10)
+		sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2f);
+
+	spin_unlock_irqrestore(&sscape->lock, flags);
+
+	data &= 0xf;
+	if (ret == 0 && data > 7) {
+		snd_printk(KERN_ERR
+				"sscape: timeout reading firmware version\n");
+		ret = -EAGAIN;
+	}
+
+	return (ret == 0) ? data : ret;
+}
+
+/*
+ * Upload the microcode into the SoundScape.
+ */
+static int sscape_upload_microcode(struct snd_card *card, int version)
+{
+	struct soundscape *sscape = get_card_soundscape(card);
+	const struct firmware *init_fw = NULL;
+	char name[14];
+	int err;
+
+	snprintf(name, sizeof(name), "sndscape.co%d", version);
+
+	err = request_firmware(&init_fw, name, card->dev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "sscape: Error loading sndscape.co%d",
+				version);
+		return err;
+	}
+	err = upload_dma_data(sscape, init_fw->data, init_fw->size);
+	if (err == 0)
+		snd_printk(KERN_INFO "sscape: MIDI firmware loaded %zu KBs\n",
+				init_fw->size >> 10);
+
+	release_firmware(init_fw);
+
+	return err;
+}
+
+/*
+ * Mixer control for the SoundScape's MIDI device.
+ */
+static int sscape_midi_info(struct snd_kcontrol *ctl,
+			    struct snd_ctl_elem_info *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(struct snd_kcontrol *kctl,
+			   struct snd_ctl_elem_value *uctl)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kctl);
+	struct snd_card *card = chip->card;
+	register struct soundscape *s = get_card_soundscape(card);
+	unsigned long flags;
+
+	spin_lock_irqsave(&s->lock, flags);
+	uctl->value.integer.value[0] = s->midi_vol;
+	spin_unlock_irqrestore(&s->lock, flags);
+	return 0;
+}
+
+static int sscape_midi_put(struct snd_kcontrol *kctl,
+			   struct snd_ctl_elem_value *uctl)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kctl);
+	struct snd_card *card = chip->card;
+	struct soundscape *s = get_card_soundscape(card);
+	unsigned long flags;
+	int change;
+	unsigned char new_val;
+
+	spin_lock_irqsave(&s->lock, flags);
+
+	new_val = uctl->value.integer.value[0] & 127;
+	/*
+	 * 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 == new_val) {
+		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, new_val, 100)
+		 && host_write_ctrl_unsafe(s->io_base, CMD_XXX_MIDI_VOL, 100)
+		 && host_write_ctrl_unsafe(s->io_base, new_val, 100);
+	s->midi_vol = new_val;
+__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 const struct snd_kcontrol_new 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 get_irq_config(int sscape_type, int irq)
+{
+	static const int valid_irq[] = { 9, 5, 7, 10 };
+	static const int old_irq[] = { 9, 7, 5, 15 };
+	unsigned cfg;
+
+	if (sscape_type == MEDIA_FX) {
+		for (cfg = 0; cfg < ARRAY_SIZE(old_irq); ++cfg)
+			if (irq == old_irq[cfg])
+				return cfg;
+	} else {
+		for (cfg = 0; cfg < ARRAY_SIZE(valid_irq); ++cfg)
+			if (irq == valid_irq[cfg])
+				return cfg;
+	}
+
+	return INVALID_IRQ;
+}
+
+/*
+ * Perform certain arcane port-checks to see whether there
+ * is a SoundScape board lurking behind the given ports.
+ */
+static int detect_sscape(struct soundscape *s, long wss_io)
+{
+	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->ic_type = IC_ODIE;
+	else if ((d & 0x60) != 0)
+		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;
+
+	outb(0xfe, ODIE_ADDR_IO(s->io_base));
+	d = inb(ODIE_DATA_IO(s->io_base));
+	if (s->type != SSCAPE_VIVO && (d & 0x9f) != 0x0e)
+		goto _done;
+
+	if (s->ic_type == IC_OPUS)
+		activate_ad1845_unsafe(s->io_base);
+
+	if (s->type == SSCAPE_VIVO)
+		wss_io += 4;
+
+	d  = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
+	sscape_write_unsafe(s->io_base, GA_HMCTL_REG, d | 0xc0);
+
+	/* wait for WSS codec */
+	for (d = 0; d < 500; d++) {
+		if ((inb(wss_io) & 0x80) == 0)
+			break;
+		spin_unlock_irqrestore(&s->lock, flags);
+		msleep(1);
+		spin_lock_irqsave(&s->lock, flags);
+	}
+
+	if ((inb(wss_io) & 0x80) != 0)
+		goto _done;
+
+	if (inb(wss_io + 2) == 0xff)
+		goto _done;
+
+	d  = sscape_read_unsafe(s->io_base, GA_HMCTL_REG) & 0x3f;
+	sscape_write_unsafe(s->io_base, GA_HMCTL_REG, d);
+
+	if ((inb(wss_io) & 0x80) != 0)
+		s->type = MEDIA_FX;
+
+	d = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
+	sscape_write_unsafe(s->io_base, GA_HMCTL_REG, d | 0xc0);
+	/* wait for WSS codec */
+	for (d = 0; d < 500; d++) {
+		if ((inb(wss_io) & 0x80) == 0)
+			break;
+		spin_unlock_irqrestore(&s->lock, flags);
+		msleep(1);
+		spin_lock_irqsave(&s->lock, flags);
+	}
+
+	/*
+	 * 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(struct snd_mpu401 *mpu)
+{
+	if (!verify_mpu401(mpu)) {
+		snd_printk(KERN_ERR "sscape: MIDI disabled, "
+				    "please load firmware\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+/*
+ * Initialse an MPU-401 subdevice for MIDI support on the SoundScape.
+ */
+static int create_mpu401(struct snd_card *card, int devnum,
+			 unsigned long port, int irq)
+{
+	struct soundscape *sscape = get_card_soundscape(card);
+	struct snd_rawmidi *rawmidi;
+	int err;
+
+	err = snd_mpu401_uart_new(card, devnum, MPU401_HW_MPU401, port,
+				  MPU401_INFO_INTEGRATED, irq, &rawmidi);
+	if (err == 0) {
+		struct snd_mpu401 *mpu = rawmidi->private_data;
+		mpu->open_input = mpu401_open;
+		mpu->open_output = mpu401_open;
+		mpu->private_data = sscape;
+
+		initialise_mpu401(mpu);
+	}
+
+	return err;
+}
+
+
+/*
+ * 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 create_ad1845(struct snd_card *card, unsigned port,
+			 int irq, int dma1, int dma2)
+{
+	register struct soundscape *sscape = get_card_soundscape(card);
+	struct snd_wss *chip;
+	int err;
+	int codec_type = WSS_HW_DETECT;
+
+	switch (sscape->type) {
+	case MEDIA_FX:
+	case SSCAPE:
+		/*
+		 * There are some freak examples of early Soundscape cards
+		 * with CS4231 instead of AD1848/CS4248. Unfortunately, the
+		 * CS4231 works only in CS4248 compatibility mode on
+		 * these cards so force it.
+		 */
+		if (sscape->ic_type != IC_OPUS)
+			codec_type = WSS_HW_AD1848;
+		break;
+
+	case SSCAPE_VIVO:
+		port += 4;
+		break;
+	default:
+		break;
+	}
+
+	err = snd_wss_create(card, port, -1, irq, dma1, dma2,
+			     codec_type, WSS_HWSHARE_DMA1, &chip);
+	if (!err) {
+		unsigned long flags;
+
+		if (sscape->type != SSCAPE_VIVO) {
+			/*
+			 * 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_wss_mce_up(chip);
+			spin_lock_irqsave(&chip->reg_lock, flags);
+			snd_wss_out(chip, AD1845_CLOCK, 0x20);
+			spin_unlock_irqrestore(&chip->reg_lock, flags);
+			snd_wss_mce_down(chip);
+
+		}
+
+		err = snd_wss_pcm(chip, 0);
+		if (err < 0) {
+			snd_printk(KERN_ERR "sscape: No PCM device "
+					    "for AD1845 chip\n");
+			goto _error;
+		}
+
+		err = snd_wss_mixer(chip);
+		if (err < 0) {
+			snd_printk(KERN_ERR "sscape: No mixer device "
+					    "for AD1845 chip\n");
+			goto _error;
+		}
+		if (chip->hardware != WSS_HW_AD1848) {
+			err = snd_wss_timer(chip, 0);
+			if (err < 0) {
+				snd_printk(KERN_ERR "sscape: No timer device "
+						    "for AD1845 chip\n");
+				goto _error;
+			}
+		}
+
+		if (sscape->type != SSCAPE_VIVO) {
+			err = snd_ctl_add(card,
+					  snd_ctl_new1(&midi_mixer_ctl, chip));
+			if (err < 0) {
+				snd_printk(KERN_ERR "sscape: Could not create "
+						    "MIDI mixer control\n");
+				goto _error;
+			}
+		}
+
+		sscape->chip = chip;
+	}
+
+_error:
+	return err;
+}
+
+
+/*
+ * Create an ALSA soundcard entry for the SoundScape, using
+ * the given list of port, IRQ and DMA resources.
+ */
+static int create_sscape(int dev, struct snd_card *card)
+{
+	struct soundscape *sscape = get_card_soundscape(card);
+	unsigned dma_cfg;
+	unsigned irq_cfg;
+	unsigned mpu_irq_cfg;
+	struct resource *io_res;
+	struct resource *wss_res;
+	unsigned long flags;
+	int err;
+	int val;
+	const char *name;
+
+	/*
+	 * Grab IO ports that we will need to probe so that we
+	 * can detect and control this hardware ...
+	 */
+	io_res = request_region(port[dev], 8, "SoundScape");
+	if (!io_res) {
+		snd_printk(KERN_ERR
+			   "sscape: can't grab port 0x%lx\n", port[dev]);
+		return -EBUSY;
+	}
+	wss_res = NULL;
+	if (sscape->type == SSCAPE_VIVO) {
+		wss_res = request_region(wss_port[dev], 4, "SoundScape");
+		if (!wss_res) {
+			snd_printk(KERN_ERR "sscape: can't grab port 0x%lx\n",
+					    wss_port[dev]);
+			err = -EBUSY;
+			goto _release_region;
+		}
+	}
+
+	/*
+	 * Grab one DMA channel ...
+	 */
+	err = request_dma(dma[dev], "SoundScape");
+	if (err < 0) {
+		snd_printk(KERN_ERR "sscape: can't grab DMA %d\n", dma[dev]);
+		goto _release_region;
+	}
+
+	spin_lock_init(&sscape->lock);
+	sscape->io_res = io_res;
+	sscape->wss_res = wss_res;
+	sscape->io_base = port[dev];
+
+	if (!detect_sscape(sscape, wss_port[dev])) {
+		printk(KERN_ERR "sscape: hardware not detected at 0x%x\n",
+			sscape->io_base);
+		err = -ENODEV;
+		goto _release_dma;
+	}
+
+	switch (sscape->type) {
+	case MEDIA_FX:
+		name = "MediaFX/SoundFX";
+		break;
+	case SSCAPE:
+		name = "Soundscape";
+		break;
+	case SSCAPE_PNP:
+		name = "Soundscape PnP";
+		break;
+	case SSCAPE_VIVO:
+		name = "Soundscape VIVO";
+		break;
+	default:
+		name = "unknown Soundscape";
+		break;
+	}
+
+	printk(KERN_INFO "sscape: %s card detected at 0x%x, using IRQ %d, DMA %d\n",
+			 name, sscape->io_base, irq[dev], dma[dev]);
+
+	/*
+	 * Check that the user didn't pass us garbage data ...
+	 */
+	irq_cfg = get_irq_config(sscape->type, irq[dev]);
+	if (irq_cfg == INVALID_IRQ) {
+		snd_printk(KERN_ERR "sscape: Invalid IRQ %d\n", irq[dev]);
+		err = -ENXIO;
+		goto _release_dma;
+	}
+
+	mpu_irq_cfg = get_irq_config(sscape->type, mpu_irq[dev]);
+	if (mpu_irq_cfg == INVALID_IRQ) {
+		snd_printk(KERN_ERR "sscape: Invalid IRQ %d\n", mpu_irq[dev]);
+		err = -ENXIO;
+		goto _release_dma;
+	}
+
+	/*
+	 * 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);
+
+	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_OPUS ? 0x40 : 0x70);
+	sscape_write_unsafe(sscape->io_base, GA_DMAA_REG, dma_cfg);
+	sscape_write_unsafe(sscape->io_base, GA_DMAB_REG, 0x20);
+
+	mpu_irq_cfg |= mpu_irq_cfg << 2;
+	val = sscape_read_unsafe(sscape->io_base, GA_HMCTL_REG) & 0xF7;
+	if (joystick[dev])
+		val |= 8;
+	sscape_write_unsafe(sscape->io_base, GA_HMCTL_REG, val | 0x10);
+	sscape_write_unsafe(sscape->io_base, GA_INTCFG_REG, 0xf0 | mpu_irq_cfg);
+	sscape_write_unsafe(sscape->io_base,
+			    GA_CDCFG_REG, 0x09 | DMA_8BIT
+			    | (dma[dev] << 4) | (irq_cfg << 1));
+	/*
+	 * Enable the master IRQ ...
+	 */
+	sscape_write_unsafe(sscape->io_base, GA_INTENA_REG, 0x80);
+
+	spin_unlock_irqrestore(&sscape->lock, flags);
+
+	/*
+	 * We have now enabled the codec chip, and so we should
+	 * detect the AD1845 device ...
+	 */
+	err = create_ad1845(card, wss_port[dev], irq[dev],
+			    dma[dev], dma2[dev]);
+	if (err < 0) {
+		snd_printk(KERN_ERR
+				"sscape: No AD1845 device at 0x%lx, IRQ %d\n",
+				wss_port[dev], irq[dev]);
+		goto _release_dma;
+	}
+	strcpy(card->driver, "SoundScape");
+	strcpy(card->shortname, name);
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s at 0x%lx, IRQ %d, DMA1 %d, DMA2 %d\n",
+		 name, sscape->chip->port, sscape->chip->irq,
+		 sscape->chip->dma1, sscape->chip->dma2);
+
+#define MIDI_DEVNUM  0
+	if (sscape->type != SSCAPE_VIVO) {
+		err = sscape_upload_bootblock(card);
+		if (err >= 0)
+			err = sscape_upload_microcode(card, err);
+
+		if (err == 0) {
+			err = create_mpu401(card, MIDI_DEVNUM, port[dev],
+					    mpu_irq[dev]);
+			if (err < 0) {
+				snd_printk(KERN_ERR "sscape: Failed to create "
+						"MPU-401 device at 0x%lx\n",
+						port[dev]);
+				goto _release_dma;
+			}
+
+			/*
+			 * Initialize mixer
+			 */
+			spin_lock_irqsave(&sscape->lock, flags);
+			sscape->midi_vol = 0;
+			host_write_ctrl_unsafe(sscape->io_base,
+						CMD_SET_MIDI_VOL, 100);
+			host_write_ctrl_unsafe(sscape->io_base,
+						sscape->midi_vol, 100);
+			host_write_ctrl_unsafe(sscape->io_base,
+						CMD_XXX_MIDI_VOL, 100);
+			host_write_ctrl_unsafe(sscape->io_base,
+						sscape->midi_vol, 100);
+			host_write_ctrl_unsafe(sscape->io_base,
+						CMD_SET_EXTMIDI, 100);
+			host_write_ctrl_unsafe(sscape->io_base,
+						0, 100);
+			host_write_ctrl_unsafe(sscape->io_base, CMD_ACK, 100);
+
+			set_midi_mode_unsafe(sscape->io_base);
+			spin_unlock_irqrestore(&sscape->lock, flags);
+		}
+	}
+
+	/*
+	 * 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;
+
+	return 0;
+
+_release_dma:
+	free_dma(dma[dev]);
+
+_release_region:
+	release_and_free_resource(wss_res);
+	release_and_free_resource(io_res);
+
+	return err;
+}
+
+
+static int snd_sscape_match(struct device *pdev, unsigned int i)
+{
+	/*
+	 * Make sure we were given ALL of the other parameters.
+	 */
+	if (port[i] == SNDRV_AUTO_PORT)
+		return 0;
+
+	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 0;
+	}
+
+	return 1;
+}
+
+static int snd_sscape_probe(struct device *pdev, unsigned int dev)
+{
+	struct snd_card *card;
+	struct soundscape *sscape;
+	int ret;
+
+	ret = snd_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+			   sizeof(struct soundscape), &card);
+	if (ret < 0)
+		return ret;
+
+	sscape = get_card_soundscape(card);
+	sscape->type = SSCAPE;
+
+	dma[dev] &= 0x03;
+
+	ret = create_sscape(dev, card);
+	if (ret < 0)
+		goto _release_card;
+
+	ret = snd_card_register(card);
+	if (ret < 0) {
+		snd_printk(KERN_ERR "sscape: Failed to register sound card\n");
+		goto _release_card;
+	}
+	dev_set_drvdata(pdev, card);
+	return 0;
+
+_release_card:
+	snd_card_free(card);
+	return ret;
+}
+
+static int snd_sscape_remove(struct device *devptr, unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(devptr));
+	return 0;
+}
+
+#define DEV_NAME "sscape"
+
+static struct isa_driver snd_sscape_driver = {
+	.match		= snd_sscape_match,
+	.probe		= snd_sscape_probe,
+	.remove		= snd_sscape_remove,
+	/* FIXME: suspend/resume */
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+#ifdef CONFIG_PNP
+static inline int get_next_autoindex(int i)
+{
+	while (i < SNDRV_CARDS && port[i] != SNDRV_AUTO_PORT)
+		++i;
+	return i;
+}
+
+
+static int sscape_pnp_detect(struct pnp_card_link *pcard,
+			     const struct pnp_card_device_id *pid)
+{
+	static int idx = 0;
+	struct pnp_dev *dev;
+	struct snd_card *card;
+	struct soundscape *sscape;
+	int ret;
+
+	/*
+	 * Allow this function to fail *quietly* if all the ISA PnP
+	 * devices were configured using module parameters instead.
+	 */
+	idx = get_next_autoindex(idx);
+	if (idx >= SNDRV_CARDS)
+		return -ENOSPC;
+
+	/*
+	 * Check that we still have room for another sound card ...
+	 */
+	dev = pnp_request_card_device(pcard, pid->devs[0].id, NULL);
+	if (!dev)
+		return -ENODEV;
+
+	if (!pnp_is_active(dev)) {
+		if (pnp_activate_dev(dev) < 0) {
+			snd_printk(KERN_INFO "sscape: device is inactive\n");
+			return -EBUSY;
+		}
+	}
+
+	/*
+	 * Create a new ALSA sound card entry, in anticipation
+	 * of detecting our hardware ...
+	 */
+	ret = snd_card_new(&pcard->card->dev,
+			   index[idx], id[idx], THIS_MODULE,
+			   sizeof(struct soundscape), &card);
+	if (ret < 0)
+		return ret;
+
+	sscape = get_card_soundscape(card);
+
+	/*
+	 * Identify card model ...
+	 */
+	if (!strncmp("ENS4081", pid->id, 7))
+		sscape->type = SSCAPE_VIVO;
+	else
+		sscape->type = SSCAPE_PNP;
+
+	/*
+	 * Read the correct parameters off the ISA PnP bus ...
+	 */
+	port[idx] = pnp_port_start(dev, 0);
+	irq[idx] = pnp_irq(dev, 0);
+	mpu_irq[idx] = pnp_irq(dev, 1);
+	dma[idx] = pnp_dma(dev, 0) & 0x03;
+	if (sscape->type == SSCAPE_PNP) {
+		dma2[idx] = dma[idx];
+		wss_port[idx] = CODEC_IO(port[idx]);
+	} else {
+		wss_port[idx] = pnp_port_start(dev, 1);
+		dma2[idx] = pnp_dma(dev, 1);
+	}
+
+	ret = create_sscape(idx, card);
+	if (ret < 0)
+		goto _release_card;
+
+	ret = snd_card_register(card);
+	if (ret < 0) {
+		snd_printk(KERN_ERR "sscape: Failed to register sound card\n");
+		goto _release_card;
+	}
+
+	pnp_set_card_drvdata(pcard, card);
+	++idx;
+	return 0;
+
+_release_card:
+	snd_card_free(card);
+	return ret;
+}
+
+static void sscape_pnp_remove(struct pnp_card_link *pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+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 = sscape_pnp_remove,
+};
+
+#endif /* CONFIG_PNP */
+
+static int __init sscape_init(void)
+{
+	int err;
+
+	err = isa_register_driver(&snd_sscape_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+	if (!err)
+		isa_registered = 1;
+
+	err = pnp_register_card_driver(&sscape_pnpc_driver);
+	if (!err)
+		pnp_registered = 1;
+
+	if (isa_registered)
+		err = 0;
+#endif
+	return err;
+}
+
+static void __exit sscape_exit(void)
+{
+#ifdef CONFIG_PNP
+	if (pnp_registered)
+		pnp_unregister_card_driver(&sscape_pnpc_driver);
+	if (isa_registered)
+#endif
+		isa_unregister_driver(&snd_sscape_driver);
+}
+
+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..601bddd
--- /dev/null
+++ b/sound/isa/wavefront/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.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..b1989fa
--- /dev/null
+++ b/sound/isa/wavefront/wavefront.c
@@ -0,0 +1,682 @@
+/*
+ *  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 <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/pnp.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/opl3.h>
+#include <sound/wss.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 bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	    /* Enable this card */
+#ifdef CONFIG_PNP
+static bool isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+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 bool use_cs4232_midi[SNDRV_CARDS];
+
+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_hw_array(cs4232_pcm_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(cs4232_pcm_port, "Port # for CS4232 PCM interface.");
+module_param_hw_array(cs4232_pcm_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(cs4232_pcm_irq, "IRQ # for CS4232 PCM interface.");
+module_param_hw_array(dma1, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for CS4232 PCM interface.");
+module_param_hw_array(dma2, int, dma, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for CS4232 PCM interface.");
+module_param_hw_array(cs4232_mpu_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(cs4232_mpu_port, "port # for CS4232 MPU-401 interface.");
+module_param_hw_array(cs4232_mpu_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(cs4232_mpu_irq, "IRQ # for CS4232 MPU-401 interface.");
+module_param_hw_array(ics2115_irq, int, irq, NULL, 0444);
+MODULE_PARM_DESC(ics2115_irq, "IRQ # for ICS2115.");
+module_param_hw_array(ics2115_port, long, ioport, NULL, 0444);
+MODULE_PARM_DESC(ics2115_port, "Port # for ICS2115.");
+module_param_hw_array(fm_port, long, ioport, 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)");
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+
+static const 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
+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;
+	int err;
+
+	/* 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)
+		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)
+			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)
+		return -EBUSY;
+
+	/* PCM/FM initialization */
+
+	pdev = acard->wss;
+
+	/* 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.
+
+	*/
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "PnP WSS pnp configure failure\n");
+		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;
+	
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "PnP ICS2115 pnp configure failure\n");
+		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;
+
+		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 (KERN_INFO "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]);
+	
+	return 0;
+}
+
+#endif /* CONFIG_PNP */
+
+static irqreturn_t snd_wavefront_ics2115_interrupt(int irq, void *dev_id)
+{
+	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 struct snd_hwdep *snd_wavefront_new_synth(struct snd_card *card,
+						 int hw_dev,
+						 snd_wavefront_card_t *acard)
+{
+	struct snd_hwdep *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 struct snd_hwdep *snd_wavefront_new_fx(struct snd_card *card,
+					      int hw_dev,
+					      snd_wavefront_card_t *acard,
+					      unsigned long port)
+
+{
+	struct snd_hwdep *fx_processor;
+
+	if (snd_wavefront_fx_start (&acard->wavefront)) {
+		snd_printk (KERN_ERR "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 struct snd_rawmidi *snd_wavefront_new_midi(struct snd_card *card,
+						  int midi_dev,
+						  snd_wavefront_card_t *acard,
+						  unsigned long port,
+						  snd_wavefront_mpu_id mpu)
+
+{
+	struct snd_rawmidi *rmidi;
+	static int first = 1;
+
+	if (first) {
+		first = 0;
+		acard->wavefront.midi.base = port;
+		if (snd_wavefront_midi_start (acard)) {
+			snd_printk (KERN_ERR "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(struct snd_card *card)
+{
+	snd_wavefront_card_t *acard = (snd_wavefront_card_t *)card->private_data;
+	
+	if (acard) {
+		release_and_free_resource(acard->wavefront.res_base);
+		if (acard->wavefront.irq > 0)
+			free_irq(acard->wavefront.irq, (void *)acard);
+	}
+}
+
+static int snd_wavefront_card_new(struct device *pdev, int dev,
+				  struct snd_card **cardp)
+{
+	struct snd_card *card;
+	snd_wavefront_card_t *acard;
+	int err;
+
+	err = snd_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+			   sizeof(snd_wavefront_card_t), &card);
+	if (err < 0)
+		return err;
+
+	acard = 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);
+	acard->wavefront.card = card;
+	card->private_free = snd_wavefront_free;
+
+	*cardp = card;
+	return 0;
+}
+
+static int
+snd_wavefront_probe (struct snd_card *card, int dev)
+{
+	snd_wavefront_card_t *acard = card->private_data;
+	struct snd_wss *chip;
+	struct snd_hwdep *wavefront_synth;
+	struct snd_rawmidi *ics2115_internal_rmidi = NULL;
+	struct snd_rawmidi *ics2115_external_rmidi = NULL;
+	struct snd_hwdep *fx_processor;
+	int hw_dev = 0, midi_dev = 0, err;
+
+	/* --------- PCM --------------- */
+
+	err = snd_wss_create(card, cs4232_pcm_port[dev], -1,
+			     cs4232_pcm_irq[dev], dma1[dev], dma2[dev],
+			     WSS_HW_DETECT, 0, &chip);
+	if (err < 0) {
+		snd_printk(KERN_ERR "can't allocate WSS device\n");
+		return err;
+	}
+
+	err = snd_wss_pcm(chip, 0);
+	if (err < 0)
+		return err;
+
+	err = snd_wss_timer(chip, 0);
+	if (err < 0)
+		return err;
+
+	/* ---------- OPL3 synth --------- */
+
+	if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+		struct snd_opl3 *opl3;
+
+		err = snd_opl3_create(card, fm_port[dev], fm_port[dev] + 2,
+				      OPL3_HW_OPL3_CS, 0, &opl3);
+		if (err < 0) {
+			snd_printk (KERN_ERR "can't allocate or detect OPL3 synth\n");
+			return err;
+		}
+
+		err = snd_opl3_hwdep_new(opl3, hw_dev, 1, NULL);
+		if (err < 0)
+			return err;
+		hw_dev++;
+	}
+
+	/* ------- ICS2115 Wavetable synth ------- */
+
+	acard->wavefront.res_base = request_region(ics2115_port[dev], 16,
+						   "ICS2115");
+	if (acard->wavefront.res_base == NULL) {
+		snd_printk(KERN_ERR "unable to grab ICS2115 i/o region 0x%lx-0x%lx\n",
+			   ics2115_port[dev], ics2115_port[dev] + 16 - 1);
+		return -EBUSY;
+	}
+	if (request_irq(ics2115_irq[dev], snd_wavefront_ics2115_interrupt,
+			0, "ICS2115", acard)) {
+		snd_printk(KERN_ERR "unable to use ICS2115 IRQ %d\n", ics2115_irq[dev]);
+		return -EBUSY;
+	}
+	
+	acard->wavefront.irq = ics2115_irq[dev];
+	acard->wavefront.base = ics2115_port[dev];
+
+	wavefront_synth = snd_wavefront_new_synth(card, hw_dev, acard);
+	if (wavefront_synth == NULL) {
+		snd_printk (KERN_ERR "can't create WaveFront synth device\n");
+		return -ENOMEM;
+	}
+
+	strcpy (wavefront_synth->name, "ICS2115 Wavetable MIDI Synthesizer");
+	wavefront_synth->iface = SNDRV_HWDEP_IFACE_ICS2115;
+	hw_dev++;
+
+	/* --------- Mixer ------------ */
+
+	err = snd_wss_mixer(chip);
+	if (err < 0) {
+		snd_printk (KERN_ERR "can't allocate mixer device\n");
+		return err;
+	}
+
+	/* -------- CS4232 MPU-401 interface -------- */
+
+	if (cs4232_mpu_port[dev] > 0 && cs4232_mpu_port[dev] != SNDRV_AUTO_PORT) {
+		err = snd_mpu401_uart_new(card, midi_dev, MPU401_HW_CS4232,
+					  cs4232_mpu_port[dev], 0,
+					  cs4232_mpu_irq[dev], NULL);
+		if (err < 0) {
+			snd_printk (KERN_ERR "can't allocate CS4232 MPU-401 device\n");
+			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 (KERN_ERR "can't setup ICS2115 internal MIDI device\n");
+			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 (KERN_ERR "can't setup ICS2115 external MIDI device\n");
+			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 (KERN_ERR "can't setup FX device\n");
+			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]);
+
+	return snd_card_register(card);
+}	
+
+static int snd_wavefront_isa_match(struct device *pdev,
+				   unsigned int dev)
+{
+	if (!enable[dev])
+		return 0;
+#ifdef CONFIG_PNP
+	if (isapnp[dev])
+		return 0;
+#endif
+	if (cs4232_pcm_port[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR "specify CS4232 port\n");
+		return 0;
+	}
+	if (ics2115_port[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR "specify ICS2115 port\n");
+		return 0;
+	}
+	return 1;
+}
+
+static int snd_wavefront_isa_probe(struct device *pdev,
+				   unsigned int dev)
+{
+	struct snd_card *card;
+	int err;
+
+	err = snd_wavefront_card_new(pdev, dev, &card);
+	if (err < 0)
+		return err;
+	if ((err = snd_wavefront_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	
+	dev_set_drvdata(pdev, card);
+	return 0;
+}
+
+static int snd_wavefront_isa_remove(struct device *devptr,
+				    unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(devptr));
+	return 0;
+}
+
+#define DEV_NAME "wavefront"
+
+static struct isa_driver snd_wavefront_driver = {
+	.match		= snd_wavefront_isa_match,
+	.probe		= snd_wavefront_isa_probe,
+	.remove		= snd_wavefront_isa_remove,
+	/* FIXME: suspend, resume */
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+
+#ifdef CONFIG_PNP
+static int snd_wavefront_pnp_detect(struct pnp_card_link *pcard,
+				    const struct pnp_card_device_id *pid)
+{
+	static int dev;
+	struct snd_card *card;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+
+	res = snd_wavefront_card_new(&pcard->card->dev, dev, &card);
+	if (res < 0)
+		return res;
+
+	if (snd_wavefront_pnp (dev, card->private_data, pcard, pid) < 0) {
+		if (cs4232_pcm_port[dev] == SNDRV_AUTO_PORT) {
+			snd_printk (KERN_ERR "isapnp detection failed\n");
+			snd_card_free (card);
+			return -ENODEV;
+		}
+	}
+
+	if ((res = snd_wavefront_probe(card, dev)) < 0)
+		return res;
+
+	pnp_set_card_drvdata(pcard, card);
+	dev++;
+	return 0;
+}
+
+static void snd_wavefront_pnp_remove(struct pnp_card_link *pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+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		= snd_wavefront_pnp_remove,
+	/* FIXME: suspend,resume */
+};
+
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_wavefront_init(void)
+{
+	int err;
+
+	err = isa_register_driver(&snd_wavefront_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+	if (!err)
+		isa_registered = 1;
+
+	err = pnp_register_card_driver(&wavefront_pnpc_driver);
+	if (!err)
+		pnp_registered = 1;
+
+	if (isa_registered)
+		err = 0;
+#endif
+	return err;
+}
+
+static void __exit alsa_card_wavefront_exit(void)
+{
+#ifdef CONFIG_PNP
+	if (pnp_registered)
+		pnp_unregister_card_driver(&wavefront_pnpc_driver);
+	if (isa_registered)
+#endif
+		isa_unregister_driver(&snd_wavefront_driver);
+}
+
+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..0608a5a
--- /dev/null
+++ b/sound/isa/wavefront/wavefront_fx.c
@@ -0,0 +1,285 @@
+/*
+ *  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 <linux/io.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/firmware.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 */
+
+#define WAIT_IDLE	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 (struct snd_hwdep *hw, struct file *file)
+
+{
+	if (!try_module_get(hw->card->module))
+		return -EFAULT;
+	file->private_data = hw;
+	return 0;
+}
+
+int 
+snd_wavefront_fx_release (struct snd_hwdep *hw, struct file *file)
+
+{
+	module_put(hw->card->module);
+	return 0;
+}
+
+int
+snd_wavefront_fx_ioctl (struct snd_hwdep *sdev, struct file *file,
+			unsigned int cmd, unsigned long arg)
+
+{
+	struct snd_card *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;
+
+	card = sdev->card;
+	if (snd_BUG_ON(!card))
+		return -ENODEV;
+	if (snd_BUG_ON(!card->private_data))
+		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 = memdup_user((unsigned char __user *)
+						r.data[3],
+						r.data[2] * sizeof(short));
+			if (IS_ERR(page_data))
+				return PTR_ERR(page_data);
+			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.
+
+   This is the approach of just dumping the whole I/O
+   sequence as a series of port/value pairs and a simple loop
+   that outputs it.
+*/
+
+int
+snd_wavefront_fx_start (snd_wavefront_t *dev)
+{
+	unsigned int i;
+	int err;
+	const struct firmware *firmware = NULL;
+
+	if (dev->fx_initialized)
+		return 0;
+
+	err = request_firmware(&firmware, "yamaha/yss225_registers.bin",
+			       dev->card->dev);
+	if (err < 0) {
+		err = -1;
+		goto out;
+	}
+
+	for (i = 0; i + 1 < firmware->size; i += 2) {
+		if (firmware->data[i] >= 8 && firmware->data[i] < 16) {
+			outb(firmware->data[i + 1],
+			     dev->base + firmware->data[i]);
+		} else if (firmware->data[i] == WAIT_IDLE) {
+			if (!wavefront_fx_idle(dev)) {
+				err = -1;
+				goto out;
+			}
+		} else {
+			snd_printk(KERN_ERR "invalid address"
+				   " in register data\n");
+			err = -1;
+			goto out;
+		}
+	}
+
+	dev->fx_initialized = 1;
+	err = 0;
+
+out:
+	release_firmware(firmware);
+	return err;
+}
+
+MODULE_FIRMWARE("yamaha/yss225_registers.bin");
diff --git a/sound/isa/wavefront/wavefront_midi.c b/sound/isa/wavefront/wavefront_midi.c
new file mode 100644
index 0000000..556b147
--- /dev/null
+++ b/sound/isa/wavefront/wavefront_midi.c
@@ -0,0 +1,575 @@
+/*
+ * 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 <linux/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 (struct snd_rawmidi_substream *substream)
+
+{
+	struct snd_card *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(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	snd_wavefront_midi_t *midi;
+	snd_wavefront_mpu_id mpu;
+
+	if (snd_BUG_ON(!substream || !substream->rmidi))
+		return -ENXIO;
+	if (snd_BUG_ON(!substream->rmidi->private_data))
+		return -ENXIO;
+
+	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(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	snd_wavefront_midi_t *midi;
+	snd_wavefront_mpu_id mpu;
+
+	if (snd_BUG_ON(!substream || !substream->rmidi))
+		return -ENXIO;
+	if (snd_BUG_ON(!substream->rmidi->private_data))
+		return -ENXIO;
+
+	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(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	snd_wavefront_midi_t *midi;
+	snd_wavefront_mpu_id mpu;
+
+	if (snd_BUG_ON(!substream || !substream->rmidi))
+		return -ENXIO;
+	if (snd_BUG_ON(!substream->rmidi->private_data))
+		return -ENXIO;
+
+	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(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	snd_wavefront_midi_t *midi;
+	snd_wavefront_mpu_id mpu;
+
+	if (snd_BUG_ON(!substream || !substream->rmidi))
+		return -ENXIO;
+	if (snd_BUG_ON(!substream->rmidi->private_data))
+		return -ENXIO;
+
+	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(struct snd_rawmidi_substream *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(struct timer_list *t)
+{
+	snd_wavefront_midi_t *midi = from_timer(midi, t, timer);
+	snd_wavefront_card_t *card = midi->timer_card;
+	unsigned long flags;
+	
+	spin_lock_irqsave (&midi->virtual, flags);
+	mod_timer(&midi->timer, 1 + jiffies);
+	spin_unlock_irqrestore (&midi->virtual, flags);
+	snd_wavefront_midi_output_write(card);
+}
+
+static void snd_wavefront_midi_output_trigger(struct snd_rawmidi_substream *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) {
+				timer_setup(&midi->timer,
+					    snd_wavefront_midi_output_timer,
+					    0);
+				mod_timer(&midi->timer, 1 + jiffies);
+			}
+			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 struct snd_rawmidi_substream *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
+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 consecutive 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;
+}
+
+const struct snd_rawmidi_ops snd_wavefront_midi_output =
+{
+	.open =		snd_wavefront_midi_output_open,
+	.close =	snd_wavefront_midi_output_close,
+	.trigger =	snd_wavefront_midi_output_trigger,
+};
+
+const struct snd_rawmidi_ops 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..0b1e4b3
--- /dev/null
+++ b/sound/isa/wavefront/wavefront_synth.c
@@ -0,0 +1,2209 @@
+/* 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 <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/sched/signal.h>
+#include <linux/firmware.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/module.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 */
+
+#define DEFAULT_OSPATH	"wavefront.os"
+static char *ospath = DEFAULT_OSPATH; /* the firmware file name */
+
+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, "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
+
+#define DPRINT(cond, ...) \
+       if ((dev->debug & (cond)) == (cond)) { \
+	     snd_printk (__VA_ARGS__); \
+       }
+#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 *);
+
+struct wavefront_command {
+	int cmd;
+	char *action;
+	unsigned int read_cnt;
+	unsigned int write_cnt;
+	int need_ack;
+};
+
+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 struct 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 struct 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 NULL;
+}
+
+static inline int
+wavefront_status (snd_wavefront_t *dev) 
+
+{
+	return inb (dev->status_port);
+}
+
+static int
+wavefront_sleep (int limit)
+
+{
+	schedule_timeout_interruptible(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;
+	struct wavefront_command *wfcmd;
+
+	if ((wfcmd = wavefront_get_command (cmd)) == NULL) {
+		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(KERN_WARNING "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);
+
+	if (header->number >= ARRAY_SIZE(dev->patch_status))
+		return -EINVAL;
+
+	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);
+
+	if (header->number >= ARRAY_SIZE(dev->prog_status))
+		return -EINVAL;
+
+	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 = 0;
+	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->number >= WF_MAX_SAMPLE)
+		return -EINVAL;
+
+	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 = ALIGN(length - written, 8);
+		}
+
+		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(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 (struct snd_hwdep *hw, struct file *file)
+
+{
+	if (!try_module_get(hw->card->module))
+		return -EFAULT;
+	file->private_data = hw;
+	return 0;
+}
+
+int 
+snd_wavefront_synth_release (struct snd_hwdep *hw, struct file *file)
+
+{
+	module_put(hw->card->module);
+	return 0;
+}
+
+int
+snd_wavefront_synth_ioctl (struct snd_hwdep *hw, struct file *file,
+			   unsigned int cmd, unsigned long arg)
+
+{
+	struct snd_card *card;
+	snd_wavefront_t *dev;
+	snd_wavefront_card_t *acard;
+	wavefront_control *wc;
+	void __user *argp = (void __user *)arg;
+	int err;
+
+	card = (struct snd_card *) hw->card;
+
+	if (snd_BUG_ON(!card))
+		return -ENODEV;
+	if (snd_BUG_ON(!card->private_data))
+		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 = memdup_user(argp, sizeof(*wc));
+		if (IS_ERR(wc))
+			return PTR_ERR(wc);
+
+		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
+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
+wavefront_should_cause_interrupt (snd_wavefront_t *dev, 
+				  int val, int port, unsigned long timeout)
+
+{
+	wait_queue_entry_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 (!dev->irq_ok && time_before(jiffies, timeout)) {
+		schedule_timeout_uninterruptible(1);
+		barrier();
+	}
+}
+
+static int
+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);
+}
+
+static int
+wavefront_download_firmware (snd_wavefront_t *dev, char *path)
+
+{
+	const unsigned char *buf;
+	int len, err;
+	int section_cnt_downloaded = 0;
+	const struct firmware *firmware;
+
+	err = request_firmware(&firmware, path, dev->card->dev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "firmware (%s) download failed!!!\n", path);
+		return 1;
+	}
+
+	len = 0;
+	buf = firmware->data;
+	for (;;) {
+		int section_length = *(signed char *)buf;
+		if (section_length == 0)
+			break;
+		if (section_length < 0 || section_length > WF_SECTION_MAX) {
+			snd_printk(KERN_ERR
+				   "invalid firmware section length %d\n",
+				   section_length);
+			goto failure;
+		}
+		buf++;
+		len++;
+
+		if (firmware->size < len + section_length) {
+			snd_printk(KERN_ERR "firmware section read error.\n");
+			goto failure;
+		}
+
+		/* Send command */
+		if (wavefront_write(dev, WFC_DOWNLOAD_OS))
+			goto failure;
+	
+		for (; section_length; section_length--) {
+			if (wavefront_write(dev, *buf))
+				goto failure;
+			buf++;
+			len++;
+		}
+	
+		/* get ACK */
+		if (!wavefront_wait(dev, STAT_CAN_READ)) {
+			snd_printk(KERN_ERR "time out for firmware ACK.\n");
+			goto failure;
+		}
+		err = inb(dev->data_port);
+		if (err != WF_ACK) {
+			snd_printk(KERN_ERR
+				   "download of section #%d not "
+				   "acknowledged, ack = 0x%x\n",
+				   section_cnt_downloaded + 1, err);
+			goto failure;
+		}
+
+		section_cnt_downloaded++;
+	}
+
+	release_firmware(firmware);
+	return 0;
+
+ failure:
+	release_firmware(firmware);
+	snd_printk(KERN_ERR "firmware download failed!!!\n");
+	return 1;
+}
+
+
+static int
+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
+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
+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;
+}
+
+MODULE_FIRMWARE(DEFAULT_OSPATH);
diff --git a/sound/isa/wss/Makefile b/sound/isa/wss/Makefile
new file mode 100644
index 0000000..454fee7
--- /dev/null
+++ b/sound/isa/wss/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2008 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-wss-lib-objs := wss_lib.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_WSS_LIB) += snd-wss-lib.o
+
diff --git a/sound/isa/wss/wss_lib.c b/sound/isa/wss/wss_lib.c
new file mode 100644
index 0000000..3a50088
--- /dev/null
+++ b/sound/isa/wss/wss_lib.c
@@ -0,0 +1,2279 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+#include <asm/dma.h>
+#include <asm/irq.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.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 const unsigned int rates[14] = {
+	5510, 6620, 8000, 9600, 11025, 16000, 18900, 22050,
+	27042, 32000, 33075, 37800, 44100, 48000
+};
+
+static const struct snd_pcm_hw_constraint_list hw_constraints_rates = {
+	.count = ARRAY_SIZE(rates),
+	.list = rates,
+	.mask = 0,
+};
+
+static int snd_wss_xrate(struct snd_pcm_runtime *runtime)
+{
+	return snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+					  &hw_constraints_rates);
+}
+
+static unsigned char snd_wss_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 */
+};
+
+static unsigned char snd_opti93x_original_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 */
+};
+
+/*
+ *  Basic I/O functions
+ */
+
+static inline void wss_outb(struct snd_wss *chip, u8 offset, u8 val)
+{
+	outb(val, chip->port + offset);
+}
+
+static inline u8 wss_inb(struct snd_wss *chip, u8 offset)
+{
+	return inb(chip->port + offset);
+}
+
+static void snd_wss_wait(struct snd_wss *chip)
+{
+	int timeout;
+
+	for (timeout = 250;
+	     timeout > 0 && (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
+	     timeout--)
+		udelay(100);
+}
+
+static void snd_wss_dout(struct snd_wss *chip, unsigned char reg,
+			 unsigned char value)
+{
+	int timeout;
+
+	for (timeout = 250;
+	     timeout > 0 && (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
+	     timeout--)
+		udelay(10);
+	wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg);
+	wss_outb(chip, CS4231P(REG), value);
+	mb();
+}
+
+void snd_wss_out(struct snd_wss *chip, unsigned char reg, unsigned char value)
+{
+	snd_wss_wait(chip);
+#ifdef CONFIG_SND_DEBUG
+	if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+		snd_printk(KERN_DEBUG "out: auto calibration time out "
+			   "- reg = 0x%x, value = 0x%x\n", reg, value);
+#endif
+	wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg);
+	wss_outb(chip, CS4231P(REG), value);
+	chip->image[reg] = value;
+	mb();
+	snd_printdd("codec out - reg 0x%x = 0x%x\n",
+			chip->mce_bit | reg, value);
+}
+EXPORT_SYMBOL(snd_wss_out);
+
+unsigned char snd_wss_in(struct snd_wss *chip, unsigned char reg)
+{
+	snd_wss_wait(chip);
+#ifdef CONFIG_SND_DEBUG
+	if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+		snd_printk(KERN_DEBUG "in: auto calibration time out "
+			   "- reg = 0x%x\n", reg);
+#endif
+	wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg);
+	mb();
+	return wss_inb(chip, CS4231P(REG));
+}
+EXPORT_SYMBOL(snd_wss_in);
+
+void snd_cs4236_ext_out(struct snd_wss *chip, unsigned char reg,
+			unsigned char val)
+{
+	wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | 0x17);
+	wss_outb(chip, CS4231P(REG),
+		 reg | (chip->image[CS4236_EXT_REG] & 0x01));
+	wss_outb(chip, CS4231P(REG), val);
+	chip->eimage[CS4236_REG(reg)] = val;
+#if 0
+	printk(KERN_DEBUG "ext out : reg = 0x%x, val = 0x%x\n", reg, val);
+#endif
+}
+EXPORT_SYMBOL(snd_cs4236_ext_out);
+
+unsigned char snd_cs4236_ext_in(struct snd_wss *chip, unsigned char reg)
+{
+	wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | 0x17);
+	wss_outb(chip, CS4231P(REG),
+		 reg | (chip->image[CS4236_EXT_REG] & 0x01));
+#if 1
+	return wss_inb(chip, CS4231P(REG));
+#else
+	{
+		unsigned char res;
+		res = wss_inb(chip, CS4231P(REG));
+		printk(KERN_DEBUG "ext in : reg = 0x%x, val = 0x%x\n",
+		       reg, res);
+		return res;
+	}
+#endif
+}
+EXPORT_SYMBOL(snd_cs4236_ext_in);
+
+#if 0
+
+static void snd_wss_debug(struct snd_wss *chip)
+{
+	printk(KERN_DEBUG
+		"CS4231 REGS:      INDEX = 0x%02x  "
+		"                 STATUS = 0x%02x\n",
+					wss_inb(chip, CS4231P(REGSEL)),
+					wss_inb(chip, CS4231P(STATUS)));
+	printk(KERN_DEBUG
+		"  0x00: left input      = 0x%02x  "
+		"  0x10: alt 1 (CFIG 2)  = 0x%02x\n",
+					snd_wss_in(chip, 0x00),
+					snd_wss_in(chip, 0x10));
+	printk(KERN_DEBUG
+		"  0x01: right input     = 0x%02x  "
+		"  0x11: alt 2 (CFIG 3)  = 0x%02x\n",
+					snd_wss_in(chip, 0x01),
+					snd_wss_in(chip, 0x11));
+	printk(KERN_DEBUG
+		"  0x02: GF1 left input  = 0x%02x  "
+		"  0x12: left line in    = 0x%02x\n",
+					snd_wss_in(chip, 0x02),
+					snd_wss_in(chip, 0x12));
+	printk(KERN_DEBUG
+		"  0x03: GF1 right input = 0x%02x  "
+		"  0x13: right line in   = 0x%02x\n",
+					snd_wss_in(chip, 0x03),
+					snd_wss_in(chip, 0x13));
+	printk(KERN_DEBUG
+		"  0x04: CD left input   = 0x%02x  "
+		"  0x14: timer low       = 0x%02x\n",
+					snd_wss_in(chip, 0x04),
+					snd_wss_in(chip, 0x14));
+	printk(KERN_DEBUG
+		"  0x05: CD right input  = 0x%02x  "
+		"  0x15: timer high      = 0x%02x\n",
+					snd_wss_in(chip, 0x05),
+					snd_wss_in(chip, 0x15));
+	printk(KERN_DEBUG
+		"  0x06: left output     = 0x%02x  "
+		"  0x16: left MIC (PnP)  = 0x%02x\n",
+					snd_wss_in(chip, 0x06),
+					snd_wss_in(chip, 0x16));
+	printk(KERN_DEBUG
+		"  0x07: right output    = 0x%02x  "
+		"  0x17: right MIC (PnP) = 0x%02x\n",
+					snd_wss_in(chip, 0x07),
+					snd_wss_in(chip, 0x17));
+	printk(KERN_DEBUG
+		"  0x08: playback format = 0x%02x  "
+		"  0x18: IRQ status      = 0x%02x\n",
+					snd_wss_in(chip, 0x08),
+					snd_wss_in(chip, 0x18));
+	printk(KERN_DEBUG
+		"  0x09: iface (CFIG 1)  = 0x%02x  "
+		"  0x19: left line out   = 0x%02x\n",
+					snd_wss_in(chip, 0x09),
+					snd_wss_in(chip, 0x19));
+	printk(KERN_DEBUG
+		"  0x0a: pin control     = 0x%02x  "
+		"  0x1a: mono control    = 0x%02x\n",
+					snd_wss_in(chip, 0x0a),
+					snd_wss_in(chip, 0x1a));
+	printk(KERN_DEBUG
+		"  0x0b: init & status   = 0x%02x  "
+		"  0x1b: right line out  = 0x%02x\n",
+					snd_wss_in(chip, 0x0b),
+					snd_wss_in(chip, 0x1b));
+	printk(KERN_DEBUG
+		"  0x0c: revision & mode = 0x%02x  "
+		"  0x1c: record format   = 0x%02x\n",
+					snd_wss_in(chip, 0x0c),
+					snd_wss_in(chip, 0x1c));
+	printk(KERN_DEBUG
+		"  0x0d: loopback        = 0x%02x  "
+		"  0x1d: var freq (PnP)  = 0x%02x\n",
+					snd_wss_in(chip, 0x0d),
+					snd_wss_in(chip, 0x1d));
+	printk(KERN_DEBUG
+		"  0x0e: ply upr count   = 0x%02x  "
+		"  0x1e: ply lwr count   = 0x%02x\n",
+					snd_wss_in(chip, 0x0e),
+					snd_wss_in(chip, 0x1e));
+	printk(KERN_DEBUG
+		"  0x0f: rec upr count   = 0x%02x  "
+		"  0x1f: rec lwr count   = 0x%02x\n",
+					snd_wss_in(chip, 0x0f),
+					snd_wss_in(chip, 0x1f));
+}
+
+#endif
+
+/*
+ *  CS4231 detection / MCE routines
+ */
+
+static void snd_wss_busy_wait(struct snd_wss *chip)
+{
+	int timeout;
+
+	/* huh.. looks like this sequence is proper for CS4231A chip (GUS MAX) */
+	for (timeout = 5; timeout > 0; timeout--)
+		wss_inb(chip, CS4231P(REGSEL));
+	/* end of cleanup sequence */
+	for (timeout = 25000;
+	     timeout > 0 && (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
+	     timeout--)
+		udelay(10);
+}
+
+void snd_wss_mce_up(struct snd_wss *chip)
+{
+	unsigned long flags;
+	int timeout;
+
+	snd_wss_wait(chip);
+#ifdef CONFIG_SND_DEBUG
+	if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+		snd_printk(KERN_DEBUG
+			   "mce_up - auto calibration time out (0)\n");
+#endif
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->mce_bit |= CS4231_MCE;
+	timeout = wss_inb(chip, CS4231P(REGSEL));
+	if (timeout == 0x80)
+		snd_printk(KERN_DEBUG "mce_up [0x%lx]: "
+			   "serious init problem - codec still busy\n",
+			   chip->port);
+	if (!(timeout & CS4231_MCE))
+		wss_outb(chip, CS4231P(REGSEL),
+			 chip->mce_bit | (timeout & 0x1f));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+EXPORT_SYMBOL(snd_wss_mce_up);
+
+void snd_wss_mce_down(struct snd_wss *chip)
+{
+	unsigned long flags;
+	unsigned long end_time;
+	int timeout;
+	int hw_mask = WSS_HW_CS4231_MASK | WSS_HW_CS4232_MASK | WSS_HW_AD1848;
+
+	snd_wss_busy_wait(chip);
+
+#ifdef CONFIG_SND_DEBUG
+	if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+		snd_printk(KERN_DEBUG "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 = wss_inb(chip, CS4231P(REGSEL));
+	wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (timeout == 0x80)
+		snd_printk(KERN_DEBUG "mce_down [0x%lx]: "
+			   "serious init problem - codec still busy\n",
+			   chip->port);
+	if ((timeout & CS4231_MCE) == 0 || !(chip->hardware & hw_mask))
+		return;
+
+	/*
+	 * Wait for (possible -- during init auto-calibration may not be set)
+	 * calibration process to start. Needs up to 5 sample periods on AD1848
+	 * which at the slowest possible rate of 5.5125 kHz means 907 us.
+	 */
+	msleep(1);
+
+	snd_printdd("(1) jiffies = %lu\n", jiffies);
+
+	/* check condition up to 250 ms */
+	end_time = jiffies + msecs_to_jiffies(250);
+	while (snd_wss_in(chip, CS4231_TEST_INIT) &
+		CS4231_CALIB_IN_PROGRESS) {
+
+		if (time_after(jiffies, end_time)) {
+			snd_printk(KERN_ERR "mce_down - "
+					"auto calibration time out (2)\n");
+			return;
+		}
+		msleep(1);
+	}
+
+	snd_printdd("(2) jiffies = %lu\n", jiffies);
+
+	/* check condition up to 100 ms */
+	end_time = jiffies + msecs_to_jiffies(100);
+	while (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) {
+		if (time_after(jiffies, end_time)) {
+			snd_printk(KERN_ERR "mce_down - auto calibration time out (3)\n");
+			return;
+		}
+		msleep(1);
+	}
+
+	snd_printdd("(3) jiffies = %lu\n", jiffies);
+	snd_printd("mce_down - exit = 0x%x\n", wss_inb(chip, CS4231P(REGSEL)));
+}
+EXPORT_SYMBOL(snd_wss_mce_down);
+
+static unsigned int snd_wss_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_wss_trigger(struct snd_pcm_substream *substream,
+			   int cmd)
+{
+	struct snd_wss *chip = snd_pcm_substream_chip(substream);
+	int result = 0;
+	unsigned int what;
+	struct snd_pcm_substream *s;
+	int do_start;
+
+	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_entry(s, substream) {
+		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_wss_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
+	spin_unlock(&chip->reg_lock);
+#if 0
+	snd_wss_debug(chip);
+#endif
+	return result;
+}
+
+/*
+ *  CODEC I/O
+ */
+
+static unsigned char snd_wss_get_rate(unsigned int rate)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(rates); i++)
+		if (rate == rates[i])
+			return freq_bits[i];
+	// snd_BUG();
+	return freq_bits[ARRAY_SIZE(rates) - 1];
+}
+
+static unsigned char snd_wss_get_format(struct snd_wss *chip,
+					snd_pcm_format_t 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(KERN_DEBUG "get_format: 0x%x (mode=0x%x)\n", format, mode);
+#endif
+	return rformat;
+}
+
+static void snd_wss_calibrate_mute(struct snd_wss *chip, int mute)
+{
+	unsigned long flags;
+
+	mute = mute ? 0x80 : 0;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->calibrate_mute == mute) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return;
+	}
+	if (!mute) {
+		snd_wss_dout(chip, CS4231_LEFT_INPUT,
+			     chip->image[CS4231_LEFT_INPUT]);
+		snd_wss_dout(chip, CS4231_RIGHT_INPUT,
+			     chip->image[CS4231_RIGHT_INPUT]);
+		snd_wss_dout(chip, CS4231_LOOPBACK,
+			     chip->image[CS4231_LOOPBACK]);
+	} else {
+		snd_wss_dout(chip, CS4231_LEFT_INPUT,
+			     0);
+		snd_wss_dout(chip, CS4231_RIGHT_INPUT,
+			     0);
+		snd_wss_dout(chip, CS4231_LOOPBACK,
+			     0xfd);
+	}
+
+	snd_wss_dout(chip, CS4231_AUX1_LEFT_INPUT,
+		     mute | chip->image[CS4231_AUX1_LEFT_INPUT]);
+	snd_wss_dout(chip, CS4231_AUX1_RIGHT_INPUT,
+		     mute | chip->image[CS4231_AUX1_RIGHT_INPUT]);
+	snd_wss_dout(chip, CS4231_AUX2_LEFT_INPUT,
+		     mute | chip->image[CS4231_AUX2_LEFT_INPUT]);
+	snd_wss_dout(chip, CS4231_AUX2_RIGHT_INPUT,
+		     mute | chip->image[CS4231_AUX2_RIGHT_INPUT]);
+	snd_wss_dout(chip, CS4231_LEFT_OUTPUT,
+		     mute | chip->image[CS4231_LEFT_OUTPUT]);
+	snd_wss_dout(chip, CS4231_RIGHT_OUTPUT,
+		     mute | chip->image[CS4231_RIGHT_OUTPUT]);
+	if (!(chip->hardware & WSS_HW_AD1848_MASK)) {
+		snd_wss_dout(chip, CS4231_LEFT_LINE_IN,
+			     mute | chip->image[CS4231_LEFT_LINE_IN]);
+		snd_wss_dout(chip, CS4231_RIGHT_LINE_IN,
+			     mute | chip->image[CS4231_RIGHT_LINE_IN]);
+		snd_wss_dout(chip, CS4231_MONO_CTRL,
+			     mute ? 0xc0 : chip->image[CS4231_MONO_CTRL]);
+	}
+	if (chip->hardware == WSS_HW_INTERWAVE) {
+		snd_wss_dout(chip, CS4231_LEFT_MIC_INPUT,
+			     mute | chip->image[CS4231_LEFT_MIC_INPUT]);
+		snd_wss_dout(chip, CS4231_RIGHT_MIC_INPUT,
+			     mute | chip->image[CS4231_RIGHT_MIC_INPUT]);
+		snd_wss_dout(chip, CS4231_LINE_LEFT_OUTPUT,
+			     mute | chip->image[CS4231_LINE_LEFT_OUTPUT]);
+		snd_wss_dout(chip, CS4231_LINE_RIGHT_OUTPUT,
+			     mute | chip->image[CS4231_LINE_RIGHT_OUTPUT]);
+	}
+	chip->calibrate_mute = mute;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_wss_playback_format(struct snd_wss *chip,
+				       struct snd_pcm_hw_params *params,
+				       unsigned char pdfr)
+{
+	unsigned long flags;
+	int full_calib = 1;
+
+	mutex_lock(&chip->mce_mutex);
+	if (chip->hardware == WSS_HW_CS4231A ||
+	    (chip->hardware & WSS_HW_CS4232_MASK)) {
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		if ((chip->image[CS4231_PLAYBK_FORMAT] & 0x0f) == (pdfr & 0x0f)) {	/* rate is same? */
+			snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+				    chip->image[CS4231_ALT_FEATURE_1] | 0x10);
+			chip->image[CS4231_PLAYBK_FORMAT] = pdfr;
+			snd_wss_out(chip, CS4231_PLAYBK_FORMAT,
+				    chip->image[CS4231_PLAYBK_FORMAT]);
+			snd_wss_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);
+	} else if (chip->hardware == WSS_HW_AD1845) {
+		unsigned rate = params_rate(params);
+
+		/*
+		 * 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.
+		 */
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		snd_wss_out(chip, CS4231_PLAYBK_FORMAT, (pdfr & 0xf0));
+		snd_wss_out(chip, AD1845_UPR_FREQ_SEL, (rate >> 8) & 0xff);
+		snd_wss_out(chip, AD1845_LWR_FREQ_SEL, rate & 0xff);
+		full_calib = 0;
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+	}
+	if (full_calib) {
+		snd_wss_mce_up(chip);
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		if (chip->hardware != WSS_HW_INTERWAVE && !chip->single_dma) {
+			if (chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE)
+				pdfr = (pdfr & 0xf0) |
+				       (chip->image[CS4231_REC_FORMAT] & 0x0f);
+		} else {
+			chip->image[CS4231_PLAYBK_FORMAT] = pdfr;
+		}
+		snd_wss_out(chip, CS4231_PLAYBK_FORMAT, pdfr);
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		if (chip->hardware == WSS_HW_OPL3SA2)
+			udelay(100);	/* this seems to help */
+		snd_wss_mce_down(chip);
+	}
+	mutex_unlock(&chip->mce_mutex);
+}
+
+static void snd_wss_capture_format(struct snd_wss *chip,
+				   struct snd_pcm_hw_params *params,
+				   unsigned char cdfr)
+{
+	unsigned long flags;
+	int full_calib = 1;
+
+	mutex_lock(&chip->mce_mutex);
+	if (chip->hardware == WSS_HW_CS4231A ||
+	    (chip->hardware & WSS_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_wss_out(chip, CS4231_ALT_FEATURE_1,
+				chip->image[CS4231_ALT_FEATURE_1] | 0x20);
+			snd_wss_out(chip, CS4231_REC_FORMAT,
+				chip->image[CS4231_REC_FORMAT] = cdfr);
+			snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+				chip->image[CS4231_ALT_FEATURE_1] &= ~0x20);
+			full_calib = 0;
+		}
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+	} else if (chip->hardware == WSS_HW_AD1845) {
+		unsigned rate = params_rate(params);
+
+		/*
+		 * Program the AD1845 correctly for the capture 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.
+		 */
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		snd_wss_out(chip, CS4231_REC_FORMAT, (cdfr & 0xf0));
+		snd_wss_out(chip, AD1845_UPR_FREQ_SEL, (rate >> 8) & 0xff);
+		snd_wss_out(chip, AD1845_LWR_FREQ_SEL, rate & 0xff);
+		full_calib = 0;
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+	}
+	if (full_calib) {
+		snd_wss_mce_up(chip);
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		if (chip->hardware != WSS_HW_INTERWAVE &&
+		    !(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) {
+			if (chip->single_dma)
+				snd_wss_out(chip, CS4231_PLAYBK_FORMAT, cdfr);
+			else
+				snd_wss_out(chip, CS4231_PLAYBK_FORMAT,
+				   (chip->image[CS4231_PLAYBK_FORMAT] & 0xf0) |
+				   (cdfr & 0x0f));
+			spin_unlock_irqrestore(&chip->reg_lock, flags);
+			snd_wss_mce_down(chip);
+			snd_wss_mce_up(chip);
+			spin_lock_irqsave(&chip->reg_lock, flags);
+		}
+		if (chip->hardware & WSS_HW_AD1848_MASK)
+			snd_wss_out(chip, CS4231_PLAYBK_FORMAT, cdfr);
+		else
+			snd_wss_out(chip, CS4231_REC_FORMAT, cdfr);
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		snd_wss_mce_down(chip);
+	}
+	mutex_unlock(&chip->mce_mutex);
+}
+
+/*
+ *  Timer interface
+ */
+
+static unsigned long snd_wss_timer_resolution(struct snd_timer *timer)
+{
+	struct snd_wss *chip = snd_timer_chip(timer);
+	if (chip->hardware & WSS_HW_CS4236B_MASK)
+		return 14467;
+	else
+		return chip->image[CS4231_PLAYBK_FORMAT] & 1 ? 9969 : 9920;
+}
+
+static int snd_wss_timer_start(struct snd_timer *timer)
+{
+	unsigned long flags;
+	unsigned int ticks;
+	struct snd_wss *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]) {
+		chip->image[CS4231_TIMER_HIGH] = (unsigned char) (ticks >> 8);
+		snd_wss_out(chip, CS4231_TIMER_HIGH,
+			    chip->image[CS4231_TIMER_HIGH]);
+		chip->image[CS4231_TIMER_LOW] = (unsigned char) ticks;
+		snd_wss_out(chip, CS4231_TIMER_LOW,
+			    chip->image[CS4231_TIMER_LOW]);
+		snd_wss_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_wss_timer_stop(struct snd_timer *timer)
+{
+	unsigned long flags;
+	struct snd_wss *chip = snd_timer_chip(timer);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->image[CS4231_ALT_FEATURE_1] &= ~CS4231_TIMER_ENABLE;
+	snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+		    chip->image[CS4231_ALT_FEATURE_1]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static void snd_wss_init(struct snd_wss *chip)
+{
+	unsigned long flags;
+
+	snd_wss_calibrate_mute(chip, 1);
+	snd_wss_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printk(KERN_DEBUG "init: (1)\n");
+#endif
+	snd_wss_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_wss_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_wss_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printk(KERN_DEBUG "init: (2)\n");
+#endif
+
+	snd_wss_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->image[CS4231_IFACE_CTRL] &= ~CS4231_AUTOCALIB;
+	snd_wss_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
+	snd_wss_out(chip,
+		    CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_wss_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printk(KERN_DEBUG "init: (3) - afei = 0x%x\n",
+		   chip->image[CS4231_ALT_FEATURE_1]);
+#endif
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_wss_out(chip, CS4231_ALT_FEATURE_2,
+		    chip->image[CS4231_ALT_FEATURE_2]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	snd_wss_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_wss_out(chip, CS4231_PLAYBK_FORMAT,
+		    chip->image[CS4231_PLAYBK_FORMAT]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_wss_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printk(KERN_DEBUG "init: (4)\n");
+#endif
+
+	snd_wss_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (!(chip->hardware & WSS_HW_AD1848_MASK))
+		snd_wss_out(chip, CS4231_REC_FORMAT,
+			    chip->image[CS4231_REC_FORMAT]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_wss_mce_down(chip);
+	snd_wss_calibrate_mute(chip, 0);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printk(KERN_DEBUG "init: (5)\n");
+#endif
+}
+
+static int snd_wss_open(struct snd_wss *chip, unsigned int mode)
+{
+	unsigned long flags;
+
+	mutex_lock(&chip->open_mutex);
+	if ((chip->mode & mode) ||
+	    ((chip->mode & WSS_MODE_OPEN) && chip->single_dma)) {
+		mutex_unlock(&chip->open_mutex);
+		return -EAGAIN;
+	}
+	if (chip->mode & WSS_MODE_OPEN) {
+		chip->mode |= mode;
+		mutex_unlock(&chip->open_mutex);
+		return 0;
+	}
+	/* ok. now enable and ack CODEC IRQ */
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (!(chip->hardware & WSS_HW_AD1848_MASK)) {
+		snd_wss_out(chip, CS4231_IRQ_STATUS,
+			    CS4231_PLAYBACK_IRQ |
+			    CS4231_RECORD_IRQ |
+			    CS4231_TIMER_IRQ);
+		snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
+	}
+	wss_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
+	wss_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
+	chip->image[CS4231_PIN_CTRL] |= CS4231_IRQ_ENABLE;
+	snd_wss_out(chip, CS4231_PIN_CTRL, chip->image[CS4231_PIN_CTRL]);
+	if (!(chip->hardware & WSS_HW_AD1848_MASK)) {
+		snd_wss_out(chip, CS4231_IRQ_STATUS,
+			    CS4231_PLAYBACK_IRQ |
+			    CS4231_RECORD_IRQ |
+			    CS4231_TIMER_IRQ);
+		snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	chip->mode = mode;
+	mutex_unlock(&chip->open_mutex);
+	return 0;
+}
+
+static void snd_wss_close(struct snd_wss *chip, unsigned int mode)
+{
+	unsigned long flags;
+
+	mutex_lock(&chip->open_mutex);
+	chip->mode &= ~mode;
+	if (chip->mode & WSS_MODE_OPEN) {
+		mutex_unlock(&chip->open_mutex);
+		return;
+	}
+	/* disable IRQ */
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (!(chip->hardware & WSS_HW_AD1848_MASK))
+		snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
+	wss_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
+	wss_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
+	chip->image[CS4231_PIN_CTRL] &= ~CS4231_IRQ_ENABLE;
+	snd_wss_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_wss_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_wss_out(chip, CS4231_IFACE_CTRL,
+			    chip->image[CS4231_IFACE_CTRL]);
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		snd_wss_mce_down(chip);
+		spin_lock_irqsave(&chip->reg_lock, flags);
+	}
+
+	/* clear IRQ again */
+	if (!(chip->hardware & WSS_HW_AD1848_MASK))
+		snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
+	wss_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
+	wss_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	chip->mode = 0;
+	mutex_unlock(&chip->open_mutex);
+}
+
+/*
+ *  timer open/close
+ */
+
+static int snd_wss_timer_open(struct snd_timer *timer)
+{
+	struct snd_wss *chip = snd_timer_chip(timer);
+	snd_wss_open(chip, WSS_MODE_TIMER);
+	return 0;
+}
+
+static int snd_wss_timer_close(struct snd_timer *timer)
+{
+	struct snd_wss *chip = snd_timer_chip(timer);
+	snd_wss_close(chip, WSS_MODE_TIMER);
+	return 0;
+}
+
+static struct snd_timer_hardware snd_wss_timer_table =
+{
+	.flags =	SNDRV_TIMER_HW_AUTO,
+	.resolution =	9945,
+	.ticks =	65535,
+	.open =		snd_wss_timer_open,
+	.close =	snd_wss_timer_close,
+	.c_resolution = snd_wss_timer_resolution,
+	.start =	snd_wss_timer_start,
+	.stop =		snd_wss_timer_stop,
+};
+
+/*
+ *  ok.. exported functions..
+ */
+
+static int snd_wss_playback_hw_params(struct snd_pcm_substream *substream,
+					 struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_wss *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_wss_get_format(chip, params_format(hw_params),
+				params_channels(hw_params)) |
+				snd_wss_get_rate(params_rate(hw_params));
+	chip->set_playback_format(chip, hw_params, new_pdfr);
+	return 0;
+}
+
+static int snd_wss_playback_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_wss_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_wss *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *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_wss_get_count(chip->image[CS4231_PLAYBK_FORMAT], count) - 1;
+	snd_wss_out(chip, CS4231_PLY_LWR_CNT, (unsigned char) count);
+	snd_wss_out(chip, CS4231_PLY_UPR_CNT, (unsigned char) (count >> 8));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+#if 0
+	snd_wss_debug(chip);
+#endif
+	return 0;
+}
+
+static int snd_wss_capture_hw_params(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_wss *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_wss_get_format(chip, params_format(hw_params),
+			   params_channels(hw_params)) |
+			   snd_wss_get_rate(params_rate(hw_params));
+	chip->set_capture_format(chip, hw_params, new_cdfr);
+	return 0;
+}
+
+static int snd_wss_capture_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_wss_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_wss *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *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);
+	if (chip->hardware & WSS_HW_AD1848_MASK)
+		count = snd_wss_get_count(chip->image[CS4231_PLAYBK_FORMAT],
+					  count);
+	else
+		count = snd_wss_get_count(chip->image[CS4231_REC_FORMAT],
+					  count);
+	count--;
+	if (chip->single_dma && chip->hardware != WSS_HW_INTERWAVE) {
+		snd_wss_out(chip, CS4231_PLY_LWR_CNT, (unsigned char) count);
+		snd_wss_out(chip, CS4231_PLY_UPR_CNT,
+			    (unsigned char) (count >> 8));
+	} else {
+		snd_wss_out(chip, CS4231_REC_LWR_CNT, (unsigned char) count);
+		snd_wss_out(chip, CS4231_REC_UPR_CNT,
+			    (unsigned char) (count >> 8));
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+void snd_wss_overrange(struct snd_wss *chip)
+{
+	unsigned long flags;
+	unsigned char res;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	res = snd_wss_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++;
+}
+EXPORT_SYMBOL(snd_wss_overrange);
+
+irqreturn_t snd_wss_interrupt(int irq, void *dev_id)
+{
+	struct snd_wss *chip = dev_id;
+	unsigned char status;
+
+	if (chip->hardware & WSS_HW_AD1848_MASK)
+		/* pretend it was the only possible irq for AD1848 */
+		status = CS4231_PLAYBACK_IRQ;
+	else
+		status = snd_wss_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 != WSS_HW_INTERWAVE) {
+		if (status & CS4231_PLAYBACK_IRQ) {
+			if (chip->mode & WSS_MODE_PLAY) {
+				if (chip->playback_substream)
+					snd_pcm_period_elapsed(chip->playback_substream);
+			}
+			if (chip->mode & WSS_MODE_RECORD) {
+				if (chip->capture_substream) {
+					snd_wss_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_wss_overrange(chip);
+				snd_pcm_period_elapsed(chip->capture_substream);
+			}
+		}
+	}
+
+	spin_lock(&chip->reg_lock);
+	status = ~CS4231_ALL_IRQS | ~status;
+	if (chip->hardware & WSS_HW_AD1848_MASK)
+		wss_outb(chip, CS4231P(STATUS), 0);
+	else
+		snd_wss_out(chip, CS4231_IRQ_STATUS, status);
+	spin_unlock(&chip->reg_lock);
+	return IRQ_HANDLED;
+}
+EXPORT_SYMBOL(snd_wss_interrupt);
+
+static snd_pcm_uframes_t snd_wss_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_wss *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_wss_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_wss *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);
+}
+
+/*
+
+ */
+
+static int snd_ad1848_probe(struct snd_wss *chip)
+{
+	unsigned long timeout = jiffies + msecs_to_jiffies(1000);
+	unsigned long flags;
+	unsigned char r;
+	unsigned short hardware = 0;
+	int err = 0;
+	int i;
+
+	while (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) {
+		if (time_after(jiffies, timeout))
+			return -ENODEV;
+		cond_resched();
+	}
+	spin_lock_irqsave(&chip->reg_lock, flags);
+
+	/* set CS423x MODE 1 */
+	snd_wss_dout(chip, CS4231_MISC_INFO, 0);
+
+	snd_wss_dout(chip, CS4231_RIGHT_INPUT, 0x45); /* 0x55 & ~0x10 */
+	r = snd_wss_in(chip, CS4231_RIGHT_INPUT);
+	if (r != 0x45) {
+		/* RMGE always high on AD1847 */
+		if ((r & ~CS4231_ENABLE_MIC_GAIN) != 0x45) {
+			err = -ENODEV;
+			goto out;
+		}
+		hardware = WSS_HW_AD1847;
+	} else {
+		snd_wss_dout(chip, CS4231_LEFT_INPUT,  0xaa);
+		r = snd_wss_in(chip, CS4231_LEFT_INPUT);
+		/* L/RMGE always low on AT2320 */
+		if ((r | CS4231_ENABLE_MIC_GAIN) != 0xaa) {
+			err = -ENODEV;
+			goto out;
+		}
+	}
+
+	/* clear pending IRQ */
+	wss_inb(chip, CS4231P(STATUS));
+	wss_outb(chip, CS4231P(STATUS), 0);
+	mb();
+
+	if ((chip->hardware & WSS_HW_TYPE_MASK) != WSS_HW_DETECT)
+		goto out;
+
+	if (hardware) {
+		chip->hardware = hardware;
+		goto out;
+	}
+
+	r = snd_wss_in(chip, CS4231_MISC_INFO);
+
+	/* set CS423x MODE 2 */
+	snd_wss_dout(chip, CS4231_MISC_INFO, CS4231_MODE2);
+	for (i = 0; i < 16; i++) {
+		if (snd_wss_in(chip, i) != snd_wss_in(chip, 16 + i)) {
+			/* we have more than 16 registers: check ID */
+			if ((r & 0xf) != 0xa)
+				goto out_mode;
+			/*
+			 * on CMI8330, CS4231_VERSION is volume control and
+			 * can be set to 0
+			 */
+			snd_wss_dout(chip, CS4231_VERSION, 0);
+			r = snd_wss_in(chip, CS4231_VERSION) & 0xe7;
+			if (!r)
+				chip->hardware = WSS_HW_CMI8330;
+			goto out_mode;
+		}
+	}
+	if (r & 0x80)
+		chip->hardware = WSS_HW_CS4248;
+	else
+		chip->hardware = WSS_HW_AD1848;
+out_mode:
+	snd_wss_dout(chip, CS4231_MISC_INFO, 0);
+out:
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return err;
+}
+
+static int snd_wss_probe(struct snd_wss *chip)
+{
+	unsigned long flags;
+	int i, id, rev, regnum;
+	unsigned char *ptr;
+	unsigned int hw;
+
+	id = snd_ad1848_probe(chip);
+	if (id < 0)
+		return id;
+
+	hw = chip->hardware;
+	if ((hw & WSS_HW_TYPE_MASK) == WSS_HW_DETECT) {
+		for (i = 0; i < 50; i++) {
+			mb();
+			if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+				msleep(2);
+			else {
+				spin_lock_irqsave(&chip->reg_lock, flags);
+				snd_wss_out(chip, CS4231_MISC_INFO,
+					    CS4231_MODE2);
+				id = snd_wss_in(chip, CS4231_MISC_INFO) & 0x0f;
+				spin_unlock_irqrestore(&chip->reg_lock, flags);
+				if (id == 0x0a)
+					break;	/* this is valid value */
+			}
+		}
+		snd_printdd("wss: port = 0x%lx, id = 0x%x\n", chip->port, id);
+		if (id != 0x0a)
+			return -ENODEV;	/* no valid device found */
+
+		rev = snd_wss_in(chip, CS4231_VERSION) & 0xe7;
+		snd_printdd("CS4231: VERSION (I25) = 0x%x\n", rev);
+		if (rev == 0x80) {
+			unsigned char tmp = snd_wss_in(chip, 23);
+			snd_wss_out(chip, 23, ~tmp);
+			if (snd_wss_in(chip, 23) != tmp)
+				chip->hardware = WSS_HW_AD1845;
+			else
+				chip->hardware = WSS_HW_CS4231;
+		} else if (rev == 0xa0) {
+			chip->hardware = WSS_HW_CS4231A;
+		} else if (rev == 0xa2) {
+			chip->hardware = WSS_HW_CS4232;
+		} else if (rev == 0xb2) {
+			chip->hardware = WSS_HW_CS4232A;
+		} else if (rev == 0x83) {
+			chip->hardware = WSS_HW_CS4236;
+		} else if (rev == 0x03) {
+			chip->hardware = WSS_HW_CS4236B;
+		} else {
+			snd_printk(KERN_ERR
+				   "unknown CS chip with version 0x%x\n", rev);
+			return -ENODEV;		/* unknown CS4231 chip? */
+		}
+	}
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	wss_inb(chip, CS4231P(STATUS));	/* clear any pendings IRQ */
+	wss_outb(chip, CS4231P(STATUS), 0);
+	mb();
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	if (!(chip->hardware & WSS_HW_AD1848_MASK))
+		chip->image[CS4231_MISC_INFO] = CS4231_MODE2;
+	switch (chip->hardware) {
+	case WSS_HW_INTERWAVE:
+		chip->image[CS4231_MISC_INFO] = CS4231_IW_MODE3;
+		break;
+	case WSS_HW_CS4235:
+	case WSS_HW_CS4236B:
+	case WSS_HW_CS4237B:
+	case WSS_HW_CS4238B:
+	case WSS_HW_CS4239:
+		if (hw == WSS_HW_DETECT3)
+			chip->image[CS4231_MISC_INFO] = CS4231_4236_MODE3;
+		else
+			chip->hardware = WSS_HW_CS4236;
+		break;
+	}
+
+	chip->image[CS4231_IFACE_CTRL] =
+	    (chip->image[CS4231_IFACE_CTRL] & ~CS4231_SINGLE_DMA) |
+	    (chip->single_dma ? CS4231_SINGLE_DMA : 0);
+	if (chip->hardware != WSS_HW_OPTI93X) {
+		chip->image[CS4231_ALT_FEATURE_1] = 0x80;
+		chip->image[CS4231_ALT_FEATURE_2] =
+			chip->hardware == WSS_HW_INTERWAVE ? 0xc2 : 0x01;
+	}
+	/* enable fine grained frequency selection */
+	if (chip->hardware == WSS_HW_AD1845)
+		chip->image[AD1845_PWR_DOWN] = 8;
+
+	ptr = (unsigned char *) &chip->image;
+	regnum = (chip->hardware & WSS_HW_AD1848_MASK) ? 16 : 32;
+	snd_wss_mce_down(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	for (i = 0; i < regnum; i++)	/* ok.. fill all registers */
+		snd_wss_out(chip, i, *ptr++);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_wss_mce_up(chip);
+	snd_wss_mce_down(chip);
+
+	mdelay(2);
+
+	/* ok.. try check hardware version for CS4236+ chips */
+	if ((hw & WSS_HW_TYPE_MASK) == WSS_HW_DETECT) {
+		if (chip->hardware == WSS_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 = WSS_HW_CS4235;
+				switch (id >> 5) {
+				case 4:
+				case 5:
+				case 6:
+					break;
+				default:
+					snd_printk(KERN_WARNING
+						"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 = WSS_HW_CS4236B;
+					break;
+				default:
+					snd_printk(KERN_WARNING
+						"unknown CS4236 chip "
+						"(enhanced version = 0x%x)\n",
+						id);
+				}
+			} else if ((id & 0x1f) == 0x08) {	/* CS4237B */
+				chip->hardware = WSS_HW_CS4237B;
+				switch (id >> 5) {
+				case 4:
+				case 5:
+				case 6:
+				case 7:
+					break;
+				default:
+					snd_printk(KERN_WARNING
+						"unknown CS4237B chip "
+						"(enhanced version = 0x%x)\n",
+						id);
+				}
+			} else if ((id & 0x1f) == 0x09) {	/* CS4238B */
+				chip->hardware = WSS_HW_CS4238B;
+				switch (id >> 5) {
+				case 5:
+				case 6:
+				case 7:
+					break;
+				default:
+					snd_printk(KERN_WARNING
+						"unknown CS4238B chip "
+						"(enhanced version = 0x%x)\n",
+						id);
+				}
+			} else if ((id & 0x1f) == 0x1e) {	/* CS4239 */
+				chip->hardware = WSS_HW_CS4239;
+				switch (id >> 5) {
+				case 4:
+				case 5:
+				case 6:
+					break;
+				default:
+					snd_printk(KERN_WARNING
+						"unknown CS4239 chip "
+						"(enhanced version = 0x%x)\n",
+						id);
+				}
+			} else {
+				snd_printk(KERN_WARNING
+					   "unknown CS4236/CS423xB chip "
+					   "(enhanced version = 0x%x)\n", id);
+			}
+		}
+	}
+	return 0;		/* all things are ok.. */
+}
+
+/*
+
+ */
+
+static const struct snd_pcm_hardware snd_wss_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 =		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 const struct snd_pcm_hardware snd_wss_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_wss_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_wss *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	runtime->hw = snd_wss_playback;
+
+	/* hardware limitation of older chipsets */
+	if (chip->hardware & WSS_HW_AD1848_MASK)
+		runtime->hw.formats &= ~(SNDRV_PCM_FMTBIT_IMA_ADPCM |
+					 SNDRV_PCM_FMTBIT_S16_BE);
+
+	/* hardware bug in InterWave chipset */
+	if (chip->hardware == WSS_HW_INTERWAVE && chip->dma1 > 3)
+		runtime->hw.formats &= ~SNDRV_PCM_FMTBIT_MU_LAW;
+
+	/* hardware limitation of cheap chips */
+	if (chip->hardware == WSS_HW_CS4235 ||
+	    chip->hardware == WSS_HW_CS4239)
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE;
+
+	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;
+	}
+
+	err = snd_wss_open(chip, WSS_MODE_PLAY);
+	if (err < 0) {
+		if (chip->release_dma)
+			chip->release_dma(chip, chip->dma_private_data, chip->dma1);
+		return err;
+	}
+	chip->playback_substream = substream;
+	snd_pcm_set_sync(substream);
+	chip->rate_constraint(runtime);
+	return 0;
+}
+
+static int snd_wss_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_wss *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	runtime->hw = snd_wss_capture;
+
+	/* hardware limitation of older chipsets */
+	if (chip->hardware & WSS_HW_AD1848_MASK)
+		runtime->hw.formats &= ~(SNDRV_PCM_FMTBIT_IMA_ADPCM |
+					 SNDRV_PCM_FMTBIT_S16_BE);
+
+	/* hardware limitation of cheap chips */
+	if (chip->hardware == WSS_HW_CS4235 ||
+	    chip->hardware == WSS_HW_CS4239 ||
+	    chip->hardware == WSS_HW_OPTI93X)
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 |
+				      SNDRV_PCM_FMTBIT_S16_LE;
+
+	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;
+	}
+
+	err = snd_wss_open(chip, WSS_MODE_RECORD);
+	if (err < 0) {
+		if (chip->release_dma)
+			chip->release_dma(chip, chip->dma_private_data, chip->dma2);
+		return err;
+	}
+	chip->capture_substream = substream;
+	snd_pcm_set_sync(substream);
+	chip->rate_constraint(runtime);
+	return 0;
+}
+
+static int snd_wss_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_wss *chip = snd_pcm_substream_chip(substream);
+
+	chip->playback_substream = NULL;
+	snd_wss_close(chip, WSS_MODE_PLAY);
+	return 0;
+}
+
+static int snd_wss_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_wss *chip = snd_pcm_substream_chip(substream);
+
+	chip->capture_substream = NULL;
+	snd_wss_close(chip, WSS_MODE_RECORD);
+	return 0;
+}
+
+static void snd_wss_thinkpad_twiddle(struct snd_wss *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
+
+/* lowlevel suspend callback for CS4231 */
+static void snd_wss_suspend(struct snd_wss *chip)
+{
+	int reg;
+	unsigned long flags;
+
+	snd_pcm_suspend_all(chip->pcm);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	for (reg = 0; reg < 32; reg++)
+		chip->image[reg] = snd_wss_in(chip, reg);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (chip->thinkpad_flag)
+		snd_wss_thinkpad_twiddle(chip, 0);
+}
+
+/* lowlevel resume callback for CS4231 */
+static void snd_wss_resume(struct snd_wss *chip)
+{
+	int reg;
+	unsigned long flags;
+	/* int timeout; */
+
+	if (chip->thinkpad_flag)
+		snd_wss_thinkpad_twiddle(chip, 1);
+	snd_wss_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	for (reg = 0; reg < 32; reg++) {
+		switch (reg) {
+		case CS4231_VERSION:
+			break;
+		default:
+			snd_wss_out(chip, reg, chip->image[reg]);
+			break;
+		}
+	}
+	/* Yamaha needs this to resume properly */
+	if (chip->hardware == WSS_HW_OPL3SA2)
+		snd_wss_out(chip, CS4231_PLAYBK_FORMAT,
+			    chip->image[CS4231_PLAYBK_FORMAT]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+#if 1
+	snd_wss_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_wss_mce_down(), but doesn't
+	   include rescheduling.  -- iwai
+	   */
+	snd_wss_busy_wait(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->mce_bit &= ~CS4231_MCE;
+	timeout = wss_inb(chip, CS4231P(REGSEL));
+	wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (timeout == 0x80)
+		snd_printk(KERN_ERR "down [0x%lx]: serious init problem "
+			   "- codec still busy\n", chip->port);
+	if ((timeout & CS4231_MCE) == 0 ||
+	    !(chip->hardware & (WSS_HW_CS4231_MASK | WSS_HW_CS4232_MASK))) {
+		return;
+	}
+	snd_wss_busy_wait(chip);
+#endif
+}
+#endif /* CONFIG_PM */
+
+static int snd_wss_free(struct snd_wss *chip)
+{
+	release_and_free_resource(chip->res_port);
+	release_and_free_resource(chip->res_cport);
+	if (chip->irq >= 0) {
+		disable_irq(chip->irq);
+		if (!(chip->hwshare & WSS_HWSHARE_IRQ))
+			free_irq(chip->irq, (void *) chip);
+	}
+	if (!(chip->hwshare & WSS_HWSHARE_DMA1) && chip->dma1 >= 0) {
+		snd_dma_disable(chip->dma1);
+		free_dma(chip->dma1);
+	}
+	if (!(chip->hwshare & WSS_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_wss_dev_free(struct snd_device *device)
+{
+	struct snd_wss *chip = device->device_data;
+	return snd_wss_free(chip);
+}
+
+const char *snd_wss_chip_id(struct snd_wss *chip)
+{
+	switch (chip->hardware) {
+	case WSS_HW_CS4231:
+		return "CS4231";
+	case WSS_HW_CS4231A:
+		return "CS4231A";
+	case WSS_HW_CS4232:
+		return "CS4232";
+	case WSS_HW_CS4232A:
+		return "CS4232A";
+	case WSS_HW_CS4235:
+		return "CS4235";
+	case WSS_HW_CS4236:
+		return "CS4236";
+	case WSS_HW_CS4236B:
+		return "CS4236B";
+	case WSS_HW_CS4237B:
+		return "CS4237B";
+	case WSS_HW_CS4238B:
+		return "CS4238B";
+	case WSS_HW_CS4239:
+		return "CS4239";
+	case WSS_HW_INTERWAVE:
+		return "AMD InterWave";
+	case WSS_HW_OPL3SA2:
+		return chip->card->shortname;
+	case WSS_HW_AD1845:
+		return "AD1845";
+	case WSS_HW_OPTI93X:
+		return "OPTi 93x";
+	case WSS_HW_AD1847:
+		return "AD1847";
+	case WSS_HW_AD1848:
+		return "AD1848";
+	case WSS_HW_CS4248:
+		return "CS4248";
+	case WSS_HW_CMI8330:
+		return "CMI8330/C3D";
+	default:
+		return "???";
+	}
+}
+EXPORT_SYMBOL(snd_wss_chip_id);
+
+static int snd_wss_new(struct snd_card *card,
+			  unsigned short hardware,
+			  unsigned short hwshare,
+			  struct snd_wss **rchip)
+{
+	struct snd_wss *chip;
+
+	*rchip = NULL;
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+	chip->hardware = hardware;
+	chip->hwshare = hwshare;
+
+	spin_lock_init(&chip->reg_lock);
+	mutex_init(&chip->mce_mutex);
+	mutex_init(&chip->open_mutex);
+	chip->card = card;
+	chip->rate_constraint = snd_wss_xrate;
+	chip->set_playback_format = snd_wss_playback_format;
+	chip->set_capture_format = snd_wss_capture_format;
+	if (chip->hardware == WSS_HW_OPTI93X)
+		memcpy(&chip->image, &snd_opti93x_original_image,
+		       sizeof(snd_opti93x_original_image));
+	else
+		memcpy(&chip->image, &snd_wss_original_image,
+		       sizeof(snd_wss_original_image));
+	if (chip->hardware & WSS_HW_AD1848_MASK) {
+		chip->image[CS4231_PIN_CTRL] = 0;
+		chip->image[CS4231_TEST_INIT] = 0;
+	}
+
+	*rchip = chip;
+	return 0;
+}
+
+int snd_wss_create(struct snd_card *card,
+		      unsigned long port,
+		      unsigned long cport,
+		      int irq, int dma1, int dma2,
+		      unsigned short hardware,
+		      unsigned short hwshare,
+		      struct snd_wss **rchip)
+{
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_wss_dev_free,
+	};
+	struct snd_wss *chip;
+	int err;
+
+	err = snd_wss_new(card, hardware, hwshare, &chip);
+	if (err < 0)
+		return err;
+
+	chip->irq = -1;
+	chip->dma1 = -1;
+	chip->dma2 = -1;
+
+	chip->res_port = request_region(port, 4, "WSS");
+	if (!chip->res_port) {
+		snd_printk(KERN_ERR "wss: can't grab port 0x%lx\n", port);
+		snd_wss_free(chip);
+		return -EBUSY;
+	}
+	chip->port = port;
+	if ((long)cport >= 0) {
+		chip->res_cport = request_region(cport, 8, "CS4232 Control");
+		if (!chip->res_cport) {
+			snd_printk(KERN_ERR
+				"wss: can't grab control port 0x%lx\n", cport);
+			snd_wss_free(chip);
+			return -ENODEV;
+		}
+	}
+	chip->cport = cport;
+	if (!(hwshare & WSS_HWSHARE_IRQ))
+		if (request_irq(irq, snd_wss_interrupt, 0,
+				"WSS", (void *) chip)) {
+			snd_printk(KERN_ERR "wss: can't grab IRQ %d\n", irq);
+			snd_wss_free(chip);
+			return -EBUSY;
+		}
+	chip->irq = irq;
+	if (!(hwshare & WSS_HWSHARE_DMA1) && request_dma(dma1, "WSS - 1")) {
+		snd_printk(KERN_ERR "wss: can't grab DMA1 %d\n", dma1);
+		snd_wss_free(chip);
+		return -EBUSY;
+	}
+	chip->dma1 = dma1;
+	if (!(hwshare & WSS_HWSHARE_DMA2) && dma1 != dma2 &&
+	      dma2 >= 0 && request_dma(dma2, "WSS - 2")) {
+		snd_printk(KERN_ERR "wss: can't grab DMA2 %d\n", dma2);
+		snd_wss_free(chip);
+		return -EBUSY;
+	}
+	if (dma1 == dma2 || dma2 < 0) {
+		chip->single_dma = 1;
+		chip->dma2 = chip->dma1;
+	} else
+		chip->dma2 = dma2;
+
+	if (hardware == WSS_HW_THINKPAD) {
+		chip->thinkpad_flag = 1;
+		chip->hardware = WSS_HW_DETECT; /* reset */
+		snd_wss_thinkpad_twiddle(chip, 1);
+	}
+
+	/* global setup */
+	if (snd_wss_probe(chip) < 0) {
+		snd_wss_free(chip);
+		return -ENODEV;
+	}
+	snd_wss_init(chip);
+
+#if 0
+	if (chip->hardware & WSS_HW_CS4232_MASK) {
+		if (chip->res_cport == NULL)
+			snd_printk(KERN_ERR "CS4232 control port features are "
+				   "not accessible\n");
+	}
+#endif
+
+	/* Register device */
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err < 0) {
+		snd_wss_free(chip);
+		return err;
+	}
+
+#ifdef CONFIG_PM
+	/* Power Management */
+	chip->suspend = snd_wss_suspend;
+	chip->resume = snd_wss_resume;
+#endif
+
+	*rchip = chip;
+	return 0;
+}
+EXPORT_SYMBOL(snd_wss_create);
+
+static const struct snd_pcm_ops snd_wss_playback_ops = {
+	.open =		snd_wss_playback_open,
+	.close =	snd_wss_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_wss_playback_hw_params,
+	.hw_free =	snd_wss_playback_hw_free,
+	.prepare =	snd_wss_playback_prepare,
+	.trigger =	snd_wss_trigger,
+	.pointer =	snd_wss_playback_pointer,
+};
+
+static const struct snd_pcm_ops snd_wss_capture_ops = {
+	.open =		snd_wss_capture_open,
+	.close =	snd_wss_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_wss_capture_hw_params,
+	.hw_free =	snd_wss_capture_hw_free,
+	.prepare =	snd_wss_capture_prepare,
+	.trigger =	snd_wss_trigger,
+	.pointer =	snd_wss_capture_pointer,
+};
+
+int snd_wss_pcm(struct snd_wss *chip, int device)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(chip->card, "WSS", device, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_wss_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_wss_capture_ops);
+
+	/* global setup */
+	pcm->private_data = chip;
+	pcm->info_flags = 0;
+	if (chip->single_dma)
+		pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX;
+	if (chip->hardware != WSS_HW_INTERWAVE)
+		pcm->info_flags |= SNDRV_PCM_INFO_JOINT_DUPLEX;
+	strcpy(pcm->name, snd_wss_chip_id(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;
+	return 0;
+}
+EXPORT_SYMBOL(snd_wss_pcm);
+
+static void snd_wss_timer_free(struct snd_timer *timer)
+{
+	struct snd_wss *chip = timer->private_data;
+	chip->timer = NULL;
+}
+
+int snd_wss_timer(struct snd_wss *chip, int device)
+{
+	struct snd_timer *timer;
+	struct snd_timer_id 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_wss_chip_id(chip));
+	timer->private_data = chip;
+	timer->private_free = snd_wss_timer_free;
+	timer->hw = snd_wss_timer_table;
+	chip->timer = timer;
+	return 0;
+}
+EXPORT_SYMBOL(snd_wss_timer);
+
+/*
+ *  MIXER part
+ */
+
+static int snd_wss_info_mux(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts[4] = {
+		"Line", "Aux", "Mic", "Mix"
+	};
+	static const char * const opl3sa_texts[4] = {
+		"Line", "CD", "Mic", "Mix"
+	};
+	static const char * const gusmax_texts[4] = {
+		"Line", "Synth", "Mic", "Mix"
+	};
+	const char * const *ptexts = texts;
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+
+	if (snd_BUG_ON(!chip->card))
+		return -EINVAL;
+	if (!strcmp(chip->card->driver, "GUS MAX"))
+		ptexts = gusmax_texts;
+	switch (chip->hardware) {
+	case WSS_HW_INTERWAVE:
+		ptexts = gusmax_texts;
+		break;
+	case WSS_HW_OPTI93X:
+	case WSS_HW_OPL3SA2:
+		ptexts = opl3sa_texts;
+		break;
+	}
+	return snd_ctl_enum_info(uinfo, 2, 4, ptexts);
+}
+
+static int snd_wss_get_mux(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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_wss_put_mux(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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_wss_out(chip, CS4231_LEFT_INPUT, left);
+	snd_wss_out(chip, CS4231_RIGHT_INPUT, right);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+int snd_wss_info_single(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_info *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;
+}
+EXPORT_SYMBOL(snd_wss_info_single);
+
+int snd_wss_get_single(struct snd_kcontrol *kcontrol,
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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;
+}
+EXPORT_SYMBOL(snd_wss_get_single);
+
+int snd_wss_put_single(struct snd_kcontrol *kcontrol,
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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_wss_out(chip, reg, val);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+EXPORT_SYMBOL(snd_wss_put_single);
+
+int snd_wss_info_double(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_info *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;
+}
+EXPORT_SYMBOL(snd_wss_info_double);
+
+int snd_wss_get_double(struct snd_kcontrol *kcontrol,
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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;
+}
+EXPORT_SYMBOL(snd_wss_get_double);
+
+int snd_wss_put_double(struct snd_kcontrol *kcontrol,
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *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_wss_out(chip, left_reg, val1);
+		snd_wss_out(chip, right_reg, val2);
+	} else {
+		mask = (mask << shift_left) | (mask << shift_right);
+		val1 = (chip->image[left_reg] & ~mask) | val1 | val2;
+		change = val1 != chip->image[left_reg];
+		snd_wss_out(chip, left_reg, val1);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+EXPORT_SYMBOL(snd_wss_put_double);
+
+static const DECLARE_TLV_DB_SCALE(db_scale_6bit, -9450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_rec_gain, 0, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_4bit, -4500, 300, 0);
+
+static struct snd_kcontrol_new snd_wss_controls[] = {
+WSS_DOUBLE("PCM Playback Switch", 0,
+		CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("PCM Playback Volume", 0,
+		CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1,
+		db_scale_6bit),
+WSS_DOUBLE("Aux Playback Switch", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Aux Playback Volume", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1,
+		db_scale_5bit_12db_max),
+WSS_DOUBLE("Aux Playback Switch", 1,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Aux Playback Volume", 1,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1,
+		db_scale_5bit_12db_max),
+WSS_DOUBLE_TLV("Capture Volume", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT,
+		0, 0, 15, 0, db_scale_rec_gain),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Capture Source",
+	.info = snd_wss_info_mux,
+	.get = snd_wss_get_mux,
+	.put = snd_wss_put_mux,
+},
+WSS_DOUBLE("Mic Boost (+20dB)", 0,
+		CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 5, 5, 1, 0),
+WSS_SINGLE("Loopback Capture Switch", 0,
+		CS4231_LOOPBACK, 0, 1, 0),
+WSS_SINGLE_TLV("Loopback Capture Volume", 0, CS4231_LOOPBACK, 2, 63, 1,
+		db_scale_6bit),
+WSS_DOUBLE("Line Playback Switch", 0,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Line Playback Volume", 0,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1,
+		db_scale_5bit_12db_max),
+WSS_SINGLE("Beep Playback Switch", 0,
+		CS4231_MONO_CTRL, 7, 1, 1),
+WSS_SINGLE_TLV("Beep Playback Volume", 0,
+		CS4231_MONO_CTRL, 0, 15, 1,
+		db_scale_4bit),
+WSS_SINGLE("Mono Output Playback Switch", 0,
+		CS4231_MONO_CTRL, 6, 1, 1),
+WSS_SINGLE("Beep Bypass Playback Switch", 0,
+		CS4231_MONO_CTRL, 5, 1, 0),
+};
+
+int snd_wss_mixer(struct snd_wss *chip)
+{
+	struct snd_card *card;
+	unsigned int idx;
+	int err;
+	int count = ARRAY_SIZE(snd_wss_controls);
+
+	if (snd_BUG_ON(!chip || !chip->pcm))
+		return -EINVAL;
+
+	card = chip->card;
+
+	strcpy(card->mixername, chip->pcm->name);
+
+	/* Use only the first 11 entries on AD1848 */
+	if (chip->hardware & WSS_HW_AD1848_MASK)
+		count = 11;
+	/* There is no loopback on OPTI93X */
+	else if (chip->hardware == WSS_HW_OPTI93X)
+		count = 9;
+
+	for (idx = 0; idx < count; idx++) {
+		err = snd_ctl_add(card,
+				snd_ctl_new1(&snd_wss_controls[idx],
+					     chip));
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+EXPORT_SYMBOL(snd_wss_mixer);
+
+const struct snd_pcm_ops *snd_wss_get_pcm_ops(int direction)
+{
+	return direction == SNDRV_PCM_STREAM_PLAYBACK ?
+		&snd_wss_playback_ops : &snd_wss_capture_ops;
+}
+EXPORT_SYMBOL(snd_wss_get_pcm_ops);