Linux-2.6.12-rc2

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

Let it rip!
diff --git a/sound/isa/sb/Makefile b/sound/isa/sb/Makefile
new file mode 100644
index 0000000..fd9d9c5
--- /dev/null
+++ b/sound/isa/sb/Makefile
@@ -0,0 +1,39 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-sb-common-objs := sb_common.o sb_mixer.o
+snd-sb8-dsp-objs := sb8_main.o sb8_midi.o
+snd-sb16-dsp-objs := sb16_main.o
+snd-sb16-csp-objs := sb16_csp.o
+snd-sb8-objs := sb8.o
+snd-sb16-objs := sb16.o
+snd-sbawe-objs := sbawe.o emu8000.o
+snd-emu8000-synth-objs := emu8000_synth.o emu8000_callback.o emu8000_patch.o emu8000_pcm.o
+snd-es968-objs := es968.o
+
+#
+# this function returns:
+#   "m" - CONFIG_SND_SEQUENCER is m
+#   <empty string> - CONFIG_SND_SEQUENCER is undefined
+#   otherwise parameter #1 value
+#
+sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1)))
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_ALS100) += snd-sb16-dsp.o snd-sb-common.o
+obj-$(CONFIG_SND_CMI8330) += snd-sb16-dsp.o snd-sb-common.o
+obj-$(CONFIG_SND_DT019X) += snd-sb16-dsp.o snd-sb-common.o
+obj-$(CONFIG_SND_SB8) += snd-sb8.o snd-sb8-dsp.o snd-sb-common.o
+obj-$(CONFIG_SND_SB16) += snd-sb16.o snd-sb16-dsp.o snd-sb-common.o
+obj-$(CONFIG_SND_SBAWE) += snd-sbawe.o snd-sb16-dsp.o snd-sb-common.o
+obj-$(CONFIG_SND_ES968) += snd-es968.o snd-sb8-dsp.o snd-sb-common.o
+obj-$(CONFIG_SND_ALS4000) += snd-sb-common.o
+ifeq ($(CONFIG_SND_SB16_CSP),y)
+  obj-$(CONFIG_SND_SB16) += snd-sb16-csp.o
+  obj-$(CONFIG_SND_SBAWE) += snd-sb16-csp.o
+endif
+obj-$(call sequencer,$(CONFIG_SND_SBAWE)) += snd-emu8000-synth.o
+
+obj-m := $(sort $(obj-m))
diff --git a/sound/isa/sb/emu8000.c b/sound/isa/sb/emu8000.c
new file mode 100644
index 0000000..028af40
--- /dev/null
+++ b/sound/isa/sb/emu8000.c
@@ -0,0 +1,1170 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *     and (c) 1999 Steve Ratcliffe <steve@parabola.demon.co.uk>
+ *  Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *  Routines for control of EMU8000 chip
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <sound/driver.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/emu8000.h>
+#include <sound/emu8000_reg.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/init.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+
+/*
+ * emu8000 register controls
+ */
+
+/*
+ * The following routines read and write registers on the emu8000.  They
+ * should always be called via the EMU8000*READ/WRITE macros and never
+ * directly.  The macros handle the port number and command word.
+ */
+/* Write a word */
+void snd_emu8000_poke(emu8000_t *emu, unsigned int port, unsigned int reg, unsigned int val)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	if (reg != emu->last_reg) {
+		outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
+		emu->last_reg = reg;
+	}
+	outw((unsigned short)val, port); /* Send data */
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+}
+
+/* Read a word */
+unsigned short snd_emu8000_peek(emu8000_t *emu, unsigned int port, unsigned int reg)
+{
+	unsigned short res;
+	unsigned long flags;
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	if (reg != emu->last_reg) {
+		outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
+		emu->last_reg = reg;
+	}
+	res = inw(port);	/* Read data */
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return res;
+}
+
+/* Write a double word */
+void snd_emu8000_poke_dw(emu8000_t *emu, unsigned int port, unsigned int reg, unsigned int val)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	if (reg != emu->last_reg) {
+		outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
+		emu->last_reg = reg;
+	}
+	outw((unsigned short)val, port); /* Send low word of data */
+	outw((unsigned short)(val>>16), port+2); /* Send high word of data */
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+}
+
+/* Read a double word */
+unsigned int snd_emu8000_peek_dw(emu8000_t *emu, unsigned int port, unsigned int reg)
+{
+	unsigned short low;
+	unsigned int res;
+	unsigned long flags;
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	if (reg != emu->last_reg) {
+		outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
+		emu->last_reg = reg;
+	}
+	low = inw(port);	/* Read low word of data */
+	res = low + (inw(port+2) << 16);
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return res;
+}
+
+/*
+ * Set up / close a channel to be used for DMA.
+ */
+/*exported*/ void
+snd_emu8000_dma_chan(emu8000_t *emu, int ch, int mode)
+{
+	unsigned right_bit = (mode & EMU8000_RAM_RIGHT) ? 0x01000000 : 0;
+	mode &= EMU8000_RAM_MODE_MASK;
+	if (mode == EMU8000_RAM_CLOSE) {
+		EMU8000_CCCA_WRITE(emu, ch, 0);
+		EMU8000_DCYSUSV_WRITE(emu, ch, 0x807F);
+		return;
+	}
+	EMU8000_DCYSUSV_WRITE(emu, ch, 0x80);
+	EMU8000_VTFT_WRITE(emu, ch, 0);
+	EMU8000_CVCF_WRITE(emu, ch, 0);
+	EMU8000_PTRX_WRITE(emu, ch, 0x40000000);
+	EMU8000_CPF_WRITE(emu, ch, 0x40000000);
+	EMU8000_PSST_WRITE(emu, ch, 0);
+	EMU8000_CSL_WRITE(emu, ch, 0);
+	if (mode == EMU8000_RAM_WRITE) /* DMA write */
+		EMU8000_CCCA_WRITE(emu, ch, 0x06000000 | right_bit);
+	else	   /* DMA read */
+		EMU8000_CCCA_WRITE(emu, ch, 0x04000000 | right_bit);
+}
+
+/*
+ */
+static void __init
+snd_emu8000_read_wait(emu8000_t *emu)
+{
+	while ((EMU8000_SMALR_READ(emu) & 0x80000000) != 0) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(1);
+		if (signal_pending(current))
+			break;
+	}
+}
+
+/*
+ */
+static void __init
+snd_emu8000_write_wait(emu8000_t *emu)
+{
+	while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(1);
+		if (signal_pending(current))
+			break;
+	}
+}
+
+/*
+ * detect a card at the given port
+ */
+static int __init
+snd_emu8000_detect(emu8000_t *emu)
+{
+	/* Initialise */
+	EMU8000_HWCF1_WRITE(emu, 0x0059);
+	EMU8000_HWCF2_WRITE(emu, 0x0020);
+	EMU8000_HWCF3_WRITE(emu, 0x0000);
+	/* Check for a recognisable emu8000 */
+	/*
+	if ((EMU8000_U1_READ(emu) & 0x000f) != 0x000c)
+		return -ENODEV;
+		*/
+	if ((EMU8000_HWCF1_READ(emu) & 0x007e) != 0x0058)
+		return -ENODEV;
+	if ((EMU8000_HWCF2_READ(emu) & 0x0003) != 0x0003)
+		return -ENODEV;
+
+	snd_printdd("EMU8000 [0x%lx]: Synth chip found\n",
+                    emu->port1);
+	return 0;
+}
+
+
+/*
+ * intiailize audio channels
+ */
+static void __init
+init_audio(emu8000_t *emu)
+{
+	int ch;
+
+	/* turn off envelope engines */
+	for (ch = 0; ch < EMU8000_CHANNELS; ch++)
+		EMU8000_DCYSUSV_WRITE(emu, ch, 0x80);
+  
+	/* reset all other parameters to zero */
+	for (ch = 0; ch < EMU8000_CHANNELS; ch++) {
+		EMU8000_ENVVOL_WRITE(emu, ch, 0);
+		EMU8000_ENVVAL_WRITE(emu, ch, 0);
+		EMU8000_DCYSUS_WRITE(emu, ch, 0);
+		EMU8000_ATKHLDV_WRITE(emu, ch, 0);
+		EMU8000_LFO1VAL_WRITE(emu, ch, 0);
+		EMU8000_ATKHLD_WRITE(emu, ch, 0);
+		EMU8000_LFO2VAL_WRITE(emu, ch, 0);
+		EMU8000_IP_WRITE(emu, ch, 0);
+		EMU8000_IFATN_WRITE(emu, ch, 0);
+		EMU8000_PEFE_WRITE(emu, ch, 0);
+		EMU8000_FMMOD_WRITE(emu, ch, 0);
+		EMU8000_TREMFRQ_WRITE(emu, ch, 0);
+		EMU8000_FM2FRQ2_WRITE(emu, ch, 0);
+		EMU8000_PTRX_WRITE(emu, ch, 0);
+		EMU8000_VTFT_WRITE(emu, ch, 0);
+		EMU8000_PSST_WRITE(emu, ch, 0);
+		EMU8000_CSL_WRITE(emu, ch, 0);
+		EMU8000_CCCA_WRITE(emu, ch, 0);
+	}
+
+	for (ch = 0; ch < EMU8000_CHANNELS; ch++) {
+		EMU8000_CPF_WRITE(emu, ch, 0);
+		EMU8000_CVCF_WRITE(emu, ch, 0);
+	}
+}
+
+
+/*
+ * initialize DMA address
+ */
+static void __init
+init_dma(emu8000_t *emu)
+{
+	EMU8000_SMALR_WRITE(emu, 0);
+	EMU8000_SMARR_WRITE(emu, 0);
+	EMU8000_SMALW_WRITE(emu, 0);
+	EMU8000_SMARW_WRITE(emu, 0);
+}
+
+/*
+ * initialization arrays; from ADIP
+ */
+static unsigned short init1[128] /*__devinitdata*/ = {
+	0x03ff, 0x0030,  0x07ff, 0x0130, 0x0bff, 0x0230,  0x0fff, 0x0330,
+	0x13ff, 0x0430,  0x17ff, 0x0530, 0x1bff, 0x0630,  0x1fff, 0x0730,
+	0x23ff, 0x0830,  0x27ff, 0x0930, 0x2bff, 0x0a30,  0x2fff, 0x0b30,
+	0x33ff, 0x0c30,  0x37ff, 0x0d30, 0x3bff, 0x0e30,  0x3fff, 0x0f30,
+
+	0x43ff, 0x0030,  0x47ff, 0x0130, 0x4bff, 0x0230,  0x4fff, 0x0330,
+	0x53ff, 0x0430,  0x57ff, 0x0530, 0x5bff, 0x0630,  0x5fff, 0x0730,
+	0x63ff, 0x0830,  0x67ff, 0x0930, 0x6bff, 0x0a30,  0x6fff, 0x0b30,
+	0x73ff, 0x0c30,  0x77ff, 0x0d30, 0x7bff, 0x0e30,  0x7fff, 0x0f30,
+
+	0x83ff, 0x0030,  0x87ff, 0x0130, 0x8bff, 0x0230,  0x8fff, 0x0330,
+	0x93ff, 0x0430,  0x97ff, 0x0530, 0x9bff, 0x0630,  0x9fff, 0x0730,
+	0xa3ff, 0x0830,  0xa7ff, 0x0930, 0xabff, 0x0a30,  0xafff, 0x0b30,
+	0xb3ff, 0x0c30,  0xb7ff, 0x0d30, 0xbbff, 0x0e30,  0xbfff, 0x0f30,
+
+	0xc3ff, 0x0030,  0xc7ff, 0x0130, 0xcbff, 0x0230,  0xcfff, 0x0330,
+	0xd3ff, 0x0430,  0xd7ff, 0x0530, 0xdbff, 0x0630,  0xdfff, 0x0730,
+	0xe3ff, 0x0830,  0xe7ff, 0x0930, 0xebff, 0x0a30,  0xefff, 0x0b30,
+	0xf3ff, 0x0c30,  0xf7ff, 0x0d30, 0xfbff, 0x0e30,  0xffff, 0x0f30,
+};
+
+static unsigned short init2[128] /*__devinitdata*/ = {
+	0x03ff, 0x8030, 0x07ff, 0x8130, 0x0bff, 0x8230, 0x0fff, 0x8330,
+	0x13ff, 0x8430, 0x17ff, 0x8530, 0x1bff, 0x8630, 0x1fff, 0x8730,
+	0x23ff, 0x8830, 0x27ff, 0x8930, 0x2bff, 0x8a30, 0x2fff, 0x8b30,
+	0x33ff, 0x8c30, 0x37ff, 0x8d30, 0x3bff, 0x8e30, 0x3fff, 0x8f30,
+
+	0x43ff, 0x8030, 0x47ff, 0x8130, 0x4bff, 0x8230, 0x4fff, 0x8330,
+	0x53ff, 0x8430, 0x57ff, 0x8530, 0x5bff, 0x8630, 0x5fff, 0x8730,
+	0x63ff, 0x8830, 0x67ff, 0x8930, 0x6bff, 0x8a30, 0x6fff, 0x8b30,
+	0x73ff, 0x8c30, 0x77ff, 0x8d30, 0x7bff, 0x8e30, 0x7fff, 0x8f30,
+
+	0x83ff, 0x8030, 0x87ff, 0x8130, 0x8bff, 0x8230, 0x8fff, 0x8330,
+	0x93ff, 0x8430, 0x97ff, 0x8530, 0x9bff, 0x8630, 0x9fff, 0x8730,
+	0xa3ff, 0x8830, 0xa7ff, 0x8930, 0xabff, 0x8a30, 0xafff, 0x8b30,
+	0xb3ff, 0x8c30, 0xb7ff, 0x8d30, 0xbbff, 0x8e30, 0xbfff, 0x8f30,
+
+	0xc3ff, 0x8030, 0xc7ff, 0x8130, 0xcbff, 0x8230, 0xcfff, 0x8330,
+	0xd3ff, 0x8430, 0xd7ff, 0x8530, 0xdbff, 0x8630, 0xdfff, 0x8730,
+	0xe3ff, 0x8830, 0xe7ff, 0x8930, 0xebff, 0x8a30, 0xefff, 0x8b30,
+	0xf3ff, 0x8c30, 0xf7ff, 0x8d30, 0xfbff, 0x8e30, 0xffff, 0x8f30,
+};
+
+static unsigned short init3[128] /*__devinitdata*/ = {
+	0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5,
+	0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x8F7C, 0x167E, 0xF254,
+	0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x8BAA, 0x1B6D, 0xF234,
+	0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x86E7, 0x229E, 0xF224,
+
+	0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x87F6, 0x2C28, 0xF254,
+	0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x8F02, 0x1341, 0xF264,
+	0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x8FA9, 0x3EB5, 0xF294,
+	0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0xC4C3, 0x3EBB, 0xC5C3,
+
+	0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x8671, 0x14FD, 0x8287,
+	0x3EBC, 0xE610, 0x3EC8, 0x8C7B, 0x031A, 0x87E6, 0x3EC8, 0x86F7,
+	0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x821F, 0x3ECA, 0x8386,
+	0x3EC1, 0x8C03, 0x3EC9, 0x831E, 0x3ECA, 0x8C4C, 0x3EBF, 0x8C55,
+
+	0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x8EAD, 0x3EC8, 0xD308,
+	0x3EC2, 0x8F7E, 0x3ECB, 0x8219, 0x3ECB, 0xD26E, 0x3EC5, 0x831F,
+	0x3EC6, 0xC308, 0x3EC3, 0xB2FF, 0x3EC9, 0x8265, 0x3EC9, 0x8319,
+	0x1342, 0xD36E, 0x3EC7, 0xB3FF, 0x0000, 0x8365, 0x1420, 0x9570,
+};
+
+static unsigned short init4[128] /*__devinitdata*/ = {
+	0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5,
+	0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x0F7C, 0x167E, 0x7254,
+	0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x0BAA, 0x1B6D, 0x7234,
+	0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x06E7, 0x229E, 0x7224,
+
+	0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x07F6, 0x2C28, 0x7254,
+	0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x0F02, 0x1341, 0x7264,
+	0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x0FA9, 0x3EB5, 0x7294,
+	0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0x44C3, 0x3EBB, 0x45C3,
+
+	0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x0671, 0x14FD, 0x0287,
+	0x3EBC, 0xE610, 0x3EC8, 0x0C7B, 0x031A, 0x07E6, 0x3EC8, 0x86F7,
+	0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x021F, 0x3ECA, 0x0386,
+	0x3EC1, 0x0C03, 0x3EC9, 0x031E, 0x3ECA, 0x8C4C, 0x3EBF, 0x0C55,
+
+	0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x0EAD, 0x3EC8, 0xD308,
+	0x3EC2, 0x8F7E, 0x3ECB, 0x0219, 0x3ECB, 0xD26E, 0x3EC5, 0x031F,
+	0x3EC6, 0xC308, 0x3EC3, 0x32FF, 0x3EC9, 0x0265, 0x3EC9, 0x8319,
+	0x1342, 0xD36E, 0x3EC7, 0x33FF, 0x0000, 0x8365, 0x1420, 0x9570,
+};
+
+/* send an initialization array
+ * Taken from the oss driver, not obvious from the doc how this
+ * is meant to work
+ */
+static void __init
+send_array(emu8000_t *emu, unsigned short *data, int size)
+{
+	int i;
+	unsigned short *p;
+
+	p = data;
+	for (i = 0; i < size; i++, p++)
+		EMU8000_INIT1_WRITE(emu, i, *p);
+	for (i = 0; i < size; i++, p++)
+		EMU8000_INIT2_WRITE(emu, i, *p);
+	for (i = 0; i < size; i++, p++)
+		EMU8000_INIT3_WRITE(emu, i, *p);
+	for (i = 0; i < size; i++, p++)
+		EMU8000_INIT4_WRITE(emu, i, *p);
+}
+
+
+/*
+ * Send initialization arrays to start up, this just follows the
+ * initialisation sequence in the adip.
+ */
+static void __init
+init_arrays(emu8000_t *emu)
+{
+	send_array(emu, init1, ARRAY_SIZE(init1)/4);
+
+	msleep((1024 * 1000) / 44100); /* wait for 1024 clocks */
+	send_array(emu, init2, ARRAY_SIZE(init2)/4);
+	send_array(emu, init3, ARRAY_SIZE(init3)/4);
+
+	EMU8000_HWCF4_WRITE(emu, 0);
+	EMU8000_HWCF5_WRITE(emu, 0x83);
+	EMU8000_HWCF6_WRITE(emu, 0x8000);
+
+	send_array(emu, init4, ARRAY_SIZE(init4)/4);
+}
+
+
+#define UNIQUE_ID1	0xa5b9
+#define UNIQUE_ID2	0x9d53
+
+/*
+ * Size the onboard memory.
+ * This is written so as not to need arbitary delays after the write. It
+ * seems that the only way to do this is to use the one channel and keep
+ * reallocating between read and write.
+ */
+static void __init
+size_dram(emu8000_t *emu)
+{
+	int i, size;
+
+	if (emu->dram_checked)
+		return;
+
+	size = 0;
+
+	/* write out a magic number */
+	snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_WRITE);
+	snd_emu8000_dma_chan(emu, 1, EMU8000_RAM_READ);
+	EMU8000_SMALW_WRITE(emu, EMU8000_DRAM_OFFSET);
+	EMU8000_SMLD_WRITE(emu, UNIQUE_ID1);
+	snd_emu8000_init_fm(emu); /* This must really be here and not 2 lines back even */
+
+	while (size < EMU8000_MAX_DRAM) {
+
+		size += 512 * 1024;  /* increment 512kbytes */
+
+		/* Write a unique data on the test address.
+		 * if the address is out of range, the data is written on
+		 * 0x200000(=EMU8000_DRAM_OFFSET).  Then the id word is
+		 * changed by this data.
+		 */
+		/*snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_WRITE);*/
+		EMU8000_SMALW_WRITE(emu, EMU8000_DRAM_OFFSET + (size>>1));
+		EMU8000_SMLD_WRITE(emu, UNIQUE_ID2);
+		snd_emu8000_write_wait(emu);
+
+		/*
+		 * read the data on the just written DRAM address
+		 * if not the same then we have reached the end of ram.
+		 */
+		/*snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_READ);*/
+		EMU8000_SMALR_WRITE(emu, EMU8000_DRAM_OFFSET + (size>>1));
+		/*snd_emu8000_read_wait(emu);*/
+		EMU8000_SMLD_READ(emu); /* discard stale data  */
+		if (EMU8000_SMLD_READ(emu) != UNIQUE_ID2)
+			break; /* we must have wrapped around */
+
+		snd_emu8000_read_wait(emu);
+
+		/*
+		 * If it is the same it could be that the address just
+		 * wraps back to the beginning; so check to see if the
+		 * initial value has been overwritten.
+		 */
+		EMU8000_SMALR_WRITE(emu, EMU8000_DRAM_OFFSET);
+		EMU8000_SMLD_READ(emu); /* discard stale data  */
+		if (EMU8000_SMLD_READ(emu) != UNIQUE_ID1)
+			break; /* we must have wrapped around */
+		snd_emu8000_read_wait(emu);
+	}
+
+	/* wait until FULL bit in SMAxW register is false */
+	for (i = 0; i < 10000; i++) {
+		if ((EMU8000_SMALW_READ(emu) & 0x80000000) == 0)
+			break;
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(1);
+		if (signal_pending(current))
+			break;
+	}
+	snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_CLOSE);
+	snd_emu8000_dma_chan(emu, 1, EMU8000_RAM_CLOSE);
+
+	snd_printdd("EMU8000 [0x%lx]: %d Kb on-board memory detected\n",
+		    emu->port1, size/1024);
+
+	emu->mem_size = size;
+	emu->dram_checked = 1;
+}
+
+
+/*
+ * Initiailise the FM section.  You have to do this to use sample RAM
+ * and therefore lose 2 voices.
+ */
+/*exported*/ void
+snd_emu8000_init_fm(emu8000_t *emu)
+{
+	unsigned long flags;
+
+	/* Initialize the last two channels for DRAM refresh and producing
+	   the reverb and chorus effects for Yamaha OPL-3 synthesizer */
+
+	/* 31: FM left channel, 0xffffe0-0xffffe8 */
+	EMU8000_DCYSUSV_WRITE(emu, 30, 0x80);
+	EMU8000_PSST_WRITE(emu, 30, 0xFFFFFFE0); /* full left */
+	EMU8000_CSL_WRITE(emu, 30, 0x00FFFFE8 | (emu->fm_chorus_depth << 24));
+	EMU8000_PTRX_WRITE(emu, 30, (emu->fm_reverb_depth << 8));
+	EMU8000_CPF_WRITE(emu, 30, 0);
+	EMU8000_CCCA_WRITE(emu, 30, 0x00FFFFE3);
+
+	/* 32: FM right channel, 0xfffff0-0xfffff8 */
+	EMU8000_DCYSUSV_WRITE(emu, 31, 0x80);
+	EMU8000_PSST_WRITE(emu, 31, 0x00FFFFF0); /* full right */
+	EMU8000_CSL_WRITE(emu, 31, 0x00FFFFF8 | (emu->fm_chorus_depth << 24));
+	EMU8000_PTRX_WRITE(emu, 31, (emu->fm_reverb_depth << 8));
+	EMU8000_CPF_WRITE(emu, 31, 0x8000);
+	EMU8000_CCCA_WRITE(emu, 31, 0x00FFFFF3);
+
+	snd_emu8000_poke((emu), EMU8000_DATA0(emu), EMU8000_CMD(1, (30)), 0);
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	while (!(inw(EMU8000_PTR(emu)) & 0x1000))
+		;
+	while ((inw(EMU8000_PTR(emu)) & 0x1000))
+		;
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	snd_emu8000_poke((emu), EMU8000_DATA0(emu), EMU8000_CMD(1, (30)), 0x4828);
+	/* this is really odd part.. */
+	outb(0x3C, EMU8000_PTR(emu));
+	outb(0, EMU8000_DATA1(emu));
+
+	/* skew volume & cutoff */
+	EMU8000_VTFT_WRITE(emu, 30, 0x8000FFFF);
+	EMU8000_VTFT_WRITE(emu, 31, 0x8000FFFF);
+}
+
+
+/*
+ * The main initialization routine.
+ */
+static void __init
+snd_emu8000_init_hw(emu8000_t *emu)
+{
+	int i;
+
+	emu->last_reg = 0xffff; /* reset the last register index */
+
+	/* initialize hardware configuration */
+	EMU8000_HWCF1_WRITE(emu, 0x0059);
+	EMU8000_HWCF2_WRITE(emu, 0x0020);
+
+	/* disable audio; this seems to reduce a clicking noise a bit.. */
+	EMU8000_HWCF3_WRITE(emu, 0);
+
+	/* initialize audio channels */
+	init_audio(emu);
+
+	/* initialize DMA */
+	init_dma(emu);
+
+	/* initialize init arrays */
+	init_arrays(emu);
+
+	/*
+	 * Initialize the FM section of the AWE32, this is needed
+	 * for DRAM refresh as well
+	 */
+	snd_emu8000_init_fm(emu);
+
+	/* terminate all voices */
+	for (i = 0; i < EMU8000_DRAM_VOICES; i++)
+		EMU8000_DCYSUSV_WRITE(emu, 0, 0x807F);
+	
+	/* check DRAM memory size */
+	size_dram(emu);
+
+	/* enable audio */
+	EMU8000_HWCF3_WRITE(emu, 0x4);
+
+	/* set equzlier, chorus and reverb modes */
+	snd_emu8000_update_equalizer(emu);
+	snd_emu8000_update_chorus_mode(emu);
+	snd_emu8000_update_reverb_mode(emu);
+}
+
+
+/*----------------------------------------------------------------
+ * Bass/Treble Equalizer
+ *----------------------------------------------------------------*/
+
+static unsigned short bass_parm[12][3] = {
+	{0xD26A, 0xD36A, 0x0000}, /* -12 dB */
+	{0xD25B, 0xD35B, 0x0000}, /*  -8 */
+	{0xD24C, 0xD34C, 0x0000}, /*  -6 */
+	{0xD23D, 0xD33D, 0x0000}, /*  -4 */
+	{0xD21F, 0xD31F, 0x0000}, /*  -2 */
+	{0xC208, 0xC308, 0x0001}, /*   0 (HW default) */
+	{0xC219, 0xC319, 0x0001}, /*  +2 */
+	{0xC22A, 0xC32A, 0x0001}, /*  +4 */
+	{0xC24C, 0xC34C, 0x0001}, /*  +6 */
+	{0xC26E, 0xC36E, 0x0001}, /*  +8 */
+	{0xC248, 0xC384, 0x0002}, /* +10 */
+	{0xC26A, 0xC36A, 0x0002}, /* +12 dB */
+};
+
+static unsigned short treble_parm[12][9] = {
+	{0x821E, 0xC26A, 0x031E, 0xC36A, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, /* -12 dB */
+	{0x821E, 0xC25B, 0x031E, 0xC35B, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001},
+	{0x821E, 0xC24C, 0x031E, 0xC34C, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001},
+	{0x821E, 0xC23D, 0x031E, 0xC33D, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001},
+	{0x821E, 0xC21F, 0x031E, 0xC31F, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001},
+	{0x821E, 0xD208, 0x031E, 0xD308, 0x021E, 0xD208, 0x831E, 0xD308, 0x0002},
+	{0x821E, 0xD208, 0x031E, 0xD308, 0x021D, 0xD219, 0x831D, 0xD319, 0x0002},
+	{0x821E, 0xD208, 0x031E, 0xD308, 0x021C, 0xD22A, 0x831C, 0xD32A, 0x0002},
+	{0x821E, 0xD208, 0x031E, 0xD308, 0x021A, 0xD24C, 0x831A, 0xD34C, 0x0002},
+	{0x821E, 0xD208, 0x031E, 0xD308, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002}, /* +8 (HW default) */
+	{0x821D, 0xD219, 0x031D, 0xD319, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002},
+	{0x821C, 0xD22A, 0x031C, 0xD32A, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002}  /* +12 dB */
+};
+
+
+/*
+ * set Emu8000 digital equalizer; from 0 to 11 [-12dB - 12dB]
+ */
+/*exported*/ void
+snd_emu8000_update_equalizer(emu8000_t *emu)
+{
+	unsigned short w;
+	int bass = emu->bass_level;
+	int treble = emu->treble_level;
+
+	if (bass < 0 || bass > 11 || treble < 0 || treble > 11)
+		return;
+	EMU8000_INIT4_WRITE(emu, 0x01, bass_parm[bass][0]);
+	EMU8000_INIT4_WRITE(emu, 0x11, bass_parm[bass][1]);
+	EMU8000_INIT3_WRITE(emu, 0x11, treble_parm[treble][0]);
+	EMU8000_INIT3_WRITE(emu, 0x13, treble_parm[treble][1]);
+	EMU8000_INIT3_WRITE(emu, 0x1b, treble_parm[treble][2]);
+	EMU8000_INIT4_WRITE(emu, 0x07, treble_parm[treble][3]);
+	EMU8000_INIT4_WRITE(emu, 0x0b, treble_parm[treble][4]);
+	EMU8000_INIT4_WRITE(emu, 0x0d, treble_parm[treble][5]);
+	EMU8000_INIT4_WRITE(emu, 0x17, treble_parm[treble][6]);
+	EMU8000_INIT4_WRITE(emu, 0x19, treble_parm[treble][7]);
+	w = bass_parm[bass][2] + treble_parm[treble][8];
+	EMU8000_INIT4_WRITE(emu, 0x15, (unsigned short)(w + 0x0262));
+	EMU8000_INIT4_WRITE(emu, 0x1d, (unsigned short)(w + 0x8362));
+}
+
+
+/*----------------------------------------------------------------
+ * Chorus mode control
+ *----------------------------------------------------------------*/
+
+/*
+ * chorus mode parameters
+ */
+#define SNDRV_EMU8000_CHORUS_1		0
+#define	SNDRV_EMU8000_CHORUS_2		1
+#define	SNDRV_EMU8000_CHORUS_3		2
+#define	SNDRV_EMU8000_CHORUS_4		3
+#define	SNDRV_EMU8000_CHORUS_FEEDBACK	4
+#define	SNDRV_EMU8000_CHORUS_FLANGER	5
+#define	SNDRV_EMU8000_CHORUS_SHORTDELAY	6
+#define	SNDRV_EMU8000_CHORUS_SHORTDELAY2	7
+#define SNDRV_EMU8000_CHORUS_PREDEFINED	8
+/* user can define chorus modes up to 32 */
+#define SNDRV_EMU8000_CHORUS_NUMBERS	32
+
+typedef struct soundfont_chorus_fx_t {
+	unsigned short feedback;	/* feedback level (0xE600-0xE6FF) */
+	unsigned short delay_offset;	/* delay (0-0x0DA3) [1/44100 sec] */
+	unsigned short lfo_depth;	/* LFO depth (0xBC00-0xBCFF) */
+	unsigned int delay;	/* right delay (0-0xFFFFFFFF) [1/256/44100 sec] */
+	unsigned int lfo_freq;		/* LFO freq LFO freq (0-0xFFFFFFFF) */
+} soundfont_chorus_fx_t;
+
+/* 5 parameters for each chorus mode; 3 x 16bit, 2 x 32bit */
+static char chorus_defined[SNDRV_EMU8000_CHORUS_NUMBERS];
+static soundfont_chorus_fx_t chorus_parm[SNDRV_EMU8000_CHORUS_NUMBERS] = {
+	{0xE600, 0x03F6, 0xBC2C ,0x00000000, 0x0000006D}, /* chorus 1 */
+	{0xE608, 0x031A, 0xBC6E, 0x00000000, 0x0000017C}, /* chorus 2 */
+	{0xE610, 0x031A, 0xBC84, 0x00000000, 0x00000083}, /* chorus 3 */
+	{0xE620, 0x0269, 0xBC6E, 0x00000000, 0x0000017C}, /* chorus 4 */
+	{0xE680, 0x04D3, 0xBCA6, 0x00000000, 0x0000005B}, /* feedback */
+	{0xE6E0, 0x044E, 0xBC37, 0x00000000, 0x00000026}, /* flanger */
+	{0xE600, 0x0B06, 0xBC00, 0x0006E000, 0x00000083}, /* short delay */
+	{0xE6C0, 0x0B06, 0xBC00, 0x0006E000, 0x00000083}, /* short delay + feedback */
+};
+
+/*exported*/ int
+snd_emu8000_load_chorus_fx(emu8000_t *emu, int mode, const void __user *buf, long len)
+{
+	soundfont_chorus_fx_t rec;
+	if (mode < SNDRV_EMU8000_CHORUS_PREDEFINED || mode >= SNDRV_EMU8000_CHORUS_NUMBERS) {
+		snd_printk(KERN_WARNING "invalid chorus mode %d for uploading\n", mode);
+		return -EINVAL;
+	}
+	if (len < (long)sizeof(rec) || copy_from_user(&rec, buf, sizeof(rec)))
+		return -EFAULT;
+	chorus_parm[mode] = rec;
+	chorus_defined[mode] = 1;
+	return 0;
+}
+
+/*exported*/ void
+snd_emu8000_update_chorus_mode(emu8000_t *emu)
+{
+	int effect = emu->chorus_mode;
+	if (effect < 0 || effect >= SNDRV_EMU8000_CHORUS_NUMBERS ||
+	    (effect >= SNDRV_EMU8000_CHORUS_PREDEFINED && !chorus_defined[effect]))
+		return;
+	EMU8000_INIT3_WRITE(emu, 0x09, chorus_parm[effect].feedback);
+	EMU8000_INIT3_WRITE(emu, 0x0c, chorus_parm[effect].delay_offset);
+	EMU8000_INIT4_WRITE(emu, 0x03, chorus_parm[effect].lfo_depth);
+	EMU8000_HWCF4_WRITE(emu, chorus_parm[effect].delay);
+	EMU8000_HWCF5_WRITE(emu, chorus_parm[effect].lfo_freq);
+	EMU8000_HWCF6_WRITE(emu, 0x8000);
+	EMU8000_HWCF7_WRITE(emu, 0x0000);
+}
+
+/*----------------------------------------------------------------
+ * Reverb mode control
+ *----------------------------------------------------------------*/
+
+/*
+ * reverb mode parameters
+ */
+#define	SNDRV_EMU8000_REVERB_ROOM1	0
+#define SNDRV_EMU8000_REVERB_ROOM2	1
+#define	SNDRV_EMU8000_REVERB_ROOM3	2
+#define	SNDRV_EMU8000_REVERB_HALL1	3
+#define	SNDRV_EMU8000_REVERB_HALL2	4
+#define	SNDRV_EMU8000_REVERB_PLATE	5
+#define	SNDRV_EMU8000_REVERB_DELAY	6
+#define	SNDRV_EMU8000_REVERB_PANNINGDELAY 7
+#define SNDRV_EMU8000_REVERB_PREDEFINED	8
+/* user can define reverb modes up to 32 */
+#define SNDRV_EMU8000_REVERB_NUMBERS	32
+
+typedef struct soundfont_reverb_fx_t {
+	unsigned short parms[28];
+} soundfont_reverb_fx_t;
+
+/* reverb mode settings; write the following 28 data of 16 bit length
+ *   on the corresponding ports in the reverb_cmds array
+ */
+static char reverb_defined[SNDRV_EMU8000_CHORUS_NUMBERS];
+static soundfont_reverb_fx_t reverb_parm[SNDRV_EMU8000_REVERB_NUMBERS] = {
+{{  /* room 1 */
+	0xB488, 0xA450, 0x9550, 0x84B5, 0x383A, 0x3EB5, 0x72F4,
+	0x72A4, 0x7254, 0x7204, 0x7204, 0x7204, 0x4416, 0x4516,
+	0xA490, 0xA590, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429,
+	0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528,
+}},
+{{  /* room 2 */
+	0xB488, 0xA458, 0x9558, 0x84B5, 0x383A, 0x3EB5, 0x7284,
+	0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4448, 0x4548,
+	0xA440, 0xA540, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429,
+	0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528,
+}},
+{{  /* room 3 */
+	0xB488, 0xA460, 0x9560, 0x84B5, 0x383A, 0x3EB5, 0x7284,
+	0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4416, 0x4516,
+	0xA490, 0xA590, 0x842C, 0x852C, 0x842C, 0x852C, 0x842B,
+	0x852B, 0x842B, 0x852B, 0x842A, 0x852A, 0x842A, 0x852A,
+}},
+{{  /* hall 1 */
+	0xB488, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7284,
+	0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4448, 0x4548,
+	0xA440, 0xA540, 0x842B, 0x852B, 0x842B, 0x852B, 0x842A,
+	0x852A, 0x842A, 0x852A, 0x8429, 0x8529, 0x8429, 0x8529,
+}},
+{{  /* hall 2 */
+	0xB488, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7254,
+	0x7234, 0x7224, 0x7254, 0x7264, 0x7294, 0x44C3, 0x45C3,
+	0xA404, 0xA504, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429,
+	0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528,
+}},
+{{  /* plate */
+	0xB4FF, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7234,
+	0x7234, 0x7234, 0x7234, 0x7234, 0x7234, 0x4448, 0x4548,
+	0xA440, 0xA540, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429,
+	0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528,
+}},
+{{  /* delay */
+	0xB4FF, 0xA470, 0x9500, 0x84B5, 0x333A, 0x39B5, 0x7204,
+	0x7204, 0x7204, 0x7204, 0x7204, 0x72F4, 0x4400, 0x4500,
+	0xA4FF, 0xA5FF, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420,
+	0x8520, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, 0x8520,
+}},
+{{  /* panning delay */
+	0xB4FF, 0xA490, 0x9590, 0x8474, 0x333A, 0x39B5, 0x7204,
+	0x7204, 0x7204, 0x7204, 0x7204, 0x72F4, 0x4400, 0x4500,
+	0xA4FF, 0xA5FF, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420,
+	0x8520, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, 0x8520,
+}},
+};
+
+enum { DATA1, DATA2 };
+#define AWE_INIT1(c)	EMU8000_CMD(2,c), DATA1
+#define AWE_INIT2(c)	EMU8000_CMD(2,c), DATA2
+#define AWE_INIT3(c)	EMU8000_CMD(3,c), DATA1
+#define AWE_INIT4(c)	EMU8000_CMD(3,c), DATA2
+
+static struct reverb_cmd_pair {
+	unsigned short cmd, port;
+} reverb_cmds[28] = {
+  {AWE_INIT1(0x03)}, {AWE_INIT1(0x05)}, {AWE_INIT4(0x1F)}, {AWE_INIT1(0x07)},
+  {AWE_INIT2(0x14)}, {AWE_INIT2(0x16)}, {AWE_INIT1(0x0F)}, {AWE_INIT1(0x17)},
+  {AWE_INIT1(0x1F)}, {AWE_INIT2(0x07)}, {AWE_INIT2(0x0F)}, {AWE_INIT2(0x17)},
+  {AWE_INIT2(0x1D)}, {AWE_INIT2(0x1F)}, {AWE_INIT3(0x01)}, {AWE_INIT3(0x03)},
+  {AWE_INIT1(0x09)}, {AWE_INIT1(0x0B)}, {AWE_INIT1(0x11)}, {AWE_INIT1(0x13)},
+  {AWE_INIT1(0x19)}, {AWE_INIT1(0x1B)}, {AWE_INIT2(0x01)}, {AWE_INIT2(0x03)},
+  {AWE_INIT2(0x09)}, {AWE_INIT2(0x0B)}, {AWE_INIT2(0x11)}, {AWE_INIT2(0x13)},
+};
+
+/*exported*/ int
+snd_emu8000_load_reverb_fx(emu8000_t *emu, int mode, const void __user *buf, long len)
+{
+	soundfont_reverb_fx_t rec;
+
+	if (mode < SNDRV_EMU8000_REVERB_PREDEFINED || mode >= SNDRV_EMU8000_REVERB_NUMBERS) {
+		snd_printk(KERN_WARNING "invalid reverb mode %d for uploading\n", mode);
+		return -EINVAL;
+	}
+	if (len < (long)sizeof(rec) || copy_from_user(&rec, buf, sizeof(rec)))
+		return -EFAULT;
+	reverb_parm[mode] = rec;
+	reverb_defined[mode] = 1;
+	return 0;
+}
+
+/*exported*/ void
+snd_emu8000_update_reverb_mode(emu8000_t *emu)
+{
+	int effect = emu->reverb_mode;
+	int i;
+
+	if (effect < 0 || effect >= SNDRV_EMU8000_REVERB_NUMBERS ||
+	    (effect >= SNDRV_EMU8000_REVERB_PREDEFINED && !reverb_defined[effect]))
+		return;
+	for (i = 0; i < 28; i++) {
+		int port;
+		if (reverb_cmds[i].port == DATA1)
+			port = EMU8000_DATA1(emu);
+		else
+			port = EMU8000_DATA2(emu);
+		snd_emu8000_poke(emu, port, reverb_cmds[i].cmd, reverb_parm[effect].parms[i]);
+	}
+}
+
+
+/*----------------------------------------------------------------
+ * mixer interface
+ *----------------------------------------------------------------*/
+
+/*
+ * bass/treble
+ */
+static int mixer_bass_treble_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 11;
+	return 0;
+}
+
+static int mixer_bass_treble_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	emu8000_t *emu = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = kcontrol->private_value ? emu->treble_level : emu->bass_level;
+	return 0;
+}
+
+static int mixer_bass_treble_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	emu8000_t *emu = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned short val1;
+	
+	val1 = ucontrol->value.integer.value[0] % 12;
+	spin_lock_irqsave(&emu->control_lock, flags);
+	if (kcontrol->private_value) {
+		change = val1 != emu->treble_level;
+		emu->treble_level = val1;
+	} else {
+		change = val1 != emu->bass_level;
+		emu->bass_level = val1;
+	}
+	spin_unlock_irqrestore(&emu->control_lock, flags);
+	snd_emu8000_update_equalizer(emu);
+	return change;
+}
+
+static snd_kcontrol_new_t mixer_bass_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Synth Tone Control - Bass",
+	.info = mixer_bass_treble_info,
+	.get = mixer_bass_treble_get,
+	.put = mixer_bass_treble_put,
+	.private_value = 0,
+};
+
+static snd_kcontrol_new_t mixer_treble_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Synth Tone Control - Treble",
+	.info = mixer_bass_treble_info,
+	.get = mixer_bass_treble_get,
+	.put = mixer_bass_treble_put,
+	.private_value = 1,
+};
+
+/*
+ * chorus/reverb mode
+ */
+static int mixer_chorus_reverb_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = kcontrol->private_value ? (SNDRV_EMU8000_CHORUS_NUMBERS-1) : (SNDRV_EMU8000_REVERB_NUMBERS-1);
+	return 0;
+}
+
+static int mixer_chorus_reverb_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	emu8000_t *emu = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = kcontrol->private_value ? emu->chorus_mode : emu->reverb_mode;
+	return 0;
+}
+
+static int mixer_chorus_reverb_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	emu8000_t *emu = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned short val1;
+	
+	spin_lock_irqsave(&emu->control_lock, flags);
+	if (kcontrol->private_value) {
+		val1 = ucontrol->value.integer.value[0] % SNDRV_EMU8000_CHORUS_NUMBERS;
+		change = val1 != emu->chorus_mode;
+		emu->chorus_mode = val1;
+	} else {
+		val1 = ucontrol->value.integer.value[0] % SNDRV_EMU8000_REVERB_NUMBERS;
+		change = val1 != emu->reverb_mode;
+		emu->reverb_mode = val1;
+	}
+	spin_unlock_irqrestore(&emu->control_lock, flags);
+	if (change) {
+		if (kcontrol->private_value)
+			snd_emu8000_update_chorus_mode(emu);
+		else
+			snd_emu8000_update_reverb_mode(emu);
+	}
+	return change;
+}
+
+static snd_kcontrol_new_t mixer_chorus_mode_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Chorus Mode",
+	.info = mixer_chorus_reverb_info,
+	.get = mixer_chorus_reverb_get,
+	.put = mixer_chorus_reverb_put,
+	.private_value = 1,
+};
+
+static snd_kcontrol_new_t mixer_reverb_mode_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Reverb Mode",
+	.info = mixer_chorus_reverb_info,
+	.get = mixer_chorus_reverb_get,
+	.put = mixer_chorus_reverb_put,
+	.private_value = 0,
+};
+
+/*
+ * FM OPL3 chorus/reverb depth
+ */
+static int mixer_fm_depth_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 255;
+	return 0;
+}
+
+static int mixer_fm_depth_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	emu8000_t *emu = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = kcontrol->private_value ? emu->fm_chorus_depth : emu->fm_reverb_depth;
+	return 0;
+}
+
+static int mixer_fm_depth_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	emu8000_t *emu = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned short val1;
+	
+	val1 = ucontrol->value.integer.value[0] % 256;
+	spin_lock_irqsave(&emu->control_lock, flags);
+	if (kcontrol->private_value) {
+		change = val1 != emu->fm_chorus_depth;
+		emu->fm_chorus_depth = val1;
+	} else {
+		change = val1 != emu->fm_reverb_depth;
+		emu->fm_reverb_depth = val1;
+	}
+	spin_unlock_irqrestore(&emu->control_lock, flags);
+	if (change)
+		snd_emu8000_init_fm(emu);
+	return change;
+}
+
+static snd_kcontrol_new_t mixer_fm_chorus_depth_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "FM Chorus Depth",
+	.info = mixer_fm_depth_info,
+	.get = mixer_fm_depth_get,
+	.put = mixer_fm_depth_put,
+	.private_value = 1,
+};
+
+static snd_kcontrol_new_t mixer_fm_reverb_depth_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "FM Reverb Depth",
+	.info = mixer_fm_depth_info,
+	.get = mixer_fm_depth_get,
+	.put = mixer_fm_depth_put,
+	.private_value = 0,
+};
+
+
+static snd_kcontrol_new_t *mixer_defs[EMU8000_NUM_CONTROLS] = {
+	&mixer_bass_control,
+	&mixer_treble_control,
+	&mixer_chorus_mode_control,
+	&mixer_reverb_mode_control,
+	&mixer_fm_chorus_depth_control,
+	&mixer_fm_reverb_depth_control,
+};
+
+/*
+ * create and attach mixer elements for WaveTable treble/bass controls
+ */
+static int __init
+snd_emu8000_create_mixer(snd_card_t *card, emu8000_t *emu)
+{
+	int i, err = 0;
+
+	snd_assert(emu != NULL && card != NULL, return -EINVAL);
+
+	spin_lock_init(&emu->control_lock);
+
+	memset(emu->controls, 0, sizeof(emu->controls));
+	for (i = 0; i < EMU8000_NUM_CONTROLS; i++) {
+		if ((err = snd_ctl_add(card, emu->controls[i] = snd_ctl_new1(mixer_defs[i], emu))) < 0)
+			goto __error;
+	}
+	return 0;
+
+__error:
+	for (i = 0; i < EMU8000_NUM_CONTROLS; i++) {
+		down_write(&card->controls_rwsem);
+		if (emu->controls[i])
+			snd_ctl_remove(card, emu->controls[i]);
+		up_write(&card->controls_rwsem);
+	}
+	return err;
+}
+
+
+/*
+ * free resources
+ */
+static int snd_emu8000_free(emu8000_t *hw)
+{
+	if (hw->res_port1) {
+		release_resource(hw->res_port1);
+		kfree_nocheck(hw->res_port1);
+	}
+	if (hw->res_port2) {
+		release_resource(hw->res_port2);
+		kfree_nocheck(hw->res_port2);
+	}
+	if (hw->res_port3) {
+		release_resource(hw->res_port3);
+		kfree_nocheck(hw->res_port3);
+	}
+	kfree(hw);
+	return 0;
+}
+
+/*
+ */
+static int snd_emu8000_dev_free(snd_device_t *device)
+{
+	emu8000_t *hw = device->device_data;
+	return snd_emu8000_free(hw);
+}
+
+/*
+ * initialize and register emu8000 synth device.
+ */
+int __init
+snd_emu8000_new(snd_card_t *card, int index, long port, int seq_ports, snd_seq_device_t **awe_ret)
+{
+	snd_seq_device_t *awe;
+	emu8000_t *hw;
+	int err;
+	static snd_device_ops_t ops = {
+		.dev_free = snd_emu8000_dev_free,
+	};
+
+	if (awe_ret)
+		*awe_ret = NULL;
+
+	if (seq_ports <= 0)
+		return 0;
+
+	hw = kcalloc(1, sizeof(*hw), GFP_KERNEL);
+	if (hw == NULL)
+		return -ENOMEM;
+	spin_lock_init(&hw->reg_lock);
+	hw->index = index;
+	hw->port1 = port;
+	hw->port2 = port + 0x400;
+	hw->port3 = port + 0x800;
+	if (!(hw->res_port1 = request_region(hw->port1, 4, "Emu8000-1")) ||
+	    !(hw->res_port2 = request_region(hw->port2, 4, "Emu8000-2")) ||
+	    !(hw->res_port3 = request_region(hw->port3, 4, "Emu8000-3"))) {
+		snd_printk(KERN_ERR "sbawe: can't grab ports 0x%lx, 0x%lx, 0x%lx\n", hw->port1, hw->port2, hw->port3);
+		snd_emu8000_free(hw);
+		return -EBUSY;
+	}
+	hw->mem_size = 0;
+	hw->card = card;
+	hw->seq_ports = seq_ports;
+	hw->bass_level = 5;
+	hw->treble_level = 9;
+	hw->chorus_mode = 2;
+	hw->reverb_mode = 4;
+	hw->fm_chorus_depth = 0;
+	hw->fm_reverb_depth = 0;
+
+	if (snd_emu8000_detect(hw) < 0) {
+		snd_emu8000_free(hw);
+		return -ENODEV;
+	}
+
+	snd_emu8000_init_hw(hw);
+	if ((err = snd_emu8000_create_mixer(card, hw)) < 0) {
+		snd_emu8000_free(hw);
+		return err;
+	}
+	
+	if ((err = snd_device_new(card, SNDRV_DEV_CODEC, hw, &ops)) < 0) {
+		snd_emu8000_free(hw);
+		return err;
+	}
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+	if (snd_seq_device_new(card, index, SNDRV_SEQ_DEV_ID_EMU8000,
+			       sizeof(emu8000_t*), &awe) >= 0) {
+		strcpy(awe->name, "EMU-8000");
+		*(emu8000_t**)SNDRV_SEQ_DEVICE_ARGPTR(awe) = hw;
+	}
+#else
+	awe = NULL;
+#endif
+	if (awe_ret)
+		*awe_ret = awe;
+
+	return 0;
+}
+
+
+/*
+ * exported stuff
+ */
+
+EXPORT_SYMBOL(snd_emu8000_poke);
+EXPORT_SYMBOL(snd_emu8000_peek);
+EXPORT_SYMBOL(snd_emu8000_poke_dw);
+EXPORT_SYMBOL(snd_emu8000_peek_dw);
+EXPORT_SYMBOL(snd_emu8000_dma_chan);
+EXPORT_SYMBOL(snd_emu8000_init_fm);
+EXPORT_SYMBOL(snd_emu8000_load_chorus_fx);
+EXPORT_SYMBOL(snd_emu8000_load_reverb_fx);
+EXPORT_SYMBOL(snd_emu8000_update_chorus_mode);
+EXPORT_SYMBOL(snd_emu8000_update_reverb_mode);
+EXPORT_SYMBOL(snd_emu8000_update_equalizer);
diff --git a/sound/isa/sb/emu8000_callback.c b/sound/isa/sb/emu8000_callback.c
new file mode 100644
index 0000000..1cc4101
--- /dev/null
+++ b/sound/isa/sb/emu8000_callback.c
@@ -0,0 +1,543 @@
+/*
+ *  synth callback routines for the emu8000 (AWE32/64)
+ *
+ *  Copyright (C) 1999 Steve Ratcliffe
+ *  Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include "emu8000_local.h"
+#include <sound/asoundef.h>
+
+/*
+ * prototypes
+ */
+static snd_emux_voice_t *get_voice(snd_emux_t *emu, snd_emux_port_t *port);
+static int start_voice(snd_emux_voice_t *vp);
+static void trigger_voice(snd_emux_voice_t *vp);
+static void release_voice(snd_emux_voice_t *vp);
+static void update_voice(snd_emux_voice_t *vp, int update);
+static void reset_voice(snd_emux_t *emu, int ch);
+static void terminate_voice(snd_emux_voice_t *vp);
+static void sysex(snd_emux_t *emu, char *buf, int len, int parsed, snd_midi_channel_set_t *chset);
+#ifdef CONFIG_SND_SEQUENCER_OSS
+static int oss_ioctl(snd_emux_t *emu, int cmd, int p1, int p2);
+#endif
+static int load_fx(snd_emux_t *emu, int type, int mode, const void __user *buf, long len);
+
+static void set_pitch(emu8000_t *hw, snd_emux_voice_t *vp);
+static void set_volume(emu8000_t *hw, snd_emux_voice_t *vp);
+static void set_pan(emu8000_t *hw, snd_emux_voice_t *vp);
+static void set_fmmod(emu8000_t *hw, snd_emux_voice_t *vp);
+static void set_tremfreq(emu8000_t *hw, snd_emux_voice_t *vp);
+static void set_fm2frq2(emu8000_t *hw, snd_emux_voice_t *vp);
+static void set_filterQ(emu8000_t *hw, snd_emux_voice_t *vp);
+static void snd_emu8000_tweak_voice(emu8000_t *emu, int ch);
+
+/*
+ * Ensure a value is between two points
+ * macro evaluates its args more than once, so changed to upper-case.
+ */
+#define LIMITVALUE(x, a, b) do { if ((x) < (a)) (x) = (a); else if ((x) > (b)) (x) = (b); } while (0)
+#define LIMITMAX(x, a) do {if ((x) > (a)) (x) = (a); } while (0)
+
+
+/*
+ * set up operators
+ */
+static snd_emux_operators_t emu8000_ops = {
+	.owner =	THIS_MODULE,
+	.get_voice =	get_voice,
+	.prepare =	start_voice,
+	.trigger =	trigger_voice,
+	.release =	release_voice,
+	.update =	update_voice,
+	.terminate =	terminate_voice,
+	.reset =	reset_voice,
+	.sample_new =	snd_emu8000_sample_new,
+	.sample_free =	snd_emu8000_sample_free,
+	.sample_reset = snd_emu8000_sample_reset,
+	.load_fx =	load_fx,
+	.sysex =	sysex,
+#ifdef CONFIG_SND_SEQUENCER_OSS
+	.oss_ioctl =	oss_ioctl,
+#endif
+};
+
+void
+snd_emu8000_ops_setup(emu8000_t *hw)
+{
+	hw->emu->ops = emu8000_ops;
+}
+
+
+
+/*
+ * Terminate a voice
+ */
+static void
+release_voice(snd_emux_voice_t *vp)
+{
+	int dcysusv;
+	emu8000_t *hw;
+
+	hw = vp->hw;
+	dcysusv = 0x8000 | (unsigned char)vp->reg.parm.modrelease;
+	EMU8000_DCYSUS_WRITE(hw, vp->ch, dcysusv);
+	dcysusv = 0x8000 | (unsigned char)vp->reg.parm.volrelease;
+	EMU8000_DCYSUSV_WRITE(hw, vp->ch, dcysusv);
+}
+
+
+/*
+ */
+static void
+terminate_voice(snd_emux_voice_t *vp)
+{
+	emu8000_t *hw; 
+
+	hw = vp->hw;
+	EMU8000_DCYSUSV_WRITE(hw, vp->ch, 0x807F);
+}
+
+
+/*
+ */
+static void
+update_voice(snd_emux_voice_t *vp, int update)
+{
+	emu8000_t *hw;
+
+	hw = vp->hw;
+	if (update & SNDRV_EMUX_UPDATE_VOLUME)
+		set_volume(hw, vp);
+	if (update & SNDRV_EMUX_UPDATE_PITCH)
+		set_pitch(hw, vp);
+	if ((update & SNDRV_EMUX_UPDATE_PAN) &&
+	    vp->port->ctrls[EMUX_MD_REALTIME_PAN])
+		set_pan(hw, vp);
+	if (update & SNDRV_EMUX_UPDATE_FMMOD)
+		set_fmmod(hw, vp);
+	if (update & SNDRV_EMUX_UPDATE_TREMFREQ)
+		set_tremfreq(hw, vp);
+	if (update & SNDRV_EMUX_UPDATE_FM2FRQ2)
+		set_fm2frq2(hw, vp);
+	if (update & SNDRV_EMUX_UPDATE_Q)
+		set_filterQ(hw, vp);
+}
+
+
+/*
+ * Find a channel (voice) within the EMU that is not in use or at least
+ * less in use than other channels.  Always returns a valid pointer
+ * no matter what.  If there is a real shortage of voices then one
+ * will be cut. Such is life.
+ *
+ * The channel index (vp->ch) must be initialized in this routine.
+ * In Emu8k, it is identical with the array index.
+ */
+static snd_emux_voice_t *
+get_voice(snd_emux_t *emu, snd_emux_port_t *port)
+{
+	int  i;
+	snd_emux_voice_t *vp;
+	emu8000_t *hw;
+
+	/* what we are looking for, in order of preference */
+	enum {
+		OFF=0, RELEASED, PLAYING, END
+	};
+
+	/* Keeps track of what we are finding */
+	struct best {
+		unsigned int  time;
+		int voice;
+	} best[END];
+	struct best *bp;
+
+	hw = emu->hw;
+
+	for (i = 0; i < END; i++) {
+		best[i].time = (unsigned int)(-1); /* XXX MAX_?INT really */;
+		best[i].voice = -1;
+	}
+
+	/*
+	 * Go through them all and get a best one to use.
+	 */
+	for (i = 0; i < emu->max_voices; i++) {
+		int state, val;
+
+		vp = &emu->voices[i];
+		state = vp->state;
+
+		if (state == SNDRV_EMUX_ST_OFF)
+			bp = best + OFF;
+		else if (state == SNDRV_EMUX_ST_RELEASED ||
+			 state == SNDRV_EMUX_ST_PENDING) {
+			bp = best + RELEASED;
+			val = (EMU8000_CVCF_READ(hw, vp->ch) >> 16) & 0xffff;
+			if (! val)
+				bp = best + OFF;
+		}
+		else if (state & SNDRV_EMUX_ST_ON)
+			bp = best + PLAYING;
+		else
+			continue;
+
+		/* check if sample is finished playing (non-looping only) */
+		if (state != SNDRV_EMUX_ST_OFF &&
+		    (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_SINGLESHOT)) {
+			val = EMU8000_CCCA_READ(hw, vp->ch) & 0xffffff;
+			if (val >= vp->reg.loopstart)
+				bp = best + OFF;
+		}
+
+		if (vp->time < bp->time) {
+			bp->time = vp->time;
+			bp->voice = i;
+		}
+	}
+
+	for (i = 0; i < END; i++) {
+		if (best[i].voice >= 0) {
+			vp = &emu->voices[best[i].voice];
+			vp->ch = best[i].voice;
+			return vp;
+		}
+	}
+
+	/* not found */
+	return NULL;
+}
+
+/*
+ */
+static int
+start_voice(snd_emux_voice_t *vp)
+{
+	unsigned int temp;
+	int ch;
+	int addr;
+	snd_midi_channel_t *chan;
+	emu8000_t *hw;
+
+	hw = vp->hw;
+	ch = vp->ch;
+	chan = vp->chan;
+
+	/* channel to be silent and idle */
+	EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080);
+	EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF);
+	EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF);
+	EMU8000_PTRX_WRITE(hw, ch, 0);
+	EMU8000_CPF_WRITE(hw, ch, 0);
+
+	/* set pitch offset */
+	set_pitch(hw, vp);
+
+	/* set envelope parameters */
+	EMU8000_ENVVAL_WRITE(hw, ch, vp->reg.parm.moddelay);
+	EMU8000_ATKHLD_WRITE(hw, ch, vp->reg.parm.modatkhld);
+	EMU8000_DCYSUS_WRITE(hw, ch, vp->reg.parm.moddcysus);
+	EMU8000_ENVVOL_WRITE(hw, ch, vp->reg.parm.voldelay);
+	EMU8000_ATKHLDV_WRITE(hw, ch, vp->reg.parm.volatkhld);
+	/* decay/sustain parameter for volume envelope is used
+	   for triggerg the voice */
+
+	/* cutoff and volume */
+	set_volume(hw, vp);
+
+	/* modulation envelope heights */
+	EMU8000_PEFE_WRITE(hw, ch, vp->reg.parm.pefe);
+
+	/* lfo1/2 delay */
+	EMU8000_LFO1VAL_WRITE(hw, ch, vp->reg.parm.lfo1delay);
+	EMU8000_LFO2VAL_WRITE(hw, ch, vp->reg.parm.lfo2delay);
+
+	/* lfo1 pitch & cutoff shift */
+	set_fmmod(hw, vp);
+	/* lfo1 volume & freq */
+	set_tremfreq(hw, vp);
+	/* lfo2 pitch & freq */
+	set_fm2frq2(hw, vp);
+	/* pan & loop start */
+	set_pan(hw, vp);
+
+	/* chorus & loop end (chorus 8bit, MSB) */
+	addr = vp->reg.loopend - 1;
+	temp = vp->reg.parm.chorus;
+	temp += (int)chan->control[MIDI_CTL_E3_CHORUS_DEPTH] * 9 / 10;
+	LIMITMAX(temp, 255);
+	temp = (temp <<24) | (unsigned int)addr;
+	EMU8000_CSL_WRITE(hw, ch, temp);
+
+	/* Q & current address (Q 4bit value, MSB) */
+	addr = vp->reg.start - 1;
+	temp = vp->reg.parm.filterQ;
+	temp = (temp<<28) | (unsigned int)addr;
+	EMU8000_CCCA_WRITE(hw, ch, temp);
+
+	/* clear unknown registers */
+	EMU8000_00A0_WRITE(hw, ch, 0);
+	EMU8000_0080_WRITE(hw, ch, 0);
+
+	/* reset volume */
+	temp = vp->vtarget << 16;
+	EMU8000_VTFT_WRITE(hw, ch, temp | vp->ftarget);
+	EMU8000_CVCF_WRITE(hw, ch, temp | 0xff00);
+
+	return 0;
+}
+
+/*
+ * Start envelope
+ */
+static void
+trigger_voice(snd_emux_voice_t *vp)
+{
+	int ch = vp->ch;
+	unsigned int temp;
+	emu8000_t *hw;
+
+	hw = vp->hw;
+
+	/* set reverb and pitch target */
+	temp = vp->reg.parm.reverb;
+	temp += (int)vp->chan->control[MIDI_CTL_E1_REVERB_DEPTH] * 9 / 10;
+	LIMITMAX(temp, 255);
+	temp = (temp << 8) | (vp->ptarget << 16) | vp->aaux;
+	EMU8000_PTRX_WRITE(hw, ch, temp);
+	EMU8000_CPF_WRITE(hw, ch, vp->ptarget << 16);
+	EMU8000_DCYSUSV_WRITE(hw, ch, vp->reg.parm.voldcysus);
+}
+
+/*
+ * reset voice parameters
+ */
+static void
+reset_voice(snd_emux_t *emu, int ch)
+{
+	emu8000_t *hw;
+
+	hw = emu->hw;
+	EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F);
+	snd_emu8000_tweak_voice(hw, ch);
+}
+
+/*
+ * Set the pitch of a possibly playing note.
+ */
+static void
+set_pitch(emu8000_t *hw, snd_emux_voice_t *vp)
+{
+	EMU8000_IP_WRITE(hw, vp->ch, vp->apitch);
+}
+
+/*
+ * Set the volume of a possibly already playing note
+ */
+static void
+set_volume(emu8000_t *hw, snd_emux_voice_t *vp)
+{
+	int  ifatn;
+
+	ifatn = (unsigned char)vp->acutoff;
+	ifatn = (ifatn << 8);
+	ifatn |= (unsigned char)vp->avol;
+	EMU8000_IFATN_WRITE(hw, vp->ch, ifatn);
+}
+
+/*
+ * Set pan and loop start address.
+ */
+static void
+set_pan(emu8000_t *hw, snd_emux_voice_t *vp)
+{
+	unsigned int temp;
+
+	temp = ((unsigned int)vp->apan<<24) | ((unsigned int)vp->reg.loopstart - 1);
+	EMU8000_PSST_WRITE(hw, vp->ch, temp);
+}
+
+#define MOD_SENSE 18
+
+static void
+set_fmmod(emu8000_t *hw, snd_emux_voice_t *vp)
+{
+	unsigned short fmmod;
+	short pitch;
+	unsigned char cutoff;
+	int modulation;
+
+	pitch = (char)(vp->reg.parm.fmmod>>8);
+	cutoff = (vp->reg.parm.fmmod & 0xff);
+	modulation = vp->chan->gm_modulation + vp->chan->midi_pressure;
+	pitch += (MOD_SENSE * modulation) / 1200;
+	LIMITVALUE(pitch, -128, 127);
+	fmmod = ((unsigned char)pitch<<8) | cutoff;
+	EMU8000_FMMOD_WRITE(hw, vp->ch, fmmod);
+}
+
+/* set tremolo (lfo1) volume & frequency */
+static void
+set_tremfreq(emu8000_t *hw, snd_emux_voice_t *vp)
+{
+	EMU8000_TREMFRQ_WRITE(hw, vp->ch, vp->reg.parm.tremfrq);
+}
+
+/* set lfo2 pitch & frequency */
+static void
+set_fm2frq2(emu8000_t *hw, snd_emux_voice_t *vp)
+{
+	unsigned short fm2frq2;
+	short pitch;
+	unsigned char freq;
+	int modulation;
+
+	pitch = (char)(vp->reg.parm.fm2frq2>>8);
+	freq = vp->reg.parm.fm2frq2 & 0xff;
+	modulation = vp->chan->gm_modulation + vp->chan->midi_pressure;
+	pitch += (MOD_SENSE * modulation) / 1200;
+	LIMITVALUE(pitch, -128, 127);
+	fm2frq2 = ((unsigned char)pitch<<8) | freq;
+	EMU8000_FM2FRQ2_WRITE(hw, vp->ch, fm2frq2);
+}
+
+/* set filterQ */
+static void
+set_filterQ(emu8000_t *hw, snd_emux_voice_t *vp)
+{
+	unsigned int addr;
+	addr = EMU8000_CCCA_READ(hw, vp->ch) & 0xffffff;
+	addr |= (vp->reg.parm.filterQ << 28);
+	EMU8000_CCCA_WRITE(hw, vp->ch, addr);
+}
+
+/*
+ * set the envelope & LFO parameters to the default values
+ */
+static void
+snd_emu8000_tweak_voice(emu8000_t *emu, int i)
+{
+	/* set all mod/vol envelope shape to minimum */
+	EMU8000_ENVVOL_WRITE(emu, i, 0x8000);
+	EMU8000_ENVVAL_WRITE(emu, i, 0x8000);
+	EMU8000_DCYSUS_WRITE(emu, i, 0x7F7F);
+	EMU8000_ATKHLDV_WRITE(emu, i, 0x7F7F);
+	EMU8000_ATKHLD_WRITE(emu, i, 0x7F7F);
+	EMU8000_PEFE_WRITE(emu, i, 0);  /* mod envelope height to zero */
+	EMU8000_LFO1VAL_WRITE(emu, i, 0x8000); /* no delay for LFO1 */
+	EMU8000_LFO2VAL_WRITE(emu, i, 0x8000);
+	EMU8000_IP_WRITE(emu, i, 0xE000);	/* no pitch shift */
+	EMU8000_IFATN_WRITE(emu, i, 0xFF00);	/* volume to minimum */
+	EMU8000_FMMOD_WRITE(emu, i, 0);
+	EMU8000_TREMFRQ_WRITE(emu, i, 0);
+	EMU8000_FM2FRQ2_WRITE(emu, i, 0);
+}
+
+/*
+ * sysex callback
+ */
+static void
+sysex(snd_emux_t *emu, char *buf, int len, int parsed, snd_midi_channel_set_t *chset)
+{
+	emu8000_t *hw;
+
+	hw = emu->hw;
+
+	switch (parsed) {
+	case SNDRV_MIDI_SYSEX_GS_CHORUS_MODE:
+		hw->chorus_mode = chset->gs_chorus_mode;
+		snd_emu8000_update_chorus_mode(hw);
+		break;
+
+	case SNDRV_MIDI_SYSEX_GS_REVERB_MODE:
+		hw->reverb_mode = chset->gs_reverb_mode;
+		snd_emu8000_update_reverb_mode(hw);
+		break;
+	}
+}
+
+
+#ifdef CONFIG_SND_SEQUENCER_OSS
+/*
+ * OSS ioctl callback
+ */
+static int
+oss_ioctl(snd_emux_t *emu, int cmd, int p1, int p2)
+{
+	emu8000_t *hw;
+
+	hw = emu->hw;
+
+	switch (cmd) {
+	case _EMUX_OSS_REVERB_MODE:
+		hw->reverb_mode = p1;
+		snd_emu8000_update_reverb_mode(hw);
+		break;
+
+	case _EMUX_OSS_CHORUS_MODE:
+		hw->chorus_mode = p1;
+		snd_emu8000_update_chorus_mode(hw);
+		break;
+
+	case _EMUX_OSS_INITIALIZE_CHIP:
+		/* snd_emu8000_init(hw); */ /*ignored*/
+		break;
+
+	case _EMUX_OSS_EQUALIZER:
+		hw->bass_level = p1;
+		hw->treble_level = p2;
+		snd_emu8000_update_equalizer(hw);
+		break;
+	}
+	return 0;
+}
+#endif
+
+
+/*
+ * additional patch keys
+ */
+
+#define SNDRV_EMU8000_LOAD_CHORUS_FX	0x10	/* optarg=mode */
+#define SNDRV_EMU8000_LOAD_REVERB_FX	0x11	/* optarg=mode */
+
+
+/*
+ * callback routine
+ */
+
+static int
+load_fx(snd_emux_t *emu, int type, int mode, const void __user *buf, long len)
+{
+	emu8000_t *hw;
+	hw = emu->hw;
+
+	/* skip header */
+	buf += 16;
+	len -= 16;
+
+	switch (type) {
+	case SNDRV_EMU8000_LOAD_CHORUS_FX:
+		return snd_emu8000_load_chorus_fx(hw, mode, buf, len);
+	case SNDRV_EMU8000_LOAD_REVERB_FX:
+		return snd_emu8000_load_reverb_fx(hw, mode, buf, len);
+	}
+	return -EINVAL;
+}
+
diff --git a/sound/isa/sb/emu8000_local.h b/sound/isa/sb/emu8000_local.h
new file mode 100644
index 0000000..ea4996a
--- /dev/null
+++ b/sound/isa/sb/emu8000_local.h
@@ -0,0 +1,43 @@
+#ifndef __EMU8000_LOCAL_H
+#define __EMU8000_LOCAL_H
+/*
+ *  Local defininitons for the emu8000 (AWE32/64)
+ *
+ *  Copyright (C) 1999 Steve Ratcliffe
+ *  Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <sound/driver.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/emu8000.h>
+#include <sound/emu8000_reg.h>
+
+/* emu8000_patch.c */
+int snd_emu8000_sample_new(snd_emux_t *rec, snd_sf_sample_t *sp, snd_util_memhdr_t *hdr, const void __user *data, long count);
+int snd_emu8000_sample_free(snd_emux_t *rec, snd_sf_sample_t *sp, snd_util_memhdr_t *hdr);
+void snd_emu8000_sample_reset(snd_emux_t *rec);
+
+/* emu8000_callback.c */
+void snd_emu8000_ops_setup(emu8000_t *emu);
+
+/* emu8000_pcm.c */
+int snd_emu8000_pcm_new(snd_card_t *card, emu8000_t *emu, int index);
+
+#endif	/* __EMU8000_LOCAL_H */
diff --git a/sound/isa/sb/emu8000_patch.c b/sound/isa/sb/emu8000_patch.c
new file mode 100644
index 0000000..4afc4a1
--- /dev/null
+++ b/sound/isa/sb/emu8000_patch.c
@@ -0,0 +1,303 @@
+/*
+ *  Patch routines for the emu8000 (AWE32/64)
+ *
+ *  Copyright (C) 1999 Steve Ratcliffe
+ *  Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include "emu8000_local.h"
+#include <asm/uaccess.h>
+#include <linux/moduleparam.h>
+
+static int emu8000_reset_addr = 0;
+module_param(emu8000_reset_addr, int, 0444);
+MODULE_PARM_DESC(emu8000_reset_addr, "reset write address at each time (makes slowdown)");
+
+
+/*
+ * Open up channels.
+ */
+static int
+snd_emu8000_open_dma(emu8000_t *emu, int write)
+{
+	int i;
+
+	/* reserve all 30 voices for loading */
+	for (i = 0; i < EMU8000_DRAM_VOICES; i++) {
+		snd_emux_lock_voice(emu->emu, i);
+		snd_emu8000_dma_chan(emu, i, write);
+	}
+
+	/* assign voice 31 and 32 to ROM */
+	EMU8000_VTFT_WRITE(emu, 30, 0);
+	EMU8000_PSST_WRITE(emu, 30, 0x1d8);
+	EMU8000_CSL_WRITE(emu, 30, 0x1e0);
+	EMU8000_CCCA_WRITE(emu, 30, 0x1d8);
+	EMU8000_VTFT_WRITE(emu, 31, 0);
+	EMU8000_PSST_WRITE(emu, 31, 0x1d8);
+	EMU8000_CSL_WRITE(emu, 31, 0x1e0);
+	EMU8000_CCCA_WRITE(emu, 31, 0x1d8);
+
+	return 0;
+}
+
+/*
+ * Close all dram channels.
+ */
+static void
+snd_emu8000_close_dma(emu8000_t *emu)
+{
+	int i;
+
+	for (i = 0; i < EMU8000_DRAM_VOICES; i++) {
+		snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE);
+		snd_emux_unlock_voice(emu->emu, i);
+	}
+}
+
+/*
+ */
+
+#define BLANK_LOOP_START	4
+#define BLANK_LOOP_END		8
+#define BLANK_LOOP_SIZE		12
+#define BLANK_HEAD_SIZE		48
+
+/*
+ * Read a word from userland, taking care of conversions from
+ * 8bit samples etc.
+ */
+static unsigned short
+read_word(const void __user *buf, int offset, int mode)
+{
+	unsigned short c;
+	if (mode & SNDRV_SFNT_SAMPLE_8BITS) {
+		unsigned char cc;
+		get_user(cc, (unsigned char __user *)buf + offset);
+		c = cc << 8; /* convert 8bit -> 16bit */
+	} else {
+#ifdef SNDRV_LITTLE_ENDIAN
+		get_user(c, (unsigned short __user *)buf + offset);
+#else
+		unsigned short cc;
+		get_user(cc, (unsigned short __user *)buf + offset);
+		c = swab16(cc);
+#endif
+	}
+	if (mode & SNDRV_SFNT_SAMPLE_UNSIGNED)
+		c ^= 0x8000; /* unsigned -> signed */
+	return c;
+}
+
+/*
+ */
+static void
+snd_emu8000_write_wait(emu8000_t *emu)
+{
+	while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(1);
+		if (signal_pending(current))
+			break;
+	}
+}
+
+/*
+ * write sample word data
+ *
+ * You should not have to keep resetting the address each time
+ * as the chip is supposed to step on the next address automatically.
+ * It mostly does, but during writes of some samples at random it
+ * completely loses words (every one in 16 roughly but with no
+ * obvious pattern).
+ *
+ * This is therefore much slower than need be, but is at least
+ * working.
+ */
+inline static void
+write_word(emu8000_t *emu, int *offset, unsigned short data)
+{
+	if (emu8000_reset_addr) {
+		if (emu8000_reset_addr > 1)
+			snd_emu8000_write_wait(emu);
+		EMU8000_SMALW_WRITE(emu, *offset);
+	}
+	EMU8000_SMLD_WRITE(emu, data);
+	*offset += 1;
+}
+
+/*
+ * Write the sample to EMU800 memory.  This routine is invoked out of
+ * the generic soundfont routines as a callback.
+ */
+int
+snd_emu8000_sample_new(snd_emux_t *rec, snd_sf_sample_t *sp,
+		       snd_util_memhdr_t *hdr, const void __user *data, long count)
+{
+	int  i;
+	int  rc;
+	int  offset;
+	int  truesize;
+	int  dram_offset, dram_start;
+	emu8000_t *emu;
+
+	emu = rec->hw;
+	snd_assert(sp != NULL, return -EINVAL);
+
+	if (sp->v.size == 0)
+		return 0;
+
+	/* be sure loop points start < end */
+	if (sp->v.loopstart > sp->v.loopend) {
+		int tmp = sp->v.loopstart;
+		sp->v.loopstart = sp->v.loopend;
+		sp->v.loopend = tmp;
+	}
+
+	/* compute true data size to be loaded */
+	truesize = sp->v.size;
+	if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP))
+		truesize += sp->v.loopend - sp->v.loopstart;
+	if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK)
+		truesize += BLANK_LOOP_SIZE;
+
+	sp->block = snd_util_mem_alloc(hdr, truesize * 2);
+	if (sp->block == NULL) {
+		/*snd_printd("EMU8000: out of memory\n");*/
+		/* not ENOMEM (for compatibility) */
+		return -ENOSPC;
+	}
+
+	if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS) {
+		if (!access_ok(VERIFY_READ, data, sp->v.size))
+			return -EFAULT;
+	} else {
+		if (!access_ok(VERIFY_READ, data, sp->v.size * 2))
+			return -EFAULT;
+	}
+
+	/* recalculate address offset */
+	sp->v.end -= sp->v.start;
+	sp->v.loopstart -= sp->v.start;
+	sp->v.loopend -= sp->v.start;
+	sp->v.start = 0;
+
+	/* dram position (in word) -- mem_offset is byte */
+	dram_offset = EMU8000_DRAM_OFFSET + (sp->block->offset >> 1);
+	dram_start = dram_offset;
+
+	/* set the total size (store onto obsolete checksum value) */
+	sp->v.truesize = truesize * 2; /* in bytes */
+
+	snd_emux_terminate_all(emu->emu);
+	if ((rc = snd_emu8000_open_dma(emu, EMU8000_RAM_WRITE)) != 0)
+		return rc;
+
+	/* Set the address to start writing at */
+	snd_emu8000_write_wait(emu);
+	EMU8000_SMALW_WRITE(emu, dram_offset);
+
+	/*snd_emu8000_init_fm(emu);*/
+
+#if 0
+	/* first block - write 48 samples for silence */
+	if (! sp->block->offset) {
+		for (i = 0; i < BLANK_HEAD_SIZE; i++) {
+			write_word(emu, &dram_offset, 0);
+		}
+	}
+#endif
+
+	offset = 0;
+	for (i = 0; i < sp->v.size; i++) {
+		unsigned short s;
+
+		s = read_word(data, offset, sp->v.mode_flags);
+		offset++;
+		write_word(emu, &dram_offset, s);
+
+		/* we may take too long time in this loop.
+		 * so give controls back to kernel if needed.
+		 */
+		cond_resched();
+
+		if (i == sp->v.loopend &&
+		    (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP)))
+		{
+			int looplen = sp->v.loopend - sp->v.loopstart;
+			int k;
+
+			/* copy reverse loop */
+			for (k = 1; k <= looplen; k++) {
+				s = read_word(data, offset - k, sp->v.mode_flags);
+				write_word(emu, &dram_offset, s);
+			}
+			if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_BIDIR_LOOP) {
+				sp->v.loopend += looplen;
+			} else {
+				sp->v.loopstart += looplen;
+				sp->v.loopend += looplen;
+			}
+			sp->v.end += looplen;
+		}
+	}
+
+	/* if no blank loop is attached in the sample, add it */
+	if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK) {
+		for (i = 0; i < BLANK_LOOP_SIZE; i++) {
+			write_word(emu, &dram_offset, 0);
+		}
+		if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_SINGLESHOT) {
+			sp->v.loopstart = sp->v.end + BLANK_LOOP_START;
+			sp->v.loopend = sp->v.end + BLANK_LOOP_END;
+		}
+	}
+
+	/* add dram offset */
+	sp->v.start += dram_start;
+	sp->v.end += dram_start;
+	sp->v.loopstart += dram_start;
+	sp->v.loopend += dram_start;
+
+	snd_emu8000_close_dma(emu);
+	snd_emu8000_init_fm(emu);
+
+	return 0;
+}
+
+/*
+ * free a sample block
+ */
+int
+snd_emu8000_sample_free(snd_emux_t *rec, snd_sf_sample_t *sp, snd_util_memhdr_t *hdr)
+{
+	if (sp->block) {
+		snd_util_mem_free(hdr, sp->block);
+		sp->block = NULL;
+	}
+	return 0;
+}
+
+
+/*
+ * sample_reset callback - terminate voices
+ */
+void
+snd_emu8000_sample_reset(snd_emux_t *rec)
+{
+	snd_emux_terminate_all(rec);
+}
diff --git a/sound/isa/sb/emu8000_pcm.c b/sound/isa/sb/emu8000_pcm.c
new file mode 100644
index 0000000..db5eb8b
--- /dev/null
+++ b/sound/isa/sb/emu8000_pcm.c
@@ -0,0 +1,704 @@
+/*
+ * pcm emulation on emu8000 wavetable
+ *
+ *  Copyright (C) 2002 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include "emu8000_local.h"
+#include <linux/init.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+
+/*
+ * define the following if you want to use this pcm with non-interleaved mode
+ */
+/* #define USE_NONINTERLEAVE */
+
+/* NOTE: for using the non-interleaved mode with alsa-lib, you have to set
+ * mmap_emulation flag to 1 in your .asoundrc, such like
+ *
+ *	pcm.emu8k {
+ *		type plug
+ *		slave.pcm {
+ *			type hw
+ *			card 0
+ *			device 1
+ *			mmap_emulation 1
+ *		}
+ *	}
+ *
+ * besides, for the time being, the non-interleaved mode doesn't work well on
+ * alsa-lib...
+ */
+
+
+typedef struct snd_emu8k_pcm emu8k_pcm_t;
+
+struct snd_emu8k_pcm {
+	emu8000_t *emu;
+	snd_pcm_substream_t *substream;
+
+	unsigned int allocated_bytes;
+	snd_util_memblk_t *block;
+	unsigned int offset;
+	unsigned int buf_size;
+	unsigned int period_size;
+	unsigned int loop_start[2];
+	unsigned int pitch;
+	int panning[2];
+	int last_ptr;
+	int period_pos;
+	int voices;
+	unsigned int dram_opened: 1;
+	unsigned int running: 1;
+	unsigned int timer_running: 1;
+	struct timer_list timer;
+	spinlock_t timer_lock;
+};
+
+#define LOOP_BLANK_SIZE		8
+
+
+/*
+ * open up channels for the simultaneous data transfer and playback
+ */
+static int
+emu8k_open_dram_for_pcm(emu8000_t *emu, int channels)
+{
+	int i;
+
+	/* reserve up to 2 voices for playback */
+	snd_emux_lock_voice(emu->emu, 0);
+	if (channels > 1)
+		snd_emux_lock_voice(emu->emu, 1);
+
+	/* reserve 28 voices for loading */
+	for (i = channels + 1; i < EMU8000_DRAM_VOICES; i++) {
+		unsigned int mode = EMU8000_RAM_WRITE;
+		snd_emux_lock_voice(emu->emu, i);
+#ifndef USE_NONINTERLEAVE
+		if (channels > 1 && (i & 1) != 0)
+			mode |= EMU8000_RAM_RIGHT;
+#endif
+		snd_emu8000_dma_chan(emu, i, mode);
+	}
+
+	/* assign voice 31 and 32 to ROM */
+	EMU8000_VTFT_WRITE(emu, 30, 0);
+	EMU8000_PSST_WRITE(emu, 30, 0x1d8);
+	EMU8000_CSL_WRITE(emu, 30, 0x1e0);
+	EMU8000_CCCA_WRITE(emu, 30, 0x1d8);
+	EMU8000_VTFT_WRITE(emu, 31, 0);
+	EMU8000_PSST_WRITE(emu, 31, 0x1d8);
+	EMU8000_CSL_WRITE(emu, 31, 0x1e0);
+	EMU8000_CCCA_WRITE(emu, 31, 0x1d8);
+
+	return 0;
+}
+
+/*
+ */
+static void
+snd_emu8000_write_wait(emu8000_t *emu, int can_schedule)
+{
+	while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
+		if (can_schedule) {
+			set_current_state(TASK_INTERRUPTIBLE);
+			schedule_timeout(1);
+			if (signal_pending(current))
+				break;
+		}
+	}
+}
+
+/*
+ * close all channels
+ */
+static void
+emu8k_close_dram(emu8000_t *emu)
+{
+	int i;
+
+	for (i = 0; i < 2; i++)
+		snd_emux_unlock_voice(emu->emu, i);
+	for (; i < EMU8000_DRAM_VOICES; i++) {
+		snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE);
+		snd_emux_unlock_voice(emu->emu, i);
+	}
+}
+
+/*
+ * convert Hz to AWE32 rate offset (see emux/soundfont.c)
+ */
+
+#define OFFSET_SAMPLERATE	1011119		/* base = 44100 */
+#define SAMPLERATE_RATIO	4096
+
+static int calc_rate_offset(int hz)
+{
+	return snd_sf_linear_to_log(hz, OFFSET_SAMPLERATE, SAMPLERATE_RATIO);
+}
+
+
+/*
+ */
+
+static snd_pcm_hardware_t emu8k_pcm_hw = {
+#ifdef USE_NONINTERLEAVE
+	.info =			SNDRV_PCM_INFO_NONINTERLEAVED,
+#else
+	.info =			SNDRV_PCM_INFO_INTERLEAVED,
+#endif
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	1024,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		1024,
+	.fifo_size =		0,
+
+};
+
+/*
+ * get the current position at the given channel from CCCA register
+ */
+static inline int emu8k_get_curpos(emu8k_pcm_t *rec, int ch)
+{
+	int val = EMU8000_CCCA_READ(rec->emu, ch) & 0xfffffff;
+	val -= rec->loop_start[ch] - 1;
+	return val;
+}
+
+
+/*
+ * timer interrupt handler
+ * check the current position and update the period if necessary.
+ */
+static void emu8k_pcm_timer_func(unsigned long data)
+{
+	emu8k_pcm_t *rec = (emu8k_pcm_t *)data;
+	int ptr, delta;
+
+	spin_lock(&rec->timer_lock);
+	/* update the current pointer */
+	ptr = emu8k_get_curpos(rec, 0);
+	if (ptr < rec->last_ptr)
+		delta = ptr + rec->buf_size - rec->last_ptr;
+	else
+		delta = ptr - rec->last_ptr;
+	rec->period_pos += delta;
+	rec->last_ptr = ptr;
+
+	/* reprogram timer */
+	rec->timer.expires = jiffies + 1;
+	add_timer(&rec->timer);
+
+	/* update period */
+	if (rec->period_pos >= (int)rec->period_size) {
+		rec->period_pos %= rec->period_size;
+		spin_unlock(&rec->timer_lock);
+		snd_pcm_period_elapsed(rec->substream);
+		return;
+	}
+	spin_unlock(&rec->timer_lock);
+}
+
+
+/*
+ * open pcm
+ * creating an instance here
+ */
+static int emu8k_pcm_open(snd_pcm_substream_t *subs)
+{
+	emu8000_t *emu = snd_pcm_substream_chip(subs);
+	emu8k_pcm_t *rec;
+	snd_pcm_runtime_t *runtime = subs->runtime;
+
+	rec = kcalloc(1, sizeof(*rec), GFP_KERNEL);
+	if (! rec)
+		return -ENOMEM;
+
+	rec->emu = emu;
+	rec->substream = subs;
+	runtime->private_data = rec;
+
+	spin_lock_init(&rec->timer_lock);
+	init_timer(&rec->timer);
+	rec->timer.function = emu8k_pcm_timer_func;
+	rec->timer.data = (unsigned long)rec;
+
+	runtime->hw = emu8k_pcm_hw;
+	runtime->hw.buffer_bytes_max = emu->mem_size - LOOP_BLANK_SIZE * 3;
+	runtime->hw.period_bytes_max = runtime->hw.buffer_bytes_max / 2;
+
+	/* use timer to update periods.. (specified in msec) */
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+				     (1000000 + HZ - 1) / HZ, UINT_MAX);
+
+	return 0;
+}
+
+static int emu8k_pcm_close(snd_pcm_substream_t *subs)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+	kfree(rec);
+	subs->runtime->private_data = NULL;
+	return 0;
+}
+
+/*
+ * calculate pitch target
+ */
+static int calc_pitch_target(int pitch)
+{
+	int ptarget = 1 << (pitch >> 12);
+	if (pitch & 0x800) ptarget += (ptarget * 0x102e) / 0x2710;
+	if (pitch & 0x400) ptarget += (ptarget * 0x764) / 0x2710;
+	if (pitch & 0x200) ptarget += (ptarget * 0x389) / 0x2710;
+	ptarget += (ptarget >> 1);
+	if (ptarget > 0xffff) ptarget = 0xffff;
+	return ptarget;
+}
+
+/*
+ * set up the voice
+ */
+static void setup_voice(emu8k_pcm_t *rec, int ch)
+{
+	emu8000_t *hw = rec->emu;
+	unsigned int temp;
+
+	/* channel to be silent and idle */
+	EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080);
+	EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF);
+	EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF);
+	EMU8000_PTRX_WRITE(hw, ch, 0);
+	EMU8000_CPF_WRITE(hw, ch, 0);
+
+	/* pitch offset */
+	EMU8000_IP_WRITE(hw, ch, rec->pitch);
+	/* set envelope parameters */
+	EMU8000_ENVVAL_WRITE(hw, ch, 0x8000);
+	EMU8000_ATKHLD_WRITE(hw, ch, 0x7f7f);
+	EMU8000_DCYSUS_WRITE(hw, ch, 0x7f7f);
+	EMU8000_ENVVOL_WRITE(hw, ch, 0x8000);
+	EMU8000_ATKHLDV_WRITE(hw, ch, 0x7f7f);
+	/* decay/sustain parameter for volume envelope is used
+	   for triggerg the voice */
+	/* modulation envelope heights */
+	EMU8000_PEFE_WRITE(hw, ch, 0x0);
+	/* lfo1/2 delay */
+	EMU8000_LFO1VAL_WRITE(hw, ch, 0x8000);
+	EMU8000_LFO2VAL_WRITE(hw, ch, 0x8000);
+	/* lfo1 pitch & cutoff shift */
+	EMU8000_FMMOD_WRITE(hw, ch, 0);
+	/* lfo1 volume & freq */
+	EMU8000_TREMFRQ_WRITE(hw, ch, 0);
+	/* lfo2 pitch & freq */
+	EMU8000_FM2FRQ2_WRITE(hw, ch, 0);
+	/* pan & loop start */
+	temp = rec->panning[ch];
+	temp = (temp <<24) | ((unsigned int)rec->loop_start[ch] - 1);
+	EMU8000_PSST_WRITE(hw, ch, temp);
+	/* chorus & loop end (chorus 8bit, MSB) */
+	temp = 0; // chorus
+	temp = (temp << 24) | ((unsigned int)rec->loop_start[ch] + rec->buf_size - 1);
+	EMU8000_CSL_WRITE(hw, ch, temp);
+	/* Q & current address (Q 4bit value, MSB) */
+	temp = 0; // filterQ
+	temp = (temp << 28) | ((unsigned int)rec->loop_start[ch] - 1);
+	EMU8000_CCCA_WRITE(hw, ch, temp);
+	/* clear unknown registers */
+	EMU8000_00A0_WRITE(hw, ch, 0);
+	EMU8000_0080_WRITE(hw, ch, 0);
+}
+
+/*
+ * trigger the voice
+ */
+static void start_voice(emu8k_pcm_t *rec, int ch)
+{
+	unsigned long flags;
+	emu8000_t *hw = rec->emu;
+	unsigned int temp, aux;
+	int pt = calc_pitch_target(rec->pitch);
+
+	/* cutoff and volume */
+	EMU8000_IFATN_WRITE(hw, ch, 0xff00);
+	EMU8000_VTFT_WRITE(hw, ch, 0xffff);
+	EMU8000_CVCF_WRITE(hw, ch, 0xffff);
+	/* trigger envelope */
+	EMU8000_DCYSUSV_WRITE(hw, ch, 0x7f7f);
+	/* set reverb and pitch target */
+	temp = 0; // reverb
+	if (rec->panning[ch] == 0)
+		aux = 0xff;
+	else
+		aux = (-rec->panning[ch]) & 0xff;
+	temp = (temp << 8) | (pt << 16) | aux;
+	EMU8000_PTRX_WRITE(hw, ch, temp);
+	EMU8000_CPF_WRITE(hw, ch, pt << 16);
+
+	/* start timer */
+	spin_lock_irqsave(&rec->timer_lock, flags);
+	if (! rec->timer_running) {
+		rec->timer.expires = jiffies + 1;
+		add_timer(&rec->timer);
+		rec->timer_running = 1;
+	}
+	spin_unlock_irqrestore(&rec->timer_lock, flags);
+}
+
+/*
+ * stop the voice immediately
+ */
+static void stop_voice(emu8k_pcm_t *rec, int ch)
+{
+	unsigned long flags;
+	emu8000_t *hw = rec->emu;
+
+	EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F);
+
+	/* stop timer */
+	spin_lock_irqsave(&rec->timer_lock, flags);
+	if (rec->timer_running) {
+		del_timer(&rec->timer);
+		rec->timer_running = 0;
+	}
+	spin_unlock_irqrestore(&rec->timer_lock, flags);
+}
+
+static int emu8k_pcm_trigger(snd_pcm_substream_t *subs, int cmd)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+	int ch;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		for (ch = 0; ch < rec->voices; ch++)
+			start_voice(rec, ch);
+		rec->running = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		rec->running = 0;
+		for (ch = 0; ch < rec->voices; ch++)
+			stop_voice(rec, ch);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+
+/*
+ * copy / silence ops
+ */
+
+/*
+ * this macro should be inserted in the copy/silence loops
+ * to reduce the latency.  without this, the system will hang up
+ * during the whole loop.
+ */
+#define CHECK_SCHEDULER() \
+do { \
+	cond_resched();\
+	if (signal_pending(current))\
+		return -EAGAIN;\
+} while (0)
+
+
+#ifdef USE_NONINTERLEAVE
+/* copy one channel block */
+static int emu8k_transfer_block(emu8000_t *emu, int offset, unsigned short *buf, int count)
+{
+	EMU8000_SMALW_WRITE(emu, offset);
+	while (count > 0) {
+		unsigned short sval;
+		CHECK_SCHEDULER();
+		get_user(sval, buf);
+		EMU8000_SMLD_WRITE(emu, sval);
+		buf++;
+		count--;
+	}
+	return 0;
+}
+
+static int emu8k_pcm_copy(snd_pcm_substream_t *subs,
+			  int voice,
+			  snd_pcm_uframes_t pos,
+			  void *src,
+			  snd_pcm_uframes_t count)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+	emu8000_t *emu = rec->emu;
+
+	snd_emu8000_write_wait(emu, 1);
+	if (voice == -1) {
+		unsigned short *buf = src;
+		int i, err;
+		count /= rec->voices;
+		for (i = 0; i < rec->voices; i++) {
+			err = emu8k_transfer_block(emu, pos + rec->loop_start[i], buf, count);
+			if (err < 0)
+				return err;
+			buf += count;
+		}
+		return 0;
+	} else {
+		return emu8k_transfer_block(emu, pos + rec->loop_start[voice], src, count);
+	}
+}
+
+/* make a channel block silence */
+static int emu8k_silence_block(emu8000_t *emu, int offset, int count)
+{
+	EMU8000_SMALW_WRITE(emu, offset);
+	while (count > 0) {
+		CHECK_SCHEDULER();
+		EMU8000_SMLD_WRITE(emu, 0);
+		count--;
+	}
+	return 0;
+}
+
+static int emu8k_pcm_silence(snd_pcm_substream_t *subs,
+			     int voice,
+			     snd_pcm_uframes_t pos,
+			     snd_pcm_uframes_t count)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+	emu8000_t *emu = rec->emu;
+
+	snd_emu8000_write_wait(emu, 1);
+	if (voice == -1 && rec->voices == 1)
+		voice = 0;
+	if (voice == -1) {
+		int err;
+		err = emu8k_silence_block(emu, pos + rec->loop_start[0], count / 2);
+		if (err < 0)
+			return err;
+		return emu8k_silence_block(emu, pos + rec->loop_start[1], count / 2);
+	} else {
+		return emu8k_silence_block(emu, pos + rec->loop_start[voice], count);
+	}
+}
+
+#else /* interleave */
+
+/*
+ * copy the interleaved data can be done easily by using
+ * DMA "left" and "right" channels on emu8k engine.
+ */
+static int emu8k_pcm_copy(snd_pcm_substream_t *subs,
+			  int voice,
+			  snd_pcm_uframes_t pos,
+			  void __user *src,
+			  snd_pcm_uframes_t count)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+	emu8000_t *emu = rec->emu;
+	unsigned short __user *buf = src;
+
+	snd_emu8000_write_wait(emu, 1);
+	EMU8000_SMALW_WRITE(emu, pos + rec->loop_start[0]);
+	if (rec->voices > 1)
+		EMU8000_SMARW_WRITE(emu, pos + rec->loop_start[1]);
+
+	while (count-- > 0) {
+		unsigned short sval;
+		CHECK_SCHEDULER();
+		get_user(sval, buf);
+		EMU8000_SMLD_WRITE(emu, sval);
+		buf++;
+		if (rec->voices > 1) {
+			CHECK_SCHEDULER();
+			get_user(sval, buf);
+			EMU8000_SMRD_WRITE(emu, sval);
+			buf++;
+		}
+	}
+	return 0;
+}
+
+static int emu8k_pcm_silence(snd_pcm_substream_t *subs,
+			     int voice,
+			     snd_pcm_uframes_t pos,
+			     snd_pcm_uframes_t count)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+	emu8000_t *emu = rec->emu;
+
+	snd_emu8000_write_wait(emu, 1);
+	EMU8000_SMALW_WRITE(emu, rec->loop_start[0] + pos);
+	if (rec->voices > 1)
+		EMU8000_SMARW_WRITE(emu, rec->loop_start[1] + pos);
+	while (count-- > 0) {
+		CHECK_SCHEDULER();
+		EMU8000_SMLD_WRITE(emu, 0);
+		if (rec->voices > 1) {
+			CHECK_SCHEDULER();
+			EMU8000_SMRD_WRITE(emu, 0);
+		}
+	}
+	return 0;
+}
+#endif
+
+
+/*
+ * allocate a memory block
+ */
+static int emu8k_pcm_hw_params(snd_pcm_substream_t *subs,
+			       snd_pcm_hw_params_t *hw_params)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+
+	if (rec->block) {
+		/* reallocation - release the old block */
+		snd_util_mem_free(rec->emu->memhdr, rec->block);
+		rec->block = NULL;
+	}
+
+	rec->allocated_bytes = params_buffer_bytes(hw_params) + LOOP_BLANK_SIZE * 4;
+	rec->block = snd_util_mem_alloc(rec->emu->memhdr, rec->allocated_bytes);
+	if (! rec->block)
+		return -ENOMEM;
+	rec->offset = EMU8000_DRAM_OFFSET + (rec->block->offset >> 1); /* in word */
+	/* at least dma_bytes must be set for non-interleaved mode */
+	subs->dma_buffer.bytes = params_buffer_bytes(hw_params);
+
+	return 0;
+}
+
+/*
+ * free the memory block
+ */
+static int emu8k_pcm_hw_free(snd_pcm_substream_t *subs)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+
+	if (rec->block) {
+		int ch;
+		for (ch = 0; ch < rec->voices; ch++)
+			stop_voice(rec, ch); // to be sure
+		if (rec->dram_opened)
+			emu8k_close_dram(rec->emu);
+		snd_util_mem_free(rec->emu->memhdr, rec->block);
+		rec->block = NULL;
+	}
+	return 0;
+}
+
+/*
+ */
+static int emu8k_pcm_prepare(snd_pcm_substream_t *subs)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+
+	rec->pitch = 0xe000 + calc_rate_offset(subs->runtime->rate);
+	rec->last_ptr = 0;
+	rec->period_pos = 0;
+
+	rec->buf_size = subs->runtime->buffer_size;
+	rec->period_size = subs->runtime->period_size;
+	rec->voices = subs->runtime->channels;
+	rec->loop_start[0] = rec->offset + LOOP_BLANK_SIZE;
+	if (rec->voices > 1)
+		rec->loop_start[1] = rec->loop_start[0] + rec->buf_size + LOOP_BLANK_SIZE;
+	if (rec->voices > 1) {
+		rec->panning[0] = 0xff;
+		rec->panning[1] = 0x00;
+	} else
+		rec->panning[0] = 0x80;
+
+	if (! rec->dram_opened) {
+		int err, i, ch;
+
+		snd_emux_terminate_all(rec->emu->emu);
+		if ((err = emu8k_open_dram_for_pcm(rec->emu, rec->voices)) != 0)
+			return err;
+		rec->dram_opened = 1;
+
+		/* clear loop blanks */
+		snd_emu8000_write_wait(rec->emu, 0);
+		EMU8000_SMALW_WRITE(rec->emu, rec->offset);
+		for (i = 0; i < LOOP_BLANK_SIZE; i++)
+			EMU8000_SMLD_WRITE(rec->emu, 0);
+		for (ch = 0; ch < rec->voices; ch++) {
+			EMU8000_SMALW_WRITE(rec->emu, rec->loop_start[ch] + rec->buf_size);
+			for (i = 0; i < LOOP_BLANK_SIZE; i++)
+				EMU8000_SMLD_WRITE(rec->emu, 0);
+		}
+	}
+
+	setup_voice(rec, 0);
+	if (rec->voices > 1)
+		setup_voice(rec, 1);
+	return 0;
+}
+
+static snd_pcm_uframes_t emu8k_pcm_pointer(snd_pcm_substream_t *subs)
+{
+	emu8k_pcm_t *rec = subs->runtime->private_data;
+	if (rec->running)
+		return emu8k_get_curpos(rec, 0);
+	return 0;
+}
+
+
+static snd_pcm_ops_t emu8k_pcm_ops = {
+	.open =		emu8k_pcm_open,
+	.close =	emu8k_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	emu8k_pcm_hw_params,
+	.hw_free =	emu8k_pcm_hw_free,
+	.prepare =	emu8k_pcm_prepare,
+	.trigger =	emu8k_pcm_trigger,
+	.pointer =	emu8k_pcm_pointer,
+	.copy =		emu8k_pcm_copy,
+	.silence =	emu8k_pcm_silence,
+};
+
+
+static void snd_emu8000_pcm_free(snd_pcm_t *pcm)
+{
+	emu8000_t *emu = pcm->private_data;
+	emu->pcm = NULL;
+}
+
+int snd_emu8000_pcm_new(snd_card_t *card, emu8000_t *emu, int index)
+{
+	snd_pcm_t *pcm;
+	int err;
+
+	if ((err = snd_pcm_new(card, "Emu8000 PCM", index, 1, 0, &pcm)) < 0)
+		return err;
+	pcm->private_data = emu;
+	pcm->private_free = snd_emu8000_pcm_free;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &emu8k_pcm_ops);
+	emu->pcm = pcm;
+
+	snd_device_register(card, pcm);
+
+	return 0;
+}
diff --git a/sound/isa/sb/emu8000_synth.c b/sound/isa/sb/emu8000_synth.c
new file mode 100644
index 0000000..1f63aa5
--- /dev/null
+++ b/sound/isa/sb/emu8000_synth.c
@@ -0,0 +1,134 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *     and (c) 1999 Steve Ratcliffe <steve@parabola.demon.co.uk>
+ *  Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *  Emu8000 synth plug-in routine
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include "emu8000_local.h"
+#include <linux/init.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Takashi Iwai, Steve Ratcliffe");
+MODULE_DESCRIPTION("Emu8000 synth plug-in routine");
+MODULE_LICENSE("GPL");
+
+/*----------------------------------------------------------------*/
+
+/*
+ * create a new hardware dependent device for Emu8000
+ */
+static int snd_emu8000_new_device(snd_seq_device_t *dev)
+{
+	emu8000_t *hw;
+	snd_emux_t *emu;
+
+	hw = *(emu8000_t**)SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	if (hw == NULL)
+		return -EINVAL;
+
+	if (hw->emu)
+		return -EBUSY; /* already exists..? */
+
+	if (snd_emux_new(&emu) < 0)
+		return -ENOMEM;
+
+	hw->emu = emu;
+	snd_emu8000_ops_setup(hw);
+
+	emu->hw = hw;
+	emu->max_voices = EMU8000_DRAM_VOICES;
+	emu->num_ports = hw->seq_ports;
+
+	if (hw->memhdr) {
+		snd_printk("memhdr is already initialized!?\n");
+		snd_util_memhdr_free(hw->memhdr);
+	}
+	hw->memhdr = snd_util_memhdr_new(hw->mem_size);
+	if (hw->memhdr == NULL) {
+		snd_emux_free(emu);
+		hw->emu = NULL;
+		return -ENOMEM;
+	}
+
+	emu->memhdr = hw->memhdr;
+	emu->midi_ports = hw->seq_ports < 2 ? hw->seq_ports : 2; /* number of virmidi ports */
+	emu->midi_devidx = 1;
+	emu->linear_panning = 1;
+	emu->hwdep_idx = 2; /* FIXED */
+
+	if (snd_emux_register(emu, dev->card, hw->index, "Emu8000") < 0) {
+		snd_emux_free(emu);
+		snd_util_memhdr_free(hw->memhdr);
+		hw->emu = NULL;
+		hw->memhdr = NULL;
+		return -ENOMEM;
+	}
+
+	if (hw->mem_size > 0)
+		snd_emu8000_pcm_new(dev->card, hw, 1);
+
+	dev->driver_data = hw;
+
+	return 0;
+}
+
+
+/*
+ * free all resources
+ */
+static int snd_emu8000_delete_device(snd_seq_device_t *dev)
+{
+	emu8000_t *hw;
+
+	if (dev->driver_data == NULL)
+		return 0; /* no synth was allocated actually */
+
+	hw = dev->driver_data;
+	if (hw->pcm)
+		snd_device_free(dev->card, hw->pcm);
+	if (hw->emu)
+		snd_emux_free(hw->emu);
+	if (hw->memhdr)
+		snd_util_memhdr_free(hw->memhdr);
+	hw->emu = NULL;
+	hw->memhdr = NULL;
+	return 0;
+}
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_emu8000_init(void)
+{
+	
+	static snd_seq_dev_ops_t ops = {
+		snd_emu8000_new_device,
+		snd_emu8000_delete_device,
+	};
+	return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_EMU8000, &ops, sizeof(emu8000_t*));
+}
+
+static void __exit alsa_emu8000_exit(void)
+{
+	snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_EMU8000);
+}
+
+module_init(alsa_emu8000_init)
+module_exit(alsa_emu8000_exit)
diff --git a/sound/isa/sb/es968.c b/sound/isa/sb/es968.c
new file mode 100644
index 0000000..c859917
--- /dev/null
+++ b/sound/isa/sb/es968.c
@@ -0,0 +1,235 @@
+
+/*
+    card-es968.c - driver for ESS AudioDrive ES968 based soundcards.
+    Copyright (C) 1999 by Massimo Piccioni <dafastidio@libero.it>
+
+    Thanks to Pierfrancesco 'qM2' Passerini.
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+*/
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/sb.h>
+
+#define PFX "es968: "
+
+MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>");
+MODULE_DESCRIPTION("ESS AudioDrive ES968");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ESS,AudioDrive ES968}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* Pnp setup */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* PnP setup */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for es968 based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for es968 based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable es968 based soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for es968 driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for es968 driver.");
+module_param_array(dma8, int, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for es968 driver.");
+
+struct snd_card_es968 {
+	struct pnp_dev *dev;
+};
+
+static struct pnp_card_device_id snd_es968_pnpids[] = {
+	{ .id = "ESS0968", .devs = { { "@@@0968" }, } },
+	{ .id = "", } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_es968_pnpids);
+
+#define	DRIVER_NAME	"snd-card-es968"
+
+static irqreturn_t snd_card_es968_interrupt(int irq, void *dev_id,
+					    struct pt_regs *regs)
+{
+	sb_t *chip = dev_id;
+
+	if (chip->open & SB_OPEN_PCM) {
+		return snd_sb8dsp_interrupt(chip);
+	} else {
+		return snd_sb8dsp_midi_interrupt(chip);
+	}
+}
+
+static int __devinit snd_card_es968_pnp(int dev, struct snd_card_es968 *acard,
+					struct pnp_card_link *card,
+					const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	struct pnp_resource_table *cfg = kmalloc(sizeof(*cfg), GFP_KERNEL);
+	int err;
+	if (!cfg)
+		return -ENOMEM;
+	acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->dev == NULL) {
+		kfree(cfg);
+		return -ENODEV;
+	}
+
+	pdev = acard->dev;
+
+	pnp_init_resource_table(cfg);
+
+	/* override resources */
+	if (port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[0], port[dev], 16);
+	if (dma8[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[0], dma8[dev], 1);
+	if (irq[dev] != SNDRV_AUTO_IRQ)
+		pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1);
+	if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0)
+		snd_printk(KERN_ERR PFX "AUDIO the requested resources are invalid, using auto config\n");
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n");
+		kfree(cfg);
+		return err;
+	}
+	port[dev] = pnp_port_start(pdev, 0);
+	dma8[dev] = pnp_dma(pdev, 1);
+	irq[dev] = pnp_irq(pdev, 0);
+
+	kfree(cfg);
+	return 0;
+}
+
+static int __init snd_card_es968_probe(int dev,
+					struct pnp_card_link *pcard,
+					const struct pnp_card_device_id *pid)
+{
+	int error;
+	sb_t *chip;
+	snd_card_t *card;
+	struct snd_card_es968 *acard;
+
+	if ((card = snd_card_new(index[dev], id[dev], THIS_MODULE,
+				 sizeof(struct snd_card_es968))) == NULL)
+		return -ENOMEM;
+	acard = (struct snd_card_es968 *)card->private_data;
+	if ((error = snd_card_es968_pnp(dev, acard, pcard, pid))) {
+		snd_card_free(card);
+		return error;
+	}
+	snd_card_set_dev(card, &pcard->card->dev);
+
+	if ((error = snd_sbdsp_create(card, port[dev],
+				      irq[dev],
+				      snd_card_es968_interrupt,
+				      dma8[dev],
+				      -1,
+				      SB_HW_AUTO, &chip)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if ((error = snd_sb8dsp_pcm(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if ((error = snd_sbmixer_new(chip)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if ((error = snd_sb8dsp_midi(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	strcpy(card->driver, "ES968");
+	strcpy(card->shortname, "ESS ES968");
+	sprintf(card->longname, "%s soundcard, %s at 0x%lx, irq %d, dma %d",
+		card->shortname, chip->name, chip->port, irq[dev], dma8[dev]);
+
+	if ((error = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	return 0;
+}
+
+static int __devinit snd_es968_pnp_detect(struct pnp_card_link *card,
+                                          const struct pnp_card_device_id *id)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev])
+			continue;
+		res = snd_card_es968_probe(dev, card, id);
+		if (res < 0)
+			return res;
+		dev++;
+		return 0;
+	}
+	return -ENODEV;
+}
+
+static void __devexit snd_es968_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_t *card = (snd_card_t *) pnp_get_card_drvdata(pcard);
+
+	snd_card_disconnect(card);
+	snd_card_free_in_thread(card);
+}
+
+static struct pnp_card_driver es968_pnpc_driver = {
+	.flags		= PNP_DRIVER_RES_DISABLE,
+	.name		= "es968",
+	.id_table	= snd_es968_pnpids,
+	.probe		= snd_es968_pnp_detect,
+	.remove		= __devexit_p(snd_es968_pnp_remove),
+};
+
+static int __init alsa_card_es968_init(void)
+{
+	int cards = pnp_register_card_driver(&es968_pnpc_driver);
+#ifdef MODULE
+	if (cards == 0) {
+		pnp_unregister_card_driver(&es968_pnpc_driver);
+		snd_printk(KERN_ERR "no ES968 based soundcards found\n");
+	}
+#endif
+	return cards ? 0 : -ENODEV;
+}
+
+static void __exit alsa_card_es968_exit(void)
+{
+	pnp_unregister_card_driver(&es968_pnpc_driver);
+}
+
+module_init(alsa_card_es968_init)
+module_exit(alsa_card_es968_exit)
diff --git a/sound/isa/sb/sb16.c b/sound/isa/sb/sb16.c
new file mode 100644
index 0000000..60e2c53
--- /dev/null
+++ b/sound/isa/sb/sb16.c
@@ -0,0 +1,678 @@
+/*
+ *  Driver for SoundBlaster 16/AWE32/AWE64 soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <asm/dma.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/sb16_csp.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/emu8000.h>
+#include <sound/seq_device.h>
+#define SNDRV_LEGACY_AUTO_PROBE
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+#ifdef SNDRV_SBAWE
+#define PFX "sbawe: "
+#else
+#define PFX "sb16: "
+#endif
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_LICENSE("GPL");
+#ifndef SNDRV_SBAWE
+MODULE_DESCRIPTION("Sound Blaster 16");
+MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB 16},"
+		"{Creative Labs,SB Vibra16S},"
+		"{Creative Labs,SB Vibra16C},"
+		"{Creative Labs,SB Vibra16CL},"
+		"{Creative Labs,SB Vibra16X}}");
+#else
+MODULE_DESCRIPTION("Sound Blaster AWE");
+MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB AWE 32},"
+		"{Creative Labs,SB AWE 64},"
+		"{Creative Labs,SB AWE 64 Gold}}");
+#endif
+
+#if 0
+#define SNDRV_DEBUG_IRQ
+#endif
+
+#if defined(SNDRV_SBAWE) && (defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE)))
+#define SNDRV_SBAWE_EMU8000
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static int isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x240,0x260,0x280 */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x330,0x300 */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+#ifdef SNDRV_SBAWE_EMU8000
+static long awe_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+#endif
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,10 */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3 */
+static int dma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 5,6,7 */
+static int mic_agc[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#ifdef CONFIG_SND_SB16_CSP
+static int csp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 0};
+#endif
+#ifdef SNDRV_SBAWE_EMU8000
+static int seq_ports[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 4};
+#endif
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for SoundBlaster 16 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for SoundBlaster 16 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable SoundBlaster 16 soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for SB16 driver.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for SB16 driver.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for SB16 PnP driver.");
+#ifdef SNDRV_SBAWE_EMU8000
+module_param_array(awe_port, long, NULL, 0444);
+MODULE_PARM_DESC(awe_port, "AWE port # for SB16 PnP driver.");
+#endif
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for SB16 driver.");
+module_param_array(dma8, int, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for SB16 driver.");
+module_param_array(dma16, int, NULL, 0444);
+MODULE_PARM_DESC(dma16, "16-bit DMA # for SB16 driver.");
+module_param_array(mic_agc, int, NULL, 0444);
+MODULE_PARM_DESC(mic_agc, "Mic Auto-Gain-Control switch.");
+#ifdef CONFIG_SND_SB16_CSP
+module_param_array(csp, int, NULL, 0444);
+MODULE_PARM_DESC(csp, "ASP/CSP chip support.");
+#endif
+#ifdef SNDRV_SBAWE_EMU8000
+module_param_array(seq_ports, int, NULL, 0444);
+MODULE_PARM_DESC(seq_ports, "Number of sequencer ports for WaveTable synth.");
+#endif
+
+struct snd_card_sb16 {
+	struct resource *fm_res;	/* used to block FM i/o region for legacy cards */
+#ifdef CONFIG_PNP
+	int dev_no;
+	struct pnp_dev *dev;
+#ifdef SNDRV_SBAWE_EMU8000
+	struct pnp_dev *devwt;
+#endif
+#endif
+};
+
+static snd_card_t *snd_sb16_legacy[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+#ifdef CONFIG_PNP
+
+static struct pnp_card_device_id snd_sb16_pnpids[] = {
+#ifndef SNDRV_SBAWE
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL0024", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL0025", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL0026", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL0027", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL0028", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL0029", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL002a", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	/* Note: This card has also a CTL0051:StereoEnhance device!!! */
+	{ .id = "CTL002b", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL002c", .devs = { { "CTL0031" } } },
+	/* Sound Blaster Vibra16S */
+	{ .id = "CTL0051", .devs = { { "CTL0001" } } },
+	/* Sound Blaster Vibra16C */
+	{ .id = "CTL0070", .devs = { { "CTL0001" } } },
+	/* Sound Blaster Vibra16CL - added by ctm@ardi.com */
+	{ .id = "CTL0080", .devs = { { "CTL0041" } } },
+	/* Sound Blaster 16 'value' PnP. It says model ct4130 on the pcb, */
+	/* but ct4131 on a sticker on the board.. */
+	{ .id = "CTL0086", .devs = { { "CTL0041" } } },
+	/* Sound Blaster Vibra16X */
+	{ .id = "CTL00f0", .devs = { { "CTL0043" } } },
+#else  /* SNDRV_SBAWE defined */
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0035", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0039", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0042", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0043", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	/* Note: This card has also a CTL0051:StereoEnhance device!!! */
+	{ .id = "CTL0044", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	/* Note: This card has also a CTL0051:StereoEnhance device!!! */
+	{ .id = "CTL0045", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0046", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0047", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0048", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0054", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL009a", .devs = { { "CTL0041" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL009c", .devs = { { "CTL0041" }, { "CTL0021" } } },
+	/* Sound Blaster 32 PnP */
+	{ .id = "CTL009f", .devs = { { "CTL0041" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL009d", .devs = { { "CTL0042" }, { "CTL0022" } } },
+	/* Sound Blaster AWE 64 PnP Gold */
+	{ .id = "CTL009e", .devs = { { "CTL0044" }, { "CTL0023" } } },
+	/* Sound Blaster AWE 64 PnP Gold */
+	{ .id = "CTL00b2", .devs = { { "CTL0044" }, { "CTL0023" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL00c1", .devs = { { "CTL0042" }, { "CTL0022" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL00c3", .devs = { { "CTL0045" }, { "CTL0022" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL00c5", .devs = { { "CTL0045" }, { "CTL0022" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL00c7", .devs = { { "CTL0045" }, { "CTL0022" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL00e4", .devs = { { "CTL0045" }, { "CTL0022" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL00e9", .devs = { { "CTL0045" }, { "CTL0022" } } },
+	/* Sound Blaster 16 PnP (AWE) */
+	{ .id = "CTL00ed", .devs = { { "CTL0041" }, { "CTL0070" } } },
+	/* Generic entries */
+	{ .id = "CTLXXXX" , .devs = { { "CTL0031" }, { "CTL0021" } } },
+	{ .id = "CTLXXXX" , .devs = { { "CTL0041" }, { "CTL0021" } } },
+	{ .id = "CTLXXXX" , .devs = { { "CTL0042" }, { "CTL0022" } } },
+	{ .id = "CTLXXXX" , .devs = { { "CTL0044" }, { "CTL0023" } } },
+	{ .id = "CTLXXXX" , .devs = { { "CTL0045" }, { "CTL0022" } } },
+#endif /* SNDRV_SBAWE */
+	/* Sound Blaster 16 PnP (Virtual PC 2004)*/
+	{ .id = "tBA03b0", .devs = { { "PNPb003" } } },
+	{ .id = "", }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_sb16_pnpids);
+
+#endif /* CONFIG_PNP */
+
+#ifdef SNDRV_SBAWE_EMU8000
+#define DRIVER_NAME	"snd-card-sbawe"
+#else
+#define DRIVER_NAME	"snd-card-sb16"
+#endif
+
+#ifdef CONFIG_PNP
+
+static int __devinit snd_card_sb16_pnp(int dev, struct snd_card_sb16 *acard,
+				       struct pnp_card_link *card,
+				       const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	struct pnp_resource_table * cfg = kmalloc(sizeof(struct pnp_resource_table), GFP_KERNEL);
+	int err;
+
+	if (!cfg) 
+		return -ENOMEM; 
+	acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->dev == NULL) { 
+		kfree(cfg); 
+		return -ENODEV; 
+	} 
+#ifdef SNDRV_SBAWE_EMU8000
+	acard->devwt = pnp_request_card_device(card, id->devs[1].id, acard->dev);
+#endif
+	/* Audio initialization */
+	pdev = acard->dev;
+
+	pnp_init_resource_table(cfg); 
+	 
+	/* override resources */ 
+
+	if (port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[0], port[dev], 16);
+	if (mpu_port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[1], mpu_port[dev], 2);
+	if (fm_port[dev] != SNDRV_AUTO_PORT)
+		pnp_resource_change(&cfg->port_resource[2], fm_port[dev], 4);
+	if (dma8[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[0], dma8[dev], 1);
+	if (dma16[dev] != SNDRV_AUTO_DMA)
+		pnp_resource_change(&cfg->dma_resource[1], dma16[dev], 1);
+	if (irq[dev] != SNDRV_AUTO_IRQ)
+		pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1);
+	if (pnp_manual_config_dev(pdev, cfg, 0) < 0) 
+		snd_printk(KERN_ERR PFX "AUDIO the requested resources are invalid, using auto config\n"); 
+	err = pnp_activate_dev(pdev); 
+	if (err < 0) { 
+		snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n"); 
+		kfree(cfg);
+		return err; 
+	} 
+	port[dev] = pnp_port_start(pdev, 0);
+	mpu_port[dev] = pnp_port_start(pdev, 1);
+	fm_port[dev] = pnp_port_start(pdev, 2);
+	dma8[dev] = pnp_dma(pdev, 0);
+	dma16[dev] = pnp_dma(pdev, 1);
+	irq[dev] = pnp_irq(pdev, 0);
+	snd_printdd("pnp SB16: port=0x%lx, mpu port=0x%lx, fm port=0x%lx\n",
+			port[dev], mpu_port[dev], fm_port[dev]);
+	snd_printdd("pnp SB16: dma1=%i, dma2=%i, irq=%i\n",
+			dma8[dev], dma16[dev], irq[dev]);
+#ifdef SNDRV_SBAWE_EMU8000
+	/* WaveTable initialization */
+	pdev = acard->devwt;
+	if (pdev != NULL) {
+		pnp_init_resource_table(cfg); 
+	 
+		/* override resources */ 
+
+		if (awe_port[dev] != SNDRV_AUTO_PORT) {
+			pnp_resource_change(&cfg->port_resource[0], awe_port[dev], 4);
+			pnp_resource_change(&cfg->port_resource[1], awe_port[dev] + 0x400, 4);
+			pnp_resource_change(&cfg->port_resource[2], awe_port[dev] + 0x800, 4);
+		}
+		if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0) 
+			snd_printk(KERN_ERR PFX "WaveTable the requested resources are invalid, using auto config\n"); 
+		err = pnp_activate_dev(pdev); 
+		if (err < 0) { 
+			goto __wt_error; 
+		} 
+		awe_port[dev] = pnp_port_start(pdev, 0);
+		snd_printdd("pnp SB16: wavetable port=0x%lx\n", pnp_port_start(pdev, 0));
+	} else {
+__wt_error:
+		if (pdev) {
+			pnp_release_card_device(pdev);
+			snd_printk(KERN_ERR PFX "WaveTable pnp configure failure\n");
+		}
+		acard->devwt = NULL;
+		awe_port[dev] = -1;
+	}
+#endif
+	kfree(cfg);
+	return 0;
+}
+
+#endif /* CONFIG_PNP */
+
+static void snd_sb16_free(snd_card_t *card)
+{
+	struct snd_card_sb16 *acard = (struct snd_card_sb16 *)card->private_data;
+        
+	if (acard == NULL)
+		return;
+	if (acard->fm_res) {
+		release_resource(acard->fm_res);
+		kfree_nocheck(acard->fm_res);
+	}
+}
+
+static int __init snd_sb16_probe(int dev,
+				 struct pnp_card_link *pcard,
+				 const struct pnp_card_device_id *pid)
+{
+	static int possible_irqs[] = {5, 9, 10, 7, -1};
+	static int possible_dmas8[] = {1, 3, 0, -1};
+	static int possible_dmas16[] = {5, 6, 7, -1};
+	int xirq, xdma8, xdma16;
+	sb_t *chip;
+	snd_card_t *card;
+	struct snd_card_sb16 *acard;
+	opl3_t *opl3;
+	snd_hwdep_t *synth = NULL;
+#ifdef CONFIG_SND_SB16_CSP
+	snd_hwdep_t *xcsp = NULL;
+#endif
+	unsigned long flags;
+	int err;
+
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE,
+			    sizeof(struct snd_card_sb16));
+	if (card == NULL)
+		return -ENOMEM;
+	acard = (struct snd_card_sb16 *) card->private_data;
+	card->private_free = snd_sb16_free;
+#ifdef CONFIG_PNP
+	if (isapnp[dev]) {
+		if ((err = snd_card_sb16_pnp(dev, acard, pcard, pid))) {
+			snd_card_free(card);
+			return err;
+		}
+		snd_card_set_dev(card, &pcard->card->dev);
+	}
+#endif
+
+	xirq = irq[dev];
+	xdma8 = dma8[dev];
+	xdma16 = dma16[dev];
+#ifdef CONFIG_PNP
+	if (!isapnp[dev]) {
+#endif
+	if (xirq == SNDRV_AUTO_IRQ) {
+		if ((xirq = snd_legacy_find_free_irq(possible_irqs)) < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR PFX "unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (xdma8 == SNDRV_AUTO_DMA) {
+		if ((xdma8 = snd_legacy_find_free_dma(possible_dmas8)) < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR PFX "unable to find a free 8-bit DMA\n");
+			return -EBUSY;
+		}
+	}
+	if (xdma16 == SNDRV_AUTO_DMA) {
+		if ((xdma16 = snd_legacy_find_free_dma(possible_dmas16)) < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR PFX "unable to find a free 16-bit DMA\n");
+			return -EBUSY;
+		}
+	}
+	/* non-PnP FM port address is hardwired with base port address */
+	fm_port[dev] = port[dev];
+	/* block the 0x388 port to avoid PnP conflicts */
+	acard->fm_res = request_region(0x388, 4, "SoundBlaster FM");
+#ifdef SNDRV_SBAWE_EMU8000
+	/* non-PnP AWE port address is hardwired with base port address */
+	awe_port[dev] = port[dev] + 0x400;
+#endif
+#ifdef CONFIG_PNP
+	}
+#endif
+
+	if ((err = snd_sbdsp_create(card,
+				    port[dev],
+				    xirq,
+				    snd_sb16dsp_interrupt,
+				    xdma8,
+				    xdma16,
+				    SB_HW_AUTO,
+				    &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (chip->hardware != SB_HW_16) {
+		snd_card_free(card);
+		snd_printdd("SB 16 chip was not detected at 0x%lx\n", port[dev]);
+		return -ENODEV;
+	}
+	chip->mpu_port = mpu_port[dev];
+#ifdef CONFIG_PNP
+	if (!isapnp[dev] && (err = snd_sb16dsp_configure(chip)) < 0) {
+#else
+	if ((err = snd_sb16dsp_configure(chip)) < 0) {
+#endif
+		snd_card_free(card);
+		return -ENXIO;
+	}
+	if ((err = snd_sb16dsp_pcm(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return -ENXIO;
+	}
+
+	strcpy(card->driver,
+#ifdef SNDRV_SBAWE_EMU8000
+			awe_port[dev] > 0 ? "SB AWE" :
+#endif
+			"SB16");
+	strcpy(card->shortname, chip->name);
+	sprintf(card->longname, "%s at 0x%lx, irq %i, dma ",
+		chip->name,
+		chip->port,
+		xirq);
+	if (xdma8 >= 0)
+		sprintf(card->longname + strlen(card->longname), "%d", xdma8);
+	if (xdma16 >= 0)
+		sprintf(card->longname + strlen(card->longname), "%s%d",
+			xdma8 >= 0 ? "&" : "", xdma16);
+
+	if (chip->mpu_port > 0 && chip->mpu_port != SNDRV_AUTO_PORT) {
+		if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_SB,
+					       chip->mpu_port, 0,
+					       xirq, 0, &chip->rmidi)) < 0) {
+			snd_card_free(card);
+			return -ENXIO;
+		}
+		chip->rmidi_callback = snd_mpu401_uart_interrupt;
+	}
+
+#ifdef SNDRV_SBAWE_EMU8000
+	if (awe_port[dev] == SNDRV_AUTO_PORT)
+		awe_port[dev] = 0; /* disable */
+#endif
+
+	if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+		if (snd_opl3_create(card, fm_port[dev], fm_port[dev] + 2,
+				    OPL3_HW_OPL3,
+				    acard->fm_res != NULL || fm_port[dev] == port[dev],
+				    &opl3) < 0) {
+			snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx\n",
+				   fm_port[dev], fm_port[dev] + 2);
+		} else {
+#ifdef SNDRV_SBAWE_EMU8000
+			int seqdev = awe_port[dev] > 0 ? 2 : 1;
+#else
+			int seqdev = 1;
+#endif
+			if ((err = snd_opl3_hwdep_new(opl3, 0, seqdev, &synth)) < 0) {
+				snd_card_free(card);
+				return -ENXIO;
+			}
+		}
+	}
+
+	if ((err = snd_sbmixer_new(chip)) < 0) {
+		snd_card_free(card);
+		return -ENXIO;
+	}
+
+#ifdef CONFIG_SND_SB16_CSP
+	/* CSP chip on SB16ASP/AWE32 */
+	if ((chip->hardware == SB_HW_16) && csp[dev]) {
+		snd_sb_csp_new(chip, synth != NULL ? 1 : 0, &xcsp);
+		if (xcsp) {
+			chip->csp = xcsp->private_data;
+			chip->hardware = SB_HW_16CSP;
+		} else {
+			snd_printk(KERN_INFO PFX "warning - CSP chip not detected on soundcard #%i\n", dev + 1);
+		}
+	}
+#endif
+#ifdef SNDRV_SBAWE_EMU8000
+	if (awe_port[dev] > 0) {
+		if (snd_emu8000_new(card, 1, awe_port[dev],
+				    seq_ports[dev], NULL) < 0) {
+			snd_printk(KERN_ERR PFX "fatal error - EMU-8000 synthesizer not detected at 0x%lx\n", awe_port[dev]);
+			snd_card_free(card);
+			return -ENXIO;
+		}
+	}
+#endif
+
+	/* setup Mic AGC */
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+	snd_sbmixer_write(chip, SB_DSP4_MIC_AGC,
+		(snd_sbmixer_read(chip, SB_DSP4_MIC_AGC) & 0x01) |
+		(mic_agc[dev] ? 0x00 : 0x01));
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (pcard)
+		pnp_set_card_drvdata(pcard, card);
+	else
+		snd_sb16_legacy[dev] = card;
+	return 0;
+}
+
+static int __init snd_sb16_probe_legacy_port(unsigned long xport)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || port[dev] != SNDRV_AUTO_PORT)
+			continue;
+#ifdef CONFIG_PNP
+		if (isapnp[dev])
+			continue;
+#endif
+		port[dev] = xport;
+		res = snd_sb16_probe(dev, NULL, NULL);
+		if (res < 0)
+			port[dev] = SNDRV_AUTO_PORT;
+		return res;
+	}
+	return -ENODEV;
+}
+
+#ifdef CONFIG_PNP
+
+static int __devinit snd_sb16_pnp_detect(struct pnp_card_link *card,
+					 const struct pnp_card_device_id *id)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || !isapnp[dev])
+			continue;
+		res = snd_sb16_probe(dev, card, id);
+		if (res < 0)
+			return res;
+		dev++;
+		return 0;
+	}
+
+	return -ENODEV;
+}
+
+static void __devexit snd_sb16_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_t *card = (snd_card_t *) pnp_get_card_drvdata(pcard);
+
+	snd_card_disconnect(card);
+	snd_card_free_in_thread(card);
+}
+
+static struct pnp_card_driver sb16_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DISABLE,
+	.name = "sb16",
+	.id_table = snd_sb16_pnpids,
+	.probe = snd_sb16_pnp_detect,
+	.remove = __devexit_p(snd_sb16_pnp_remove),
+};
+
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_sb16_init(void)
+{
+	int dev, cards = 0, i;
+	static unsigned long possible_ports[] = {0x220, 0x240, 0x260, 0x280, -1};
+
+	/* legacy non-auto cards at first */
+	for (dev = 0; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || port[dev] == SNDRV_AUTO_PORT)
+			continue;
+#ifdef CONFIG_PNP
+		if (isapnp[dev])
+			continue;
+#endif
+		if (!snd_sb16_probe(dev, NULL, NULL)) {
+			cards++;
+			continue;
+		}
+#ifdef MODULE
+		snd_printk(KERN_ERR "Sound Blaster 16+ soundcard #%i not found at 0x%lx or device busy\n", dev, port[dev]);
+#endif
+	}
+	/* legacy auto configured cards */
+	i = snd_legacy_auto_probe(possible_ports, snd_sb16_probe_legacy_port);
+	if (i > 0)
+		cards += i;
+
+#ifdef CONFIG_PNP
+	/* PnP cards at last */
+	i = pnp_register_card_driver(&sb16_pnpc_driver);
+	if (i >0)
+		cards += i;
+#endif
+
+	if (!cards) {
+#ifdef CONFIG_PNP
+		pnp_unregister_card_driver(&sb16_pnpc_driver);
+#endif
+#ifdef MODULE
+		snd_printk(KERN_ERR "Sound Blaster 16 soundcard not found or device busy\n");
+#ifdef SNDRV_SBAWE_EMU8000
+		snd_printk(KERN_ERR "In case, if you have non-AWE card, try snd-sb16 module\n");
+#else
+		snd_printk(KERN_ERR "In case, if you have AWE card, try snd-sbawe module\n");
+#endif
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_sb16_exit(void)
+{
+	int dev;
+
+#ifdef CONFIG_PNP
+	/* PnP cards first */
+	pnp_unregister_card_driver(&sb16_pnpc_driver);
+#endif
+	for (dev = 0; dev < SNDRV_CARDS; dev++)
+		snd_card_free(snd_sb16_legacy[dev]);
+}
+
+module_init(alsa_card_sb16_init)
+module_exit(alsa_card_sb16_exit)
diff --git a/sound/isa/sb/sb16_csp.c b/sound/isa/sb/sb16_csp.c
new file mode 100644
index 0000000..b62920e
--- /dev/null
+++ b/sound/isa/sb/sb16_csp.c
@@ -0,0 +1,1175 @@
+/*
+ *  Copyright (c) 1999 by Uros Bizjak <uros@kss-loka.si>
+ *                        Takashi Iwai <tiwai@suse.de>
+ *
+ *  SB16ASP/AWE32 CSP control
+ *
+ *  CSP microcode loader:
+ *   alsa-tools/sb16_csp/ 
+ *
+ *   This program is free software; you can redistribute it and/or modify 
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/sb16_csp.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Uros Bizjak <uros@kss-loka.si>");
+MODULE_DESCRIPTION("ALSA driver for SB16 Creative Signal Processor");
+MODULE_LICENSE("GPL");
+
+#ifdef SNDRV_LITTLE_ENDIAN
+#define CSP_HDR_VALUE(a,b,c,d)	((a) | ((b)<<8) | ((c)<<16) | ((d)<<24))
+#else
+#define CSP_HDR_VALUE(a,b,c,d)	((d) | ((c)<<8) | ((b)<<16) | ((a)<<24))
+#endif
+#define LE_SHORT(v)		le16_to_cpu(v)
+#define LE_INT(v)		le32_to_cpu(v)
+
+#define RIFF_HEADER	CSP_HDR_VALUE('R', 'I', 'F', 'F')
+#define CSP__HEADER	CSP_HDR_VALUE('C', 'S', 'P', ' ')
+#define LIST_HEADER	CSP_HDR_VALUE('L', 'I', 'S', 'T')
+#define FUNC_HEADER	CSP_HDR_VALUE('f', 'u', 'n', 'c')
+#define CODE_HEADER	CSP_HDR_VALUE('c', 'o', 'd', 'e')
+#define INIT_HEADER	CSP_HDR_VALUE('i', 'n', 'i', 't')
+#define MAIN_HEADER	CSP_HDR_VALUE('m', 'a', 'i', 'n')
+
+/*
+ * RIFF data format
+ */
+typedef struct riff_header {
+	__u32 name;
+	__u32 len;
+} riff_header_t;
+
+typedef struct desc_header {
+	riff_header_t info;
+	__u16 func_nr;
+	__u16 VOC_type;
+	__u16 flags_play_rec;
+	__u16 flags_16bit_8bit;
+	__u16 flags_stereo_mono;
+	__u16 flags_rates;
+} desc_header_t;
+
+/*
+ * prototypes
+ */
+static void snd_sb_csp_free(snd_hwdep_t *hw);
+static int snd_sb_csp_open(snd_hwdep_t * hw, struct file *file);
+static int snd_sb_csp_ioctl(snd_hwdep_t * hw, struct file *file, unsigned int cmd, unsigned long arg);
+static int snd_sb_csp_release(snd_hwdep_t * hw, struct file *file);
+
+static int csp_detect(sb_t *chip, int *version);
+static int set_codec_parameter(sb_t *chip, unsigned char par, unsigned char val);
+static int set_register(sb_t *chip, unsigned char reg, unsigned char val);
+static int read_register(sb_t *chip, unsigned char reg);
+static int set_mode_register(sb_t *chip, unsigned char mode);
+static int get_version(sb_t *chip);
+
+static int snd_sb_csp_riff_load(snd_sb_csp_t * p, snd_sb_csp_microcode_t __user * code);
+static int snd_sb_csp_unload(snd_sb_csp_t * p);
+static int snd_sb_csp_load_user(snd_sb_csp_t * p, const unsigned char __user *buf, int size, int load_flags);
+static int snd_sb_csp_autoload(snd_sb_csp_t * p, int pcm_sfmt, int play_rec_mode);
+static int snd_sb_csp_check_version(snd_sb_csp_t * p);
+
+static int snd_sb_csp_use(snd_sb_csp_t * p);
+static int snd_sb_csp_unuse(snd_sb_csp_t * p);
+static int snd_sb_csp_start(snd_sb_csp_t * p, int sample_width, int channels);
+static int snd_sb_csp_stop(snd_sb_csp_t * p);
+static int snd_sb_csp_pause(snd_sb_csp_t * p);
+static int snd_sb_csp_restart(snd_sb_csp_t * p);
+
+static int snd_sb_qsound_build(snd_sb_csp_t * p);
+static void snd_sb_qsound_destroy(snd_sb_csp_t * p);
+static int snd_sb_csp_qsound_transfer(snd_sb_csp_t * p);
+
+static int init_proc_entry(snd_sb_csp_t * p, int device);
+static void info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer);
+
+/*
+ * Detect CSP chip and create a new instance
+ */
+int snd_sb_csp_new(sb_t *chip, int device, snd_hwdep_t ** rhwdep)
+{
+	snd_sb_csp_t *p;
+	int version, err;
+	snd_hwdep_t *hw;
+
+	if (rhwdep)
+		*rhwdep = NULL;
+
+	if (csp_detect(chip, &version))
+		return -ENODEV;
+
+	if ((err = snd_hwdep_new(chip->card, "SB16-CSP", device, &hw)) < 0)
+		return err;
+
+	if ((p = kcalloc(1, sizeof(*p), GFP_KERNEL)) == NULL) {
+		snd_device_free(chip->card, hw);
+		return -ENOMEM;
+	}
+	p->chip = chip;
+	p->version = version;
+
+	/* CSP operators */
+	p->ops.csp_use = snd_sb_csp_use;
+	p->ops.csp_unuse = snd_sb_csp_unuse;
+	p->ops.csp_autoload = snd_sb_csp_autoload;
+	p->ops.csp_start = snd_sb_csp_start;
+	p->ops.csp_stop = snd_sb_csp_stop;
+	p->ops.csp_qsound_transfer = snd_sb_csp_qsound_transfer;
+
+	init_MUTEX(&p->access_mutex);
+	sprintf(hw->name, "CSP v%d.%d", (version >> 4), (version & 0x0f));
+	hw->iface = SNDRV_HWDEP_IFACE_SB16CSP;
+	hw->private_data = p;
+	hw->private_free = snd_sb_csp_free;
+
+	/* operators - only write/ioctl */
+	hw->ops.open = snd_sb_csp_open;
+	hw->ops.ioctl = snd_sb_csp_ioctl;
+	hw->ops.release = snd_sb_csp_release;
+
+	/* create a proc entry */
+	init_proc_entry(p, device);
+	if (rhwdep)
+		*rhwdep = hw;
+	return 0;
+}
+
+/*
+ * free_private for hwdep instance
+ */
+static void snd_sb_csp_free(snd_hwdep_t *hwdep)
+{
+	snd_sb_csp_t *p = hwdep->private_data;
+	if (p) {
+		if (p->running & SNDRV_SB_CSP_ST_RUNNING)
+			snd_sb_csp_stop(p);
+		kfree(p);
+	}
+}
+
+/* ------------------------------ */
+
+/*
+ * open the device exclusively
+ */
+static int snd_sb_csp_open(snd_hwdep_t * hw, struct file *file)
+{
+	snd_sb_csp_t *p = hw->private_data;
+	return (snd_sb_csp_use(p));
+}
+
+/*
+ * ioctl for hwdep device:
+ */
+static int snd_sb_csp_ioctl(snd_hwdep_t * hw, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	snd_sb_csp_t *p = hw->private_data;
+	snd_sb_csp_info_t info;
+	snd_sb_csp_start_t start_info;
+	int err;
+
+	snd_assert(p != NULL, return -EINVAL);
+
+	if (snd_sb_csp_check_version(p))
+		return -ENODEV;
+
+	switch (cmd) {
+		/* get information */
+	case SNDRV_SB_CSP_IOCTL_INFO:
+		*info.codec_name = *p->codec_name;
+		info.func_nr = p->func_nr;
+		info.acc_format = p->acc_format;
+		info.acc_channels = p->acc_channels;
+		info.acc_width = p->acc_width;
+		info.acc_rates = p->acc_rates;
+		info.csp_mode = p->mode;
+		info.run_channels = p->run_channels;
+		info.run_width = p->run_width;
+		info.version = p->version;
+		info.state = p->running;
+		if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+			err = -EFAULT;
+		else
+			err = 0;
+		break;
+
+		/* load CSP microcode */
+	case SNDRV_SB_CSP_IOCTL_LOAD_CODE:
+		err = (p->running & SNDRV_SB_CSP_ST_RUNNING ?
+		       -EBUSY : snd_sb_csp_riff_load(p, (snd_sb_csp_microcode_t __user *) arg));
+		break;
+	case SNDRV_SB_CSP_IOCTL_UNLOAD_CODE:
+		err = (p->running & SNDRV_SB_CSP_ST_RUNNING ?
+		       -EBUSY : snd_sb_csp_unload(p));
+		break;
+
+		/* change CSP running state */
+	case SNDRV_SB_CSP_IOCTL_START:
+		if (copy_from_user(&start_info, (void __user *) arg, sizeof(start_info)))
+			err = -EFAULT;
+		else
+			err = snd_sb_csp_start(p, start_info.sample_width, start_info.channels);
+		break;
+	case SNDRV_SB_CSP_IOCTL_STOP:
+		err = snd_sb_csp_stop(p);
+		break;
+	case SNDRV_SB_CSP_IOCTL_PAUSE:
+		err = snd_sb_csp_pause(p);
+		break;
+	case SNDRV_SB_CSP_IOCTL_RESTART:
+		err = snd_sb_csp_restart(p);
+		break;
+	default:
+		err = -ENOTTY;
+		break;
+	}
+
+	return err;
+}
+
+/*
+ * close the device
+ */
+static int snd_sb_csp_release(snd_hwdep_t * hw, struct file *file)
+{
+	snd_sb_csp_t *p = hw->private_data;
+	return (snd_sb_csp_unuse(p));
+}
+
+/* ------------------------------ */
+
+/*
+ * acquire device
+ */
+static int snd_sb_csp_use(snd_sb_csp_t * p)
+{
+	down(&p->access_mutex);
+	if (p->used) {
+		up(&p->access_mutex);
+		return -EAGAIN;
+	}
+	p->used++;
+	up(&p->access_mutex);
+
+	return 0;
+
+}
+
+/*
+ * release device
+ */
+static int snd_sb_csp_unuse(snd_sb_csp_t * p)
+{
+	down(&p->access_mutex);
+	p->used--;
+	up(&p->access_mutex);
+
+	return 0;
+}
+
+/*
+ * load microcode via ioctl: 
+ * code is user-space pointer
+ */
+static int snd_sb_csp_riff_load(snd_sb_csp_t * p, snd_sb_csp_microcode_t __user * mcode)
+{
+	snd_sb_csp_mc_header_t info;
+
+	unsigned char __user *data_ptr;
+	unsigned char __user *data_end;
+	unsigned short func_nr = 0;
+
+	riff_header_t file_h, item_h, code_h;
+	__u32 item_type;
+	desc_header_t funcdesc_h;
+
+	unsigned long flags;
+	int err;
+
+	if (copy_from_user(&info, mcode, sizeof(info)))
+		return -EFAULT;
+	data_ptr = mcode->data;
+
+	if (copy_from_user(&file_h, data_ptr, sizeof(file_h)))
+		return -EFAULT;
+	if ((file_h.name != RIFF_HEADER) ||
+	    (LE_INT(file_h.len) >= SNDRV_SB_CSP_MAX_MICROCODE_FILE_SIZE - sizeof(file_h))) {
+		snd_printd("%s: Invalid RIFF header\n", __FUNCTION__);
+		return -EINVAL;
+	}
+	data_ptr += sizeof(file_h);
+	data_end = data_ptr + LE_INT(file_h.len);
+
+	if (copy_from_user(&item_type, data_ptr, sizeof(item_type)))
+		return -EFAULT;
+	if (item_type != CSP__HEADER) {
+		snd_printd("%s: Invalid RIFF file type\n", __FUNCTION__);
+		return -EINVAL;
+	}
+	data_ptr += sizeof (item_type);
+
+	for (; data_ptr < data_end; data_ptr += LE_INT(item_h.len)) {
+		if (copy_from_user(&item_h, data_ptr, sizeof(item_h)))
+			return -EFAULT;
+		data_ptr += sizeof(item_h);
+		if (item_h.name != LIST_HEADER)
+			continue;
+
+		if (copy_from_user(&item_type, data_ptr, sizeof(item_type)))
+			 return -EFAULT;
+		switch (item_type) {
+		case FUNC_HEADER:
+			if (copy_from_user(&funcdesc_h, data_ptr + sizeof(item_type), sizeof(funcdesc_h)))
+				return -EFAULT;
+			func_nr = LE_SHORT(funcdesc_h.func_nr);
+			break;
+		case CODE_HEADER:
+			if (func_nr != info.func_req)
+				break;	/* not required function, try next */
+			data_ptr += sizeof(item_type);
+
+			/* destroy QSound mixer element */
+			if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) {
+				snd_sb_qsound_destroy(p);
+			}
+			/* Clear all flags */
+			p->running = 0;
+			p->mode = 0;
+
+			/* load microcode blocks */
+			for (;;) {
+				if (data_ptr >= data_end)
+					return -EINVAL;
+				if (copy_from_user(&code_h, data_ptr, sizeof(code_h)))
+					return -EFAULT;
+
+				/* init microcode blocks */
+				if (code_h.name != INIT_HEADER)
+					break;
+				data_ptr += sizeof(code_h);
+				err = snd_sb_csp_load_user(p, data_ptr, LE_INT(code_h.len),
+						      SNDRV_SB_CSP_LOAD_INITBLOCK);
+				if (err)
+					return err;
+				data_ptr += LE_INT(code_h.len);
+			}
+			/* main microcode block */
+			if (copy_from_user(&code_h, data_ptr, sizeof(code_h)))
+				return -EFAULT;
+
+			if (code_h.name != MAIN_HEADER) {
+				snd_printd("%s: Missing 'main' microcode\n", __FUNCTION__);
+				return -EINVAL;
+			}
+			data_ptr += sizeof(code_h);
+			err = snd_sb_csp_load_user(p, data_ptr,
+						   LE_INT(code_h.len), 0);
+			if (err)
+				return err;
+
+			/* fill in codec header */
+			strlcpy(p->codec_name, info.codec_name, sizeof(p->codec_name));
+			p->func_nr = func_nr;
+			p->mode = LE_SHORT(funcdesc_h.flags_play_rec);
+			switch (LE_SHORT(funcdesc_h.VOC_type)) {
+			case 0x0001:	/* QSound decoder */
+				if (LE_SHORT(funcdesc_h.flags_play_rec) == SNDRV_SB_CSP_MODE_DSP_WRITE) {
+					if (snd_sb_qsound_build(p) == 0)
+						/* set QSound flag and clear all other mode flags */
+						p->mode = SNDRV_SB_CSP_MODE_QSOUND;
+				}
+				p->acc_format = 0;
+				break;
+			case 0x0006:	/* A Law codec */
+				p->acc_format = SNDRV_PCM_FMTBIT_A_LAW;
+				break;
+			case 0x0007:	/* Mu Law codec */
+				p->acc_format = SNDRV_PCM_FMTBIT_MU_LAW;
+				break;
+			case 0x0011:	/* what Creative thinks is IMA ADPCM codec */
+			case 0x0200:	/* Creative ADPCM codec */
+				p->acc_format = SNDRV_PCM_FMTBIT_IMA_ADPCM;
+				break;
+			case    201:	/* Text 2 Speech decoder */
+				/* TODO: Text2Speech handling routines */
+				p->acc_format = 0;
+				break;
+			case 0x0202:	/* Fast Speech 8 codec */
+			case 0x0203:	/* Fast Speech 10 codec */
+				p->acc_format = SNDRV_PCM_FMTBIT_SPECIAL;
+				break;
+			default:	/* other codecs are unsupported */
+				p->acc_format = p->acc_width = p->acc_rates = 0;
+				p->mode = 0;
+				snd_printd("%s: Unsupported CSP codec type: 0x%04x\n",
+					   __FUNCTION__,
+					   LE_SHORT(funcdesc_h.VOC_type));
+				return -EINVAL;
+			}
+			p->acc_channels = LE_SHORT(funcdesc_h.flags_stereo_mono);
+			p->acc_width = LE_SHORT(funcdesc_h.flags_16bit_8bit);
+			p->acc_rates = LE_SHORT(funcdesc_h.flags_rates);
+
+			/* Decouple CSP from IRQ and DMAREQ lines */
+			spin_lock_irqsave(&p->chip->reg_lock, flags);
+			set_mode_register(p->chip, 0xfc);
+			set_mode_register(p->chip, 0x00);
+			spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+
+			/* finished loading successfully */
+			p->running = SNDRV_SB_CSP_ST_LOADED;	/* set LOADED flag */
+			return 0;
+		}
+	}
+	snd_printd("%s: Function #%d not found\n", __FUNCTION__, info.func_req);
+	return -EINVAL;
+}
+
+/*
+ * unload CSP microcode
+ */
+static int snd_sb_csp_unload(snd_sb_csp_t * p)
+{
+	if (p->running & SNDRV_SB_CSP_ST_RUNNING)
+		return -EBUSY;
+	if (!(p->running & SNDRV_SB_CSP_ST_LOADED))
+		return -ENXIO;
+
+	/* clear supported formats */
+	p->acc_format = 0;
+	p->acc_channels = p->acc_width = p->acc_rates = 0;
+	/* destroy QSound mixer element */
+	if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) {
+		snd_sb_qsound_destroy(p);
+	}
+	/* clear all flags */
+	p->running = 0;
+	p->mode = 0;
+	return 0;
+}
+
+/*
+ * send command sequence to DSP
+ */
+static inline int command_seq(sb_t *chip, const unsigned char *seq, int size)
+{
+	int i;
+	for (i = 0; i < size; i++) {
+		if (!snd_sbdsp_command(chip, seq[i]))
+			return -EIO;
+	}
+	return 0;
+}
+
+/*
+ * set CSP codec parameter
+ */
+static int set_codec_parameter(sb_t *chip, unsigned char par, unsigned char val)
+{
+	unsigned char dsp_cmd[3];
+
+	dsp_cmd[0] = 0x05;	/* CSP set codec parameter */
+	dsp_cmd[1] = val;	/* Parameter value */
+	dsp_cmd[2] = par;	/* Parameter */
+	command_seq(chip, dsp_cmd, 3);
+	snd_sbdsp_command(chip, 0x03);	/* DSP read? */
+	if (snd_sbdsp_get_byte(chip) != par)
+		return -EIO;
+	return 0;
+}
+
+/*
+ * set CSP register
+ */
+static int set_register(sb_t *chip, unsigned char reg, unsigned char val)
+{
+	unsigned char dsp_cmd[3];
+
+	dsp_cmd[0] = 0x0e;	/* CSP set register */
+	dsp_cmd[1] = reg;	/* CSP Register */
+	dsp_cmd[2] = val;	/* value */
+	return command_seq(chip, dsp_cmd, 3);
+}
+
+/*
+ * read CSP register
+ * return < 0 -> error
+ */
+static int read_register(sb_t *chip, unsigned char reg)
+{
+	unsigned char dsp_cmd[2];
+
+	dsp_cmd[0] = 0x0f;	/* CSP read register */
+	dsp_cmd[1] = reg;	/* CSP Register */
+	command_seq(chip, dsp_cmd, 2);
+	return snd_sbdsp_get_byte(chip);	/* Read DSP value */
+}
+
+/*
+ * set CSP mode register
+ */
+static int set_mode_register(sb_t *chip, unsigned char mode)
+{
+	unsigned char dsp_cmd[2];
+
+	dsp_cmd[0] = 0x04;	/* CSP set mode register */
+	dsp_cmd[1] = mode;	/* mode */
+	return command_seq(chip, dsp_cmd, 2);
+}
+
+/*
+ * Detect CSP
+ * return 0 if CSP exists.
+ */
+static int csp_detect(sb_t *chip, int *version)
+{
+	unsigned char csp_test1, csp_test2;
+	unsigned long flags;
+	int result = -ENODEV;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+
+	set_codec_parameter(chip, 0x00, 0x00);
+	set_mode_register(chip, 0xfc);		/* 0xfc = ?? */
+
+	csp_test1 = read_register(chip, 0x83);
+	set_register(chip, 0x83, ~csp_test1);
+	csp_test2 = read_register(chip, 0x83);
+	if (csp_test2 != (csp_test1 ^ 0xff))
+		goto __fail;
+
+	set_register(chip, 0x83, csp_test1);
+	csp_test2 = read_register(chip, 0x83);
+	if (csp_test2 != csp_test1)
+		goto __fail;
+
+	set_mode_register(chip, 0x00);		/* 0x00 = ? */
+
+	*version = get_version(chip);
+	snd_sbdsp_reset(chip);	/* reset DSP after getversion! */
+	if (*version >= 0x10 && *version <= 0x1f)
+		result = 0;		/* valid version id */
+
+      __fail:
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return result;
+}
+
+/*
+ * get CSP version number
+ */
+static int get_version(sb_t *chip)
+{
+	unsigned char dsp_cmd[2];
+
+	dsp_cmd[0] = 0x08;	/* SB_DSP_!something! */
+	dsp_cmd[1] = 0x03;	/* get chip version id? */
+	command_seq(chip, dsp_cmd, 2);
+
+	return (snd_sbdsp_get_byte(chip));
+}
+
+/*
+ * check if the CSP version is valid
+ */
+static int snd_sb_csp_check_version(snd_sb_csp_t * p)
+{
+	if (p->version < 0x10 || p->version > 0x1f) {
+		snd_printd("%s: Invalid CSP version: 0x%x\n", __FUNCTION__, p->version);
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ * download microcode to CSP (microcode should have one "main" block).
+ */
+static int snd_sb_csp_load(snd_sb_csp_t * p, const unsigned char *buf, int size, int load_flags)
+{
+	int status, i;
+	int err;
+	int result = -EIO;
+	unsigned long flags;
+
+	spin_lock_irqsave(&p->chip->reg_lock, flags);
+	snd_sbdsp_command(p->chip, 0x01);	/* CSP download command */
+	if (snd_sbdsp_get_byte(p->chip)) {
+		snd_printd("%s: Download command failed\n", __FUNCTION__);
+		goto __fail;
+	}
+	/* Send CSP low byte (size - 1) */
+	snd_sbdsp_command(p->chip, (unsigned char)(size - 1));
+	/* Send high byte */
+	snd_sbdsp_command(p->chip, (unsigned char)((size - 1) >> 8));
+	/* send microcode sequence */
+	/* load from kernel space */
+	while (size--) {
+		if (!snd_sbdsp_command(p->chip, *buf++))
+			goto __fail;
+	}
+	if (snd_sbdsp_get_byte(p->chip))
+		goto __fail;
+
+	if (load_flags & SNDRV_SB_CSP_LOAD_INITBLOCK) {
+		i = 0;
+		/* some codecs (FastSpeech) take some time to initialize */
+		while (1) {
+			snd_sbdsp_command(p->chip, 0x03);
+			status = snd_sbdsp_get_byte(p->chip);
+			if (status == 0x55 || ++i >= 10)
+				break;
+			udelay (10);
+		}
+		if (status != 0x55) {
+			snd_printd("%s: Microcode initialization failed\n", __FUNCTION__);
+			goto __fail;
+		}
+	} else {
+		/*
+		 * Read mixer register SB_DSP4_DMASETUP after loading 'main' code.
+		 * Start CSP chip if no 16bit DMA channel is set - some kind
+		 * of autorun or perhaps a bugfix?
+		 */
+		spin_lock(&p->chip->mixer_lock);
+		status = snd_sbmixer_read(p->chip, SB_DSP4_DMASETUP);
+		spin_unlock(&p->chip->mixer_lock);
+		if (!(status & (SB_DMASETUP_DMA7 | SB_DMASETUP_DMA6 | SB_DMASETUP_DMA5))) {
+			err = (set_codec_parameter(p->chip, 0xaa, 0x00) ||
+			       set_codec_parameter(p->chip, 0xff, 0x00));
+			snd_sbdsp_reset(p->chip);		/* really! */
+			if (err)
+				goto __fail;
+			set_mode_register(p->chip, 0xc0);	/* c0 = STOP */
+			set_mode_register(p->chip, 0x70);	/* 70 = RUN */
+		}
+	}
+	result = 0;
+
+      __fail:
+	spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+	return result;
+}
+ 
+static int snd_sb_csp_load_user(snd_sb_csp_t * p, const unsigned char __user *buf, int size, int load_flags)
+{
+	int err = -ENOMEM;
+	unsigned char *kbuf = kmalloc(size, GFP_KERNEL);
+	if (kbuf) {
+		if (copy_from_user(kbuf, buf, size))
+			err = -EFAULT;
+		else
+			err = snd_sb_csp_load(p, kbuf, size, load_flags);
+		kfree(kbuf);
+	}
+	return err;
+}
+
+#include "sb16_csp_codecs.h"
+
+/*
+ * autoload hardware codec if necessary
+ * return 0 if CSP is loaded and ready to run (p->running != 0)
+ */
+static int snd_sb_csp_autoload(snd_sb_csp_t * p, int pcm_sfmt, int play_rec_mode)
+{
+	unsigned long flags;
+	int err = 0;
+
+	/* if CSP is running or manually loaded then exit */
+	if (p->running & (SNDRV_SB_CSP_ST_RUNNING | SNDRV_SB_CSP_ST_LOADED)) 
+		return -EBUSY;
+
+	/* autoload microcode only if requested hardware codec is not already loaded */
+	if (((1 << pcm_sfmt) & p->acc_format) && (play_rec_mode & p->mode)) {
+		p->running = SNDRV_SB_CSP_ST_AUTO;
+	} else {
+		switch (pcm_sfmt) {
+		case SNDRV_PCM_FORMAT_MU_LAW:
+			err = snd_sb_csp_load(p, &mulaw_main[0], sizeof(mulaw_main), 0);
+			p->acc_format = SNDRV_PCM_FMTBIT_MU_LAW;
+			p->mode = SNDRV_SB_CSP_MODE_DSP_READ | SNDRV_SB_CSP_MODE_DSP_WRITE;
+			break;
+		case SNDRV_PCM_FORMAT_A_LAW:
+			err = snd_sb_csp_load(p, &alaw_main[0], sizeof(alaw_main), 0);
+			p->acc_format = SNDRV_PCM_FMTBIT_A_LAW;
+			p->mode = SNDRV_SB_CSP_MODE_DSP_READ | SNDRV_SB_CSP_MODE_DSP_WRITE;
+			break;
+		case SNDRV_PCM_FORMAT_IMA_ADPCM:
+			err = snd_sb_csp_load(p, &ima_adpcm_init[0], sizeof(ima_adpcm_init),
+					      SNDRV_SB_CSP_LOAD_INITBLOCK);
+			if (err)
+				break;
+			if (play_rec_mode == SNDRV_SB_CSP_MODE_DSP_WRITE) {
+				err = snd_sb_csp_load(p, &ima_adpcm_playback[0],
+						      sizeof(ima_adpcm_playback), 0);
+				p->mode = SNDRV_SB_CSP_MODE_DSP_WRITE;
+			} else {
+				err = snd_sb_csp_load(p, &ima_adpcm_capture[0],
+						      sizeof(ima_adpcm_capture), 0);
+				p->mode = SNDRV_SB_CSP_MODE_DSP_READ;
+			}
+			p->acc_format = SNDRV_PCM_FMTBIT_IMA_ADPCM;
+			break;				  
+		default:
+			/* Decouple CSP from IRQ and DMAREQ lines */
+			if (p->running & SNDRV_SB_CSP_ST_AUTO) {
+				spin_lock_irqsave(&p->chip->reg_lock, flags);
+				set_mode_register(p->chip, 0xfc);
+				set_mode_register(p->chip, 0x00);
+				spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+				p->running = 0;			/* clear autoloaded flag */
+			}
+			return -EINVAL;
+		}
+		if (err) {
+			p->acc_format = 0;
+			p->acc_channels = p->acc_width = p->acc_rates = 0;
+
+			p->running = 0;				/* clear autoloaded flag */
+			p->mode = 0;
+			return (err);
+		} else {
+			p->running = SNDRV_SB_CSP_ST_AUTO;	/* set autoloaded flag */
+			p->acc_width = SNDRV_SB_CSP_SAMPLE_16BIT;	/* only 16 bit data */
+			p->acc_channels = SNDRV_SB_CSP_MONO | SNDRV_SB_CSP_STEREO;
+			p->acc_rates = SNDRV_SB_CSP_RATE_ALL;	/* HW codecs accept all rates */
+		}   
+
+	}
+	return (p->running & SNDRV_SB_CSP_ST_AUTO) ? 0 : -ENXIO;
+}
+
+/*
+ * start CSP
+ */
+static int snd_sb_csp_start(snd_sb_csp_t * p, int sample_width, int channels)
+{
+	unsigned char s_type;	/* sample type */
+	unsigned char mixL, mixR;
+	int result = -EIO;
+	unsigned long flags;
+
+	if (!(p->running & (SNDRV_SB_CSP_ST_LOADED | SNDRV_SB_CSP_ST_AUTO))) {
+		snd_printd("%s: Microcode not loaded\n", __FUNCTION__);
+		return -ENXIO;
+	}
+	if (p->running & SNDRV_SB_CSP_ST_RUNNING) {
+		snd_printd("%s: CSP already running\n", __FUNCTION__);
+		return -EBUSY;
+	}
+	if (!(sample_width & p->acc_width)) {
+		snd_printd("%s: Unsupported PCM sample width\n", __FUNCTION__);
+		return -EINVAL;
+	}
+	if (!(channels & p->acc_channels)) {
+		snd_printd("%s: Invalid number of channels\n", __FUNCTION__);
+		return -EINVAL;
+	}
+
+	/* Mute PCM volume */
+	spin_lock_irqsave(&p->chip->mixer_lock, flags);
+	mixL = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV);
+	mixR = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV + 1);
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL & 0x7);
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR & 0x7);
+
+	spin_lock(&p->chip->reg_lock);
+	set_mode_register(p->chip, 0xc0);	/* c0 = STOP */
+	set_mode_register(p->chip, 0x70);	/* 70 = RUN */
+
+	s_type = 0x00;
+	if (channels == SNDRV_SB_CSP_MONO)
+		s_type = 0x11;	/* 000n 000n    (n = 1 if mono) */
+	if (sample_width == SNDRV_SB_CSP_SAMPLE_8BIT)
+		s_type |= 0x22;	/* 00dX 00dX    (d = 1 if 8 bit samples) */
+
+	if (set_codec_parameter(p->chip, 0x81, s_type)) {
+		snd_printd("%s: Set sample type command failed\n", __FUNCTION__);
+		goto __fail;
+	}
+	if (set_codec_parameter(p->chip, 0x80, 0x00)) {
+		snd_printd("%s: Codec start command failed\n", __FUNCTION__);
+		goto __fail;
+	}
+	p->run_width = sample_width;
+	p->run_channels = channels;
+
+	p->running |= SNDRV_SB_CSP_ST_RUNNING;
+
+	if (p->mode & SNDRV_SB_CSP_MODE_QSOUND) {
+		set_codec_parameter(p->chip, 0xe0, 0x01);
+		/* enable QSound decoder */
+		set_codec_parameter(p->chip, 0x00, 0xff);
+		set_codec_parameter(p->chip, 0x01, 0xff);
+		p->running |= SNDRV_SB_CSP_ST_QSOUND;
+		/* set QSound startup value */
+		snd_sb_csp_qsound_transfer(p);
+	}
+	result = 0;
+
+      __fail:
+	spin_unlock(&p->chip->reg_lock);
+
+	/* restore PCM volume */
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL);
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR);
+	spin_unlock_irqrestore(&p->chip->mixer_lock, flags);
+
+	return result;
+}
+
+/*
+ * stop CSP
+ */
+static int snd_sb_csp_stop(snd_sb_csp_t * p)
+{
+	int result;
+	unsigned char mixL, mixR;
+	unsigned long flags;
+
+	if (!(p->running & SNDRV_SB_CSP_ST_RUNNING))
+		return 0;
+
+	/* Mute PCM volume */
+	spin_lock_irqsave(&p->chip->mixer_lock, flags);
+	mixL = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV);
+	mixR = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV + 1);
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL & 0x7);
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR & 0x7);
+
+	spin_lock(&p->chip->reg_lock);
+	if (p->running & SNDRV_SB_CSP_ST_QSOUND) {
+		set_codec_parameter(p->chip, 0xe0, 0x01);
+		/* disable QSound decoder */
+		set_codec_parameter(p->chip, 0x00, 0x00);
+		set_codec_parameter(p->chip, 0x01, 0x00);
+
+		p->running &= ~SNDRV_SB_CSP_ST_QSOUND;
+	}
+	result = set_mode_register(p->chip, 0xc0);	/* c0 = STOP */
+	spin_unlock(&p->chip->reg_lock);
+
+	/* restore PCM volume */
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL);
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR);
+	spin_unlock_irqrestore(&p->chip->mixer_lock, flags);
+
+	if (!(result))
+		p->running &= ~(SNDRV_SB_CSP_ST_PAUSED | SNDRV_SB_CSP_ST_RUNNING);
+	return result;
+}
+
+/*
+ * pause CSP codec and hold DMA transfer
+ */
+static int snd_sb_csp_pause(snd_sb_csp_t * p)
+{
+	int result;
+	unsigned long flags;
+
+	if (!(p->running & SNDRV_SB_CSP_ST_RUNNING))
+		return -EBUSY;
+
+	spin_lock_irqsave(&p->chip->reg_lock, flags);
+	result = set_codec_parameter(p->chip, 0x80, 0xff);
+	spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+	if (!(result))
+		p->running |= SNDRV_SB_CSP_ST_PAUSED;
+
+	return result;
+}
+
+/*
+ * restart CSP codec and resume DMA transfer
+ */
+static int snd_sb_csp_restart(snd_sb_csp_t * p)
+{
+	int result;
+	unsigned long flags;
+
+	if (!(p->running & SNDRV_SB_CSP_ST_PAUSED))
+		return -EBUSY;
+
+	spin_lock_irqsave(&p->chip->reg_lock, flags);
+	result = set_codec_parameter(p->chip, 0x80, 0x00);
+	spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+	if (!(result))
+		p->running &= ~SNDRV_SB_CSP_ST_PAUSED;
+
+	return result;
+}
+
+/* ------------------------------ */
+
+/*
+ * QSound mixer control for PCM
+ */
+
+static int snd_sb_qsound_switch_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int snd_sb_qsound_switch_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_sb_csp_t *p = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = p->q_enabled ? 1 : 0;
+	return 0;
+}
+
+static int snd_sb_qsound_switch_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_sb_csp_t *p = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned char nval;
+	
+	nval = ucontrol->value.integer.value[0] & 0x01;
+	spin_lock_irqsave(&p->q_lock, flags);
+	change = p->q_enabled != nval;
+	p->q_enabled = nval;
+	spin_unlock_irqrestore(&p->q_lock, flags);
+	return change;
+}
+
+static int snd_sb_qsound_space_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = SNDRV_SB_CSP_QSOUND_MAX_RIGHT;
+	return 0;
+}
+
+static int snd_sb_qsound_space_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_sb_csp_t *p = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&p->q_lock, flags);
+	ucontrol->value.integer.value[0] = p->qpos_left;
+	ucontrol->value.integer.value[1] = p->qpos_right;
+	spin_unlock_irqrestore(&p->q_lock, flags);
+	return 0;
+}
+
+static int snd_sb_qsound_space_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_sb_csp_t *p = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned char nval1, nval2;
+	
+	nval1 = ucontrol->value.integer.value[0];
+	if (nval1 > SNDRV_SB_CSP_QSOUND_MAX_RIGHT)
+		nval1 = SNDRV_SB_CSP_QSOUND_MAX_RIGHT;
+	nval2 = ucontrol->value.integer.value[1];
+	if (nval2 > SNDRV_SB_CSP_QSOUND_MAX_RIGHT)
+		nval2 = SNDRV_SB_CSP_QSOUND_MAX_RIGHT;
+	spin_lock_irqsave(&p->q_lock, flags);
+	change = p->qpos_left != nval1 || p->qpos_right != nval2;
+	p->qpos_left = nval1;
+	p->qpos_right = nval2;
+	p->qpos_changed = change;
+	spin_unlock_irqrestore(&p->q_lock, flags);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_sb_qsound_switch = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "3D Control - Switch",
+	.info = snd_sb_qsound_switch_info,
+	.get = snd_sb_qsound_switch_get,
+	.put = snd_sb_qsound_switch_put
+};
+
+static snd_kcontrol_new_t snd_sb_qsound_space = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "3D Control - Space",
+	.info = snd_sb_qsound_space_info,
+	.get = snd_sb_qsound_space_get,
+	.put = snd_sb_qsound_space_put
+};
+
+static int snd_sb_qsound_build(snd_sb_csp_t * p)
+{
+	snd_card_t * card;
+	int err;
+
+	snd_assert(p != NULL, return -EINVAL);
+
+	card = p->chip->card;
+	p->qpos_left = p->qpos_right = SNDRV_SB_CSP_QSOUND_MAX_RIGHT / 2;
+	p->qpos_changed = 0;
+
+	spin_lock_init(&p->q_lock);
+
+	if ((err = snd_ctl_add(card, p->qsound_switch = snd_ctl_new1(&snd_sb_qsound_switch, p))) < 0)
+		goto __error;
+	if ((err = snd_ctl_add(card, p->qsound_space = snd_ctl_new1(&snd_sb_qsound_space, p))) < 0)
+		goto __error;
+
+	return 0;
+
+     __error:
+	snd_sb_qsound_destroy(p);
+	return err;
+}
+
+static void snd_sb_qsound_destroy(snd_sb_csp_t * p)
+{
+	snd_card_t * card;
+	unsigned long flags;
+
+	snd_assert(p != NULL, return);
+
+	card = p->chip->card;	
+	
+	down_write(&card->controls_rwsem);
+	if (p->qsound_switch)
+		snd_ctl_remove(card, p->qsound_switch);
+	if (p->qsound_space)
+		snd_ctl_remove(card, p->qsound_space);
+	up_write(&card->controls_rwsem);
+
+	/* cancel pending transfer of QSound parameters */
+	spin_lock_irqsave (&p->q_lock, flags);
+	p->qpos_changed = 0;
+	spin_unlock_irqrestore (&p->q_lock, flags);
+}
+
+/*
+ * Transfer qsound parameters to CSP,
+ * function should be called from interrupt routine
+ */
+static int snd_sb_csp_qsound_transfer(snd_sb_csp_t * p)
+{
+	int err = -ENXIO;
+
+	spin_lock(&p->q_lock);
+	if (p->running & SNDRV_SB_CSP_ST_QSOUND) {
+		set_codec_parameter(p->chip, 0xe0, 0x01);
+		/* left channel */
+		set_codec_parameter(p->chip, 0x00, p->qpos_left);
+		set_codec_parameter(p->chip, 0x02, 0x00);
+		/* right channel */
+		set_codec_parameter(p->chip, 0x00, p->qpos_right);
+		set_codec_parameter(p->chip, 0x03, 0x00);
+		err = 0;
+	}
+	p->qpos_changed = 0;
+	spin_unlock(&p->q_lock);
+	return err;
+}
+
+/* ------------------------------ */
+
+/*
+ * proc interface
+ */
+static int init_proc_entry(snd_sb_csp_t * p, int device)
+{
+	char name[16];
+	snd_info_entry_t *entry;
+	sprintf(name, "cspD%d", device);
+	if (! snd_card_proc_new(p->chip->card, name, &entry))
+		snd_info_set_text_ops(entry, p, 1024, info_read);
+	return 0;
+}
+
+static void info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+	snd_sb_csp_t *p = entry->private_data;
+
+	snd_iprintf(buffer, "Creative Signal Processor [v%d.%d]\n", (p->version >> 4), (p->version & 0x0f));
+	snd_iprintf(buffer, "State: %cx%c%c%c\n", ((p->running & SNDRV_SB_CSP_ST_QSOUND) ? 'Q' : '-'),
+		    ((p->running & SNDRV_SB_CSP_ST_PAUSED) ? 'P' : '-'),
+		    ((p->running & SNDRV_SB_CSP_ST_RUNNING) ? 'R' : '-'),
+		    ((p->running & SNDRV_SB_CSP_ST_LOADED) ? 'L' : '-'));
+	if (p->running & SNDRV_SB_CSP_ST_LOADED) {
+		snd_iprintf(buffer, "Codec: %s [func #%d]\n", p->codec_name, p->func_nr);
+		snd_iprintf(buffer, "Sample rates: ");
+		if (p->acc_rates == SNDRV_SB_CSP_RATE_ALL) {
+			snd_iprintf(buffer, "All\n");
+		} else {
+			snd_iprintf(buffer, "%s%s%s%s\n",
+				    ((p->acc_rates & SNDRV_SB_CSP_RATE_8000) ? "8000Hz " : ""),
+				    ((p->acc_rates & SNDRV_SB_CSP_RATE_11025) ? "11025Hz " : ""),
+				    ((p->acc_rates & SNDRV_SB_CSP_RATE_22050) ? "22050Hz " : ""),
+				    ((p->acc_rates & SNDRV_SB_CSP_RATE_44100) ? "44100Hz" : ""));
+		}
+		if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) {
+			snd_iprintf(buffer, "QSound decoder %sabled\n",
+				    p->q_enabled ? "en" : "dis");
+		} else {
+			snd_iprintf(buffer, "PCM format ID: 0x%x (%s/%s) [%s/%s] [%s/%s]\n",
+				    p->acc_format,
+				    ((p->acc_width & SNDRV_SB_CSP_SAMPLE_16BIT) ? "16bit" : "-"),
+				    ((p->acc_width & SNDRV_SB_CSP_SAMPLE_8BIT) ? "8bit" : "-"),
+				    ((p->acc_channels & SNDRV_SB_CSP_MONO) ? "mono" : "-"),
+				    ((p->acc_channels & SNDRV_SB_CSP_STEREO) ? "stereo" : "-"),
+				    ((p->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) ? "playback" : "-"),
+				    ((p->mode & SNDRV_SB_CSP_MODE_DSP_READ) ? "capture" : "-"));
+		}
+	}
+	if (p->running & SNDRV_SB_CSP_ST_AUTO) {
+		snd_iprintf(buffer, "Autoloaded Mu-Law, A-Law or Ima-ADPCM hardware codec\n");
+	}
+	if (p->running & SNDRV_SB_CSP_ST_RUNNING) {
+		snd_iprintf(buffer, "Processing %dbit %s PCM samples\n",
+			    ((p->run_width & SNDRV_SB_CSP_SAMPLE_16BIT) ? 16 : 8),
+			    ((p->run_channels & SNDRV_SB_CSP_MONO) ? "mono" : "stereo"));
+	}
+	if (p->running & SNDRV_SB_CSP_ST_QSOUND) {
+		snd_iprintf(buffer, "Qsound position: left = 0x%x, right = 0x%x\n",
+			    p->qpos_left, p->qpos_right);
+	}
+}
+
+/* */
+
+EXPORT_SYMBOL(snd_sb_csp_new);
+
+/*
+ * INIT part
+ */
+
+static int __init alsa_sb_csp_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_sb_csp_exit(void)
+{
+}
+
+module_init(alsa_sb_csp_init)
+module_exit(alsa_sb_csp_exit)
diff --git a/sound/isa/sb/sb16_csp_codecs.h b/sound/isa/sb/sb16_csp_codecs.h
new file mode 100644
index 0000000..f0e8b0d
--- /dev/null
+++ b/sound/isa/sb/sb16_csp_codecs.h
@@ -0,0 +1,949 @@
+/*
+ *  Copyright (c) 1994 Creative Technology Ltd.
+ *  Microcode files for SB16 Advanced Signal Processor
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+static unsigned char mulaw_main[] = {
+	0x00, 0x10, 0x00, 0x44, 0x08, 0x00, 0x00, 0x44,
+	0x00, 0xb1, 0x00, 0x44, 0x00, 0x61, 0x00, 0x44,
+	0x08, 0x50, 0x00, 0x44, 0x0d, 0xf2, 0x61, 0xa8,
+	0x44, 0x04, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45,
+	0x40, 0x49, 0x39, 0xac, 0x55, 0x55, 0x71, 0x8b,
+	0x50, 0x05, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39,
+	0xff, 0x2e, 0x21, 0x49, 0xff, 0x0f, 0xd4, 0x49,
+	0x20, 0x01, 0x09, 0x0e, 0x20, 0x00, 0x71, 0x8b,
+	0xa8, 0x01, 0xa8, 0x80, 0x88, 0x01, 0xa8, 0x80,
+	0xa8, 0x00, 0x00, 0x80, 0xd2, 0x00, 0x71, 0x8b,
+	0x88, 0x00, 0xa8, 0x80, 0xa8, 0x04, 0xb3, 0x80,
+	0x20, 0x07, 0xb3, 0x80, 0x88, 0x03, 0xb1, 0x80,
+	0xc0, 0x00, 0x09, 0x5c, 0xc2, 0x01, 0x00, 0x82,
+	0xa1, 0x00, 0x71, 0x8b, 0xcd, 0x00, 0x04, 0x19,
+	0xa2, 0x20, 0x71, 0x8b, 0xcf, 0x00, 0x04, 0x19,
+	0x00, 0x00, 0xb1, 0x80, 0xc2, 0x00, 0x04, 0x19,
+	0x00, 0x40, 0x00, 0x14, 0x08, 0x40, 0x04, 0x24,
+	0x00, 0x00, 0x34, 0x49, 0x0c, 0x40, 0x00, 0x44,
+	0x44, 0x04, 0x04, 0x39, 0x00, 0x00, 0x40, 0x45,
+	0x32, 0x00, 0x09, 0x5c, 0x00, 0x00, 0x0c, 0x39,
+	0x00, 0x00, 0x40, 0x45, 0x40, 0x40, 0x09, 0xef,
+	0xff, 0x20, 0x09, 0xcf, 0x00, 0x04, 0x63, 0xa1,
+	0x50, 0x03, 0x33, 0x80, 0x00, 0x04, 0xa3, 0x80,
+	0x00, 0xff, 0xc2, 0x8b, 0x00, 0xd0, 0x04, 0x54,
+	0x04, 0xe0, 0x00, 0xc4, 0x20, 0x03, 0x80, 0xc0,
+	0x30, 0x00, 0x00, 0x88, 0x00, 0x00, 0x7a, 0x0a,
+	0xd0, 0x01, 0x00, 0x82, 0x00, 0x60, 0x00, 0x44,
+	0xc0, 0x00, 0x00, 0x99, 0x00, 0x60, 0x00, 0x44,
+	0x00, 0xff, 0xc2, 0x8b, 0x20, 0x00, 0x00, 0x80,
+	0x00, 0x0d, 0x42, 0x8b, 0x08, 0x32, 0x00, 0xc4,
+	0x00, 0x0e, 0x42, 0x8b, 0x00, 0xa2, 0x00, 0xc4,
+	0x00, 0x1e, 0x42, 0x8b, 0x0c, 0xb2, 0x00, 0xc4,
+	0x00, 0x8e, 0x42, 0x8b, 0x00, 0x62, 0x00, 0xc4,
+	0x00, 0x9e, 0x42, 0x8b, 0x08, 0x52, 0x00, 0xc4,
+	0x00, 0xbe, 0x42, 0x8b, 0x08, 0x52, 0x00, 0xc4,
+	0x00, 0x04, 0x42, 0x8b, 0x04, 0x72, 0x00, 0xc4,
+	0x00, 0x24, 0x42, 0x8b, 0x00, 0xd2, 0x00, 0xc4,
+	0x00, 0x55, 0x42, 0x8b, 0x00, 0x60, 0x00, 0xc4,
+	0x00, 0x00, 0x40, 0x45, 0x20, 0x01, 0x79, 0x80,
+	0x00, 0x30, 0x42, 0x8b, 0x08, 0x82, 0x00, 0xc4,
+	0x00, 0x00, 0x40, 0x45, 0x00, 0x00, 0x71, 0x8b,
+	0x40, 0x01, 0x00, 0x80, 0x00, 0x60, 0x00, 0x44,
+	0xff, 0x00, 0xe2, 0xab, 0x00, 0xb2, 0x00, 0xc4,
+	0x0f, 0xf2, 0xa8, 0xa8, 0x20, 0x00, 0xb1, 0x88,
+	0x00, 0x00, 0x41, 0x02, 0x4d, 0xf2, 0x00, 0x39,
+	0xc0, 0x01, 0x00, 0x82, 0x00, 0x60, 0x00, 0x44,
+	0x0d, 0xf2, 0xa3, 0xa8, 0x4d, 0xf2, 0x00, 0x39,
+	0x00, 0x60, 0x00, 0x44, 0xff, 0x00, 0xe2, 0xab,
+	0x20, 0x00, 0x00, 0x88, 0x00, 0x00, 0x61, 0x02,
+	0x4d, 0xf2, 0x04, 0x19, 0x00, 0x60, 0x00, 0x44,
+	0xff, 0x00, 0xe2, 0xab, 0xa0, 0x00, 0x00, 0x88,
+	0x00, 0x00, 0x61, 0x10, 0x4d, 0xf2, 0x04, 0x19,
+	0x00, 0x60, 0x00, 0x44, 0xff, 0x20, 0xe2, 0xab,
+	0x60, 0x00, 0x00, 0x88, 0x00, 0x00, 0x71, 0xc0,
+	0x4d, 0xf2, 0x04, 0x19, 0x00, 0x60, 0x00, 0x44,
+	0x00, 0x00, 0x79, 0x80, 0x00, 0xe2, 0x00, 0x84,
+	0x03, 0x03, 0x04, 0x49, 0x08, 0xc2, 0x00, 0x54,
+	0x00, 0x60, 0x04, 0x64, 0x00, 0x60, 0x00, 0x44,
+	0x00, 0x00, 0x63, 0x80, 0x00, 0x00, 0x06, 0x19,
+	0x03, 0x00, 0x04, 0x49, 0x00, 0x60, 0x00, 0x44,
+	0x20, 0x01, 0x63, 0x80, 0x00, 0x00, 0x06, 0x19,
+	0x00, 0x20, 0xe2, 0x8b, 0x0c, 0xf2, 0x00, 0x84,
+	0x3e, 0x00, 0x51, 0x8b, 0xc0, 0x20, 0x00, 0x39,
+	0x08, 0x01, 0x00, 0x44, 0x6c, 0x00, 0x51, 0x8b,
+	0xc0, 0x20, 0x00, 0x39, 0x00, 0x02, 0xe2, 0x8b,
+	0x04, 0x21, 0x00, 0x84, 0xfd, 0x00, 0x51, 0x8b,
+	0xc2, 0x20, 0x00, 0x39, 0x00, 0x11, 0x00, 0x44,
+	0xfe, 0x00, 0x51, 0x8b, 0xc2, 0x20, 0x00, 0x39,
+	0xe5, 0x00, 0x71, 0x8b, 0xcd, 0x00, 0x00, 0x39,
+	0x00, 0x00, 0xb1, 0x80, 0xc9, 0x20, 0x04, 0x19,
+	0xcb, 0x20, 0x04, 0x19, 0xc1, 0x20, 0x04, 0x19,
+	0xc3, 0x20, 0x04, 0x19, 0x10, 0x00, 0x71, 0x8b,
+	0xc7, 0x20, 0x04, 0x19, 0x5e, 0x00, 0x71, 0x8b,
+	0xcf, 0x00, 0x00, 0x39, 0x00, 0x00, 0xb1, 0x80,
+	0xc4, 0x20, 0x04, 0x19, 0xc6, 0x20, 0x04, 0x19,
+	0xc8, 0x20, 0x04, 0x19, 0xca, 0x20, 0x04, 0x19,
+	0x20, 0x00, 0x71, 0x8b, 0xcc, 0x20, 0x04, 0x19,
+	0x03, 0x00, 0x04, 0x49, 0x00, 0x60, 0x00, 0x44,
+	0x09, 0x04, 0x61, 0xa8, 0xc1, 0x00, 0x04, 0x19,
+	0x0b, 0x04, 0x61, 0xa8, 0xca, 0x00, 0x04, 0x19,
+	0x04, 0x60, 0x00, 0xd4, 0x0d, 0x00, 0x61, 0x0a,
+	0x90, 0x40, 0x09, 0x8f, 0x00, 0x01, 0x00, 0x45,
+	0x0f, 0x00, 0x61, 0x0a, 0x00, 0x40, 0x09, 0x8f,
+	0x00, 0x01, 0x00, 0x45, 0x82, 0x00, 0x09, 0x2e,
+	0x80, 0x40, 0x09, 0xcf, 0x02, 0x00, 0x61, 0x22,
+	0x43, 0x25, 0x61, 0x22, 0x40, 0x33, 0x00, 0x80,
+	0x08, 0xa8, 0x00, 0x44, 0x20, 0x31, 0x49, 0x5c,
+	0x92, 0x00, 0x09, 0x4e, 0x02, 0x03, 0x09, 0x2e,
+	0x00, 0x00, 0xa3, 0x02, 0xc0, 0x00, 0x71, 0xc0,
+	0x20, 0x00, 0xeb, 0x80, 0x00, 0x04, 0xc2, 0x8b,
+	0x20, 0x04, 0x61, 0x80, 0x00, 0x04, 0x7a, 0x02,
+	0xcb, 0x00, 0xa8, 0x58, 0xb0, 0x05, 0xf3, 0x80,
+	0x20, 0x04, 0xa8, 0x10, 0x00, 0x00, 0x10, 0x39,
+	0xb0, 0x00, 0xe0, 0x8b, 0x20, 0x01, 0x00, 0x80,
+	0x00, 0x00, 0x63, 0xcb, 0x00, 0x00, 0x7a, 0x02,
+	0x40, 0x00, 0x01, 0x5b, 0x20, 0x00, 0x00, 0x80,
+	0x00, 0x00, 0x4a, 0xcb, 0x20, 0x00, 0x13, 0x80,
+	0x20, 0x00, 0x7a, 0x80, 0xe0, 0x21, 0x00, 0xc0,
+	0x08, 0x00, 0x08, 0x49, 0x10, 0x41, 0x09, 0x8e,
+	0xff, 0xff, 0x62, 0x8b, 0x00, 0x04, 0x61, 0x22,
+	0x00, 0x03, 0x00, 0x45, 0x22, 0x01, 0x33, 0x80,
+	0x20, 0x01, 0xa3, 0x02, 0x00, 0x00, 0x7a, 0x80,
+	0xc0, 0x00, 0x00, 0x82, 0x07, 0x20, 0x40, 0x0a,
+	0x08, 0x83, 0x00, 0x84, 0x40, 0x21, 0x00, 0x80,
+	0x40, 0x05, 0x93, 0x10, 0xc7, 0x20, 0x00, 0x39,
+	0x00, 0x00, 0x40, 0x45, 0x07, 0x20, 0x40, 0x0a,
+	0x0c, 0xa3, 0x00, 0x84, 0x08, 0x00, 0x00, 0x82,
+	0x0c, 0x24, 0x61, 0x50, 0x40, 0x01, 0x00, 0x80,
+	0xc7, 0x20, 0x00, 0x39, 0x00, 0x00, 0x40, 0x45,
+	0x00, 0x04, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39,
+	0x42, 0x01, 0x09, 0x0e, 0x02, 0x20, 0x61, 0x0a,
+	0x00, 0x01, 0x00, 0x45, 0x0c, 0x20, 0x60, 0x0a,
+	0x00, 0x73, 0x00, 0x84, 0x00, 0x04, 0xb1, 0x80,
+	0x00, 0x00, 0x06, 0x39, 0x0c, 0x61, 0x04, 0xd4,
+	0x00, 0x24, 0x71, 0xc0, 0x20, 0x33, 0x33, 0xc0,
+	0xe0, 0x01, 0xa3, 0x82, 0x22, 0x03, 0x7a, 0x02,
+	0xc3, 0x01, 0xa3, 0x82, 0x20, 0x01, 0x33, 0x80,
+	0x00, 0x00, 0x7a, 0x80, 0xc2, 0x01, 0xb3, 0x50,
+	0xcc, 0x20, 0x00, 0x39, 0x00, 0x00, 0x71, 0x80,
+	0x00, 0xf3, 0x00, 0x44, 0x0c, 0x20, 0x60, 0x0a,
+	0x00, 0xd3, 0x00, 0x84, 0x00, 0x04, 0xb1, 0x80,
+	0x00, 0x00, 0x06, 0x39, 0x0c, 0x61, 0x04, 0xd4,
+	0x00, 0x00, 0xb3, 0x10, 0xcc, 0x20, 0x00, 0x39,
+	0x00, 0x00, 0x71, 0xc0, 0x00, 0xf3, 0x00, 0x44,
+	0xcc, 0x20, 0x00, 0x39, 0x00, 0x20, 0x71, 0xc0,
+	0x00, 0x30, 0x71, 0xc0, 0x00, 0xf3, 0x00, 0x44,
+	0x20, 0x01, 0x00, 0x80, 0xff, 0xff, 0x62, 0x8b,
+	0x20, 0x01, 0x33, 0x80, 0x00, 0x00, 0x83, 0x80,
+	0x20, 0x00, 0x7a, 0x80, 0x20, 0xe1, 0x09, 0x5c,
+	0x82, 0x00, 0x09, 0x2f, 0x80, 0x4a, 0x09, 0x8e,
+	0xe0, 0x01, 0xb3, 0x82, 0x20, 0x04, 0xa3, 0x80,
+	0x00, 0x00, 0x7a, 0xcb, 0x03, 0x00, 0xa8, 0x18,
+	0x00, 0x00, 0x10, 0x39, 0x08, 0x04, 0xea, 0x10,
+	0x08, 0x04, 0x7a, 0x10, 0x20, 0x00, 0x00, 0x80,
+	0x40, 0x00, 0x21, 0xcb, 0x0c, 0x00, 0xe8, 0x10,
+	0x00, 0x00, 0x41, 0x02, 0x0c, 0x00, 0xeb, 0x10,
+	0xf2, 0x01, 0x00, 0x82, 0x40, 0x21, 0x33, 0x02,
+	0x08, 0x20, 0x61, 0x0a, 0xc4, 0x00, 0x04, 0x19,
+	0xc7, 0x00, 0x00, 0x99, 0x02, 0x00, 0x61, 0x0a,
+	0x0c, 0xe8, 0x04, 0x14, 0x01, 0x00, 0x61, 0x0a,
+	0x03, 0x00, 0x48, 0x0a, 0x00, 0xb8, 0x04, 0x54,
+	0xc3, 0x00, 0x04, 0x19, 0x0c, 0xb8, 0x00, 0x44,
+	0x08, 0x00, 0xc8, 0x0a, 0x0c, 0xb8, 0x04, 0x54,
+	0xc8, 0x00, 0x04, 0x19, 0x0a, 0x00, 0x61, 0x0a,
+	0x09, 0x00, 0x48, 0x0a, 0x00, 0x68, 0x04, 0x54,
+	0xc9, 0x00, 0x04, 0x19, 0x0c, 0x68, 0x00, 0x44,
+	0x0b, 0x00, 0xc8, 0x0a, 0x0c, 0x68, 0x04, 0x54,
+	0xcb, 0x00, 0x04, 0x19, 0x04, 0x00, 0x61, 0x0a,
+	0x06, 0x00, 0x48, 0x0a, 0x00, 0x78, 0x04, 0x54,
+	0xc6, 0x00, 0x04, 0x19, 0x0c, 0x78, 0x00, 0x44,
+	0x05, 0x00, 0xc8, 0x0a, 0x0c, 0x78, 0x04, 0x54,
+	0xc5, 0x00, 0x04, 0x19, 0x07, 0x00, 0x61, 0x0a,
+	0x0c, 0x00, 0x48, 0x0a, 0x00, 0xe8, 0x04, 0x54,
+	0xcc, 0x00, 0x04, 0x19, 0x0c, 0xe8, 0x00, 0x44,
+	0x0e, 0x00, 0xc8, 0x0a, 0x0c, 0xe8, 0x04, 0x54,
+	0xce, 0x00, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45,
+	0x20, 0x10, 0x71, 0x8b, 0x09, 0x3f, 0x07, 0x00
+};
+
+static unsigned char alaw_main[] = {
+	0x00, 0x10, 0x00, 0x44, 0x08, 0x00, 0x00, 0x44,
+	0x00, 0xb1, 0x00, 0x44, 0x00, 0x61, 0x00, 0x44,
+	0x08, 0x50, 0x00, 0x44, 0x0d, 0xf2, 0x61, 0xa8,
+	0x44, 0x04, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45,
+	0x40, 0x49, 0x39, 0xac, 0x55, 0x55, 0x71, 0x8b,
+	0x50, 0x05, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39,
+	0xff, 0x2e, 0x21, 0x49, 0xff, 0x0f, 0xd4, 0x49,
+	0x20, 0x01, 0x09, 0x0e, 0x20, 0x00, 0x71, 0x8b,
+	0xa8, 0x01, 0xa8, 0x80, 0x88, 0x01, 0xa8, 0x80,
+	0xa8, 0x00, 0x00, 0x80, 0xd2, 0x00, 0x71, 0x8b,
+	0x88, 0x00, 0xa8, 0x80, 0xa8, 0x04, 0xb3, 0x80,
+	0x20, 0x07, 0xb3, 0x80, 0x88, 0x03, 0xb1, 0x80,
+	0xc0, 0x00, 0x09, 0x5c, 0xc2, 0x01, 0x00, 0x82,
+	0xa1, 0x00, 0x71, 0x8b, 0xcd, 0x00, 0x04, 0x19,
+	0x21, 0x20, 0x71, 0x8b, 0xcf, 0x00, 0x04, 0x19,
+	0x00, 0x00, 0xb1, 0x80, 0xc2, 0x00, 0x04, 0x19,
+	0x00, 0x40, 0x00, 0x14, 0x08, 0x40, 0x04, 0x24,
+	0x00, 0x00, 0x34, 0x49, 0x0c, 0x40, 0x00, 0x44,
+	0x44, 0x04, 0x04, 0x39, 0x00, 0x00, 0x40, 0x45,
+	0x32, 0x00, 0x09, 0x5c, 0x00, 0x00, 0x0c, 0x39,
+	0x00, 0x00, 0x40, 0x45, 0x40, 0x40, 0x09, 0xef,
+	0xff, 0x20, 0x09, 0xcf, 0x00, 0x04, 0x63, 0xa1,
+	0x50, 0x03, 0x33, 0x80, 0x00, 0x04, 0xa3, 0x80,
+	0x00, 0xff, 0xc2, 0x8b, 0x00, 0xd0, 0x04, 0x54,
+	0x04, 0xe0, 0x00, 0xc4, 0x20, 0x03, 0x80, 0xc0,
+	0x30, 0x00, 0x00, 0x88, 0x00, 0x00, 0x7a, 0x0a,
+	0xd0, 0x01, 0x00, 0x82, 0x00, 0x60, 0x00, 0x44,
+	0xc0, 0x00, 0x00, 0x99, 0x00, 0x60, 0x00, 0x44,
+	0x00, 0xff, 0xc2, 0x8b, 0x20, 0x00, 0x00, 0x80,
+	0x00, 0x0d, 0x42, 0x8b, 0x08, 0x32, 0x00, 0xc4,
+	0x00, 0x0e, 0x42, 0x8b, 0x00, 0xa2, 0x00, 0xc4,
+	0x00, 0x1e, 0x42, 0x8b, 0x0c, 0xb2, 0x00, 0xc4,
+	0x00, 0x8e, 0x42, 0x8b, 0x00, 0x62, 0x00, 0xc4,
+	0x00, 0x9e, 0x42, 0x8b, 0x08, 0x52, 0x00, 0xc4,
+	0x00, 0xbe, 0x42, 0x8b, 0x08, 0x52, 0x00, 0xc4,
+	0x00, 0x04, 0x42, 0x8b, 0x04, 0x72, 0x00, 0xc4,
+	0x00, 0x24, 0x42, 0x8b, 0x00, 0xd2, 0x00, 0xc4,
+	0x00, 0x55, 0x42, 0x8b, 0x00, 0x60, 0x00, 0xc4,
+	0x00, 0x00, 0x40, 0x45, 0x20, 0x01, 0x79, 0x80,
+	0x00, 0x30, 0x42, 0x8b, 0x08, 0x82, 0x00, 0xc4,
+	0x00, 0x00, 0x40, 0x45, 0x00, 0x00, 0x71, 0x8b,
+	0x40, 0x01, 0x00, 0x80, 0x00, 0x60, 0x00, 0x44,
+	0xff, 0x00, 0xe2, 0xab, 0x00, 0xb2, 0x00, 0xc4,
+	0x0f, 0xf2, 0xa8, 0xa8, 0x20, 0x00, 0xb1, 0x88,
+	0x00, 0x00, 0x41, 0x02, 0x4d, 0xf2, 0x00, 0x39,
+	0xc0, 0x01, 0x00, 0x82, 0x00, 0x60, 0x00, 0x44,
+	0x0d, 0xf2, 0xa3, 0xa8, 0x4d, 0xf2, 0x00, 0x39,
+	0x00, 0x60, 0x00, 0x44, 0xff, 0x00, 0xe2, 0xab,
+	0x20, 0x00, 0x00, 0x88, 0x00, 0x00, 0x61, 0x02,
+	0x4d, 0xf2, 0x04, 0x19, 0x00, 0x60, 0x00, 0x44,
+	0xff, 0x00, 0xe2, 0xab, 0xa0, 0x00, 0x00, 0x88,
+	0x00, 0x00, 0x61, 0x10, 0x4d, 0xf2, 0x04, 0x19,
+	0x00, 0x60, 0x00, 0x44, 0xff, 0x20, 0xe2, 0xab,
+	0x60, 0x00, 0x00, 0x88, 0x00, 0x00, 0x71, 0xc0,
+	0x4d, 0xf2, 0x04, 0x19, 0x00, 0x60, 0x00, 0x44,
+	0x00, 0x00, 0x79, 0x80, 0x00, 0xe2, 0x00, 0x84,
+	0x03, 0x03, 0x04, 0x49, 0x04, 0xc2, 0x00, 0x54,
+	0x00, 0x60, 0x04, 0x64, 0x00, 0x60, 0x00, 0x44,
+	0x00, 0x00, 0x63, 0x80, 0x00, 0x00, 0x06, 0x19,
+	0x03, 0x00, 0x04, 0x49, 0x00, 0x60, 0x00, 0x44,
+	0x20, 0x01, 0x63, 0x80, 0x00, 0x00, 0x06, 0x19,
+	0x00, 0x20, 0xe2, 0x8b, 0x0c, 0xf2, 0x00, 0x84,
+	0xbe, 0x00, 0x51, 0x8b, 0xc0, 0x20, 0x00, 0x39,
+	0x08, 0x01, 0x00, 0x44, 0xec, 0x00, 0x51, 0x8b,
+	0xc0, 0x20, 0x00, 0x39, 0x00, 0x02, 0xe2, 0x8b,
+	0x04, 0x21, 0x00, 0x84, 0x3f, 0x00, 0x51, 0x8b,
+	0xc2, 0x20, 0x00, 0x39, 0x00, 0x11, 0x00, 0x44,
+	0x3d, 0x00, 0x51, 0x8b, 0xc2, 0x20, 0x00, 0x39,
+	0xe5, 0x00, 0x71, 0x8b, 0xcd, 0x00, 0x00, 0x39,
+	0x00, 0x00, 0xb1, 0x80, 0xc9, 0x20, 0x04, 0x19,
+	0xcb, 0x20, 0x04, 0x19, 0xc1, 0x20, 0x04, 0x19,
+	0xc3, 0x20, 0x04, 0x19, 0x10, 0x00, 0x71, 0x8b,
+	0xc7, 0x20, 0x04, 0x19, 0xde, 0x00, 0x51, 0x8b,
+	0xcf, 0x00, 0x00, 0x39, 0x00, 0x01, 0xb1, 0x80,
+	0xc4, 0x20, 0x04, 0x19, 0xc6, 0x20, 0x04, 0x19,
+	0xc8, 0x20, 0x04, 0x19, 0xca, 0x20, 0x04, 0x19,
+	0x20, 0x00, 0x71, 0x8b, 0xcc, 0x20, 0x04, 0x19,
+	0x03, 0x00, 0x04, 0x49, 0x00, 0x60, 0x00, 0x44,
+	0x09, 0x04, 0x61, 0xa8, 0xc1, 0x00, 0x04, 0x19,
+	0x0b, 0x04, 0x61, 0xa8, 0xca, 0x00, 0x04, 0x19,
+	0x04, 0x60, 0x00, 0xd4, 0x0d, 0x00, 0x61, 0x0a,
+	0x90, 0x40, 0x09, 0x8f, 0x00, 0x01, 0x00, 0x45,
+	0x0f, 0x00, 0x61, 0x0a, 0x00, 0x40, 0x09, 0x8f,
+	0x00, 0x01, 0x00, 0x45, 0x82, 0x00, 0x09, 0x2e,
+	0x80, 0x40, 0x09, 0xcf, 0x02, 0x00, 0x61, 0x22,
+	0x43, 0x25, 0x61, 0x22, 0x40, 0x33, 0x00, 0x80,
+	0x08, 0x48, 0x00, 0x44, 0x20, 0xb1, 0x49, 0x5c,
+	0x92, 0x00, 0x09, 0x4e, 0x02, 0x03, 0x09, 0x2e,
+	0x00, 0x00, 0xa3, 0x02, 0xc0, 0x00, 0x71, 0xc0,
+	0x20, 0x00, 0xeb, 0x80, 0x00, 0x04, 0xc2, 0x8b,
+	0x20, 0x04, 0x61, 0x80, 0x00, 0x04, 0x7a, 0x02,
+	0xc0, 0x00, 0x00, 0x82, 0x0c, 0xc3, 0x08, 0x49,
+	0xb0, 0x01, 0xf3, 0x80, 0x00, 0x00, 0x10, 0x39,
+	0x20, 0x00, 0x0c, 0x89, 0x0c, 0x88, 0x08, 0x49,
+	0x03, 0x00, 0xa8, 0x18, 0x00, 0x00, 0x10, 0x39,
+	0xbd, 0xff, 0x62, 0x8b, 0x20, 0x01, 0x00, 0x80,
+	0x00, 0x00, 0x63, 0xcb, 0x00, 0x00, 0x7a, 0x02,
+	0x40, 0x00, 0x01, 0x5b, 0x20, 0x00, 0x00, 0x80,
+	0x00, 0x00, 0x4a, 0xcb, 0x20, 0x00, 0x13, 0x80,
+	0x20, 0x00, 0x7a, 0x80, 0xe0, 0x21, 0x00, 0xc0,
+	0x08, 0x00, 0x08, 0x49, 0x10, 0x41, 0x09, 0x8e,
+	0xae, 0xae, 0x62, 0x8b, 0x00, 0x04, 0x61, 0x22,
+	0x00, 0x03, 0x00, 0x45, 0x22, 0x01, 0x33, 0x80,
+	0x20, 0x01, 0xa3, 0x02, 0x00, 0x00, 0x7a, 0x80,
+	0xc0, 0x00, 0x00, 0x82, 0x07, 0x20, 0x40, 0x0a,
+	0x08, 0xa3, 0x00, 0x84, 0x40, 0x21, 0x00, 0x80,
+	0x40, 0x05, 0x93, 0x10, 0xc7, 0x20, 0x00, 0x39,
+	0x00, 0x00, 0x40, 0x45, 0x07, 0x20, 0x40, 0x0a,
+	0x0c, 0x93, 0x00, 0x84, 0x08, 0x00, 0x00, 0x82,
+	0x0c, 0x24, 0x61, 0x50, 0x40, 0x01, 0x00, 0x80,
+	0xc7, 0x20, 0x00, 0x39, 0x00, 0x00, 0x40, 0x45,
+	0x00, 0x04, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39,
+	0x42, 0x01, 0x09, 0x0e, 0x02, 0x20, 0x61, 0x0a,
+	0x00, 0x01, 0x00, 0x45, 0x0c, 0x20, 0x60, 0x0a,
+	0x00, 0xc3, 0x00, 0x84, 0x00, 0x04, 0xb1, 0x80,
+	0x00, 0x00, 0x06, 0x39, 0x0c, 0x61, 0x04, 0xd4,
+	0x00, 0x24, 0x71, 0xc0, 0x20, 0x33, 0x33, 0xc0,
+	0xe0, 0x01, 0xa3, 0x82, 0x22, 0x03, 0x7a, 0x02,
+	0xc3, 0x01, 0xa3, 0x82, 0x20, 0x01, 0x33, 0x80,
+	0x00, 0x00, 0x7a, 0x80, 0xc2, 0x01, 0xb3, 0x50,
+	0xcc, 0x20, 0x00, 0x39, 0x00, 0x00, 0x71, 0x80,
+	0x00, 0x08, 0x00, 0x44, 0x0c, 0x20, 0x60, 0x0a,
+	0x00, 0xf3, 0x00, 0x84, 0x00, 0x04, 0xb1, 0x80,
+	0x00, 0x00, 0x06, 0x39, 0x0c, 0x61, 0x04, 0xd4,
+	0x00, 0x00, 0x71, 0xc0, 0x00, 0x00, 0x93, 0x10,
+	0xcc, 0x20, 0x00, 0x39, 0x00, 0x08, 0x00, 0x44,
+	0xcc, 0x20, 0x00, 0x39, 0x00, 0x20, 0x00, 0xc0,
+	0x00, 0x30, 0x71, 0xc0, 0x00, 0x08, 0x00, 0x44,
+	0x20, 0x01, 0x00, 0x80, 0xae, 0xae, 0x62, 0x8b,
+	0x20, 0x01, 0x33, 0x80, 0x00, 0x00, 0x83, 0x80,
+	0x20, 0x00, 0x7a, 0x80, 0x20, 0xa1, 0x49, 0x5c,
+	0x82, 0x00, 0x09, 0x6e, 0x80, 0x4a, 0x09, 0x8e,
+	0xe0, 0x01, 0xb3, 0x82, 0x20, 0x04, 0xa3, 0x80,
+	0x00, 0x00, 0x7a, 0xcb, 0x28, 0x04, 0xea, 0x10,
+	0x0c, 0x04, 0x7a, 0x10, 0x70, 0x00, 0xc0, 0x8b,
+	0x00, 0x00, 0x10, 0x39, 0x90, 0x03, 0x00, 0x80,
+	0x40, 0x00, 0x21, 0x5b, 0x90, 0x00, 0x61, 0x80,
+	0x0c, 0x8a, 0x08, 0x49, 0x00, 0x00, 0x1c, 0x19,
+	0x40, 0x00, 0x08, 0x5b, 0x08, 0x00, 0x08, 0x49,
+	0x20, 0x02, 0x00, 0x80, 0x03, 0x00, 0xa8, 0x18,
+	0x00, 0x00, 0x14, 0x19, 0x40, 0x00, 0x21, 0xcb,
+	0x00, 0x00, 0x41, 0x02, 0x00, 0x00, 0xeb, 0x80,
+	0xf2, 0x01, 0x00, 0x82, 0x40, 0x21, 0x33, 0x02,
+	0x08, 0x20, 0x61, 0x0a, 0xc4, 0x00, 0x04, 0x19,
+	0xc7, 0x00, 0x00, 0x99, 0x02, 0x00, 0x61, 0x0a,
+	0x0c, 0x0a, 0x04, 0x14, 0x01, 0x00, 0x61, 0x0a,
+	0x03, 0x00, 0x48, 0x0a, 0x00, 0x58, 0x04, 0x54,
+	0xc3, 0x00, 0x04, 0x19, 0x0c, 0x58, 0x00, 0x44,
+	0x08, 0x00, 0xc8, 0x0a, 0x0c, 0x58, 0x04, 0x54,
+	0xc8, 0x00, 0x04, 0x19, 0x0a, 0x00, 0x61, 0x0a,
+	0x09, 0x00, 0x48, 0x0a, 0x00, 0xc8, 0x04, 0x54,
+	0xc9, 0x00, 0x04, 0x19, 0x0c, 0xc8, 0x00, 0x44,
+	0x0b, 0x00, 0xc8, 0x0a, 0x0c, 0xc8, 0x04, 0x54,
+	0xcb, 0x00, 0x04, 0x19, 0x04, 0x00, 0x61, 0x0a,
+	0x06, 0x00, 0x48, 0x0a, 0x00, 0xd8, 0x04, 0x54,
+	0xc6, 0x00, 0x04, 0x19, 0x0c, 0xd8, 0x00, 0x44,
+	0x05, 0x00, 0xc8, 0x0a, 0x0c, 0xd8, 0x04, 0x54,
+	0xc5, 0x00, 0x04, 0x19, 0x07, 0x00, 0x61, 0x0a,
+	0x0c, 0x00, 0x48, 0x0a, 0x00, 0x0a, 0x04, 0x54,
+	0xcc, 0x00, 0x04, 0x19, 0x0c, 0x0a, 0x00, 0x44,
+	0x0e, 0x00, 0xc8, 0x0a, 0x0c, 0x0a, 0x04, 0x54,
+	0xce, 0x00, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45,
+	0x20, 0x10, 0x71, 0x8b, 0x08, 0x42, 0x06, 0x00
+};
+
+
+static unsigned char ima_adpcm_init[] = {
+	0x00, 0x10, 0x00, 0x44, 0x00, 0x00, 0x40, 0x45,
+	0x00, 0x00, 0x40, 0x45, 0x00, 0x00, 0x40, 0x45,
+	0x00, 0x00, 0x40, 0x45, 0xaa, 0xaa, 0x71, 0x8b,
+	0x44, 0x04, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45,
+	0xff, 0x6e, 0x21, 0x49, 0xff, 0x0f, 0xd4, 0x49,
+	0x40, 0x49, 0x39, 0xac, 0x55, 0x55, 0x71, 0x8b,
+	0x50, 0x05, 0xb1, 0x80, 0x62, 0x00, 0x19, 0x0e,
+	0x21, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xb0, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x40, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x60, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x50, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x70, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xc0, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xe0, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xd0, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x02, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x22, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x32, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xa2, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xb2, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x62, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xc2, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xf2, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x11, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xa1, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x61, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xe1, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x13, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xb3, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xc3, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x18, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x68, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x0a, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x4a, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x29, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x79, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x9b, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x14, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xf4, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xe6, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xe5, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xd7, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x2e, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x9d, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xef, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xb2, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x33, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x2a, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x3b, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x46, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x2c, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xdd, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x01, 0x10, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x9a, 0x10, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x16, 0x10, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x8e, 0x10, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xc2, 0x30, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xc9, 0x30, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x3c, 0x30, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x81, 0x80, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xd4, 0x80, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x10, 0xa0, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x34, 0xa0, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x02, 0x90, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x75, 0x90, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x9a, 0xb0, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x12, 0x40, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x0d, 0x40, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x3c, 0x60, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xe7, 0x50, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x0e, 0x70, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xff, 0xc0, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xc8, 0xd0, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x57, 0xf0, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xc8, 0x22, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xb0, 0x32, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xdd, 0x82, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x90, 0xb2, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x8a, 0x62, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xce, 0x72, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xa5, 0xd2, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x97, 0x21, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xa2, 0xa1, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x5c, 0x41, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xfe, 0xc1, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x7a, 0x23, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x78, 0x93, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x67, 0x73, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x17, 0x28, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x88, 0x48, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xdb, 0xf8, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x2b, 0xba, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xf1, 0x09, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xdc, 0x69, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x19, 0x8b, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0xff, 0xfb, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x20, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80,
+	0x52, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0xff, 0xff, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0xc2, 0x00, 0x00, 0x82, 0xc2, 0x00, 0x00, 0x82,
+	0xc2, 0x00, 0x00, 0x82, 0x10, 0x00, 0x71, 0x8b,
+	0xc2, 0x00, 0x00, 0x82, 0x80, 0x00, 0x71, 0x8b,
+	0xc2, 0x00, 0x00, 0x82, 0x90, 0x00, 0x71, 0x8b,
+	0xc2, 0x00, 0x00, 0x82, 0x40, 0x00, 0x71, 0x8b,
+	0xc2, 0x00, 0x00, 0x82, 0xff, 0xff, 0x71, 0x8b,
+	0xc2, 0x00, 0x00, 0x82, 0xc2, 0x00, 0x00, 0x82,
+	0xc2, 0x00, 0x00, 0x82, 0xc2, 0x00, 0x00, 0x82,
+	0x10, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0x80, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0x90, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0x40, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0xff, 0xfb, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0x00, 0x04, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0x4a, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0x00, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0x00, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82,
+	0xc2, 0x00, 0x00, 0x82, 0xc2, 0x30, 0x04, 0x19,
+	0x10, 0x00, 0x09, 0x4f, 0xc2, 0x01, 0x00, 0x82,
+	0xc2, 0x01, 0x00, 0x82, 0xc2, 0x01, 0x00, 0x82,
+	0xc2, 0x01, 0x00, 0x82, 0xc2, 0x01, 0x00, 0x82,
+	0xc2, 0x01, 0x00, 0x82, 0xc2, 0x01, 0x00, 0x82,
+	0xc2, 0x01, 0x00, 0x82, 0xc2, 0x01, 0x00, 0x82,
+	0xc2, 0x01, 0x00, 0x82, 0xc2, 0x01, 0x00, 0x82,
+	0xc2, 0x01, 0x00, 0x82, 0xc2, 0x01, 0x00, 0x82,
+	0x00, 0x10, 0x71, 0x8b, 0xc1, 0x30, 0x04, 0x19,
+	0x93, 0x00, 0x01, 0x4f, 0xcd, 0x30, 0x00, 0x09,
+	0xcf, 0x30, 0x00, 0x09, 0x00, 0x00, 0x34, 0x49,
+	0x00, 0x08, 0x00, 0x44, 0xc8, 0x54, 0x11, 0x00
+};
+
+static unsigned char ima_adpcm_playback[] = {
+	0x00, 0x10, 0x00, 0x44, 0x08, 0x00, 0x00, 0x44,
+	0x0c, 0x50, 0x00, 0x44, 0x00, 0x70, 0x00, 0x44,
+	0x04, 0x70, 0x00, 0x44, 0x0d, 0xf2, 0x61, 0xa8,
+	0x44, 0x04, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45,
+	0x00, 0x04, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39,
+	0xff, 0x2e, 0x21, 0x49, 0xff, 0x0d, 0xd4, 0x49,
+	0x40, 0x49, 0x39, 0xac, 0x55, 0x55, 0x71, 0x8b,
+	0x50, 0x01, 0xb1, 0x80, 0x00, 0x01, 0xb1, 0x80,
+	0xc9, 0x20, 0x04, 0x19, 0x51, 0x00, 0x71, 0x8b,
+	0xcd, 0x00, 0x04, 0x19, 0xe4, 0x20, 0x71, 0x8b,
+	0xcf, 0x00, 0x04, 0x19, 0x80, 0x00, 0x71, 0x8b,
+	0xcb, 0x20, 0x04, 0x19, 0x10, 0x00, 0x71, 0x8b,
+	0xc4, 0x20, 0x04, 0x19, 0x65, 0x00, 0x51, 0x8b,
+	0xc2, 0x20, 0x00, 0x39, 0x00, 0x00, 0xb1, 0x80,
+	0xc2, 0x30, 0x04, 0x19, 0x00, 0x00, 0x63, 0x80,
+	0xc1, 0xa0, 0x04, 0x19, 0x93, 0x00, 0x01, 0x4f,
+	0xcd, 0x30, 0x00, 0x09, 0xcf, 0x30, 0x00, 0x09,
+	0x04, 0x40, 0x00, 0x14, 0x0c, 0x40, 0x00, 0x14,
+	0x00, 0x04, 0x61, 0xa8, 0x02, 0x04, 0x61, 0xa8,
+	0x04, 0x60, 0x04, 0x24, 0x00, 0x00, 0x34, 0x49,
+	0x00, 0x50, 0x00, 0x44, 0x44, 0x04, 0x04, 0x39,
+	0x00, 0x00, 0x40, 0x45, 0x00, 0x00, 0x40, 0x45,
+	0x0f, 0x00, 0x61, 0x0a, 0x00, 0x01, 0x00, 0x45,
+	0x40, 0x40, 0x09, 0xef, 0xff, 0x20, 0x09, 0xcf,
+	0x00, 0x04, 0x63, 0xa1, 0x50, 0x03, 0x33, 0x80,
+	0x00, 0x04, 0xa3, 0x80, 0x00, 0xff, 0xc2, 0x8b,
+	0x08, 0xf0, 0x04, 0x54, 0x0c, 0xd0, 0x00, 0xc4,
+	0x20, 0x03, 0x80, 0xc0, 0x30, 0x00, 0x00, 0x88,
+	0x00, 0x00, 0x7a, 0x0a, 0xd0, 0x01, 0x00, 0x82,
+	0x08, 0x50, 0x00, 0x44, 0xc0, 0x00, 0x00, 0x99,
+	0x08, 0x50, 0x00, 0x44, 0x00, 0xff, 0xc2, 0x8b,
+	0x20, 0x00, 0x00, 0x80, 0x00, 0x0d, 0x42, 0x8b,
+	0x00, 0xa2, 0x00, 0xc4, 0x00, 0x0e, 0x42, 0x8b,
+	0x0c, 0x92, 0x00, 0xc4, 0x00, 0x1e, 0x42, 0x8b,
+	0x04, 0x62, 0x00, 0xc4, 0x00, 0x8e, 0x42, 0x8b,
+	0x0c, 0x52, 0x00, 0xc4, 0x00, 0x9e, 0x42, 0x8b,
+	0x00, 0xc2, 0x00, 0xc4, 0x00, 0xbe, 0x42, 0x8b,
+	0x00, 0xc2, 0x00, 0xc4, 0x00, 0x04, 0x42, 0x8b,
+	0x00, 0xf2, 0x00, 0xc4, 0x00, 0x24, 0x42, 0x8b,
+	0x00, 0x91, 0x00, 0xc4, 0x00, 0x55, 0x42, 0x8b,
+	0x08, 0x50, 0x00, 0xc4, 0x00, 0x3f, 0x42, 0x8b,
+	0x08, 0xe2, 0x00, 0xc4, 0x00, 0x00, 0x40, 0x45,
+	0x20, 0x01, 0x79, 0x80, 0x00, 0x30, 0x42, 0x8b,
+	0x00, 0x92, 0x00, 0xc4, 0x00, 0x00, 0x40, 0x45,
+	0x00, 0x00, 0x71, 0x8b, 0x40, 0x01, 0x00, 0x80,
+	0x08, 0x50, 0x00, 0x44, 0xff, 0x00, 0xe2, 0xab,
+	0x0c, 0x42, 0x00, 0xc4, 0x0f, 0xf2, 0xa8, 0xa8,
+	0x20, 0x00, 0xb1, 0x88, 0x00, 0x00, 0x41, 0x02,
+	0x4d, 0xf2, 0x00, 0x39, 0xc0, 0x01, 0x00, 0x82,
+	0x08, 0x50, 0x00, 0x44, 0x0d, 0xf2, 0xa3, 0xa8,
+	0x4d, 0xf2, 0x00, 0x39, 0x08, 0x50, 0x00, 0x44,
+	0xff, 0x00, 0xe2, 0xab, 0x20, 0x00, 0x00, 0x88,
+	0x00, 0x00, 0x61, 0x02, 0x4d, 0xf2, 0x04, 0x19,
+	0x08, 0x50, 0x00, 0x44, 0xff, 0x00, 0xe2, 0xab,
+	0xa0, 0x00, 0x00, 0x88, 0x00, 0x00, 0x61, 0x10,
+	0x4d, 0xf2, 0x04, 0x19, 0x08, 0x50, 0x00, 0x44,
+	0xff, 0x20, 0xe2, 0xab, 0x60, 0x00, 0x00, 0x88,
+	0x00, 0x00, 0x71, 0xc0, 0x4d, 0xf2, 0x04, 0x19,
+	0x08, 0x50, 0x00, 0x44, 0x00, 0x00, 0x7a, 0x0a,
+	0x20, 0x01, 0xf0, 0x80, 0x01, 0xa0, 0x41, 0x0a,
+	0x04, 0xd2, 0x00, 0xc4, 0x20, 0x01, 0xf0, 0x80,
+	0xc1, 0x30, 0x04, 0x19, 0x08, 0x50, 0x00, 0x44,
+	0x00, 0x00, 0x79, 0x80, 0x00, 0xa1, 0x00, 0x84,
+	0xb5, 0x00, 0x51, 0x8b, 0xcf, 0x00, 0x00, 0x39,
+	0x00, 0x01, 0xb1, 0x80, 0x88, 0x00, 0x04, 0x19,
+	0x8a, 0x00, 0x04, 0x19, 0xc8, 0x20, 0x04, 0x19,
+	0xca, 0x20, 0x04, 0x19, 0xc2, 0x30, 0x04, 0x19,
+	0xcd, 0x10, 0x04, 0x19, 0xcf, 0x10, 0x04, 0x19,
+	0xb0, 0x00, 0x71, 0x8b, 0x8c, 0x00, 0x04, 0x19,
+	0x8e, 0x00, 0x04, 0x19, 0x10, 0x00, 0x71, 0x8b,
+	0xc4, 0x20, 0x04, 0x19, 0x93, 0x00, 0x01, 0x4f,
+	0xcd, 0x30, 0x00, 0x09, 0xcf, 0x30, 0x00, 0x09,
+	0x03, 0x03, 0x04, 0x49, 0x04, 0x81, 0x00, 0x54,
+	0x08, 0x50, 0x04, 0x64, 0x08, 0x50, 0x00, 0x44,
+	0x00, 0x00, 0x63, 0x80, 0x00, 0x00, 0x06, 0x19,
+	0x03, 0x00, 0x04, 0x49, 0x08, 0x50, 0x00, 0x44,
+	0x20, 0x01, 0x63, 0x80, 0x00, 0x00, 0x06, 0x19,
+	0x00, 0x02, 0xe2, 0x8b, 0x08, 0x41, 0x00, 0x84,
+	0x65, 0x00, 0x51, 0x8b, 0xc2, 0x20, 0x00, 0x39,
+	0x00, 0x00, 0x63, 0x80, 0xc1, 0xa0, 0x04, 0x19,
+	0x08, 0x61, 0x00, 0x44, 0x2d, 0x00, 0x51, 0x8b,
+	0xc2, 0x20, 0x00, 0x39, 0x00, 0x00, 0xb1, 0x80,
+	0xc1, 0xa0, 0x04, 0x19, 0x03, 0x00, 0x04, 0x49,
+	0x08, 0x50, 0x00, 0x44, 0x02, 0x20, 0x61, 0x0a,
+	0x00, 0x01, 0x00, 0x45, 0x02, 0x30, 0x61, 0x0a,
+	0x04, 0x03, 0x00, 0xc4, 0x05, 0xb0, 0xc8, 0x18,
+	0x04, 0x71, 0x00, 0xc4, 0x00, 0x13, 0x00, 0x44,
+	0x00, 0x79, 0x08, 0x44, 0x00, 0x04, 0x79, 0x80,
+	0x00, 0x49, 0x00, 0xc4, 0xca, 0x20, 0x04, 0x19,
+	0x4a, 0x04, 0x04, 0x19, 0xff, 0x00, 0xe2, 0x8b,
+	0x0c, 0xf9, 0x08, 0x44, 0xcf, 0x10, 0x04, 0x19,
+	0x0c, 0x2b, 0x08, 0x44, 0x8e, 0x00, 0x04, 0x19,
+	0x03, 0x30, 0x61, 0x0a, 0xc8, 0x20, 0x00, 0x39,
+	0x48, 0x04, 0x00, 0x39, 0x0a, 0x30, 0x61, 0x0a,
+	0x0c, 0xf9, 0x08, 0x44, 0xcd, 0x10, 0x04, 0x19,
+	0x0c, 0x2b, 0x08, 0x44, 0x8c, 0x00, 0x04, 0x19,
+	0x0c, 0xd9, 0x08, 0x44, 0x0c, 0x5a, 0x00, 0x44,
+	0x00, 0x79, 0x08, 0x44, 0x00, 0x04, 0x79, 0x80,
+	0x00, 0x49, 0x00, 0xc4, 0xc3, 0x30, 0x04, 0x19,
+	0xca, 0x30, 0x00, 0x99, 0x0c, 0xd9, 0x08, 0x44,
+	0x42, 0x0a, 0x09, 0x0e, 0x00, 0x01, 0x33, 0x11,
+	0x8c, 0x01, 0xa3, 0x80, 0x00, 0x01, 0x7a, 0x10,
+	0x80, 0x05, 0xb1, 0x80, 0x05, 0xb0, 0xe0, 0x18,
+	0x00, 0x93, 0x00, 0x84, 0x00, 0x79, 0x08, 0x44,
+	0x00, 0x04, 0x79, 0x80, 0x00, 0x49, 0x00, 0xc4,
+	0x0c, 0x1b, 0x08, 0x44, 0x88, 0x00, 0x04, 0x19,
+	0x8a, 0x00, 0x00, 0x99, 0x0c, 0xd9, 0x08, 0x44,
+	0x42, 0x0a, 0x09, 0x0e, 0x80, 0x00, 0x71, 0x8b,
+	0xc0, 0x04, 0xb1, 0x82, 0x10, 0x00, 0xe0, 0x0b,
+	0x00, 0x43, 0x00, 0x84, 0x02, 0x30, 0x61, 0x0a,
+	0x01, 0x30, 0xc8, 0x0a, 0x00, 0x43, 0x00, 0x84,
+	0x00, 0x00, 0xb1, 0x80, 0xc2, 0x30, 0x04, 0x19,
+	0x0c, 0xa8, 0x00, 0x44, 0x02, 0x30, 0x61, 0x0a,
+	0x00, 0xd3, 0x00, 0xc4, 0x05, 0xb0, 0xc8, 0x18,
+	0x04, 0x63, 0x00, 0xc4, 0x08, 0xf3, 0x00, 0x44,
+	0x00, 0x79, 0x08, 0x44, 0x00, 0x04, 0x79, 0x80,
+	0x00, 0x49, 0x00, 0xc4, 0x20, 0x00, 0x04, 0x19,
+	0xff, 0x00, 0xe2, 0x8b, 0x0c, 0xf9, 0x08, 0x44,
+	0xcd, 0x10, 0x04, 0x19, 0xcf, 0x10, 0x04, 0x19,
+	0x0c, 0x2b, 0x08, 0x44, 0x8c, 0x00, 0x04, 0x19,
+	0x8e, 0x00, 0x04, 0x19, 0x03, 0x30, 0x61, 0x0a,
+	0xc8, 0x20, 0x00, 0x39, 0xca, 0x20, 0x00, 0x39,
+	0x48, 0x04, 0x00, 0x39, 0x4a, 0x04, 0x00, 0x39,
+	0x0c, 0xd9, 0x08, 0x44, 0x0c, 0x5a, 0x00, 0x44,
+	0x00, 0x79, 0x08, 0x44, 0x00, 0x04, 0x79, 0x80,
+	0x00, 0x49, 0x00, 0xc4, 0xc3, 0x30, 0x04, 0x19,
+	0x0c, 0xd9, 0x08, 0x44, 0x42, 0x0a, 0x09, 0x0e,
+	0x05, 0xb0, 0xe0, 0x18, 0x00, 0x18, 0x00, 0x84,
+	0x00, 0x79, 0x08, 0x44, 0x00, 0x04, 0x79, 0x80,
+	0x00, 0x49, 0x00, 0xc4, 0x0c, 0x1b, 0x08, 0x44,
+	0x80, 0x01, 0x00, 0x80, 0x0c, 0xd9, 0x08, 0x44,
+	0x42, 0x0a, 0x09, 0x0e, 0x80, 0x00, 0x71, 0x8b,
+	0xc0, 0x04, 0xb1, 0x82, 0x10, 0x00, 0xe0, 0x0b,
+	0x00, 0x88, 0x00, 0x84, 0x02, 0x30, 0x61, 0x0a,
+	0x01, 0x30, 0xc8, 0x0a, 0x00, 0x88, 0x00, 0x84,
+	0x00, 0x00, 0xb1, 0x80, 0xc2, 0x30, 0x04, 0x19,
+	0x00, 0x01, 0x00, 0x11, 0x00, 0x0f, 0xe2, 0x8b,
+	0x00, 0x00, 0x41, 0xcb, 0x8c, 0x00, 0x00, 0x80,
+	0x00, 0x00, 0x48, 0xcb, 0x20, 0x00, 0x7a, 0x80,
+	0x80, 0x01, 0x00, 0x80, 0x82, 0x0c, 0x09, 0x6e,
+	0x03, 0x08, 0x09, 0x0e, 0x80, 0x40, 0x09, 0xcf,
+	0x00, 0x01, 0x71, 0xc2, 0x00, 0x08, 0xc2, 0x1b,
+	0x04, 0xb8, 0x00, 0xc4, 0x20, 0x05, 0xa8, 0x80,
+	0x20, 0x01, 0xf0, 0x80, 0x00, 0x01, 0xc2, 0x1b,
+	0x04, 0x48, 0x00, 0xc4, 0x20, 0x05, 0xa8, 0x80,
+	0x20, 0x01, 0xf0, 0x80, 0x00, 0x02, 0xc2, 0x1b,
+	0x04, 0x68, 0x00, 0xc4, 0x20, 0x05, 0xa8, 0x80,
+	0x20, 0x01, 0xf0, 0x80, 0x20, 0x03, 0xa8, 0x80,
+	0x00, 0x01, 0x00, 0x11, 0x00, 0x04, 0xc2, 0x8b,
+	0x08, 0x78, 0x00, 0xc4, 0x00, 0x00, 0xe9, 0x80,
+	0x05, 0xb0, 0xa8, 0x18, 0x00, 0x00, 0x4a, 0xcb,
+	0x20, 0x00, 0xa8, 0x22, 0xd0, 0x01, 0x00, 0x82,
+	0x40, 0x01, 0x00, 0x80, 0xc4, 0x00, 0x04, 0x19,
+	0xb0, 0x00, 0xe2, 0x8b, 0x06, 0x20, 0xa8, 0x0a,
+	0x2d, 0x10, 0x61, 0x0a, 0xd1, 0x08, 0x09, 0x2e,
+	0x00, 0x01, 0xa8, 0x02, 0x0c, 0xf9, 0x08, 0x44,
+	0xcd, 0x10, 0x04, 0x19, 0x0c, 0x2b, 0x08, 0x44,
+	0x03, 0x08, 0x09, 0x0e, 0x9a, 0x25, 0xb1, 0x60,
+	0xa2, 0x0e, 0x09, 0x6e, 0x03, 0x00, 0x09, 0x0f,
+	0x00, 0x01, 0x71, 0x82, 0x20, 0x01, 0x00, 0x80,
+	0x00, 0x00, 0x61, 0xcb, 0x80, 0x01, 0x00, 0x80,
+	0x03, 0x00, 0x09, 0x0f, 0x00, 0x01, 0x71, 0xc2,
+	0x00, 0x08, 0xc2, 0x1b, 0x0c, 0x2a, 0x00, 0xc4,
+	0x20, 0x05, 0xa8, 0x80, 0x20, 0x01, 0xf0, 0x80,
+	0x00, 0x01, 0xc2, 0x1b, 0x0c, 0x1a, 0x00, 0xc4,
+	0x20, 0x05, 0xa8, 0x80, 0x20, 0x01, 0xf0, 0x80,
+	0x00, 0x02, 0xc2, 0x1b, 0x0c, 0x3a, 0x00, 0xc4,
+	0x20, 0x05, 0xa8, 0x80, 0x20, 0x01, 0xf0, 0x80,
+	0x20, 0x03, 0xa8, 0x80, 0x00, 0x01, 0x00, 0x11,
+	0x00, 0x04, 0xc2, 0x8b, 0x04, 0xaa, 0x00, 0xc4,
+	0x00, 0x00, 0xe9, 0x80, 0x05, 0xb0, 0xa8, 0x18,
+	0x00, 0x00, 0x4a, 0xcb, 0x20, 0x00, 0xa8, 0x22,
+	0xd0, 0x01, 0x00, 0x82, 0x40, 0x01, 0x00, 0x80,
+	0xc7, 0x00, 0x04, 0x19, 0xb0, 0x00, 0xe2, 0x8b,
+	0x06, 0x20, 0xa8, 0x0a, 0x2f, 0x10, 0x61, 0x0a,
+	0xf1, 0x08, 0x09, 0x2e, 0x00, 0x01, 0xa8, 0x02,
+	0x0c, 0xf9, 0x08, 0x44, 0xcf, 0x10, 0x04, 0x19,
+	0x0c, 0x2b, 0x08, 0x44, 0x9f, 0x35, 0xb1, 0x60,
+	0x03, 0x08, 0x09, 0x0e, 0x00, 0x01, 0x71, 0x82,
+	0x20, 0x01, 0x00, 0x80, 0x00, 0x00, 0x61, 0xcb,
+	0x80, 0x01, 0x00, 0x80, 0xe4, 0x20, 0x71, 0x8b,
+	0x00, 0x01, 0x00, 0x45, 0x90, 0x40, 0x09, 0x8f,
+	0x00, 0x05, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39,
+	0x08, 0x19, 0x04, 0xd4, 0x93, 0x00, 0x01, 0x4f,
+	0xe7, 0x00, 0x01, 0x6f, 0x0d, 0x30, 0x61, 0x0a,
+	0x20, 0x04, 0x61, 0xa8, 0xc2, 0x00, 0x00, 0x82,
+	0x02, 0x04, 0x61, 0xa8, 0xc2, 0x00, 0x00, 0x82,
+	0xcd, 0x30, 0x00, 0x09, 0x02, 0x00, 0x00, 0x02,
+	0x02, 0x00, 0x00, 0x02, 0xc0, 0x80, 0x00, 0x09,
+	0x20, 0x00, 0x09, 0x49, 0x0f, 0x30, 0x61, 0x0a,
+	0x0d, 0x30, 0xc8, 0x0a, 0x00, 0x29, 0x00, 0xc4,
+	0x00, 0x80, 0xc8, 0x0a, 0x00, 0x29, 0x00, 0xc4,
+	0x00, 0x04, 0xb1, 0x80, 0x00, 0x00, 0x06, 0x39,
+	0xc9, 0x20, 0x04, 0x39, 0x00, 0x39, 0x00, 0x44,
+	0x00, 0x04, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39,
+	0x00, 0x04, 0xb1, 0x80, 0xc9, 0x20, 0x04, 0x39,
+	0x00, 0x39, 0x00, 0x44, 0x09, 0x20, 0x23, 0x0a,
+	0x00, 0x00, 0x06, 0x19, 0xc9, 0x20, 0x04, 0x19,
+	0x00, 0x00, 0x40, 0x45, 0x02, 0x00, 0x61, 0x0a,
+	0x0c, 0xb9, 0x04, 0x14, 0x04, 0x00, 0x61, 0x0a,
+	0x06, 0x00, 0x48, 0x0a, 0x00, 0xa9, 0x04, 0x54,
+	0xc6, 0x00, 0x04, 0x19, 0x0c, 0xa9, 0x00, 0x44,
+	0x05, 0x00, 0xc8, 0x0a, 0x0c, 0xa9, 0x04, 0x54,
+	0xc5, 0x00, 0x04, 0x19, 0x07, 0x00, 0x61, 0x0a,
+	0x0c, 0x00, 0x48, 0x0a, 0x00, 0xb9, 0x04, 0x54,
+	0xcc, 0x00, 0x04, 0x19, 0x0c, 0xb9, 0x00, 0x44,
+	0x0e, 0x00, 0xc8, 0x0a, 0x0c, 0xb9, 0x04, 0x54,
+	0xce, 0x00, 0x04, 0x19, 0x0c, 0x5a, 0x00, 0x44,
+	0x82, 0x0d, 0x09, 0x2e, 0x80, 0x40, 0x09, 0xcf,
+	0x00, 0xdf, 0x71, 0x8b, 0x80, 0x01, 0x00, 0x80,
+	0x02, 0xc1, 0x00, 0x22, 0x03, 0xc1, 0x00, 0x22,
+	0x00, 0x01, 0x65, 0x80, 0xd2, 0x05, 0x65, 0x82,
+	0x40, 0x21, 0x00, 0x80, 0xd3, 0x03, 0x00, 0x82,
+	0x40, 0x33, 0x00, 0x80, 0x0c, 0x5a, 0x00, 0x44,
+	0x0f, 0x30, 0x61, 0x0a, 0x0d, 0x30, 0xc8, 0x0a,
+	0x08, 0xd9, 0x00, 0xc4, 0x93, 0x00, 0x01, 0x4f,
+	0xe7, 0x00, 0x01, 0x6f, 0x0f, 0x30, 0x61, 0x0a,
+	0x20, 0x00, 0x00, 0x88, 0x02, 0x00, 0x61, 0x02,
+	0x02, 0x00, 0x00, 0x03, 0xcf, 0x30, 0x00, 0x09,
+	0x20, 0x00, 0x09, 0x49, 0x00, 0x04, 0x63, 0x80,
+	0x04, 0xd9, 0x00, 0x44, 0x00, 0x04, 0xb1, 0x80,
+	0x00, 0x00, 0x00, 0x46, 0x02, 0x30, 0x61, 0x0a,
+	0x05, 0xb0, 0xa8, 0x18, 0xc2, 0x30, 0x04, 0x19,
+	0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0xc8, 0x0a,
+	0x0c, 0x0b, 0x04, 0x14, 0x0e, 0x10, 0x61, 0x0a,
+	0x04, 0x2b, 0x00, 0x44, 0x0c, 0x10, 0xc8, 0x0a,
+	0x04, 0x2b, 0x04, 0x54, 0x0c, 0x10, 0x61, 0x0a,
+	0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0xa8, 0x18,
+	0xa0, 0x00, 0x00, 0x88, 0x00, 0x01, 0x71, 0x82,
+	0x00, 0x00, 0x00, 0x46, 0x00, 0x04, 0x33, 0x80,
+	0x00, 0x00, 0x83, 0x80, 0x20, 0x04, 0x7a, 0x80,
+	0x20, 0x01, 0x33, 0x80, 0x00, 0x00, 0x83, 0x80,
+	0x20, 0x00, 0x7a, 0x80, 0x20, 0x03, 0x00, 0x80,
+	0x00, 0x00, 0x00, 0x46, 0x16, 0xce, 0x11, 0x00
+};
+
+static unsigned char ima_adpcm_capture[] = {
+	0x00, 0x10, 0x00, 0x44, 0x08, 0x00, 0x00, 0x44,
+	0x00, 0x70, 0x00, 0x44, 0x08, 0xd0, 0x00, 0x44,
+	0x00, 0xf0, 0x00, 0x44, 0x0d, 0xf2, 0x61, 0xa8,
+	0x44, 0x04, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45,
+	0x00, 0x04, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39,
+	0xff, 0x2e, 0x21, 0x49, 0xff, 0x0c, 0xd4, 0x49,
+	0x40, 0x49, 0x39, 0xac, 0x55, 0x55, 0x71, 0x8b,
+	0x50, 0x01, 0xb1, 0x80, 0x00, 0x00, 0x71, 0x8b,
+	0xc2, 0x30, 0x04, 0x19, 0xc0, 0xa0, 0x04, 0x19,
+	0xc2, 0xa0, 0x04, 0x19, 0x89, 0x00, 0x71, 0x8b,
+	0xc8, 0x30, 0x04, 0x19, 0x71, 0x00, 0x71, 0x8b,
+	0xcd, 0x00, 0x04, 0x19, 0xcf, 0x00, 0x04, 0x19,
+	0x80, 0x00, 0x71, 0x8b, 0xcb, 0x20, 0x04, 0x19,
+	0x20, 0x00, 0x71, 0x8b, 0xc4, 0x20, 0x04, 0x19,
+	0x47, 0x00, 0x51, 0x8b, 0xc0, 0x20, 0x00, 0x39,
+	0x00, 0x00, 0x63, 0x80, 0xc1, 0xa0, 0x04, 0x19,
+	0x93, 0x00, 0x01, 0x4f, 0xcd, 0x30, 0x00, 0x09,
+	0xcf, 0x30, 0x00, 0x09, 0x0c, 0x40, 0x00, 0x14,
+	0x00, 0x60, 0x00, 0x14, 0x00, 0x04, 0x61, 0xa8,
+	0x02, 0x04, 0x61, 0xa8, 0x0c, 0x60, 0x04, 0x24,
+	0x00, 0x00, 0x34, 0x49, 0x08, 0x50, 0x00, 0x44,
+	0x44, 0x04, 0x04, 0x39, 0x00, 0x00, 0x40, 0x45,
+	0x08, 0x30, 0x61, 0x0a, 0x05, 0xb0, 0xe8, 0x18,
+	0x0c, 0xc0, 0x04, 0x54, 0xc8, 0x30, 0x04, 0x19,
+	0x09, 0x04, 0x00, 0xa8, 0x0b, 0x04, 0x00, 0xa8,
+	0x00, 0x00, 0x40, 0x45, 0x09, 0x04, 0x61, 0xa8,
+	0xc1, 0x00, 0x04, 0x19, 0x0b, 0x04, 0x61, 0xa8,
+	0xca, 0x00, 0x04, 0x19, 0x0d, 0x00, 0x61, 0x0a,
+	0x00, 0x01, 0x00, 0x45, 0x0f, 0x00, 0x61, 0x0a,
+	0x00, 0x40, 0x09, 0x8f, 0x00, 0x01, 0x00, 0x45,
+	0x40, 0x40, 0x09, 0xef, 0xff, 0x20, 0x09, 0xcf,
+	0x00, 0x04, 0x63, 0xa1, 0x50, 0x03, 0x33, 0x80,
+	0x00, 0x04, 0xa3, 0x80, 0x00, 0xff, 0xc2, 0x8b,
+	0x0c, 0x12, 0x04, 0x54, 0x08, 0x12, 0x00, 0xc4,
+	0x20, 0x03, 0x80, 0xc0, 0x30, 0x00, 0x00, 0x88,
+	0x00, 0x00, 0x7a, 0x0a, 0xd0, 0x01, 0x00, 0x82,
+	0x04, 0x50, 0x00, 0x44, 0xc0, 0x00, 0x00, 0x99,
+	0x04, 0x50, 0x00, 0x44, 0x00, 0xff, 0xc2, 0x8b,
+	0x20, 0x00, 0x00, 0x80, 0x00, 0x0d, 0x42, 0x8b,
+	0x04, 0x42, 0x00, 0xc4, 0x00, 0x0e, 0x42, 0x8b,
+	0x08, 0x52, 0x00, 0xc4, 0x00, 0x1e, 0x42, 0x8b,
+	0x00, 0xe2, 0x00, 0xc4, 0x00, 0x8e, 0x42, 0x8b,
+	0x08, 0xd2, 0x00, 0xc4, 0x00, 0x9e, 0x42, 0x8b,
+	0x04, 0xf2, 0x00, 0xc4, 0x00, 0xbe, 0x42, 0x8b,
+	0x04, 0xf2, 0x00, 0xc4, 0x00, 0x04, 0x42, 0x8b,
+	0x04, 0x11, 0x00, 0xc4, 0x00, 0x24, 0x42, 0x8b,
+	0x0c, 0x61, 0x00, 0xc4, 0x00, 0x55, 0x42, 0x8b,
+	0x04, 0x50, 0x00, 0xc4, 0x00, 0x3f, 0x42, 0x8b,
+	0x0c, 0x01, 0x00, 0xc4, 0x00, 0x00, 0x40, 0x45,
+	0x20, 0x01, 0x79, 0x80, 0x00, 0x30, 0x42, 0x8b,
+	0x04, 0x62, 0x00, 0xc4, 0x00, 0x00, 0x40, 0x45,
+	0x00, 0x00, 0x71, 0x8b, 0x40, 0x01, 0x00, 0x80,
+	0x04, 0x50, 0x00, 0x44, 0xff, 0x00, 0xe2, 0xab,
+	0x08, 0xc2, 0x00, 0xc4, 0x0f, 0xf2, 0xa8, 0xa8,
+	0x20, 0x00, 0xb1, 0x88, 0x00, 0x00, 0x41, 0x02,
+	0x4d, 0xf2, 0x00, 0x39, 0xc0, 0x01, 0x00, 0x82,
+	0x04, 0x50, 0x00, 0x44, 0x0d, 0xf2, 0xa3, 0xa8,
+	0x4d, 0xf2, 0x00, 0x39, 0x04, 0x50, 0x00, 0x44,
+	0xff, 0x00, 0xe2, 0xab, 0x20, 0x00, 0x00, 0x88,
+	0x00, 0x00, 0x61, 0x02, 0x4d, 0xf2, 0x04, 0x19,
+	0x04, 0x50, 0x00, 0x44, 0xff, 0x00, 0xe2, 0xab,
+	0xa0, 0x00, 0x00, 0x88, 0x00, 0x00, 0x61, 0x10,
+	0x4d, 0xf2, 0x04, 0x19, 0x04, 0x50, 0x00, 0x44,
+	0xff, 0x20, 0xe2, 0xab, 0x60, 0x00, 0x00, 0x88,
+	0x00, 0x00, 0x71, 0xc0, 0x4d, 0xf2, 0x04, 0x19,
+	0x04, 0x50, 0x00, 0x44, 0x00, 0x00, 0x7a, 0x0a,
+	0x20, 0x01, 0xf0, 0x80, 0x01, 0xa0, 0x41, 0x0a,
+	0x00, 0x11, 0x00, 0xc4, 0x20, 0x01, 0xf0, 0x80,
+	0xc1, 0x30, 0x04, 0x19, 0x04, 0x50, 0x00, 0x44,
+	0x00, 0x00, 0x79, 0x80, 0x0c, 0x41, 0x00, 0x84,
+	0x89, 0x00, 0x71, 0x8b, 0xc8, 0x30, 0x04, 0x19,
+	0x97, 0x00, 0x71, 0x8b, 0xcd, 0x00, 0x00, 0x39,
+	0x00, 0x01, 0xb1, 0x80, 0x80, 0x00, 0x04, 0x19,
+	0x82, 0x00, 0x04, 0x19, 0xc1, 0x20, 0x04, 0x19,
+	0xc3, 0x20, 0x04, 0x19, 0xc2, 0x30, 0x04, 0x19,
+	0xcd, 0x10, 0x04, 0x19, 0xcf, 0x10, 0x04, 0x19,
+	0xb0, 0x00, 0x71, 0x8b, 0x84, 0x00, 0x04, 0x19,
+	0x86, 0x00, 0x04, 0x19, 0x80, 0x00, 0x71, 0x8b,
+	0xcb, 0x20, 0x04, 0x19, 0x93, 0x00, 0x01, 0x4f,
+	0xcd, 0x30, 0x00, 0x09, 0xcf, 0x30, 0x00, 0x09,
+	0x03, 0x02, 0x04, 0x49, 0x08, 0x41, 0x00, 0x14,
+	0x04, 0x50, 0x00, 0x44, 0x00, 0x00, 0x63, 0x80,
+	0x00, 0x00, 0x06, 0x19, 0x03, 0x00, 0x04, 0x49,
+	0x04, 0x50, 0x00, 0x44, 0x20, 0x01, 0x63, 0x80,
+	0x00, 0x00, 0x06, 0x19, 0x00, 0x20, 0xe2, 0x8b,
+	0x00, 0xc1, 0x00, 0x84, 0x47, 0x00, 0x51, 0x8b,
+	0xc0, 0x20, 0x00, 0x39, 0x00, 0x00, 0x63, 0x80,
+	0xc1, 0xa0, 0x04, 0x19, 0x00, 0xe1, 0x00, 0x44,
+	0xbd, 0x00, 0x51, 0x8b, 0xc0, 0x20, 0x00, 0x39,
+	0x00, 0x00, 0xb1, 0x80, 0xc1, 0xa0, 0x04, 0x19,
+	0x03, 0x00, 0x04, 0x49, 0x04, 0x50, 0x00, 0x44,
+	0x00, 0x20, 0x61, 0x0a, 0x00, 0x01, 0x00, 0x45,
+	0x02, 0x30, 0x61, 0x0a, 0x0c, 0x83, 0x00, 0xc4,
+	0x0c, 0x78, 0x08, 0x44, 0x04, 0x5a, 0x08, 0x44,
+	0xb2, 0x00, 0x09, 0x4f, 0x10, 0x42, 0x09, 0x8e,
+	0x05, 0xb0, 0xe0, 0x18, 0x04, 0x23, 0x00, 0x84,
+	0x0c, 0x01, 0x00, 0x11, 0x08, 0x05, 0x61, 0x10,
+	0x00, 0x49, 0x08, 0x44, 0x00, 0x48, 0x08, 0x44,
+	0xb2, 0x00, 0x09, 0x4f, 0x80, 0x00, 0x71, 0x8b,
+	0xc0, 0x00, 0x00, 0x82, 0x0c, 0x01, 0x33, 0x10,
+	0x28, 0x01, 0xa3, 0x10, 0x00, 0x01, 0x7a, 0x80,
+	0x8c, 0x01, 0x00, 0x80, 0x02, 0x30, 0x61, 0x0a,
+	0x20, 0x00, 0x04, 0x19, 0x0c, 0x83, 0x00, 0xc4,
+	0x05, 0xb0, 0xc8, 0x18, 0x08, 0x43, 0x00, 0xc4,
+	0x01, 0x30, 0xc8, 0x0a, 0x0c, 0x38, 0x00, 0xc4,
+	0x08, 0x88, 0x00, 0x44, 0x0c, 0x78, 0x08, 0x44,
+	0x04, 0x5a, 0x08, 0x44, 0x00, 0x00, 0xa3, 0x18,
+	0x80, 0x00, 0x04, 0x19, 0x0b, 0x04, 0x61, 0xa8,
+	0xc3, 0x20, 0x00, 0x39, 0xc3, 0x30, 0x04, 0x19,
+	0x0f, 0x10, 0x61, 0x0a, 0xca, 0x30, 0x04, 0x19,
+	0x09, 0x04, 0x41, 0xa8, 0xe1, 0x20, 0x00, 0x39,
+	0xd1, 0x00, 0x09, 0x4f, 0x00, 0x04, 0x61, 0x02,
+	0x08, 0x63, 0x00, 0x44, 0x03, 0x30, 0x41, 0x0a,
+	0x20, 0x00, 0x00, 0x39, 0xa3, 0x00, 0x09, 0x4f,
+	0x00, 0x04, 0x61, 0x02, 0x00, 0x48, 0x08, 0x44,
+	0x08, 0x88, 0x00, 0x44, 0x02, 0x30, 0x61, 0x0a,
+	0x00, 0x08, 0x00, 0xc4, 0x0c, 0x78, 0x08, 0x44,
+	0x04, 0x5a, 0x08, 0x44, 0xb2, 0x00, 0x09, 0x0f,
+	0x10, 0x40, 0x09, 0x8e, 0x00, 0x00, 0x68, 0x5b,
+	0x20, 0x04, 0xb1, 0x80, 0x02, 0x00, 0x61, 0x5b,
+	0x88, 0x03, 0x7a, 0x80, 0xac, 0x01, 0x00, 0x80,
+	0x05, 0xb0, 0xe0, 0x18, 0x00, 0xd3, 0x00, 0x84,
+	0x00, 0x49, 0x08, 0x44, 0x00, 0x48, 0x08, 0x44,
+	0xb2, 0x00, 0x09, 0x0f, 0x80, 0x00, 0x71, 0x8b,
+	0xc0, 0x00, 0x00, 0x82, 0x02, 0x30, 0x61, 0x0a,
+	0x00, 0x08, 0x00, 0xc4, 0x05, 0xb0, 0xc8, 0x18,
+	0x0c, 0x18, 0x00, 0xc4, 0x01, 0x30, 0xc8, 0x0a,
+	0x0c, 0x38, 0x00, 0xc4, 0x08, 0x88, 0x00, 0x44,
+	0x0c, 0x78, 0x08, 0x44, 0x00, 0x00, 0x61, 0x18,
+	0x20, 0x05, 0xb1, 0x80, 0x00, 0x00, 0x68, 0xcb,
+	0x80, 0x00, 0x04, 0x19, 0x0d, 0x10, 0x61, 0x0a,
+	0xc3, 0x30, 0x04, 0x19, 0x0b, 0x04, 0x41, 0xa8,
+	0x09, 0x04, 0x41, 0xa8, 0xe1, 0x20, 0x00, 0x39,
+	0x08, 0x38, 0x00, 0x44, 0x03, 0x30, 0x41, 0x0a,
+	0x20, 0x04, 0xb1, 0x80, 0x00, 0x48, 0x08, 0x44,
+	0x08, 0x88, 0x00, 0x44, 0x00, 0x00, 0xb1, 0x80,
+	0xc2, 0x30, 0x04, 0x19, 0x0c, 0xb8, 0x00, 0xd4,
+	0x0f, 0x30, 0x61, 0x0a, 0x0d, 0x30, 0xc8, 0x0a,
+	0x0c, 0xb8, 0x00, 0xc4, 0x93, 0x00, 0x01, 0x4f,
+	0xe7, 0x00, 0x01, 0x6f, 0x0f, 0x30, 0x61, 0x0a,
+	0x20, 0x00, 0x00, 0x88, 0x02, 0x00, 0x61, 0x02,
+	0x41, 0x04, 0x04, 0x19, 0x02, 0x04, 0x61, 0x02,
+	0x43, 0x04, 0x04, 0x39, 0xcf, 0x30, 0x00, 0x09,
+	0x20, 0x00, 0x09, 0x49, 0x00, 0x59, 0x00, 0x44,
+	0x93, 0x00, 0x01, 0x4f, 0xe7, 0x00, 0x01, 0x6f,
+	0x0d, 0x30, 0x61, 0x0a, 0x20, 0x00, 0x61, 0x88,
+	0xc2, 0x00, 0x00, 0x82, 0xc2, 0x03, 0x00, 0x82,
+	0xcd, 0x30, 0x00, 0x09, 0x20, 0x00, 0x09, 0x49,
+	0x0f, 0x30, 0x61, 0x0a, 0x0d, 0x30, 0xc8, 0x0a,
+	0x0c, 0x58, 0x00, 0x84, 0x02, 0x30, 0x61, 0x0a,
+	0x05, 0xb0, 0xa8, 0x18, 0xc2, 0x30, 0x04, 0x19,
+	0x00, 0x00, 0x00, 0x46, 0x90, 0x40, 0x09, 0x8f,
+	0x12, 0x04, 0x09, 0x6e, 0x03, 0x00, 0x09, 0x0e,
+	0x00, 0x01, 0x71, 0x82, 0x20, 0x01, 0x00, 0x80,
+	0x00, 0x00, 0x61, 0xcb, 0x80, 0x04, 0xb1, 0x80,
+	0x00, 0x01, 0xe0, 0x60, 0x0c, 0xd8, 0x04, 0x14,
+	0x00, 0x01, 0xeb, 0x80, 0x40, 0x00, 0x52, 0x1b,
+	0x80, 0x00, 0x79, 0x80, 0xc0, 0x01, 0x71, 0xc2,
+	0x20, 0x00, 0xc0, 0x80, 0x08, 0x0a, 0x04, 0x54,
+	0xc0, 0x04, 0xa8, 0x82, 0x80, 0x00, 0x72, 0x1b,
+	0x80, 0x00, 0x00, 0x80, 0x00, 0x01, 0xf0, 0x80,
+	0x20, 0x00, 0xc0, 0x80, 0x0c, 0x2a, 0x04, 0x54,
+	0xc0, 0x04, 0xa8, 0x82, 0x10, 0x00, 0x72, 0x1b,
+	0x80, 0x00, 0x00, 0x80, 0x00, 0x01, 0xf0, 0x80,
+	0x20, 0x00, 0xc0, 0x80, 0x08, 0x3a, 0x04, 0x54,
+	0xc0, 0x04, 0xa8, 0x82, 0x20, 0x00, 0x72, 0x1b,
+	0x80, 0x00, 0x00, 0x80, 0xc0, 0x03, 0xf0, 0x82,
+	0x20, 0x00, 0xa0, 0x80, 0x00, 0x01, 0x00, 0x11,
+	0x40, 0x00, 0xc2, 0x8b, 0x00, 0xaa, 0x00, 0xc4,
+	0x00, 0x00, 0xe9, 0x80, 0x05, 0xb0, 0xa8, 0x18,
+	0x00, 0x01, 0xa8, 0x22, 0xd0, 0x01, 0x00, 0x82,
+	0xf0, 0x00, 0xe2, 0x1b, 0x06, 0x20, 0xa8, 0x0a,
+	0x2d, 0x10, 0x61, 0x0a, 0xd1, 0x00, 0x09, 0x2e,
+	0x00, 0x01, 0xa8, 0x02, 0x0e, 0x10, 0xc8, 0x0a,
+	0x0c, 0xba, 0x04, 0x14, 0x0e, 0x10, 0x61, 0x0a,
+	0x04, 0x4a, 0x00, 0x44, 0x0c, 0x10, 0xc8, 0x0a,
+	0x04, 0x4a, 0x04, 0x54, 0x0c, 0x10, 0x61, 0x0a,
+	0xd0, 0x01, 0x00, 0x82, 0x00, 0x10, 0xa8, 0x18,
+	0xa0, 0x00, 0x00, 0x88, 0x00, 0x01, 0x71, 0x82,
+	0x03, 0x00, 0x09, 0x0e, 0x9a, 0x01, 0x00, 0x60,
+	0x32, 0x00, 0x09, 0x2e, 0x00, 0x00, 0x00, 0x46,
+	0x00, 0x01, 0x71, 0x82, 0x20, 0x01, 0x00, 0x80,
+	0x00, 0x00, 0x61, 0xcb, 0x80, 0x24, 0xb1, 0xc0,
+	0x00, 0x31, 0xe0, 0x60, 0x0c, 0xca, 0x04, 0x14,
+	0x00, 0x01, 0xeb, 0x80, 0x40, 0x00, 0x52, 0x1b,
+	0x80, 0x00, 0x79, 0x80, 0xc0, 0x01, 0x71, 0xc2,
+	0x20, 0x00, 0xc0, 0x80, 0x08, 0xda, 0x04, 0x54,
+	0xc0, 0x04, 0xa8, 0x82, 0x80, 0x00, 0x72, 0x1b,
+	0x80, 0x00, 0x00, 0x80, 0x00, 0x01, 0xf0, 0x80,
+	0x20, 0x00, 0xc0, 0x80, 0x0c, 0xfa, 0x04, 0x54,
+	0xc0, 0x04, 0xa8, 0x82, 0x10, 0x00, 0x72, 0x1b,
+	0x80, 0x00, 0x00, 0x80, 0x00, 0x01, 0xf0, 0x80,
+	0x20, 0x00, 0xc0, 0x80, 0x08, 0x29, 0x04, 0x54,
+	0xc0, 0x04, 0xa8, 0x82, 0x20, 0x00, 0x72, 0x1b,
+	0x80, 0x00, 0x00, 0x80, 0xc0, 0x03, 0xf0, 0x82,
+	0x20, 0x00, 0xa0, 0x80, 0x00, 0x01, 0x00, 0x11,
+	0x40, 0x00, 0xc2, 0x8b, 0x00, 0x39, 0x00, 0xc4,
+	0x00, 0x00, 0xe9, 0x80, 0x05, 0xb0, 0xa8, 0x18,
+	0x00, 0x01, 0xa8, 0x22, 0xd0, 0x01, 0x00, 0x82,
+	0xb0, 0x00, 0xe2, 0x1b, 0x06, 0x20, 0xa8, 0x0a,
+	0x2f, 0x10, 0x61, 0x0a, 0xf1, 0x00, 0x09, 0x2e,
+	0x00, 0x01, 0xa8, 0x02, 0x0e, 0x10, 0xc8, 0x0a,
+	0x0c, 0xa9, 0x04, 0x14, 0x0e, 0x10, 0x61, 0x0a,
+	0x04, 0x99, 0x00, 0x44, 0x0c, 0x10, 0xc8, 0x0a,
+	0x04, 0x99, 0x04, 0x54, 0x0c, 0x10, 0x61, 0x0a,
+	0xd0, 0x01, 0x00, 0x82, 0x00, 0x10, 0xa8, 0x18,
+	0xa0, 0x00, 0x00, 0x88, 0x00, 0x01, 0x71, 0x82,
+	0x9f, 0x01, 0x00, 0x60, 0x00, 0x00, 0x00, 0x46,
+	0x00, 0x00, 0x33, 0x80, 0x00, 0x00, 0x83, 0x80,
+	0x20, 0x00, 0x7a, 0x80, 0x20, 0x07, 0x33, 0x80,
+	0x00, 0x00, 0x83, 0x80, 0x20, 0x04, 0x7a, 0x80,
+	0x20, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x46,
+	0x02, 0x00, 0x61, 0x0a, 0x04, 0x1b, 0x04, 0x14,
+	0x01, 0x00, 0x61, 0x0a, 0x03, 0x00, 0x48, 0x0a,
+	0x0c, 0x79, 0x04, 0x54, 0xc3, 0x00, 0x04, 0x19,
+	0x04, 0xc9, 0x00, 0x44, 0x08, 0x00, 0xc8, 0x0a,
+	0x04, 0xc9, 0x04, 0x54, 0xc8, 0x00, 0x04, 0x19,
+	0x0a, 0x00, 0x61, 0x0a, 0x09, 0x00, 0x48, 0x0a,
+	0x0c, 0xe9, 0x04, 0x54, 0xc9, 0x00, 0x04, 0x19,
+	0x04, 0xd9, 0x00, 0x44, 0x0b, 0x00, 0xc8, 0x0a,
+	0x04, 0xd9, 0x04, 0x54, 0xcb, 0x00, 0x04, 0x19,
+	0x04, 0x00, 0x61, 0x0a, 0x06, 0x00, 0x48, 0x0a,
+	0x0c, 0xf9, 0x04, 0x54, 0xc6, 0x00, 0x04, 0x19,
+	0x04, 0x0b, 0x00, 0x44, 0x05, 0x00, 0xc8, 0x0a,
+	0x04, 0x0b, 0x04, 0x54, 0xc5, 0x00, 0x04, 0x19,
+	0x07, 0x00, 0x61, 0x0a, 0x0c, 0x00, 0x48, 0x0a,
+	0x0c, 0x2b, 0x04, 0x54, 0xcc, 0x00, 0x04, 0x19,
+	0x04, 0x1b, 0x00, 0x44, 0x0e, 0x00, 0xc8, 0x0a,
+	0x04, 0x1b, 0x04, 0x54, 0xce, 0x00, 0x04, 0x19,
+	0x00, 0x00, 0x40, 0x45, 0x92, 0x20, 0x71, 0x8b,
+	0xa6, 0xc5, 0x11, 0x00
+};
diff --git a/sound/isa/sb/sb16_main.c b/sound/isa/sb/sb16_main.c
new file mode 100644
index 0000000..a6a0fa5
--- /dev/null
+++ b/sound/isa/sb/sb16_main.c
@@ -0,0 +1,916 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  Routines for control of 16-bit SoundBlaster cards and clones
+ *  Note: This is very ugly hardware which uses one 8-bit DMA channel and
+ *        second 16-bit DMA channel. Unfortunately 8-bit DMA channel can't
+ *        transfer 16-bit samples and 16-bit DMA channels can't transfer
+ *        8-bit samples. This make full duplex more complicated than
+ *        can be... People, don't buy these soundcards for full 16-bit
+ *        duplex!!!
+ *  Note: 16-bit wide is assigned to first direction which made request.
+ *        With full duplex - playback is preferred with abstract layer.
+ *
+ *  Note: Some chip revisions have hardware bug. Changing capture
+ *        channel from full-duplex 8bit DMA to 16bit DMA will block
+ *        16bit DMA transfers from DSP chip (capture) until 8bit transfer
+ *        to DSP chip (playback) starts. This bug can be avoided with
+ *        "16bit DMA Allocation" setting set to Playback or Capture.
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/sb16_csp.h>
+#include <sound/mpu401.h>
+#include <sound/control.h>
+#include <sound/info.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Routines for control of 16-bit SoundBlaster cards and clones");
+MODULE_LICENSE("GPL");
+
+#ifdef CONFIG_SND_SB16_CSP
+static void snd_sb16_csp_playback_prepare(sb_t *chip, snd_pcm_runtime_t *runtime)
+{
+	if (chip->hardware == SB_HW_16CSP) {
+		snd_sb_csp_t *csp = chip->csp;
+
+		if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
+			/* manually loaded codec */
+			if ((csp->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) &&
+			    ((1U << runtime->format) == csp->acc_format)) {
+				/* Supported runtime PCM format for playback */
+				if (csp->ops.csp_use(csp) == 0) {
+					/* If CSP was successfully acquired */
+					goto __start_CSP;
+				}
+			} else if ((csp->mode & SNDRV_SB_CSP_MODE_QSOUND) && (csp->q_enabled)) {
+				/* QSound decoder is loaded and enabled */
+				if ((1 << runtime->format) & (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
+							      SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE)) {
+					/* Only for simple PCM formats */
+					if (csp->ops.csp_use(csp) == 0) {
+						/* If CSP was successfully acquired */
+						goto __start_CSP;
+					}
+				}
+			}
+		} else if (csp->ops.csp_use(csp) == 0) {
+			/* Acquire CSP and try to autoload hardware codec */
+			if (csp->ops.csp_autoload(csp, runtime->format, SNDRV_SB_CSP_MODE_DSP_WRITE)) {
+				/* Unsupported format, release CSP */
+				csp->ops.csp_unuse(csp);
+			} else {
+		      __start_CSP:
+				/* Try to start CSP */
+				if (csp->ops.csp_start(csp, (chip->mode & SB_MODE_PLAYBACK_16) ?
+						       SNDRV_SB_CSP_SAMPLE_16BIT : SNDRV_SB_CSP_SAMPLE_8BIT,
+						       (runtime->channels > 1) ?
+						       SNDRV_SB_CSP_STEREO : SNDRV_SB_CSP_MONO)) {
+					/* Failed, release CSP */
+					csp->ops.csp_unuse(csp);
+				} else {
+					/* Success, CSP acquired and running */
+					chip->open = SNDRV_SB_CSP_MODE_DSP_WRITE;
+				}
+			}
+		}
+	}
+}
+
+static void snd_sb16_csp_capture_prepare(sb_t *chip, snd_pcm_runtime_t *runtime)
+{
+	if (chip->hardware == SB_HW_16CSP) {
+		snd_sb_csp_t *csp = chip->csp;
+
+		if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
+			/* manually loaded codec */
+			if ((csp->mode & SNDRV_SB_CSP_MODE_DSP_READ) &&
+			    ((1U << runtime->format) == csp->acc_format)) {
+				/* Supported runtime PCM format for capture */
+				if (csp->ops.csp_use(csp) == 0) {
+					/* If CSP was successfully acquired */
+					goto __start_CSP;
+				}
+			}
+		} else if (csp->ops.csp_use(csp) == 0) {
+			/* Acquire CSP and try to autoload hardware codec */
+			if (csp->ops.csp_autoload(csp, runtime->format, SNDRV_SB_CSP_MODE_DSP_READ)) {
+				/* Unsupported format, release CSP */
+				csp->ops.csp_unuse(csp);
+			} else {
+		      __start_CSP:
+				/* Try to start CSP */
+				if (csp->ops.csp_start(csp, (chip->mode & SB_MODE_CAPTURE_16) ?
+						       SNDRV_SB_CSP_SAMPLE_16BIT : SNDRV_SB_CSP_SAMPLE_8BIT,
+						       (runtime->channels > 1) ?
+						       SNDRV_SB_CSP_STEREO : SNDRV_SB_CSP_MONO)) {
+					/* Failed, release CSP */
+					csp->ops.csp_unuse(csp);
+				} else {
+					/* Success, CSP acquired and running */
+					chip->open = SNDRV_SB_CSP_MODE_DSP_READ;
+				}
+			}
+		}
+	}
+}
+
+static void snd_sb16_csp_update(sb_t *chip)
+{
+	if (chip->hardware == SB_HW_16CSP) {
+		snd_sb_csp_t *csp = chip->csp;
+
+		if (csp->qpos_changed) {
+			spin_lock(&chip->reg_lock);
+			csp->ops.csp_qsound_transfer (csp);
+			spin_unlock(&chip->reg_lock);
+		}
+	}
+}
+
+static void snd_sb16_csp_playback_open(sb_t *chip, snd_pcm_runtime_t *runtime)
+{
+	/* CSP decoders (QSound excluded) support only 16bit transfers */
+	if (chip->hardware == SB_HW_16CSP) {
+		snd_sb_csp_t *csp = chip->csp;
+
+		if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
+			/* manually loaded codec */
+			if (csp->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) {
+				runtime->hw.formats |= csp->acc_format;
+			}
+		} else {
+			/* autoloaded codecs */
+			runtime->hw.formats |= SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
+					       SNDRV_PCM_FMTBIT_IMA_ADPCM;
+		}
+	}
+}
+
+static void snd_sb16_csp_playback_close(sb_t *chip)
+{
+	if ((chip->hardware == SB_HW_16CSP) && (chip->open == SNDRV_SB_CSP_MODE_DSP_WRITE)) {
+		snd_sb_csp_t *csp = chip->csp;
+
+		if (csp->ops.csp_stop(csp) == 0) {
+			csp->ops.csp_unuse(csp);
+			chip->open = 0;
+		}
+	}
+}
+
+static void snd_sb16_csp_capture_open(sb_t *chip, snd_pcm_runtime_t *runtime)
+{
+	/* CSP coders support only 16bit transfers */
+	if (chip->hardware == SB_HW_16CSP) {
+		snd_sb_csp_t *csp = chip->csp;
+
+		if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
+			/* manually loaded codec */
+			if (csp->mode & SNDRV_SB_CSP_MODE_DSP_READ) {
+				runtime->hw.formats |= csp->acc_format;
+			}
+		} else {
+			/* autoloaded codecs */
+			runtime->hw.formats |= SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
+					       SNDRV_PCM_FMTBIT_IMA_ADPCM;
+		}
+	}
+}
+
+static void snd_sb16_csp_capture_close(sb_t *chip)
+{
+	if ((chip->hardware == SB_HW_16CSP) && (chip->open == SNDRV_SB_CSP_MODE_DSP_READ)) {
+		snd_sb_csp_t *csp = chip->csp;
+
+		if (csp->ops.csp_stop(csp) == 0) {
+			csp->ops.csp_unuse(csp);
+			chip->open = 0;
+		}
+	}
+}
+#else
+#define snd_sb16_csp_playback_prepare(chip, runtime)	/*nop*/
+#define snd_sb16_csp_capture_prepare(chip, runtime)	/*nop*/
+#define snd_sb16_csp_update(chip)			/*nop*/
+#define snd_sb16_csp_playback_open(chip, runtime)	/*nop*/
+#define snd_sb16_csp_playback_close(chip)		/*nop*/
+#define snd_sb16_csp_capture_open(chip, runtime)	/*nop*/
+#define snd_sb16_csp_capture_close(chip)      	 	/*nop*/
+#endif
+
+
+static void snd_sb16_setup_rate(sb_t *chip,
+				unsigned short rate,
+				int channel)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->mode & (channel == SNDRV_PCM_STREAM_PLAYBACK ? SB_MODE_PLAYBACK_16 : SB_MODE_CAPTURE_16))
+		snd_sb_ack_16bit(chip);
+	else
+		snd_sb_ack_8bit(chip);
+	if (!(chip->mode & SB_RATE_LOCK)) {
+		chip->locked_rate = rate;
+		snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE_IN);
+		snd_sbdsp_command(chip, rate >> 8);
+		snd_sbdsp_command(chip, rate & 0xff);
+		snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE_OUT);
+		snd_sbdsp_command(chip, rate >> 8);
+		snd_sbdsp_command(chip, rate & 0xff);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static int snd_sb16_hw_params(snd_pcm_substream_t * substream,
+			      snd_pcm_hw_params_t * hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_sb16_hw_free(snd_pcm_substream_t * substream)
+{
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int snd_sb16_playback_prepare(snd_pcm_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned char format;
+	unsigned int size, count, dma;
+
+	snd_sb16_csp_playback_prepare(chip, runtime);
+	if (snd_pcm_format_unsigned(runtime->format) > 0) {
+		format = runtime->channels > 1 ? SB_DSP4_MODE_UNS_STEREO : SB_DSP4_MODE_UNS_MONO;
+	} else {
+		format = runtime->channels > 1 ? SB_DSP4_MODE_SIGN_STEREO : SB_DSP4_MODE_SIGN_MONO;
+	}
+
+	snd_sb16_setup_rate(chip, runtime->rate, SNDRV_PCM_STREAM_PLAYBACK);
+	size = chip->p_dma_size = snd_pcm_lib_buffer_bytes(substream);
+	dma = (chip->mode & SB_MODE_PLAYBACK_8) ? chip->dma8 : chip->dma16;
+	snd_dma_program(dma, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
+
+	count = snd_pcm_lib_period_bytes(substream);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->mode & SB_MODE_PLAYBACK_16) {
+		count >>= 1;
+		count--;
+		snd_sbdsp_command(chip, SB_DSP4_OUT16_AI);
+		snd_sbdsp_command(chip, format);
+		snd_sbdsp_command(chip, count & 0xff);
+		snd_sbdsp_command(chip, count >> 8);
+		snd_sbdsp_command(chip, SB_DSP_DMA16_OFF);
+	} else {
+		count--;
+		snd_sbdsp_command(chip, SB_DSP4_OUT8_AI);
+		snd_sbdsp_command(chip, format);
+		snd_sbdsp_command(chip, count & 0xff);
+		snd_sbdsp_command(chip, count >> 8);
+		snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_sb16_playback_trigger(snd_pcm_substream_t * substream,
+				     int cmd)
+{
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	int result = 0;
+
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		chip->mode |= SB_RATE_LOCK_PLAYBACK;
+		snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_OFF : SB_DSP_DMA8_OFF);
+		/* next two lines are needed for some types of DSP4 (SB AWE 32 - 4.13) */
+		if (chip->mode & SB_RATE_LOCK_CAPTURE)
+			snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
+		chip->mode &= ~SB_RATE_LOCK_PLAYBACK;
+		break;
+	default:
+		result = -EINVAL;
+	}
+	spin_unlock(&chip->reg_lock);
+	return result;
+}
+
+static int snd_sb16_capture_prepare(snd_pcm_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned char format;
+	unsigned int size, count, dma;
+
+	snd_sb16_csp_capture_prepare(chip, runtime);
+	if (snd_pcm_format_unsigned(runtime->format) > 0) {
+		format = runtime->channels > 1 ? SB_DSP4_MODE_UNS_STEREO : SB_DSP4_MODE_UNS_MONO;
+	} else {
+		format = runtime->channels > 1 ? SB_DSP4_MODE_SIGN_STEREO : SB_DSP4_MODE_SIGN_MONO;
+	}
+	snd_sb16_setup_rate(chip, runtime->rate, SNDRV_PCM_STREAM_CAPTURE);
+	size = chip->c_dma_size = snd_pcm_lib_buffer_bytes(substream);
+	dma = (chip->mode & SB_MODE_CAPTURE_8) ? chip->dma8 : chip->dma16;
+	snd_dma_program(dma, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
+
+	count = snd_pcm_lib_period_bytes(substream);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->mode & SB_MODE_CAPTURE_16) {
+		count >>= 1;
+		count--;
+		snd_sbdsp_command(chip, SB_DSP4_IN16_AI);
+		snd_sbdsp_command(chip, format);
+		snd_sbdsp_command(chip, count & 0xff);
+		snd_sbdsp_command(chip, count >> 8);
+		snd_sbdsp_command(chip, SB_DSP_DMA16_OFF);
+	} else {
+		count--;
+		snd_sbdsp_command(chip, SB_DSP4_IN8_AI);
+		snd_sbdsp_command(chip, format);
+		snd_sbdsp_command(chip, count & 0xff);
+		snd_sbdsp_command(chip, count >> 8);
+		snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_sb16_capture_trigger(snd_pcm_substream_t * substream,
+				    int cmd)
+{
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	int result = 0;
+
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		chip->mode |= SB_RATE_LOCK_CAPTURE;
+		snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_OFF : SB_DSP_DMA8_OFF);
+		/* next two lines are needed for some types of DSP4 (SB AWE 32 - 4.13) */
+		if (chip->mode & SB_RATE_LOCK_PLAYBACK)
+			snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
+		chip->mode &= ~SB_RATE_LOCK_CAPTURE;
+		break;
+	default:
+		result = -EINVAL;
+	}
+	spin_unlock(&chip->reg_lock);
+	return result;
+}
+
+irqreturn_t snd_sb16dsp_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	sb_t *chip = dev_id;
+	unsigned char status;
+	int ok;
+
+	spin_lock(&chip->mixer_lock);
+	status = snd_sbmixer_read(chip, SB_DSP4_IRQSTATUS);
+	spin_unlock(&chip->mixer_lock);
+	if ((status & SB_IRQTYPE_MPUIN) && chip->rmidi_callback)
+		chip->rmidi_callback(irq, chip->rmidi->private_data, regs);
+	if (status & SB_IRQTYPE_8BIT) {
+		ok = 0;
+		if (chip->mode & SB_MODE_PLAYBACK_8) {
+			snd_pcm_period_elapsed(chip->playback_substream);
+			snd_sb16_csp_update(chip);
+			ok++;
+		}
+		if (chip->mode & SB_MODE_CAPTURE_8) {
+			snd_pcm_period_elapsed(chip->capture_substream);
+			ok++;
+		}
+		spin_lock(&chip->reg_lock);
+		if (!ok)
+			snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+		snd_sb_ack_8bit(chip);
+		spin_unlock(&chip->reg_lock);
+	}
+	if (status & SB_IRQTYPE_16BIT) {
+		ok = 0;
+		if (chip->mode & SB_MODE_PLAYBACK_16) {
+			snd_pcm_period_elapsed(chip->playback_substream);
+			snd_sb16_csp_update(chip);
+			ok++;
+		}
+		if (chip->mode & SB_MODE_CAPTURE_16) {
+			snd_pcm_period_elapsed(chip->capture_substream);
+			ok++;
+		}
+		spin_lock(&chip->reg_lock);
+		if (!ok)
+			snd_sbdsp_command(chip, SB_DSP_DMA16_OFF);
+		snd_sb_ack_16bit(chip);
+		spin_unlock(&chip->reg_lock);
+	}
+	return IRQ_HANDLED;
+}
+
+/*
+
+ */
+
+static snd_pcm_uframes_t snd_sb16_playback_pointer(snd_pcm_substream_t * substream)
+{
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	unsigned int dma;
+	size_t ptr;
+
+	dma = (chip->mode & SB_MODE_PLAYBACK_8) ? chip->dma8 : chip->dma16;
+	ptr = snd_dma_pointer(dma, chip->p_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_sb16_capture_pointer(snd_pcm_substream_t * substream)
+{
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	unsigned int dma;
+	size_t ptr;
+
+	dma = (chip->mode & SB_MODE_CAPTURE_8) ? chip->dma8 : chip->dma16;
+	ptr = snd_dma_pointer(dma, chip->c_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+/*
+
+ */
+
+static snd_pcm_hardware_t snd_sb16_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		0,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100,
+	.rate_min =		4000,
+	.rate_max =		44100,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static snd_pcm_hardware_t snd_sb16_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		0,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100,
+	.rate_min =		4000,
+	.rate_max =		44100,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/*
+ *  open/close
+ */
+
+static int snd_sb16_playback_open(snd_pcm_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (chip->mode & SB_MODE_PLAYBACK) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		return -EAGAIN;
+	}
+	runtime->hw = snd_sb16_playback;
+
+	/* skip if 16 bit DMA was reserved for capture */
+	if (chip->force_mode16 & SB_MODE_CAPTURE_16)
+		goto __skip_16bit;
+
+	if (chip->dma16 >= 0 && !(chip->mode & SB_MODE_CAPTURE_16)) {
+		chip->mode |= SB_MODE_PLAYBACK_16;
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
+		/* Vibra16X hack */
+		if (chip->dma16 <= 3) {
+			runtime->hw.buffer_bytes_max =
+			runtime->hw.period_bytes_max = 64 * 1024;
+		} else {
+			snd_sb16_csp_playback_open(chip, runtime);
+		}
+		goto __open_ok;
+	}
+
+      __skip_16bit:
+	if (chip->dma8 >= 0 && !(chip->mode & SB_MODE_CAPTURE_8)) {
+		chip->mode |= SB_MODE_PLAYBACK_8;
+		/* DSP v 4.xx can transfer 16bit data through 8bit DMA channel, SBHWPG 2-7 */
+		if (chip->dma16 < 0) {
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
+			chip->mode |= SB_MODE_PLAYBACK_16;
+		} else {
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8;
+		}
+		runtime->hw.buffer_bytes_max =
+		runtime->hw.period_bytes_max = 64 * 1024;
+		goto __open_ok;
+	}
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return -EAGAIN;
+
+      __open_ok:
+	if (chip->hardware == SB_HW_ALS100)
+		runtime->hw.rate_max = 48000;
+	if (chip->mode & SB_RATE_LOCK)
+		runtime->hw.rate_min = runtime->hw.rate_max = chip->locked_rate;
+	chip->playback_substream = substream;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return 0;
+}
+
+static int snd_sb16_playback_close(snd_pcm_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+
+	snd_sb16_csp_playback_close(chip);
+	spin_lock_irqsave(&chip->open_lock, flags);
+	chip->playback_substream = NULL;
+	chip->mode &= ~SB_MODE_PLAYBACK;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return 0;
+}
+
+static int snd_sb16_capture_open(snd_pcm_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (chip->mode & SB_MODE_CAPTURE) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		return -EAGAIN;
+	}
+	runtime->hw = snd_sb16_capture;
+
+	/* skip if 16 bit DMA was reserved for playback */
+	if (chip->force_mode16 & SB_MODE_PLAYBACK_16)
+		goto __skip_16bit;
+
+	if (chip->dma16 >= 0 && !(chip->mode & SB_MODE_PLAYBACK_16)) {
+		chip->mode |= SB_MODE_CAPTURE_16;
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
+		/* Vibra16X hack */
+		if (chip->dma16 <= 3) {
+			runtime->hw.buffer_bytes_max =
+			runtime->hw.period_bytes_max = 64 * 1024;
+		} else {
+			snd_sb16_csp_capture_open(chip, runtime);
+		}
+		goto __open_ok;
+	}
+
+      __skip_16bit:
+	if (chip->dma8 >= 0 && !(chip->mode & SB_MODE_PLAYBACK_8)) {
+		chip->mode |= SB_MODE_CAPTURE_8;
+		/* DSP v 4.xx can transfer 16bit data through 8bit DMA channel, SBHWPG 2-7 */
+		if (chip->dma16 < 0) {
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
+			chip->mode |= SB_MODE_CAPTURE_16;
+		} else {
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8;
+		}
+		runtime->hw.buffer_bytes_max =
+		runtime->hw.period_bytes_max = 64 * 1024;
+		goto __open_ok;
+	}
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return -EAGAIN;
+
+      __open_ok:
+	if (chip->hardware == SB_HW_ALS100)
+		runtime->hw.rate_max = 48000;
+	if (chip->mode & SB_RATE_LOCK)
+		runtime->hw.rate_min = runtime->hw.rate_max = chip->locked_rate;
+	chip->capture_substream = substream;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return 0;
+}
+
+static int snd_sb16_capture_close(snd_pcm_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+
+	snd_sb16_csp_capture_close(chip);
+	spin_lock_irqsave(&chip->open_lock, flags);
+	chip->capture_substream = NULL;
+	chip->mode &= ~SB_MODE_CAPTURE;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return 0;
+}
+
+/*
+ *  DMA control interface
+ */
+
+static int snd_sb16_set_dma_mode(sb_t *chip, int what)
+{
+	if (chip->dma8 < 0 || chip->dma16 < 0) {
+		snd_assert(what == 0, return -EINVAL);
+		return 0;
+	}
+	if (what == 0) {
+		chip->force_mode16 = 0;
+	} else if (what == 1) {
+		chip->force_mode16 = SB_MODE_PLAYBACK_16;
+	} else if (what == 2) {
+		chip->force_mode16 = SB_MODE_CAPTURE_16;
+	} else {
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int snd_sb16_get_dma_mode(sb_t *chip)
+{
+	if (chip->dma8 < 0 || chip->dma16 < 0)
+		return 0;
+	switch (chip->force_mode16) {
+	case SB_MODE_PLAYBACK_16:
+		return 1;
+	case SB_MODE_CAPTURE_16:
+		return 2;
+	default:
+		return 0;
+	}
+}
+
+static int snd_sb16_dma_control_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	static char *texts[3] = {
+		"Auto", "Playback", "Capture"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item > 2)
+		uinfo->value.enumerated.item = 2;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_sb16_dma_control_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.enumerated.item[0] = snd_sb16_get_dma_mode(chip);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_sb16_dma_control_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned char nval, oval;
+	int change;
+	
+	if ((nval = ucontrol->value.enumerated.item[0]) > 2)
+		return -EINVAL;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	oval = snd_sb16_get_dma_mode(chip);
+	change = nval != oval;
+	snd_sb16_set_dma_mode(chip, nval);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_sb16_dma_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+	.name = "16-bit DMA Allocation",
+	.info = snd_sb16_dma_control_info,
+	.get = snd_sb16_dma_control_get,
+	.put = snd_sb16_dma_control_put
+};
+
+/*
+ *  Initialization part
+ */
+ 
+int snd_sb16dsp_configure(sb_t * chip)
+{
+	unsigned long flags;
+	unsigned char irqreg = 0, dmareg = 0, mpureg;
+	unsigned char realirq, realdma, realmpureg;
+	/* note: mpu register should be present only on SB16 Vibra soundcards */
+
+	// printk("codec->irq=%i, codec->dma8=%i, codec->dma16=%i\n", chip->irq, chip->dma8, chip->dma16);
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+	mpureg = snd_sbmixer_read(chip, SB_DSP4_MPUSETUP) & ~0x06;
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+	switch (chip->irq) {
+	case 2:
+	case 9:
+		irqreg |= SB_IRQSETUP_IRQ9;
+		break;
+	case 5:
+		irqreg |= SB_IRQSETUP_IRQ5;
+		break;
+	case 7:
+		irqreg |= SB_IRQSETUP_IRQ7;
+		break;
+	case 10:
+		irqreg |= SB_IRQSETUP_IRQ10;
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (chip->dma8 >= 0) {
+		switch (chip->dma8) {
+		case 0:
+			dmareg |= SB_DMASETUP_DMA0;
+			break;
+		case 1:
+			dmareg |= SB_DMASETUP_DMA1;
+			break;
+		case 3:
+			dmareg |= SB_DMASETUP_DMA3;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+	if (chip->dma16 >= 0 && chip->dma16 != chip->dma8) {
+		switch (chip->dma16) {
+		case 5:
+			dmareg |= SB_DMASETUP_DMA5;
+			break;
+		case 6:
+			dmareg |= SB_DMASETUP_DMA6;
+			break;
+		case 7:
+			dmareg |= SB_DMASETUP_DMA7;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+	switch (chip->mpu_port) {
+	case 0x300:
+		mpureg |= 0x04;
+		break;
+	case 0x330:
+		mpureg |= 0x00;
+		break;
+	default:
+		mpureg |= 0x02;	/* disable MPU */
+	}
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+
+	snd_sbmixer_write(chip, SB_DSP4_IRQSETUP, irqreg);
+	realirq = snd_sbmixer_read(chip, SB_DSP4_IRQSETUP);
+
+	snd_sbmixer_write(chip, SB_DSP4_DMASETUP, dmareg);
+	realdma = snd_sbmixer_read(chip, SB_DSP4_DMASETUP);
+
+	snd_sbmixer_write(chip, SB_DSP4_MPUSETUP, mpureg);
+	realmpureg = snd_sbmixer_read(chip, SB_DSP4_MPUSETUP);
+
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+	if ((~realirq) & irqreg || (~realdma) & dmareg) {
+		snd_printk("SB16 [0x%lx]: unable to set DMA & IRQ (PnP device?)\n", chip->port);
+		snd_printk("SB16 [0x%lx]: wanted: irqreg=0x%x, dmareg=0x%x, mpureg = 0x%x\n", chip->port, realirq, realdma, realmpureg);
+		snd_printk("SB16 [0x%lx]:    got: irqreg=0x%x, dmareg=0x%x, mpureg = 0x%x\n", chip->port, irqreg, dmareg, mpureg);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static snd_pcm_ops_t snd_sb16_playback_ops = {
+	.open =		snd_sb16_playback_open,
+	.close =	snd_sb16_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_sb16_hw_params,
+	.hw_free =	snd_sb16_hw_free,
+	.prepare =	snd_sb16_playback_prepare,
+	.trigger =	snd_sb16_playback_trigger,
+	.pointer =	snd_sb16_playback_pointer,
+};
+
+static snd_pcm_ops_t snd_sb16_capture_ops = {
+	.open =		snd_sb16_capture_open,
+	.close =	snd_sb16_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_sb16_hw_params,
+	.hw_free =	snd_sb16_hw_free,
+	.prepare =	snd_sb16_capture_prepare,
+	.trigger =	snd_sb16_capture_trigger,
+	.pointer =	snd_sb16_capture_pointer,
+};
+
+static void snd_sb16dsp_pcm_free(snd_pcm_t *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+int snd_sb16dsp_pcm(sb_t * chip, int device, snd_pcm_t ** rpcm)
+{
+	snd_card_t *card = chip->card;
+	snd_pcm_t *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if ((err = snd_pcm_new(card, "SB16 DSP", device, 1, 1, &pcm)) < 0)
+		return err;
+	sprintf(pcm->name, "DSP v%i.%i", chip->version >> 8, chip->version & 0xff);
+	pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
+	pcm->private_data = chip;
+	pcm->private_free = snd_sb16dsp_pcm_free;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sb16_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_sb16_capture_ops);
+
+	if (chip->dma16 >= 0 && chip->dma8 != chip->dma16)
+		snd_ctl_add(card, snd_ctl_new1(&snd_sb16_dma_control, chip));
+	else
+		pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, 128*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+const snd_pcm_ops_t *snd_sb16dsp_get_pcm_ops(int direction)
+{
+	return direction == SNDRV_PCM_STREAM_PLAYBACK ?
+		&snd_sb16_playback_ops : &snd_sb16_capture_ops;
+}
+
+EXPORT_SYMBOL(snd_sb16dsp_pcm);
+EXPORT_SYMBOL(snd_sb16dsp_get_pcm_ops);
+EXPORT_SYMBOL(snd_sb16dsp_configure);
+EXPORT_SYMBOL(snd_sb16dsp_interrupt);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_sb16_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_sb16_exit(void)
+{
+}
+
+module_init(alsa_sb16_init)
+module_exit(alsa_sb16_exit)
diff --git a/sound/isa/sb/sb8.c b/sound/isa/sb/sb8.c
new file mode 100644
index 0000000..e2cbc42
--- /dev/null
+++ b/sound/isa/sb/sb8.c
@@ -0,0 +1,223 @@
+/*
+ *  Driver for SoundBlaster 1.0/2.0/Pro soundcards and compatible
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/opl3.h>
+#define SNDRV_LEGACY_AUTO_PROBE
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Sound Blaster 1.0/2.0/Pro");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB 1.0/SB 2.0/SB Pro}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x240,0x260 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,10 */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 1,3 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Sound Blaster soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Sound Blaster soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Sound Blaster soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for SB8 driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for SB8 driver.");
+module_param_array(dma8, int, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for SB8 driver.");
+
+struct snd_sb8 {
+	struct resource *fm_res;	/* used to block FM i/o region for legacy cards */
+};
+
+static snd_card_t *snd_sb8_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+static irqreturn_t snd_sb8_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	sb_t *chip = dev_id;
+
+	if (chip->open & SB_OPEN_PCM) {
+		return snd_sb8dsp_interrupt(chip);
+	} else {
+		return snd_sb8dsp_midi_interrupt(chip);
+	}
+}
+
+static void snd_sb8_free(snd_card_t *card)
+{
+	struct snd_sb8 *acard = (struct snd_sb8 *)card->private_data;
+
+	if (acard == NULL)
+		return;
+	if (acard->fm_res) {
+		release_resource(acard->fm_res);
+		kfree_nocheck(acard->fm_res);
+	}
+}
+
+static int __init snd_sb8_probe(int dev)
+{
+	sb_t *chip;
+	snd_card_t *card;
+	struct snd_sb8 *acard;
+	opl3_t *opl3;
+	int err;
+
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE,
+			    sizeof(struct snd_sb8));
+	if (card == NULL)
+		return -ENOMEM;
+	acard = (struct snd_sb8 *)card->private_data;
+	card->private_free = snd_sb8_free;
+
+	/* block the 0x388 port to avoid PnP conflicts */
+	acard->fm_res = request_region(0x388, 4, "SoundBlaster FM");
+
+	if ((err = snd_sbdsp_create(card, port[dev], irq[dev],
+				    snd_sb8_interrupt,
+				    dma8[dev],
+				    -1,
+				    SB_HW_AUTO,
+				    &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (chip->hardware >= SB_HW_16) {
+		snd_card_free(card);
+		if (chip->hardware == SB_HW_ALS100)
+			snd_printdd("ALS100 chip detected at 0x%lx, try snd-als100 module\n",
+				    port[dev]);
+		else
+			snd_printdd("SB 16 chip detected at 0x%lx, try snd-sb16 module\n",
+				    port[dev]);
+		return -ENODEV;
+	}
+
+	if ((err = snd_sb8dsp_pcm(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_sbmixer_new(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (chip->hardware == SB_HW_10 || chip->hardware == SB_HW_20) {
+		if ((err = snd_opl3_create(card, chip->port + 8, 0,
+					   OPL3_HW_AUTO, 1,
+					   &opl3)) < 0) {
+			snd_printk(KERN_ERR "sb8: no OPL device at 0x%lx\n", chip->port + 8);
+		}
+	} else {
+		if ((err = snd_opl3_create(card, chip->port, chip->port + 2,
+					   OPL3_HW_AUTO, 1,
+					   &opl3)) < 0) {
+			snd_printk(KERN_ERR "sb8: no OPL device at 0x%lx-0x%lx\n",
+				   chip->port, chip->port + 2);
+		}
+	}
+	if (err >= 0) {
+		if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+	}
+
+	if ((err = snd_sb8dsp_midi(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->driver, chip->hardware == SB_HW_PRO ? "SB Pro" : "SB8");
+	strcpy(card->shortname, chip->name);
+	sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+		chip->name,
+		chip->port,
+		irq[dev], dma8[dev]);
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	snd_sb8_cards[dev] = card;
+	return 0;
+}
+
+static int __init snd_card_sb8_legacy_auto_probe(unsigned long xport)
+{
+        static int dev;
+        int res;
+
+        for ( ; dev < SNDRV_CARDS; dev++) {
+                if (!enable[dev] || port[dev] != SNDRV_AUTO_PORT)
+                        continue;
+                port[dev] = xport;
+                res = snd_sb8_probe(dev);
+                if (res < 0)
+                        port[dev] = SNDRV_AUTO_PORT;
+                return res;
+        }
+        return -ENODEV;
+}
+
+static int __init alsa_card_sb8_init(void)
+{
+	static unsigned long possible_ports[] = {0x220, 0x240, 0x260, -1};
+	int dev, cards, i;
+
+	for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev]; dev++) {
+		if (port[dev] == SNDRV_AUTO_PORT)
+			continue;
+		if (snd_sb8_probe(dev) >= 0)
+			cards++;
+	}
+	i = snd_legacy_auto_probe(possible_ports, snd_card_sb8_legacy_auto_probe);
+	if (i > 0)
+		cards += i;
+
+	if (!cards) {
+#ifdef MODULE
+		snd_printk(KERN_ERR "Sound Blaster soundcard not found or device busy\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_sb8_exit(void)
+{
+	int idx;
+
+	for (idx = 0; idx < SNDRV_CARDS; idx++)
+		snd_card_free(snd_sb8_cards[idx]);
+}
+
+module_init(alsa_card_sb8_init)
+module_exit(alsa_card_sb8_exit)
diff --git a/sound/isa/sb/sb8_main.c b/sound/isa/sb/sb8_main.c
new file mode 100644
index 0000000..87c9b1b
--- /dev/null
+++ b/sound/isa/sb/sb8_main.c
@@ -0,0 +1,565 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *                   Uros Bizjak <uros@kss-loka.si>
+ *
+ *  Routines for control of 8-bit SoundBlaster cards and clones
+ *  Please note: I don't have access to old SB8 soundcards.
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ * --
+ *
+ * Thu Apr 29 20:36:17 BST 1999 George David Morrison <gdm@gedamo.demon.co.uk>
+ *   DSP can't respond to commands whilst in "high speed" mode. Caused 
+ *   glitching during playback. Fixed.
+ *
+ * Wed Jul 12 22:02:55 CEST 2000 Uros Bizjak <uros@kss-loka.si>
+ *   Cleaned up and rewrote lowlevel routines.
+ */
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>, Uros Bizjak <uros@kss-loka.si>");
+MODULE_DESCRIPTION("Routines for control of 8-bit SoundBlaster cards and clones");
+MODULE_LICENSE("GPL");
+
+#define SB8_CLOCK	1000000
+#define SB8_DEN(v)	((SB8_CLOCK + (v) / 2) / (v))
+#define SB8_RATE(v)	(SB8_CLOCK / SB8_DEN(v))
+
+static ratnum_t clock = {
+	.num = SB8_CLOCK,
+	.den_min = 1,
+	.den_max = 256,
+	.den_step = 1,
+};
+
+static snd_pcm_hw_constraint_ratnums_t hw_constraints_clock = {
+	.nrats = 1,
+	.rats = &clock,
+};
+
+static ratnum_t stereo_clocks[] = {
+	{
+		.num = SB8_CLOCK,
+		.den_min = SB8_DEN(22050),
+		.den_max = SB8_DEN(22050),
+		.den_step = 1,
+	},
+	{
+		.num = SB8_CLOCK,
+		.den_min = SB8_DEN(11025),
+		.den_max = SB8_DEN(11025),
+		.den_step = 1,
+	}
+};
+
+static int snd_sb8_hw_constraint_rate_channels(snd_pcm_hw_params_t *params,
+					       snd_pcm_hw_rule_t *rule)
+{
+	snd_interval_t *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	if (c->min > 1) {
+	  	unsigned int num = 0, den = 0;
+		int err = snd_interval_ratnum(hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE),
+					  2, stereo_clocks, &num, &den);
+		if (err >= 0 && den) {
+			params->rate_num = num;
+			params->rate_den = den;
+		}
+		return err;
+	}
+	return 0;
+}
+
+static int snd_sb8_hw_constraint_channels_rate(snd_pcm_hw_params_t *params,
+					       snd_pcm_hw_rule_t *rule)
+{
+	snd_interval_t *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	if (r->min > SB8_RATE(22050) || r->max <= SB8_RATE(11025)) {
+		snd_interval_t t = { .min = 1, .max = 1 };
+		return snd_interval_refine(hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS), &t);
+	}
+	return 0;
+}
+
+static int snd_sb8_playback_prepare(snd_pcm_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned int mixreg, rate, size, count;
+
+	rate = runtime->rate;
+	switch (chip->hardware) {
+	case SB_HW_PRO:
+		if (runtime->channels > 1) {
+			snd_assert(rate == SB8_RATE(11025) || rate == SB8_RATE(22050), return -EINVAL);
+			chip->playback_format = SB_DSP_HI_OUTPUT_AUTO;
+			break;
+		}
+		/* fallthru */
+	case SB_HW_201:
+		if (rate > 23000) {
+			chip->playback_format = SB_DSP_HI_OUTPUT_AUTO;
+			break;
+		}
+		/* fallthru */
+	case SB_HW_20:
+		chip->playback_format = SB_DSP_LO_OUTPUT_AUTO;
+		break;
+	case SB_HW_10:
+		chip->playback_format = SB_DSP_OUTPUT;
+		break;
+	default:
+		return -EINVAL;
+	}
+	size = chip->p_dma_size = snd_pcm_lib_buffer_bytes(substream);
+	count = chip->p_period_size = snd_pcm_lib_period_bytes(substream);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_sbdsp_command(chip, SB_DSP_SPEAKER_ON);
+	if (runtime->channels > 1) {
+		/* set playback stereo mode */
+		spin_lock(&chip->mixer_lock);
+		mixreg = snd_sbmixer_read(chip, SB_DSP_STEREO_SW);
+		snd_sbmixer_write(chip, SB_DSP_STEREO_SW, mixreg | 0x02);
+		spin_unlock(&chip->mixer_lock);
+
+		/* Soundblaster hardware programming reference guide, 3-23 */
+		snd_sbdsp_command(chip, SB_DSP_DMA8_EXIT);
+		runtime->dma_area[0] = 0x80;
+		snd_dma_program(chip->dma8, runtime->dma_addr, 1, DMA_MODE_WRITE);
+		/* force interrupt */
+		chip->mode = SB_MODE_HALT;
+		snd_sbdsp_command(chip, SB_DSP_OUTPUT);
+		snd_sbdsp_command(chip, 0);
+		snd_sbdsp_command(chip, 0);
+	}
+	snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE);
+	if (runtime->channels > 1) {
+		snd_sbdsp_command(chip, 256 - runtime->rate_den / 2);
+		spin_lock(&chip->mixer_lock);
+		/* save output filter status and turn it off */
+		mixreg = snd_sbmixer_read(chip, SB_DSP_PLAYBACK_FILT);
+		snd_sbmixer_write(chip, SB_DSP_PLAYBACK_FILT, mixreg | 0x20);
+		spin_unlock(&chip->mixer_lock);
+		/* just use force_mode16 for temporary storate... */
+		chip->force_mode16 = mixreg;
+	} else {
+		snd_sbdsp_command(chip, 256 - runtime->rate_den);
+	}
+	if (chip->playback_format != SB_DSP_OUTPUT) {
+		count--;
+		snd_sbdsp_command(chip, SB_DSP_BLOCK_SIZE);
+		snd_sbdsp_command(chip, count & 0xff);
+		snd_sbdsp_command(chip, count >> 8);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_dma_program(chip->dma8, runtime->dma_addr,
+			size, DMA_MODE_WRITE | DMA_AUTOINIT);
+	return 0;
+}
+
+static int snd_sb8_playback_trigger(snd_pcm_substream_t * substream,
+				    int cmd)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	unsigned int count;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		snd_sbdsp_command(chip, chip->playback_format);
+		if (chip->playback_format == SB_DSP_OUTPUT) {
+			count = chip->p_period_size - 1;
+			snd_sbdsp_command(chip, count & 0xff);
+			snd_sbdsp_command(chip, count >> 8);
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		if (chip->playback_format == SB_DSP_HI_OUTPUT_AUTO) {
+			snd_pcm_runtime_t *runtime = substream->runtime;
+			snd_sbdsp_reset(chip);
+			if (runtime->channels > 1) {
+				spin_lock(&chip->mixer_lock);
+				/* restore output filter and set hardware to mono mode */ 
+				snd_sbmixer_write(chip, SB_DSP_STEREO_SW, chip->force_mode16 & ~0x02);
+				spin_unlock(&chip->mixer_lock);
+			}
+		} else {
+			snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+		}
+		snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	chip->mode = (cmd == SNDRV_PCM_TRIGGER_START) ? SB_MODE_PLAYBACK_8 : SB_MODE_HALT;
+	return 0;
+}
+
+static int snd_sb8_hw_params(snd_pcm_substream_t * substream,
+			     snd_pcm_hw_params_t * hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_sb8_hw_free(snd_pcm_substream_t * substream)
+{
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int snd_sb8_capture_prepare(snd_pcm_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned int mixreg, rate, size, count;
+
+	rate = runtime->rate;
+	switch (chip->hardware) {
+	case SB_HW_PRO:
+		if (runtime->channels > 1) {
+			snd_assert(rate == SB8_RATE(11025) || rate == SB8_RATE(22050), return -EINVAL);
+			chip->capture_format = SB_DSP_HI_INPUT_AUTO;
+			break;
+		}
+		chip->capture_format = (rate > 23000) ? SB_DSP_HI_INPUT_AUTO : SB_DSP_LO_INPUT_AUTO;
+		break;
+	case SB_HW_201:
+		if (rate > 13000) {
+			chip->capture_format = SB_DSP_HI_INPUT_AUTO;
+			break;
+		}
+		/* fallthru */
+	case SB_HW_20:
+		chip->capture_format = SB_DSP_LO_INPUT_AUTO;
+		break;
+	case SB_HW_10:
+		chip->capture_format = SB_DSP_INPUT;
+		break;
+	default:
+		return -EINVAL;
+	}
+	size = chip->c_dma_size = snd_pcm_lib_buffer_bytes(substream);
+	count = chip->c_period_size = snd_pcm_lib_period_bytes(substream);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF);
+	if (runtime->channels > 1)
+		snd_sbdsp_command(chip, SB_DSP_STEREO_8BIT);
+	snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE);
+	if (runtime->channels > 1) {
+		snd_sbdsp_command(chip, 256 - runtime->rate_den / 2);
+		spin_lock(&chip->mixer_lock);
+		/* save input filter status and turn it off */
+		mixreg = snd_sbmixer_read(chip, SB_DSP_CAPTURE_FILT);
+		snd_sbmixer_write(chip, SB_DSP_CAPTURE_FILT, mixreg | 0x20);
+		spin_unlock(&chip->mixer_lock);
+		/* just use force_mode16 for temporary storate... */
+		chip->force_mode16 = mixreg;
+	} else {
+		snd_sbdsp_command(chip, 256 - runtime->rate_den);
+	}
+	if (chip->capture_format != SB_DSP_OUTPUT) {
+		count--;
+		snd_sbdsp_command(chip, SB_DSP_BLOCK_SIZE);
+		snd_sbdsp_command(chip, count & 0xff);
+		snd_sbdsp_command(chip, count >> 8);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_dma_program(chip->dma8, runtime->dma_addr,
+			size, DMA_MODE_READ | DMA_AUTOINIT);
+	return 0;
+}
+
+static int snd_sb8_capture_trigger(snd_pcm_substream_t * substream,
+				   int cmd)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	unsigned int count;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		snd_sbdsp_command(chip, chip->capture_format);
+		if (chip->capture_format == SB_DSP_INPUT) {
+			count = chip->c_period_size - 1;
+			snd_sbdsp_command(chip, count & 0xff);
+			snd_sbdsp_command(chip, count >> 8);
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		if (chip->capture_format == SB_DSP_HI_INPUT_AUTO) {
+			snd_pcm_runtime_t *runtime = substream->runtime;
+			snd_sbdsp_reset(chip);
+			if (runtime->channels > 1) {
+				/* restore input filter status */
+				spin_lock(&chip->mixer_lock);
+				snd_sbmixer_write(chip, SB_DSP_CAPTURE_FILT, chip->force_mode16);
+				spin_unlock(&chip->mixer_lock);
+				/* set hardware to mono mode */
+				snd_sbdsp_command(chip, SB_DSP_MONO_8BIT);
+			}
+		} else {
+			snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+		}
+		snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	chip->mode = (cmd == SNDRV_PCM_TRIGGER_START) ? SB_MODE_CAPTURE_8 : SB_MODE_HALT;
+	return 0;
+}
+
+irqreturn_t snd_sb8dsp_interrupt(sb_t *chip)
+{
+	snd_pcm_substream_t *substream;
+	snd_pcm_runtime_t *runtime;
+
+#if 0
+	snd_printk("sb8: interrupt\n");
+#endif
+	snd_sb_ack_8bit(chip);
+	switch (chip->mode) {
+	case SB_MODE_PLAYBACK_8:	/* ok.. playback is active */
+		substream = chip->playback_substream;
+		runtime = substream->runtime;
+		if (chip->playback_format == SB_DSP_OUTPUT)
+		    	snd_sb8_playback_trigger(substream, SNDRV_PCM_TRIGGER_START);
+		snd_pcm_period_elapsed(substream);
+		break;
+	case SB_MODE_CAPTURE_8:
+		substream = chip->capture_substream;
+		runtime = substream->runtime;
+		if (chip->capture_format == SB_DSP_INPUT)
+		    	snd_sb8_capture_trigger(substream, SNDRV_PCM_TRIGGER_START);
+		snd_pcm_period_elapsed(substream);
+		break;
+	}
+	return IRQ_HANDLED;
+}
+
+static snd_pcm_uframes_t snd_sb8_playback_pointer(snd_pcm_substream_t * substream)
+{
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (chip->mode != SB_MODE_PLAYBACK_8)
+		return 0;
+	ptr = snd_dma_pointer(chip->dma8, chip->p_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_sb8_capture_pointer(snd_pcm_substream_t * substream)
+{
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (chip->mode != SB_MODE_CAPTURE_8)
+		return 0;
+	ptr = snd_dma_pointer(chip->dma8, chip->c_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+/*
+
+ */
+
+static snd_pcm_hardware_t snd_sb8_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		 SNDRV_PCM_FMTBIT_U8,
+	.rates =		(SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000 |
+				 SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_22050),
+	.rate_min =		4000,
+	.rate_max =		23000,
+	.channels_min =		1,
+	.channels_max =		1,
+	.buffer_bytes_max =	65536,
+	.period_bytes_min =	64,
+	.period_bytes_max =	65536,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static snd_pcm_hardware_t snd_sb8_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_U8,
+	.rates =		(SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000 |
+				 SNDRV_PCM_RATE_11025),
+	.rate_min =		4000,
+	.rate_max =		13000,
+	.channels_min =		1,
+	.channels_max =		1,
+	.buffer_bytes_max =	65536,
+	.period_bytes_min =	64,
+	.period_bytes_max =	65536,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/*
+ *
+ */
+ 
+static int snd_sb8_open(snd_pcm_substream_t *substream)
+{
+	sb_t *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (chip->open) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		return -EAGAIN;
+	}
+	chip->open |= SB_OPEN_PCM;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		chip->playback_substream = substream;
+		runtime->hw = snd_sb8_playback;
+	} else {
+		chip->capture_substream = substream;
+		runtime->hw = snd_sb8_capture;
+	}
+	switch (chip->hardware) {
+	case SB_HW_PRO:
+		runtime->hw.rate_max = 44100;
+		runtime->hw.channels_max = 2;
+		snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				    snd_sb8_hw_constraint_rate_channels, NULL,
+				    SNDRV_PCM_HW_PARAM_CHANNELS,
+				    SNDRV_PCM_HW_PARAM_RATE, -1);
+		snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				     snd_sb8_hw_constraint_channels_rate, NULL,
+				     SNDRV_PCM_HW_PARAM_RATE, -1);
+		break;
+	case SB_HW_201:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			runtime->hw.rate_max = 44100;
+		} else {
+			runtime->hw.rate_max = 15000;
+		}
+	default:
+		break;
+	}
+	snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      &hw_constraints_clock);
+	return 0;	
+}
+
+static int snd_sb8_close(snd_pcm_substream_t *substream)
+{
+	unsigned long flags;
+	sb_t *chip = snd_pcm_substream_chip(substream);
+
+	chip->playback_substream = NULL;
+	chip->capture_substream = NULL;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	chip->open &= ~SB_OPEN_PCM;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return 0;
+}
+
+/*
+ *  Initialization part
+ */
+ 
+static snd_pcm_ops_t snd_sb8_playback_ops = {
+	.open =			snd_sb8_open,
+	.close =		snd_sb8_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_sb8_hw_params,
+	.hw_free =		snd_sb8_hw_free,
+	.prepare =		snd_sb8_playback_prepare,
+	.trigger =		snd_sb8_playback_trigger,
+	.pointer =		snd_sb8_playback_pointer,
+};
+
+static snd_pcm_ops_t snd_sb8_capture_ops = {
+	.open =			snd_sb8_open,
+	.close =		snd_sb8_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_sb8_hw_params,
+	.hw_free =		snd_sb8_hw_free,
+	.prepare =		snd_sb8_capture_prepare,
+	.trigger =		snd_sb8_capture_trigger,
+	.pointer =		snd_sb8_capture_pointer,
+};
+
+static void snd_sb8dsp_pcm_free(snd_pcm_t *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+int snd_sb8dsp_pcm(sb_t *chip, int device, snd_pcm_t ** rpcm)
+{
+	snd_card_t *card = chip->card;
+	snd_pcm_t *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if ((err = snd_pcm_new(card, "SB8 DSP", device, 1, 1, &pcm)) < 0)
+		return err;
+	sprintf(pcm->name, "DSP v%i.%i", chip->version >> 8, chip->version & 0xff);
+	pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+	pcm->private_data = chip;
+	pcm->private_free = snd_sb8dsp_pcm_free;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sb8_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_sb8_capture_ops);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, 64*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_sb8dsp_pcm);
+EXPORT_SYMBOL(snd_sb8dsp_interrupt);
+  /* sb8_midi.c */
+EXPORT_SYMBOL(snd_sb8dsp_midi_interrupt);
+EXPORT_SYMBOL(snd_sb8dsp_midi);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_sb8_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_sb8_exit(void)
+{
+}
+
+module_init(alsa_sb8_init)
+module_exit(alsa_sb8_exit)
diff --git a/sound/isa/sb/sb8_midi.c b/sound/isa/sb/sb8_midi.c
new file mode 100644
index 0000000..d2c633a
--- /dev/null
+++ b/sound/isa/sb/sb8_midi.c
@@ -0,0 +1,293 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  Routines for control of SoundBlaster cards - MIDI interface
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ * --
+ *
+ * Sun May  9 22:54:38 BST 1999 George David Morrison <gdm@gedamo.demon.co.uk>
+ *   Fixed typo in snd_sb8dsp_midi_new_device which prevented midi from 
+ *   working.
+ *
+ * Sun May 11 12:34:56 UTC 2003 Clemens Ladisch <clemens@ladisch.de>
+ *   Added full duplex UART mode for DSP version 2.0 and later.
+ */
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+
+/*
+
+ */
+
+irqreturn_t snd_sb8dsp_midi_interrupt(sb_t * chip)
+{
+	snd_rawmidi_t *rmidi;
+	int max = 64;
+	char byte;
+
+	if (chip == NULL || (rmidi = chip->rmidi) == NULL) {
+		inb(SBP(chip, DATA_AVAIL));	/* ack interrupt */
+		return IRQ_NONE;
+	}
+	spin_lock(&chip->midi_input_lock);
+	while (max-- > 0) {
+		if (inb(SBP(chip, DATA_AVAIL)) & 0x80) {
+			byte = inb(SBP(chip, READ));
+			if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) {
+				snd_rawmidi_receive(chip->midi_substream_input, &byte, 1);
+			}
+		}
+	}
+	spin_unlock(&chip->midi_input_lock);
+	return IRQ_HANDLED;
+}
+
+/*
+
+ */
+
+static int snd_sb8dsp_midi_input_open(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip;
+	unsigned int valid_open_flags;
+
+	chip = substream->rmidi->private_data;
+	valid_open_flags = chip->hardware >= SB_HW_20
+		? SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER : 0;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (chip->open & ~valid_open_flags) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		return -EAGAIN;
+	}
+	chip->open |= SB_OPEN_MIDI_INPUT;
+	chip->midi_substream_input = substream;
+	if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		snd_sbdsp_reset(chip);		/* reset DSP */
+		if (chip->hardware >= SB_HW_20)
+			snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ);
+	} else {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+	}
+	return 0;
+}
+
+static int snd_sb8dsp_midi_output_open(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip;
+	unsigned int valid_open_flags;
+
+	chip = substream->rmidi->private_data;
+	valid_open_flags = chip->hardware >= SB_HW_20
+		? SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER : 0;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (chip->open & ~valid_open_flags) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		return -EAGAIN;
+	}
+	chip->open |= SB_OPEN_MIDI_OUTPUT;
+	chip->midi_substream_output = substream;
+	if (!(chip->open & SB_OPEN_MIDI_INPUT)) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		snd_sbdsp_reset(chip);		/* reset DSP */
+		if (chip->hardware >= SB_HW_20)
+			snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ);
+	} else {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+	}
+	return 0;
+}
+
+static int snd_sb8dsp_midi_input_close(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip;
+
+	chip = substream->rmidi->private_data;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	chip->open &= ~(SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER);
+	chip->midi_substream_input = NULL;
+	if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		snd_sbdsp_reset(chip);		/* reset DSP */
+	} else {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+	}
+	return 0;
+}
+
+static int snd_sb8dsp_midi_output_close(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip;
+
+	chip = substream->rmidi->private_data;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	chip->open &= ~(SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER);
+	chip->midi_substream_output = NULL;
+	if (!(chip->open & SB_OPEN_MIDI_INPUT)) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		snd_sbdsp_reset(chip);		/* reset DSP */
+	} else {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+	}
+	return 0;
+}
+
+static void snd_sb8dsp_midi_input_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+	unsigned long flags;
+	sb_t *chip;
+
+	chip = substream->rmidi->private_data;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (up) {
+		if (!(chip->open & SB_OPEN_MIDI_INPUT_TRIGGER)) {
+			if (chip->hardware < SB_HW_20)
+				snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ);
+			chip->open |= SB_OPEN_MIDI_INPUT_TRIGGER;
+		}
+	} else {
+		if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) {
+			if (chip->hardware < SB_HW_20)
+				snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ);
+			chip->open &= ~SB_OPEN_MIDI_INPUT_TRIGGER;
+		}
+	}
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+}
+
+static void snd_sb8dsp_midi_output_write(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	sb_t *chip;
+	char byte;
+	int max = 32;
+
+	/* how big is Tx FIFO? */
+	chip = substream->rmidi->private_data;
+	while (max-- > 0) {
+		spin_lock_irqsave(&chip->open_lock, flags);
+		if (snd_rawmidi_transmit_peek(substream, &byte, 1) != 1) {
+			chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER;
+			del_timer(&chip->midi_timer);
+			spin_unlock_irqrestore(&chip->open_lock, flags);
+			break;
+		}
+		if (chip->hardware >= SB_HW_20) {
+			int timeout = 8;
+			while ((inb(SBP(chip, STATUS)) & 0x80) != 0 && --timeout > 0)
+				;
+			if (timeout == 0) {
+				/* Tx FIFO full - try again later */
+				spin_unlock_irqrestore(&chip->open_lock, flags);
+				break;
+			}
+			outb(byte, SBP(chip, WRITE));
+		} else {
+			snd_sbdsp_command(chip, SB_DSP_MIDI_OUTPUT);
+			snd_sbdsp_command(chip, byte);
+		}
+		snd_rawmidi_transmit_ack(substream, 1);
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+	}
+}
+
+static void snd_sb8dsp_midi_output_timer(unsigned long data)
+{
+	snd_rawmidi_substream_t * substream = (snd_rawmidi_substream_t *) data;
+	sb_t * chip = substream->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->open_lock, flags);
+	chip->midi_timer.expires = 1 + jiffies;
+	add_timer(&chip->midi_timer);
+	spin_unlock_irqrestore(&chip->open_lock, flags);	
+	snd_sb8dsp_midi_output_write(substream);
+}
+
+static void snd_sb8dsp_midi_output_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+	unsigned long flags;
+	sb_t *chip;
+
+	chip = substream->rmidi->private_data;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (up) {
+		if (!(chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER)) {
+			init_timer(&chip->midi_timer);
+			chip->midi_timer.function = snd_sb8dsp_midi_output_timer;
+			chip->midi_timer.data = (unsigned long) substream;
+			chip->midi_timer.expires = 1 + jiffies;
+			add_timer(&chip->midi_timer);
+			chip->open |= SB_OPEN_MIDI_OUTPUT_TRIGGER;
+		}
+	} else {
+		if (chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER) {
+			chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER;
+		}
+	}
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+
+	if (up)
+		snd_sb8dsp_midi_output_write(substream);
+}
+
+/*
+
+ */
+
+static snd_rawmidi_ops_t snd_sb8dsp_midi_output =
+{
+	.open =		snd_sb8dsp_midi_output_open,
+	.close =	snd_sb8dsp_midi_output_close,
+	.trigger =	snd_sb8dsp_midi_output_trigger,
+};
+
+static snd_rawmidi_ops_t snd_sb8dsp_midi_input =
+{
+	.open =		snd_sb8dsp_midi_input_open,
+	.close =	snd_sb8dsp_midi_input_close,
+	.trigger =	snd_sb8dsp_midi_input_trigger,
+};
+
+int snd_sb8dsp_midi(sb_t *chip, int device, snd_rawmidi_t ** rrawmidi)
+{
+	snd_rawmidi_t *rmidi;
+	int err;
+
+	if (rrawmidi)
+		*rrawmidi = NULL;
+	if ((err = snd_rawmidi_new(chip->card, "SB8 MIDI", device, 1, 1, &rmidi)) < 0)
+		return err;
+	strcpy(rmidi->name, "SB8 MIDI");
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_sb8dsp_midi_output);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_sb8dsp_midi_input);
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT;
+	if (chip->hardware >= SB_HW_20)
+		rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
+	rmidi->private_data = chip;
+	chip->rmidi = rmidi;
+	if (rrawmidi)
+		*rrawmidi = rmidi;
+	return 0;
+}
diff --git a/sound/isa/sb/sb_common.c b/sound/isa/sb/sb_common.c
new file mode 100644
index 0000000..5b6bde2
--- /dev/null
+++ b/sound/isa/sb/sb_common.c
@@ -0,0 +1,313 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *                   Uros Bizjak <uros@kss-loka.si>
+ *
+ *  Lowlevel routines for control of Sound Blaster cards
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/initval.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("ALSA lowlevel driver for Sound Blaster cards");
+MODULE_LICENSE("GPL");
+
+#define BUSY_LOOPS 100000
+
+#undef IO_DEBUG
+
+int snd_sbdsp_command(sb_t *chip, unsigned char val)
+{
+	int i;
+#ifdef IO_DEBUG
+	snd_printk("command 0x%x\n", val);
+#endif
+	for (i = BUSY_LOOPS; i; i--)
+		if ((inb(SBP(chip, STATUS)) & 0x80) == 0) {
+			outb(val, SBP(chip, COMMAND));
+			return 1;
+		}
+	snd_printd("%s [0x%lx]: timeout (0x%x)\n", __FUNCTION__, chip->port, val);
+	return 0;
+}
+
+int snd_sbdsp_get_byte(sb_t *chip)
+{
+	int val;
+	int i;
+	for (i = BUSY_LOOPS; i; i--) {
+		if (inb(SBP(chip, DATA_AVAIL)) & 0x80) {
+			val = inb(SBP(chip, READ));
+#ifdef IO_DEBUG
+			snd_printk("get_byte 0x%x\n", val);
+#endif
+			return val;
+		}
+	}
+	snd_printd("%s [0x%lx]: timeout\n", __FUNCTION__, chip->port);
+	return -ENODEV;
+}
+
+int snd_sbdsp_reset(sb_t *chip)
+{
+	int i;
+
+	outb(1, SBP(chip, RESET));
+	udelay(10);
+	outb(0, SBP(chip, RESET));
+	udelay(30);
+	for (i = BUSY_LOOPS; i; i--)
+		if (inb(SBP(chip, DATA_AVAIL)) & 0x80) {
+			if (inb(SBP(chip, READ)) == 0xaa)
+				return 0;
+			else
+				break;
+		}
+	snd_printdd("%s [0x%lx] failed...\n", __FUNCTION__, chip->port);
+	return -ENODEV;
+}
+
+static int snd_sbdsp_version(sb_t * chip)
+{
+	unsigned int result = -ENODEV;
+
+	snd_sbdsp_command(chip, SB_DSP_GET_VERSION);
+	result = (short) snd_sbdsp_get_byte(chip) << 8;
+	result |= (short) snd_sbdsp_get_byte(chip);
+	return result;
+}
+
+static int snd_sbdsp_probe(sb_t * chip)
+{
+	int version;
+	int major, minor;
+	char *str;
+	unsigned long flags;
+
+	/*
+	 *  initialization sequence
+	 */
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (snd_sbdsp_reset(chip) < 0) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return -ENODEV;
+	}
+	version = snd_sbdsp_version(chip);
+	if (version < 0) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return -ENODEV;
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	major = version >> 8;
+	minor = version & 0xff;
+	snd_printdd("SB [0x%lx]: DSP chip found, version = %i.%i\n",
+		    chip->port, major, minor);
+	
+	switch (chip->hardware) {
+	case SB_HW_AUTO:
+		switch (major) {
+		case 1:
+			chip->hardware = SB_HW_10;
+			str = "1.0";
+			break;
+		case 2:
+			if (minor) {
+				chip->hardware = SB_HW_201;
+				str = "2.01+";
+			} else {
+				chip->hardware = SB_HW_20;
+				str = "2.0";
+			}
+			break;
+		case 3:
+			chip->hardware = SB_HW_PRO;
+			str = "Pro";
+			break;
+		case 4:
+			chip->hardware = SB_HW_16;
+			str = "16";
+			break;
+		default:
+			snd_printk("SB [0x%lx]: unknown DSP chip version %i.%i\n",
+				   chip->port, major, minor);
+			return -ENODEV;
+		}
+		break;
+	case SB_HW_ALS100:
+		str = "16 (ALS-100)";
+		break;
+	case SB_HW_ALS4000:
+		str = "16 (ALS-4000)";
+		break;
+	case SB_HW_DT019X:
+		str = "(DT019X/ALS007)";
+		break;
+	default:
+		return -ENODEV;
+	}
+	sprintf(chip->name, "Sound Blaster %s", str);
+	chip->version = (major << 8) | minor;
+	return 0;
+}
+
+static int snd_sbdsp_free(sb_t *chip)
+{
+	if (chip->res_port) {
+		release_resource(chip->res_port);
+		kfree_nocheck(chip->res_port);
+	}
+	if (chip->irq >= 0)
+		free_irq(chip->irq, (void *) chip);
+#ifdef CONFIG_ISA
+	if (chip->dma8 >= 0) {
+		disable_dma(chip->dma8);
+		free_dma(chip->dma8);
+	}
+	if (chip->dma16 >= 0 && chip->dma16 != chip->dma8) {
+		disable_dma(chip->dma16);
+		free_dma(chip->dma16);
+	}
+#endif
+	kfree(chip);
+	return 0;
+}
+
+static int snd_sbdsp_dev_free(snd_device_t *device)
+{
+	sb_t *chip = device->device_data;
+	return snd_sbdsp_free(chip);
+}
+
+int snd_sbdsp_create(snd_card_t *card,
+		     unsigned long port,
+		     int irq,
+		     irqreturn_t (*irq_handler)(int, void *, struct pt_regs *),
+		     int dma8,
+		     int dma16,
+		     unsigned short hardware,
+		     sb_t **r_chip)
+{
+	sb_t *chip;
+	int err;
+	static snd_device_ops_t ops = {
+		.dev_free =	snd_sbdsp_dev_free,
+	};
+
+	snd_assert(r_chip != NULL, return -EINVAL);
+	*r_chip = NULL;
+	chip = kcalloc(1, sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+	spin_lock_init(&chip->reg_lock);
+	spin_lock_init(&chip->open_lock);
+	spin_lock_init(&chip->midi_input_lock);
+	spin_lock_init(&chip->mixer_lock);
+	chip->irq = -1;
+	chip->dma8 = -1;
+	chip->dma16 = -1;
+	chip->port = port;
+	
+	if (request_irq(irq, irq_handler, hardware == SB_HW_ALS4000 ?
+			SA_INTERRUPT | SA_SHIRQ : SA_INTERRUPT,
+			"SoundBlaster", (void *) chip)) {
+		snd_printk(KERN_ERR "sb: can't grab irq %d\n", irq);
+		snd_sbdsp_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = irq;
+
+	if (hardware == SB_HW_ALS4000)
+		goto __skip_allocation;
+	
+	if ((chip->res_port = request_region(port, 16, "SoundBlaster")) == NULL) {
+		snd_printk(KERN_ERR "sb: can't grab port 0x%lx\n", port);
+		snd_sbdsp_free(chip);
+		return -EBUSY;
+	}
+
+#ifdef CONFIG_ISA
+	if (dma8 >= 0 && request_dma(dma8, "SoundBlaster - 8bit")) {
+		snd_printk(KERN_ERR "sb: can't grab DMA8 %d\n", dma8);
+		snd_sbdsp_free(chip);
+		return -EBUSY;
+	}
+	chip->dma8 = dma8;
+	if (dma16 >= 0) {
+		if (hardware != SB_HW_ALS100 && (dma16 < 5 || dma16 > 7)) {
+			/* no duplex */
+			dma16 = -1;
+		} else if (request_dma(dma16, "SoundBlaster - 16bit")) {
+			snd_printk(KERN_ERR "sb: can't grab DMA16 %d\n", dma16);
+			snd_sbdsp_free(chip);
+			return -EBUSY;
+		}
+	}
+	chip->dma16 = dma16;
+#endif
+
+      __skip_allocation:
+	chip->card = card;
+	chip->hardware = hardware;
+	if ((err = snd_sbdsp_probe(chip)) < 0) {
+		snd_sbdsp_free(chip);
+		return err;
+	}
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_sbdsp_free(chip);
+		return err;
+	}
+	*r_chip = chip;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_sbdsp_command);
+EXPORT_SYMBOL(snd_sbdsp_get_byte);
+EXPORT_SYMBOL(snd_sbdsp_reset);
+EXPORT_SYMBOL(snd_sbdsp_create);
+/* sb_mixer.c */
+EXPORT_SYMBOL(snd_sbmixer_write);
+EXPORT_SYMBOL(snd_sbmixer_read);
+EXPORT_SYMBOL(snd_sbmixer_new);
+EXPORT_SYMBOL(snd_sbmixer_add_ctl);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_sb_common_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_sb_common_exit(void)
+{
+}
+
+module_init(alsa_sb_common_init)
+module_exit(alsa_sb_common_exit)
diff --git a/sound/isa/sb/sb_mixer.c b/sound/isa/sb/sb_mixer.c
new file mode 100644
index 0000000..cc5a2c6
--- /dev/null
+++ b/sound/isa/sb/sb_mixer.c
@@ -0,0 +1,844 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  Routines for Sound Blaster mixer control
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/control.h>
+
+#undef IO_DEBUG
+
+void snd_sbmixer_write(sb_t *chip, unsigned char reg, unsigned char data)
+{
+	outb(reg, SBP(chip, MIXER_ADDR));
+	udelay(10);
+	outb(data, SBP(chip, MIXER_DATA));
+	udelay(10);
+#ifdef IO_DEBUG
+	snd_printk("mixer_write 0x%x 0x%x\n", reg, data);
+#endif
+}
+
+unsigned char snd_sbmixer_read(sb_t *chip, unsigned char reg)
+{
+	unsigned char result;
+
+	outb(reg, SBP(chip, MIXER_ADDR));
+	udelay(10);
+	result = inb(SBP(chip, MIXER_DATA));
+	udelay(10);
+#ifdef IO_DEBUG
+	snd_printk("mixer_read 0x%x 0x%x\n", reg, result);
+#endif
+	return result;
+}
+
+/*
+ * Single channel mixer element
+ */
+
+static int snd_sbmixer_info_single(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_sbmixer_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 16) & 0xff;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	unsigned char val;
+
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	val = (snd_sbmixer_read(sb, reg) >> shift) & mask;
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	ucontrol->value.integer.value[0] = val;
+	return 0;
+}
+
+static int snd_sbmixer_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 16) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned char val, oval;
+
+	val = (ucontrol->value.integer.value[0] & mask) << shift;
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, reg);
+	val = (oval & ~(mask << shift)) | val;
+	change = val != oval;
+	if (change)
+		snd_sbmixer_write(sb, reg, val);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	return change;
+}
+
+/*
+ * Double channel mixer element
+ */
+
+static int snd_sbmixer_info_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_sbmixer_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int left_shift = (kcontrol->private_value >> 16) & 0x07;
+	int right_shift = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	unsigned char left, right;
+
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	left = (snd_sbmixer_read(sb, left_reg) >> left_shift) & mask;
+	right = (snd_sbmixer_read(sb, right_reg) >> right_shift) & mask;
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	ucontrol->value.integer.value[0] = left;
+	ucontrol->value.integer.value[1] = right;
+	return 0;
+}
+
+static int snd_sbmixer_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int left_shift = (kcontrol->private_value >> 16) & 0x07;
+	int right_shift = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned char left, right, oleft, oright;
+
+	left = (ucontrol->value.integer.value[0] & mask) << left_shift;
+	right = (ucontrol->value.integer.value[1] & mask) << right_shift;
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	if (left_reg == right_reg) {
+		oleft = snd_sbmixer_read(sb, left_reg);
+		left = (oleft & ~((mask << left_shift) | (mask << right_shift))) | left | right;
+		change = left != oleft;
+		if (change)
+			snd_sbmixer_write(sb, left_reg, left);
+	} else {
+		oleft = snd_sbmixer_read(sb, left_reg);
+		oright = snd_sbmixer_read(sb, right_reg);
+		left = (oleft & ~(mask << left_shift)) | left;
+		right = (oright & ~(mask << right_shift)) | right;
+		change = left != oleft || right != oright;
+		if (change) {
+			snd_sbmixer_write(sb, left_reg, left);
+			snd_sbmixer_write(sb, right_reg, right);
+		}
+	}
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	return change;
+}
+
+/*
+ * DT-019x / ALS-007 capture/input switch
+ */
+
+static int snd_dt019x_input_sw_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	static char *texts[5] = {
+		"CD", "Mic", "Line", "Synth", "Master"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 5;
+	if (uinfo->value.enumerated.item > 4)
+		uinfo->value.enumerated.item = 4;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_dt019x_input_sw_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned char oval;
+	
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, SB_DT019X_CAPTURE_SW);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	switch (oval & 0x07) {
+	case SB_DT019X_CAP_CD:
+		ucontrol->value.enumerated.item[0] = 0;
+		break;
+	case SB_DT019X_CAP_MIC:
+		ucontrol->value.enumerated.item[0] = 1;
+		break;
+	case SB_DT019X_CAP_LINE:
+		ucontrol->value.enumerated.item[0] = 2;
+		break;
+	case SB_DT019X_CAP_MAIN:
+		ucontrol->value.enumerated.item[0] = 4;
+		break;
+	/* To record the synth on these cards you must record the main.   */
+	/* Thus SB_DT019X_CAP_SYNTH == SB_DT019X_CAP_MAIN and would cause */
+	/* duplicate case labels if left uncommented. */
+	/* case SB_DT019X_CAP_SYNTH:
+	 *	ucontrol->value.enumerated.item[0] = 3;
+	 *	break;
+	 */
+	default:
+		ucontrol->value.enumerated.item[0] = 4;
+		break;
+	}
+	return 0;
+}
+
+static int snd_dt019x_input_sw_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned char nval, oval;
+	
+	if (ucontrol->value.enumerated.item[0] > 4)
+		return -EINVAL;
+	switch (ucontrol->value.enumerated.item[0]) {
+	case 0:
+		nval = SB_DT019X_CAP_CD;
+		break;
+	case 1:
+		nval = SB_DT019X_CAP_MIC;
+		break;
+	case 2:
+		nval = SB_DT019X_CAP_LINE;
+		break;
+	case 3:
+		nval = SB_DT019X_CAP_SYNTH;
+		break;
+	case 4:
+		nval = SB_DT019X_CAP_MAIN;
+		break;
+	default:
+		nval = SB_DT019X_CAP_MAIN;
+	}
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, SB_DT019X_CAPTURE_SW);
+	change = nval != oval;
+	if (change)
+		snd_sbmixer_write(sb, SB_DT019X_CAPTURE_SW, nval);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	return change;
+}
+
+/*
+ * SBPRO input multiplexer
+ */
+
+static int snd_sb8mixer_info_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	static char *texts[3] = {
+		"Mic", "CD", "Line"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item > 2)
+		uinfo->value.enumerated.item = 2;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+
+static int snd_sb8mixer_get_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned char oval;
+	
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, SB_DSP_CAPTURE_SOURCE);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	switch ((oval >> 0x01) & 0x03) {
+	case SB_DSP_MIXS_CD:
+		ucontrol->value.enumerated.item[0] = 1;
+		break;
+	case SB_DSP_MIXS_LINE:
+		ucontrol->value.enumerated.item[0] = 2;
+		break;
+	default:
+		ucontrol->value.enumerated.item[0] = 0;
+		break;
+	}
+	return 0;
+}
+
+static int snd_sb8mixer_put_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned char nval, oval;
+	
+	if (ucontrol->value.enumerated.item[0] > 2)
+		return -EINVAL;
+	switch (ucontrol->value.enumerated.item[0]) {
+	case 1:
+		nval = SB_DSP_MIXS_CD;
+		break;
+	case 2:
+		nval = SB_DSP_MIXS_LINE;
+		break;
+	default:
+		nval = SB_DSP_MIXS_MIC;
+	}
+	nval <<= 1;
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, SB_DSP_CAPTURE_SOURCE);
+	nval |= oval & ~0x06;
+	change = nval != oval;
+	if (change)
+		snd_sbmixer_write(sb, SB_DSP_CAPTURE_SOURCE, nval);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	return change;
+}
+
+/*
+ * SB16 input switch
+ */
+
+static int snd_sb16mixer_info_input_sw(snd_kcontrol_t * kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 4;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int snd_sb16mixer_get_input_sw(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg1 = kcontrol->private_value & 0xff;
+	int reg2 = (kcontrol->private_value >> 8) & 0xff;
+	int left_shift = (kcontrol->private_value >> 16) & 0x0f;
+	int right_shift = (kcontrol->private_value >> 24) & 0x0f;
+	unsigned char val1, val2;
+
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	val1 = snd_sbmixer_read(sb, reg1);
+	val2 = snd_sbmixer_read(sb, reg2);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	ucontrol->value.integer.value[0] = (val1 >> left_shift) & 0x01;
+	ucontrol->value.integer.value[1] = (val2 >> left_shift) & 0x01;
+	ucontrol->value.integer.value[2] = (val1 >> right_shift) & 0x01;
+	ucontrol->value.integer.value[3] = (val2 >> right_shift) & 0x01;
+	return 0;
+}                                                                                                                   
+
+static int snd_sb16mixer_put_input_sw(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	sb_t *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg1 = kcontrol->private_value & 0xff;
+	int reg2 = (kcontrol->private_value >> 8) & 0xff;
+	int left_shift = (kcontrol->private_value >> 16) & 0x0f;
+	int right_shift = (kcontrol->private_value >> 24) & 0x0f;
+	int change;
+	unsigned char val1, val2, oval1, oval2;
+
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval1 = snd_sbmixer_read(sb, reg1);
+	oval2 = snd_sbmixer_read(sb, reg2);
+	val1 = oval1 & ~((1 << left_shift) | (1 << right_shift));
+	val2 = oval2 & ~((1 << left_shift) | (1 << right_shift));
+	val1 |= (ucontrol->value.integer.value[0] & 1) << left_shift;
+	val2 |= (ucontrol->value.integer.value[1] & 1) << left_shift;
+	val1 |= (ucontrol->value.integer.value[2] & 1) << right_shift;
+	val2 |= (ucontrol->value.integer.value[3] & 1) << right_shift;
+	change = val1 != oval1 || val2 != oval2;
+	if (change) {
+		snd_sbmixer_write(sb, reg1, val1);
+		snd_sbmixer_write(sb, reg2, val2);
+	}
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	return change;
+}
+
+
+/*
+ */
+/*
+ */
+int snd_sbmixer_add_ctl(sb_t *chip, const char *name, int index, int type, unsigned long value)
+{
+	static snd_kcontrol_new_t newctls[] = {
+		[SB_MIX_SINGLE] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_sbmixer_info_single,
+			.get = snd_sbmixer_get_single,
+			.put = snd_sbmixer_put_single,
+		},
+		[SB_MIX_DOUBLE] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_sbmixer_info_double,
+			.get = snd_sbmixer_get_double,
+			.put = snd_sbmixer_put_double,
+		},
+		[SB_MIX_INPUT_SW] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_sb16mixer_info_input_sw,
+			.get = snd_sb16mixer_get_input_sw,
+			.put = snd_sb16mixer_put_input_sw,
+		},
+		[SB_MIX_CAPTURE_PRO] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_sb8mixer_info_mux,
+			.get = snd_sb8mixer_get_mux,
+			.put = snd_sb8mixer_put_mux,
+		},
+		[SB_MIX_CAPTURE_DT019X] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_dt019x_input_sw_info,
+			.get = snd_dt019x_input_sw_get,
+			.put = snd_dt019x_input_sw_put,
+		},
+	};
+	snd_kcontrol_t *ctl;
+	int err;
+
+	ctl = snd_ctl_new1(&newctls[type], chip);
+	if (! ctl)
+		return -ENOMEM;
+	strlcpy(ctl->id.name, name, sizeof(ctl->id.name));
+	ctl->id.index = index;
+	ctl->private_value = value;
+	if ((err = snd_ctl_add(chip->card, ctl)) < 0) {
+		snd_ctl_free_one(ctl);
+		return err;
+	}
+	return 0;
+}
+
+/*
+ * SB 2.0 specific mixer elements
+ */
+
+static struct sbmix_elem snd_sb20_ctl_master_play_vol =
+	SB_SINGLE("Master Playback Volume", SB_DSP20_MASTER_DEV, 1, 7);
+static struct sbmix_elem snd_sb20_ctl_pcm_play_vol =
+	SB_SINGLE("PCM Playback Volume", SB_DSP20_PCM_DEV, 1, 3);
+static struct sbmix_elem snd_sb20_ctl_synth_play_vol =
+	SB_SINGLE("Synth Playback Volume", SB_DSP20_FM_DEV, 1, 7);
+static struct sbmix_elem snd_sb20_ctl_cd_play_vol =
+	SB_SINGLE("CD Playback Volume", SB_DSP20_CD_DEV, 1, 7);
+
+static struct sbmix_elem *snd_sb20_controls[] = {
+	&snd_sb20_ctl_master_play_vol,
+	&snd_sb20_ctl_pcm_play_vol,
+	&snd_sb20_ctl_synth_play_vol,
+	&snd_sb20_ctl_cd_play_vol
+};
+
+static unsigned char snd_sb20_init_values[][2] = {
+	{ SB_DSP20_MASTER_DEV, 0 },
+	{ SB_DSP20_FM_DEV, 0 },
+};
+
+/*
+ * SB Pro specific mixer elements
+ */
+static struct sbmix_elem snd_sbpro_ctl_master_play_vol =
+	SB_DOUBLE("Master Playback Volume", SB_DSP_MASTER_DEV, SB_DSP_MASTER_DEV, 5, 1, 7);
+static struct sbmix_elem snd_sbpro_ctl_pcm_play_vol =
+	SB_DOUBLE("PCM Playback Volume", SB_DSP_PCM_DEV, SB_DSP_PCM_DEV, 5, 1, 7);
+static struct sbmix_elem snd_sbpro_ctl_pcm_play_filter =
+	SB_SINGLE("PCM Playback Filter", SB_DSP_PLAYBACK_FILT, 5, 1);
+static struct sbmix_elem snd_sbpro_ctl_synth_play_vol =
+	SB_DOUBLE("Synth Playback Volume", SB_DSP_FM_DEV, SB_DSP_FM_DEV, 5, 1, 7);
+static struct sbmix_elem snd_sbpro_ctl_cd_play_vol =
+	SB_DOUBLE("CD Playback Volume", SB_DSP_CD_DEV, SB_DSP_CD_DEV, 5, 1, 7);
+static struct sbmix_elem snd_sbpro_ctl_line_play_vol =
+	SB_DOUBLE("Line Playback Volume", SB_DSP_LINE_DEV, SB_DSP_LINE_DEV, 5, 1, 7);
+static struct sbmix_elem snd_sbpro_ctl_mic_play_vol =
+	SB_SINGLE("Mic Playback Volume", SB_DSP_MIC_DEV, 1, 3);
+static struct sbmix_elem snd_sbpro_ctl_capture_source =
+	{
+		.name = "Capture Source",
+		.type = SB_MIX_CAPTURE_PRO
+	};
+static struct sbmix_elem snd_sbpro_ctl_capture_filter =
+	SB_SINGLE("Capture Filter", SB_DSP_CAPTURE_FILT, 5, 1);
+static struct sbmix_elem snd_sbpro_ctl_capture_low_filter =
+	SB_SINGLE("Capture Low-Pass Filter", SB_DSP_CAPTURE_FILT, 3, 1);
+
+static struct sbmix_elem *snd_sbpro_controls[] = {
+	&snd_sbpro_ctl_master_play_vol,
+	&snd_sbpro_ctl_pcm_play_vol,
+	&snd_sbpro_ctl_pcm_play_filter,
+	&snd_sbpro_ctl_synth_play_vol,
+	&snd_sbpro_ctl_cd_play_vol,
+	&snd_sbpro_ctl_line_play_vol,
+	&snd_sbpro_ctl_mic_play_vol,
+	&snd_sbpro_ctl_capture_source,
+	&snd_sbpro_ctl_capture_filter,
+	&snd_sbpro_ctl_capture_low_filter
+};
+
+static unsigned char snd_sbpro_init_values[][2] = {
+	{ SB_DSP_MASTER_DEV, 0 },
+	{ SB_DSP_PCM_DEV, 0 },
+	{ SB_DSP_FM_DEV, 0 },
+};
+
+/*
+ * SB16 specific mixer elements
+ */
+static struct sbmix_elem snd_sb16_ctl_master_play_vol =
+	SB_DOUBLE("Master Playback Volume", SB_DSP4_MASTER_DEV, (SB_DSP4_MASTER_DEV + 1), 3, 3, 31);
+static struct sbmix_elem snd_sb16_ctl_3d_enhance_switch =
+	SB_SINGLE("3D Enhancement Switch", SB_DSP4_3DSE, 0, 1);
+static struct sbmix_elem snd_sb16_ctl_tone_bass =
+	SB_DOUBLE("Tone Control - Bass", SB_DSP4_BASS_DEV, (SB_DSP4_BASS_DEV + 1), 4, 4, 15);
+static struct sbmix_elem snd_sb16_ctl_tone_treble =
+	SB_DOUBLE("Tone Control - Treble", SB_DSP4_TREBLE_DEV, (SB_DSP4_TREBLE_DEV + 1), 4, 4, 15);
+static struct sbmix_elem snd_sb16_ctl_pcm_play_vol =
+	SB_DOUBLE("PCM Playback Volume", SB_DSP4_PCM_DEV, (SB_DSP4_PCM_DEV + 1), 3, 3, 31);
+static struct sbmix_elem snd_sb16_ctl_synth_capture_route =
+	SB16_INPUT_SW("Synth Capture Route", SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 6, 5);
+static struct sbmix_elem snd_sb16_ctl_synth_play_vol =
+	SB_DOUBLE("Synth Playback Volume", SB_DSP4_SYNTH_DEV, (SB_DSP4_SYNTH_DEV + 1), 3, 3, 31);
+static struct sbmix_elem snd_sb16_ctl_cd_capture_route =
+	SB16_INPUT_SW("CD Capture Route", SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 2, 1);
+static struct sbmix_elem snd_sb16_ctl_cd_play_switch =
+	SB_DOUBLE("CD Playback Switch", SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 2, 1, 1);
+static struct sbmix_elem snd_sb16_ctl_cd_play_vol =
+	SB_DOUBLE("CD Playback Volume", SB_DSP4_CD_DEV, (SB_DSP4_CD_DEV + 1), 3, 3, 31);
+static struct sbmix_elem snd_sb16_ctl_line_capture_route =
+	SB16_INPUT_SW("Line Capture Route", SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 4, 3);
+static struct sbmix_elem snd_sb16_ctl_line_play_switch =
+	SB_DOUBLE("Line Playback Switch", SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 4, 3, 1);
+static struct sbmix_elem snd_sb16_ctl_line_play_vol =
+	SB_DOUBLE("Line Playback Volume", SB_DSP4_LINE_DEV, (SB_DSP4_LINE_DEV + 1), 3, 3, 31);
+static struct sbmix_elem snd_sb16_ctl_mic_capture_route =
+	SB16_INPUT_SW("Mic Capture Route", SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 0, 0);
+static struct sbmix_elem snd_sb16_ctl_mic_play_switch =
+	SB_SINGLE("Mic Playback Switch", SB_DSP4_OUTPUT_SW, 0, 1);
+static struct sbmix_elem snd_sb16_ctl_mic_play_vol =
+	SB_SINGLE("Mic Playback Volume", SB_DSP4_MIC_DEV, 3, 31);
+static struct sbmix_elem snd_sb16_ctl_pc_speaker_vol =
+	SB_SINGLE("PC Speaker Volume", SB_DSP4_SPEAKER_DEV, 6, 3);
+static struct sbmix_elem snd_sb16_ctl_capture_vol =
+	SB_DOUBLE("Capture Volume", SB_DSP4_IGAIN_DEV, (SB_DSP4_IGAIN_DEV + 1), 6, 6, 3);
+static struct sbmix_elem snd_sb16_ctl_play_vol =
+	SB_DOUBLE("Playback Volume", SB_DSP4_OGAIN_DEV, (SB_DSP4_OGAIN_DEV + 1), 6, 6, 3);
+static struct sbmix_elem snd_sb16_ctl_auto_mic_gain =
+	SB_SINGLE("Mic Auto Gain", SB_DSP4_MIC_AGC, 0, 1);
+
+static struct sbmix_elem *snd_sb16_controls[] = {
+	&snd_sb16_ctl_master_play_vol,
+	&snd_sb16_ctl_3d_enhance_switch,
+	&snd_sb16_ctl_tone_bass,
+	&snd_sb16_ctl_tone_treble,
+	&snd_sb16_ctl_pcm_play_vol,
+	&snd_sb16_ctl_synth_capture_route,
+	&snd_sb16_ctl_synth_play_vol,
+	&snd_sb16_ctl_cd_capture_route,
+	&snd_sb16_ctl_cd_play_switch,
+	&snd_sb16_ctl_cd_play_vol,
+	&snd_sb16_ctl_line_capture_route,
+	&snd_sb16_ctl_line_play_switch,
+	&snd_sb16_ctl_line_play_vol,
+	&snd_sb16_ctl_mic_capture_route,
+	&snd_sb16_ctl_mic_play_switch,
+	&snd_sb16_ctl_mic_play_vol,
+	&snd_sb16_ctl_pc_speaker_vol,
+	&snd_sb16_ctl_capture_vol,
+	&snd_sb16_ctl_play_vol,
+	&snd_sb16_ctl_auto_mic_gain
+};
+
+static unsigned char snd_sb16_init_values[][2] = {
+	{ SB_DSP4_MASTER_DEV + 0, 0 },
+	{ SB_DSP4_MASTER_DEV + 1, 0 },
+	{ SB_DSP4_PCM_DEV + 0, 0 },
+	{ SB_DSP4_PCM_DEV + 1, 0 },
+	{ SB_DSP4_SYNTH_DEV + 0, 0 },
+	{ SB_DSP4_SYNTH_DEV + 1, 0 },
+	{ SB_DSP4_INPUT_LEFT, 0 },
+	{ SB_DSP4_INPUT_RIGHT, 0 },
+	{ SB_DSP4_OUTPUT_SW, 0 },
+	{ SB_DSP4_SPEAKER_DEV, 0 },
+};
+
+/*
+ * DT019x specific mixer elements
+ */
+static struct sbmix_elem snd_dt019x_ctl_master_play_vol =
+	SB_DOUBLE("Master Playback Volume", SB_DT019X_MASTER_DEV, SB_DT019X_MASTER_DEV, 4,0, 15);
+static struct sbmix_elem snd_dt019x_ctl_pcm_play_vol =
+	SB_DOUBLE("PCM Playback Volume", SB_DT019X_PCM_DEV, SB_DT019X_PCM_DEV, 4,0, 15);
+static struct sbmix_elem snd_dt019x_ctl_synth_play_vol =
+	SB_DOUBLE("Synth Playback Volume", SB_DT019X_SYNTH_DEV, SB_DT019X_SYNTH_DEV, 4,0, 15);
+static struct sbmix_elem snd_dt019x_ctl_cd_play_vol =
+	SB_DOUBLE("CD Playback Volume", SB_DT019X_CD_DEV, SB_DT019X_CD_DEV, 4,0, 15);
+static struct sbmix_elem snd_dt019x_ctl_mic_play_vol =
+	SB_SINGLE("Mic Playback Volume", SB_DT019X_MIC_DEV, 4, 7);
+static struct sbmix_elem snd_dt019x_ctl_pc_speaker_vol =
+	SB_SINGLE("PC Speaker Volume", SB_DT019X_SPKR_DEV, 0,  7);
+static struct sbmix_elem snd_dt019x_ctl_line_play_vol =
+	SB_DOUBLE("Line Playback Volume", SB_DT019X_LINE_DEV, SB_DT019X_LINE_DEV, 4,0, 15);
+static struct sbmix_elem snd_dt019x_ctl_pcm_play_switch =
+	SB_DOUBLE("PCM Playback Switch", SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 2,1, 1);
+static struct sbmix_elem snd_dt019x_ctl_synth_play_switch =
+	SB_DOUBLE("Synth Playback Switch", SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 4,3, 1);
+static struct sbmix_elem snd_dt019x_ctl_capture_source =
+	{
+		.name = "Capture Source",
+		.type = SB_MIX_CAPTURE_DT019X
+	};
+
+static struct sbmix_elem *snd_dt019x_controls[] = {
+	&snd_dt019x_ctl_master_play_vol,
+	&snd_dt019x_ctl_pcm_play_vol,
+	&snd_dt019x_ctl_synth_play_vol,
+	&snd_dt019x_ctl_cd_play_vol,
+	&snd_dt019x_ctl_mic_play_vol,
+	&snd_dt019x_ctl_pc_speaker_vol,
+	&snd_dt019x_ctl_line_play_vol,
+	&snd_sb16_ctl_mic_play_switch,
+	&snd_sb16_ctl_cd_play_switch,
+	&snd_sb16_ctl_line_play_switch,
+	&snd_dt019x_ctl_pcm_play_switch,
+	&snd_dt019x_ctl_synth_play_switch,
+	&snd_dt019x_ctl_capture_source
+};
+
+static unsigned char snd_dt019x_init_values[][2] = {
+        { SB_DT019X_MASTER_DEV, 0 },
+        { SB_DT019X_PCM_DEV, 0 },
+        { SB_DT019X_SYNTH_DEV, 0 },
+        { SB_DT019X_CD_DEV, 0 },
+        { SB_DT019X_MIC_DEV, 0 },	/* Includes PC-speaker in high nibble */
+        { SB_DT019X_LINE_DEV, 0 },
+        { SB_DSP4_OUTPUT_SW, 0 },
+        { SB_DT019X_OUTPUT_SW2, 0 },
+        { SB_DT019X_CAPTURE_SW, 0x06 },
+};
+
+/*
+ * ALS4000 specific mixer elements
+ */
+/* FIXME: SB_ALS4000_MONO_IO_CTRL needs output select ctrl ! */
+static struct sbmix_elem snd_als4000_ctl_mono_output_switch =
+	SB_SINGLE("Mono Output Switch", SB_ALS4000_MONO_IO_CTRL, 5, 1);
+/* FIXME: mono input switch also available on DT019X ? */
+static struct sbmix_elem snd_als4000_ctl_mono_input_switch =
+	SB_SINGLE("Mono Input Switch", SB_DT019X_OUTPUT_SW2, 0, 1);
+static struct sbmix_elem snd_als4000_ctl_mic_20db_boost =
+	SB_SINGLE("Mic Boost (+20dB)", SB_ALS4000_MIC_IN_GAIN, 0, 0x03);
+static struct sbmix_elem snd_als4000_ctl_mixer_out_to_in =
+	SB_SINGLE("Mixer Out To In", SB_ALS4000_MIC_IN_GAIN, 7, 0x01);
+/* FIXME: 3D needs much more sophisticated controls, many more features ! */
+static struct sbmix_elem snd_als4000_ctl_3d_output_switch =
+	SB_SINGLE("3D Output Switch", SB_ALS4000_3D_SND_FX, 6, 0x01);
+static struct sbmix_elem snd_als4000_ctl_3d_output_ratio =
+	SB_SINGLE("3D Output Ratio", SB_ALS4000_3D_SND_FX, 0, 0x07);
+static struct sbmix_elem snd_als4000_ctl_3d_poweroff_switch =
+	SB_SINGLE("3D PowerOff Switch", SB_ALS4000_3D_TIME_DELAY, 4, 0x01);
+static struct sbmix_elem snd_als4000_ctl_3d_delay =
+	SB_SINGLE("3D Delay", SB_ALS4000_3D_TIME_DELAY, 0, 0x0f);
+#if NOT_AVAILABLE
+static struct sbmix_elem snd_als4000_ctl_fmdac =
+	SB_SINGLE("FMDAC Switch (Option ?)", SB_ALS4000_FMDAC, 0, 0x01);
+static struct sbmix_elem snd_als4000_ctl_qsound =
+	SB_SINGLE("QSound Mode", SB_ALS4000_QSOUND, 1, 0x1f);
+#endif
+
+static struct sbmix_elem *snd_als4000_controls[] = {
+	&snd_sb16_ctl_master_play_vol,
+	&snd_dt019x_ctl_pcm_play_switch,
+	&snd_sb16_ctl_pcm_play_vol,
+	&snd_sb16_ctl_synth_capture_route,
+	&snd_dt019x_ctl_synth_play_switch,
+	&snd_sb16_ctl_synth_play_vol,
+	&snd_sb16_ctl_cd_capture_route,
+	&snd_sb16_ctl_cd_play_switch,
+	&snd_sb16_ctl_cd_play_vol,
+	&snd_sb16_ctl_line_capture_route,
+	&snd_sb16_ctl_line_play_switch,
+	&snd_sb16_ctl_line_play_vol,
+	&snd_sb16_ctl_mic_capture_route,
+	&snd_als4000_ctl_mic_20db_boost,
+	&snd_sb16_ctl_auto_mic_gain,
+	&snd_sb16_ctl_mic_play_switch,
+	&snd_sb16_ctl_mic_play_vol,
+	&snd_sb16_ctl_pc_speaker_vol,
+	&snd_sb16_ctl_capture_vol,
+	&snd_sb16_ctl_play_vol,
+	&snd_als4000_ctl_mono_output_switch,
+	&snd_als4000_ctl_mono_input_switch,
+	&snd_als4000_ctl_mixer_out_to_in,
+	&snd_als4000_ctl_3d_output_switch,
+	&snd_als4000_ctl_3d_output_ratio,
+	&snd_als4000_ctl_3d_delay,
+	&snd_als4000_ctl_3d_poweroff_switch,
+#if NOT_AVAILABLE
+	&snd_als4000_ctl_fmdac,
+	&snd_als4000_ctl_qsound,
+#endif
+};
+
+static unsigned char snd_als4000_init_values[][2] = {
+	{ SB_DSP4_MASTER_DEV + 0, 0 },
+	{ SB_DSP4_MASTER_DEV + 1, 0 },
+	{ SB_DSP4_PCM_DEV + 0, 0 },
+	{ SB_DSP4_PCM_DEV + 1, 0 },
+	{ SB_DSP4_SYNTH_DEV + 0, 0 },
+	{ SB_DSP4_SYNTH_DEV + 1, 0 },
+	{ SB_DSP4_SPEAKER_DEV, 0 },
+	{ SB_DSP4_OUTPUT_SW, 0 },
+	{ SB_DSP4_INPUT_LEFT, 0 },
+	{ SB_DSP4_INPUT_RIGHT, 0 },
+	{ SB_DT019X_OUTPUT_SW2, 0 },
+	{ SB_ALS4000_MIC_IN_GAIN, 0 },
+};
+
+
+/*
+ */
+static int snd_sbmixer_init(sb_t *chip,
+			    struct sbmix_elem **controls,
+			    int controls_count,
+			    unsigned char map[][2],
+			    int map_count,
+			    char *name)
+{
+	unsigned long flags;
+	snd_card_t *card = chip->card;
+	int idx, err;
+
+	/* mixer reset */
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+	snd_sbmixer_write(chip, 0x00, 0x00);
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+
+	/* mute and zero volume channels */
+	for (idx = 0; idx < map_count; idx++) {
+		spin_lock_irqsave(&chip->mixer_lock, flags);
+		snd_sbmixer_write(chip, map[idx][0], map[idx][1]);
+		spin_unlock_irqrestore(&chip->mixer_lock, flags);
+	}
+
+	for (idx = 0; idx < controls_count; idx++) {
+		if ((err = snd_sbmixer_add_ctl_elem(chip, controls[idx])) < 0)
+			return err;
+	}
+	snd_component_add(card, name);
+	strcpy(card->mixername, name);
+	return 0;
+}
+
+int snd_sbmixer_new(sb_t *chip)
+{
+	snd_card_t * card;
+	int err;
+
+	snd_assert(chip != NULL && chip->card != NULL, return -EINVAL);
+
+	card = chip->card;
+
+	switch (chip->hardware) {
+	case SB_HW_10:
+		return 0; /* no mixer chip on SB1.x */
+	case SB_HW_20:
+	case SB_HW_201:
+		if ((err = snd_sbmixer_init(chip,
+					    snd_sb20_controls,
+					    ARRAY_SIZE(snd_sb20_controls),
+					    snd_sb20_init_values,
+					    ARRAY_SIZE(snd_sb20_init_values),
+					    "CTL1335")) < 0)
+			return err;
+		break;
+	case SB_HW_PRO:
+		if ((err = snd_sbmixer_init(chip,
+					    snd_sbpro_controls,
+					    ARRAY_SIZE(snd_sbpro_controls),
+					    snd_sbpro_init_values,
+					    ARRAY_SIZE(snd_sbpro_init_values),
+					    "CTL1345")) < 0)
+			return err;
+		break;
+	case SB_HW_16:
+	case SB_HW_ALS100:
+		if ((err = snd_sbmixer_init(chip,
+					    snd_sb16_controls,
+					    ARRAY_SIZE(snd_sb16_controls),
+					    snd_sb16_init_values,
+					    ARRAY_SIZE(snd_sb16_init_values),
+					    "CTL1745")) < 0)
+			return err;
+		break;
+	case SB_HW_ALS4000:
+		if ((err = snd_sbmixer_init(chip,
+					    snd_als4000_controls,
+					    ARRAY_SIZE(snd_als4000_controls),
+					    snd_als4000_init_values,
+					    ARRAY_SIZE(snd_als4000_init_values),
+					    "ALS4000")) < 0)
+			return err;
+		break;
+	case SB_HW_DT019X:
+		if ((err = snd_sbmixer_init(chip,
+					    snd_dt019x_controls,
+					    ARRAY_SIZE(snd_dt019x_controls),
+					    snd_dt019x_init_values,
+					    ARRAY_SIZE(snd_dt019x_init_values),
+					    "DT019X")) < 0)
+		break;
+	default:
+		strcpy(card->mixername, "???");
+	}
+	return 0;
+}
diff --git a/sound/isa/sb/sbawe.c b/sound/isa/sb/sbawe.c
new file mode 100644
index 0000000..2ec52a3
--- /dev/null
+++ b/sound/isa/sb/sbawe.c
@@ -0,0 +1,2 @@
+#define SNDRV_SBAWE
+#include "sb16.c"