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/drivers/Kconfig b/sound/drivers/Kconfig
new file mode 100644
index 0000000..3b2bee1
--- /dev/null
+++ b/sound/drivers/Kconfig
@@ -0,0 +1,98 @@
+# ALSA generic drivers
+
+menu "Generic devices"
+	depends on SND!=n
+
+
+config SND_MPU401_UART
+        tristate
+	select SND_TIMER
+        select SND_RAWMIDI
+
+config SND_OPL3_LIB
+	tristate
+	select SND_TIMER
+	select SND_HWDEP
+
+config SND_OPL4_LIB
+	tristate
+	select SND_TIMER
+	select SND_HWDEP
+
+config SND_VX_LIB
+	tristate
+	select SND_HWDEP
+	select SND_PCM
+
+
+config SND_DUMMY
+	tristate "Dummy (/dev/null) soundcard"
+	depends on SND
+	select SND_PCM
+	help
+	  Say Y here to include the dummy driver.  This driver does
+	  nothing, but emulates various mixer controls and PCM devices.
+
+	  You don't need this unless you're testing the hardware support
+	  of programs using the ALSA API.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-dummy.
+
+config SND_VIRMIDI
+	tristate "Virtual MIDI soundcard"
+	depends on SND_SEQUENCER
+	select SND_TIMER
+	select SND_RAWMIDI
+	help
+	  Say Y here to include the virtual MIDI driver.  This driver
+	  allows to connect applications using raw MIDI devices to
+	  sequencer clients.
+
+	  If you don't know what MIDI is, say N here.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-virmidi.
+
+config SND_MTPAV
+	tristate "MOTU MidiTimePiece AV multiport MIDI"
+	depends on SND
+	select SND_TIMER
+	select SND_RAWMIDI
+	help
+	  To use a MOTU MidiTimePiece AV multiport MIDI adapter
+	  connected to the parallel port, say Y here and make sure that
+	  the standard parallel port driver isn't used for the port.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-mtpav.
+
+config SND_SERIAL_U16550
+	tristate "UART16550 serial MIDI driver"
+	depends on SND
+	select SND_TIMER
+	select SND_RAWMIDI
+	help
+	  To include support for MIDI serial port interfaces, say Y here
+	  and read <file:Documentation/sound/alsa/serial-u16550.txt>.
+	  This driver works with serial UARTs 16550 and better.
+
+	  This driver accesses the serial port hardware directly, so
+	  make sure that the standard serial driver isn't used or
+	  deactivated with setserial before loading this driver.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-serial-u16550.
+
+config SND_MPU401
+	tristate "Generic MPU-401 UART driver"
+	depends on SND
+	select SND_MPU401_UART
+	help
+	  Say Y here to include support for MIDI ports compatible with
+	  the Roland MPU-401 interface in UART mode.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-mpu401.
+
+endmenu
diff --git a/sound/drivers/Makefile b/sound/drivers/Makefile
new file mode 100644
index 0000000..cb98c3d
--- /dev/null
+++ b/sound/drivers/Makefile
@@ -0,0 +1,17 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-dummy-objs := dummy.o
+snd-mtpav-objs := mtpav.o
+snd-serial-u16550-objs := serial-u16550.o
+snd-virmidi-objs := virmidi.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_DUMMY) += snd-dummy.o
+obj-$(CONFIG_SND_VIRMIDI) += snd-virmidi.o
+obj-$(CONFIG_SND_SERIAL_U16550) += snd-serial-u16550.o
+obj-$(CONFIG_SND_MTPAV) += snd-mtpav.o
+
+obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/
diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c
new file mode 100644
index 0000000..a61640c
--- /dev/null
+++ b/sound/drivers/dummy.c
@@ -0,0 +1,643 @@
+/*
+ *  Dummy soundcard
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Dummy soundcard (/dev/null)");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ALSA,Dummy soundcard}}");
+
+#define MAX_PCM_DEVICES		4
+#define MAX_PCM_SUBSTREAMS	16
+#define MAX_MIDI_DEVICES	2
+
+#if 0 /* emu10k1 emulation */
+#define MAX_BUFFER_SIZE		(128 * 1024)
+static int emu10k1_playback_constraints(snd_pcm_runtime_t *runtime)
+{
+	int err;
+	if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+		return err;
+	if ((err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 256, UINT_MAX)) < 0)
+		return err;
+	return 0;
+}
+#define add_playback_constraints emu10k1_playback_constraints
+#endif
+
+#if 0 /* RME9652 emulation */
+#define MAX_BUFFER_SIZE		(26 * 64 * 1024)
+#define USE_FORMATS		SNDRV_PCM_FMTBIT_S32_LE
+#define USE_CHANNELS_MIN	26
+#define USE_CHANNELS_MAX	26
+#define USE_PERIODS_MIN		2
+#define USE_PERIODS_MAX		2
+#endif
+
+#if 0 /* ICE1712 emulation */
+#define MAX_BUFFER_SIZE		(256 * 1024)
+#define USE_FORMATS		SNDRV_PCM_FMTBIT_S32_LE
+#define USE_CHANNELS_MIN	10
+#define USE_CHANNELS_MAX	10
+#define USE_PERIODS_MIN		1
+#define USE_PERIODS_MAX		1024
+#endif
+
+#if 0 /* UDA1341 emulation */
+#define MAX_BUFFER_SIZE		(16380)
+#define USE_FORMATS		SNDRV_PCM_FMTBIT_S16_LE
+#define USE_CHANNELS_MIN	2
+#define USE_CHANNELS_MAX	2
+#define USE_PERIODS_MIN		2
+#define USE_PERIODS_MAX		255
+#endif
+
+#if 0 /* simple AC97 bridge (intel8x0) with 48kHz AC97 only codec */
+#define USE_FORMATS		SNDRV_PCM_FMTBIT_S16_LE
+#define USE_CHANNELS_MIN	2
+#define USE_CHANNELS_MAX	2
+#define USE_RATE		SNDRV_PCM_RATE_48000
+#define USE_RATE_MIN		48000
+#define USE_RATE_MAX		48000
+#endif
+
+
+/* defaults */
+#ifndef MAX_BUFFER_SIZE
+#define MAX_BUFFER_SIZE		(64*1024)
+#endif
+#ifndef USE_FORMATS
+#define USE_FORMATS 		(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE)
+#endif
+#ifndef USE_RATE
+#define USE_RATE		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000
+#define USE_RATE_MIN		5500
+#define USE_RATE_MAX		48000
+#endif
+#ifndef USE_CHANNELS_MIN
+#define USE_CHANNELS_MIN 	1
+#endif
+#ifndef USE_CHANNELS_MAX
+#define USE_CHANNELS_MAX 	2
+#endif
+#ifndef USE_PERIODS_MIN
+#define USE_PERIODS_MIN 	1
+#endif
+#ifndef USE_PERIODS_MAX
+#define USE_PERIODS_MAX 	1024
+#endif
+#ifndef add_playback_constraints
+#define add_playback_constraints(x) 0
+#endif
+#ifndef add_capture_constraints
+#define add_capture_constraints(x) 0
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0};
+static int pcm_devs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8};
+//static int midi_devs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for dummy soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for dummy soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable this dummy soundcard.");
+module_param_array(pcm_devs, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_devs, "PCM devices # (0-4) for dummy driver.");
+module_param_array(pcm_substreams, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-16) for dummy driver.");
+//module_param_array(midi_devs, int, NULL, 0444);
+//MODULE_PARM_DESC(midi_devs, "MIDI devices # (0-2) for dummy driver.");
+
+#define MIXER_ADDR_MASTER	0
+#define MIXER_ADDR_LINE		1
+#define MIXER_ADDR_MIC		2
+#define MIXER_ADDR_SYNTH	3
+#define MIXER_ADDR_CD		4
+#define MIXER_ADDR_LAST		4
+
+typedef struct snd_card_dummy {
+	snd_card_t *card;
+	spinlock_t mixer_lock;
+	int mixer_volume[MIXER_ADDR_LAST+1][2];
+	int capture_source[MIXER_ADDR_LAST+1][2];
+} snd_card_dummy_t;
+
+typedef struct snd_card_dummy_pcm {
+	snd_card_dummy_t *dummy;
+	spinlock_t lock;
+	struct timer_list timer;
+	unsigned int pcm_size;
+	unsigned int pcm_count;
+	unsigned int pcm_bps;		/* bytes per second */
+	unsigned int pcm_jiffie;	/* bytes per one jiffie */
+	unsigned int pcm_irq_pos;	/* IRQ position */
+	unsigned int pcm_buf_pos;	/* position in buffer */
+	snd_pcm_substream_t *substream;
+} snd_card_dummy_pcm_t;
+
+static snd_card_t *snd_dummy_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+
+static void snd_card_dummy_pcm_timer_start(snd_pcm_substream_t * substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_card_dummy_pcm_t *dpcm = runtime->private_data;
+
+	dpcm->timer.expires = 1 + jiffies;
+	add_timer(&dpcm->timer);
+}
+
+static void snd_card_dummy_pcm_timer_stop(snd_pcm_substream_t * substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_card_dummy_pcm_t *dpcm = runtime->private_data;
+
+	del_timer(&dpcm->timer);
+}
+
+static int snd_card_dummy_playback_trigger(snd_pcm_substream_t * substream,
+					   int cmd)
+{
+	if (cmd == SNDRV_PCM_TRIGGER_START) {
+		snd_card_dummy_pcm_timer_start(substream);
+	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		snd_card_dummy_pcm_timer_stop(substream);
+	} else {
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int snd_card_dummy_capture_trigger(snd_pcm_substream_t * substream,
+					  int cmd)
+{
+	if (cmd == SNDRV_PCM_TRIGGER_START) {
+		snd_card_dummy_pcm_timer_start(substream);
+	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		snd_card_dummy_pcm_timer_stop(substream);
+	} else {
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int snd_card_dummy_pcm_prepare(snd_pcm_substream_t * substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_card_dummy_pcm_t *dpcm = runtime->private_data;
+	unsigned int bps;
+
+	bps = runtime->rate * runtime->channels;
+	bps *= snd_pcm_format_width(runtime->format);
+	bps /= 8;
+	if (bps <= 0)
+		return -EINVAL;
+	dpcm->pcm_bps = bps;
+	dpcm->pcm_jiffie = bps / HZ;
+	dpcm->pcm_size = snd_pcm_lib_buffer_bytes(substream);
+	dpcm->pcm_count = snd_pcm_lib_period_bytes(substream);
+	dpcm->pcm_irq_pos = 0;
+	dpcm->pcm_buf_pos = 0;
+	return 0;
+}
+
+static int snd_card_dummy_playback_prepare(snd_pcm_substream_t * substream)
+{
+	return snd_card_dummy_pcm_prepare(substream);
+}
+
+static int snd_card_dummy_capture_prepare(snd_pcm_substream_t * substream)
+{
+	return snd_card_dummy_pcm_prepare(substream);
+}
+
+static void snd_card_dummy_pcm_timer_function(unsigned long data)
+{
+	snd_card_dummy_pcm_t *dpcm = (snd_card_dummy_pcm_t *)data;
+	
+	dpcm->timer.expires = 1 + jiffies;
+	add_timer(&dpcm->timer);
+	spin_lock_irq(&dpcm->lock);
+	dpcm->pcm_irq_pos += dpcm->pcm_jiffie;
+	dpcm->pcm_buf_pos += dpcm->pcm_jiffie;
+	dpcm->pcm_buf_pos %= dpcm->pcm_size;
+	if (dpcm->pcm_irq_pos >= dpcm->pcm_count) {
+		dpcm->pcm_irq_pos %= dpcm->pcm_count;
+		snd_pcm_period_elapsed(dpcm->substream);
+	}
+	spin_unlock_irq(&dpcm->lock);	
+}
+
+static snd_pcm_uframes_t snd_card_dummy_playback_pointer(snd_pcm_substream_t * substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_card_dummy_pcm_t *dpcm = runtime->private_data;
+
+	return bytes_to_frames(runtime, dpcm->pcm_buf_pos);
+}
+
+static snd_pcm_uframes_t snd_card_dummy_capture_pointer(snd_pcm_substream_t * substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_card_dummy_pcm_t *dpcm = runtime->private_data;
+
+	return bytes_to_frames(runtime, dpcm->pcm_buf_pos);
+}
+
+static snd_pcm_hardware_t snd_card_dummy_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		USE_FORMATS,
+	.rates =		USE_RATE,
+	.rate_min =		USE_RATE_MIN,
+	.rate_max =		USE_RATE_MAX,
+	.channels_min =		USE_CHANNELS_MIN,
+	.channels_max =		USE_CHANNELS_MAX,
+	.buffer_bytes_max =	MAX_BUFFER_SIZE,
+	.period_bytes_min =	64,
+	.period_bytes_max =	MAX_BUFFER_SIZE,
+	.periods_min =		USE_PERIODS_MIN,
+	.periods_max =		USE_PERIODS_MAX,
+	.fifo_size =		0,
+};
+
+static snd_pcm_hardware_t snd_card_dummy_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		USE_FORMATS,
+	.rates =		USE_RATE,
+	.rate_min =		USE_RATE_MIN,
+	.rate_max =		USE_RATE_MAX,
+	.channels_min =		USE_CHANNELS_MIN,
+	.channels_max =		USE_CHANNELS_MAX,
+	.buffer_bytes_max =	MAX_BUFFER_SIZE,
+	.period_bytes_min =	64,
+	.period_bytes_max =	MAX_BUFFER_SIZE,
+	.periods_min =		USE_PERIODS_MIN,
+	.periods_max =		USE_PERIODS_MAX,
+	.fifo_size =		0,
+};
+
+static void snd_card_dummy_runtime_free(snd_pcm_runtime_t *runtime)
+{
+	snd_card_dummy_pcm_t *dpcm = runtime->private_data;
+	kfree(dpcm);
+}
+
+static int snd_card_dummy_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_card_dummy_hw_free(snd_pcm_substream_t * substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_card_dummy_playback_open(snd_pcm_substream_t * substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_card_dummy_pcm_t *dpcm;
+	int err;
+
+	dpcm = kcalloc(1, sizeof(*dpcm), GFP_KERNEL);
+	if (dpcm == NULL)
+		return -ENOMEM;
+	init_timer(&dpcm->timer);
+	dpcm->timer.data = (unsigned long) dpcm;
+	dpcm->timer.function = snd_card_dummy_pcm_timer_function;
+	spin_lock_init(&dpcm->lock);
+	dpcm->substream = substream;
+	runtime->private_data = dpcm;
+	runtime->private_free = snd_card_dummy_runtime_free;
+	runtime->hw = snd_card_dummy_playback;
+	if (substream->pcm->device & 1) {
+		runtime->hw.info &= ~SNDRV_PCM_INFO_INTERLEAVED;
+		runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED;
+	}
+	if (substream->pcm->device & 2)
+		runtime->hw.info &= ~(SNDRV_PCM_INFO_MMAP|SNDRV_PCM_INFO_MMAP_VALID);
+	if ((err = add_playback_constraints(runtime)) < 0) {
+		kfree(dpcm);
+		return err;
+	}
+
+	return 0;
+}
+
+static int snd_card_dummy_capture_open(snd_pcm_substream_t * substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_card_dummy_pcm_t *dpcm;
+	int err;
+
+	dpcm = kcalloc(1, sizeof(*dpcm), GFP_KERNEL);
+	if (dpcm == NULL)
+		return -ENOMEM;
+	init_timer(&dpcm->timer);
+	dpcm->timer.data = (unsigned long) dpcm;
+	dpcm->timer.function = snd_card_dummy_pcm_timer_function;
+	spin_lock_init(&dpcm->lock);
+	dpcm->substream = substream;
+	runtime->private_data = dpcm;
+	runtime->private_free = snd_card_dummy_runtime_free;
+	runtime->hw = snd_card_dummy_capture;
+	if (substream->pcm->device == 1) {
+		runtime->hw.info &= ~SNDRV_PCM_INFO_INTERLEAVED;
+		runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED;
+	}
+	if (substream->pcm->device & 2)
+		runtime->hw.info &= ~(SNDRV_PCM_INFO_MMAP|SNDRV_PCM_INFO_MMAP_VALID);
+	if ((err = add_capture_constraints(runtime)) < 0) {
+		kfree(dpcm);
+		return err;
+	}
+
+	return 0;
+}
+
+static int snd_card_dummy_playback_close(snd_pcm_substream_t * substream)
+{
+	return 0;
+}
+
+static int snd_card_dummy_capture_close(snd_pcm_substream_t * substream)
+{
+	return 0;
+}
+
+static snd_pcm_ops_t snd_card_dummy_playback_ops = {
+	.open =			snd_card_dummy_playback_open,
+	.close =		snd_card_dummy_playback_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_card_dummy_hw_params,
+	.hw_free =		snd_card_dummy_hw_free,
+	.prepare =		snd_card_dummy_playback_prepare,
+	.trigger =		snd_card_dummy_playback_trigger,
+	.pointer =		snd_card_dummy_playback_pointer,
+};
+
+static snd_pcm_ops_t snd_card_dummy_capture_ops = {
+	.open =			snd_card_dummy_capture_open,
+	.close =		snd_card_dummy_capture_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_card_dummy_hw_params,
+	.hw_free =		snd_card_dummy_hw_free,
+	.prepare =		snd_card_dummy_capture_prepare,
+	.trigger =		snd_card_dummy_capture_trigger,
+	.pointer =		snd_card_dummy_capture_pointer,
+};
+
+static int __init snd_card_dummy_pcm(snd_card_dummy_t *dummy, int device, int substreams)
+{
+	snd_pcm_t *pcm;
+	int err;
+
+	if ((err = snd_pcm_new(dummy->card, "Dummy PCM", device, substreams, substreams, &pcm)) < 0)
+		return err;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_card_dummy_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_card_dummy_capture_ops);
+	pcm->private_data = dummy;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "Dummy PCM");
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+					      snd_dma_continuous_data(GFP_KERNEL),
+					      0, 64*1024);
+	return 0;
+}
+
+#define DUMMY_VOLUME(xname, xindex, addr) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_dummy_volume_info, \
+  .get = snd_dummy_volume_get, .put = snd_dummy_volume_put, \
+  .private_value = addr }
+
+static int snd_dummy_volume_info(snd_kcontrol_t * kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = -50;
+	uinfo->value.integer.max = 100;
+	return 0;
+}
+ 
+static int snd_dummy_volume_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_card_dummy_t *dummy = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int addr = kcontrol->private_value;
+
+	spin_lock_irqsave(&dummy->mixer_lock, flags);
+	ucontrol->value.integer.value[0] = dummy->mixer_volume[addr][0];
+	ucontrol->value.integer.value[1] = dummy->mixer_volume[addr][1];
+	spin_unlock_irqrestore(&dummy->mixer_lock, flags);
+	return 0;
+}
+
+static int snd_dummy_volume_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_card_dummy_t *dummy = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change, addr = kcontrol->private_value;
+	int left, right;
+
+	left = ucontrol->value.integer.value[0];
+	if (left < -50)
+		left = -50;
+	if (left > 100)
+		left = 100;
+	right = ucontrol->value.integer.value[1];
+	if (right < -50)
+		right = -50;
+	if (right > 100)
+		right = 100;
+	spin_lock_irqsave(&dummy->mixer_lock, flags);
+	change = dummy->mixer_volume[addr][0] != left ||
+	         dummy->mixer_volume[addr][1] != right;
+	dummy->mixer_volume[addr][0] = left;
+	dummy->mixer_volume[addr][1] = right;
+	spin_unlock_irqrestore(&dummy->mixer_lock, flags);
+	return change;
+}
+
+#define DUMMY_CAPSRC(xname, xindex, addr) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_dummy_capsrc_info, \
+  .get = snd_dummy_capsrc_get, .put = snd_dummy_capsrc_put, \
+  .private_value = addr }
+
+static int snd_dummy_capsrc_info(snd_kcontrol_t * kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+ 
+static int snd_dummy_capsrc_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_card_dummy_t *dummy = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int addr = kcontrol->private_value;
+
+	spin_lock_irqsave(&dummy->mixer_lock, flags);
+	ucontrol->value.integer.value[0] = dummy->capture_source[addr][0];
+	ucontrol->value.integer.value[1] = dummy->capture_source[addr][1];
+	spin_unlock_irqrestore(&dummy->mixer_lock, flags);
+	return 0;
+}
+
+static int snd_dummy_capsrc_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	snd_card_dummy_t *dummy = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change, addr = kcontrol->private_value;
+	int left, right;
+
+	left = ucontrol->value.integer.value[0] & 1;
+	right = ucontrol->value.integer.value[1] & 1;
+	spin_lock_irqsave(&dummy->mixer_lock, flags);
+	change = dummy->capture_source[addr][0] != left &&
+	         dummy->capture_source[addr][1] != right;
+	dummy->capture_source[addr][0] = left;
+	dummy->capture_source[addr][1] = right;
+	spin_unlock_irqrestore(&dummy->mixer_lock, flags);
+	return change;
+}
+
+static snd_kcontrol_new_t snd_dummy_controls[] = {
+DUMMY_VOLUME("Master Volume", 0, MIXER_ADDR_MASTER),
+DUMMY_CAPSRC("Master Capture Switch", 0, MIXER_ADDR_MASTER),
+DUMMY_VOLUME("Synth Volume", 0, MIXER_ADDR_SYNTH),
+DUMMY_CAPSRC("Synth Capture Switch", 0, MIXER_ADDR_MASTER),
+DUMMY_VOLUME("Line Volume", 0, MIXER_ADDR_LINE),
+DUMMY_CAPSRC("Line Capture Switch", 0, MIXER_ADDR_MASTER),
+DUMMY_VOLUME("Mic Volume", 0, MIXER_ADDR_MIC),
+DUMMY_CAPSRC("Mic Capture Switch", 0, MIXER_ADDR_MASTER),
+DUMMY_VOLUME("CD Volume", 0, MIXER_ADDR_CD),
+DUMMY_CAPSRC("CD Capture Switch", 0, MIXER_ADDR_MASTER)
+};
+
+static int __init snd_card_dummy_new_mixer(snd_card_dummy_t * dummy)
+{
+	snd_card_t *card = dummy->card;
+	unsigned int idx;
+	int err;
+
+	snd_assert(dummy != NULL, return -EINVAL);
+	spin_lock_init(&dummy->mixer_lock);
+	strcpy(card->mixername, "Dummy Mixer");
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_dummy_controls); idx++) {
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_dummy_controls[idx], dummy))) < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int __init snd_card_dummy_probe(int dev)
+{
+	snd_card_t *card;
+	struct snd_card_dummy *dummy;
+	int idx, err;
+
+	if (!enable[dev])
+		return -ENODEV;
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE,
+			    sizeof(struct snd_card_dummy));
+	if (card == NULL)
+		return -ENOMEM;
+	dummy = (struct snd_card_dummy *)card->private_data;
+	dummy->card = card;
+	for (idx = 0; idx < MAX_PCM_DEVICES && idx < pcm_devs[dev]; idx++) {
+		if (pcm_substreams[dev] < 1)
+			pcm_substreams[dev] = 1;
+		if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS)
+			pcm_substreams[dev] = MAX_PCM_SUBSTREAMS;
+		if ((err = snd_card_dummy_pcm(dummy, idx, pcm_substreams[dev])) < 0)
+			goto __nodev;
+	}
+	if ((err = snd_card_dummy_new_mixer(dummy)) < 0)
+		goto __nodev;
+	strcpy(card->driver, "Dummy");
+	strcpy(card->shortname, "Dummy");
+	sprintf(card->longname, "Dummy %i", dev + 1);
+	if ((err = snd_card_register(card)) == 0) {
+		snd_dummy_cards[dev] = card;
+		return 0;
+	}
+      __nodev:
+	snd_card_free(card);
+	return err;
+}
+
+static int __init alsa_card_dummy_init(void)
+{
+	int dev, cards;
+
+	for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev]; dev++) {
+		if (snd_card_dummy_probe(dev) < 0) {
+#ifdef MODULE
+			printk(KERN_ERR "Dummy soundcard #%i not found or device busy\n", dev + 1);
+#endif
+			break;
+		}
+		cards++;
+	}
+	if (!cards) {
+#ifdef MODULE
+		printk(KERN_ERR "Dummy soundcard not found or device busy\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_dummy_exit(void)
+{
+	int idx;
+
+	for (idx = 0; idx < SNDRV_CARDS; idx++)
+		snd_card_free(snd_dummy_cards[idx]);
+}
+
+module_init(alsa_card_dummy_init)
+module_exit(alsa_card_dummy_exit)
diff --git a/sound/drivers/mpu401/Makefile b/sound/drivers/mpu401/Makefile
new file mode 100644
index 0000000..3fe185d
--- /dev/null
+++ b/sound/drivers/mpu401/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-mpu401-objs := mpu401.o
+snd-mpu401-uart-objs := mpu401_uart.o
+
+obj-$(CONFIG_SND_MPU401_UART) += snd-mpu401-uart.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_MPU401) += snd-mpu401.o
diff --git a/sound/drivers/mpu401/mpu401.c b/sound/drivers/mpu401/mpu401.c
new file mode 100644
index 0000000..cb36ecb
--- /dev/null
+++ b/sound/drivers/mpu401/mpu401.c
@@ -0,0 +1,229 @@
+/*
+ *  Driver for generic MPU-401 boards (UART mode only)
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  Copyright (c) 2004 by Castet Matthieu <castet.matthieu@free.fr>
+ *
+ *
+ *   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/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/mpu401.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("MPU-401 UART");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -2}; /* exclude the first card */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
+#ifdef CONFIG_PNP
+static int pnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* MPU-401 port number */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* MPU-401 IRQ */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for MPU-401 device.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for MPU-401 device.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable MPU-401 device.");
+#ifdef CONFIG_PNP
+module_param_array(pnp, bool, NULL, 0444);
+MODULE_PARM_DESC(pnp, "PnP detection for MPU-401 device.");
+#endif
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for MPU-401 device.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for MPU-401 device.");
+
+static snd_card_t *snd_mpu401_legacy_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+static int pnp_registered = 0;
+
+static int snd_mpu401_create(int dev, snd_card_t **rcard)
+{
+	snd_card_t *card;
+	int err;
+
+	*rcard = NULL;
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
+	if (card == NULL)
+		return -ENOMEM;
+	strcpy(card->driver, "MPU-401 UART");
+	strcpy(card->shortname, card->driver);
+	sprintf(card->longname, "%s at %#lx, ", card->shortname, port[dev]);
+	if (irq[dev] >= 0) {
+		sprintf(card->longname + strlen(card->longname), "irq %d", irq[dev]);
+	} else {
+		strcat(card->longname, "polled");
+	}
+
+	if (snd_mpu401_uart_new(card, 0,
+				MPU401_HW_MPU401,
+				port[dev], 0,
+				irq[dev], irq[dev] >= 0 ? SA_INTERRUPT : 0, NULL) < 0) {
+		printk(KERN_ERR "MPU401 not detected at 0x%lx\n", port[dev]);
+		snd_card_free(card);
+		return -ENODEV;
+	}
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	*rcard = card;
+	return 0;
+}
+
+static int __devinit snd_mpu401_probe(int dev)
+{
+	if (port[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR "specify port\n");
+		return -EINVAL;
+	}
+	if (irq[dev] == SNDRV_AUTO_IRQ) {
+		snd_printk(KERN_ERR "specify or disable IRQ\n");
+		return -EINVAL;
+	}
+	return snd_mpu401_create(dev, &snd_mpu401_legacy_cards[dev]);
+}
+
+#ifdef CONFIG_PNP
+
+#define IO_EXTENT 2
+
+static struct pnp_device_id snd_mpu401_pnpids[] = {
+	{ .id = "PNPb006" },
+	{ .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp, snd_mpu401_pnpids);
+
+static int __init snd_mpu401_pnp(int dev, struct pnp_dev *device,
+				 const struct pnp_device_id *id)
+{
+	if (!pnp_port_valid(device, 0) ||
+	    pnp_port_flags(device, 0) & IORESOURCE_DISABLED) {
+		snd_printk(KERN_ERR "no PnP port\n");
+		return -ENODEV;
+	}
+	if (pnp_port_len(device, 0) < IO_EXTENT) {
+		snd_printk(KERN_ERR "PnP port length is %ld, expected %d\n",
+			   pnp_port_len(device, 0), IO_EXTENT);
+		return -ENODEV;
+	}
+	port[dev] = pnp_port_start(device, 0);
+
+	if (!pnp_irq_valid(device, 0) ||
+	    pnp_irq_flags(device, 0) & IORESOURCE_DISABLED) {
+		snd_printk(KERN_WARNING "no PnP irq, using polling\n");
+		irq[dev] = -1;
+	} else {
+		irq[dev] = pnp_irq(device, 0);
+	}
+	return 0;
+}
+
+static int __devinit snd_mpu401_pnp_probe(struct pnp_dev *pnp_dev,
+					  const struct pnp_device_id *id)
+{
+	static int dev;
+	snd_card_t *card;
+	int err;
+
+	for ( ; dev < SNDRV_CARDS; ++dev) {
+		if (!enable[dev] || !pnp[dev])
+			continue;
+		err = snd_mpu401_pnp(dev, pnp_dev, id);
+		if (err < 0)
+			return err;
+		err = snd_mpu401_create(dev, &card);
+		if (err < 0)
+			return err;
+		snd_card_set_dev(card, &pnp_dev->dev);
+		pnp_set_drvdata(pnp_dev, card);
+		++dev;
+		return 0;
+	}
+	return -ENODEV;
+}
+
+static void __devexit snd_mpu401_pnp_remove(struct pnp_dev *dev)
+{
+	snd_card_t *card = (snd_card_t *) pnp_get_drvdata(dev);
+
+	snd_card_disconnect(card);
+	snd_card_free_in_thread(card);
+}
+
+static struct pnp_driver snd_mpu401_pnp_driver = {
+	.name = "mpu401",
+	.id_table = snd_mpu401_pnpids,
+	.probe = snd_mpu401_pnp_probe,
+	.remove = __devexit_p(snd_mpu401_pnp_remove),
+};
+#else
+static struct pnp_driver snd_mpu401_pnp_driver;
+#endif
+
+static int __init alsa_card_mpu401_init(void)
+{
+	int dev, devices = 0;
+	int err;
+
+	for (dev = 0; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev])
+			continue;
+#ifdef CONFIG_PNP
+		if (pnp[dev])
+			continue;
+#endif
+		if (snd_mpu401_probe(dev) >= 0)
+			devices++;
+	}
+	if ((err = pnp_register_driver(&snd_mpu401_pnp_driver)) >= 0) {
+		pnp_registered = 1;
+		devices += err;
+	}
+
+	if (!devices) {
+#ifdef MODULE
+		printk(KERN_ERR "MPU-401 device not found or device busy\n");
+#endif
+		if (pnp_registered)
+			pnp_unregister_driver(&snd_mpu401_pnp_driver);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_mpu401_exit(void)
+{
+	int idx;
+
+	if (pnp_registered)
+		pnp_unregister_driver(&snd_mpu401_pnp_driver);
+	for (idx = 0; idx < SNDRV_CARDS; idx++)
+		snd_card_free(snd_mpu401_legacy_cards[idx]);
+}
+
+module_init(alsa_card_mpu401_init)
+module_exit(alsa_card_mpu401_exit)
diff --git a/sound/drivers/mpu401/mpu401_uart.c b/sound/drivers/mpu401/mpu401_uart.c
new file mode 100644
index 0000000..0f83c52
--- /dev/null
+++ b/sound/drivers/mpu401/mpu401_uart.c
@@ -0,0 +1,541 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *  Routines for control of MPU-401 in UART mode
+ *
+ *  MPU-401 supports UART mode which is not capable generate transmit
+ *  interrupts thus output is done via polling. Also, if irq < 0, then
+ *  input is done also via polling. Do not expect good performance.
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ *   13-03-2003:
+ *      Added support for different kind of hardware I/O. Build in choices
+ *      are port and mmio. For other kind of I/O, set mpu->read and
+ *      mpu->write to your own I/O functions.
+ *
+ */
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <sound/core.h>
+#include <sound/mpu401.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Routines for control of MPU-401 in UART mode");
+MODULE_LICENSE("GPL");
+
+static void snd_mpu401_uart_input_read(mpu401_t * mpu);
+static void snd_mpu401_uart_output_write(mpu401_t * mpu);
+
+/*
+
+ */
+
+#define snd_mpu401_input_avail(mpu)	(!(mpu->read(mpu, MPU401C(mpu)) & 0x80))
+#define snd_mpu401_output_ready(mpu)	(!(mpu->read(mpu, MPU401C(mpu)) & 0x40))
+
+#define MPU401_RESET		0xff
+#define MPU401_ENTER_UART	0x3f
+#define MPU401_ACK		0xfe
+
+/* Build in lowlevel io */
+static void mpu401_write_port(mpu401_t *mpu, unsigned char data, unsigned long addr)
+{
+	outb(data, addr);
+}
+
+static unsigned char mpu401_read_port(mpu401_t *mpu, unsigned long addr)
+{
+	return inb(addr);
+}
+
+static void mpu401_write_mmio(mpu401_t *mpu, unsigned char data, unsigned long addr)
+{
+	writeb(data, (void __iomem *)addr);
+}
+
+static unsigned char mpu401_read_mmio(mpu401_t *mpu, unsigned long addr)
+{
+	return readb((void __iomem *)addr);
+}
+/*  */
+
+static void snd_mpu401_uart_clear_rx(mpu401_t *mpu)
+{
+	int timeout = 100000;
+	for (; timeout > 0 && snd_mpu401_input_avail(mpu); timeout--)
+		mpu->read(mpu, MPU401D(mpu));
+#ifdef CONFIG_SND_DEBUG
+	if (timeout <= 0)
+		snd_printk("cmd: clear rx timeout (status = 0x%x)\n", mpu->read(mpu, MPU401C(mpu)));
+#endif
+}
+
+static void _snd_mpu401_uart_interrupt(mpu401_t *mpu)
+{
+	spin_lock(&mpu->input_lock);
+	if (test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode)) {
+		snd_mpu401_uart_input_read(mpu);
+	} else {
+		snd_mpu401_uart_clear_rx(mpu);
+	}
+	spin_unlock(&mpu->input_lock);
+ 	/* ok. for better Tx performance try do some output when input is done */
+	if (test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode) &&
+	    test_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode)) {
+		spin_lock(&mpu->output_lock);
+		snd_mpu401_uart_output_write(mpu);
+		spin_unlock(&mpu->output_lock);
+	}
+}
+
+/**
+ * snd_mpu401_uart_interrupt - generic MPU401-UART interrupt handler
+ * @irq: the irq number
+ * @dev_id: mpu401 instance
+ * @regs: the reigster
+ *
+ * Processes the interrupt for MPU401-UART i/o.
+ */
+irqreturn_t snd_mpu401_uart_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	mpu401_t *mpu = dev_id;
+	
+	if (mpu == NULL)
+		return IRQ_NONE;
+	_snd_mpu401_uart_interrupt(mpu);
+	return IRQ_HANDLED;
+}
+
+/*
+ * timer callback
+ * reprogram the timer and call the interrupt job
+ */
+static void snd_mpu401_uart_timer(unsigned long data)
+{
+	mpu401_t *mpu = (mpu401_t *)data;
+
+	spin_lock(&mpu->timer_lock);
+	/*mpu->mode |= MPU401_MODE_TIMER;*/
+	mpu->timer.expires = 1 + jiffies;
+	add_timer(&mpu->timer);
+	spin_unlock(&mpu->timer_lock);
+	if (mpu->rmidi)
+		_snd_mpu401_uart_interrupt(mpu);
+}
+
+/*
+ * initialize the timer callback if not programmed yet
+ */
+static void snd_mpu401_uart_add_timer (mpu401_t *mpu, int input)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave (&mpu->timer_lock, flags);
+	if (mpu->timer_invoked == 0) {
+		init_timer(&mpu->timer);
+		mpu->timer.data = (unsigned long)mpu;
+		mpu->timer.function = snd_mpu401_uart_timer;
+		mpu->timer.expires = 1 + jiffies;
+		add_timer(&mpu->timer);
+	} 
+	mpu->timer_invoked |= input ? MPU401_MODE_INPUT_TIMER : MPU401_MODE_OUTPUT_TIMER;
+	spin_unlock_irqrestore (&mpu->timer_lock, flags);
+}
+
+/*
+ * remove the timer callback if still active
+ */
+static void snd_mpu401_uart_remove_timer (mpu401_t *mpu, int input)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave (&mpu->timer_lock, flags);
+	if (mpu->timer_invoked) {
+		mpu->timer_invoked &= input ? ~MPU401_MODE_INPUT_TIMER : ~MPU401_MODE_OUTPUT_TIMER;
+		if (! mpu->timer_invoked)
+			del_timer(&mpu->timer);
+	}
+	spin_unlock_irqrestore (&mpu->timer_lock, flags);
+}
+
+/*
+
+ */
+
+static void snd_mpu401_uart_cmd(mpu401_t * mpu, unsigned char cmd, int ack)
+{
+	unsigned long flags;
+	int timeout, ok;
+
+	spin_lock_irqsave(&mpu->input_lock, flags);
+	if (mpu->hardware != MPU401_HW_TRID4DWAVE) {
+		mpu->write(mpu, 0x00, MPU401D(mpu));
+		/*snd_mpu401_uart_clear_rx(mpu);*/
+	}
+	/* ok. standard MPU-401 initialization */
+	if (mpu->hardware != MPU401_HW_SB) {
+		for (timeout = 1000; timeout > 0 && !snd_mpu401_output_ready(mpu); timeout--)
+			udelay(10);
+#ifdef CONFIG_SND_DEBUG
+		if (!timeout)
+			snd_printk("cmd: tx timeout (status = 0x%x)\n", mpu->read(mpu, MPU401C(mpu)));
+#endif
+	}
+	mpu->write(mpu, cmd, MPU401C(mpu));
+	if (ack) {
+		ok = 0;
+		timeout = 10000;
+		while (!ok && timeout-- > 0) {
+			if (snd_mpu401_input_avail(mpu)) {
+				if (mpu->read(mpu, MPU401D(mpu)) == MPU401_ACK)
+					ok = 1;
+			}
+		}
+		if (!ok && mpu->read(mpu, MPU401D(mpu)) == MPU401_ACK)
+			ok = 1;
+	} else {
+		ok = 1;
+	}
+	spin_unlock_irqrestore(&mpu->input_lock, flags);
+	if (! ok)
+		snd_printk("cmd: 0x%x failed at 0x%lx (status = 0x%x, data = 0x%x)\n", cmd, mpu->port, mpu->read(mpu, MPU401C(mpu)), mpu->read(mpu, MPU401D(mpu)));
+	// snd_printk("cmd: 0x%x at 0x%lx (status = 0x%x, data = 0x%x)\n", cmd, mpu->port, mpu->read(mpu, MPU401C(mpu)), mpu->read(mpu, MPU401D(mpu)));
+}
+
+/*
+ * input/output open/close - protected by open_mutex in rawmidi.c
+ */
+static int snd_mpu401_uart_input_open(snd_rawmidi_substream_t * substream)
+{
+	mpu401_t *mpu;
+	int err;
+
+	mpu = substream->rmidi->private_data;
+	if (mpu->open_input && (err = mpu->open_input(mpu)) < 0)
+		return err;
+	if (! test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode)) {
+		snd_mpu401_uart_cmd(mpu, MPU401_RESET, 1);
+		snd_mpu401_uart_cmd(mpu, MPU401_ENTER_UART, 1);
+	}
+	mpu->substream_input = substream;
+	set_bit(MPU401_MODE_BIT_INPUT, &mpu->mode);
+	return 0;
+}
+
+static int snd_mpu401_uart_output_open(snd_rawmidi_substream_t * substream)
+{
+	mpu401_t *mpu;
+	int err;
+
+	mpu = substream->rmidi->private_data;
+	if (mpu->open_output && (err = mpu->open_output(mpu)) < 0)
+		return err;
+	if (! test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode)) {
+		snd_mpu401_uart_cmd(mpu, MPU401_RESET, 1);
+		snd_mpu401_uart_cmd(mpu, MPU401_ENTER_UART, 1);
+	}
+	mpu->substream_output = substream;
+	set_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode);
+	return 0;
+}
+
+static int snd_mpu401_uart_input_close(snd_rawmidi_substream_t * substream)
+{
+	mpu401_t *mpu;
+
+	mpu = substream->rmidi->private_data;
+	clear_bit(MPU401_MODE_BIT_INPUT, &mpu->mode);
+	mpu->substream_input = NULL;
+	if (! test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode))
+		snd_mpu401_uart_cmd(mpu, MPU401_RESET, 0);
+	if (mpu->close_input)
+		mpu->close_input(mpu);
+	return 0;
+}
+
+static int snd_mpu401_uart_output_close(snd_rawmidi_substream_t * substream)
+{
+	mpu401_t *mpu;
+
+	mpu = substream->rmidi->private_data;
+	clear_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode);
+	mpu->substream_output = NULL;
+	if (! test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode))
+		snd_mpu401_uart_cmd(mpu, MPU401_RESET, 0);
+	if (mpu->close_output)
+		mpu->close_output(mpu);
+	return 0;
+}
+
+/*
+ * trigger input callback
+ */
+static void snd_mpu401_uart_input_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+	unsigned long flags;
+	mpu401_t *mpu;
+	int max = 64;
+
+	mpu = substream->rmidi->private_data;
+	if (up) {
+		if (! test_and_set_bit(MPU401_MODE_BIT_INPUT_TRIGGER, &mpu->mode)) {
+			/* first time - flush FIFO */
+			while (max-- > 0)
+				mpu->read(mpu, MPU401D(mpu));
+			if (mpu->irq < 0)
+				snd_mpu401_uart_add_timer(mpu, 1);
+		}
+		
+		/* read data in advance */
+		spin_lock_irqsave(&mpu->input_lock, flags);
+		snd_mpu401_uart_input_read(mpu);
+		spin_unlock_irqrestore(&mpu->input_lock, flags);
+	} else {
+		if (mpu->irq < 0)
+			snd_mpu401_uart_remove_timer(mpu, 1);
+		clear_bit(MPU401_MODE_BIT_INPUT_TRIGGER, &mpu->mode);
+	}
+}
+
+/*
+ * transfer input pending data
+ * call with input_lock spinlock held
+ */
+static void snd_mpu401_uart_input_read(mpu401_t * mpu)
+{
+	int max = 128;
+	unsigned char byte;
+
+	while (max-- > 0) {
+		if (snd_mpu401_input_avail(mpu)) {
+			byte = mpu->read(mpu, MPU401D(mpu));
+			if (test_bit(MPU401_MODE_BIT_INPUT_TRIGGER, &mpu->mode))
+				snd_rawmidi_receive(mpu->substream_input, &byte, 1);
+		} else {
+			break; /* input not available */
+		}
+	}
+}
+
+/*
+ *  Tx FIFO sizes:
+ *    CS4237B			- 16 bytes
+ *    AudioDrive ES1688         - 12 bytes
+ *    S3 SonicVibes             -  8 bytes
+ *    SoundBlaster AWE 64       -  2 bytes (ugly hardware)
+ */
+
+/*
+ * write output pending bytes
+ * call with output_lock spinlock held
+ */
+static void snd_mpu401_uart_output_write(mpu401_t * mpu)
+{
+	unsigned char byte;
+	int max = 256, timeout;
+
+	do {
+		if (snd_rawmidi_transmit_peek(mpu->substream_output, &byte, 1) == 1) {
+			for (timeout = 100; timeout > 0; timeout--) {
+				if (snd_mpu401_output_ready(mpu)) {
+					mpu->write(mpu, byte, MPU401D(mpu));
+					snd_rawmidi_transmit_ack(mpu->substream_output, 1);
+					break;
+				}
+			}
+			if (timeout == 0)
+				break;	/* Tx FIFO full - try again later */
+		} else {
+			snd_mpu401_uart_remove_timer (mpu, 0);
+			break;	/* no other data - leave the tx loop */
+		}
+	} while (--max > 0);
+}
+
+/*
+ * output trigger callback
+ */
+static void snd_mpu401_uart_output_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+	unsigned long flags;
+	mpu401_t *mpu;
+
+	mpu = substream->rmidi->private_data;
+	if (up) {
+		set_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode);
+
+		/* try to add the timer at each output trigger,
+		 * since the output timer might have been removed in
+		 * snd_mpu401_uart_output_write().
+		 */
+		snd_mpu401_uart_add_timer(mpu, 0);
+
+		/* output pending data */
+		spin_lock_irqsave(&mpu->output_lock, flags);
+		snd_mpu401_uart_output_write(mpu);
+		spin_unlock_irqrestore(&mpu->output_lock, flags);
+	} else {
+		snd_mpu401_uart_remove_timer(mpu, 0);
+		clear_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode);
+	}
+}
+
+/*
+
+ */
+
+static snd_rawmidi_ops_t snd_mpu401_uart_output =
+{
+	.open =		snd_mpu401_uart_output_open,
+	.close =	snd_mpu401_uart_output_close,
+	.trigger =	snd_mpu401_uart_output_trigger,
+};
+
+static snd_rawmidi_ops_t snd_mpu401_uart_input =
+{
+	.open =		snd_mpu401_uart_input_open,
+	.close =	snd_mpu401_uart_input_close,
+	.trigger =	snd_mpu401_uart_input_trigger,
+};
+
+static void snd_mpu401_uart_free(snd_rawmidi_t *rmidi)
+{
+	mpu401_t *mpu = rmidi->private_data;
+	if (mpu->irq_flags && mpu->irq >= 0)
+		free_irq(mpu->irq, (void *) mpu);
+	if (mpu->res) {
+		release_resource(mpu->res);
+		kfree_nocheck(mpu->res);
+	}
+	kfree(mpu);
+}
+
+/**
+ * snd_mpu401_uart_new - create an MPU401-UART instance
+ * @card: the card instance
+ * @device: the device index, zero-based
+ * @hardware: the hardware type, MPU401_HW_XXXX
+ * @port: the base address of MPU401 port
+ * @integrated: non-zero if the port was already reserved by the chip
+ * @irq: the irq number, -1 if no interrupt for mpu
+ * @irq_flags: the irq request flags (SA_XXX), 0 if irq was already reserved.
+ * @rrawmidi: the pointer to store the new rawmidi instance
+ *
+ * Creates a new MPU-401 instance.
+ *
+ * Note that the rawmidi instance is returned on the rrawmidi argument,
+ * not the mpu401 instance itself.  To access to the mpu401 instance,
+ * cast from rawmidi->private_data (with mpu401_t magic-cast).
+ *
+ * Returns zero if successful, or a negative error code.
+ */
+int snd_mpu401_uart_new(snd_card_t * card, int device,
+			unsigned short hardware,
+			unsigned long port, int integrated,
+			int irq, int irq_flags,
+			snd_rawmidi_t ** rrawmidi)
+{
+	mpu401_t *mpu;
+	snd_rawmidi_t *rmidi;
+	int err;
+
+	if (rrawmidi)
+		*rrawmidi = NULL;
+	if ((err = snd_rawmidi_new(card, "MPU-401U", device, 1, 1, &rmidi)) < 0)
+		return err;
+	mpu = kcalloc(1, sizeof(*mpu), GFP_KERNEL);
+	if (mpu == NULL) {
+		snd_device_free(card, rmidi);
+		return -ENOMEM;
+	}
+	rmidi->private_data = mpu;
+	rmidi->private_free = snd_mpu401_uart_free;
+	spin_lock_init(&mpu->input_lock);
+	spin_lock_init(&mpu->output_lock);
+	spin_lock_init(&mpu->timer_lock);
+	mpu->hardware = hardware;
+	if (!integrated) {
+		int res_size = hardware == MPU401_HW_PC98II ? 4 : 2;
+		if ((mpu->res = request_region(port, res_size, "MPU401 UART")) == NULL) {
+			snd_printk(KERN_ERR "mpu401_uart: unable to grab port 0x%lx size %d\n", port, res_size);
+			snd_device_free(card, rmidi);
+			return -EBUSY;
+		}
+	}
+	switch (hardware) {
+	case MPU401_HW_AUREAL:
+		mpu->write = mpu401_write_mmio;
+		mpu->read = mpu401_read_mmio;
+		break;
+	default:
+		mpu->write = mpu401_write_port;
+		mpu->read = mpu401_read_port;
+		break;
+	}
+	mpu->port = port;
+	if (hardware == MPU401_HW_PC98II)
+		mpu->cport = port + 2;
+	else
+		mpu->cport = port + 1;
+	if (irq >= 0 && irq_flags) {
+		if (request_irq(irq, snd_mpu401_uart_interrupt, irq_flags, "MPU401 UART", (void *) mpu)) {
+			snd_printk(KERN_ERR "mpu401_uart: unable to grab IRQ %d\n", irq);
+			snd_device_free(card, rmidi);
+			return -EBUSY;
+		}
+	}
+	mpu->irq = irq;
+	mpu->irq_flags = irq_flags;
+	if (card->shortname[0])
+		snprintf(rmidi->name, sizeof(rmidi->name), "%s MIDI", card->shortname);
+	else
+		sprintf(rmidi->name, "MPU-401 MIDI %d-%d", card->number, device);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_mpu401_uart_output);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_mpu401_uart_input);
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT |
+	                     SNDRV_RAWMIDI_INFO_INPUT |
+	                     SNDRV_RAWMIDI_INFO_DUPLEX;
+	mpu->rmidi = rmidi;
+	if (rrawmidi)
+		*rrawmidi = rmidi;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_mpu401_uart_interrupt);
+EXPORT_SYMBOL(snd_mpu401_uart_new);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_mpu401_uart_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_mpu401_uart_exit(void)
+{
+}
+
+module_init(alsa_mpu401_uart_init)
+module_exit(alsa_mpu401_uart_exit)
diff --git a/sound/drivers/mtpav.c b/sound/drivers/mtpav.c
new file mode 100644
index 0000000..1280a57
--- /dev/null
+++ b/sound/drivers/mtpav.c
@@ -0,0 +1,795 @@
+/*
+ *      MOTU Midi Timepiece ALSA Main routines
+ *      Copyright by Michael T. Mayers (c) Jan 09, 2000
+ *      mail: michael@tweakoz.com
+ *      Thanks to John Galbraith
+ *
+ *      This program is free software; you can redistribute it and/or modify
+ *      it under the terms of the GNU General Public License as published by
+ *      the Free Software Foundation; either version 2 of the License, or
+ *      (at your option) any later version.
+ *
+ *      This program is distributed in the hope that it will be useful,
+ *      but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *      GNU General Public License for more details.
+ *
+ *      You should have received a copy of the GNU General Public License
+ *      along with this program; if not, write to the Free Software
+ *      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ *
+ *      This driver is for the 'Mark Of The Unicorn' (MOTU)
+ *      MidiTimePiece AV multiport MIDI interface 
+ *
+ *      IOPORTS
+ *      -------
+ *      8 MIDI Ins and 8 MIDI outs
+ *      Video Sync In (BNC), Word Sync Out (BNC), 
+ *      ADAT Sync Out (DB9)
+ *      SMPTE in/out (1/4")
+ *      2 programmable pedal/footswitch inputs and 4 programmable MIDI controller knobs.
+ *      Macintosh RS422 serial port
+ *      RS422 "network" port for ganging multiple MTP's
+ *      PC Parallel Port ( which this driver currently uses )
+ *
+ *      MISC FEATURES
+ *      -------------
+ *      Hardware MIDI routing, merging, and filtering   
+ *      MIDI Synchronization to Video, ADAT, SMPTE and other Clock sources
+ *      128 'scene' memories, recallable from MIDI program change
+ *
+ *
+ * ChangeLog
+ * Jun 11 2001	Takashi Iwai <tiwai@suse.de>
+ *      - Recoded & debugged
+ *      - Added timer interrupt for midi outputs
+ *      - hwports is between 1 and 8, which specifies the number of hardware ports.
+ *        The three global ports, computer, adat and broadcast ports, are created
+ *        always after h/w and remote ports.
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+#include <linux/delay.h>
+
+#include <asm/io.h>
+
+/*
+ *      globals
+ */
+MODULE_AUTHOR("Michael T. Mayers");
+MODULE_DESCRIPTION("MOTU MidiTimePiece AV multiport MIDI");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{MOTU,MidiTimePiece AV multiport MIDI}}");
+
+// io resources
+#define MTPAV_IOBASE		0x378
+#define MTPAV_IRQ		7
+#define MTPAV_MAX_PORTS		8
+
+static int index = SNDRV_DEFAULT_IDX1;
+static char *id = SNDRV_DEFAULT_STR1;
+static long port = MTPAV_IOBASE;	/* 0x378, 0x278 */
+static int irq = MTPAV_IRQ;		/* 7, 5 */
+static int hwports = MTPAV_MAX_PORTS;	/* use hardware ports 1-8 */
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for MotuMTPAV MIDI.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for MotuMTPAV MIDI.");
+module_param(port, long, 0444);
+MODULE_PARM_DESC(port, "Parallel port # for MotuMTPAV MIDI.");
+module_param(irq, int, 0444);
+MODULE_PARM_DESC(irq, "Parallel IRQ # for MotuMTPAV MIDI.");
+module_param(hwports, int, 0444);
+MODULE_PARM_DESC(hwports, "Hardware ports # for MotuMTPAV MIDI.");
+
+/*
+ *      defines
+ */
+//#define USE_FAKE_MTP //       don't actually read/write to MTP device (for debugging without an actual unit) (does not work yet)
+
+// parallel port usage masks
+#define SIGS_BYTE 0x08
+#define SIGS_RFD 0x80
+#define SIGS_IRQ 0x40
+#define SIGS_IN0 0x10
+#define SIGS_IN1 0x20
+
+#define SIGC_WRITE 0x04
+#define SIGC_READ 0x08
+#define SIGC_INTEN 0x10
+
+#define DREG 0
+#define SREG 1
+#define CREG 2
+
+//
+#define MTPAV_MODE_INPUT_OPENED		0x01
+#define MTPAV_MODE_OUTPUT_OPENED	0x02
+#define MTPAV_MODE_INPUT_TRIGGERED	0x04
+#define MTPAV_MODE_OUTPUT_TRIGGERED	0x08
+
+#define NUMPORTS (0x12+1)
+
+
+/*
+ */
+
+typedef struct mtpav_port {
+	u8 number;
+	u8 hwport;
+	u8 mode;
+	u8 running_status;
+	snd_rawmidi_substream_t *input;
+	snd_rawmidi_substream_t *output;
+} mtpav_port_t;
+
+typedef struct mtpav {
+	snd_card_t *card;
+	unsigned long port;
+	struct resource *res_port;
+	int irq;			/* interrupt (for inputs) */
+	spinlock_t spinlock;
+	int share_irq;			/* number of accesses to input interrupts */
+	int istimer;			/* number of accesses to timer interrupts */
+	struct timer_list timer;	/* timer interrupts for outputs */
+	snd_rawmidi_t *rmidi;
+	int num_ports;		/* number of hw ports (1-8) */
+	mtpav_port_t ports[NUMPORTS];	/* all ports including computer, adat and bc */
+
+	u32 inmidiport;		/* selected input midi port */
+	u32 inmidistate;	/* during midi command 0xf5 */
+
+	u32 outmidihwport;	/* selected output midi hw port */
+} mtpav_t;
+
+
+/*
+ * global instance
+ * hey, we handle at most only one card..
+ */
+static mtpav_t *mtp_card;
+
+/*
+ * possible hardware ports (selected by 0xf5 port message)
+ *      0x00		all ports
+ *      0x01 .. 0x08    this MTP's ports 1..8
+ *      0x09 .. 0x10    networked MTP's ports (9..16)
+ *      0x11            networked MTP's computer port
+ *      0x63            to ADAT
+ *
+ * mappig:
+ *  subdevice 0 - (X-1)    ports
+ *            X - (2*X-1)  networked ports
+ *            X            computer
+ *            X+1          ADAT
+ *            X+2          all ports
+ *
+ *  where X = chip->num_ports
+ */
+
+#define MTPAV_PIDX_COMPUTER	0
+#define MTPAV_PIDX_ADAT		1
+#define MTPAV_PIDX_BROADCAST	2
+
+
+static int translate_subdevice_to_hwport(mtpav_t *chip, int subdev)
+{
+	if (subdev < 0)
+		return 0x01; /* invalid - use port 0 as default */
+	else if (subdev < chip->num_ports)
+		return subdev + 1; /* single mtp port */
+	else if (subdev < chip->num_ports * 2)
+		return subdev - chip->num_ports + 0x09; /* remote port */
+	else if (subdev == chip->num_ports * 2 + MTPAV_PIDX_COMPUTER)
+		return 0x11; /* computer port */
+	else if (subdev == chip->num_ports + MTPAV_PIDX_ADAT)
+		return 0x63;		/* ADAT */
+	return 0; /* all ports */
+}
+
+static int translate_hwport_to_subdevice(mtpav_t *chip, int hwport)
+{
+	int p;
+	if (hwport <= 0x00) /* all ports */
+		return chip->num_ports + MTPAV_PIDX_BROADCAST;
+	else if (hwport <= 0x08) { /* single port */
+		p = hwport - 1;
+		if (p >= chip->num_ports)
+			p = 0;
+		return p;
+	} else if (hwport <= 0x10) { /* remote port */
+		p = hwport - 0x09 + chip->num_ports;
+		if (p >= chip->num_ports * 2)
+			p = chip->num_ports;
+		return p;
+	} else if (hwport == 0x11)  /* computer port */
+		return chip->num_ports + MTPAV_PIDX_COMPUTER;
+	else  /* ADAT */
+		return chip->num_ports + MTPAV_PIDX_ADAT;
+}
+
+
+/*
+ */
+
+static u8 snd_mtpav_getreg(mtpav_t *chip, u16 reg)
+{
+	u8 rval = 0;
+
+	if (reg == SREG) {
+		rval = inb(chip->port + SREG);
+		rval = (rval & 0xf8);
+	} else if (reg == CREG) {
+		rval = inb(chip->port + CREG);
+		rval = (rval & 0x1c);
+	}
+
+	return rval;
+}
+
+/*
+ */
+
+static void snd_mtpav_mputreg(mtpav_t *chip, u16 reg, u8 val)
+{
+	if (reg == DREG) {
+		outb(val, chip->port + DREG);
+	} else if (reg == CREG) {
+		outb(val, chip->port + CREG);
+	}
+}
+
+/*
+ */
+
+static void snd_mtpav_wait_rfdhi(mtpav_t *chip)
+{
+	int counts = 10000;
+	u8 sbyte;
+
+	sbyte = snd_mtpav_getreg(chip, SREG);
+	while (!(sbyte & SIGS_RFD) && counts--) {
+		sbyte = snd_mtpav_getreg(chip, SREG);
+		udelay(10);
+	}
+}
+
+static void snd_mtpav_send_byte(mtpav_t *chip, u8 byte)
+{
+	u8 tcbyt;
+	u8 clrwrite;
+	u8 setwrite;
+
+	snd_mtpav_wait_rfdhi(chip);
+
+	/////////////////
+
+	tcbyt = snd_mtpav_getreg(chip, CREG);
+	clrwrite = tcbyt & (SIGC_WRITE ^ 0xff);
+	setwrite = tcbyt | SIGC_WRITE;
+
+	snd_mtpav_mputreg(chip, DREG, byte);
+	snd_mtpav_mputreg(chip, CREG, clrwrite);	// clear write bit
+
+	snd_mtpav_mputreg(chip, CREG, setwrite);	// set write bit
+
+}
+
+
+/*
+ */
+
+/* call this with spin lock held */
+static void snd_mtpav_output_port_write(mtpav_port_t *port,
+					snd_rawmidi_substream_t *substream)
+{
+	u8 outbyte;
+
+	// Get the outbyte first, so we can emulate running status if
+	// necessary
+	if (snd_rawmidi_transmit(substream, &outbyte, 1) != 1)
+		return;
+
+	// send port change command if necessary
+
+	if (port->hwport != mtp_card->outmidihwport) {
+		mtp_card->outmidihwport = port->hwport;
+
+		snd_mtpav_send_byte(mtp_card, 0xf5);
+		snd_mtpav_send_byte(mtp_card, port->hwport);
+		//snd_printk("new outport: 0x%x\n", (unsigned int) port->hwport);
+
+		if (!(outbyte & 0x80) && port->running_status)
+			snd_mtpav_send_byte(mtp_card, port->running_status);
+	}
+
+	// send data
+
+	do {
+		if (outbyte & 0x80)
+			port->running_status = outbyte;
+		
+		snd_mtpav_send_byte(mtp_card, outbyte);
+	} while (snd_rawmidi_transmit(substream, &outbyte, 1) == 1);
+}
+
+static void snd_mtpav_output_write(snd_rawmidi_substream_t * substream)
+{
+	mtpav_port_t *port = &mtp_card->ports[substream->number];
+	unsigned long flags;
+
+	spin_lock_irqsave(&mtp_card->spinlock, flags);
+	snd_mtpav_output_port_write(port, substream);
+	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
+}
+
+
+/*
+ *      mtpav control
+ */
+
+static void snd_mtpav_portscan(mtpav_t *chip)	// put mtp into smart routing mode
+{
+	u8 p;
+
+	for (p = 0; p < 8; p++) {
+		snd_mtpav_send_byte(chip, 0xf5);
+		snd_mtpav_send_byte(chip, p);
+		snd_mtpav_send_byte(chip, 0xfe);
+	}
+}
+
+/*
+ */
+
+static int snd_mtpav_input_open(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	mtpav_port_t *portp = &mtp_card->ports[substream->number];
+
+	//printk("mtpav port: %d opened\n", (int) substream->number);
+	spin_lock_irqsave(&mtp_card->spinlock, flags);
+	portp->mode |= MTPAV_MODE_INPUT_OPENED;
+	portp->input = substream;
+	if (mtp_card->share_irq++ == 0)
+		snd_mtpav_mputreg(mtp_card, CREG, (SIGC_INTEN | SIGC_WRITE));	// enable pport interrupts
+	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
+	return 0;
+}
+
+/*
+ */
+
+static int snd_mtpav_input_close(snd_rawmidi_substream_t *substream)
+{
+	unsigned long flags;
+	mtpav_port_t *portp = &mtp_card->ports[substream->number];
+
+	//printk("mtpav port: %d closed\n", (int) portp);
+
+	spin_lock_irqsave(&mtp_card->spinlock, flags);
+
+	portp->mode &= (~MTPAV_MODE_INPUT_OPENED);
+	portp->input = NULL;
+	if (--mtp_card->share_irq == 0)
+		snd_mtpav_mputreg(mtp_card, CREG, 0);	// disable pport interrupts
+
+	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
+	return 0;
+}
+
+/*
+ */
+
+static void snd_mtpav_input_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+	unsigned long flags;
+	mtpav_port_t *portp = &mtp_card->ports[substream->number];
+
+	spin_lock_irqsave(&mtp_card->spinlock, flags);
+	if (up)
+		portp->mode |= MTPAV_MODE_INPUT_TRIGGERED;
+	else
+		portp->mode &= ~MTPAV_MODE_INPUT_TRIGGERED;
+	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
+
+}
+
+
+/*
+ * timer interrupt for outputs
+ */
+
+static void snd_mtpav_output_timer(unsigned long data)
+{
+	unsigned long flags;
+	mtpav_t *chip = (mtpav_t *)data;
+	int p;
+
+	spin_lock_irqsave(&chip->spinlock, flags);
+	/* reprogram timer */
+	chip->timer.expires = 1 + jiffies;
+	add_timer(&chip->timer);
+	/* process each port */
+	for (p = 0; p <= chip->num_ports * 2 + MTPAV_PIDX_BROADCAST; p++) {
+		mtpav_port_t *portp = &mtp_card->ports[p];
+		if ((portp->mode & MTPAV_MODE_OUTPUT_TRIGGERED) && portp->output)
+			snd_mtpav_output_port_write(portp, portp->output);
+	}
+	spin_unlock_irqrestore(&chip->spinlock, flags);
+}
+
+/* spinlock held! */
+static void snd_mtpav_add_output_timer(mtpav_t *chip)
+{
+	init_timer(&chip->timer);
+	chip->timer.function = snd_mtpav_output_timer;
+	chip->timer.data = (unsigned long) mtp_card;
+	chip->timer.expires = 1 + jiffies;
+	add_timer(&chip->timer);
+}
+
+/* spinlock held! */
+static void snd_mtpav_remove_output_timer(mtpav_t *chip)
+{
+	del_timer(&chip->timer);
+}
+
+/*
+ */
+
+static int snd_mtpav_output_open(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	mtpav_port_t *portp = &mtp_card->ports[substream->number];
+
+	spin_lock_irqsave(&mtp_card->spinlock, flags);
+	portp->mode |= MTPAV_MODE_OUTPUT_OPENED;
+	portp->output = substream;
+	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
+	return 0;
+};
+
+/*
+ */
+
+static int snd_mtpav_output_close(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	mtpav_port_t *portp = &mtp_card->ports[substream->number];
+
+	spin_lock_irqsave(&mtp_card->spinlock, flags);
+	portp->mode &= (~MTPAV_MODE_OUTPUT_OPENED);
+	portp->output = NULL;
+	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
+	return 0;
+};
+
+/*
+ */
+
+static void snd_mtpav_output_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+	unsigned long flags;
+	mtpav_port_t *portp = &mtp_card->ports[substream->number];
+
+	spin_lock_irqsave(&mtp_card->spinlock, flags);
+	if (up) {
+		if (! (portp->mode  & MTPAV_MODE_OUTPUT_TRIGGERED)) {
+			if (mtp_card->istimer++ == 0)
+				snd_mtpav_add_output_timer(mtp_card);
+			portp->mode |= MTPAV_MODE_OUTPUT_TRIGGERED;
+		}
+	} else {
+		portp->mode &= ~MTPAV_MODE_OUTPUT_TRIGGERED;
+		if (--mtp_card->istimer == 0)
+			snd_mtpav_remove_output_timer(mtp_card);
+	}
+	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
+
+	if (up)
+		snd_mtpav_output_write(substream);
+}
+
+/*
+ * midi interrupt for inputs
+ */
+
+static void snd_mtpav_inmidi_process(mtpav_t *mcrd, u8 inbyte)
+{
+	mtpav_port_t *portp;
+
+	if ((int)mcrd->inmidiport > mcrd->num_ports * 2 + MTPAV_PIDX_BROADCAST)
+		return;
+
+	portp = &mcrd->ports[mcrd->inmidiport];
+	if (portp->mode & MTPAV_MODE_INPUT_TRIGGERED) {
+		snd_rawmidi_receive(portp->input, &inbyte, 1);
+	}
+}
+
+static void snd_mtpav_inmidi_h(mtpav_t * mcrd, u8 inbyte)
+{
+	snd_assert(mcrd, return);
+
+	if (inbyte >= 0xf8) {
+		/* real-time midi code */
+		snd_mtpav_inmidi_process(mcrd, inbyte);
+		return;
+	}
+
+	if (mcrd->inmidistate == 0) {	// awaiting command
+		if (inbyte == 0xf5)	// MTP port #
+			mcrd->inmidistate = 1;
+		else
+			snd_mtpav_inmidi_process(mcrd, inbyte);
+	} else if (mcrd->inmidistate) {
+		mcrd->inmidiport = translate_hwport_to_subdevice(mcrd, inbyte);
+		mcrd->inmidistate = 0;
+	}
+}
+
+static void snd_mtpav_read_bytes(mtpav_t * mcrd)
+{
+	u8 clrread, setread;
+	u8 mtp_read_byte;
+	u8 sr, cbyt;
+	int i;
+
+	u8 sbyt = snd_mtpav_getreg(mcrd, SREG);
+
+	//printk("snd_mtpav_read_bytes() sbyt: 0x%x\n", sbyt);
+
+	if (!(sbyt & SIGS_BYTE))
+		return;
+
+	cbyt = snd_mtpav_getreg(mcrd, CREG);
+	clrread = cbyt & (SIGC_READ ^ 0xff);
+	setread = cbyt | SIGC_READ;
+
+	do {
+
+		mtp_read_byte = 0;
+		for (i = 0; i < 4; i++) {
+			snd_mtpav_mputreg(mcrd, CREG, setread);
+			sr = snd_mtpav_getreg(mcrd, SREG);
+			snd_mtpav_mputreg(mcrd, CREG, clrread);
+
+			sr &= SIGS_IN0 | SIGS_IN1;
+			sr >>= 4;
+			mtp_read_byte |= sr << (i * 2);
+		}
+
+		snd_mtpav_inmidi_h(mcrd, mtp_read_byte);
+
+		sbyt = snd_mtpav_getreg(mcrd, SREG);
+
+	} while (sbyt & SIGS_BYTE);
+}
+
+static irqreturn_t snd_mtpav_irqh(int irq, void *dev_id, struct pt_regs *regs)
+{
+	mtpav_t *mcard = dev_id;
+
+	//printk("irqh()\n");
+	spin_lock(&mcard->spinlock);
+	snd_mtpav_read_bytes(mcard);
+	spin_unlock(&mcard->spinlock);
+	return IRQ_HANDLED;
+}
+
+/*
+ * get ISA resources
+ */
+static int snd_mtpav_get_ISA(mtpav_t * mcard)
+{
+	if ((mcard->res_port = request_region(port, 3, "MotuMTPAV MIDI")) == NULL) {
+		snd_printk("MTVAP port 0x%lx is busy\n", port);
+		return -EBUSY;
+	}
+	mcard->port = port;
+	if (request_irq(irq, snd_mtpav_irqh, SA_INTERRUPT, "MOTU MTPAV", (void *)mcard)) {
+		snd_printk("MTVAP IRQ %d busy\n", irq);
+		return -EBUSY;
+	}
+	mcard->irq = irq;
+	return 0;
+}
+
+
+/*
+ */
+
+static snd_rawmidi_ops_t snd_mtpav_output = {
+	.open =		snd_mtpav_output_open,
+	.close =	snd_mtpav_output_close,
+	.trigger =	snd_mtpav_output_trigger,
+};
+
+static snd_rawmidi_ops_t snd_mtpav_input = {
+	.open =		snd_mtpav_input_open,
+	.close =	snd_mtpav_input_close,
+	.trigger =	snd_mtpav_input_trigger,
+};
+
+
+/*
+ * get RAWMIDI resources
+ */
+
+static void snd_mtpav_set_name(mtpav_t *chip, snd_rawmidi_substream_t *substream)
+{
+	if (substream->number >= 0 && substream->number < chip->num_ports)
+		sprintf(substream->name, "MTP direct %d", (substream->number % chip->num_ports) + 1);
+	else if (substream->number >= 8 && substream->number < chip->num_ports * 2)
+		sprintf(substream->name, "MTP remote %d", (substream->number % chip->num_ports) + 1);
+	else if (substream->number == chip->num_ports * 2)
+		strcpy(substream->name, "MTP computer");
+	else if (substream->number == chip->num_ports * 2 + 1)
+		strcpy(substream->name, "MTP ADAT");
+	else
+		strcpy(substream->name, "MTP broadcast");
+}
+
+static int snd_mtpav_get_RAWMIDI(mtpav_t * mcard)
+{
+	int rval = 0;
+	snd_rawmidi_t *rawmidi;
+	snd_rawmidi_substream_t *substream;
+	struct list_head *list;
+
+	//printk("entering snd_mtpav_get_RAWMIDI\n");
+
+	if (hwports < 1)
+		mcard->num_ports = 1;
+	else if (hwports > 8)
+		mcard->num_ports = 8;
+	else
+		mcard->num_ports = hwports;
+
+	if ((rval = snd_rawmidi_new(mcard->card, "MotuMIDI", 0,
+				    mcard->num_ports * 2 + MTPAV_PIDX_BROADCAST + 1,
+				    mcard->num_ports * 2 + MTPAV_PIDX_BROADCAST + 1,
+				    &mcard->rmidi)) < 0)
+		return rval;
+	rawmidi = mcard->rmidi;
+
+	list_for_each(list, &rawmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams) {
+		substream = list_entry(list, snd_rawmidi_substream_t, list);
+		snd_mtpav_set_name(mcard, substream);
+		substream->ops = &snd_mtpav_input;
+	}
+	list_for_each(list, &rawmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams) {
+		substream = list_entry(list, snd_rawmidi_substream_t, list);
+		snd_mtpav_set_name(mcard, substream);
+		substream->ops = &snd_mtpav_output;
+		mcard->ports[substream->number].hwport = translate_subdevice_to_hwport(mcard, substream->number);
+	}
+	rawmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT |
+			       SNDRV_RAWMIDI_INFO_DUPLEX;
+	sprintf(rawmidi->name, "MTP AV MIDI");
+	//printk("exiting snd_mtpav_get_RAWMIDI() \n");
+	return 0;
+}
+
+/*
+ */
+
+static mtpav_t *new_mtpav(void)
+{
+	mtpav_t *ncrd = kcalloc(1, sizeof(*ncrd), GFP_KERNEL);
+	if (ncrd != NULL) {
+		spin_lock_init(&ncrd->spinlock);
+
+		init_timer(&ncrd->timer);
+		ncrd->card = NULL;
+		ncrd->irq = -1;
+		ncrd->share_irq = 0;
+
+		ncrd->inmidiport = 0xffffffff;
+		ncrd->inmidistate = 0;
+		ncrd->outmidihwport = 0xffffffff;
+	}
+	return ncrd;
+}
+
+/*
+ */
+
+static void free_mtpav(mtpav_t * crd)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&crd->spinlock, flags);
+	if (crd->istimer > 0)
+		snd_mtpav_remove_output_timer(crd);
+	spin_unlock_irqrestore(&crd->spinlock, flags);
+	if (crd->irq >= 0)
+		free_irq(crd->irq, (void *)crd);
+	if (crd->res_port) {
+		release_resource(crd->res_port);
+		kfree_nocheck(crd->res_port);
+	}
+	kfree(crd);
+}
+
+/*
+ */
+
+static int __init alsa_card_mtpav_init(void)
+{
+	int err = 0;
+	char longname_buffer[80];
+
+	mtp_card = new_mtpav();
+	if (mtp_card == NULL)
+		return -ENOMEM;
+
+	mtp_card->card = snd_card_new(index, id, THIS_MODULE, 0);
+	if (mtp_card->card == NULL) {
+		free_mtpav(mtp_card);
+		return -ENOMEM;
+	}
+
+	err = snd_mtpav_get_ISA(mtp_card);
+	//printk("snd_mtpav_get_ISA returned: %d\n", err);
+	if (err < 0)
+		goto __error;
+
+	strcpy(mtp_card->card->driver, "MTPAV");
+	strcpy(mtp_card->card->shortname, "MTPAV on parallel port");
+	memset(longname_buffer, 0, sizeof(longname_buffer));
+	sprintf(longname_buffer, "MTPAV on parallel port at");
+
+	err = snd_mtpav_get_RAWMIDI(mtp_card);
+	//snd_printk("snd_mtapv_get_RAWMIDI returned: %d\n", err);
+	if (err < 0)
+		goto __error;
+
+	err = snd_card_register(mtp_card->card);	// don't snd_card_register until AFTER all cards reources done!
+
+	//printk("snd_card_register returned %d\n", err);
+	if (err < 0)
+		goto __error;
+
+
+	snd_mtpav_portscan(mtp_card);
+
+	printk(KERN_INFO "Motu MidiTimePiece on parallel port irq: %d ioport: 0x%lx\n", irq, port);
+
+	return 0;
+
+      __error:
+	snd_card_free(mtp_card->card);
+	free_mtpav(mtp_card);
+	return err;
+}
+
+/*
+ */
+
+static void __exit alsa_card_mtpav_exit(void)
+{
+	if (mtp_card == NULL)
+		return;
+	if (mtp_card->card)
+		snd_card_free(mtp_card->card);
+	free_mtpav(mtp_card);
+}
+
+/*
+ */
+
+module_init(alsa_card_mtpav_init)
+module_exit(alsa_card_mtpav_exit)
diff --git a/sound/drivers/opl3/Makefile b/sound/drivers/opl3/Makefile
new file mode 100644
index 0000000..1205978
--- /dev/null
+++ b/sound/drivers/opl3/Makefile
@@ -0,0 +1,22 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-opl3-lib-objs := opl3_lib.o opl3_synth.o
+snd-opl3-synth-objs := opl3_seq.o opl3_midi.o opl3_drums.o
+ifeq ($(CONFIG_SND_SEQUENCER_OSS),y)
+snd-opl3-synth-objs += opl3_oss.o
+endif
+
+#
+# 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)))
+
+obj-$(CONFIG_SND_OPL3_LIB) += snd-opl3-lib.o
+obj-$(CONFIG_SND_OPL4_LIB) += snd-opl3-lib.o
+obj-$(call sequencer,$(CONFIG_SND_OPL3_LIB)) += snd-opl3-synth.o
diff --git a/sound/drivers/opl3/opl3_drums.c b/sound/drivers/opl3/opl3_drums.c
new file mode 100644
index 0000000..f263326
--- /dev/null
+++ b/sound/drivers/opl3/opl3_drums.c
@@ -0,0 +1,223 @@
+/*
+ *  Copyright (c) by Uros Bizjak <uros@kss-loka.si>
+ *
+ *   OPL2/OPL3/OPL4 FM routines for internal percussion channels
+ *
+ *   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 "opl3_voice.h"
+
+extern char snd_opl3_regmap[MAX_OPL2_VOICES][4];
+
+static char snd_opl3_drum_table[47] =
+{
+	OPL3_BASSDRUM_ON,  OPL3_BASSDRUM_ON,  OPL3_HIHAT_ON,	/* 35 - 37 */
+	OPL3_SNAREDRUM_ON, OPL3_HIHAT_ON,     OPL3_SNAREDRUM_ON, /* 38 - 40 */
+	OPL3_BASSDRUM_ON,  OPL3_HIHAT_ON,     OPL3_BASSDRUM_ON,	/* 41 - 43 */
+	OPL3_HIHAT_ON,     OPL3_TOMTOM_ON,    OPL3_HIHAT_ON,	/* 44 - 46 */
+	OPL3_TOMTOM_ON,    OPL3_TOMTOM_ON,    OPL3_CYMBAL_ON,	/* 47 - 49 */
+
+	OPL3_TOMTOM_ON,    OPL3_CYMBAL_ON,    OPL3_CYMBAL_ON,	/* 50 - 52 */
+	OPL3_CYMBAL_ON,    OPL3_CYMBAL_ON,    OPL3_CYMBAL_ON,	/* 53 - 55 */
+	OPL3_HIHAT_ON,     OPL3_CYMBAL_ON,    OPL3_TOMTOM_ON,	/* 56 - 58 */
+	OPL3_CYMBAL_ON,    OPL3_TOMTOM_ON,    OPL3_TOMTOM_ON,	/* 59 - 61 */
+	OPL3_HIHAT_ON,     OPL3_TOMTOM_ON,    OPL3_TOMTOM_ON,	/* 62 - 64 */
+
+	OPL3_TOMTOM_ON,    OPL3_TOMTOM_ON,    OPL3_TOMTOM_ON,	/* 65 - 67 */
+	OPL3_TOMTOM_ON,    OPL3_HIHAT_ON,     OPL3_HIHAT_ON,	/* 68 - 70 */
+	OPL3_HIHAT_ON,     OPL3_HIHAT_ON,     OPL3_TOMTOM_ON,	/* 71 - 73 */
+	OPL3_TOMTOM_ON,    OPL3_TOMTOM_ON,    OPL3_TOMTOM_ON,	/* 74 - 76 */
+	OPL3_TOMTOM_ON,    OPL3_TOMTOM_ON,    OPL3_TOMTOM_ON,	/* 77 - 79 */
+	OPL3_CYMBAL_ON,    OPL3_CYMBAL_ON			/* 80 - 81 */
+};
+
+typedef struct snd_opl3_drum_voice {
+	int voice;
+	int op;
+	unsigned char am_vib;
+	unsigned char ksl_level;
+	unsigned char attack_decay;
+	unsigned char sustain_release;
+	unsigned char feedback_connection;
+	unsigned char wave_select;
+} snd_opl3_drum_voice_t;
+
+typedef struct snd_opl3_drum_note {
+	int voice;
+	unsigned char fnum;
+	unsigned char octave_f;
+	unsigned char feedback_connection;
+} snd_opl3_drum_note_t;
+
+static snd_opl3_drum_voice_t bass_op0 = {6, 0, 0x00, 0x32, 0xf8, 0x66, 0x30, 0x00};
+static snd_opl3_drum_voice_t bass_op1 = {6, 1, 0x00, 0x03, 0xf6, 0x57, 0x30, 0x00};
+static snd_opl3_drum_note_t bass_note = {6, 0x90, 0x09};
+
+static snd_opl3_drum_voice_t hihat = {7, 0, 0x00, 0x03, 0xf0, 0x06, 0x20, 0x00};
+
+static snd_opl3_drum_voice_t snare = {7, 1, 0x00, 0x03, 0xf0, 0x07, 0x20, 0x02};
+static snd_opl3_drum_note_t snare_note = {7, 0xf4, 0x0d};
+
+static snd_opl3_drum_voice_t tomtom = {8, 0, 0x02, 0x03, 0xf0, 0x06, 0x10, 0x00};
+static snd_opl3_drum_note_t tomtom_note = {8, 0xf4, 0x09};
+
+static snd_opl3_drum_voice_t cymbal = {8, 1, 0x04, 0x03, 0xf0, 0x06, 0x10, 0x00};
+
+/*
+ * set drum voice characteristics
+ */
+static void snd_opl3_drum_voice_set(opl3_t *opl3, snd_opl3_drum_voice_t *data)
+{
+	unsigned char op_offset = snd_opl3_regmap[data->voice][data->op];
+	unsigned char voice_offset = data->voice;
+	unsigned short opl3_reg;
+	
+	/* Set OPL3 AM_VIB register */ 
+	opl3_reg = OPL3_LEFT | (OPL3_REG_AM_VIB + op_offset);
+	opl3->command(opl3, opl3_reg, data->am_vib);
+
+	/* Set OPL3 KSL_LEVEL register */ 
+	opl3_reg = OPL3_LEFT | (OPL3_REG_KSL_LEVEL + op_offset);
+	opl3->command(opl3, opl3_reg, data->ksl_level);
+
+	/* Set OPL3 ATTACK_DECAY register */ 
+	opl3_reg = OPL3_LEFT | (OPL3_REG_ATTACK_DECAY + op_offset);
+	opl3->command(opl3, opl3_reg, data->attack_decay);
+
+	/* Set OPL3 SUSTAIN_RELEASE register */ 
+	opl3_reg = OPL3_LEFT | (OPL3_REG_SUSTAIN_RELEASE + op_offset);
+	opl3->command(opl3, opl3_reg, data->sustain_release);
+
+	/* Set OPL3 FEEDBACK_CONNECTION register */ 
+	opl3_reg = OPL3_LEFT | (OPL3_REG_FEEDBACK_CONNECTION + voice_offset);
+	opl3->command(opl3, opl3_reg, data->feedback_connection);
+
+	/* Select waveform */
+	opl3_reg = OPL3_LEFT | (OPL3_REG_WAVE_SELECT + op_offset);
+	opl3->command(opl3, opl3_reg, data->wave_select);
+}
+
+/*
+ * Set drum voice pitch
+ */
+static void snd_opl3_drum_note_set(opl3_t *opl3, snd_opl3_drum_note_t *data)
+{
+	unsigned char voice_offset = data->voice;
+	unsigned short opl3_reg;
+
+	/* Set OPL3 FNUM_LOW register */ 
+	opl3_reg = OPL3_LEFT | (OPL3_REG_FNUM_LOW + voice_offset);
+	opl3->command(opl3, opl3_reg, data->fnum);
+
+	/* Set OPL3 KEYON_BLOCK register */ 
+	opl3_reg = OPL3_LEFT | (OPL3_REG_KEYON_BLOCK + voice_offset);
+	opl3->command(opl3, opl3_reg, data->octave_f);
+}
+
+/*
+ * Set drum voice volume and position
+ */
+static void snd_opl3_drum_vol_set(opl3_t *opl3, snd_opl3_drum_voice_t *data,
+				  int vel, snd_midi_channel_t *chan)
+{
+	unsigned char op_offset = snd_opl3_regmap[data->voice][data->op];
+	unsigned char voice_offset = data->voice;
+	unsigned char reg_val;
+	unsigned short opl3_reg;
+
+	/* Set OPL3 KSL_LEVEL register */ 
+	reg_val = data->ksl_level;
+	snd_opl3_calc_volume(&reg_val, vel, chan);
+	opl3_reg = OPL3_LEFT | (OPL3_REG_KSL_LEVEL + op_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+
+	/* Set OPL3 FEEDBACK_CONNECTION register */ 
+	/* Set output voice connection */
+	reg_val = data->feedback_connection | OPL3_STEREO_BITS;
+	if (chan->gm_pan < 43)
+		reg_val &= ~OPL3_VOICE_TO_RIGHT;
+	if (chan->gm_pan > 85)
+		reg_val &= ~OPL3_VOICE_TO_LEFT;
+	opl3_reg = OPL3_LEFT | (OPL3_REG_FEEDBACK_CONNECTION + voice_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+}
+
+/*
+ * Loads drum voices at init time
+ */
+void snd_opl3_load_drums(opl3_t *opl3)
+{
+	snd_opl3_drum_voice_set(opl3, &bass_op0);
+	snd_opl3_drum_voice_set(opl3, &bass_op1);
+	snd_opl3_drum_note_set(opl3, &bass_note);
+
+	snd_opl3_drum_voice_set(opl3, &hihat);
+
+	snd_opl3_drum_voice_set(opl3, &snare);
+	snd_opl3_drum_note_set(opl3, &snare_note);
+
+	snd_opl3_drum_voice_set(opl3, &tomtom);
+	snd_opl3_drum_note_set(opl3, &tomtom_note);
+
+	snd_opl3_drum_voice_set(opl3, &cymbal);
+}
+
+/*
+ * Switch drum voice on or off
+ */
+void snd_opl3_drum_switch(opl3_t *opl3, int note, int vel, int on_off,
+			  snd_midi_channel_t *chan)
+{
+	unsigned char drum_mask;
+	snd_opl3_drum_voice_t *drum_voice;
+
+	if (!(opl3->drum_reg & OPL3_PERCUSSION_ENABLE))
+		return;
+
+	if ((note < 35) || (note > 81))
+		return;
+	drum_mask = snd_opl3_drum_table[note - 35];
+
+	if (on_off) {
+		switch (drum_mask) {
+		case OPL3_BASSDRUM_ON:
+			drum_voice = &bass_op1;
+			break;
+		case OPL3_HIHAT_ON:
+			drum_voice = &hihat;
+			break;
+		case OPL3_SNAREDRUM_ON:
+			drum_voice = &snare;
+			break;
+		case OPL3_TOMTOM_ON:
+			drum_voice = &tomtom;
+			break;
+		case OPL3_CYMBAL_ON:
+			drum_voice = &cymbal;
+			break;
+		default:
+			drum_voice = &tomtom;
+		}
+
+		snd_opl3_drum_vol_set(opl3, drum_voice, vel, chan);
+		opl3->drum_reg |= drum_mask;
+	} else {
+		opl3->drum_reg &= ~drum_mask;
+	}
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION,
+			 opl3->drum_reg);
+}
diff --git a/sound/drivers/opl3/opl3_lib.c b/sound/drivers/opl3/opl3_lib.c
new file mode 100644
index 0000000..c313e52
--- /dev/null
+++ b/sound/drivers/opl3/opl3_lib.c
@@ -0,0 +1,558 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>,
+ *                   Hannu Savolainen 1993-1996,
+ *                   Rob Hooft
+ *                   
+ *  Routines for control of AdLib FM cards (OPL2/OPL3/OPL4 chips)
+ *
+ *  Most if code is ported from OSS/Lite.
+ *
+ *   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/opl3.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <sound/minors.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>, Hannu Savolainen 1993-1996, Rob Hooft");
+MODULE_DESCRIPTION("Routines for control of AdLib FM cards (OPL2/OPL3/OPL4 chips)");
+MODULE_LICENSE("GPL");
+
+extern char snd_opl3_regmap[MAX_OPL2_VOICES][4];
+
+static void snd_opl2_command(opl3_t * opl3, unsigned short cmd, unsigned char val)
+{
+	unsigned long flags;
+	unsigned long port;
+
+	/*
+	 * The original 2-OP synth requires a quite long delay
+	 * after writing to a register.
+	 */
+
+	port = (cmd & OPL3_RIGHT) ? opl3->r_port : opl3->l_port;
+
+	spin_lock_irqsave(&opl3->reg_lock, flags);
+
+	outb((unsigned char) cmd, port);
+	udelay(10);
+
+	outb((unsigned char) val, port + 1);
+	udelay(30);
+
+	spin_unlock_irqrestore(&opl3->reg_lock, flags);
+}
+
+static void snd_opl3_command(opl3_t * opl3, unsigned short cmd, unsigned char val)
+{
+	unsigned long flags;
+	unsigned long port;
+
+	/*
+	 * The OPL-3 survives with just two INBs
+	 * after writing to a register.
+	 */
+
+	port = (cmd & OPL3_RIGHT) ? opl3->r_port : opl3->l_port;
+
+	spin_lock_irqsave(&opl3->reg_lock, flags);
+
+	outb((unsigned char) cmd, port);
+	inb(opl3->l_port);
+	inb(opl3->l_port);
+
+	outb((unsigned char) val, port + 1);
+	inb(opl3->l_port);
+	inb(opl3->l_port);
+
+	spin_unlock_irqrestore(&opl3->reg_lock, flags);
+}
+
+static int snd_opl3_detect(opl3_t * opl3)
+{
+	/*
+	 * This function returns 1 if the FM chip is present at the given I/O port
+	 * The detection algorithm plays with the timer built in the FM chip and
+	 * looks for a change in the status register.
+	 *
+	 * Note! The timers of the FM chip are not connected to AdLib (and compatible)
+	 * boards.
+	 *
+	 * Note2! The chip is initialized if detected.
+	 */
+
+	unsigned char stat1, stat2, signature;
+
+	/* Reset timers 1 and 2 */
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER1_MASK | OPL3_TIMER2_MASK);
+	/* Reset the IRQ of the FM chip */
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_IRQ_RESET);
+	signature = stat1 = inb(opl3->l_port);	/* Status register */
+	if ((stat1 & 0xe0) != 0x00) {	/* Should be 0x00 */
+		snd_printd("OPL3: stat1 = 0x%x\n", stat1);
+		return -ENODEV;
+	}
+	/* Set timer1 to 0xff */
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER1, 0xff);
+	/* Unmask and start timer 1 */
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER2_MASK | OPL3_TIMER1_START);
+	/* Now we have to delay at least 80us */
+	udelay(200);
+	/* Read status after timers have expired */
+	stat2 = inb(opl3->l_port);
+	/* Stop the timers */
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER1_MASK | OPL3_TIMER2_MASK);
+	/* Reset the IRQ of the FM chip */
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_IRQ_RESET);
+	if ((stat2 & 0xe0) != 0xc0) {	/* There is no YM3812 */
+		snd_printd("OPL3: stat2 = 0x%x\n", stat2);
+		return -ENODEV;
+	}
+
+	/* If the toplevel code knows exactly the type of chip, don't try
+	   to detect it. */
+	if (opl3->hardware != OPL3_HW_AUTO)
+		return 0;
+
+	/* There is a FM chip on this address. Detect the type (OPL2 to OPL4) */
+	if (signature == 0x06) {	/* OPL2 */
+		opl3->hardware = OPL3_HW_OPL2;
+	} else {
+		/*
+		 * If we had an OPL4 chip, opl3->hardware would have been set
+		 * by the OPL4 driver; so we can assume OPL3 here.
+		 */
+		snd_assert(opl3->r_port != 0, return -ENODEV);
+		opl3->hardware = OPL3_HW_OPL3;
+	}
+	return 0;
+}
+
+/*
+ *  AdLib timers
+ */
+
+/*
+ *  Timer 1 - 80us
+ */
+
+static int snd_opl3_timer1_start(snd_timer_t * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	unsigned int ticks;
+	opl3_t *opl3;
+
+	opl3 = snd_timer_chip(timer);
+	spin_lock_irqsave(&opl3->timer_lock, flags);
+	ticks = timer->sticks;
+	tmp = (opl3->timer_enable | OPL3_TIMER1_START) & ~OPL3_TIMER1_MASK;
+	opl3->timer_enable = tmp;
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER1, 256 - ticks);	/* timer 1 count */
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* enable timer 1 IRQ */
+	spin_unlock_irqrestore(&opl3->timer_lock, flags);
+	return 0;
+}
+
+static int snd_opl3_timer1_stop(snd_timer_t * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	opl3_t *opl3;
+
+	opl3 = snd_timer_chip(timer);
+	spin_lock_irqsave(&opl3->timer_lock, flags);
+	tmp = (opl3->timer_enable | OPL3_TIMER1_MASK) & ~OPL3_TIMER1_START;
+	opl3->timer_enable = tmp;
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* disable timer #1 */
+	spin_unlock_irqrestore(&opl3->timer_lock, flags);
+	return 0;
+}
+
+/*
+ *  Timer 2 - 320us
+ */
+
+static int snd_opl3_timer2_start(snd_timer_t * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	unsigned int ticks;
+	opl3_t *opl3;
+
+	opl3 = snd_timer_chip(timer);
+	spin_lock_irqsave(&opl3->timer_lock, flags);
+	ticks = timer->sticks;
+	tmp = (opl3->timer_enable | OPL3_TIMER2_START) & ~OPL3_TIMER2_MASK;
+	opl3->timer_enable = tmp;
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER2, 256 - ticks);	/* timer 1 count */
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* enable timer 1 IRQ */
+	spin_unlock_irqrestore(&opl3->timer_lock, flags);
+	return 0;
+}
+
+static int snd_opl3_timer2_stop(snd_timer_t * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	opl3_t *opl3;
+
+	opl3 = snd_timer_chip(timer);
+	spin_lock_irqsave(&opl3->timer_lock, flags);
+	tmp = (opl3->timer_enable | OPL3_TIMER2_MASK) & ~OPL3_TIMER2_START;
+	opl3->timer_enable = tmp;
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* disable timer #1 */
+	spin_unlock_irqrestore(&opl3->timer_lock, flags);
+	return 0;
+}
+
+/*
+
+ */
+
+static struct _snd_timer_hardware snd_opl3_timer1 =
+{
+	.flags =	SNDRV_TIMER_HW_STOP,
+	.resolution =	80000,
+	.ticks =	256,
+	.start =	snd_opl3_timer1_start,
+	.stop =		snd_opl3_timer1_stop,
+};
+
+static struct _snd_timer_hardware snd_opl3_timer2 =
+{
+	.flags =	SNDRV_TIMER_HW_STOP,
+	.resolution =	320000,
+	.ticks =	256,
+	.start =	snd_opl3_timer2_start,
+	.stop =		snd_opl3_timer2_stop,
+};
+
+static int snd_opl3_timer1_init(opl3_t * opl3, int timer_no)
+{
+	snd_timer_t *timer = NULL;
+	snd_timer_id_t tid;
+	int err;
+
+	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
+	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+	tid.card = opl3->card->number;
+	tid.device = timer_no;
+	tid.subdevice = 0;
+	if ((err = snd_timer_new(opl3->card, "AdLib timer #1", &tid, &timer)) >= 0) {
+		strcpy(timer->name, "AdLib timer #1");
+		timer->private_data = opl3;
+		timer->hw = snd_opl3_timer1;
+	}
+	opl3->timer1 = timer;
+	return err;
+}
+
+static int snd_opl3_timer2_init(opl3_t * opl3, int timer_no)
+{
+	snd_timer_t *timer = NULL;
+	snd_timer_id_t tid;
+	int err;
+
+	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
+	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+	tid.card = opl3->card->number;
+	tid.device = timer_no;
+	tid.subdevice = 0;
+	if ((err = snd_timer_new(opl3->card, "AdLib timer #2", &tid, &timer)) >= 0) {
+		strcpy(timer->name, "AdLib timer #2");
+		timer->private_data = opl3;
+		timer->hw = snd_opl3_timer2;
+	}
+	opl3->timer2 = timer;
+	return err;
+}
+
+/*
+
+ */
+
+void snd_opl3_interrupt(snd_hwdep_t * hw)
+{
+	unsigned char status;
+	opl3_t *opl3;
+	snd_timer_t *timer;
+
+	if (hw == NULL)
+		return;
+
+	opl3 = hw->private_data;
+	status = inb(opl3->l_port);
+#if 0
+	snd_printk("AdLib IRQ status = 0x%x\n", status);
+#endif
+	if (!(status & 0x80))
+		return;
+
+	if (status & 0x40) {
+		timer = opl3->timer1;
+		snd_timer_interrupt(timer, timer->sticks);
+	}
+	if (status & 0x20) {
+		timer = opl3->timer2;
+		snd_timer_interrupt(timer, timer->sticks);
+	}
+}
+
+/*
+
+ */
+
+static int snd_opl3_free(opl3_t *opl3)
+{
+	snd_assert(opl3 != NULL, return -ENXIO);
+	if (opl3->private_free)
+		opl3->private_free(opl3);
+	if (opl3->res_l_port) {
+		release_resource(opl3->res_l_port);
+		kfree_nocheck(opl3->res_l_port);
+	}
+	if (opl3->res_r_port) {
+		release_resource(opl3->res_r_port);
+		kfree_nocheck(opl3->res_r_port);
+	}
+	kfree(opl3);
+	return 0;
+}
+
+static int snd_opl3_dev_free(snd_device_t *device)
+{
+	opl3_t *opl3 = device->device_data;
+	return snd_opl3_free(opl3);
+}
+
+int snd_opl3_new(snd_card_t *card,
+		 unsigned short hardware,
+		 opl3_t **ropl3)
+{
+	static snd_device_ops_t ops = {
+		.dev_free = snd_opl3_dev_free,
+	};
+	opl3_t *opl3;
+	int err;
+
+	*ropl3 = NULL;
+	opl3 = kcalloc(1, sizeof(*opl3), GFP_KERNEL);
+	if (opl3 == NULL)
+		return -ENOMEM;
+
+	opl3->card = card;
+	opl3->hardware = hardware;
+	spin_lock_init(&opl3->reg_lock);
+	spin_lock_init(&opl3->timer_lock);
+	init_MUTEX(&opl3->access_mutex);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_CODEC, opl3, &ops)) < 0) {
+		snd_opl3_free(opl3);
+		return err;
+	}
+
+	*ropl3 = opl3;
+	return 0;
+}
+
+int snd_opl3_init(opl3_t *opl3)
+{
+	if (! opl3->command) {
+		printk(KERN_ERR "snd_opl3_init: command not defined!\n");
+		return -EINVAL;
+	}
+
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TEST, OPL3_ENABLE_WAVE_SELECT);
+	/* Melodic mode */
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, 0x00);
+
+	switch (opl3->hardware & OPL3_HW_MASK) {
+	case OPL3_HW_OPL2:
+		opl3->max_voices = MAX_OPL2_VOICES;
+		break;
+	case OPL3_HW_OPL3:
+	case OPL3_HW_OPL4:
+		opl3->max_voices = MAX_OPL3_VOICES;
+		/* Enter OPL3 mode */
+		opl3->command(opl3, OPL3_RIGHT | OPL3_REG_MODE, OPL3_OPL3_ENABLE);
+	}
+	return 0;
+}
+
+int snd_opl3_create(snd_card_t * card,
+		    unsigned long l_port,
+		    unsigned long r_port,
+		    unsigned short hardware,
+		    int integrated,
+		    opl3_t ** ropl3)
+{
+	opl3_t *opl3;
+	int err;
+
+	*ropl3 = NULL;
+	if ((err = snd_opl3_new(card, hardware, &opl3)) < 0)
+		return err;
+	if (! integrated) {
+		if ((opl3->res_l_port = request_region(l_port, 2, "OPL2/3 (left)")) == NULL) {
+			snd_printk(KERN_ERR "opl3: can't grab left port 0x%lx\n", l_port);
+			snd_opl3_free(opl3);
+			return -EBUSY;
+		}
+		if (r_port != 0 &&
+		    (opl3->res_r_port = request_region(r_port, 2, "OPL2/3 (right)")) == NULL) {
+			snd_printk(KERN_ERR "opl3: can't grab right port 0x%lx\n", r_port);
+			snd_opl3_free(opl3);
+			return -EBUSY;
+		}
+	}
+	opl3->l_port = l_port;
+	opl3->r_port = r_port;
+
+	switch (opl3->hardware) {
+	/* some hardware doesn't support timers */
+	case OPL3_HW_OPL3_SV:
+	case OPL3_HW_OPL3_CS:
+	case OPL3_HW_OPL3_FM801:
+		opl3->command = &snd_opl3_command;
+		break;
+	default:
+		opl3->command = &snd_opl2_command;
+		if ((err = snd_opl3_detect(opl3)) < 0) {
+			snd_printd("OPL2/3 chip not detected at 0x%lx/0x%lx\n",
+				   opl3->l_port, opl3->r_port);
+			snd_opl3_free(opl3);
+			return err;
+		}
+		/* detect routine returns correct hardware type */
+		switch (opl3->hardware & OPL3_HW_MASK) {
+		case OPL3_HW_OPL3:
+		case OPL3_HW_OPL4:
+			opl3->command = &snd_opl3_command;
+		}
+	}
+
+	snd_opl3_init(opl3);
+
+	*ropl3 = opl3;
+	return 0;
+}
+
+int snd_opl3_timer_new(opl3_t * opl3, int timer1_dev, int timer2_dev)
+{
+	int err;
+
+	if (timer1_dev >= 0)
+		if ((err = snd_opl3_timer1_init(opl3, timer1_dev)) < 0)
+			return err;
+	if (timer2_dev >= 0) {
+		if ((err = snd_opl3_timer2_init(opl3, timer2_dev)) < 0) {
+			snd_device_free(opl3->card, opl3->timer1);
+			opl3->timer1 = NULL;
+			return err;
+		}
+	}
+	return 0;
+}
+
+int snd_opl3_hwdep_new(opl3_t * opl3,
+		       int device, int seq_device,
+		       snd_hwdep_t ** rhwdep)
+{
+	snd_hwdep_t *hw;
+	snd_card_t *card = opl3->card;
+	int err;
+
+	if (rhwdep)
+		*rhwdep = NULL;
+
+	/* create hardware dependent device (direct FM) */
+
+	if ((err = snd_hwdep_new(card, "OPL2/OPL3", device, &hw)) < 0) {
+		snd_device_free(card, opl3);
+		return err;
+	}
+	hw->private_data = opl3;
+#ifdef CONFIG_SND_OSSEMUL
+	if (device == 0) {
+		hw->oss_type = SNDRV_OSS_DEVICE_TYPE_DMFM;
+		sprintf(hw->oss_dev, "dmfm%i", card->number);
+	}
+#endif
+	strcpy(hw->name, hw->id);
+	switch (opl3->hardware & OPL3_HW_MASK) {
+	case OPL3_HW_OPL2:
+		strcpy(hw->name, "OPL2 FM");
+		hw->iface = SNDRV_HWDEP_IFACE_OPL2;
+		break;
+	case OPL3_HW_OPL3:
+		strcpy(hw->name, "OPL3 FM");
+		hw->iface = SNDRV_HWDEP_IFACE_OPL3;
+		break;
+	case OPL3_HW_OPL4:
+		strcpy(hw->name, "OPL4 FM");
+		hw->iface = SNDRV_HWDEP_IFACE_OPL4;
+		break;
+	}
+
+	/* operators - only ioctl */
+	hw->ops.open = snd_opl3_open;
+	hw->ops.ioctl = snd_opl3_ioctl;
+	hw->ops.release = snd_opl3_release;
+
+	opl3->seq_dev_num = seq_device;
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+	if (snd_seq_device_new(card, seq_device, SNDRV_SEQ_DEV_ID_OPL3,
+			       sizeof(opl3_t*), &opl3->seq_dev) >= 0) {
+		strcpy(opl3->seq_dev->name, hw->name);
+		*(opl3_t**)SNDRV_SEQ_DEVICE_ARGPTR(opl3->seq_dev) = opl3;
+	}
+#endif
+	if (rhwdep)
+		*rhwdep = hw;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_opl3_interrupt);
+EXPORT_SYMBOL(snd_opl3_new);
+EXPORT_SYMBOL(snd_opl3_init);
+EXPORT_SYMBOL(snd_opl3_create);
+EXPORT_SYMBOL(snd_opl3_timer_new);
+EXPORT_SYMBOL(snd_opl3_hwdep_new);
+
+/* opl3_synth.c */
+EXPORT_SYMBOL(snd_opl3_regmap);
+EXPORT_SYMBOL(snd_opl3_reset);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_opl3_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_opl3_exit(void)
+{
+}
+
+module_init(alsa_opl3_init)
+module_exit(alsa_opl3_exit)
diff --git a/sound/drivers/opl3/opl3_midi.c b/sound/drivers/opl3/opl3_midi.c
new file mode 100644
index 0000000..93d6740
--- /dev/null
+++ b/sound/drivers/opl3/opl3_midi.c
@@ -0,0 +1,873 @@
+/*
+ *  Copyright (c) by Uros Bizjak <uros@kss-loka.si>
+ *
+ *  Midi synth routines for OPL2/OPL3/OPL4 FM
+ *
+ *   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
+ *
+ */
+
+#undef DEBUG_ALLOC
+#undef DEBUG_MIDI
+
+#include "opl3_voice.h"
+#include <sound/asoundef.h>
+
+extern char snd_opl3_regmap[MAX_OPL2_VOICES][4];
+
+extern int use_internal_drums;
+
+/*
+ * The next table looks magical, but it certainly is not. Its values have
+ * been calculated as table[i]=8*log(i/64)/log(2) with an obvious exception
+ * for i=0. This log-table converts a linear volume-scaling (0..127) to a
+ * logarithmic scaling as present in the FM-synthesizer chips. so :    Volume
+ * 64 =  0 db = relative volume  0 and:    Volume 32 = -6 db = relative
+ * volume -8 it was implemented as a table because it is only 128 bytes and
+ * it saves a lot of log() calculations. (Rob Hooft <hooft@chem.ruu.nl>)
+ */
+
+static char opl3_volume_table[128] =
+{
+	-63, -48, -40, -35, -32, -29, -27, -26,
+	-24, -23, -21, -20, -19, -18, -18, -17,
+	-16, -15, -15, -14, -13, -13, -12, -12,
+	-11, -11, -10, -10, -10, -9, -9, -8,
+	-8, -8, -7, -7, -7, -6, -6, -6,
+	-5, -5, -5, -5, -4, -4, -4, -4,
+	-3, -3, -3, -3, -2, -2, -2, -2,
+	-2, -1, -1, -1, -1, 0, 0, 0,
+	0, 0, 0, 1, 1, 1, 1, 1,
+	1, 2, 2, 2, 2, 2, 2, 2,
+	3, 3, 3, 3, 3, 3, 3, 4,
+	4, 4, 4, 4, 4, 4, 4, 5,
+	5, 5, 5, 5, 5, 5, 5, 5,
+	6, 6, 6, 6, 6, 6, 6, 6,
+	6, 7, 7, 7, 7, 7, 7, 7,
+	7, 7, 7, 8, 8, 8, 8, 8
+};
+
+void snd_opl3_calc_volume(unsigned char *volbyte, int vel,
+			  snd_midi_channel_t *chan)
+{
+	int oldvol, newvol, n;
+	int volume;
+
+	volume = (vel * chan->gm_volume * chan->gm_expression) / (127*127);
+	if (volume > 127)
+		volume = 127;
+
+	oldvol = OPL3_TOTAL_LEVEL_MASK - (*volbyte & OPL3_TOTAL_LEVEL_MASK);
+
+	newvol = opl3_volume_table[volume] + oldvol;
+	if (newvol > OPL3_TOTAL_LEVEL_MASK)
+		newvol = OPL3_TOTAL_LEVEL_MASK;
+	else if (newvol < 0)
+		newvol = 0;
+
+	n = OPL3_TOTAL_LEVEL_MASK - (newvol & OPL3_TOTAL_LEVEL_MASK);
+
+	*volbyte = (*volbyte & OPL3_KSL_MASK) | (n & OPL3_TOTAL_LEVEL_MASK);
+}
+
+/*
+ * Converts the note frequency to block and fnum values for the FM chip
+ */
+static short opl3_note_table[16] =
+{
+	305, 323,	/* for pitch bending, -2 semitones */
+	343, 363, 385, 408, 432, 458, 485, 514, 544, 577, 611, 647,
+	686, 726	/* for pitch bending, +2 semitones */
+};
+
+static void snd_opl3_calc_pitch(unsigned char *fnum, unsigned char *blocknum,
+				int note, snd_midi_channel_t *chan)
+{
+	int block = ((note / 12) & 0x07) - 1;
+	int idx = (note % 12) + 2;
+	int freq;
+
+	if (chan->midi_pitchbend) {
+		int pitchbend = chan->midi_pitchbend;
+		int segment;
+
+		if (pitchbend > 0x1FFF)
+			pitchbend = 0x1FFF;
+
+		segment = pitchbend / 0x1000;
+		freq = opl3_note_table[idx+segment];
+		freq += ((opl3_note_table[idx+segment+1] - freq) *
+			 (pitchbend % 0x1000)) / 0x1000;
+	} else {
+		freq = opl3_note_table[idx];
+	}
+
+	*fnum = (unsigned char) freq;
+	*blocknum = ((freq >> 8) & OPL3_FNUM_HIGH_MASK) |
+		((block << 2) & OPL3_BLOCKNUM_MASK);
+}
+
+
+#ifdef DEBUG_ALLOC
+static void debug_alloc(opl3_t *opl3, char *s, int voice) {
+	int i;
+	char *str = "x.24";
+
+	printk("time %.5i: %s [%.2i]: ", opl3->use_time, s, voice);
+	for (i = 0; i < opl3->max_voices; i++)
+		printk("%c", *(str + opl3->voices[i].state + 1));
+	printk("\n");
+}
+#endif
+
+/*
+ * Get a FM voice (channel) to play a note on.
+ */
+static int opl3_get_voice(opl3_t *opl3, int instr_4op,
+			  snd_midi_channel_t *chan) {
+	int chan_4op_1;		/* first voice for 4op instrument */
+	int chan_4op_2;		/* second voice for 4op instrument */
+
+	snd_opl3_voice_t *vp, *vp2;
+	unsigned int voice_time;
+	int i;
+
+#ifdef DEBUG_ALLOC
+	char *alloc_type[3] = { "FREE     ", "CHEAP    ", "EXPENSIVE" };
+#endif
+
+	/* This is our "allocation cost" table */
+	enum {
+		FREE = 0, CHEAP, EXPENSIVE, END
+	};
+
+	/* Keeps track of what we are finding */
+	struct best {
+		unsigned int time;
+		int voice;
+	} best[END];
+	struct best *bp;
+
+	for (i = 0; i < END; i++) {
+		best[i].time = (unsigned int)(-1); /* XXX MAX_?INT really */;
+		best[i].voice = -1;
+	}
+
+	/* Look through all the channels for the most suitable. */
+	for (i = 0; i < opl3->max_voices; i++) {
+		vp = &opl3->voices[i];
+
+		if (vp->state == SNDRV_OPL3_ST_NOT_AVAIL)
+		  /* skip unavailable channels, allocated by
+		     drum voices or by bounded 4op voices) */
+			continue;
+
+		voice_time = vp->time;
+		bp = best;
+
+		chan_4op_1 = ((i < 3) || (i > 8 && i < 12));
+		chan_4op_2 = ((i > 2 && i < 6) || (i > 11 && i < 15));
+		if (instr_4op) {
+			/* allocate 4op voice */
+			/* skip channels unavailable to 4op instrument */
+			if (!chan_4op_1)
+				continue;
+
+			if (vp->state)
+				/* kill one voice, CHEAP */
+				bp++;
+			/* get state of bounded 2op channel
+			   to be allocated for 4op instrument */
+			vp2 = &opl3->voices[i + 3];
+			if (vp2->state == SNDRV_OPL3_ST_ON_2OP) {
+				/* kill two voices, EXPENSIVE */
+				bp++;
+				voice_time = (voice_time > vp->time) ?
+					voice_time : vp->time;
+			}
+		} else {
+			/* allocate 2op voice */
+			if ((chan_4op_1) || (chan_4op_2))
+				/* use bounded channels for 2op, CHEAP */
+				bp++;
+			else if (vp->state)
+				/* kill one voice on 2op channel, CHEAP */
+				bp++;
+			/* raise kill cost to EXPENSIVE for all channels */
+			if (vp->state)
+				bp++;
+		}
+		if (voice_time < bp->time) {
+			bp->time = voice_time;
+			bp->voice = i;
+		}
+	}
+
+	for (i = 0; i < END; i++) {
+		if (best[i].voice >= 0) {
+#ifdef DEBUG_ALLOC
+			printk("%s %iop allocation on voice %i\n",
+			       alloc_type[i], instr_4op ? 4 : 2,
+			       best[i].voice);
+#endif
+			return best[i].voice;
+		}
+	}
+	/* not found */
+	return -1;
+}
+
+/* ------------------------------ */
+
+/*
+ * System timer interrupt function
+ */
+void snd_opl3_timer_func(unsigned long data)
+{
+
+	opl3_t *opl3 = (opl3_t *)data;
+	int again = 0;
+	int i;
+
+	spin_lock(&opl3->sys_timer_lock);
+	for (i = 0; i < opl3->max_voices; i++) {
+		snd_opl3_voice_t *vp = &opl3->voices[i];
+		if (vp->state > 0 && vp->note_off_check) {
+			if (vp->note_off == jiffies)
+				snd_opl3_note_off(opl3, vp->note, 0, vp->chan);
+			else
+				again++;
+		}
+	}
+	if (again) {
+		opl3->tlist.expires = jiffies + 1;	/* invoke again */
+		add_timer(&opl3->tlist);
+	} else {
+		opl3->sys_timer_status = 0;
+	}
+	spin_unlock(&opl3->sys_timer_lock);
+}
+
+/*
+ * Start system timer
+ */
+static void snd_opl3_start_timer(opl3_t *opl3)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&opl3->sys_timer_lock, flags);
+	if (! opl3->sys_timer_status) {
+		opl3->tlist.expires = jiffies + 1;
+		add_timer(&opl3->tlist);
+		opl3->sys_timer_status = 1;
+	}
+	spin_unlock_irqrestore(&opl3->sys_timer_lock, flags);
+}
+
+/* ------------------------------ */
+
+
+static int snd_opl3_oss_map[MAX_OPL3_VOICES] = {
+	0, 1, 2, 9, 10, 11, 6, 7, 8, 15, 16, 17, 3, 4 ,5, 12, 13, 14
+};
+
+/*
+ * Start a note.
+ */
+void snd_opl3_note_on(void *p, int note, int vel, snd_midi_channel_t *chan)
+{
+	opl3_t *opl3;
+	snd_seq_instr_t wanted;
+	snd_seq_kinstr_t *kinstr;
+	int instr_4op;
+
+	int voice;
+	snd_opl3_voice_t *vp, *vp2;
+	unsigned short connect_mask;
+	unsigned char connection;
+	unsigned char vol_op[4];
+
+	int extra_prg = 0;
+
+	unsigned short reg_side;
+	unsigned char op_offset;
+	unsigned char voice_offset;
+	unsigned short opl3_reg;
+	unsigned char reg_val;
+
+	int key = note;
+	unsigned char fnum, blocknum;
+	int i;
+
+	fm_instrument_t *fm;
+	unsigned long flags;
+
+	opl3 = p;
+
+#ifdef DEBUG_MIDI
+	snd_printk("Note on, ch %i, inst %i, note %i, vel %i\n",
+		   chan->number, chan->midi_program, note, vel);
+#endif
+	wanted.cluster = 0;
+	wanted.std = SNDRV_SEQ_INSTR_TYPE2_OPL2_3;
+
+	/* in SYNTH mode, application takes care of voices */
+	/* in SEQ mode, drum voice numbers are notes on drum channel */
+	if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
+		if (chan->drum_channel) {
+			/* percussion instruments are located in bank 128 */
+			wanted.bank = 128;
+			wanted.prg = note;
+		} else {
+			wanted.bank = chan->gm_bank_select;
+			wanted.prg = chan->midi_program;
+		}
+	} else {
+		/* Prepare for OSS mode */
+		if (chan->number >= MAX_OPL3_VOICES)
+			return;
+
+		/* OSS instruments are located in bank 127 */
+		wanted.bank = 127;
+		wanted.prg = chan->midi_program;
+	}
+
+	spin_lock_irqsave(&opl3->voice_lock, flags);
+
+	if (use_internal_drums) {
+		snd_opl3_drum_switch(opl3, note, vel, 1, chan);
+		spin_unlock_irqrestore(&opl3->voice_lock, flags);
+		return;
+	}
+
+ __extra_prg:
+	kinstr = snd_seq_instr_find(opl3->ilist, &wanted, 1, 0);
+	if (kinstr == NULL) {
+		spin_unlock_irqrestore(&opl3->voice_lock, flags);
+		return;
+	}
+
+	fm = KINSTR_DATA(kinstr);
+
+	switch (fm->type) {
+	case FM_PATCH_OPL2:
+		instr_4op = 0;
+		break;
+	case FM_PATCH_OPL3:
+		if (opl3->hardware >= OPL3_HW_OPL3) {
+			instr_4op = 1;
+			break;
+		}
+	default:
+		snd_seq_instr_free_use(opl3->ilist, kinstr);
+		spin_unlock_irqrestore(&opl3->voice_lock, flags);
+		return;
+	}
+
+#ifdef DEBUG_MIDI
+	snd_printk("  --> OPL%i instrument: %s\n",
+		   instr_4op ? 3 : 2, kinstr->name);
+#endif
+	/* in SYNTH mode, application takes care of voices */
+	/* in SEQ mode, allocate voice on free OPL3 channel */
+	if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
+		voice = opl3_get_voice(opl3, instr_4op, chan);
+	} else {
+		/* remap OSS voice */
+		voice = snd_opl3_oss_map[chan->number];		
+	}
+
+	if (voice < MAX_OPL2_VOICES) {
+		/* Left register block for voices 0 .. 8 */
+		reg_side = OPL3_LEFT;
+		voice_offset = voice;
+		connect_mask = (OPL3_LEFT_4OP_0 << voice_offset) & 0x07;
+	} else {
+		/* Right register block for voices 9 .. 17 */
+		reg_side = OPL3_RIGHT;
+		voice_offset = voice - MAX_OPL2_VOICES;
+		connect_mask = (OPL3_RIGHT_4OP_0 << voice_offset) & 0x38;
+	}
+
+	/* kill voice on channel */
+	vp = &opl3->voices[voice];
+	if (vp->state > 0) {
+		opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
+		reg_val = vp->keyon_reg & ~OPL3_KEYON_BIT;
+		opl3->command(opl3, opl3_reg, reg_val);
+	}
+	if (instr_4op) {
+		vp2 = &opl3->voices[voice + 3];
+		if (vp->state > 0) {
+			opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK +
+					       voice_offset + 3);
+			reg_val = vp->keyon_reg & ~OPL3_KEYON_BIT;
+			opl3->command(opl3, opl3_reg, reg_val);
+		}
+	}
+
+	/* set connection register */
+	if (instr_4op) {
+		if ((opl3->connection_reg ^ connect_mask) & connect_mask) {
+			opl3->connection_reg |= connect_mask;
+			/* set connection bit */
+			opl3_reg = OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT;
+			opl3->command(opl3, opl3_reg, opl3->connection_reg);
+		}
+	} else {
+		if ((opl3->connection_reg ^ ~connect_mask) & connect_mask) {
+			opl3->connection_reg &= ~connect_mask;
+			/* clear connection bit */
+			opl3_reg = OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT;
+			opl3->command(opl3, opl3_reg, opl3->connection_reg);
+		}
+	}
+
+#ifdef DEBUG_MIDI
+	snd_printk("  --> setting OPL3 connection: 0x%x\n",
+		   opl3->connection_reg);
+#endif
+	/*
+	 * calculate volume depending on connection
+	 * between FM operators (see include/opl3.h)
+	 */
+	for (i = 0; i < (instr_4op ? 4 : 2); i++)
+		vol_op[i] = fm->op[i].ksl_level;
+
+	connection = fm->feedback_connection[0] & 0x01;
+	if (instr_4op) {
+		connection <<= 1;
+		connection |= fm->feedback_connection[1] & 0x01;
+
+		snd_opl3_calc_volume(&vol_op[3], vel, chan);
+		switch (connection) {
+		case 0x03:
+			snd_opl3_calc_volume(&vol_op[2], vel, chan);
+			/* fallthru */
+		case 0x02:
+			snd_opl3_calc_volume(&vol_op[0], vel, chan);
+			break;
+		case 0x01:
+			snd_opl3_calc_volume(&vol_op[1], vel, chan);
+		}
+	} else {
+		snd_opl3_calc_volume(&vol_op[1], vel, chan);
+		if (connection)
+			snd_opl3_calc_volume(&vol_op[0], vel, chan);
+	}
+
+	/* Program the FM voice characteristics */
+	for (i = 0; i < (instr_4op ? 4 : 2); i++) {
+#ifdef DEBUG_MIDI
+		snd_printk("  --> programming operator %i\n", i);
+#endif
+		op_offset = snd_opl3_regmap[voice_offset][i];
+
+		/* Set OPL3 AM_VIB register of requested voice/operator */ 
+		reg_val = fm->op[i].am_vib;
+		opl3_reg = reg_side | (OPL3_REG_AM_VIB + op_offset);
+		opl3->command(opl3, opl3_reg, reg_val);
+
+		/* Set OPL3 KSL_LEVEL register of requested voice/operator */ 
+		reg_val = vol_op[i];
+		opl3_reg = reg_side | (OPL3_REG_KSL_LEVEL + op_offset);
+		opl3->command(opl3, opl3_reg, reg_val);
+
+		/* Set OPL3 ATTACK_DECAY register of requested voice/operator */ 
+		reg_val = fm->op[i].attack_decay;
+		opl3_reg = reg_side | (OPL3_REG_ATTACK_DECAY + op_offset);
+		opl3->command(opl3, opl3_reg, reg_val);
+
+		/* Set OPL3 SUSTAIN_RELEASE register of requested voice/operator */ 
+		reg_val = fm->op[i].sustain_release;
+		opl3_reg = reg_side | (OPL3_REG_SUSTAIN_RELEASE + op_offset);
+		opl3->command(opl3, opl3_reg, reg_val);
+
+		/* Select waveform */
+		reg_val = fm->op[i].wave_select;
+		opl3_reg = reg_side | (OPL3_REG_WAVE_SELECT + op_offset);
+		opl3->command(opl3, opl3_reg, reg_val);
+	}
+
+	/* Set operator feedback and 2op inter-operator connection */
+	reg_val = fm->feedback_connection[0];
+	/* Set output voice connection */
+	reg_val |= OPL3_STEREO_BITS;
+	if (chan->gm_pan < 43)
+		reg_val &= ~OPL3_VOICE_TO_RIGHT;
+	if (chan->gm_pan > 85)
+		reg_val &= ~OPL3_VOICE_TO_LEFT;
+	opl3_reg = reg_side | (OPL3_REG_FEEDBACK_CONNECTION + voice_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+
+	if (instr_4op) {
+		/* Set 4op inter-operator connection */
+		reg_val = fm->feedback_connection[1] & OPL3_CONNECTION_BIT;
+		/* Set output voice connection */
+		reg_val |= OPL3_STEREO_BITS;
+		if (chan->gm_pan < 43)
+			reg_val &= ~OPL3_VOICE_TO_RIGHT;
+		if (chan->gm_pan > 85)
+			reg_val &= ~OPL3_VOICE_TO_LEFT;
+		opl3_reg = reg_side | (OPL3_REG_FEEDBACK_CONNECTION +
+				       voice_offset + 3);
+		opl3->command(opl3, opl3_reg, reg_val);
+	}
+
+	/*
+	 * Special treatment of percussion notes for fm:
+	 * Requested pitch is really program, and pitch for
+	 * device is whatever was specified in the patch library.
+	 */
+	if (fm->fix_key)
+		note = fm->fix_key;
+	/*
+	 * use transpose if defined in patch library
+	 */
+	if (fm->trnsps)
+		note += (fm->trnsps - 64);
+
+	snd_opl3_calc_pitch(&fnum, &blocknum, note, chan);
+
+	/* Set OPL3 FNUM_LOW register of requested voice */
+	opl3_reg = reg_side | (OPL3_REG_FNUM_LOW + voice_offset);
+	opl3->command(opl3, opl3_reg, fnum);
+
+	opl3->voices[voice].keyon_reg = blocknum;
+
+	/* Set output sound flag */
+	blocknum |= OPL3_KEYON_BIT;
+
+#ifdef DEBUG_MIDI
+	snd_printk("  --> trigger voice %i\n", voice);
+#endif
+	/* Set OPL3 KEYON_BLOCK register of requested voice */ 
+	opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
+	opl3->command(opl3, opl3_reg, blocknum);
+
+	/* kill note after fixed duration (in centiseconds) */
+	if (fm->fix_dur) {
+		opl3->voices[voice].note_off = jiffies +
+			(fm->fix_dur * HZ) / 100;
+		snd_opl3_start_timer(opl3);
+		opl3->voices[voice].note_off_check = 1;
+	} else
+		opl3->voices[voice].note_off_check = 0;
+
+	/* get extra pgm, but avoid possible loops */
+	extra_prg = (extra_prg) ? 0 : fm->modes;
+
+	snd_seq_instr_free_use(opl3->ilist, kinstr);
+
+	/* do the bookkeeping */
+	vp->time = opl3->use_time++;
+	vp->note = key;
+	vp->chan = chan;
+
+	if (instr_4op) {
+		vp->state = SNDRV_OPL3_ST_ON_4OP;
+
+		vp2 = &opl3->voices[voice + 3];
+		vp2->time = opl3->use_time++;
+		vp2->note = key;
+		vp2->chan = chan;
+		vp2->state = SNDRV_OPL3_ST_NOT_AVAIL;
+	} else {
+		if (vp->state == SNDRV_OPL3_ST_ON_4OP) {
+			/* 4op killed by 2op, release bounded voice */
+			vp2 = &opl3->voices[voice + 3];
+			vp2->time = opl3->use_time++;
+			vp2->state = SNDRV_OPL3_ST_OFF;
+		}
+		vp->state = SNDRV_OPL3_ST_ON_2OP;
+	}
+
+#ifdef DEBUG_ALLOC
+	debug_alloc(opl3, "note on ", voice);
+#endif
+
+	/* allocate extra program if specified in patch library */
+	if (extra_prg) {
+		if (extra_prg > 128) {
+			wanted.bank = 128;
+			/* percussions start at 35 */
+			wanted.prg = extra_prg - 128 + 35 - 1;
+		} else {
+			wanted.bank = 0;
+			wanted.prg = extra_prg - 1;
+		}
+#ifdef DEBUG_MIDI
+		snd_printk(" *** allocating extra program\n");
+#endif
+		goto __extra_prg;
+	}
+	spin_unlock_irqrestore(&opl3->voice_lock, flags);
+}
+
+static void snd_opl3_kill_voice(opl3_t *opl3, int voice)
+{
+	unsigned short reg_side;
+	unsigned char voice_offset;
+	unsigned short opl3_reg;
+
+	snd_opl3_voice_t *vp, *vp2;
+
+	snd_assert(voice < MAX_OPL3_VOICES, return);
+
+	vp = &opl3->voices[voice];
+	if (voice < MAX_OPL2_VOICES) {
+		/* Left register block for voices 0 .. 8 */
+		reg_side = OPL3_LEFT;
+		voice_offset = voice;
+	} else {
+		/* Right register block for voices 9 .. 17 */
+		reg_side = OPL3_RIGHT;
+		voice_offset = voice - MAX_OPL2_VOICES;
+	}
+
+	/* kill voice */
+#ifdef DEBUG_MIDI
+	snd_printk("  --> kill voice %i\n", voice);
+#endif
+	opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
+	/* clear Key ON bit */
+	opl3->command(opl3, opl3_reg, vp->keyon_reg);
+
+	/* do the bookkeeping */
+	vp->time = opl3->use_time++;
+
+	if (vp->state == SNDRV_OPL3_ST_ON_4OP) {
+		vp2 = &opl3->voices[voice + 3];
+
+		vp2->time = opl3->use_time++;
+		vp2->state = SNDRV_OPL3_ST_OFF;
+	}
+	vp->state = SNDRV_OPL3_ST_OFF;
+#ifdef DEBUG_ALLOC
+	debug_alloc(opl3, "note off", voice);
+#endif
+
+}
+
+/*
+ * Release a note in response to a midi note off.
+ */
+void snd_opl3_note_off(void *p, int note, int vel, snd_midi_channel_t *chan)
+{
+  	opl3_t *opl3;
+
+	int voice;
+	snd_opl3_voice_t *vp;
+
+	unsigned long flags;
+
+	opl3 = p;
+
+#ifdef DEBUG_MIDI
+	snd_printk("Note off, ch %i, inst %i, note %i\n",
+		   chan->number, chan->midi_program, note);
+#endif
+
+	spin_lock_irqsave(&opl3->voice_lock, flags);
+
+	if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
+		if (chan->drum_channel && use_internal_drums) {
+			snd_opl3_drum_switch(opl3, note, vel, 0, chan);
+			spin_unlock_irqrestore(&opl3->voice_lock, flags);
+			return;
+		}
+		/* this loop will hopefully kill all extra voices, because
+		   they are grouped by the same channel and note values */
+		for (voice = 0; voice < opl3->max_voices; voice++) {
+			vp = &opl3->voices[voice];
+			if (vp->state > 0 && vp->chan == chan && vp->note == note) {
+				snd_opl3_kill_voice(opl3, voice);
+			}
+		}
+	} else {
+		/* remap OSS voices */
+		if (chan->number < MAX_OPL3_VOICES) {
+			voice = snd_opl3_oss_map[chan->number];		
+			snd_opl3_kill_voice(opl3, voice);
+		}
+	}
+	spin_unlock_irqrestore(&opl3->voice_lock, flags);
+}
+
+/*
+ * key pressure change
+ */
+void snd_opl3_key_press(void *p, int note, int vel, snd_midi_channel_t *chan)
+{
+  	opl3_t *opl3;
+
+	opl3 = p;
+#ifdef DEBUG_MIDI
+	snd_printk("Key pressure, ch#: %i, inst#: %i\n",
+		   chan->number, chan->midi_program);
+#endif
+}
+
+/*
+ * terminate note
+ */
+void snd_opl3_terminate_note(void *p, int note, snd_midi_channel_t *chan)
+{
+  	opl3_t *opl3;
+
+	opl3 = p;
+#ifdef DEBUG_MIDI
+	snd_printk("Terminate note, ch#: %i, inst#: %i\n",
+		   chan->number, chan->midi_program);
+#endif
+}
+
+static void snd_opl3_update_pitch(opl3_t *opl3, int voice)
+{
+	unsigned short reg_side;
+	unsigned char voice_offset;
+	unsigned short opl3_reg;
+
+	unsigned char fnum, blocknum;
+
+	snd_opl3_voice_t *vp;
+
+	snd_assert(voice < MAX_OPL3_VOICES, return);
+
+	vp = &opl3->voices[voice];
+	if (vp->chan == NULL)
+		return; /* not allocated? */
+
+	if (voice < MAX_OPL2_VOICES) {
+		/* Left register block for voices 0 .. 8 */
+		reg_side = OPL3_LEFT;
+		voice_offset = voice;
+	} else {
+		/* Right register block for voices 9 .. 17 */
+		reg_side = OPL3_RIGHT;
+		voice_offset = voice - MAX_OPL2_VOICES;
+	}
+
+	snd_opl3_calc_pitch(&fnum, &blocknum, vp->note, vp->chan);
+
+	/* Set OPL3 FNUM_LOW register of requested voice */
+	opl3_reg = reg_side | (OPL3_REG_FNUM_LOW + voice_offset);
+	opl3->command(opl3, opl3_reg, fnum);
+
+	vp->keyon_reg = blocknum;
+
+	/* Set output sound flag */
+	blocknum |= OPL3_KEYON_BIT;
+
+	/* Set OPL3 KEYON_BLOCK register of requested voice */ 
+	opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
+	opl3->command(opl3, opl3_reg, blocknum);
+
+	vp->time = opl3->use_time++;
+}
+
+/*
+ * Update voice pitch controller
+ */
+static void snd_opl3_pitch_ctrl(opl3_t *opl3, snd_midi_channel_t *chan)
+{
+	int voice;
+	snd_opl3_voice_t *vp;
+
+	unsigned long flags;
+
+	spin_lock_irqsave(&opl3->voice_lock, flags);
+
+	if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
+		for (voice = 0; voice < opl3->max_voices; voice++) {
+			vp = &opl3->voices[voice];
+			if (vp->state > 0 && vp->chan == chan) {
+				snd_opl3_update_pitch(opl3, voice);
+			}
+		}
+	} else {
+		/* remap OSS voices */
+		if (chan->number < MAX_OPL3_VOICES) {
+			voice = snd_opl3_oss_map[chan->number];		
+			snd_opl3_update_pitch(opl3, voice);
+		}
+	}
+	spin_unlock_irqrestore(&opl3->voice_lock, flags);
+}
+
+/*
+ * Deal with a controler type event.  This includes all types of
+ * control events, not just the midi controllers
+ */
+void snd_opl3_control(void *p, int type, snd_midi_channel_t *chan)
+{
+  	opl3_t *opl3;
+
+	opl3 = p;
+#ifdef DEBUG_MIDI
+	snd_printk("Controller, TYPE = %i, ch#: %i, inst#: %i\n",
+		   type, chan->number, chan->midi_program);
+#endif
+
+	switch (type) {
+	case MIDI_CTL_MSB_MODWHEEL:
+		if (chan->control[MIDI_CTL_MSB_MODWHEEL] > 63)
+			opl3->drum_reg |= OPL3_VIBRATO_DEPTH;
+		else 
+			opl3->drum_reg &= ~OPL3_VIBRATO_DEPTH;
+		opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION,
+				 opl3->drum_reg);
+		break;
+	case MIDI_CTL_E2_TREMOLO_DEPTH:
+		if (chan->control[MIDI_CTL_E2_TREMOLO_DEPTH] > 63)
+			opl3->drum_reg |= OPL3_TREMOLO_DEPTH;
+		else 
+			opl3->drum_reg &= ~OPL3_TREMOLO_DEPTH;
+		opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION,
+				 opl3->drum_reg);
+		break;
+	case MIDI_CTL_PITCHBEND:
+		snd_opl3_pitch_ctrl(opl3, chan);
+		break;
+	}
+}
+
+/*
+ * NRPN events
+ */
+void snd_opl3_nrpn(void *p, snd_midi_channel_t *chan,
+		   snd_midi_channel_set_t *chset)
+{
+  	opl3_t *opl3;
+
+	opl3 = p;
+#ifdef DEBUG_MIDI
+	snd_printk("NRPN, ch#: %i, inst#: %i\n",
+		   chan->number, chan->midi_program);
+#endif
+}
+
+/*
+ * receive sysex
+ */
+void snd_opl3_sysex(void *p, unsigned char *buf, int len,
+		    int parsed, snd_midi_channel_set_t *chset)
+{
+  	opl3_t *opl3;
+
+	opl3 = p;
+#ifdef DEBUG_MIDI
+	snd_printk("SYSEX\n");
+#endif
+}
diff --git a/sound/drivers/opl3/opl3_oss.c b/sound/drivers/opl3/opl3_oss.c
new file mode 100644
index 0000000..33da334
--- /dev/null
+++ b/sound/drivers/opl3/opl3_oss.c
@@ -0,0 +1,356 @@
+/*
+ *  Interface for OSS sequencer emulation
+ *
+ *  Copyright (C) 2000 Uros Bizjak <uros@kss-loka.si>
+ *
+ *   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 "opl3_voice.h"
+#include <linux/slab.h>
+
+static int snd_opl3_open_seq_oss(snd_seq_oss_arg_t *arg, void *closure);
+static int snd_opl3_close_seq_oss(snd_seq_oss_arg_t *arg);
+static int snd_opl3_ioctl_seq_oss(snd_seq_oss_arg_t *arg, unsigned int cmd, unsigned long ioarg);
+static int snd_opl3_load_patch_seq_oss(snd_seq_oss_arg_t *arg, int format, const char __user *buf, int offs, int count);
+static int snd_opl3_reset_seq_oss(snd_seq_oss_arg_t *arg);
+
+/* */
+
+static inline mm_segment_t snd_enter_user(void)
+{
+	mm_segment_t fs = get_fs();
+	set_fs(get_ds());
+	return fs;
+}
+
+static inline void snd_leave_user(mm_segment_t fs)
+{
+	set_fs(fs);
+}
+
+/* operators */
+
+extern snd_midi_op_t opl3_ops;
+
+static snd_seq_oss_callback_t oss_callback = {
+	.owner = 	THIS_MODULE,
+	.open =		snd_opl3_open_seq_oss,
+	.close =	snd_opl3_close_seq_oss,
+	.ioctl =	snd_opl3_ioctl_seq_oss,
+	.load_patch =	snd_opl3_load_patch_seq_oss,
+	.reset =	snd_opl3_reset_seq_oss,
+};
+
+static int snd_opl3_oss_event_input(snd_seq_event_t *ev, int direct,
+				    void *private_data, int atomic, int hop)
+{
+	opl3_t *opl3 = private_data;
+
+	if (ev->type != SNDRV_SEQ_EVENT_OSS)
+		snd_midi_process_event(&opl3_ops, ev, opl3->oss_chset);
+	return 0;
+}
+
+/* ------------------------------ */
+
+static void snd_opl3_oss_free_port(void *private_data)
+{
+	opl3_t *opl3 = private_data;
+
+	snd_midi_channel_free_set(opl3->oss_chset);
+}
+
+static int snd_opl3_oss_create_port(opl3_t * opl3)
+{
+	snd_seq_port_callback_t callbacks;
+	char name[32];
+	int voices, opl_ver;
+
+	voices = (opl3->hardware < OPL3_HW_OPL3) ?
+		MAX_OPL2_VOICES : MAX_OPL3_VOICES;
+	opl3->oss_chset = snd_midi_channel_alloc_set(voices);
+	if (opl3->oss_chset == NULL)
+		return -ENOMEM;
+	opl3->oss_chset->private_data = opl3;
+
+	memset(&callbacks, 0, sizeof(callbacks));
+	callbacks.owner = THIS_MODULE;
+	callbacks.event_input = snd_opl3_oss_event_input;
+	callbacks.private_free = snd_opl3_oss_free_port;
+	callbacks.private_data = opl3;
+
+	opl_ver = (opl3->hardware & OPL3_HW_MASK) >> 8;
+	sprintf(name, "OPL%i OSS Port", opl_ver);
+
+	opl3->oss_chset->client = opl3->seq_client;
+	opl3->oss_chset->port = snd_seq_event_port_attach(opl3->seq_client, &callbacks,
+							  SNDRV_SEQ_PORT_CAP_WRITE,
+							  SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+							  SNDRV_SEQ_PORT_TYPE_MIDI_GM |
+							  SNDRV_SEQ_PORT_TYPE_SYNTH,
+							  voices, voices,
+							  name);
+	if (opl3->oss_chset->port < 0) {
+		snd_midi_channel_free_set(opl3->oss_chset);
+		return opl3->oss_chset->port;
+	}
+	return 0;
+}
+
+/* ------------------------------ */
+
+/* register OSS synth */
+void snd_opl3_init_seq_oss(opl3_t *opl3, char *name)
+{
+	snd_seq_oss_reg_t *arg;
+	snd_seq_device_t *dev;
+
+	if (snd_seq_device_new(opl3->card, 0, SNDRV_SEQ_DEV_ID_OSS,
+			       sizeof(snd_seq_oss_reg_t), &dev) < 0)
+		return;
+
+	opl3->oss_seq_dev = dev;
+	strlcpy(dev->name, name, sizeof(dev->name));
+	arg = SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	arg->type = SYNTH_TYPE_FM;
+	if (opl3->hardware < OPL3_HW_OPL3) {
+		arg->subtype = FM_TYPE_ADLIB;
+		arg->nvoices = MAX_OPL2_VOICES;
+	} else {
+		arg->subtype = FM_TYPE_OPL3;
+		arg->nvoices = MAX_OPL3_VOICES;
+	}
+	arg->oper = oss_callback;
+	arg->private_data = opl3;
+
+	snd_opl3_oss_create_port(opl3);
+
+	/* register to OSS synth table */
+	snd_device_register(opl3->card, dev);
+}
+
+/* unregister */
+void snd_opl3_free_seq_oss(opl3_t *opl3)
+{
+	if (opl3->oss_seq_dev) {
+		snd_device_free(opl3->card, opl3->oss_seq_dev);
+		opl3->oss_seq_dev = NULL;
+	}
+}
+
+/* ------------------------------ */
+
+/* open OSS sequencer */
+static int snd_opl3_open_seq_oss(snd_seq_oss_arg_t *arg, void *closure)
+{
+	opl3_t *opl3 = closure;
+	int err;
+
+	snd_assert(arg != NULL, return -ENXIO);
+
+	if ((err = snd_opl3_synth_setup(opl3)) < 0)
+		return err;
+
+	/* fill the argument data */
+	arg->private_data = opl3;
+	arg->addr.client = opl3->oss_chset->client;
+	arg->addr.port = opl3->oss_chset->port;
+
+	if ((err = snd_opl3_synth_use_inc(opl3)) < 0)
+		return err;
+
+	opl3->synth_mode = SNDRV_OPL3_MODE_SYNTH;
+	return 0;
+}
+
+/* close OSS sequencer */
+static int snd_opl3_close_seq_oss(snd_seq_oss_arg_t *arg)
+{
+	opl3_t *opl3;
+
+	snd_assert(arg != NULL, return -ENXIO);
+	opl3 = arg->private_data;
+
+	snd_opl3_synth_cleanup(opl3);
+
+	snd_opl3_synth_use_dec(opl3);
+	return 0;
+}
+
+/* load patch */
+
+/* offsets for SBI params */
+#define AM_VIB		0
+#define KSL_LEVEL	2
+#define ATTACK_DECAY	4
+#define SUSTAIN_RELEASE	6
+#define WAVE_SELECT	8
+
+/* offset for SBI instrument */
+#define CONNECTION	10
+#define OFFSET_4OP	11
+
+/* from sound_config.h */
+#define SBFM_MAXINSTR	256
+
+static int snd_opl3_load_patch_seq_oss(snd_seq_oss_arg_t *arg, int format,
+				       const char __user *buf, int offs, int count)
+{
+	opl3_t *opl3;
+	int err = -EINVAL;
+
+	snd_assert(arg != NULL, return -ENXIO);
+	opl3 = arg->private_data;
+
+	if ((format == FM_PATCH) || (format == OPL3_PATCH)) {
+		struct sbi_instrument sbi;
+
+		size_t size;
+		snd_seq_instr_header_t *put;
+		snd_seq_instr_data_t *data;
+		fm_xinstrument_t *xinstr;
+
+		snd_seq_event_t ev;
+		int i;
+
+		mm_segment_t fs;
+
+		if (count < (int)sizeof(sbi)) {
+			snd_printk("FM Error: Patch record too short\n");
+			return -EINVAL;
+		}
+		if (copy_from_user(&sbi, buf, sizeof(sbi)))
+			return -EFAULT;
+
+		if (sbi.channel < 0 || sbi.channel >= SBFM_MAXINSTR) {
+			snd_printk("FM Error: Invalid instrument number %d\n", sbi.channel);
+			return -EINVAL;
+		}
+
+		size = sizeof(*put) + sizeof(fm_xinstrument_t);
+		put = kcalloc(1, size, GFP_KERNEL);
+		if (put == NULL)
+			return -ENOMEM;
+		/* build header */
+		data = &put->data;
+		data->type = SNDRV_SEQ_INSTR_ATYPE_DATA;
+		strcpy(data->data.format, SNDRV_SEQ_INSTR_ID_OPL2_3);
+		/* build data section */
+		xinstr = (fm_xinstrument_t *)(data + 1);
+		xinstr->stype = FM_STRU_INSTR;
+        
+		for (i = 0; i < 2; i++) {
+			xinstr->op[i].am_vib = sbi.operators[AM_VIB + i];
+			xinstr->op[i].ksl_level = sbi.operators[KSL_LEVEL + i];
+			xinstr->op[i].attack_decay = sbi.operators[ATTACK_DECAY + i];
+			xinstr->op[i].sustain_release = sbi.operators[SUSTAIN_RELEASE + i];
+			xinstr->op[i].wave_select = sbi.operators[WAVE_SELECT + i];
+		}
+		xinstr->feedback_connection[0] = sbi.operators[CONNECTION];
+
+		if (format == OPL3_PATCH) {
+			xinstr->type = FM_PATCH_OPL3;
+			for (i = 0; i < 2; i++) {
+				xinstr->op[i+2].am_vib = sbi.operators[OFFSET_4OP + AM_VIB + i];
+				xinstr->op[i+2].ksl_level = sbi.operators[OFFSET_4OP + KSL_LEVEL + i];
+				xinstr->op[i+2].attack_decay = sbi.operators[OFFSET_4OP + ATTACK_DECAY + i];
+				xinstr->op[i+2].sustain_release = sbi.operators[OFFSET_4OP + SUSTAIN_RELEASE + i];
+				xinstr->op[i+2].wave_select = sbi.operators[OFFSET_4OP + WAVE_SELECT + i];
+			}
+			xinstr->feedback_connection[1] = sbi.operators[OFFSET_4OP + CONNECTION];
+		} else {
+			xinstr->type = FM_PATCH_OPL2;
+		}
+
+		put->id.instr.std = SNDRV_SEQ_INSTR_TYPE2_OPL2_3;
+		put->id.instr.bank = 127;
+		put->id.instr.prg = sbi.channel;
+		put->cmd = SNDRV_SEQ_INSTR_PUT_CMD_CREATE;
+
+		memset (&ev, 0, sizeof(ev));
+		ev.source.client = SNDRV_SEQ_CLIENT_OSS;
+		ev.dest = arg->addr; 
+
+		ev.flags = SNDRV_SEQ_EVENT_LENGTH_VARUSR;
+		ev.queue = SNDRV_SEQ_QUEUE_DIRECT;
+
+		fs = snd_enter_user();
+	__again:
+		ev.type = SNDRV_SEQ_EVENT_INSTR_PUT;
+		ev.data.ext.len = size;
+		ev.data.ext.ptr = put;
+
+		err = snd_seq_instr_event(&opl3->fm_ops, opl3->ilist, &ev,
+				    opl3->seq_client, 0, 0);
+		if (err == -EBUSY) {
+			snd_seq_instr_header_t remove;
+
+			memset (&remove, 0, sizeof(remove));
+			remove.cmd = SNDRV_SEQ_INSTR_FREE_CMD_SINGLE;
+			remove.id.instr = put->id.instr;
+
+			/* remove instrument */
+			ev.type = SNDRV_SEQ_EVENT_INSTR_FREE;
+			ev.data.ext.len = sizeof(remove);
+			ev.data.ext.ptr = &remove;
+
+			snd_seq_instr_event(&opl3->fm_ops, opl3->ilist, &ev,
+					    opl3->seq_client, 0, 0);
+			goto __again;
+		}
+		snd_leave_user(fs);
+
+		kfree(put);
+	}
+	return err;
+}
+
+/* ioctl */
+static int snd_opl3_ioctl_seq_oss(snd_seq_oss_arg_t *arg, unsigned int cmd,
+				  unsigned long ioarg)
+{
+	opl3_t *opl3;
+
+	snd_assert(arg != NULL, return -ENXIO);
+	opl3 = arg->private_data;
+	switch (cmd) {
+		case SNDCTL_FM_LOAD_INSTR:
+			snd_printk("OPL3: Obsolete ioctl(SNDCTL_FM_LOAD_INSTR) used. Fix the program.\n");
+			return -EINVAL;
+
+		case SNDCTL_SYNTH_MEMAVL:
+			return 0x7fffffff;
+
+		case SNDCTL_FM_4OP_ENABLE:
+			// handled automatically by OPL instrument type
+			return 0;
+
+		default:
+			return -EINVAL;
+	}
+	return 0;
+}
+
+/* reset device */
+static int snd_opl3_reset_seq_oss(snd_seq_oss_arg_t *arg)
+{
+	opl3_t *opl3;
+
+	snd_assert(arg != NULL, return -ENXIO);
+	opl3 = arg->private_data;
+
+	return 0;
+}
diff --git a/sound/drivers/opl3/opl3_seq.c b/sound/drivers/opl3/opl3_seq.c
new file mode 100644
index 0000000..136964b
--- /dev/null
+++ b/sound/drivers/opl3/opl3_seq.c
@@ -0,0 +1,314 @@
+/*
+ *  Copyright (c) by Uros Bizjak <uros@kss-loka.si>
+ *
+ *  Midi Sequencer interface routines for OPL2/OPL3/OPL4 FM
+ *
+ *  OPL2/3 FM instrument loader:
+ *   alsa-tools/seq/sbiload/
+ *
+ *   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 "opl3_voice.h"
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Uros Bizjak <uros@kss-loka.si>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("ALSA driver for OPL3 FM synth");
+
+int use_internal_drums = 0;
+module_param(use_internal_drums, bool, 0444);
+MODULE_PARM_DESC(use_internal_drums, "Enable internal OPL2/3 drums.");
+
+int snd_opl3_synth_use_inc(opl3_t * opl3)
+{
+	if (!try_module_get(opl3->card->module))
+		return -EFAULT;
+	return 0;
+
+}
+
+void snd_opl3_synth_use_dec(opl3_t * opl3)
+{
+	module_put(opl3->card->module);
+}
+
+int snd_opl3_synth_setup(opl3_t * opl3)
+{
+	int idx;
+
+	down(&opl3->access_mutex);
+	if (opl3->used) {
+		up(&opl3->access_mutex);
+		return -EBUSY;
+	}
+	opl3->used++;
+	up(&opl3->access_mutex);
+
+	snd_opl3_reset(opl3);
+
+	for (idx = 0; idx < MAX_OPL3_VOICES; idx++) {
+		opl3->voices[idx].state = SNDRV_OPL3_ST_OFF;
+		opl3->voices[idx].time = 0;
+		opl3->voices[idx].keyon_reg = 0x00;
+	}
+	opl3->use_time = 0;
+	opl3->connection_reg = 0x00;
+	if (opl3->hardware >= OPL3_HW_OPL3) {
+		/* Clear 4-op connections */
+		opl3->command(opl3, OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT,
+				 opl3->connection_reg);
+		opl3->max_voices = MAX_OPL3_VOICES;
+	}
+	return 0;
+}
+
+void snd_opl3_synth_cleanup(opl3_t * opl3)
+{
+	unsigned long flags;
+
+	/* Stop system timer */
+	spin_lock_irqsave(&opl3->sys_timer_lock, flags);
+	if (opl3->sys_timer_status) {
+		del_timer(&opl3->tlist);
+		opl3->sys_timer_status = 0;
+	}
+	spin_unlock_irqrestore(&opl3->sys_timer_lock, flags);
+
+	snd_opl3_reset(opl3);
+	down(&opl3->access_mutex);
+	opl3->used--;
+	up(&opl3->access_mutex);
+}
+
+static int snd_opl3_synth_use(void *private_data, snd_seq_port_subscribe_t * info)
+{
+	opl3_t *opl3 = private_data;
+	int err;
+
+	if ((err = snd_opl3_synth_setup(opl3)) < 0)
+		return err;
+
+	if (use_internal_drums) {
+		/* Percussion mode */
+		opl3->voices[6].state = opl3->voices[7].state = 
+			opl3->voices[8].state = SNDRV_OPL3_ST_NOT_AVAIL;
+		snd_opl3_load_drums(opl3);
+		opl3->drum_reg = OPL3_PERCUSSION_ENABLE;
+		opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, opl3->drum_reg);
+	} else {
+		opl3->drum_reg = 0x00;
+	}
+
+	if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM) {
+		if ((err = snd_opl3_synth_use_inc(opl3)) < 0)
+			return err;
+	}
+	opl3->synth_mode = SNDRV_OPL3_MODE_SEQ;
+	return 0;
+}
+
+static int snd_opl3_synth_unuse(void *private_data, snd_seq_port_subscribe_t * info)
+{
+	opl3_t *opl3 = private_data;
+
+	snd_opl3_synth_cleanup(opl3);
+
+	if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM)
+		snd_opl3_synth_use_dec(opl3);
+	return 0;
+}
+
+/*
+ * MIDI emulation operators
+ */
+snd_midi_op_t opl3_ops = {
+	.note_on =		snd_opl3_note_on,
+	.note_off =		snd_opl3_note_off,
+	.key_press =		snd_opl3_key_press,
+	.note_terminate =	snd_opl3_terminate_note,
+	.control =		snd_opl3_control,
+	.nrpn =			snd_opl3_nrpn,
+	.sysex =		snd_opl3_sysex,
+};
+
+static int snd_opl3_synth_event_input(snd_seq_event_t * ev, int direct,
+				      void *private_data, int atomic, int hop)
+{
+	opl3_t *opl3 = private_data;
+
+	if (ev->type >= SNDRV_SEQ_EVENT_INSTR_BEGIN &&
+	    ev->type <= SNDRV_SEQ_EVENT_INSTR_CHANGE) {
+		if (direct) {
+			snd_seq_instr_event(&opl3->fm_ops, opl3->ilist, ev,
+					    opl3->seq_client, atomic, hop);
+		}
+	} else {
+		snd_midi_process_event(&opl3_ops, ev, opl3->chset);
+	}
+	return 0;
+}
+
+/* ------------------------------ */
+
+static void snd_opl3_synth_free_port(void *private_data)
+{
+	opl3_t *opl3 = private_data;
+
+	snd_midi_channel_free_set(opl3->chset);
+}
+
+static int snd_opl3_synth_create_port(opl3_t * opl3)
+{
+	snd_seq_port_callback_t callbacks;
+	char name[32];
+	int voices, opl_ver;
+
+	voices = (opl3->hardware < OPL3_HW_OPL3) ?
+		MAX_OPL2_VOICES : MAX_OPL3_VOICES;
+	opl3->chset = snd_midi_channel_alloc_set(16);
+	if (opl3->chset == NULL)
+		return -ENOMEM;
+	opl3->chset->private_data = opl3;
+
+	memset(&callbacks, 0, sizeof(callbacks));
+	callbacks.owner = THIS_MODULE;
+	callbacks.use = snd_opl3_synth_use;
+	callbacks.unuse = snd_opl3_synth_unuse;
+	callbacks.event_input = snd_opl3_synth_event_input;
+	callbacks.private_free = snd_opl3_synth_free_port;
+	callbacks.private_data = opl3;
+
+	opl_ver = (opl3->hardware & OPL3_HW_MASK) >> 8;
+	sprintf(name, "OPL%i FM Port", opl_ver);
+
+	opl3->chset->client = opl3->seq_client;
+	opl3->chset->port = snd_seq_event_port_attach(opl3->seq_client, &callbacks,
+						      SNDRV_SEQ_PORT_CAP_WRITE |
+						      SNDRV_SEQ_PORT_CAP_SUBS_WRITE,
+						      SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+						      SNDRV_SEQ_PORT_TYPE_MIDI_GM |
+						      SNDRV_SEQ_PORT_TYPE_SYNTH,
+						      16, voices,
+						      name);
+	if (opl3->chset->port < 0) {
+		snd_midi_channel_free_set(opl3->chset);
+		return opl3->chset->port;
+	}
+	return 0;
+}
+
+/* ------------------------------ */
+
+static int snd_opl3_seq_new_device(snd_seq_device_t *dev)
+{
+	opl3_t *opl3;
+	int client;
+	snd_seq_client_callback_t callbacks;
+	snd_seq_client_info_t cinfo;
+	int opl_ver;
+
+	opl3 = *(opl3_t **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	if (opl3 == NULL)
+		return -EINVAL;
+
+	spin_lock_init(&opl3->voice_lock);
+
+	opl3->seq_client = -1;
+
+	/* allocate new client */
+	memset(&callbacks, 0, sizeof(callbacks));
+	callbacks.private_data = opl3;
+	callbacks.allow_output = callbacks.allow_input = 1;
+	client = opl3->seq_client =
+	    snd_seq_create_kernel_client(opl3->card, opl3->seq_dev_num, &callbacks);
+	if (client < 0)
+		return client;
+
+	/* change name of client */
+	memset(&cinfo, 0, sizeof(cinfo));
+	cinfo.client = client;
+	cinfo.type = KERNEL_CLIENT;
+	opl_ver = (opl3->hardware & OPL3_HW_MASK) >> 8;
+	sprintf(cinfo.name, "OPL%i FM synth", opl_ver);
+	snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &cinfo);
+
+	snd_opl3_synth_create_port(opl3);
+
+	/* initialize instrument list */
+	opl3->ilist = snd_seq_instr_list_new();
+	if (opl3->ilist == NULL) {
+		snd_seq_delete_kernel_client(client);
+		opl3->seq_client = -1;
+		return -ENOMEM;
+	}
+	opl3->ilist->flags = SNDRV_SEQ_INSTR_FLG_DIRECT;
+	snd_seq_fm_init(&opl3->fm_ops, NULL);
+
+	/* setup system timer */
+	init_timer(&opl3->tlist);
+	opl3->tlist.function = snd_opl3_timer_func;
+	opl3->tlist.data = (unsigned long) opl3;
+	spin_lock_init(&opl3->sys_timer_lock);
+	opl3->sys_timer_status = 0;
+
+#ifdef CONFIG_SND_SEQUENCER_OSS
+	snd_opl3_init_seq_oss(opl3, cinfo.name);
+#endif
+	return 0;
+}
+
+static int snd_opl3_seq_delete_device(snd_seq_device_t *dev)
+{
+	opl3_t *opl3;
+
+	opl3 = *(opl3_t **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	if (opl3 == NULL)
+		return -EINVAL;
+
+#ifdef CONFIG_SND_SEQUENCER_OSS
+	snd_opl3_free_seq_oss(opl3);
+#endif
+	if (opl3->seq_client >= 0) {
+		snd_seq_delete_kernel_client(opl3->seq_client);
+		opl3->seq_client = -1;
+	}
+	if (opl3->ilist)
+		snd_seq_instr_list_free(&opl3->ilist);
+	return 0;
+}
+
+static int __init alsa_opl3_seq_init(void)
+{
+	static snd_seq_dev_ops_t ops =
+	{
+		snd_opl3_seq_new_device,
+		snd_opl3_seq_delete_device
+	};
+
+	return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_OPL3, &ops,
+					      sizeof(opl3_t*));
+}
+
+static void __exit alsa_opl3_seq_exit(void)
+{
+	snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_OPL3);
+}
+
+module_init(alsa_opl3_seq_init)
+module_exit(alsa_opl3_seq_exit)
diff --git a/sound/drivers/opl3/opl3_synth.c b/sound/drivers/opl3/opl3_synth.c
new file mode 100644
index 0000000..04f9f95
--- /dev/null
+++ b/sound/drivers/opl3/opl3_synth.c
@@ -0,0 +1,447 @@
+/*
+ *  Copyright (c) by Uros Bizjak <uros@kss-loka.si>
+ *                   
+ *  Routines for OPL2/OPL3/OPL4 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/opl3.h>
+#include <sound/asound_fm.h>
+
+/*
+ *    There is 18 possible 2 OP voices
+ *      (9 in the left and 9 in the right).
+ *      The first OP is the modulator and 2nd is the carrier.
+ *
+ *      The first three voices in the both sides may be connected
+ *      with another voice to a 4 OP voice. For example voice 0
+ *      can be connected with voice 3. The operators of voice 3 are
+ *      used as operators 3 and 4 of the new 4 OP voice.
+ *      In this case the 2 OP voice number 0 is the 'first half' and
+ *      voice 3 is the second.
+ */
+
+
+/*
+ *    Register offset table for OPL2/3 voices,
+ *    OPL2 / one OPL3 register array side only
+ */
+
+char snd_opl3_regmap[MAX_OPL2_VOICES][4] =
+{
+/*	  OP1   OP2   OP3   OP4		*/
+/*	 ------------------------	*/
+	{ 0x00, 0x03, 0x08, 0x0b },
+	{ 0x01, 0x04, 0x09, 0x0c },
+	{ 0x02, 0x05, 0x0a, 0x0d },
+
+	{ 0x08, 0x0b, 0x00, 0x00 },
+	{ 0x09, 0x0c, 0x00, 0x00 },
+	{ 0x0a, 0x0d, 0x00, 0x00 },
+
+	{ 0x10, 0x13, 0x00, 0x00 },	/* used by percussive voices */
+	{ 0x11, 0x14, 0x00, 0x00 },	/* if the percussive mode */
+	{ 0x12, 0x15, 0x00, 0x00 }	/* is selected (only left reg block) */
+};
+
+/*
+ * prototypes
+ */
+static int snd_opl3_play_note(opl3_t * opl3, snd_dm_fm_note_t * note);
+static int snd_opl3_set_voice(opl3_t * opl3, snd_dm_fm_voice_t * voice);
+static int snd_opl3_set_params(opl3_t * opl3, snd_dm_fm_params_t * params);
+static int snd_opl3_set_mode(opl3_t * opl3, int mode);
+static int snd_opl3_set_connection(opl3_t * opl3, int connection);
+
+/* ------------------------------ */
+
+/*
+ * open the device exclusively
+ */
+int snd_opl3_open(snd_hwdep_t * hw, struct file *file)
+{
+	opl3_t *opl3 = hw->private_data;
+
+	down(&opl3->access_mutex);
+	if (opl3->used) {
+		up(&opl3->access_mutex);
+		return -EAGAIN;
+	}
+	opl3->used++;
+	up(&opl3->access_mutex);
+
+	return 0;
+}
+
+/*
+ * ioctl for hwdep device:
+ */
+int snd_opl3_ioctl(snd_hwdep_t * hw, struct file *file,
+		   unsigned int cmd, unsigned long arg)
+{
+	opl3_t *opl3 = hw->private_data;
+	void __user *argp = (void __user *)arg;
+
+	snd_assert(opl3 != NULL, return -EINVAL);
+
+	switch (cmd) {
+		/* get information */
+	case SNDRV_DM_FM_IOCTL_INFO:
+		{
+			snd_dm_fm_info_t info;
+
+			info.fm_mode = opl3->fm_mode;
+			info.rhythm = opl3->rhythm;
+			if (copy_to_user(argp, &info, sizeof(snd_dm_fm_info_t)))
+				return -EFAULT;
+			return 0;
+		}
+
+	case SNDRV_DM_FM_IOCTL_RESET:
+#ifdef CONFIG_SND_OSSEMUL
+	case SNDRV_DM_FM_OSS_IOCTL_RESET:
+#endif
+		snd_opl3_reset(opl3);
+		return 0;
+
+	case SNDRV_DM_FM_IOCTL_PLAY_NOTE:
+#ifdef CONFIG_SND_OSSEMUL
+	case SNDRV_DM_FM_OSS_IOCTL_PLAY_NOTE:
+#endif
+		{
+			snd_dm_fm_note_t note;
+			if (copy_from_user(&note, argp, sizeof(snd_dm_fm_note_t)))
+				return -EFAULT;
+			return snd_opl3_play_note(opl3, &note);
+		}
+
+	case SNDRV_DM_FM_IOCTL_SET_VOICE:
+#ifdef CONFIG_SND_OSSEMUL
+	case SNDRV_DM_FM_OSS_IOCTL_SET_VOICE:
+#endif
+		{
+			snd_dm_fm_voice_t voice;
+			if (copy_from_user(&voice, argp, sizeof(snd_dm_fm_voice_t)))
+				return -EFAULT;
+			return snd_opl3_set_voice(opl3, &voice);
+		}
+
+	case SNDRV_DM_FM_IOCTL_SET_PARAMS:
+#ifdef CONFIG_SND_OSSEMUL
+	case SNDRV_DM_FM_OSS_IOCTL_SET_PARAMS:
+#endif
+		{
+			snd_dm_fm_params_t params;
+			if (copy_from_user(&params, argp, sizeof(snd_dm_fm_params_t)))
+				return -EFAULT;
+			return snd_opl3_set_params(opl3, &params);
+		}
+
+	case SNDRV_DM_FM_IOCTL_SET_MODE:
+#ifdef CONFIG_SND_OSSEMUL
+	case SNDRV_DM_FM_OSS_IOCTL_SET_MODE:
+#endif
+		return snd_opl3_set_mode(opl3, (int) arg);
+
+	case SNDRV_DM_FM_IOCTL_SET_CONNECTION:
+#ifdef CONFIG_SND_OSSEMUL
+	case SNDRV_DM_FM_OSS_IOCTL_SET_OPL:
+#endif
+		return snd_opl3_set_connection(opl3, (int) arg);
+
+#ifdef CONFIG_SND_DEBUG
+	default:
+		snd_printk("unknown IOCTL: 0x%x\n", cmd);
+#endif
+	}
+	return -ENOTTY;
+}
+
+/*
+ * close the device
+ */
+int snd_opl3_release(snd_hwdep_t * hw, struct file *file)
+{
+	opl3_t *opl3 = hw->private_data;
+
+	snd_opl3_reset(opl3);
+	down(&opl3->access_mutex);
+	opl3->used--;
+	up(&opl3->access_mutex);
+
+	return 0;
+}
+
+/* ------------------------------ */
+
+void snd_opl3_reset(opl3_t * opl3)
+{
+	unsigned short opl3_reg;
+
+	unsigned short reg_side;
+	unsigned char voice_offset;
+
+	int max_voices, i;
+
+	max_voices = (opl3->hardware < OPL3_HW_OPL3) ?
+		MAX_OPL2_VOICES : MAX_OPL3_VOICES;
+
+	for (i = 0; i < max_voices; i++) {
+		/* Get register array side and offset of voice */
+		if (i < MAX_OPL2_VOICES) {
+			/* Left register block for voices 0 .. 8 */
+			reg_side = OPL3_LEFT;
+			voice_offset = i;
+		} else {
+			/* Right register block for voices 9 .. 17 */
+			reg_side = OPL3_RIGHT;
+			voice_offset = i - MAX_OPL2_VOICES;
+		}
+		opl3_reg = reg_side | (OPL3_REG_KSL_LEVEL + snd_opl3_regmap[voice_offset][0]);
+		opl3->command(opl3, opl3_reg, OPL3_TOTAL_LEVEL_MASK); /* Operator 1 volume */
+		opl3_reg = reg_side | (OPL3_REG_KSL_LEVEL + snd_opl3_regmap[voice_offset][1]);
+		opl3->command(opl3, opl3_reg, OPL3_TOTAL_LEVEL_MASK); /* Operator 2 volume */
+
+		opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
+		opl3->command(opl3, opl3_reg, 0x00);	/* Note off */
+	}
+
+	opl3->max_voices = MAX_OPL2_VOICES;
+	opl3->fm_mode = SNDRV_DM_FM_MODE_OPL2;
+
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TEST, OPL3_ENABLE_WAVE_SELECT);
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, 0x00);	/* Melodic mode */
+	opl3->rhythm = 0;
+}
+
+
+static int snd_opl3_play_note(opl3_t * opl3, snd_dm_fm_note_t * note)
+{
+	unsigned short reg_side;
+	unsigned char voice_offset;
+
+	unsigned short opl3_reg;
+	unsigned char reg_val;
+
+	/* Voices 0 -  8 in OPL2 mode */
+	/* Voices 0 - 17 in OPL3 mode */
+	if (note->voice >= ((opl3->fm_mode == SNDRV_DM_FM_MODE_OPL3) ?
+			    MAX_OPL3_VOICES : MAX_OPL2_VOICES))
+		return -EINVAL;
+
+	/* Get register array side and offset of voice */
+	if (note->voice < MAX_OPL2_VOICES) {
+		/* Left register block for voices 0 .. 8 */
+		reg_side = OPL3_LEFT;
+		voice_offset = note->voice;
+	} else {
+		/* Right register block for voices 9 .. 17 */
+		reg_side = OPL3_RIGHT;
+		voice_offset = note->voice - MAX_OPL2_VOICES;
+	}
+
+	/* Set lower 8 bits of note frequency */
+	reg_val = (unsigned char) note->fnum;
+	opl3_reg = reg_side | (OPL3_REG_FNUM_LOW + voice_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+	
+	reg_val = 0x00;
+	/* Set output sound flag */
+	if (note->key_on)
+		reg_val |= OPL3_KEYON_BIT;
+	/* Set octave */
+	reg_val |= (note->octave << 2) & OPL3_BLOCKNUM_MASK;
+	/* Set higher 2 bits of note frequency */
+	reg_val |= (unsigned char) (note->fnum >> 8) & OPL3_FNUM_HIGH_MASK;
+
+	/* Set OPL3 KEYON_BLOCK register of requested voice */ 
+	opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+
+	return 0;
+}
+
+
+static int snd_opl3_set_voice(opl3_t * opl3, snd_dm_fm_voice_t * voice)
+{
+	unsigned short reg_side;
+	unsigned char op_offset;
+	unsigned char voice_offset;
+
+	unsigned short opl3_reg;
+	unsigned char reg_val;
+
+	/* Only operators 1 and 2 */
+	if (voice->op > 1)
+		return -EINVAL;
+	/* Voices 0 -  8 in OPL2 mode */
+	/* Voices 0 - 17 in OPL3 mode */
+	if (voice->voice >= ((opl3->fm_mode == SNDRV_DM_FM_MODE_OPL3) ?
+			     MAX_OPL3_VOICES : MAX_OPL2_VOICES))
+		return -EINVAL;
+
+	/* Get register array side and offset of voice */
+	if (voice->voice < MAX_OPL2_VOICES) {
+		/* Left register block for voices 0 .. 8 */
+		reg_side = OPL3_LEFT;
+		voice_offset = voice->voice;
+	} else {
+		/* Right register block for voices 9 .. 17 */
+		reg_side = OPL3_RIGHT;
+		voice_offset = voice->voice - MAX_OPL2_VOICES;
+	}
+	/* Get register offset of operator */
+	op_offset = snd_opl3_regmap[voice_offset][voice->op];
+
+	reg_val = 0x00;
+	/* Set amplitude modulation (tremolo) effect */
+	if (voice->am)
+		reg_val |= OPL3_TREMOLO_ON;
+	/* Set vibrato effect */
+	if (voice->vibrato)
+		reg_val |= OPL3_VIBRATO_ON;
+	/* Set sustaining sound phase */
+	if (voice->do_sustain)
+		reg_val |= OPL3_SUSTAIN_ON;
+	/* Set keyboard scaling bit */ 
+	if (voice->kbd_scale)
+		reg_val |= OPL3_KSR;
+	/* Set harmonic or frequency multiplier */
+	reg_val |= voice->harmonic & OPL3_MULTIPLE_MASK;
+
+	/* Set OPL3 AM_VIB register of requested voice/operator */ 
+	opl3_reg = reg_side | (OPL3_REG_AM_VIB + op_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+
+	/* Set decreasing volume of higher notes */
+	reg_val = (voice->scale_level << 6) & OPL3_KSL_MASK;
+	/* Set output volume */
+	reg_val |= ~voice->volume & OPL3_TOTAL_LEVEL_MASK;
+
+	/* Set OPL3 KSL_LEVEL register of requested voice/operator */ 
+	opl3_reg = reg_side | (OPL3_REG_KSL_LEVEL + op_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+
+	/* Set attack phase level */
+	reg_val = (voice->attack << 4) & OPL3_ATTACK_MASK;
+	/* Set decay phase level */
+	reg_val |= voice->decay & OPL3_DECAY_MASK;
+
+	/* Set OPL3 ATTACK_DECAY register of requested voice/operator */ 
+	opl3_reg = reg_side | (OPL3_REG_ATTACK_DECAY + op_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+
+	/* Set sustain phase level */
+	reg_val = (voice->sustain << 4) & OPL3_SUSTAIN_MASK;
+	/* Set release phase level */
+	reg_val |= voice->release & OPL3_RELEASE_MASK;
+
+	/* Set OPL3 SUSTAIN_RELEASE register of requested voice/operator */ 
+	opl3_reg = reg_side | (OPL3_REG_SUSTAIN_RELEASE + op_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+
+	/* Set inter-operator feedback */
+	reg_val = (voice->feedback << 1) & OPL3_FEEDBACK_MASK;
+	/* Set inter-operator connection */
+	if (voice->connection)
+		reg_val |= OPL3_CONNECTION_BIT;
+	/* OPL-3 only */
+	if (opl3->fm_mode == SNDRV_DM_FM_MODE_OPL3) {
+		if (voice->left)
+			reg_val |= OPL3_VOICE_TO_LEFT;
+		if (voice->right)
+			reg_val |= OPL3_VOICE_TO_RIGHT;
+	}
+	/* Feedback/connection bits are applicable to voice */
+	opl3_reg = reg_side | (OPL3_REG_FEEDBACK_CONNECTION + voice_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+
+	/* Select waveform */
+	reg_val = voice->waveform & OPL3_WAVE_SELECT_MASK;
+	opl3_reg = reg_side | (OPL3_REG_WAVE_SELECT + op_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+
+	return 0;
+}
+
+static int snd_opl3_set_params(opl3_t * opl3, snd_dm_fm_params_t * params)
+{
+	unsigned char reg_val;
+
+	reg_val = 0x00;
+	/* Set keyboard split method */
+	if (params->kbd_split)
+		reg_val |= OPL3_KEYBOARD_SPLIT;
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_KBD_SPLIT, reg_val);
+
+	reg_val = 0x00;
+	/* Set amplitude modulation (tremolo) depth */
+	if (params->am_depth)
+		reg_val |= OPL3_TREMOLO_DEPTH;
+	/* Set vibrato depth */
+	if (params->vib_depth)
+		reg_val |= OPL3_VIBRATO_DEPTH;
+	/* Set percussion mode */
+	if (params->rhythm) {
+		reg_val |= OPL3_PERCUSSION_ENABLE;
+		opl3->rhythm = 1;
+	} else {
+		opl3->rhythm = 0;
+	}
+	/* Play percussion instruments */
+	if (params->bass)
+		reg_val |= OPL3_BASSDRUM_ON;
+	if (params->snare)
+		reg_val |= OPL3_SNAREDRUM_ON;
+	if (params->tomtom)
+		reg_val |= OPL3_TOMTOM_ON;
+	if (params->cymbal)
+		reg_val |= OPL3_CYMBAL_ON;
+	if (params->hihat)
+		reg_val |= OPL3_HIHAT_ON;
+
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, reg_val);
+	return 0;
+}
+
+static int snd_opl3_set_mode(opl3_t * opl3, int mode)
+{
+	if ((mode == SNDRV_DM_FM_MODE_OPL3) && (opl3->hardware < OPL3_HW_OPL3))
+		return -EINVAL;
+
+	opl3->fm_mode = mode;
+	if (opl3->hardware >= OPL3_HW_OPL3)
+		opl3->command(opl3, OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT, 0x00);	/* Clear 4-op connections */
+
+	return 0;
+}
+
+static int snd_opl3_set_connection(opl3_t * opl3, int connection)
+{
+	unsigned char reg_val;
+
+	/* OPL-3 only */
+	if (opl3->fm_mode != SNDRV_DM_FM_MODE_OPL3)
+		return -EINVAL;
+
+	reg_val = connection & (OPL3_RIGHT_4OP_0 | OPL3_RIGHT_4OP_1 | OPL3_RIGHT_4OP_2 |
+				OPL3_LEFT_4OP_0 | OPL3_LEFT_4OP_1 | OPL3_LEFT_4OP_2);
+	/* Set 4-op connections */
+	opl3->command(opl3, OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT, reg_val);
+
+	return 0;
+}
diff --git a/sound/drivers/opl3/opl3_voice.h b/sound/drivers/opl3/opl3_voice.h
new file mode 100644
index 0000000..63346a5
--- /dev/null
+++ b/sound/drivers/opl3/opl3_voice.h
@@ -0,0 +1,52 @@
+#ifndef __OPL3_VOICE_H
+#define __OPL3_VOICE_H
+
+/*
+ *  Copyright (c) 2000 Uros Bizjak <uros@kss-loka.si>
+ *
+ *   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/opl3.h>
+
+/* Prototypes for opl3_seq.c */
+int snd_opl3_synth_use_inc(opl3_t * opl3);
+void snd_opl3_synth_use_dec(opl3_t * opl3);
+int snd_opl3_synth_setup(opl3_t * opl3);
+void snd_opl3_synth_cleanup(opl3_t * opl3);
+
+/* Prototypes for opl3_midi.c */
+void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan);
+void snd_opl3_note_off(void *p, int note, int vel, struct snd_midi_channel *chan);
+void snd_opl3_key_press(void *p, int note, int vel, struct snd_midi_channel *chan);
+void snd_opl3_terminate_note(void *p, int note, snd_midi_channel_t *chan);
+void snd_opl3_control(void *p, int type, struct snd_midi_channel *chan);
+void snd_opl3_nrpn(void *p, snd_midi_channel_t *chan, snd_midi_channel_set_t *chset);
+void snd_opl3_sysex(void *p, unsigned char *buf, int len, int parsed, snd_midi_channel_set_t *chset);
+
+void snd_opl3_calc_volume(unsigned char *reg, int vel, snd_midi_channel_t *chan);
+void snd_opl3_timer_func(unsigned long data);
+
+/* Prototypes for opl3_drums.c */
+void snd_opl3_load_drums(opl3_t *opl3);
+void snd_opl3_drum_switch(opl3_t *opl3, int note, int on_off, int vel, snd_midi_channel_t *chan);
+
+/* Prototypes for opl3_oss.c */
+#ifdef CONFIG_SND_SEQUENCER_OSS
+void snd_opl3_init_seq_oss(opl3_t *opl3, char *name);
+void snd_opl3_free_seq_oss(opl3_t *opl3);
+#endif
+
+#endif
diff --git a/sound/drivers/opl4/Makefile b/sound/drivers/opl4/Makefile
new file mode 100644
index 0000000..141aacb
--- /dev/null
+++ b/sound/drivers/opl4/Makefile
@@ -0,0 +1,18 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-opl4-lib-objs := opl4_lib.o opl4_mixer.o opl4_proc.o
+snd-opl4-synth-objs := opl4_seq.o opl4_synth.o yrw801.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)))
+
+obj-$(CONFIG_SND_OPL4_LIB) += snd-opl4-lib.o
+obj-$(call sequencer,$(CONFIG_SND_OPL4_LIB)) += snd-opl4-synth.o
diff --git a/sound/drivers/opl4/opl4_lib.c b/sound/drivers/opl4/opl4_lib.c
new file mode 100644
index 0000000..8261464
--- /dev/null
+++ b/sound/drivers/opl4/opl4_lib.c
@@ -0,0 +1,281 @@
+/*
+ * Functions for accessing OPL4 devices
+ * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.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 "opl4_local.h"
+#include <sound/initval.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <asm/io.h>
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("OPL4 driver");
+MODULE_LICENSE("GPL");
+
+static void inline snd_opl4_wait(opl4_t *opl4)
+{
+	int timeout = 10;
+	while ((inb(opl4->fm_port) & OPL4_STATUS_BUSY) && --timeout > 0)
+		;
+}
+
+void snd_opl4_write(opl4_t *opl4, u8 reg, u8 value)
+{
+	snd_opl4_wait(opl4);
+	outb(reg, opl4->pcm_port);
+
+	snd_opl4_wait(opl4);
+	outb(value, opl4->pcm_port + 1);
+}
+
+u8 snd_opl4_read(opl4_t *opl4, u8 reg)
+{
+	snd_opl4_wait(opl4);
+	outb(reg, opl4->pcm_port);
+
+	snd_opl4_wait(opl4);
+	return inb(opl4->pcm_port + 1);
+}
+
+void snd_opl4_read_memory(opl4_t *opl4, char *buf, int offset, int size)
+{
+	unsigned long flags;
+	u8 memcfg;
+
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+
+	memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION);
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT);
+
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16);
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8);
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset);
+
+	snd_opl4_wait(opl4);
+	outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port);
+	snd_opl4_wait(opl4);
+	insb(opl4->pcm_port + 1, buf, size);
+
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg);
+
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+}
+
+void snd_opl4_write_memory(opl4_t *opl4, const char *buf, int offset, int size)
+{
+	unsigned long flags;
+	u8 memcfg;
+
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+
+	memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION);
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT);
+
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16);
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8);
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset);
+
+	snd_opl4_wait(opl4);
+	outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port);
+	snd_opl4_wait(opl4);
+	outsb(opl4->pcm_port + 1, buf, size);
+
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg);
+
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+}
+
+static void snd_opl4_enable_opl4(opl4_t *opl4)
+{
+	outb(OPL3_REG_MODE, opl4->fm_port + 2);
+	inb(opl4->fm_port);
+	inb(opl4->fm_port);
+	outb(OPL3_OPL3_ENABLE | OPL3_OPL4_ENABLE, opl4->fm_port + 3);
+	inb(opl4->fm_port);
+	inb(opl4->fm_port);
+}
+
+static int snd_opl4_detect(opl4_t *opl4)
+{
+	u8 id1, id2;
+
+	snd_opl4_enable_opl4(opl4);
+
+	id1 = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION);
+	snd_printdd("OPL4[02]=%02x\n", id1);
+	switch (id1 & OPL4_DEVICE_ID_MASK) {
+	case 0x20:
+		opl4->hardware = OPL3_HW_OPL4;
+		break;
+	case 0x40:
+		opl4->hardware = OPL3_HW_OPL4_ML;
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x00);
+	snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0xff);
+	id1 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_FM);
+	id2 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_PCM);
+	snd_printdd("OPL4 id1=%02x id2=%02x\n", id1, id2);
+       	if (id1 != 0x00 || id2 != 0xff)
+		return -ENODEV;
+
+	snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x3f);
+	snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0x3f);
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, 0x00);
+	return 0;
+}
+
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+static void snd_opl4_seq_dev_free(snd_seq_device_t *seq_dev)
+{
+	opl4_t *opl4 = seq_dev->private_data;
+	opl4->seq_dev = NULL;
+}
+
+static int snd_opl4_create_seq_dev(opl4_t *opl4, int seq_device)
+{
+	opl4->seq_dev_num = seq_device;
+	if (snd_seq_device_new(opl4->card, seq_device, SNDRV_SEQ_DEV_ID_OPL4,
+			       sizeof(opl4_t *), &opl4->seq_dev) >= 0) {
+		strcpy(opl4->seq_dev->name, "OPL4 Wavetable");
+		*(opl4_t **)SNDRV_SEQ_DEVICE_ARGPTR(opl4->seq_dev) = opl4;
+		opl4->seq_dev->private_data = opl4;
+		opl4->seq_dev->private_free = snd_opl4_seq_dev_free;
+	}
+	return 0;
+}
+#endif
+
+static void snd_opl4_free(opl4_t *opl4)
+{
+#ifdef CONFIG_PROC_FS
+	snd_opl4_free_proc(opl4);
+#endif
+	if (opl4->res_fm_port) {
+		release_resource(opl4->res_fm_port);
+		kfree_nocheck(opl4->res_fm_port);
+	}
+	if (opl4->res_pcm_port) {
+		release_resource(opl4->res_pcm_port);
+		kfree_nocheck(opl4->res_pcm_port);
+	}
+	kfree(opl4);
+}
+
+static int snd_opl4_dev_free(snd_device_t *device)
+{
+	opl4_t *opl4 = device->device_data;
+	snd_opl4_free(opl4);
+	return 0;
+}
+
+int snd_opl4_create(snd_card_t *card,
+		    unsigned long fm_port, unsigned long pcm_port,
+		    int seq_device,
+		    opl3_t **ropl3, opl4_t **ropl4)
+{
+	opl4_t *opl4;
+	opl3_t *opl3;
+	int err;
+	static snd_device_ops_t ops = {
+		.dev_free = snd_opl4_dev_free
+	};
+
+	if (ropl3)
+		*ropl3 = NULL;
+	if (ropl4)
+		*ropl4 = NULL;
+
+	opl4 = kcalloc(1, sizeof(*opl4), GFP_KERNEL);
+	if (!opl4)
+		return -ENOMEM;
+
+	opl4->res_fm_port = request_region(fm_port, 8, "OPL4 FM");
+	opl4->res_pcm_port = request_region(pcm_port, 8, "OPL4 PCM/MIX");
+	if (!opl4->res_fm_port || !opl4->res_pcm_port) {
+		snd_printk(KERN_ERR "opl4: can't grab ports 0x%lx, 0x%lx\n", fm_port, pcm_port);
+		snd_opl4_free(opl4);
+		return -EBUSY;
+	}
+
+	opl4->card = card;
+	opl4->fm_port = fm_port;
+	opl4->pcm_port = pcm_port;
+	spin_lock_init(&opl4->reg_lock);
+	init_MUTEX(&opl4->access_mutex);
+
+	err = snd_opl4_detect(opl4);
+	if (err < 0) {
+		snd_opl4_free(opl4);
+		snd_printd("OPL4 chip not detected at %#lx/%#lx\n", fm_port, pcm_port);
+		return err;
+	}
+
+	err = snd_device_new(card, SNDRV_DEV_CODEC, opl4, &ops);
+	if (err < 0) {
+		snd_opl4_free(opl4);
+		return err;
+	}
+
+	err = snd_opl3_create(card, fm_port, fm_port + 2, opl4->hardware, 1, &opl3);
+	if (err < 0) {
+		snd_device_free(card, opl4);
+		return err;
+	}
+
+	/* opl3 initialization disabled opl4, so reenable */
+	snd_opl4_enable_opl4(opl4);
+
+	snd_opl4_create_mixer(opl4);
+#ifdef CONFIG_PROC_FS
+	snd_opl4_create_proc(opl4);
+#endif
+
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+	opl4->seq_client = -1;
+	if (opl4->hardware < OPL3_HW_OPL4_ML)
+		snd_opl4_create_seq_dev(opl4, seq_device);
+#endif
+
+	if (ropl3)
+		*ropl3 = opl3;
+	if (ropl4)
+		*ropl4 = opl4;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_opl4_write);
+EXPORT_SYMBOL(snd_opl4_read);
+EXPORT_SYMBOL(snd_opl4_write_memory);
+EXPORT_SYMBOL(snd_opl4_read_memory);
+EXPORT_SYMBOL(snd_opl4_create);
+
+static int __init alsa_opl4_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_opl4_exit(void)
+{
+}
+
+module_init(alsa_opl4_init)
+module_exit(alsa_opl4_exit)
diff --git a/sound/drivers/opl4/opl4_local.h b/sound/drivers/opl4/opl4_local.h
new file mode 100644
index 0000000..c455680
--- /dev/null
+++ b/sound/drivers/opl4/opl4_local.h
@@ -0,0 +1,232 @@
+/*
+ * Local definitions for the OPL4 driver
+ *
+ * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed and/or modified 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 SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef __OPL4_LOCAL_H
+#define __OPL4_LOCAL_H
+
+#include <sound/opl4.h>
+
+/*
+ * Register numbers
+ */
+
+#define OPL4_REG_TEST0			0x00
+#define OPL4_REG_TEST1			0x01
+
+#define OPL4_REG_MEMORY_CONFIGURATION	0x02
+#define   OPL4_MODE_BIT			0x01
+#define   OPL4_MTYPE_BIT		0x02
+#define   OPL4_TONE_HEADER_MASK		0x1c
+#define   OPL4_DEVICE_ID_MASK		0xe0
+
+#define OPL4_REG_MEMORY_ADDRESS_HIGH	0x03
+#define OPL4_REG_MEMORY_ADDRESS_MID	0x04
+#define OPL4_REG_MEMORY_ADDRESS_LOW	0x05
+#define OPL4_REG_MEMORY_DATA		0x06
+
+/*
+ * Offsets to the register banks for voices. To get the
+ * register number just add the voice number to the bank offset.
+ *
+ * Wave Table Number low bits (0x08 to 0x1F)
+ */
+#define OPL4_REG_TONE_NUMBER		0x08
+
+/* Wave Table Number high bit, F-Number low bits (0x20 to 0x37) */
+#define OPL4_REG_F_NUMBER		0x20
+#define   OPL4_TONE_NUMBER_BIT8		0x01
+#define   OPL4_F_NUMBER_LOW_MASK	0xfe
+
+/* F-Number high bits, Octave, Pseudo-Reverb (0x38 to 0x4F) */
+#define OPL4_REG_OCTAVE			0x38
+#define   OPL4_F_NUMBER_HIGH_MASK	0x07
+#define   OPL4_BLOCK_MASK		0xf0
+#define   OPL4_PSEUDO_REVERB_BIT	0x08
+
+/* Total Level, Level Direct (0x50 to 0x67) */
+#define OPL4_REG_LEVEL			0x50
+#define   OPL4_TOTAL_LEVEL_MASK		0xfe
+#define   OPL4_LEVEL_DIRECT_BIT		0x01
+
+/* Key On, Damp, LFO RST, CH, Panpot (0x68 to 0x7F) */
+#define OPL4_REG_MISC			0x68
+#define   OPL4_KEY_ON_BIT		0x80
+#define   OPL4_DAMP_BIT			0x40
+#define   OPL4_LFO_RESET_BIT		0x20
+#define   OPL4_OUTPUT_CHANNEL_BIT	0x10
+#define   OPL4_PAN_POT_MASK		0x0f
+
+/* LFO, VIB (0x80 to 0x97) */
+#define OPL4_REG_LFO_VIBRATO		0x80
+#define   OPL4_LFO_FREQUENCY_MASK	0x38
+#define   OPL4_VIBRATO_DEPTH_MASK	0x07
+#define   OPL4_CHORUS_SEND_MASK		0xc0 /* ML only */
+
+/* Attack / Decay 1 rate (0x98 to 0xAF) */
+#define OPL4_REG_ATTACK_DECAY1		0x98
+#define   OPL4_ATTACK_RATE_MASK		0xf0
+#define   OPL4_DECAY1_RATE_MASK		0x0f
+
+/* Decay level / 2 rate (0xB0 to 0xC7) */
+#define OPL4_REG_LEVEL_DECAY2		0xb0
+#define   OPL4_DECAY_LEVEL_MASK		0xf0
+#define   OPL4_DECAY2_RATE_MASK		0x0f
+
+/* Release rate / Rate correction (0xC8 to 0xDF) */
+#define OPL4_REG_RELEASE_CORRECTION	0xc8
+#define   OPL4_RELEASE_RATE_MASK	0x0f
+#define   OPL4_RATE_INTERPOLATION_MASK	0xf0
+
+/* AM (0xE0 to 0xF7) */
+#define OPL4_REG_TREMOLO		0xe0
+#define   OPL4_TREMOLO_DEPTH_MASK	0x07
+#define   OPL4_REVERB_SEND_MASK		0xe0 /* ML only */
+
+/* Mixer */
+#define OPL4_REG_MIX_CONTROL_FM		0xf8
+#define OPL4_REG_MIX_CONTROL_PCM	0xf9
+#define   OPL4_MIX_LEFT_MASK		0x07
+#define   OPL4_MIX_RIGHT_MASK		0x38
+
+#define OPL4_REG_ATC			0xfa
+#define   OPL4_ATC_BIT			0x01 /* ???, ML only */
+
+/* bits in the OPL3 Status register */
+#define OPL4_STATUS_BUSY		0x01
+#define OPL4_STATUS_LOAD		0x02
+
+
+#define OPL4_MAX_VOICES 24
+
+#define SNDRV_SEQ_DEV_ID_OPL4 "opl4-synth"
+
+
+typedef struct opl4_sound {
+	u16 tone;
+	s16 pitch_offset;
+	u8 key_scaling;
+	s8 panpot;
+	u8 vibrato;
+	u8 tone_attenuate;
+	u8 volume_factor;
+	u8 reg_lfo_vibrato;
+	u8 reg_attack_decay1;
+	u8 reg_level_decay2;
+	u8 reg_release_correction;
+	u8 reg_tremolo;
+} opl4_sound_t;
+
+typedef struct opl4_region {
+	u8 key_min, key_max;
+	opl4_sound_t sound;
+} opl4_region_t;
+
+typedef struct opl4_region_ptr {
+	int count;
+	const opl4_region_t *regions;
+} opl4_region_ptr_t;
+
+typedef struct opl4_voice {
+	struct list_head list;
+	int number;
+	snd_midi_channel_t *chan;
+	int note;
+	int velocity;
+	const opl4_sound_t *sound;
+	u8 level_direct;
+	u8 reg_f_number;
+	u8 reg_misc;
+	u8 reg_lfo_vibrato;
+} opl4_voice_t;
+
+struct opl4 {
+	unsigned long fm_port;
+	unsigned long pcm_port;
+	struct resource *res_fm_port;
+	struct resource *res_pcm_port;
+	unsigned short hardware;
+	spinlock_t reg_lock;
+	snd_card_t *card;
+
+#ifdef CONFIG_PROC_FS
+	snd_info_entry_t *proc_entry;
+	int memory_access;
+#endif
+	struct semaphore access_mutex;
+
+#if defined(CONFIG_SND_SEQUENCER) || defined(CONFIG_SND_SEQUENCER_MODULE)
+	int used;
+
+	int seq_dev_num;
+	int seq_client;
+	snd_seq_device_t *seq_dev;
+
+	snd_midi_channel_set_t *chset;
+	opl4_voice_t voices[OPL4_MAX_VOICES];
+	struct list_head off_voices;
+	struct list_head on_voices;
+#endif
+};
+
+/* opl4_lib.c */
+void snd_opl4_write(opl4_t *opl4, u8 reg, u8 value);
+u8 snd_opl4_read(opl4_t *opl4, u8 reg);
+void snd_opl4_read_memory(opl4_t *opl4, char *buf, int offset, int size);
+void snd_opl4_write_memory(opl4_t *opl4, const char *buf, int offset, int size);
+
+/* opl4_mixer.c */
+int snd_opl4_create_mixer(opl4_t *opl4);
+
+#ifdef CONFIG_PROC_FS
+/* opl4_proc.c */
+int snd_opl4_create_proc(opl4_t *opl4);
+void snd_opl4_free_proc(opl4_t *opl4);
+#endif
+
+/* opl4_seq.c */
+extern int volume_boost;
+
+/* opl4_synth.c */
+void snd_opl4_synth_reset(opl4_t *opl4);
+void snd_opl4_synth_shutdown(opl4_t *opl4);
+void snd_opl4_note_on(void *p, int note, int vel, snd_midi_channel_t *chan);
+void snd_opl4_note_off(void *p, int note, int vel, snd_midi_channel_t *chan);
+void snd_opl4_terminate_note(void *p, int note, snd_midi_channel_t *chan);
+void snd_opl4_control(void *p, int type, snd_midi_channel_t *chan);
+void snd_opl4_sysex(void *p, unsigned char *buf, int len, int parsed, snd_midi_channel_set_t *chset);
+
+/* yrw801.c */
+int snd_yrw801_detect(opl4_t *opl4);
+extern const opl4_region_ptr_t snd_yrw801_regions[];
+
+#endif /* __OPL4_LOCAL_H */
diff --git a/sound/drivers/opl4/opl4_mixer.c b/sound/drivers/opl4/opl4_mixer.c
new file mode 100644
index 0000000..ec7a228
--- /dev/null
+++ b/sound/drivers/opl4/opl4_mixer.c
@@ -0,0 +1,95 @@
+/*
+ * OPL4 mixer functions
+ * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.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 "opl4_local.h"
+#include <sound/control.h>
+
+static int snd_opl4_ctl_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 = 7;
+	return 0;
+}
+
+static int snd_opl4_ctl_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	opl4_t *opl4 = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	u8 reg = kcontrol->private_value;
+	u8 value;
+
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+	value = snd_opl4_read(opl4, reg);
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+	ucontrol->value.integer.value[0] = 7 - (value & 7);
+	ucontrol->value.integer.value[1] = 7 - ((value >> 3) & 7);
+	return 0;
+}
+
+static int snd_opl4_ctl_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	opl4_t *opl4 = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	u8 reg = kcontrol->private_value;
+	u8 value, old_value;
+
+	value = (7 - (ucontrol->value.integer.value[0] & 7)) |
+		((7 - (ucontrol->value.integer.value[1] & 7)) << 3);
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+	old_value = snd_opl4_read(opl4, reg);
+	snd_opl4_write(opl4, reg, value);
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+	return value != old_value;
+}
+
+static snd_kcontrol_new_t snd_opl4_controls[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "FM Playback Volume",
+		.info = snd_opl4_ctl_info,
+		.get = snd_opl4_ctl_get,
+		.put = snd_opl4_ctl_put,
+		.private_value = OPL4_REG_MIX_CONTROL_FM
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Wavetable Playback Volume",
+		.info = snd_opl4_ctl_info,
+		.get = snd_opl4_ctl_get,
+		.put = snd_opl4_ctl_put,
+		.private_value = OPL4_REG_MIX_CONTROL_PCM
+	}
+};
+
+int snd_opl4_create_mixer(opl4_t *opl4)
+{
+	snd_card_t *card = opl4->card;
+	int i, err;
+
+	strcat(card->mixername, ",OPL4");
+
+	for (i = 0; i < 2; ++i) {
+		err = snd_ctl_add(card, snd_ctl_new1(&snd_opl4_controls[i], opl4));
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
diff --git a/sound/drivers/opl4/opl4_proc.c b/sound/drivers/opl4/opl4_proc.c
new file mode 100644
index 0000000..6a14862
--- /dev/null
+++ b/sound/drivers/opl4/opl4_proc.c
@@ -0,0 +1,166 @@
+/*
+ * Functions for the OPL4 proc file
+ * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.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 "opl4_local.h"
+#include <linux/vmalloc.h>
+#include <sound/info.h>
+
+#ifdef CONFIG_PROC_FS
+
+static int snd_opl4_mem_proc_open(snd_info_entry_t *entry,
+				  unsigned short mode, void **file_private_data)
+{
+	opl4_t *opl4 = entry->private_data;
+
+	down(&opl4->access_mutex);
+	if (opl4->memory_access) {
+		up(&opl4->access_mutex);
+		return -EBUSY;
+	}
+	opl4->memory_access++;
+	up(&opl4->access_mutex);
+	return 0;
+}
+
+static int snd_opl4_mem_proc_release(snd_info_entry_t *entry,
+				     unsigned short mode, void *file_private_data)
+{
+	opl4_t *opl4 = entry->private_data;
+
+	down(&opl4->access_mutex);
+	opl4->memory_access--;
+	up(&opl4->access_mutex);
+	return 0;
+}
+
+static long snd_opl4_mem_proc_read(snd_info_entry_t *entry, void *file_private_data,
+				   struct file *file, char __user *_buf,
+				   unsigned long count, unsigned long pos)
+{
+	opl4_t *opl4 = entry->private_data;
+	long size;
+	char* buf;
+
+	size = count;
+	if (pos + size > entry->size)
+		size = entry->size - pos;
+	if (size > 0) {
+		buf = vmalloc(size);
+		if (!buf)
+			return -ENOMEM;
+		snd_opl4_read_memory(opl4, buf, pos, size);
+		if (copy_to_user(_buf, buf, size)) {
+			vfree(buf);
+			return -EFAULT;
+		}
+		vfree(buf);
+		return size;
+	}
+	return 0;
+}
+
+static long snd_opl4_mem_proc_write(snd_info_entry_t *entry, void *file_private_data,
+				    struct file *file, const char __user *_buf,
+				    unsigned long count, unsigned long pos)
+{
+	opl4_t *opl4 = entry->private_data;
+	long size;
+	char *buf;
+
+	size = count;
+	if (pos + size > entry->size)
+		size = entry->size - pos;
+	if (size > 0) {
+		buf = vmalloc(size);
+		if (!buf)
+			return -ENOMEM;
+		if (copy_from_user(buf, _buf, size)) {
+			vfree(buf);
+			return -EFAULT;
+		}
+		snd_opl4_write_memory(opl4, buf, pos, size);
+		vfree(buf);
+		return size;
+	}
+	return 0;
+}
+
+static long long snd_opl4_mem_proc_llseek(snd_info_entry_t *entry, void *file_private_data,
+					  struct file *file, long long offset, int orig)
+{
+	switch (orig) {
+	case 0: /* SEEK_SET */
+		file->f_pos = offset;
+		break;
+	case 1: /* SEEK_CUR */
+		file->f_pos += offset;
+		break;
+	case 2: /* SEEK_END, offset is negative */
+		file->f_pos = entry->size + offset;
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (file->f_pos > entry->size)
+		file->f_pos = entry->size;
+	return file->f_pos;
+}
+
+static struct snd_info_entry_ops snd_opl4_mem_proc_ops = {
+	.open = snd_opl4_mem_proc_open,
+	.release = snd_opl4_mem_proc_release,
+	.read = snd_opl4_mem_proc_read,
+	.write = snd_opl4_mem_proc_write,
+	.llseek = snd_opl4_mem_proc_llseek,
+};
+
+int snd_opl4_create_proc(opl4_t *opl4)
+{
+	snd_info_entry_t *entry;
+
+	entry = snd_info_create_card_entry(opl4->card, "opl4-mem", opl4->card->proc_root);
+	if (entry) {
+		if (opl4->hardware < OPL3_HW_OPL4_ML) {
+			/* OPL4 can access 4 MB external ROM/SRAM */
+			entry->mode |= S_IWUSR;
+			entry->size = 4 * 1024 * 1024;
+		} else {
+			/* OPL4-ML has 1 MB internal ROM */
+			entry->size = 1 * 1024 * 1024;
+		}
+		entry->content = SNDRV_INFO_CONTENT_DATA;
+		entry->c.ops = &snd_opl4_mem_proc_ops;
+		entry->module = THIS_MODULE;
+		entry->private_data = opl4;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	opl4->proc_entry = entry;
+	return 0;
+}
+
+void snd_opl4_free_proc(opl4_t *opl4)
+{
+	if (opl4->proc_entry)
+		snd_info_unregister(opl4->proc_entry);
+}
+
+#endif /* CONFIG_PROC_FS */
diff --git a/sound/drivers/opl4/opl4_seq.c b/sound/drivers/opl4/opl4_seq.c
new file mode 100644
index 0000000..958dfe8
--- /dev/null
+++ b/sound/drivers/opl4/opl4_seq.c
@@ -0,0 +1,223 @@
+/*
+ * OPL4 sequencer functions
+ *
+ * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed and/or modified 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 SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opl4_local.h"
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("OPL4 wavetable synth driver");
+MODULE_LICENSE("Dual BSD/GPL");
+
+int volume_boost = 8;
+
+module_param(volume_boost, int, 0644);
+MODULE_PARM_DESC(volume_boost, "Additional volume for OPL4 wavetable sounds.");
+
+static int snd_opl4_seq_use_inc(opl4_t *opl4)
+{
+	if (!try_module_get(opl4->card->module))
+		return -EFAULT;
+	return 0;
+}
+
+static void snd_opl4_seq_use_dec(opl4_t *opl4)
+{
+	module_put(opl4->card->module);
+}
+
+static int snd_opl4_seq_use(void *private_data, snd_seq_port_subscribe_t *info)
+{
+	opl4_t *opl4 = private_data;
+	int err;
+
+	down(&opl4->access_mutex);
+
+	if (opl4->used) {
+		up(&opl4->access_mutex);
+		return -EBUSY;
+	}
+	opl4->used++;
+
+	if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM) {
+		err = snd_opl4_seq_use_inc(opl4);
+		if (err < 0) {
+			up(&opl4->access_mutex);
+			return err;
+		}
+	}
+
+	up(&opl4->access_mutex);
+
+	snd_opl4_synth_reset(opl4);
+	return 0;
+}
+
+static int snd_opl4_seq_unuse(void *private_data, snd_seq_port_subscribe_t *info)
+{
+	opl4_t *opl4 = private_data;
+
+	snd_opl4_synth_shutdown(opl4);
+
+	down(&opl4->access_mutex);
+	opl4->used--;
+	up(&opl4->access_mutex);
+
+	if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM)
+		snd_opl4_seq_use_dec(opl4);
+	return 0;
+}
+
+static snd_midi_op_t opl4_ops = {
+	.note_on =		snd_opl4_note_on,
+	.note_off =		snd_opl4_note_off,
+	.note_terminate =	snd_opl4_terminate_note,
+	.control =		snd_opl4_control,
+	.sysex =		snd_opl4_sysex,
+};
+
+static int snd_opl4_seq_event_input(snd_seq_event_t *ev, int direct,
+				    void *private_data, int atomic, int hop)
+{
+	opl4_t *opl4 = private_data;
+
+	snd_midi_process_event(&opl4_ops, ev, opl4->chset);
+	return 0;
+}
+
+static void snd_opl4_seq_free_port(void *private_data)
+{
+	opl4_t *opl4 = private_data;
+
+	snd_midi_channel_free_set(opl4->chset);
+}
+
+static int snd_opl4_seq_new_device(snd_seq_device_t *dev)
+{
+	opl4_t *opl4;
+	int client;
+	snd_seq_client_callback_t callbacks;
+	snd_seq_client_info_t cinfo;
+	snd_seq_port_callback_t pcallbacks;
+
+	opl4 = *(opl4_t **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	if (!opl4)
+		return -EINVAL;
+
+	if (snd_yrw801_detect(opl4) < 0)
+		return -ENODEV;
+
+	opl4->chset = snd_midi_channel_alloc_set(16);
+	if (!opl4->chset)
+		return -ENOMEM;
+	opl4->chset->private_data = opl4;
+
+	/* allocate new client */
+	memset(&callbacks, 0, sizeof(callbacks));
+	callbacks.private_data = opl4;
+	callbacks.allow_output = callbacks.allow_input = 1;
+	client = snd_seq_create_kernel_client(opl4->card, opl4->seq_dev_num, &callbacks);
+	if (client < 0) {
+		snd_midi_channel_free_set(opl4->chset);
+		return client;
+	}
+	opl4->seq_client = client;
+	opl4->chset->client = client;
+
+	/* change name of client */
+	memset(&cinfo, 0, sizeof(cinfo));
+	cinfo.client = client;
+	cinfo.type = KERNEL_CLIENT;
+	strcpy(cinfo.name, "OPL4 Wavetable");
+	snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &cinfo);
+
+	/* create new port */
+	memset(&pcallbacks, 0, sizeof(pcallbacks));
+	pcallbacks.owner = THIS_MODULE;
+	pcallbacks.use = snd_opl4_seq_use;
+	pcallbacks.unuse = snd_opl4_seq_unuse;
+	pcallbacks.event_input = snd_opl4_seq_event_input;
+	pcallbacks.private_free = snd_opl4_seq_free_port;
+	pcallbacks.private_data = opl4;
+
+	opl4->chset->port = snd_seq_event_port_attach(client, &pcallbacks,
+						      SNDRV_SEQ_PORT_CAP_WRITE |
+						      SNDRV_SEQ_PORT_CAP_SUBS_WRITE,
+						      SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+						      SNDRV_SEQ_PORT_TYPE_MIDI_GM,
+						      16, 24,
+						      "OPL4 Wavetable Port");
+	if (opl4->chset->port < 0) {
+		int err = opl4->chset->port;
+		snd_midi_channel_free_set(opl4->chset);
+		snd_seq_delete_kernel_client(client);
+		opl4->seq_client = -1;
+		return err;
+	}
+	return 0;
+}
+
+static int snd_opl4_seq_delete_device(snd_seq_device_t *dev)
+{
+	opl4_t *opl4;
+
+	opl4 = *(opl4_t **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	if (!opl4)
+		return -EINVAL;
+
+	if (opl4->seq_client >= 0) {
+		snd_seq_delete_kernel_client(opl4->seq_client);
+		opl4->seq_client = -1;
+	}
+	return 0;
+}
+
+static int __init alsa_opl4_synth_init(void)
+{
+	static snd_seq_dev_ops_t ops = {
+		snd_opl4_seq_new_device,
+		snd_opl4_seq_delete_device
+	};
+
+	return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_OPL4, &ops,
+					      sizeof(opl4_t*));
+}
+
+static void __exit alsa_opl4_synth_exit(void)
+{
+	snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_OPL4);
+}
+
+module_init(alsa_opl4_synth_init)
+module_exit(alsa_opl4_synth_exit)
diff --git a/sound/drivers/opl4/opl4_synth.c b/sound/drivers/opl4/opl4_synth.c
new file mode 100644
index 0000000..b146a1c99
--- /dev/null
+++ b/sound/drivers/opl4/opl4_synth.c
@@ -0,0 +1,630 @@
+/*
+ * OPL4 MIDI synthesizer functions
+ *
+ * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed and/or modified 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 SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opl4_local.h"
+#include <linux/delay.h>
+#include <asm/io.h>
+#include <sound/asoundef.h>
+
+/* GM2 controllers */
+#ifndef MIDI_CTL_RELEASE_TIME
+#define MIDI_CTL_RELEASE_TIME	0x48
+#define MIDI_CTL_ATTACK_TIME	0x49
+#define MIDI_CTL_DECAY_TIME	0x4b
+#define MIDI_CTL_VIBRATO_RATE	0x4c
+#define MIDI_CTL_VIBRATO_DEPTH	0x4d
+#define MIDI_CTL_VIBRATO_DELAY	0x4e
+#endif
+
+/*
+ * This table maps 100/128 cents to F_NUMBER.
+ */
+static const s16 snd_opl4_pitch_map[0x600] = {
+	0x000,0x000,0x001,0x001,0x002,0x002,0x003,0x003,
+	0x004,0x004,0x005,0x005,0x006,0x006,0x006,0x007,
+	0x007,0x008,0x008,0x009,0x009,0x00a,0x00a,0x00b,
+	0x00b,0x00c,0x00c,0x00d,0x00d,0x00d,0x00e,0x00e,
+	0x00f,0x00f,0x010,0x010,0x011,0x011,0x012,0x012,
+	0x013,0x013,0x014,0x014,0x015,0x015,0x015,0x016,
+	0x016,0x017,0x017,0x018,0x018,0x019,0x019,0x01a,
+	0x01a,0x01b,0x01b,0x01c,0x01c,0x01d,0x01d,0x01e,
+	0x01e,0x01e,0x01f,0x01f,0x020,0x020,0x021,0x021,
+	0x022,0x022,0x023,0x023,0x024,0x024,0x025,0x025,
+	0x026,0x026,0x027,0x027,0x028,0x028,0x029,0x029,
+	0x029,0x02a,0x02a,0x02b,0x02b,0x02c,0x02c,0x02d,
+	0x02d,0x02e,0x02e,0x02f,0x02f,0x030,0x030,0x031,
+	0x031,0x032,0x032,0x033,0x033,0x034,0x034,0x035,
+	0x035,0x036,0x036,0x037,0x037,0x038,0x038,0x038,
+	0x039,0x039,0x03a,0x03a,0x03b,0x03b,0x03c,0x03c,
+	0x03d,0x03d,0x03e,0x03e,0x03f,0x03f,0x040,0x040,
+	0x041,0x041,0x042,0x042,0x043,0x043,0x044,0x044,
+	0x045,0x045,0x046,0x046,0x047,0x047,0x048,0x048,
+	0x049,0x049,0x04a,0x04a,0x04b,0x04b,0x04c,0x04c,
+	0x04d,0x04d,0x04e,0x04e,0x04f,0x04f,0x050,0x050,
+	0x051,0x051,0x052,0x052,0x053,0x053,0x054,0x054,
+	0x055,0x055,0x056,0x056,0x057,0x057,0x058,0x058,
+	0x059,0x059,0x05a,0x05a,0x05b,0x05b,0x05c,0x05c,
+	0x05d,0x05d,0x05e,0x05e,0x05f,0x05f,0x060,0x060,
+	0x061,0x061,0x062,0x062,0x063,0x063,0x064,0x064,
+	0x065,0x065,0x066,0x066,0x067,0x067,0x068,0x068,
+	0x069,0x069,0x06a,0x06a,0x06b,0x06b,0x06c,0x06c,
+	0x06d,0x06d,0x06e,0x06e,0x06f,0x06f,0x070,0x071,
+	0x071,0x072,0x072,0x073,0x073,0x074,0x074,0x075,
+	0x075,0x076,0x076,0x077,0x077,0x078,0x078,0x079,
+	0x079,0x07a,0x07a,0x07b,0x07b,0x07c,0x07c,0x07d,
+	0x07d,0x07e,0x07e,0x07f,0x07f,0x080,0x081,0x081,
+	0x082,0x082,0x083,0x083,0x084,0x084,0x085,0x085,
+	0x086,0x086,0x087,0x087,0x088,0x088,0x089,0x089,
+	0x08a,0x08a,0x08b,0x08b,0x08c,0x08d,0x08d,0x08e,
+	0x08e,0x08f,0x08f,0x090,0x090,0x091,0x091,0x092,
+	0x092,0x093,0x093,0x094,0x094,0x095,0x096,0x096,
+	0x097,0x097,0x098,0x098,0x099,0x099,0x09a,0x09a,
+	0x09b,0x09b,0x09c,0x09c,0x09d,0x09d,0x09e,0x09f,
+	0x09f,0x0a0,0x0a0,0x0a1,0x0a1,0x0a2,0x0a2,0x0a3,
+	0x0a3,0x0a4,0x0a4,0x0a5,0x0a6,0x0a6,0x0a7,0x0a7,
+	0x0a8,0x0a8,0x0a9,0x0a9,0x0aa,0x0aa,0x0ab,0x0ab,
+	0x0ac,0x0ad,0x0ad,0x0ae,0x0ae,0x0af,0x0af,0x0b0,
+	0x0b0,0x0b1,0x0b1,0x0b2,0x0b2,0x0b3,0x0b4,0x0b4,
+	0x0b5,0x0b5,0x0b6,0x0b6,0x0b7,0x0b7,0x0b8,0x0b8,
+	0x0b9,0x0ba,0x0ba,0x0bb,0x0bb,0x0bc,0x0bc,0x0bd,
+	0x0bd,0x0be,0x0be,0x0bf,0x0c0,0x0c0,0x0c1,0x0c1,
+	0x0c2,0x0c2,0x0c3,0x0c3,0x0c4,0x0c4,0x0c5,0x0c6,
+	0x0c6,0x0c7,0x0c7,0x0c8,0x0c8,0x0c9,0x0c9,0x0ca,
+	0x0cb,0x0cb,0x0cc,0x0cc,0x0cd,0x0cd,0x0ce,0x0ce,
+	0x0cf,0x0d0,0x0d0,0x0d1,0x0d1,0x0d2,0x0d2,0x0d3,
+	0x0d3,0x0d4,0x0d5,0x0d5,0x0d6,0x0d6,0x0d7,0x0d7,
+	0x0d8,0x0d8,0x0d9,0x0da,0x0da,0x0db,0x0db,0x0dc,
+	0x0dc,0x0dd,0x0de,0x0de,0x0df,0x0df,0x0e0,0x0e0,
+	0x0e1,0x0e1,0x0e2,0x0e3,0x0e3,0x0e4,0x0e4,0x0e5,
+	0x0e5,0x0e6,0x0e7,0x0e7,0x0e8,0x0e8,0x0e9,0x0e9,
+	0x0ea,0x0eb,0x0eb,0x0ec,0x0ec,0x0ed,0x0ed,0x0ee,
+	0x0ef,0x0ef,0x0f0,0x0f0,0x0f1,0x0f1,0x0f2,0x0f3,
+	0x0f3,0x0f4,0x0f4,0x0f5,0x0f5,0x0f6,0x0f7,0x0f7,
+	0x0f8,0x0f8,0x0f9,0x0f9,0x0fa,0x0fb,0x0fb,0x0fc,
+	0x0fc,0x0fd,0x0fd,0x0fe,0x0ff,0x0ff,0x100,0x100,
+	0x101,0x101,0x102,0x103,0x103,0x104,0x104,0x105,
+	0x106,0x106,0x107,0x107,0x108,0x108,0x109,0x10a,
+	0x10a,0x10b,0x10b,0x10c,0x10c,0x10d,0x10e,0x10e,
+	0x10f,0x10f,0x110,0x111,0x111,0x112,0x112,0x113,
+	0x114,0x114,0x115,0x115,0x116,0x116,0x117,0x118,
+	0x118,0x119,0x119,0x11a,0x11b,0x11b,0x11c,0x11c,
+	0x11d,0x11e,0x11e,0x11f,0x11f,0x120,0x120,0x121,
+	0x122,0x122,0x123,0x123,0x124,0x125,0x125,0x126,
+	0x126,0x127,0x128,0x128,0x129,0x129,0x12a,0x12b,
+	0x12b,0x12c,0x12c,0x12d,0x12e,0x12e,0x12f,0x12f,
+	0x130,0x131,0x131,0x132,0x132,0x133,0x134,0x134,
+	0x135,0x135,0x136,0x137,0x137,0x138,0x138,0x139,
+	0x13a,0x13a,0x13b,0x13b,0x13c,0x13d,0x13d,0x13e,
+	0x13e,0x13f,0x140,0x140,0x141,0x141,0x142,0x143,
+	0x143,0x144,0x144,0x145,0x146,0x146,0x147,0x148,
+	0x148,0x149,0x149,0x14a,0x14b,0x14b,0x14c,0x14c,
+	0x14d,0x14e,0x14e,0x14f,0x14f,0x150,0x151,0x151,
+	0x152,0x153,0x153,0x154,0x154,0x155,0x156,0x156,
+	0x157,0x157,0x158,0x159,0x159,0x15a,0x15b,0x15b,
+	0x15c,0x15c,0x15d,0x15e,0x15e,0x15f,0x160,0x160,
+	0x161,0x161,0x162,0x163,0x163,0x164,0x165,0x165,
+	0x166,0x166,0x167,0x168,0x168,0x169,0x16a,0x16a,
+	0x16b,0x16b,0x16c,0x16d,0x16d,0x16e,0x16f,0x16f,
+	0x170,0x170,0x171,0x172,0x172,0x173,0x174,0x174,
+	0x175,0x175,0x176,0x177,0x177,0x178,0x179,0x179,
+	0x17a,0x17a,0x17b,0x17c,0x17c,0x17d,0x17e,0x17e,
+	0x17f,0x180,0x180,0x181,0x181,0x182,0x183,0x183,
+	0x184,0x185,0x185,0x186,0x187,0x187,0x188,0x188,
+	0x189,0x18a,0x18a,0x18b,0x18c,0x18c,0x18d,0x18e,
+	0x18e,0x18f,0x190,0x190,0x191,0x191,0x192,0x193,
+	0x193,0x194,0x195,0x195,0x196,0x197,0x197,0x198,
+	0x199,0x199,0x19a,0x19a,0x19b,0x19c,0x19c,0x19d,
+	0x19e,0x19e,0x19f,0x1a0,0x1a0,0x1a1,0x1a2,0x1a2,
+	0x1a3,0x1a4,0x1a4,0x1a5,0x1a6,0x1a6,0x1a7,0x1a8,
+	0x1a8,0x1a9,0x1a9,0x1aa,0x1ab,0x1ab,0x1ac,0x1ad,
+	0x1ad,0x1ae,0x1af,0x1af,0x1b0,0x1b1,0x1b1,0x1b2,
+	0x1b3,0x1b3,0x1b4,0x1b5,0x1b5,0x1b6,0x1b7,0x1b7,
+	0x1b8,0x1b9,0x1b9,0x1ba,0x1bb,0x1bb,0x1bc,0x1bd,
+	0x1bd,0x1be,0x1bf,0x1bf,0x1c0,0x1c1,0x1c1,0x1c2,
+	0x1c3,0x1c3,0x1c4,0x1c5,0x1c5,0x1c6,0x1c7,0x1c7,
+	0x1c8,0x1c9,0x1c9,0x1ca,0x1cb,0x1cb,0x1cc,0x1cd,
+	0x1cd,0x1ce,0x1cf,0x1cf,0x1d0,0x1d1,0x1d1,0x1d2,
+	0x1d3,0x1d3,0x1d4,0x1d5,0x1d5,0x1d6,0x1d7,0x1d7,
+	0x1d8,0x1d9,0x1d9,0x1da,0x1db,0x1db,0x1dc,0x1dd,
+	0x1dd,0x1de,0x1df,0x1df,0x1e0,0x1e1,0x1e1,0x1e2,
+	0x1e3,0x1e4,0x1e4,0x1e5,0x1e6,0x1e6,0x1e7,0x1e8,
+	0x1e8,0x1e9,0x1ea,0x1ea,0x1eb,0x1ec,0x1ec,0x1ed,
+	0x1ee,0x1ee,0x1ef,0x1f0,0x1f0,0x1f1,0x1f2,0x1f3,
+	0x1f3,0x1f4,0x1f5,0x1f5,0x1f6,0x1f7,0x1f7,0x1f8,
+	0x1f9,0x1f9,0x1fa,0x1fb,0x1fb,0x1fc,0x1fd,0x1fe,
+	0x1fe,0x1ff,0x200,0x200,0x201,0x202,0x202,0x203,
+	0x204,0x205,0x205,0x206,0x207,0x207,0x208,0x209,
+	0x209,0x20a,0x20b,0x20b,0x20c,0x20d,0x20e,0x20e,
+	0x20f,0x210,0x210,0x211,0x212,0x212,0x213,0x214,
+	0x215,0x215,0x216,0x217,0x217,0x218,0x219,0x21a,
+	0x21a,0x21b,0x21c,0x21c,0x21d,0x21e,0x21e,0x21f,
+	0x220,0x221,0x221,0x222,0x223,0x223,0x224,0x225,
+	0x226,0x226,0x227,0x228,0x228,0x229,0x22a,0x22b,
+	0x22b,0x22c,0x22d,0x22d,0x22e,0x22f,0x230,0x230,
+	0x231,0x232,0x232,0x233,0x234,0x235,0x235,0x236,
+	0x237,0x237,0x238,0x239,0x23a,0x23a,0x23b,0x23c,
+	0x23c,0x23d,0x23e,0x23f,0x23f,0x240,0x241,0x241,
+	0x242,0x243,0x244,0x244,0x245,0x246,0x247,0x247,
+	0x248,0x249,0x249,0x24a,0x24b,0x24c,0x24c,0x24d,
+	0x24e,0x24f,0x24f,0x250,0x251,0x251,0x252,0x253,
+	0x254,0x254,0x255,0x256,0x257,0x257,0x258,0x259,
+	0x259,0x25a,0x25b,0x25c,0x25c,0x25d,0x25e,0x25f,
+	0x25f,0x260,0x261,0x262,0x262,0x263,0x264,0x265,
+	0x265,0x266,0x267,0x267,0x268,0x269,0x26a,0x26a,
+	0x26b,0x26c,0x26d,0x26d,0x26e,0x26f,0x270,0x270,
+	0x271,0x272,0x273,0x273,0x274,0x275,0x276,0x276,
+	0x277,0x278,0x279,0x279,0x27a,0x27b,0x27c,0x27c,
+	0x27d,0x27e,0x27f,0x27f,0x280,0x281,0x282,0x282,
+	0x283,0x284,0x285,0x285,0x286,0x287,0x288,0x288,
+	0x289,0x28a,0x28b,0x28b,0x28c,0x28d,0x28e,0x28e,
+	0x28f,0x290,0x291,0x291,0x292,0x293,0x294,0x294,
+	0x295,0x296,0x297,0x298,0x298,0x299,0x29a,0x29b,
+	0x29b,0x29c,0x29d,0x29e,0x29e,0x29f,0x2a0,0x2a1,
+	0x2a1,0x2a2,0x2a3,0x2a4,0x2a5,0x2a5,0x2a6,0x2a7,
+	0x2a8,0x2a8,0x2a9,0x2aa,0x2ab,0x2ab,0x2ac,0x2ad,
+	0x2ae,0x2af,0x2af,0x2b0,0x2b1,0x2b2,0x2b2,0x2b3,
+	0x2b4,0x2b5,0x2b5,0x2b6,0x2b7,0x2b8,0x2b9,0x2b9,
+	0x2ba,0x2bb,0x2bc,0x2bc,0x2bd,0x2be,0x2bf,0x2c0,
+	0x2c0,0x2c1,0x2c2,0x2c3,0x2c4,0x2c4,0x2c5,0x2c6,
+	0x2c7,0x2c7,0x2c8,0x2c9,0x2ca,0x2cb,0x2cb,0x2cc,
+	0x2cd,0x2ce,0x2ce,0x2cf,0x2d0,0x2d1,0x2d2,0x2d2,
+	0x2d3,0x2d4,0x2d5,0x2d6,0x2d6,0x2d7,0x2d8,0x2d9,
+	0x2da,0x2da,0x2db,0x2dc,0x2dd,0x2dd,0x2de,0x2df,
+	0x2e0,0x2e1,0x2e1,0x2e2,0x2e3,0x2e4,0x2e5,0x2e5,
+	0x2e6,0x2e7,0x2e8,0x2e9,0x2e9,0x2ea,0x2eb,0x2ec,
+	0x2ed,0x2ed,0x2ee,0x2ef,0x2f0,0x2f1,0x2f1,0x2f2,
+	0x2f3,0x2f4,0x2f5,0x2f5,0x2f6,0x2f7,0x2f8,0x2f9,
+	0x2f9,0x2fa,0x2fb,0x2fc,0x2fd,0x2fd,0x2fe,0x2ff,
+	0x300,0x301,0x302,0x302,0x303,0x304,0x305,0x306,
+	0x306,0x307,0x308,0x309,0x30a,0x30a,0x30b,0x30c,
+	0x30d,0x30e,0x30f,0x30f,0x310,0x311,0x312,0x313,
+	0x313,0x314,0x315,0x316,0x317,0x318,0x318,0x319,
+	0x31a,0x31b,0x31c,0x31c,0x31d,0x31e,0x31f,0x320,
+	0x321,0x321,0x322,0x323,0x324,0x325,0x326,0x326,
+	0x327,0x328,0x329,0x32a,0x32a,0x32b,0x32c,0x32d,
+	0x32e,0x32f,0x32f,0x330,0x331,0x332,0x333,0x334,
+	0x334,0x335,0x336,0x337,0x338,0x339,0x339,0x33a,
+	0x33b,0x33c,0x33d,0x33e,0x33e,0x33f,0x340,0x341,
+	0x342,0x343,0x343,0x344,0x345,0x346,0x347,0x348,
+	0x349,0x349,0x34a,0x34b,0x34c,0x34d,0x34e,0x34e,
+	0x34f,0x350,0x351,0x352,0x353,0x353,0x354,0x355,
+	0x356,0x357,0x358,0x359,0x359,0x35a,0x35b,0x35c,
+	0x35d,0x35e,0x35f,0x35f,0x360,0x361,0x362,0x363,
+	0x364,0x364,0x365,0x366,0x367,0x368,0x369,0x36a,
+	0x36a,0x36b,0x36c,0x36d,0x36e,0x36f,0x370,0x370,
+	0x371,0x372,0x373,0x374,0x375,0x376,0x377,0x377,
+	0x378,0x379,0x37a,0x37b,0x37c,0x37d,0x37d,0x37e,
+	0x37f,0x380,0x381,0x382,0x383,0x383,0x384,0x385,
+	0x386,0x387,0x388,0x389,0x38a,0x38a,0x38b,0x38c,
+	0x38d,0x38e,0x38f,0x390,0x391,0x391,0x392,0x393,
+	0x394,0x395,0x396,0x397,0x398,0x398,0x399,0x39a,
+	0x39b,0x39c,0x39d,0x39e,0x39f,0x39f,0x3a0,0x3a1,
+	0x3a2,0x3a3,0x3a4,0x3a5,0x3a6,0x3a7,0x3a7,0x3a8,
+	0x3a9,0x3aa,0x3ab,0x3ac,0x3ad,0x3ae,0x3ae,0x3af,
+	0x3b0,0x3b1,0x3b2,0x3b3,0x3b4,0x3b5,0x3b6,0x3b6,
+	0x3b7,0x3b8,0x3b9,0x3ba,0x3bb,0x3bc,0x3bd,0x3be,
+	0x3bf,0x3bf,0x3c0,0x3c1,0x3c2,0x3c3,0x3c4,0x3c5,
+	0x3c6,0x3c7,0x3c7,0x3c8,0x3c9,0x3ca,0x3cb,0x3cc,
+	0x3cd,0x3ce,0x3cf,0x3d0,0x3d1,0x3d1,0x3d2,0x3d3,
+	0x3d4,0x3d5,0x3d6,0x3d7,0x3d8,0x3d9,0x3da,0x3da,
+	0x3db,0x3dc,0x3dd,0x3de,0x3df,0x3e0,0x3e1,0x3e2,
+	0x3e3,0x3e4,0x3e4,0x3e5,0x3e6,0x3e7,0x3e8,0x3e9,
+	0x3ea,0x3eb,0x3ec,0x3ed,0x3ee,0x3ef,0x3ef,0x3f0,
+	0x3f1,0x3f2,0x3f3,0x3f4,0x3f5,0x3f6,0x3f7,0x3f8,
+	0x3f9,0x3fa,0x3fa,0x3fb,0x3fc,0x3fd,0x3fe,0x3ff
+};
+
+/*
+ * Attenuation according to GM recommendations, in -0.375 dB units.
+ * table[v] = 40 * log(v / 127) / -0.375
+ */
+static unsigned char snd_opl4_volume_table[128] = {
+	255,224,192,173,160,150,141,134,
+	128,122,117,113,109,105,102, 99,
+	 96, 93, 90, 88, 85, 83, 81, 79,
+	 77, 75, 73, 71, 70, 68, 67, 65,
+	 64, 62, 61, 59, 58, 57, 56, 54,
+	 53, 52, 51, 50, 49, 48, 47, 46,
+	 45, 44, 43, 42, 41, 40, 39, 39,
+	 38, 37, 36, 35, 34, 34, 33, 32,
+	 31, 31, 30, 29, 29, 28, 27, 27,
+	 26, 25, 25, 24, 24, 23, 22, 22,
+	 21, 21, 20, 19, 19, 18, 18, 17,
+	 17, 16, 16, 15, 15, 14, 14, 13,
+	 13, 12, 12, 11, 11, 10, 10,  9,
+	  9,  9,  8,  8,  7,  7,  6,  6,
+	  6,  5,  5,  4,  4,  4,  3,  3,
+	  2,  2,  2,  1,  1,  0,  0,  0
+};
+
+/*
+ * Initializes all voices.
+ */
+void snd_opl4_synth_reset(opl4_t *opl4)
+{
+	unsigned long flags;
+	int i;
+
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+	for (i = 0; i < OPL4_MAX_VOICES; i++)
+		snd_opl4_write(opl4, OPL4_REG_MISC + i, OPL4_DAMP_BIT);
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+
+	INIT_LIST_HEAD(&opl4->off_voices);
+	INIT_LIST_HEAD(&opl4->on_voices);
+	memset(opl4->voices, 0, sizeof(opl4->voices));
+	for (i = 0; i < OPL4_MAX_VOICES; i++) {
+		opl4->voices[i].number = i;
+		list_add_tail(&opl4->voices[i].list, &opl4->off_voices);
+	}
+
+	snd_midi_channel_set_clear(opl4->chset);
+}
+
+/*
+ * Shuts down all voices.
+ */
+void snd_opl4_synth_shutdown(opl4_t *opl4)
+{
+	unsigned long flags;
+	int i;
+
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+	for (i = 0; i < OPL4_MAX_VOICES; i++)
+		snd_opl4_write(opl4, OPL4_REG_MISC + i,
+			       opl4->voices[i].reg_misc & ~OPL4_KEY_ON_BIT);
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+}
+
+/*
+ * Executes the callback for all voices playing the specified note.
+ */
+static void snd_opl4_do_for_note(opl4_t *opl4, int note, snd_midi_channel_t *chan,
+				 void (*func)(opl4_t *opl4, opl4_voice_t *voice))
+{
+	int i;
+	unsigned long flags;
+	opl4_voice_t *voice;
+
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+	for (i = 0; i < OPL4_MAX_VOICES; i++) {
+		voice = &opl4->voices[i];
+		if (voice->chan == chan && voice->note == note) {
+			func(opl4, voice);
+		}
+	}
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+}
+
+/*
+ * Executes the callback for all voices of to the specified channel.
+ */
+static void snd_opl4_do_for_channel(opl4_t *opl4, snd_midi_channel_t *chan,
+				    void (*func)(opl4_t *opl4, opl4_voice_t *voice))
+{
+	int i;
+	unsigned long flags;
+	opl4_voice_t *voice;
+
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+	for (i = 0; i < OPL4_MAX_VOICES; i++) {
+		voice = &opl4->voices[i];
+		if (voice->chan == chan) {
+			func(opl4, voice);
+		}
+	}
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+}
+
+/*
+ * Executes the callback for all active voices.
+ */
+static void snd_opl4_do_for_all(opl4_t *opl4,
+				void (*func)(opl4_t *opl4, opl4_voice_t *voice))
+{
+	int i;
+	unsigned long flags;
+	opl4_voice_t *voice;
+
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+	for (i = 0; i < OPL4_MAX_VOICES; i++) {
+		voice = &opl4->voices[i];
+		if (voice->chan)
+			func(opl4, voice);
+	}
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+}
+
+static void snd_opl4_update_volume(opl4_t *opl4, opl4_voice_t *voice)
+{
+	int att;
+
+	att = voice->sound->tone_attenuate;
+	att += snd_opl4_volume_table[opl4->chset->gs_master_volume & 0x7f];
+	att += snd_opl4_volume_table[voice->chan->gm_volume & 0x7f];
+	att += snd_opl4_volume_table[voice->chan->gm_expression & 0x7f];
+	att += snd_opl4_volume_table[voice->velocity];
+	att = 0x7f - (0x7f - att) * (voice->sound->volume_factor) / 0xfe - volume_boost;
+	if (att < 0)
+		att = 0;
+	else if (att > 0x7e)
+		att = 0x7e;
+	snd_opl4_write(opl4, OPL4_REG_LEVEL + voice->number,
+		       (att << 1) | voice->level_direct);
+	voice->level_direct = 0;
+}
+
+static void snd_opl4_update_pan(opl4_t *opl4, opl4_voice_t *voice)
+{
+	int pan = voice->sound->panpot;
+
+	if (!voice->chan->drum_channel)
+		pan += (voice->chan->control[MIDI_CTL_MSB_PAN] - 0x40) >> 3;
+	if (pan < -7)
+		pan = -7;
+	else if (pan > 7)
+		pan = 7;
+	voice->reg_misc = (voice->reg_misc & ~OPL4_PAN_POT_MASK)
+		| (pan & OPL4_PAN_POT_MASK);
+	snd_opl4_write(opl4, OPL4_REG_MISC + voice->number, voice->reg_misc);
+}
+
+static void snd_opl4_update_vibrato_depth(opl4_t *opl4, opl4_voice_t *voice)
+{
+	int depth;
+
+	if (voice->chan->drum_channel)
+		return;
+	depth = (7 - voice->sound->vibrato)
+		* (voice->chan->control[MIDI_CTL_VIBRATO_DEPTH] & 0x7f);
+	depth = (depth >> 7) + voice->sound->vibrato;
+	voice->reg_lfo_vibrato &= ~OPL4_VIBRATO_DEPTH_MASK;
+	voice->reg_lfo_vibrato |= depth & OPL4_VIBRATO_DEPTH_MASK;
+	snd_opl4_write(opl4, OPL4_REG_LFO_VIBRATO + voice->number,
+		       voice->reg_lfo_vibrato);
+}
+
+static void snd_opl4_update_pitch(opl4_t *opl4, opl4_voice_t *voice)
+{
+	snd_midi_channel_t *chan = voice->chan;
+	int note, pitch, octave;
+
+	note = chan->drum_channel ? 60 : voice->note;
+	/*
+	 * pitch is in 100/128 cents, so 0x80 is one semitone and
+	 * 0x600 is one octave.
+	 */
+	pitch = ((note - 60) << 7) * voice->sound->key_scaling / 100 + (60 << 7);
+	pitch += voice->sound->pitch_offset;
+	if (!chan->drum_channel)
+		pitch += chan->gm_rpn_coarse_tuning;
+	pitch += chan->gm_rpn_fine_tuning >> 7;
+	pitch += chan->midi_pitchbend * chan->gm_rpn_pitch_bend_range / 0x2000;
+	if (pitch < 0)
+		pitch = 0;
+	else if (pitch >= 0x6000)
+		pitch = 0x5fff;
+	octave = pitch / 0x600 - 8;
+	pitch = snd_opl4_pitch_map[pitch % 0x600];
+
+	snd_opl4_write(opl4, OPL4_REG_OCTAVE + voice->number,
+		       (octave << 4) | ((pitch >> 7) & OPL4_F_NUMBER_HIGH_MASK));
+	voice->reg_f_number = (voice->reg_f_number & OPL4_TONE_NUMBER_BIT8)
+		| ((pitch << 1) & OPL4_F_NUMBER_LOW_MASK);
+	snd_opl4_write(opl4, OPL4_REG_F_NUMBER + voice->number, voice->reg_f_number);
+}
+
+static void snd_opl4_update_tone_parameters(opl4_t *opl4, opl4_voice_t *voice)
+{
+	snd_opl4_write(opl4, OPL4_REG_ATTACK_DECAY1 + voice->number,
+		       voice->sound->reg_attack_decay1);
+	snd_opl4_write(opl4, OPL4_REG_LEVEL_DECAY2 + voice->number,
+		       voice->sound->reg_level_decay2);
+	snd_opl4_write(opl4, OPL4_REG_RELEASE_CORRECTION + voice->number,
+		       voice->sound->reg_release_correction);
+	snd_opl4_write(opl4, OPL4_REG_TREMOLO + voice->number,
+		       voice->sound->reg_tremolo);
+}
+
+/* allocate one voice */
+static opl4_voice_t *snd_opl4_get_voice(opl4_t *opl4)
+{
+	/* first, try to get the oldest key-off voice */
+	if (!list_empty(&opl4->off_voices))
+		return list_entry(opl4->off_voices.next, opl4_voice_t, list);
+	/* then get the oldest key-on voice */
+	snd_assert(!list_empty(&opl4->on_voices), );
+	return list_entry(opl4->on_voices.next, opl4_voice_t, list);
+}
+
+static void snd_opl4_wait_for_wave_headers(opl4_t *opl4)
+{
+	int timeout = 200;
+
+	while ((inb(opl4->fm_port) & OPL4_STATUS_LOAD) && --timeout > 0)
+		udelay(10);
+}
+
+void snd_opl4_note_on(void *private_data, int note, int vel, snd_midi_channel_t *chan)
+{
+	opl4_t *opl4 = private_data;
+	const opl4_region_ptr_t *regions;
+	opl4_voice_t *voice[2];
+	const opl4_sound_t *sound[2];
+	int voices = 0, i;
+	unsigned long flags;
+
+	/* determine the number of voices and voice parameters */
+	i = chan->drum_channel ? 0x80 : (chan->midi_program & 0x7f);
+	regions = &snd_yrw801_regions[i];
+	for (i = 0; i < regions->count; i++) {
+		if (note >= regions->regions[i].key_min &&
+		    note <= regions->regions[i].key_max) {
+			sound[voices] = &regions->regions[i].sound;
+			if (++voices >= 2)
+				break;
+		}
+	}
+
+	/* allocate and initialize the needed voices */
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+	for (i = 0; i < voices; i++) {
+		voice[i] = snd_opl4_get_voice(opl4);
+		list_del(&voice[i]->list);
+		list_add_tail(&voice[i]->list, &opl4->on_voices);
+		voice[i]->chan = chan;
+		voice[i]->note = note;
+		voice[i]->velocity = vel & 0x7f;
+		voice[i]->sound = sound[i];
+	}
+
+	/* set tone number (triggers header loading) */
+	for (i = 0; i < voices; i++) {
+		voice[i]->reg_f_number =
+			(sound[i]->tone >> 8) & OPL4_TONE_NUMBER_BIT8;
+		snd_opl4_write(opl4, OPL4_REG_F_NUMBER + voice[i]->number,
+			       voice[i]->reg_f_number);
+		snd_opl4_write(opl4, OPL4_REG_TONE_NUMBER + voice[i]->number,
+			       sound[i]->tone & 0xff);
+	}
+
+	/* set parameters which can be set while loading */
+	for (i = 0; i < voices; i++) {
+		voice[i]->reg_misc = OPL4_LFO_RESET_BIT;
+		snd_opl4_update_pan(opl4, voice[i]);
+		snd_opl4_update_pitch(opl4, voice[i]);
+		voice[i]->level_direct = OPL4_LEVEL_DIRECT_BIT;
+		snd_opl4_update_volume(opl4, voice[i]);
+	}
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+
+	/* wait for completion of loading */
+	snd_opl4_wait_for_wave_headers(opl4);
+
+	/* set remaining parameters */
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+	for (i = 0; i < voices; i++) {
+		snd_opl4_update_tone_parameters(opl4, voice[i]);
+		voice[i]->reg_lfo_vibrato = voice[i]->sound->reg_lfo_vibrato;
+		snd_opl4_update_vibrato_depth(opl4, voice[i]);
+	}
+
+	/* finally, switch on all voices */
+	for (i = 0; i < voices; i++) {
+		voice[i]->reg_misc =
+			(voice[i]->reg_misc & 0x1f) | OPL4_KEY_ON_BIT;
+		snd_opl4_write(opl4, OPL4_REG_MISC + voice[i]->number,
+			       voice[i]->reg_misc);
+	}
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+}
+
+static void snd_opl4_voice_off(opl4_t *opl4, opl4_voice_t *voice)
+{
+	list_del(&voice->list);
+	list_add_tail(&voice->list, &opl4->off_voices);
+
+	voice->reg_misc &= ~OPL4_KEY_ON_BIT;
+	snd_opl4_write(opl4, OPL4_REG_MISC + voice->number, voice->reg_misc);
+}
+
+void snd_opl4_note_off(void *private_data, int note, int vel, snd_midi_channel_t *chan)
+{
+	opl4_t *opl4 = private_data;
+
+	snd_opl4_do_for_note(opl4, note, chan, snd_opl4_voice_off);
+}
+
+static void snd_opl4_terminate_voice(opl4_t *opl4, opl4_voice_t *voice)
+{
+	list_del(&voice->list);
+	list_add_tail(&voice->list, &opl4->off_voices);
+
+	voice->reg_misc = (voice->reg_misc & ~OPL4_KEY_ON_BIT) | OPL4_DAMP_BIT;
+	snd_opl4_write(opl4, OPL4_REG_MISC + voice->number, voice->reg_misc);
+}
+
+void snd_opl4_terminate_note(void *private_data, int note, snd_midi_channel_t *chan)
+{
+	opl4_t *opl4 = private_data;
+
+	snd_opl4_do_for_note(opl4, note, chan, snd_opl4_terminate_voice);
+}
+
+void snd_opl4_control(void *private_data, int type, snd_midi_channel_t *chan)
+{
+	opl4_t *opl4 = private_data;
+
+	switch (type) {
+	case MIDI_CTL_MSB_MODWHEEL:
+		chan->control[MIDI_CTL_VIBRATO_DEPTH] = chan->control[MIDI_CTL_MSB_MODWHEEL];
+		snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_vibrato_depth);
+		break;
+	case MIDI_CTL_MSB_MAIN_VOLUME:
+		snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_volume);
+		break;
+	case MIDI_CTL_MSB_PAN:
+		snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_pan);
+		break;
+	case MIDI_CTL_MSB_EXPRESSION:
+		snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_volume);
+		break;
+	case MIDI_CTL_VIBRATO_RATE:
+		/* not yet supported */
+		break;
+	case MIDI_CTL_VIBRATO_DEPTH:
+		snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_vibrato_depth);
+		break;
+	case MIDI_CTL_VIBRATO_DELAY:
+		/* not yet supported */
+		break;
+	case MIDI_CTL_E1_REVERB_DEPTH:
+		/*
+		 * Each OPL4 voice has a bit called "Pseudo-Reverb", but
+		 * IMHO _not_ using it enhances the listening experience.
+		 */
+		break;
+	case MIDI_CTL_PITCHBEND:
+		snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_pitch);
+		break;
+	}
+}
+
+void snd_opl4_sysex(void *private_data, unsigned char *buf, int len,
+		    int parsed, snd_midi_channel_set_t *chset)
+{
+	opl4_t *opl4 = private_data;
+
+	if (parsed == SNDRV_MIDI_SYSEX_GS_MASTER_VOLUME)
+		snd_opl4_do_for_all(opl4, snd_opl4_update_volume);
+}
diff --git a/sound/drivers/opl4/yrw801.c b/sound/drivers/opl4/yrw801.c
new file mode 100644
index 0000000..a51174d
--- /dev/null
+++ b/sound/drivers/opl4/yrw801.c
@@ -0,0 +1,961 @@
+/*
+ * Information about the Yamaha YRW801 wavetable ROM chip
+ *
+ * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed and/or modified 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 SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opl4_local.h"
+
+int snd_yrw801_detect(opl4_t *opl4)
+{
+	char buf[15];
+
+	snd_opl4_read_memory(opl4, buf, 0x001200, 15);
+	if (memcmp(buf, "CopyrightYAMAHA", 15))
+		return -ENODEV;
+	snd_opl4_read_memory(opl4, buf, 0x1ffffe, 2);
+	if (buf[0] != 0x01)
+		return -ENODEV;
+	snd_printdd("YRW801 ROM version %02x.%02x\n", buf[0], buf[1]);
+	return 0;
+}
+
+/*
+ * The instrument definitions are stored statically because, in practice, the
+ * OPL4 is always coupled with a YRW801. Dynamic instrument loading would be
+ * required if downloading sample data to external SRAM was actually supported
+ * by this driver.
+ */
+
+static const opl4_region_t regions_00[] = { /* Acoustic Grand Piano */
+	{0x14, 0x27, {0x12c,7474,100, 0,0,0x00,0xc8,0x20,0xf2,0x13,0x08,0x0}},
+	{0x28, 0x2d, {0x12d,6816,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x2e, 0x33, {0x12e,5899,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x34, 0x39, {0x12f,5290,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x3a, 0x3f, {0x130,4260,100, 0,0,0x0a,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x40, 0x45, {0x131,3625,100, 0,0,0x0a,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x46, 0x4b, {0x132,3116,100, 0,0,0x04,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x4c, 0x52, {0x133,2081,100, 0,0,0x03,0xc8,0x20,0xf2,0x14,0x18,0x0}},
+	{0x53, 0x58, {0x134,1444,100, 0,0,0x07,0xc8,0x20,0xf3,0x14,0x18,0x0}},
+	{0x59, 0x6d, {0x135,1915,100, 0,0,0x00,0xc8,0x20,0xf4,0x15,0x08,0x0}}
+};
+static const opl4_region_t regions_01[] = { /* Bright Acoustic Piano */
+	{0x14, 0x2d, {0x12c,7474,100, 0,0,0x00,0xc8,0x20,0xf2,0x13,0x08,0x0}},
+	{0x2e, 0x33, {0x12d,6816,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x34, 0x39, {0x12e,5899,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x3a, 0x3f, {0x12f,5290,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x40, 0x45, {0x130,4260,100, 0,0,0x0a,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x46, 0x4b, {0x131,3625,100, 0,0,0x0a,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x4c, 0x52, {0x132,3116,100, 0,0,0x04,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x53, 0x58, {0x133,2081,100, 0,0,0x07,0xc8,0x20,0xf2,0x14,0x18,0x0}},
+	{0x59, 0x5e, {0x134,1444,100, 0,0,0x0a,0xc8,0x20,0xf3,0x14,0x18,0x0}},
+	{0x5f, 0x6d, {0x135,1915,100, 0,0,0x00,0xc8,0x20,0xf4,0x15,0x08,0x0}}
+};
+static const opl4_region_t regions_02[] = { /* Electric Grand Piano */
+	{0x14, 0x2d, {0x12c,7476,100, 1,0,0x00,0xae,0x20,0xf2,0x13,0x07,0x0}},
+	{0x2e, 0x33, {0x12d,6818,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}},
+	{0x34, 0x39, {0x12e,5901,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}},
+	{0x3a, 0x3f, {0x12f,5292,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}},
+	{0x40, 0x45, {0x130,4262,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}},
+	{0x46, 0x4b, {0x131,3627,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}},
+	{0x4c, 0x52, {0x132,3118,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}},
+	{0x53, 0x58, {0x133,2083,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x17,0x0}},
+	{0x59, 0x5e, {0x134,1446,100, 1,0,0x00,0xae,0x20,0xf3,0x14,0x17,0x0}},
+	{0x5f, 0x6d, {0x135,1917,100, 1,0,0x00,0xae,0x20,0xf4,0x15,0x07,0x0}},
+	{0x00, 0x7f, {0x06c,6375,100,-1,0,0x00,0xc2,0x28,0xf4,0x23,0x18,0x0}}
+};
+static const opl4_region_t regions_03[] = { /* Honky-Tonk Piano */
+	{0x14, 0x27, {0x12c,7474,100, 0,0,0x00,0xb4,0x20,0xf2,0x13,0x08,0x0}},
+	{0x28, 0x2d, {0x12d,6816,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x2e, 0x33, {0x12e,5899,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x34, 0x39, {0x12f,5290,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x3a, 0x3f, {0x130,4260,100, 0,0,0x0a,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x40, 0x45, {0x131,3625,100, 0,0,0x0a,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x46, 0x4b, {0x132,3116,100, 0,0,0x04,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x4c, 0x52, {0x133,2081,100, 0,0,0x03,0xb4,0x20,0xf2,0x14,0x18,0x0}},
+	{0x53, 0x58, {0x134,1444,100, 0,0,0x07,0xb4,0x20,0xf3,0x14,0x18,0x0}},
+	{0x59, 0x6d, {0x135,1915,100, 0,0,0x00,0xb4,0x20,0xf4,0x15,0x08,0x0}},
+	{0x14, 0x27, {0x12c,7486,100, 0,0,0x00,0xb4,0x20,0xf2,0x13,0x08,0x0}},
+	{0x28, 0x2d, {0x12d,6803,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x2e, 0x33, {0x12e,5912,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x34, 0x39, {0x12f,5275,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x3a, 0x3f, {0x130,4274,100, 0,0,0x0a,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x40, 0x45, {0x131,3611,100, 0,0,0x0a,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x46, 0x4b, {0x132,3129,100, 0,0,0x04,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x4c, 0x52, {0x133,2074,100, 0,0,0x07,0xb4,0x20,0xf2,0x14,0x18,0x0}},
+	{0x53, 0x58, {0x134,1457,100, 0,0,0x01,0xb4,0x20,0xf3,0x14,0x18,0x0}},
+	{0x59, 0x6d, {0x135,1903,100, 0,0,0x00,0xb4,0x20,0xf4,0x15,0x08,0x0}}
+};
+static const opl4_region_t regions_04[] = { /* Electric Piano 1 */
+	{0x15, 0x6c, {0x00b,6570,100, 0,0,0x00,0x28,0x38,0xf0,0x00,0x0c,0x0}},
+	{0x00, 0x7f, {0x06c,6375,100, 0,2,0x00,0xb0,0x22,0xf4,0x23,0x19,0x0}}
+};
+static const opl4_region_t regions_05[] = { /* Electric Piano 2 */
+	{0x14, 0x27, {0x12c,7476,100, 0,3,0x00,0xa2,0x1b,0xf2,0x13,0x08,0x0}},
+	{0x28, 0x2d, {0x12d,6818,100, 0,3,0x00,0xa2,0x1b,0xf2,0x14,0x08,0x0}},
+	{0x2e, 0x33, {0x12e,5901,100, 0,3,0x00,0xa2,0x1b,0xf2,0x14,0x08,0x0}},
+	{0x34, 0x39, {0x12f,5292,100, 0,3,0x00,0xa2,0x1b,0xf2,0x14,0x08,0x0}},
+	{0x3a, 0x3f, {0x130,4262,100, 0,3,0x0a,0xa2,0x1b,0xf2,0x14,0x08,0x0}},
+	{0x40, 0x45, {0x131,3627,100, 0,3,0x0a,0xa2,0x1b,0xf2,0x14,0x08,0x0}},
+	{0x46, 0x4b, {0x132,3118,100, 0,3,0x04,0xa2,0x1b,0xf2,0x14,0x08,0x0}},
+	{0x4c, 0x52, {0x133,2083,100, 0,3,0x03,0xa2,0x1b,0xf2,0x14,0x18,0x0}},
+	{0x53, 0x58, {0x134,1446,100, 0,3,0x07,0xa2,0x1b,0xf3,0x14,0x18,0x0}},
+	{0x59, 0x6d, {0x135,1917,100, 0,3,0x00,0xa2,0x1b,0xf4,0x15,0x08,0x0}},
+	{0x14, 0x2d, {0x12c,7472,100, 0,0,0x00,0xa2,0x18,0xf2,0x13,0x08,0x0}},
+	{0x2e, 0x33, {0x12d,6814,100, 0,0,0x00,0xa2,0x18,0xf2,0x14,0x08,0x0}},
+	{0x34, 0x39, {0x12e,5897,100, 0,0,0x00,0xa2,0x18,0xf2,0x14,0x08,0x0}},
+	{0x3a, 0x3f, {0x12f,5288,100, 0,0,0x00,0xa2,0x18,0xf2,0x14,0x08,0x0}},
+	{0x40, 0x45, {0x130,4258,100, 0,0,0x0a,0xa2,0x18,0xf2,0x14,0x08,0x0}},
+	{0x46, 0x4b, {0x131,3623,100, 0,0,0x0a,0xa2,0x18,0xf2,0x14,0x08,0x0}},
+	{0x4c, 0x52, {0x132,3114,100, 0,0,0x04,0xa2,0x18,0xf2,0x14,0x08,0x0}},
+	{0x53, 0x58, {0x133,2079,100, 0,0,0x07,0xa2,0x18,0xf2,0x14,0x18,0x0}},
+	{0x59, 0x5e, {0x134,1442,100, 0,0,0x0a,0xa2,0x18,0xf3,0x14,0x18,0x0}},
+	{0x5f, 0x6d, {0x135,1913,100, 0,0,0x00,0xa2,0x18,0xf4,0x15,0x08,0x0}}
+};
+static const opl4_region_t regions_06[] = { /* Harpsichord */
+	{0x15, 0x39, {0x080,5158,100, 0,0,0x00,0xb2,0x20,0xf5,0x24,0x19,0x0}},
+	{0x3a, 0x3f, {0x081,4408,100, 0,0,0x00,0xb2,0x20,0xf5,0x25,0x09,0x0}},
+	{0x40, 0x45, {0x082,3622,100, 0,0,0x00,0xb2,0x20,0xf5,0x25,0x09,0x0}},
+	{0x46, 0x4d, {0x083,2843,100, 0,0,0x00,0xb2,0x20,0xf5,0x25,0x19,0x0}},
+	{0x4e, 0x6c, {0x084,1307,100, 0,0,0x00,0xb2,0x20,0xf5,0x25,0x29,0x0}}
+};
+static const opl4_region_t regions_07[] = { /* Clavinet */
+	{0x15, 0x51, {0x027,5009,100, 0,0,0x00,0xd2,0x28,0xf5,0x13,0x2b,0x0}},
+	{0x52, 0x6c, {0x028,3495,100, 0,0,0x00,0xd2,0x28,0xf5,0x13,0x3b,0x0}}
+};
+static const opl4_region_t regions_08[] = { /* Celesta */
+	{0x15, 0x6c, {0x02b,3267,100, 0,0,0x00,0xdc,0x20,0xf4,0x15,0x07,0x3}}
+};
+static const opl4_region_t regions_09[] = { /* Glockenspiel */
+	{0x15, 0x78, {0x0f3, 285,100, 0,0,0x00,0xc2,0x28,0xf6,0x25,0x25,0x0}}
+};
+static const opl4_region_t regions_0a[] = { /* Music Box */
+	{0x15, 0x6c, {0x0f3,3362,100, 0,0,0x00,0xb6,0x20,0xa6,0x25,0x25,0x0}},
+	{0x15, 0x6c, {0x101,4773,100, 0,0,0x00,0xaa,0x20,0xd4,0x14,0x16,0x0}}
+};
+static const opl4_region_t regions_0b[] = { /* Vibraphone */
+	{0x15, 0x6c, {0x101,4778,100, 0,0,0x00,0xc0,0x28,0xf4,0x14,0x16,0x4}}
+};
+static const opl4_region_t regions_0c[] = { /* Marimba */
+	{0x15, 0x3f, {0x0f4,4778,100, 0,0,0x00,0xc4,0x38,0xf7,0x47,0x08,0x0}},
+	{0x40, 0x4c, {0x0f5,3217,100, 0,0,0x00,0xc4,0x38,0xf7,0x47,0x08,0x0}},
+	{0x4d, 0x5a, {0x0f5,3217,100, 0,0,0x00,0xc4,0x38,0xf7,0x48,0x08,0x0}},
+	{0x5b, 0x7f, {0x0f5,3218,100, 0,0,0x00,0xc4,0x38,0xf7,0x48,0x18,0x0}}
+};
+static const opl4_region_t regions_0d[] = { /* Xylophone */
+	{0x00, 0x7f, {0x136,1729,100, 0,0,0x00,0xd2,0x38,0xf0,0x06,0x36,0x0}}
+};
+static const opl4_region_t regions_0e[] = { /* Tubular Bell */
+	{0x01, 0x7f, {0x0ff,3999,100, 0,1,0x00,0x90,0x21,0xf4,0xa3,0x25,0x1}}
+};
+static const opl4_region_t regions_0f[] = { /* Dulcimer */
+	{0x00, 0x7f, {0x03f,4236,100, 0,1,0x00,0xbc,0x29,0xf5,0x16,0x07,0x0}},
+	{0x00, 0x7f, {0x040,4236,100, 0,2,0x0e,0x94,0x2a,0xf5,0x16,0x07,0x0}}
+};
+static const opl4_region_t regions_10[] = { /* Drawbar Organ */
+	{0x01, 0x7f, {0x08e,4394,100, 0,2,0x14,0xc2,0x3a,0xf0,0x00,0x0a,0x0}}
+};
+static const opl4_region_t regions_11[] = { /* Percussive Organ */
+	{0x15, 0x3b, {0x08c,6062,100, 0,3,0x00,0xbe,0x3b,0xf0,0x00,0x09,0x0}},
+	{0x3c, 0x6c, {0x08d,2984,100, 0,3,0x00,0xbe,0x3b,0xf0,0x00,0x09,0x0}}
+};
+static const opl4_region_t regions_12[] = { /* Rock Organ */
+	{0x15, 0x30, {0x128,6574,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}},
+	{0x31, 0x3c, {0x129,5040,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}},
+	{0x3d, 0x48, {0x12a,3498,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}},
+	{0x49, 0x54, {0x12b,1957,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}},
+	{0x55, 0x6c, {0x127, 423,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}}
+};
+static const opl4_region_t regions_13[] = { /* Church Organ */
+	{0x15, 0x29, {0x087,7466,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}},
+	{0x2a, 0x30, {0x088,6456,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}},
+	{0x31, 0x38, {0x089,5428,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}},
+	{0x39, 0x41, {0x08a,4408,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}},
+	{0x42, 0x6c, {0x08b,3406,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}}
+};
+static const opl4_region_t regions_14[] = { /* Reed Organ */
+	{0x00, 0x53, {0x0ac,5570,100, 0,0,0x06,0xc0,0x38,0xf0,0x00,0x09,0x1}},
+	{0x54, 0x7f, {0x0ad,2497,100, 0,0,0x00,0xc0,0x38,0xf0,0x00,0x09,0x1}}
+};
+static const opl4_region_t regions_15[] = { /* Accordion */
+	{0x15, 0x4c, {0x006,4261,100, 0,2,0x00,0xa4,0x22,0x90,0x00,0x09,0x0}},
+	{0x4d, 0x6c, {0x007,1530,100, 0,2,0x00,0xa4,0x22,0x90,0x00,0x09,0x0}},
+	{0x15, 0x6c, {0x070,4391,100, 0,3,0x00,0x8a,0x23,0xa0,0x00,0x09,0x0}}
+};
+static const opl4_region_t regions_16[] = { /* Harmonica */
+	{0x15, 0x6c, {0x070,4408,100, 0,0,0x00,0xae,0x30,0xa0,0x00,0x09,0x2}}
+};
+static const opl4_region_t regions_17[] = { /* Tango Accordion */
+	{0x00, 0x53, {0x0ac,5573,100, 0,0,0x00,0xae,0x38,0xf0,0x00,0x09,0x0}},
+	{0x54, 0x7f, {0x0ad,2500,100, 0,0,0x00,0xae,0x38,0xf0,0x00,0x09,0x0}},
+	{0x15, 0x6c, {0x041,8479,100, 0,2,0x00,0x6a,0x3a,0x75,0x20,0x0a,0x0}}
+};
+static const opl4_region_t regions_18[] = { /* Nylon Guitar */
+	{0x15, 0x2f, {0x0b3,6964,100, 0,0,0x05,0xca,0x28,0xf5,0x34,0x09,0x0}},
+	{0x30, 0x36, {0x0b7,5567,100, 0,0,0x0c,0xca,0x28,0xf5,0x34,0x09,0x0}},
+	{0x37, 0x3c, {0x0b5,4653,100, 0,0,0x00,0xca,0x28,0xf6,0x34,0x09,0x0}},
+	{0x3d, 0x43, {0x0b4,3892,100, 0,0,0x00,0xca,0x28,0xf6,0x35,0x09,0x0}},
+	{0x44, 0x60, {0x0b6,2723,100, 0,0,0x00,0xca,0x28,0xf6,0x35,0x19,0x0}}
+};
+static const opl4_region_t regions_19[] = { /* Steel Guitar */
+	{0x15, 0x31, {0x00c,6937,100, 0,0,0x00,0xbc,0x28,0xf0,0x04,0x19,0x0}},
+	{0x32, 0x38, {0x00d,5410,100, 0,0,0x00,0xbc,0x28,0xf0,0x05,0x09,0x0}},
+	{0x39, 0x47, {0x00e,4379,100, 0,0,0x00,0xbc,0x28,0xf5,0x94,0x09,0x0}},
+	{0x48, 0x6c, {0x00f,2843,100, 0,0,0x00,0xbc,0x28,0xf6,0x95,0x09,0x0}}
+};
+static const opl4_region_t regions_1a[] = { /* Jazz Guitar */
+	{0x15, 0x31, {0x05a,6832,100, 0,0,0x00,0xca,0x28,0xf6,0x34,0x09,0x0}},
+	{0x32, 0x3f, {0x05b,4897,100, 0,0,0x00,0xca,0x28,0xf6,0x34,0x09,0x0}},
+	{0x40, 0x6c, {0x05c,3218,100, 0,0,0x00,0xca,0x28,0xf6,0x34,0x09,0x0}}
+};
+static const opl4_region_t regions_1b[] = { /* Clean Guitar */
+	{0x15, 0x2c, {0x061,7053,100, 0,1,0x00,0xb4,0x29,0xf5,0x54,0x0a,0x0}},
+	{0x2d, 0x31, {0x060,6434,100, 0,1,0x00,0xb4,0x29,0xf5,0x54,0x0a,0x0}},
+	{0x32, 0x38, {0x063,5764,100, 0,1,0x00,0xbe,0x29,0xf5,0x55,0x0a,0x0}},
+	{0x39, 0x3f, {0x062,4627,100, 0,1,0x00,0xb4,0x29,0xf5,0x55,0x0a,0x0}},
+	{0x40, 0x44, {0x065,3963,100, 0,1,0x00,0xb4,0x29,0xf5,0x55,0x1a,0x0}},
+	{0x45, 0x4b, {0x064,3313,100, 0,1,0x00,0xb4,0x29,0xf5,0x55,0x1a,0x0}},
+	{0x4c, 0x54, {0x066,2462,100, 0,1,0x00,0xb4,0x29,0xf5,0x55,0x2a,0x0}},
+	{0x55, 0x6c, {0x067,1307,100, 0,1,0x00,0xb4,0x29,0xf6,0x56,0x0a,0x0}}
+};
+static const opl4_region_t regions_1c[] = { /* Muted Guitar */
+	{0x01, 0x7f, {0x068,4408,100, 0,0,0x00,0xcc,0x28,0xf6,0x15,0x09,0x0}}
+};
+static const opl4_region_t regions_1d[] = { /* Overdriven Guitar */
+	{0x00, 0x40, {0x0a5,6589,100, 0,1,0x00,0xc0,0x29,0xf2,0x11,0x09,0x0}},
+	{0x41, 0x7f, {0x0a6,5428,100, 0,1,0x00,0xc0,0x29,0xf2,0x11,0x09,0x0}}
+};
+static const opl4_region_t regions_1e[] = { /* Distortion Guitar */
+	{0x15, 0x2a, {0x051,6928,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
+	{0x2b, 0x2e, {0x052,6433,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
+	{0x2f, 0x32, {0x053,5944,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
+	{0x33, 0x36, {0x054,5391,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
+	{0x37, 0x3a, {0x055,4897,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
+	{0x3b, 0x3e, {0x056,4408,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
+	{0x3f, 0x42, {0x057,3892,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
+	{0x43, 0x46, {0x058,3361,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
+	{0x47, 0x6c, {0x059,2784,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}}
+};
+static const opl4_region_t regions_1f[] = { /* Guitar Harmonics */
+	{0x15, 0x44, {0x05e,5499,100, 0,0,0x00,0xce,0x28,0xf4,0x24,0x09,0x0}},
+	{0x45, 0x49, {0x05d,4850,100, 0,0,0x00,0xe2,0x28,0xf4,0x24,0x09,0x0}},
+	{0x4a, 0x6c, {0x05f,4259,100, 0,0,0x00,0xce,0x28,0xf4,0x24,0x09,0x0}}
+};
+static const opl4_region_t regions_20[] = { /* Acoustic Bass */
+	{0x15, 0x30, {0x004,8053,100, 0,0,0x00,0xe2,0x18,0xf5,0x15,0x09,0x0}},
+	{0x31, 0x6c, {0x005,4754,100, 0,0,0x00,0xe2,0x18,0xf5,0x15,0x09,0x0}}
+};
+static const opl4_region_t regions_21[] = { /* Fingered Bass */
+	{0x01, 0x20, {0x04a,8762,100, 0,0,0x00,0xde,0x18,0xf6,0x14,0x09,0x0}},
+	{0x21, 0x25, {0x04b,8114,100, 0,0,0x00,0xde,0x18,0xf6,0x14,0x09,0x0}},
+	{0x26, 0x2a, {0x04c,7475,100, 0,0,0x00,0xde,0x18,0xf6,0x14,0x09,0x0}},
+	{0x2b, 0x7f, {0x04d,6841,100, 0,0,0x00,0xde,0x18,0xf6,0x14,0x09,0x0}}
+};
+static const opl4_region_t regions_22[] = { /* Picked Bass */
+	{0x15, 0x23, {0x04f,7954,100, 0,0,0x00,0xcc,0x18,0xf3,0x90,0x0a,0x0}},
+	{0x24, 0x2a, {0x050,7318,100, 0,0,0x05,0xcc,0x18,0xf3,0x90,0x1a,0x0}},
+	{0x2b, 0x2f, {0x06b,6654,100, 0,0,0x00,0xcc,0x18,0xf3,0x90,0x2a,0x0}},
+	{0x30, 0x47, {0x069,6031,100, 0,0,0x00,0xcc,0x18,0xf5,0xb0,0x0a,0x0}},
+	{0x48, 0x6c, {0x06a,5393,100, 0,0,0x00,0xcc,0x18,0xf5,0xb0,0x0a,0x0}}
+};
+static const opl4_region_t regions_23[] = { /* Fretless Bass */
+	{0x01, 0x7f, {0x04e,5297,100, 0,0,0x00,0xd2,0x10,0xf3,0x63,0x19,0x0}}
+};
+static const opl4_region_t regions_24[] = { /* Slap Bass 1 */
+	{0x15, 0x6c, {0x0a3,7606,100, 0,1,0x00,0xde,0x19,0xf5,0x32,0x1a,0x0}}
+};
+static const opl4_region_t regions_25[] = { /* Slap Bass 2 */
+	{0x01, 0x7f, {0x0a2,6694,100, 0,0,0x00,0xda,0x20,0xb0,0x02,0x09,0x0}}
+};
+static const opl4_region_t regions_26[] = { /* Synth Bass 1 */
+	{0x15, 0x6c, {0x0be,7466,100, 0,1,0x00,0xb8,0x39,0xf4,0x14,0x09,0x0}}
+};
+static const opl4_region_t regions_27[] = { /* Synth Bass 2 */
+	{0x00, 0x7f, {0x117,8103,100, 0,1,0x00,0xca,0x39,0xf3,0x50,0x08,0x0}}
+};
+static const opl4_region_t regions_28[] = { /* Violin */
+	{0x15, 0x3a, {0x105,5158,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
+	{0x3b, 0x3f, {0x102,4754,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
+	{0x40, 0x41, {0x106,4132,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
+	{0x42, 0x44, {0x107,4033,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
+	{0x45, 0x47, {0x108,3580,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
+	{0x48, 0x4a, {0x10a,2957,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
+	{0x4b, 0x4c, {0x10b,2724,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
+	{0x4d, 0x4e, {0x10c,2530,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
+	{0x4f, 0x51, {0x10d,2166,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
+	{0x52, 0x6c, {0x109,1825,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}}
+};
+static const opl4_region_t regions_29[] = { /* Viola */
+	{0x15, 0x32, {0x103,5780,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x33, 0x35, {0x104,5534,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x36, 0x38, {0x105,5158,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x39, 0x3d, {0x102,4754,100, 0,3,0x00,0xca,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x3e, 0x3f, {0x106,4132,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x40, 0x42, {0x107,4033,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x43, 0x45, {0x108,3580,100, 0,3,0x00,0xd0,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x46, 0x48, {0x10a,2957,100, 0,3,0x00,0xca,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x49, 0x4a, {0x10b,2724,100, 0,3,0x00,0xd0,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x4b, 0x4c, {0x10c,2530,100, 0,3,0x00,0xca,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x4d, 0x4f, {0x10d,2166,100, 0,3,0x00,0xd0,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x50, 0x6c, {0x109,1825,100, 0,3,0x00,0xd0,0x3b,0xa3,0x20,0x09,0x0}}
+};
+static const opl4_region_t regions_2a[] = { /* Cello */
+	{0x15, 0x2d, {0x112,6545,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x08,0x0}},
+	{0x2e, 0x37, {0x113,5764,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x08,0x0}},
+	{0x38, 0x3e, {0x115,4378,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x18,0x0}},
+	{0x3f, 0x44, {0x116,3998,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x18,0x0}},
+	{0x45, 0x6c, {0x114,3218,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x18,0x0}}
+};
+static const opl4_region_t regions_2b[] = { /* Contrabass */
+	{0x15, 0x29, {0x110,7713,100, 0,1,0x00,0xc2,0x19,0x90,0x00,0x09,0x0}},
+	{0x2a, 0x6c, {0x111,6162,100, 0,1,0x00,0xc2,0x19,0x90,0x00,0x09,0x0}}
+};
+static const opl4_region_t regions_2c[] = { /* Tremolo Strings */
+	{0x15, 0x3b, {0x0b0,4810,100, 0,0,0x0a,0xde,0x38,0xf0,0x00,0x07,0x6}},
+	{0x3c, 0x41, {0x035,4035,100, 0,0,0x05,0xde,0x38,0xf0,0x00,0x07,0x6}},
+	{0x42, 0x47, {0x033,3129,100, 0,0,0x05,0xde,0x38,0xf0,0x00,0x07,0x6}},
+	{0x48, 0x52, {0x034,2625,100, 0,0,0x05,0xde,0x38,0xf0,0x00,0x07,0x6}},
+	{0x53, 0x6c, {0x0af, 936,100, 0,0,0x00,0xde,0x38,0xf0,0x00,0x07,0x6}}
+};
+static const opl4_region_t regions_2d[] = { /* Pizzicato Strings */
+	{0x15, 0x32, {0x0b8,6186,100, 0,0,0x00,0xbc,0x28,0xf0,0x00,0x05,0x0}},
+	{0x33, 0x3b, {0x0b9,5031,100, 0,0,0x00,0xbc,0x28,0xf0,0x00,0x05,0x0}},
+	{0x3c, 0x42, {0x0bb,4146,100, 0,0,0x00,0xbc,0x28,0xf0,0x00,0x05,0x0}},
+	{0x43, 0x48, {0x0ba,3245,100, 0,0,0x00,0xc2,0x28,0xf0,0x00,0x05,0x0}},
+	{0x49, 0x6c, {0x0bc,2352,100, 0,0,0x00,0xbc,0x28,0xf0,0x00,0x05,0x0}}
+};
+static const opl4_region_t regions_2e[] = { /* Harp */
+	{0x15, 0x46, {0x07e,3740,100, 0,1,0x00,0xd2,0x29,0xf5,0x25,0x07,0x0}},
+	{0x47, 0x6c, {0x07f,2319,100, 0,1,0x00,0xd2,0x29,0xf5,0x25,0x07,0x0}}
+};
+static const opl4_region_t regions_2f[] = { /* Timpani */
+	{0x15, 0x6c, {0x100,6570,100, 0,0,0x00,0xf8,0x28,0xf0,0x05,0x16,0x0}}
+};
+static const opl4_region_t regions_30[] = { /* Strings */
+	{0x15, 0x3b, {0x13c,4806,100, 0,0,0x00,0xc8,0x20,0x80,0x00,0x07,0x0}},
+	{0x3c, 0x41, {0x13e,4035,100, 0,0,0x00,0xc8,0x20,0x80,0x00,0x07,0x0}},
+	{0x42, 0x47, {0x13d,3122,100, 0,0,0x00,0xc8,0x20,0x80,0x00,0x07,0x0}},
+	{0x48, 0x52, {0x13f,2629,100, 0,0,0x00,0xbe,0x20,0x80,0x00,0x07,0x0}},
+	{0x53, 0x6c, {0x140, 950,100, 0,0,0x00,0xbe,0x20,0x80,0x00,0x07,0x0}}
+};
+static const opl4_region_t regions_31[] = { /* Slow Strings */
+	{0x15, 0x3b, {0x0b0,4810,100, 0,1,0x0a,0xbe,0x19,0xf0,0x00,0x07,0x0}},
+	{0x3c, 0x41, {0x035,4035,100, 0,1,0x05,0xbe,0x19,0xf0,0x00,0x07,0x0}},
+	{0x42, 0x47, {0x033,3129,100, 0,1,0x05,0xbe,0x19,0xf0,0x00,0x07,0x0}},
+	{0x48, 0x52, {0x034,2625,100, 0,1,0x05,0xbe,0x19,0xf0,0x00,0x07,0x0}},
+	{0x53, 0x6c, {0x0af, 936,100, 0,1,0x00,0xbe,0x19,0xf0,0x00,0x07,0x0}}
+};
+static const opl4_region_t regions_32[] = { /* Synth Strings 1 */
+	{0x05, 0x71, {0x002,6045,100,-2,0,0x00,0xa6,0x20,0x93,0x22,0x06,0x0}},
+	{0x15, 0x6c, {0x0ae,3261,100, 2,0,0x00,0xc6,0x20,0x70,0x01,0x06,0x0}}
+};
+static const opl4_region_t regions_33[] = { /* Synth Strings 2 */
+	{0x15, 0x6c, {0x002,4513,100, 5,1,0x00,0xb4,0x19,0x70,0x00,0x06,0x0}},
+	{0x15, 0x6c, {0x002,4501,100,-5,1,0x00,0xb4,0x19,0x70,0x00,0x06,0x0}}
+};
+static const opl4_region_t regions_34[] = { /* Choir Aahs */
+	{0x15, 0x3a, {0x018,5010,100, 0,2,0x00,0xc2,0x1a,0x70,0x00,0x08,0x0}},
+	{0x3b, 0x40, {0x019,4370,100, 0,2,0x00,0xc2,0x1a,0x70,0x00,0x08,0x0}},
+	{0x41, 0x47, {0x01a,3478,100, 0,2,0x00,0xc2,0x1a,0x70,0x00,0x08,0x0}},
+	{0x48, 0x6c, {0x01b,2197,100, 0,2,0x00,0xc2,0x1a,0x70,0x00,0x08,0x0}}
+};
+static const opl4_region_t regions_35[] = { /* Voice Oohs */
+	{0x15, 0x6c, {0x029,3596,100, 0,0,0x00,0xe6,0x20,0xf7,0x20,0x08,0x0}}
+};
+static const opl4_region_t regions_36[] = { /* Synth Voice */
+	{0x15, 0x6c, {0x02a,3482,100, 0,1,0x00,0xc2,0x19,0x85,0x21,0x07,0x0}}
+};
+static const opl4_region_t regions_37[] = { /* Orchestra Hit */
+	{0x15, 0x6c, {0x049,4394,100, 0,0,0x00,0xfe,0x30,0x80,0x05,0x05,0x0}}
+};
+static const opl4_region_t regions_38[] = { /* Trumpet */
+	{0x15, 0x3c, {0x0f6,4706,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}},
+	{0x3d, 0x43, {0x0f8,3894,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}},
+	{0x44, 0x48, {0x0f7,3118,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}},
+	{0x49, 0x4e, {0x0fa,2322,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}},
+	{0x4f, 0x55, {0x0f9,1634,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}},
+	{0x56, 0x6c, {0x0fb, 786,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}}
+};
+static const opl4_region_t regions_39[] = { /* Trombone */
+	{0x15, 0x3a, {0x0f0,5053,100, 0,1,0x00,0xd6,0x21,0xf0,0x00,0x09,0x0}},
+	{0x3b, 0x3f, {0x0f1,4290,100, 0,1,0x00,0xd6,0x21,0xf0,0x00,0x09,0x0}},
+	{0x40, 0x6c, {0x0f2,3580,100, 0,1,0x00,0xd6,0x21,0xf0,0x00,0x09,0x0}}
+};
+static const opl4_region_t regions_3a[] = { /* Tuba */
+	{0x15, 0x2d, {0x085,7096,100, 0,1,0x00,0xde,0x21,0xf5,0x10,0x09,0x0}},
+	{0x2e, 0x6c, {0x086,6014,100, 0,1,0x00,0xde,0x21,0xf5,0x10,0x09,0x0}}
+};
+static const opl4_region_t regions_3b[] = { /* Muted Trumpet */
+	{0x15, 0x45, {0x0b1,4135,100, 0,0,0x00,0xcc,0x28,0xf3,0x10,0x0a,0x1}},
+	{0x46, 0x6c, {0x0b2,2599,100, 0,0,0x00,0xcc,0x28,0x83,0x10,0x0a,0x1}}
+};
+static const opl4_region_t regions_3c[] = { /* French Horns */
+	{0x15, 0x49, {0x07c,3624,100, 0,2,0x00,0xd0,0x1a,0xf0,0x00,0x09,0x0}},
+	{0x4a, 0x6c, {0x07d,2664,100, 0,2,0x00,0xd0,0x1a,0xf0,0x00,0x09,0x0}}
+};
+static const opl4_region_t regions_3d[] = { /* Brass Section */
+	{0x15, 0x42, {0x0fc,4375,100, 0,0,0x00,0xd6,0x28,0xf0,0x00,0x0a,0x0}},
+	{0x43, 0x6c, {0x0fd,2854,100, 0,0,0x00,0xd6,0x28,0xf0,0x00,0x0a,0x0}}
+};
+static const opl4_region_t regions_3e[] = { /* Synth Brass 1 */
+	{0x01, 0x27, {0x0d3,9094,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x28, 0x2d, {0x0da,8335,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x2e, 0x33, {0x0d4,7558,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x34, 0x39, {0x0db,6785,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x3a, 0x3f, {0x0d5,6042,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x40, 0x45, {0x0dc,5257,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x46, 0x4b, {0x0d6,4493,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x4c, 0x51, {0x0dd,3741,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x52, 0x57, {0x0d7,3012,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x58, 0x5d, {0x0de,2167,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x5e, 0x63, {0x0d8,1421,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x64, 0x7f, {0x0d9,-115,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x01, 0x27, {0x118,9103,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x28, 0x2d, {0x119,8340,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x2e, 0x33, {0x11a,7565,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x34, 0x39, {0x11b,6804,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x3a, 0x3f, {0x11c,6042,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x40, 0x45, {0x11d,5277,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x46, 0x4b, {0x11e,4520,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x4c, 0x51, {0x11f,3741,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x52, 0x57, {0x120,3012,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x58, 0x5d, {0x121,2166,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x5e, 0x64, {0x122,1421,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x65, 0x7f, {0x123,-115,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}}
+};
+static const opl4_region_t regions_3f[] = { /* Synth Brass 2 */
+	{0x01, 0x27, {0x118,9113,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x28, 0x2d, {0x119,8350,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x2e, 0x33, {0x11a,7575,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x34, 0x39, {0x11b,6814,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x3a, 0x3f, {0x11c,6052,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x40, 0x45, {0x11d,5287,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x46, 0x4b, {0x11e,4530,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x4c, 0x51, {0x11f,3751,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x52, 0x57, {0x120,3022,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x58, 0x5d, {0x121,2176,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x5e, 0x64, {0x122,1431,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x65, 0x7f, {0x123,-105,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x00, 0x7f, {0x124,4034,100,-3,2,0x00,0xea,0x22,0x85,0x23,0x08,0x0}}
+};
+static const opl4_region_t regions_40[] = { /* Soprano Sax */
+	{0x15, 0x3f, {0x0e3,4228,100, 0,1,0x00,0xc8,0x21,0xf5,0x20,0x0a,0x0}},
+	{0x40, 0x45, {0x0e4,3495,100, 0,1,0x00,0xc8,0x21,0xf5,0x20,0x0a,0x0}},
+	{0x46, 0x4b, {0x0e5,2660,100, 0,1,0x00,0xd6,0x21,0xf5,0x20,0x0a,0x0}},
+	{0x4c, 0x51, {0x0e6,2002,100, 0,1,0x00,0xd6,0x21,0xf5,0x20,0x0a,0x0}},
+	{0x52, 0x59, {0x0e7,1186,100, 0,1,0x00,0xd6,0x21,0xf5,0x20,0x0a,0x0}},
+	{0x59, 0x6c, {0x0e8,1730,100, 0,1,0x00,0xc8,0x21,0xf5,0x20,0x0a,0x0}}
+};
+static const opl4_region_t regions_41[] = { /* Alto Sax */
+	{0x15, 0x32, {0x092,6204,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x33, 0x35, {0x096,5812,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x36, 0x3a, {0x099,5318,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x3b, 0x3b, {0x08f,5076,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x3c, 0x3e, {0x093,4706,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x3f, 0x41, {0x097,4321,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x42, 0x44, {0x09a,3893,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x45, 0x47, {0x090,3497,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x48, 0x4a, {0x094,3119,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x4b, 0x4d, {0x098,2726,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x4e, 0x50, {0x09b,2393,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x51, 0x53, {0x091,2088,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x54, 0x6c, {0x095,1732,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}}
+};
+static const opl4_region_t regions_42[] = { /* Tenor Sax */
+	{0x24, 0x30, {0x0e9,6301,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}},
+	{0x31, 0x34, {0x0ea,5781,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}},
+	{0x35, 0x3a, {0x0eb,5053,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}},
+	{0x3b, 0x41, {0x0ed,4165,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}},
+	{0x42, 0x47, {0x0ec,3218,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}},
+	{0x48, 0x51, {0x0ee,2462,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}},
+	{0x52, 0x6c, {0x0ef,1421,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}}
+};
+static const opl4_region_t regions_43[] = { /* Baritone Sax */
+	{0x15, 0x2d, {0x0df,6714,100, 0,1,0x00,0xce,0x19,0xf0,0x00,0x0a,0x0}},
+	{0x2e, 0x34, {0x0e1,5552,100, 0,1,0x00,0xce,0x19,0xf0,0x00,0x0a,0x0}},
+	{0x35, 0x39, {0x0e2,5178,100, 0,1,0x00,0xce,0x19,0xf0,0x00,0x0a,0x0}},
+	{0x3a, 0x6c, {0x0e0,4437,100, 0,1,0x00,0xce,0x19,0xf0,0x00,0x0a,0x0}}
+};
+static const opl4_region_t regions_44[] = { /* Oboe */
+	{0x15, 0x3c, {0x042,4493,100, 0,1,0x00,0xe6,0x39,0xf4,0x10,0x0a,0x0}},
+	{0x3d, 0x43, {0x044,3702,100, 0,1,0x00,0xdc,0x39,0xf4,0x10,0x0a,0x0}},
+	{0x44, 0x49, {0x043,2956,100, 0,1,0x00,0xdc,0x39,0xf4,0x10,0x0a,0x0}},
+	{0x4a, 0x4f, {0x046,2166,100, 0,1,0x00,0xdc,0x39,0xf4,0x10,0x0a,0x0}},
+	{0x50, 0x55, {0x045,1420,100, 0,1,0x00,0xdc,0x39,0xf4,0x10,0x0a,0x0}},
+	{0x56, 0x6c, {0x047, 630,100, 0,1,0x00,0xe6,0x39,0xf4,0x10,0x0a,0x0}}
+};
+static const opl4_region_t regions_45[] = { /* English Horn */
+	{0x15, 0x38, {0x03c,5098,100, 0,1,0x00,0xc4,0x31,0xf0,0x00,0x09,0x0}},
+	{0x39, 0x3e, {0x03b,4291,100, 0,1,0x00,0xc4,0x31,0xf0,0x00,0x09,0x0}},
+	{0x3f, 0x6c, {0x03d,3540,100, 0,1,0x00,0xc4,0x31,0xf0,0x00,0x09,0x0}}
+};
+static const opl4_region_t regions_46[] = { /* Bassoon */
+	{0x15, 0x22, {0x038,7833,100, 0,1,0x00,0xc6,0x31,0xf0,0x00,0x0b,0x0}},
+	{0x23, 0x2e, {0x03a,7070,100, 0,1,0x00,0xc6,0x31,0xf0,0x00,0x0b,0x0}},
+	{0x2f, 0x6c, {0x039,6302,100, 0,1,0x00,0xc6,0x31,0xf0,0x00,0x0b,0x0}}
+};
+static const opl4_region_t regions_47[] = { /* Clarinet */
+	{0x15, 0x3b, {0x09e,5900,100, 0,1,0x00,0xc8,0x29,0xf3,0x20,0x0a,0x0}},
+	{0x3c, 0x41, {0x0a0,5158,100, 0,1,0x00,0xc8,0x29,0xf3,0x20,0x0a,0x0}},
+	{0x42, 0x4a, {0x09f,4260,100, 0,1,0x00,0xc8,0x29,0xf3,0x20,0x0a,0x0}},
+	{0x4b, 0x6c, {0x0a1,2957,100, 0,1,0x00,0xc8,0x29,0xf3,0x20,0x0a,0x0}}
+};
+static const opl4_region_t regions_48[] = { /* Piccolo */
+	{0x15, 0x40, {0x071,4803,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}},
+	{0x41, 0x4d, {0x072,3314,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}},
+	{0x4e, 0x53, {0x073,1731,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}},
+	{0x54, 0x5f, {0x074,2085,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}},
+	{0x60, 0x6c, {0x075,1421,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}}
+};
+static const opl4_region_t regions_49[] = { /* Flute */
+	{0x15, 0x40, {0x071,4803,100, 0,0,0x00,0xdc,0x38,0xf0,0x00,0x0a,0x2}},
+	{0x41, 0x4d, {0x072,3314,100, 0,0,0x00,0xdc,0x38,0xf0,0x00,0x0a,0x2}},
+	{0x4e, 0x6c, {0x073,1731,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}}
+};
+static const opl4_region_t regions_4a[] = { /* Recorder */
+	{0x15, 0x6f, {0x0bd,4897,100, 0,0,0x00,0xec,0x30,0x70,0x00,0x09,0x1}}
+};
+static const opl4_region_t regions_4b[] = { /* Pan Flute */
+	{0x15, 0x6c, {0x077,2359,100, 0,0,0x00,0xde,0x38,0xf0,0x00,0x09,0x3}}
+};
+static const opl4_region_t regions_4c[] = { /* Bottle Blow */
+	{0x15, 0x6c, {0x077,2359,100, 0,0,0x00,0xc8,0x38,0xf0,0x00,0x09,0x1}},
+	{0x01, 0x7f, {0x125,7372,100, 0,0,0x1e,0x80,0x00,0xf0,0x00,0x09,0x0}}
+};
+static const opl4_region_t regions_4d[] = { /* Shakuhachi */
+	{0x00, 0x7f, {0x0ab,4548,100, 0,0,0x00,0xd6,0x30,0xf0,0x00,0x0a,0x3}},
+	{0x15, 0x6c, {0x076,3716,100, 0,0,0x00,0xa2,0x28,0x70,0x00,0x09,0x2}}
+};
+static const opl4_region_t regions_4e[] = { /* Whistle */
+	{0x00, 0x7f, {0x0aa,1731,100, 0,4,0x00,0xd2,0x2c,0x70,0x00,0x0a,0x0}}
+};
+static const opl4_region_t regions_4f[] = { /* Ocarina */
+	{0x00, 0x7f, {0x0aa,1731,100, 0,1,0x00,0xce,0x29,0x90,0x00,0x0a,0x1}}
+};
+static const opl4_region_t regions_50[] = { /* Square Lead */
+	{0x01, 0x2a, {0x0cc,9853,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}},
+	{0x2b, 0x36, {0x0cd,6785,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}},
+	{0x37, 0x42, {0x0ca,5248,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}},
+	{0x43, 0x4e, {0x0cf,3713,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}},
+	{0x4f, 0x5a, {0x0ce,2176,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}},
+	{0x5b, 0x7f, {0x0cb, 640,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}},
+	{0x01, 0x2a, {0x0cc,9844,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}},
+	{0x2b, 0x36, {0x0cd,6776,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}},
+	{0x37, 0x42, {0x0ca,5239,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}},
+	{0x43, 0x4e, {0x0cf,3704,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}},
+	{0x4f, 0x5a, {0x0ce,2167,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}},
+	{0x5b, 0x7f, {0x0cb, 631,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}}
+};
+static const opl4_region_t regions_51[] = { /* Sawtooth Lead */
+	{0x01, 0x27, {0x118,9108,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x28, 0x2d, {0x119,8345,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x2e, 0x33, {0x11a,7570,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x34, 0x39, {0x11b,6809,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x3a, 0x3f, {0x11c,6047,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x40, 0x45, {0x11d,5282,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x46, 0x4b, {0x11e,4525,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x4c, 0x51, {0x11f,3746,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x52, 0x57, {0x120,3017,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x58, 0x5d, {0x121,2171,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x5e, 0x66, {0x122,1426,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x67, 0x7f, {0x123,-110,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x01, 0x27, {0x118,9098,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x28, 0x2d, {0x119,8335,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x2e, 0x33, {0x11a,7560,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x34, 0x39, {0x11b,6799,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x3a, 0x3f, {0x11c,6037,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x40, 0x45, {0x11d,5272,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x46, 0x4b, {0x11e,4515,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x4c, 0x51, {0x11f,3736,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x52, 0x57, {0x120,3007,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x58, 0x5d, {0x121,2161,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x5e, 0x66, {0x122,1416,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x67, 0x7f, {0x123,-120,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}
+};
+static const opl4_region_t regions_52[] = { /* Calliope Lead */
+	{0x00, 0x7f, {0x0aa,1731,100, 0,0,0x00,0xc2,0x28,0x90,0x00,0x0a,0x2}},
+	{0x15, 0x6c, {0x076,3716,100, 0,0,0x00,0xb6,0x28,0xb0,0x00,0x09,0x2}}
+};
+static const opl4_region_t regions_53[] = { /* Chiffer Lead */
+	{0x00, 0x7f, {0x13a,3665,100, 0,2,0x00,0xcc,0x2a,0xf0,0x10,0x09,0x1}},
+	{0x01, 0x7f, {0x0fe,3660,100, 0,0,0x00,0xbe,0x28,0xf3,0x10,0x17,0x0}}
+};
+static const opl4_region_t regions_54[] = { /* Charang Lead */
+	{0x00, 0x40, {0x0a5,6594,100, 0,3,0x00,0xba,0x33,0xf2,0x11,0x09,0x0}},
+	{0x41, 0x7f, {0x0a6,5433,100, 0,3,0x00,0xba,0x33,0xf2,0x11,0x09,0x0}},
+	{0x01, 0x27, {0x118,9098,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x28, 0x2d, {0x119,8335,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x2e, 0x33, {0x11a,7560,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x34, 0x39, {0x11b,6799,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x3a, 0x3f, {0x11c,6037,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x40, 0x45, {0x11d,5272,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x46, 0x4b, {0x11e,4515,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x4c, 0x51, {0x11f,3736,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x52, 0x57, {0x120,3007,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x58, 0x5d, {0x121,2161,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x5e, 0x66, {0x122,1416,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x67, 0x7f, {0x123,-120,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}}
+};
+static const opl4_region_t regions_55[] = { /* Voice Lead */
+	{0x00, 0x7f, {0x0aa,1739,100, 0,6,0x00,0x8c,0x2e,0x90,0x00,0x0a,0x0}},
+	{0x15, 0x6c, {0x02a,3474,100, 0,1,0x00,0xd8,0x29,0xf0,0x05,0x0a,0x0}}
+};
+static const opl4_region_t regions_56[] = { /* 5ths Lead */
+	{0x01, 0x27, {0x118,8468,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x28, 0x2d, {0x119,7705,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x2e, 0x33, {0x11a,6930,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x34, 0x39, {0x11b,6169,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x3a, 0x3f, {0x11c,5407,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x40, 0x45, {0x11d,4642,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x46, 0x4b, {0x11e,3885,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x4c, 0x51, {0x11f,3106,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x52, 0x57, {0x120,2377,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x58, 0x5d, {0x121,1531,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x5e, 0x64, {0x122, 786,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x65, 0x7f, {0x123,-750,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x05, 0x71, {0x002,4503,100, 0,1,0x00,0xb8,0x31,0xb3,0x20,0x0b,0x0}}
+};
+static const opl4_region_t regions_57[] = { /* Bass & Lead */
+	{0x00, 0x7f, {0x117,8109,100, 0,1,0x00,0xbc,0x29,0xf3,0x50,0x08,0x0}},
+	{0x01, 0x27, {0x118,9097,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x28, 0x2d, {0x119,8334,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x2e, 0x33, {0x11a,7559,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x34, 0x39, {0x11b,6798,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x3a, 0x3f, {0x11c,6036,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x40, 0x45, {0x11d,5271,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x46, 0x4b, {0x11e,4514,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x4c, 0x51, {0x11f,3735,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x52, 0x57, {0x120,3006,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x58, 0x5d, {0x121,2160,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x5e, 0x66, {0x122,1415,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x67, 0x7f, {0x123,-121,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}}
+};
+static const opl4_region_t regions_58[] = { /* New Age Pad */
+	{0x15, 0x6c, {0x002,4501,100, 0,4,0x00,0xa4,0x24,0x80,0x01,0x05,0x0}},
+	{0x15, 0x6c, {0x0f3,4253,100, 0,3,0x00,0x8c,0x23,0xa2,0x14,0x06,0x1}}
+};
+static const opl4_region_t regions_59[] = { /* Warm Pad */
+	{0x15, 0x6c, {0x04e,5306,100, 2,2,0x00,0x92,0x2a,0x34,0x23,0x05,0x2}},
+	{0x15, 0x6c, {0x029,3575,100,-2,2,0x00,0xbe,0x22,0x31,0x23,0x06,0x0}}
+};
+static const opl4_region_t regions_5a[] = { /* Polysynth Pad */
+	{0x01, 0x27, {0x118,9111,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x28, 0x2d, {0x119,8348,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x2e, 0x33, {0x11a,7573,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x34, 0x39, {0x11b,6812,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x3a, 0x3f, {0x11c,6050,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x40, 0x45, {0x11d,5285,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x46, 0x4b, {0x11e,4528,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x4c, 0x51, {0x11f,3749,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x52, 0x57, {0x120,3020,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x58, 0x5d, {0x121,2174,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x5e, 0x66, {0x122,1429,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x67, 0x7f, {0x123,-107,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x00, 0x7f, {0x124,4024,100, 0,2,0x00,0xae,0x22,0xe5,0x20,0x08,0x0}}
+};
+static const opl4_region_t regions_5b[] = { /* Choir Pad */
+	{0x15, 0x3a, {0x018,5010,100, 0,5,0x00,0xb0,0x25,0x70,0x00,0x06,0x0}},
+	{0x3b, 0x40, {0x019,4370,100, 0,5,0x00,0xb0,0x25,0x70,0x00,0x06,0x0}},
+	{0x41, 0x47, {0x01a,3478,100, 0,5,0x00,0xb0,0x25,0x70,0x00,0x06,0x0}},
+	{0x48, 0x6c, {0x01b,2197,100, 0,5,0x00,0xb0,0x25,0x70,0x00,0x06,0x0}},
+	{0x15, 0x6c, {0x02a,3482,100, 0,4,0x00,0x98,0x24,0x65,0x21,0x06,0x0}}
+};
+static const opl4_region_t regions_5c[] = { /* Bowed Pad */
+	{0x15, 0x6c, {0x101,4790,100,-1,1,0x00,0xbe,0x19,0x44,0x14,0x16,0x0}},
+	{0x00, 0x7f, {0x0aa,1720,100, 1,1,0x00,0x94,0x19,0x40,0x00,0x06,0x0}}
+};
+static const opl4_region_t regions_5d[] = { /* Metallic Pad */
+	{0x15, 0x31, {0x00c,6943,100, 0,2,0x00,0xa0,0x0a,0x60,0x03,0x06,0x0}},
+	{0x32, 0x38, {0x00d,5416,100, 0,2,0x00,0xa0,0x0a,0x60,0x03,0x06,0x0}},
+	{0x39, 0x47, {0x00e,4385,100, 0,2,0x00,0xa0,0x0a,0x60,0x03,0x06,0x0}},
+	{0x48, 0x6c, {0x00f,2849,100, 0,2,0x00,0xa0,0x0a,0x60,0x03,0x06,0x0}},
+	{0x00, 0x7f, {0x03f,4224,100, 0,1,0x00,0x9c,0x31,0x65,0x16,0x07,0x0}}
+};
+static const opl4_region_t regions_5e[] = { /* Halo Pad */
+	{0x00, 0x7f, {0x124,4038,100, 0,2,0x00,0xa6,0x1a,0x85,0x23,0x08,0x0}},
+	{0x15, 0x6c, {0x02a,3471,100, 0,3,0x00,0xc0,0x1b,0xc0,0x05,0x06,0x0}}
+};
+static const opl4_region_t regions_5f[] = { /* Sweep Pad */
+	{0x01, 0x27, {0x0d3,9100,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x28, 0x2d, {0x0da,8341,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x2e, 0x33, {0x0d4,7564,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x34, 0x39, {0x0db,6791,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x3a, 0x3f, {0x0d5,6048,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x40, 0x45, {0x0dc,5263,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x46, 0x4b, {0x0d6,4499,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x4c, 0x51, {0x0dd,3747,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x52, 0x57, {0x0d7,3018,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x58, 0x5d, {0x0de,2173,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x5e, 0x63, {0x0d8,1427,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x64, 0x7f, {0x0d9,-109,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x01, 0x27, {0x0d3,9088,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x28, 0x2d, {0x0da,8329,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x2e, 0x33, {0x0d4,7552,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x34, 0x39, {0x0db,6779,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x3a, 0x3f, {0x0d5,6036,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x40, 0x45, {0x0dc,5251,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x46, 0x4b, {0x0d6,4487,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x4c, 0x51, {0x0dd,3735,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x52, 0x57, {0x0d7,3006,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x58, 0x5d, {0x0de,2161,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x5e, 0x63, {0x0d8,1415,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x64, 0x7f, {0x0d9,-121,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}}
+};
+static const opl4_region_t regions_60[] = { /* Ice Rain */
+	{0x01, 0x7f, {0x04e,9345,100, 0,2,0x00,0xcc,0x22,0xa3,0x63,0x17,0x0}},
+	{0x00, 0x7f, {0x143,5586, 20, 0,2,0x00,0x6e,0x2a,0xf0,0x05,0x05,0x0}}
+};
+static const opl4_region_t regions_61[] = { /* Soundtrack */
+	{0x15, 0x6c, {0x002,4501,100, 0,2,0x00,0xb6,0x2a,0x60,0x01,0x05,0x0}},
+	{0x15, 0x6c, {0x0f3,1160,100, 0,5,0x00,0xa8,0x2d,0x52,0x14,0x06,0x2}}
+};
+static const opl4_region_t regions_62[] = { /* Crystal */
+	{0x15, 0x6c, {0x0f3,1826,100, 0,3,0x00,0xb8,0x33,0xf6,0x25,0x25,0x0}},
+	{0x15, 0x2c, {0x06d,7454,100, 0,3,0x00,0xac,0x3b,0x85,0x24,0x06,0x0}},
+	{0x2d, 0x36, {0x06e,5925,100, 0,3,0x00,0xac,0x3b,0x85,0x24,0x06,0x0}},
+	{0x37, 0x6c, {0x06f,4403,100, 0,3,0x09,0xac,0x3b,0x85,0x24,0x06,0x0}}
+};
+static const opl4_region_t regions_63[] = { /* Atmosphere */
+	{0x05, 0x71, {0x002,4509,100, 0,2,0x00,0xc8,0x32,0x73,0x22,0x06,0x1}},
+	{0x15, 0x2f, {0x0b3,6964,100, 0,2,0x05,0xc2,0x32,0xf5,0x34,0x07,0x2}},
+	{0x30, 0x36, {0x0b7,5567,100, 0,2,0x0c,0xc2,0x32,0xf5,0x34,0x07,0x2}},
+	{0x37, 0x3c, {0x0b5,4653,100, 0,2,0x00,0xc2,0x32,0xf6,0x34,0x07,0x2}},
+	{0x3d, 0x43, {0x0b4,3892,100, 0,2,0x00,0xc2,0x32,0xf6,0x35,0x07,0x2}},
+	{0x44, 0x60, {0x0b6,2723,100, 0,2,0x00,0xc2,0x32,0xf6,0x35,0x17,0x2}}
+};
+static const opl4_region_t regions_64[] = { /* Brightness */
+	{0x00, 0x7f, {0x137,5285,100, 0,2,0x00,0xbe,0x2a,0xa5,0x18,0x08,0x0}},
+	{0x15, 0x6c, {0x02a,3481,100, 0,1,0x00,0xc8,0x29,0x80,0x05,0x05,0x0}}
+};
+static const opl4_region_t regions_65[] = { /* Goblins */
+	{0x15, 0x6c, {0x002,4501,100,-1,2,0x00,0xca,0x2a,0x40,0x01,0x05,0x0}},
+	{0x15, 0x6c, {0x009,9679, 20, 1,4,0x00,0x3c,0x0c,0x22,0x11,0x06,0x0}}
+};
+static const opl4_region_t regions_66[] = { /* Echoes */
+	{0x15, 0x6c, {0x02a,3487,100, 0,3,0x00,0xae,0x2b,0xf5,0x21,0x06,0x0}},
+	{0x00, 0x7f, {0x124,4027,100, 0,3,0x00,0xae,0x2b,0x85,0x23,0x07,0x0}}
+};
+static const opl4_region_t regions_67[] = { /* Sci-Fi */
+	{0x15, 0x31, {0x00c,6940,100, 0,3,0x00,0xc8,0x2b,0x90,0x05,0x06,0x3}},
+	{0x32, 0x38, {0x00d,5413,100, 0,3,0x00,0xc8,0x2b,0x90,0x05,0x06,0x3}},
+	{0x39, 0x47, {0x00e,4382,100, 0,3,0x00,0xc8,0x2b,0x90,0x05,0x06,0x3}},
+	{0x48, 0x6c, {0x00f,2846,100, 0,3,0x00,0xc8,0x2b,0x90,0x05,0x06,0x3}},
+	{0x15, 0x6c, {0x002,4498,100, 0,2,0x00,0xd4,0x22,0x80,0x01,0x05,0x0}}
+};
+static const opl4_region_t regions_68[] = { /* Sitar */
+	{0x00, 0x7f, {0x10f,4408,100, 0,2,0x00,0xc4,0x32,0xf4,0x15,0x16,0x1}}
+};
+static const opl4_region_t regions_69[] = { /* Banjo */
+	{0x15, 0x34, {0x013,5685,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}},
+	{0x35, 0x38, {0x014,5009,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}},
+	{0x39, 0x3c, {0x012,4520,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}},
+	{0x3d, 0x44, {0x015,3622,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}},
+	{0x45, 0x4c, {0x017,2661,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}},
+	{0x4d, 0x6d, {0x016,1632,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}}
+};
+static const opl4_region_t regions_6a[] = { /* Shamisen */
+	{0x15, 0x6c, {0x10e,3273,100, 0,0,0x00,0xc0,0x28,0xf7,0x76,0x08,0x0}}
+};
+static const opl4_region_t regions_6b[] = { /* Koto */
+	{0x00, 0x7f, {0x0a9,4033,100, 0,0,0x00,0xc6,0x20,0xf0,0x06,0x07,0x0}}
+};
+static const opl4_region_t regions_6c[] = { /* Kalimba */
+	{0x00, 0x7f, {0x137,3749,100, 0,0,0x00,0xce,0x38,0xf5,0x18,0x08,0x0}}
+};
+static const opl4_region_t regions_6d[] = { /* Bagpipe */
+	{0x15, 0x39, {0x0a4,7683,100, 0,4,0x00,0xc0,0x1c,0xf0,0x00,0x09,0x0}},
+	{0x15, 0x39, {0x0a7,7680,100, 0,1,0x00,0xaa,0x19,0xf0,0x00,0x09,0x0}},
+	{0x3a, 0x6c, {0x0a8,3697,100, 0,1,0x00,0xaa,0x19,0xf0,0x00,0x09,0x0}}
+};
+static const opl4_region_t regions_6e[] = { /* Fiddle */
+	{0x15, 0x3a, {0x105,5158,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
+	{0x3b, 0x3f, {0x102,4754,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
+	{0x40, 0x41, {0x106,4132,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
+	{0x42, 0x44, {0x107,4033,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
+	{0x45, 0x47, {0x108,3580,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
+	{0x48, 0x4a, {0x10a,2957,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
+	{0x4b, 0x4c, {0x10b,2724,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
+	{0x4d, 0x4e, {0x10c,2530,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
+	{0x4f, 0x51, {0x10d,2166,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
+	{0x52, 0x6c, {0x109,1825,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}}
+};
+static const opl4_region_t regions_6f[] = { /* Shanai */
+	{0x15, 0x6c, {0x041,6946,100, 0,1,0x00,0xc4,0x31,0x95,0x20,0x09,0x0}}
+};
+static const opl4_region_t regions_70[] = { /* Tinkle Bell */
+	{0x15, 0x73, {0x0f3,1821,100, 0,3,0x00,0xc8,0x3b,0xd6,0x25,0x25,0x0}},
+	{0x00, 0x7f, {0x137,5669,100, 0,3,0x00,0x66,0x3b,0xf5,0x18,0x08,0x0}}
+};
+static const opl4_region_t regions_71[] = { /* Agogo */
+	{0x15, 0x74, {0x00b,2474,100, 0,0,0x00,0xd2,0x38,0xf0,0x00,0x09,0x0}}
+};
+static const opl4_region_t regions_72[] = { /* Steel Drums */
+	{0x01, 0x7f, {0x0fe,3670,100, 0,0,0x00,0xca,0x38,0xf3,0x06,0x17,0x1}},
+	{0x15, 0x6c, {0x100,9602,100, 0,0,0x00,0x54,0x38,0xb0,0x05,0x16,0x1}}
+};
+static const opl4_region_t regions_73[] = { /* Woodblock */
+	{0x15, 0x6c, {0x02c,2963, 50, 0,0,0x07,0xd4,0x00,0xf0,0x00,0x09,0x0}}
+};
+static const opl4_region_t regions_74[] = { /* Taiko Drum */
+	{0x13, 0x6c, {0x03e,1194, 50, 0,0,0x00,0xaa,0x38,0xf0,0x04,0x04,0x0}}
+};
+static const opl4_region_t regions_75[] = { /* Melodic Tom */
+	{0x15, 0x6c, {0x0c7,6418, 50, 0,0,0x00,0xe4,0x38,0xf0,0x05,0x01,0x0}}
+};
+static const opl4_region_t regions_76[] = { /* Synth Drum */
+	{0x15, 0x6c, {0x026,3898, 50, 0,0,0x00,0xd0,0x38,0xf0,0x04,0x04,0x0}}
+};
+static const opl4_region_t regions_77[] = { /* Reverse Cymbal */
+	{0x15, 0x6c, {0x031,4138, 50, 0,0,0x00,0xfe,0x38,0x3a,0xf0,0x09,0x0}}
+};
+static const opl4_region_t regions_78[] = { /* Guitar Fret Noise */
+	{0x15, 0x6c, {0x138,5266,100, 0,0,0x00,0xa0,0x38,0xf0,0x00,0x09,0x0}}
+};
+static const opl4_region_t regions_79[] = { /* Breath Noise */
+	{0x01, 0x7f, {0x125,4269,100, 0,0,0x1e,0xd0,0x38,0xf0,0x00,0x09,0x0}}
+};
+static const opl4_region_t regions_7a[] = { /* Seashore */
+	{0x15, 0x6c, {0x008,2965, 20,-2,0,0x00,0xfe,0x00,0x20,0x03,0x04,0x0}},
+	{0x01, 0x7f, {0x037,4394, 20, 2,0,0x14,0xfe,0x00,0x20,0x04,0x05,0x0}}
+};
+static const opl4_region_t regions_7b[] = { /* Bird Tweet */
+	{0x15, 0x6c, {0x009,8078,  5,-4,7,0x00,0xc2,0x0f,0x22,0x12,0x07,0x0}},
+	{0x15, 0x6c, {0x009,3583,  5, 4,5,0x00,0xae,0x15,0x72,0x12,0x07,0x0}}
+};
+static const opl4_region_t regions_7c[] = { /* Telephone Ring */
+	{0x15, 0x6c, {0x003,3602, 10, 0,0,0x00,0xce,0x00,0xf0,0x00,0x0f,0x0}}
+};
+static const opl4_region_t regions_7d[] = { /* Helicopter */
+	{0x0c, 0x7f, {0x001,2965, 10,-2,0,0x00,0xe0,0x08,0x30,0x01,0x07,0x0}},
+	{0x01, 0x7f, {0x037,4394, 10, 2,0,0x44,0x76,0x00,0x30,0x01,0x07,0x0}}
+};
+static const opl4_region_t regions_7e[] = { /* Applause */
+	{0x15, 0x6c, {0x036,8273, 20,-6,7,0x00,0xc4,0x0f,0x70,0x01,0x05,0x0}},
+	{0x15, 0x6c, {0x036,8115,  5, 6,7,0x00,0xc6,0x07,0x70,0x01,0x05,0x0}}
+};
+static const opl4_region_t regions_7f[] = { /* Gun Shot */
+	{0x15, 0x6c, {0x139,2858, 20, 0,0,0x00,0xbe,0x38,0xf0,0x03,0x00,0x0}}
+};
+static const opl4_region_t regions_drums[] = {
+	{0x18, 0x18, {0x0cb,6397,100, 3,0,0x00,0xf4,0x38,0xc9,0x1c,0x0c,0x0}},
+	{0x19, 0x19, {0x0c4,3714,100, 0,0,0x00,0xe0,0x00,0x97,0x19,0x09,0x0}},
+	{0x1a, 0x1a, {0x0c4,3519,100, 0,0,0x00,0xea,0x00,0x61,0x01,0x07,0x0}},
+	{0x1b, 0x1b, {0x0c4,3586,100, 0,0,0x00,0xea,0x00,0xf7,0x19,0x09,0x0}},
+	{0x1c, 0x1c, {0x0c4,3586,100, 0,0,0x00,0xea,0x00,0x81,0x01,0x07,0x0}},
+	{0x1e, 0x1e, {0x0c3,4783,100, 0,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x1f, 0x1f, {0x0d1,4042,100, 0,0,0x00,0xd6,0x00,0xf0,0x05,0x05,0x0}},
+	{0x20, 0x20, {0x0d2,5943,100, 0,0,0x00,0xcc,0x00,0xf0,0x00,0x09,0x0}},
+	{0x21, 0x21, {0x011,3842,100, 0,0,0x00,0xea,0x00,0xf0,0x16,0x06,0x0}},
+	{0x23, 0x23, {0x011,4098,100, 0,0,0x00,0xea,0x00,0xf0,0x16,0x06,0x0}},
+	{0x24, 0x24, {0x011,4370,100, 0,0,0x00,0xea,0x00,0xf0,0x00,0x06,0x0}},
+	{0x25, 0x25, {0x0d2,4404,100, 0,0,0x00,0xd6,0x00,0xf0,0x00,0x06,0x0}},
+	{0x26, 0x26, {0x0d1,4298,100, 0,0,0x00,0xd6,0x00,0xf0,0x05,0x05,0x0}},
+	{0x27, 0x27, {0x00a,4403,100,-1,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}},
+	{0x28, 0x28, {0x0d1,4554,100, 0,0,0x00,0xdc,0x00,0xf0,0x07,0x07,0x0}},
+	{0x29, 0x29, {0x0c8,4242,100,-4,0,0x00,0xd6,0x00,0xf6,0x16,0x06,0x0}},
+	{0x2a, 0x2a, {0x079,6160,100, 2,0,0x00,0xe0,0x00,0xf5,0x19,0x09,0x0}},
+	{0x2b, 0x2b, {0x0c8,4626,100,-3,0,0x00,0xd6,0x00,0xf6,0x16,0x06,0x0}},
+	{0x2c, 0x2c, {0x07b,6039,100, 2,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}},
+	{0x2d, 0x2d, {0x0c8,5394,100,-2,0,0x00,0xd6,0x00,0xf6,0x16,0x06,0x0}},
+	{0x2e, 0x2e, {0x07a,5690,100, 2,0,0x00,0xd6,0x00,0xf0,0x00,0x05,0x0}},
+	{0x2f, 0x2f, {0x0c7,5185,100, 2,0,0x00,0xe0,0x00,0xf6,0x17,0x07,0x0}},
+	{0x30, 0x30, {0x0c7,5650,100, 3,0,0x00,0xe0,0x00,0xf6,0x17,0x07,0x0}},
+	{0x31, 0x31, {0x031,4395,100, 2,0,0x00,0xea,0x00,0xf0,0x05,0x05,0x0}},
+	{0x32, 0x32, {0x0c7,6162,100, 4,0,0x00,0xe0,0x00,0xf6,0x17,0x07,0x0}},
+	{0x33, 0x33, {0x02e,4391,100,-2,0,0x00,0xea,0x00,0xf0,0x05,0x05,0x0}},
+	{0x34, 0x34, {0x07a,3009,100,-2,0,0x00,0xea,0x00,0xf2,0x15,0x05,0x0}},
+	{0x35, 0x35, {0x021,4522,100,-3,0,0x00,0xd6,0x00,0xf0,0x05,0x05,0x0}},
+	{0x36, 0x36, {0x025,5163,100, 1,0,0x00,0xe0,0x00,0xf0,0x00,0x09,0x0}},
+	{0x37, 0x37, {0x031,5287,100,-1,0,0x00,0xea,0x00,0xf5,0x16,0x06,0x0}},
+	{0x38, 0x38, {0x01d,4395,100, 2,0,0x00,0xe0,0x00,0xf0,0x00,0x09,0x0}},
+	{0x39, 0x39, {0x031,4647,100,-2,0,0x00,0xea,0x00,0xf4,0x16,0x06,0x0}},
+	{0x3a, 0x3a, {0x09d,4426,100,-4,0,0x00,0xe0,0x00,0xf4,0x17,0x07,0x0}},
+	{0x3b, 0x3b, {0x02e,4659,100,-2,0,0x00,0xea,0x00,0xf0,0x06,0x06,0x0}},
+	{0x3c, 0x3c, {0x01c,4769,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x3d, 0x3d, {0x01c,4611,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x3e, 0x3e, {0x01e,4402,100,-3,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x3f, 0x3f, {0x01f,4387,100,-3,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x40, 0x40, {0x01f,3983,100,-2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x41, 0x41, {0x09c,4526,100, 2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x42, 0x42, {0x09c,4016,100, 2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x43, 0x43, {0x00b,4739,100,-4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x44, 0x44, {0x00b,4179,100,-4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x45, 0x45, {0x02f,4787,100,-4,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}},
+	{0x46, 0x46, {0x030,4665,100,-4,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}},
+	{0x47, 0x47, {0x144,4519,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x0b,0x0}},
+	{0x48, 0x48, {0x144,4111,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x0b,0x0}},
+	{0x49, 0x49, {0x024,6408,100, 3,0,0x00,0xe0,0x00,0xf0,0x00,0x09,0x0}},
+	{0x4a, 0x4a, {0x024,4144,100, 3,0,0x00,0xcc,0x00,0xf0,0x00,0x09,0x0}},
+	{0x4b, 0x4b, {0x020,4001,100, 2,0,0x00,0xe0,0x00,0xf0,0x00,0x09,0x0}},
+	{0x4c, 0x4c, {0x02c,4402,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x4d, 0x4d, {0x02c,3612,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x4e, 0x4e, {0x022,4129,100,-2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x4f, 0x4f, {0x023,4147,100,-2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x50, 0x50, {0x032,4412,100,-4,0,0x00,0xd6,0x00,0xf0,0x08,0x09,0x0}},
+	{0x51, 0x51, {0x032,4385,100,-4,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}},
+	{0x52, 0x52, {0x02f,5935,100,-1,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}}
+};
+
+#define REGION(num) { ARRAY_SIZE(regions ## num), regions ## num }
+const opl4_region_ptr_t snd_yrw801_regions[0x81] = {
+	REGION(_00), REGION(_01), REGION(_02), REGION(_03),
+	REGION(_04), REGION(_05), REGION(_06), REGION(_07),
+	REGION(_08), REGION(_09), REGION(_0a), REGION(_0b),
+	REGION(_0c), REGION(_0d), REGION(_0e), REGION(_0f),
+	REGION(_10), REGION(_11), REGION(_12), REGION(_13),
+	REGION(_14), REGION(_15), REGION(_16), REGION(_17),
+	REGION(_18), REGION(_19), REGION(_1a), REGION(_1b),
+	REGION(_1c), REGION(_1d), REGION(_1e), REGION(_1f),
+	REGION(_20), REGION(_21), REGION(_22), REGION(_23),
+	REGION(_24), REGION(_25), REGION(_26), REGION(_27),
+	REGION(_28), REGION(_29), REGION(_2a), REGION(_2b),
+	REGION(_2c), REGION(_2d), REGION(_2e), REGION(_2f),
+	REGION(_30), REGION(_31), REGION(_32), REGION(_33),
+	REGION(_34), REGION(_35), REGION(_36), REGION(_37),
+	REGION(_38), REGION(_39), REGION(_3a), REGION(_3b),
+	REGION(_3c), REGION(_3d), REGION(_3e), REGION(_3f),
+	REGION(_40), REGION(_41), REGION(_42), REGION(_43),
+	REGION(_44), REGION(_45), REGION(_46), REGION(_47),
+	REGION(_48), REGION(_49), REGION(_4a), REGION(_4b),
+	REGION(_4c), REGION(_4d), REGION(_4e), REGION(_4f),
+	REGION(_50), REGION(_51), REGION(_52), REGION(_53),
+	REGION(_54), REGION(_55), REGION(_56), REGION(_57),
+	REGION(_58), REGION(_59), REGION(_5a), REGION(_5b),
+	REGION(_5c), REGION(_5d), REGION(_5e), REGION(_5f),
+	REGION(_60), REGION(_61), REGION(_62), REGION(_63),
+	REGION(_64), REGION(_65), REGION(_66), REGION(_67),
+	REGION(_68), REGION(_69), REGION(_6a), REGION(_6b),
+	REGION(_6c), REGION(_6d), REGION(_6e), REGION(_6f),
+	REGION(_70), REGION(_71), REGION(_72), REGION(_73),
+	REGION(_74), REGION(_75), REGION(_76), REGION(_77),
+	REGION(_78), REGION(_79), REGION(_7a), REGION(_7b),
+	REGION(_7c), REGION(_7d), REGION(_7e), REGION(_7f),
+	REGION(_drums)
+};
diff --git a/sound/drivers/serial-u16550.c b/sound/drivers/serial-u16550.c
new file mode 100644
index 0000000..964b97e
--- /dev/null
+++ b/sound/drivers/serial-u16550.c
@@ -0,0 +1,990 @@
+/*
+ *   serial.c
+ *   Copyright (c) by Jaroslav Kysela <perex@suse.cz>,
+ *                    Isaku Yamahata <yamahata@private.email.ne.jp>,
+ *		      George Hansper <ghansper@apana.org.au>,
+ *		      Hannu Savolainen
+ *
+ *   This code is based on the code from ALSA 0.5.9, but heavily rewritten.
+ *
+ *   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
+ *
+ * Sat Mar 31 17:27:57 PST 2001 tim.mann@compaq.com 
+ *      Added support for the Midiator MS-124T and for the MS-124W in
+ *      Single Addressed (S/A) or Multiple Burst (M/B) mode, with
+ *      power derived either parasitically from the serial port or
+ *      from a separate power supply.
+ *
+ *      More documentation can be found in serial-u16550.txt.
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/rawmidi.h>
+#include <sound/initval.h>
+
+#include <linux/serial_reg.h>
+
+#include <asm/io.h>
+
+MODULE_DESCRIPTION("MIDI serial u16550");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ALSA, MIDI serial u16550}}");
+
+#define SNDRV_SERIAL_SOUNDCANVAS 0 /* Roland Soundcanvas; F5 NN selects part */
+#define SNDRV_SERIAL_MS124T 1      /* Midiator MS-124T */
+#define SNDRV_SERIAL_MS124W_SA 2   /* Midiator MS-124W in S/A mode */
+#define SNDRV_SERIAL_MS124W_MB 3   /* Midiator MS-124W in M/B mode */
+#define SNDRV_SERIAL_GENERIC 4     /* Generic Interface */
+#define SNDRV_SERIAL_MAX_ADAPTOR SNDRV_SERIAL_GENERIC
+static char *adaptor_names[] = {
+	"Soundcanvas",
+        "MS-124T",
+	"MS-124W S/A",
+	"MS-124W M/B",
+	"Generic"
+};
+
+#define SNDRV_SERIAL_NORMALBUFF 0 /* Normal blocking buffer operation */
+#define SNDRV_SERIAL_DROPBUFF   1 /* Non-blocking discard operation */
+
+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; /* 0x3f8,0x2f8,0x3e8,0x2e8 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; 	/* 3,4,5,7,9,10,11,14,15 */
+static int speed[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 38400}; /* 9600,19200,38400,57600,115200 */
+static int base[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 115200}; /* baud base */
+static int outs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};	 /* 1 to 16 */
+static int ins[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};	/* 1 to 16 */
+static int adaptor[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = SNDRV_SERIAL_SOUNDCANVAS};
+static int droponfull[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS -1)] = SNDRV_SERIAL_NORMALBUFF };
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Serial MIDI.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Serial MIDI.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable UART16550A chip.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for UART16550A chip.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for UART16550A chip.");
+module_param_array(speed, int, NULL, 0444);
+MODULE_PARM_DESC(speed, "Speed in bauds.");
+module_param_array(base, int, NULL, 0444);
+MODULE_PARM_DESC(base, "Base for divisor in bauds.");
+module_param_array(outs, int, NULL, 0444);
+MODULE_PARM_DESC(outs, "Number of MIDI outputs.");
+module_param_array(ins, int, NULL, 0444);
+MODULE_PARM_DESC(ins, "Number of MIDI inputs.");
+module_param_array(droponfull, bool, NULL, 0444);
+MODULE_PARM_DESC(droponfull, "Flag to enable drop-on-full buffer mode");
+
+module_param_array(adaptor, int, NULL, 0444);
+MODULE_PARM_DESC(adaptor, "Type of adaptor.");
+
+/*#define SNDRV_SERIAL_MS124W_MB_NOCOMBO 1*/  /* Address outs as 0-3 instead of bitmap */
+
+#define SNDRV_SERIAL_MAX_OUTS	16		/* max 64, min 16 */
+#define SNDRV_SERIAL_MAX_INS	16		/* max 64, min 16 */
+
+#define TX_BUFF_SIZE		(1<<15)		/* Must be 2^n */
+#define TX_BUFF_MASK		(TX_BUFF_SIZE - 1)
+
+#define SERIAL_MODE_NOT_OPENED 		(0)
+#define SERIAL_MODE_INPUT_OPEN		(1 << 0)
+#define SERIAL_MODE_OUTPUT_OPEN		(1 << 1)
+#define SERIAL_MODE_INPUT_TRIGGERED	(1 << 2)
+#define SERIAL_MODE_OUTPUT_TRIGGERED	(1 << 3)
+
+typedef struct _snd_uart16550 {
+	snd_card_t *card;
+	snd_rawmidi_t *rmidi;
+	snd_rawmidi_substream_t *midi_output[SNDRV_SERIAL_MAX_OUTS];
+	snd_rawmidi_substream_t *midi_input[SNDRV_SERIAL_MAX_INS];
+
+	int filemode;		//open status of file
+
+	spinlock_t open_lock;
+
+	int irq;
+
+	unsigned long base;
+	struct resource *res_base;
+
+	unsigned int speed;
+	unsigned int speed_base;
+	unsigned char divisor;
+
+	unsigned char old_divisor_lsb;
+	unsigned char old_divisor_msb;
+	unsigned char old_line_ctrl_reg;
+
+	// parameter for using of write loop
+	short int fifo_limit;	//used in uart16550
+        short int fifo_count;	//used in uart16550
+
+	// type of adaptor
+	int adaptor;
+
+	// inputs
+	int prev_in;
+	unsigned char rstatus;
+
+	// outputs
+	int prev_out;
+	unsigned char prev_status[SNDRV_SERIAL_MAX_OUTS];
+
+	// write buffer and its writing/reading position
+	unsigned char tx_buff[TX_BUFF_SIZE];
+	int buff_in_count;
+        int buff_in;
+        int buff_out;
+        int drop_on_full;
+
+	// wait timer
+	unsigned int timer_running:1;
+	struct timer_list buffer_timer;
+
+} snd_uart16550_t;
+
+static snd_card_t *snd_serial_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+inline static void snd_uart16550_add_timer(snd_uart16550_t *uart)
+{
+	if (! uart->timer_running) {
+		/* timer 38600bps * 10bit * 16byte */
+		uart->buffer_timer.expires = jiffies + (HZ+255)/256;
+		uart->timer_running = 1;
+		add_timer(&uart->buffer_timer);
+	}
+}
+
+inline static void snd_uart16550_del_timer(snd_uart16550_t *uart)
+{
+	if (uart->timer_running) {
+		del_timer(&uart->buffer_timer);
+		uart->timer_running = 0;
+	}
+}
+
+/* This macro is only used in snd_uart16550_io_loop */
+inline static void snd_uart16550_buffer_output(snd_uart16550_t *uart)
+{
+	unsigned short buff_out = uart->buff_out;
+	if( uart->buff_in_count > 0 ) {
+		outb(uart->tx_buff[buff_out], uart->base + UART_TX);
+		uart->fifo_count++;
+		buff_out++;
+		buff_out &= TX_BUFF_MASK;
+		uart->buff_out = buff_out;
+		uart->buff_in_count--;
+	}
+}
+
+/* This loop should be called with interrupts disabled
+ * We don't want to interrupt this, 
+ * as we're already handling an interrupt 
+ */
+static void snd_uart16550_io_loop(snd_uart16550_t * uart)
+{
+	unsigned char c, status;
+	int substream;
+
+	/* recall previous stream */
+	substream = uart->prev_in;
+
+	/* Read Loop */
+	while ((status = inb(uart->base + UART_LSR)) & UART_LSR_DR) {
+		/* while receive data ready */
+		c = inb(uart->base + UART_RX);
+
+		/* keep track of last status byte */
+		if (c & 0x80) {
+			uart->rstatus = c;
+		}
+
+		/* handle stream switch */
+		if (uart->adaptor == SNDRV_SERIAL_GENERIC) {
+			if (uart->rstatus == 0xf5) {
+				if (c <= SNDRV_SERIAL_MAX_INS && c > 0)
+					substream = c - 1;
+				if (c != 0xf5)
+					uart->rstatus = 0; /* prevent future bytes from being interpreted as streams */
+			}
+			else if ((uart->filemode & SERIAL_MODE_INPUT_OPEN) && (uart->midi_input[substream] != NULL)) {
+				snd_rawmidi_receive(uart->midi_input[substream], &c, 1);
+			}
+		} else if ((uart->filemode & SERIAL_MODE_INPUT_OPEN) && (uart->midi_input[substream] != NULL)) {
+			snd_rawmidi_receive(uart->midi_input[substream], &c, 1);
+		}
+
+		if (status & UART_LSR_OE)
+			snd_printk("%s: Overrun on device at 0x%lx\n",
+			       uart->rmidi->name, uart->base);
+	}
+
+	/* remember the last stream */
+	uart->prev_in = substream;
+
+	/* no need of check SERIAL_MODE_OUTPUT_OPEN because if not,
+	   buffer is never filled. */
+	/* Check write status */
+	if (status & UART_LSR_THRE) {
+		uart->fifo_count = 0;
+	}
+	if (uart->adaptor == SNDRV_SERIAL_MS124W_SA
+	   || uart->adaptor == SNDRV_SERIAL_GENERIC) {
+		/* Can't use FIFO, must send only when CTS is true */
+		status = inb(uart->base + UART_MSR);
+		while( (uart->fifo_count == 0) && (status & UART_MSR_CTS) &&
+		      (uart->buff_in_count > 0) ) {
+		       snd_uart16550_buffer_output(uart);
+		       status = inb( uart->base + UART_MSR );
+		}
+	} else {
+		/* Write loop */
+		while (uart->fifo_count < uart->fifo_limit	/* Can we write ? */
+		       && uart->buff_in_count > 0)	/* Do we want to? */
+			snd_uart16550_buffer_output(uart);
+	}
+	if (uart->irq < 0 && uart->buff_in_count > 0)
+		snd_uart16550_add_timer(uart);
+}
+
+/* NOTES ON SERVICING INTERUPTS
+ * ---------------------------
+ * After receiving a interrupt, it is important to indicate to the UART that
+ * this has been done. 
+ * For a Rx interrupt, this is done by reading the received byte.
+ * For a Tx interrupt this is done by either:
+ * a) Writing a byte
+ * b) Reading the IIR
+ * It is particularly important to read the IIR if a Tx interrupt is received
+ * when there is no data in tx_buff[], as in this case there no other
+ * indication that the interrupt has been serviced, and it remains outstanding
+ * indefinitely. This has the curious side effect that and no further interrupts
+ * will be generated from this device AT ALL!!.
+ * It is also desirable to clear outstanding interrupts when the device is
+ * opened/closed.
+ *
+ *
+ * Note that some devices need OUT2 to be set before they will generate
+ * interrupts at all. (Possibly tied to an internal pull-up on CTS?)
+ */
+static irqreturn_t snd_uart16550_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	snd_uart16550_t *uart;
+
+	uart = (snd_uart16550_t *) dev_id;
+	spin_lock(&uart->open_lock);
+	if (uart->filemode == SERIAL_MODE_NOT_OPENED) {
+		spin_unlock(&uart->open_lock);
+		return IRQ_NONE;
+	}
+	inb(uart->base + UART_IIR);		/* indicate to the UART that the interrupt has been serviced */
+	snd_uart16550_io_loop(uart);
+	spin_unlock(&uart->open_lock);
+	return IRQ_HANDLED;
+}
+
+/* When the polling mode, this function calls snd_uart16550_io_loop. */
+static void snd_uart16550_buffer_timer(unsigned long data)
+{
+	unsigned long flags;
+	snd_uart16550_t *uart;
+
+	uart = (snd_uart16550_t *)data;
+	spin_lock_irqsave(&uart->open_lock, flags);
+	snd_uart16550_del_timer(uart);
+	snd_uart16550_io_loop(uart);
+	spin_unlock_irqrestore(&uart->open_lock, flags);
+}
+
+/*
+ *  this method probes, if an uart sits on given port
+ *  return 0 if found
+ *  return negative error if not found
+ */
+static int __init snd_uart16550_detect(snd_uart16550_t *uart)
+{
+	unsigned long io_base = uart->base;
+	int ok;
+	unsigned char c;
+
+	/* Do some vague tests for the presence of the uart */
+	if (io_base == 0 || io_base == SNDRV_AUTO_PORT) {
+		return -ENODEV;	/* Not configured */
+	}
+
+	uart->res_base = request_region(io_base, 8, "Serial MIDI");
+	if (uart->res_base == NULL) {
+		snd_printk(KERN_ERR "u16550: can't grab port 0x%lx\n", io_base);
+		return -EBUSY;
+	}
+
+	ok = 1;			/* uart detected unless one of the following tests should fail */
+	/* 8 data-bits, 1 stop-bit, parity off, DLAB = 0 */
+	outb(UART_LCR_WLEN8, io_base + UART_LCR); /* Line Control Register */
+	c = inb(io_base + UART_IER);
+	/* The top four bits of the IER should always == 0 */
+	if ((c & 0xf0) != 0)
+		ok = 0;		/* failed */
+
+	outb(0xaa, io_base + UART_SCR);
+	/* Write arbitrary data into the scratch reg */
+	c = inb(io_base + UART_SCR);
+	/* If it comes back, it's OK */
+	if (c != 0xaa)
+		ok = 0;		/* failed */
+
+	outb(0x55, io_base + UART_SCR);
+	/* Write arbitrary data into the scratch reg */
+	c = inb(io_base + UART_SCR);
+	/* If it comes back, it's OK */
+	if (c != 0x55)
+		ok = 0;		/* failed */
+
+	return ok;
+}
+
+static void snd_uart16550_do_open(snd_uart16550_t * uart)
+{
+	char byte;
+
+	/* Initialize basic variables */
+	uart->buff_in_count = 0;
+	uart->buff_in = 0;
+	uart->buff_out = 0;
+	uart->fifo_limit = 1;
+	uart->fifo_count = 0;
+	uart->timer_running = 0;
+
+	outb(UART_FCR_ENABLE_FIFO	/* Enable FIFO's (if available) */
+	     | UART_FCR_CLEAR_RCVR	/* Clear receiver FIFO */
+	     | UART_FCR_CLEAR_XMIT	/* Clear transmitter FIFO */
+	     | UART_FCR_TRIGGER_4	/* Set FIFO trigger at 4-bytes */
+	/* NOTE: interrupt generated after T=(time)4-bytes
+	 * if less than UART_FCR_TRIGGER bytes received
+	 */
+	     ,uart->base + UART_FCR);	/* FIFO Control Register */
+
+	if ((inb(uart->base + UART_IIR) & 0xf0) == 0xc0)
+		uart->fifo_limit = 16;
+	if (uart->divisor != 0) {
+		uart->old_line_ctrl_reg = inb(uart->base + UART_LCR);
+		outb(UART_LCR_DLAB	/* Divisor latch access bit */
+		     ,uart->base + UART_LCR);	/* Line Control Register */
+		uart->old_divisor_lsb = inb(uart->base + UART_DLL);
+		uart->old_divisor_msb = inb(uart->base + UART_DLM);
+
+		outb(uart->divisor
+		     ,uart->base + UART_DLL);	/* Divisor Latch Low */
+		outb(0
+		     ,uart->base + UART_DLM);	/* Divisor Latch High */
+		/* DLAB is reset to 0 in next outb() */
+	}
+	/* Set serial parameters (parity off, etc) */
+	outb(UART_LCR_WLEN8	/* 8 data-bits */
+	     | 0		/* 1 stop-bit */
+	     | 0		/* parity off */
+	     | 0		/* DLAB = 0 */
+	     ,uart->base + UART_LCR);	/* Line Control Register */
+
+	switch (uart->adaptor) {
+	default:
+		outb(UART_MCR_RTS	/* Set Request-To-Send line active */
+		     | UART_MCR_DTR	/* Set Data-Terminal-Ready line active */
+		     | UART_MCR_OUT2	/* Set OUT2 - not always required, but when
+					 * it is, it is ESSENTIAL for enabling interrupts
+				 */
+		     ,uart->base + UART_MCR);	/* Modem Control Register */
+		break;
+	case SNDRV_SERIAL_MS124W_SA:
+	case SNDRV_SERIAL_MS124W_MB:
+		/* MS-124W can draw power from RTS and DTR if they
+		   are in opposite states. */ 
+		outb(UART_MCR_RTS | (0&UART_MCR_DTR) | UART_MCR_OUT2,
+		     uart->base + UART_MCR);
+		break;
+	case SNDRV_SERIAL_MS124T:
+		/* MS-124T can draw power from RTS and/or DTR (preferably
+		   both) if they are both asserted. */
+		outb(UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2,
+		     uart->base + UART_MCR);
+		break;
+	}
+
+	if (uart->irq < 0) {
+		byte = (0 & UART_IER_RDI)	/* Disable Receiver data interrupt */
+		    |(0 & UART_IER_THRI)	/* Disable Transmitter holding register empty interrupt */
+		    ;
+	} else if (uart->adaptor == SNDRV_SERIAL_MS124W_SA) {
+		byte = UART_IER_RDI	/* Enable Receiver data interrupt */
+		    | UART_IER_MSI	/* Enable Modem status interrupt */
+		    ;
+	} else if (uart->adaptor == SNDRV_SERIAL_GENERIC) {
+		byte = UART_IER_RDI	/* Enable Receiver data interrupt */
+		    | UART_IER_MSI	/* Enable Modem status interrupt */
+		    | UART_IER_THRI	/* Enable Transmitter holding register empty interrupt */
+		    ;
+	} else {
+		byte = UART_IER_RDI	/* Enable Receiver data interrupt */
+		    | UART_IER_THRI	/* Enable Transmitter holding register empty interrupt */
+		    ;
+	}
+	outb(byte, uart->base + UART_IER);	/* Interupt enable Register */
+
+	inb(uart->base + UART_LSR);	/* Clear any pre-existing overrun indication */
+	inb(uart->base + UART_IIR);	/* Clear any pre-existing transmit interrupt */
+	inb(uart->base + UART_RX);	/* Clear any pre-existing receive interrupt */
+}
+
+static void snd_uart16550_do_close(snd_uart16550_t * uart)
+{
+	if (uart->irq < 0)
+		snd_uart16550_del_timer(uart);
+
+	/* NOTE: may need to disable interrupts before de-registering out handler.
+	 * For now, the consequences are harmless.
+	 */
+
+	outb((0 & UART_IER_RDI)		/* Disable Receiver data interrupt */
+	     |(0 & UART_IER_THRI)	/* Disable Transmitter holding register empty interrupt */
+	     ,uart->base + UART_IER);	/* Interupt enable Register */
+
+	switch (uart->adaptor) {
+	default:
+		outb((0 & UART_MCR_RTS)		/* Deactivate Request-To-Send line  */
+		     |(0 & UART_MCR_DTR)	/* Deactivate Data-Terminal-Ready line */
+		     |(0 & UART_MCR_OUT2)	/* Deactivate OUT2 */
+		     ,uart->base + UART_MCR);	/* Modem Control Register */
+	  break;
+	case SNDRV_SERIAL_MS124W_SA:
+	case SNDRV_SERIAL_MS124W_MB:
+		/* MS-124W can draw power from RTS and DTR if they
+		   are in opposite states; leave it powered. */ 
+		outb(UART_MCR_RTS | (0&UART_MCR_DTR) | (0&UART_MCR_OUT2),
+		     uart->base + UART_MCR);
+		break;
+	case SNDRV_SERIAL_MS124T:
+		/* MS-124T can draw power from RTS and/or DTR (preferably
+		   both) if they are both asserted; leave it powered. */
+		outb(UART_MCR_RTS | UART_MCR_DTR | (0&UART_MCR_OUT2),
+		     uart->base + UART_MCR);
+		break;
+	}
+
+	inb(uart->base + UART_IIR);	/* Clear any outstanding interrupts */
+
+	/* Restore old divisor */
+	if (uart->divisor != 0) {
+		outb(UART_LCR_DLAB		/* Divisor latch access bit */
+		     ,uart->base + UART_LCR);	/* Line Control Register */
+		outb(uart->old_divisor_lsb
+		     ,uart->base + UART_DLL);	/* Divisor Latch Low */
+		outb(uart->old_divisor_msb
+		     ,uart->base + UART_DLM);	/* Divisor Latch High */
+		/* Restore old LCR (data bits, stop bits, parity, DLAB) */
+		outb(uart->old_line_ctrl_reg
+		     ,uart->base + UART_LCR);	/* Line Control Register */
+	}
+}
+
+static int snd_uart16550_input_open(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	snd_uart16550_t *uart = substream->rmidi->private_data;
+
+	spin_lock_irqsave(&uart->open_lock, flags);
+	if (uart->filemode == SERIAL_MODE_NOT_OPENED)
+		snd_uart16550_do_open(uart);
+	uart->filemode |= SERIAL_MODE_INPUT_OPEN;
+	uart->midi_input[substream->number] = substream;
+	spin_unlock_irqrestore(&uart->open_lock, flags);
+	return 0;
+}
+
+static int snd_uart16550_input_close(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	snd_uart16550_t *uart = substream->rmidi->private_data;
+
+	spin_lock_irqsave(&uart->open_lock, flags);
+	uart->filemode &= ~SERIAL_MODE_INPUT_OPEN;
+	uart->midi_input[substream->number] = NULL;
+	if (uart->filemode == SERIAL_MODE_NOT_OPENED)
+		snd_uart16550_do_close(uart);
+	spin_unlock_irqrestore(&uart->open_lock, flags);
+	return 0;
+}
+
+static void snd_uart16550_input_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+	unsigned long flags;
+	snd_uart16550_t *uart = substream->rmidi->private_data;
+
+	spin_lock_irqsave(&uart->open_lock, flags);
+	if (up) {
+		uart->filemode |= SERIAL_MODE_INPUT_TRIGGERED;
+	} else {
+		uart->filemode &= ~SERIAL_MODE_INPUT_TRIGGERED;
+	}
+	spin_unlock_irqrestore(&uart->open_lock, flags);
+}
+
+static int snd_uart16550_output_open(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	snd_uart16550_t *uart = substream->rmidi->private_data;
+
+	spin_lock_irqsave(&uart->open_lock, flags);
+	if (uart->filemode == SERIAL_MODE_NOT_OPENED)
+		snd_uart16550_do_open(uart);
+	uart->filemode |= SERIAL_MODE_OUTPUT_OPEN;
+	uart->midi_output[substream->number] = substream;
+	spin_unlock_irqrestore(&uart->open_lock, flags);
+	return 0;
+};
+
+static int snd_uart16550_output_close(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	snd_uart16550_t *uart = substream->rmidi->private_data;
+
+	spin_lock_irqsave(&uart->open_lock, flags);
+	uart->filemode &= ~SERIAL_MODE_OUTPUT_OPEN;
+	uart->midi_output[substream->number] = NULL;
+	if (uart->filemode == SERIAL_MODE_NOT_OPENED)
+		snd_uart16550_do_close(uart);
+	spin_unlock_irqrestore(&uart->open_lock, flags);
+	return 0;
+};
+
+inline static int snd_uart16550_buffer_can_write( snd_uart16550_t *uart, int Num )
+{
+	if( uart->buff_in_count + Num < TX_BUFF_SIZE )
+		return 1;
+	else
+		return 0;
+}
+
+inline static int snd_uart16550_write_buffer(snd_uart16550_t *uart, unsigned char byte)
+{
+	unsigned short buff_in = uart->buff_in;
+	if( uart->buff_in_count < TX_BUFF_SIZE ) {
+		uart->tx_buff[buff_in] = byte;
+		buff_in++;
+		buff_in &= TX_BUFF_MASK;
+		uart->buff_in = buff_in;
+		uart->buff_in_count++;
+		if (uart->irq < 0) /* polling mode */
+			snd_uart16550_add_timer(uart);
+		return 1;
+	} else
+		return 0;
+}
+
+static int snd_uart16550_output_byte(snd_uart16550_t *uart, snd_rawmidi_substream_t * substream, unsigned char midi_byte)
+{
+	if (uart->buff_in_count == 0                            /* Buffer empty? */
+	    && ((uart->adaptor != SNDRV_SERIAL_MS124W_SA &&
+	    uart->adaptor != SNDRV_SERIAL_GENERIC) ||
+		(uart->fifo_count == 0                               /* FIFO empty? */
+		 && (inb(uart->base + UART_MSR) & UART_MSR_CTS)))) { /* CTS? */
+
+	        /* Tx Buffer Empty - try to write immediately */
+		if ((inb(uart->base + UART_LSR) & UART_LSR_THRE) != 0) {
+		        /* Transmitter holding register (and Tx FIFO) empty */
+		        uart->fifo_count = 1;
+			outb(midi_byte, uart->base + UART_TX);
+		} else {
+		        if (uart->fifo_count < uart->fifo_limit) {
+			        uart->fifo_count++;
+				outb(midi_byte, uart->base + UART_TX);
+			} else {
+			        /* Cannot write (buffer empty) - put char in buffer */
+				snd_uart16550_write_buffer(uart, midi_byte);
+			}
+		}
+	} else {
+		if( !snd_uart16550_write_buffer(uart, midi_byte) ) {
+			snd_printk("%s: Buffer overrun on device at 0x%lx\n",
+				   uart->rmidi->name, uart->base);
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+static void snd_uart16550_output_write(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	unsigned char midi_byte, addr_byte;
+	snd_uart16550_t *uart = substream->rmidi->private_data;
+	char first;
+	static unsigned long lasttime=0;
+	
+	/* Interupts are disabled during the updating of the tx_buff,
+	 * since it is 'bad' to have two processes updating the same
+	 * variables (ie buff_in & buff_out)
+	 */
+
+	spin_lock_irqsave(&uart->open_lock, flags);
+
+	if (uart->irq < 0)	//polling
+		snd_uart16550_io_loop(uart);
+
+	if (uart->adaptor == SNDRV_SERIAL_MS124W_MB) {
+		while (1) {
+			/* buffer full? */
+			/* in this mode we need two bytes of space */
+			if (uart->buff_in_count > TX_BUFF_SIZE - 2)
+				break;
+			if (snd_rawmidi_transmit(substream, &midi_byte, 1) != 1)
+				break;
+#ifdef SNDRV_SERIAL_MS124W_MB_NOCOMBO
+			/* select exactly one of the four ports */
+			addr_byte = (1 << (substream->number + 4)) | 0x08;
+#else
+			/* select any combination of the four ports */
+			addr_byte = (substream->number << 4) | 0x08;
+			/* ...except none */
+			if (addr_byte == 0x08) addr_byte = 0xf8;
+#endif
+			snd_uart16550_output_byte(uart, substream, addr_byte);
+			/* send midi byte */
+			snd_uart16550_output_byte(uart, substream, midi_byte);
+		}
+	} else {
+		first = 0;
+		while( 1 == snd_rawmidi_transmit_peek(substream, &midi_byte, 1) ) {
+			/* Also send F5 after 3 seconds with no data to handle device disconnect */
+			if (first == 0 && (uart->adaptor == SNDRV_SERIAL_SOUNDCANVAS ||
+				uart->adaptor == SNDRV_SERIAL_GENERIC) &&
+			   (uart->prev_out != substream->number || jiffies-lasttime > 3*HZ)) {
+
+				if( snd_uart16550_buffer_can_write( uart, 3 ) ) {
+					/* Roland Soundcanvas part selection */
+					/* If this substream of the data is different previous
+					   substream in this uart, send the change part event */
+					uart->prev_out = substream->number;
+					/* change part */
+					snd_uart16550_output_byte(uart, substream, 0xf5);
+					/* data */
+					snd_uart16550_output_byte(uart, substream, uart->prev_out + 1);
+					/* If midi_byte is a data byte, send the previous status byte */
+					if ((midi_byte < 0x80) && (uart->adaptor == SNDRV_SERIAL_SOUNDCANVAS))
+						snd_uart16550_output_byte(uart, substream, uart->prev_status[uart->prev_out]);
+				} else if( !uart->drop_on_full )
+					break;
+
+			}
+
+			/* send midi byte */
+			if( !snd_uart16550_output_byte(uart, substream, midi_byte) && !uart->drop_on_full )
+				break;
+
+			if (midi_byte >= 0x80 && midi_byte < 0xf0)
+				uart->prev_status[uart->prev_out] = midi_byte;
+			first = 1;
+
+			snd_rawmidi_transmit_ack( substream, 1 );
+		}
+		lasttime = jiffies;
+	}
+	spin_unlock_irqrestore(&uart->open_lock, flags);
+}
+
+static void snd_uart16550_output_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+	unsigned long flags;
+	snd_uart16550_t *uart = substream->rmidi->private_data;
+
+	spin_lock_irqsave(&uart->open_lock, flags);
+	if (up) {
+		uart->filemode |= SERIAL_MODE_OUTPUT_TRIGGERED;
+	} else {
+		uart->filemode &= ~SERIAL_MODE_OUTPUT_TRIGGERED;
+	}
+	spin_unlock_irqrestore(&uart->open_lock, flags);
+	if (up)
+		snd_uart16550_output_write(substream);
+}
+
+static snd_rawmidi_ops_t snd_uart16550_output =
+{
+	.open =		snd_uart16550_output_open,
+	.close =	snd_uart16550_output_close,
+	.trigger =	snd_uart16550_output_trigger,
+};
+
+static snd_rawmidi_ops_t snd_uart16550_input =
+{
+	.open =		snd_uart16550_input_open,
+	.close =	snd_uart16550_input_close,
+	.trigger =	snd_uart16550_input_trigger,
+};
+
+static int snd_uart16550_free(snd_uart16550_t *uart)
+{
+	if (uart->irq >= 0)
+		free_irq(uart->irq, (void *)uart);
+	if (uart->res_base) {
+		release_resource(uart->res_base);
+		kfree_nocheck(uart->res_base);
+	}
+	kfree(uart);
+	return 0;
+};
+
+static int snd_uart16550_dev_free(snd_device_t *device)
+{
+	snd_uart16550_t *uart = device->device_data;
+	return snd_uart16550_free(uart);
+}
+
+static int __init snd_uart16550_create(snd_card_t * card,
+				       unsigned long iobase,
+				       int irq,
+				       unsigned int speed,
+				       unsigned int base,
+				       int adaptor,
+				       int droponfull,
+				       snd_uart16550_t **ruart)
+{
+	static snd_device_ops_t ops = {
+		.dev_free =	snd_uart16550_dev_free,
+	};
+	snd_uart16550_t *uart;
+	int err;
+
+
+	if ((uart = kcalloc(1, sizeof(*uart), GFP_KERNEL)) == NULL)
+		return -ENOMEM;
+	uart->adaptor = adaptor;
+	uart->card = card;
+	spin_lock_init(&uart->open_lock);
+	uart->irq = -1;
+	uart->base = iobase;
+	uart->drop_on_full = droponfull;
+
+	if ((err = snd_uart16550_detect(uart)) <= 0) {
+		printk(KERN_ERR "no UART detected at 0x%lx\n", iobase);
+		return err;
+	}
+
+	if (irq >= 0 && irq != SNDRV_AUTO_IRQ) {
+		if (request_irq(irq, snd_uart16550_interrupt,
+				SA_INTERRUPT, "Serial MIDI", (void *) uart)) {
+			snd_printk("irq %d busy. Using Polling.\n", irq);
+		} else {
+			uart->irq = irq;
+		}
+	}
+	uart->divisor = base / speed;
+	uart->speed = base / (unsigned int)uart->divisor;
+	uart->speed_base = base;
+	uart->prev_out = -1;
+	uart->prev_in = 0;
+	uart->rstatus = 0;
+	memset(uart->prev_status, 0x80, sizeof(unsigned char) * SNDRV_SERIAL_MAX_OUTS);
+	init_timer(&uart->buffer_timer);
+	uart->buffer_timer.function = snd_uart16550_buffer_timer;
+	uart->buffer_timer.data = (unsigned long)uart;
+	uart->timer_running = 0;
+
+	/* Register device */
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, uart, &ops)) < 0) {
+		snd_uart16550_free(uart);
+		return err;
+	}
+
+	switch (uart->adaptor) {
+	case SNDRV_SERIAL_MS124W_SA:
+	case SNDRV_SERIAL_MS124W_MB:
+		/* MS-124W can draw power from RTS and DTR if they
+		   are in opposite states. */ 
+		outb(UART_MCR_RTS | (0&UART_MCR_DTR), uart->base + UART_MCR);
+		break;
+	case SNDRV_SERIAL_MS124T:
+		/* MS-124T can draw power from RTS and/or DTR (preferably
+		   both) if they are asserted. */
+		outb(UART_MCR_RTS | UART_MCR_DTR, uart->base + UART_MCR);
+		break;
+	default:
+		break;
+	}
+
+	if (ruart)
+		*ruart = uart;
+
+	return 0;
+}
+
+static void __init snd_uart16550_substreams(snd_rawmidi_str_t *stream)
+{
+	struct list_head *list;
+
+	list_for_each(list, &stream->substreams) {
+		snd_rawmidi_substream_t *substream = list_entry(list, snd_rawmidi_substream_t, list);
+		sprintf(substream->name, "Serial MIDI %d", substream->number + 1);
+	}
+}
+
+static int __init snd_uart16550_rmidi(snd_uart16550_t *uart, int device, int outs, int ins, snd_rawmidi_t **rmidi)
+{
+	snd_rawmidi_t *rrawmidi;
+	int err;
+
+	if ((err = snd_rawmidi_new(uart->card, "UART Serial MIDI", device, outs, ins, &rrawmidi)) < 0)
+		return err;
+	snd_rawmidi_set_ops(rrawmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_uart16550_input);
+	snd_rawmidi_set_ops(rrawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_uart16550_output);
+	strcpy(rrawmidi->name, "Serial MIDI");
+	snd_uart16550_substreams(&rrawmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]);
+	snd_uart16550_substreams(&rrawmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]);
+	rrawmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
+			       SNDRV_RAWMIDI_INFO_INPUT |
+			       SNDRV_RAWMIDI_INFO_DUPLEX;
+	rrawmidi->private_data = uart;
+	if (rmidi)
+		*rmidi = rrawmidi;
+	return 0;
+}
+
+static int __init snd_serial_probe(int dev)
+{
+	snd_card_t *card;
+	snd_uart16550_t *uart;
+	int err;
+
+	if (!enable[dev])
+		return -ENOENT;
+
+	switch (adaptor[dev]) {
+	case SNDRV_SERIAL_SOUNDCANVAS:
+		ins[dev] = 1;
+		break;
+	case SNDRV_SERIAL_MS124T:
+	case SNDRV_SERIAL_MS124W_SA:
+		outs[dev] = 1;
+		ins[dev] = 1;
+		break;
+	case SNDRV_SERIAL_MS124W_MB:
+		outs[dev] = 16;
+		ins[dev] = 1;
+		break;
+	case SNDRV_SERIAL_GENERIC:
+		break;
+	default:
+		snd_printk("Adaptor type is out of range 0-%d (%d)\n",
+			   SNDRV_SERIAL_MAX_ADAPTOR, adaptor[dev]);
+		return -ENODEV;
+	}
+
+	if (outs[dev] < 1 || outs[dev] > SNDRV_SERIAL_MAX_OUTS) {
+		snd_printk("Count of outputs is out of range 1-%d (%d)\n",
+			   SNDRV_SERIAL_MAX_OUTS, outs[dev]);
+		return -ENODEV;
+	}
+
+	if (ins[dev] < 1 || ins[dev] > SNDRV_SERIAL_MAX_INS) {
+		snd_printk("Count of inputs is out of range 1-%d (%d)\n",
+			   SNDRV_SERIAL_MAX_INS, ins[dev]);
+		return -ENODEV;
+	}
+
+	card  = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
+	if (card == NULL)
+		return -ENOMEM;
+
+	strcpy(card->driver, "Serial");
+	strcpy(card->shortname, "Serial MIDI (UART16550A)");
+
+	if ((err = snd_uart16550_create(card,
+					port[dev],
+					irq[dev],
+					speed[dev],
+					base[dev],
+					adaptor[dev],
+					droponfull[dev],
+					&uart)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if ((err = snd_uart16550_rmidi(uart, 0, outs[dev], ins[dev], &uart->rmidi)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	sprintf(card->longname, "%s at 0x%lx, irq %d speed %d div %d outs %d ins %d adaptor %s droponfull %d",
+		card->shortname,
+		uart->base,
+		uart->irq,
+		uart->speed,
+		(int)uart->divisor,
+		outs[dev],
+		ins[dev],
+		adaptor_names[uart->adaptor],
+		uart->drop_on_full);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	snd_serial_cards[dev] = card;
+	return 0;
+}
+
+static int __init alsa_card_serial_init(void)
+{
+	int dev = 0;
+	int cards = 0;
+
+	for (dev = 0; dev < SNDRV_CARDS; dev++) {
+		if (snd_serial_probe(dev) == 0)
+			cards++;
+	}
+
+	if (cards == 0) {
+#ifdef MODULE
+		printk(KERN_ERR "serial midi soundcard not found or device busy\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_serial_exit(void)
+{
+	int dev;
+
+	for (dev = 0; dev < SNDRV_CARDS; dev++) {
+		if (snd_serial_cards[dev] != NULL)
+			snd_card_free(snd_serial_cards[dev]);
+	}
+}
+
+module_init(alsa_card_serial_init)
+module_exit(alsa_card_serial_exit)
diff --git a/sound/drivers/virmidi.c b/sound/drivers/virmidi.c
new file mode 100644
index 0000000..5937711
--- /dev/null
+++ b/sound/drivers/virmidi.c
@@ -0,0 +1,159 @@
+/*
+ *  Dummy soundcard for virtual rawmidi devices
+ *
+ *  Copyright (c) 2000 by 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
+ *
+ */
+
+/*
+ * VIRTUAL RAW MIDI DEVICE CARDS
+ *
+ * This dummy card contains up to 4 virtual rawmidi devices.
+ * They are not real rawmidi devices but just associated with sequencer
+ * clients, so that any input/output sources can be connected as a raw
+ * MIDI device arbitrary.
+ * Also, multiple access is allowed to a single rawmidi device.
+ *
+ * Typical usage is like following:
+ * - Load snd-virmidi module.
+ *	# modprobe snd-virmidi index=2
+ *   Then, sequencer clients 72:0 to 75:0 will be created, which are
+ *   mapped from /dev/snd/midiC1D0 to /dev/snd/midiC1D3, respectively.
+ *
+ * - Connect input/output via aconnect.
+ *	% aconnect 64:0 72:0	# keyboard input redirection 64:0 -> 72:0
+ *	% aconnect 72:0 65:0	# output device redirection 72:0 -> 65:0
+ *
+ * - Run application using a midi device (eg. /dev/snd/midiC1D0)
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/seq_kernel.h>
+#include <sound/seq_virmidi.h>
+#include <sound/initval.h>
+
+/* hack: OSS defines midi_devs, so undefine it (versioned symbols) */
+#undef midi_devs
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("Dummy soundcard for virtual rawmidi devices");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ALSA,Virtual rawmidi device}}");
+
+#define MAX_MIDI_DEVICES	8
+
+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] = {1, [1 ... (SNDRV_CARDS - 1)] = 0};
+static int midi_devs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 4};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for virmidi soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for virmidi soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable this soundcard.");
+module_param_array(midi_devs, int, NULL, 0444);
+MODULE_PARM_DESC(midi_devs, "MIDI devices # (1-8)");
+
+typedef struct snd_card_virmidi {
+	snd_card_t *card;
+	snd_rawmidi_t *midi[MAX_MIDI_DEVICES];
+} snd_card_virmidi_t;
+
+static snd_card_t *snd_virmidi_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+
+static int __init snd_card_virmidi_probe(int dev)
+{
+	snd_card_t *card;
+	struct snd_card_virmidi *vmidi;
+	int idx, err;
+
+	if (!enable[dev])
+		return -ENODEV;
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE,
+			    sizeof(struct snd_card_virmidi));
+	if (card == NULL)
+		return -ENOMEM;
+	vmidi = (struct snd_card_virmidi *)card->private_data;
+	vmidi->card = card;
+
+	if (midi_devs[dev] > MAX_MIDI_DEVICES) {
+		snd_printk("too much midi devices for virmidi %d: force to use %d\n", dev, MAX_MIDI_DEVICES);
+		midi_devs[dev] = MAX_MIDI_DEVICES;
+	}
+	for (idx = 0; idx < midi_devs[dev]; idx++) {
+		snd_rawmidi_t *rmidi;
+		snd_virmidi_dev_t *rdev;
+		if ((err = snd_virmidi_new(card, idx, &rmidi)) < 0)
+			goto __nodev;
+		rdev = rmidi->private_data;
+		vmidi->midi[idx] = rmidi;
+		strcpy(rmidi->name, "Virtual Raw MIDI");
+		rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH;
+	}
+	
+	strcpy(card->driver, "VirMIDI");
+	strcpy(card->shortname, "VirMIDI");
+	sprintf(card->longname, "Virtual MIDI Card %i", dev + 1);
+	if ((err = snd_card_register(card)) == 0) {
+		snd_virmidi_cards[dev] = card;
+		return 0;
+	}
+      __nodev:
+	snd_card_free(card);
+	return err;
+}
+
+static int __init alsa_card_virmidi_init(void)
+{
+	int dev, cards;
+
+	for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev]; dev++) {
+		if (snd_card_virmidi_probe(dev) < 0) {
+#ifdef MODULE
+			printk(KERN_ERR "Card-VirMIDI #%i not found or device busy\n", dev + 1);
+#endif
+			break;
+		}
+		cards++;
+	}
+	if (!cards) {
+#ifdef MODULE
+		printk(KERN_ERR "Card-VirMIDI soundcard not found or device busy\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_virmidi_exit(void)
+{
+	int dev;
+
+	for (dev = 0; dev < SNDRV_CARDS; dev++)
+		snd_card_free(snd_virmidi_cards[dev]);
+}
+
+module_init(alsa_card_virmidi_init)
+module_exit(alsa_card_virmidi_exit)
diff --git a/sound/drivers/vx/Makefile b/sound/drivers/vx/Makefile
new file mode 100644
index 0000000..269bd85
--- /dev/null
+++ b/sound/drivers/vx/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-vx-lib-objs := vx_core.o vx_hwdep.o vx_pcm.o vx_mixer.o vx_cmd.o vx_uer.o
+
+obj-$(CONFIG_SND_VX_LIB) += snd-vx-lib.o
diff --git a/sound/drivers/vx/vx_cmd.c b/sound/drivers/vx/vx_cmd.c
new file mode 100644
index 0000000..7a22134
--- /dev/null
+++ b/sound/drivers/vx/vx_cmd.c
@@ -0,0 +1,109 @@
+/*
+ * Driver for Digigram VX soundcards
+ *
+ * DSP commands
+ *
+ * Copyright (c) 2002 by 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 <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/vx_core.h>
+#include "vx_cmd.h"
+
+/*
+ * Array of DSP commands
+ */
+static struct vx_cmd_info vx_dsp_cmds[] = {
+[CMD_VERSION] =			{ 0x010000, 2, RMH_SSIZE_FIXED, 1 },
+[CMD_SUPPORTED] =		{ 0x020000, 1, RMH_SSIZE_FIXED, 2 },
+[CMD_TEST_IT] =			{ 0x040000, 1, RMH_SSIZE_FIXED, 1 },
+[CMD_SEND_IRQA] =		{ 0x070001, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_IBL] =			{ 0x080000, 1, RMH_SSIZE_FIXED, 4 },
+[CMD_ASYNC] =			{ 0x0A0000, 1, RMH_SSIZE_ARG, 0 },
+[CMD_RES_PIPE] =		{ 0x400000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_FREE_PIPE] =		{ 0x410000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_CONF_PIPE] =		{ 0x42A101, 2, RMH_SSIZE_FIXED, 0 },
+[CMD_ABORT_CONF_PIPE] =		{ 0x42A100, 2, RMH_SSIZE_FIXED, 0 },
+[CMD_PARAM_OUTPUT_PIPE] =	{ 0x43A000, 2, RMH_SSIZE_FIXED, 0 },
+[CMD_STOP_PIPE] =		{ 0x470004, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_PIPE_STATE] =		{ 0x480000, 1, RMH_SSIZE_FIXED, 1 },
+[CMD_PIPE_SPL_COUNT] =		{ 0x49A000, 2, RMH_SSIZE_FIXED, 2 },
+[CMD_CAN_START_PIPE] =		{ 0x4b0000, 1, RMH_SSIZE_FIXED, 1 },
+[CMD_SIZE_HBUFFER] =		{ 0x4C0000, 1, RMH_SSIZE_FIXED, 1 },
+[CMD_START_STREAM] =		{ 0x80A000, 2, RMH_SSIZE_FIXED, 0 },
+[CMD_START_ONE_STREAM] =	{ 0x800000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_PAUSE_STREAM] =		{ 0x81A000, 2, RMH_SSIZE_FIXED, 0 },
+[CMD_PAUSE_ONE_STREAM] =	{ 0x810000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_STREAM_OUT_LEVEL_ADJUST] =	{ 0x828000, 2, RMH_SSIZE_FIXED, 0 },
+[CMD_STOP_STREAM] =		{ 0x830000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_FORMAT_STREAM_OUT] =	{ 0x868000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_FORMAT_STREAM_IN] =	{ 0x878800, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_GET_STREAM_STATE] =	{ 0x890001, 2, RMH_SSIZE_FIXED, 1 },
+[CMD_DROP_BYTES_AWAY] =		{ 0x8A8000, 2, RMH_SSIZE_FIXED, 0 },
+[CMD_GET_REMAINING_BYTES] =	{ 0x8D0800, 1, RMH_SSIZE_FIXED, 2 },
+[CMD_CONNECT_AUDIO] =		{ 0xC10000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_AUDIO_LEVEL_ADJUST] =	{ 0xC2A000, 3, RMH_SSIZE_FIXED, 0 },
+[CMD_AUDIO_VU_PIC_METER] =	{ 0xC3A003, 2, RMH_SSIZE_FIXED, 1 },
+[CMD_GET_AUDIO_LEVELS] =	{ 0xC4A000, 2, RMH_SSIZE_FIXED, 0 },
+[CMD_GET_NOTIFY_EVENT] =	{ 0x4D0000, 1, RMH_SSIZE_ARG, 0 },
+[CMD_INFO_NOTIFIED] =		{ 0x0B0000, 1, RMH_SSIZE_FIXED, 2 },
+[CMD_ACCESS_IO_FCT] =		{ 0x098000, 1, RMH_SSIZE_ARG, 0 },
+[CMD_STATUS_R_BUFFERS] =	{ 0x440000, 1, RMH_SSIZE_ARG, 0 },
+[CMD_UPDATE_R_BUFFERS] =	{ 0x848000, 4, RMH_SSIZE_FIXED, 0 },
+[CMD_LOAD_EFFECT_CONTEXT] =	{ 0x0c8000, 3, RMH_SSIZE_FIXED, 1 },
+[CMD_EFFECT_ONE_PIPE] =		{ 0x458000, 0, RMH_SSIZE_FIXED, 0 },
+[CMD_MODIFY_CLOCK] =		{ 0x0d0000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_STREAM1_OUT_SET_N_LEVELS] ={ 0x858000, 3, RMH_SSIZE_FIXED, 0 },
+[CMD_PURGE_STREAM_DCMDS] =	{ 0x8b8000, 3, RMH_SSIZE_FIXED, 0 },
+[CMD_NOTIFY_PIPE_TIME] =	{ 0x4e0000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_LOAD_EFFECT_CONTEXT_PACKET] = { 0x0c8000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_RELIC_R_BUFFER] =		{ 0x8e0800, 1, RMH_SSIZE_FIXED, 1 },
+[CMD_RESYNC_AUDIO_INPUTS] =	{ 0x0e0000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_NOTIFY_STREAM_TIME] =	{ 0x8f0000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_STREAM_SAMPLE_COUNT] =	{ 0x900000, 1, RMH_SSIZE_FIXED, 2 },
+[CMD_CONFIG_TIME_CODE] =	{ 0x050000, 2, RMH_SSIZE_FIXED, 0 },
+[CMD_GET_TIME_CODE] =		{ 0x060000, 1, RMH_SSIZE_FIXED, 5 },
+[CMD_MANAGE_SIGNAL] =		{ 0x0f0000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_PARAMETER_STREAM_OUT] =	{ 0x91A000, 3, RMH_SSIZE_FIXED, 0 },
+[CMD_READ_BOARD_FREQ] =		{ 0x030000, 1, RMH_SSIZE_FIXED, 2 },
+[CMD_GET_STREAM_LEVELS] =	{ 0x8c0000, 1, RMH_SSIZE_FIXED, 3 },
+[CMD_PURGE_PIPE_DCMDS] =	{ 0x4f8000, 3, RMH_SSIZE_FIXED, 0 },
+// [CMD_SET_STREAM_OUT_EFFECTS] =	{ 0x888000, 34, RMH_SSIZE_FIXED, 0 },
+// [CMD_GET_STREAM_OUT_EFFECTS] =	{ 0x928000, 2, RMH_SSIZE_FIXED, 32 },
+[CMD_CONNECT_MONITORING] =	{ 0xC00000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_STREAM2_OUT_SET_N_LEVELS] = { 0x938000, 3, RMH_SSIZE_FIXED, 0 },
+[CMD_CANCEL_R_BUFFERS] =	{ 0x948000, 4, RMH_SSIZE_FIXED, 0 },
+[CMD_NOTIFY_END_OF_BUFFER] =	{ 0x950000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_GET_STREAM_VU_METER] =	{ 0x95A000, 2, RMH_SSIZE_ARG, 0 },
+};
+
+/**
+ * vx_init_rmh - initialize the RMH instance
+ * @rmh: the rmh pointer to be initialized
+ * @cmd: the rmh command to be set
+ */
+void vx_init_rmh(struct vx_rmh *rmh, unsigned int cmd)
+{
+	snd_assert(cmd < CMD_LAST_INDEX, return);
+	rmh->LgCmd = vx_dsp_cmds[cmd].length;
+	rmh->LgStat = vx_dsp_cmds[cmd].st_length;
+	rmh->DspStat = vx_dsp_cmds[cmd].st_type;
+	rmh->Cmd[0] = vx_dsp_cmds[cmd].opcode;
+}
+
diff --git a/sound/drivers/vx/vx_cmd.h b/sound/drivers/vx/vx_cmd.h
new file mode 100644
index 0000000..a85248b
--- /dev/null
+++ b/sound/drivers/vx/vx_cmd.h
@@ -0,0 +1,246 @@
+/*
+ * Driver for Digigram VX soundcards
+ *
+ * Definitions of DSP commands
+ *
+ * Copyright (c) 2002 by 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
+ */
+
+#ifndef __VX_CMD_H
+#define __VX_CMD_H
+
+enum {
+	CMD_VERSION,
+	CMD_SUPPORTED,
+	CMD_TEST_IT,
+	CMD_SEND_IRQA,
+	CMD_IBL,
+	CMD_ASYNC,
+	CMD_RES_PIPE,
+	CMD_FREE_PIPE,
+	CMD_CONF_PIPE,
+	CMD_ABORT_CONF_PIPE,
+	CMD_PARAM_OUTPUT_PIPE,
+	CMD_STOP_PIPE,
+	CMD_PIPE_STATE,
+	CMD_PIPE_SPL_COUNT,
+	CMD_CAN_START_PIPE,
+	CMD_SIZE_HBUFFER,
+	CMD_START_STREAM,
+	CMD_START_ONE_STREAM,
+	CMD_PAUSE_STREAM,
+	CMD_PAUSE_ONE_STREAM,
+	CMD_STREAM_OUT_LEVEL_ADJUST,
+	CMD_STOP_STREAM,
+	CMD_FORMAT_STREAM_OUT,
+	CMD_FORMAT_STREAM_IN,
+	CMD_GET_STREAM_STATE,
+	CMD_DROP_BYTES_AWAY,
+	CMD_GET_REMAINING_BYTES,
+	CMD_CONNECT_AUDIO,
+	CMD_AUDIO_LEVEL_ADJUST,
+	CMD_AUDIO_VU_PIC_METER,
+	CMD_GET_AUDIO_LEVELS,
+	CMD_GET_NOTIFY_EVENT,
+	CMD_INFO_NOTIFIED,
+	CMD_ACCESS_IO_FCT,
+	CMD_STATUS_R_BUFFERS,
+	CMD_UPDATE_R_BUFFERS,
+	CMD_LOAD_EFFECT_CONTEXT,
+	CMD_EFFECT_ONE_PIPE,
+	CMD_MODIFY_CLOCK,
+	CMD_STREAM1_OUT_SET_N_LEVELS,
+	CMD_PURGE_STREAM_DCMDS,
+	CMD_NOTIFY_PIPE_TIME,
+	CMD_LOAD_EFFECT_CONTEXT_PACKET,
+	CMD_RELIC_R_BUFFER,
+	CMD_RESYNC_AUDIO_INPUTS,
+	CMD_NOTIFY_STREAM_TIME,
+	CMD_STREAM_SAMPLE_COUNT,
+	CMD_CONFIG_TIME_CODE,
+	CMD_GET_TIME_CODE,
+	CMD_MANAGE_SIGNAL,
+	CMD_PARAMETER_STREAM_OUT,
+	CMD_READ_BOARD_FREQ,
+	CMD_GET_STREAM_LEVELS,
+	CMD_PURGE_PIPE_DCMDS,
+	// CMD_SET_STREAM_OUT_EFFECTS,
+	// CMD_GET_STREAM_OUT_EFFECTS,
+	CMD_CONNECT_MONITORING,
+	CMD_STREAM2_OUT_SET_N_LEVELS,
+	CMD_CANCEL_R_BUFFERS,
+	CMD_NOTIFY_END_OF_BUFFER,
+	CMD_GET_STREAM_VU_METER,
+	CMD_LAST_INDEX
+};
+
+struct vx_cmd_info {
+	unsigned int opcode;	/* command word */
+	int length;		/* command length (in words) */
+	int st_type;		/* status type (RMH_SSIZE_XXX) */
+	int st_length;		/* fixed length */
+};
+
+/* Family and code op of some DSP requests. */
+#define CODE_OP_PIPE_TIME                       0x004e0000
+#define CODE_OP_START_STREAM                    0x00800000
+#define CODE_OP_PAUSE_STREAM                    0x00810000
+#define CODE_OP_OUT_STREAM_LEVEL                0x00820000
+#define CODE_OP_UPDATE_R_BUFFERS                0x00840000
+#define CODE_OP_OUT_STREAM1_LEVEL_CURVE         0x00850000
+#define CODE_OP_OUT_STREAM2_LEVEL_CURVE         0x00930000
+#define CODE_OP_OUT_STREAM_FORMAT               0x00860000
+#define CODE_OP_STREAM_TIME                     0x008f0000
+#define CODE_OP_OUT_STREAM_EXTRAPARAMETER       0x00910000
+#define CODE_OP_OUT_AUDIO_LEVEL                 0x00c20000
+
+#define NOTIFY_LAST_COMMAND     0x00400000
+
+/* Values for a user delay */
+#define DC_DIFFERED_DELAY       (1<<BIT_DIFFERED_COMMAND)
+#define DC_NOTIFY_DELAY         (1<<BIT_NOTIFIED_COMMAND)
+#define DC_HBUFFER_DELAY        (1<<BIT_TIME_RELATIVE_TO_BUFFER)
+#define DC_MULTIPLE_DELAY       (1<<BIT_RESERVED)
+#define DC_STREAM_TIME_DELAY    (1<<BIT_STREAM_TIME)
+#define DC_CANCELLED_DELAY      (1<<BIT_CANCELLED_COMMAND)
+
+/* Values for tiDelayed field in TIME_INFO structure,
+ * and for pbPause field in PLAY_BUFFER_INFO structure
+ */
+#define BIT_DIFFERED_COMMAND                0
+#define BIT_NOTIFIED_COMMAND                1
+#define BIT_TIME_RELATIVE_TO_BUFFER         2
+#define BIT_RESERVED                        3
+#define BIT_STREAM_TIME                     4
+#define BIT_CANCELLED_COMMAND               5
+
+/* Access to the "Size" field of the response of the CMD_GET_NOTIFY_EVENT request. */
+#define GET_NOTIFY_EVENT_SIZE_FIELD_MASK    0x000000ff
+
+/* DSP commands general masks */
+#define OPCODE_MASK                 0x00ff0000
+#define DSP_DIFFERED_COMMAND_MASK   0x0000C000
+
+/* Notifications (NOTIFY_INFO) */
+#define ALL_CMDS_NOTIFIED                   0x0000  // reserved
+#define START_STREAM_NOTIFIED               0x0001
+#define PAUSE_STREAM_NOTIFIED               0x0002
+#define OUT_STREAM_LEVEL_NOTIFIED           0x0003
+#define OUT_STREAM_PARAMETER_NOTIFIED       0x0004  // left for backward compatibility
+#define OUT_STREAM_FORMAT_NOTIFIED          0x0004
+#define PIPE_TIME_NOTIFIED                  0x0005
+#define OUT_AUDIO_LEVEL_NOTIFIED            0x0006
+#define OUT_STREAM_LEVEL_CURVE_NOTIFIED     0x0007
+#define STREAM_TIME_NOTIFIED                0x0008
+#define OUT_STREAM_EXTRAPARAMETER_NOTIFIED  0x0009
+#define UNKNOWN_COMMAND_NOTIFIED            0xffff
+
+/* Output pipe parameters setting */
+#define MASK_VALID_PIPE_MPEG_PARAM      0x000040
+#define MASK_VALID_PIPE_BACKWARD_PARAM  0x000020
+#define MASK_SET_PIPE_MPEG_PARAM        0x000002
+#define MASK_SET_PIPE_BACKWARD_PARAM    0x000001
+
+#define MASK_DSP_WORD           0x00FFFFFF
+#define MASK_ALL_STREAM         0x00FFFFFF
+#define MASK_DSP_WORD_LEVEL     0x000001FF
+#define MASK_FIRST_FIELD        0x0000001F
+#define FIELD_SIZE              5
+
+#define COMMAND_RECORD_MASK     0x000800
+
+/* PipeManagement definition bits (PIPE_DECL_INFO) */
+#define P_UNDERRUN_SKIP_SOUND_MASK				0x01
+#define P_PREPARE_FOR_MPEG3_MASK				0x02
+#define P_DO_NOT_RESET_ANALOG_LEVELS			0x04
+#define P_ALLOW_UNDER_ALLOCATION_MASK			0x08
+#define P_DATA_MODE_MASK				0x10
+#define P_ASIO_BUFFER_MANAGEMENT_MASK			0x20
+
+#define BIT_SKIP_SOUND					0x08	// bit 3
+#define BIT_DATA_MODE					0x10	// bit 4
+    
+/* Bits in the CMD_MODIFY_CLOCK request. */
+#define CMD_MODIFY_CLOCK_FD_BIT     0x00000001
+#define CMD_MODIFY_CLOCK_T_BIT      0x00000002
+#define CMD_MODIFY_CLOCK_S_BIT      0x00000004
+
+/* Access to the results of the CMD_GET_TIME_CODE RMH. */
+#define TIME_CODE_V_MASK            0x00800000
+#define TIME_CODE_N_MASK            0x00400000
+#define TIME_CODE_B_MASK            0x00200000
+#define TIME_CODE_W_MASK            0x00100000
+
+/* Values for the CMD_MANAGE_SIGNAL RMH. */
+#define MANAGE_SIGNAL_TIME_CODE     0x01
+#define MANAGE_SIGNAL_MIDI          0x02
+
+/* Values for the CMD_CONFIG_TIME_CODE RMH. */
+#define CONFIG_TIME_CODE_CANCEL     0x00001000
+    
+/* Mask to get only the effective time from the
+ * high word out of the 2 returned by the DSP
+ */
+#define PCX_TIME_HI_MASK        0x000fffff
+
+/* Values for setting a H-Buffer time */
+#define HBUFFER_TIME_HIGH       0x00200000
+#define HBUFFER_TIME_LOW        0x00000000
+
+#define NOTIFY_MASK_TIME_HIGH   0x00400000
+#define MULTIPLE_MASK_TIME_HIGH 0x00100000
+#define STREAM_MASK_TIME_HIGH   0x00800000
+
+
+/*
+ *
+ */
+void vx_init_rmh(struct vx_rmh *rmh, unsigned int cmd);
+
+/**
+ * vx_send_pipe_cmd_params - fill first command word for pipe commands
+ * @rmh: the rmh to be modified
+ * @is_capture: 0 = playback, 1 = capture operation
+ * @param1: first pipe-parameter
+ * @param2: second pipe-parameter
+ */
+static inline void vx_set_pipe_cmd_params(struct vx_rmh *rmh, int is_capture,
+					  int param1, int param2)
+{
+	if (is_capture)
+		rmh->Cmd[0] |= COMMAND_RECORD_MASK;
+	rmh->Cmd[0] |= (((u32)param1 & MASK_FIRST_FIELD) << FIELD_SIZE) & MASK_DSP_WORD;
+		
+	if (param2)
+		rmh->Cmd[0] |= ((u32)param2 & MASK_FIRST_FIELD) & MASK_DSP_WORD;
+	
+}
+
+/**
+ * vx_set_stream_cmd_params - fill first command word for stream commands
+ * @rmh: the rmh to be modified
+ * @is_capture: 0 = playback, 1 = capture operation
+ * @pipe: the pipe index (zero-based)
+ */
+static inline void vx_set_stream_cmd_params(struct vx_rmh *rmh, int is_capture, int pipe)
+{
+	if (is_capture)
+		rmh->Cmd[0] |= COMMAND_RECORD_MASK;
+	rmh->Cmd[0] |= (((u32)pipe & MASK_FIRST_FIELD) << FIELD_SIZE) & MASK_DSP_WORD;
+}
+
+#endif /* __VX_CMD_H */
diff --git a/sound/drivers/vx/vx_core.c b/sound/drivers/vx/vx_core.c
new file mode 100644
index 0000000..c6fa5af
--- /dev/null
+++ b/sound/drivers/vx/vx_core.c
@@ -0,0 +1,837 @@
+/*
+ * Driver for Digigram VX soundcards
+ *
+ * Hardware core part
+ *
+ * Copyright (c) 2002 by 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/delay.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/asoundef.h>
+#include <sound/info.h>
+#include <asm/io.h>
+#include <sound/vx_core.h>
+#include "vx_cmd.h"
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("Common routines for Digigram VX drivers");
+MODULE_LICENSE("GPL");
+
+
+/*
+ * snd_vx_delay - delay for the specified time
+ * @xmsec: the time to delay in msec
+ */
+void snd_vx_delay(vx_core_t *chip, int xmsec)
+{
+	if (! in_interrupt() && xmsec >= 1000 / HZ)
+		msleep(xmsec);
+	else
+		mdelay(xmsec);
+}
+
+/*
+ * vx_check_reg_bit - wait for the specified bit is set/reset on a register
+ * @reg: register to check
+ * @mask: bit mask
+ * @bit: resultant bit to be checked
+ * @time: time-out of loop in msec
+ *
+ * returns zero if a bit matches, or a negative error code.
+ */
+int snd_vx_check_reg_bit(vx_core_t *chip, int reg, int mask, int bit, int time)
+{
+	unsigned long end_time = jiffies + (time * HZ + 999) / 1000;
+#ifdef CONFIG_SND_DEBUG
+	static char *reg_names[VX_REG_MAX] = {
+		"ICR", "CVR", "ISR", "IVR", "RXH", "RXM", "RXL",
+		"DMA", "CDSP", "RFREQ", "RUER/V2", "DATA", "MEMIRQ",
+		"ACQ", "BIT0", "BIT1", "MIC0", "MIC1", "MIC2",
+		"MIC3", "INTCSR", "CNTRL", "GPIOC",
+		"LOFREQ", "HIFREQ", "CSUER", "RUER"
+	};
+#endif
+	do {
+		if ((snd_vx_inb(chip, reg) & mask) == bit)
+			return 0;
+		//snd_vx_delay(chip, 10);
+	} while (time_after_eq(end_time, jiffies));
+	snd_printd(KERN_DEBUG "vx_check_reg_bit: timeout, reg=%s, mask=0x%x, val=0x%x\n", reg_names[reg], mask, snd_vx_inb(chip, reg));
+	return -EIO;
+}
+
+/*
+ * vx_send_irq_dsp - set command irq bit
+ * @num: the requested IRQ type, IRQ_XXX
+ *
+ * this triggers the specified IRQ request
+ * returns 0 if successful, or a negative error code.
+ * 
+ */
+static int vx_send_irq_dsp(vx_core_t *chip, int num)
+{
+	int nirq;
+
+	/* wait for Hc = 0 */
+	if (snd_vx_check_reg_bit(chip, VX_CVR, CVR_HC, 0, 200) < 0)
+		return -EIO;
+
+	nirq = num;
+	if (vx_has_new_dsp(chip))
+		nirq += VXP_IRQ_OFFSET;
+	vx_outb(chip, CVR, (nirq >> 1) | CVR_HC);
+	return 0;
+}
+
+
+/*
+ * vx_reset_chk - reset CHK bit on ISR
+ *
+ * returns 0 if successful, or a negative error code.
+ */
+static int vx_reset_chk(vx_core_t *chip)
+{
+	/* Reset irq CHK */
+	if (vx_send_irq_dsp(chip, IRQ_RESET_CHK) < 0)
+		return -EIO;
+	/* Wait until CHK = 0 */
+	if (vx_check_isr(chip, ISR_CHK, 0, 200) < 0)
+		return -EIO;
+	return 0;
+}
+
+/*
+ * vx_transfer_end - terminate message transfer
+ * @cmd: IRQ message to send (IRQ_MESS_XXX_END)
+ *
+ * returns 0 if successful, or a negative error code.
+ * the error code can be VX-specific, retrieved via vx_get_error().
+ * NB: call with spinlock held!
+ */
+static int vx_transfer_end(vx_core_t *chip, int cmd)
+{
+	int err;
+
+	if ((err = vx_reset_chk(chip)) < 0)
+		return err;
+
+	/* irq MESS_READ/WRITE_END */
+	if ((err = vx_send_irq_dsp(chip, cmd)) < 0)
+		return err;
+
+	/* Wait CHK = 1 */
+	if ((err = vx_wait_isr_bit(chip, ISR_CHK)) < 0)
+		return err;
+
+	/* If error, Read RX */
+	if ((err = vx_inb(chip, ISR)) & ISR_ERR) {
+		if ((err = vx_wait_for_rx_full(chip)) < 0) {
+			snd_printd(KERN_DEBUG "transfer_end: error in rx_full\n");
+			return err;
+		}
+		err = vx_inb(chip, RXH) << 16;
+		err |= vx_inb(chip, RXM) << 8;
+		err |= vx_inb(chip, RXL);
+		snd_printd(KERN_DEBUG "transfer_end: error = 0x%x\n", err);
+		return -(VX_ERR_MASK | err);
+	}
+	return 0;
+}
+
+/*
+ * vx_read_status - return the status rmh
+ * @rmh: rmh record to store the status
+ *
+ * returns 0 if successful, or a negative error code.
+ * the error code can be VX-specific, retrieved via vx_get_error().
+ * NB: call with spinlock held!
+ */
+static int vx_read_status(vx_core_t *chip, struct vx_rmh *rmh)
+{
+	int i, err, val, size;
+
+	/* no read necessary? */
+	if (rmh->DspStat == RMH_SSIZE_FIXED && rmh->LgStat == 0)
+		return 0;
+
+	/* Wait for RX full (with timeout protection)
+	 * The first word of status is in RX
+	 */
+	err = vx_wait_for_rx_full(chip);
+	if (err < 0)
+		return err;
+
+	/* Read RX */
+	val = vx_inb(chip, RXH) << 16;
+	val |= vx_inb(chip, RXM) << 8;
+	val |= vx_inb(chip, RXL);
+
+	/* If status given by DSP, let's decode its size */
+	switch (rmh->DspStat) {
+	case RMH_SSIZE_ARG:
+		size = val & 0xff;
+		rmh->Stat[0] = val & 0xffff00;
+		rmh->LgStat = size + 1;
+		break;
+	case RMH_SSIZE_MASK:
+		/* Let's count the arg numbers from a mask */
+		rmh->Stat[0] = val;
+		size = 0;
+		while (val) {
+			if (val & 0x01)
+				size++;
+			val >>= 1;
+		}
+		rmh->LgStat = size + 1;
+		break;
+	default:
+		/* else retrieve the status length given by the driver */
+		size = rmh->LgStat;
+		rmh->Stat[0] = val;  /* Val is the status 1st word */
+		size--;              /* hence adjust remaining length */
+		break;
+        }
+
+	if (size < 1)
+		return 0;
+	snd_assert(size <= SIZE_MAX_STATUS, return -EINVAL);
+
+	for (i = 1; i <= size; i++) {
+		/* trigger an irq MESS_WRITE_NEXT */
+		err = vx_send_irq_dsp(chip, IRQ_MESS_WRITE_NEXT);
+		if (err < 0)
+			return err;
+		/* Wait for RX full (with timeout protection) */
+		err = vx_wait_for_rx_full(chip);
+		if (err < 0)
+			return err;
+		rmh->Stat[i] = vx_inb(chip, RXH) << 16;
+		rmh->Stat[i] |= vx_inb(chip, RXM) <<  8;
+		rmh->Stat[i] |= vx_inb(chip, RXL);
+	}
+
+	return vx_transfer_end(chip, IRQ_MESS_WRITE_END);
+}
+
+
+#define MASK_MORE_THAN_1_WORD_COMMAND   0x00008000
+#define MASK_1_WORD_COMMAND             0x00ff7fff
+
+/*
+ * vx_send_msg_nolock - send a DSP message and read back the status
+ * @rmh: the rmh record to send and receive
+ *
+ * returns 0 if successful, or a negative error code.
+ * the error code can be VX-specific, retrieved via vx_get_error().
+ * 
+ * this function doesn't call spinlock at all.
+ */
+int vx_send_msg_nolock(vx_core_t *chip, struct vx_rmh *rmh)
+{
+	int i, err;
+	
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return -EBUSY;
+
+	if ((err = vx_reset_chk(chip)) < 0) {
+		snd_printd(KERN_DEBUG "vx_send_msg: vx_reset_chk error\n");
+		return err;
+	}
+
+#if 0
+	printk(KERN_DEBUG "rmh: cmd = 0x%06x, length = %d, stype = %d\n",
+	       rmh->Cmd[0], rmh->LgCmd, rmh->DspStat);
+	if (rmh->LgCmd > 1) {
+		printk(KERN_DEBUG "  ");
+		for (i = 1; i < rmh->LgCmd; i++)
+			printk("0x%06x ", rmh->Cmd[i]);
+		printk("\n");
+	}
+#endif
+	/* Check bit M is set according to length of the command */
+	if (rmh->LgCmd > 1)
+		rmh->Cmd[0] |= MASK_MORE_THAN_1_WORD_COMMAND;
+	else
+		rmh->Cmd[0] &= MASK_1_WORD_COMMAND;
+
+	/* Wait for TX empty */
+	if ((err = vx_wait_isr_bit(chip, ISR_TX_EMPTY)) < 0) {
+		snd_printd(KERN_DEBUG "vx_send_msg: wait tx empty error\n");
+		return err;
+	}
+
+	/* Write Cmd[0] */
+	vx_outb(chip, TXH, (rmh->Cmd[0] >> 16) & 0xff);
+	vx_outb(chip, TXM, (rmh->Cmd[0] >> 8) & 0xff);
+	vx_outb(chip, TXL, rmh->Cmd[0] & 0xff);
+
+	/* Trigger irq MESSAGE */
+	if ((err = vx_send_irq_dsp(chip, IRQ_MESSAGE)) < 0) {
+		snd_printd(KERN_DEBUG "vx_send_msg: send IRQ_MESSAGE error\n");
+		return err;
+	}
+
+	/* Wait for CHK = 1 */
+	if ((err = vx_wait_isr_bit(chip, ISR_CHK)) < 0)
+		return err;
+
+	/* If error, get error value from RX */
+	if (vx_inb(chip, ISR) & ISR_ERR) {
+		if ((err = vx_wait_for_rx_full(chip)) < 0) {
+			snd_printd(KERN_DEBUG "vx_send_msg: rx_full read error\n");
+			return err;
+		}
+		err = vx_inb(chip, RXH) << 16;
+		err |= vx_inb(chip, RXM) << 8;
+		err |= vx_inb(chip, RXL);
+		snd_printd(KERN_DEBUG "msg got error = 0x%x at cmd[0]\n", err);
+		err = -(VX_ERR_MASK | err);
+		return err;
+	}
+
+	/* Send the other words */
+	if (rmh->LgCmd > 1) {
+		for (i = 1; i < rmh->LgCmd; i++) {
+			/* Wait for TX ready */
+			if ((err = vx_wait_isr_bit(chip, ISR_TX_READY)) < 0) {
+				snd_printd(KERN_DEBUG "vx_send_msg: tx_ready error\n");
+				return err;
+			}
+
+			/* Write Cmd[i] */
+			vx_outb(chip, TXH, (rmh->Cmd[i] >> 16) & 0xff);
+			vx_outb(chip, TXM, (rmh->Cmd[i] >> 8) & 0xff);
+			vx_outb(chip, TXL, rmh->Cmd[i] & 0xff);
+
+			/* Trigger irq MESS_READ_NEXT */
+			if ((err = vx_send_irq_dsp(chip, IRQ_MESS_READ_NEXT)) < 0) {
+				snd_printd(KERN_DEBUG "vx_send_msg: IRQ_READ_NEXT error\n");
+				return err;
+			}
+		}
+		/* Wait for TX empty */
+		if ((err = vx_wait_isr_bit(chip, ISR_TX_READY)) < 0) {
+			snd_printd(KERN_DEBUG "vx_send_msg: TX_READY error\n");
+			return err;
+		}
+		/* End of transfer */
+		err = vx_transfer_end(chip, IRQ_MESS_READ_END);
+		if (err < 0)
+			return err;
+	}
+
+	return vx_read_status(chip, rmh);
+}
+
+
+/*
+ * vx_send_msg - send a DSP message with spinlock
+ * @rmh: the rmh record to send and receive
+ *
+ * returns 0 if successful, or a negative error code.
+ * see vx_send_msg_nolock().
+ */
+int vx_send_msg(vx_core_t *chip, struct vx_rmh *rmh)
+{
+	unsigned long flags;
+	int err;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	err = vx_send_msg_nolock(chip, rmh);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return err;
+}
+
+
+/*
+ * vx_send_rih_nolock - send an RIH to xilinx
+ * @cmd: the command to send
+ *
+ * returns 0 if successful, or a negative error code.
+ * the error code can be VX-specific, retrieved via vx_get_error().
+ *
+ * this function doesn't call spinlock at all.
+ *
+ * unlike RMH, no command is sent to DSP.
+ */
+int vx_send_rih_nolock(vx_core_t *chip, int cmd)
+{
+	int err;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return -EBUSY;
+
+#if 0
+	printk(KERN_DEBUG "send_rih: cmd = 0x%x\n", cmd);
+#endif
+	if ((err = vx_reset_chk(chip)) < 0)
+		return err;
+	/* send the IRQ */
+	if ((err = vx_send_irq_dsp(chip, cmd)) < 0)
+		return err;
+	/* Wait CHK = 1 */
+	if ((err = vx_wait_isr_bit(chip, ISR_CHK)) < 0)
+		return err;
+	/* If error, read RX */
+	if (vx_inb(chip, ISR) & ISR_ERR) {
+		if ((err = vx_wait_for_rx_full(chip)) < 0)
+			return err;
+		err = vx_inb(chip, RXH) << 16;
+		err |= vx_inb(chip, RXM) << 8;
+		err |= vx_inb(chip, RXL);
+		return -(VX_ERR_MASK | err);
+	}
+	return 0;
+}
+
+
+/*
+ * vx_send_rih - send an RIH with spinlock
+ * @cmd: the command to send
+ *
+ * see vx_send_rih_nolock().
+ */
+int vx_send_rih(vx_core_t *chip, int cmd)
+{
+	unsigned long flags;
+	int err;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	err = vx_send_rih_nolock(chip, cmd);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return err;
+}
+
+#define END_OF_RESET_WAIT_TIME		500	/* us */
+
+/**
+ * snd_vx_boot_xilinx - boot up the xilinx interface
+ * @boot: the boot record to load
+ */
+int snd_vx_load_boot_image(vx_core_t *chip, const struct firmware *boot)
+{
+	unsigned int i;
+	int no_fillup = vx_has_new_dsp(chip);
+
+	/* check the length of boot image */
+	snd_assert(boot->size > 0, return -EINVAL);
+	snd_assert(boot->size % 3 == 0, return -EINVAL);
+#if 0
+	{
+		/* more strict check */
+		unsigned int c = ((u32)boot->data[0] << 16) | ((u32)boot->data[1] << 8) | boot->data[2];
+		snd_assert(boot->size == (c + 2) * 3, return -EINVAL);
+	}
+#endif
+
+	/* reset dsp */
+	vx_reset_dsp(chip);
+	
+	udelay(END_OF_RESET_WAIT_TIME); /* another wait? */
+
+	/* download boot strap */
+	for (i = 0; i < 0x600; i += 3) {
+		if (i >= boot->size) {
+			if (no_fillup)
+				break;
+			if (vx_wait_isr_bit(chip, ISR_TX_EMPTY) < 0) {
+				snd_printk(KERN_ERR "dsp boot failed at %d\n", i);
+				return -EIO;
+			}
+			vx_outb(chip, TXH, 0);
+			vx_outb(chip, TXM, 0);
+			vx_outb(chip, TXL, 0);
+		} else {
+			unsigned char *image = boot->data + i;
+			if (vx_wait_isr_bit(chip, ISR_TX_EMPTY) < 0) {
+				snd_printk(KERN_ERR "dsp boot failed at %d\n", i);
+				return -EIO;
+			}
+			vx_outb(chip, TXH, image[0]);
+			vx_outb(chip, TXM, image[1]);
+			vx_outb(chip, TXL, image[2]);
+		}
+	}
+	return 0;
+}
+
+/*
+ * vx_test_irq_src - query the source of interrupts
+ *
+ * called from irq handler only
+ */
+static int vx_test_irq_src(vx_core_t *chip, unsigned int *ret)
+{
+	int err;
+
+	vx_init_rmh(&chip->irq_rmh, CMD_TEST_IT);
+	spin_lock(&chip->lock);
+	err = vx_send_msg_nolock(chip, &chip->irq_rmh);
+	if (err < 0)
+		*ret = 0;
+	else
+		*ret = chip->irq_rmh.Stat[0];
+	spin_unlock(&chip->lock);
+	return err;
+}
+
+
+/*
+ * vx_interrupt - soft irq handler
+ */
+static void vx_interrupt(unsigned long private_data)
+{
+	vx_core_t *chip = (vx_core_t *) private_data;
+	unsigned int events;
+		
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return;
+
+	if (vx_test_irq_src(chip, &events) < 0)
+		return;
+    
+#if 0
+	if (events & 0x000800)
+		printk(KERN_ERR "DSP Stream underrun ! IRQ events = 0x%x\n", events);
+#endif
+	// printk(KERN_DEBUG "IRQ events = 0x%x\n", events);
+
+	/* We must prevent any application using this DSP
+	 * and block any further request until the application
+	 * either unregisters or reloads the DSP
+	 */
+	if (events & FATAL_DSP_ERROR) {
+		snd_printk(KERN_ERR "vx_core: fatal DSP error!!\n");
+		return;
+	}
+
+	/* The start on time code conditions are filled (ie the time code
+	 * received by the board is equal to one of those given to it).
+	 */
+	if (events & TIME_CODE_EVENT_PENDING)
+		; /* so far, nothing to do yet */
+
+	/* The frequency has changed on the board (UER mode). */
+	if (events & FREQUENCY_CHANGE_EVENT_PENDING)
+		vx_change_frequency(chip);
+
+	/* update the pcm streams */
+	vx_pcm_update_intr(chip, events);
+}
+
+
+/**
+ * snd_vx_irq_handler - interrupt handler
+ */
+irqreturn_t snd_vx_irq_handler(int irq, void *dev, struct pt_regs *regs)
+{
+	vx_core_t *chip = dev;
+
+	if (! (chip->chip_status & VX_STAT_CHIP_INIT) ||
+	    (chip->chip_status & VX_STAT_IS_STALE))
+		return IRQ_NONE;
+	if (! vx_test_and_ack(chip))
+		tasklet_hi_schedule(&chip->tq);
+	return IRQ_HANDLED;
+}
+
+
+/*
+ */
+static void vx_reset_board(vx_core_t *chip, int cold_reset)
+{
+	snd_assert(chip->ops->reset_board, return);
+
+	/* current source, later sync'ed with target */
+	chip->audio_source = VX_AUDIO_SRC_LINE;
+	if (cold_reset) {
+		chip->audio_source_target = chip->audio_source;
+		chip->clock_source = INTERNAL_QUARTZ;
+		chip->clock_mode = VX_CLOCK_MODE_AUTO;
+		chip->freq = 48000;
+		chip->uer_detected = VX_UER_MODE_NOT_PRESENT;
+		chip->uer_bits = SNDRV_PCM_DEFAULT_CON_SPDIF;
+	}
+
+	chip->ops->reset_board(chip, cold_reset);
+
+	vx_reset_codec(chip, cold_reset);
+
+	vx_set_internal_clock(chip, chip->freq);
+
+	/* Reset the DSP */
+	vx_reset_dsp(chip);
+
+	if (vx_is_pcmcia(chip)) {
+		/* Acknowledge any pending IRQ and reset the MEMIRQ flag. */
+		vx_test_and_ack(chip);
+		vx_validate_irq(chip, 1);
+	}
+
+	/* init CBits */
+	vx_set_iec958_status(chip, chip->uer_bits);
+}
+
+
+/*
+ * proc interface
+ */
+
+static void vx_proc_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+	vx_core_t *chip = entry->private_data;
+	static char *audio_src_vxp[] = { "Line", "Mic", "Digital" };
+	static char *audio_src_vx2[] = { "Analog", "Analog", "Digital" };
+	static char *clock_mode[] = { "Auto", "Internal", "External" };
+	static char *clock_src[] = { "Internal", "External" };
+	static char *uer_type[] = { "Consumer", "Professional", "Not Present" };
+	
+	snd_iprintf(buffer, "%s\n", chip->card->longname);
+	snd_iprintf(buffer, "Xilinx Firmware: %s\n",
+		    chip->chip_status & VX_STAT_XILINX_LOADED ? "Loaded" : "No");
+	snd_iprintf(buffer, "Device Initialized: %s\n",
+		    chip->chip_status & VX_STAT_DEVICE_INIT ? "Yes" : "No");
+	snd_iprintf(buffer, "DSP audio info:");
+	if (chip->audio_info & VX_AUDIO_INFO_REAL_TIME)
+		snd_iprintf(buffer, " realtime");
+	if (chip->audio_info & VX_AUDIO_INFO_OFFLINE)
+		snd_iprintf(buffer, " offline");
+	if (chip->audio_info & VX_AUDIO_INFO_MPEG1)
+		snd_iprintf(buffer, " mpeg1");
+	if (chip->audio_info & VX_AUDIO_INFO_MPEG2)
+		snd_iprintf(buffer, " mpeg2");
+	if (chip->audio_info & VX_AUDIO_INFO_LINEAR_8)
+		snd_iprintf(buffer, " linear8");
+	if (chip->audio_info & VX_AUDIO_INFO_LINEAR_16)
+		snd_iprintf(buffer, " linear16");
+	if (chip->audio_info & VX_AUDIO_INFO_LINEAR_24)
+		snd_iprintf(buffer, " linear24");
+	snd_iprintf(buffer, "\n");
+	snd_iprintf(buffer, "Input Source: %s\n", vx_is_pcmcia(chip) ?
+		    audio_src_vxp[chip->audio_source] :
+		    audio_src_vx2[chip->audio_source]);
+	snd_iprintf(buffer, "Clock Mode: %s\n", clock_mode[chip->clock_mode]);
+	snd_iprintf(buffer, "Clock Source: %s\n", clock_src[chip->clock_source]);
+	snd_iprintf(buffer, "Frequency: %d\n", chip->freq);
+	snd_iprintf(buffer, "Detected Frequency: %d\n", chip->freq_detected);
+	snd_iprintf(buffer, "Detected UER type: %s\n", uer_type[chip->uer_detected]);
+	snd_iprintf(buffer, "Min/Max/Cur IBL: %d/%d/%d (granularity=%d)\n",
+		    chip->ibl.min_size, chip->ibl.max_size, chip->ibl.size,
+		    chip->ibl.granularity);
+}
+
+static void vx_proc_init(vx_core_t *chip)
+{
+	snd_info_entry_t *entry;
+
+	if (! snd_card_proc_new(chip->card, "vx-status", &entry))
+		snd_info_set_text_ops(entry, chip, 1024, vx_proc_read);
+}
+
+
+/**
+ * snd_vx_dsp_boot - load the DSP boot
+ */
+int snd_vx_dsp_boot(vx_core_t *chip, const struct firmware *boot)
+{
+	int err;
+	int cold_reset = !(chip->chip_status & VX_STAT_DEVICE_INIT);
+
+	vx_reset_board(chip, cold_reset);
+	vx_validate_irq(chip, 0);
+
+	if ((err = snd_vx_load_boot_image(chip, boot)) < 0)
+		return err;
+	snd_vx_delay(chip, 10);
+
+	return 0;
+}
+
+/**
+ * snd_vx_dsp_load - load the DSP image
+ */
+int snd_vx_dsp_load(vx_core_t *chip, const struct firmware *dsp)
+{
+	unsigned int i;
+	int err;
+	unsigned int csum = 0;
+	unsigned char *image, *cptr;
+
+	snd_assert(dsp->size % 3 == 0, return -EINVAL);
+
+	vx_toggle_dac_mute(chip, 1);
+
+	/* Transfert data buffer from PC to DSP */
+	for (i = 0; i < dsp->size; i += 3) {
+		image = dsp->data + i;
+		/* Wait DSP ready for a new read */
+		if ((err = vx_wait_isr_bit(chip, ISR_TX_EMPTY)) < 0) {
+			printk("dsp loading error at position %d\n", i);
+			return err;
+		}
+		cptr = image;
+		csum ^= *cptr;
+		csum = (csum >> 24) | (csum << 8);
+		vx_outb(chip, TXH, *cptr++);
+		csum ^= *cptr;
+		csum = (csum >> 24) | (csum << 8);
+		vx_outb(chip, TXM, *cptr++);
+		csum ^= *cptr;
+		csum = (csum >> 24) | (csum << 8);
+		vx_outb(chip, TXL, *cptr++);
+	}
+	snd_printdd(KERN_DEBUG "checksum = 0x%08x\n", csum);
+
+	snd_vx_delay(chip, 200);
+
+	if ((err = vx_wait_isr_bit(chip, ISR_CHK)) < 0)
+		return err;
+
+	vx_toggle_dac_mute(chip, 0);
+
+	vx_test_and_ack(chip);
+	vx_validate_irq(chip, 1);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ * suspend
+ */
+static int snd_vx_suspend(snd_card_t *card, pm_message_t state)
+{
+	vx_core_t *chip = card->pm_private_data;
+	unsigned int i;
+
+	snd_assert(chip, return -EINVAL);
+
+	chip->chip_status |= VX_STAT_IN_SUSPEND;
+	for (i = 0; i < chip->hw->num_codecs; i++)
+		snd_pcm_suspend_all(chip->pcm[i]);
+
+	return 0;
+}
+
+/*
+ * resume
+ */
+static int snd_vx_resume(snd_card_t *card)
+{
+	vx_core_t *chip = card->pm_private_data;
+	int i, err;
+
+	snd_assert(chip, return -EINVAL);
+
+	chip->chip_status &= ~VX_STAT_CHIP_INIT;
+
+	for (i = 0; i < 4; i++) {
+		if (! chip->firmware[i])
+			continue;
+		err = chip->ops->load_dsp(chip, i, chip->firmware[i]);
+		if (err < 0) {
+			snd_printk(KERN_ERR "vx: firmware resume error at DSP %d\n", i);
+			return -EIO;
+		}
+	}
+
+	chip->chip_status |= VX_STAT_CHIP_INIT;
+	chip->chip_status &= ~VX_STAT_IN_SUSPEND;
+
+	return 0;
+}
+
+#endif
+
+/**
+ * snd_vx_create - constructor for vx_core_t
+ * @hw: hardware specific record
+ *
+ * this function allocates the instance and prepare for the hardware
+ * initialization.
+ *
+ * return the instance pointer if successful, NULL in error.
+ */
+vx_core_t *snd_vx_create(snd_card_t *card, struct snd_vx_hardware *hw,
+			 struct snd_vx_ops *ops,
+			 int extra_size)
+{
+	vx_core_t *chip;
+
+	snd_assert(card && hw && ops, return NULL);
+
+	chip = kcalloc(1, sizeof(*chip) + extra_size, GFP_KERNEL);
+	if (! chip) {
+		snd_printk(KERN_ERR "vx_core: no memory\n");
+		return NULL;
+	}
+	spin_lock_init(&chip->lock);
+	spin_lock_init(&chip->irq_lock);
+	chip->irq = -1;
+	chip->hw = hw;
+	chip->type = hw->type;
+	chip->ops = ops;
+	tasklet_init(&chip->tq, vx_interrupt, (unsigned long)chip);
+	init_MUTEX(&chip->mixer_mutex);
+
+	chip->card = card;
+	card->private_data = chip;
+	strcpy(card->driver, hw->name);
+	sprintf(card->shortname, "Digigram %s", hw->name);
+
+	snd_card_set_pm_callback(card, snd_vx_suspend, snd_vx_resume, chip);
+
+	vx_proc_init(chip);
+
+	return chip;
+}
+
+/*
+ * module entries
+ */
+static int __init alsa_vx_core_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_vx_core_exit(void)
+{
+}
+
+module_init(alsa_vx_core_init)
+module_exit(alsa_vx_core_exit)
+
+/*
+ * exports
+ */
+EXPORT_SYMBOL(snd_vx_check_reg_bit);
+EXPORT_SYMBOL(snd_vx_create);
+EXPORT_SYMBOL(snd_vx_setup_firmware);
+EXPORT_SYMBOL(snd_vx_free_firmware);
+EXPORT_SYMBOL(snd_vx_irq_handler);
+EXPORT_SYMBOL(snd_vx_delay);
+EXPORT_SYMBOL(snd_vx_dsp_boot);
+EXPORT_SYMBOL(snd_vx_dsp_load);
+EXPORT_SYMBOL(snd_vx_load_boot_image);
diff --git a/sound/drivers/vx/vx_hwdep.c b/sound/drivers/vx/vx_hwdep.c
new file mode 100644
index 0000000..9a3dc3c
--- /dev/null
+++ b/sound/drivers/vx/vx_hwdep.c
@@ -0,0 +1,249 @@
+/*
+ * Driver for Digigram VX soundcards
+ *
+ * DSP firmware management
+ *
+ * Copyright (c) 2002 by 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/device.h>
+#include <linux/firmware.h>
+#include <sound/core.h>
+#include <sound/hwdep.h>
+#include <sound/vx_core.h>
+
+#ifdef SND_VX_FW_LOADER
+
+int snd_vx_setup_firmware(vx_core_t *chip)
+{
+	static char *fw_files[VX_TYPE_NUMS][4] = {
+		[VX_TYPE_BOARD] = {
+			NULL, "x1_1_vx2.xlx", "bd56002.boot", "l_1_vx2.d56",
+		},
+		[VX_TYPE_V2] = {
+			NULL, "x1_2_v22.xlx", "bd563v2.boot", "l_1_v22.d56",
+		},
+		[VX_TYPE_MIC] = {
+			NULL, "x1_2_v22.xlx", "bd563v2.boot", "l_1_v22.d56",
+		},
+		[VX_TYPE_VXPOCKET] = {
+			"bx_1_vxp.b56", "x1_1_vxp.xlx", "bd563s3.boot", "l_1_vxp.d56"
+		},
+		[VX_TYPE_VXP440] = {
+			"bx_1_vp4.b56", "x1_1_vp4.xlx", "bd563s3.boot", "l_1_vp4.d56"
+		},
+	};
+
+	int i, err;
+
+	for (i = 0; i < 4; i++) {
+		char path[32];
+		const struct firmware *fw;
+		if (! fw_files[chip->type][i])
+			continue;
+		sprintf(path, "vx/%s", fw_files[chip->type][i]);
+		if (request_firmware(&fw, path, chip->dev)) {
+			snd_printk(KERN_ERR "vx: can't load firmware %s\n", path);
+			return -ENOENT;
+		}
+		err = chip->ops->load_dsp(chip, i, fw);
+		if (err < 0) {
+			release_firmware(fw);
+			return err;
+		}
+		if (i == 1)
+			chip->chip_status |= VX_STAT_XILINX_LOADED;
+#ifdef CONFIG_PM
+		chip->firmware[i] = fw;
+#else
+		release_firmware(fw);
+#endif
+	}
+
+	/* ok, we reached to the last one */
+	/* create the devices if not built yet */
+	if ((err = snd_vx_pcm_new(chip)) < 0)
+		return err;
+
+	if ((err = snd_vx_mixer_new(chip)) < 0)
+		return err;
+
+	if (chip->ops->add_controls)
+		if ((err = chip->ops->add_controls(chip)) < 0)
+			return err;
+
+	chip->chip_status |= VX_STAT_DEVICE_INIT;
+	chip->chip_status |= VX_STAT_CHIP_INIT;
+
+	return snd_card_register(chip->card);
+}
+
+/* exported */
+void snd_vx_free_firmware(vx_core_t *chip)
+{
+#ifdef CONFIG_PM
+	int i;
+	for (i = 0; i < 4; i++)
+		release_firmware(chip->firmware[i]);
+#endif
+}
+
+#else /* old style firmware loading */
+
+static int vx_hwdep_open(snd_hwdep_t *hw, struct file *file)
+{
+	return 0;
+}
+
+static int vx_hwdep_release(snd_hwdep_t *hw, struct file *file)
+{
+	return 0;
+}
+
+static int vx_hwdep_dsp_status(snd_hwdep_t *hw, snd_hwdep_dsp_status_t *info)
+{
+	static char *type_ids[VX_TYPE_NUMS] = {
+		[VX_TYPE_BOARD] = "vxboard",
+		[VX_TYPE_V2] = "vx222",
+		[VX_TYPE_MIC] = "vx222",
+		[VX_TYPE_VXPOCKET] = "vxpocket",
+		[VX_TYPE_VXP440] = "vxp440",
+	};
+	vx_core_t *vx = hw->private_data;
+
+	snd_assert(type_ids[vx->type], return -EINVAL);
+	strcpy(info->id, type_ids[vx->type]);
+	if (vx_is_pcmcia(vx))
+		info->num_dsps = 4;
+	else
+		info->num_dsps = 3;
+	if (vx->chip_status & VX_STAT_CHIP_INIT)
+		info->chip_ready = 1;
+	info->version = VX_DRIVER_VERSION;
+	return 0;
+}
+
+static void free_fw(const struct firmware *fw)
+{
+	if (fw) {
+		vfree(fw->data);
+		kfree(fw);
+	}
+}
+
+static int vx_hwdep_dsp_load(snd_hwdep_t *hw, snd_hwdep_dsp_image_t *dsp)
+{
+	vx_core_t *vx = hw->private_data;
+	int index, err;
+	struct firmware *fw;
+
+	snd_assert(vx->ops->load_dsp, return -ENXIO);
+
+	fw = kmalloc(sizeof(*fw), GFP_KERNEL);
+	if (! fw) {
+		snd_printk(KERN_ERR "cannot allocate firmware\n");
+		return -ENOMEM;
+	}
+	fw->size = dsp->length;
+	fw->data = vmalloc(fw->size);
+	if (! fw->data) {
+		snd_printk(KERN_ERR "cannot allocate firmware image (length=%d)\n",
+			   (int)fw->size);
+		kfree(fw);
+		return -ENOMEM;
+	}
+	if (copy_from_user(fw->data, dsp->image, dsp->length)) {
+		free_fw(fw);
+		return -EFAULT;
+	}
+
+	index = dsp->index;
+	if (! vx_is_pcmcia(vx))
+		index++;
+	err = vx->ops->load_dsp(vx, index, fw);
+	if (err < 0) {
+		free_fw(fw);
+		return err;
+	}
+#ifdef CONFIG_PM
+	vx->firmware[index] = fw;
+#else
+	free_fw(fw);
+#endif
+
+	if (index == 1)
+		vx->chip_status |= VX_STAT_XILINX_LOADED;
+	if (index < 3)
+		return 0;
+
+	/* ok, we reached to the last one */
+	/* create the devices if not built yet */
+	if (! (vx->chip_status & VX_STAT_DEVICE_INIT)) {
+		if ((err = snd_vx_pcm_new(vx)) < 0)
+			return err;
+
+		if ((err = snd_vx_mixer_new(vx)) < 0)
+			return err;
+
+		if (vx->ops->add_controls)
+			if ((err = vx->ops->add_controls(vx)) < 0)
+				return err;
+
+		if ((err = snd_card_register(vx->card)) < 0)
+			return err;
+
+		vx->chip_status |= VX_STAT_DEVICE_INIT;
+	}
+	vx->chip_status |= VX_STAT_CHIP_INIT;
+	return 0;
+}
+
+
+/* exported */
+int snd_vx_setup_firmware(vx_core_t *chip)
+{
+	int err;
+	snd_hwdep_t *hw;
+
+	if ((err = snd_hwdep_new(chip->card, SND_VX_HWDEP_ID, 0, &hw)) < 0)
+		return err;
+
+	hw->iface = SNDRV_HWDEP_IFACE_VX;
+	hw->private_data = chip;
+	hw->ops.open = vx_hwdep_open;
+	hw->ops.release = vx_hwdep_release;
+	hw->ops.dsp_status = vx_hwdep_dsp_status;
+	hw->ops.dsp_load = vx_hwdep_dsp_load;
+	hw->exclusive = 1;
+	sprintf(hw->name, "VX Loader (%s)", chip->card->driver);
+	chip->hwdep = hw;
+
+	return snd_card_register(chip->card);
+}
+
+/* exported */
+void snd_vx_free_firmware(vx_core_t *chip)
+{
+#ifdef CONFIG_PM
+	int i;
+	for (i = 0; i < 4; i++)
+		free_fw(chip->firmware[i]);
+#endif
+}
+
+#endif /* SND_VX_FW_LOADER */
diff --git a/sound/drivers/vx/vx_mixer.c b/sound/drivers/vx/vx_mixer.c
new file mode 100644
index 0000000..f00c888
--- /dev/null
+++ b/sound/drivers/vx/vx_mixer.c
@@ -0,0 +1,1000 @@
+/*
+ * Driver for Digigram VX soundcards
+ *
+ * Common mixer part
+ *
+ * Copyright (c) 2002 by 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 <sound/core.h>
+#include <sound/control.h>
+#include <sound/vx_core.h>
+#include "vx_cmd.h"
+
+
+/*
+ * write a codec data (24bit)
+ */
+static void vx_write_codec_reg(vx_core_t *chip, int codec, unsigned int data)
+{
+	unsigned long flags;
+
+	snd_assert(chip->ops->write_codec, return);
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	chip->ops->write_codec(chip, codec, data);
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+/*
+ * Data type used to access the Codec
+ */
+typedef union {
+	u32 l;
+#ifdef SNDRV_BIG_ENDIAN
+	struct w {
+		u16 h;
+		u16 l;
+	} w;
+	struct b {
+		u8 hh;
+		u8 mh;
+		u8 ml;
+		u8 ll;
+	} b;
+#else /* LITTLE_ENDIAN */
+	struct w {
+		u16 l;
+		u16 h;
+	} w;
+	struct b {
+		u8 ll;
+		u8 ml;
+		u8 mh;
+		u8 hh;
+	} b;
+#endif
+} vx_codec_data_t;
+
+#define SET_CDC_DATA_SEL(di,s)          ((di).b.mh = (u8) (s))
+#define SET_CDC_DATA_REG(di,r)          ((di).b.ml = (u8) (r))
+#define SET_CDC_DATA_VAL(di,d)          ((di).b.ll = (u8) (d))
+#define SET_CDC_DATA_INIT(di)           ((di).l = 0L, SET_CDC_DATA_SEL(di,XX_CODEC_SELECTOR))
+
+/*
+ * set up codec register and write the value
+ * @codec: the codec id, 0 or 1
+ * @reg: register index
+ * @val: data value
+ */
+static void vx_set_codec_reg(vx_core_t *chip, int codec, int reg, int val)
+{
+	vx_codec_data_t data;
+	/* DAC control register */
+	SET_CDC_DATA_INIT(data);
+	SET_CDC_DATA_REG(data, reg);
+	SET_CDC_DATA_VAL(data, val);
+	vx_write_codec_reg(chip, codec, data.l);
+}
+
+
+/*
+ * vx_set_analog_output_level - set the output attenuation level
+ * @codec: the output codec, 0 or 1.  (1 for VXP440 only)
+ * @left: left output level, 0 = mute
+ * @right: right output level
+ */
+static void vx_set_analog_output_level(vx_core_t *chip, int codec, int left, int right)
+{
+	left  = chip->hw->output_level_max - left;
+	right = chip->hw->output_level_max - right;
+
+	if (chip->ops->akm_write) {
+		chip->ops->akm_write(chip, XX_CODEC_LEVEL_LEFT_REGISTER, left);
+		chip->ops->akm_write(chip, XX_CODEC_LEVEL_RIGHT_REGISTER, right);
+	} else {
+		/* convert to attenuation level: 0 = 0dB (max), 0xe3 = -113.5 dB (min) */
+		vx_set_codec_reg(chip, codec, XX_CODEC_LEVEL_LEFT_REGISTER, left);
+		vx_set_codec_reg(chip, codec, XX_CODEC_LEVEL_RIGHT_REGISTER, right);
+	}
+}
+
+
+/*
+ * vx_toggle_dac_mute -  mute/unmute DAC
+ * @mute: 0 = unmute, 1 = mute
+ */
+
+#define DAC_ATTEN_MIN	0x08
+#define DAC_ATTEN_MAX	0x38
+
+void vx_toggle_dac_mute(vx_core_t *chip, int mute)
+{
+	unsigned int i;
+	for (i = 0; i < chip->hw->num_codecs; i++) {
+		if (chip->ops->akm_write)
+			chip->ops->akm_write(chip, XX_CODEC_DAC_CONTROL_REGISTER, mute); /* XXX */
+		else
+			vx_set_codec_reg(chip, i, XX_CODEC_DAC_CONTROL_REGISTER,
+					 mute ? DAC_ATTEN_MAX : DAC_ATTEN_MIN);
+	}
+}
+
+/*
+ * vx_reset_codec - reset and initialize the codecs
+ */
+void vx_reset_codec(vx_core_t *chip, int cold_reset)
+{
+	unsigned int i;
+	int port = chip->type >= VX_TYPE_VXPOCKET ? 0x75 : 0x65;
+
+	chip->ops->reset_codec(chip);
+
+	/* AKM codecs should be initialized in reset_codec callback */
+	if (! chip->ops->akm_write) {
+		/* initialize old codecs */
+		for (i = 0; i < chip->hw->num_codecs; i++) {
+			/* DAC control register (change level when zero crossing + mute) */
+			vx_set_codec_reg(chip, i, XX_CODEC_DAC_CONTROL_REGISTER, DAC_ATTEN_MAX);
+			/* ADC control register */
+			vx_set_codec_reg(chip, i, XX_CODEC_ADC_CONTROL_REGISTER, 0x00);
+			/* Port mode register */
+			vx_set_codec_reg(chip, i, XX_CODEC_PORT_MODE_REGISTER, port);
+			/* Clock control register */
+			vx_set_codec_reg(chip, i, XX_CODEC_CLOCK_CONTROL_REGISTER, 0x00);
+		}
+	}
+
+	/* mute analog output */
+	for (i = 0; i < chip->hw->num_codecs; i++) {
+		chip->output_level[i][0] = 0;
+		chip->output_level[i][1] = 0;
+		vx_set_analog_output_level(chip, i, 0, 0);
+	}
+}
+
+/*
+ * change the audio input source
+ * @src: the target source (VX_AUDIO_SRC_XXX)
+ */
+static void vx_change_audio_source(vx_core_t *chip, int src)
+{
+	unsigned long flags;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	chip->ops->change_audio_source(chip, src);
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+
+/*
+ * change the audio source if necessary and possible
+ * returns 1 if the source is actually changed.
+ */
+int vx_sync_audio_source(vx_core_t *chip)
+{
+	if (chip->audio_source_target == chip->audio_source ||
+	    chip->pcm_running)
+		return 0;
+	vx_change_audio_source(chip, chip->audio_source_target);
+	chip->audio_source = chip->audio_source_target;
+	return 1;
+}
+
+
+/*
+ * audio level, mute, monitoring
+ */
+struct vx_audio_level {
+	unsigned int has_level: 1;
+	unsigned int has_monitor_level: 1;
+	unsigned int has_mute: 1;
+	unsigned int has_monitor_mute: 1;
+	unsigned int mute;
+	unsigned int monitor_mute;
+	short level;
+	short monitor_level;
+};
+
+static int vx_adjust_audio_level(vx_core_t *chip, int audio, int capture,
+				 struct vx_audio_level *info)
+{
+	struct vx_rmh rmh;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return -EBUSY;
+
+        vx_init_rmh(&rmh, CMD_AUDIO_LEVEL_ADJUST);
+	if (capture)
+		rmh.Cmd[0] |= COMMAND_RECORD_MASK;
+	/* Add Audio IO mask */
+	rmh.Cmd[1] = 1 << audio;
+	rmh.Cmd[2] = 0;
+	if (info->has_level) {
+		rmh.Cmd[0] |=  VALID_AUDIO_IO_DIGITAL_LEVEL;
+		rmh.Cmd[2] |= info->level;
+        }
+	if (info->has_monitor_level) {
+		rmh.Cmd[0] |=  VALID_AUDIO_IO_MONITORING_LEVEL;
+		rmh.Cmd[2] |= ((unsigned int)info->monitor_level << 10);
+        }
+	if (info->has_mute) { 
+		rmh.Cmd[0] |= VALID_AUDIO_IO_MUTE_LEVEL;
+		if (info->mute)
+			rmh.Cmd[2] |= AUDIO_IO_HAS_MUTE_LEVEL;
+	}
+	if (info->has_monitor_mute) {
+		/* validate flag for M2 at least to unmute it */ 
+		rmh.Cmd[0] |=  VALID_AUDIO_IO_MUTE_MONITORING_1 | VALID_AUDIO_IO_MUTE_MONITORING_2;
+		if (info->monitor_mute)
+			rmh.Cmd[2] |= AUDIO_IO_HAS_MUTE_MONITORING_1;
+	}
+
+	return vx_send_msg(chip, &rmh);
+}
+
+    
+#if 0 // not used
+static int vx_read_audio_level(vx_core_t *chip, int audio, int capture,
+			       struct vx_audio_level *info)
+{
+	int err;
+	struct vx_rmh rmh;
+
+	memset(info, 0, sizeof(*info));
+        vx_init_rmh(&rmh, CMD_GET_AUDIO_LEVELS);
+	if (capture)
+		rmh.Cmd[0] |= COMMAND_RECORD_MASK;
+	/* Add Audio IO mask */
+	rmh.Cmd[1] = 1 << audio;
+	err = vx_send_msg(chip, &rmh);
+	if (err < 0)
+		return err;
+	info.level = rmh.Stat[0] & MASK_DSP_WORD_LEVEL;
+	info.monitor_level = (rmh.Stat[0] >> 10) & MASK_DSP_WORD_LEVEL;
+	info.mute = (rmh.Stat[i] & AUDIO_IO_HAS_MUTE_LEVEL) ? 1 : 0;
+	info.monitor_mute = (rmh.Stat[i] & AUDIO_IO_HAS_MUTE_MONITORING_1) ? 1 : 0;
+	return 0;
+}
+#endif // not used
+
+/*
+ * set the monitoring level and mute state of the given audio
+ * no more static, because must be called from vx_pcm to demute monitoring
+ */
+int vx_set_monitor_level(vx_core_t *chip, int audio, int level, int active)
+{
+	struct vx_audio_level info;
+
+	memset(&info, 0, sizeof(info));
+	info.has_monitor_level = 1;
+	info.monitor_level = level;
+	info.has_monitor_mute = 1;
+	info.monitor_mute = !active;
+	chip->audio_monitor[audio] = level;
+	chip->audio_monitor_active[audio] = active;
+	return vx_adjust_audio_level(chip, audio, 0, &info); /* playback only */
+}
+
+
+/*
+ * set the mute status of the given audio
+ */
+static int vx_set_audio_switch(vx_core_t *chip, int audio, int active)
+{
+	struct vx_audio_level info;
+
+	memset(&info, 0, sizeof(info));
+	info.has_mute = 1;
+	info.mute = !active;
+	chip->audio_active[audio] = active;
+	return vx_adjust_audio_level(chip, audio, 0, &info); /* playback only */
+}
+
+/*
+ * set the mute status of the given audio
+ */
+static int vx_set_audio_gain(vx_core_t *chip, int audio, int capture, int level)
+{
+	struct vx_audio_level info;
+
+	memset(&info, 0, sizeof(info));
+	info.has_level = 1;
+	info.level = level;
+	chip->audio_gain[capture][audio] = level;
+	return vx_adjust_audio_level(chip, audio, capture, &info);
+}
+
+/*
+ * reset all audio levels
+ */
+static void vx_reset_audio_levels(vx_core_t *chip)
+{
+	unsigned int i, c;
+	struct vx_audio_level info;
+
+	memset(chip->audio_gain, 0, sizeof(chip->audio_gain));
+	memset(chip->audio_active, 0, sizeof(chip->audio_active));
+	memset(chip->audio_monitor, 0, sizeof(chip->audio_monitor));
+	memset(chip->audio_monitor_active, 0, sizeof(chip->audio_monitor_active));
+
+	for (c = 0; c < 2; c++) {
+		for (i = 0; i < chip->hw->num_ins * 2; i++) {
+			memset(&info, 0, sizeof(info));
+			if (c == 0) {
+				info.has_monitor_level = 1;
+				info.has_mute = 1;
+				info.has_monitor_mute = 1;
+			}
+			info.has_level = 1;
+			info.level = CVAL_0DB; /* default: 0dB */
+			vx_adjust_audio_level(chip, i, c, &info);
+			chip->audio_gain[c][i] = CVAL_0DB;
+			chip->audio_monitor[i] = CVAL_0DB;
+		}
+	}
+}
+
+
+/*
+ * VU, peak meter record
+ */
+
+#define VU_METER_CHANNELS	2
+
+struct vx_vu_meter {
+	int saturated;
+	int vu_level;
+	int peak_level;
+};
+
+/*
+ * get the VU and peak meter values
+ * @audio: the audio index
+ * @capture: 0 = playback, 1 = capture operation
+ * @info: the array of vx_vu_meter records (size = 2).
+ */
+static int vx_get_audio_vu_meter(vx_core_t *chip, int audio, int capture, struct vx_vu_meter *info)
+{
+	struct vx_rmh rmh;
+	int i, err;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return -EBUSY;
+
+	vx_init_rmh(&rmh, CMD_AUDIO_VU_PIC_METER);
+	rmh.LgStat += 2 * VU_METER_CHANNELS;
+	if (capture)
+		rmh.Cmd[0] |= COMMAND_RECORD_MASK;
+    
+        /* Add Audio IO mask */
+	rmh.Cmd[1] = 0;
+	for (i = 0; i < VU_METER_CHANNELS; i++)
+		rmh.Cmd[1] |= 1 << (audio + i);
+	err = vx_send_msg(chip, &rmh);
+	if (err < 0)
+		return err;
+	/* Read response */
+	for (i = 0; i < 2 * VU_METER_CHANNELS; i +=2) {
+		info->saturated = (rmh.Stat[0] & (1 << (audio + i))) ? 1 : 0;
+		info->vu_level = rmh.Stat[i + 1];
+		info->peak_level = rmh.Stat[i + 2];
+		info++;
+	}
+	return 0;
+}
+   
+
+/*
+ * control API entries
+ */
+
+/*
+ * output level control
+ */
+static int vx_output_level_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = chip->hw->output_level_max;
+	return 0;
+}
+
+static int vx_output_level_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	int codec = kcontrol->id.index;
+	down(&chip->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->output_level[codec][0];
+	ucontrol->value.integer.value[1] = chip->output_level[codec][1];
+	up(&chip->mixer_mutex);
+	return 0;
+}
+
+static int vx_output_level_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	int codec = kcontrol->id.index;
+	down(&chip->mixer_mutex);
+	if (ucontrol->value.integer.value[0] != chip->output_level[codec][0] ||
+	    ucontrol->value.integer.value[1] != chip->output_level[codec][1]) {
+		vx_set_analog_output_level(chip, codec,
+					   ucontrol->value.integer.value[0],
+					   ucontrol->value.integer.value[1]);
+		chip->output_level[codec][0] = ucontrol->value.integer.value[0];
+		chip->output_level[codec][1] = ucontrol->value.integer.value[1];
+		up(&chip->mixer_mutex);
+		return 1;
+	}
+	up(&chip->mixer_mutex);
+	return 0;
+}
+
+static snd_kcontrol_new_t vx_control_output_level = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"Master Playback Volume",
+	.info =		vx_output_level_info,
+	.get =		vx_output_level_get,
+	.put =		vx_output_level_put,
+};
+
+/*
+ * audio source select
+ */
+static int vx_audio_src_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	static char *texts_mic[3] = {
+		"Digital", "Line", "Mic"
+	};
+	static char *texts_vx2[2] = {
+		"Digital", "Analog"
+	};
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	if (chip->type >= VX_TYPE_VXPOCKET) {
+		uinfo->value.enumerated.items = 3;
+		if (uinfo->value.enumerated.item > 2)
+			uinfo->value.enumerated.item = 2;
+		strcpy(uinfo->value.enumerated.name,
+		       texts_mic[uinfo->value.enumerated.item]);
+	} else {
+		uinfo->value.enumerated.items = 2;
+		if (uinfo->value.enumerated.item > 1)
+			uinfo->value.enumerated.item = 1;
+		strcpy(uinfo->value.enumerated.name,
+		       texts_vx2[uinfo->value.enumerated.item]);
+	}
+	return 0;
+}
+
+static int vx_audio_src_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.enumerated.item[0] = chip->audio_source_target;
+	return 0;
+}
+
+static int vx_audio_src_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	down(&chip->mixer_mutex);
+	if (chip->audio_source_target != ucontrol->value.enumerated.item[0]) {
+		chip->audio_source_target = ucontrol->value.enumerated.item[0];
+		vx_sync_audio_source(chip);
+		up(&chip->mixer_mutex);
+		return 1;
+	}
+	up(&chip->mixer_mutex);
+	return 0;
+}
+
+static snd_kcontrol_new_t vx_control_audio_src = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"Capture Source",
+	.info =		vx_audio_src_info,
+	.get =		vx_audio_src_get,
+	.put =		vx_audio_src_put,
+};
+
+/*
+ * clock mode selection
+ */
+static int vx_clock_mode_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	static char *texts[3] = {
+		"Auto", "Internal", "External"
+	};
+
+	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 vx_clock_mode_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.enumerated.item[0] = chip->clock_mode;
+	return 0;
+}
+
+static int vx_clock_mode_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	down(&chip->mixer_mutex);
+	if (chip->clock_mode != ucontrol->value.enumerated.item[0]) {
+		chip->clock_mode = ucontrol->value.enumerated.item[0];
+		vx_set_clock(chip, chip->freq);
+		up(&chip->mixer_mutex);
+		return 1;
+	}
+	up(&chip->mixer_mutex);
+	return 0;
+}
+
+static snd_kcontrol_new_t vx_control_clock_mode = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"Clock Mode",
+	.info =		vx_clock_mode_info,
+	.get =		vx_clock_mode_get,
+	.put =		vx_clock_mode_put,
+};
+
+/*
+ * Audio Gain
+ */
+static int vx_audio_gain_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 = CVAL_MAX;
+	return 0;
+}
+
+static int vx_audio_gain_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	int audio = kcontrol->private_value & 0xff;
+	int capture = (kcontrol->private_value >> 8) & 1;
+
+	down(&chip->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->audio_gain[capture][audio];
+	ucontrol->value.integer.value[1] = chip->audio_gain[capture][audio+1];
+	up(&chip->mixer_mutex);
+	return 0;
+}
+
+static int vx_audio_gain_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	int audio = kcontrol->private_value & 0xff;
+	int capture = (kcontrol->private_value >> 8) & 1;
+
+	down(&chip->mixer_mutex);
+	if (ucontrol->value.integer.value[0] != chip->audio_gain[capture][audio] ||
+	    ucontrol->value.integer.value[1] != chip->audio_gain[capture][audio+1]) {
+		vx_set_audio_gain(chip, audio, capture, ucontrol->value.integer.value[0]);
+		vx_set_audio_gain(chip, audio+1, capture, ucontrol->value.integer.value[1]);
+		up(&chip->mixer_mutex);
+		return 1;
+	}
+	up(&chip->mixer_mutex);
+	return 0;
+}
+
+static int vx_audio_monitor_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	int audio = kcontrol->private_value & 0xff;
+
+	down(&chip->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->audio_monitor[audio];
+	ucontrol->value.integer.value[1] = chip->audio_monitor[audio+1];
+	up(&chip->mixer_mutex);
+	return 0;
+}
+
+static int vx_audio_monitor_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	int audio = kcontrol->private_value & 0xff;
+
+	down(&chip->mixer_mutex);
+	if (ucontrol->value.integer.value[0] != chip->audio_monitor[audio] ||
+	    ucontrol->value.integer.value[1] != chip->audio_monitor[audio+1]) {
+		vx_set_monitor_level(chip, audio, ucontrol->value.integer.value[0],
+				     chip->audio_monitor_active[audio]);
+		vx_set_monitor_level(chip, audio+1, ucontrol->value.integer.value[1],
+				     chip->audio_monitor_active[audio+1]);
+		up(&chip->mixer_mutex);
+		return 1;
+	}
+	up(&chip->mixer_mutex);
+	return 0;
+}
+
+static int vx_audio_sw_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int vx_audio_sw_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	int audio = kcontrol->private_value & 0xff;
+
+	down(&chip->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->audio_active[audio];
+	ucontrol->value.integer.value[1] = chip->audio_active[audio+1];
+	up(&chip->mixer_mutex);
+	return 0;
+}
+
+static int vx_audio_sw_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	int audio = kcontrol->private_value & 0xff;
+
+	down(&chip->mixer_mutex);
+	if (ucontrol->value.integer.value[0] != chip->audio_active[audio] ||
+	    ucontrol->value.integer.value[1] != chip->audio_active[audio+1]) {
+		vx_set_audio_switch(chip, audio, ucontrol->value.integer.value[0]);
+		vx_set_audio_switch(chip, audio+1, ucontrol->value.integer.value[1]);
+		up(&chip->mixer_mutex);
+		return 1;
+	}
+	up(&chip->mixer_mutex);
+	return 0;
+}
+
+static int vx_monitor_sw_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	int audio = kcontrol->private_value & 0xff;
+
+	down(&chip->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->audio_monitor_active[audio];
+	ucontrol->value.integer.value[1] = chip->audio_monitor_active[audio+1];
+	up(&chip->mixer_mutex);
+	return 0;
+}
+
+static int vx_monitor_sw_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	int audio = kcontrol->private_value & 0xff;
+
+	down(&chip->mixer_mutex);
+	if (ucontrol->value.integer.value[0] != chip->audio_monitor_active[audio] ||
+	    ucontrol->value.integer.value[1] != chip->audio_monitor_active[audio+1]) {
+		vx_set_monitor_level(chip, audio, chip->audio_monitor[audio],
+				     ucontrol->value.integer.value[0]);
+		vx_set_monitor_level(chip, audio+1, chip->audio_monitor[audio+1],
+				     ucontrol->value.integer.value[1]);
+		up(&chip->mixer_mutex);
+		return 1;
+	}
+	up(&chip->mixer_mutex);
+	return 0;
+}
+
+static snd_kcontrol_new_t vx_control_audio_gain = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	/* name will be filled later */
+	.info =         vx_audio_gain_info,
+	.get =          vx_audio_gain_get,
+	.put =          vx_audio_gain_put
+};
+static snd_kcontrol_new_t vx_control_output_switch = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "PCM Playback Switch",
+	.info =         vx_audio_sw_info,
+	.get =          vx_audio_sw_get,
+	.put =          vx_audio_sw_put
+};
+static snd_kcontrol_new_t vx_control_monitor_gain = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Monitoring Volume",
+	.info =         vx_audio_gain_info,	/* shared */
+	.get =          vx_audio_monitor_get,
+	.put =          vx_audio_monitor_put
+};
+static snd_kcontrol_new_t vx_control_monitor_switch = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Monitoring Switch",
+	.info =         vx_audio_sw_info,	/* shared */
+	.get =          vx_monitor_sw_get,
+	.put =          vx_monitor_sw_put
+};
+
+
+/*
+ * IEC958 status bits
+ */
+static int vx_iec958_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int vx_iec958_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+
+	down(&chip->mixer_mutex);
+	ucontrol->value.iec958.status[0] = (chip->uer_bits >> 0) & 0xff;
+	ucontrol->value.iec958.status[1] = (chip->uer_bits >> 8) & 0xff;
+	ucontrol->value.iec958.status[2] = (chip->uer_bits >> 16) & 0xff;
+	ucontrol->value.iec958.status[3] = (chip->uer_bits >> 24) & 0xff;
+	up(&chip->mixer_mutex);
+        return 0;
+}
+
+static int vx_iec958_mask_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = 0xff;
+	ucontrol->value.iec958.status[1] = 0xff;
+	ucontrol->value.iec958.status[2] = 0xff;
+	ucontrol->value.iec958.status[3] = 0xff;
+        return 0;
+}
+
+static int vx_iec958_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+
+	val = (ucontrol->value.iec958.status[0] << 0) |
+	      (ucontrol->value.iec958.status[1] << 8) |
+	      (ucontrol->value.iec958.status[2] << 16) |
+	      (ucontrol->value.iec958.status[3] << 24);
+	down(&chip->mixer_mutex);
+	if (chip->uer_bits != val) {
+		chip->uer_bits = val;
+		vx_set_iec958_status(chip, val);
+		up(&chip->mixer_mutex);
+		return 1;
+	}
+	up(&chip->mixer_mutex);
+	return 0;
+}
+
+static snd_kcontrol_new_t vx_control_iec958_mask = {
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
+	.info =		vx_iec958_info,	/* shared */
+	.get =		vx_iec958_mask_get,
+};
+
+static snd_kcontrol_new_t vx_control_iec958 = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+	.info =         vx_iec958_info,
+	.get =          vx_iec958_get,
+	.put =          vx_iec958_put
+};
+
+
+/*
+ * VU meter
+ */
+
+#define METER_MAX	0xff
+#define METER_SHIFT	16
+
+static int vx_vu_meter_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 = METER_MAX;
+	return 0;
+}
+
+static int vx_vu_meter_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	struct vx_vu_meter meter[2];
+	int audio = kcontrol->private_value & 0xff;
+	int capture = (kcontrol->private_value >> 8) & 1;
+
+	vx_get_audio_vu_meter(chip, audio, capture, meter);
+	ucontrol->value.integer.value[0] = meter[0].vu_level >> METER_SHIFT;
+	ucontrol->value.integer.value[1] = meter[1].vu_level >> METER_SHIFT;
+	return 0;
+}
+
+static int vx_peak_meter_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	struct vx_vu_meter meter[2];
+	int audio = kcontrol->private_value & 0xff;
+	int capture = (kcontrol->private_value >> 8) & 1;
+
+	vx_get_audio_vu_meter(chip, audio, capture, meter);
+	ucontrol->value.integer.value[0] = meter[0].peak_level >> METER_SHIFT;
+	ucontrol->value.integer.value[1] = meter[1].peak_level >> METER_SHIFT;
+	return 0;
+}
+
+static int vx_saturation_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int vx_saturation_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	vx_core_t *chip = snd_kcontrol_chip(kcontrol);
+	struct vx_vu_meter meter[2];
+	int audio = kcontrol->private_value & 0xff;
+
+	vx_get_audio_vu_meter(chip, audio, 1, meter); /* capture only */
+	ucontrol->value.integer.value[0] = meter[0].saturated;
+	ucontrol->value.integer.value[1] = meter[1].saturated;
+	return 0;
+}
+
+static snd_kcontrol_new_t vx_control_vu_meter = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	/* name will be filled later */
+	.info =		vx_vu_meter_info,
+	.get =		vx_vu_meter_get,
+};
+
+static snd_kcontrol_new_t vx_control_peak_meter = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	/* name will be filled later */
+	.info =		vx_vu_meter_info,	/* shared */
+	.get =		vx_peak_meter_get,
+};
+
+static snd_kcontrol_new_t vx_control_saturation = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"Input Saturation",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		vx_saturation_info,
+	.get =		vx_saturation_get,
+};
+
+
+
+/*
+ *
+ */
+
+int snd_vx_mixer_new(vx_core_t *chip)
+{
+	unsigned int i, c;
+	int err;
+	snd_kcontrol_new_t temp;
+	snd_card_t *card = chip->card;
+	char name[32];
+
+	strcpy(card->mixername, card->driver);
+
+	/* output level controls */
+	for (i = 0; i < chip->hw->num_outs; i++) {
+		temp = vx_control_output_level;
+		temp.index = i;
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0)
+			return err;
+	}
+
+	/* PCM volumes, switches, monitoring */
+	for (i = 0; i < chip->hw->num_outs; i++) {
+		int val = i * 2;
+		temp = vx_control_audio_gain;
+		temp.index = i;
+		temp.name = "PCM Playback Volume";
+		temp.private_value = val;
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0)
+			return err;
+		temp = vx_control_output_switch;
+		temp.index = i;
+		temp.private_value = val;
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0)
+			return err;
+		temp = vx_control_monitor_gain;
+		temp.index = i;
+		temp.private_value = val;
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0)
+			return err;
+		temp = vx_control_monitor_switch;
+		temp.index = i;
+		temp.private_value = val;
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0)
+			return err;
+	}
+	for (i = 0; i < chip->hw->num_outs; i++) {
+		temp = vx_control_audio_gain;
+		temp.index = i;
+		temp.name = "PCM Capture Volume";
+		temp.private_value = (i * 2) | (1 << 8);
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0)
+			return err;
+	}
+
+	/* Audio source */
+	if ((err = snd_ctl_add(card, snd_ctl_new1(&vx_control_audio_src, chip))) < 0)
+		return err;
+	/* clock mode */
+	if ((err = snd_ctl_add(card, snd_ctl_new1(&vx_control_clock_mode, chip))) < 0)
+		return err;
+	/* IEC958 controls */
+	if ((err = snd_ctl_add(card, snd_ctl_new1(&vx_control_iec958_mask, chip))) < 0)
+		return err;
+	if ((err = snd_ctl_add(card, snd_ctl_new1(&vx_control_iec958, chip))) < 0)
+		return err;
+	/* VU, peak, saturation meters */
+	for (c = 0; c < 2; c++) {
+		static char *dir[2] = { "Output", "Input" };
+		for (i = 0; i < chip->hw->num_ins; i++) {
+			int val = (i * 2) | (c << 8);
+			if (c == 1) {
+				temp = vx_control_saturation;
+				temp.index = i;
+				temp.private_value = val;
+				if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0)
+					return err;
+			}
+			sprintf(name, "%s VU Meter", dir[c]);
+			temp = vx_control_vu_meter;
+			temp.index = i;
+			temp.name = name;
+			temp.private_value = val;
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0)
+				return err;
+			sprintf(name, "%s Peak Meter", dir[c]);
+			temp = vx_control_peak_meter;
+			temp.index = i;
+			temp.name = name;
+			temp.private_value = val;
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0)
+				return err;
+		}
+	}
+	vx_reset_audio_levels(chip);
+	return 0;
+}
diff --git a/sound/drivers/vx/vx_pcm.c b/sound/drivers/vx/vx_pcm.c
new file mode 100644
index 0000000..9858717
--- /dev/null
+++ b/sound/drivers/vx/vx_pcm.c
@@ -0,0 +1,1312 @@
+/*
+ * Driver for Digigram VX soundcards
+ *
+ * PCM part
+ *
+ * Copyright (c) 2002,2003 by 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
+ *
+ *
+ * STRATEGY
+ *  for playback, we send series of "chunks", which size is equal with the
+ *  IBL size, typically 126 samples.  at each end of chunk, the end-of-buffer
+ *  interrupt is notified, and the interrupt handler will feed the next chunk.
+ *
+ *  the current position is calculated from the sample count RMH.
+ *  pipe->transferred is the counter of data which has been already transferred.
+ *  if this counter reaches to the period size, snd_pcm_period_elapsed() will
+ *  be issued.
+ *
+ *  for capture, the situation is much easier.
+ *  to get a low latency response, we'll check the capture streams at each
+ *  interrupt (capture stream has no EOB notification).  if the pending
+ *  data is accumulated to the period size, snd_pcm_period_elapsed() is
+ *  called and the pointer is updated.
+ *
+ *  the current point of read buffer is kept in pipe->hw_ptr.  note that
+ *  this is in bytes.
+ *
+ *
+ * TODO
+ *  - linked trigger for full-duplex mode.
+ *  - scheduled action on the stream.
+ */
+
+#include <sound/driver.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/asoundef.h>
+#include <sound/pcm.h>
+#include <sound/vx_core.h>
+#include "vx_cmd.h"
+
+
+/*
+ * we use a vmalloc'ed (sg-)buffer
+ */
+
+/* get the physical page pointer on the given offset */
+static struct page *snd_pcm_get_vmalloc_page(snd_pcm_substream_t *subs, unsigned long offset)
+{
+	void *pageptr = subs->runtime->dma_area + offset;
+	return vmalloc_to_page(pageptr);
+}
+
+/*
+ * allocate a buffer via vmalloc_32().
+ * called from hw_params
+ * NOTE: this may be called not only once per pcm open!
+ */
+static int snd_pcm_alloc_vmalloc_buffer(snd_pcm_substream_t *subs, size_t size)
+{
+	snd_pcm_runtime_t *runtime = subs->runtime;
+	if (runtime->dma_area) {
+		/* already allocated */
+		if (runtime->dma_bytes >= size)
+			return 0; /* already enough large */
+		vfree_nocheck(runtime->dma_area); /* bypass the memory wrapper */
+	}
+	runtime->dma_area = vmalloc_32(size);
+	if (! runtime->dma_area)
+		return -ENOMEM;
+	memset(runtime->dma_area, 0, size);
+	runtime->dma_bytes = size;
+	return 1; /* changed */
+}
+
+/*
+ * free the buffer.
+ * called from hw_free callback
+ * NOTE: this may be called not only once per pcm open!
+ */
+static int snd_pcm_free_vmalloc_buffer(snd_pcm_substream_t *subs)
+{
+	snd_pcm_runtime_t *runtime = subs->runtime;
+	if (runtime->dma_area) {
+		vfree_nocheck(runtime->dma_area); /* bypass the memory wrapper */
+		runtime->dma_area = NULL;
+	}
+	return 0;
+}
+
+
+/*
+ * read three pending pcm bytes via inb()
+ */
+static void vx_pcm_read_per_bytes(vx_core_t *chip, snd_pcm_runtime_t *runtime, vx_pipe_t *pipe)
+{
+	int offset = pipe->hw_ptr;
+	unsigned char *buf = (unsigned char *)(runtime->dma_area + offset);
+	*buf++ = vx_inb(chip, RXH);
+	if (++offset >= pipe->buffer_bytes) {
+		offset = 0;
+		buf = (unsigned char *)runtime->dma_area;
+	}
+	*buf++ = vx_inb(chip, RXM);
+	if (++offset >= pipe->buffer_bytes) {
+		offset = 0;
+		buf = (unsigned char *)runtime->dma_area;
+	}
+	*buf++ = vx_inb(chip, RXL);
+	if (++offset >= pipe->buffer_bytes) {
+		offset = 0;
+		buf = (unsigned char *)runtime->dma_area;
+	}
+	pipe->hw_ptr = offset;
+}
+
+/*
+ * vx_set_pcx_time - convert from the PC time to the RMH status time.
+ * @pc_time: the pointer for the PC-time to set
+ * @dsp_time: the pointer for RMH status time array
+ */
+static void vx_set_pcx_time(vx_core_t *chip, pcx_time_t *pc_time, unsigned int *dsp_time)
+{
+	dsp_time[0] = (unsigned int)((*pc_time) >> 24) & PCX_TIME_HI_MASK;
+	dsp_time[1] = (unsigned int)(*pc_time) &  MASK_DSP_WORD;
+}
+
+/*
+ * vx_set_differed_time - set the differed time if specified
+ * @rmh: the rmh record to modify
+ * @pipe: the pipe to be checked
+ *
+ * if the pipe is programmed with the differed time, set the DSP time
+ * on the rmh and changes its command length.
+ *
+ * returns the increase of the command length.
+ */
+static int vx_set_differed_time(vx_core_t *chip, struct vx_rmh *rmh, vx_pipe_t *pipe)
+{
+	/* Update The length added to the RMH command by the timestamp */
+	if (! (pipe->differed_type & DC_DIFFERED_DELAY))
+		return 0;
+		
+	/* Set the T bit */
+	rmh->Cmd[0] |= DSP_DIFFERED_COMMAND_MASK;
+
+	/* Time stamp is the 1st following parameter */
+	vx_set_pcx_time(chip, &pipe->pcx_time, &rmh->Cmd[1]);
+
+	/* Add the flags to a notified differed command */
+	if (pipe->differed_type & DC_NOTIFY_DELAY)
+		rmh->Cmd[1] |= NOTIFY_MASK_TIME_HIGH ;
+
+	/* Add the flags to a multiple differed command */
+	if (pipe->differed_type & DC_MULTIPLE_DELAY)
+		rmh->Cmd[1] |= MULTIPLE_MASK_TIME_HIGH;
+
+	/* Add the flags to a stream-time differed command */
+	if (pipe->differed_type & DC_STREAM_TIME_DELAY)
+		rmh->Cmd[1] |= STREAM_MASK_TIME_HIGH;
+		
+	rmh->LgCmd += 2;
+	return 2;
+}
+
+/*
+ * vx_set_stream_format - send the stream format command
+ * @pipe: the affected pipe
+ * @data: format bitmask
+ */
+static int vx_set_stream_format(vx_core_t *chip, vx_pipe_t *pipe, unsigned int data)
+{
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, pipe->is_capture ?
+		    CMD_FORMAT_STREAM_IN : CMD_FORMAT_STREAM_OUT);
+	rmh.Cmd[0] |= pipe->number << FIELD_SIZE;
+
+        /* Command might be longer since we may have to add a timestamp */
+	vx_set_differed_time(chip, &rmh, pipe);
+
+	rmh.Cmd[rmh.LgCmd] = (data & 0xFFFFFF00) >> 8;
+	rmh.Cmd[rmh.LgCmd + 1] = (data & 0xFF) << 16 /*| (datal & 0xFFFF00) >> 8*/;
+	rmh.LgCmd += 2;
+    
+	return vx_send_msg(chip, &rmh);
+}
+
+
+/*
+ * vx_set_format - set the format of a pipe
+ * @pipe: the affected pipe
+ * @runtime: pcm runtime instance to be referred
+ *
+ * returns 0 if successful, or a negative error code.
+ */
+static int vx_set_format(vx_core_t *chip, vx_pipe_t *pipe,
+			 snd_pcm_runtime_t *runtime)
+{
+	unsigned int header = HEADER_FMT_BASE;
+
+	if (runtime->channels == 1)
+		header |= HEADER_FMT_MONO;
+	if (snd_pcm_format_little_endian(runtime->format))
+		header |= HEADER_FMT_INTEL;
+	if (runtime->rate < 32000 && runtime->rate > 11025)
+		header |= HEADER_FMT_UPTO32;
+	else if (runtime->rate <= 11025)
+		header |= HEADER_FMT_UPTO11;
+
+	switch (snd_pcm_format_physical_width(runtime->format)) {
+	// case 8: break;
+	case 16: header |= HEADER_FMT_16BITS; break;
+	case 24: header |= HEADER_FMT_24BITS; break;
+	default : 
+		snd_BUG();
+		return -EINVAL;
+        };
+
+	return vx_set_stream_format(chip, pipe, header);
+}
+
+/*
+ * set / query the IBL size
+ */
+static int vx_set_ibl(vx_core_t *chip, struct vx_ibl_info *info)
+{
+	int err;
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_IBL);
+	rmh.Cmd[0] |= info->size & 0x03ffff;
+	err = vx_send_msg(chip, &rmh);
+	if (err < 0)
+		return err;
+	info->size = rmh.Stat[0];
+	info->max_size = rmh.Stat[1];
+	info->min_size = rmh.Stat[2];
+	info->granularity = rmh.Stat[3];
+	snd_printdd(KERN_DEBUG "vx_set_ibl: size = %d, max = %d, min = %d, gran = %d\n",
+		   info->size, info->max_size, info->min_size, info->granularity);
+	return 0;
+}
+
+
+/*
+ * vx_get_pipe_state - get the state of a pipe
+ * @pipe: the pipe to be checked
+ * @state: the pointer for the returned state
+ *
+ * checks the state of a given pipe, and stores the state (1 = running,
+ * 0 = paused) on the given pointer.
+ *
+ * called from trigger callback only
+ */
+static int vx_get_pipe_state(vx_core_t *chip, vx_pipe_t *pipe, int *state)
+{
+	int err;
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_PIPE_STATE);
+	vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0);
+	err = vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ 
+	if (! err)
+		*state = (rmh.Stat[0] & (1 << pipe->number)) ? 1 : 0;
+	return err;
+}
+
+
+/*
+ * vx_query_hbuffer_size - query available h-buffer size in bytes
+ * @pipe: the pipe to be checked
+ *
+ * return the available size on h-buffer in bytes,
+ * or a negative error code.
+ *
+ * NOTE: calling this function always switches to the stream mode.
+ *       you'll need to disconnect the host to get back to the
+ *       normal mode.
+ */
+static int vx_query_hbuffer_size(vx_core_t *chip, vx_pipe_t *pipe)
+{
+	int result;
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_SIZE_HBUFFER);
+	vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0);
+	if (pipe->is_capture)
+		rmh.Cmd[0] |= 0x00000001;
+	result = vx_send_msg(chip, &rmh);
+	if (! result)
+		result = rmh.Stat[0] & 0xffff;
+	return result;
+}
+
+
+/*
+ * vx_pipe_can_start - query whether a pipe is ready for start
+ * @pipe: the pipe to be checked
+ *
+ * return 1 if ready, 0 if not ready, and negative value on error.
+ *
+ * called from trigger callback only
+ */
+static int vx_pipe_can_start(vx_core_t *chip, vx_pipe_t *pipe)
+{
+	int err;
+	struct vx_rmh rmh;
+        
+	vx_init_rmh(&rmh, CMD_CAN_START_PIPE);
+	vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0);
+	rmh.Cmd[0] |= 1;
+
+	err = vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ 
+	if (! err) {
+		if (rmh.Stat[0])
+			err = 1;
+	}
+	return err;
+}
+
+/*
+ * vx_conf_pipe - tell the pipe to stand by and wait for IRQA.
+ * @pipe: the pipe to be configured
+ */
+static int vx_conf_pipe(vx_core_t *chip, vx_pipe_t *pipe)
+{
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_CONF_PIPE);
+	if (pipe->is_capture)
+		rmh.Cmd[0] |= COMMAND_RECORD_MASK;
+	rmh.Cmd[1] = 1 << pipe->number;
+	return vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */
+}
+
+/*
+ * vx_send_irqa - trigger IRQA
+ */
+static int vx_send_irqa(vx_core_t *chip)
+{
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_SEND_IRQA);
+	return vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ 
+}
+
+
+#define MAX_WAIT_FOR_DSP        250
+/*
+ * vx boards do not support inter-card sync, besides
+ * only 126 samples require to be prepared before a pipe can start
+ */
+#define CAN_START_DELAY         2	/* wait 2ms only before asking if the pipe is ready*/
+#define WAIT_STATE_DELAY        2	/* wait 2ms after irqA was requested and check if the pipe state toggled*/
+
+/*
+ * vx_toggle_pipe - start / pause a pipe
+ * @pipe: the pipe to be triggered
+ * @state: start = 1, pause = 0
+ *
+ * called from trigger callback only
+ *
+ */
+static int vx_toggle_pipe(vx_core_t *chip, vx_pipe_t *pipe, int state)
+{
+	int err, i, cur_state;
+
+	/* Check the pipe is not already in the requested state */
+	if (vx_get_pipe_state(chip, pipe, &cur_state) < 0)
+		return -EBADFD;
+	if (state == cur_state)
+		return 0;
+
+	/* If a start is requested, ask the DSP to get prepared
+	 * and wait for a positive acknowledge (when there are
+	 * enough sound buffer for this pipe)
+	 */
+	if (state) {
+		for (i = 0 ; i < MAX_WAIT_FOR_DSP; i++) {
+			err = vx_pipe_can_start(chip, pipe);
+			if (err > 0)
+				break;
+			/* Wait for a few, before asking again
+			 * to avoid flooding the DSP with our requests
+			 */
+			mdelay(1);
+		}
+	}
+    
+	if ((err = vx_conf_pipe(chip, pipe)) < 0)
+		return err;
+
+	if ((err = vx_send_irqa(chip)) < 0)
+		return err;
+    
+	/* If it completes successfully, wait for the pipes
+	 * reaching the expected state before returning
+	 * Check one pipe only (since they are synchronous)
+	 */
+	for (i = 0; i < MAX_WAIT_FOR_DSP; i++) {
+		err = vx_get_pipe_state(chip, pipe, &cur_state);
+		if (err < 0 || cur_state == state)
+			break;
+		err = -EIO;
+		mdelay(1);
+	}
+	return err < 0 ? -EIO : 0;
+}
+
+    
+/*
+ * vx_stop_pipe - stop a pipe
+ * @pipe: the pipe to be stopped
+ *
+ * called from trigger callback only
+ */
+static int vx_stop_pipe(vx_core_t *chip, vx_pipe_t *pipe)
+{
+	struct vx_rmh rmh;
+	vx_init_rmh(&rmh, CMD_STOP_PIPE);
+	vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0);
+	return vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ 
+}
+
+
+/*
+ * vx_alloc_pipe - allocate a pipe and initialize the pipe instance
+ * @capture: 0 = playback, 1 = capture operation
+ * @audioid: the audio id to be assigned
+ * @num_audio: number of audio channels
+ * @pipep: the returned pipe instance
+ *
+ * return 0 on success, or a negative error code.
+ */
+static int vx_alloc_pipe(vx_core_t *chip, int capture,
+			 int audioid, int num_audio,
+			 vx_pipe_t **pipep)
+{
+	int err;
+	vx_pipe_t *pipe;
+	struct vx_rmh rmh;
+	int data_mode;
+
+	*pipep = NULL;
+	vx_init_rmh(&rmh, CMD_RES_PIPE);
+	vx_set_pipe_cmd_params(&rmh, capture, audioid, num_audio);
+#if 0	// NYI
+	if (underrun_skip_sound)
+		rmh.Cmd[0] |= BIT_SKIP_SOUND;
+#endif	// NYI
+	data_mode = (chip->uer_bits & IEC958_AES0_NONAUDIO) != 0;
+	if (! capture && data_mode)
+		rmh.Cmd[0] |= BIT_DATA_MODE;
+	err = vx_send_msg(chip, &rmh);
+	if (err < 0)
+		return err;
+
+	/* initialize the pipe record */
+	pipe = kcalloc(1, sizeof(*pipe), GFP_KERNEL);
+	if (! pipe) {
+		/* release the pipe */
+		vx_init_rmh(&rmh, CMD_FREE_PIPE);
+		vx_set_pipe_cmd_params(&rmh, capture, audioid, 0);
+		vx_send_msg(chip, &rmh);
+		return -ENOMEM;
+	}
+
+	/* the pipe index should be identical with the audio index */
+	pipe->number = audioid;
+	pipe->is_capture = capture;
+	pipe->channels = num_audio;
+	pipe->differed_type = 0;
+	pipe->pcx_time = 0;
+	pipe->data_mode = data_mode;
+	*pipep = pipe;
+
+	return 0;
+}
+
+
+/*
+ * vx_free_pipe - release a pipe
+ * @pipe: pipe to be released
+ */
+static int vx_free_pipe(vx_core_t *chip, vx_pipe_t *pipe)
+{
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_FREE_PIPE);
+	vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0);
+	vx_send_msg(chip, &rmh);
+
+	kfree(pipe);
+	return 0;
+}
+
+
+/*
+ * vx_start_stream - start the stream
+ *
+ * called from trigger callback only
+ */
+static int vx_start_stream(vx_core_t *chip, vx_pipe_t *pipe)
+{
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_START_ONE_STREAM);
+	vx_set_stream_cmd_params(&rmh, pipe->is_capture, pipe->number);
+	vx_set_differed_time(chip, &rmh, pipe);
+	return vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ 
+}
+
+
+/*
+ * vx_stop_stream - stop the stream
+ *
+ * called from trigger callback only
+ */
+static int vx_stop_stream(vx_core_t *chip, vx_pipe_t *pipe)
+{
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_STOP_STREAM);
+	vx_set_stream_cmd_params(&rmh, pipe->is_capture, pipe->number);
+	return vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ 
+}
+
+
+/*
+ * playback hw information
+ */
+
+static snd_pcm_hardware_t vx_pcm_playback_hw = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_RESUME),
+	.formats =		/*SNDRV_PCM_FMTBIT_U8 |*/ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	126,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		VX_MAX_PERIODS,
+	.fifo_size =		126,
+};
+
+
+static void vx_pcm_delayed_start(unsigned long arg);
+
+/*
+ * vx_pcm_playback_open - open callback for playback
+ */
+static int vx_pcm_playback_open(snd_pcm_substream_t *subs)
+{
+	snd_pcm_runtime_t *runtime = subs->runtime;
+	vx_core_t *chip = snd_pcm_substream_chip(subs);
+	vx_pipe_t *pipe = NULL;
+	unsigned int audio;
+	int err;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return -EBUSY;
+
+	audio = subs->pcm->device * 2;
+	snd_assert(audio < chip->audio_outs, return -EINVAL);
+	
+	/* playback pipe may have been already allocated for monitoring */
+	pipe = chip->playback_pipes[audio];
+	if (! pipe) {
+		/* not allocated yet */
+		err = vx_alloc_pipe(chip, 0, audio, 2, &pipe); /* stereo playback */
+		if (err < 0)
+			return err;
+		chip->playback_pipes[audio] = pipe;
+	}
+	/* open for playback */
+	pipe->references++;
+
+	pipe->substream = subs;
+	tasklet_init(&pipe->start_tq, vx_pcm_delayed_start, (unsigned long)subs);
+	chip->playback_pipes[audio] = pipe;
+
+	runtime->hw = vx_pcm_playback_hw;
+	runtime->hw.period_bytes_min = chip->ibl.size;
+	runtime->private_data = pipe;
+
+	/* align to 4 bytes (otherwise will be problematic when 24bit is used) */ 
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 4);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 4);
+
+	return 0;
+}
+
+/*
+ * vx_pcm_playback_close - close callback for playback
+ */
+static int vx_pcm_playback_close(snd_pcm_substream_t *subs)
+{
+	vx_core_t *chip = snd_pcm_substream_chip(subs);
+	vx_pipe_t *pipe;
+
+	if (! subs->runtime->private_data)
+		return -EINVAL;
+
+	pipe = subs->runtime->private_data;
+
+	if (--pipe->references == 0) {
+		chip->playback_pipes[pipe->number] = NULL;
+		vx_free_pipe(chip, pipe);
+	}
+
+	return 0;
+
+}
+
+
+/*
+ * vx_notify_end_of_buffer - send "end-of-buffer" notifier at the given pipe
+ * @pipe: the pipe to notify
+ *
+ * NB: call with a certain lock.
+ */
+static int vx_notify_end_of_buffer(vx_core_t *chip, vx_pipe_t *pipe)
+{
+	int err;
+	struct vx_rmh rmh;  /* use a temporary rmh here */
+
+	/* Toggle Dsp Host Interface into Message mode */
+	vx_send_rih_nolock(chip, IRQ_PAUSE_START_CONNECT);
+	vx_init_rmh(&rmh, CMD_NOTIFY_END_OF_BUFFER);
+	vx_set_stream_cmd_params(&rmh, 0, pipe->number);
+	err = vx_send_msg_nolock(chip, &rmh);
+	if (err < 0)
+		return err;
+	/* Toggle Dsp Host Interface back to sound transfer mode */
+	vx_send_rih_nolock(chip, IRQ_PAUSE_START_CONNECT);
+	return 0;
+}
+
+/*
+ * vx_pcm_playback_transfer_chunk - transfer a single chunk
+ * @subs: substream
+ * @pipe: the pipe to transfer
+ * @size: chunk size in bytes
+ *
+ * transfer a single buffer chunk.  EOB notificaton is added after that.
+ * called from the interrupt handler, too.
+ *
+ * return 0 if ok.
+ */
+static int vx_pcm_playback_transfer_chunk(vx_core_t *chip, snd_pcm_runtime_t *runtime, vx_pipe_t *pipe, int size)
+{
+	int space, err = 0;
+
+	space = vx_query_hbuffer_size(chip, pipe);
+	if (space < 0) {
+		/* disconnect the host, SIZE_HBUF command always switches to the stream mode */
+		vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT);
+		snd_printd("error hbuffer\n");
+		return space;
+	}
+	if (space < size) {
+		vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT);
+		snd_printd("no enough hbuffer space %d\n", space);
+		return -EIO; /* XRUN */
+	}
+		
+	/* we don't need irqsave here, because this function
+	 * is called from either trigger callback or irq handler
+	 */
+	spin_lock(&chip->lock); 
+	vx_pseudo_dma_write(chip, runtime, pipe, size);
+	err = vx_notify_end_of_buffer(chip, pipe);
+	/* disconnect the host, SIZE_HBUF command always switches to the stream mode */
+	vx_send_rih_nolock(chip, IRQ_CONNECT_STREAM_NEXT);
+	spin_unlock(&chip->lock);
+	return err;
+}
+
+/*
+ * update the position of the given pipe.
+ * pipe->position is updated and wrapped within the buffer size.
+ * pipe->transferred is updated, too, but the size is not wrapped,
+ * so that the caller can check the total transferred size later
+ * (to call snd_pcm_period_elapsed).
+ */
+static int vx_update_pipe_position(vx_core_t *chip, snd_pcm_runtime_t *runtime, vx_pipe_t *pipe)
+{
+	struct vx_rmh rmh;
+	int err, update;
+	u64 count;
+
+	vx_init_rmh(&rmh, CMD_STREAM_SAMPLE_COUNT);
+	vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0);
+	err = vx_send_msg(chip, &rmh);
+	if (err < 0)
+		return err;
+
+	count = ((u64)(rmh.Stat[0] & 0xfffff) << 24) | (u64)rmh.Stat[1];
+	update = (int)(count - pipe->cur_count);
+	pipe->cur_count = count;
+	pipe->position += update;
+	if (pipe->position >= (int)runtime->buffer_size)
+		pipe->position %= runtime->buffer_size;
+	pipe->transferred += update;
+	return 0;
+}
+
+/*
+ * transfer the pending playback buffer data to DSP
+ * called from interrupt handler
+ */
+static void vx_pcm_playback_transfer(vx_core_t *chip, snd_pcm_substream_t *subs, vx_pipe_t *pipe, int nchunks)
+{
+	int i, err;
+	snd_pcm_runtime_t *runtime = subs->runtime;
+
+	if (! pipe->prepared || (chip->chip_status & VX_STAT_IS_STALE))
+		return;
+	for (i = 0; i < nchunks; i++) {
+		if ((err = vx_pcm_playback_transfer_chunk(chip, runtime, pipe,
+							  chip->ibl.size)) < 0)
+			return;
+	}
+}
+
+/*
+ * update the playback position and call snd_pcm_period_elapsed() if necessary
+ * called from interrupt handler
+ */
+static void vx_pcm_playback_update(vx_core_t *chip, snd_pcm_substream_t *subs, vx_pipe_t *pipe)
+{
+	int err;
+	snd_pcm_runtime_t *runtime = subs->runtime;
+
+	if (pipe->running && ! (chip->chip_status & VX_STAT_IS_STALE)) {
+		if ((err = vx_update_pipe_position(chip, runtime, pipe)) < 0)
+			return;
+		if (pipe->transferred >= (int)runtime->period_size) {
+			pipe->transferred %= runtime->period_size;
+			snd_pcm_period_elapsed(subs);
+		}
+	}
+}
+
+/*
+ * start the stream and pipe.
+ * this function is called from tasklet, which is invoked by the trigger
+ * START callback.
+ */
+static void vx_pcm_delayed_start(unsigned long arg)
+{
+	snd_pcm_substream_t *subs = (snd_pcm_substream_t *)arg;
+	vx_core_t *chip = subs->pcm->private_data;
+	vx_pipe_t *pipe = subs->runtime->private_data;
+	int err;
+
+	/*  printk( KERN_DEBUG "DDDD tasklet delayed start jiffies = %ld\n", jiffies);*/
+
+	if ((err = vx_start_stream(chip, pipe)) < 0) {
+		snd_printk(KERN_ERR "vx: cannot start stream\n");
+		return;
+	}
+	if ((err = vx_toggle_pipe(chip, pipe, 1)) < 0) {
+		snd_printk(KERN_ERR "vx: cannot start pipe\n");
+		return;
+	}
+	/*   printk( KERN_DEBUG "dddd tasklet delayed start jiffies = %ld \n", jiffies);*/
+}
+
+/*
+ * vx_pcm_playback_trigger - trigger callback for playback
+ */
+static int vx_pcm_trigger(snd_pcm_substream_t *subs, int cmd)
+{
+	vx_core_t *chip = snd_pcm_substream_chip(subs);
+	vx_pipe_t *pipe = subs->runtime->private_data;
+	int err;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return -EBUSY;
+		
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		if (! pipe->is_capture)
+			vx_pcm_playback_transfer(chip, subs, pipe, 2);
+		/* FIXME:
+		 * we trigger the pipe using tasklet, so that the interrupts are
+		 * issued surely after the trigger is completed.
+		 */ 
+		tasklet_hi_schedule(&pipe->start_tq);
+		chip->pcm_running++;
+		pipe->running = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		vx_toggle_pipe(chip, pipe, 0);
+		vx_stop_pipe(chip, pipe);
+		vx_stop_stream(chip, pipe);
+		chip->pcm_running--;
+		pipe->running = 0;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if ((err = vx_toggle_pipe(chip, pipe, 0)) < 0)
+			return err;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if ((err = vx_toggle_pipe(chip, pipe, 1)) < 0)
+			return err;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*
+ * vx_pcm_playback_pointer - pointer callback for playback
+ */
+static snd_pcm_uframes_t vx_pcm_playback_pointer(snd_pcm_substream_t *subs)
+{
+	snd_pcm_runtime_t *runtime = subs->runtime;
+	vx_pipe_t *pipe = runtime->private_data;
+	return pipe->position;
+}
+
+/*
+ * vx_pcm_hw_params - hw_params callback for playback and capture
+ */
+static int vx_pcm_hw_params(snd_pcm_substream_t *subs,
+				     snd_pcm_hw_params_t *hw_params)
+{
+	return snd_pcm_alloc_vmalloc_buffer(subs, params_buffer_bytes(hw_params));
+}
+
+/*
+ * vx_pcm_hw_free - hw_free callback for playback and capture
+ */
+static int vx_pcm_hw_free(snd_pcm_substream_t *subs)
+{
+	return snd_pcm_free_vmalloc_buffer(subs);
+}
+
+/*
+ * vx_pcm_prepare - prepare callback for playback and capture
+ */
+static int vx_pcm_prepare(snd_pcm_substream_t *subs)
+{
+	vx_core_t *chip = snd_pcm_substream_chip(subs);
+	snd_pcm_runtime_t *runtime = subs->runtime;
+	vx_pipe_t *pipe = runtime->private_data;
+	int err, data_mode;
+	// int max_size, nchunks;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return -EBUSY;
+
+	data_mode = (chip->uer_bits & IEC958_AES0_NONAUDIO) != 0;
+	if (data_mode != pipe->data_mode && ! pipe->is_capture) {
+		/* IEC958 status (raw-mode) was changed */
+		/* we reopen the pipe */
+		struct vx_rmh rmh;
+		snd_printdd(KERN_DEBUG "reopen the pipe with data_mode = %d\n", data_mode);
+		vx_init_rmh(&rmh, CMD_FREE_PIPE);
+		vx_set_pipe_cmd_params(&rmh, 0, pipe->number, 0);
+		if ((err = vx_send_msg(chip, &rmh)) < 0)
+			return err;
+		vx_init_rmh(&rmh, CMD_RES_PIPE);
+		vx_set_pipe_cmd_params(&rmh, 0, pipe->number, pipe->channels);
+		if (data_mode)
+			rmh.Cmd[0] |= BIT_DATA_MODE;
+		if ((err = vx_send_msg(chip, &rmh)) < 0)
+			return err;
+		pipe->data_mode = data_mode;
+	}
+
+	if (chip->pcm_running && chip->freq != runtime->rate) {
+		snd_printk(KERN_ERR "vx: cannot set different clock %d from the current %d\n", runtime->rate, chip->freq);
+		return -EINVAL;
+	}
+	vx_set_clock(chip, runtime->rate);
+
+	if ((err = vx_set_format(chip, pipe, runtime)) < 0)
+		return err;
+
+	if (vx_is_pcmcia(chip)) {
+		pipe->align = 2; /* 16bit word */
+	} else {
+		pipe->align = 4; /* 32bit word */
+	}
+
+	pipe->buffer_bytes = frames_to_bytes(runtime, runtime->buffer_size);
+	pipe->period_bytes = frames_to_bytes(runtime, runtime->period_size);
+	pipe->hw_ptr = 0;
+
+	/* set the timestamp */
+	vx_update_pipe_position(chip, runtime, pipe);
+	/* clear again */
+	pipe->transferred = 0;
+	pipe->position = 0;
+
+	pipe->prepared = 1;
+
+	return 0;
+}
+
+
+/*
+ * operators for PCM playback
+ */
+static snd_pcm_ops_t vx_pcm_playback_ops = {
+	.open =		vx_pcm_playback_open,
+	.close =	vx_pcm_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	vx_pcm_hw_params,
+	.hw_free =	vx_pcm_hw_free,
+	.prepare =	vx_pcm_prepare,
+	.trigger =	vx_pcm_trigger,
+	.pointer =	vx_pcm_playback_pointer,
+	.page =		snd_pcm_get_vmalloc_page,
+};
+
+
+/*
+ * playback hw information
+ */
+
+static snd_pcm_hardware_t vx_pcm_capture_hw = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_RESUME),
+	.formats =		/*SNDRV_PCM_FMTBIT_U8 |*/ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	126,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		VX_MAX_PERIODS,
+	.fifo_size =		126,
+};
+
+
+/*
+ * vx_pcm_capture_open - open callback for capture
+ */
+static int vx_pcm_capture_open(snd_pcm_substream_t *subs)
+{
+	snd_pcm_runtime_t *runtime = subs->runtime;
+	vx_core_t *chip = snd_pcm_substream_chip(subs);
+	vx_pipe_t *pipe;
+	vx_pipe_t *pipe_out_monitoring = NULL;
+	unsigned int audio;
+	int err;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return -EBUSY;
+
+	audio = subs->pcm->device * 2;
+	snd_assert(audio < chip->audio_ins, return -EINVAL);
+	err = vx_alloc_pipe(chip, 1, audio, 2, &pipe);
+	if (err < 0)
+		return err;
+	pipe->substream = subs;
+	tasklet_init(&pipe->start_tq, vx_pcm_delayed_start, (unsigned long)subs);
+	chip->capture_pipes[audio] = pipe;
+
+	/* check if monitoring is needed */
+	if (chip->audio_monitor_active[audio]) {
+		pipe_out_monitoring = chip->playback_pipes[audio];
+		if (! pipe_out_monitoring) {
+			/* allocate a pipe */
+			err = vx_alloc_pipe(chip, 0, audio, 2, &pipe_out_monitoring);
+			if (err < 0)
+				return err;
+			chip->playback_pipes[audio] = pipe_out_monitoring;
+		}
+		pipe_out_monitoring->references++;
+		/* 
+		   if an output pipe is available, it's audios still may need to be 
+		   unmuted. hence we'll have to call a mixer entry point.
+		*/
+		vx_set_monitor_level(chip, audio, chip->audio_monitor[audio], chip->audio_monitor_active[audio]);
+		/* assuming stereo */
+		vx_set_monitor_level(chip, audio+1, chip->audio_monitor[audio+1], chip->audio_monitor_active[audio+1]); 
+	}
+
+	pipe->monitoring_pipe = pipe_out_monitoring; /* default value NULL */
+
+	runtime->hw = vx_pcm_capture_hw;
+	runtime->hw.period_bytes_min = chip->ibl.size;
+	runtime->private_data = pipe;
+
+	/* align to 4 bytes (otherwise will be problematic when 24bit is used) */ 
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 4);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 4);
+
+	return 0;
+}
+
+/*
+ * vx_pcm_capture_close - close callback for capture
+ */
+static int vx_pcm_capture_close(snd_pcm_substream_t *subs)
+{
+	vx_core_t *chip = snd_pcm_substream_chip(subs);
+	vx_pipe_t *pipe;
+	vx_pipe_t *pipe_out_monitoring;
+	
+	if (! subs->runtime->private_data)
+		return -EINVAL;
+	pipe = subs->runtime->private_data;
+	chip->capture_pipes[pipe->number] = NULL;
+
+	pipe_out_monitoring = pipe->monitoring_pipe;
+
+	/*
+	  if an output pipe is attached to this input, 
+	  check if it needs to be released.
+	*/
+	if (pipe_out_monitoring) {
+		if (--pipe_out_monitoring->references == 0) {
+			vx_free_pipe(chip, pipe_out_monitoring);
+			chip->playback_pipes[pipe->number] = NULL;
+			pipe->monitoring_pipe = NULL;
+		}
+	}
+	
+	vx_free_pipe(chip, pipe);
+	return 0;
+}
+
+
+
+#define DMA_READ_ALIGN	6	/* hardware alignment for read */
+
+/*
+ * vx_pcm_capture_update - update the capture buffer
+ */
+static void vx_pcm_capture_update(vx_core_t *chip, snd_pcm_substream_t *subs, vx_pipe_t *pipe)
+{
+	int size, space, count;
+	snd_pcm_runtime_t *runtime = subs->runtime;
+
+	if (! pipe->prepared || (chip->chip_status & VX_STAT_IS_STALE))
+		return;
+
+	size = runtime->buffer_size - snd_pcm_capture_avail(runtime);
+	if (! size)
+		return;
+	size = frames_to_bytes(runtime, size);
+	space = vx_query_hbuffer_size(chip, pipe);
+	if (space < 0)
+		goto _error;
+	if (size > space)
+		size = space;
+	size = (size / 3) * 3; /* align to 3 bytes */
+	if (size < DMA_READ_ALIGN)
+		goto _error;
+
+	/* keep the last 6 bytes, they will be read after disconnection */
+	count = size - DMA_READ_ALIGN;
+	/* read bytes until the current pointer reaches to the aligned position
+	 * for word-transfer
+	 */
+	while (count > 0) {
+		if ((pipe->hw_ptr % pipe->align) == 0)
+			break;
+		if (vx_wait_for_rx_full(chip) < 0)
+			goto _error;
+		vx_pcm_read_per_bytes(chip, runtime, pipe);
+		count -= 3;
+	}
+	if (count > 0) {
+		/* ok, let's accelerate! */
+		int align = pipe->align * 3;
+		space = (count / align) * align;
+		vx_pseudo_dma_read(chip, runtime, pipe, space);
+		count -= space;
+	}
+	/* read the rest of bytes */
+	while (count > 0) {
+		if (vx_wait_for_rx_full(chip) < 0)
+			goto _error;
+		vx_pcm_read_per_bytes(chip, runtime, pipe);
+		count -= 3;
+	}
+	/* disconnect the host, SIZE_HBUF command always switches to the stream mode */
+	vx_send_rih_nolock(chip, IRQ_CONNECT_STREAM_NEXT);
+	/* read the last pending 6 bytes */
+	count = DMA_READ_ALIGN;
+	while (count > 0) {
+		vx_pcm_read_per_bytes(chip, runtime, pipe);
+		count -= 3;
+	}
+	/* update the position */
+	pipe->transferred += size;
+	if (pipe->transferred >= pipe->period_bytes) {
+		pipe->transferred %= pipe->period_bytes;
+		snd_pcm_period_elapsed(subs);
+	}
+	return;
+
+ _error:
+	/* disconnect the host, SIZE_HBUF command always switches to the stream mode */
+	vx_send_rih_nolock(chip, IRQ_CONNECT_STREAM_NEXT);
+	return;
+}
+
+/*
+ * vx_pcm_capture_pointer - pointer callback for capture
+ */
+static snd_pcm_uframes_t vx_pcm_capture_pointer(snd_pcm_substream_t *subs)
+{
+	snd_pcm_runtime_t *runtime = subs->runtime;
+	vx_pipe_t *pipe = runtime->private_data;
+	return bytes_to_frames(runtime, pipe->hw_ptr);
+}
+
+/*
+ * operators for PCM capture
+ */
+static snd_pcm_ops_t vx_pcm_capture_ops = {
+	.open =		vx_pcm_capture_open,
+	.close =	vx_pcm_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	vx_pcm_hw_params,
+	.hw_free =	vx_pcm_hw_free,
+	.prepare =	vx_pcm_prepare,
+	.trigger =	vx_pcm_trigger,
+	.pointer =	vx_pcm_capture_pointer,
+	.page =		snd_pcm_get_vmalloc_page,
+};
+
+
+/*
+ * interrupt handler for pcm streams
+ */
+void vx_pcm_update_intr(vx_core_t *chip, unsigned int events)
+{
+	unsigned int i;
+	vx_pipe_t *pipe;
+
+#define EVENT_MASK	(END_OF_BUFFER_EVENTS_PENDING|ASYNC_EVENTS_PENDING)
+
+	if (events & EVENT_MASK) {
+		vx_init_rmh(&chip->irq_rmh, CMD_ASYNC);
+		if (events & ASYNC_EVENTS_PENDING)
+			chip->irq_rmh.Cmd[0] |= 0x00000001;	/* SEL_ASYNC_EVENTS */
+		if (events & END_OF_BUFFER_EVENTS_PENDING)
+			chip->irq_rmh.Cmd[0] |= 0x00000002;	/* SEL_END_OF_BUF_EVENTS */
+
+		if (vx_send_msg(chip, &chip->irq_rmh) < 0) {
+			snd_printdd(KERN_ERR "msg send error!!\n");
+			return;
+		}
+
+		i = 1;
+		while (i < chip->irq_rmh.LgStat) {
+			int p, buf, capture, eob;
+			p = chip->irq_rmh.Stat[i] & MASK_FIRST_FIELD;
+			capture = (chip->irq_rmh.Stat[i] & 0x400000) ? 1 : 0;
+			eob = (chip->irq_rmh.Stat[i] & 0x800000) ? 1 : 0;
+			i++;
+			if (events & ASYNC_EVENTS_PENDING)
+				i++;
+			buf = 1; /* force to transfer */
+			if (events & END_OF_BUFFER_EVENTS_PENDING) {
+				if (eob)
+					buf = chip->irq_rmh.Stat[i];
+				i++;
+			}
+			if (capture)
+				continue;
+			snd_assert(p >= 0 && (unsigned int)p < chip->audio_outs,);
+			pipe = chip->playback_pipes[p];
+			if (pipe && pipe->substream) {
+				vx_pcm_playback_update(chip, pipe->substream, pipe);
+				vx_pcm_playback_transfer(chip, pipe->substream, pipe, buf);
+			}
+		}
+	}
+
+	/* update the capture pcm pointers as frequently as possible */
+	for (i = 0; i < chip->audio_ins; i++) {
+		pipe = chip->capture_pipes[i];
+		if (pipe && pipe->substream)
+			vx_pcm_capture_update(chip, pipe->substream, pipe);
+	}
+}
+
+
+/*
+ * vx_init_audio_io - check the availabe audio i/o and allocate pipe arrays
+ */
+static int vx_init_audio_io(vx_core_t *chip)
+{
+	struct vx_rmh rmh;
+	int preferred;
+
+	vx_init_rmh(&rmh, CMD_SUPPORTED);
+	if (vx_send_msg(chip, &rmh) < 0) {
+		snd_printk(KERN_ERR "vx: cannot get the supported audio data\n");
+		return -ENXIO;
+	}
+
+	chip->audio_outs = rmh.Stat[0] & MASK_FIRST_FIELD;
+	chip->audio_ins = (rmh.Stat[0] >> (FIELD_SIZE*2)) & MASK_FIRST_FIELD;
+	chip->audio_info = rmh.Stat[1];
+
+	/* allocate pipes */
+	chip->playback_pipes = kmalloc(sizeof(vx_pipe_t *) * chip->audio_outs, GFP_KERNEL);
+	chip->capture_pipes = kmalloc(sizeof(vx_pipe_t *) * chip->audio_ins, GFP_KERNEL);
+	if (! chip->playback_pipes || ! chip->capture_pipes)
+		return -ENOMEM;
+
+	memset(chip->playback_pipes, 0, sizeof(vx_pipe_t *) * chip->audio_outs);
+	memset(chip->capture_pipes, 0, sizeof(vx_pipe_t *) * chip->audio_ins);
+
+	preferred = chip->ibl.size;
+	chip->ibl.size = 0;
+	vx_set_ibl(chip, &chip->ibl); /* query the info */
+	if (preferred > 0) {
+		chip->ibl.size = ((preferred + chip->ibl.granularity - 1) / chip->ibl.granularity) * chip->ibl.granularity;
+		if (chip->ibl.size > chip->ibl.max_size)
+			chip->ibl.size = chip->ibl.max_size;
+	} else
+		chip->ibl.size = chip->ibl.min_size; /* set to the minimum */
+	vx_set_ibl(chip, &chip->ibl);
+
+	return 0;
+}
+
+
+/*
+ * free callback for pcm
+ */
+static void snd_vx_pcm_free(snd_pcm_t *pcm)
+{
+	vx_core_t *chip = pcm->private_data;
+	chip->pcm[pcm->device] = NULL;
+	if (chip->playback_pipes) {
+		kfree(chip->playback_pipes);
+		chip->playback_pipes = NULL;
+	}
+	if (chip->capture_pipes) {
+		kfree(chip->capture_pipes);
+		chip->capture_pipes = NULL;
+	}
+}
+
+/*
+ * snd_vx_pcm_new - create and initialize a pcm
+ */
+int snd_vx_pcm_new(vx_core_t *chip)
+{
+	snd_pcm_t *pcm;
+	unsigned int i;
+	int err;
+
+	if ((err = vx_init_audio_io(chip)) < 0)
+		return err;
+
+	for (i = 0; i < chip->hw->num_codecs; i++) {
+		unsigned int outs, ins;
+		outs = chip->audio_outs > i * 2 ? 1 : 0;
+		ins = chip->audio_ins > i * 2 ? 1 : 0;
+		if (! outs && ! ins)
+			break;
+		err = snd_pcm_new(chip->card, "VX PCM", i,
+				  outs, ins, &pcm);
+		if (err < 0)
+			return err;
+		if (outs)
+			snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &vx_pcm_playback_ops);
+		if (ins)
+			snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &vx_pcm_capture_ops);
+
+		pcm->private_data = chip;
+		pcm->private_free = snd_vx_pcm_free;
+		pcm->info_flags = 0;
+		strcpy(pcm->name, chip->card->shortname);
+		chip->pcm[i] = pcm;
+	}
+
+	return 0;
+}
diff --git a/sound/drivers/vx/vx_uer.c b/sound/drivers/vx/vx_uer.c
new file mode 100644
index 0000000..1811471
--- /dev/null
+++ b/sound/drivers/vx/vx_uer.c
@@ -0,0 +1,321 @@
+/*
+ * Driver for Digigram VX soundcards
+ *
+ * IEC958 stuff
+ *
+ * Copyright (c) 2002 by 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/delay.h>
+#include <sound/core.h>
+#include <sound/vx_core.h>
+#include "vx_cmd.h"
+
+
+/*
+ * vx_modify_board_clock - tell the board that its clock has been modified
+ * @sync: DSP needs to resynchronize its FIFO
+ */
+static int vx_modify_board_clock(vx_core_t *chip, int sync)
+{
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_MODIFY_CLOCK);
+	/* Ask the DSP to resynchronize its FIFO. */
+	if (sync)
+		rmh.Cmd[0] |= CMD_MODIFY_CLOCK_S_BIT;
+	return vx_send_msg(chip, &rmh);
+}
+
+/*
+ * vx_modify_board_inputs - resync audio inputs
+ */
+static int vx_modify_board_inputs(vx_core_t *chip)
+{
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_RESYNC_AUDIO_INPUTS);
+        rmh.Cmd[0] |= 1 << 0; /* reference: AUDIO 0 */
+	return vx_send_msg(chip, &rmh);
+}
+
+/*
+ * vx_read_one_cbit - read one bit from UER config
+ * @index: the bit index
+ * returns 0 or 1.
+ */
+static int vx_read_one_cbit(vx_core_t *chip, int index)
+{
+	unsigned long flags;
+	int val;
+	spin_lock_irqsave(&chip->lock, flags);
+	if (chip->type >= VX_TYPE_VXPOCKET) {
+		vx_outb(chip, CSUER, 1); /* read */
+		vx_outb(chip, RUER, index & XX_UER_CBITS_OFFSET_MASK);
+		val = (vx_inb(chip, RUER) >> 7) & 0x01;
+	} else {
+		vx_outl(chip, CSUER, 1); /* read */
+		vx_outl(chip, RUER, index & XX_UER_CBITS_OFFSET_MASK);
+		val = (vx_inl(chip, RUER) >> 7) & 0x01;
+	}
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return val;
+}
+
+/*
+ * vx_write_one_cbit - write one bit to UER config
+ * @index: the bit index
+ * @val: bit value, 0 or 1
+ */
+static void vx_write_one_cbit(vx_core_t *chip, int index, int val)
+{
+	unsigned long flags;
+	val = !!val;	/* 0 or 1 */
+	spin_lock_irqsave(&chip->lock, flags);
+	if (vx_is_pcmcia(chip)) {
+		vx_outb(chip, CSUER, 0); /* write */
+		vx_outb(chip, RUER, (val << 7) | (index & XX_UER_CBITS_OFFSET_MASK));
+	} else {
+		vx_outl(chip, CSUER, 0); /* write */
+		vx_outl(chip, RUER, (val << 7) | (index & XX_UER_CBITS_OFFSET_MASK));
+	}
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+/*
+ * vx_read_uer_status - read the current UER status
+ * @mode: pointer to store the UER mode, VX_UER_MODE_XXX
+ *
+ * returns the frequency of UER, or 0 if not sync,
+ * or a negative error code.
+ */
+static int vx_read_uer_status(vx_core_t *chip, int *mode)
+{
+	int val, freq;
+
+	/* Default values */
+	freq = 0;
+
+	/* Read UER status */
+	if (vx_is_pcmcia(chip))
+	    val = vx_inb(chip, CSUER);
+	else
+	    val = vx_inl(chip, CSUER);
+	if (val < 0)
+		return val;
+	/* If clock is present, read frequency */
+	if (val & VX_SUER_CLOCK_PRESENT_MASK) {
+		switch (val & VX_SUER_FREQ_MASK) {
+		case VX_SUER_FREQ_32KHz_MASK:
+			freq = 32000;
+			break;
+		case VX_SUER_FREQ_44KHz_MASK:
+			freq = 44100;
+			break;
+		case VX_SUER_FREQ_48KHz_MASK:
+			freq = 48000;
+			break;
+		}
+        }
+	if (val & VX_SUER_DATA_PRESENT_MASK)
+		/* bit 0 corresponds to consumer/professional bit */
+		*mode = vx_read_one_cbit(chip, 0) ?
+			VX_UER_MODE_PROFESSIONAL : VX_UER_MODE_CONSUMER;
+	else
+		*mode = VX_UER_MODE_NOT_PRESENT;
+
+	return freq;
+}
+
+
+/*
+ * compute the sample clock value from frequency
+ *
+ * The formula is as follows:
+ *
+ *    HexFreq = (dword) ((double) ((double) 28224000 / (double) Frequency))
+ *    switch ( HexFreq & 0x00000F00 )
+ *    case 0x00000100: ;
+ *    case 0x00000200:
+ *    case 0x00000300: HexFreq -= 0x00000201 ;
+ *    case 0x00000400:
+ *    case 0x00000500:
+ *    case 0x00000600:
+ *    case 0x00000700: HexFreq = (dword) (((double) 28224000 / (double) (Frequency*2)) - 1)
+ *    default        : HexFreq = (dword) ((double) 28224000 / (double) (Frequency*4)) - 0x000001FF
+ */
+
+static int vx_calc_clock_from_freq(vx_core_t *chip, int freq)
+{
+#define XX_FECH48000                    0x0000004B
+#define XX_FECH32000                    0x00000171
+#define XX_FECH24000                    0x0000024B
+#define XX_FECH16000                    0x00000371
+#define XX_FECH12000                    0x0000044B
+#define XX_FECH8000                     0x00000571
+#define XX_FECH44100                    0x0000007F
+#define XX_FECH29400                    0x0000016F
+#define XX_FECH22050                    0x0000027F
+#define XX_FECH14000                    0x000003EF
+#define XX_FECH11025                    0x0000047F
+#define XX_FECH7350                     0x000005BF
+
+	switch (freq) {
+	case 48000:     return XX_FECH48000;
+	case 44100:     return XX_FECH44100;
+	case 32000:     return XX_FECH32000;
+	case 29400:     return XX_FECH29400;
+	case 24000:     return XX_FECH24000;
+	case 22050:     return XX_FECH22050;
+	case 16000:     return XX_FECH16000;
+	case 14000:     return XX_FECH14000;
+	case 12000:     return XX_FECH12000;
+	case 11025:     return XX_FECH11025;
+	case 8000:      return XX_FECH8000;
+	case 7350:      return XX_FECH7350;
+	default:        return freq;   /* The value is already correct */
+	}
+}
+
+
+/*
+ * vx_change_clock_source - change the clock source
+ * @source: the new source
+ */
+static void vx_change_clock_source(vx_core_t *chip, int source)
+{
+	unsigned long flags;
+
+	/* we mute DAC to prevent clicks */
+	vx_toggle_dac_mute(chip, 1);
+	spin_lock_irqsave(&chip->lock, flags);
+	chip->ops->set_clock_source(chip, source);
+	chip->clock_source = source;
+	spin_unlock_irqrestore(&chip->lock, flags);
+	/* unmute */
+	vx_toggle_dac_mute(chip, 0);
+}
+
+
+/*
+ * set the internal clock
+ */
+void vx_set_internal_clock(vx_core_t *chip, unsigned int freq)
+{
+	int clock;
+	unsigned long flags;
+	/* Get real clock value */
+	clock = vx_calc_clock_from_freq(chip, freq);
+	snd_printdd(KERN_DEBUG "set internal clock to 0x%x from freq %d\n", clock, freq);
+	spin_lock_irqsave(&chip->lock, flags);
+	if (vx_is_pcmcia(chip)) {
+		vx_outb(chip, HIFREQ, (clock >> 8) & 0x0f);
+		vx_outb(chip, LOFREQ, clock & 0xff);
+	} else {
+		vx_outl(chip, HIFREQ, (clock >> 8) & 0x0f);
+		vx_outl(chip, LOFREQ, clock & 0xff);
+	}
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+
+/*
+ * set the iec958 status bits
+ * @bits: 32-bit status bits
+ */
+void vx_set_iec958_status(vx_core_t *chip, unsigned int bits)
+{
+	int i;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return;
+
+	for (i = 0; i < 32; i++)
+		vx_write_one_cbit(chip, i, bits & (1 << i));
+}
+
+
+/*
+ * vx_set_clock - change the clock and audio source if necessary
+ */
+int vx_set_clock(vx_core_t *chip, unsigned int freq)
+{
+	int src_changed = 0;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return 0;
+
+	/* change the audio source if possible */
+	vx_sync_audio_source(chip);
+
+	if (chip->clock_mode == VX_CLOCK_MODE_EXTERNAL ||
+	    (chip->clock_mode == VX_CLOCK_MODE_AUTO &&
+	     chip->audio_source == VX_AUDIO_SRC_DIGITAL)) {
+		if (chip->clock_source != UER_SYNC) {
+			vx_change_clock_source(chip, UER_SYNC);
+			mdelay(6);
+			src_changed = 1;
+		}
+	} else if (chip->clock_mode == VX_CLOCK_MODE_INTERNAL ||
+		   (chip->clock_mode == VX_CLOCK_MODE_AUTO &&
+		    chip->audio_source != VX_AUDIO_SRC_DIGITAL)) {
+		if (chip->clock_source != INTERNAL_QUARTZ) {
+			vx_change_clock_source(chip, INTERNAL_QUARTZ);
+			src_changed = 1;
+		}
+		if (chip->freq == freq)
+			return 0;
+		vx_set_internal_clock(chip, freq);
+		if (src_changed)
+			vx_modify_board_inputs(chip);
+	}
+	if (chip->freq == freq)
+		return 0;
+	chip->freq = freq;
+	vx_modify_board_clock(chip, 1);
+	return 0;
+}
+
+
+/*
+ * vx_change_frequency - called from interrupt handler
+ */
+int vx_change_frequency(vx_core_t *chip)
+{
+	int freq;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return 0;
+
+	if (chip->clock_source == INTERNAL_QUARTZ)
+		return 0;
+	/*
+	 * Read the real UER board frequency
+	 */
+	freq = vx_read_uer_status(chip, &chip->uer_detected);
+	if (freq < 0)
+		return freq;
+	/*
+	 * The frequency computed by the DSP is good and
+	 * is different from the previous computed.
+	 */
+	if (freq == 48000 || freq == 44100 || freq == 32000)
+		chip->freq_detected = freq;
+
+	return 0;
+}