summary refs log tree commit diff
path: root/sound/core
diff options
context:
space:
mode:
Diffstat (limited to 'sound/core')
-rw-r--r--sound/core/Kconfig133
-rw-r--r--sound/core/Makefile33
-rw-r--r--sound/core/control.c1375
-rw-r--r--sound/core/control_compat.c412
-rw-r--r--sound/core/device.c240
-rw-r--r--sound/core/hwdep.c524
-rw-r--r--sound/core/hwdep_compat.c77
-rw-r--r--sound/core/info.c989
-rw-r--r--sound/core/info_oss.c137
-rw-r--r--sound/core/init.c887
-rw-r--r--sound/core/isadma.c103
-rw-r--r--sound/core/memalloc.c663
-rw-r--r--sound/core/memory.c306
-rw-r--r--sound/core/misc.c76
-rw-r--r--sound/core/oss/Makefile12
-rw-r--r--sound/core/oss/copy.c87
-rw-r--r--sound/core/oss/io.c134
-rw-r--r--sound/core/oss/linear.c158
-rw-r--r--sound/core/oss/mixer_oss.c1340
-rw-r--r--sound/core/oss/mulaw.c308
-rw-r--r--sound/core/oss/pcm_oss.c2530
-rw-r--r--sound/core/oss/pcm_plugin.c921
-rw-r--r--sound/core/oss/pcm_plugin.h250
-rw-r--r--sound/core/oss/plugin_ops.h536
-rw-r--r--sound/core/oss/rate.c378
-rw-r--r--sound/core/oss/route.c519
-rw-r--r--sound/core/pcm.c1074
-rw-r--r--sound/core/pcm_compat.c513
-rw-r--r--sound/core/pcm_lib.c2612
-rw-r--r--sound/core/pcm_memory.c363
-rw-r--r--sound/core/pcm_misc.c481
-rw-r--r--sound/core/pcm_native.c3364
-rw-r--r--sound/core/pcm_timer.c161
-rw-r--r--sound/core/rawmidi.c1680
-rw-r--r--sound/core/rawmidi_compat.c120
-rw-r--r--sound/core/rtctimer.c188
-rw-r--r--sound/core/seq/Makefile44
-rw-r--r--sound/core/seq/instr/Makefile23
-rw-r--r--sound/core/seq/instr/ainstr_fm.c156
-rw-r--r--sound/core/seq/instr/ainstr_gf1.c358
-rw-r--r--sound/core/seq/instr/ainstr_iw.c622
-rw-r--r--sound/core/seq/instr/ainstr_simple.c215
-rw-r--r--sound/core/seq/oss/Makefile10
-rw-r--r--sound/core/seq/oss/seq_oss.c317
-rw-r--r--sound/core/seq/oss/seq_oss_device.h198
-rw-r--r--sound/core/seq/oss/seq_oss_event.c447
-rw-r--r--sound/core/seq/oss/seq_oss_event.h112
-rw-r--r--sound/core/seq/oss/seq_oss_init.c555
-rw-r--r--sound/core/seq/oss/seq_oss_ioctl.c209
-rw-r--r--sound/core/seq/oss/seq_oss_midi.c710
-rw-r--r--sound/core/seq/oss/seq_oss_midi.h49
-rw-r--r--sound/core/seq/oss/seq_oss_readq.c234
-rw-r--r--sound/core/seq/oss/seq_oss_readq.h56
-rw-r--r--sound/core/seq/oss/seq_oss_rw.c216
-rw-r--r--sound/core/seq/oss/seq_oss_synth.c659
-rw-r--r--sound/core/seq/oss/seq_oss_synth.h49
-rw-r--r--sound/core/seq/oss/seq_oss_timer.c283
-rw-r--r--sound/core/seq/oss/seq_oss_timer.h70
-rw-r--r--sound/core/seq/oss/seq_oss_writeq.c170
-rw-r--r--sound/core/seq/oss/seq_oss_writeq.h50
-rw-r--r--sound/core/seq/seq.c147
-rw-r--r--sound/core/seq/seq_clientmgr.c2503
-rw-r--r--sound/core/seq/seq_clientmgr.h104
-rw-r--r--sound/core/seq/seq_compat.c137
-rw-r--r--sound/core/seq/seq_device.c575
-rw-r--r--sound/core/seq/seq_dummy.c273
-rw-r--r--sound/core/seq/seq_fifo.c264
-rw-r--r--sound/core/seq/seq_fifo.h72
-rw-r--r--sound/core/seq/seq_info.c75
-rw-r--r--sound/core/seq/seq_info.h36
-rw-r--r--sound/core/seq/seq_instr.c653
-rw-r--r--sound/core/seq/seq_lock.c48
-rw-r--r--sound/core/seq/seq_lock.h33
-rw-r--r--sound/core/seq/seq_memory.c510
-rw-r--r--sound/core/seq/seq_memory.h104
-rw-r--r--sound/core/seq/seq_midi.c489
-rw-r--r--sound/core/seq/seq_midi_emul.c735
-rw-r--r--sound/core/seq/seq_midi_event.c539
-rw-r--r--sound/core/seq/seq_ports.c674
-rw-r--r--sound/core/seq/seq_ports.h128
-rw-r--r--sound/core/seq/seq_prioq.c449
-rw-r--r--sound/core/seq/seq_prioq.h62
-rw-r--r--sound/core/seq/seq_queue.c783
-rw-r--r--sound/core/seq/seq_queue.h140
-rw-r--r--sound/core/seq/seq_system.c190
-rw-r--r--sound/core/seq/seq_system.h46
-rw-r--r--sound/core/seq/seq_timer.c435
-rw-r--r--sound/core/seq/seq_timer.h141
-rw-r--r--sound/core/seq/seq_virmidi.c551
-rw-r--r--sound/core/sgbuf.c111
-rw-r--r--sound/core/sound.c491
-rw-r--r--sound/core/sound_oss.c250
-rw-r--r--sound/core/timer.c1901
-rw-r--r--sound/core/timer_compat.c119
-rw-r--r--sound/core/wrappers.c50
95 files changed, 43314 insertions, 0 deletions
diff --git a/sound/core/Kconfig b/sound/core/Kconfig
new file mode 100644
index 000000000000..d1e800b9866d
--- /dev/null
+++ b/sound/core/Kconfig
@@ -0,0 +1,133 @@
+# ALSA soundcard-configuration
+config SND_TIMER
+	tristate
+	depends on SND
+
+config SND_PCM
+	tristate
+	select SND_TIMER
+	depends on SND
+
+config SND_HWDEP
+	tristate
+	depends on SND
+
+config SND_RAWMIDI
+	tristate
+	depends on SND
+
+config SND_SEQUENCER
+	tristate "Sequencer support"
+	depends on SND
+	select SND_TIMER
+	help
+	  Say Y or M to enable MIDI sequencer and router support.  This
+	  feature allows routing and enqueueing of MIDI events.  Events
+	  can be processed at a given time.
+
+	  Many programs require this feature, so you should enable it
+	  unless you know what you're doing.
+
+config SND_SEQ_DUMMY
+	tristate "Sequencer dummy client"
+	depends on SND_SEQUENCER
+	help
+	  Say Y here to enable the dummy sequencer client.  This client
+	  is a simple MIDI-through client: all normal input events are
+	  redirected to the output port immediately.
+
+	  You don't need this unless you want to connect many MIDI
+	  devices or applications together.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-seq-dummy.
+
+config SND_OSSEMUL
+	bool
+	depends on SND
+
+config SND_MIXER_OSS
+	tristate "OSS Mixer API"
+	depends on SND
+	select SND_OSSEMUL
+	help
+	  To enable OSS mixer API emulation (/dev/mixer*), say Y here
+	  and read <file:Documentation/sound/alsa/OSS-Emulation.txt>.
+
+	  Many programs still use the OSS API, so say Y.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-mixer-oss.
+
+config SND_PCM_OSS
+	tristate "OSS PCM (digital audio) API"
+	depends on SND
+	select SND_OSSEMUL
+	select SND_PCM
+	help
+	  To enable OSS digital audio (PCM) emulation (/dev/dsp*), say Y
+	  here and read <file:Documentation/sound/alsa/OSS-Emulation.txt>.
+
+	  Many programs still use the OSS API, so say Y.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-pcm-oss.
+
+config SND_SEQUENCER_OSS
+	bool "OSS Sequencer API"
+	depends on SND && SND_SEQUENCER
+	select SND_OSSEMUL
+	help
+	  Say Y here to enable OSS sequencer emulation (both
+	  /dev/sequencer and /dev/music interfaces).
+
+	  Many programs still use the OSS API, so say Y.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-seq-oss.
+
+config SND_RTCTIMER
+	tristate "RTC Timer support"
+	depends on SND && RTC
+	select SND_TIMER
+	help
+	  Say Y here to enable RTC timer support for ALSA.  ALSA uses
+	  the RTC timer as a precise timing source and maps the RTC
+	  timer to ALSA's timer interface.  The ALSA sequencer code also
+	  can use this timing source.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-rtctimer.
+
+config SND_VERBOSE_PRINTK
+	bool "Verbose printk"
+	depends on SND
+	help
+	  Say Y here to enable verbose log messages.  These messages
+	  will help to identify source file and position containing
+	  printed messages.
+
+	  You don't need this unless you're debugging ALSA.
+
+config SND_DEBUG
+	bool "Debug"
+	depends on SND
+	help
+	  Say Y here to enable ALSA debug code.
+
+config SND_DEBUG_MEMORY
+	bool "Debug memory"
+	depends on SND_DEBUG
+	help
+	  Say Y here to enable debugging of memory allocations.
+
+config SND_DEBUG_DETECT
+	bool "Debug detection"
+	depends on SND_DEBUG
+	help
+	  Say Y here to enable extra-verbose log messages printed when
+	  detecting devices.
+
+config SND_GENERIC_PM
+	bool
+	depends on SND
diff --git a/sound/core/Makefile b/sound/core/Makefile
new file mode 100644
index 000000000000..764ac184b223
--- /dev/null
+++ b/sound/core/Makefile
@@ -0,0 +1,33 @@
+#
+# Makefile for ALSA
+# Copyright (c) 1999,2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-objs     := sound.o init.o memory.o info.o control.o misc.o \
+                device.o wrappers.o
+ifeq ($(CONFIG_ISA),y)
+snd-objs     += isadma.o
+endif
+ifeq ($(CONFIG_SND_OSSEMUL),y)
+snd-objs     += sound_oss.o info_oss.o
+endif
+
+snd-pcm-objs := pcm.o pcm_native.o pcm_lib.o pcm_timer.o pcm_misc.o \
+		pcm_memory.o
+
+snd-page-alloc-objs := memalloc.o sgbuf.o
+
+snd-rawmidi-objs  := rawmidi.o
+snd-timer-objs    := timer.o
+snd-rtctimer-objs := rtctimer.o
+snd-hwdep-objs    := hwdep.o
+
+obj-$(CONFIG_SND) 		+= snd.o
+obj-$(CONFIG_SND_HWDEP)		+= snd-hwdep.o
+obj-$(CONFIG_SND_TIMER)		+= snd-timer.o
+obj-$(CONFIG_SND_RTCTIMER)	+= snd-rtctimer.o
+obj-$(CONFIG_SND_PCM)		+= snd-pcm.o snd-page-alloc.o
+obj-$(CONFIG_SND_RAWMIDI)	+= snd-rawmidi.o
+
+obj-$(CONFIG_SND_OSSEMUL)	+= oss/
+obj-$(CONFIG_SND_SEQUENCER)	+= seq/
diff --git a/sound/core/control.c b/sound/core/control.c
new file mode 100644
index 000000000000..f4ea6bff1dd3
--- /dev/null
+++ b/sound/core/control.c
@@ -0,0 +1,1375 @@
+/*
+ *  Routines for driver control interface
+ *  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/threads.h>
+#include <linux/interrupt.h>
+#include <linux/smp_lock.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/info.h>
+#include <sound/control.h>
+
+/* max number of user-defined controls */
+#define MAX_USER_CONTROLS	32
+
+typedef struct _snd_kctl_ioctl {
+	struct list_head list;		/* list of all ioctls */
+	snd_kctl_ioctl_func_t fioctl;
+} snd_kctl_ioctl_t;
+
+#define snd_kctl_ioctl(n) list_entry(n, snd_kctl_ioctl_t, list)
+
+static DECLARE_RWSEM(snd_ioctl_rwsem);
+static LIST_HEAD(snd_control_ioctls);
+#ifdef CONFIG_COMPAT
+static LIST_HEAD(snd_control_compat_ioctls);
+#endif
+
+static int snd_ctl_open(struct inode *inode, struct file *file)
+{
+	int cardnum = SNDRV_MINOR_CARD(iminor(inode));
+	unsigned long flags;
+	snd_card_t *card;
+	snd_ctl_file_t *ctl;
+	int err;
+
+	card = snd_cards[cardnum];
+	if (!card) {
+		err = -ENODEV;
+		goto __error1;
+	}
+	err = snd_card_file_add(card, file);
+	if (err < 0) {
+		err = -ENODEV;
+		goto __error1;
+	}
+	if (!try_module_get(card->module)) {
+		err = -EFAULT;
+		goto __error2;
+	}
+	ctl = kcalloc(1, sizeof(*ctl), GFP_KERNEL);
+	if (ctl == NULL) {
+		err = -ENOMEM;
+		goto __error;
+	}
+	INIT_LIST_HEAD(&ctl->events);
+	init_waitqueue_head(&ctl->change_sleep);
+	spin_lock_init(&ctl->read_lock);
+	ctl->card = card;
+	ctl->pid = current->pid;
+	file->private_data = ctl;
+	write_lock_irqsave(&card->ctl_files_rwlock, flags);
+	list_add_tail(&ctl->list, &card->ctl_files);
+	write_unlock_irqrestore(&card->ctl_files_rwlock, flags);
+	return 0;
+
+      __error:
+	module_put(card->module);
+      __error2:
+	snd_card_file_remove(card, file);
+      __error1:
+      	return err;
+}
+
+static void snd_ctl_empty_read_queue(snd_ctl_file_t * ctl)
+{
+	snd_kctl_event_t *cread;
+	
+	spin_lock(&ctl->read_lock);
+	while (!list_empty(&ctl->events)) {
+		cread = snd_kctl_event(ctl->events.next);
+		list_del(&cread->list);
+		kfree(cread);
+	}
+	spin_unlock(&ctl->read_lock);
+}
+
+static int snd_ctl_release(struct inode *inode, struct file *file)
+{
+	unsigned long flags;
+	struct list_head *list;
+	snd_card_t *card;
+	snd_ctl_file_t *ctl;
+	snd_kcontrol_t *control;
+	unsigned int idx;
+
+	ctl = file->private_data;
+	fasync_helper(-1, file, 0, &ctl->fasync);
+	file->private_data = NULL;
+	card = ctl->card;
+	write_lock_irqsave(&card->ctl_files_rwlock, flags);
+	list_del(&ctl->list);
+	write_unlock_irqrestore(&card->ctl_files_rwlock, flags);
+	down_write(&card->controls_rwsem);
+	list_for_each(list, &card->controls) {
+		control = snd_kcontrol(list);
+		for (idx = 0; idx < control->count; idx++)
+			if (control->vd[idx].owner == ctl)
+				control->vd[idx].owner = NULL;
+	}
+	up_write(&card->controls_rwsem);
+	snd_ctl_empty_read_queue(ctl);
+	kfree(ctl);
+	module_put(card->module);
+	snd_card_file_remove(card, file);
+	return 0;
+}
+
+void snd_ctl_notify(snd_card_t *card, unsigned int mask, snd_ctl_elem_id_t *id)
+{
+	unsigned long flags;
+	struct list_head *flist;
+	snd_ctl_file_t *ctl;
+	snd_kctl_event_t *ev;
+	
+	snd_runtime_check(card != NULL && id != NULL, return);
+	read_lock(&card->ctl_files_rwlock);
+#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
+	card->mixer_oss_change_count++;
+#endif
+	list_for_each(flist, &card->ctl_files) {
+		struct list_head *elist;
+		ctl = snd_ctl_file(flist);
+		if (!ctl->subscribed)
+			continue;
+		spin_lock_irqsave(&ctl->read_lock, flags);
+		list_for_each(elist, &ctl->events) {
+			ev = snd_kctl_event(elist);
+			if (ev->id.numid == id->numid) {
+				ev->mask |= mask;
+				goto _found;
+			}
+		}
+		ev = kcalloc(1, sizeof(*ev), GFP_ATOMIC);
+		if (ev) {
+			ev->id = *id;
+			ev->mask = mask;
+			list_add_tail(&ev->list, &ctl->events);
+		} else {
+			snd_printk(KERN_ERR "No memory available to allocate event\n");
+		}
+	_found:
+		wake_up(&ctl->change_sleep);
+		spin_unlock_irqrestore(&ctl->read_lock, flags);
+		kill_fasync(&ctl->fasync, SIGIO, POLL_IN);
+	}
+	read_unlock(&card->ctl_files_rwlock);
+}
+
+/**
+ * snd_ctl_new - create a control instance from the template
+ * @control: the control template
+ * @access: the default control access
+ *
+ * Allocates a new snd_kcontrol_t instance and copies the given template 
+ * to the new instance. It does not copy volatile data (access).
+ *
+ * Returns the pointer of the new instance, or NULL on failure.
+ */
+snd_kcontrol_t *snd_ctl_new(snd_kcontrol_t * control, unsigned int access)
+{
+	snd_kcontrol_t *kctl;
+	unsigned int idx;
+	
+	snd_runtime_check(control != NULL, return NULL);
+	snd_runtime_check(control->count > 0, return NULL);
+	kctl = kcalloc(1, sizeof(*kctl) + sizeof(snd_kcontrol_volatile_t) * control->count, GFP_KERNEL);
+	if (kctl == NULL)
+		return NULL;
+	*kctl = *control;
+	for (idx = 0; idx < kctl->count; idx++)
+		kctl->vd[idx].access = access;
+	return kctl;
+}
+
+/**
+ * snd_ctl_new1 - create a control instance from the template
+ * @ncontrol: the initialization record
+ * @private_data: the private data to set
+ *
+ * Allocates a new snd_kcontrol_t instance and initialize from the given 
+ * template.  When the access field of ncontrol is 0, it's assumed as
+ * READWRITE access. When the count field is 0, it's assumes as one.
+ *
+ * Returns the pointer of the newly generated instance, or NULL on failure.
+ */
+snd_kcontrol_t *snd_ctl_new1(snd_kcontrol_new_t * ncontrol, void *private_data)
+{
+	snd_kcontrol_t kctl;
+	unsigned int access;
+	
+	snd_runtime_check(ncontrol != NULL, return NULL);
+	snd_assert(ncontrol->info != NULL, return NULL);
+	memset(&kctl, 0, sizeof(kctl));
+	kctl.id.iface = ncontrol->iface;
+	kctl.id.device = ncontrol->device;
+	kctl.id.subdevice = ncontrol->subdevice;
+	if (ncontrol->name)
+		strlcpy(kctl.id.name, ncontrol->name, sizeof(kctl.id.name));
+	kctl.id.index = ncontrol->index;
+	kctl.count = ncontrol->count ? ncontrol->count : 1;
+	access = ncontrol->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE :
+		 (ncontrol->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|SNDRV_CTL_ELEM_ACCESS_INACTIVE|
+		 		      SNDRV_CTL_ELEM_ACCESS_DINDIRECT|SNDRV_CTL_ELEM_ACCESS_INDIRECT));
+	kctl.info = ncontrol->info;
+	kctl.get = ncontrol->get;
+	kctl.put = ncontrol->put;
+	kctl.private_value = ncontrol->private_value;
+	kctl.private_data = private_data;
+	return snd_ctl_new(&kctl, access);
+}
+
+/**
+ * snd_ctl_free_one - release the control instance
+ * @kcontrol: the control instance
+ *
+ * Releases the control instance created via snd_ctl_new()
+ * or snd_ctl_new1().
+ * Don't call this after the control was added to the card.
+ */
+void snd_ctl_free_one(snd_kcontrol_t * kcontrol)
+{
+	if (kcontrol) {
+		if (kcontrol->private_free)
+			kcontrol->private_free(kcontrol);
+		kfree(kcontrol);
+	}
+}
+
+static unsigned int snd_ctl_hole_check(snd_card_t * card,
+				       unsigned int count)
+{
+	struct list_head *list;
+	snd_kcontrol_t *kctl;
+
+	list_for_each(list, &card->controls) {
+		kctl = snd_kcontrol(list);
+		if ((kctl->id.numid <= card->last_numid &&
+		     kctl->id.numid + kctl->count > card->last_numid) ||
+		    (kctl->id.numid <= card->last_numid + count - 1 &&
+		     kctl->id.numid + kctl->count > card->last_numid + count - 1))
+		    	return card->last_numid = kctl->id.numid + kctl->count - 1;
+	}
+	return card->last_numid;
+}
+
+static int snd_ctl_find_hole(snd_card_t * card, unsigned int count)
+{
+	unsigned int last_numid, iter = 100000;
+
+	last_numid = card->last_numid;
+	while (last_numid != snd_ctl_hole_check(card, count)) {
+		if (--iter == 0) {
+			/* this situation is very unlikely */
+			snd_printk(KERN_ERR "unable to allocate new control numid\n");
+			return -ENOMEM;
+		}
+		last_numid = card->last_numid;
+	}
+	return 0;
+}
+
+/**
+ * snd_ctl_add - add the control instance to the card
+ * @card: the card instance
+ * @kcontrol: the control instance to add
+ *
+ * Adds the control instance created via snd_ctl_new() or
+ * snd_ctl_new1() to the given card. Assigns also an unique
+ * numid used for fast search.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ *
+ * It frees automatically the control which cannot be added.
+ */
+int snd_ctl_add(snd_card_t * card, snd_kcontrol_t * kcontrol)
+{
+	snd_ctl_elem_id_t id;
+	unsigned int idx;
+
+	snd_runtime_check(card != NULL && kcontrol != NULL, return -EINVAL);
+	snd_assert(kcontrol->info != NULL, return -EINVAL);
+	id = kcontrol->id;
+	down_write(&card->controls_rwsem);
+	if (snd_ctl_find_id(card, &id)) {
+		up_write(&card->controls_rwsem);
+		snd_ctl_free_one(kcontrol);
+		snd_printd(KERN_ERR "control %i:%i:%i:%s:%i is already present\n",
+					id.iface,
+					id.device,
+					id.subdevice,
+					id.name,
+					id.index);
+		return -EBUSY;
+	}
+	if (snd_ctl_find_hole(card, kcontrol->count) < 0) {
+		up_write(&card->controls_rwsem);
+		snd_ctl_free_one(kcontrol);
+		return -ENOMEM;
+	}
+	list_add_tail(&kcontrol->list, &card->controls);
+	card->controls_count += kcontrol->count;
+	kcontrol->id.numid = card->last_numid + 1;
+	card->last_numid += kcontrol->count;
+	up_write(&card->controls_rwsem);
+	for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++)
+		snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id);
+	return 0;
+}
+
+/**
+ * snd_ctl_remove - remove the control from the card and release it
+ * @card: the card instance
+ * @kcontrol: the control instance to remove
+ *
+ * Removes the control from the card and then releases the instance.
+ * You don't need to call snd_ctl_free_one(). You must be in
+ * the write lock - down_write(&card->controls_rwsem).
+ * 
+ * Returns 0 if successful, or a negative error code on failure.
+ */
+int snd_ctl_remove(snd_card_t * card, snd_kcontrol_t * kcontrol)
+{
+	snd_ctl_elem_id_t id;
+	unsigned int idx;
+
+	snd_runtime_check(card != NULL && kcontrol != NULL, return -EINVAL);
+	list_del(&kcontrol->list);
+	card->controls_count -= kcontrol->count;
+	id = kcontrol->id;
+	for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++)
+		snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_REMOVE, &id);
+	snd_ctl_free_one(kcontrol);
+	return 0;
+}
+
+/**
+ * snd_ctl_remove_id - remove the control of the given id and release it
+ * @card: the card instance
+ * @id: the control id to remove
+ *
+ * Finds the control instance with the given id, removes it from the
+ * card list and releases it.
+ * 
+ * Returns 0 if successful, or a negative error code on failure.
+ */
+int snd_ctl_remove_id(snd_card_t * card, snd_ctl_elem_id_t *id)
+{
+	snd_kcontrol_t *kctl;
+	int ret;
+
+	down_write(&card->controls_rwsem);
+	kctl = snd_ctl_find_id(card, id);
+	if (kctl == NULL) {
+		up_write(&card->controls_rwsem);
+		return -ENOENT;
+	}
+	ret = snd_ctl_remove(card, kctl);
+	up_write(&card->controls_rwsem);
+	return ret;
+}
+
+/**
+ * snd_ctl_remove_unlocked_id - remove the unlocked control of the given id and release it
+ * @file: active control handle
+ * @id: the control id to remove
+ *
+ * Finds the control instance with the given id, removes it from the
+ * card list and releases it.
+ * 
+ * Returns 0 if successful, or a negative error code on failure.
+ */
+static int snd_ctl_remove_unlocked_id(snd_ctl_file_t * file, snd_ctl_elem_id_t *id)
+{
+	snd_card_t *card = file->card;
+	snd_kcontrol_t *kctl;
+	int idx, ret;
+
+	down_write(&card->controls_rwsem);
+	kctl = snd_ctl_find_id(card, id);
+	if (kctl == NULL) {
+		up_write(&card->controls_rwsem);
+		return -ENOENT;
+	}
+	for (idx = 0; idx < kctl->count; idx++)
+		if (kctl->vd[idx].owner != NULL && kctl->vd[idx].owner != file) {
+			up_write(&card->controls_rwsem);
+			return -EBUSY;
+		}
+	ret = snd_ctl_remove(card, kctl);
+	up_write(&card->controls_rwsem);
+	return ret;
+}
+
+/**
+ * snd_ctl_rename_id - replace the id of a control on the card
+ * @card: the card instance
+ * @src_id: the old id
+ * @dst_id: the new id
+ *
+ * Finds the control with the old id from the card, and replaces the
+ * id with the new one.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_ctl_rename_id(snd_card_t * card, snd_ctl_elem_id_t *src_id, snd_ctl_elem_id_t *dst_id)
+{
+	snd_kcontrol_t *kctl;
+
+	down_write(&card->controls_rwsem);
+	kctl = snd_ctl_find_id(card, src_id);
+	if (kctl == NULL) {
+		up_write(&card->controls_rwsem);
+		return -ENOENT;
+	}
+	kctl->id = *dst_id;
+	kctl->id.numid = card->last_numid + 1;
+	card->last_numid += kctl->count;
+	up_write(&card->controls_rwsem);
+	return 0;
+}
+
+/**
+ * snd_ctl_find_numid - find the control instance with the given number-id
+ * @card: the card instance
+ * @numid: the number-id to search
+ *
+ * Finds the control instance with the given number-id from the card.
+ *
+ * Returns the pointer of the instance if found, or NULL if not.
+ *
+ * The caller must down card->controls_rwsem before calling this function
+ * (if the race condition can happen).
+ */
+snd_kcontrol_t *snd_ctl_find_numid(snd_card_t * card, unsigned int numid)
+{
+	struct list_head *list;
+	snd_kcontrol_t *kctl;
+
+	snd_runtime_check(card != NULL && numid != 0, return NULL);
+	list_for_each(list, &card->controls) {
+		kctl = snd_kcontrol(list);
+		if (kctl->id.numid <= numid && kctl->id.numid + kctl->count > numid)
+			return kctl;
+	}
+	return NULL;
+}
+
+/**
+ * snd_ctl_find_id - find the control instance with the given id
+ * @card: the card instance
+ * @id: the id to search
+ *
+ * Finds the control instance with the given id from the card.
+ *
+ * Returns the pointer of the instance if found, or NULL if not.
+ *
+ * The caller must down card->controls_rwsem before calling this function
+ * (if the race condition can happen).
+ */
+snd_kcontrol_t *snd_ctl_find_id(snd_card_t * card, snd_ctl_elem_id_t *id)
+{
+	struct list_head *list;
+	snd_kcontrol_t *kctl;
+
+	snd_runtime_check(card != NULL && id != NULL, return NULL);
+	if (id->numid != 0)
+		return snd_ctl_find_numid(card, id->numid);
+	list_for_each(list, &card->controls) {
+		kctl = snd_kcontrol(list);
+		if (kctl->id.iface != id->iface)
+			continue;
+		if (kctl->id.device != id->device)
+			continue;
+		if (kctl->id.subdevice != id->subdevice)
+			continue;
+		if (strncmp(kctl->id.name, id->name, sizeof(kctl->id.name)))
+			continue;
+		if (kctl->id.index > id->index)
+			continue;
+		if (kctl->id.index + kctl->count <= id->index)
+			continue;
+		return kctl;
+	}
+	return NULL;
+}
+
+static int snd_ctl_card_info(snd_card_t * card, snd_ctl_file_t * ctl,
+			     unsigned int cmd, void __user *arg)
+{
+	snd_ctl_card_info_t *info;
+
+	info = kcalloc(1, sizeof(*info), GFP_KERNEL);
+	if (! info)
+		return -ENOMEM;
+	down_read(&snd_ioctl_rwsem);
+	info->card = card->number;
+	strlcpy(info->id, card->id, sizeof(info->id));
+	strlcpy(info->driver, card->driver, sizeof(info->driver));
+	strlcpy(info->name, card->shortname, sizeof(info->name));
+	strlcpy(info->longname, card->longname, sizeof(info->longname));
+	strlcpy(info->mixername, card->mixername, sizeof(info->mixername));
+	strlcpy(info->components, card->components, sizeof(info->components));
+	up_read(&snd_ioctl_rwsem);
+	if (copy_to_user(arg, info, sizeof(snd_ctl_card_info_t))) {
+		kfree(info);
+		return -EFAULT;
+	}
+	kfree(info);
+	return 0;
+}
+
+static int snd_ctl_elem_list(snd_card_t *card, snd_ctl_elem_list_t __user *_list)
+{
+	struct list_head *plist;
+	snd_ctl_elem_list_t list;
+	snd_kcontrol_t *kctl;
+	snd_ctl_elem_id_t *dst, *id;
+	unsigned int offset, space, first, jidx;
+	
+	if (copy_from_user(&list, _list, sizeof(list)))
+		return -EFAULT;
+	offset = list.offset;
+	space = list.space;
+	first = 0;
+	/* try limit maximum space */
+	if (space > 16384)
+		return -ENOMEM;
+	if (space > 0) {
+		/* allocate temporary buffer for atomic operation */
+		dst = vmalloc(space * sizeof(snd_ctl_elem_id_t));
+		if (dst == NULL)
+			return -ENOMEM;
+		down_read(&card->controls_rwsem);
+		list.count = card->controls_count;
+		plist = card->controls.next;
+		while (plist != &card->controls) {
+			if (offset == 0)
+				break;
+			kctl = snd_kcontrol(plist);
+			if (offset < kctl->count)
+				break;
+			offset -= kctl->count;
+			plist = plist->next;
+		}
+		list.used = 0;
+		id = dst;
+		while (space > 0 && plist != &card->controls) {
+			kctl = snd_kcontrol(plist);
+			for (jidx = offset; space > 0 && jidx < kctl->count; jidx++) {
+				snd_ctl_build_ioff(id, kctl, jidx);
+				id++;
+				space--;
+				list.used++;
+			}
+			plist = plist->next;
+			offset = 0;
+		}
+		up_read(&card->controls_rwsem);
+		if (list.used > 0 && copy_to_user(list.pids, dst, list.used * sizeof(snd_ctl_elem_id_t))) {
+			vfree(dst);
+			return -EFAULT;
+		}
+		vfree(dst);
+	} else {
+		down_read(&card->controls_rwsem);
+		list.count = card->controls_count;
+		up_read(&card->controls_rwsem);
+	}
+	if (copy_to_user(_list, &list, sizeof(list)))
+		return -EFAULT;
+	return 0;
+}
+
+static int snd_ctl_elem_info(snd_ctl_file_t *ctl, snd_ctl_elem_info_t *info)
+{
+	snd_card_t *card = ctl->card;
+	snd_kcontrol_t *kctl;
+	snd_kcontrol_volatile_t *vd;
+	unsigned int index_offset;
+	int result;
+	
+	down_read(&card->controls_rwsem);
+	kctl = snd_ctl_find_id(card, &info->id);
+	if (kctl == NULL) {
+		up_read(&card->controls_rwsem);
+		return -ENOENT;
+	}
+#ifdef CONFIG_SND_DEBUG
+	info->access = 0;
+#endif
+	result = kctl->info(kctl, info);
+	if (result >= 0) {
+		snd_assert(info->access == 0, );
+		index_offset = snd_ctl_get_ioff(kctl, &info->id);
+		vd = &kctl->vd[index_offset];
+		snd_ctl_build_ioff(&info->id, kctl, index_offset);
+		info->access = vd->access;
+		if (vd->owner) {
+			info->access |= SNDRV_CTL_ELEM_ACCESS_LOCK;
+			if (vd->owner == ctl)
+				info->access |= SNDRV_CTL_ELEM_ACCESS_OWNER;
+			info->owner = vd->owner_pid;
+		} else {
+			info->owner = -1;
+		}
+	}
+	up_read(&card->controls_rwsem);
+	return result;
+}
+
+static int snd_ctl_elem_info_user(snd_ctl_file_t *ctl, snd_ctl_elem_info_t __user *_info)
+{
+	snd_ctl_elem_info_t info;
+	int result;
+
+	if (copy_from_user(&info, _info, sizeof(info)))
+		return -EFAULT;
+	result = snd_ctl_elem_info(ctl, &info);
+	if (result >= 0)
+		if (copy_to_user(_info, &info, sizeof(info)))
+			return -EFAULT;
+	return result;
+}
+
+int snd_ctl_elem_read(snd_card_t *card, snd_ctl_elem_value_t *control)
+{
+	snd_kcontrol_t *kctl;
+	snd_kcontrol_volatile_t *vd;
+	unsigned int index_offset;
+	int result, indirect;
+
+	down_read(&card->controls_rwsem);
+	kctl = snd_ctl_find_id(card, &control->id);
+	if (kctl == NULL) {
+		result = -ENOENT;
+	} else {
+		index_offset = snd_ctl_get_ioff(kctl, &control->id);
+		vd = &kctl->vd[index_offset];
+		indirect = vd->access & SNDRV_CTL_ELEM_ACCESS_INDIRECT ? 1 : 0;
+		if (control->indirect != indirect) {
+			result = -EACCES;
+		} else {
+			if ((vd->access & SNDRV_CTL_ELEM_ACCESS_READ) && kctl->get != NULL) {
+				snd_ctl_build_ioff(&control->id, kctl, index_offset);
+				result = kctl->get(kctl, control);
+			} else {
+				result = -EPERM;
+			}
+		}
+	}
+	up_read(&card->controls_rwsem);
+	return result;
+}
+
+static int snd_ctl_elem_read_user(snd_card_t *card, snd_ctl_elem_value_t __user *_control)
+{
+	snd_ctl_elem_value_t *control;
+	int result;
+	
+	control = kmalloc(sizeof(*control), GFP_KERNEL);
+	if (control == NULL)
+		return -ENOMEM;	
+	if (copy_from_user(control, _control, sizeof(*control))) {
+		kfree(control);
+		return -EFAULT;
+	}
+	result = snd_ctl_elem_read(card, control);
+	if (result >= 0)
+		if (copy_to_user(_control, control, sizeof(*control)))
+			result = -EFAULT;
+	kfree(control);
+	return result;
+}
+
+int snd_ctl_elem_write(snd_card_t *card, snd_ctl_file_t *file, snd_ctl_elem_value_t *control)
+{
+	snd_kcontrol_t *kctl;
+	snd_kcontrol_volatile_t *vd;
+	unsigned int index_offset;
+	int result, indirect;
+
+	down_read(&card->controls_rwsem);
+	kctl = snd_ctl_find_id(card, &control->id);
+	if (kctl == NULL) {
+		result = -ENOENT;
+	} else {
+		index_offset = snd_ctl_get_ioff(kctl, &control->id);
+		vd = &kctl->vd[index_offset];
+		indirect = vd->access & SNDRV_CTL_ELEM_ACCESS_INDIRECT ? 1 : 0;
+		if (control->indirect != indirect) {
+			result = -EACCES;
+		} else {
+			if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_WRITE) ||
+			    kctl->put == NULL ||
+			    (file && vd->owner != NULL && vd->owner != file)) {
+				result = -EPERM;
+			} else {
+				snd_ctl_build_ioff(&control->id, kctl, index_offset);
+				result = kctl->put(kctl, control);
+			}
+			if (result > 0) {
+				up_read(&card->controls_rwsem);
+				snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &control->id);
+				return 0;
+			}
+		}
+	}
+	up_read(&card->controls_rwsem);
+	return result;
+}
+
+static int snd_ctl_elem_write_user(snd_ctl_file_t *file, snd_ctl_elem_value_t __user *_control)
+{
+	snd_ctl_elem_value_t *control;
+	int result;
+
+	control = kmalloc(sizeof(*control), GFP_KERNEL);
+	if (control == NULL)
+		return -ENOMEM;	
+	if (copy_from_user(control, _control, sizeof(*control))) {
+		kfree(control);
+		return -EFAULT;
+	}
+	result = snd_ctl_elem_write(file->card, file, control);
+	if (result >= 0)
+		if (copy_to_user(_control, control, sizeof(*control)))
+			result = -EFAULT;
+	kfree(control);
+	return result;
+}
+
+static int snd_ctl_elem_lock(snd_ctl_file_t *file, snd_ctl_elem_id_t __user *_id)
+{
+	snd_card_t *card = file->card;
+	snd_ctl_elem_id_t id;
+	snd_kcontrol_t *kctl;
+	snd_kcontrol_volatile_t *vd;
+	int result;
+	
+	if (copy_from_user(&id, _id, sizeof(id)))
+		return -EFAULT;
+	down_write(&card->controls_rwsem);
+	kctl = snd_ctl_find_id(card, &id);
+	if (kctl == NULL) {
+		result = -ENOENT;
+	} else {
+		vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)];
+		if (vd->owner != NULL)
+			result = -EBUSY;
+		else {
+			vd->owner = file;
+			vd->owner_pid = current->pid;
+			result = 0;
+		}
+	}
+	up_write(&card->controls_rwsem);
+	return result;
+}
+
+static int snd_ctl_elem_unlock(snd_ctl_file_t *file, snd_ctl_elem_id_t __user *_id)
+{
+	snd_card_t *card = file->card;
+	snd_ctl_elem_id_t id;
+	snd_kcontrol_t *kctl;
+	snd_kcontrol_volatile_t *vd;
+	int result;
+	
+	if (copy_from_user(&id, _id, sizeof(id)))
+		return -EFAULT;
+	down_write(&card->controls_rwsem);
+	kctl = snd_ctl_find_id(card, &id);
+	if (kctl == NULL) {
+		result = -ENOENT;
+	} else {
+		vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)];
+		if (vd->owner == NULL)
+			result = -EINVAL;
+		else if (vd->owner != file)
+			result = -EPERM;
+		else {
+			vd->owner = NULL;
+			vd->owner_pid = 0;
+			result = 0;
+		}
+	}
+	up_write(&card->controls_rwsem);
+	return result;
+}
+
+struct user_element {
+	snd_ctl_elem_info_t info;
+	void *elem_data;		/* element data */
+	unsigned long elem_data_size;	/* size of element data in bytes */
+	void *priv_data;		/* private data (like strings for enumerated type) */
+	unsigned long priv_data_size;	/* size of private data in bytes */
+};
+
+static int snd_ctl_elem_user_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	struct user_element *ue = kcontrol->private_data;
+
+	*uinfo = ue->info;
+	return 0;
+}
+
+static int snd_ctl_elem_user_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	struct user_element *ue = kcontrol->private_data;
+
+	memcpy(&ucontrol->value, ue->elem_data, ue->elem_data_size);
+	return 0;
+}
+
+static int snd_ctl_elem_user_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+	int change;
+	struct user_element *ue = kcontrol->private_data;
+	
+	change = memcmp(&ucontrol->value, ue->elem_data, ue->elem_data_size) != 0;
+	if (change)
+		memcpy(ue->elem_data, &ucontrol->value, ue->elem_data_size);
+	return change;
+}
+
+static void snd_ctl_elem_user_free(snd_kcontrol_t * kcontrol)
+{
+	kfree(kcontrol->private_data);
+}
+
+static int snd_ctl_elem_add(snd_ctl_file_t *file, snd_ctl_elem_info_t *info, int replace)
+{
+	snd_card_t *card = file->card;
+	snd_kcontrol_t kctl, *_kctl;
+	unsigned int access;
+	long private_size;
+	struct user_element *ue;
+	int idx, err;
+	
+	if (card->user_ctl_count >= MAX_USER_CONTROLS)
+		return -ENOMEM;
+	if (info->count > 1024)
+		return -EINVAL;
+	access = info->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE :
+		(info->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|SNDRV_CTL_ELEM_ACCESS_INACTIVE));
+	info->id.numid = 0;
+	memset(&kctl, 0, sizeof(kctl));
+	down_write(&card->controls_rwsem);
+	_kctl = snd_ctl_find_id(card, &info->id);
+	err = 0;
+	if (_kctl) {
+		if (replace)
+			err = snd_ctl_remove(card, _kctl);
+		else
+			err = -EBUSY;
+	} else {
+		if (replace)
+			err = -ENOENT;
+	}
+	up_write(&card->controls_rwsem);
+	if (err < 0)
+		return err;
+	memcpy(&kctl.id, &info->id, sizeof(info->id));
+	kctl.count = info->owner ? info->owner : 1;
+	access |= SNDRV_CTL_ELEM_ACCESS_USER;
+	kctl.info = snd_ctl_elem_user_info;
+	if (access & SNDRV_CTL_ELEM_ACCESS_READ)
+		kctl.get = snd_ctl_elem_user_get;
+	if (access & SNDRV_CTL_ELEM_ACCESS_WRITE)
+		kctl.put = snd_ctl_elem_user_put;
+	switch (info->type) {
+	case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+		private_size = sizeof(char);
+		if (info->count > 128)
+			return -EINVAL;
+		break;
+	case SNDRV_CTL_ELEM_TYPE_INTEGER:
+		private_size = sizeof(long);
+		if (info->count > 128)
+			return -EINVAL;
+		break;
+	case SNDRV_CTL_ELEM_TYPE_INTEGER64:
+		private_size = sizeof(long long);
+		if (info->count > 64)
+			return -EINVAL;
+		break;
+	case SNDRV_CTL_ELEM_TYPE_BYTES:
+		private_size = sizeof(unsigned char);
+		if (info->count > 512)
+			return -EINVAL;
+		break;
+	case SNDRV_CTL_ELEM_TYPE_IEC958:
+		private_size = sizeof(struct sndrv_aes_iec958);
+		if (info->count != 1)
+			return -EINVAL;
+		break;
+	default:
+		return -EINVAL;
+	}
+	private_size *= info->count;
+	ue = kcalloc(1, sizeof(struct user_element) + private_size, GFP_KERNEL);
+	if (ue == NULL)
+		return -ENOMEM;
+	ue->info = *info;
+	ue->elem_data = (char *)ue + sizeof(*ue);
+	ue->elem_data_size = private_size;
+	kctl.private_free = snd_ctl_elem_user_free;
+	_kctl = snd_ctl_new(&kctl, access);
+	if (_kctl == NULL) {
+		kfree(_kctl->private_data);
+		return -ENOMEM;
+	}
+	_kctl->private_data = ue;
+	for (idx = 0; idx < _kctl->count; idx++)
+		_kctl->vd[idx].owner = file;
+	err = snd_ctl_add(card, _kctl);
+	if (err < 0) {
+		snd_ctl_free_one(_kctl);
+		return err;
+	}
+
+	down_write(&card->controls_rwsem);
+	card->user_ctl_count++;
+	up_write(&card->controls_rwsem);
+
+	return 0;
+}
+
+static int snd_ctl_elem_add_user(snd_ctl_file_t *file, snd_ctl_elem_info_t __user *_info, int replace)
+{
+	snd_ctl_elem_info_t info;
+	if (copy_from_user(&info, _info, sizeof(info)))
+		return -EFAULT;
+	return snd_ctl_elem_add(file, &info, replace);
+}
+
+static int snd_ctl_elem_remove(snd_ctl_file_t *file, snd_ctl_elem_id_t __user *_id)
+{
+	snd_ctl_elem_id_t id;
+	int err;
+
+	if (copy_from_user(&id, _id, sizeof(id)))
+		return -EFAULT;
+	err = snd_ctl_remove_unlocked_id(file, &id);
+	if (! err) {
+		snd_card_t *card = file->card;
+		down_write(&card->controls_rwsem);
+		card->user_ctl_count--;
+		up_write(&card->controls_rwsem);
+	}
+	return err;
+}
+
+static int snd_ctl_subscribe_events(snd_ctl_file_t *file, int __user *ptr)
+{
+	int subscribe;
+	if (get_user(subscribe, ptr))
+		return -EFAULT;
+	if (subscribe < 0) {
+		subscribe = file->subscribed;
+		if (put_user(subscribe, ptr))
+			return -EFAULT;
+		return 0;
+	}
+	if (subscribe) {
+		file->subscribed = 1;
+		return 0;
+	} else if (file->subscribed) {
+		snd_ctl_empty_read_queue(file);
+		file->subscribed = 0;
+	}
+	return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ * change the power state
+ */
+static int snd_ctl_set_power_state(snd_card_t *card, unsigned int power_state)
+{
+	switch (power_state) {
+	case SNDRV_CTL_POWER_D0:
+		if (card->power_state != power_state) {
+			card->pm_resume(card);
+			snd_power_change_state(card, power_state);
+		}
+		break;
+	case SNDRV_CTL_POWER_D3hot:
+		if (card->power_state != power_state) {
+			card->pm_suspend(card, PMSG_SUSPEND);
+			snd_power_change_state(card, power_state);
+		}
+		break;
+	case SNDRV_CTL_POWER_D1:
+	case SNDRV_CTL_POWER_D2:
+	case SNDRV_CTL_POWER_D3cold:
+		/* not supported yet */
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+#endif
+
+static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	snd_ctl_file_t *ctl;
+	snd_card_t *card;
+	struct list_head *list;
+	snd_kctl_ioctl_t *p;
+	void __user *argp = (void __user *)arg;
+	int __user *ip = argp;
+	int err;
+
+	ctl = file->private_data;
+	card = ctl->card;
+	snd_assert(card != NULL, return -ENXIO);
+	switch (cmd) {
+	case SNDRV_CTL_IOCTL_PVERSION:
+		return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0;
+	case SNDRV_CTL_IOCTL_CARD_INFO:
+		return snd_ctl_card_info(card, ctl, cmd, argp);
+	case SNDRV_CTL_IOCTL_ELEM_LIST:
+		return snd_ctl_elem_list(ctl->card, argp);
+	case SNDRV_CTL_IOCTL_ELEM_INFO:
+		return snd_ctl_elem_info_user(ctl, argp);
+	case SNDRV_CTL_IOCTL_ELEM_READ:
+		return snd_ctl_elem_read_user(ctl->card, argp);
+	case SNDRV_CTL_IOCTL_ELEM_WRITE:
+		return snd_ctl_elem_write_user(ctl, argp);
+	case SNDRV_CTL_IOCTL_ELEM_LOCK:
+		return snd_ctl_elem_lock(ctl, argp);
+	case SNDRV_CTL_IOCTL_ELEM_UNLOCK:
+		return snd_ctl_elem_unlock(ctl, argp);
+	case SNDRV_CTL_IOCTL_ELEM_ADD:
+		return snd_ctl_elem_add_user(ctl, argp, 0);
+	case SNDRV_CTL_IOCTL_ELEM_REPLACE:
+		return snd_ctl_elem_add_user(ctl, argp, 1);
+	case SNDRV_CTL_IOCTL_ELEM_REMOVE:
+		return snd_ctl_elem_remove(ctl, argp);
+	case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
+		return snd_ctl_subscribe_events(ctl, ip);
+	case SNDRV_CTL_IOCTL_POWER:
+		if (get_user(err, ip))
+			return -EFAULT;
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+#ifdef CONFIG_PM
+		if (card->pm_suspend && card->pm_resume) {
+			snd_power_lock(card);
+			err = snd_ctl_set_power_state(card, err);
+			snd_power_unlock(card);
+		} else
+#endif
+			err = -ENOPROTOOPT;
+		return err;
+	case SNDRV_CTL_IOCTL_POWER_STATE:
+#ifdef CONFIG_PM
+		return put_user(card->power_state, ip) ? -EFAULT : 0;
+#else
+		return put_user(SNDRV_CTL_POWER_D0, ip) ? -EFAULT : 0;
+#endif
+	}
+	down_read(&snd_ioctl_rwsem);
+	list_for_each(list, &snd_control_ioctls) {
+		p = list_entry(list, snd_kctl_ioctl_t, list);
+		err = p->fioctl(card, ctl, cmd, arg);
+		if (err != -ENOIOCTLCMD) {
+			up_read(&snd_ioctl_rwsem);
+			return err;
+		}
+	}
+	up_read(&snd_ioctl_rwsem);
+	snd_printd("unknown ioctl = 0x%x\n", cmd);
+	return -ENOTTY;
+}
+
+static ssize_t snd_ctl_read(struct file *file, char __user *buffer, size_t count, loff_t * offset)
+{
+	snd_ctl_file_t *ctl;
+	int err = 0;
+	ssize_t result = 0;
+
+	ctl = file->private_data;
+	snd_assert(ctl != NULL && ctl->card != NULL, return -ENXIO);
+	if (!ctl->subscribed)
+		return -EBADFD;
+	if (count < sizeof(snd_ctl_event_t))
+		return -EINVAL;
+	spin_lock_irq(&ctl->read_lock);
+	while (count >= sizeof(snd_ctl_event_t)) {
+		snd_ctl_event_t ev;
+		snd_kctl_event_t *kev;
+		while (list_empty(&ctl->events)) {
+			wait_queue_t wait;
+			if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) {
+				err = -EAGAIN;
+				goto __end_lock;
+			}
+			init_waitqueue_entry(&wait, current);
+			add_wait_queue(&ctl->change_sleep, &wait);
+			set_current_state(TASK_INTERRUPTIBLE);
+			spin_unlock_irq(&ctl->read_lock);
+			schedule();
+			remove_wait_queue(&ctl->change_sleep, &wait);
+			if (signal_pending(current))
+				return result > 0 ? result : -ERESTARTSYS;
+			spin_lock_irq(&ctl->read_lock);
+		}
+		kev = snd_kctl_event(ctl->events.next);
+		ev.type = SNDRV_CTL_EVENT_ELEM;
+		ev.data.elem.mask = kev->mask;
+		ev.data.elem.id = kev->id;
+		list_del(&kev->list);
+		spin_unlock_irq(&ctl->read_lock);
+		kfree(kev);
+		if (copy_to_user(buffer, &ev, sizeof(snd_ctl_event_t))) {
+			err = -EFAULT;
+			goto __end;
+		}
+		spin_lock_irq(&ctl->read_lock);
+		buffer += sizeof(snd_ctl_event_t);
+		count -= sizeof(snd_ctl_event_t);
+		result += sizeof(snd_ctl_event_t);
+	}
+      __end_lock:
+	spin_unlock_irq(&ctl->read_lock);
+      __end:
+      	return result > 0 ? result : err;
+}
+
+static unsigned int snd_ctl_poll(struct file *file, poll_table * wait)
+{
+	unsigned int mask;
+	snd_ctl_file_t *ctl;
+
+	ctl = file->private_data;
+	if (!ctl->subscribed)
+		return 0;
+	poll_wait(file, &ctl->change_sleep, wait);
+
+	mask = 0;
+	if (!list_empty(&ctl->events))
+		mask |= POLLIN | POLLRDNORM;
+
+	return mask;
+}
+
+/*
+ * register the device-specific control-ioctls.
+ * called from each device manager like pcm.c, hwdep.c, etc.
+ */
+static int _snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn, struct list_head *lists)
+{
+	snd_kctl_ioctl_t *pn;
+
+	pn = kcalloc(1, sizeof(snd_kctl_ioctl_t), GFP_KERNEL);
+	if (pn == NULL)
+		return -ENOMEM;
+	pn->fioctl = fcn;
+	down_write(&snd_ioctl_rwsem);
+	list_add_tail(&pn->list, lists);
+	up_write(&snd_ioctl_rwsem);
+	return 0;
+}
+
+int snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn)
+{
+	return _snd_ctl_register_ioctl(fcn, &snd_control_ioctls);
+}
+
+#ifdef CONFIG_COMPAT
+int snd_ctl_register_ioctl_compat(snd_kctl_ioctl_func_t fcn)
+{
+	return _snd_ctl_register_ioctl(fcn, &snd_control_compat_ioctls);
+}
+#endif
+
+/*
+ * de-register the device-specific control-ioctls.
+ */
+static int _snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn, struct list_head *lists)
+{
+	struct list_head *list;
+	snd_kctl_ioctl_t *p;
+
+	snd_runtime_check(fcn != NULL, return -EINVAL);
+	down_write(&snd_ioctl_rwsem);
+	list_for_each(list, lists) {
+		p = list_entry(list, snd_kctl_ioctl_t, list);
+		if (p->fioctl == fcn) {
+			list_del(&p->list);
+			up_write(&snd_ioctl_rwsem);
+			kfree(p);
+			return 0;
+		}
+	}
+	up_write(&snd_ioctl_rwsem);
+	snd_BUG();
+	return -EINVAL;
+}
+
+int snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn)
+{
+	return _snd_ctl_unregister_ioctl(fcn, &snd_control_ioctls);
+}
+
+#ifdef CONFIG_COMPAT
+int snd_ctl_unregister_ioctl_compat(snd_kctl_ioctl_func_t fcn)
+{
+	return _snd_ctl_unregister_ioctl(fcn, &snd_control_compat_ioctls);
+}
+
+#endif
+
+static int snd_ctl_fasync(int fd, struct file * file, int on)
+{
+	snd_ctl_file_t *ctl;
+	int err;
+	ctl = file->private_data;
+	err = fasync_helper(fd, file, on, &ctl->fasync);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+/*
+ * ioctl32 compat
+ */
+#ifdef CONFIG_COMPAT
+#include "control_compat.c"
+#else
+#define snd_ctl_ioctl_compat	NULL
+#endif
+
+/*
+ *  INIT PART
+ */
+
+static struct file_operations snd_ctl_f_ops =
+{
+	.owner =	THIS_MODULE,
+	.read =		snd_ctl_read,
+	.open =		snd_ctl_open,
+	.release =	snd_ctl_release,
+	.poll =		snd_ctl_poll,
+	.unlocked_ioctl =	snd_ctl_ioctl,
+	.compat_ioctl =	snd_ctl_ioctl_compat,
+	.fasync =	snd_ctl_fasync,
+};
+
+static snd_minor_t snd_ctl_reg =
+{
+	.comment =	"ctl",
+	.f_ops =	&snd_ctl_f_ops,
+};
+
+/*
+ * registration of the control device
+ */
+static int snd_ctl_dev_register(snd_device_t *device)
+{
+	snd_card_t *card = device->device_data;
+	int err, cardnum;
+	char name[16];
+
+	snd_assert(card != NULL, return -ENXIO);
+	cardnum = card->number;
+	snd_assert(cardnum >= 0 && cardnum < SNDRV_CARDS, return -ENXIO);
+	sprintf(name, "controlC%i", cardnum);
+	if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL,
+					card, 0, &snd_ctl_reg, name)) < 0)
+		return err;
+	return 0;
+}
+
+/*
+ * disconnection of the control device
+ */
+static int snd_ctl_dev_disconnect(snd_device_t *device)
+{
+	snd_card_t *card = device->device_data;
+	struct list_head *flist;
+	snd_ctl_file_t *ctl;
+
+	down_read(&card->controls_rwsem);
+	list_for_each(flist, &card->ctl_files) {
+		ctl = snd_ctl_file(flist);
+		wake_up(&ctl->change_sleep);
+		kill_fasync(&ctl->fasync, SIGIO, POLL_ERR);
+	}
+	up_read(&card->controls_rwsem);
+	return 0;
+}
+
+/*
+ * free all controls
+ */
+static int snd_ctl_dev_free(snd_device_t *device)
+{
+	snd_card_t *card = device->device_data;
+	snd_kcontrol_t *control;
+
+	down_write(&card->controls_rwsem);
+	while (!list_empty(&card->controls)) {
+		control = snd_kcontrol(card->controls.next);
+		snd_ctl_remove(card, control);
+	}
+	up_write(&card->controls_rwsem);
+	return 0;
+}
+
+/*
+ * de-registration of the control device
+ */
+static int snd_ctl_dev_unregister(snd_device_t *device)
+{
+	snd_card_t *card = device->device_data;
+	int err, cardnum;
+
+	snd_assert(card != NULL, return -ENXIO);
+	cardnum = card->number;
+	snd_assert(cardnum >= 0 && cardnum < SNDRV_CARDS, return -ENXIO);
+	if ((err = snd_unregister_device(SNDRV_DEVICE_TYPE_CONTROL, card, 0)) < 0)
+		return err;
+	return snd_ctl_dev_free(device);
+}
+
+/*
+ * create control core:
+ * called from init.c
+ */
+int snd_ctl_create(snd_card_t *card)
+{
+	static snd_device_ops_t ops = {
+		.dev_free = snd_ctl_dev_free,
+		.dev_register =	snd_ctl_dev_register,
+		.dev_disconnect = snd_ctl_dev_disconnect,
+		.dev_unregister = snd_ctl_dev_unregister
+	};
+
+	snd_assert(card != NULL, return -ENXIO);
+	return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
+}
diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c
new file mode 100644
index 000000000000..7fdabea4bfc8
--- /dev/null
+++ b/sound/core/control_compat.c
@@ -0,0 +1,412 @@
+/*
+ * compat ioctls for control API
+ *
+ *   Copyright (c) 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
+ */
+
+/* this file included from control.c */
+
+#include <linux/compat.h>
+
+struct sndrv_ctl_elem_list32 {
+	u32 offset;
+	u32 space;
+	u32 used;
+	u32 count;
+	u32 pids;
+	unsigned char reserved[50];
+} /* don't set packed attribute here */;
+
+static int snd_ctl_elem_list_compat(snd_card_t *card, struct sndrv_ctl_elem_list32 __user *data32)
+{
+	struct sndrv_ctl_elem_list __user *data;
+	compat_caddr_t ptr;
+	int err;
+
+	data = compat_alloc_user_space(sizeof(*data));
+
+	/* offset, space, used, count */
+	if (copy_in_user(data, data32, 4 * sizeof(u32)))
+		return -EFAULT;
+	/* pids */
+	if (get_user(ptr, &data32->pids) ||
+	    put_user(compat_ptr(ptr), &data->pids))
+		return -EFAULT;
+	err = snd_ctl_elem_list(card, data);
+	if (err < 0)
+		return err;
+	/* copy the result */
+	if (copy_in_user(data32, data, 4 * sizeof(u32)))
+		return -EFAULT;
+	return 0;
+}
+
+/*
+ * control element info
+ * it uses union, so the things are not easy..
+ */
+
+struct sndrv_ctl_elem_info32 {
+	struct sndrv_ctl_elem_id id; // the size of struct is same
+	s32 type;
+	u32 access;
+	u32 count;
+	s32 owner;
+	union {
+		struct {
+			s32 min;
+			s32 max;
+			s32 step;
+		} integer;
+		struct {
+			u64 min;
+			u64 max;
+			u64 step;
+		} integer64;
+		struct {
+			u32 items;
+			u32 item;
+			char name[64];
+		} enumerated;
+		unsigned char reserved[128];
+	} value;
+	unsigned char reserved[64];
+} __attribute__((packed));
+
+static int snd_ctl_elem_info_compat(snd_ctl_file_t *ctl, struct sndrv_ctl_elem_info32 __user *data32)
+{
+	struct sndrv_ctl_elem_info *data;
+	int err;
+
+	data = kcalloc(1, sizeof(*data), GFP_KERNEL);
+	if (! data)
+		return -ENOMEM;
+
+	err = -EFAULT;
+	/* copy id */
+	if (copy_from_user(&data->id, &data32->id, sizeof(data->id)))
+		goto error;
+	/* we need to copy the item index.
+	 * hope this doesn't break anything..
+	 */
+	if (get_user(data->value.enumerated.item, &data32->value.enumerated.item))
+		goto error;
+	err = snd_ctl_elem_info(ctl, data);
+	if (err < 0)
+		goto error;
+	/* restore info to 32bit */
+	err = -EFAULT;
+	/* id, type, access, count */
+	if (copy_to_user(&data32->id, &data->id, sizeof(data->id)) ||
+	    copy_to_user(&data32->type, &data->type, 3 * sizeof(u32)))
+		goto error;
+	if (put_user(data->owner, &data32->owner))
+		goto error;
+	switch (data->type) {
+	case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+	case SNDRV_CTL_ELEM_TYPE_INTEGER:
+		if (put_user(data->value.integer.min, &data32->value.integer.min) ||
+		    put_user(data->value.integer.max, &data32->value.integer.max) ||
+		    put_user(data->value.integer.step, &data32->value.integer.step))
+			goto error;
+		break;
+	case SNDRV_CTL_ELEM_TYPE_INTEGER64:
+		if (copy_to_user(&data32->value.integer64,
+				 &data->value.integer64,
+				 sizeof(data->value.integer64)))
+			goto error;
+		break;
+	case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
+		if (copy_to_user(&data32->value.enumerated,
+				 &data->value.enumerated,
+				 sizeof(data->value.enumerated)))
+			goto error;
+		break;
+	default:
+		break;
+	}
+	err = 0;
+ error:
+	kfree(data);
+	return err;
+}
+
+/* read / write */
+struct sndrv_ctl_elem_value32 {
+	struct sndrv_ctl_elem_id id;
+	unsigned int indirect;	/* bit-field causes misalignment */
+        union {
+		s32 integer[128];
+		unsigned char data[512];
+#ifndef CONFIG_X86_64
+		s64 integer64[64];
+#endif
+        } value;
+        unsigned char reserved[128];
+};
+
+
+/* get the value type and count of the control */
+static int get_ctl_type(snd_card_t *card, snd_ctl_elem_id_t *id, int *countp)
+{
+	snd_kcontrol_t *kctl;
+	snd_ctl_elem_info_t info;
+	int err;
+
+	down_read(&card->controls_rwsem);
+	kctl = snd_ctl_find_id(card, id);
+	if (! kctl) {
+		up_read(&card->controls_rwsem);
+		return -ENXIO;
+	}
+	info.id = *id;
+	err = kctl->info(kctl, &info);
+	up_read(&card->controls_rwsem);
+	if (err >= 0) {
+		err = info.type;
+		*countp = info.count;
+	}
+	return err;
+}
+
+static int get_elem_size(int type, int count)
+{
+	switch (type) {
+	case SNDRV_CTL_ELEM_TYPE_INTEGER64:
+		return sizeof(s64) * count;
+	case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
+		return sizeof(int) * count;
+	case SNDRV_CTL_ELEM_TYPE_BYTES:
+		return 512;
+	case SNDRV_CTL_ELEM_TYPE_IEC958:
+		return sizeof(struct sndrv_aes_iec958);
+	default:
+		return -1;
+	}
+}
+
+static int copy_ctl_value_from_user(snd_card_t *card,
+				    struct sndrv_ctl_elem_value *data,
+				    struct sndrv_ctl_elem_value32 __user *data32,
+				    int *typep, int *countp)
+{
+	int i, type, count, size;
+	unsigned int indirect;
+
+	if (copy_from_user(&data->id, &data32->id, sizeof(data->id)))
+		return -EFAULT;
+	if (get_user(indirect, &data32->indirect))
+		return -EFAULT;
+	if (indirect)
+		return -EINVAL;
+	type = get_ctl_type(card, &data->id, &count);
+	if (type < 0)
+		return type;
+
+	if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN ||
+	    type == SNDRV_CTL_ELEM_TYPE_INTEGER) {
+		for (i = 0; i < count; i++) {
+			int val;
+			if (get_user(val, &data32->value.integer[i]))
+				return -EFAULT;
+			data->value.integer.value[i] = val;
+		}
+	} else {
+		size = get_elem_size(type, count);
+		if (size < 0) {
+			printk(KERN_ERR "snd_ioctl32_ctl_elem_value: unknown type %d\n", type);
+			return -EINVAL;
+		}
+		if (copy_from_user(data->value.bytes.data,
+				   data32->value.data, size))
+			return -EFAULT;
+	}
+
+	*typep = type;
+	*countp = count;
+	return 0;
+}
+
+/* restore the value to 32bit */
+static int copy_ctl_value_to_user(struct sndrv_ctl_elem_value32 __user *data32,
+				  struct sndrv_ctl_elem_value *data,
+				  int type, int count)
+{
+	int i, size;
+
+	if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN ||
+	    type == SNDRV_CTL_ELEM_TYPE_INTEGER) {
+		for (i = 0; i < count; i++) {
+			int val;
+			val = data->value.integer.value[i];
+			if (put_user(val, &data32->value.integer[i]))
+				return -EFAULT;
+		}
+	} else {
+		size = get_elem_size(type, count);
+		if (copy_to_user(data32->value.data,
+				 data->value.bytes.data, size))
+			return -EFAULT;
+	}
+	return 0;
+}
+
+static int snd_ctl_elem_read_user_compat(snd_card_t *card, 
+					 struct sndrv_ctl_elem_value32 __user *data32)
+{
+	struct sndrv_ctl_elem_value *data;
+	int err, type, count;
+
+	data = kcalloc(1, sizeof(*data), GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+
+	if ((err = copy_ctl_value_from_user(card, data, data32, &type, &count)) < 0)
+		goto error;
+	if ((err = snd_ctl_elem_read(card, data)) < 0)
+		goto error;
+	err = copy_ctl_value_to_user(data32, data, type, count);
+ error:
+	kfree(data);
+	return err;
+}
+
+static int snd_ctl_elem_write_user_compat(snd_ctl_file_t *file,
+					  struct sndrv_ctl_elem_value32 __user *data32)
+{
+	struct sndrv_ctl_elem_value *data;
+	int err, type, count;
+
+	data = kcalloc(1, sizeof(*data), GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+
+	if ((err = copy_ctl_value_from_user(file->card, data, data32, &type, &count)) < 0)
+		goto error;
+	if ((err = snd_ctl_elem_write(file->card, file, data)) < 0)
+		goto error;
+	err = copy_ctl_value_to_user(data32, data, type, count);
+ error:
+	kfree(data);
+	return err;
+}
+
+/* add or replace a user control */
+static int snd_ctl_elem_add_compat(snd_ctl_file_t *file,
+				   struct sndrv_ctl_elem_info32 __user *data32,
+				   int replace)
+{
+	struct sndrv_ctl_elem_info *data;
+	int err;
+
+	data = kcalloc(1, sizeof(*data), GFP_KERNEL);
+	if (! data)
+		return -ENOMEM;
+
+	err = -EFAULT;
+	/* id, type, access, count */ \
+	if (copy_from_user(&data->id, &data32->id, sizeof(data->id)) ||
+	    copy_from_user(&data->type, &data32->type, 3 * sizeof(u32)))
+		goto error;
+	if (get_user(data->owner, &data32->owner) ||
+	    get_user(data->type, &data32->type))
+		goto error;
+	switch (data->type) {
+	case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+	case SNDRV_CTL_ELEM_TYPE_INTEGER:
+		if (get_user(data->value.integer.min, &data32->value.integer.min) ||
+		    get_user(data->value.integer.max, &data32->value.integer.max) ||
+		    get_user(data->value.integer.step, &data32->value.integer.step))
+			goto error;
+		break;
+	case SNDRV_CTL_ELEM_TYPE_INTEGER64:
+		if (copy_from_user(&data->value.integer64,
+				   &data32->value.integer64,
+				   sizeof(data->value.integer64)))
+			goto error;
+		break;
+	case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
+		if (copy_from_user(&data->value.enumerated,
+				   &data32->value.enumerated,
+				   sizeof(data->value.enumerated)))
+			goto error;
+		break;
+	default:
+		break;
+	}
+	err = snd_ctl_elem_add(file, data, replace);
+ error:
+	kfree(data);
+	return err;
+}  
+
+enum {
+	SNDRV_CTL_IOCTL_ELEM_LIST32 = _IOWR('U', 0x10, struct sndrv_ctl_elem_list32),
+	SNDRV_CTL_IOCTL_ELEM_INFO32 = _IOWR('U', 0x11, struct sndrv_ctl_elem_info32),
+	SNDRV_CTL_IOCTL_ELEM_READ32 = _IOWR('U', 0x12, struct sndrv_ctl_elem_value32),
+	SNDRV_CTL_IOCTL_ELEM_WRITE32 = _IOWR('U', 0x13, struct sndrv_ctl_elem_value32),
+	SNDRV_CTL_IOCTL_ELEM_ADD32 = _IOWR('U', 0x17, struct sndrv_ctl_elem_info32),
+	SNDRV_CTL_IOCTL_ELEM_REPLACE32 = _IOWR('U', 0x18, struct sndrv_ctl_elem_info32),
+};
+
+static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	snd_ctl_file_t *ctl;
+	struct list_head *list;
+	void __user *argp = compat_ptr(arg);
+	int err;
+
+	ctl = file->private_data;
+	snd_assert(ctl && ctl->card, return -ENXIO);
+
+	switch (cmd) {
+	case SNDRV_CTL_IOCTL_PVERSION:
+	case SNDRV_CTL_IOCTL_CARD_INFO:
+	case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
+	case SNDRV_CTL_IOCTL_POWER:
+	case SNDRV_CTL_IOCTL_POWER_STATE:
+	case SNDRV_CTL_IOCTL_ELEM_LOCK:
+	case SNDRV_CTL_IOCTL_ELEM_UNLOCK:
+		return snd_ctl_ioctl(file, cmd, (unsigned long)argp);
+	case SNDRV_CTL_IOCTL_ELEM_LIST32:
+		return snd_ctl_elem_list_compat(ctl->card, argp);
+	case SNDRV_CTL_IOCTL_ELEM_INFO32:
+		return snd_ctl_elem_info_compat(ctl, argp);
+	case SNDRV_CTL_IOCTL_ELEM_READ32:
+		return snd_ctl_elem_read_user_compat(ctl->card, argp);
+	case SNDRV_CTL_IOCTL_ELEM_WRITE32:
+		return snd_ctl_elem_write_user_compat(ctl, argp);
+	case SNDRV_CTL_IOCTL_ELEM_ADD32:
+		return snd_ctl_elem_add_compat(ctl, argp, 0);
+	case SNDRV_CTL_IOCTL_ELEM_REPLACE32:
+		return snd_ctl_elem_add_compat(ctl, argp, 1);
+	}
+
+	down_read(&snd_ioctl_rwsem);
+	list_for_each(list, &snd_control_compat_ioctls) {
+		snd_kctl_ioctl_t *p = list_entry(list, snd_kctl_ioctl_t, list);
+		if (p->fioctl) {
+			err = p->fioctl(ctl->card, ctl, cmd, arg);
+			if (err != -ENOIOCTLCMD) {
+				up_read(&snd_ioctl_rwsem);
+				return err;
+			}
+		}
+	}
+	up_read(&snd_ioctl_rwsem);
+	return -ENOIOCTLCMD;
+}
diff --git a/sound/core/device.c b/sound/core/device.c
new file mode 100644
index 000000000000..18c71f913d2a
--- /dev/null
+++ b/sound/core/device.c
@@ -0,0 +1,240 @@
+/*
+ *  Device management routines
+ *  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/slab.h>
+#include <linux/time.h>
+#include <linux/errno.h>
+#include <sound/core.h>
+
+/**
+ * snd_device_new - create an ALSA device component
+ * @card: the card instance
+ * @type: the device type, SNDRV_DEV_TYPE_XXX
+ * @device_data: the data pointer of this device
+ * @ops: the operator table
+ *
+ * Creates a new device component for the given data pointer.
+ * The device will be assigned to the card and managed together
+ * by the card.
+ *
+ * The data pointer plays a role as the identifier, too, so the
+ * pointer address must be unique and unchanged.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_device_new(snd_card_t *card, snd_device_type_t type,
+		   void *device_data, snd_device_ops_t *ops)
+{
+	snd_device_t *dev;
+
+	snd_assert(card != NULL && device_data != NULL && ops != NULL, return -ENXIO);
+	dev = kcalloc(1, sizeof(*dev), GFP_KERNEL);
+	if (dev == NULL)
+		return -ENOMEM;
+	dev->card = card;
+	dev->type = type;
+	dev->state = SNDRV_DEV_BUILD;
+	dev->device_data = device_data;
+	dev->ops = ops;
+	list_add(&dev->list, &card->devices);	/* add to the head of list */
+	return 0;
+}
+
+/**
+ * snd_device_free - release the device from the card
+ * @card: the card instance
+ * @device_data: the data pointer to release
+ *
+ * Removes the device from the list on the card and invokes the
+ * callback, dev_unregister or dev_free, corresponding to the state.
+ * Then release the device.
+ *
+ * Returns zero if successful, or a negative error code on failure or if the
+ * device not found.
+ */
+int snd_device_free(snd_card_t *card, void *device_data)
+{
+	struct list_head *list;
+	snd_device_t *dev;
+	
+	snd_assert(card != NULL, return -ENXIO);
+	snd_assert(device_data != NULL, return -ENXIO);
+	list_for_each(list, &card->devices) {
+		dev = snd_device(list);
+		if (dev->device_data != device_data)
+			continue;
+		/* unlink */
+		list_del(&dev->list);
+		if ((dev->state == SNDRV_DEV_REGISTERED || dev->state == SNDRV_DEV_DISCONNECTED) &&
+		    dev->ops->dev_unregister) {
+			if (dev->ops->dev_unregister(dev))
+				snd_printk(KERN_ERR "device unregister failure\n");
+		} else {
+			if (dev->ops->dev_free) {
+				if (dev->ops->dev_free(dev))
+					snd_printk(KERN_ERR "device free failure\n");
+			}
+		}
+		kfree(dev);
+		return 0;
+	}
+	snd_printd("device free %p (from %p), not found\n", device_data, __builtin_return_address(0));
+	return -ENXIO;
+}
+
+/**
+ * snd_device_free - disconnect the device
+ * @card: the card instance
+ * @device_data: the data pointer to disconnect
+ *
+ * Turns the device into the disconnection state, invoking
+ * dev_disconnect callback, if the device was already registered.
+ *
+ * Usually called from snd_card_disconnect().
+ *
+ * Returns zero if successful, or a negative error code on failure or if the
+ * device not found.
+ */
+int snd_device_disconnect(snd_card_t *card, void *device_data)
+{
+	struct list_head *list;
+	snd_device_t *dev;
+	
+	snd_assert(card != NULL, return -ENXIO);
+	snd_assert(device_data != NULL, return -ENXIO);
+	list_for_each(list, &card->devices) {
+		dev = snd_device(list);
+		if (dev->device_data != device_data)
+			continue;
+		if (dev->state == SNDRV_DEV_REGISTERED && dev->ops->dev_disconnect) {
+			if (dev->ops->dev_disconnect(dev))
+				snd_printk(KERN_ERR "device disconnect failure\n");
+			dev->state = SNDRV_DEV_DISCONNECTED;
+		}
+		return 0;
+	}
+	snd_printd("device disconnect %p (from %p), not found\n", device_data, __builtin_return_address(0));
+	return -ENXIO;
+}
+
+/**
+ * snd_device_register - register the device
+ * @card: the card instance
+ * @device_data: the data pointer to register
+ *
+ * Registers the device which was already created via
+ * snd_device_new().  Usually this is called from snd_card_register(),
+ * but it can be called later if any new devices are created after
+ * invocation of snd_card_register().
+ *
+ * Returns zero if successful, or a negative error code on failure or if the
+ * device not found.
+ */
+int snd_device_register(snd_card_t *card, void *device_data)
+{
+	struct list_head *list;
+	snd_device_t *dev;
+	int err;
+	
+	snd_assert(card != NULL && device_data != NULL, return -ENXIO);
+	list_for_each(list, &card->devices) {
+		dev = snd_device(list);
+		if (dev->device_data != device_data)
+			continue;
+		if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) {
+			if ((err = dev->ops->dev_register(dev)) < 0)
+				return err;
+			dev->state = SNDRV_DEV_REGISTERED;
+			return 0;
+		}
+		return -EBUSY;
+	}
+	snd_BUG();
+	return -ENXIO;
+}
+
+/*
+ * register all the devices on the card.
+ * called from init.c
+ */
+int snd_device_register_all(snd_card_t *card)
+{
+	struct list_head *list;
+	snd_device_t *dev;
+	int err;
+	
+	snd_assert(card != NULL, return -ENXIO);
+	list_for_each(list, &card->devices) {
+		dev = snd_device(list);
+		if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) {
+			if ((err = dev->ops->dev_register(dev)) < 0)
+				return err;
+			dev->state = SNDRV_DEV_REGISTERED;
+		}
+	}
+	return 0;
+}
+
+/*
+ * disconnect all the devices on the card.
+ * called from init.c
+ */
+int snd_device_disconnect_all(snd_card_t *card)
+{
+	snd_device_t *dev;
+	struct list_head *list;
+	int err = 0;
+
+	snd_assert(card != NULL, return -ENXIO);
+	list_for_each(list, &card->devices) {
+		dev = snd_device(list);
+		if (snd_device_disconnect(card, dev->device_data) < 0)
+			err = -ENXIO;
+	}
+	return err;
+}
+
+/*
+ * release all the devices on the card.
+ * called from init.c
+ */
+int snd_device_free_all(snd_card_t *card, snd_device_cmd_t cmd)
+{
+	snd_device_t *dev;
+	struct list_head *list;
+	int err;
+	unsigned int range_low, range_high;
+
+	snd_assert(card != NULL, return -ENXIO);
+	range_low = cmd * SNDRV_DEV_TYPE_RANGE_SIZE;
+	range_high = range_low + SNDRV_DEV_TYPE_RANGE_SIZE - 1;
+      __again:
+	list_for_each(list, &card->devices) {
+		dev = snd_device(list);		
+		if (dev->type >= range_low && dev->type <= range_high) {
+			if ((err = snd_device_free(card, dev->device_data)) < 0)
+				return err;
+			goto __again;
+		}
+	}
+	return 0;
+}
diff --git a/sound/core/hwdep.c b/sound/core/hwdep.c
new file mode 100644
index 000000000000..997dd41c584e
--- /dev/null
+++ b/sound/core/hwdep.c
@@ -0,0 +1,524 @@
+/*
+ *  Hardware dependent layer
+ *  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/major.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/minors.h>
+#include <sound/hwdep.h>
+#include <sound/info.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Hardware dependent layer");
+MODULE_LICENSE("GPL");
+
+static snd_hwdep_t *snd_hwdep_devices[SNDRV_CARDS * SNDRV_MINOR_HWDEPS];
+
+static DECLARE_MUTEX(register_mutex);
+
+static int snd_hwdep_free(snd_hwdep_t *hwdep);
+static int snd_hwdep_dev_free(snd_device_t *device);
+static int snd_hwdep_dev_register(snd_device_t *device);
+static int snd_hwdep_dev_unregister(snd_device_t *device);
+
+/*
+
+ */
+
+static loff_t snd_hwdep_llseek(struct file * file, loff_t offset, int orig)
+{
+	snd_hwdep_t *hw = file->private_data;
+	if (hw->ops.llseek)
+		return hw->ops.llseek(hw, file, offset, orig);
+	return -ENXIO;
+}
+
+static ssize_t snd_hwdep_read(struct file * file, char __user *buf, size_t count, loff_t *offset)
+{
+	snd_hwdep_t *hw = file->private_data;
+	if (hw->ops.read)
+		return hw->ops.read(hw, buf, count, offset);
+	return -ENXIO;	
+}
+
+static ssize_t snd_hwdep_write(struct file * file, const char __user *buf, size_t count, loff_t *offset)
+{
+	snd_hwdep_t *hw = file->private_data;
+	if (hw->ops.write)
+		return hw->ops.write(hw, buf, count, offset);
+	return -ENXIO;	
+}
+
+static int snd_hwdep_open(struct inode *inode, struct file * file)
+{
+	int major = imajor(inode);
+	int cardnum;
+	int device;
+	snd_hwdep_t *hw;
+	int err;
+	wait_queue_t wait;
+
+	switch (major) {
+	case CONFIG_SND_MAJOR:
+		cardnum = SNDRV_MINOR_CARD(iminor(inode));
+		device = SNDRV_MINOR_DEVICE(iminor(inode)) - SNDRV_MINOR_HWDEP;
+		break;
+#ifdef CONFIG_SND_OSSEMUL
+	case SOUND_MAJOR:
+		cardnum = SNDRV_MINOR_OSS_CARD(iminor(inode));
+		device = 0;
+		break;
+#endif
+	default:
+		return -ENXIO;
+	}
+	cardnum %= SNDRV_CARDS;
+	device %= SNDRV_MINOR_HWDEPS;
+	hw = snd_hwdep_devices[(cardnum * SNDRV_MINOR_HWDEPS) + device];
+	if (hw == NULL)
+		return -ENODEV;
+
+	if (!hw->ops.open)
+		return -ENXIO;
+#ifdef CONFIG_SND_OSSEMUL
+	if (major == SOUND_MAJOR && hw->oss_type < 0)
+		return -ENXIO;
+#endif
+
+	if (!try_module_get(hw->card->module))
+		return -EFAULT;
+
+	init_waitqueue_entry(&wait, current);
+	add_wait_queue(&hw->open_wait, &wait);
+	down(&hw->open_mutex);
+	while (1) {
+		if (hw->exclusive && hw->used > 0) {
+			err = -EBUSY;
+			break;
+		}
+		err = hw->ops.open(hw, file);
+		if (err >= 0)
+			break;
+		if (err == -EAGAIN) {
+			if (file->f_flags & O_NONBLOCK) {
+				err = -EBUSY;
+				break;
+			}
+		} else
+			break;
+		set_current_state(TASK_INTERRUPTIBLE);
+		up(&hw->open_mutex);
+		schedule();
+		down(&hw->open_mutex);
+		if (signal_pending(current)) {
+			err = -ERESTARTSYS;
+			break;
+		}
+	}
+	remove_wait_queue(&hw->open_wait, &wait);
+	if (err >= 0) {
+		err = snd_card_file_add(hw->card, file);
+		if (err >= 0) {
+			file->private_data = hw;
+			hw->used++;
+		} else {
+			if (hw->ops.release)
+				hw->ops.release(hw, file);
+		}
+	}
+	up(&hw->open_mutex);
+	if (err < 0)
+		module_put(hw->card->module);
+	return err;
+}
+
+static int snd_hwdep_release(struct inode *inode, struct file * file)
+{
+	int err = -ENXIO;
+	snd_hwdep_t *hw = file->private_data;
+	down(&hw->open_mutex);
+	if (hw->ops.release) {
+		err = hw->ops.release(hw, file);
+		wake_up(&hw->open_wait);
+	}
+	if (hw->used > 0)
+		hw->used--;
+	snd_card_file_remove(hw->card, file);
+	up(&hw->open_mutex);
+	module_put(hw->card->module);
+	return err;
+}
+
+static unsigned int snd_hwdep_poll(struct file * file, poll_table * wait)
+{
+	snd_hwdep_t *hw = file->private_data;
+	if (hw->ops.poll)
+		return hw->ops.poll(hw, file, wait);
+	return 0;
+}
+
+static int snd_hwdep_info(snd_hwdep_t *hw, snd_hwdep_info_t __user *_info)
+{
+	snd_hwdep_info_t info;
+	
+	memset(&info, 0, sizeof(info));
+	info.card = hw->card->number;
+	strlcpy(info.id, hw->id, sizeof(info.id));	
+	strlcpy(info.name, hw->name, sizeof(info.name));
+	info.iface = hw->iface;
+	if (copy_to_user(_info, &info, sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+static int snd_hwdep_dsp_status(snd_hwdep_t *hw, snd_hwdep_dsp_status_t __user *_info)
+{
+	snd_hwdep_dsp_status_t info;
+	int err;
+	
+	if (! hw->ops.dsp_status)
+		return -ENXIO;
+	memset(&info, 0, sizeof(info));
+	info.dsp_loaded = hw->dsp_loaded;
+	if ((err = hw->ops.dsp_status(hw, &info)) < 0)
+		return err;
+	if (copy_to_user(_info, &info, sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+static int snd_hwdep_dsp_load(snd_hwdep_t *hw, snd_hwdep_dsp_image_t __user *_info)
+{
+	snd_hwdep_dsp_image_t info;
+	int err;
+	
+	if (! hw->ops.dsp_load)
+		return -ENXIO;
+	memset(&info, 0, sizeof(info));
+	if (copy_from_user(&info, _info, sizeof(info)))
+		return -EFAULT;
+	/* check whether the dsp was already loaded */
+	if (hw->dsp_loaded & (1 << info.index))
+		return -EBUSY;
+	if (!access_ok(VERIFY_READ, info.image, info.length))
+		return -EFAULT;
+	err = hw->ops.dsp_load(hw, &info);
+	if (err < 0)
+		return err;
+	hw->dsp_loaded |= (1 << info.index);
+	return 0;
+}
+
+static long snd_hwdep_ioctl(struct file * file, unsigned int cmd, unsigned long arg)
+{
+	snd_hwdep_t *hw = file->private_data;
+	void __user *argp = (void __user *)arg;
+	switch (cmd) {
+	case SNDRV_HWDEP_IOCTL_PVERSION:
+		return put_user(SNDRV_HWDEP_VERSION, (int __user *)argp);
+	case SNDRV_HWDEP_IOCTL_INFO:
+		return snd_hwdep_info(hw, argp);
+	case SNDRV_HWDEP_IOCTL_DSP_STATUS:
+		return snd_hwdep_dsp_status(hw, argp);
+	case SNDRV_HWDEP_IOCTL_DSP_LOAD:
+		return snd_hwdep_dsp_load(hw, argp);
+	}
+	if (hw->ops.ioctl)
+		return hw->ops.ioctl(hw, file, cmd, arg);
+	return -ENOTTY;
+}
+
+static int snd_hwdep_mmap(struct file * file, struct vm_area_struct * vma)
+{
+	snd_hwdep_t *hw = file->private_data;
+	if (hw->ops.mmap)
+		return hw->ops.mmap(hw, file, vma);
+	return -ENXIO;
+}
+
+static int snd_hwdep_control_ioctl(snd_card_t * card, snd_ctl_file_t * control,
+				   unsigned int cmd, unsigned long arg)
+{
+	unsigned int tmp;
+	
+	tmp = card->number * SNDRV_MINOR_HWDEPS;
+	switch (cmd) {
+	case SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE:
+		{
+			int device;
+
+			if (get_user(device, (int __user *)arg))
+				return -EFAULT;
+			device = device < 0 ? 0 : device + 1;
+			while (device < SNDRV_MINOR_HWDEPS) {
+				if (snd_hwdep_devices[tmp + device])
+					break;
+				device++;
+			}
+			if (device >= SNDRV_MINOR_HWDEPS)
+				device = -1;
+			if (put_user(device, (int __user *)arg))
+				return -EFAULT;
+			return 0;
+		}
+	case SNDRV_CTL_IOCTL_HWDEP_INFO:
+		{
+			snd_hwdep_info_t __user *info = (snd_hwdep_info_t __user *)arg;
+			int device;
+			snd_hwdep_t *hwdep;
+
+			if (get_user(device, &info->device))
+				return -EFAULT;
+			if (device < 0 || device >= SNDRV_MINOR_HWDEPS)
+				return -ENXIO;
+			hwdep = snd_hwdep_devices[tmp + device];
+			if (hwdep == NULL)
+				return -ENXIO;
+			return snd_hwdep_info(hwdep, info);
+		}
+	}
+	return -ENOIOCTLCMD;
+}
+
+#ifdef CONFIG_COMPAT
+#include "hwdep_compat.c"
+#else
+#define snd_hwdep_ioctl_compat	NULL
+#endif
+
+/*
+
+ */
+
+static struct file_operations snd_hwdep_f_ops =
+{
+	.owner = 	THIS_MODULE,
+	.llseek =	snd_hwdep_llseek,
+	.read = 	snd_hwdep_read,
+	.write =	snd_hwdep_write,
+	.open =		snd_hwdep_open,
+	.release =	snd_hwdep_release,
+	.poll =		snd_hwdep_poll,
+	.unlocked_ioctl =	snd_hwdep_ioctl,
+	.compat_ioctl =	snd_hwdep_ioctl_compat,
+	.mmap =		snd_hwdep_mmap,
+};
+
+static snd_minor_t snd_hwdep_reg =
+{
+	.comment =	"hardware dependent",
+	.f_ops =	&snd_hwdep_f_ops,
+};
+
+/**
+ * snd_hwdep_new - create a new hwdep instance
+ * @card: the card instance
+ * @id: the id string
+ * @device: the device index (zero-based)
+ * @rhwdep: the pointer to store the new hwdep instance
+ *
+ * Creates a new hwdep instance with the given index on the card.
+ * The callbacks (hwdep->ops) must be set on the returned instance
+ * after this call manually by the caller.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_hwdep_new(snd_card_t * card, char *id, int device, snd_hwdep_t ** rhwdep)
+{
+	snd_hwdep_t *hwdep;
+	int err;
+	static snd_device_ops_t ops = {
+		.dev_free = snd_hwdep_dev_free,
+		.dev_register = snd_hwdep_dev_register,
+		.dev_unregister = snd_hwdep_dev_unregister
+	};
+
+	snd_assert(rhwdep != NULL, return -EINVAL);
+	*rhwdep = NULL;
+	snd_assert(card != NULL, return -ENXIO);
+	hwdep = kcalloc(1, sizeof(*hwdep), GFP_KERNEL);
+	if (hwdep == NULL)
+		return -ENOMEM;
+	hwdep->card = card;
+	hwdep->device = device;
+	if (id) {
+		strlcpy(hwdep->id, id, sizeof(hwdep->id));
+	}
+#ifdef CONFIG_SND_OSSEMUL
+	hwdep->oss_type = -1;
+#endif
+	if ((err = snd_device_new(card, SNDRV_DEV_HWDEP, hwdep, &ops)) < 0) {
+		snd_hwdep_free(hwdep);
+		return err;
+	}
+	init_waitqueue_head(&hwdep->open_wait);
+	init_MUTEX(&hwdep->open_mutex);
+	*rhwdep = hwdep;
+	return 0;
+}
+
+static int snd_hwdep_free(snd_hwdep_t *hwdep)
+{
+	snd_assert(hwdep != NULL, return -ENXIO);
+	if (hwdep->private_free)
+		hwdep->private_free(hwdep);
+	kfree(hwdep);
+	return 0;
+}
+
+static int snd_hwdep_dev_free(snd_device_t *device)
+{
+	snd_hwdep_t *hwdep = device->device_data;
+	return snd_hwdep_free(hwdep);
+}
+
+static int snd_hwdep_dev_register(snd_device_t *device)
+{
+	snd_hwdep_t *hwdep = device->device_data;
+	int idx, err;
+	char name[32];
+
+	down(&register_mutex);
+	idx = (hwdep->card->number * SNDRV_MINOR_HWDEPS) + hwdep->device;
+	if (snd_hwdep_devices[idx]) {
+		up(&register_mutex);
+		return -EBUSY;
+	}
+	snd_hwdep_devices[idx] = hwdep;
+	sprintf(name, "hwC%iD%i", hwdep->card->number, hwdep->device);
+	if ((err = snd_register_device(SNDRV_DEVICE_TYPE_HWDEP,
+				       hwdep->card, hwdep->device,
+				       &snd_hwdep_reg, name)) < 0) {
+		snd_printk(KERN_ERR "unable to register hardware dependent device %i:%i\n",
+			   hwdep->card->number, hwdep->device);
+		snd_hwdep_devices[idx] = NULL;
+		up(&register_mutex);
+		return err;
+	}
+#ifdef CONFIG_SND_OSSEMUL
+	hwdep->ossreg = 0;
+	if (hwdep->oss_type >= 0) {
+		if ((hwdep->oss_type == SNDRV_OSS_DEVICE_TYPE_DMFM) && (hwdep->device != 0)) {
+			snd_printk (KERN_WARNING "only hwdep device 0 can be registered as OSS direct FM device!\n");
+		} else {
+			if (snd_register_oss_device(hwdep->oss_type,
+						    hwdep->card, hwdep->device,
+						    &snd_hwdep_reg, hwdep->oss_dev) < 0) {
+				snd_printk(KERN_ERR "unable to register OSS compatibility device %i:%i\n",
+					   hwdep->card->number, hwdep->device);
+			} else
+				hwdep->ossreg = 1;
+		}
+	}
+#endif
+	up(&register_mutex);
+	return 0;
+}
+
+static int snd_hwdep_dev_unregister(snd_device_t *device)
+{
+	snd_hwdep_t *hwdep = device->device_data;
+	int idx;
+
+	snd_assert(hwdep != NULL, return -ENXIO);
+	down(&register_mutex);
+	idx = (hwdep->card->number * SNDRV_MINOR_HWDEPS) + hwdep->device;
+	if (snd_hwdep_devices[idx] != hwdep) {
+		up(&register_mutex);
+		return -EINVAL;
+	}
+#ifdef CONFIG_SND_OSSEMUL
+	if (hwdep->ossreg)
+		snd_unregister_oss_device(hwdep->oss_type, hwdep->card, hwdep->device);
+#endif
+	snd_unregister_device(SNDRV_DEVICE_TYPE_HWDEP, hwdep->card, hwdep->device);
+	snd_hwdep_devices[idx] = NULL;
+	up(&register_mutex);
+	return snd_hwdep_free(hwdep);
+}
+
+/*
+ *  Info interface
+ */
+
+static void snd_hwdep_proc_read(snd_info_entry_t *entry,
+				snd_info_buffer_t * buffer)
+{
+	int idx;
+	snd_hwdep_t *hwdep;
+
+	down(&register_mutex);
+	for (idx = 0; idx < SNDRV_CARDS * SNDRV_MINOR_HWDEPS; idx++) {
+		hwdep = snd_hwdep_devices[idx];
+		if (hwdep == NULL)
+			continue;
+		snd_iprintf(buffer, "%02i-%02i: %s\n",
+					idx / SNDRV_MINOR_HWDEPS,
+					idx % SNDRV_MINOR_HWDEPS,
+					hwdep->name);
+	}
+	up(&register_mutex);
+}
+
+/*
+ *  ENTRY functions
+ */
+
+static snd_info_entry_t *snd_hwdep_proc_entry = NULL;
+
+static int __init alsa_hwdep_init(void)
+{
+	snd_info_entry_t *entry;
+
+	memset(snd_hwdep_devices, 0, sizeof(snd_hwdep_devices));
+	if ((entry = snd_info_create_module_entry(THIS_MODULE, "hwdep", NULL)) != NULL) {
+		entry->c.text.read_size = 512;
+		entry->c.text.read = snd_hwdep_proc_read;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	snd_hwdep_proc_entry = entry;
+	snd_ctl_register_ioctl(snd_hwdep_control_ioctl);
+	snd_ctl_register_ioctl_compat(snd_hwdep_control_ioctl);
+	return 0;
+}
+
+static void __exit alsa_hwdep_exit(void)
+{
+	snd_ctl_unregister_ioctl(snd_hwdep_control_ioctl);
+	snd_ctl_unregister_ioctl_compat(snd_hwdep_control_ioctl);
+	if (snd_hwdep_proc_entry) {
+		snd_info_unregister(snd_hwdep_proc_entry);
+		snd_hwdep_proc_entry = NULL;
+	}
+}
+
+module_init(alsa_hwdep_init)
+module_exit(alsa_hwdep_exit)
+
+EXPORT_SYMBOL(snd_hwdep_new);
diff --git a/sound/core/hwdep_compat.c b/sound/core/hwdep_compat.c
new file mode 100644
index 000000000000..6866f423d4b9
--- /dev/null
+++ b/sound/core/hwdep_compat.c
@@ -0,0 +1,77 @@
+/*
+ *   32bit -> 64bit ioctl wrapper for hwdep API
+ *   Copyright (c) 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
+ *
+ */
+
+/* This file is included from hwdep.c */
+
+#include <linux/compat.h>
+
+struct sndrv_hwdep_dsp_image32 {
+	u32 index;
+	unsigned char name[64];
+	u32 image;	/* pointer */
+	u32 length;
+	u32 driver_data;
+} /* don't set packed attribute here */;
+
+static int snd_hwdep_dsp_load_compat(snd_hwdep_t *hw,
+				     struct sndrv_hwdep_dsp_image32 __user *src)
+{
+	struct sndrv_hwdep_dsp_image *dst;
+	compat_caddr_t ptr;
+	u32 val;
+
+	dst = compat_alloc_user_space(sizeof(*dst));
+
+	/* index and name */
+	if (copy_in_user(dst, src, 4 + 64))
+		return -EFAULT;
+	if (get_user(ptr, &src->image) ||
+	    put_user(compat_ptr(ptr), &dst->image))
+		return -EFAULT;
+	if (get_user(val, &src->length) ||
+	    put_user(val, &dst->length))
+		return -EFAULT;
+	if (get_user(val, &src->driver_data) ||
+	    put_user(val, &dst->driver_data))
+		return -EFAULT;
+
+	return snd_hwdep_dsp_load(hw, dst);
+}
+
+enum {
+	SNDRV_HWDEP_IOCTL_DSP_LOAD32   = _IOW('H', 0x03, struct sndrv_hwdep_dsp_image32)
+};
+
+static long snd_hwdep_ioctl_compat(struct file * file, unsigned int cmd, unsigned long arg)
+{
+	snd_hwdep_t *hw = file->private_data;
+	void __user *argp = compat_ptr(arg);
+	switch (cmd) {
+	case SNDRV_HWDEP_IOCTL_PVERSION:
+	case SNDRV_HWDEP_IOCTL_INFO:
+	case SNDRV_HWDEP_IOCTL_DSP_STATUS:
+		return snd_hwdep_ioctl(file, cmd, (unsigned long)argp);
+	case SNDRV_HWDEP_IOCTL_DSP_LOAD32:
+		return snd_hwdep_dsp_load_compat(hw, argp);
+	}
+	if (hw->ops.ioctl_compat)
+		return hw->ops.ioctl_compat(hw, file, cmd, arg);
+	return -ENOIOCTLCMD;
+}
diff --git a/sound/core/info.c b/sound/core/info.c
new file mode 100644
index 000000000000..31faffe01cb0
--- /dev/null
+++ b/sound/core/info.c
@@ -0,0 +1,989 @@
+/*
+ *  Information interface for ALSA driver
+ *  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/vmalloc.h>
+#include <linux/time.h>
+#include <linux/smp_lock.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/info.h>
+#include <sound/version.h>
+#include <linux/proc_fs.h>
+#include <linux/devfs_fs_kernel.h>
+#include <stdarg.h>
+
+/*
+ *
+ */
+
+int snd_info_check_reserved_words(const char *str)
+{
+	static char *reserved[] =
+	{
+		"version",
+		"meminfo",
+		"memdebug",
+		"detect",
+		"devices",
+		"oss",
+		"cards",
+		"timers",
+		"synth",
+		"pcm",
+		"seq",
+		NULL
+	};
+	char **xstr = reserved;
+
+	while (*xstr) {
+		if (!strcmp(*xstr, str))
+			return 0;
+		xstr++;
+	}
+	if (!strncmp(str, "card", 4))
+		return 0;
+	return 1;
+}
+
+#ifdef CONFIG_PROC_FS
+
+static DECLARE_MUTEX(info_mutex);
+
+typedef struct _snd_info_private_data {
+	snd_info_buffer_t *rbuffer;
+	snd_info_buffer_t *wbuffer;
+	snd_info_entry_t *entry;
+	void *file_private_data;
+} snd_info_private_data_t;
+
+static int snd_info_version_init(void);
+static int snd_info_version_done(void);
+
+
+/**
+ * snd_iprintf - printf on the procfs buffer
+ * @buffer: the procfs buffer
+ * @fmt: the printf format
+ *
+ * Outputs the string on the procfs buffer just like printf().
+ *
+ * Returns the size of output string.
+ */
+int snd_iprintf(snd_info_buffer_t * buffer, char *fmt,...)
+{
+	va_list args;
+	int len, res;
+
+	if (buffer->stop || buffer->error)
+		return 0;
+	len = buffer->len - buffer->size;
+	va_start(args, fmt);
+	res = vsnprintf(buffer->curr, len, fmt, args);
+	va_end(args);
+	if (res >= len) {
+		buffer->stop = 1;
+		return 0;
+	}
+	buffer->curr += res;
+	buffer->size += res;
+	return res;
+}
+
+/*
+
+ */
+
+static struct proc_dir_entry *snd_proc_root = NULL;
+snd_info_entry_t *snd_seq_root = NULL;
+#ifdef CONFIG_SND_OSSEMUL
+snd_info_entry_t *snd_oss_root = NULL;
+#endif
+
+static inline void snd_info_entry_prepare(struct proc_dir_entry *de)
+{
+	de->owner = THIS_MODULE;
+}
+
+static void snd_remove_proc_entry(struct proc_dir_entry *parent,
+				  struct proc_dir_entry *de)
+{
+	if (de)
+		remove_proc_entry(de->name, parent);
+}
+
+static loff_t snd_info_entry_llseek(struct file *file, loff_t offset, int orig)
+{
+	snd_info_private_data_t *data;
+	struct snd_info_entry *entry;
+	loff_t ret;
+
+	data = file->private_data;
+	entry = data->entry;
+	lock_kernel();
+	switch (entry->content) {
+	case SNDRV_INFO_CONTENT_TEXT:
+		switch (orig) {
+		case 0:	/* SEEK_SET */
+			file->f_pos = offset;
+			ret = file->f_pos;
+			goto out;
+		case 1:	/* SEEK_CUR */
+			file->f_pos += offset;
+			ret = file->f_pos;
+			goto out;
+		case 2:	/* SEEK_END */
+		default:
+			ret = -EINVAL;
+			goto out;
+		}
+		break;
+	case SNDRV_INFO_CONTENT_DATA:
+		if (entry->c.ops->llseek) {
+			ret = entry->c.ops->llseek(entry,
+						    data->file_private_data,
+						    file, offset, orig);
+			goto out;
+		}
+		break;
+	}
+	ret = -ENXIO;
+out:
+	unlock_kernel();
+	return ret;
+}
+
+static ssize_t snd_info_entry_read(struct file *file, char __user *buffer,
+				   size_t count, loff_t * offset)
+{
+	snd_info_private_data_t *data;
+	struct snd_info_entry *entry;
+	snd_info_buffer_t *buf;
+	size_t size = 0;
+	loff_t pos;
+
+	data = file->private_data;
+	snd_assert(data != NULL, return -ENXIO);
+	pos = *offset;
+	if (pos < 0 || (long) pos != pos || (ssize_t) count < 0)
+		return -EIO;
+	if ((unsigned long) pos + (unsigned long) count < (unsigned long) pos)
+		return -EIO;
+	entry = data->entry;
+	switch (entry->content) {
+	case SNDRV_INFO_CONTENT_TEXT:
+		buf = data->rbuffer;
+		if (buf == NULL)
+			return -EIO;
+		if (pos >= buf->size)
+			return 0;
+		size = buf->size - pos;
+		size = min(count, size);
+		if (copy_to_user(buffer, buf->buffer + pos, size))
+			return -EFAULT;
+		break;
+	case SNDRV_INFO_CONTENT_DATA:
+		if (entry->c.ops->read)
+			size = entry->c.ops->read(entry,
+						  data->file_private_data,
+						  file, buffer, count, pos);
+		break;
+	}
+	if ((ssize_t) size > 0)
+		*offset = pos + size;
+	return size;
+}
+
+static ssize_t snd_info_entry_write(struct file *file, const char __user *buffer,
+				    size_t count, loff_t * offset)
+{
+	snd_info_private_data_t *data;
+	struct snd_info_entry *entry;
+	snd_info_buffer_t *buf;
+	size_t size = 0;
+	loff_t pos;
+
+	data = file->private_data;
+	snd_assert(data != NULL, return -ENXIO);
+	entry = data->entry;
+	pos = *offset;
+	if (pos < 0 || (long) pos != pos || (ssize_t) count < 0)
+		return -EIO;
+	if ((unsigned long) pos + (unsigned long) count < (unsigned long) pos)
+		return -EIO;
+	switch (entry->content) {
+	case SNDRV_INFO_CONTENT_TEXT:
+		buf = data->wbuffer;
+		if (buf == NULL)
+			return -EIO;
+		if (pos >= buf->len)
+			return -ENOMEM;
+		size = buf->len - pos;
+		size = min(count, size);
+		if (copy_from_user(buf->buffer + pos, buffer, size))
+			return -EFAULT;
+		if ((long)buf->size < pos + size)
+			buf->size = pos + size;
+		break;
+	case SNDRV_INFO_CONTENT_DATA:
+		if (entry->c.ops->write)
+			size = entry->c.ops->write(entry,
+						   data->file_private_data,
+						   file, buffer, count, pos);
+		break;
+	}
+	if ((ssize_t) size > 0)
+		*offset = pos + size;
+	return size;
+}
+
+static int snd_info_entry_open(struct inode *inode, struct file *file)
+{
+	snd_info_entry_t *entry;
+	snd_info_private_data_t *data;
+	snd_info_buffer_t *buffer;
+	struct proc_dir_entry *p;
+	int mode, err;
+
+	down(&info_mutex);
+	p = PDE(inode);
+	entry = p == NULL ? NULL : (snd_info_entry_t *)p->data;
+	if (entry == NULL || entry->disconnected) {
+		up(&info_mutex);
+		return -ENODEV;
+	}
+	if (!try_module_get(entry->module)) {
+		err = -EFAULT;
+		goto __error1;
+	}
+	mode = file->f_flags & O_ACCMODE;
+	if (mode == O_RDONLY || mode == O_RDWR) {
+		if ((entry->content == SNDRV_INFO_CONTENT_TEXT &&
+		     !entry->c.text.read_size) ||
+		    (entry->content == SNDRV_INFO_CONTENT_DATA &&
+		     entry->c.ops->read == NULL)) {
+		    	err = -ENODEV;
+		    	goto __error;
+		}
+	}
+	if (mode == O_WRONLY || mode == O_RDWR) {
+		if ((entry->content == SNDRV_INFO_CONTENT_TEXT &&
+		     !entry->c.text.write_size) ||
+		    (entry->content == SNDRV_INFO_CONTENT_DATA &&
+		     entry->c.ops->write == NULL)) {
+		    	err = -ENODEV;
+		    	goto __error;
+		}
+	}
+	data = kcalloc(1, sizeof(*data), GFP_KERNEL);
+	if (data == NULL) {
+		err = -ENOMEM;
+		goto __error;
+	}
+	data->entry = entry;
+	switch (entry->content) {
+	case SNDRV_INFO_CONTENT_TEXT:
+		if (mode == O_RDONLY || mode == O_RDWR) {
+			buffer = kcalloc(1, sizeof(*buffer), GFP_KERNEL);
+			if (buffer == NULL) {
+				kfree(data);
+				err = -ENOMEM;
+				goto __error;
+			}
+			buffer->len = (entry->c.text.read_size +
+				      (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
+			buffer->buffer = vmalloc(buffer->len);
+			if (buffer->buffer == NULL) {
+				kfree(buffer);
+				kfree(data);
+				err = -ENOMEM;
+				goto __error;
+			}
+			buffer->curr = buffer->buffer;
+			data->rbuffer = buffer;
+		}
+		if (mode == O_WRONLY || mode == O_RDWR) {
+			buffer = kcalloc(1, sizeof(*buffer), GFP_KERNEL);
+			if (buffer == NULL) {
+				if (mode == O_RDWR) {
+					vfree(data->rbuffer->buffer);
+					kfree(data->rbuffer);
+				}
+				kfree(data);
+				err = -ENOMEM;
+				goto __error;
+			}
+			buffer->len = (entry->c.text.write_size +
+				      (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
+			buffer->buffer = vmalloc(buffer->len);
+			if (buffer->buffer == NULL) {
+				if (mode == O_RDWR) {
+					vfree(data->rbuffer->buffer);
+					kfree(data->rbuffer);
+				}
+				kfree(buffer);
+				kfree(data);
+				err = -ENOMEM;
+				goto __error;
+			}
+			buffer->curr = buffer->buffer;
+			data->wbuffer = buffer;
+		}
+		break;
+	case SNDRV_INFO_CONTENT_DATA:	/* data */
+		if (entry->c.ops->open) {
+			if ((err = entry->c.ops->open(entry, mode,
+						      &data->file_private_data)) < 0) {
+				kfree(data);
+				goto __error;
+			}
+		}
+		break;
+	}
+	file->private_data = data;
+	up(&info_mutex);
+	if (entry->content == SNDRV_INFO_CONTENT_TEXT &&
+	    (mode == O_RDONLY || mode == O_RDWR)) {
+		if (entry->c.text.read) {
+			down(&entry->access);
+			entry->c.text.read(entry, data->rbuffer);
+			up(&entry->access);
+		}
+	}
+	return 0;
+
+      __error:
+	module_put(entry->module);
+      __error1:
+	up(&info_mutex);
+	return err;
+}
+
+static int snd_info_entry_release(struct inode *inode, struct file *file)
+{
+	snd_info_entry_t *entry;
+	snd_info_private_data_t *data;
+	int mode;
+
+	mode = file->f_flags & O_ACCMODE;
+	data = file->private_data;
+	entry = data->entry;
+	switch (entry->content) {
+	case SNDRV_INFO_CONTENT_TEXT:
+		if (mode == O_RDONLY || mode == O_RDWR) {
+			vfree(data->rbuffer->buffer);
+			kfree(data->rbuffer);
+		}
+		if (mode == O_WRONLY || mode == O_RDWR) {
+			if (entry->c.text.write) {
+				entry->c.text.write(entry, data->wbuffer);
+				if (data->wbuffer->error) {
+					snd_printk(KERN_WARNING "data write error to %s (%i)\n",
+						entry->name,
+						data->wbuffer->error);
+				}
+			}
+			vfree(data->wbuffer->buffer);
+			kfree(data->wbuffer);
+		}
+		break;
+	case SNDRV_INFO_CONTENT_DATA:
+		if (entry->c.ops->release)
+			entry->c.ops->release(entry, mode,
+					      data->file_private_data);
+		break;
+	}
+	module_put(entry->module);
+	kfree(data);
+	return 0;
+}
+
+static unsigned int snd_info_entry_poll(struct file *file, poll_table * wait)
+{
+	snd_info_private_data_t *data;
+	struct snd_info_entry *entry;
+	unsigned int mask;
+
+	data = file->private_data;
+	if (data == NULL)
+		return 0;
+	entry = data->entry;
+	mask = 0;
+	switch (entry->content) {
+	case SNDRV_INFO_CONTENT_DATA:
+		if (entry->c.ops->poll)
+			return entry->c.ops->poll(entry,
+						  data->file_private_data,
+						  file, wait);
+		if (entry->c.ops->read)
+			mask |= POLLIN | POLLRDNORM;
+		if (entry->c.ops->write)
+			mask |= POLLOUT | POLLWRNORM;
+		break;
+	}
+	return mask;
+}
+
+static inline int _snd_info_entry_ioctl(struct inode *inode, struct file *file,
+					unsigned int cmd, unsigned long arg)
+{
+	snd_info_private_data_t *data;
+	struct snd_info_entry *entry;
+
+	data = file->private_data;
+	if (data == NULL)
+		return 0;
+	entry = data->entry;
+	switch (entry->content) {
+	case SNDRV_INFO_CONTENT_DATA:
+		if (entry->c.ops->ioctl)
+			return entry->c.ops->ioctl(entry,
+						   data->file_private_data,
+						   file, cmd, arg);
+		break;
+	}
+	return -ENOTTY;
+}
+
+/* FIXME: need to unlock BKL to allow preemption */
+static int snd_info_entry_ioctl(struct inode *inode, struct file *file,
+				unsigned int cmd, unsigned long arg)
+{
+	int err;
+	unlock_kernel();
+	err = _snd_info_entry_ioctl(inode, file, cmd, arg);
+	lock_kernel();
+	return err;
+}
+
+static int snd_info_entry_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct inode *inode = file->f_dentry->d_inode;
+	snd_info_private_data_t *data;
+	struct snd_info_entry *entry;
+
+	data = file->private_data;
+	if (data == NULL)
+		return 0;
+	entry = data->entry;
+	switch (entry->content) {
+	case SNDRV_INFO_CONTENT_DATA:
+		if (entry->c.ops->mmap)
+			return entry->c.ops->mmap(entry,
+						  data->file_private_data,
+						  inode, file, vma);
+		break;
+	}
+	return -ENXIO;
+}
+
+static struct file_operations snd_info_entry_operations =
+{
+	.owner =	THIS_MODULE,
+	.llseek =	snd_info_entry_llseek,
+	.read =		snd_info_entry_read,
+	.write =	snd_info_entry_write,
+	.poll =		snd_info_entry_poll,
+	.ioctl =	snd_info_entry_ioctl,
+	.mmap =		snd_info_entry_mmap,
+	.open =		snd_info_entry_open,
+	.release =	snd_info_entry_release,
+};
+
+/**
+ * snd_create_proc_entry - create a procfs entry
+ * @name: the name of the proc file
+ * @mode: the file permission bits, S_Ixxx
+ * @parent: the parent proc-directory entry
+ *
+ * Creates a new proc file entry with the given name and permission
+ * on the given directory.
+ *
+ * Returns the pointer of new instance or NULL on failure.
+ */
+static struct proc_dir_entry *snd_create_proc_entry(const char *name, mode_t mode,
+						    struct proc_dir_entry *parent)
+{
+	struct proc_dir_entry *p;
+	p = create_proc_entry(name, mode, parent);
+	if (p)
+		snd_info_entry_prepare(p);
+	return p;
+}
+
+int __init snd_info_init(void)
+{
+	struct proc_dir_entry *p;
+
+	p = snd_create_proc_entry("asound", S_IFDIR | S_IRUGO | S_IXUGO, &proc_root);
+	if (p == NULL)
+		return -ENOMEM;
+	snd_proc_root = p;
+#ifdef CONFIG_SND_OSSEMUL
+	{
+		snd_info_entry_t *entry;
+		if ((entry = snd_info_create_module_entry(THIS_MODULE, "oss", NULL)) == NULL)
+			return -ENOMEM;
+		entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			return -ENOMEM;
+		}
+		snd_oss_root = entry;
+	}
+#endif
+#if defined(CONFIG_SND_SEQUENCER) || defined(CONFIG_SND_SEQUENCER_MODULE)
+	{
+		snd_info_entry_t *entry;
+		if ((entry = snd_info_create_module_entry(THIS_MODULE, "seq", NULL)) == NULL)
+			return -ENOMEM;
+		entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			return -ENOMEM;
+		}
+		snd_seq_root = entry;
+	}
+#endif
+	snd_info_version_init();
+	snd_memory_info_init();
+	snd_minor_info_init();
+	snd_minor_info_oss_init();
+	snd_card_info_init();
+	return 0;
+}
+
+int __exit snd_info_done(void)
+{
+	snd_card_info_done();
+	snd_minor_info_oss_done();
+	snd_minor_info_done();
+	snd_memory_info_done();
+	snd_info_version_done();
+	if (snd_proc_root) {
+#if defined(CONFIG_SND_SEQUENCER) || defined(CONFIG_SND_SEQUENCER_MODULE)
+		if (snd_seq_root)
+			snd_info_unregister(snd_seq_root);
+#endif
+#ifdef CONFIG_SND_OSSEMUL
+		if (snd_oss_root)
+			snd_info_unregister(snd_oss_root);
+#endif
+		snd_remove_proc_entry(&proc_root, snd_proc_root);
+	}
+	return 0;
+}
+
+/*
+
+ */
+
+
+/*
+ * create a card proc file
+ * called from init.c
+ */
+int snd_info_card_create(snd_card_t * card)
+{
+	char str[8];
+	snd_info_entry_t *entry;
+
+	snd_assert(card != NULL, return -ENXIO);
+
+	sprintf(str, "card%i", card->number);
+	if ((entry = snd_info_create_module_entry(card->module, str, NULL)) == NULL)
+		return -ENOMEM;
+	entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
+	if (snd_info_register(entry) < 0) {
+		snd_info_free_entry(entry);
+		return -ENOMEM;
+	}
+	card->proc_root = entry;
+	return 0;
+}
+
+/*
+ * register the card proc file
+ * called from init.c
+ */
+int snd_info_card_register(snd_card_t * card)
+{
+	struct proc_dir_entry *p;
+
+	snd_assert(card != NULL, return -ENXIO);
+
+	if (!strcmp(card->id, card->proc_root->name))
+		return 0;
+
+	p = proc_symlink(card->id, snd_proc_root, card->proc_root->name);
+	if (p == NULL)
+		return -ENOMEM;
+	card->proc_root_link = p;
+	return 0;
+}
+
+/*
+ * de-register the card proc file
+ * called from init.c
+ */
+int snd_info_card_free(snd_card_t * card)
+{
+	snd_assert(card != NULL, return -ENXIO);
+	if (card->proc_root_link) {
+		snd_remove_proc_entry(snd_proc_root, card->proc_root_link);
+		card->proc_root_link = NULL;
+	}
+	if (card->proc_root) {
+		snd_info_unregister(card->proc_root);
+		card->proc_root = NULL;
+	}
+	return 0;
+}
+
+
+/**
+ * snd_info_get_line - read one line from the procfs buffer
+ * @buffer: the procfs buffer
+ * @line: the buffer to store
+ * @len: the max. buffer size - 1
+ *
+ * Reads one line from the buffer and stores the string.
+ *
+ * Returns zero if successful, or 1 if error or EOF.
+ */
+int snd_info_get_line(snd_info_buffer_t * buffer, char *line, int len)
+{
+	int c = -1;
+
+	if (len <= 0 || buffer->stop || buffer->error)
+		return 1;
+	while (--len > 0) {
+		c = *buffer->curr++;
+		if (c == '\n') {
+			if ((buffer->curr - buffer->buffer) >= (long)buffer->size) {
+				buffer->stop = 1;
+			}
+			break;
+		}
+		*line++ = c;
+		if ((buffer->curr - buffer->buffer) >= (long)buffer->size) {
+			buffer->stop = 1;
+			break;
+		}
+	}
+	while (c != '\n' && !buffer->stop) {
+		c = *buffer->curr++;
+		if ((buffer->curr - buffer->buffer) >= (long)buffer->size) {
+			buffer->stop = 1;
+		}
+	}
+	*line = '\0';
+	return 0;
+}
+
+/**
+ * snd_info_get_line - parse a string token
+ * @dest: the buffer to store the string token
+ * @src: the original string
+ * @len: the max. length of token - 1
+ *
+ * Parses the original string and copy a token to the given
+ * string buffer.
+ *
+ * Returns the updated pointer of the original string so that
+ * it can be used for the next call.
+ */
+char *snd_info_get_str(char *dest, char *src, int len)
+{
+	int c;
+
+	while (*src == ' ' || *src == '\t')
+		src++;
+	if (*src == '"' || *src == '\'') {
+		c = *src++;
+		while (--len > 0 && *src && *src != c) {
+			*dest++ = *src++;
+		}
+		if (*src == c)
+			src++;
+	} else {
+		while (--len > 0 && *src && *src != ' ' && *src != '\t') {
+			*dest++ = *src++;
+		}
+	}
+	*dest = 0;
+	while (*src == ' ' || *src == '\t')
+		src++;
+	return src;
+}
+
+/**
+ * snd_info_create_entry - create an info entry
+ * @name: the proc file name
+ *
+ * Creates an info entry with the given file name and initializes as
+ * the default state.
+ *
+ * Usually called from other functions such as
+ * snd_info_create_card_entry().
+ *
+ * Returns the pointer of the new instance, or NULL on failure.
+ */
+static snd_info_entry_t *snd_info_create_entry(const char *name)
+{
+	snd_info_entry_t *entry;
+	entry = kcalloc(1, sizeof(*entry), GFP_KERNEL);
+	if (entry == NULL)
+		return NULL;
+	entry->name = snd_kmalloc_strdup(name, GFP_KERNEL);
+	if (entry->name == NULL) {
+		kfree(entry);
+		return NULL;
+	}
+	entry->mode = S_IFREG | S_IRUGO;
+	entry->content = SNDRV_INFO_CONTENT_TEXT;
+	init_MUTEX(&entry->access);
+	return entry;
+}
+
+/**
+ * snd_info_create_module_entry - create an info entry for the given module
+ * @module: the module pointer
+ * @name: the file name
+ * @parent: the parent directory
+ *
+ * Creates a new info entry and assigns it to the given module.
+ *
+ * Returns the pointer of the new instance, or NULL on failure.
+ */
+snd_info_entry_t *snd_info_create_module_entry(struct module * module,
+					       const char *name,
+					       snd_info_entry_t *parent)
+{
+	snd_info_entry_t *entry = snd_info_create_entry(name);
+	if (entry) {
+		entry->module = module;
+		entry->parent = parent;
+	}
+	return entry;
+}
+
+/**
+ * snd_info_create_card_entry - create an info entry for the given card
+ * @card: the card instance
+ * @name: the file name
+ * @parent: the parent directory
+ *
+ * Creates a new info entry and assigns it to the given card.
+ *
+ * Returns the pointer of the new instance, or NULL on failure.
+ */
+snd_info_entry_t *snd_info_create_card_entry(snd_card_t * card,
+					     const char *name,
+					     snd_info_entry_t * parent)
+{
+	snd_info_entry_t *entry = snd_info_create_entry(name);
+	if (entry) {
+		entry->module = card->module;
+		entry->card = card;
+		entry->parent = parent;
+	}
+	return entry;
+}
+
+static int snd_info_dev_free_entry(snd_device_t *device)
+{
+	snd_info_entry_t *entry = device->device_data;
+	snd_info_free_entry(entry);
+	return 0;
+}
+
+static int snd_info_dev_register_entry(snd_device_t *device)
+{
+	snd_info_entry_t *entry = device->device_data;
+	return snd_info_register(entry);
+}
+
+static int snd_info_dev_disconnect_entry(snd_device_t *device)
+{
+	snd_info_entry_t *entry = device->device_data;
+	entry->disconnected = 1;
+	return 0;
+}
+
+static int snd_info_dev_unregister_entry(snd_device_t *device)
+{
+	snd_info_entry_t *entry = device->device_data;
+	return snd_info_unregister(entry);
+}
+
+/**
+ * snd_card_proc_new - create an info entry for the given card
+ * @card: the card instance
+ * @name: the file name
+ * @entryp: the pointer to store the new info entry
+ *
+ * Creates a new info entry and assigns it to the given card.
+ * Unlike snd_info_create_card_entry(), this function registers the
+ * info entry as an ALSA device component, so that it can be
+ * unregistered/released without explicit call.
+ * Also, you don't have to register this entry via snd_info_register(),
+ * since this will be registered by snd_card_register() automatically.
+ *
+ * The parent is assumed as card->proc_root.
+ *
+ * For releasing this entry, use snd_device_free() instead of
+ * snd_info_free_entry(). 
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_card_proc_new(snd_card_t *card, const char *name,
+		      snd_info_entry_t **entryp)
+{
+	static snd_device_ops_t ops = {
+		.dev_free = snd_info_dev_free_entry,
+		.dev_register =	snd_info_dev_register_entry,
+		.dev_disconnect = snd_info_dev_disconnect_entry,
+		.dev_unregister = snd_info_dev_unregister_entry
+	};
+	snd_info_entry_t *entry;
+	int err;
+
+	entry = snd_info_create_card_entry(card, name, card->proc_root);
+	if (! entry)
+		return -ENOMEM;
+	if ((err = snd_device_new(card, SNDRV_DEV_INFO, entry, &ops)) < 0) {
+		snd_info_free_entry(entry);
+		return err;
+	}
+	if (entryp)
+		*entryp = entry;
+	return 0;
+}
+
+/**
+ * snd_info_free_entry - release the info entry
+ * @entry: the info entry
+ *
+ * Releases the info entry.  Don't call this after registered.
+ */
+void snd_info_free_entry(snd_info_entry_t * entry)
+{
+	if (entry == NULL)
+		return;
+	kfree(entry->name);
+	if (entry->private_free)
+		entry->private_free(entry);
+	kfree(entry);
+}
+
+/**
+ * snd_info_register - register the info entry
+ * @entry: the info entry
+ *
+ * Registers the proc info entry.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_info_register(snd_info_entry_t * entry)
+{
+	struct proc_dir_entry *root, *p = NULL;
+
+	snd_assert(entry != NULL, return -ENXIO);
+	root = entry->parent == NULL ? snd_proc_root : entry->parent->p;
+	down(&info_mutex);
+	p = snd_create_proc_entry(entry->name, entry->mode, root);
+	if (!p) {
+		up(&info_mutex);
+		return -ENOMEM;
+	}
+	p->owner = entry->module;
+	if (!S_ISDIR(entry->mode))
+		p->proc_fops = &snd_info_entry_operations;
+	p->size = entry->size;
+	p->data = entry;
+	entry->p = p;
+	up(&info_mutex);
+	return 0;
+}
+
+/**
+ * snd_info_unregister - de-register the info entry
+ * @entry: the info entry
+ *
+ * De-registers the info entry and releases the instance.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_info_unregister(snd_info_entry_t * entry)
+{
+	struct proc_dir_entry *root;
+
+	snd_assert(entry != NULL && entry->p != NULL, return -ENXIO);
+	root = entry->parent == NULL ? snd_proc_root : entry->parent->p;
+	snd_assert(root, return -ENXIO);
+	down(&info_mutex);
+	snd_remove_proc_entry(root, entry->p);
+	up(&info_mutex);
+	snd_info_free_entry(entry);
+	return 0;
+}
+
+/*
+
+ */
+
+static snd_info_entry_t *snd_info_version_entry = NULL;
+
+static void snd_info_version_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+	snd_iprintf(buffer,
+		    "Advanced Linux Sound Architecture Driver Version "
+		    CONFIG_SND_VERSION CONFIG_SND_DATE ".\n"
+		   );
+}
+
+static int __init snd_info_version_init(void)
+{
+	snd_info_entry_t *entry;
+
+	entry = snd_info_create_module_entry(THIS_MODULE, "version", NULL);
+	if (entry == NULL)
+		return -ENOMEM;
+	entry->c.text.read_size = 256;
+	entry->c.text.read = snd_info_version_read;
+	if (snd_info_register(entry) < 0) {
+		snd_info_free_entry(entry);
+		return -ENOMEM;
+	}
+	snd_info_version_entry = entry;
+	return 0;
+}
+
+static int __exit snd_info_version_done(void)
+{
+	if (snd_info_version_entry)
+		snd_info_unregister(snd_info_version_entry);
+	return 0;
+}
+
+#endif /* CONFIG_PROC_FS */
diff --git a/sound/core/info_oss.c b/sound/core/info_oss.c
new file mode 100644
index 000000000000..f9e4ce443454
--- /dev/null
+++ b/sound/core/info_oss.c
@@ -0,0 +1,137 @@
+/*
+ *  Information interface for ALSA driver
+ *  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/slab.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/info.h>
+#include <sound/version.h>
+#include <linux/utsname.h>
+
+#if defined(CONFIG_SND_OSSEMUL) && defined(CONFIG_PROC_FS)
+
+/*
+ *  OSS compatible part
+ */
+
+static DECLARE_MUTEX(strings);
+static char *snd_sndstat_strings[SNDRV_CARDS][SNDRV_OSS_INFO_DEV_COUNT];
+static snd_info_entry_t *snd_sndstat_proc_entry;
+
+int snd_oss_info_register(int dev, int num, char *string)
+{
+	char *x;
+
+	snd_assert(dev >= 0 && dev < SNDRV_OSS_INFO_DEV_COUNT, return -ENXIO);
+	snd_assert(num >= 0 && num < SNDRV_CARDS, return -ENXIO);
+	down(&strings);
+	if (string == NULL) {
+		if ((x = snd_sndstat_strings[num][dev]) != NULL) {
+			kfree(x);
+			x = NULL;
+		}
+	} else {
+		x = snd_kmalloc_strdup(string, GFP_KERNEL);
+		if (x == NULL) {
+			up(&strings);
+			return -ENOMEM;
+		}
+	}
+	snd_sndstat_strings[num][dev] = x;
+	up(&strings);
+	return 0;
+}
+
+extern void snd_card_info_read_oss(snd_info_buffer_t * buffer);
+
+static int snd_sndstat_show_strings(snd_info_buffer_t * buf, char *id, int dev)
+{
+	int idx, ok = -1;
+	char *str;
+
+	snd_iprintf(buf, "\n%s:", id);
+	down(&strings);
+	for (idx = 0; idx < SNDRV_CARDS; idx++) {
+		str = snd_sndstat_strings[idx][dev];
+		if (str) {
+			if (ok < 0) {
+				snd_iprintf(buf, "\n");
+				ok++;
+			}
+			snd_iprintf(buf, "%i: %s\n", idx, str);
+		}
+	}
+	up(&strings);
+	if (ok < 0)
+		snd_iprintf(buf, " NOT ENABLED IN CONFIG\n");
+	return ok;
+}
+
+static void snd_sndstat_proc_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+	snd_iprintf(buffer, "Sound Driver:3.8.1a-980706 (ALSA v" CONFIG_SND_VERSION " emulation code)\n");
+	snd_iprintf(buffer, "Kernel: %s %s %s %s %s\n",
+		    system_utsname.sysname,
+		    system_utsname.nodename,
+		    system_utsname.release,
+		    system_utsname.version,
+		    system_utsname.machine);
+	snd_iprintf(buffer, "Config options: 0\n");
+	snd_iprintf(buffer, "\nInstalled drivers: \n");
+	snd_iprintf(buffer, "Type 10: ALSA emulation\n");
+	snd_iprintf(buffer, "\nCard config: \n");
+	snd_card_info_read_oss(buffer);
+	snd_sndstat_show_strings(buffer, "Audio devices", SNDRV_OSS_INFO_DEV_AUDIO);
+	snd_sndstat_show_strings(buffer, "Synth devices", SNDRV_OSS_INFO_DEV_SYNTH);
+	snd_sndstat_show_strings(buffer, "Midi devices", SNDRV_OSS_INFO_DEV_MIDI);
+	snd_sndstat_show_strings(buffer, "Timers", SNDRV_OSS_INFO_DEV_TIMERS);
+	snd_sndstat_show_strings(buffer, "Mixers", SNDRV_OSS_INFO_DEV_MIXERS);
+}
+
+int snd_info_minor_register(void)
+{
+	snd_info_entry_t *entry;
+
+	memset(snd_sndstat_strings, 0, sizeof(snd_sndstat_strings));
+	if ((entry = snd_info_create_module_entry(THIS_MODULE, "sndstat", snd_oss_root)) != NULL) {
+		entry->c.text.read_size = 2048;
+		entry->c.text.read = snd_sndstat_proc_read;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	snd_sndstat_proc_entry = entry;
+	return 0;
+}
+
+int snd_info_minor_unregister(void)
+{
+	if (snd_sndstat_proc_entry) {
+		snd_info_unregister(snd_sndstat_proc_entry);
+		snd_sndstat_proc_entry = NULL;
+	}
+	return 0;
+}
+
+#endif /* CONFIG_SND_OSSEMUL */
diff --git a/sound/core/init.c b/sound/core/init.c
new file mode 100644
index 000000000000..3f1fa8eabb72
--- /dev/null
+++ b/sound/core/init.c
@@ -0,0 +1,887 @@
+/*
+ *  Initialization routines
+ *  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/sched.h>
+#include <linux/file.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/ctype.h>
+#include <linux/pci.h>
+#include <linux/pm.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+
+struct snd_shutdown_f_ops {
+	struct file_operations f_ops;
+	struct snd_shutdown_f_ops *next;
+};
+
+unsigned int snd_cards_lock = 0;	/* locked for registering/using */
+snd_card_t *snd_cards[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = NULL};
+DEFINE_RWLOCK(snd_card_rwlock);
+
+#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
+int (*snd_mixer_oss_notify_callback)(snd_card_t *card, int free_flag);
+#endif
+
+static void snd_card_id_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+	snd_iprintf(buffer, "%s\n", entry->card->id);
+}
+
+static void snd_card_free_thread(void * __card);
+
+/**
+ *  snd_card_new - create and initialize a soundcard structure
+ *  @idx: card index (address) [0 ... (SNDRV_CARDS-1)]
+ *  @xid: card identification (ASCII string)
+ *  @module: top level module for locking
+ *  @extra_size: allocate this extra size after the main soundcard structure
+ *
+ *  Creates and initializes a soundcard structure.
+ *
+ *  Returns kmallocated snd_card_t structure. Creates the ALSA control interface
+ *  (which is blocked until snd_card_register function is called).
+ */
+snd_card_t *snd_card_new(int idx, const char *xid,
+			 struct module *module, int extra_size)
+{
+	snd_card_t *card;
+	int err;
+
+	if (extra_size < 0)
+		extra_size = 0;
+	card = kcalloc(1, sizeof(*card) + extra_size, GFP_KERNEL);
+	if (card == NULL)
+		return NULL;
+	if (xid) {
+		if (!snd_info_check_reserved_words(xid))
+			goto __error;
+		strlcpy(card->id, xid, sizeof(card->id));
+	}
+	err = 0;
+	write_lock(&snd_card_rwlock);
+	if (idx < 0) {
+		int idx2;
+		for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++)
+			if (~snd_cards_lock & idx & 1<<idx2) {
+				idx = idx2;
+				if (idx >= snd_ecards_limit)
+					snd_ecards_limit = idx + 1;
+				break;
+			}
+	} else if (idx < snd_ecards_limit) {
+		if (snd_cards_lock & (1 << idx))
+			err = -ENODEV;	/* invalid */
+	} else if (idx < SNDRV_CARDS)
+		snd_ecards_limit = idx + 1; /* increase the limit */
+	else
+		err = -ENODEV;
+	if (idx < 0 || err < 0) {
+		write_unlock(&snd_card_rwlock);
+		snd_printk(KERN_ERR "cannot find the slot for index %d (range 0-%i)\n", idx, snd_ecards_limit - 1);
+		goto __error;
+	}
+	snd_cards_lock |= 1 << idx;		/* lock it */
+	write_unlock(&snd_card_rwlock);
+	card->number = idx;
+	card->module = module;
+	INIT_LIST_HEAD(&card->devices);
+	init_rwsem(&card->controls_rwsem);
+	rwlock_init(&card->ctl_files_rwlock);
+	INIT_LIST_HEAD(&card->controls);
+	INIT_LIST_HEAD(&card->ctl_files);
+	spin_lock_init(&card->files_lock);
+	init_waitqueue_head(&card->shutdown_sleep);
+	INIT_WORK(&card->free_workq, snd_card_free_thread, card);
+#ifdef CONFIG_PM
+	init_MUTEX(&card->power_lock);
+	init_waitqueue_head(&card->power_sleep);
+#endif
+	/* the control interface cannot be accessed from the user space until */
+	/* snd_cards_bitmask and snd_cards are set with snd_card_register */
+	if ((err = snd_ctl_create(card)) < 0) {
+		snd_printd("unable to register control minors\n");
+		goto __error;
+	}
+	if ((err = snd_info_card_create(card)) < 0) {
+		snd_printd("unable to create card info\n");
+		goto __error_ctl;
+	}
+	if (extra_size > 0)
+		card->private_data = (char *)card + sizeof(snd_card_t);
+	return card;
+
+      __error_ctl:
+	snd_device_free_all(card, SNDRV_DEV_CMD_PRE);
+      __error:
+	kfree(card);
+      	return NULL;
+}
+
+static unsigned int snd_disconnect_poll(struct file * file, poll_table * wait)
+{
+	return POLLERR | POLLNVAL;
+}
+
+/**
+ *  snd_card_disconnect - disconnect all APIs from the file-operations (user space)
+ *  @card: soundcard structure
+ *
+ *  Disconnects all APIs from the file-operations (user space).
+ *
+ *  Returns zero, otherwise a negative error code.
+ *
+ *  Note: The current implementation replaces all active file->f_op with special
+ *        dummy file operations (they do nothing except release).
+ */
+int snd_card_disconnect(snd_card_t * card)
+{
+	struct snd_monitor_file *mfile;
+	struct file *file;
+	struct snd_shutdown_f_ops *s_f_ops;
+	struct file_operations *f_ops, *old_f_ops;
+	int err;
+
+	spin_lock(&card->files_lock);
+	if (card->shutdown) {
+		spin_unlock(&card->files_lock);
+		return 0;
+	}
+	card->shutdown = 1;
+	spin_unlock(&card->files_lock);
+
+	/* phase 1: disable fops (user space) operations for ALSA API */
+	write_lock(&snd_card_rwlock);
+	snd_cards[card->number] = NULL;
+	write_unlock(&snd_card_rwlock);
+	
+	/* phase 2: replace file->f_op with special dummy operations */
+	
+	spin_lock(&card->files_lock);
+	mfile = card->files;
+	while (mfile) {
+		file = mfile->file;
+
+		/* it's critical part, use endless loop */
+		/* we have no room to fail */
+		s_f_ops = kmalloc(sizeof(struct snd_shutdown_f_ops), GFP_ATOMIC);
+		if (s_f_ops == NULL)
+			panic("Atomic allocation failed for snd_shutdown_f_ops!");
+
+		f_ops = &s_f_ops->f_ops;
+
+		memset(f_ops, 0, sizeof(*f_ops));
+		f_ops->owner = file->f_op->owner;
+		f_ops->release = file->f_op->release;
+		f_ops->poll = snd_disconnect_poll;
+
+		s_f_ops->next = card->s_f_ops;
+		card->s_f_ops = s_f_ops;
+		
+		f_ops = fops_get(f_ops);
+
+		old_f_ops = file->f_op;
+		file->f_op = f_ops;	/* must be atomic */
+		fops_put(old_f_ops);
+		
+		mfile = mfile->next;
+	}
+	spin_unlock(&card->files_lock);	
+
+	/* phase 3: notify all connected devices about disconnection */
+	/* at this point, they cannot respond to any calls except release() */
+
+#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
+	if (snd_mixer_oss_notify_callback)
+		snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_DISCONNECT);
+#endif
+
+	/* notify all devices that we are disconnected */
+	err = snd_device_disconnect_all(card);
+	if (err < 0)
+		snd_printk(KERN_ERR "not all devices for card %i can be disconnected\n", card->number);
+
+	return 0;	
+}
+
+#if defined(CONFIG_PM) && defined(CONFIG_SND_GENERIC_PM)
+static void snd_generic_device_unregister(struct snd_generic_device *dev);
+#endif
+
+/**
+ *  snd_card_free - frees given soundcard structure
+ *  @card: soundcard structure
+ *
+ *  This function releases the soundcard structure and the all assigned
+ *  devices automatically.  That is, you don't have to release the devices
+ *  by yourself.
+ *
+ *  Returns zero. Frees all associated devices and frees the control
+ *  interface associated to given soundcard.
+ */
+int snd_card_free(snd_card_t * card)
+{
+	struct snd_shutdown_f_ops *s_f_ops;
+
+	if (card == NULL)
+		return -EINVAL;
+	write_lock(&snd_card_rwlock);
+	snd_cards[card->number] = NULL;
+	write_unlock(&snd_card_rwlock);
+
+#ifdef CONFIG_PM
+	wake_up(&card->power_sleep);
+#ifdef CONFIG_SND_GENERIC_PM
+	if (card->pm_dev) {
+		snd_generic_device_unregister(card->pm_dev);
+		card->pm_dev = NULL;
+	}
+#endif
+#endif
+
+	/* wait, until all devices are ready for the free operation */
+	wait_event(card->shutdown_sleep, card->files == NULL);
+
+#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
+	if (snd_mixer_oss_notify_callback)
+		snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE);
+#endif
+	if (snd_device_free_all(card, SNDRV_DEV_CMD_PRE) < 0) {
+		snd_printk(KERN_ERR "unable to free all devices (pre)\n");
+		/* Fatal, but this situation should never occur */
+	}
+	if (snd_device_free_all(card, SNDRV_DEV_CMD_NORMAL) < 0) {
+		snd_printk(KERN_ERR "unable to free all devices (normal)\n");
+		/* Fatal, but this situation should never occur */
+	}
+	if (snd_device_free_all(card, SNDRV_DEV_CMD_POST) < 0) {
+		snd_printk(KERN_ERR "unable to free all devices (post)\n");
+		/* Fatal, but this situation should never occur */
+	}
+	if (card->private_free)
+		card->private_free(card);
+	if (card->proc_id)
+		snd_info_unregister(card->proc_id);
+	if (snd_info_card_free(card) < 0) {
+		snd_printk(KERN_WARNING "unable to free card info\n");
+		/* Not fatal error */
+	}
+	while (card->s_f_ops) {
+		s_f_ops = card->s_f_ops;
+		card->s_f_ops = s_f_ops->next;
+		kfree(s_f_ops);
+	}
+	write_lock(&snd_card_rwlock);
+	snd_cards_lock &= ~(1 << card->number);
+	write_unlock(&snd_card_rwlock);
+	kfree(card);
+	return 0;
+}
+
+static void snd_card_free_thread(void * __card)
+{
+	snd_card_t *card = __card;
+	struct module * module = card->module;
+
+	if (!try_module_get(module)) {
+		snd_printk(KERN_ERR "unable to lock toplevel module for card %i in free thread\n", card->number);
+		module = NULL;
+	}
+
+	snd_card_free(card);
+
+	module_put(module);
+}
+
+/**
+ *  snd_card_free_in_thread - call snd_card_free() in thread
+ *  @card: soundcard structure
+ *
+ *  This function schedules the call of snd_card_free() function in a
+ *  work queue.  When all devices are released (non-busy), the work
+ *  is woken up and calls snd_card_free().
+ *
+ *  When a card can be disconnected at any time by hotplug service,
+ *  this function should be used in disconnect (or detach) callback
+ *  instead of calling snd_card_free() directly.
+ *  
+ *  Returns - zero otherwise a negative error code if the start of thread failed.
+ */
+int snd_card_free_in_thread(snd_card_t * card)
+{
+	if (card->files == NULL) {
+		snd_card_free(card);
+		return 0;
+	}
+
+	if (schedule_work(&card->free_workq))
+		return 0;
+
+	snd_printk(KERN_ERR "schedule_work() failed in snd_card_free_in_thread for card %i\n", card->number);
+	/* try to free the structure immediately */
+	snd_card_free(card);
+	return -EFAULT;
+}
+
+static void choose_default_id(snd_card_t * card)
+{
+	int i, len, idx_flag = 0, loops = 8;
+	char *id, *spos;
+	
+	id = spos = card->shortname;	
+	while (*id != '\0') {
+		if (*id == ' ')
+			spos = id + 1;
+		id++;
+	}
+	id = card->id;
+	while (*spos != '\0' && !isalnum(*spos))
+		spos++;
+	if (isdigit(*spos))
+		*id++ = isalpha(card->shortname[0]) ? card->shortname[0] : 'D';
+	while (*spos != '\0' && (size_t)(id - card->id) < sizeof(card->id) - 1) {
+		if (isalnum(*spos))
+			*id++ = *spos;
+		spos++;
+	}
+	*id = '\0';
+
+	id = card->id;
+	
+	if (*id == '\0')
+		strcpy(id, "default");
+
+	while (1) {
+	      	if (loops-- == 0) {
+      			snd_printk(KERN_ERR "unable to choose default card id (%s)\n", id);
+      			strcpy(card->id, card->proc_root->name);
+      			return;
+      		}
+	      	if (!snd_info_check_reserved_words(id))
+      			goto __change;
+		for (i = 0; i < snd_ecards_limit; i++) {
+			if (snd_cards[i] && !strcmp(snd_cards[i]->id, id))
+				goto __change;
+		}
+		break;
+
+	      __change:
+		len = strlen(id);
+		if (idx_flag)
+			id[len-1]++;
+		else if ((size_t)len <= sizeof(card->id) - 3) {
+			strcat(id, "_1");
+			idx_flag++;
+		} else {
+			spos = id + len - 2;
+			if ((size_t)len <= sizeof(card->id) - 2)
+				spos++;
+			*spos++ = '_';
+			*spos++ = '1';
+			*spos++ = '\0';
+			idx_flag++;
+		}
+	}
+}
+
+/**
+ *  snd_card_register - register the soundcard
+ *  @card: soundcard structure
+ *
+ *  This function registers all the devices assigned to the soundcard.
+ *  Until calling this, the ALSA control interface is blocked from the
+ *  external accesses.  Thus, you should call this function at the end
+ *  of the initialization of the card.
+ *
+ *  Returns zero otherwise a negative error code if the registrain failed.
+ */
+int snd_card_register(snd_card_t * card)
+{
+	int err;
+	snd_info_entry_t *entry;
+
+	snd_runtime_check(card != NULL, return -EINVAL);
+	if ((err = snd_device_register_all(card)) < 0)
+		return err;
+	write_lock(&snd_card_rwlock);
+	if (snd_cards[card->number]) {
+		/* already registered */
+		write_unlock(&snd_card_rwlock);
+		return 0;
+	}
+	if (card->id[0] == '\0')
+		choose_default_id(card);
+	snd_cards[card->number] = card;
+	write_unlock(&snd_card_rwlock);
+	if ((err = snd_info_card_register(card)) < 0) {
+		snd_printd("unable to create card info\n");
+		goto __skip_info;
+	}
+	if ((entry = snd_info_create_card_entry(card, "id", card->proc_root)) == NULL) {
+		snd_printd("unable to create card entry\n");
+		goto __skip_info;
+	}
+	entry->c.text.read_size = PAGE_SIZE;
+	entry->c.text.read = snd_card_id_read;
+	if (snd_info_register(entry) < 0) {
+		snd_info_free_entry(entry);
+		entry = NULL;
+	}
+	card->proc_id = entry;
+      __skip_info:
+#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
+	if (snd_mixer_oss_notify_callback)
+		snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);
+#endif
+	return 0;
+}
+
+static snd_info_entry_t *snd_card_info_entry = NULL;
+
+static void snd_card_info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+	int idx, count;
+	snd_card_t *card;
+
+	for (idx = count = 0; idx < SNDRV_CARDS; idx++) {
+		read_lock(&snd_card_rwlock);
+		if ((card = snd_cards[idx]) != NULL) {
+			count++;
+			snd_iprintf(buffer, "%i [%-15s]: %s - %s\n",
+					idx,
+					card->id,
+					card->driver,
+					card->shortname);
+			snd_iprintf(buffer, "                     %s\n",
+					card->longname);
+		}
+		read_unlock(&snd_card_rwlock);
+	}
+	if (!count)
+		snd_iprintf(buffer, "--- no soundcards ---\n");
+}
+
+#if defined(CONFIG_SND_OSSEMUL) && defined(CONFIG_PROC_FS)
+
+void snd_card_info_read_oss(snd_info_buffer_t * buffer)
+{
+	int idx, count;
+	snd_card_t *card;
+
+	for (idx = count = 0; idx < SNDRV_CARDS; idx++) {
+		read_lock(&snd_card_rwlock);
+		if ((card = snd_cards[idx]) != NULL) {
+			count++;
+			snd_iprintf(buffer, "%s\n", card->longname);
+		}
+		read_unlock(&snd_card_rwlock);
+	}
+	if (!count) {
+		snd_iprintf(buffer, "--- no soundcards ---\n");
+	}
+}
+
+#endif
+
+#ifdef MODULE
+static snd_info_entry_t *snd_card_module_info_entry;
+static void snd_card_module_info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+	int idx;
+	snd_card_t *card;
+
+	for (idx = 0; idx < SNDRV_CARDS; idx++) {
+		read_lock(&snd_card_rwlock);
+		if ((card = snd_cards[idx]) != NULL)
+			snd_iprintf(buffer, "%i %s\n", idx, card->module->name);
+		read_unlock(&snd_card_rwlock);
+	}
+}
+#endif
+
+int __init snd_card_info_init(void)
+{
+	snd_info_entry_t *entry;
+
+	entry = snd_info_create_module_entry(THIS_MODULE, "cards", NULL);
+	snd_runtime_check(entry != NULL, return -ENOMEM);
+	entry->c.text.read_size = PAGE_SIZE;
+	entry->c.text.read = snd_card_info_read;
+	if (snd_info_register(entry) < 0) {
+		snd_info_free_entry(entry);
+		return -ENOMEM;
+	}
+	snd_card_info_entry = entry;
+
+#ifdef MODULE
+	entry = snd_info_create_module_entry(THIS_MODULE, "modules", NULL);
+	if (entry) {
+		entry->c.text.read_size = PAGE_SIZE;
+		entry->c.text.read = snd_card_module_info_read;
+		if (snd_info_register(entry) < 0)
+			snd_info_free_entry(entry);
+		else
+			snd_card_module_info_entry = entry;
+	}
+#endif
+
+	return 0;
+}
+
+int __exit snd_card_info_done(void)
+{
+	if (snd_card_info_entry)
+		snd_info_unregister(snd_card_info_entry);
+#ifdef MODULE
+	if (snd_card_module_info_entry)
+		snd_info_unregister(snd_card_module_info_entry);
+#endif
+	return 0;
+}
+
+/**
+ *  snd_component_add - add a component string
+ *  @card: soundcard structure
+ *  @component: the component id string
+ *
+ *  This function adds the component id string to the supported list.
+ *  The component can be referred from the alsa-lib.
+ *
+ *  Returns zero otherwise a negative error code.
+ */
+  
+int snd_component_add(snd_card_t *card, const char *component)
+{
+	char *ptr;
+	int len = strlen(component);
+
+	ptr = strstr(card->components, component);
+	if (ptr != NULL) {
+		if (ptr[len] == '\0' || ptr[len] == ' ')	/* already there */
+			return 1;
+	}
+	if (strlen(card->components) + 1 + len + 1 > sizeof(card->components)) {
+		snd_BUG();
+		return -ENOMEM;
+	}
+	if (card->components[0] != '\0')
+		strcat(card->components, " ");
+	strcat(card->components, component);
+	return 0;
+}
+
+/**
+ *  snd_card_file_add - add the file to the file list of the card
+ *  @card: soundcard structure
+ *  @file: file pointer
+ *
+ *  This function adds the file to the file linked-list of the card.
+ *  This linked-list is used to keep tracking the connection state,
+ *  and to avoid the release of busy resources by hotplug.
+ *
+ *  Returns zero or a negative error code.
+ */
+int snd_card_file_add(snd_card_t *card, struct file *file)
+{
+	struct snd_monitor_file *mfile;
+
+	mfile = kmalloc(sizeof(*mfile), GFP_KERNEL);
+	if (mfile == NULL)
+		return -ENOMEM;
+	mfile->file = file;
+	mfile->next = NULL;
+	spin_lock(&card->files_lock);
+	if (card->shutdown) {
+		spin_unlock(&card->files_lock);
+		kfree(mfile);
+		return -ENODEV;
+	}
+	mfile->next = card->files;
+	card->files = mfile;
+	spin_unlock(&card->files_lock);
+	return 0;
+}
+
+/**
+ *  snd_card_file_remove - remove the file from the file list
+ *  @card: soundcard structure
+ *  @file: file pointer
+ *
+ *  This function removes the file formerly added to the card via
+ *  snd_card_file_add() function.
+ *  If all files are removed and the release of the card is
+ *  scheduled, it will wake up the the thread to call snd_card_free()
+ *  (see snd_card_free_in_thread() function).
+ *
+ *  Returns zero or a negative error code.
+ */
+int snd_card_file_remove(snd_card_t *card, struct file *file)
+{
+	struct snd_monitor_file *mfile, *pfile = NULL;
+
+	spin_lock(&card->files_lock);
+	mfile = card->files;
+	while (mfile) {
+		if (mfile->file == file) {
+			if (pfile)
+				pfile->next = mfile->next;
+			else
+				card->files = mfile->next;
+			break;
+		}
+		pfile = mfile;
+		mfile = mfile->next;
+	}
+	spin_unlock(&card->files_lock);
+	if (card->files == NULL)
+		wake_up(&card->shutdown_sleep);
+	if (!mfile) {
+		snd_printk(KERN_ERR "ALSA card file remove problem (%p)\n", file);
+		return -ENOENT;
+	}
+	kfree(mfile);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+/**
+ *  snd_power_wait - wait until the power-state is changed.
+ *  @card: soundcard structure
+ *  @power_state: expected power state
+ *  @file: file structure for the O_NONBLOCK check (optional)
+ *
+ *  Waits until the power-state is changed.
+ *
+ *  Note: the power lock must be active before call.
+ */
+int snd_power_wait(snd_card_t *card, unsigned int power_state, struct file *file)
+{
+	wait_queue_t wait;
+	int result = 0;
+
+	/* fastpath */
+	if (snd_power_get_state(card) == power_state)
+		return 0;
+	init_waitqueue_entry(&wait, current);
+	add_wait_queue(&card->power_sleep, &wait);
+	while (1) {
+		if (card->shutdown) {
+			result = -ENODEV;
+			break;
+		}
+		if (snd_power_get_state(card) == power_state)
+			break;
+#if 0 /* block all devices */
+		if (file && (file->f_flags & O_NONBLOCK)) {
+			result = -EAGAIN;
+			break;
+		}
+#endif
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		snd_power_unlock(card);
+		schedule_timeout(30 * HZ);
+		snd_power_lock(card);
+	}
+	remove_wait_queue(&card->power_sleep, &wait);
+	return result;
+}
+
+/**
+ * snd_card_set_pm_callback - set the PCI power-management callbacks
+ * @card: soundcard structure
+ * @suspend: suspend callback function
+ * @resume: resume callback function
+ * @private_data: private data to pass to the callback functions
+ *
+ * Sets the power-management callback functions of the card.
+ * These callbacks are called from ALSA's common PCI suspend/resume
+ * handler and from the control API.
+ */
+int snd_card_set_pm_callback(snd_card_t *card,
+			     int (*suspend)(snd_card_t *, pm_message_t),
+			     int (*resume)(snd_card_t *),
+			     void *private_data)
+{
+	card->pm_suspend = suspend;
+	card->pm_resume = resume;
+	card->pm_private_data = private_data;
+	return 0;
+}
+
+#ifdef CONFIG_SND_GENERIC_PM
+/*
+ * use platform_device for generic power-management without a proper bus
+ * (e.g. ISA)
+ */
+struct snd_generic_device {
+	struct platform_device pdev;
+	snd_card_t *card;
+};
+
+#define get_snd_generic_card(dev)	container_of(to_platform_device(dev), struct snd_generic_device, pdev)->card
+
+#define SND_GENERIC_NAME	"snd_generic_pm"
+
+static int snd_generic_suspend(struct device *dev, u32 state, u32 level);
+static int snd_generic_resume(struct device *dev, u32 level);
+
+static struct device_driver snd_generic_driver = {
+	.name		= SND_GENERIC_NAME,
+	.bus		= &platform_bus_type,
+	.suspend	= snd_generic_suspend,
+	.resume		= snd_generic_resume,
+};
+
+static int generic_driver_registered;
+
+static void generic_driver_unregister(void)
+{
+	if (generic_driver_registered) {
+		generic_driver_registered--;
+		if (! generic_driver_registered)
+			driver_unregister(&snd_generic_driver);
+	}
+}
+
+static struct snd_generic_device *snd_generic_device_register(snd_card_t *card)
+{
+	struct snd_generic_device *dev;
+
+	if (! generic_driver_registered) {
+		if (driver_register(&snd_generic_driver) < 0)
+			return NULL;
+	}
+	generic_driver_registered++;
+
+	dev = kcalloc(1, sizeof(*dev), GFP_KERNEL);
+	if (! dev) {
+		generic_driver_unregister();
+		return NULL;
+	}
+
+	dev->pdev.name = SND_GENERIC_NAME;
+	dev->pdev.id = card->number;
+	dev->card = card;
+	if (platform_device_register(&dev->pdev) < 0) {
+		kfree(dev);
+		generic_driver_unregister();
+		return NULL;
+	}
+	return dev;
+}
+
+static void snd_generic_device_unregister(struct snd_generic_device *dev)
+{
+	platform_device_unregister(&dev->pdev);
+	kfree(dev);
+	generic_driver_unregister();
+}
+
+/* suspend/resume callbacks for snd_generic platform device */
+static int snd_generic_suspend(struct device *dev, u32 state, u32 level)
+{
+	snd_card_t *card;
+
+	if (level != SUSPEND_DISABLE)
+		return 0;
+
+	card = get_snd_generic_card(dev);
+	if (card->power_state == SNDRV_CTL_POWER_D3hot)
+		return 0;
+	card->pm_suspend(card, PMSG_SUSPEND);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	return 0;
+}
+
+static int snd_generic_resume(struct device *dev, u32 level)
+{
+	snd_card_t *card;
+
+	if (level != RESUME_ENABLE)
+		return 0;
+
+	card = get_snd_generic_card(dev);
+	if (card->power_state == SNDRV_CTL_POWER_D0)
+		return 0;
+	card->pm_resume(card);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+
+/**
+ * snd_card_set_generic_pm_callback - set the generic power-management callbacks
+ * @card: soundcard structure
+ * @suspend: suspend callback function
+ * @resume: resume callback function
+ * @private_data: private data to pass to the callback functions
+ *
+ * Registers the power-management and sets the lowlevel callbacks for
+ * the given card.  These callbacks are called from the ALSA's common
+ * PM handler and from the control API.
+ */
+int snd_card_set_generic_pm_callback(snd_card_t *card,
+				 int (*suspend)(snd_card_t *, pm_message_t),
+				 int (*resume)(snd_card_t *),
+				 void *private_data)
+{
+	card->pm_dev = snd_generic_device_register(card);
+	if (! card->pm_dev)
+		return -ENOMEM;
+	snd_card_set_pm_callback(card, suspend, resume, private_data);
+	return 0;
+}
+#endif /* CONFIG_SND_GENERIC_PM */
+
+#ifdef CONFIG_PCI
+int snd_card_pci_suspend(struct pci_dev *dev, pm_message_t state)
+{
+	snd_card_t *card = pci_get_drvdata(dev);
+	int err;
+	if (! card || ! card->pm_suspend)
+		return 0;
+	if (card->power_state == SNDRV_CTL_POWER_D3hot)
+		return 0;
+	err = card->pm_suspend(card, PMSG_SUSPEND);
+	pci_save_state(dev);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	return err;
+}
+
+int snd_card_pci_resume(struct pci_dev *dev)
+{
+	snd_card_t *card = pci_get_drvdata(dev);
+	if (! card || ! card->pm_resume)
+		return 0;
+	if (card->power_state == SNDRV_CTL_POWER_D0)
+		return 0;
+	/* restore the PCI config space */
+	pci_restore_state(dev);
+	card->pm_resume(card);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+#endif /* CONFIG_PM */
diff --git a/sound/core/isadma.c b/sound/core/isadma.c
new file mode 100644
index 000000000000..1a378951da5b
--- /dev/null
+++ b/sound/core/isadma.c
@@ -0,0 +1,103 @@
+/*
+ *  ISA DMA support functions
+ *  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
+ *
+ */
+
+/*
+ * Defining following add some delay. Maybe this helps for some broken
+ * ISA DMA controllers.
+ */
+
+#undef HAVE_REALLY_SLOW_DMA_CONTROLLER
+
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <asm/dma.h>
+
+/**
+ * snd_dma_program - program an ISA DMA transfer
+ * @dma: the dma number
+ * @addr: the physical address of the buffer
+ * @size: the DMA transfer size
+ * @mode: the DMA transfer mode, DMA_MODE_XXX
+ *
+ * Programs an ISA DMA transfer for the given buffer.
+ */
+void snd_dma_program(unsigned long dma,
+		     unsigned long addr, unsigned int size,
+                     unsigned short mode)
+{
+	unsigned long flags;
+
+	flags = claim_dma_lock();
+	disable_dma(dma);
+	clear_dma_ff(dma);
+	set_dma_mode(dma, mode);
+	set_dma_addr(dma, addr);
+	set_dma_count(dma, size);
+	if (!(mode & DMA_MODE_NO_ENABLE))
+		enable_dma(dma);
+	release_dma_lock(flags);
+}
+
+/**
+ * snd_dma_disable - stop the ISA DMA transfer
+ * @dma: the dma number
+ *
+ * Stops the ISA DMA transfer.
+ */
+void snd_dma_disable(unsigned long dma)
+{
+	unsigned long flags;
+
+	flags = claim_dma_lock();
+	clear_dma_ff(dma);
+	disable_dma(dma);
+	release_dma_lock(flags);
+}
+
+/**
+ * snd_dma_pointer - return the current pointer to DMA transfer buffer in bytes
+ * @dma: the dma number
+ * @size: the dma transfer size
+ *
+ * Returns the current pointer in DMA tranfer buffer in bytes
+ */
+unsigned int snd_dma_pointer(unsigned long dma, unsigned int size)
+{
+	unsigned long flags;
+	unsigned int result;
+
+	flags = claim_dma_lock();
+	clear_dma_ff(dma);
+	if (!isa_dma_bridge_buggy)
+		disable_dma(dma);
+	result = get_dma_residue(dma);
+	if (!isa_dma_bridge_buggy)
+		enable_dma(dma);
+	release_dma_lock(flags);
+#ifdef CONFIG_SND_DEBUG
+	if (result > size)
+		snd_printk(KERN_ERR "pointer (0x%x) for DMA #%ld is greater than transfer size (0x%x)\n", result, dma, size);
+#endif
+	if (result >= size || result == 0)
+		return 0;
+	else
+		return size - result;
+}
diff --git a/sound/core/memalloc.c b/sound/core/memalloc.c
new file mode 100644
index 000000000000..344a83fd7c2e
--- /dev/null
+++ b/sound/core/memalloc.c
@@ -0,0 +1,663 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *                   Takashi Iwai <tiwai@suse.de>
+ * 
+ *  Generic memory allocators
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/dma-mapping.h>
+#include <linux/moduleparam.h>
+#include <asm/semaphore.h>
+#include <sound/memalloc.h>
+#ifdef CONFIG_SBUS
+#include <asm/sbus.h>
+#endif
+
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>, Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Memory allocator for ALSA system.");
+MODULE_LICENSE("GPL");
+
+
+#ifndef SNDRV_CARDS
+#define SNDRV_CARDS	8
+#endif
+
+/* FIXME: so far only some PCI devices have the preallocation table */
+#ifdef CONFIG_PCI
+static int enable[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1};
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable cards to allocate buffers.");
+#endif
+
+/*
+ */
+
+void *snd_malloc_sgbuf_pages(struct device *device,
+                             size_t size, struct snd_dma_buffer *dmab,
+			     size_t *res_size);
+int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab);
+
+/*
+ */
+
+static DECLARE_MUTEX(list_mutex);
+static LIST_HEAD(mem_list_head);
+
+/* buffer preservation list */
+struct snd_mem_list {
+	struct snd_dma_buffer buffer;
+	unsigned int id;
+	struct list_head list;
+};
+
+/* id for pre-allocated buffers */
+#define SNDRV_DMA_DEVICE_UNUSED (unsigned int)-1
+
+#ifdef CONFIG_SND_DEBUG
+#define __ASTRING__(x) #x
+#define snd_assert(expr, args...) do {\
+	if (!(expr)) {\
+		printk(KERN_ERR "snd-malloc: BUG? (%s) (called from %p)\n", __ASTRING__(expr), __builtin_return_address(0));\
+		args;\
+	}\
+} while (0)
+#else
+#define snd_assert(expr, args...) /**/
+#endif
+
+/*
+ *  Hacks
+ */
+
+#if defined(__i386__) || defined(__ppc__) || defined(__x86_64__)
+/*
+ * A hack to allocate large buffers via dma_alloc_coherent()
+ *
+ * since dma_alloc_coherent always tries GFP_DMA when the requested
+ * pci memory region is below 32bit, it happens quite often that even
+ * 2 order of pages cannot be allocated.
+ *
+ * so in the following, we allocate at first without dma_mask, so that
+ * allocation will be done without GFP_DMA.  if the area doesn't match
+ * with the requested region, then realloate with the original dma_mask
+ * again.
+ *
+ * Really, we want to move this type of thing into dma_alloc_coherent()
+ * so dma_mask doesn't have to be messed with.
+ */
+
+static void *snd_dma_hack_alloc_coherent(struct device *dev, size_t size,
+					 dma_addr_t *dma_handle, int flags)
+{
+	void *ret;
+	u64 dma_mask, coherent_dma_mask;
+
+	if (dev == NULL || !dev->dma_mask)
+		return dma_alloc_coherent(dev, size, dma_handle, flags);
+	dma_mask = *dev->dma_mask;
+	coherent_dma_mask = dev->coherent_dma_mask;
+	*dev->dma_mask = 0xffffffff; 	/* do without masking */
+	dev->coherent_dma_mask = 0xffffffff; 	/* do without masking */
+	ret = dma_alloc_coherent(dev, size, dma_handle, flags);
+	*dev->dma_mask = dma_mask;	/* restore */
+	dev->coherent_dma_mask = coherent_dma_mask;	/* restore */
+	if (ret) {
+		/* obtained address is out of range? */
+		if (((unsigned long)*dma_handle + size - 1) & ~dma_mask) {
+			/* reallocate with the proper mask */
+			dma_free_coherent(dev, size, ret, *dma_handle);
+			ret = dma_alloc_coherent(dev, size, dma_handle, flags);
+		}
+	} else {
+		/* wish to success now with the proper mask... */
+		if (dma_mask != 0xffffffffUL) {
+			/* allocation with GFP_ATOMIC to avoid the long stall */
+			flags &= ~GFP_KERNEL;
+			flags |= GFP_ATOMIC;
+			ret = dma_alloc_coherent(dev, size, dma_handle, flags);
+		}
+	}
+	return ret;
+}
+
+/* redefine dma_alloc_coherent for some architectures */
+#undef dma_alloc_coherent
+#define dma_alloc_coherent snd_dma_hack_alloc_coherent
+
+#endif /* arch */
+
+#if ! defined(__arm__)
+#define NEED_RESERVE_PAGES
+#endif
+
+/*
+ *
+ *  Generic memory allocators
+ *
+ */
+
+static long snd_allocated_pages; /* holding the number of allocated pages */
+
+static inline void inc_snd_pages(int order)
+{
+	snd_allocated_pages += 1 << order;
+}
+
+static inline void dec_snd_pages(int order)
+{
+	snd_allocated_pages -= 1 << order;
+}
+
+static void mark_pages(struct page *page, int order)
+{
+	struct page *last_page = page + (1 << order);
+	while (page < last_page)
+		SetPageReserved(page++);
+}
+
+static void unmark_pages(struct page *page, int order)
+{
+	struct page *last_page = page + (1 << order);
+	while (page < last_page)
+		ClearPageReserved(page++);
+}
+
+/**
+ * snd_malloc_pages - allocate pages with the given size
+ * @size: the size to allocate in bytes
+ * @gfp_flags: the allocation conditions, GFP_XXX
+ *
+ * Allocates the physically contiguous pages with the given size.
+ *
+ * Returns the pointer of the buffer, or NULL if no enoguh memory.
+ */
+void *snd_malloc_pages(size_t size, unsigned int gfp_flags)
+{
+	int pg;
+	void *res;
+
+	snd_assert(size > 0, return NULL);
+	snd_assert(gfp_flags != 0, return NULL);
+	pg = get_order(size);
+	if ((res = (void *) __get_free_pages(gfp_flags, pg)) != NULL) {
+		mark_pages(virt_to_page(res), pg);
+		inc_snd_pages(pg);
+	}
+	return res;
+}
+
+/**
+ * snd_free_pages - release the pages
+ * @ptr: the buffer pointer to release
+ * @size: the allocated buffer size
+ *
+ * Releases the buffer allocated via snd_malloc_pages().
+ */
+void snd_free_pages(void *ptr, size_t size)
+{
+	int pg;
+
+	if (ptr == NULL)
+		return;
+	pg = get_order(size);
+	dec_snd_pages(pg);
+	unmark_pages(virt_to_page(ptr), pg);
+	free_pages((unsigned long) ptr, pg);
+}
+
+/*
+ *
+ *  Bus-specific memory allocators
+ *
+ */
+
+/* allocate the coherent DMA pages */
+static void *snd_malloc_dev_pages(struct device *dev, size_t size, dma_addr_t *dma)
+{
+	int pg;
+	void *res;
+	unsigned int gfp_flags;
+
+	snd_assert(size > 0, return NULL);
+	snd_assert(dma != NULL, return NULL);
+	pg = get_order(size);
+	gfp_flags = GFP_KERNEL
+		| __GFP_NORETRY /* don't trigger OOM-killer */
+		| __GFP_NOWARN; /* no stack trace print - this call is non-critical */
+	res = dma_alloc_coherent(dev, PAGE_SIZE << pg, dma, gfp_flags);
+	if (res != NULL) {
+#ifdef NEED_RESERVE_PAGES
+		mark_pages(virt_to_page(res), pg); /* should be dma_to_page() */
+#endif
+		inc_snd_pages(pg);
+	}
+
+	return res;
+}
+
+/* free the coherent DMA pages */
+static void snd_free_dev_pages(struct device *dev, size_t size, void *ptr,
+			       dma_addr_t dma)
+{
+	int pg;
+
+	if (ptr == NULL)
+		return;
+	pg = get_order(size);
+	dec_snd_pages(pg);
+#ifdef NEED_RESERVE_PAGES
+	unmark_pages(virt_to_page(ptr), pg); /* should be dma_to_page() */
+#endif
+	dma_free_coherent(dev, PAGE_SIZE << pg, ptr, dma);
+}
+
+#ifdef CONFIG_SBUS
+
+static void *snd_malloc_sbus_pages(struct device *dev, size_t size,
+				   dma_addr_t *dma_addr)
+{
+	struct sbus_dev *sdev = (struct sbus_dev *)dev;
+	int pg;
+	void *res;
+
+	snd_assert(size > 0, return NULL);
+	snd_assert(dma_addr != NULL, return NULL);
+	pg = get_order(size);
+	res = sbus_alloc_consistent(sdev, PAGE_SIZE * (1 << pg), dma_addr);
+	if (res != NULL)
+		inc_snd_pages(pg);
+	return res;
+}
+
+static void snd_free_sbus_pages(struct device *dev, size_t size,
+				void *ptr, dma_addr_t dma_addr)
+{
+	struct sbus_dev *sdev = (struct sbus_dev *)dev;
+	int pg;
+
+	if (ptr == NULL)
+		return;
+	pg = get_order(size);
+	dec_snd_pages(pg);
+	sbus_free_consistent(sdev, PAGE_SIZE * (1 << pg), ptr, dma_addr);
+}
+
+#endif /* CONFIG_SBUS */
+
+/*
+ *
+ *  ALSA generic memory management
+ *
+ */
+
+
+/**
+ * snd_dma_alloc_pages - allocate the buffer area according to the given type
+ * @type: the DMA buffer type
+ * @device: the device pointer
+ * @size: the buffer size to allocate
+ * @dmab: buffer allocation record to store the allocated data
+ *
+ * Calls the memory-allocator function for the corresponding
+ * buffer type.
+ * 
+ * Returns zero if the buffer with the given size is allocated successfuly,
+ * other a negative value at error.
+ */
+int snd_dma_alloc_pages(int type, struct device *device, size_t size,
+			struct snd_dma_buffer *dmab)
+{
+	snd_assert(size > 0, return -ENXIO);
+	snd_assert(dmab != NULL, return -ENXIO);
+
+	dmab->dev.type = type;
+	dmab->dev.dev = device;
+	dmab->bytes = 0;
+	switch (type) {
+	case SNDRV_DMA_TYPE_CONTINUOUS:
+		dmab->area = snd_malloc_pages(size, (unsigned long)device);
+		dmab->addr = 0;
+		break;
+#ifdef CONFIG_SBUS
+	case SNDRV_DMA_TYPE_SBUS:
+		dmab->area = snd_malloc_sbus_pages(device, size, &dmab->addr);
+		break;
+#endif
+	case SNDRV_DMA_TYPE_DEV:
+		dmab->area = snd_malloc_dev_pages(device, size, &dmab->addr);
+		break;
+	case SNDRV_DMA_TYPE_DEV_SG:
+		snd_malloc_sgbuf_pages(device, size, dmab, NULL);
+		break;
+	default:
+		printk(KERN_ERR "snd-malloc: invalid device type %d\n", type);
+		dmab->area = NULL;
+		dmab->addr = 0;
+		return -ENXIO;
+	}
+	if (! dmab->area)
+		return -ENOMEM;
+	dmab->bytes = size;
+	return 0;
+}
+
+/**
+ * snd_dma_alloc_pages_fallback - allocate the buffer area according to the given type with fallback
+ * @type: the DMA buffer type
+ * @device: the device pointer
+ * @size: the buffer size to allocate
+ * @dmab: buffer allocation record to store the allocated data
+ *
+ * Calls the memory-allocator function for the corresponding
+ * buffer type.  When no space is left, this function reduces the size and
+ * tries to allocate again.  The size actually allocated is stored in
+ * res_size argument.
+ * 
+ * Returns zero if the buffer with the given size is allocated successfuly,
+ * other a negative value at error.
+ */
+int snd_dma_alloc_pages_fallback(int type, struct device *device, size_t size,
+				 struct snd_dma_buffer *dmab)
+{
+	int err;
+
+	snd_assert(size > 0, return -ENXIO);
+	snd_assert(dmab != NULL, return -ENXIO);
+
+	while ((err = snd_dma_alloc_pages(type, device, size, dmab)) < 0) {
+		if (err != -ENOMEM)
+			return err;
+		size >>= 1;
+		if (size <= PAGE_SIZE)
+			return -ENOMEM;
+	}
+	if (! dmab->area)
+		return -ENOMEM;
+	return 0;
+}
+
+
+/**
+ * snd_dma_free_pages - release the allocated buffer
+ * @dmab: the buffer allocation record to release
+ *
+ * Releases the allocated buffer via snd_dma_alloc_pages().
+ */
+void snd_dma_free_pages(struct snd_dma_buffer *dmab)
+{
+	switch (dmab->dev.type) {
+	case SNDRV_DMA_TYPE_CONTINUOUS:
+		snd_free_pages(dmab->area, dmab->bytes);
+		break;
+#ifdef CONFIG_SBUS
+	case SNDRV_DMA_TYPE_SBUS:
+		snd_free_sbus_pages(dmab->dev.dev, dmab->bytes, dmab->area, dmab->addr);
+		break;
+#endif
+	case SNDRV_DMA_TYPE_DEV:
+		snd_free_dev_pages(dmab->dev.dev, dmab->bytes, dmab->area, dmab->addr);
+		break;
+	case SNDRV_DMA_TYPE_DEV_SG:
+		snd_free_sgbuf_pages(dmab);
+		break;
+	default:
+		printk(KERN_ERR "snd-malloc: invalid device type %d\n", dmab->dev.type);
+	}
+}
+
+
+/**
+ * snd_dma_get_reserved - get the reserved buffer for the given device
+ * @dmab: the buffer allocation record to store
+ * @id: the buffer id
+ *
+ * Looks for the reserved-buffer list and re-uses if the same buffer
+ * is found in the list.  When the buffer is found, it's removed from the free list.
+ *
+ * Returns the size of buffer if the buffer is found, or zero if not found.
+ */
+size_t snd_dma_get_reserved_buf(struct snd_dma_buffer *dmab, unsigned int id)
+{
+	struct list_head *p;
+	struct snd_mem_list *mem;
+
+	snd_assert(dmab, return 0);
+
+	down(&list_mutex);
+	list_for_each(p, &mem_list_head) {
+		mem = list_entry(p, struct snd_mem_list, list);
+		if (mem->id == id &&
+		    ! memcmp(&mem->buffer.dev, &dmab->dev, sizeof(dmab->dev))) {
+			list_del(p);
+			*dmab = mem->buffer;
+			kfree(mem);
+			up(&list_mutex);
+			return dmab->bytes;
+		}
+	}
+	up(&list_mutex);
+	return 0;
+}
+
+/**
+ * snd_dma_reserve_buf - reserve the buffer
+ * @dmab: the buffer to reserve
+ * @id: the buffer id
+ *
+ * Reserves the given buffer as a reserved buffer.
+ * 
+ * Returns zero if successful, or a negative code at error.
+ */
+int snd_dma_reserve_buf(struct snd_dma_buffer *dmab, unsigned int id)
+{
+	struct snd_mem_list *mem;
+
+	snd_assert(dmab, return -EINVAL);
+	mem = kmalloc(sizeof(*mem), GFP_KERNEL);
+	if (! mem)
+		return -ENOMEM;
+	down(&list_mutex);
+	mem->buffer = *dmab;
+	mem->id = id;
+	list_add_tail(&mem->list, &mem_list_head);
+	up(&list_mutex);
+	return 0;
+}
+
+/*
+ * purge all reserved buffers
+ */
+static void free_all_reserved_pages(void)
+{
+	struct list_head *p;
+	struct snd_mem_list *mem;
+
+	down(&list_mutex);
+	while (! list_empty(&mem_list_head)) {
+		p = mem_list_head.next;
+		mem = list_entry(p, struct snd_mem_list, list);
+		list_del(p);
+		snd_dma_free_pages(&mem->buffer);
+		kfree(mem);
+	}
+	up(&list_mutex);
+}
+
+
+
+/*
+ * allocation of buffers for pre-defined devices
+ */
+
+#ifdef CONFIG_PCI
+/* FIXME: for pci only - other bus? */
+struct prealloc_dev {
+	unsigned short vendor;
+	unsigned short device;
+	unsigned long dma_mask;
+	unsigned int size;
+	unsigned int buffers;
+};
+
+#define HAMMERFALL_BUFFER_SIZE    (16*1024*4*(26+1)+0x10000)
+
+static struct prealloc_dev prealloc_devices[] __initdata = {
+	{
+		/* hammerfall */
+		.vendor = 0x10ee,
+		.device = 0x3fc4,
+		.dma_mask = 0xffffffff,
+		.size = HAMMERFALL_BUFFER_SIZE,
+		.buffers = 2
+	},
+	{
+		/* HDSP */
+		.vendor = 0x10ee,
+		.device = 0x3fc5,
+		.dma_mask = 0xffffffff,
+		.size = HAMMERFALL_BUFFER_SIZE,
+		.buffers = 2
+	},
+	{ }, /* terminator */
+};
+
+static void __init preallocate_cards(void)
+{
+	struct pci_dev *pci = NULL;
+	int card;
+
+	card = 0;
+
+	while ((pci = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pci)) != NULL) {
+		struct prealloc_dev *dev;
+		unsigned int i;
+		if (card >= SNDRV_CARDS)
+			break;
+		for (dev = prealloc_devices; dev->vendor; dev++) {
+			if (dev->vendor == pci->vendor && dev->device == pci->device)
+				break;
+		}
+		if (! dev->vendor)
+			continue;
+		if (! enable[card++]) {
+			printk(KERN_DEBUG "snd-page-alloc: skipping card %d, device %04x:%04x\n", card, pci->vendor, pci->device);
+			continue;
+		}
+			
+		if (pci_set_dma_mask(pci, dev->dma_mask) < 0 ||
+		    pci_set_consistent_dma_mask(pci, dev->dma_mask) < 0) {
+			printk(KERN_ERR "snd-page-alloc: cannot set DMA mask %lx for pci %04x:%04x\n", dev->dma_mask, dev->vendor, dev->device);
+			continue;
+		}
+		for (i = 0; i < dev->buffers; i++) {
+			struct snd_dma_buffer dmab;
+			memset(&dmab, 0, sizeof(dmab));
+			if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+						dev->size, &dmab) < 0)
+				printk(KERN_WARNING "snd-page-alloc: cannot allocate buffer pages (size = %d)\n", dev->size);
+			else
+				snd_dma_reserve_buf(&dmab, snd_dma_pci_buf_id(pci));
+		}
+	}
+}
+#else
+#define preallocate_cards()	/* NOP */
+#endif
+
+
+#ifdef CONFIG_PROC_FS
+/*
+ * proc file interface
+ */
+static int snd_mem_proc_read(char *page, char **start, off_t off,
+			     int count, int *eof, void *data)
+{
+	int len = 0;
+	long pages = snd_allocated_pages >> (PAGE_SHIFT-12);
+	struct list_head *p;
+	struct snd_mem_list *mem;
+	int devno;
+	static char *types[] = { "UNKNOWN", "CONT", "DEV", "DEV-SG", "SBUS" };
+
+	down(&list_mutex);
+	len += snprintf(page + len, count - len,
+			"pages  : %li bytes (%li pages per %likB)\n",
+			pages * PAGE_SIZE, pages, PAGE_SIZE / 1024);
+	devno = 0;
+	list_for_each(p, &mem_list_head) {
+		mem = list_entry(p, struct snd_mem_list, list);
+		devno++;
+		len += snprintf(page + len, count - len,
+				"buffer %d : ID %08x : type %s\n",
+				devno, mem->id, types[mem->buffer.dev.type]);
+		len += snprintf(page + len, count - len,
+				"  addr = 0x%lx, size = %d bytes\n",
+				(unsigned long)mem->buffer.addr, (int)mem->buffer.bytes);
+	}
+	up(&list_mutex);
+	return len;
+}
+#endif /* CONFIG_PROC_FS */
+
+/*
+ * module entry
+ */
+
+static int __init snd_mem_init(void)
+{
+#ifdef CONFIG_PROC_FS
+	create_proc_read_entry("driver/snd-page-alloc", 0, NULL, snd_mem_proc_read, NULL);
+#endif
+	preallocate_cards();
+	return 0;
+}
+
+static void __exit snd_mem_exit(void)
+{
+	remove_proc_entry("driver/snd-page-alloc", NULL);
+	free_all_reserved_pages();
+	if (snd_allocated_pages > 0)
+		printk(KERN_ERR "snd-malloc: Memory leak?  pages not freed = %li\n", snd_allocated_pages);
+}
+
+
+module_init(snd_mem_init)
+module_exit(snd_mem_exit)
+
+
+/*
+ * exports
+ */
+EXPORT_SYMBOL(snd_dma_alloc_pages);
+EXPORT_SYMBOL(snd_dma_alloc_pages_fallback);
+EXPORT_SYMBOL(snd_dma_free_pages);
+
+EXPORT_SYMBOL(snd_dma_get_reserved_buf);
+EXPORT_SYMBOL(snd_dma_reserve_buf);
+
+EXPORT_SYMBOL(snd_malloc_pages);
+EXPORT_SYMBOL(snd_free_pages);
diff --git a/sound/core/memory.c b/sound/core/memory.c
new file mode 100644
index 000000000000..20860fec9364
--- /dev/null
+++ b/sound/core/memory.c
@@ -0,0 +1,306 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ * 
+ *  Memory allocation helpers.
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include <sound/info.h>
+
+/*
+ *  memory allocation helpers and debug routines
+ */
+
+#ifdef CONFIG_SND_DEBUG_MEMORY
+
+struct snd_alloc_track {
+	unsigned long magic;
+	void *caller;
+	size_t size;
+	struct list_head list;
+	long data[0];
+};
+
+#define snd_alloc_track_entry(obj) (struct snd_alloc_track *)((char*)obj - (unsigned long)((struct snd_alloc_track *)0)->data)
+
+static long snd_alloc_kmalloc;
+static long snd_alloc_vmalloc;
+static LIST_HEAD(snd_alloc_kmalloc_list);
+static LIST_HEAD(snd_alloc_vmalloc_list);
+static DEFINE_SPINLOCK(snd_alloc_kmalloc_lock);
+static DEFINE_SPINLOCK(snd_alloc_vmalloc_lock);
+#define KMALLOC_MAGIC 0x87654321
+#define VMALLOC_MAGIC 0x87654320
+static snd_info_entry_t *snd_memory_info_entry;
+
+void snd_memory_init(void)
+{
+	snd_alloc_kmalloc = 0;
+	snd_alloc_vmalloc = 0;
+}
+
+void snd_memory_done(void)
+{
+	struct list_head *head;
+	struct snd_alloc_track *t;
+
+	if (snd_alloc_kmalloc > 0)
+		snd_printk(KERN_ERR "Not freed snd_alloc_kmalloc = %li\n", snd_alloc_kmalloc);
+	if (snd_alloc_vmalloc > 0)
+		snd_printk(KERN_ERR "Not freed snd_alloc_vmalloc = %li\n", snd_alloc_vmalloc);
+	list_for_each_prev(head, &snd_alloc_kmalloc_list) {
+		t = list_entry(head, struct snd_alloc_track, list);
+		if (t->magic != KMALLOC_MAGIC) {
+			snd_printk(KERN_ERR "Corrupted kmalloc\n");
+			break;
+		}
+		snd_printk(KERN_ERR "kmalloc(%ld) from %p not freed\n", (long) t->size, t->caller);
+	}
+	list_for_each_prev(head, &snd_alloc_vmalloc_list) {
+		t = list_entry(head, struct snd_alloc_track, list);
+		if (t->magic != VMALLOC_MAGIC) {
+			snd_printk(KERN_ERR "Corrupted vmalloc\n");
+			break;
+		}
+		snd_printk(KERN_ERR "vmalloc(%ld) from %p not freed\n", (long) t->size, t->caller);
+	}
+}
+
+static void *__snd_kmalloc(size_t size, int flags, void *caller)
+{
+	unsigned long cpu_flags;
+	struct snd_alloc_track *t;
+	void *ptr;
+	
+	ptr = snd_wrapper_kmalloc(size + sizeof(struct snd_alloc_track), flags);
+	if (ptr != NULL) {
+		t = (struct snd_alloc_track *)ptr;
+		t->magic = KMALLOC_MAGIC;
+		t->caller = caller;
+		spin_lock_irqsave(&snd_alloc_kmalloc_lock, cpu_flags);
+		list_add_tail(&t->list, &snd_alloc_kmalloc_list);
+		spin_unlock_irqrestore(&snd_alloc_kmalloc_lock, cpu_flags);
+		t->size = size;
+		snd_alloc_kmalloc += size;
+		ptr = t->data;
+	}
+	return ptr;
+}
+
+#define _snd_kmalloc(size, flags) __snd_kmalloc((size), (flags), __builtin_return_address(0));
+void *snd_hidden_kmalloc(size_t size, int flags)
+{
+	return _snd_kmalloc(size, flags);
+}
+
+void *snd_hidden_kcalloc(size_t n, size_t size, int flags)
+{
+	void *ret = NULL;
+	if (n != 0 && size > INT_MAX / n)
+		return ret;
+	ret = _snd_kmalloc(n * size, flags);
+	if (ret)
+		memset(ret, 0, n * size);
+	return ret;
+}
+
+void snd_hidden_kfree(const void *obj)
+{
+	unsigned long flags;
+	struct snd_alloc_track *t;
+	if (obj == NULL)
+		return;
+	t = snd_alloc_track_entry(obj);
+	if (t->magic != KMALLOC_MAGIC) {
+		snd_printk(KERN_WARNING "bad kfree (called from %p)\n", __builtin_return_address(0));
+		return;
+	}
+	spin_lock_irqsave(&snd_alloc_kmalloc_lock, flags);
+	list_del(&t->list);
+	spin_unlock_irqrestore(&snd_alloc_kmalloc_lock, flags);
+	t->magic = 0;
+	snd_alloc_kmalloc -= t->size;
+	obj = t;
+	snd_wrapper_kfree(obj);
+}
+
+void *snd_hidden_vmalloc(unsigned long size)
+{
+	void *ptr;
+	ptr = snd_wrapper_vmalloc(size + sizeof(struct snd_alloc_track));
+	if (ptr) {
+		struct snd_alloc_track *t = (struct snd_alloc_track *)ptr;
+		t->magic = VMALLOC_MAGIC;
+		t->caller = __builtin_return_address(0);
+		spin_lock(&snd_alloc_vmalloc_lock);
+		list_add_tail(&t->list, &snd_alloc_vmalloc_list);
+		spin_unlock(&snd_alloc_vmalloc_lock);
+		t->size = size;
+		snd_alloc_vmalloc += size;
+		ptr = t->data;
+	}
+	return ptr;
+}
+
+void snd_hidden_vfree(void *obj)
+{
+	struct snd_alloc_track *t;
+	if (obj == NULL)
+		return;
+	t = snd_alloc_track_entry(obj);
+	if (t->magic != VMALLOC_MAGIC) {
+		snd_printk(KERN_ERR "bad vfree (called from %p)\n", __builtin_return_address(0));
+		return;
+	}
+	spin_lock(&snd_alloc_vmalloc_lock);
+	list_del(&t->list);
+	spin_unlock(&snd_alloc_vmalloc_lock);
+	t->magic = 0;
+	snd_alloc_vmalloc -= t->size;
+	obj = t;
+	snd_wrapper_vfree(obj);
+}
+
+static void snd_memory_info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+	snd_iprintf(buffer, "kmalloc: %li bytes\n", snd_alloc_kmalloc);
+	snd_iprintf(buffer, "vmalloc: %li bytes\n", snd_alloc_vmalloc);
+}
+
+int __init snd_memory_info_init(void)
+{
+	snd_info_entry_t *entry;
+
+	entry = snd_info_create_module_entry(THIS_MODULE, "meminfo", NULL);
+	if (entry) {
+		entry->c.text.read_size = 256;
+		entry->c.text.read = snd_memory_info_read;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	snd_memory_info_entry = entry;
+	return 0;
+}
+
+int __exit snd_memory_info_done(void)
+{
+	if (snd_memory_info_entry)
+		snd_info_unregister(snd_memory_info_entry);
+	return 0;
+}
+
+#else
+
+#define _snd_kmalloc kmalloc
+
+#endif /* CONFIG_SND_DEBUG_MEMORY */
+
+/**
+ * snd_kmalloc_strdup - copy the string
+ * @string: the original string
+ * @flags: allocation conditions, GFP_XXX
+ *
+ * Allocates a memory chunk via kmalloc() and copies the string to it.
+ *
+ * Returns the pointer, or NULL if no enoguh memory.
+ */
+char *snd_kmalloc_strdup(const char *string, int flags)
+{
+	size_t len;
+	char *ptr;
+
+	if (!string)
+		return NULL;
+	len = strlen(string) + 1;
+	ptr = _snd_kmalloc(len, flags);
+	if (ptr)
+		memcpy(ptr, string, len);
+	return ptr;
+}
+
+/**
+ * copy_to_user_fromio - copy data from mmio-space to user-space
+ * @dst: the destination pointer on user-space
+ * @src: the source pointer on mmio
+ * @count: the data size to copy in bytes
+ *
+ * Copies the data from mmio-space to user-space.
+ *
+ * Returns zero if successful, or non-zero on failure.
+ */
+int copy_to_user_fromio(void __user *dst, const volatile void __iomem *src, size_t count)
+{
+#if defined(__i386__) || defined(CONFIG_SPARC32)
+	return copy_to_user(dst, (const void*)src, count) ? -EFAULT : 0;
+#else
+	char buf[256];
+	while (count) {
+		size_t c = count;
+		if (c > sizeof(buf))
+			c = sizeof(buf);
+		memcpy_fromio(buf, (void __iomem *)src, c);
+		if (copy_to_user(dst, buf, c))
+			return -EFAULT;
+		count -= c;
+		dst += c;
+		src += c;
+	}
+	return 0;
+#endif
+}
+
+/**
+ * copy_from_user_toio - copy data from user-space to mmio-space
+ * @dst: the destination pointer on mmio-space
+ * @src: the source pointer on user-space
+ * @count: the data size to copy in bytes
+ *
+ * Copies the data from user-space to mmio-space.
+ *
+ * Returns zero if successful, or non-zero on failure.
+ */
+int copy_from_user_toio(volatile void __iomem *dst, const void __user *src, size_t count)
+{
+#if defined(__i386__) || defined(CONFIG_SPARC32)
+	return copy_from_user((void*)dst, src, count) ? -EFAULT : 0;
+#else
+	char buf[256];
+	while (count) {
+		size_t c = count;
+		if (c > sizeof(buf))
+			c = sizeof(buf);
+		if (copy_from_user(buf, src, c))
+			return -EFAULT;
+		memcpy_toio(dst, buf, c);
+		count -= c;
+		dst += c;
+		src += c;
+	}
+	return 0;
+#endif
+}
diff --git a/sound/core/misc.c b/sound/core/misc.c
new file mode 100644
index 000000000000..1a81fe4df218
--- /dev/null
+++ b/sound/core/misc.c
@@ -0,0 +1,76 @@
+/*
+ *  Misc and compatibility things
+ *  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/sched.h>
+#include <linux/time.h>
+#include <sound/core.h>
+
+int snd_task_name(struct task_struct *task, char *name, size_t size)
+{
+	unsigned int idx;
+
+	snd_assert(task != NULL && name != NULL && size >= 2, return -EINVAL);
+	for (idx = 0; idx < sizeof(task->comm) && idx + 1 < size; idx++)
+		name[idx] = task->comm[idx];
+	name[idx] = '\0';
+	return 0;
+}
+
+#ifdef CONFIG_SND_VERBOSE_PRINTK
+void snd_verbose_printk(const char *file, int line, const char *format, ...)
+{
+	va_list args;
+	
+	if (format[0] == '<' && format[1] >= '0' && format[1] <= '9' && format[2] == '>') {
+		char tmp[] = "<0>";
+		tmp[1] = format[1];
+		printk("%sALSA %s:%d: ", tmp, file, line);
+		format += 3;
+	} else {
+		printk("ALSA %s:%d: ", file, line);
+	}
+	va_start(args, format);
+	vprintk(format, args);
+	va_end(args);
+}
+#endif
+
+#if defined(CONFIG_SND_DEBUG) && defined(CONFIG_SND_VERBOSE_PRINTK)
+void snd_verbose_printd(const char *file, int line, const char *format, ...)
+{
+	va_list args;
+	
+	if (format[0] == '<' && format[1] >= '0' && format[1] <= '9' && format[2] == '>') {
+		char tmp[] = "<0>";
+		tmp[1] = format[1];
+		printk("%sALSA %s:%d: ", tmp, file, line);
+		format += 3;
+	} else {
+		printk(KERN_DEBUG "ALSA %s:%d: ", file, line);
+	}
+	va_start(args, format);
+	vprintk(format, args);
+	va_end(args);
+
+}
+#endif
diff --git a/sound/core/oss/Makefile b/sound/core/oss/Makefile
new file mode 100644
index 000000000000..e6d5a045ba27
--- /dev/null
+++ b/sound/core/oss/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for ALSA
+# Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-mixer-oss-objs := mixer_oss.o
+
+snd-pcm-oss-objs := pcm_oss.o pcm_plugin.o \
+		    io.o copy.o linear.o mulaw.o route.o rate.o
+
+obj-$(CONFIG_SND_MIXER_OSS) += snd-mixer-oss.o
+obj-$(CONFIG_SND_PCM_OSS) += snd-pcm-oss.o
diff --git a/sound/core/oss/copy.c b/sound/core/oss/copy.c
new file mode 100644
index 000000000000..edecbe7417bd
--- /dev/null
+++ b/sound/core/oss/copy.c
@@ -0,0 +1,87 @@
+/*
+ *  Linear conversion Plug-In
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include "pcm_plugin.h"
+
+static snd_pcm_sframes_t copy_transfer(snd_pcm_plugin_t *plugin,
+			     const snd_pcm_plugin_channel_t *src_channels,
+			     snd_pcm_plugin_channel_t *dst_channels,
+			     snd_pcm_uframes_t frames)
+{
+	unsigned int channel;
+	unsigned int nchannels;
+
+	snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO);
+	if (frames == 0)
+		return 0;
+	nchannels = plugin->src_format.channels;
+	for (channel = 0; channel < nchannels; channel++) {
+		snd_assert(src_channels->area.first % 8 == 0 &&
+			   src_channels->area.step % 8 == 0,
+			   return -ENXIO);
+		snd_assert(dst_channels->area.first % 8 == 0 &&
+			   dst_channels->area.step % 8 == 0,
+			   return -ENXIO);
+		if (!src_channels->enabled) {
+			if (dst_channels->wanted)
+				snd_pcm_area_silence(&dst_channels->area, 0, frames, plugin->dst_format.format);
+			dst_channels->enabled = 0;
+			continue;
+		}
+		dst_channels->enabled = 1;
+		snd_pcm_area_copy(&src_channels->area, 0, &dst_channels->area, 0, frames, plugin->src_format.format);
+		src_channels++;
+		dst_channels++;
+	}
+	return frames;
+}
+
+int snd_pcm_plugin_build_copy(snd_pcm_plug_t *plug,
+			      snd_pcm_plugin_format_t *src_format,
+			      snd_pcm_plugin_format_t *dst_format,
+			      snd_pcm_plugin_t **r_plugin)
+{
+	int err;
+	snd_pcm_plugin_t *plugin;
+	int width;
+
+	snd_assert(r_plugin != NULL, return -ENXIO);
+	*r_plugin = NULL;
+
+	snd_assert(src_format->format == dst_format->format, return -ENXIO);
+	snd_assert(src_format->rate == dst_format->rate, return -ENXIO);
+	snd_assert(src_format->channels == dst_format->channels, return -ENXIO);
+
+	width = snd_pcm_format_physical_width(src_format->format);
+	snd_assert(width > 0, return -ENXIO);
+
+	err = snd_pcm_plugin_build(plug, "copy", src_format, dst_format,
+				   0, &plugin);
+	if (err < 0)
+		return err;
+	plugin->transfer = copy_transfer;
+	*r_plugin = plugin;
+	return 0;
+}
diff --git a/sound/core/oss/io.c b/sound/core/oss/io.c
new file mode 100644
index 000000000000..bb1c99a5b734
--- /dev/null
+++ b/sound/core/oss/io.c
@@ -0,0 +1,134 @@
+/*
+ *  PCM I/O Plug-In Interface
+ *  Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "pcm_plugin.h"
+
+#define pcm_write(plug,buf,count) snd_pcm_oss_write3(plug,buf,count,1)
+#define pcm_writev(plug,vec,count) snd_pcm_oss_writev3(plug,vec,count,1)
+#define pcm_read(plug,buf,count) snd_pcm_oss_read3(plug,buf,count,1)
+#define pcm_readv(plug,vec,count) snd_pcm_oss_readv3(plug,vec,count,1)
+
+/*
+ *  Basic io plugin
+ */
+ 
+static snd_pcm_sframes_t io_playback_transfer(snd_pcm_plugin_t *plugin,
+				    const snd_pcm_plugin_channel_t *src_channels,
+				    snd_pcm_plugin_channel_t *dst_channels ATTRIBUTE_UNUSED,
+				    snd_pcm_uframes_t frames)
+{
+	snd_assert(plugin != NULL, return -ENXIO);
+	snd_assert(src_channels != NULL, return -ENXIO);
+	if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
+		return pcm_write(plugin->plug, src_channels->area.addr, frames);
+	} else {
+		int channel, channels = plugin->dst_format.channels;
+		void **bufs = (void**)plugin->extra_data;
+		snd_assert(bufs != NULL, return -ENXIO);
+		for (channel = 0; channel < channels; channel++) {
+			if (src_channels[channel].enabled)
+				bufs[channel] = src_channels[channel].area.addr;
+			else
+				bufs[channel] = NULL;
+		}
+		return pcm_writev(plugin->plug, bufs, frames);
+	}
+}
+ 
+static snd_pcm_sframes_t io_capture_transfer(snd_pcm_plugin_t *plugin,
+				   const snd_pcm_plugin_channel_t *src_channels ATTRIBUTE_UNUSED,
+				   snd_pcm_plugin_channel_t *dst_channels,
+				   snd_pcm_uframes_t frames)
+{
+	snd_assert(plugin != NULL, return -ENXIO);
+	snd_assert(dst_channels != NULL, return -ENXIO);
+	if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
+		return pcm_read(plugin->plug, dst_channels->area.addr, frames);
+	} else {
+		int channel, channels = plugin->dst_format.channels;
+		void **bufs = (void**)plugin->extra_data;
+		snd_assert(bufs != NULL, return -ENXIO);
+		for (channel = 0; channel < channels; channel++) {
+			if (dst_channels[channel].enabled)
+				bufs[channel] = dst_channels[channel].area.addr;
+			else
+				bufs[channel] = NULL;
+		}
+		return pcm_readv(plugin->plug, bufs, frames);
+	}
+	return 0;
+}
+ 
+static snd_pcm_sframes_t io_src_channels(snd_pcm_plugin_t *plugin,
+			     snd_pcm_uframes_t frames,
+			     snd_pcm_plugin_channel_t **channels)
+{
+	int err;
+	unsigned int channel;
+	snd_pcm_plugin_channel_t *v;
+	err = snd_pcm_plugin_client_channels(plugin, frames, &v);
+	if (err < 0)
+		return err;
+	*channels = v;
+	if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
+		for (channel = 0; channel < plugin->src_format.channels; ++channel, ++v)
+			v->wanted = 1;
+	}
+	return frames;
+}
+
+int snd_pcm_plugin_build_io(snd_pcm_plug_t *plug,
+			    snd_pcm_hw_params_t *params,
+			    snd_pcm_plugin_t **r_plugin)
+{
+	int err;
+	snd_pcm_plugin_format_t format;
+	snd_pcm_plugin_t *plugin;
+
+	snd_assert(r_plugin != NULL, return -ENXIO);
+	*r_plugin = NULL;
+	snd_assert(plug != NULL && params != NULL, return -ENXIO);
+	format.format = params_format(params);
+	format.rate = params_rate(params);
+	format.channels = params_channels(params);
+	err = snd_pcm_plugin_build(plug, "I/O io",
+				   &format, &format,
+				   sizeof(void *) * format.channels,
+				   &plugin);
+	if (err < 0)
+		return err;
+	plugin->access = params_access(params);
+	if (snd_pcm_plug_stream(plug) == SNDRV_PCM_STREAM_PLAYBACK) {
+		plugin->transfer = io_playback_transfer;
+		if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED)
+			plugin->client_channels = io_src_channels;
+	} else {
+		plugin->transfer = io_capture_transfer;
+	}
+
+	*r_plugin = plugin;
+	return 0;
+}
diff --git a/sound/core/oss/linear.c b/sound/core/oss/linear.c
new file mode 100644
index 000000000000..12ed27a57b27
--- /dev/null
+++ b/sound/core/oss/linear.c
@@ -0,0 +1,158 @@
+/*
+ *  Linear conversion Plug-In
+ *  Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>,
+ *			  Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include "pcm_plugin.h"
+
+/*
+ *  Basic linear conversion plugin
+ */
+ 
+typedef struct linear_private_data {
+	int conv;
+} linear_t;
+
+static void convert(snd_pcm_plugin_t *plugin,
+		    const snd_pcm_plugin_channel_t *src_channels,
+		    snd_pcm_plugin_channel_t *dst_channels,
+		    snd_pcm_uframes_t frames)
+{
+#define CONV_LABELS
+#include "plugin_ops.h"
+#undef CONV_LABELS
+	linear_t *data = (linear_t *)plugin->extra_data;
+	void *conv = conv_labels[data->conv];
+	int channel;
+	int nchannels = plugin->src_format.channels;
+	for (channel = 0; channel < nchannels; ++channel) {
+		char *src;
+		char *dst;
+		int src_step, dst_step;
+		snd_pcm_uframes_t frames1;
+		if (!src_channels[channel].enabled) {
+			if (dst_channels[channel].wanted)
+				snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format);
+			dst_channels[channel].enabled = 0;
+			continue;
+		}
+		dst_channels[channel].enabled = 1;
+		src = src_channels[channel].area.addr + src_channels[channel].area.first / 8;
+		dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
+		src_step = src_channels[channel].area.step / 8;
+		dst_step = dst_channels[channel].area.step / 8;
+		frames1 = frames;
+		while (frames1-- > 0) {
+			goto *conv;
+#define CONV_END after
+#include "plugin_ops.h"
+#undef CONV_END
+		after:
+			src += src_step;
+			dst += dst_step;
+		}
+	}
+}
+
+static snd_pcm_sframes_t linear_transfer(snd_pcm_plugin_t *plugin,
+			       const snd_pcm_plugin_channel_t *src_channels,
+			       snd_pcm_plugin_channel_t *dst_channels,
+			       snd_pcm_uframes_t frames)
+{
+	linear_t *data;
+
+	snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO);
+	data = (linear_t *)plugin->extra_data;
+	if (frames == 0)
+		return 0;
+#ifdef CONFIG_SND_DEBUG
+	{
+		unsigned int channel;
+		for (channel = 0; channel < plugin->src_format.channels; channel++) {
+			snd_assert(src_channels[channel].area.first % 8 == 0 &&
+				   src_channels[channel].area.step % 8 == 0,
+				   return -ENXIO);
+			snd_assert(dst_channels[channel].area.first % 8 == 0 &&
+				   dst_channels[channel].area.step % 8 == 0,
+				   return -ENXIO);
+		}
+	}
+#endif
+	convert(plugin, src_channels, dst_channels, frames);
+	return frames;
+}
+
+int conv_index(int src_format, int dst_format)
+{
+	int src_endian, dst_endian, sign, src_width, dst_width;
+
+	sign = (snd_pcm_format_signed(src_format) !=
+		snd_pcm_format_signed(dst_format));
+#ifdef SNDRV_LITTLE_ENDIAN
+	src_endian = snd_pcm_format_big_endian(src_format);
+	dst_endian = snd_pcm_format_big_endian(dst_format);
+#else
+	src_endian = snd_pcm_format_little_endian(src_format);
+	dst_endian = snd_pcm_format_little_endian(dst_format);
+#endif
+
+	if (src_endian < 0)
+		src_endian = 0;
+	if (dst_endian < 0)
+		dst_endian = 0;
+
+	src_width = snd_pcm_format_width(src_format) / 8 - 1;
+	dst_width = snd_pcm_format_width(dst_format) / 8 - 1;
+
+	return src_width * 32 + src_endian * 16 + sign * 8 + dst_width * 2 + dst_endian;
+}
+
+int snd_pcm_plugin_build_linear(snd_pcm_plug_t *plug,
+				snd_pcm_plugin_format_t *src_format,
+				snd_pcm_plugin_format_t *dst_format,
+				snd_pcm_plugin_t **r_plugin)
+{
+	int err;
+	struct linear_private_data *data;
+	snd_pcm_plugin_t *plugin;
+
+	snd_assert(r_plugin != NULL, return -ENXIO);
+	*r_plugin = NULL;
+
+	snd_assert(src_format->rate == dst_format->rate, return -ENXIO);
+	snd_assert(src_format->channels == dst_format->channels, return -ENXIO);
+	snd_assert(snd_pcm_format_linear(src_format->format) &&
+		   snd_pcm_format_linear(dst_format->format), return -ENXIO);
+
+	err = snd_pcm_plugin_build(plug, "linear format conversion",
+				   src_format, dst_format,
+				   sizeof(linear_t), &plugin);
+	if (err < 0)
+		return err;
+	data = (linear_t *)plugin->extra_data;
+	data->conv = conv_index(src_format->format, dst_format->format);
+	plugin->transfer = linear_transfer;
+	*r_plugin = plugin;
+	return 0;
+}
diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c
new file mode 100644
index 000000000000..98ed9a9f0da6
--- /dev/null
+++ b/sound/core/oss/mixer_oss.c
@@ -0,0 +1,1340 @@
+/*
+ *  OSS emulation layer for the mixer interface
+ *  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/smp_lock.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/mixer_oss.h>
+#include <linux/soundcard.h>
+
+#define OSS_ALSAEMULVER         _SIOR ('M', 249, int)
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Mixer OSS emulation for ALSA.");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_MIXER);
+
+static int snd_mixer_oss_open(struct inode *inode, struct file *file)
+{
+	int cardnum = SNDRV_MINOR_OSS_CARD(iminor(inode));
+	snd_card_t *card;
+	snd_mixer_oss_file_t *fmixer;
+	int err;
+
+	if ((card = snd_cards[cardnum]) == NULL)
+		return -ENODEV;
+	if (card->mixer_oss == NULL)
+		return -ENODEV;
+	err = snd_card_file_add(card, file);
+	if (err < 0)
+		return err;
+	fmixer = kcalloc(1, sizeof(*fmixer), GFP_KERNEL);
+	if (fmixer == NULL) {
+		snd_card_file_remove(card, file);
+		return -ENOMEM;
+	}
+	fmixer->card = card;
+	fmixer->mixer = card->mixer_oss;
+	file->private_data = fmixer;
+	if (!try_module_get(card->module)) {
+		kfree(fmixer);
+		snd_card_file_remove(card, file);
+		return -EFAULT;
+	}
+	return 0;
+}
+
+static int snd_mixer_oss_release(struct inode *inode, struct file *file)
+{
+	snd_mixer_oss_file_t *fmixer;
+
+	if (file->private_data) {
+		fmixer = (snd_mixer_oss_file_t *) file->private_data;
+		module_put(fmixer->card->module);
+		snd_card_file_remove(fmixer->card, file);
+		kfree(fmixer);
+	}
+	return 0;
+}
+
+static int snd_mixer_oss_info(snd_mixer_oss_file_t *fmixer,
+			      mixer_info __user *_info)
+{
+	snd_card_t *card = fmixer->card;
+	snd_mixer_oss_t *mixer = fmixer->mixer;
+	struct mixer_info info;
+	
+	memset(&info, 0, sizeof(info));
+	strlcpy(info.id, mixer && mixer->id[0] ? mixer->id : card->driver, sizeof(info.id));
+	strlcpy(info.name, mixer && mixer->name[0] ? mixer->name : card->mixername, sizeof(info.name));
+	info.modify_counter = card->mixer_oss_change_count;
+	if (copy_to_user(_info, &info, sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+static int snd_mixer_oss_info_obsolete(snd_mixer_oss_file_t *fmixer,
+				       _old_mixer_info __user *_info)
+{
+	snd_card_t *card = fmixer->card;
+	snd_mixer_oss_t *mixer = fmixer->mixer;
+	_old_mixer_info info;
+	
+	memset(&info, 0, sizeof(info));
+	strlcpy(info.id, mixer && mixer->id[0] ? mixer->id : card->driver, sizeof(info.id));
+	strlcpy(info.name, mixer && mixer->name[0] ? mixer->name : card->mixername, sizeof(info.name));
+	if (copy_to_user(_info, &info, sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+static int snd_mixer_oss_caps(snd_mixer_oss_file_t *fmixer)
+{
+	snd_mixer_oss_t *mixer = fmixer->mixer;
+	int result = 0;
+
+	if (mixer == NULL)
+		return -EIO;
+	if (mixer->get_recsrc && mixer->put_recsrc)
+		result |= SOUND_CAP_EXCL_INPUT;
+	return result;
+}
+
+static int snd_mixer_oss_devmask(snd_mixer_oss_file_t *fmixer)
+{
+	snd_mixer_oss_t *mixer = fmixer->mixer;
+	snd_mixer_oss_slot_t *pslot;
+	int result = 0, chn;
+
+	if (mixer == NULL)
+		return -EIO;
+	for (chn = 0; chn < 31; chn++) {
+		pslot = &mixer->slots[chn];
+		if (pslot->put_volume || pslot->put_recsrc)
+			result |= 1 << chn;
+	}
+	return result;
+}
+
+static int snd_mixer_oss_stereodevs(snd_mixer_oss_file_t *fmixer)
+{
+	snd_mixer_oss_t *mixer = fmixer->mixer;
+	snd_mixer_oss_slot_t *pslot;
+	int result = 0, chn;
+
+	if (mixer == NULL)
+		return -EIO;
+	for (chn = 0; chn < 31; chn++) {
+		pslot = &mixer->slots[chn];
+		if (pslot->put_volume && pslot->stereo)
+			result |= 1 << chn;
+	}
+	return result;
+}
+
+static int snd_mixer_oss_recmask(snd_mixer_oss_file_t *fmixer)
+{
+	snd_mixer_oss_t *mixer = fmixer->mixer;
+	int result = 0;
+
+	if (mixer == NULL)
+		return -EIO;
+	if (mixer->put_recsrc && mixer->get_recsrc) {	/* exclusive */
+		result = mixer->mask_recsrc;
+	} else {
+		snd_mixer_oss_slot_t *pslot;
+		int chn;
+		for (chn = 0; chn < 31; chn++) {
+			pslot = &mixer->slots[chn];
+			if (pslot->put_recsrc)
+				result |= 1 << chn;
+		}
+	}
+	return result;
+}
+
+static int snd_mixer_oss_get_recsrc(snd_mixer_oss_file_t *fmixer)
+{
+	snd_mixer_oss_t *mixer = fmixer->mixer;
+	int result = 0;
+
+	if (mixer == NULL)
+		return -EIO;
+	if (mixer->put_recsrc && mixer->get_recsrc) {	/* exclusive */
+		int err;
+		if ((err = mixer->get_recsrc(fmixer, &result)) < 0)
+			return err;
+		result = 1 << result;
+	} else {
+		snd_mixer_oss_slot_t *pslot;
+		int chn;
+		for (chn = 0; chn < 31; chn++) {
+			pslot = &mixer->slots[chn];
+			if (pslot->get_recsrc) {
+				int active = 0;
+				pslot->get_recsrc(fmixer, pslot, &active);
+				if (active)
+					result |= 1 << chn;
+			}
+		}
+	}
+	return mixer->oss_recsrc = result;
+}
+
+static int snd_mixer_oss_set_recsrc(snd_mixer_oss_file_t *fmixer, int recsrc)
+{
+	snd_mixer_oss_t *mixer = fmixer->mixer;
+	snd_mixer_oss_slot_t *pslot;
+	int chn, active;
+	int result = 0;
+
+	if (mixer == NULL)
+		return -EIO;
+	if (mixer->get_recsrc && mixer->put_recsrc) {	/* exclusive input */
+		if (recsrc & ~mixer->oss_recsrc)
+			recsrc &= ~mixer->oss_recsrc;
+		mixer->put_recsrc(fmixer, ffz(~recsrc));
+		mixer->get_recsrc(fmixer, &result);
+		result = 1 << result;
+	}
+	for (chn = 0; chn < 31; chn++) {
+		pslot = &mixer->slots[chn];
+		if (pslot->put_recsrc) {
+			active = (recsrc & (1 << chn)) ? 1 : 0;
+			pslot->put_recsrc(fmixer, pslot, active);
+		}
+	}
+	if (! result) {
+		for (chn = 0; chn < 31; chn++) {
+			pslot = &mixer->slots[chn];
+			if (pslot->get_recsrc) {
+				active = 0;
+				pslot->get_recsrc(fmixer, pslot, &active);
+				if (active)
+					result |= 1 << chn;
+			}
+		}
+	}
+	return result;
+}
+
+static int snd_mixer_oss_get_volume(snd_mixer_oss_file_t *fmixer, int slot)
+{
+	snd_mixer_oss_t *mixer = fmixer->mixer;
+	snd_mixer_oss_slot_t *pslot;
+	int result = 0, left, right;
+
+	if (mixer == NULL || slot > 30)
+		return -EIO;
+	pslot = &mixer->slots[slot];
+	left = pslot->volume[0];
+	right = pslot->volume[1];
+	if (pslot->get_volume)
+		result = pslot->get_volume(fmixer, pslot, &left, &right);
+	if (!pslot->stereo)
+		right = left;
+	snd_assert(left >= 0 && left <= 100, return -EIO);
+	snd_assert(right >= 0 && right <= 100, return -EIO);
+	if (result >= 0) {
+		pslot->volume[0] = left;
+		pslot->volume[1] = right;
+	 	result = (left & 0xff) | ((right & 0xff) << 8);
+	}
+	return result;
+}
+
+static int snd_mixer_oss_set_volume(snd_mixer_oss_file_t *fmixer,
+				    int slot, int volume)
+{
+	snd_mixer_oss_t *mixer = fmixer->mixer;
+	snd_mixer_oss_slot_t *pslot;
+	int result = 0, left = volume & 0xff, right = (volume >> 8) & 0xff;
+
+	if (mixer == NULL || slot > 30)
+		return -EIO;
+	pslot = &mixer->slots[slot];
+	if (left > 100)
+		left = 100;
+	if (right > 100)
+		right = 100;
+	if (!pslot->stereo)
+		right = left;
+	if (pslot->put_volume)
+		result = pslot->put_volume(fmixer, pslot, left, right);
+	if (result < 0)
+		return result;
+	pslot->volume[0] = left;
+	pslot->volume[1] = right;
+ 	return (left & 0xff) | ((right & 0xff) << 8);
+}
+
+static int snd_mixer_oss_ioctl1(snd_mixer_oss_file_t *fmixer, unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	int tmp;
+
+	snd_assert(fmixer != NULL, return -ENXIO);
+	if (((cmd >> 8) & 0xff) == 'M') {
+		switch (cmd) {
+		case SOUND_MIXER_INFO:
+			return snd_mixer_oss_info(fmixer, argp);
+		case SOUND_OLD_MIXER_INFO:
+ 			return snd_mixer_oss_info_obsolete(fmixer, argp);
+		case SOUND_MIXER_WRITE_RECSRC:
+			if (get_user(tmp, p))
+				return -EFAULT;
+			tmp = snd_mixer_oss_set_recsrc(fmixer, tmp);
+			if (tmp < 0)
+				return tmp;
+			return put_user(tmp, p);
+		case OSS_GETVERSION:
+			return put_user(SNDRV_OSS_VERSION, p);
+		case OSS_ALSAEMULVER:
+			return put_user(1, p);
+		case SOUND_MIXER_READ_DEVMASK:
+			tmp = snd_mixer_oss_devmask(fmixer);
+			if (tmp < 0)
+				return tmp;
+			return put_user(tmp, p);
+		case SOUND_MIXER_READ_STEREODEVS:
+			tmp = snd_mixer_oss_stereodevs(fmixer);
+			if (tmp < 0)
+				return tmp;
+			return put_user(tmp, p);
+		case SOUND_MIXER_READ_RECMASK:
+			tmp = snd_mixer_oss_recmask(fmixer);
+			if (tmp < 0)
+				return tmp;
+			return put_user(tmp, p);
+		case SOUND_MIXER_READ_CAPS:
+			tmp = snd_mixer_oss_caps(fmixer);
+			if (tmp < 0)
+				return tmp;
+			return put_user(tmp, p);
+		case SOUND_MIXER_READ_RECSRC:
+			tmp = snd_mixer_oss_get_recsrc(fmixer);
+			if (tmp < 0)
+				return tmp;
+			return put_user(tmp, p);
+		}
+	}
+	if (cmd & SIOC_IN) {
+		if (get_user(tmp, p))
+			return -EFAULT;
+		tmp = snd_mixer_oss_set_volume(fmixer, cmd & 0xff, tmp);
+		if (tmp < 0)
+			return tmp;
+		return put_user(tmp, p);
+	} else if (cmd & SIOC_OUT) {
+		tmp = snd_mixer_oss_get_volume(fmixer, cmd & 0xff);
+		if (tmp < 0)
+			return tmp;
+		return put_user(tmp, p);
+	}
+	return -ENXIO;
+}
+
+static long snd_mixer_oss_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	return snd_mixer_oss_ioctl1((snd_mixer_oss_file_t *) file->private_data, cmd, arg);
+}
+
+int snd_mixer_oss_ioctl_card(snd_card_t *card, unsigned int cmd, unsigned long arg)
+{
+	snd_mixer_oss_file_t fmixer;
+	
+	snd_assert(card != NULL, return -ENXIO);
+	if (card->mixer_oss == NULL)
+		return -ENXIO;
+	memset(&fmixer, 0, sizeof(fmixer));
+	fmixer.card = card;
+	fmixer.mixer = card->mixer_oss;
+	return snd_mixer_oss_ioctl1(&fmixer, cmd, arg);
+}
+
+#ifdef CONFIG_COMPAT
+/* all compatible */
+#define snd_mixer_oss_ioctl_compat	snd_mixer_oss_ioctl
+#else
+#define snd_mixer_oss_ioctl_compat	NULL
+#endif
+
+/*
+ *  REGISTRATION PART
+ */
+
+static struct file_operations snd_mixer_oss_f_ops =
+{
+	.owner =	THIS_MODULE,
+	.open =		snd_mixer_oss_open,
+	.release =	snd_mixer_oss_release,
+	.unlocked_ioctl =	snd_mixer_oss_ioctl,
+	.compat_ioctl =	snd_mixer_oss_ioctl_compat,
+};
+
+static snd_minor_t snd_mixer_oss_reg =
+{
+	.comment =	"mixer",
+	.f_ops =	&snd_mixer_oss_f_ops,
+};
+
+/*
+ *  utilities
+ */
+
+static long snd_mixer_oss_conv(long val, long omin, long omax, long nmin, long nmax)
+{
+	long orange = omax - omin, nrange = nmax - nmin;
+	
+	if (orange == 0)
+		return 0;
+	return ((nrange * (val - omin)) + (orange / 2)) / orange + nmin;
+}
+
+/* convert from alsa native to oss values (0-100) */
+static long snd_mixer_oss_conv1(long val, long min, long max, int *old)
+{
+	if (val == snd_mixer_oss_conv(*old, 0, 100, min, max))
+		return *old;
+	return snd_mixer_oss_conv(val, min, max, 0, 100);
+}
+
+/* convert from oss to alsa native values */
+static long snd_mixer_oss_conv2(long val, long min, long max)
+{
+	return snd_mixer_oss_conv(val, 0, 100, min, max);
+}
+
+#if 0
+static void snd_mixer_oss_recsrce_set(snd_card_t *card, int slot)
+{
+	snd_mixer_oss_t *mixer = card->mixer_oss;
+	if (mixer)
+		mixer->mask_recsrc |= 1 << slot;
+}
+
+static int snd_mixer_oss_recsrce_get(snd_card_t *card, int slot)
+{
+	snd_mixer_oss_t *mixer = card->mixer_oss;
+	if (mixer && (mixer->mask_recsrc & (1 << slot)))
+		return 1;
+	return 0;
+}
+#endif
+
+#define SNDRV_MIXER_OSS_SIGNATURE		0x65999250
+
+#define SNDRV_MIXER_OSS_ITEM_GLOBAL	0
+#define SNDRV_MIXER_OSS_ITEM_GSWITCH	1
+#define SNDRV_MIXER_OSS_ITEM_GROUTE	2
+#define SNDRV_MIXER_OSS_ITEM_GVOLUME	3
+#define SNDRV_MIXER_OSS_ITEM_PSWITCH	4
+#define SNDRV_MIXER_OSS_ITEM_PROUTE	5
+#define SNDRV_MIXER_OSS_ITEM_PVOLUME	6
+#define SNDRV_MIXER_OSS_ITEM_CSWITCH	7
+#define SNDRV_MIXER_OSS_ITEM_CROUTE	8
+#define SNDRV_MIXER_OSS_ITEM_CVOLUME	9
+#define SNDRV_MIXER_OSS_ITEM_CAPTURE	10
+
+#define SNDRV_MIXER_OSS_ITEM_COUNT	11
+
+#define SNDRV_MIXER_OSS_PRESENT_GLOBAL	(1<<0)
+#define SNDRV_MIXER_OSS_PRESENT_GSWITCH	(1<<1)
+#define SNDRV_MIXER_OSS_PRESENT_GROUTE	(1<<2)
+#define SNDRV_MIXER_OSS_PRESENT_GVOLUME	(1<<3)
+#define SNDRV_MIXER_OSS_PRESENT_PSWITCH	(1<<4)
+#define SNDRV_MIXER_OSS_PRESENT_PROUTE	(1<<5)
+#define SNDRV_MIXER_OSS_PRESENT_PVOLUME	(1<<6)
+#define SNDRV_MIXER_OSS_PRESENT_CSWITCH	(1<<7)
+#define SNDRV_MIXER_OSS_PRESENT_CROUTE	(1<<8)
+#define SNDRV_MIXER_OSS_PRESENT_CVOLUME	(1<<9)
+#define SNDRV_MIXER_OSS_PRESENT_CAPTURE	(1<<10)
+
+struct slot {
+	unsigned int signature;
+	unsigned int present;
+	unsigned int channels;
+	unsigned int numid[SNDRV_MIXER_OSS_ITEM_COUNT];
+	unsigned int capture_item;
+	struct snd_mixer_oss_assign_table *assigned;
+	unsigned int allocated: 1;
+};
+
+#define ID_UNKNOWN	((unsigned int)-1)
+
+static snd_kcontrol_t *snd_mixer_oss_test_id(snd_mixer_oss_t *mixer, const char *name, int index)
+{
+	snd_card_t * card = mixer->card;
+	snd_ctl_elem_id_t id;
+	
+	memset(&id, 0, sizeof(id));
+	id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strcpy(id.name, name);
+	id.index = index;
+	return snd_ctl_find_id(card, &id);
+}
+
+static void snd_mixer_oss_get_volume1_vol(snd_mixer_oss_file_t *fmixer,
+					  snd_mixer_oss_slot_t *pslot,
+					  unsigned int numid,
+					  int *left, int *right)
+{
+	snd_ctl_elem_info_t *uinfo;
+	snd_ctl_elem_value_t *uctl;
+	snd_kcontrol_t *kctl;
+	snd_card_t *card = fmixer->card;
+
+	if (numid == ID_UNKNOWN)
+		return;
+	down_read(&card->controls_rwsem);
+	if ((kctl = snd_ctl_find_numid(card, numid)) == NULL) {
+		up_read(&card->controls_rwsem);
+		return;
+	}
+	uinfo = kcalloc(1, sizeof(*uinfo), GFP_KERNEL);
+	uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL);
+	if (uinfo == NULL || uctl == NULL)
+		goto __unalloc;
+	snd_runtime_check(!kctl->info(kctl, uinfo), goto __unalloc);
+	snd_runtime_check(!kctl->get(kctl, uctl), goto __unalloc);
+	snd_runtime_check(uinfo->type != SNDRV_CTL_ELEM_TYPE_BOOLEAN || uinfo->value.integer.min != 0 || uinfo->value.integer.max != 1, goto __unalloc);
+	*left = snd_mixer_oss_conv1(uctl->value.integer.value[0], uinfo->value.integer.min, uinfo->value.integer.max, &pslot->volume[0]);
+	if (uinfo->count > 1)
+		*right = snd_mixer_oss_conv1(uctl->value.integer.value[1], uinfo->value.integer.min, uinfo->value.integer.max, &pslot->volume[1]);
+      __unalloc:
+	up_read(&card->controls_rwsem);
+      	kfree(uctl);
+      	kfree(uinfo);
+}
+
+static void snd_mixer_oss_get_volume1_sw(snd_mixer_oss_file_t *fmixer,
+					 snd_mixer_oss_slot_t *pslot,
+					 unsigned int numid,
+					 int *left, int *right,
+					 int route)
+{
+	snd_ctl_elem_info_t *uinfo;
+	snd_ctl_elem_value_t *uctl;
+	snd_kcontrol_t *kctl;
+	snd_card_t *card = fmixer->card;
+
+	if (numid == ID_UNKNOWN)
+		return;
+	down_read(&card->controls_rwsem);
+	if ((kctl = snd_ctl_find_numid(card, numid)) == NULL) {
+		up_read(&card->controls_rwsem);
+		return;
+	}
+	uinfo = kcalloc(1, sizeof(*uinfo), GFP_KERNEL);
+	uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL);
+	if (uinfo == NULL || uctl == NULL)
+		goto __unalloc;
+	snd_runtime_check(!kctl->info(kctl, uinfo), goto __unalloc);
+	snd_runtime_check(!kctl->get(kctl, uctl), goto __unalloc);
+	if (!uctl->value.integer.value[0]) {
+		*left = 0;
+		if (uinfo->count == 1)
+			*right = 0;
+	}
+	if (uinfo->count > 1 && !uctl->value.integer.value[route ? 3 : 1])
+		*right = 0;
+      __unalloc:
+	up_read(&card->controls_rwsem);
+      	kfree(uctl);
+	kfree(uinfo);
+}
+
+static int snd_mixer_oss_get_volume1(snd_mixer_oss_file_t *fmixer,
+				     snd_mixer_oss_slot_t *pslot,
+				     int *left, int *right)
+{
+	struct slot *slot = (struct slot *)pslot->private_data;
+	
+	*left = *right = 100;
+	if (slot->present & SNDRV_MIXER_OSS_PRESENT_PVOLUME) {
+		snd_mixer_oss_get_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PVOLUME], left, right);
+	} else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GVOLUME) {
+		snd_mixer_oss_get_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GVOLUME], left, right);
+	} else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GLOBAL) {
+		snd_mixer_oss_get_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GLOBAL], left, right);
+	}
+	if (slot->present & SNDRV_MIXER_OSS_PRESENT_PSWITCH) {
+		snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PSWITCH], left, right, 0);
+	} else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GSWITCH) {
+		snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GSWITCH], left, right, 0);
+	} else if (slot->present & SNDRV_MIXER_OSS_PRESENT_PROUTE) {
+		snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PROUTE], left, right, 1);
+	} else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GROUTE) {
+		snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GROUTE], left, right, 1);
+	}
+	return 0;
+}
+
+static void snd_mixer_oss_put_volume1_vol(snd_mixer_oss_file_t *fmixer,
+					  snd_mixer_oss_slot_t *pslot,
+					  unsigned int numid,
+					  int left, int right)
+{
+	snd_ctl_elem_info_t *uinfo;
+	snd_ctl_elem_value_t *uctl;
+	snd_kcontrol_t *kctl;
+	snd_card_t *card = fmixer->card;
+	int res;
+
+	if (numid == ID_UNKNOWN)
+		return;
+	down_read(&card->controls_rwsem);
+	if ((kctl = snd_ctl_find_numid(card, numid)) == NULL)
+		return;
+	uinfo = kcalloc(1, sizeof(*uinfo), GFP_KERNEL);
+	uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL);
+	if (uinfo == NULL || uctl == NULL)
+		goto __unalloc;
+	snd_runtime_check(!kctl->info(kctl, uinfo), goto __unalloc);
+	snd_runtime_check(uinfo->type != SNDRV_CTL_ELEM_TYPE_BOOLEAN || uinfo->value.integer.min != 0 || uinfo->value.integer.max != 1, goto __unalloc);
+	uctl->value.integer.value[0] = snd_mixer_oss_conv2(left, uinfo->value.integer.min, uinfo->value.integer.max);
+	if (uinfo->count > 1)
+		uctl->value.integer.value[1] = snd_mixer_oss_conv2(right, uinfo->value.integer.min, uinfo->value.integer.max);
+	snd_runtime_check((res = kctl->put(kctl, uctl)) >= 0, goto __unalloc);
+	if (res > 0)
+		snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id);
+      __unalloc:
+	up_read(&card->controls_rwsem);
+      	kfree(uctl);
+	kfree(uinfo);
+}
+
+static void snd_mixer_oss_put_volume1_sw(snd_mixer_oss_file_t *fmixer,
+					 snd_mixer_oss_slot_t *pslot,
+					 unsigned int numid,
+					 int left, int right,
+					 int route)
+{
+	snd_ctl_elem_info_t *uinfo;
+	snd_ctl_elem_value_t *uctl;
+	snd_kcontrol_t *kctl;
+	snd_card_t *card = fmixer->card;
+	int res;
+
+	if (numid == ID_UNKNOWN)
+		return;
+	down_read(&card->controls_rwsem);
+	if ((kctl = snd_ctl_find_numid(card, numid)) == NULL) {
+		up_read(&fmixer->card->controls_rwsem);
+		return;
+	}
+	uinfo = kcalloc(1, sizeof(*uinfo), GFP_KERNEL);
+	uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL);
+	if (uinfo == NULL || uctl == NULL)
+		goto __unalloc;
+	snd_runtime_check(!kctl->info(kctl, uinfo), goto __unalloc);
+	if (uinfo->count > 1) {
+		uctl->value.integer.value[0] = left > 0 ? 1 : 0;
+		uctl->value.integer.value[route ? 3 : 1] = right > 0 ? 1 : 0;
+		if (route) {
+			uctl->value.integer.value[1] =
+			uctl->value.integer.value[2] = 0;
+		}
+	} else {
+		uctl->value.integer.value[0] = (left > 0 || right > 0) ? 1 : 0;
+	}
+	snd_runtime_check((res = kctl->put(kctl, uctl)) >= 0, goto __unalloc);
+	if (res > 0)
+		snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id);
+      __unalloc:
+	up_read(&card->controls_rwsem);
+      	kfree(uctl);
+	kfree(uinfo);
+}
+
+static int snd_mixer_oss_put_volume1(snd_mixer_oss_file_t *fmixer,
+				     snd_mixer_oss_slot_t *pslot,
+				     int left, int right)
+{
+	struct slot *slot = (struct slot *)pslot->private_data;
+	
+	if (slot->present & SNDRV_MIXER_OSS_PRESENT_PVOLUME) {
+		snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PVOLUME], left, right);
+		if (slot->present & SNDRV_MIXER_OSS_PRESENT_CVOLUME)
+			snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CVOLUME], left, right);
+	} else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GVOLUME) {
+		snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GVOLUME], left, right);
+	} else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GLOBAL) {
+		snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GLOBAL], left, right);
+	}
+	if (left || right) {
+		if (slot->present & SNDRV_MIXER_OSS_PRESENT_PSWITCH)
+			snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PSWITCH], left, right, 0);
+		if (slot->present & SNDRV_MIXER_OSS_PRESENT_GSWITCH)
+			snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GSWITCH], left, right, 0);
+		if (slot->present & SNDRV_MIXER_OSS_PRESENT_PROUTE)
+			snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PROUTE], left, right, 1);
+		if (slot->present & SNDRV_MIXER_OSS_PRESENT_GROUTE)
+			snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GROUTE], left, right, 1);
+	} else {
+		if (slot->present & SNDRV_MIXER_OSS_PRESENT_PSWITCH) {
+			snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PSWITCH], left, right, 0);
+		} else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GSWITCH) {
+			snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GSWITCH], left, right, 0);
+		} else if (slot->present & SNDRV_MIXER_OSS_PRESENT_PROUTE) {
+			snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PROUTE], left, right, 1);
+		} else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GROUTE) {
+			snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GROUTE], left, right, 1);
+		}
+	}
+	return 0;
+}
+
+static int snd_mixer_oss_get_recsrc1_sw(snd_mixer_oss_file_t *fmixer,
+					snd_mixer_oss_slot_t *pslot,
+					int *active)
+{
+	struct slot *slot = (struct slot *)pslot->private_data;
+	int left, right;
+	
+	left = right = 1;
+	snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], &left, &right, 0);
+	*active = (left || right) ? 1 : 0;
+	return 0;
+}
+
+static int snd_mixer_oss_get_recsrc1_route(snd_mixer_oss_file_t *fmixer,
+					   snd_mixer_oss_slot_t *pslot,
+					   int *active)
+{
+	struct slot *slot = (struct slot *)pslot->private_data;
+	int left, right;
+	
+	left = right = 1;
+	snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], &left, &right, 1);
+	*active = (left || right) ? 1 : 0;
+	return 0;
+}
+
+static int snd_mixer_oss_put_recsrc1_sw(snd_mixer_oss_file_t *fmixer,
+					snd_mixer_oss_slot_t *pslot,
+					int active)
+{
+	struct slot *slot = (struct slot *)pslot->private_data;
+	
+	snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], active, active, 0);
+	return 0;
+}
+
+static int snd_mixer_oss_put_recsrc1_route(snd_mixer_oss_file_t *fmixer,
+					   snd_mixer_oss_slot_t *pslot,
+					   int active)
+{
+	struct slot *slot = (struct slot *)pslot->private_data;
+	
+	snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], active, active, 1);
+	return 0;
+}
+
+static int snd_mixer_oss_get_recsrc2(snd_mixer_oss_file_t *fmixer, unsigned int *active_index)
+{
+	snd_card_t *card = fmixer->card;
+	snd_mixer_oss_t *mixer = fmixer->mixer;
+	snd_kcontrol_t *kctl;
+	snd_mixer_oss_slot_t *pslot;
+	struct slot *slot;
+	snd_ctl_elem_info_t *uinfo;
+	snd_ctl_elem_value_t *uctl;
+	int err, idx;
+	
+	uinfo = kcalloc(1, sizeof(*uinfo), GFP_KERNEL);
+	uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL);
+	if (uinfo == NULL || uctl == NULL) {
+		err = -ENOMEM;
+		goto __unlock;
+	}
+	down_read(&card->controls_rwsem);
+	kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0);
+	snd_runtime_check(kctl != NULL, err = -ENOENT; goto __unlock);
+	snd_runtime_check(!(err = kctl->info(kctl, uinfo)), goto __unlock);
+	snd_runtime_check(!(err = kctl->get(kctl, uctl)), goto __unlock);
+	for (idx = 0; idx < 32; idx++) {
+		if (!(mixer->mask_recsrc & (1 << idx)))
+			continue;
+		pslot = &mixer->slots[idx];
+		slot = (struct slot *)pslot->private_data;
+		if (slot->signature != SNDRV_MIXER_OSS_SIGNATURE)
+			continue;
+		if (!(slot->present & SNDRV_MIXER_OSS_PRESENT_CAPTURE))
+			continue;
+		if (slot->capture_item == uctl->value.enumerated.item[0]) {
+			*active_index = idx;
+			break;
+		}
+	}
+	err = 0;
+      __unlock:
+     	up_read(&card->controls_rwsem);
+      	kfree(uctl);
+      	kfree(uinfo);
+      	return err;
+}
+
+static int snd_mixer_oss_put_recsrc2(snd_mixer_oss_file_t *fmixer, unsigned int active_index)
+{
+	snd_card_t *card = fmixer->card;
+	snd_mixer_oss_t *mixer = fmixer->mixer;
+	snd_kcontrol_t *kctl;
+	snd_mixer_oss_slot_t *pslot;
+	struct slot *slot = NULL;
+	snd_ctl_elem_info_t *uinfo;
+	snd_ctl_elem_value_t *uctl;
+	int err;
+	unsigned int idx;
+
+	uinfo = kcalloc(1, sizeof(*uinfo), GFP_KERNEL);
+	uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL);
+	if (uinfo == NULL || uctl == NULL) {
+		err = -ENOMEM;
+		goto __unlock;
+	}
+	down_read(&card->controls_rwsem);
+	kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0);
+	snd_runtime_check(kctl != NULL, err = -ENOENT; goto __unlock);
+	snd_runtime_check(!(err = kctl->info(kctl, uinfo)), goto __unlock);
+	for (idx = 0; idx < 32; idx++) {
+		if (!(mixer->mask_recsrc & (1 << idx)))
+			continue;
+		pslot = &mixer->slots[idx];
+		slot = (struct slot *)pslot->private_data;
+		if (slot->signature != SNDRV_MIXER_OSS_SIGNATURE)
+			continue;
+		if (!(slot->present & SNDRV_MIXER_OSS_PRESENT_CAPTURE))
+			continue;
+		if (idx == active_index)
+			break;
+		slot = NULL;
+	}
+	snd_runtime_check(slot != NULL, goto __unlock);
+	for (idx = 0; idx < uinfo->count; idx++)
+		uctl->value.enumerated.item[idx] = slot->capture_item;
+	snd_runtime_check((err = kctl->put(kctl, uctl)) >= 0, );
+	if (err > 0)
+		snd_ctl_notify(fmixer->card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id);
+	err = 0;
+      __unlock:
+	up_read(&card->controls_rwsem);
+	kfree(uctl);
+	kfree(uinfo);
+	return err;
+}
+
+struct snd_mixer_oss_assign_table {
+	int oss_id;
+	const char *name;
+	int index;
+};
+
+static int snd_mixer_oss_build_test(snd_mixer_oss_t *mixer, struct slot *slot, const char *name, int index, int item)
+{
+	snd_ctl_elem_info_t *info;
+	snd_kcontrol_t *kcontrol;
+	snd_card_t *card = mixer->card;
+	int err;
+
+	down_read(&card->controls_rwsem);
+	kcontrol = snd_mixer_oss_test_id(mixer, name, index);
+	if (kcontrol == NULL) {
+		up_read(&card->controls_rwsem);
+		return 0;
+	}
+	info = kmalloc(sizeof(*info), GFP_KERNEL);
+	if (! info) {
+		up_read(&card->controls_rwsem);
+		return -ENOMEM;
+	}
+	if ((err = kcontrol->info(kcontrol, info)) < 0) {
+		up_read(&card->controls_rwsem);
+		kfree(info);
+		return err;
+	}
+	slot->numid[item] = kcontrol->id.numid;
+	up_read(&card->controls_rwsem);
+	if (info->count > slot->channels)
+		slot->channels = info->count;
+	slot->present |= 1 << item;
+	kfree(info);
+	return 0;
+}
+
+static void snd_mixer_oss_slot_free(snd_mixer_oss_slot_t *chn)
+{
+	struct slot *p = (struct slot *)chn->private_data;
+	if (p) {
+		if (p->allocated && p->assigned) {
+			kfree(p->assigned->name);
+			kfree(p->assigned);
+		}
+		kfree(p);
+	}
+}
+
+static void mixer_slot_clear(snd_mixer_oss_slot_t *rslot)
+{
+	int idx = rslot->number; /* remember this */
+	if (rslot->private_free)
+		rslot->private_free(rslot);
+	memset(rslot, 0, sizeof(*rslot));
+	rslot->number = idx;
+}
+
+/*
+ * build an OSS mixer element.
+ * ptr_allocated means the entry is dynamically allocated (change via proc file).
+ * when replace_old = 1, the old entry is replaced with the new one.
+ */
+static int snd_mixer_oss_build_input(snd_mixer_oss_t *mixer, struct snd_mixer_oss_assign_table *ptr, int ptr_allocated, int replace_old)
+{
+	struct slot slot;
+	struct slot *pslot;
+	snd_kcontrol_t *kctl;
+	snd_mixer_oss_slot_t *rslot;
+	char str[64];	
+	
+	/* check if already assigned */
+	if (mixer->slots[ptr->oss_id].get_volume && ! replace_old)
+		return 0;
+
+	memset(&slot, 0, sizeof(slot));
+	memset(slot.numid, 0xff, sizeof(slot.numid)); /* ID_UNKNOWN */
+	if (snd_mixer_oss_build_test(mixer, &slot, ptr->name, ptr->index,
+				     SNDRV_MIXER_OSS_ITEM_GLOBAL))
+		return 0;
+	sprintf(str, "%s Switch", ptr->name);
+	if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index,
+				     SNDRV_MIXER_OSS_ITEM_GSWITCH))
+		return 0;
+	sprintf(str, "%s Route", ptr->name);
+	if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index,
+				     SNDRV_MIXER_OSS_ITEM_GROUTE))
+		return 0;
+	sprintf(str, "%s Volume", ptr->name);
+	if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index,
+				     SNDRV_MIXER_OSS_ITEM_GVOLUME))
+		return 0;
+	sprintf(str, "%s Playback Switch", ptr->name);
+	if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index,
+				     SNDRV_MIXER_OSS_ITEM_PSWITCH))
+		return 0;
+	sprintf(str, "%s Playback Route", ptr->name);
+	if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index,
+				     SNDRV_MIXER_OSS_ITEM_PROUTE))
+		return 0;
+	sprintf(str, "%s Playback Volume", ptr->name);
+	if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index,
+				     SNDRV_MIXER_OSS_ITEM_PVOLUME))
+		return 0;
+	sprintf(str, "%s Capture Switch", ptr->name);
+	if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index,
+				     SNDRV_MIXER_OSS_ITEM_CSWITCH))
+		return 0;
+	sprintf(str, "%s Capture Route", ptr->name);
+	if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index,
+				     SNDRV_MIXER_OSS_ITEM_CROUTE))
+		return 0;
+	sprintf(str, "%s Capture Volume", ptr->name);
+	if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index,
+				     SNDRV_MIXER_OSS_ITEM_CVOLUME))
+		return 0;
+	down_read(&mixer->card->controls_rwsem);
+	if (ptr->index == 0 && (kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0)) != NULL) {
+		snd_ctl_elem_info_t *uinfo;
+
+		uinfo = kmalloc(sizeof(*uinfo), GFP_KERNEL);
+		if (! uinfo) {
+			up_read(&mixer->card->controls_rwsem);
+			return -ENOMEM;
+		}
+			
+		memset(uinfo, 0, sizeof(*uinfo));
+		if (kctl->info(kctl, uinfo)) {
+			up_read(&mixer->card->controls_rwsem);
+			return 0;
+		}
+		strcpy(str, ptr->name);
+		if (!strcmp(str, "Master"))
+			strcpy(str, "Mix");
+		if (!strcmp(str, "Master Mono"))
+			strcpy(str, "Mix Mono");
+		slot.capture_item = 0;
+		if (!strcmp(uinfo->value.enumerated.name, str)) {
+			slot.present |= SNDRV_MIXER_OSS_PRESENT_CAPTURE;
+		} else {
+			for (slot.capture_item = 1; slot.capture_item < uinfo->value.enumerated.items; slot.capture_item++) {
+				uinfo->value.enumerated.item = slot.capture_item;
+				if (kctl->info(kctl, uinfo)) {
+					up_read(&mixer->card->controls_rwsem);
+					return 0;
+				}
+				if (!strcmp(uinfo->value.enumerated.name, str)) {
+					slot.present |= SNDRV_MIXER_OSS_PRESENT_CAPTURE;
+					break;
+				}
+			}
+		}
+		kfree(uinfo);
+	}
+	up_read(&mixer->card->controls_rwsem);
+	if (slot.present != 0) {
+		pslot = (struct slot *)kmalloc(sizeof(slot), GFP_KERNEL);
+		snd_runtime_check(pslot != NULL, return -ENOMEM);
+		*pslot = slot;
+		pslot->signature = SNDRV_MIXER_OSS_SIGNATURE;
+		pslot->assigned = ptr;
+		pslot->allocated = ptr_allocated;
+		rslot = &mixer->slots[ptr->oss_id];
+		mixer_slot_clear(rslot);
+		rslot->stereo = slot.channels > 1 ? 1 : 0;
+		rslot->get_volume = snd_mixer_oss_get_volume1;
+		rslot->put_volume = snd_mixer_oss_put_volume1;
+		/* note: ES18xx have both Capture Source and XX Capture Volume !!! */
+		if (slot.present & SNDRV_MIXER_OSS_PRESENT_CSWITCH) {
+			rslot->get_recsrc = snd_mixer_oss_get_recsrc1_sw;
+			rslot->put_recsrc = snd_mixer_oss_put_recsrc1_sw;
+		} else if (slot.present & SNDRV_MIXER_OSS_PRESENT_CROUTE) {
+			rslot->get_recsrc = snd_mixer_oss_get_recsrc1_route;
+			rslot->put_recsrc = snd_mixer_oss_put_recsrc1_route;
+		} else if (slot.present & SNDRV_MIXER_OSS_PRESENT_CAPTURE) {
+			mixer->mask_recsrc |= 1 << ptr->oss_id;
+		}
+		rslot->private_data = pslot;
+		rslot->private_free = snd_mixer_oss_slot_free;
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ */
+#define MIXER_VOL(name) [SOUND_MIXER_##name] = #name
+static char *oss_mixer_names[SNDRV_OSS_MAX_MIXERS] = {
+	MIXER_VOL(VOLUME),
+	MIXER_VOL(BASS),
+	MIXER_VOL(TREBLE),
+	MIXER_VOL(SYNTH),
+	MIXER_VOL(PCM),
+	MIXER_VOL(SPEAKER),
+	MIXER_VOL(LINE),
+	MIXER_VOL(MIC),
+	MIXER_VOL(CD),
+	MIXER_VOL(IMIX),
+	MIXER_VOL(ALTPCM),
+	MIXER_VOL(RECLEV),
+	MIXER_VOL(IGAIN),
+	MIXER_VOL(OGAIN),
+	MIXER_VOL(LINE1),
+	MIXER_VOL(LINE2),
+	MIXER_VOL(LINE3),
+	MIXER_VOL(DIGITAL1),
+	MIXER_VOL(DIGITAL2),
+	MIXER_VOL(DIGITAL3),
+	MIXER_VOL(PHONEIN),
+	MIXER_VOL(PHONEOUT),
+	MIXER_VOL(VIDEO),
+	MIXER_VOL(RADIO),
+	MIXER_VOL(MONITOR),
+};
+	
+/*
+ *  /proc interface
+ */
+
+static void snd_mixer_oss_proc_read(snd_info_entry_t *entry,
+				    snd_info_buffer_t * buffer)
+{
+	snd_mixer_oss_t *mixer = entry->private_data;
+	int i;
+
+	down(&mixer->reg_mutex);
+	for (i = 0; i < SNDRV_OSS_MAX_MIXERS; i++) {
+		struct slot *p;
+
+		if (! oss_mixer_names[i])
+			continue;
+		p = (struct slot *)mixer->slots[i].private_data;
+		snd_iprintf(buffer, "%s ", oss_mixer_names[i]);
+		if (p && p->assigned)
+			snd_iprintf(buffer, "\"%s\" %d\n",
+				    p->assigned->name,
+				    p->assigned->index);
+		else
+			snd_iprintf(buffer, "\"\" 0\n");
+	}
+	up(&mixer->reg_mutex);
+}
+
+static void snd_mixer_oss_proc_write(snd_info_entry_t *entry,
+				     snd_info_buffer_t * buffer)
+{
+	snd_mixer_oss_t *mixer = entry->private_data;
+	char line[128], str[32], idxstr[16], *cptr;
+	int ch, idx;
+	struct snd_mixer_oss_assign_table *tbl;
+	struct slot *slot;
+
+	while (!snd_info_get_line(buffer, line, sizeof(line))) {
+		cptr = snd_info_get_str(str, line, sizeof(str));
+		for (ch = 0; ch < SNDRV_OSS_MAX_MIXERS; ch++)
+			if (oss_mixer_names[ch] && strcmp(oss_mixer_names[ch], str) == 0)
+				break;
+		if (ch >= SNDRV_OSS_MAX_MIXERS) {
+			snd_printk(KERN_ERR "mixer_oss: invalid OSS volume '%s'\n", str);
+			continue;
+		}
+		cptr = snd_info_get_str(str, cptr, sizeof(str));
+		if (! *str) {
+			/* remove the entry */
+			down(&mixer->reg_mutex);
+			mixer_slot_clear(&mixer->slots[ch]);
+			up(&mixer->reg_mutex);
+			continue;
+		}
+		snd_info_get_str(idxstr, cptr, sizeof(idxstr));
+		idx = simple_strtoul(idxstr, NULL, 10);
+		if (idx >= 0x4000) { /* too big */
+			snd_printk(KERN_ERR "mixer_oss: invalid index %d\n", idx);
+			continue;
+		}
+		down(&mixer->reg_mutex);
+		slot = (struct slot *)mixer->slots[ch].private_data;
+		if (slot && slot->assigned &&
+		    slot->assigned->index == idx && ! strcmp(slot->assigned->name, str))
+			/* not changed */
+			goto __unlock;
+		tbl = kmalloc(sizeof(*tbl), GFP_KERNEL);
+		if (! tbl) {
+			snd_printk(KERN_ERR "mixer_oss: no memory\n");
+			goto __unlock;
+		}
+		tbl->oss_id = ch;
+		tbl->name = snd_kmalloc_strdup(str, GFP_KERNEL);
+		if (! tbl->name) {
+			kfree(tbl);
+			goto __unlock;
+		}
+		tbl->index = idx;
+		if (snd_mixer_oss_build_input(mixer, tbl, 1, 1) <= 0) {
+			kfree(tbl->name);
+			kfree(tbl);
+		}
+	__unlock:
+		up(&mixer->reg_mutex);
+	}
+}
+
+static void snd_mixer_oss_proc_init(snd_mixer_oss_t *mixer)
+{
+	snd_info_entry_t *entry;
+
+	entry = snd_info_create_card_entry(mixer->card, "oss_mixer",
+					   mixer->card->proc_root);
+	if (! entry)
+		return;
+	entry->content = SNDRV_INFO_CONTENT_TEXT;
+	entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
+	entry->c.text.read_size = 8192;
+	entry->c.text.read = snd_mixer_oss_proc_read;
+	entry->c.text.write_size = 8192;
+	entry->c.text.write = snd_mixer_oss_proc_write;
+	entry->private_data = mixer;
+	if (snd_info_register(entry) < 0) {
+		snd_info_free_entry(entry);
+		entry = NULL;
+	}
+	mixer->proc_entry = entry;
+}
+
+static void snd_mixer_oss_proc_done(snd_mixer_oss_t *mixer)
+{
+	if (mixer->proc_entry) {
+		snd_info_unregister(mixer->proc_entry);
+		mixer->proc_entry = NULL;
+	}
+}
+
+static void snd_mixer_oss_build(snd_mixer_oss_t *mixer)
+{
+	static struct snd_mixer_oss_assign_table table[] = {
+		{ SOUND_MIXER_VOLUME, 	"Master",		0 },
+		{ SOUND_MIXER_VOLUME, 	"Front",		0 }, /* fallback */
+		{ SOUND_MIXER_BASS,	"Tone Control - Bass",	0 },
+		{ SOUND_MIXER_TREBLE,	"Tone Control - Treble", 0 },
+		{ SOUND_MIXER_SYNTH,	"Synth",		0 },
+		{ SOUND_MIXER_SYNTH,	"FM",			0 }, /* fallback */
+		{ SOUND_MIXER_SYNTH,	"Music",		0 }, /* fallback */
+		{ SOUND_MIXER_PCM,	"PCM",			0 },
+		{ SOUND_MIXER_SPEAKER,	"PC Speaker", 		0 },
+		{ SOUND_MIXER_LINE,	"Line", 		0 },
+		{ SOUND_MIXER_MIC,	"Mic", 			0 },
+		{ SOUND_MIXER_CD,	"CD", 			0 },
+		{ SOUND_MIXER_IMIX,	"Monitor Mix", 		0 },
+		{ SOUND_MIXER_ALTPCM,	"PCM",			1 },
+		{ SOUND_MIXER_ALTPCM,	"Headphone",		0 }, /* fallback */
+		{ SOUND_MIXER_ALTPCM,	"Wave",			0 }, /* fallback */
+		{ SOUND_MIXER_RECLEV,	"-- nothing --",	0 },
+		{ SOUND_MIXER_IGAIN,	"Capture",		0 },
+		{ SOUND_MIXER_OGAIN,	"Playback",		0 },
+		{ SOUND_MIXER_LINE1,	"Aux",			0 },
+		{ SOUND_MIXER_LINE2,	"Aux",			1 },
+		{ SOUND_MIXER_LINE3,	"Aux",			2 },
+		{ SOUND_MIXER_DIGITAL1,	"Digital",		0 },
+		{ SOUND_MIXER_DIGITAL1,	"IEC958",		0 }, /* fallback */
+		{ SOUND_MIXER_DIGITAL1,	"IEC958 Optical",	0 }, /* fallback */
+		{ SOUND_MIXER_DIGITAL1,	"IEC958 Coaxial",	0 }, /* fallback */
+		{ SOUND_MIXER_DIGITAL2,	"Digital",		1 },
+		{ SOUND_MIXER_DIGITAL3,	"Digital",		2 },
+		{ SOUND_MIXER_PHONEIN,	"Phone",		0 },
+		{ SOUND_MIXER_PHONEOUT,	"Master Mono",		0 },
+		{ SOUND_MIXER_PHONEOUT,	"Phone",		0 }, /* fallback */
+		{ SOUND_MIXER_VIDEO,	"Video",		0 },
+		{ SOUND_MIXER_RADIO,	"Radio",		0 },
+		{ SOUND_MIXER_MONITOR,	"Monitor",		0 }
+	};
+	unsigned int idx;
+	
+	for (idx = 0; idx < ARRAY_SIZE(table); idx++)
+		snd_mixer_oss_build_input(mixer, &table[idx], 0, 0);
+	if (mixer->mask_recsrc) {
+		mixer->get_recsrc = snd_mixer_oss_get_recsrc2;
+		mixer->put_recsrc = snd_mixer_oss_put_recsrc2;
+	}
+}
+
+/*
+ *
+ */
+
+static int snd_mixer_oss_free1(void *private)
+{
+	snd_mixer_oss_t *mixer = private;
+	snd_card_t * card;
+	int idx;
+ 
+	snd_assert(mixer != NULL, return -ENXIO);
+	card = mixer->card;
+	snd_assert(mixer == card->mixer_oss, return -ENXIO);
+	card->mixer_oss = NULL;
+	for (idx = 0; idx < SNDRV_OSS_MAX_MIXERS; idx++) {
+		snd_mixer_oss_slot_t *chn = &mixer->slots[idx];
+		if (chn->private_free)
+			chn->private_free(chn);
+	}
+	kfree(mixer);
+	return 0;
+}
+
+static int snd_mixer_oss_notify_handler(snd_card_t * card, int cmd)
+{
+	snd_mixer_oss_t *mixer;
+
+	if (cmd == SND_MIXER_OSS_NOTIFY_REGISTER) {
+		char name[128];
+		int idx, err;
+
+		mixer = kcalloc(2, sizeof(*mixer), GFP_KERNEL);
+		if (mixer == NULL)
+			return -ENOMEM;
+		init_MUTEX(&mixer->reg_mutex);
+		sprintf(name, "mixer%i%i", card->number, 0);
+		if ((err = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER,
+						   card, 0,
+						   &snd_mixer_oss_reg,
+						   name)) < 0) {
+			snd_printk("unable to register OSS mixer device %i:%i\n", card->number, 0);
+			kfree(mixer);
+			return err;
+		}
+		mixer->oss_dev_alloc = 1;
+		mixer->card = card;
+		if (*card->mixername)
+			strlcpy(mixer->name, card->mixername, sizeof(mixer->name));
+		else
+			strlcpy(mixer->name, name, sizeof(mixer->name));
+#ifdef SNDRV_OSS_INFO_DEV_MIXERS
+		snd_oss_info_register(SNDRV_OSS_INFO_DEV_MIXERS,
+				      card->number,
+				      mixer->name);
+#endif
+		for (idx = 0; idx < SNDRV_OSS_MAX_MIXERS; idx++)
+			mixer->slots[idx].number = idx;
+		card->mixer_oss = mixer;
+		snd_mixer_oss_build(mixer);
+		snd_mixer_oss_proc_init(mixer);
+	} else if (cmd == SND_MIXER_OSS_NOTIFY_DISCONNECT) {
+		mixer = card->mixer_oss;
+		if (mixer == NULL || !mixer->oss_dev_alloc)
+			return 0;
+		snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER, mixer->card, 0);
+		mixer->oss_dev_alloc = 0;
+	} else {		/* free */
+		mixer = card->mixer_oss;
+		if (mixer == NULL)
+			return 0;
+#ifdef SNDRV_OSS_INFO_DEV_MIXERS
+		snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_MIXERS, mixer->card->number);
+#endif
+		if (mixer->oss_dev_alloc)
+			snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER, mixer->card, 0);
+		snd_mixer_oss_proc_done(mixer);
+		return snd_mixer_oss_free1(mixer);
+	}
+	return 0;
+}
+
+static int __init alsa_mixer_oss_init(void)
+{
+	int idx;
+	
+	snd_mixer_oss_notify_callback = snd_mixer_oss_notify_handler;
+	for (idx = 0; idx < SNDRV_CARDS; idx++) {
+		if (snd_cards[idx])
+			snd_mixer_oss_notify_handler(snd_cards[idx], SND_MIXER_OSS_NOTIFY_REGISTER);
+	}
+	return 0;
+}
+
+static void __exit alsa_mixer_oss_exit(void)
+{
+	int idx;
+
+	snd_mixer_oss_notify_callback = NULL;
+	for (idx = 0; idx < SNDRV_CARDS; idx++) {
+		if (snd_cards[idx])
+			snd_mixer_oss_notify_handler(snd_cards[idx], SND_MIXER_OSS_NOTIFY_FREE);
+	}
+}
+
+module_init(alsa_mixer_oss_init)
+module_exit(alsa_mixer_oss_exit)
+
+EXPORT_SYMBOL(snd_mixer_oss_ioctl_card);
diff --git a/sound/core/oss/mulaw.c b/sound/core/oss/mulaw.c
new file mode 100644
index 000000000000..44ec4c66eb19
--- /dev/null
+++ b/sound/core/oss/mulaw.c
@@ -0,0 +1,308 @@
+/*
+ *  Mu-Law conversion Plug-In Interface
+ *  Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+ *                        Uros Bizjak <uros@kss-loka.si>
+ *
+ *  Based on reference implementation by Sun Microsystems, Inc.
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include "pcm_plugin.h"
+
+#define	SIGN_BIT	(0x80)		/* Sign bit for a u-law byte. */
+#define	QUANT_MASK	(0xf)		/* Quantization field mask. */
+#define	NSEGS		(8)		/* Number of u-law segments. */
+#define	SEG_SHIFT	(4)		/* Left shift for segment number. */
+#define	SEG_MASK	(0x70)		/* Segment field mask. */
+
+static inline int val_seg(int val)
+{
+	int r = 0;
+	val >>= 7;
+	if (val & 0xf0) {
+		val >>= 4;
+		r += 4;
+	}
+	if (val & 0x0c) {
+		val >>= 2;
+		r += 2;
+	}
+	if (val & 0x02)
+		r += 1;
+	return r;
+}
+
+#define	BIAS		(0x84)		/* Bias for linear code. */
+
+/*
+ * linear2ulaw() - Convert a linear PCM value to u-law
+ *
+ * In order to simplify the encoding process, the original linear magnitude
+ * is biased by adding 33 which shifts the encoding range from (0 - 8158) to
+ * (33 - 8191). The result can be seen in the following encoding table:
+ *
+ *	Biased Linear Input Code	Compressed Code
+ *	------------------------	---------------
+ *	00000001wxyza			000wxyz
+ *	0000001wxyzab			001wxyz
+ *	000001wxyzabc			010wxyz
+ *	00001wxyzabcd			011wxyz
+ *	0001wxyzabcde			100wxyz
+ *	001wxyzabcdef			101wxyz
+ *	01wxyzabcdefg			110wxyz
+ *	1wxyzabcdefgh			111wxyz
+ *
+ * Each biased linear code has a leading 1 which identifies the segment
+ * number. The value of the segment number is equal to 7 minus the number
+ * of leading 0's. The quantization interval is directly available as the
+ * four bits wxyz.  * The trailing bits (a - h) are ignored.
+ *
+ * Ordinarily the complement of the resulting code word is used for
+ * transmission, and so the code word is complemented before it is returned.
+ *
+ * For further information see John C. Bellamy's Digital Telephony, 1982,
+ * John Wiley & Sons, pps 98-111 and 472-476.
+ */
+static unsigned char linear2ulaw(int pcm_val)	/* 2's complement (16-bit range) */
+{
+	int mask;
+	int seg;
+	unsigned char uval;
+
+	/* Get the sign and the magnitude of the value. */
+	if (pcm_val < 0) {
+		pcm_val = BIAS - pcm_val;
+		mask = 0x7F;
+	} else {
+		pcm_val += BIAS;
+		mask = 0xFF;
+	}
+	if (pcm_val > 0x7FFF)
+		pcm_val = 0x7FFF;
+
+	/* Convert the scaled magnitude to segment number. */
+	seg = val_seg(pcm_val);
+
+	/*
+	 * Combine the sign, segment, quantization bits;
+	 * and complement the code word.
+	 */
+	uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0xF);
+	return uval ^ mask;
+}
+
+/*
+ * ulaw2linear() - Convert a u-law value to 16-bit linear PCM
+ *
+ * First, a biased linear code is derived from the code word. An unbiased
+ * output can then be obtained by subtracting 33 from the biased code.
+ *
+ * Note that this function expects to be passed the complement of the
+ * original code word. This is in keeping with ISDN conventions.
+ */
+static int ulaw2linear(unsigned char u_val)
+{
+	int t;
+
+	/* Complement to obtain normal u-law value. */
+	u_val = ~u_val;
+
+	/*
+	 * Extract and bias the quantization bits. Then
+	 * shift up by the segment number and subtract out the bias.
+	 */
+	t = ((u_val & QUANT_MASK) << 3) + BIAS;
+	t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT;
+
+	return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS));
+}
+
+/*
+ *  Basic Mu-Law plugin
+ */
+
+typedef void (*mulaw_f)(snd_pcm_plugin_t *plugin,
+			const snd_pcm_plugin_channel_t *src_channels,
+			snd_pcm_plugin_channel_t *dst_channels,
+			snd_pcm_uframes_t frames);
+
+typedef struct mulaw_private_data {
+	mulaw_f func;
+	int conv;
+} mulaw_t;
+
+static void mulaw_decode(snd_pcm_plugin_t *plugin,
+			const snd_pcm_plugin_channel_t *src_channels,
+			snd_pcm_plugin_channel_t *dst_channels,
+			snd_pcm_uframes_t frames)
+{
+#define PUT_S16_LABELS
+#include "plugin_ops.h"
+#undef PUT_S16_LABELS
+	mulaw_t *data = (mulaw_t *)plugin->extra_data;
+	void *put = put_s16_labels[data->conv];
+	int channel;
+	int nchannels = plugin->src_format.channels;
+	for (channel = 0; channel < nchannels; ++channel) {
+		char *src;
+		char *dst;
+		int src_step, dst_step;
+		snd_pcm_uframes_t frames1;
+		if (!src_channels[channel].enabled) {
+			if (dst_channels[channel].wanted)
+				snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format);
+			dst_channels[channel].enabled = 0;
+			continue;
+		}
+		dst_channels[channel].enabled = 1;
+		src = src_channels[channel].area.addr + src_channels[channel].area.first / 8;
+		dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
+		src_step = src_channels[channel].area.step / 8;
+		dst_step = dst_channels[channel].area.step / 8;
+		frames1 = frames;
+		while (frames1-- > 0) {
+			signed short sample = ulaw2linear(*src);
+			goto *put;
+#define PUT_S16_END after
+#include "plugin_ops.h"
+#undef PUT_S16_END
+		after:
+			src += src_step;
+			dst += dst_step;
+		}
+	}
+}
+
+static void mulaw_encode(snd_pcm_plugin_t *plugin,
+			const snd_pcm_plugin_channel_t *src_channels,
+			snd_pcm_plugin_channel_t *dst_channels,
+			snd_pcm_uframes_t frames)
+{
+#define GET_S16_LABELS
+#include "plugin_ops.h"
+#undef GET_S16_LABELS
+	mulaw_t *data = (mulaw_t *)plugin->extra_data;
+	void *get = get_s16_labels[data->conv];
+	int channel;
+	int nchannels = plugin->src_format.channels;
+	signed short sample = 0;
+	for (channel = 0; channel < nchannels; ++channel) {
+		char *src;
+		char *dst;
+		int src_step, dst_step;
+		snd_pcm_uframes_t frames1;
+		if (!src_channels[channel].enabled) {
+			if (dst_channels[channel].wanted)
+				snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format);
+			dst_channels[channel].enabled = 0;
+			continue;
+		}
+		dst_channels[channel].enabled = 1;
+		src = src_channels[channel].area.addr + src_channels[channel].area.first / 8;
+		dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
+		src_step = src_channels[channel].area.step / 8;
+		dst_step = dst_channels[channel].area.step / 8;
+		frames1 = frames;
+		while (frames1-- > 0) {
+			goto *get;
+#define GET_S16_END after
+#include "plugin_ops.h"
+#undef GET_S16_END
+		after:
+			*dst = linear2ulaw(sample);
+			src += src_step;
+			dst += dst_step;
+		}
+	}
+}
+
+static snd_pcm_sframes_t mulaw_transfer(snd_pcm_plugin_t *plugin,
+			      const snd_pcm_plugin_channel_t *src_channels,
+			      snd_pcm_plugin_channel_t *dst_channels,
+			      snd_pcm_uframes_t frames)
+{
+	mulaw_t *data;
+
+	snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO);
+	if (frames == 0)
+		return 0;
+#ifdef CONFIG_SND_DEBUG
+	{
+		unsigned int channel;
+		for (channel = 0; channel < plugin->src_format.channels; channel++) {
+			snd_assert(src_channels[channel].area.first % 8 == 0 &&
+				   src_channels[channel].area.step % 8 == 0,
+				   return -ENXIO);
+			snd_assert(dst_channels[channel].area.first % 8 == 0 &&
+				   dst_channels[channel].area.step % 8 == 0,
+				   return -ENXIO);
+		}
+	}
+#endif
+	data = (mulaw_t *)plugin->extra_data;
+	data->func(plugin, src_channels, dst_channels, frames);
+	return frames;
+}
+
+int snd_pcm_plugin_build_mulaw(snd_pcm_plug_t *plug,
+			       snd_pcm_plugin_format_t *src_format,
+			       snd_pcm_plugin_format_t *dst_format,
+			       snd_pcm_plugin_t **r_plugin)
+{
+	int err;
+	mulaw_t *data;
+	snd_pcm_plugin_t *plugin;
+	snd_pcm_plugin_format_t *format;
+	mulaw_f func;
+
+	snd_assert(r_plugin != NULL, return -ENXIO);
+	*r_plugin = NULL;
+
+	snd_assert(src_format->rate == dst_format->rate, return -ENXIO);
+	snd_assert(src_format->channels == dst_format->channels, return -ENXIO);
+
+	if (dst_format->format == SNDRV_PCM_FORMAT_MU_LAW) {
+		format = src_format;
+		func = mulaw_encode;
+	}
+	else if (src_format->format == SNDRV_PCM_FORMAT_MU_LAW) {
+		format = dst_format;
+		func = mulaw_decode;
+	}
+	else {
+		snd_BUG();
+		return -EINVAL;
+	}
+	snd_assert(snd_pcm_format_linear(format->format) != 0, return -ENXIO);
+
+	err = snd_pcm_plugin_build(plug, "Mu-Law<->linear conversion",
+				   src_format, dst_format,
+				   sizeof(mulaw_t), &plugin);
+	if (err < 0)
+		return err;
+	data = (mulaw_t*)plugin->extra_data;
+	data->func = func;
+	data->conv = getput_index(format->format);
+	snd_assert(data->conv >= 0 && data->conv < 4*2*2, return -EINVAL);
+	plugin->transfer = mulaw_transfer;
+	*r_plugin = plugin;
+	return 0;
+}
diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c
new file mode 100644
index 000000000000..1a805020f57a
--- /dev/null
+++ b/sound/core/oss/pcm_oss.c
@@ -0,0 +1,2530 @@
+/*
+ *  Digital Audio (PCM) abstract layer / OSS compatible
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#if 0
+#define PLUGIN_DEBUG
+#endif
+#if 0
+#define OSS_DEBUG
+#endif
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/vmalloc.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "pcm_plugin.h"
+#include <sound/info.h>
+#include <linux/soundcard.h>
+#include <sound/initval.h>
+
+#define OSS_ALSAEMULVER		_SIOR ('M', 249, int)
+
+static int dsp_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 0};
+static int adsp_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1};
+static int nonblock_open = 1;
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>, Abramo Bagnara <abramo@alsa-project.org>");
+MODULE_DESCRIPTION("PCM OSS emulation for ALSA.");
+MODULE_LICENSE("GPL");
+module_param_array(dsp_map, int, NULL, 0444);
+MODULE_PARM_DESC(dsp_map, "PCM device number assigned to 1st OSS device.");
+module_param_array(adsp_map, int, NULL, 0444);
+MODULE_PARM_DESC(adsp_map, "PCM device number assigned to 2nd OSS device.");
+module_param(nonblock_open, bool, 0644);
+MODULE_PARM_DESC(nonblock_open, "Don't block opening busy PCM devices.");
+MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_PCM);
+MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_PCM1);
+
+extern int snd_mixer_oss_ioctl_card(snd_card_t *card, unsigned int cmd, unsigned long arg);
+static int snd_pcm_oss_get_rate(snd_pcm_oss_file_t *pcm_oss_file);
+static int snd_pcm_oss_get_channels(snd_pcm_oss_file_t *pcm_oss_file);
+static int snd_pcm_oss_get_format(snd_pcm_oss_file_t *pcm_oss_file);
+
+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);
+}
+
+static int snd_pcm_oss_plugin_clear(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_plugin_t *plugin, *next;
+	
+	plugin = runtime->oss.plugin_first;
+	while (plugin) {
+		next = plugin->next;
+		snd_pcm_plugin_free(plugin);
+		plugin = next;
+	}
+	runtime->oss.plugin_first = runtime->oss.plugin_last = NULL;
+	return 0;
+}
+
+static int snd_pcm_plugin_insert(snd_pcm_plugin_t *plugin)
+{
+	snd_pcm_runtime_t *runtime = plugin->plug->runtime;
+	plugin->next = runtime->oss.plugin_first;
+	plugin->prev = NULL;
+	if (runtime->oss.plugin_first) {
+		runtime->oss.plugin_first->prev = plugin;
+		runtime->oss.plugin_first = plugin;
+	} else {
+		runtime->oss.plugin_last =
+		runtime->oss.plugin_first = plugin;
+	}
+	return 0;
+}
+
+int snd_pcm_plugin_append(snd_pcm_plugin_t *plugin)
+{
+	snd_pcm_runtime_t *runtime = plugin->plug->runtime;
+	plugin->next = NULL;
+	plugin->prev = runtime->oss.plugin_last;
+	if (runtime->oss.plugin_last) {
+		runtime->oss.plugin_last->next = plugin;
+		runtime->oss.plugin_last = plugin;
+	} else {
+		runtime->oss.plugin_last =
+		runtime->oss.plugin_first = plugin;
+	}
+	return 0;
+}
+
+static long snd_pcm_oss_bytes(snd_pcm_substream_t *substream, long frames)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_uframes_t buffer_size = snd_pcm_lib_buffer_bytes(substream);
+	frames = frames_to_bytes(runtime, frames);
+	if (buffer_size == runtime->oss.buffer_bytes)
+		return frames;
+	return (runtime->oss.buffer_bytes * frames) / buffer_size;
+}
+
+static long snd_pcm_alsa_frames(snd_pcm_substream_t *substream, long bytes)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_uframes_t buffer_size = snd_pcm_lib_buffer_bytes(substream);
+	if (buffer_size == runtime->oss.buffer_bytes)
+		return bytes_to_frames(runtime, bytes);
+	return bytes_to_frames(runtime, (buffer_size * bytes) / runtime->oss.buffer_bytes);
+}
+
+static int snd_pcm_oss_format_from(int format)
+{
+	switch (format) {
+	case AFMT_MU_LAW:	return SNDRV_PCM_FORMAT_MU_LAW;
+	case AFMT_A_LAW:	return SNDRV_PCM_FORMAT_A_LAW;
+	case AFMT_IMA_ADPCM:	return SNDRV_PCM_FORMAT_IMA_ADPCM;
+	case AFMT_U8:		return SNDRV_PCM_FORMAT_U8;
+	case AFMT_S16_LE:	return SNDRV_PCM_FORMAT_S16_LE;
+	case AFMT_S16_BE:	return SNDRV_PCM_FORMAT_S16_BE;
+	case AFMT_S8:		return SNDRV_PCM_FORMAT_S8;
+	case AFMT_U16_LE:	return SNDRV_PCM_FORMAT_U16_LE;
+	case AFMT_U16_BE:	return SNDRV_PCM_FORMAT_U16_BE;
+	case AFMT_MPEG:		return SNDRV_PCM_FORMAT_MPEG;
+	default:		return SNDRV_PCM_FORMAT_U8;
+	}
+}
+
+static int snd_pcm_oss_format_to(int format)
+{
+	switch (format) {
+	case SNDRV_PCM_FORMAT_MU_LAW:	return AFMT_MU_LAW;
+	case SNDRV_PCM_FORMAT_A_LAW:	return AFMT_A_LAW;
+	case SNDRV_PCM_FORMAT_IMA_ADPCM:	return AFMT_IMA_ADPCM;
+	case SNDRV_PCM_FORMAT_U8:		return AFMT_U8;
+	case SNDRV_PCM_FORMAT_S16_LE:	return AFMT_S16_LE;
+	case SNDRV_PCM_FORMAT_S16_BE:	return AFMT_S16_BE;
+	case SNDRV_PCM_FORMAT_S8:		return AFMT_S8;
+	case SNDRV_PCM_FORMAT_U16_LE:	return AFMT_U16_LE;
+	case SNDRV_PCM_FORMAT_U16_BE:	return AFMT_U16_BE;
+	case SNDRV_PCM_FORMAT_MPEG:		return AFMT_MPEG;
+	default:			return -EINVAL;
+	}
+}
+
+static int snd_pcm_oss_period_size(snd_pcm_substream_t *substream, 
+				   snd_pcm_hw_params_t *oss_params,
+				   snd_pcm_hw_params_t *slave_params)
+{
+	size_t s;
+	size_t oss_buffer_size, oss_period_size, oss_periods;
+	size_t min_period_size, max_period_size;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	size_t oss_frame_size;
+
+	oss_frame_size = snd_pcm_format_physical_width(params_format(oss_params)) *
+			 params_channels(oss_params) / 8;
+
+	oss_buffer_size = snd_pcm_plug_client_size(substream,
+						   snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, NULL)) * oss_frame_size;
+	oss_buffer_size = 1 << ld2(oss_buffer_size);
+	if (atomic_read(&runtime->mmap_count)) {
+		if (oss_buffer_size > runtime->oss.mmap_bytes)
+			oss_buffer_size = runtime->oss.mmap_bytes;
+	}
+
+	if (substream->oss.setup &&
+	    substream->oss.setup->period_size > 16)
+		oss_period_size = substream->oss.setup->period_size;
+	else if (runtime->oss.fragshift) {
+		oss_period_size = 1 << runtime->oss.fragshift;
+		if (oss_period_size > oss_buffer_size / 2)
+			oss_period_size = oss_buffer_size / 2;
+	} else {
+		int sd;
+		size_t bytes_per_sec = params_rate(oss_params) * snd_pcm_format_physical_width(params_format(oss_params)) * params_channels(oss_params) / 8;
+
+		oss_period_size = oss_buffer_size;
+		do {
+			oss_period_size /= 2;
+		} while (oss_period_size > bytes_per_sec);
+		if (runtime->oss.subdivision == 0) {
+			sd = 4;
+			if (oss_period_size / sd > 4096)
+				sd *= 2;
+			if (oss_period_size / sd < 4096)
+				sd = 1;
+		} else
+			sd = runtime->oss.subdivision;
+		oss_period_size /= sd;
+		if (oss_period_size < 16)
+			oss_period_size = 16;
+	}
+
+	min_period_size = snd_pcm_plug_client_size(substream,
+						   snd_pcm_hw_param_value_min(slave_params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, NULL));
+	min_period_size *= oss_frame_size;
+	min_period_size = 1 << (ld2(min_period_size - 1) + 1);
+	if (oss_period_size < min_period_size)
+		oss_period_size = min_period_size;
+
+	max_period_size = snd_pcm_plug_client_size(substream,
+						   snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, NULL));
+	max_period_size *= oss_frame_size;
+	max_period_size = 1 << ld2(max_period_size);
+	if (oss_period_size > max_period_size)
+		oss_period_size = max_period_size;
+
+	oss_periods = oss_buffer_size / oss_period_size;
+
+	if (substream->oss.setup) {
+		if (substream->oss.setup->periods > 1)
+			oss_periods = substream->oss.setup->periods;
+	}
+
+	s = snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_PERIODS, NULL);
+	if (runtime->oss.maxfrags && s > runtime->oss.maxfrags)
+		s = runtime->oss.maxfrags;
+	if (oss_periods > s)
+		oss_periods = s;
+
+	s = snd_pcm_hw_param_value_min(slave_params, SNDRV_PCM_HW_PARAM_PERIODS, NULL);
+	if (s < 2)
+		s = 2;
+	if (oss_periods < s)
+		oss_periods = s;
+
+	while (oss_period_size * oss_periods > oss_buffer_size)
+		oss_period_size /= 2;
+
+	snd_assert(oss_period_size >= 16, return -EINVAL);
+	runtime->oss.period_bytes = oss_period_size;
+	runtime->oss.period_frames = 1;
+	runtime->oss.periods = oss_periods;
+	return 0;
+}
+
+static int choose_rate(snd_pcm_substream_t *substream,
+		       snd_pcm_hw_params_t *params, unsigned int best_rate)
+{
+	snd_interval_t *it;
+	snd_pcm_hw_params_t *save;
+	unsigned int rate, prev;
+
+	save = kmalloc(sizeof(*save), GFP_KERNEL);
+	if (save == NULL)
+		return -ENOMEM;
+	*save = *params;
+	it = hw_param_interval(save, SNDRV_PCM_HW_PARAM_RATE);
+
+	/* try multiples of the best rate */
+	rate = best_rate;
+	for (;;) {
+		if (it->max < rate || (it->max == rate && it->openmax))
+			break;
+		if (it->min < rate || (it->min == rate && !it->openmin)) {
+			int ret;
+			ret = snd_pcm_hw_param_set(substream, params,
+						   SNDRV_PCM_HW_PARAM_RATE,
+						   rate, 0);
+			if (ret == (int)rate) {
+				kfree(save);
+				return rate;
+			}
+			*params = *save;
+		}
+		prev = rate;
+		rate += best_rate;
+		if (rate <= prev)
+			break;
+	}
+
+	/* not found, use the nearest rate */
+	kfree(save);
+	return snd_pcm_hw_param_near(substream, params, SNDRV_PCM_HW_PARAM_RATE, best_rate, NULL);
+}
+
+static int snd_pcm_oss_change_params(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_hw_params_t *params, *sparams;
+	snd_pcm_sw_params_t *sw_params;
+	ssize_t oss_buffer_size, oss_period_size;
+	size_t oss_frame_size;
+	int err;
+	int direct;
+	int format, sformat, n;
+	snd_mask_t sformat_mask;
+	snd_mask_t mask;
+
+	sw_params = kmalloc(sizeof(*sw_params), GFP_KERNEL);
+	params = kmalloc(sizeof(*params), GFP_KERNEL);
+	sparams = kmalloc(sizeof(*sparams), GFP_KERNEL);
+	if (!sw_params || !params || !sparams) {
+		snd_printd("No memory\n");
+		err = -ENOMEM;
+		goto failure;
+	}
+
+	if (atomic_read(&runtime->mmap_count)) {
+		direct = 1;
+	} else {
+		snd_pcm_oss_setup_t *setup = substream->oss.setup;
+		direct = (setup != NULL && setup->direct);
+	}
+
+	_snd_pcm_hw_params_any(sparams);
+	_snd_pcm_hw_param_setinteger(sparams, SNDRV_PCM_HW_PARAM_PERIODS);
+	_snd_pcm_hw_param_min(sparams, SNDRV_PCM_HW_PARAM_PERIODS, 2, 0);
+	snd_mask_none(&mask);
+	if (atomic_read(&runtime->mmap_count))
+		snd_mask_set(&mask, SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
+	else {
+		snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_INTERLEAVED);
+		if (!direct)
+			snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_NONINTERLEAVED);
+	}
+	err = snd_pcm_hw_param_mask(substream, sparams, SNDRV_PCM_HW_PARAM_ACCESS, &mask);
+	if (err < 0) {
+		snd_printd("No usable accesses\n");
+		err = -EINVAL;
+		goto failure;
+	}
+	choose_rate(substream, sparams, runtime->oss.rate);
+	snd_pcm_hw_param_near(substream, sparams, SNDRV_PCM_HW_PARAM_CHANNELS, runtime->oss.channels, NULL);
+
+	format = snd_pcm_oss_format_from(runtime->oss.format);
+
+	sformat_mask = *hw_param_mask(sparams, SNDRV_PCM_HW_PARAM_FORMAT);
+	if (direct)
+		sformat = format;
+	else
+		sformat = snd_pcm_plug_slave_format(format, &sformat_mask);
+
+	if (sformat < 0 || !snd_mask_test(&sformat_mask, sformat)) {
+		for (sformat = 0; sformat <= SNDRV_PCM_FORMAT_LAST; sformat++) {
+			if (snd_mask_test(&sformat_mask, sformat) &&
+			    snd_pcm_oss_format_to(sformat) >= 0)
+				break;
+		}
+		if (sformat > SNDRV_PCM_FORMAT_LAST) {
+			snd_printd("Cannot find a format!!!\n");
+			err = -EINVAL;
+			goto failure;
+		}
+	}
+	err = _snd_pcm_hw_param_set(sparams, SNDRV_PCM_HW_PARAM_FORMAT, sformat, 0);
+	snd_assert(err >= 0, goto failure);
+
+	if (direct) {
+		memcpy(params, sparams, sizeof(*params));
+	} else {
+		_snd_pcm_hw_params_any(params);
+		_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS,
+				      SNDRV_PCM_ACCESS_RW_INTERLEAVED, 0);
+		_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT,
+				      snd_pcm_oss_format_from(runtime->oss.format), 0);
+		_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS,
+				      runtime->oss.channels, 0);
+		_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE,
+				      runtime->oss.rate, 0);
+		pdprintf("client: access = %i, format = %i, channels = %i, rate = %i\n",
+			 params_access(params), params_format(params),
+			 params_channels(params), params_rate(params));
+	}
+	pdprintf("slave: access = %i, format = %i, channels = %i, rate = %i\n",
+		 params_access(sparams), params_format(sparams),
+		 params_channels(sparams), params_rate(sparams));
+
+	oss_frame_size = snd_pcm_format_physical_width(params_format(params)) *
+			 params_channels(params) / 8;
+
+	snd_pcm_oss_plugin_clear(substream);
+	if (!direct) {
+		/* add necessary plugins */
+		snd_pcm_oss_plugin_clear(substream);
+		if ((err = snd_pcm_plug_format_plugins(substream,
+						       params, 
+						       sparams)) < 0) {
+			snd_printd("snd_pcm_plug_format_plugins failed: %i\n", err);
+			snd_pcm_oss_plugin_clear(substream);
+			goto failure;
+		}
+		if (runtime->oss.plugin_first) {
+			snd_pcm_plugin_t *plugin;
+			if ((err = snd_pcm_plugin_build_io(substream, sparams, &plugin)) < 0) {
+				snd_printd("snd_pcm_plugin_build_io failed: %i\n", err);
+				snd_pcm_oss_plugin_clear(substream);
+				goto failure;
+			}
+			if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+				err = snd_pcm_plugin_append(plugin);
+			} else {
+				err = snd_pcm_plugin_insert(plugin);
+			}
+			if (err < 0) {
+				snd_pcm_oss_plugin_clear(substream);
+				goto failure;
+			}
+		}
+	}
+
+	err = snd_pcm_oss_period_size(substream, params, sparams);
+	if (err < 0)
+		goto failure;
+
+	n = snd_pcm_plug_slave_size(substream, runtime->oss.period_bytes / oss_frame_size);
+	err = snd_pcm_hw_param_near(substream, sparams, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, n, NULL);
+	snd_assert(err >= 0, goto failure);
+
+	err = snd_pcm_hw_param_near(substream, sparams, SNDRV_PCM_HW_PARAM_PERIODS,
+				     runtime->oss.periods, NULL);
+	snd_assert(err >= 0, goto failure);
+
+	snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
+
+	if ((err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_HW_PARAMS, sparams)) < 0) {
+		snd_printd("HW_PARAMS failed: %i\n", err);
+		goto failure;
+	}
+
+	memset(sw_params, 0, sizeof(*sw_params));
+	if (runtime->oss.trigger) {
+		sw_params->start_threshold = 1;
+	} else {
+		sw_params->start_threshold = runtime->boundary;
+	}
+	if (atomic_read(&runtime->mmap_count) || substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+		sw_params->stop_threshold = runtime->boundary;
+	else
+		sw_params->stop_threshold = runtime->buffer_size;
+	sw_params->tstamp_mode = SNDRV_PCM_TSTAMP_NONE;
+	sw_params->period_step = 1;
+	sw_params->sleep_min = 0;
+	sw_params->avail_min = 1;
+	sw_params->xfer_align = 1;
+	if (atomic_read(&runtime->mmap_count) ||
+	    (substream->oss.setup && substream->oss.setup->nosilence)) {
+		sw_params->silence_threshold = 0;
+		sw_params->silence_size = 0;
+	} else {
+		snd_pcm_uframes_t frames;
+		frames = runtime->period_size + 16;
+		if (frames > runtime->buffer_size)
+			frames = runtime->buffer_size;
+		sw_params->silence_threshold = frames;
+		sw_params->silence_size = frames;
+	}
+
+	if ((err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_SW_PARAMS, sw_params)) < 0) {
+		snd_printd("SW_PARAMS failed: %i\n", err);
+		goto failure;
+	}
+
+	runtime->oss.periods = params_periods(sparams);
+	oss_period_size = snd_pcm_plug_client_size(substream, params_period_size(sparams));
+	snd_assert(oss_period_size >= 0, err = -EINVAL; goto failure);
+	if (runtime->oss.plugin_first) {
+		err = snd_pcm_plug_alloc(substream, oss_period_size);
+		if (err < 0)
+			goto failure;
+	}
+	oss_period_size *= oss_frame_size;
+
+	oss_buffer_size = oss_period_size * runtime->oss.periods;
+	snd_assert(oss_buffer_size >= 0, err = -EINVAL; goto failure);
+
+	runtime->oss.period_bytes = oss_period_size;
+	runtime->oss.buffer_bytes = oss_buffer_size;
+
+	pdprintf("oss: period bytes = %i, buffer bytes = %i\n",
+		 runtime->oss.period_bytes,
+		 runtime->oss.buffer_bytes);
+	pdprintf("slave: period_size = %i, buffer_size = %i\n",
+		 params_period_size(sparams),
+		 params_buffer_size(sparams));
+
+	runtime->oss.format = snd_pcm_oss_format_to(params_format(params));
+	runtime->oss.channels = params_channels(params);
+	runtime->oss.rate = params_rate(params);
+
+	runtime->oss.params = 0;
+	runtime->oss.prepare = 1;
+	vfree(runtime->oss.buffer);
+	runtime->oss.buffer = vmalloc(runtime->oss.period_bytes);
+	runtime->oss.buffer_used = 0;
+	if (runtime->dma_area)
+		snd_pcm_format_set_silence(runtime->format, runtime->dma_area, bytes_to_samples(runtime, runtime->dma_bytes));
+
+	runtime->oss.period_frames = snd_pcm_alsa_frames(substream, oss_period_size);
+
+	err = 0;
+failure:
+	kfree(sw_params);
+	kfree(params);
+	kfree(sparams);
+	return err;
+}
+
+static int snd_pcm_oss_get_active_substream(snd_pcm_oss_file_t *pcm_oss_file, snd_pcm_substream_t **r_substream)
+{
+	int idx, err;
+	snd_pcm_substream_t *asubstream = NULL, *substream;
+
+	for (idx = 0; idx < 2; idx++) {
+		substream = pcm_oss_file->streams[idx];
+		if (substream == NULL)
+			continue;
+		if (asubstream == NULL)
+			asubstream = substream;
+		if (substream->runtime->oss.params) {
+			err = snd_pcm_oss_change_params(substream);
+			if (err < 0)
+				return err;
+		}
+	}
+	snd_assert(asubstream != NULL, return -EIO);
+	if (r_substream)
+		*r_substream = asubstream;
+	return 0;
+}
+
+static int snd_pcm_oss_prepare(snd_pcm_substream_t *substream)
+{
+	int err;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+
+	err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_PREPARE, NULL);
+	if (err < 0) {
+		snd_printd("snd_pcm_oss_prepare: SNDRV_PCM_IOCTL_PREPARE failed\n");
+		return err;
+	}
+	runtime->oss.prepare = 0;
+	runtime->oss.prev_hw_ptr_interrupt = 0;
+	runtime->oss.period_ptr = 0;
+	runtime->oss.buffer_used = 0;
+
+	return 0;
+}
+
+static int snd_pcm_oss_make_ready(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime;
+	int err;
+
+	if (substream == NULL)
+		return 0;
+	runtime = substream->runtime;
+	if (runtime->oss.params) {
+		err = snd_pcm_oss_change_params(substream);
+		if (err < 0)
+			return err;
+	}
+	if (runtime->oss.prepare) {
+		err = snd_pcm_oss_prepare(substream);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int snd_pcm_oss_capture_position_fixup(snd_pcm_substream_t *substream, snd_pcm_sframes_t *delay)
+{
+	snd_pcm_runtime_t *runtime;
+	snd_pcm_uframes_t frames;
+	int err = 0;
+
+	while (1) {
+		err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, delay);
+		if (err < 0)
+			break;
+		runtime = substream->runtime;
+		if (*delay <= (snd_pcm_sframes_t)runtime->buffer_size)
+			break;
+		/* in case of overrun, skip whole periods like OSS/Linux driver does */
+		/* until avail(delay) <= buffer_size */
+		frames = (*delay - runtime->buffer_size) + runtime->period_size - 1;
+		frames /= runtime->period_size;
+		frames *= runtime->period_size;
+		err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_FORWARD, &frames);
+		if (err < 0)
+			break;
+	}
+	return err;
+}
+
+snd_pcm_sframes_t snd_pcm_oss_write3(snd_pcm_substream_t *substream, const char *ptr, snd_pcm_uframes_t frames, int in_kernel)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int ret;
+	while (1) {
+		if (runtime->status->state == SNDRV_PCM_STATE_XRUN ||
+		    runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+#ifdef OSS_DEBUG
+			if (runtime->status->state == SNDRV_PCM_STATE_XRUN)
+				printk("pcm_oss: write: recovering from XRUN\n");
+			else
+				printk("pcm_oss: write: recovering from SUSPEND\n");
+#endif
+			ret = snd_pcm_oss_prepare(substream);
+			if (ret < 0)
+				break;
+		}
+		if (in_kernel) {
+			mm_segment_t fs;
+			fs = snd_enter_user();
+			ret = snd_pcm_lib_write(substream, (void __user *)ptr, frames);
+			snd_leave_user(fs);
+		} else {
+			ret = snd_pcm_lib_write(substream, (void __user *)ptr, frames);
+		}
+		if (ret != -EPIPE && ret != -ESTRPIPE)
+			break;
+		/* test, if we can't store new data, because the stream */
+		/* has not been started */
+		if (runtime->status->state == SNDRV_PCM_STATE_PREPARED)
+			return -EAGAIN;
+	}
+	return ret;
+}
+
+snd_pcm_sframes_t snd_pcm_oss_read3(snd_pcm_substream_t *substream, char *ptr, snd_pcm_uframes_t frames, int in_kernel)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_sframes_t delay;
+	int ret;
+	while (1) {
+		if (runtime->status->state == SNDRV_PCM_STATE_XRUN ||
+		    runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+#ifdef OSS_DEBUG
+			if (runtime->status->state == SNDRV_PCM_STATE_XRUN)
+				printk("pcm_oss: read: recovering from XRUN\n");
+			else
+				printk("pcm_oss: read: recovering from SUSPEND\n");
+#endif
+			ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL);
+			if (ret < 0)
+				break;
+		} else if (runtime->status->state == SNDRV_PCM_STATE_SETUP) {
+			ret = snd_pcm_oss_prepare(substream);
+			if (ret < 0)
+				break;
+		}
+		ret = snd_pcm_oss_capture_position_fixup(substream, &delay);
+		if (ret < 0)
+			break;
+		if (in_kernel) {
+			mm_segment_t fs;
+			fs = snd_enter_user();
+			ret = snd_pcm_lib_read(substream, (void __user *)ptr, frames);
+			snd_leave_user(fs);
+		} else {
+			ret = snd_pcm_lib_read(substream, (void __user *)ptr, frames);
+		}
+		if (ret == -EPIPE) {
+			if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) {
+				ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
+				if (ret < 0)
+					break;
+			}
+			continue;
+		}
+		if (ret != -ESTRPIPE)
+			break;
+	}
+	return ret;
+}
+
+snd_pcm_sframes_t snd_pcm_oss_writev3(snd_pcm_substream_t *substream, void **bufs, snd_pcm_uframes_t frames, int in_kernel)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int ret;
+	while (1) {
+		if (runtime->status->state == SNDRV_PCM_STATE_XRUN ||
+		    runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+#ifdef OSS_DEBUG
+			if (runtime->status->state == SNDRV_PCM_STATE_XRUN)
+				printk("pcm_oss: writev: recovering from XRUN\n");
+			else
+				printk("pcm_oss: writev: recovering from SUSPEND\n");
+#endif
+			ret = snd_pcm_oss_prepare(substream);
+			if (ret < 0)
+				break;
+		}
+		if (in_kernel) {
+			mm_segment_t fs;
+			fs = snd_enter_user();
+			ret = snd_pcm_lib_writev(substream, (void __user **)bufs, frames);
+			snd_leave_user(fs);
+		} else {
+			ret = snd_pcm_lib_writev(substream, (void __user **)bufs, frames);
+		}
+		if (ret != -EPIPE && ret != -ESTRPIPE)
+			break;
+
+		/* test, if we can't store new data, because the stream */
+		/* has not been started */
+		if (runtime->status->state == SNDRV_PCM_STATE_PREPARED)
+			return -EAGAIN;
+	}
+	return ret;
+}
+	
+snd_pcm_sframes_t snd_pcm_oss_readv3(snd_pcm_substream_t *substream, void **bufs, snd_pcm_uframes_t frames, int in_kernel)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int ret;
+	while (1) {
+		if (runtime->status->state == SNDRV_PCM_STATE_XRUN ||
+		    runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+#ifdef OSS_DEBUG
+			if (runtime->status->state == SNDRV_PCM_STATE_XRUN)
+				printk("pcm_oss: readv: recovering from XRUN\n");
+			else
+				printk("pcm_oss: readv: recovering from SUSPEND\n");
+#endif
+			ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL);
+			if (ret < 0)
+				break;
+		} else if (runtime->status->state == SNDRV_PCM_STATE_SETUP) {
+			ret = snd_pcm_oss_prepare(substream);
+			if (ret < 0)
+				break;
+		}
+		if (in_kernel) {
+			mm_segment_t fs;
+			fs = snd_enter_user();
+			ret = snd_pcm_lib_readv(substream, (void __user **)bufs, frames);
+			snd_leave_user(fs);
+		} else {
+			ret = snd_pcm_lib_readv(substream, (void __user **)bufs, frames);
+		}
+		if (ret != -EPIPE && ret != -ESTRPIPE)
+			break;
+	}
+	return ret;
+}
+
+static ssize_t snd_pcm_oss_write2(snd_pcm_substream_t *substream, const char *buf, size_t bytes, int in_kernel)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_sframes_t frames, frames1;
+	if (runtime->oss.plugin_first) {
+		snd_pcm_plugin_channel_t *channels;
+		size_t oss_frame_bytes = (runtime->oss.plugin_first->src_width * runtime->oss.plugin_first->src_format.channels) / 8;
+		if (!in_kernel) {
+			if (copy_from_user(runtime->oss.buffer, (const char __user *)buf, bytes))
+				return -EFAULT;
+			buf = runtime->oss.buffer;
+		}
+		frames = bytes / oss_frame_bytes;
+		frames1 = snd_pcm_plug_client_channels_buf(substream, (char *)buf, frames, &channels);
+		if (frames1 < 0)
+			return frames1;
+		frames1 = snd_pcm_plug_write_transfer(substream, channels, frames1);
+		if (frames1 <= 0)
+			return frames1;
+		bytes = frames1 * oss_frame_bytes;
+	} else {
+		frames = bytes_to_frames(runtime, bytes);
+		frames1 = snd_pcm_oss_write3(substream, buf, frames, in_kernel);
+		if (frames1 <= 0)
+			return frames1;
+		bytes = frames_to_bytes(runtime, frames1);
+	}
+	return bytes;
+}
+
+static ssize_t snd_pcm_oss_write1(snd_pcm_substream_t *substream, const char __user *buf, size_t bytes)
+{
+	size_t xfer = 0;
+	ssize_t tmp;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+
+	if (atomic_read(&runtime->mmap_count))
+		return -ENXIO;
+
+	if ((tmp = snd_pcm_oss_make_ready(substream)) < 0)
+		return tmp;
+	while (bytes > 0) {
+		if (bytes < runtime->oss.period_bytes || runtime->oss.buffer_used > 0) {
+			tmp = bytes;
+			if (tmp + runtime->oss.buffer_used > runtime->oss.period_bytes)
+				tmp = runtime->oss.period_bytes - runtime->oss.buffer_used;
+			if (tmp > 0) {
+				if (copy_from_user(runtime->oss.buffer + runtime->oss.buffer_used, buf, tmp))
+					return xfer > 0 ? (snd_pcm_sframes_t)xfer : -EFAULT;
+			}
+			runtime->oss.buffer_used += tmp;
+			buf += tmp;
+			bytes -= tmp;
+			xfer += tmp;
+			if ((substream->oss.setup != NULL && substream->oss.setup->partialfrag) ||
+			    runtime->oss.buffer_used == runtime->oss.period_bytes) {
+				tmp = snd_pcm_oss_write2(substream, runtime->oss.buffer + runtime->oss.period_ptr, 
+							 runtime->oss.buffer_used - runtime->oss.period_ptr, 1);
+				if (tmp <= 0)
+					return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
+				runtime->oss.bytes += tmp;
+				runtime->oss.period_ptr += tmp;
+				runtime->oss.period_ptr %= runtime->oss.period_bytes;
+				if (runtime->oss.period_ptr == 0 ||
+				    runtime->oss.period_ptr == runtime->oss.buffer_used)
+					runtime->oss.buffer_used = 0;
+				else if ((substream->ffile->f_flags & O_NONBLOCK) != 0)
+					return xfer > 0 ? xfer : -EAGAIN;
+			}
+		} else {
+			tmp = snd_pcm_oss_write2(substream, (const char *)buf, runtime->oss.period_bytes, 0);
+			if (tmp <= 0)
+				return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
+			runtime->oss.bytes += tmp;
+			buf += tmp;
+			bytes -= tmp;
+			xfer += tmp;
+			if ((substream->ffile->f_flags & O_NONBLOCK) != 0 &&
+			    tmp != runtime->oss.period_bytes)
+				break;
+		}
+	}
+	return xfer;
+}
+
+static ssize_t snd_pcm_oss_read2(snd_pcm_substream_t *substream, char *buf, size_t bytes, int in_kernel)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_sframes_t frames, frames1;
+	char __user *final_dst = (char __user *)buf;
+	if (runtime->oss.plugin_first) {
+		snd_pcm_plugin_channel_t *channels;
+		size_t oss_frame_bytes = (runtime->oss.plugin_last->dst_width * runtime->oss.plugin_last->dst_format.channels) / 8;
+		if (!in_kernel)
+			buf = runtime->oss.buffer;
+		frames = bytes / oss_frame_bytes;
+		frames1 = snd_pcm_plug_client_channels_buf(substream, buf, frames, &channels);
+		if (frames1 < 0)
+			return frames1;
+		frames1 = snd_pcm_plug_read_transfer(substream, channels, frames1);
+		if (frames1 <= 0)
+			return frames1;
+		bytes = frames1 * oss_frame_bytes;
+		if (!in_kernel && copy_to_user(final_dst, buf, bytes))
+			return -EFAULT;
+	} else {
+		frames = bytes_to_frames(runtime, bytes);
+		frames1 = snd_pcm_oss_read3(substream, buf, frames, in_kernel);
+		if (frames1 <= 0)
+			return frames1;
+		bytes = frames_to_bytes(runtime, frames1);
+	}
+	return bytes;
+}
+
+static ssize_t snd_pcm_oss_read1(snd_pcm_substream_t *substream, char __user *buf, size_t bytes)
+{
+	size_t xfer = 0;
+	ssize_t tmp;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+
+	if (atomic_read(&runtime->mmap_count))
+		return -ENXIO;
+
+	if ((tmp = snd_pcm_oss_make_ready(substream)) < 0)
+		return tmp;
+	while (bytes > 0) {
+		if (bytes < runtime->oss.period_bytes || runtime->oss.buffer_used > 0) {
+			if (runtime->oss.buffer_used == 0) {
+				tmp = snd_pcm_oss_read2(substream, runtime->oss.buffer, runtime->oss.period_bytes, 1);
+				if (tmp <= 0)
+					return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
+				runtime->oss.bytes += tmp;
+				runtime->oss.period_ptr = tmp;
+				runtime->oss.buffer_used = tmp;
+			}
+			tmp = bytes;
+			if ((size_t) tmp > runtime->oss.buffer_used)
+				tmp = runtime->oss.buffer_used;
+			if (copy_to_user(buf, runtime->oss.buffer + (runtime->oss.period_ptr - runtime->oss.buffer_used), tmp))
+				return xfer > 0 ? (snd_pcm_sframes_t)xfer : -EFAULT;
+			buf += tmp;
+			bytes -= tmp;
+			xfer += tmp;
+			runtime->oss.buffer_used -= tmp;
+		} else {
+			tmp = snd_pcm_oss_read2(substream, (char *)buf, runtime->oss.period_bytes, 0);
+			if (tmp <= 0)
+				return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
+			runtime->oss.bytes += tmp;
+			buf += tmp;
+			bytes -= tmp;
+			xfer += tmp;
+		}
+	}
+	return xfer;
+}
+
+static int snd_pcm_oss_reset(snd_pcm_oss_file_t *pcm_oss_file)
+{
+	snd_pcm_substream_t *substream;
+
+	substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+	if (substream != NULL) {
+		snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
+		substream->runtime->oss.prepare = 1;
+	}
+	substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+	if (substream != NULL) {
+		snd_pcm_kernel_capture_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
+		substream->runtime->oss.prepare = 1;
+	}
+	return 0;
+}
+
+static int snd_pcm_oss_post(snd_pcm_oss_file_t *pcm_oss_file)
+{
+	snd_pcm_substream_t *substream;
+	int err;
+
+	substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+	if (substream != NULL) {
+		if ((err = snd_pcm_oss_make_ready(substream)) < 0)
+			return err;
+		snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_START, NULL);
+	}
+	/* note: all errors from the start action are ignored */
+	/* OSS apps do not know, how to handle them */
+	return 0;
+}
+
+static int snd_pcm_oss_sync1(snd_pcm_substream_t *substream, size_t size)
+{
+	snd_pcm_runtime_t *runtime;
+	ssize_t result = 0;
+	long res;
+	wait_queue_t wait;
+
+	runtime = substream->runtime;
+	init_waitqueue_entry(&wait, current);
+	add_wait_queue(&runtime->sleep, &wait);
+#ifdef OSS_DEBUG
+	printk("sync1: size = %li\n", size);
+#endif
+	while (1) {
+		result = snd_pcm_oss_write2(substream, runtime->oss.buffer, size, 1);
+		if (result > 0) {
+			runtime->oss.buffer_used = 0;
+			result = 0;
+			break;
+		}
+		if (result != 0 && result != -EAGAIN)
+			break;
+		result = 0;
+		set_current_state(TASK_INTERRUPTIBLE);
+		snd_pcm_stream_lock_irq(substream);
+		res = runtime->status->state;
+		snd_pcm_stream_unlock_irq(substream);
+		if (res != SNDRV_PCM_STATE_RUNNING) {
+			set_current_state(TASK_RUNNING);
+			break;
+		}
+		res = schedule_timeout(10 * HZ);
+		if (signal_pending(current)) {
+			result = -ERESTARTSYS;
+			break;
+		}
+		if (res == 0) {
+			snd_printk(KERN_ERR "OSS sync error - DMA timeout\n");
+			result = -EIO;
+			break;
+		}
+	}
+	remove_wait_queue(&runtime->sleep, &wait);
+	return result;
+}
+
+static int snd_pcm_oss_sync(snd_pcm_oss_file_t *pcm_oss_file)
+{
+	int err = 0;
+	unsigned int saved_f_flags;
+	snd_pcm_substream_t *substream;
+	snd_pcm_runtime_t *runtime;
+	snd_pcm_format_t format;
+	unsigned long width;
+	size_t size;
+
+	substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+	if (substream != NULL) {
+		runtime = substream->runtime;
+		if (atomic_read(&runtime->mmap_count))
+			goto __direct;
+		if ((err = snd_pcm_oss_make_ready(substream)) < 0)
+			return err;
+		format = snd_pcm_oss_format_from(runtime->oss.format);
+		width = snd_pcm_format_physical_width(format);
+		if (runtime->oss.buffer_used > 0) {
+#ifdef OSS_DEBUG
+			printk("sync: buffer_used\n");
+#endif
+			size = (8 * (runtime->oss.period_bytes - runtime->oss.buffer_used) + 7) / width;
+			snd_pcm_format_set_silence(format,
+						   runtime->oss.buffer + runtime->oss.buffer_used,
+						   size);
+			err = snd_pcm_oss_sync1(substream, runtime->oss.period_bytes);
+			if (err < 0)
+				return err;
+		} else if (runtime->oss.period_ptr > 0) {
+#ifdef OSS_DEBUG
+			printk("sync: period_ptr\n");
+#endif
+			size = runtime->oss.period_bytes - runtime->oss.period_ptr;
+			snd_pcm_format_set_silence(format,
+						   runtime->oss.buffer,
+						   size * 8 / width);
+			err = snd_pcm_oss_sync1(substream, size);
+			if (err < 0)
+				return err;
+		}
+		/*
+		 * The ALSA's period might be a bit large than OSS one.
+		 * Fill the remain portion of ALSA period with zeros.
+		 */
+		size = runtime->control->appl_ptr % runtime->period_size;
+		if (size > 0) {
+			size = runtime->period_size - size;
+			if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
+				size = (runtime->frame_bits * size) / 8;
+				while (size > 0) {
+					mm_segment_t fs;
+					size_t size1 = size < runtime->oss.period_bytes ? size : runtime->oss.period_bytes;
+					size -= size1;
+					size1 *= 8;
+					size1 /= runtime->sample_bits;
+					snd_pcm_format_set_silence(runtime->format,
+								   runtime->oss.buffer,
+								   size1);
+					fs = snd_enter_user();
+					snd_pcm_lib_write(substream, (void __user *)runtime->oss.buffer, size1);
+					snd_leave_user(fs);
+				}
+			} else if (runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) {
+				void __user *buffers[runtime->channels];
+				memset(buffers, 0, runtime->channels * sizeof(void *));
+				snd_pcm_lib_writev(substream, buffers, size);
+			}
+		}
+		/*
+		 * finish sync: drain the buffer
+		 */
+	      __direct:
+		saved_f_flags = substream->ffile->f_flags;
+		substream->ffile->f_flags &= ~O_NONBLOCK;
+		err = snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL);
+		substream->ffile->f_flags = saved_f_flags;
+		if (err < 0)
+			return err;
+		runtime->oss.prepare = 1;
+	}
+
+	substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+	if (substream != NULL) {
+		if ((err = snd_pcm_oss_make_ready(substream)) < 0)
+			return err;
+		runtime = substream->runtime;
+		err = snd_pcm_kernel_capture_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
+		if (err < 0)
+			return err;
+		runtime->oss.buffer_used = 0;
+		runtime->oss.prepare = 1;
+	}
+	return 0;
+}
+
+static int snd_pcm_oss_set_rate(snd_pcm_oss_file_t *pcm_oss_file, int rate)
+{
+	int idx;
+
+	for (idx = 1; idx >= 0; --idx) {
+		snd_pcm_substream_t *substream = pcm_oss_file->streams[idx];
+		snd_pcm_runtime_t *runtime;
+		if (substream == NULL)
+			continue;
+		runtime = substream->runtime;
+		if (rate < 1000)
+			rate = 1000;
+		else if (rate > 192000)
+			rate = 192000;
+		if (runtime->oss.rate != rate) {
+			runtime->oss.params = 1;
+			runtime->oss.rate = rate;
+		}
+	}
+	return snd_pcm_oss_get_rate(pcm_oss_file);
+}
+
+static int snd_pcm_oss_get_rate(snd_pcm_oss_file_t *pcm_oss_file)
+{
+	snd_pcm_substream_t *substream;
+	int err;
+	
+	if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0)
+		return err;
+	return substream->runtime->oss.rate;
+}
+
+static int snd_pcm_oss_set_channels(snd_pcm_oss_file_t *pcm_oss_file, unsigned int channels)
+{
+	int idx;
+	if (channels < 1)
+		channels = 1;
+	if (channels > 128)
+		return -EINVAL;
+	for (idx = 1; idx >= 0; --idx) {
+		snd_pcm_substream_t *substream = pcm_oss_file->streams[idx];
+		snd_pcm_runtime_t *runtime;
+		if (substream == NULL)
+			continue;
+		runtime = substream->runtime;
+		if (runtime->oss.channels != channels) {
+			runtime->oss.params = 1;
+			runtime->oss.channels = channels;
+		}
+	}
+	return snd_pcm_oss_get_channels(pcm_oss_file);
+}
+
+static int snd_pcm_oss_get_channels(snd_pcm_oss_file_t *pcm_oss_file)
+{
+	snd_pcm_substream_t *substream;
+	int err;
+	
+	if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0)
+		return err;
+	return substream->runtime->oss.channels;
+}
+
+static int snd_pcm_oss_get_block_size(snd_pcm_oss_file_t *pcm_oss_file)
+{
+	snd_pcm_substream_t *substream;
+	int err;
+	
+	if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0)
+		return err;
+	return substream->runtime->oss.period_bytes;
+}
+
+static int snd_pcm_oss_get_formats(snd_pcm_oss_file_t *pcm_oss_file)
+{
+	snd_pcm_substream_t *substream;
+	int err;
+	int direct;
+	snd_pcm_hw_params_t *params;
+	unsigned int formats = 0;
+	snd_mask_t format_mask;
+	int fmt;
+
+	if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0)
+		return err;
+	if (atomic_read(&substream->runtime->mmap_count)) {
+		direct = 1;
+	} else {
+		snd_pcm_oss_setup_t *setup = substream->oss.setup;
+		direct = (setup != NULL && setup->direct);
+	}
+	if (!direct)
+		return AFMT_MU_LAW | AFMT_U8 |
+		       AFMT_S16_LE | AFMT_S16_BE |
+		       AFMT_S8 | AFMT_U16_LE |
+		       AFMT_U16_BE;
+	params = kmalloc(sizeof(*params), GFP_KERNEL);
+	if (!params)
+		return -ENOMEM;
+	_snd_pcm_hw_params_any(params);
+	err = snd_pcm_hw_refine(substream, params);
+	format_mask = *hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); 
+	kfree(params);
+	snd_assert(err >= 0, return err);
+	for (fmt = 0; fmt < 32; ++fmt) {
+		if (snd_mask_test(&format_mask, fmt)) {
+			int f = snd_pcm_oss_format_to(fmt);
+			if (f >= 0)
+				formats |= f;
+		}
+	}
+	return formats;
+}
+
+static int snd_pcm_oss_set_format(snd_pcm_oss_file_t *pcm_oss_file, int format)
+{
+	int formats, idx;
+	
+	if (format != AFMT_QUERY) {
+		formats = snd_pcm_oss_get_formats(pcm_oss_file);
+		if (!(formats & format))
+			format = AFMT_U8;
+		for (idx = 1; idx >= 0; --idx) {
+			snd_pcm_substream_t *substream = pcm_oss_file->streams[idx];
+			snd_pcm_runtime_t *runtime;
+			if (substream == NULL)
+				continue;
+			runtime = substream->runtime;
+			if (runtime->oss.format != format) {
+				runtime->oss.params = 1;
+				runtime->oss.format = format;
+			}
+		}
+	}
+	return snd_pcm_oss_get_format(pcm_oss_file);
+}
+
+static int snd_pcm_oss_get_format(snd_pcm_oss_file_t *pcm_oss_file)
+{
+	snd_pcm_substream_t *substream;
+	int err;
+	
+	if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0)
+		return err;
+	return substream->runtime->oss.format;
+}
+
+static int snd_pcm_oss_set_subdivide1(snd_pcm_substream_t *substream, int subdivide)
+{
+	snd_pcm_runtime_t *runtime;
+
+	if (substream == NULL)
+		return 0;
+	runtime = substream->runtime;
+	if (subdivide == 0) {
+		subdivide = runtime->oss.subdivision;
+		if (subdivide == 0)
+			subdivide = 1;
+		return subdivide;
+	}
+	if (runtime->oss.subdivision || runtime->oss.fragshift)
+		return -EINVAL;
+	if (subdivide != 1 && subdivide != 2 && subdivide != 4 &&
+	    subdivide != 8 && subdivide != 16)
+		return -EINVAL;
+	runtime->oss.subdivision = subdivide;
+	runtime->oss.params = 1;
+	return subdivide;
+}
+
+static int snd_pcm_oss_set_subdivide(snd_pcm_oss_file_t *pcm_oss_file, int subdivide)
+{
+	int err = -EINVAL, idx;
+
+	for (idx = 1; idx >= 0; --idx) {
+		snd_pcm_substream_t *substream = pcm_oss_file->streams[idx];
+		if (substream == NULL)
+			continue;
+		if ((err = snd_pcm_oss_set_subdivide1(substream, subdivide)) < 0)
+			return err;
+	}
+	return err;
+}
+
+static int snd_pcm_oss_set_fragment1(snd_pcm_substream_t *substream, unsigned int val)
+{
+	snd_pcm_runtime_t *runtime;
+
+	if (substream == NULL)
+		return 0;
+	runtime = substream->runtime;
+	if (runtime->oss.subdivision || runtime->oss.fragshift)
+		return -EINVAL;
+	runtime->oss.fragshift = val & 0xffff;
+	runtime->oss.maxfrags = (val >> 16) & 0xffff;
+	if (runtime->oss.fragshift < 4)		/* < 16 */
+		runtime->oss.fragshift = 4;
+	if (runtime->oss.maxfrags < 2)
+		runtime->oss.maxfrags = 2;
+	runtime->oss.params = 1;
+	return 0;
+}
+
+static int snd_pcm_oss_set_fragment(snd_pcm_oss_file_t *pcm_oss_file, unsigned int val)
+{
+	int err = -EINVAL, idx;
+
+	for (idx = 1; idx >= 0; --idx) {
+		snd_pcm_substream_t *substream = pcm_oss_file->streams[idx];
+		if (substream == NULL)
+			continue;
+		if ((err = snd_pcm_oss_set_fragment1(substream, val)) < 0)
+			return err;
+	}
+	return err;
+}
+
+static int snd_pcm_oss_nonblock(struct file * file)
+{
+	file->f_flags |= O_NONBLOCK;
+	return 0;
+}
+
+static int snd_pcm_oss_get_caps1(snd_pcm_substream_t *substream, int res)
+{
+
+	if (substream == NULL) {
+		res &= ~DSP_CAP_DUPLEX;
+		return res;
+	}
+#ifdef DSP_CAP_MULTI
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		if (substream->pstr->substream_count > 1)
+			res |= DSP_CAP_MULTI;
+#endif
+	/* DSP_CAP_REALTIME is set all times: */
+	/* all ALSA drivers can return actual pointer in ring buffer */
+#if defined(DSP_CAP_REALTIME) && 0
+	{
+		snd_pcm_runtime_t *runtime = substream->runtime;
+		if (runtime->info & (SNDRV_PCM_INFO_BLOCK_TRANSFER|SNDRV_PCM_INFO_BATCH))
+			res &= ~DSP_CAP_REALTIME;
+	}
+#endif
+	return res;
+}
+
+static int snd_pcm_oss_get_caps(snd_pcm_oss_file_t *pcm_oss_file)
+{
+	int result, idx;
+	
+	result = DSP_CAP_TRIGGER | DSP_CAP_MMAP	| DSP_CAP_DUPLEX | DSP_CAP_REALTIME;
+	for (idx = 0; idx < 2; idx++) {
+		snd_pcm_substream_t *substream = pcm_oss_file->streams[idx];
+		result = snd_pcm_oss_get_caps1(substream, result);
+	}
+	result |= 0x0001;	/* revision - same as SB AWE 64 */
+	return result;
+}
+
+static void snd_pcm_oss_simulate_fill(snd_pcm_substream_t *substream, snd_pcm_uframes_t hw_ptr)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_uframes_t appl_ptr;
+	appl_ptr = hw_ptr + runtime->buffer_size;
+	appl_ptr %= runtime->boundary;
+	runtime->control->appl_ptr = appl_ptr;
+}
+
+static int snd_pcm_oss_set_trigger(snd_pcm_oss_file_t *pcm_oss_file, int trigger)
+{
+	snd_pcm_runtime_t *runtime;
+	snd_pcm_substream_t *psubstream = NULL, *csubstream = NULL;
+	int err, cmd;
+
+#ifdef OSS_DEBUG
+	printk("pcm_oss: trigger = 0x%x\n", trigger);
+#endif
+	
+	psubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+	csubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+
+	if (psubstream) {
+		if ((err = snd_pcm_oss_make_ready(psubstream)) < 0)
+			return err;
+	}
+	if (csubstream) {
+		if ((err = snd_pcm_oss_make_ready(csubstream)) < 0)
+			return err;
+	}
+      	if (psubstream) {
+      		runtime = psubstream->runtime;
+		if (trigger & PCM_ENABLE_OUTPUT) {
+			if (runtime->oss.trigger)
+				goto _skip1;
+			if (atomic_read(&psubstream->runtime->mmap_count))
+				snd_pcm_oss_simulate_fill(psubstream, runtime->hw_ptr_interrupt);
+			runtime->oss.trigger = 1;
+			runtime->start_threshold = 1;
+			cmd = SNDRV_PCM_IOCTL_START;
+		} else {
+			if (!runtime->oss.trigger)
+				goto _skip1;
+			runtime->oss.trigger = 0;
+			runtime->start_threshold = runtime->boundary;
+			cmd = SNDRV_PCM_IOCTL_DROP;
+			runtime->oss.prepare = 1;
+		}
+		err = snd_pcm_kernel_playback_ioctl(psubstream, cmd, NULL);
+		if (err < 0)
+			return err;
+	}
+ _skip1:
+	if (csubstream) {
+      		runtime = csubstream->runtime;
+		if (trigger & PCM_ENABLE_INPUT) {
+			if (runtime->oss.trigger)
+				goto _skip2;
+			runtime->oss.trigger = 1;
+			runtime->start_threshold = 1;
+			cmd = SNDRV_PCM_IOCTL_START;
+		} else {
+			if (!runtime->oss.trigger)
+				goto _skip2;
+			runtime->oss.trigger = 0;
+			runtime->start_threshold = runtime->boundary;
+			cmd = SNDRV_PCM_IOCTL_DROP;
+			runtime->oss.prepare = 1;
+		}
+		err = snd_pcm_kernel_capture_ioctl(csubstream, cmd, NULL);
+		if (err < 0)
+			return err;
+	}
+ _skip2:
+	return 0;
+}
+
+static int snd_pcm_oss_get_trigger(snd_pcm_oss_file_t *pcm_oss_file)
+{
+	snd_pcm_substream_t *psubstream = NULL, *csubstream = NULL;
+	int result = 0;
+
+	psubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+	csubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+	if (psubstream && psubstream->runtime && psubstream->runtime->oss.trigger)
+		result |= PCM_ENABLE_OUTPUT;
+	if (csubstream && csubstream->runtime && csubstream->runtime->oss.trigger)
+		result |= PCM_ENABLE_INPUT;
+	return result;
+}
+
+static int snd_pcm_oss_get_odelay(snd_pcm_oss_file_t *pcm_oss_file)
+{
+	snd_pcm_substream_t *substream;
+	snd_pcm_runtime_t *runtime;
+	snd_pcm_sframes_t delay;
+	int err;
+
+	substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+	if (substream == NULL)
+		return -EINVAL;
+	if ((err = snd_pcm_oss_make_ready(substream)) < 0)
+		return err;
+	runtime = substream->runtime;
+	if (runtime->oss.params || runtime->oss.prepare)
+		return 0;
+	err = snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &delay);
+	if (err == -EPIPE)
+		delay = 0;	/* hack for broken OSS applications */
+	else if (err < 0)
+		return err;
+	return snd_pcm_oss_bytes(substream, delay);
+}
+
+static int snd_pcm_oss_get_ptr(snd_pcm_oss_file_t *pcm_oss_file, int stream, struct count_info __user * _info)
+{	
+	snd_pcm_substream_t *substream;
+	snd_pcm_runtime_t *runtime;
+	snd_pcm_sframes_t delay;
+	int fixup;
+	struct count_info info;
+	int err;
+
+	if (_info == NULL)
+		return -EFAULT;
+	substream = pcm_oss_file->streams[stream];
+	if (substream == NULL)
+		return -EINVAL;
+	if ((err = snd_pcm_oss_make_ready(substream)) < 0)
+		return err;
+	runtime = substream->runtime;
+	if (runtime->oss.params || runtime->oss.prepare) {
+		memset(&info, 0, sizeof(info));
+		if (copy_to_user(_info, &info, sizeof(info)))
+			return -EFAULT;
+		return 0;
+	}
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &delay);
+		if (err == -EPIPE || err == -ESTRPIPE || (! err && delay < 0)) {
+			err = 0;
+			delay = 0;
+			fixup = 0;
+		} else {
+			fixup = runtime->oss.buffer_used;
+		}
+	} else {
+		err = snd_pcm_oss_capture_position_fixup(substream, &delay);
+		fixup = -runtime->oss.buffer_used;
+	}
+	if (err < 0)
+		return err;
+	info.ptr = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr % runtime->buffer_size);
+	if (atomic_read(&runtime->mmap_count)) {
+		snd_pcm_sframes_t n;
+		n = (delay = runtime->hw_ptr_interrupt) - runtime->oss.prev_hw_ptr_interrupt;
+		if (n < 0)
+			n += runtime->boundary;
+		info.blocks = n / runtime->period_size;
+		runtime->oss.prev_hw_ptr_interrupt = delay;
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			snd_pcm_oss_simulate_fill(substream, delay);
+		info.bytes = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr) & INT_MAX;
+	} else {
+		delay = snd_pcm_oss_bytes(substream, delay) + fixup;
+		info.blocks = delay / runtime->oss.period_bytes;
+		if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+			info.bytes = (runtime->oss.bytes - delay) & INT_MAX;
+		else
+			info.bytes = (runtime->oss.bytes + delay) & INT_MAX;
+	}
+	if (copy_to_user(_info, &info, sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+static int snd_pcm_oss_get_space(snd_pcm_oss_file_t *pcm_oss_file, int stream, struct audio_buf_info __user *_info)
+{
+	snd_pcm_substream_t *substream;
+	snd_pcm_runtime_t *runtime;
+	snd_pcm_sframes_t avail;
+	int fixup;
+	struct audio_buf_info info;
+	int err;
+
+	if (_info == NULL)
+		return -EFAULT;
+	substream = pcm_oss_file->streams[stream];
+	if (substream == NULL)
+		return -EINVAL;
+	runtime = substream->runtime;
+
+	if (runtime->oss.params &&
+	    (err = snd_pcm_oss_change_params(substream)) < 0)
+		return err;
+
+	info.fragsize = runtime->oss.period_bytes;
+	info.fragstotal = runtime->periods;
+	if (runtime->oss.prepare) {
+		if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			info.bytes = runtime->oss.period_bytes * runtime->oss.periods;
+			info.fragments = runtime->oss.periods;
+		} else {
+			info.bytes = 0;
+			info.fragments = 0;
+		}
+	} else {
+		if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &avail);
+			if (err == -EPIPE || err == -ESTRPIPE || (! err && avail < 0)) {
+				avail = runtime->buffer_size;
+				err = 0;
+				fixup = 0;
+			} else {
+				avail = runtime->buffer_size - avail;
+				fixup = -runtime->oss.buffer_used;
+			}
+		} else {
+			err = snd_pcm_oss_capture_position_fixup(substream, &avail);
+			fixup = runtime->oss.buffer_used;
+		}
+		if (err < 0)
+			return err;
+		info.bytes = snd_pcm_oss_bytes(substream, avail) + fixup;
+		info.fragments = info.bytes / runtime->oss.period_bytes;
+	}
+
+#ifdef OSS_DEBUG
+	printk("pcm_oss: space: bytes = %i, fragments = %i, fragstotal = %i, fragsize = %i\n", info.bytes, info.fragments, info.fragstotal, info.fragsize);
+#endif
+	if (copy_to_user(_info, &info, sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+static int snd_pcm_oss_get_mapbuf(snd_pcm_oss_file_t *pcm_oss_file, int stream, struct buffmem_desc __user * _info)
+{
+	// it won't be probably implemented
+	// snd_printd("TODO: snd_pcm_oss_get_mapbuf\n");
+	return -EINVAL;
+}
+
+static snd_pcm_oss_setup_t *snd_pcm_oss_look_for_setup(snd_pcm_t *pcm, int stream, const char *task_name)
+{
+	const char *ptr, *ptrl;
+	snd_pcm_oss_setup_t *setup;
+
+	down(&pcm->streams[stream].oss.setup_mutex);
+	for (setup = pcm->streams[stream].oss.setup_list; setup; setup = setup->next) {
+		if (!strcmp(setup->task_name, task_name)) {
+			up(&pcm->streams[stream].oss.setup_mutex);
+			return setup;
+		}
+	}
+	ptr = ptrl = task_name;
+	while (*ptr) {
+		if (*ptr == '/')
+			ptrl = ptr + 1;
+		ptr++;
+	}
+	if (ptrl == task_name) {
+		goto __not_found;
+		return NULL;
+	}
+	for (setup = pcm->streams[stream].oss.setup_list; setup; setup = setup->next) {
+		if (!strcmp(setup->task_name, ptrl)) {
+			up(&pcm->streams[stream].oss.setup_mutex);
+			return setup;
+		}
+	}
+      __not_found:
+	up(&pcm->streams[stream].oss.setup_mutex);
+	return NULL;
+}
+
+static void snd_pcm_oss_init_substream(snd_pcm_substream_t *substream,
+				       snd_pcm_oss_setup_t *setup,
+				       int minor)
+{
+	snd_pcm_runtime_t *runtime;
+
+	substream->oss.oss = 1;
+	substream->oss.setup = setup;
+	runtime = substream->runtime;
+	runtime->oss.params = 1;
+	runtime->oss.trigger = 1;
+	runtime->oss.rate = 8000;
+	switch (SNDRV_MINOR_OSS_DEVICE(minor)) {
+	case SNDRV_MINOR_OSS_PCM_8:
+		runtime->oss.format = AFMT_U8;
+		break;
+	case SNDRV_MINOR_OSS_PCM_16:
+		runtime->oss.format = AFMT_S16_LE;
+		break;
+	default:
+		runtime->oss.format = AFMT_MU_LAW;
+	}
+	runtime->oss.channels = 1;
+	runtime->oss.fragshift = 0;
+	runtime->oss.maxfrags = 0;
+	runtime->oss.subdivision = 0;
+}
+
+static void snd_pcm_oss_release_substream(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime;
+	runtime = substream->runtime;
+	vfree(runtime->oss.buffer);
+	snd_pcm_oss_plugin_clear(substream);
+	substream->oss.file = NULL;
+	substream->oss.oss = 0;
+}
+
+static int snd_pcm_oss_release_file(snd_pcm_oss_file_t *pcm_oss_file)
+{
+	int cidx;
+	snd_assert(pcm_oss_file != NULL, return -ENXIO);
+	for (cidx = 0; cidx < 2; ++cidx) {
+		snd_pcm_substream_t *substream = pcm_oss_file->streams[cidx];
+		snd_pcm_runtime_t *runtime;
+		if (substream == NULL)
+			continue;
+		runtime = substream->runtime;
+		
+		snd_pcm_stream_lock_irq(substream);
+		if (snd_pcm_running(substream))
+			snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
+		snd_pcm_stream_unlock_irq(substream);
+		if (substream->open_flag) {
+			if (substream->ops->hw_free != NULL)
+				substream->ops->hw_free(substream);
+			substream->ops->close(substream);
+			substream->open_flag = 0;
+		}
+		substream->ffile = NULL;
+		snd_pcm_oss_release_substream(substream);
+		snd_pcm_release_substream(substream);
+	}
+	kfree(pcm_oss_file);
+	return 0;
+}
+
+static int snd_pcm_oss_open_file(struct file *file,
+				 snd_pcm_t *pcm,
+				 snd_pcm_oss_file_t **rpcm_oss_file,
+				 int minor,
+				 snd_pcm_oss_setup_t *psetup,
+				 snd_pcm_oss_setup_t *csetup)
+{
+	int err = 0;
+	snd_pcm_oss_file_t *pcm_oss_file;
+	snd_pcm_substream_t *psubstream = NULL, *csubstream = NULL;
+	unsigned int f_mode = file->f_mode;
+
+	snd_assert(rpcm_oss_file != NULL, return -EINVAL);
+	*rpcm_oss_file = NULL;
+
+	pcm_oss_file = kcalloc(1, sizeof(*pcm_oss_file), GFP_KERNEL);
+	if (pcm_oss_file == NULL)
+		return -ENOMEM;
+
+	if ((f_mode & (FMODE_WRITE|FMODE_READ)) == (FMODE_WRITE|FMODE_READ) &&
+	    (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX))
+		f_mode = FMODE_WRITE;
+	if ((f_mode & FMODE_WRITE) && !(psetup && psetup->disable)) {
+		if ((err = snd_pcm_open_substream(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+					       &psubstream)) < 0) {
+			snd_pcm_oss_release_file(pcm_oss_file);
+			return err;
+		}
+		pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK] = psubstream;
+	}
+	if ((f_mode & FMODE_READ) && !(csetup && csetup->disable)) {
+		if ((err = snd_pcm_open_substream(pcm, SNDRV_PCM_STREAM_CAPTURE, 
+					       &csubstream)) < 0) {
+			if (!(f_mode & FMODE_WRITE) || err != -ENODEV) {
+				snd_pcm_oss_release_file(pcm_oss_file);
+				return err;
+			} else {
+				csubstream = NULL;
+			}
+		}
+		pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE] = csubstream;
+	}
+	
+	if (psubstream == NULL && csubstream == NULL) {
+		snd_pcm_oss_release_file(pcm_oss_file);
+		return -EINVAL;
+	}
+	if (psubstream != NULL) {
+		psubstream->oss.file = pcm_oss_file;
+		err = snd_pcm_hw_constraints_init(psubstream);
+		if (err < 0) {
+			snd_printd("snd_pcm_hw_constraint_init failed\n");
+			snd_pcm_oss_release_file(pcm_oss_file);
+			return err;
+		}
+		if ((err = psubstream->ops->open(psubstream)) < 0) {
+			snd_pcm_oss_release_file(pcm_oss_file);
+			return err;
+		}
+		psubstream->open_flag = 1;
+		err = snd_pcm_hw_constraints_complete(psubstream);
+		if (err < 0) {
+			snd_printd("snd_pcm_hw_constraint_complete failed\n");
+			snd_pcm_oss_release_file(pcm_oss_file);
+			return err;
+		}
+		psubstream->ffile = file;
+		snd_pcm_oss_init_substream(psubstream, psetup, minor);
+	}
+	if (csubstream != NULL) {
+		csubstream->oss.file = pcm_oss_file;
+		err = snd_pcm_hw_constraints_init(csubstream);
+		if (err < 0) {
+			snd_printd("snd_pcm_hw_constraint_init failed\n");
+			snd_pcm_oss_release_file(pcm_oss_file);
+			return err;
+		}
+		if ((err = csubstream->ops->open(csubstream)) < 0) {
+			snd_pcm_oss_release_file(pcm_oss_file);
+			return err;
+		}
+		csubstream->open_flag = 1;
+		err = snd_pcm_hw_constraints_complete(csubstream);
+		if (err < 0) {
+			snd_printd("snd_pcm_hw_constraint_complete failed\n");
+			snd_pcm_oss_release_file(pcm_oss_file);
+			return err;
+		}
+		csubstream->ffile = file;
+		snd_pcm_oss_init_substream(csubstream, csetup, minor);
+	}
+
+	file->private_data = pcm_oss_file;
+	*rpcm_oss_file = pcm_oss_file;
+	return 0;
+}
+
+
+static int snd_pcm_oss_open(struct inode *inode, struct file *file)
+{
+	int minor = iminor(inode);
+	int cardnum = SNDRV_MINOR_OSS_CARD(minor);
+	int device;
+	int err;
+	char task_name[32];
+	snd_pcm_t *pcm;
+	snd_pcm_oss_file_t *pcm_oss_file;
+	snd_pcm_oss_setup_t *psetup = NULL, *csetup = NULL;
+	int nonblock;
+	wait_queue_t wait;
+
+	snd_assert(cardnum >= 0 && cardnum < SNDRV_CARDS, return -ENXIO);
+	device = SNDRV_MINOR_OSS_DEVICE(minor) == SNDRV_MINOR_OSS_PCM1 ?
+		adsp_map[cardnum] : dsp_map[cardnum];
+
+	pcm = snd_pcm_devices[(cardnum * SNDRV_PCM_DEVICES) + device];
+	if (pcm == NULL) {
+		err = -ENODEV;
+		goto __error1;
+	}
+	err = snd_card_file_add(pcm->card, file);
+	if (err < 0)
+		goto __error1;
+	if (!try_module_get(pcm->card->module)) {
+		err = -EFAULT;
+		goto __error2;
+	}
+	if (snd_task_name(current, task_name, sizeof(task_name)) < 0) {
+		err = -EFAULT;
+		goto __error;
+	}
+	if (file->f_mode & FMODE_WRITE)
+		psetup = snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_PLAYBACK, task_name);
+	if (file->f_mode & FMODE_READ)
+		csetup = snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_CAPTURE, task_name);
+
+	nonblock = !!(file->f_flags & O_NONBLOCK);
+	if (psetup && !psetup->disable) {
+		if (psetup->nonblock)
+			nonblock = 1;
+		else if (psetup->block)
+			nonblock = 0;
+	} else if (csetup && !csetup->disable) {
+		if (csetup->nonblock)
+			nonblock = 1;
+		else if (csetup->block)
+			nonblock = 0;
+	}
+	if (!nonblock)
+		nonblock = nonblock_open;
+
+	init_waitqueue_entry(&wait, current);
+	add_wait_queue(&pcm->open_wait, &wait);
+	down(&pcm->open_mutex);
+	while (1) {
+		err = snd_pcm_oss_open_file(file, pcm, &pcm_oss_file,
+					    minor, psetup, csetup);
+		if (err >= 0)
+			break;
+		if (err == -EAGAIN) {
+			if (nonblock) {
+				err = -EBUSY;
+				break;
+			}
+		} else
+			break;
+		set_current_state(TASK_INTERRUPTIBLE);
+		up(&pcm->open_mutex);
+		schedule();
+		down(&pcm->open_mutex);
+		if (signal_pending(current)) {
+			err = -ERESTARTSYS;
+			break;
+		}
+	}
+	remove_wait_queue(&pcm->open_wait, &wait);
+	up(&pcm->open_mutex);
+	if (err < 0)
+		goto __error;
+	return err;
+
+      __error:
+     	module_put(pcm->card->module);
+      __error2:
+      	snd_card_file_remove(pcm->card, file);
+      __error1:
+	return err;
+}
+
+static int snd_pcm_oss_release(struct inode *inode, struct file *file)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_substream_t *substream;
+	snd_pcm_oss_file_t *pcm_oss_file;
+
+	pcm_oss_file = file->private_data;
+	substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+	if (substream == NULL)
+		substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+	snd_assert(substream != NULL, return -ENXIO);
+	pcm = substream->pcm;
+	snd_pcm_oss_sync(pcm_oss_file);
+	down(&pcm->open_mutex);
+	snd_pcm_oss_release_file(pcm_oss_file);
+	up(&pcm->open_mutex);
+	wake_up(&pcm->open_wait);
+	module_put(pcm->card->module);
+	snd_card_file_remove(pcm->card, file);
+	return 0;
+}
+
+static long snd_pcm_oss_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	snd_pcm_oss_file_t *pcm_oss_file;
+	int __user *p = (int __user *)arg;
+	int res;
+
+	pcm_oss_file = file->private_data;
+	if (cmd == OSS_GETVERSION)
+		return put_user(SNDRV_OSS_VERSION, p);
+	if (cmd == OSS_ALSAEMULVER)
+		return put_user(1, p);
+#if defined(CONFIG_SND_MIXER_OSS) || (defined(MODULE) && defined(CONFIG_SND_MIXER_OSS_MODULE))
+	if (((cmd >> 8) & 0xff) == 'M')	{	/* mixer ioctl - for OSS compatibility */
+		snd_pcm_substream_t *substream;
+		int idx;
+		for (idx = 0; idx < 2; ++idx) {
+			substream = pcm_oss_file->streams[idx];
+			if (substream != NULL)
+				break;
+		}
+		snd_assert(substream != NULL, return -ENXIO);
+		return snd_mixer_oss_ioctl_card(substream->pcm->card, cmd, arg);
+	}
+#endif
+	if (((cmd >> 8) & 0xff) != 'P')
+		return -EINVAL;
+#ifdef OSS_DEBUG
+	printk("pcm_oss: ioctl = 0x%x\n", cmd);
+#endif
+	switch (cmd) {
+	case SNDCTL_DSP_RESET:
+		return snd_pcm_oss_reset(pcm_oss_file);
+	case SNDCTL_DSP_SYNC:
+		return snd_pcm_oss_sync(pcm_oss_file);
+	case SNDCTL_DSP_SPEED:
+		if (get_user(res, p))
+			return -EFAULT;
+		if ((res = snd_pcm_oss_set_rate(pcm_oss_file, res))<0)
+			return res;
+		return put_user(res, p);
+	case SOUND_PCM_READ_RATE:
+		res = snd_pcm_oss_get_rate(pcm_oss_file);
+		if (res < 0)
+			return res;
+		return put_user(res, p);
+	case SNDCTL_DSP_STEREO:
+		if (get_user(res, p))
+			return -EFAULT;
+		res = res > 0 ? 2 : 1;
+		if ((res = snd_pcm_oss_set_channels(pcm_oss_file, res)) < 0)
+			return res;
+		return put_user(--res, p);
+	case SNDCTL_DSP_GETBLKSIZE:
+		res = snd_pcm_oss_get_block_size(pcm_oss_file);
+		if (res < 0)
+			return res;
+		return put_user(res, p);
+	case SNDCTL_DSP_SETFMT:
+		if (get_user(res, p))
+			return -EFAULT;
+		res = snd_pcm_oss_set_format(pcm_oss_file, res);
+		if (res < 0)
+			return res;
+		return put_user(res, p);
+	case SOUND_PCM_READ_BITS:
+		res = snd_pcm_oss_get_format(pcm_oss_file);
+		if (res < 0)
+			return res;
+		return put_user(res, p);
+	case SNDCTL_DSP_CHANNELS:
+		if (get_user(res, p))
+			return -EFAULT;
+		res = snd_pcm_oss_set_channels(pcm_oss_file, res);
+		if (res < 0)
+			return res;
+		return put_user(res, p);
+	case SOUND_PCM_READ_CHANNELS:
+		res = snd_pcm_oss_get_channels(pcm_oss_file);
+		if (res < 0)
+			return res;
+		return put_user(res, p);
+	case SOUND_PCM_WRITE_FILTER:
+	case SOUND_PCM_READ_FILTER:
+		return -EIO;
+	case SNDCTL_DSP_POST:
+		return snd_pcm_oss_post(pcm_oss_file);
+	case SNDCTL_DSP_SUBDIVIDE:
+		if (get_user(res, p))
+			return -EFAULT;
+		res = snd_pcm_oss_set_subdivide(pcm_oss_file, res);
+		if (res < 0)
+			return res;
+		return put_user(res, p);
+	case SNDCTL_DSP_SETFRAGMENT:
+		if (get_user(res, p))
+			return -EFAULT;
+		return snd_pcm_oss_set_fragment(pcm_oss_file, res);
+	case SNDCTL_DSP_GETFMTS:
+		res = snd_pcm_oss_get_formats(pcm_oss_file);
+		if (res < 0)
+			return res;
+		return put_user(res, p);
+	case SNDCTL_DSP_GETOSPACE:
+	case SNDCTL_DSP_GETISPACE:
+		return snd_pcm_oss_get_space(pcm_oss_file,
+			cmd == SNDCTL_DSP_GETISPACE ?
+				SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK,
+			(struct audio_buf_info __user *) arg);
+	case SNDCTL_DSP_NONBLOCK:
+		return snd_pcm_oss_nonblock(file);
+	case SNDCTL_DSP_GETCAPS:
+		res = snd_pcm_oss_get_caps(pcm_oss_file);
+		if (res < 0)
+			return res;
+		return put_user(res, p);
+	case SNDCTL_DSP_GETTRIGGER:
+		res = snd_pcm_oss_get_trigger(pcm_oss_file);
+		if (res < 0)
+			return res;
+		return put_user(res, p);
+	case SNDCTL_DSP_SETTRIGGER:
+		if (get_user(res, p))
+			return -EFAULT;
+		return snd_pcm_oss_set_trigger(pcm_oss_file, res);
+	case SNDCTL_DSP_GETIPTR:
+	case SNDCTL_DSP_GETOPTR:
+		return snd_pcm_oss_get_ptr(pcm_oss_file,
+			cmd == SNDCTL_DSP_GETIPTR ?
+				SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK,
+			(struct count_info __user *) arg);
+	case SNDCTL_DSP_MAPINBUF:
+	case SNDCTL_DSP_MAPOUTBUF:
+		return snd_pcm_oss_get_mapbuf(pcm_oss_file,
+			cmd == SNDCTL_DSP_MAPINBUF ?
+				SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK,
+			(struct buffmem_desc __user *) arg);
+	case SNDCTL_DSP_SETSYNCRO:
+		/* stop DMA now.. */
+		return 0;
+	case SNDCTL_DSP_SETDUPLEX:
+		if (snd_pcm_oss_get_caps(pcm_oss_file) & DSP_CAP_DUPLEX)
+			return 0;
+		return -EIO;
+	case SNDCTL_DSP_GETODELAY:
+		res = snd_pcm_oss_get_odelay(pcm_oss_file);
+		if (res < 0) {
+			/* it's for sure, some broken apps don't check for error codes */
+			put_user(0, p);
+			return res;
+		}
+		return put_user(res, p);
+	case SNDCTL_DSP_PROFILE:
+		return 0;	/* silently ignore */
+	default:
+		snd_printd("pcm_oss: unknown command = 0x%x\n", cmd);
+	}
+	return -EINVAL;
+}
+
+#ifdef CONFIG_COMPAT
+/* all compatible */
+#define snd_pcm_oss_ioctl_compat	snd_pcm_oss_ioctl
+#else
+#define snd_pcm_oss_ioctl_compat	NULL
+#endif
+
+static ssize_t snd_pcm_oss_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
+{
+	snd_pcm_oss_file_t *pcm_oss_file;
+	snd_pcm_substream_t *substream;
+
+	pcm_oss_file = file->private_data;
+	substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+	if (substream == NULL)
+		return -ENXIO;
+#ifndef OSS_DEBUG
+	return snd_pcm_oss_read1(substream, buf, count);
+#else
+	{
+		ssize_t res = snd_pcm_oss_read1(substream, buf, count);
+		printk("pcm_oss: read %li bytes (returned %li bytes)\n", (long)count, (long)res);
+		return res;
+	}
+#endif
+}
+
+static ssize_t snd_pcm_oss_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
+{
+	snd_pcm_oss_file_t *pcm_oss_file;
+	snd_pcm_substream_t *substream;
+	long result;
+
+	pcm_oss_file = file->private_data;
+	substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+	if (substream == NULL)
+		return -ENXIO;
+	up(&file->f_dentry->d_inode->i_sem);
+	result = snd_pcm_oss_write1(substream, buf, count);
+	down(&file->f_dentry->d_inode->i_sem);
+#ifdef OSS_DEBUG
+	printk("pcm_oss: write %li bytes (wrote %li bytes)\n", (long)count, (long)result);
+#endif
+	return result;
+}
+
+static int snd_pcm_oss_playback_ready(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	if (atomic_read(&runtime->mmap_count))
+		return runtime->oss.prev_hw_ptr_interrupt != runtime->hw_ptr_interrupt;
+	else
+		return snd_pcm_playback_avail(runtime) >= runtime->oss.period_frames;
+}
+
+static int snd_pcm_oss_capture_ready(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	if (atomic_read(&runtime->mmap_count))
+		return runtime->oss.prev_hw_ptr_interrupt != runtime->hw_ptr_interrupt;
+	else
+		return snd_pcm_capture_avail(runtime) >= runtime->oss.period_frames;
+}
+
+static unsigned int snd_pcm_oss_poll(struct file *file, poll_table * wait)
+{
+	snd_pcm_oss_file_t *pcm_oss_file;
+	unsigned int mask;
+	snd_pcm_substream_t *psubstream = NULL, *csubstream = NULL;
+	
+	pcm_oss_file = file->private_data;
+
+	psubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+	csubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+
+	mask = 0;
+	if (psubstream != NULL) {
+		snd_pcm_runtime_t *runtime = psubstream->runtime;
+		poll_wait(file, &runtime->sleep, wait);
+		snd_pcm_stream_lock_irq(psubstream);
+		if (runtime->status->state != SNDRV_PCM_STATE_DRAINING &&
+		    (runtime->status->state != SNDRV_PCM_STATE_RUNNING ||
+		     snd_pcm_oss_playback_ready(psubstream)))
+			mask |= POLLOUT | POLLWRNORM;
+		snd_pcm_stream_unlock_irq(psubstream);
+	}
+	if (csubstream != NULL) {
+		snd_pcm_runtime_t *runtime = csubstream->runtime;
+		enum sndrv_pcm_state ostate;
+		poll_wait(file, &runtime->sleep, wait);
+		snd_pcm_stream_lock_irq(csubstream);
+		if ((ostate = runtime->status->state) != SNDRV_PCM_STATE_RUNNING ||
+		    snd_pcm_oss_capture_ready(csubstream))
+			mask |= POLLIN | POLLRDNORM;
+		snd_pcm_stream_unlock_irq(csubstream);
+		if (ostate != SNDRV_PCM_STATE_RUNNING && runtime->oss.trigger) {
+			snd_pcm_oss_file_t ofile;
+			memset(&ofile, 0, sizeof(ofile));
+			ofile.streams[SNDRV_PCM_STREAM_CAPTURE] = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+			runtime->oss.trigger = 0;
+			snd_pcm_oss_set_trigger(&ofile, PCM_ENABLE_INPUT);
+		}
+	}
+
+	return mask;
+}
+
+static int snd_pcm_oss_mmap(struct file *file, struct vm_area_struct *area)
+{
+	snd_pcm_oss_file_t *pcm_oss_file;
+	snd_pcm_substream_t *substream = NULL;
+	snd_pcm_runtime_t *runtime;
+	int err;
+
+#ifdef OSS_DEBUG
+	printk("pcm_oss: mmap begin\n");
+#endif
+	pcm_oss_file = file->private_data;
+	switch ((area->vm_flags & (VM_READ | VM_WRITE))) {
+	case VM_READ | VM_WRITE:
+		substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+		if (substream)
+			break;
+		/* Fall through */
+	case VM_READ:
+		substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+		break;
+	case VM_WRITE:
+		substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+		break;
+	default:
+		return -EINVAL;
+	}
+	/* set VM_READ access as well to fix memset() routines that do
+	   reads before writes (to improve performance) */
+	area->vm_flags |= VM_READ;
+	if (substream == NULL)
+		return -ENXIO;
+	runtime = substream->runtime;
+	if (!(runtime->info & SNDRV_PCM_INFO_MMAP_VALID))
+		return -EIO;
+	if (runtime->info & SNDRV_PCM_INFO_INTERLEAVED)
+		runtime->access = SNDRV_PCM_ACCESS_MMAP_INTERLEAVED;
+	else
+		return -EIO;
+	
+	if (runtime->oss.params) {
+		if ((err = snd_pcm_oss_change_params(substream)) < 0)
+			return err;
+	}
+	if (runtime->oss.plugin_first != NULL)
+		return -EIO;
+
+	if (area->vm_pgoff != 0)
+		return -EINVAL;
+
+	err = snd_pcm_mmap_data(substream, file, area);
+	if (err < 0)
+		return err;
+	runtime->oss.mmap_bytes = area->vm_end - area->vm_start;
+	runtime->silence_threshold = 0;
+	runtime->silence_size = 0;
+#ifdef OSS_DEBUG
+	printk("pcm_oss: mmap ok, bytes = 0x%x\n", runtime->oss.mmap_bytes);
+#endif
+	/* In mmap mode we never stop */
+	runtime->stop_threshold = runtime->boundary;
+
+	return 0;
+}
+
+/*
+ *  /proc interface
+ */
+
+static void snd_pcm_oss_proc_read(snd_info_entry_t *entry,
+				  snd_info_buffer_t * buffer)
+{
+	snd_pcm_str_t *pstr = (snd_pcm_str_t *)entry->private_data;
+	snd_pcm_oss_setup_t *setup = pstr->oss.setup_list;
+	down(&pstr->oss.setup_mutex);
+	while (setup) {
+		snd_iprintf(buffer, "%s %u %u%s%s%s%s%s%s\n",
+			    setup->task_name,
+			    setup->periods,
+			    setup->period_size,
+			    setup->disable ? " disable" : "",
+			    setup->direct ? " direct" : "",
+			    setup->block ? " block" : "",
+			    setup->nonblock ? " non-block" : "",
+			    setup->partialfrag ? " partial-frag" : "",
+			    setup->nosilence ? " no-silence" : "");
+		setup = setup->next;
+	}
+	up(&pstr->oss.setup_mutex);
+}
+
+static void snd_pcm_oss_proc_free_setup_list(snd_pcm_str_t * pstr)
+{
+	unsigned int idx;
+	snd_pcm_substream_t *substream;
+	snd_pcm_oss_setup_t *setup, *setupn;
+
+	for (idx = 0, substream = pstr->substream;
+	     idx < pstr->substream_count; idx++, substream = substream->next)
+		substream->oss.setup = NULL;
+	for (setup = pstr->oss.setup_list, pstr->oss.setup_list = NULL;
+	     setup; setup = setupn) {
+		setupn = setup->next;
+		kfree(setup->task_name);
+		kfree(setup);
+	}
+	pstr->oss.setup_list = NULL;
+}
+
+static void snd_pcm_oss_proc_write(snd_info_entry_t *entry,
+				   snd_info_buffer_t * buffer)
+{
+	snd_pcm_str_t *pstr = (snd_pcm_str_t *)entry->private_data;
+	char line[128], str[32], task_name[32], *ptr;
+	int idx1;
+	snd_pcm_oss_setup_t *setup, *setup1, template;
+
+	while (!snd_info_get_line(buffer, line, sizeof(line))) {
+		down(&pstr->oss.setup_mutex);
+		memset(&template, 0, sizeof(template));
+		ptr = snd_info_get_str(task_name, line, sizeof(task_name));
+		if (!strcmp(task_name, "clear") || !strcmp(task_name, "erase")) {
+			snd_pcm_oss_proc_free_setup_list(pstr);
+			up(&pstr->oss.setup_mutex);
+			continue;
+		}
+		for (setup = pstr->oss.setup_list; setup; setup = setup->next) {
+			if (!strcmp(setup->task_name, task_name)) {
+				template = *setup;
+				break;
+			}
+		}
+		ptr = snd_info_get_str(str, ptr, sizeof(str));
+		template.periods = simple_strtoul(str, NULL, 10);
+		ptr = snd_info_get_str(str, ptr, sizeof(str));
+		template.period_size = simple_strtoul(str, NULL, 10);
+		for (idx1 = 31; idx1 >= 0; idx1--)
+			if (template.period_size & (1 << idx1))
+				break;
+		for (idx1--; idx1 >= 0; idx1--)
+			template.period_size &= ~(1 << idx1);
+		do {
+			ptr = snd_info_get_str(str, ptr, sizeof(str));
+			if (!strcmp(str, "disable")) {
+				template.disable = 1;
+			} else if (!strcmp(str, "direct")) {
+				template.direct = 1;
+			} else if (!strcmp(str, "block")) {
+				template.block = 1;
+			} else if (!strcmp(str, "non-block")) {
+				template.nonblock = 1;
+			} else if (!strcmp(str, "partial-frag")) {
+				template.partialfrag = 1;
+			} else if (!strcmp(str, "no-silence")) {
+				template.nosilence = 1;
+			}
+		} while (*str);
+		if (setup == NULL) {
+			setup = (snd_pcm_oss_setup_t *) kmalloc(sizeof(snd_pcm_oss_setup_t), GFP_KERNEL);
+			if (setup) {
+				if (pstr->oss.setup_list == NULL) {
+					pstr->oss.setup_list = setup;
+				} else {
+					for (setup1 = pstr->oss.setup_list; setup1->next; setup1 = setup1->next);
+					setup1->next = setup;
+				}
+				template.task_name = snd_kmalloc_strdup(task_name, GFP_KERNEL);
+			} else {
+				buffer->error = -ENOMEM;
+			}
+		}
+		if (setup)
+			*setup = template;
+		up(&pstr->oss.setup_mutex);
+	}
+}
+
+static void snd_pcm_oss_proc_init(snd_pcm_t *pcm)
+{
+	int stream;
+	for (stream = 0; stream < 2; ++stream) {
+		snd_info_entry_t *entry;
+		snd_pcm_str_t *pstr = &pcm->streams[stream];
+		if (pstr->substream_count == 0)
+			continue;
+		if ((entry = snd_info_create_card_entry(pcm->card, "oss", pstr->proc_root)) != NULL) {
+			entry->content = SNDRV_INFO_CONTENT_TEXT;
+			entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
+			entry->c.text.read_size = 8192;
+			entry->c.text.read = snd_pcm_oss_proc_read;
+			entry->c.text.write_size = 8192;
+			entry->c.text.write = snd_pcm_oss_proc_write;
+			entry->private_data = pstr;
+			if (snd_info_register(entry) < 0) {
+				snd_info_free_entry(entry);
+				entry = NULL;
+			}
+		}
+		pstr->oss.proc_entry = entry;
+	}
+}
+
+static void snd_pcm_oss_proc_done(snd_pcm_t *pcm)
+{
+	int stream;
+	for (stream = 0; stream < 2; ++stream) {
+		snd_pcm_str_t *pstr = &pcm->streams[stream];
+		if (pstr->oss.proc_entry) {
+			snd_info_unregister(pstr->oss.proc_entry);
+			pstr->oss.proc_entry = NULL;
+			snd_pcm_oss_proc_free_setup_list(pstr);
+		}
+	}
+}
+
+/*
+ *  ENTRY functions
+ */
+
+static struct file_operations snd_pcm_oss_f_reg =
+{
+	.owner =	THIS_MODULE,
+	.read =		snd_pcm_oss_read,
+	.write =	snd_pcm_oss_write,
+	.open =		snd_pcm_oss_open,
+	.release =	snd_pcm_oss_release,
+	.poll =		snd_pcm_oss_poll,
+	.unlocked_ioctl =	snd_pcm_oss_ioctl,
+	.compat_ioctl =	snd_pcm_oss_ioctl_compat,
+	.mmap =		snd_pcm_oss_mmap,
+};
+
+static snd_minor_t snd_pcm_oss_reg =
+{
+	.comment =	"digital audio",
+	.f_ops =	&snd_pcm_oss_f_reg,
+};
+
+static void register_oss_dsp(snd_pcm_t *pcm, int index)
+{
+	char name[128];
+	sprintf(name, "dsp%i%i", pcm->card->number, pcm->device);
+	if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM,
+				    pcm->card, index, &snd_pcm_oss_reg,
+				    name) < 0) {
+		snd_printk("unable to register OSS PCM device %i:%i\n", pcm->card->number, pcm->device);
+	}
+}
+
+static int snd_pcm_oss_register_minor(snd_pcm_t * pcm)
+{
+	pcm->oss.reg = 0;
+	if (dsp_map[pcm->card->number] == (int)pcm->device) {
+		char name[128];
+		int duplex;
+		register_oss_dsp(pcm, 0);
+		duplex = (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count > 0 && 
+			      pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count && 
+			      !(pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX));
+		sprintf(name, "%s%s", pcm->name, duplex ? " (DUPLEX)" : "");
+#ifdef SNDRV_OSS_INFO_DEV_AUDIO
+		snd_oss_info_register(SNDRV_OSS_INFO_DEV_AUDIO,
+				      pcm->card->number,
+				      name);
+#endif
+		pcm->oss.reg++;
+		pcm->oss.reg_mask |= 1;
+	}
+	if (adsp_map[pcm->card->number] == (int)pcm->device) {
+		register_oss_dsp(pcm, 1);
+		pcm->oss.reg++;
+		pcm->oss.reg_mask |= 2;
+	}
+
+	if (pcm->oss.reg)
+		snd_pcm_oss_proc_init(pcm);
+
+	return 0;
+}
+
+static int snd_pcm_oss_disconnect_minor(snd_pcm_t * pcm)
+{
+	if (pcm->oss.reg) {
+		if (pcm->oss.reg_mask & 1) {
+			pcm->oss.reg_mask &= ~1;
+			snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM,
+						  pcm->card, 0);
+		}
+		if (pcm->oss.reg_mask & 2) {
+			pcm->oss.reg_mask &= ~2;
+			snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM,
+						  pcm->card, 1);
+		}
+	}
+	return 0;
+}
+
+static int snd_pcm_oss_unregister_minor(snd_pcm_t * pcm)
+{
+	snd_pcm_oss_disconnect_minor(pcm);
+	if (pcm->oss.reg) {
+		if (dsp_map[pcm->card->number] == (int)pcm->device) {
+#ifdef SNDRV_OSS_INFO_DEV_AUDIO
+			snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_AUDIO, pcm->card->number);
+#endif
+		}
+		pcm->oss.reg = 0;
+		snd_pcm_oss_proc_done(pcm);
+	}
+	return 0;
+}
+
+static snd_pcm_notify_t snd_pcm_oss_notify =
+{
+	.n_register =	snd_pcm_oss_register_minor,
+	.n_disconnect = snd_pcm_oss_disconnect_minor,
+	.n_unregister =	snd_pcm_oss_unregister_minor,
+};
+
+static int __init alsa_pcm_oss_init(void)
+{
+	int i;
+	int err;
+
+	/* check device map table */
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		if (dsp_map[i] < 0 || dsp_map[i] >= SNDRV_PCM_DEVICES) {
+			snd_printk("invalid dsp_map[%d] = %d\n", i, dsp_map[i]);
+			dsp_map[i] = 0;
+		}
+		if (adsp_map[i] < 0 || adsp_map[i] >= SNDRV_PCM_DEVICES) {
+			snd_printk("invalid adsp_map[%d] = %d\n", i, adsp_map[i]);
+			adsp_map[i] = 1;
+		}
+	}
+	if ((err = snd_pcm_notify(&snd_pcm_oss_notify, 0)) < 0)
+		return err;
+	return 0;
+}
+
+static void __exit alsa_pcm_oss_exit(void)
+{
+	snd_pcm_notify(&snd_pcm_oss_notify, 1);
+}
+
+module_init(alsa_pcm_oss_init)
+module_exit(alsa_pcm_oss_exit)
diff --git a/sound/core/oss/pcm_plugin.c b/sound/core/oss/pcm_plugin.c
new file mode 100644
index 000000000000..6bb31009f0b4
--- /dev/null
+++ b/sound/core/oss/pcm_plugin.c
@@ -0,0 +1,921 @@
+/*
+ *  PCM Plug-In shared (kernel/library) code
+ *  Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#if 0
+#define PLUGIN_DEBUG
+#endif
+
+#include <sound/driver.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/vmalloc.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "pcm_plugin.h"
+
+#define snd_pcm_plug_first(plug) ((plug)->runtime->oss.plugin_first)
+#define snd_pcm_plug_last(plug) ((plug)->runtime->oss.plugin_last)
+
+static int snd_pcm_plugin_src_channels_mask(snd_pcm_plugin_t *plugin,
+					    bitset_t *dst_vmask,
+					    bitset_t **src_vmask)
+{
+	bitset_t *vmask = plugin->src_vmask;
+	bitset_copy(vmask, dst_vmask, plugin->src_format.channels);
+	*src_vmask = vmask;
+	return 0;
+}
+
+static int snd_pcm_plugin_dst_channels_mask(snd_pcm_plugin_t *plugin,
+					    bitset_t *src_vmask,
+					    bitset_t **dst_vmask)
+{
+	bitset_t *vmask = plugin->dst_vmask;
+	bitset_copy(vmask, src_vmask, plugin->dst_format.channels);
+	*dst_vmask = vmask;
+	return 0;
+}
+
+/*
+ *  because some cards might have rates "very close", we ignore
+ *  all "resampling" requests within +-5%
+ */
+static int rate_match(unsigned int src_rate, unsigned int dst_rate)
+{
+	unsigned int low = (src_rate * 95) / 100;
+	unsigned int high = (src_rate * 105) / 100;
+	return dst_rate >= low && dst_rate <= high;
+}
+
+static int snd_pcm_plugin_alloc(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t frames)
+{
+	snd_pcm_plugin_format_t *format;
+	ssize_t width;
+	size_t size;
+	unsigned int channel;
+	snd_pcm_plugin_channel_t *c;
+
+	if (plugin->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		format = &plugin->src_format;
+	} else {
+		format = &plugin->dst_format;
+	}
+	if ((width = snd_pcm_format_physical_width(format->format)) < 0)
+		return width;
+	size = frames * format->channels * width;
+	snd_assert((size % 8) == 0, return -ENXIO);
+	size /= 8;
+	if (plugin->buf_frames < frames) {
+		vfree(plugin->buf);
+		plugin->buf = vmalloc(size);
+		plugin->buf_frames = frames;
+	}
+	if (!plugin->buf) {
+		plugin->buf_frames = 0;
+		return -ENOMEM;
+	}
+	c = plugin->buf_channels;
+	if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
+		for (channel = 0; channel < format->channels; channel++, c++) {
+			c->frames = frames;
+			c->enabled = 1;
+			c->wanted = 0;
+			c->area.addr = plugin->buf;
+			c->area.first = channel * width;
+			c->area.step = format->channels * width;
+		}
+	} else if (plugin->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) {
+		snd_assert((size % format->channels) == 0,);
+		size /= format->channels;
+		for (channel = 0; channel < format->channels; channel++, c++) {
+			c->frames = frames;
+			c->enabled = 1;
+			c->wanted = 0;
+			c->area.addr = plugin->buf + (channel * size);
+			c->area.first = 0;
+			c->area.step = width;
+		}
+	} else
+		return -EINVAL;
+	return 0;
+}
+
+int snd_pcm_plug_alloc(snd_pcm_plug_t *plug, snd_pcm_uframes_t frames)
+{
+	int err;
+	snd_assert(snd_pcm_plug_first(plug) != NULL, return -ENXIO);
+	if (snd_pcm_plug_stream(plug) == SNDRV_PCM_STREAM_PLAYBACK) {
+		snd_pcm_plugin_t *plugin = snd_pcm_plug_first(plug);
+		while (plugin->next) {
+			if (plugin->dst_frames)
+				frames = plugin->dst_frames(plugin, frames);
+			snd_assert(frames > 0, return -ENXIO);
+			plugin = plugin->next;
+			err = snd_pcm_plugin_alloc(plugin, frames);
+			if (err < 0)
+				return err;
+		}
+	} else {
+		snd_pcm_plugin_t *plugin = snd_pcm_plug_last(plug);
+		while (plugin->prev) {
+			if (plugin->src_frames)
+				frames = plugin->src_frames(plugin, frames);
+			snd_assert(frames > 0, return -ENXIO);
+			plugin = plugin->prev;
+			err = snd_pcm_plugin_alloc(plugin, frames);
+			if (err < 0)
+				return err;
+		}
+	}
+	return 0;
+}
+
+
+snd_pcm_sframes_t snd_pcm_plugin_client_channels(snd_pcm_plugin_t *plugin,
+				       snd_pcm_uframes_t frames,
+				       snd_pcm_plugin_channel_t **channels)
+{
+	*channels = plugin->buf_channels;
+	return frames;
+}
+
+int snd_pcm_plugin_build(snd_pcm_plug_t *plug,
+			 const char *name,
+			 snd_pcm_plugin_format_t *src_format,
+			 snd_pcm_plugin_format_t *dst_format,
+			 size_t extra,
+			 snd_pcm_plugin_t **ret)
+{
+	snd_pcm_plugin_t *plugin;
+	unsigned int channels;
+	
+	snd_assert(plug != NULL, return -ENXIO);
+	snd_assert(src_format != NULL && dst_format != NULL, return -ENXIO);
+	plugin = kcalloc(1, sizeof(*plugin) + extra, GFP_KERNEL);
+	if (plugin == NULL)
+		return -ENOMEM;
+	plugin->name = name;
+	plugin->plug = plug;
+	plugin->stream = snd_pcm_plug_stream(plug);
+	plugin->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
+	plugin->src_format = *src_format;
+	plugin->src_width = snd_pcm_format_physical_width(src_format->format);
+	snd_assert(plugin->src_width > 0, );
+	plugin->dst_format = *dst_format;
+	plugin->dst_width = snd_pcm_format_physical_width(dst_format->format);
+	snd_assert(plugin->dst_width > 0, );
+	if (plugin->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		channels = src_format->channels;
+	else
+		channels = dst_format->channels;
+	plugin->buf_channels = kcalloc(channels, sizeof(*plugin->buf_channels), GFP_KERNEL);
+	if (plugin->buf_channels == NULL) {
+		snd_pcm_plugin_free(plugin);
+		return -ENOMEM;
+	}
+	plugin->src_vmask = bitset_alloc(src_format->channels);
+	if (plugin->src_vmask == NULL) {
+		snd_pcm_plugin_free(plugin);
+		return -ENOMEM;
+	}
+	plugin->dst_vmask = bitset_alloc(dst_format->channels);
+	if (plugin->dst_vmask == NULL) {
+		snd_pcm_plugin_free(plugin);
+		return -ENOMEM;
+	}
+	plugin->client_channels = snd_pcm_plugin_client_channels;
+	plugin->src_channels_mask = snd_pcm_plugin_src_channels_mask;
+	plugin->dst_channels_mask = snd_pcm_plugin_dst_channels_mask;
+	*ret = plugin;
+	return 0;
+}
+
+int snd_pcm_plugin_free(snd_pcm_plugin_t *plugin)
+{
+	if (! plugin)
+		return 0;
+	if (plugin->private_free)
+		plugin->private_free(plugin);
+	kfree(plugin->buf_channels);
+	vfree(plugin->buf);
+	kfree(plugin->src_vmask);
+	kfree(plugin->dst_vmask);
+	kfree(plugin);
+	return 0;
+}
+
+snd_pcm_sframes_t snd_pcm_plug_client_size(snd_pcm_plug_t *plug, snd_pcm_uframes_t drv_frames)
+{
+	snd_pcm_plugin_t *plugin, *plugin_prev, *plugin_next;
+	int stream = snd_pcm_plug_stream(plug);
+
+	snd_assert(plug != NULL, return -ENXIO);
+	if (drv_frames == 0)
+		return 0;
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		plugin = snd_pcm_plug_last(plug);
+		while (plugin && drv_frames > 0) {
+			plugin_prev = plugin->prev;
+			if (plugin->src_frames)
+				drv_frames = plugin->src_frames(plugin, drv_frames);
+			plugin = plugin_prev;
+		}
+	} else if (stream == SNDRV_PCM_STREAM_CAPTURE) {
+		plugin = snd_pcm_plug_first(plug);
+		while (plugin && drv_frames > 0) {
+			plugin_next = plugin->next;
+			if (plugin->dst_frames)
+				drv_frames = plugin->dst_frames(plugin, drv_frames);
+			plugin = plugin_next;
+		}
+	} else
+		snd_BUG();
+	return drv_frames;
+}
+
+snd_pcm_sframes_t snd_pcm_plug_slave_size(snd_pcm_plug_t *plug, snd_pcm_uframes_t clt_frames)
+{
+	snd_pcm_plugin_t *plugin, *plugin_prev, *plugin_next;
+	snd_pcm_sframes_t frames;
+	int stream = snd_pcm_plug_stream(plug);
+	
+	snd_assert(plug != NULL, return -ENXIO);
+	if (clt_frames == 0)
+		return 0;
+	frames = clt_frames;
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		plugin = snd_pcm_plug_first(plug);
+		while (plugin && frames > 0) {
+			plugin_next = plugin->next;
+			if (plugin->dst_frames) {
+				frames = plugin->dst_frames(plugin, frames);
+				if (frames < 0)
+					return frames;
+			}
+			plugin = plugin_next;
+		}
+	} else if (stream == SNDRV_PCM_STREAM_CAPTURE) {
+		plugin = snd_pcm_plug_last(plug);
+		while (plugin) {
+			plugin_prev = plugin->prev;
+			if (plugin->src_frames) {
+				frames = plugin->src_frames(plugin, frames);
+				if (frames < 0)
+					return frames;
+			}
+			plugin = plugin_prev;
+		}
+	} else
+		snd_BUG();
+	return frames;
+}
+
+static int snd_pcm_plug_formats(snd_mask_t *mask, int format)
+{
+	snd_mask_t formats = *mask;
+	u64 linfmts = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 |
+		       SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_S16_LE |
+		       SNDRV_PCM_FMTBIT_U16_BE | SNDRV_PCM_FMTBIT_S16_BE |
+		       SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_S24_LE |
+		       SNDRV_PCM_FMTBIT_U24_BE | SNDRV_PCM_FMTBIT_S24_BE |
+		       SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE |
+		       SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE);
+	snd_mask_set(&formats, SNDRV_PCM_FORMAT_MU_LAW);
+	
+	if (formats.bits[0] & (u32)linfmts)
+		formats.bits[0] |= (u32)linfmts;
+	if (formats.bits[1] & (u32)(linfmts >> 32))
+		formats.bits[1] |= (u32)(linfmts >> 32);
+	return snd_mask_test(&formats, format);
+}
+
+static int preferred_formats[] = {
+	SNDRV_PCM_FORMAT_S16_LE,
+	SNDRV_PCM_FORMAT_S16_BE,
+	SNDRV_PCM_FORMAT_U16_LE,
+	SNDRV_PCM_FORMAT_U16_BE,
+	SNDRV_PCM_FORMAT_S24_LE,
+	SNDRV_PCM_FORMAT_S24_BE,
+	SNDRV_PCM_FORMAT_U24_LE,
+	SNDRV_PCM_FORMAT_U24_BE,
+	SNDRV_PCM_FORMAT_S32_LE,
+	SNDRV_PCM_FORMAT_S32_BE,
+	SNDRV_PCM_FORMAT_U32_LE,
+	SNDRV_PCM_FORMAT_U32_BE,
+	SNDRV_PCM_FORMAT_S8,
+	SNDRV_PCM_FORMAT_U8
+};
+
+int snd_pcm_plug_slave_format(int format, snd_mask_t *format_mask)
+{
+	if (snd_mask_test(format_mask, format))
+		return format;
+	if (! snd_pcm_plug_formats(format_mask, format))
+		return -EINVAL;
+	if (snd_pcm_format_linear(format)) {
+		int width = snd_pcm_format_width(format);
+		int unsignd = snd_pcm_format_unsigned(format);
+		int big = snd_pcm_format_big_endian(format);
+		int format1;
+		int wid, width1=width;
+		int dwidth1 = 8;
+		for (wid = 0; wid < 4; ++wid) {
+			int end, big1 = big;
+			for (end = 0; end < 2; ++end) {
+				int sgn, unsignd1 = unsignd;
+				for (sgn = 0; sgn < 2; ++sgn) {
+					format1 = snd_pcm_build_linear_format(width1, unsignd1, big1);
+					if (format1 >= 0 &&
+					    snd_mask_test(format_mask, format1))
+						goto _found;
+					unsignd1 = !unsignd1;
+				}
+				big1 = !big1;
+			}
+			if (width1 == 32) {
+				dwidth1 = -dwidth1;
+				width1 = width;
+			}
+			width1 += dwidth1;
+		}
+		return -EINVAL;
+	_found:
+		return format1;
+	} else {
+		unsigned int i;
+		switch (format) {
+		case SNDRV_PCM_FORMAT_MU_LAW:
+			for (i = 0; i < ARRAY_SIZE(preferred_formats); ++i) {
+				int format1 = preferred_formats[i];
+				if (snd_mask_test(format_mask, format1))
+					return format1;
+			}
+		default:
+			return -EINVAL;
+		}
+	}
+}
+
+int snd_pcm_plug_format_plugins(snd_pcm_plug_t *plug,
+				snd_pcm_hw_params_t *params,
+				snd_pcm_hw_params_t *slave_params)
+{
+	snd_pcm_plugin_format_t tmpformat;
+	snd_pcm_plugin_format_t dstformat;
+	snd_pcm_plugin_format_t srcformat;
+	int src_access, dst_access;
+	snd_pcm_plugin_t *plugin = NULL;
+	int err;
+	int stream = snd_pcm_plug_stream(plug);
+	int slave_interleaved = (params_channels(slave_params) == 1 ||
+				 params_access(slave_params) == SNDRV_PCM_ACCESS_RW_INTERLEAVED);
+
+	switch (stream) {
+	case SNDRV_PCM_STREAM_PLAYBACK:
+		dstformat.format = params_format(slave_params);
+		dstformat.rate = params_rate(slave_params);
+		dstformat.channels = params_channels(slave_params);
+		srcformat.format = params_format(params);
+		srcformat.rate = params_rate(params);
+		srcformat.channels = params_channels(params);
+		src_access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
+		dst_access = (slave_interleaved ? SNDRV_PCM_ACCESS_RW_INTERLEAVED :
+						  SNDRV_PCM_ACCESS_RW_NONINTERLEAVED);
+		break;
+	case SNDRV_PCM_STREAM_CAPTURE:
+		dstformat.format = params_format(params);
+		dstformat.rate = params_rate(params);
+		dstformat.channels = params_channels(params);
+		srcformat.format = params_format(slave_params);
+		srcformat.rate = params_rate(slave_params);
+		srcformat.channels = params_channels(slave_params);
+		src_access = (slave_interleaved ? SNDRV_PCM_ACCESS_RW_INTERLEAVED :
+						  SNDRV_PCM_ACCESS_RW_NONINTERLEAVED);
+		dst_access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
+		break;
+	default:
+		snd_BUG();
+		return -EINVAL;
+	}
+	tmpformat = srcformat;
+		
+	pdprintf("srcformat: format=%i, rate=%i, channels=%i\n", 
+		 srcformat.format,
+		 srcformat.rate,
+		 srcformat.channels);
+	pdprintf("dstformat: format=%i, rate=%i, channels=%i\n", 
+		 dstformat.format,
+		 dstformat.rate,
+		 dstformat.channels);
+
+	/* Format change (linearization) */
+	if ((srcformat.format != dstformat.format ||
+	     !rate_match(srcformat.rate, dstformat.rate) ||
+	     srcformat.channels != dstformat.channels) &&
+	    !snd_pcm_format_linear(srcformat.format)) {
+		if (snd_pcm_format_linear(dstformat.format))
+			tmpformat.format = dstformat.format;
+		else
+			tmpformat.format = SNDRV_PCM_FORMAT_S16;
+		switch (srcformat.format) {
+		case SNDRV_PCM_FORMAT_MU_LAW:
+			err = snd_pcm_plugin_build_mulaw(plug,
+							 &srcformat, &tmpformat,
+							 &plugin);
+			break;
+		default:
+			return -EINVAL;
+		}
+		pdprintf("format change: src=%i, dst=%i returns %i\n", srcformat.format, tmpformat.format, err);
+		if (err < 0)
+			return err;
+		err = snd_pcm_plugin_append(plugin);
+		if (err < 0) {
+			snd_pcm_plugin_free(plugin);
+			return err;
+		}
+		srcformat = tmpformat;
+		src_access = dst_access;
+	}
+
+	/* channels reduction */
+	if (srcformat.channels > dstformat.channels) {
+		int sv = srcformat.channels;
+		int dv = dstformat.channels;
+		route_ttable_entry_t *ttable = kcalloc(dv * sv, sizeof(*ttable), GFP_KERNEL);
+		if (ttable == NULL)
+			return -ENOMEM;
+#if 1
+		if (sv == 2 && dv == 1) {
+			ttable[0] = HALF;
+			ttable[1] = HALF;
+		} else
+#endif
+		{
+			int v;
+			for (v = 0; v < dv; ++v)
+				ttable[v * sv + v] = FULL;
+		}
+		tmpformat.channels = dstformat.channels;
+		if (rate_match(srcformat.rate, dstformat.rate) &&
+		    snd_pcm_format_linear(dstformat.format))
+			tmpformat.format = dstformat.format;
+		err = snd_pcm_plugin_build_route(plug,
+						 &srcformat, &tmpformat,
+						 ttable, &plugin);
+		kfree(ttable);
+		pdprintf("channels reduction: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, err);
+		if (err < 0) {
+			snd_pcm_plugin_free(plugin);
+			return err;
+		}
+		err = snd_pcm_plugin_append(plugin);
+		if (err < 0) {
+			snd_pcm_plugin_free(plugin);
+			return err;
+		}
+		srcformat = tmpformat;
+		src_access = dst_access;
+	}
+
+	/* rate resampling */
+	if (!rate_match(srcformat.rate, dstformat.rate)) {
+		tmpformat.rate = dstformat.rate;
+		if (srcformat.channels == dstformat.channels &&
+		    snd_pcm_format_linear(dstformat.format))
+			tmpformat.format = dstformat.format;
+        	err = snd_pcm_plugin_build_rate(plug,
+        					&srcformat, &tmpformat,
+						&plugin);
+		pdprintf("rate down resampling: src=%i, dst=%i returns %i\n", srcformat.rate, tmpformat.rate, err);
+		if (err < 0) {
+			snd_pcm_plugin_free(plugin);
+			return err;
+		}      					    
+		err = snd_pcm_plugin_append(plugin);
+		if (err < 0) {
+			snd_pcm_plugin_free(plugin);
+			return err;
+		}
+		srcformat = tmpformat;
+		src_access = dst_access;
+        }
+
+	/* channels extension  */
+	if (srcformat.channels < dstformat.channels) {
+		int sv = srcformat.channels;
+		int dv = dstformat.channels;
+		route_ttable_entry_t *ttable = kcalloc(dv * sv, sizeof(*ttable), GFP_KERNEL);
+		if (ttable == NULL)
+			return -ENOMEM;
+#if 0
+		{
+			int v;
+			for (v = 0; v < sv; ++v)
+				ttable[v * sv + v] = FULL;
+		}
+#else
+		{
+			/* Playback is spreaded on all channels */
+			int vd, vs;
+			for (vd = 0, vs = 0; vd < dv; ++vd) {
+				ttable[vd * sv + vs] = FULL;
+				vs++;
+				if (vs == sv)
+					vs = 0;
+			}
+		}
+#endif
+		tmpformat.channels = dstformat.channels;
+		if (snd_pcm_format_linear(dstformat.format))
+			tmpformat.format = dstformat.format;
+		err = snd_pcm_plugin_build_route(plug,
+						 &srcformat, &tmpformat,
+						 ttable, &plugin);
+		kfree(ttable);
+		pdprintf("channels extension: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, err);
+		if (err < 0) {
+			snd_pcm_plugin_free(plugin);
+			return err;
+		}      					    
+		err = snd_pcm_plugin_append(plugin);
+		if (err < 0) {
+			snd_pcm_plugin_free(plugin);
+			return err;
+		}
+		srcformat = tmpformat;
+		src_access = dst_access;
+	}
+
+	/* format change */
+	if (srcformat.format != dstformat.format) {
+		tmpformat.format = dstformat.format;
+		if (tmpformat.format == SNDRV_PCM_FORMAT_MU_LAW) {
+			err = snd_pcm_plugin_build_mulaw(plug,
+							 &srcformat, &tmpformat,
+							 &plugin);
+		}
+		else if (snd_pcm_format_linear(srcformat.format) &&
+			 snd_pcm_format_linear(tmpformat.format)) {
+			err = snd_pcm_plugin_build_linear(plug,
+							  &srcformat, &tmpformat,
+							  &plugin);
+		}
+		else
+			return -EINVAL;
+		pdprintf("format change: src=%i, dst=%i returns %i\n", srcformat.format, tmpformat.format, err);
+		if (err < 0)
+			return err;
+		err = snd_pcm_plugin_append(plugin);
+		if (err < 0) {
+			snd_pcm_plugin_free(plugin);
+			return err;
+		}
+		srcformat = tmpformat;
+		src_access = dst_access;
+	}
+
+	/* de-interleave */
+	if (src_access != dst_access) {
+		err = snd_pcm_plugin_build_copy(plug,
+						&srcformat,
+						&tmpformat,
+						&plugin);
+		pdprintf("interleave change (copy: returns %i)\n", err);
+		if (err < 0)
+			return err;
+		err = snd_pcm_plugin_append(plugin);
+		if (err < 0) {
+			snd_pcm_plugin_free(plugin);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+snd_pcm_sframes_t snd_pcm_plug_client_channels_buf(snd_pcm_plug_t *plug,
+					 char *buf,
+					 snd_pcm_uframes_t count,
+					 snd_pcm_plugin_channel_t **channels)
+{
+	snd_pcm_plugin_t *plugin;
+	snd_pcm_plugin_channel_t *v;
+	snd_pcm_plugin_format_t *format;
+	int width, nchannels, channel;
+	int stream = snd_pcm_plug_stream(plug);
+
+	snd_assert(buf != NULL, return -ENXIO);
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		plugin = snd_pcm_plug_first(plug);
+		format = &plugin->src_format;
+	} else {
+		plugin = snd_pcm_plug_last(plug);
+		format = &plugin->dst_format;
+	}
+	v = plugin->buf_channels;
+	*channels = v;
+	if ((width = snd_pcm_format_physical_width(format->format)) < 0)
+		return width;
+	nchannels = format->channels;
+	snd_assert(plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || format->channels <= 1, return -ENXIO);
+	for (channel = 0; channel < nchannels; channel++, v++) {
+		v->frames = count;
+		v->enabled = 1;
+		v->wanted = (stream == SNDRV_PCM_STREAM_CAPTURE);
+		v->area.addr = buf;
+		v->area.first = channel * width;
+		v->area.step = nchannels * width;
+	}
+	return count;
+}
+
+static int snd_pcm_plug_playback_channels_mask(snd_pcm_plug_t *plug,
+					       bitset_t *client_vmask)
+{
+	snd_pcm_plugin_t *plugin = snd_pcm_plug_last(plug);
+	if (plugin == NULL) {
+		return 0;
+	} else {
+		int schannels = plugin->dst_format.channels;
+		bitset_t bs[bitset_size(schannels)];
+		bitset_t *srcmask;
+		bitset_t *dstmask = bs;
+		int err;
+		bitset_one(dstmask, schannels);
+		if (plugin == NULL) {
+			bitset_and(client_vmask, dstmask, schannels);
+			return 0;
+		}
+		while (1) {
+			err = plugin->src_channels_mask(plugin, dstmask, &srcmask);
+			if (err < 0)
+				return err;
+			dstmask = srcmask;
+			if (plugin->prev == NULL)
+				break;
+			plugin = plugin->prev;
+		}
+		bitset_and(client_vmask, dstmask, plugin->src_format.channels);
+		return 0;
+	}
+}
+
+static int snd_pcm_plug_playback_disable_useless_channels(snd_pcm_plug_t *plug,
+							  snd_pcm_plugin_channel_t *src_channels)
+{
+	snd_pcm_plugin_t *plugin = snd_pcm_plug_first(plug);
+	unsigned int nchannels = plugin->src_format.channels;
+	bitset_t bs[bitset_size(nchannels)];
+	bitset_t *srcmask = bs;
+	int err;
+	unsigned int channel;
+	for (channel = 0; channel < nchannels; channel++) {
+		if (src_channels[channel].enabled)
+			bitset_set(srcmask, channel);
+		else
+			bitset_reset(srcmask, channel);
+	}
+	err = snd_pcm_plug_playback_channels_mask(plug, srcmask);
+	if (err < 0)
+		return err;
+	for (channel = 0; channel < nchannels; channel++) {
+		if (!bitset_get(srcmask, channel))
+			src_channels[channel].enabled = 0;
+	}
+	return 0;
+}
+
+static int snd_pcm_plug_capture_disable_useless_channels(snd_pcm_plug_t *plug,
+							 snd_pcm_plugin_channel_t *src_channels,
+							 snd_pcm_plugin_channel_t *client_channels)
+{
+	snd_pcm_plugin_t *plugin = snd_pcm_plug_last(plug);
+	unsigned int nchannels = plugin->dst_format.channels;
+	bitset_t bs[bitset_size(nchannels)];
+	bitset_t *dstmask = bs;
+	bitset_t *srcmask;
+	int err;
+	unsigned int channel;
+	for (channel = 0; channel < nchannels; channel++) {
+		if (client_channels[channel].enabled)
+			bitset_set(dstmask, channel);
+		else
+			bitset_reset(dstmask, channel);
+	}
+	while (plugin) {
+		err = plugin->src_channels_mask(plugin, dstmask, &srcmask);
+		if (err < 0)
+			return err;
+		dstmask = srcmask;
+		plugin = plugin->prev;
+	}
+	plugin = snd_pcm_plug_first(plug);
+	nchannels = plugin->src_format.channels;
+	for (channel = 0; channel < nchannels; channel++) {
+		if (!bitset_get(dstmask, channel))
+			src_channels[channel].enabled = 0;
+	}
+	return 0;
+}
+
+snd_pcm_sframes_t snd_pcm_plug_write_transfer(snd_pcm_plug_t *plug, snd_pcm_plugin_channel_t *src_channels, snd_pcm_uframes_t size)
+{
+	snd_pcm_plugin_t *plugin, *next;
+	snd_pcm_plugin_channel_t *dst_channels;
+	int err;
+	snd_pcm_sframes_t frames = size;
+
+	if ((err = snd_pcm_plug_playback_disable_useless_channels(plug, src_channels)) < 0)
+		return err;
+	
+	plugin = snd_pcm_plug_first(plug);
+	while (plugin && frames > 0) {
+		if ((next = plugin->next) != NULL) {
+			snd_pcm_sframes_t frames1 = frames;
+			if (plugin->dst_frames)
+				frames1 = plugin->dst_frames(plugin, frames);
+			if ((err = next->client_channels(next, frames1, &dst_channels)) < 0) {
+				return err;
+			}
+			if (err != frames1) {
+				frames = err;
+				if (plugin->src_frames)
+					frames = plugin->src_frames(plugin, frames1);
+			}
+		} else
+			dst_channels = NULL;
+		pdprintf("write plugin: %s, %li\n", plugin->name, frames);
+		if ((frames = plugin->transfer(plugin, src_channels, dst_channels, frames)) < 0)
+			return frames;
+		src_channels = dst_channels;
+		plugin = next;
+	}
+	return snd_pcm_plug_client_size(plug, frames);
+}
+
+snd_pcm_sframes_t snd_pcm_plug_read_transfer(snd_pcm_plug_t *plug, snd_pcm_plugin_channel_t *dst_channels_final, snd_pcm_uframes_t size)
+{
+	snd_pcm_plugin_t *plugin, *next;
+	snd_pcm_plugin_channel_t *src_channels, *dst_channels;
+	snd_pcm_sframes_t frames = size;
+	int err;
+
+	frames = snd_pcm_plug_slave_size(plug, frames);
+	if (frames < 0)
+		return frames;
+
+	src_channels = NULL;
+	plugin = snd_pcm_plug_first(plug);
+	while (plugin && frames > 0) {
+		if ((next = plugin->next) != NULL) {
+			if ((err = plugin->client_channels(plugin, frames, &dst_channels)) < 0) {
+				return err;
+			}
+			frames = err;
+			if (!plugin->prev) {
+				if ((err = snd_pcm_plug_capture_disable_useless_channels(plug, dst_channels, dst_channels_final)) < 0)
+					return err;
+			}
+		} else {
+			dst_channels = dst_channels_final;
+		}
+		pdprintf("read plugin: %s, %li\n", plugin->name, frames);
+		if ((frames = plugin->transfer(plugin, src_channels, dst_channels, frames)) < 0)
+			return frames;
+		plugin = next;
+		src_channels = dst_channels;
+	}
+	return frames;
+}
+
+int snd_pcm_area_silence(const snd_pcm_channel_area_t *dst_area, size_t dst_offset,
+			 size_t samples, int format)
+{
+	/* FIXME: sub byte resolution and odd dst_offset */
+	unsigned char *dst;
+	unsigned int dst_step;
+	int width;
+	const unsigned char *silence;
+	if (!dst_area->addr)
+		return 0;
+	dst = dst_area->addr + (dst_area->first + dst_area->step * dst_offset) / 8;
+	width = snd_pcm_format_physical_width(format);
+	if (width <= 0)
+		return -EINVAL;
+	if (dst_area->step == (unsigned int) width && width >= 8)
+		return snd_pcm_format_set_silence(format, dst, samples);
+	silence = snd_pcm_format_silence_64(format);
+	if (! silence)
+		return -EINVAL;
+	dst_step = dst_area->step / 8;
+	if (width == 4) {
+		/* Ima ADPCM */
+		int dstbit = dst_area->first % 8;
+		int dstbit_step = dst_area->step % 8;
+		while (samples-- > 0) {
+			if (dstbit)
+				*dst &= 0xf0;
+			else
+				*dst &= 0x0f;
+			dst += dst_step;
+			dstbit += dstbit_step;
+			if (dstbit == 8) {
+				dst++;
+				dstbit = 0;
+			}
+		}
+	} else {
+		width /= 8;
+		while (samples-- > 0) {
+			memcpy(dst, silence, width);
+			dst += dst_step;
+		}
+	}
+	return 0;
+}
+
+int snd_pcm_area_copy(const snd_pcm_channel_area_t *src_area, size_t src_offset,
+		      const snd_pcm_channel_area_t *dst_area, size_t dst_offset,
+		      size_t samples, int format)
+{
+	/* FIXME: sub byte resolution and odd dst_offset */
+	char *src, *dst;
+	int width;
+	int src_step, dst_step;
+	src = src_area->addr + (src_area->first + src_area->step * src_offset) / 8;
+	if (!src_area->addr)
+		return snd_pcm_area_silence(dst_area, dst_offset, samples, format);
+	dst = dst_area->addr + (dst_area->first + dst_area->step * dst_offset) / 8;
+	if (!dst_area->addr)
+		return 0;
+	width = snd_pcm_format_physical_width(format);
+	if (width <= 0)
+		return -EINVAL;
+	if (src_area->step == (unsigned int) width &&
+	    dst_area->step == (unsigned int) width && width >= 8) {
+		size_t bytes = samples * width / 8;
+		memcpy(dst, src, bytes);
+		return 0;
+	}
+	src_step = src_area->step / 8;
+	dst_step = dst_area->step / 8;
+	if (width == 4) {
+		/* Ima ADPCM */
+		int srcbit = src_area->first % 8;
+		int srcbit_step = src_area->step % 8;
+		int dstbit = dst_area->first % 8;
+		int dstbit_step = dst_area->step % 8;
+		while (samples-- > 0) {
+			unsigned char srcval;
+			if (srcbit)
+				srcval = *src & 0x0f;
+			else
+				srcval = (*src & 0xf0) >> 4;
+			if (dstbit)
+				*dst = (*dst & 0xf0) | srcval;
+			else
+				*dst = (*dst & 0x0f) | (srcval << 4);
+			src += src_step;
+			srcbit += srcbit_step;
+			if (srcbit == 8) {
+				src++;
+				srcbit = 0;
+			}
+			dst += dst_step;
+			dstbit += dstbit_step;
+			if (dstbit == 8) {
+				dst++;
+				dstbit = 0;
+			}
+		}
+	} else {
+		width /= 8;
+		while (samples-- > 0) {
+			memcpy(dst, src, width);
+			src += src_step;
+			dst += dst_step;
+		}
+	}
+	return 0;
+}
diff --git a/sound/core/oss/pcm_plugin.h b/sound/core/oss/pcm_plugin.h
new file mode 100644
index 000000000000..0f86ce477490
--- /dev/null
+++ b/sound/core/oss/pcm_plugin.h
@@ -0,0 +1,250 @@
+#ifndef __PCM_PLUGIN_H
+#define __PCM_PLUGIN_H
+
+/*
+ *  Digital Audio (Plugin interface) abstract layer
+ *  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
+ *
+ */
+
+#ifndef ATTRIBUTE_UNUSED
+#define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+#endif
+
+typedef unsigned int bitset_t;
+
+static inline size_t bitset_size(int nbits)
+{
+	return (nbits + sizeof(bitset_t) * 8 - 1) / (sizeof(bitset_t) * 8);
+}
+
+static inline bitset_t *bitset_alloc(int nbits)
+{
+	return kcalloc(bitset_size(nbits), sizeof(bitset_t), GFP_KERNEL);
+}
+	
+static inline void bitset_set(bitset_t *bitmap, unsigned int pos)
+{
+	size_t bits = sizeof(*bitmap) * 8;
+	bitmap[pos / bits] |= 1 << (pos % bits);
+}
+
+static inline void bitset_reset(bitset_t *bitmap, unsigned int pos)
+{
+	size_t bits = sizeof(*bitmap) * 8;
+	bitmap[pos / bits] &= ~(1 << (pos % bits));
+}
+
+static inline int bitset_get(bitset_t *bitmap, unsigned int pos)
+{
+	size_t bits = sizeof(*bitmap) * 8;
+	return !!(bitmap[pos / bits] & (1 << (pos % bits)));
+}
+
+static inline void bitset_copy(bitset_t *dst, bitset_t *src, unsigned int nbits)
+{
+	memcpy(dst, src, bitset_size(nbits) * sizeof(bitset_t));
+}
+
+static inline void bitset_and(bitset_t *dst, bitset_t *bs, unsigned int nbits)
+{
+	bitset_t *end = dst + bitset_size(nbits);
+	while (dst < end)
+		*dst++ &= *bs++;
+}
+
+static inline void bitset_or(bitset_t *dst, bitset_t *bs, unsigned int nbits)
+{
+	bitset_t *end = dst + bitset_size(nbits);
+	while (dst < end)
+		*dst++ |= *bs++;
+}
+
+static inline void bitset_zero(bitset_t *dst, unsigned int nbits)
+{
+	bitset_t *end = dst + bitset_size(nbits);
+	while (dst < end)
+		*dst++ = 0;
+}
+
+static inline void bitset_one(bitset_t *dst, unsigned int nbits)
+{
+	bitset_t *end = dst + bitset_size(nbits);
+	while (dst < end)
+		*dst++ = ~(bitset_t)0;
+}
+
+#define snd_pcm_plug_t snd_pcm_substream_t
+#define snd_pcm_plug_stream(plug) ((plug)->stream)
+
+typedef enum {
+	INIT = 0,
+	PREPARE = 1,
+} snd_pcm_plugin_action_t;
+
+typedef struct _snd_pcm_channel_area {
+	void *addr;			/* base address of channel samples */
+	unsigned int first;		/* offset to first sample in bits */
+	unsigned int step;		/* samples distance in bits */
+} snd_pcm_channel_area_t;
+
+typedef struct _snd_pcm_plugin_channel {
+	void *aptr;			/* pointer to the allocated area */
+	snd_pcm_channel_area_t area;
+	snd_pcm_uframes_t frames;	/* allocated frames */
+	unsigned int enabled:1;		/* channel need to be processed */
+	unsigned int wanted:1;		/* channel is wanted */
+} snd_pcm_plugin_channel_t;
+
+typedef struct _snd_pcm_plugin_format {
+	int format;
+	unsigned int rate;
+	unsigned int channels;
+} snd_pcm_plugin_format_t;
+
+struct _snd_pcm_plugin {
+	const char *name;		/* plug-in name */
+	int stream;
+	snd_pcm_plugin_format_t src_format;	/* source format */
+	snd_pcm_plugin_format_t dst_format;	/* destination format */
+	int src_width;			/* sample width in bits */
+	int dst_width;			/* sample width in bits */
+	int access;
+	snd_pcm_sframes_t (*src_frames)(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t dst_frames);
+	snd_pcm_sframes_t (*dst_frames)(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t src_frames);
+	snd_pcm_sframes_t (*client_channels)(snd_pcm_plugin_t *plugin,
+				 snd_pcm_uframes_t frames,
+				 snd_pcm_plugin_channel_t **channels);
+	int (*src_channels_mask)(snd_pcm_plugin_t *plugin,
+			       bitset_t *dst_vmask,
+			       bitset_t **src_vmask);
+	int (*dst_channels_mask)(snd_pcm_plugin_t *plugin,
+			       bitset_t *src_vmask,
+			       bitset_t **dst_vmask);
+	snd_pcm_sframes_t (*transfer)(snd_pcm_plugin_t *plugin,
+			    const snd_pcm_plugin_channel_t *src_channels,
+			    snd_pcm_plugin_channel_t *dst_channels,
+			    snd_pcm_uframes_t frames);
+	int (*action)(snd_pcm_plugin_t *plugin,
+		      snd_pcm_plugin_action_t action,
+		      unsigned long data);
+	snd_pcm_plugin_t *prev;
+	snd_pcm_plugin_t *next;
+	snd_pcm_plug_t *plug;
+	void *private_data;
+	void (*private_free)(snd_pcm_plugin_t *plugin);
+	char *buf;
+	snd_pcm_uframes_t buf_frames;
+	snd_pcm_plugin_channel_t *buf_channels;
+	bitset_t *src_vmask;
+	bitset_t *dst_vmask;
+	char extra_data[0];
+};
+
+int snd_pcm_plugin_build(snd_pcm_plug_t *handle,
+                         const char *name,
+                         snd_pcm_plugin_format_t *src_format,
+                         snd_pcm_plugin_format_t *dst_format,
+                         size_t extra,
+                         snd_pcm_plugin_t **ret);
+int snd_pcm_plugin_free(snd_pcm_plugin_t *plugin);
+int snd_pcm_plugin_clear(snd_pcm_plugin_t **first);
+int snd_pcm_plug_alloc(snd_pcm_plug_t *plug, snd_pcm_uframes_t frames);
+snd_pcm_sframes_t snd_pcm_plug_client_size(snd_pcm_plug_t *handle, snd_pcm_uframes_t drv_size);
+snd_pcm_sframes_t snd_pcm_plug_slave_size(snd_pcm_plug_t *handle, snd_pcm_uframes_t clt_size);
+
+#define FULL ROUTE_PLUGIN_RESOLUTION
+#define HALF ROUTE_PLUGIN_RESOLUTION / 2
+typedef int route_ttable_entry_t;
+
+int snd_pcm_plugin_build_io(snd_pcm_plug_t *handle,
+			    snd_pcm_hw_params_t *params,
+			    snd_pcm_plugin_t **r_plugin);
+int snd_pcm_plugin_build_linear(snd_pcm_plug_t *handle,
+				snd_pcm_plugin_format_t *src_format,
+				snd_pcm_plugin_format_t *dst_format,
+				snd_pcm_plugin_t **r_plugin);
+int snd_pcm_plugin_build_mulaw(snd_pcm_plug_t *handle,
+			       snd_pcm_plugin_format_t *src_format,
+			       snd_pcm_plugin_format_t *dst_format,
+			       snd_pcm_plugin_t **r_plugin);
+int snd_pcm_plugin_build_rate(snd_pcm_plug_t *handle,
+			      snd_pcm_plugin_format_t *src_format,
+			      snd_pcm_plugin_format_t *dst_format,
+			      snd_pcm_plugin_t **r_plugin);
+int snd_pcm_plugin_build_route(snd_pcm_plug_t *handle,
+			       snd_pcm_plugin_format_t *src_format,
+			       snd_pcm_plugin_format_t *dst_format,
+			       route_ttable_entry_t *ttable,
+		               snd_pcm_plugin_t **r_plugin);
+int snd_pcm_plugin_build_copy(snd_pcm_plug_t *handle,
+			      snd_pcm_plugin_format_t *src_format,
+			      snd_pcm_plugin_format_t *dst_format,
+			      snd_pcm_plugin_t **r_plugin);
+
+int snd_pcm_plug_format_plugins(snd_pcm_plug_t *substream,
+				snd_pcm_hw_params_t *params,
+				snd_pcm_hw_params_t *slave_params);
+
+int snd_pcm_plug_slave_format(int format, snd_mask_t *format_mask);
+
+int snd_pcm_plugin_append(snd_pcm_plugin_t *plugin);
+
+snd_pcm_sframes_t snd_pcm_plug_write_transfer(snd_pcm_plug_t *handle, snd_pcm_plugin_channel_t *src_channels, snd_pcm_uframes_t size);
+snd_pcm_sframes_t snd_pcm_plug_read_transfer(snd_pcm_plug_t *handle, snd_pcm_plugin_channel_t *dst_channels_final, snd_pcm_uframes_t size);
+
+snd_pcm_sframes_t snd_pcm_plug_client_channels_buf(snd_pcm_plug_t *handle,
+					 char *buf, snd_pcm_uframes_t count,
+					 snd_pcm_plugin_channel_t **channels);
+
+snd_pcm_sframes_t snd_pcm_plugin_client_channels(snd_pcm_plugin_t *plugin,
+				       snd_pcm_uframes_t frames,
+				       snd_pcm_plugin_channel_t **channels);
+
+int snd_pcm_area_silence(const snd_pcm_channel_area_t *dst_channel, size_t dst_offset,
+			 size_t samples, int format);
+int snd_pcm_area_copy(const snd_pcm_channel_area_t *src_channel, size_t src_offset,
+		      const snd_pcm_channel_area_t *dst_channel, size_t dst_offset,
+		      size_t samples, int format);
+
+void *snd_pcm_plug_buf_alloc(snd_pcm_plug_t *plug, snd_pcm_uframes_t size);
+void snd_pcm_plug_buf_unlock(snd_pcm_plug_t *plug, void *ptr);
+snd_pcm_sframes_t snd_pcm_oss_write3(snd_pcm_substream_t *substream, const char *ptr, snd_pcm_uframes_t size, int in_kernel);
+snd_pcm_sframes_t snd_pcm_oss_read3(snd_pcm_substream_t *substream, char *ptr, snd_pcm_uframes_t size, int in_kernel);
+snd_pcm_sframes_t snd_pcm_oss_writev3(snd_pcm_substream_t *substream, void **bufs, snd_pcm_uframes_t frames, int in_kernel);
+snd_pcm_sframes_t snd_pcm_oss_readv3(snd_pcm_substream_t *substream, void **bufs, snd_pcm_uframes_t frames, int in_kernel);
+
+
+
+#define ROUTE_PLUGIN_RESOLUTION 16
+
+int getput_index(int format);
+int copy_index(int format);
+int conv_index(int src_format, int dst_format);
+
+void zero_channel(snd_pcm_plugin_t *plugin,
+		  const snd_pcm_plugin_channel_t *dst_channel,
+		  size_t samples);
+
+#ifdef PLUGIN_DEBUG
+#define pdprintf( fmt, args... ) printk( "plugin: " fmt, ##args)
+#else
+#define pdprintf( fmt, args... ) 
+#endif
+
+#endif				/* __PCM_PLUGIN_H */
diff --git a/sound/core/oss/plugin_ops.h b/sound/core/oss/plugin_ops.h
new file mode 100644
index 000000000000..0607e9566084
--- /dev/null
+++ b/sound/core/oss/plugin_ops.h
@@ -0,0 +1,536 @@
+/*
+ *  Plugin sample operators with fast switch
+ *  Copyright (c) 2000 by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+
+#define as_u8(ptr) (*(u_int8_t*)(ptr))
+#define as_u16(ptr) (*(u_int16_t*)(ptr))
+#define as_u32(ptr) (*(u_int32_t*)(ptr))
+#define as_u64(ptr) (*(u_int64_t*)(ptr))
+#define as_s8(ptr) (*(int8_t*)(ptr))
+#define as_s16(ptr) (*(int16_t*)(ptr))
+#define as_s32(ptr) (*(int32_t*)(ptr))
+#define as_s64(ptr) (*(int64_t*)(ptr))
+
+#ifdef COPY_LABELS
+static void *copy_labels[4] = {
+	&&copy_8,
+	&&copy_16,
+	&&copy_32,
+	&&copy_64
+};
+#endif
+
+#ifdef COPY_END
+while(0) {
+copy_8: as_s8(dst) = as_s8(src); goto COPY_END;
+copy_16: as_s16(dst) = as_s16(src); goto COPY_END;
+copy_32: as_s32(dst) = as_s32(src); goto COPY_END;
+copy_64: as_s64(dst) = as_s64(src); goto COPY_END;
+}
+#endif
+
+#ifdef CONV_LABELS
+/* src_wid src_endswap sign_toggle dst_wid dst_endswap */
+static void *conv_labels[4 * 2 * 2 * 4 * 2] = {
+	&&conv_xxx1_xxx1,	 /*  8h ->  8h */
+	&&conv_xxx1_xxx1,	 /*  8h ->  8s */
+	&&conv_xxx1_xx10,	 /*  8h -> 16h */
+	&&conv_xxx1_xx01,	 /*  8h -> 16s */
+	&&conv_xxx1_x100,	 /*  8h -> 24h */
+	&&conv_xxx1_001x,	 /*  8h -> 24s */
+	&&conv_xxx1_1000,	 /*  8h -> 32h */
+	&&conv_xxx1_0001,	 /*  8h -> 32s */
+	&&conv_xxx1_xxx9,	 /*  8h ^>  8h */
+	&&conv_xxx1_xxx9,	 /*  8h ^>  8s */
+	&&conv_xxx1_xx90,	 /*  8h ^> 16h */
+	&&conv_xxx1_xx09,	 /*  8h ^> 16s */
+	&&conv_xxx1_x900,	 /*  8h ^> 24h */
+	&&conv_xxx1_009x,	 /*  8h ^> 24s */
+	&&conv_xxx1_9000,	 /*  8h ^> 32h */
+	&&conv_xxx1_0009,	 /*  8h ^> 32s */
+	&&conv_xxx1_xxx1,	 /*  8s ->  8h */
+	&&conv_xxx1_xxx1,	 /*  8s ->  8s */
+	&&conv_xxx1_xx10,	 /*  8s -> 16h */
+	&&conv_xxx1_xx01,	 /*  8s -> 16s */
+	&&conv_xxx1_x100,	 /*  8s -> 24h */
+	&&conv_xxx1_001x,	 /*  8s -> 24s */
+	&&conv_xxx1_1000,	 /*  8s -> 32h */
+	&&conv_xxx1_0001,	 /*  8s -> 32s */
+	&&conv_xxx1_xxx9,	 /*  8s ^>  8h */
+	&&conv_xxx1_xxx9,	 /*  8s ^>  8s */
+	&&conv_xxx1_xx90,	 /*  8s ^> 16h */
+	&&conv_xxx1_xx09,	 /*  8s ^> 16s */
+	&&conv_xxx1_x900,	 /*  8s ^> 24h */
+	&&conv_xxx1_009x,	 /*  8s ^> 24s */
+	&&conv_xxx1_9000,	 /*  8s ^> 32h */
+	&&conv_xxx1_0009,	 /*  8s ^> 32s */
+	&&conv_xx12_xxx1,	 /* 16h ->  8h */
+	&&conv_xx12_xxx1,	 /* 16h ->  8s */
+	&&conv_xx12_xx12,	 /* 16h -> 16h */
+	&&conv_xx12_xx21,	 /* 16h -> 16s */
+	&&conv_xx12_x120,	 /* 16h -> 24h */
+	&&conv_xx12_021x,	 /* 16h -> 24s */
+	&&conv_xx12_1200,	 /* 16h -> 32h */
+	&&conv_xx12_0021,	 /* 16h -> 32s */
+	&&conv_xx12_xxx9,	 /* 16h ^>  8h */
+	&&conv_xx12_xxx9,	 /* 16h ^>  8s */
+	&&conv_xx12_xx92,	 /* 16h ^> 16h */
+	&&conv_xx12_xx29,	 /* 16h ^> 16s */
+	&&conv_xx12_x920,	 /* 16h ^> 24h */
+	&&conv_xx12_029x,	 /* 16h ^> 24s */
+	&&conv_xx12_9200,	 /* 16h ^> 32h */
+	&&conv_xx12_0029,	 /* 16h ^> 32s */
+	&&conv_xx12_xxx2,	 /* 16s ->  8h */
+	&&conv_xx12_xxx2,	 /* 16s ->  8s */
+	&&conv_xx12_xx21,	 /* 16s -> 16h */
+	&&conv_xx12_xx12,	 /* 16s -> 16s */
+	&&conv_xx12_x210,	 /* 16s -> 24h */
+	&&conv_xx12_012x,	 /* 16s -> 24s */
+	&&conv_xx12_2100,	 /* 16s -> 32h */
+	&&conv_xx12_0012,	 /* 16s -> 32s */
+	&&conv_xx12_xxxA,	 /* 16s ^>  8h */
+	&&conv_xx12_xxxA,	 /* 16s ^>  8s */
+	&&conv_xx12_xxA1,	 /* 16s ^> 16h */
+	&&conv_xx12_xx1A,	 /* 16s ^> 16s */
+	&&conv_xx12_xA10,	 /* 16s ^> 24h */
+	&&conv_xx12_01Ax,	 /* 16s ^> 24s */
+	&&conv_xx12_A100,	 /* 16s ^> 32h */
+	&&conv_xx12_001A,	 /* 16s ^> 32s */
+	&&conv_x123_xxx1,	 /* 24h ->  8h */
+	&&conv_x123_xxx1,	 /* 24h ->  8s */
+	&&conv_x123_xx12,	 /* 24h -> 16h */
+	&&conv_x123_xx21,	 /* 24h -> 16s */
+	&&conv_x123_x123,	 /* 24h -> 24h */
+	&&conv_x123_321x,	 /* 24h -> 24s */
+	&&conv_x123_1230,	 /* 24h -> 32h */
+	&&conv_x123_0321,	 /* 24h -> 32s */
+	&&conv_x123_xxx9,	 /* 24h ^>  8h */
+	&&conv_x123_xxx9,	 /* 24h ^>  8s */
+	&&conv_x123_xx92,	 /* 24h ^> 16h */
+	&&conv_x123_xx29,	 /* 24h ^> 16s */
+	&&conv_x123_x923,	 /* 24h ^> 24h */
+	&&conv_x123_329x,	 /* 24h ^> 24s */
+	&&conv_x123_9230,	 /* 24h ^> 32h */
+	&&conv_x123_0329,	 /* 24h ^> 32s */
+	&&conv_123x_xxx3,	 /* 24s ->  8h */
+	&&conv_123x_xxx3,	 /* 24s ->  8s */
+	&&conv_123x_xx32,	 /* 24s -> 16h */
+	&&conv_123x_xx23,	 /* 24s -> 16s */
+	&&conv_123x_x321,	 /* 24s -> 24h */
+	&&conv_123x_123x,	 /* 24s -> 24s */
+	&&conv_123x_3210,	 /* 24s -> 32h */
+	&&conv_123x_0123,	 /* 24s -> 32s */
+	&&conv_123x_xxxB,	 /* 24s ^>  8h */
+	&&conv_123x_xxxB,	 /* 24s ^>  8s */
+	&&conv_123x_xxB2,	 /* 24s ^> 16h */
+	&&conv_123x_xx2B,	 /* 24s ^> 16s */
+	&&conv_123x_xB21,	 /* 24s ^> 24h */
+	&&conv_123x_12Bx,	 /* 24s ^> 24s */
+	&&conv_123x_B210,	 /* 24s ^> 32h */
+	&&conv_123x_012B,	 /* 24s ^> 32s */
+	&&conv_1234_xxx1,	 /* 32h ->  8h */
+	&&conv_1234_xxx1,	 /* 32h ->  8s */
+	&&conv_1234_xx12,	 /* 32h -> 16h */
+	&&conv_1234_xx21,	 /* 32h -> 16s */
+	&&conv_1234_x123,	 /* 32h -> 24h */
+	&&conv_1234_321x,	 /* 32h -> 24s */
+	&&conv_1234_1234,	 /* 32h -> 32h */
+	&&conv_1234_4321,	 /* 32h -> 32s */
+	&&conv_1234_xxx9,	 /* 32h ^>  8h */
+	&&conv_1234_xxx9,	 /* 32h ^>  8s */
+	&&conv_1234_xx92,	 /* 32h ^> 16h */
+	&&conv_1234_xx29,	 /* 32h ^> 16s */
+	&&conv_1234_x923,	 /* 32h ^> 24h */
+	&&conv_1234_329x,	 /* 32h ^> 24s */
+	&&conv_1234_9234,	 /* 32h ^> 32h */
+	&&conv_1234_4329,	 /* 32h ^> 32s */
+	&&conv_1234_xxx4,	 /* 32s ->  8h */
+	&&conv_1234_xxx4,	 /* 32s ->  8s */
+	&&conv_1234_xx43,	 /* 32s -> 16h */
+	&&conv_1234_xx34,	 /* 32s -> 16s */
+	&&conv_1234_x432,	 /* 32s -> 24h */
+	&&conv_1234_234x,	 /* 32s -> 24s */
+	&&conv_1234_4321,	 /* 32s -> 32h */
+	&&conv_1234_1234,	 /* 32s -> 32s */
+	&&conv_1234_xxxC,	 /* 32s ^>  8h */
+	&&conv_1234_xxxC,	 /* 32s ^>  8s */
+	&&conv_1234_xxC3,	 /* 32s ^> 16h */
+	&&conv_1234_xx3C,	 /* 32s ^> 16s */
+	&&conv_1234_xC32,	 /* 32s ^> 24h */
+	&&conv_1234_23Cx,	 /* 32s ^> 24s */
+	&&conv_1234_C321,	 /* 32s ^> 32h */
+	&&conv_1234_123C,	 /* 32s ^> 32s */
+};
+#endif
+
+#ifdef CONV_END
+while(0) {
+conv_xxx1_xxx1: as_u8(dst) = as_u8(src); goto CONV_END;
+conv_xxx1_xx10: as_u16(dst) = (u_int16_t)as_u8(src) << 8; goto CONV_END;
+conv_xxx1_xx01: as_u16(dst) = (u_int16_t)as_u8(src); goto CONV_END;
+conv_xxx1_x100: as_u32(dst) = (u_int32_t)as_u8(src) << 16; goto CONV_END;
+conv_xxx1_001x: as_u32(dst) = (u_int32_t)as_u8(src) << 8; goto CONV_END;
+conv_xxx1_1000: as_u32(dst) = (u_int32_t)as_u8(src) << 24; goto CONV_END;
+conv_xxx1_0001: as_u32(dst) = (u_int32_t)as_u8(src); goto CONV_END;
+conv_xxx1_xxx9: as_u8(dst) = as_u8(src) ^ 0x80; goto CONV_END;
+conv_xxx1_xx90: as_u16(dst) = (u_int16_t)(as_u8(src) ^ 0x80) << 8; goto CONV_END;
+conv_xxx1_xx09: as_u16(dst) = (u_int16_t)(as_u8(src) ^ 0x80); goto CONV_END;
+conv_xxx1_x900: as_u32(dst) = (u_int32_t)(as_u8(src) ^ 0x80) << 16; goto CONV_END;
+conv_xxx1_009x: as_u32(dst) = (u_int32_t)(as_u8(src) ^ 0x80) << 8; goto CONV_END;
+conv_xxx1_9000: as_u32(dst) = (u_int32_t)(as_u8(src) ^ 0x80) << 24; goto CONV_END;
+conv_xxx1_0009: as_u32(dst) = (u_int32_t)(as_u8(src) ^ 0x80); goto CONV_END;
+conv_xx12_xxx1: as_u8(dst) = as_u16(src) >> 8; goto CONV_END;
+conv_xx12_xx12: as_u16(dst) = as_u16(src); goto CONV_END;
+conv_xx12_xx21: as_u16(dst) = swab16(as_u16(src)); goto CONV_END;
+conv_xx12_x120: as_u32(dst) = (u_int32_t)as_u16(src) << 8; goto CONV_END;
+conv_xx12_021x: as_u32(dst) = (u_int32_t)swab16(as_u16(src)) << 8; goto CONV_END;
+conv_xx12_1200: as_u32(dst) = (u_int32_t)as_u16(src) << 16; goto CONV_END;
+conv_xx12_0021: as_u32(dst) = (u_int32_t)swab16(as_u16(src)); goto CONV_END;
+conv_xx12_xxx9: as_u8(dst) = (as_u16(src) >> 8) ^ 0x80; goto CONV_END;
+conv_xx12_xx92: as_u16(dst) = as_u16(src) ^ 0x8000; goto CONV_END;
+conv_xx12_xx29: as_u16(dst) = swab16(as_u16(src)) ^ 0x80; goto CONV_END;
+conv_xx12_x920: as_u32(dst) = (u_int32_t)(as_u16(src) ^ 0x8000) << 8; goto CONV_END;
+conv_xx12_029x: as_u32(dst) = (u_int32_t)(swab16(as_u16(src)) ^ 0x80) << 8; goto CONV_END;
+conv_xx12_9200: as_u32(dst) = (u_int32_t)(as_u16(src) ^ 0x8000) << 16; goto CONV_END;
+conv_xx12_0029: as_u32(dst) = (u_int32_t)(swab16(as_u16(src)) ^ 0x80); goto CONV_END;
+conv_xx12_xxx2: as_u8(dst) = as_u16(src) & 0xff; goto CONV_END;
+conv_xx12_x210: as_u32(dst) = (u_int32_t)swab16(as_u16(src)) << 8; goto CONV_END;
+conv_xx12_012x: as_u32(dst) = (u_int32_t)as_u16(src) << 8; goto CONV_END;
+conv_xx12_2100: as_u32(dst) = (u_int32_t)swab16(as_u16(src)) << 16; goto CONV_END;
+conv_xx12_0012: as_u32(dst) = (u_int32_t)as_u16(src); goto CONV_END; 
+conv_xx12_xxxA: as_u8(dst) = (as_u16(src) ^ 0x80) & 0xff; goto CONV_END;
+conv_xx12_xxA1: as_u16(dst) = swab16(as_u16(src) ^ 0x80); goto CONV_END;
+conv_xx12_xx1A: as_u16(dst) = as_u16(src) ^ 0x80; goto CONV_END;
+conv_xx12_xA10: as_u32(dst) = (u_int32_t)swab16(as_u16(src) ^ 0x80) << 8; goto CONV_END;
+conv_xx12_01Ax: as_u32(dst) = (u_int32_t)(as_u16(src) ^ 0x80) << 8; goto CONV_END;
+conv_xx12_A100: as_u32(dst) = (u_int32_t)swab16(as_u16(src) ^ 0x80) << 16; goto CONV_END;
+conv_xx12_001A: as_u32(dst) = (u_int32_t)(as_u16(src) ^ 0x80); goto CONV_END;
+conv_x123_xxx1: as_u8(dst) = as_u32(src) >> 16; goto CONV_END;
+conv_x123_xx12: as_u16(dst) = as_u32(src) >> 8; goto CONV_END;
+conv_x123_xx21: as_u16(dst) = swab16(as_u32(src) >> 8); goto CONV_END;
+conv_x123_x123: as_u32(dst) = as_u32(src); goto CONV_END;
+conv_x123_321x: as_u32(dst) = swab32(as_u32(src)); goto CONV_END;
+conv_x123_1230: as_u32(dst) = as_u32(src) << 8; goto CONV_END;
+conv_x123_0321: as_u32(dst) = swab32(as_u32(src)) >> 8; goto CONV_END;
+conv_x123_xxx9: as_u8(dst) = (as_u32(src) >> 16) ^ 0x80; goto CONV_END;
+conv_x123_xx92: as_u16(dst) = (as_u32(src) >> 8) ^ 0x8000; goto CONV_END;
+conv_x123_xx29: as_u16(dst) = swab16(as_u32(src) >> 8) ^ 0x80; goto CONV_END;
+conv_x123_x923: as_u32(dst) = as_u32(src) ^ 0x800000; goto CONV_END;
+conv_x123_329x: as_u32(dst) = swab32(as_u32(src)) ^ 0x8000; goto CONV_END;
+conv_x123_9230: as_u32(dst) = (as_u32(src) ^ 0x800000) << 8; goto CONV_END;
+conv_x123_0329: as_u32(dst) = (swab32(as_u32(src)) >> 8) ^ 0x80; goto CONV_END;
+conv_123x_xxx3: as_u8(dst) = (as_u32(src) >> 8) & 0xff; goto CONV_END;
+conv_123x_xx32: as_u16(dst) = swab16(as_u32(src) >> 8); goto CONV_END;
+conv_123x_xx23: as_u16(dst) = (as_u32(src) >> 8) & 0xffff; goto CONV_END;
+conv_123x_x321: as_u32(dst) = swab32(as_u32(src)); goto CONV_END;
+conv_123x_123x: as_u32(dst) = as_u32(src); goto CONV_END;
+conv_123x_3210: as_u32(dst) = swab32(as_u32(src)) << 8; goto CONV_END;
+conv_123x_0123: as_u32(dst) = as_u32(src) >> 8; goto CONV_END;
+conv_123x_xxxB: as_u8(dst) = ((as_u32(src) >> 8) & 0xff) ^ 0x80; goto CONV_END;
+conv_123x_xxB2: as_u16(dst) = swab16((as_u32(src) >> 8) ^ 0x80); goto CONV_END;
+conv_123x_xx2B: as_u16(dst) = ((as_u32(src) >> 8) & 0xffff) ^ 0x80; goto CONV_END;
+conv_123x_xB21: as_u32(dst) = swab32(as_u32(src)) ^ 0x800000; goto CONV_END;
+conv_123x_12Bx: as_u32(dst) = as_u32(src) ^ 0x8000; goto CONV_END;
+conv_123x_B210: as_u32(dst) = swab32(as_u32(src) ^ 0x8000) << 8; goto CONV_END;
+conv_123x_012B: as_u32(dst) = (as_u32(src) >> 8) ^ 0x80; goto CONV_END;
+conv_1234_xxx1: as_u8(dst) = as_u32(src) >> 24; goto CONV_END;
+conv_1234_xx12: as_u16(dst) = as_u32(src) >> 16; goto CONV_END;
+conv_1234_xx21: as_u16(dst) = swab16(as_u32(src) >> 16); goto CONV_END;
+conv_1234_x123: as_u32(dst) = as_u32(src) >> 8; goto CONV_END;
+conv_1234_321x: as_u32(dst) = swab32(as_u32(src)) << 8; goto CONV_END;
+conv_1234_1234: as_u32(dst) = as_u32(src); goto CONV_END;
+conv_1234_4321: as_u32(dst) = swab32(as_u32(src)); goto CONV_END;
+conv_1234_xxx9: as_u8(dst) = (as_u32(src) >> 24) ^ 0x80; goto CONV_END;
+conv_1234_xx92: as_u16(dst) = (as_u32(src) >> 16) ^ 0x8000; goto CONV_END;
+conv_1234_xx29: as_u16(dst) = swab16(as_u32(src) >> 16) ^ 0x80; goto CONV_END;
+conv_1234_x923: as_u32(dst) = (as_u32(src) >> 8) ^ 0x800000; goto CONV_END;
+conv_1234_329x: as_u32(dst) = (swab32(as_u32(src)) ^ 0x80) << 8; goto CONV_END;
+conv_1234_9234: as_u32(dst) = as_u32(src) ^ 0x80000000; goto CONV_END;
+conv_1234_4329: as_u32(dst) = swab32(as_u32(src)) ^ 0x80; goto CONV_END;
+conv_1234_xxx4: as_u8(dst) = as_u32(src) & 0xff; goto CONV_END;
+conv_1234_xx43: as_u16(dst) = swab16(as_u32(src)); goto CONV_END;
+conv_1234_xx34: as_u16(dst) = as_u32(src) & 0xffff; goto CONV_END;
+conv_1234_x432: as_u32(dst) = swab32(as_u32(src)) >> 8; goto CONV_END;
+conv_1234_234x: as_u32(dst) = as_u32(src) << 8; goto CONV_END;
+conv_1234_xxxC: as_u8(dst) = (as_u32(src) & 0xff) ^ 0x80; goto CONV_END;
+conv_1234_xxC3: as_u16(dst) = swab16(as_u32(src) ^ 0x80); goto CONV_END;
+conv_1234_xx3C: as_u16(dst) = (as_u32(src) & 0xffff) ^ 0x80; goto CONV_END;
+conv_1234_xC32: as_u32(dst) = (swab32(as_u32(src)) >> 8) ^ 0x800000; goto CONV_END;
+conv_1234_23Cx: as_u32(dst) = (as_u32(src) ^ 0x80) << 8; goto CONV_END;
+conv_1234_C321: as_u32(dst) = swab32(as_u32(src) ^ 0x80); goto CONV_END;
+conv_1234_123C: as_u32(dst) = as_u32(src) ^ 0x80; goto CONV_END;
+}
+#endif
+
+#ifdef GET_S16_LABELS
+/* src_wid src_endswap unsigned */
+static void *get_s16_labels[4 * 2 * 2] = {
+	&&get_s16_xxx1_xx10,	 /*  8h -> 16h */
+	&&get_s16_xxx1_xx90,	 /*  8h ^> 16h */
+	&&get_s16_xxx1_xx10,	 /*  8s -> 16h */
+	&&get_s16_xxx1_xx90,	 /*  8s ^> 16h */
+	&&get_s16_xx12_xx12,	 /* 16h -> 16h */
+	&&get_s16_xx12_xx92,	 /* 16h ^> 16h */
+	&&get_s16_xx12_xx21,	 /* 16s -> 16h */
+	&&get_s16_xx12_xxA1,	 /* 16s ^> 16h */
+	&&get_s16_x123_xx12,	 /* 24h -> 16h */
+	&&get_s16_x123_xx92,	 /* 24h ^> 16h */
+	&&get_s16_123x_xx32,	 /* 24s -> 16h */
+	&&get_s16_123x_xxB2,	 /* 24s ^> 16h */
+	&&get_s16_1234_xx12,	 /* 32h -> 16h */
+	&&get_s16_1234_xx92,	 /* 32h ^> 16h */
+	&&get_s16_1234_xx43,	 /* 32s -> 16h */
+	&&get_s16_1234_xxC3,	 /* 32s ^> 16h */
+};
+#endif
+
+#ifdef GET_S16_END
+while(0) {
+get_s16_xxx1_xx10: sample = (u_int16_t)as_u8(src) << 8; goto GET_S16_END;
+get_s16_xxx1_xx90: sample = (u_int16_t)(as_u8(src) ^ 0x80) << 8; goto GET_S16_END;
+get_s16_xx12_xx12: sample = as_u16(src); goto GET_S16_END;
+get_s16_xx12_xx92: sample = as_u16(src) ^ 0x8000; goto GET_S16_END;
+get_s16_xx12_xx21: sample = swab16(as_u16(src)); goto GET_S16_END;
+get_s16_xx12_xxA1: sample = swab16(as_u16(src) ^ 0x80); goto GET_S16_END;
+get_s16_x123_xx12: sample = as_u32(src) >> 8; goto GET_S16_END;
+get_s16_x123_xx92: sample = (as_u32(src) >> 8) ^ 0x8000; goto GET_S16_END;
+get_s16_123x_xx32: sample = swab16(as_u32(src) >> 8); goto GET_S16_END;
+get_s16_123x_xxB2: sample = swab16((as_u32(src) >> 8) ^ 0x8000); goto GET_S16_END;
+get_s16_1234_xx12: sample = as_u32(src) >> 16; goto GET_S16_END;
+get_s16_1234_xx92: sample = (as_u32(src) >> 16) ^ 0x8000; goto GET_S16_END;
+get_s16_1234_xx43: sample = swab16(as_u32(src)); goto GET_S16_END;
+get_s16_1234_xxC3: sample = swab16(as_u32(src) ^ 0x80); goto GET_S16_END;
+}
+#endif
+
+#ifdef PUT_S16_LABELS
+/* dst_wid dst_endswap unsigned */
+static void *put_s16_labels[4 * 2 * 2] = {
+	&&put_s16_xx12_xxx1,	 /* 16h ->  8h */
+	&&put_s16_xx12_xxx9,	 /* 16h ^>  8h */
+	&&put_s16_xx12_xxx1,	 /* 16h ->  8s */
+	&&put_s16_xx12_xxx9,	 /* 16h ^>  8s */
+	&&put_s16_xx12_xx12,	 /* 16h -> 16h */
+	&&put_s16_xx12_xx92,	 /* 16h ^> 16h */
+	&&put_s16_xx12_xx21,	 /* 16h -> 16s */
+	&&put_s16_xx12_xx29,	 /* 16h ^> 16s */
+	&&put_s16_xx12_x120,	 /* 16h -> 24h */
+	&&put_s16_xx12_x920,	 /* 16h ^> 24h */
+	&&put_s16_xx12_021x,	 /* 16h -> 24s */
+	&&put_s16_xx12_029x,	 /* 16h ^> 24s */
+	&&put_s16_xx12_1200,	 /* 16h -> 32h */
+	&&put_s16_xx12_9200,	 /* 16h ^> 32h */
+	&&put_s16_xx12_0021,	 /* 16h -> 32s */
+	&&put_s16_xx12_0029,	 /* 16h ^> 32s */
+};
+#endif
+
+#ifdef PUT_S16_END
+while (0) {
+put_s16_xx12_xxx1: as_u8(dst) = sample >> 8; goto PUT_S16_END;
+put_s16_xx12_xxx9: as_u8(dst) = (sample >> 8) ^ 0x80; goto PUT_S16_END;
+put_s16_xx12_xx12: as_u16(dst) = sample; goto PUT_S16_END;
+put_s16_xx12_xx92: as_u16(dst) = sample ^ 0x8000; goto PUT_S16_END;
+put_s16_xx12_xx21: as_u16(dst) = swab16(sample); goto PUT_S16_END;
+put_s16_xx12_xx29: as_u16(dst) = swab16(sample) ^ 0x80; goto PUT_S16_END;
+put_s16_xx12_x120: as_u32(dst) = (u_int32_t)sample << 8; goto PUT_S16_END;
+put_s16_xx12_x920: as_u32(dst) = (u_int32_t)(sample ^ 0x8000) << 8; goto PUT_S16_END;
+put_s16_xx12_021x: as_u32(dst) = (u_int32_t)swab16(sample) << 8; goto PUT_S16_END;
+put_s16_xx12_029x: as_u32(dst) = (u_int32_t)(swab16(sample) ^ 0x80) << 8; goto PUT_S16_END;
+put_s16_xx12_1200: as_u32(dst) = (u_int32_t)sample << 16; goto PUT_S16_END;
+put_s16_xx12_9200: as_u32(dst) = (u_int32_t)(sample ^ 0x8000) << 16; goto PUT_S16_END;
+put_s16_xx12_0021: as_u32(dst) = (u_int32_t)swab16(sample); goto PUT_S16_END;
+put_s16_xx12_0029: as_u32(dst) = (u_int32_t)swab16(sample) ^ 0x80; goto PUT_S16_END;
+}
+#endif
+
+#if 0
+#ifdef GET32_LABELS
+/* src_wid src_endswap unsigned */
+static void *get32_labels[4 * 2 * 2] = {
+	&&get32_xxx1_1000,	 /*  8h -> 32h */
+	&&get32_xxx1_9000,	 /*  8h ^> 32h */
+	&&get32_xxx1_1000,	 /*  8s -> 32h */
+	&&get32_xxx1_9000,	 /*  8s ^> 32h */
+	&&get32_xx12_1200,	 /* 16h -> 32h */
+	&&get32_xx12_9200,	 /* 16h ^> 32h */
+	&&get32_xx12_2100,	 /* 16s -> 32h */
+	&&get32_xx12_A100,	 /* 16s ^> 32h */
+	&&get32_x123_1230,	 /* 24h -> 32h */
+	&&get32_x123_9230,	 /* 24h ^> 32h */
+	&&get32_123x_3210,	 /* 24s -> 32h */
+	&&get32_123x_B210,	 /* 24s ^> 32h */
+	&&get32_1234_1234,	 /* 32h -> 32h */
+	&&get32_1234_9234,	 /* 32h ^> 32h */
+	&&get32_1234_4321,	 /* 32s -> 32h */
+	&&get32_1234_C321,	 /* 32s ^> 32h */
+};
+#endif
+
+#ifdef GET32_END
+while (0) {
+get32_xxx1_1000: sample = (u_int32_t)as_u8(src) << 24; goto GET32_END;
+get32_xxx1_9000: sample = (u_int32_t)(as_u8(src) ^ 0x80) << 24; goto GET32_END;
+get32_xx12_1200: sample = (u_int32_t)as_u16(src) << 16; goto GET32_END;
+get32_xx12_9200: sample = (u_int32_t)(as_u16(src) ^ 0x8000) << 16; goto GET32_END;
+get32_xx12_2100: sample = (u_int32_t)swab16(as_u16(src)) << 16; goto GET32_END;
+get32_xx12_A100: sample = (u_int32_t)swab16(as_u16(src) ^ 0x80) << 16; goto GET32_END;
+get32_x123_1230: sample = as_u32(src) << 8; goto GET32_END;
+get32_x123_9230: sample = (as_u32(src) << 8) ^ 0x80000000; goto GET32_END;
+get32_123x_3210: sample = swab32(as_u32(src) >> 8); goto GET32_END;
+get32_123x_B210: sample = swab32((as_u32(src) >> 8) ^ 0x80); goto GET32_END;
+get32_1234_1234: sample = as_u32(src); goto GET32_END;
+get32_1234_9234: sample = as_u32(src) ^ 0x80000000; goto GET32_END;
+get32_1234_4321: sample = swab32(as_u32(src)); goto GET32_END;
+get32_1234_C321: sample = swab32(as_u32(src) ^ 0x80); goto GET32_END;
+}
+#endif
+#endif
+
+#ifdef PUT_U32_LABELS
+/* dst_wid dst_endswap unsigned */
+static void *put_u32_labels[4 * 2 * 2] = {
+	&&put_u32_1234_xxx9,	 /* u32h ->  s8h */
+	&&put_u32_1234_xxx1,	 /* u32h ->  u8h */
+	&&put_u32_1234_xxx9,	 /* u32h ->  s8s */
+	&&put_u32_1234_xxx1,	 /* u32h ->  u8s */
+	&&put_u32_1234_xx92,	 /* u32h -> s16h */
+	&&put_u32_1234_xx12,	 /* u32h -> u16h */
+	&&put_u32_1234_xx29,	 /* u32h -> s16s */
+	&&put_u32_1234_xx21,	 /* u32h -> u16s */
+	&&put_u32_1234_x923,	 /* u32h -> s24h */
+	&&put_u32_1234_x123,	 /* u32h -> u24h */
+	&&put_u32_1234_329x,	 /* u32h -> s24s */
+	&&put_u32_1234_321x,	 /* u32h -> u24s */
+	&&put_u32_1234_9234,	 /* u32h -> s32h */
+	&&put_u32_1234_1234,	 /* u32h -> u32h */
+	&&put_u32_1234_4329,	 /* u32h -> s32s */
+	&&put_u32_1234_4321,	 /* u32h -> u32s */
+};
+#endif
+
+#ifdef PUT_U32_END
+while (0) {
+put_u32_1234_xxx1: as_u8(dst) = sample >> 24; goto PUT_U32_END;
+put_u32_1234_xxx9: as_u8(dst) = (sample >> 24) ^ 0x80; goto PUT_U32_END;
+put_u32_1234_xx12: as_u16(dst) = sample >> 16; goto PUT_U32_END;
+put_u32_1234_xx92: as_u16(dst) = (sample >> 16) ^ 0x8000; goto PUT_U32_END;
+put_u32_1234_xx21: as_u16(dst) = swab16(sample >> 16); goto PUT_U32_END;
+put_u32_1234_xx29: as_u16(dst) = swab16(sample >> 16) ^ 0x80; goto PUT_U32_END;
+put_u32_1234_x123: as_u32(dst) = sample >> 8; goto PUT_U32_END;
+put_u32_1234_x923: as_u32(dst) = (sample >> 8) ^ 0x800000; goto PUT_U32_END;
+put_u32_1234_321x: as_u32(dst) = swab32(sample) << 8; goto PUT_U32_END;
+put_u32_1234_329x: as_u32(dst) = (swab32(sample) ^ 0x80) << 8; goto PUT_U32_END;
+put_u32_1234_1234: as_u32(dst) = sample; goto PUT_U32_END;
+put_u32_1234_9234: as_u32(dst) = sample ^ 0x80000000; goto PUT_U32_END;
+put_u32_1234_4321: as_u32(dst) = swab32(sample); goto PUT_U32_END;
+put_u32_1234_4329: as_u32(dst) = swab32(sample) ^ 0x80; goto PUT_U32_END;
+}
+#endif
+
+#ifdef GET_U_LABELS
+/* width endswap unsigned*/
+static void *get_u_labels[4 * 2 * 2] = {
+	&&get_u_s8,	/* s8  ->  u8  */
+	&&get_u_u8,	/* u8  ->  u8  */
+	&&get_u_s8,	/* s8  ->  u8  */
+	&&get_u_u8,	/* u8  ->  u8  */
+	&&get_u_s16h,	/* s16h -> u16h */
+	&&get_u_u16h,	/* u16h -> u16h */
+	&&get_u_s16s,	/* s16s -> u16h */
+	&&get_u_u16s,	/* u16s -> u16h */
+	&&get_u_s24h,	/* s24h -> u32h */
+	&&get_u_u24h,	/* u24h -> u32h */
+	&&get_u_s24s,	/* s24s -> u32h */
+	&&get_u_u24s,	/* u24s -> u32h */
+	&&get_u_s32h,	/* s32h -> u32h */
+	&&get_u_u32h,	/* u32h -> u32h */
+	&&get_u_s32s,	/* s32s -> u32h */
+	&&get_u_u32s,	/* u32s -> u32h */
+};
+#endif
+
+#ifdef GET_U_END
+while (0) {
+get_u_s8: sample = as_u8(src) ^ 0x80; goto GET_U_END;
+get_u_u8: sample = as_u8(src); goto GET_U_END;
+get_u_s16h: sample = as_u16(src) ^ 0x8000; goto GET_U_END;
+get_u_u16h: sample = as_u16(src); goto GET_U_END;
+get_u_s16s: sample = swab16(as_u16(src) ^ 0x80); goto GET_U_END;
+get_u_u16s: sample = swab16(as_u16(src)); goto GET_U_END;
+get_u_s24h: sample = (as_u32(src) ^ 0x800000); goto GET_U_END;
+get_u_u24h: sample = as_u32(src); goto GET_U_END;
+get_u_s24s: sample = swab32(as_u32(src) ^ 0x800000); goto GET_U_END;
+get_u_u24s: sample = swab32(as_u32(src)); goto GET_U_END;
+get_u_s32h: sample = as_u32(src) ^ 0x80000000; goto GET_U_END;
+get_u_u32h: sample = as_u32(src); goto GET_U_END;
+get_u_s32s: sample = swab32(as_u32(src) ^ 0x80); goto GET_U_END;
+get_u_u32s: sample = swab32(as_u32(src)); goto GET_U_END;
+}
+#endif
+
+#if 0
+#ifdef PUT_LABELS
+/* width endswap unsigned */
+static void *put_labels[4 * 2 * 2] = {
+	&&put_s8,	/* s8  ->  s8  */
+	&&put_u8,	/* u8  ->  s8  */
+	&&put_s8,	/* s8  ->  s8  */
+	&&put_u8,	/* u8  ->  s8  */
+	&&put_s16h,	/* s16h -> s16h */
+	&&put_u16h,	/* u16h -> s16h */
+	&&put_s16s,	/* s16s -> s16h */
+	&&put_u16s,	/* u16s -> s16h */
+	&&put_s24h,	/* s24h -> s32h */
+	&&put_u24h,	/* u24h -> s32h */
+	&&put_s24s,	/* s24s -> s32h */
+	&&put_u24s,	/* u24s -> s32h */
+	&&put_s32h,	/* s32h -> s32h */
+	&&put_u32h,	/* u32h -> s32h */
+	&&put_s32s,	/* s32s -> s32h */
+	&&put_u32s,	/* u32s -> s32h */
+};
+#endif
+
+#ifdef PUT_END
+put_s8: as_s8(dst) = sample; goto PUT_END;
+put_u8: as_u8(dst) = sample ^ 0x80; goto PUT_END;
+put_s16h: as_s16(dst) = sample; goto PUT_END;
+put_u16h: as_u16(dst) = sample ^ 0x8000; goto PUT_END;
+put_s16s: as_s16(dst) = swab16(sample); goto PUT_END;
+put_u16s: as_u16(dst) = swab16(sample ^ 0x80); goto PUT_END;
+put_s24h: as_s24(dst) = sample & 0xffffff; goto PUT_END;
+put_u24h: as_u24(dst) = sample ^ 0x80000000; goto PUT_END;
+put_s24s: as_s24(dst) = swab32(sample & 0xffffff); goto PUT_END;
+put_u24s: as_u24(dst) = swab32(sample ^ 0x80); goto PUT_END;
+put_s32h: as_s32(dst) = sample; goto PUT_END;
+put_u32h: as_u32(dst) = sample ^ 0x80000000; goto PUT_END;
+put_s32s: as_s32(dst) = swab32(sample); goto PUT_END;
+put_u32s: as_u32(dst) = swab32(sample ^ 0x80); goto PUT_END;
+#endif
+#endif
+
+#undef as_u8
+#undef as_u16
+#undef as_u32
+#undef as_s8
+#undef as_s16
+#undef as_s32
diff --git a/sound/core/oss/rate.c b/sound/core/oss/rate.c
new file mode 100644
index 000000000000..1096ec186714
--- /dev/null
+++ b/sound/core/oss/rate.c
@@ -0,0 +1,378 @@
+/*
+ *  Rate conversion Plug-In
+ *  Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include "pcm_plugin.h"
+
+#define SHIFT	11
+#define BITS	(1<<SHIFT)
+#define R_MASK	(BITS-1)
+
+/*
+ *  Basic rate conversion plugin
+ */
+
+typedef struct {
+	signed short last_S1;
+	signed short last_S2;
+} rate_channel_t;
+ 
+typedef void (*rate_f)(snd_pcm_plugin_t *plugin,
+		       const snd_pcm_plugin_channel_t *src_channels,
+		       snd_pcm_plugin_channel_t *dst_channels,
+		       int src_frames, int dst_frames);
+
+typedef struct rate_private_data {
+	unsigned int pitch;
+	unsigned int pos;
+	rate_f func;
+	int get, put;
+	snd_pcm_sframes_t old_src_frames, old_dst_frames;
+	rate_channel_t channels[0];
+} rate_t;
+
+static void rate_init(snd_pcm_plugin_t *plugin)
+{
+	unsigned int channel;
+	rate_t *data = (rate_t *)plugin->extra_data;
+	data->pos = 0;
+	for (channel = 0; channel < plugin->src_format.channels; channel++) {
+		data->channels[channel].last_S1 = 0;
+		data->channels[channel].last_S2 = 0;
+	}
+}
+
+static void resample_expand(snd_pcm_plugin_t *plugin,
+			    const snd_pcm_plugin_channel_t *src_channels,
+			    snd_pcm_plugin_channel_t *dst_channels,
+			    int src_frames, int dst_frames)
+{
+	unsigned int pos = 0;
+	signed int val;
+	signed short S1, S2;
+	char *src, *dst;
+	unsigned int channel;
+	int src_step, dst_step;
+	int src_frames1, dst_frames1;
+	rate_t *data = (rate_t *)plugin->extra_data;
+	rate_channel_t *rchannels = data->channels;
+
+#define GET_S16_LABELS
+#define PUT_S16_LABELS
+#include "plugin_ops.h"
+#undef GET_S16_LABELS
+#undef PUT_S16_LABELS
+	void *get = get_s16_labels[data->get];
+	void *put = put_s16_labels[data->put];
+	signed short sample = 0;
+	
+	for (channel = 0; channel < plugin->src_format.channels; channel++) {
+		pos = data->pos;
+		S1 = rchannels->last_S1;
+		S2 = rchannels->last_S2;
+		if (!src_channels[channel].enabled) {
+			if (dst_channels[channel].wanted)
+				snd_pcm_area_silence(&dst_channels[channel].area, 0, dst_frames, plugin->dst_format.format);
+			dst_channels[channel].enabled = 0;
+			continue;
+		}
+		dst_channels[channel].enabled = 1;
+		src = (char *)src_channels[channel].area.addr + src_channels[channel].area.first / 8;
+		dst = (char *)dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
+		src_step = src_channels[channel].area.step / 8;
+		dst_step = dst_channels[channel].area.step / 8;
+		src_frames1 = src_frames;
+		dst_frames1 = dst_frames;
+		while (dst_frames1-- > 0) {
+			if (pos & ~R_MASK) {
+				pos &= R_MASK;
+				S1 = S2;
+				if (src_frames1-- > 0) {
+					goto *get;
+#define GET_S16_END after_get
+#include "plugin_ops.h"
+#undef GET_S16_END
+				after_get:
+					S2 = sample;
+					src += src_step;
+				}
+			}
+			val = S1 + ((S2 - S1) * (signed int)pos) / BITS;
+			if (val < -32768)
+				val = -32768;
+			else if (val > 32767)
+				val = 32767;
+			sample = val;
+			goto *put;
+#define PUT_S16_END after_put
+#include "plugin_ops.h"
+#undef PUT_S16_END
+		after_put:
+			dst += dst_step;
+			pos += data->pitch;
+		}
+		rchannels->last_S1 = S1;
+		rchannels->last_S2 = S2;
+		rchannels++;
+	}
+	data->pos = pos;
+}
+
+static void resample_shrink(snd_pcm_plugin_t *plugin,
+			    const snd_pcm_plugin_channel_t *src_channels,
+			    snd_pcm_plugin_channel_t *dst_channels,
+			    int src_frames, int dst_frames)
+{
+	unsigned int pos = 0;
+	signed int val;
+	signed short S1, S2;
+	char *src, *dst;
+	unsigned int channel;
+	int src_step, dst_step;
+	int src_frames1, dst_frames1;
+	rate_t *data = (rate_t *)plugin->extra_data;
+	rate_channel_t *rchannels = data->channels;
+	
+#define GET_S16_LABELS
+#define PUT_S16_LABELS
+#include "plugin_ops.h"
+#undef GET_S16_LABELS
+#undef PUT_S16_LABELS
+	void *get = get_s16_labels[data->get];
+	void *put = put_s16_labels[data->put];
+	signed short sample = 0;
+
+	for (channel = 0; channel < plugin->src_format.channels; ++channel) {
+		pos = data->pos;
+		S1 = rchannels->last_S1;
+		S2 = rchannels->last_S2;
+		if (!src_channels[channel].enabled) {
+			if (dst_channels[channel].wanted)
+				snd_pcm_area_silence(&dst_channels[channel].area, 0, dst_frames, plugin->dst_format.format);
+			dst_channels[channel].enabled = 0;
+			continue;
+		}
+		dst_channels[channel].enabled = 1;
+		src = (char *)src_channels[channel].area.addr + src_channels[channel].area.first / 8;
+		dst = (char *)dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
+		src_step = src_channels[channel].area.step / 8;
+		dst_step = dst_channels[channel].area.step / 8;
+		src_frames1 = src_frames;
+		dst_frames1 = dst_frames;
+		while (dst_frames1 > 0) {
+			S1 = S2;
+			if (src_frames1-- > 0) {
+				goto *get;
+#define GET_S16_END after_get
+#include "plugin_ops.h"
+#undef GET_S16_END
+			after_get:
+				S2 = sample;
+				src += src_step;
+			}
+			if (pos & ~R_MASK) {
+				pos &= R_MASK;
+				val = S1 + ((S2 - S1) * (signed int)pos) / BITS;
+				if (val < -32768)
+					val = -32768;
+				else if (val > 32767)
+					val = 32767;
+				sample = val;
+				goto *put;
+#define PUT_S16_END after_put
+#include "plugin_ops.h"
+#undef PUT_S16_END
+			after_put:
+				dst += dst_step;
+				dst_frames1--;
+			}
+			pos += data->pitch;
+		}
+		rchannels->last_S1 = S1;
+		rchannels->last_S2 = S2;
+		rchannels++;
+	}
+	data->pos = pos;
+}
+
+static snd_pcm_sframes_t rate_src_frames(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t frames)
+{
+	rate_t *data;
+	snd_pcm_sframes_t res;
+
+	snd_assert(plugin != NULL, return -ENXIO);
+	if (frames == 0)
+		return 0;
+	data = (rate_t *)plugin->extra_data;
+	if (plugin->src_format.rate < plugin->dst_format.rate) {
+		res = (((frames * data->pitch) + (BITS/2)) >> SHIFT);
+	} else {
+		res = (((frames << SHIFT) + (data->pitch / 2)) / data->pitch);		
+	}
+	if (data->old_src_frames > 0) {
+		snd_pcm_sframes_t frames1 = frames, res1 = data->old_dst_frames;
+		while (data->old_src_frames < frames1) {
+			frames1 >>= 1;
+			res1 <<= 1;
+		}
+		while (data->old_src_frames > frames1) {
+			frames1 <<= 1;
+			res1 >>= 1;
+		}
+		if (data->old_src_frames == frames1)
+			return res1;
+	}
+	data->old_src_frames = frames;
+	data->old_dst_frames = res;
+	return res;
+}
+
+static snd_pcm_sframes_t rate_dst_frames(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t frames)
+{
+	rate_t *data;
+	snd_pcm_sframes_t res;
+
+	snd_assert(plugin != NULL, return -ENXIO);
+	if (frames == 0)
+		return 0;
+	data = (rate_t *)plugin->extra_data;
+	if (plugin->src_format.rate < plugin->dst_format.rate) {
+		res = (((frames << SHIFT) + (data->pitch / 2)) / data->pitch);
+	} else {
+		res = (((frames * data->pitch) + (BITS/2)) >> SHIFT);
+	}
+	if (data->old_dst_frames > 0) {
+		snd_pcm_sframes_t frames1 = frames, res1 = data->old_src_frames;
+		while (data->old_dst_frames < frames1) {
+			frames1 >>= 1;
+			res1 <<= 1;
+		}
+		while (data->old_dst_frames > frames1) {
+			frames1 <<= 1;
+			res1 >>= 1;
+		}
+		if (data->old_dst_frames == frames1)
+			return res1;
+	}
+	data->old_dst_frames = frames;
+	data->old_src_frames = res;
+	return res;
+}
+
+static snd_pcm_sframes_t rate_transfer(snd_pcm_plugin_t *plugin,
+			     const snd_pcm_plugin_channel_t *src_channels,
+			     snd_pcm_plugin_channel_t *dst_channels,
+			     snd_pcm_uframes_t frames)
+{
+	snd_pcm_uframes_t dst_frames;
+	rate_t *data;
+
+	snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO);
+	if (frames == 0)
+		return 0;
+#ifdef CONFIG_SND_DEBUG
+	{
+		unsigned int channel;
+		for (channel = 0; channel < plugin->src_format.channels; channel++) {
+			snd_assert(src_channels[channel].area.first % 8 == 0 &&
+				   src_channels[channel].area.step % 8 == 0,
+				   return -ENXIO);
+			snd_assert(dst_channels[channel].area.first % 8 == 0 &&
+				   dst_channels[channel].area.step % 8 == 0,
+				   return -ENXIO);
+		}
+	}
+#endif
+
+	dst_frames = rate_dst_frames(plugin, frames);
+	if (dst_frames > dst_channels[0].frames)
+		dst_frames = dst_channels[0].frames;
+	data = (rate_t *)plugin->extra_data;
+	data->func(plugin, src_channels, dst_channels, frames, dst_frames);
+	return dst_frames;
+}
+
+static int rate_action(snd_pcm_plugin_t *plugin,
+		       snd_pcm_plugin_action_t action,
+		       unsigned long udata ATTRIBUTE_UNUSED)
+{
+	snd_assert(plugin != NULL, return -ENXIO);
+	switch (action) {
+	case INIT:
+	case PREPARE:
+		rate_init(plugin);
+		break;
+	default:
+		break;
+	}
+	return 0;	/* silenty ignore other actions */
+}
+
+int snd_pcm_plugin_build_rate(snd_pcm_plug_t *plug,
+			      snd_pcm_plugin_format_t *src_format,
+			      snd_pcm_plugin_format_t *dst_format,
+			      snd_pcm_plugin_t **r_plugin)
+{
+	int err;
+	rate_t *data;
+	snd_pcm_plugin_t *plugin;
+
+	snd_assert(r_plugin != NULL, return -ENXIO);
+	*r_plugin = NULL;
+
+	snd_assert(src_format->channels == dst_format->channels, return -ENXIO);
+	snd_assert(src_format->channels > 0, return -ENXIO);
+	snd_assert(snd_pcm_format_linear(src_format->format) != 0, return -ENXIO);
+	snd_assert(snd_pcm_format_linear(dst_format->format) != 0, return -ENXIO);
+	snd_assert(src_format->rate != dst_format->rate, return -ENXIO);
+
+	err = snd_pcm_plugin_build(plug, "rate conversion",
+				   src_format, dst_format,
+				   sizeof(rate_t) + src_format->channels * sizeof(rate_channel_t),
+				   &plugin);
+	if (err < 0)
+		return err;
+	data = (rate_t *)plugin->extra_data;
+	data->get = getput_index(src_format->format);
+	snd_assert(data->get >= 0 && data->get < 4*2*2, return -EINVAL);
+	data->put = getput_index(dst_format->format);
+	snd_assert(data->put >= 0 && data->put < 4*2*2, return -EINVAL);
+
+	if (src_format->rate < dst_format->rate) {
+		data->pitch = ((src_format->rate << SHIFT) + (dst_format->rate >> 1)) / dst_format->rate;
+		data->func = resample_expand;
+	} else {
+		data->pitch = ((dst_format->rate << SHIFT) + (src_format->rate >> 1)) / src_format->rate;
+		data->func = resample_shrink;
+	}
+	data->pos = 0;
+	rate_init(plugin);
+	data->old_src_frames = data->old_dst_frames = 0;
+	plugin->transfer = rate_transfer;
+	plugin->src_frames = rate_src_frames;
+	plugin->dst_frames = rate_dst_frames;
+	plugin->action = rate_action;
+	*r_plugin = plugin;
+	return 0;
+}
diff --git a/sound/core/oss/route.c b/sound/core/oss/route.c
new file mode 100644
index 000000000000..c955b7dfdb30
--- /dev/null
+++ b/sound/core/oss/route.c
@@ -0,0 +1,519 @@
+/*
+ *  Attenuated route Plug-In
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include "pcm_plugin.h"
+
+/* The best possible hack to support missing optimization in gcc 2.7.2.3 */
+#if ROUTE_PLUGIN_RESOLUTION & (ROUTE_PLUGIN_RESOLUTION - 1) != 0
+#define div(a) a /= ROUTE_PLUGIN_RESOLUTION
+#elif ROUTE_PLUGIN_RESOLUTION == 16
+#define div(a) a >>= 4
+#else
+#error "Add some code here"
+#endif
+
+typedef struct ttable_dst ttable_dst_t;
+typedef struct route_private_data route_t;
+
+typedef void (*route_channel_f)(snd_pcm_plugin_t *plugin,
+			      const snd_pcm_plugin_channel_t *src_channels,
+			      snd_pcm_plugin_channel_t *dst_channel,
+			      ttable_dst_t* ttable, snd_pcm_uframes_t frames);
+
+typedef struct {
+	int channel;
+	int as_int;
+} ttable_src_t;
+
+struct ttable_dst {
+	int att;	/* Attenuated */
+	unsigned int nsrcs;
+	ttable_src_t* srcs;
+	route_channel_f func;
+};
+
+struct route_private_data {
+	enum {R_UINT32=0, R_UINT64=1} sum_type;
+	int get, put;
+	int conv;
+	int src_sample_size;
+	ttable_dst_t ttable[0];
+};
+
+typedef union {
+	u_int32_t as_uint32;
+	u_int64_t as_uint64;
+} sum_t;
+
+
+static void route_to_channel_from_zero(snd_pcm_plugin_t *plugin,
+				     const snd_pcm_plugin_channel_t *src_channels ATTRIBUTE_UNUSED,
+				     snd_pcm_plugin_channel_t *dst_channel,
+				     ttable_dst_t* ttable ATTRIBUTE_UNUSED, snd_pcm_uframes_t frames)
+{
+	if (dst_channel->wanted)
+		snd_pcm_area_silence(&dst_channel->area, 0, frames, plugin->dst_format.format);
+	dst_channel->enabled = 0;
+}
+
+static void route_to_channel_from_one(snd_pcm_plugin_t *plugin,
+				    const snd_pcm_plugin_channel_t *src_channels,
+				    snd_pcm_plugin_channel_t *dst_channel,
+				    ttable_dst_t* ttable, snd_pcm_uframes_t frames)
+{
+#define CONV_LABELS
+#include "plugin_ops.h"
+#undef CONV_LABELS
+	route_t *data = (route_t *)plugin->extra_data;
+	void *conv;
+	const snd_pcm_plugin_channel_t *src_channel = NULL;
+	unsigned int srcidx;
+	char *src, *dst;
+	int src_step, dst_step;
+	for (srcidx = 0; srcidx < ttable->nsrcs; ++srcidx) {
+		src_channel = &src_channels[ttable->srcs[srcidx].channel];
+		if (src_channel->area.addr != NULL)
+			break;
+	}
+	if (srcidx == ttable->nsrcs) {
+		route_to_channel_from_zero(plugin, src_channels, dst_channel, ttable, frames);
+		return;
+	}
+
+	dst_channel->enabled = 1;
+	conv = conv_labels[data->conv];
+	src = src_channel->area.addr + src_channel->area.first / 8;
+	src_step = src_channel->area.step / 8;
+	dst = dst_channel->area.addr + dst_channel->area.first / 8;
+	dst_step = dst_channel->area.step / 8;
+	while (frames-- > 0) {
+		goto *conv;
+#define CONV_END after
+#include "plugin_ops.h"
+#undef CONV_END
+	after:
+		src += src_step;
+		dst += dst_step;
+	}
+}
+
+static void route_to_channel(snd_pcm_plugin_t *plugin,
+			   const snd_pcm_plugin_channel_t *src_channels,
+			   snd_pcm_plugin_channel_t *dst_channel,
+			   ttable_dst_t* ttable, snd_pcm_uframes_t frames)
+{
+#define GET_U_LABELS
+#define PUT_U32_LABELS
+#include "plugin_ops.h"
+#undef GET_U_LABELS
+#undef PUT_U32_LABELS
+	static void *zero_labels[2] = { &&zero_int32, &&zero_int64 };
+	/* sum_type att */
+	static void *add_labels[2 * 2] = { &&add_int32_noatt, &&add_int32_att,
+				    &&add_int64_noatt, &&add_int64_att,
+	};
+	/* sum_type att shift */
+	static void *norm_labels[2 * 2 * 4] = { NULL,
+					 &&norm_int32_8_noatt,
+					 &&norm_int32_16_noatt,
+					 &&norm_int32_24_noatt,
+					 NULL,
+					 &&norm_int32_8_att,
+					 &&norm_int32_16_att,
+					 &&norm_int32_24_att,
+					 &&norm_int64_0_noatt,
+					 &&norm_int64_8_noatt,
+					 &&norm_int64_16_noatt,
+					 &&norm_int64_24_noatt,
+					 &&norm_int64_0_att,
+					 &&norm_int64_8_att,
+					 &&norm_int64_16_att,
+					 &&norm_int64_24_att,
+	};
+	route_t *data = (route_t *)plugin->extra_data;
+	void *zero, *get, *add, *norm, *put_u32;
+	int nsrcs = ttable->nsrcs;
+	char *dst;
+	int dst_step;
+	char *srcs[nsrcs];
+	int src_steps[nsrcs];
+	ttable_src_t src_tt[nsrcs];
+	u_int32_t sample = 0;
+	int srcidx, srcidx1 = 0;
+	for (srcidx = 0; srcidx < nsrcs; ++srcidx) {
+		const snd_pcm_plugin_channel_t *src_channel = &src_channels[ttable->srcs[srcidx].channel];
+		if (!src_channel->enabled)
+			continue;
+		srcs[srcidx1] = src_channel->area.addr + src_channel->area.first / 8;
+		src_steps[srcidx1] = src_channel->area.step / 8;
+		src_tt[srcidx1] = ttable->srcs[srcidx];
+		srcidx1++;
+	}
+	nsrcs = srcidx1;
+	if (nsrcs == 0) {
+		route_to_channel_from_zero(plugin, src_channels, dst_channel, ttable, frames);
+		return;
+	} else if (nsrcs == 1 && src_tt[0].as_int == ROUTE_PLUGIN_RESOLUTION) {
+		route_to_channel_from_one(plugin, src_channels, dst_channel, ttable, frames);
+		return;
+	}
+
+	dst_channel->enabled = 1;
+	zero = zero_labels[data->sum_type];
+	get = get_u_labels[data->get];
+	add = add_labels[data->sum_type * 2 + ttable->att];
+	norm = norm_labels[data->sum_type * 8 + ttable->att * 4 + 4 - data->src_sample_size];
+	put_u32 = put_u32_labels[data->put];
+	dst = dst_channel->area.addr + dst_channel->area.first / 8;
+	dst_step = dst_channel->area.step / 8;
+
+	while (frames-- > 0) {
+		ttable_src_t *ttp = src_tt;
+		sum_t sum;
+
+		/* Zero sum */
+		goto *zero;
+	zero_int32:
+		sum.as_uint32 = 0;
+		goto zero_end;
+	zero_int64: 
+		sum.as_uint64 = 0;
+		goto zero_end;
+	zero_end:
+		for (srcidx = 0; srcidx < nsrcs; ++srcidx) {
+			char *src = srcs[srcidx];
+			
+			/* Get sample */
+			goto *get;
+#define GET_U_END after_get
+#include "plugin_ops.h"
+#undef GET_U_END
+		after_get:
+
+			/* Sum */
+			goto *add;
+		add_int32_att:
+			sum.as_uint32 += sample * ttp->as_int;
+			goto after_sum;
+		add_int32_noatt:
+			if (ttp->as_int)
+				sum.as_uint32 += sample;
+			goto after_sum;
+		add_int64_att:
+			sum.as_uint64 += (u_int64_t) sample * ttp->as_int;
+			goto after_sum;
+		add_int64_noatt:
+			if (ttp->as_int)
+				sum.as_uint64 += sample;
+			goto after_sum;
+		after_sum:
+			srcs[srcidx] += src_steps[srcidx];
+			ttp++;
+		}
+		
+		/* Normalization */
+		goto *norm;
+	norm_int32_8_att:
+		sum.as_uint64 = sum.as_uint32;
+	norm_int64_8_att:
+		sum.as_uint64 <<= 8;
+	norm_int64_0_att:
+		div(sum.as_uint64);
+		goto norm_int;
+
+	norm_int32_16_att:
+		sum.as_uint64 = sum.as_uint32;
+	norm_int64_16_att:
+		sum.as_uint64 <<= 16;
+		div(sum.as_uint64);
+		goto norm_int;
+
+	norm_int32_24_att:
+		sum.as_uint64 = sum.as_uint32;
+	norm_int64_24_att:
+		sum.as_uint64 <<= 24;
+		div(sum.as_uint64);
+		goto norm_int;
+
+	norm_int32_8_noatt:
+		sum.as_uint64 = sum.as_uint32;
+	norm_int64_8_noatt:
+		sum.as_uint64 <<= 8;
+		goto norm_int;
+
+	norm_int32_16_noatt:
+		sum.as_uint64 = sum.as_uint32;
+	norm_int64_16_noatt:
+		sum.as_uint64 <<= 16;
+		goto norm_int;
+
+	norm_int32_24_noatt:
+		sum.as_uint64 = sum.as_uint32;
+	norm_int64_24_noatt:
+		sum.as_uint64 <<= 24;
+		goto norm_int;
+
+	norm_int64_0_noatt:
+	norm_int:
+		if (sum.as_uint64 > (u_int32_t)0xffffffff)
+			sample = (u_int32_t)0xffffffff;
+		else
+			sample = sum.as_uint64;
+		goto after_norm;
+
+	after_norm:
+		
+		/* Put sample */
+		goto *put_u32;
+#define PUT_U32_END after_put_u32
+#include "plugin_ops.h"
+#undef PUT_U32_END
+	after_put_u32:
+		
+		dst += dst_step;
+	}
+}
+
+static int route_src_channels_mask(snd_pcm_plugin_t *plugin,
+				   bitset_t *dst_vmask,
+				   bitset_t **src_vmask)
+{
+	route_t *data = (route_t *)plugin->extra_data;
+	int schannels = plugin->src_format.channels;
+	int dchannels = plugin->dst_format.channels;
+	bitset_t *vmask = plugin->src_vmask;
+	int channel;
+	ttable_dst_t *dp = data->ttable;
+	bitset_zero(vmask, schannels);
+	for (channel = 0; channel < dchannels; channel++, dp++) {
+		unsigned int src;
+		ttable_src_t *sp;
+		if (!bitset_get(dst_vmask, channel))
+			continue;
+		sp = dp->srcs;
+		for (src = 0; src < dp->nsrcs; src++, sp++)
+			bitset_set(vmask, sp->channel);
+	}
+	*src_vmask = vmask;
+	return 0;
+}
+
+static int route_dst_channels_mask(snd_pcm_plugin_t *plugin,
+				   bitset_t *src_vmask,
+				   bitset_t **dst_vmask)
+{
+	route_t *data = (route_t *)plugin->extra_data;
+	int dchannels = plugin->dst_format.channels;
+	bitset_t *vmask = plugin->dst_vmask;
+	int channel;
+	ttable_dst_t *dp = data->ttable;
+	bitset_zero(vmask, dchannels);
+	for (channel = 0; channel < dchannels; channel++, dp++) {
+		unsigned int src;
+		ttable_src_t *sp;
+		sp = dp->srcs;
+		for (src = 0; src < dp->nsrcs; src++, sp++) {
+			if (bitset_get(src_vmask, sp->channel)) {
+				bitset_set(vmask, channel);
+				break;
+			}
+		}
+	}
+	*dst_vmask = vmask;
+	return 0;
+}
+
+static void route_free(snd_pcm_plugin_t *plugin)
+{
+	route_t *data = (route_t *)plugin->extra_data;
+	unsigned int dst_channel;
+	for (dst_channel = 0; dst_channel < plugin->dst_format.channels; ++dst_channel) {
+		kfree(data->ttable[dst_channel].srcs);
+	}
+}
+
+static int route_load_ttable(snd_pcm_plugin_t *plugin, 
+			     const route_ttable_entry_t* src_ttable)
+{
+	route_t *data;
+	unsigned int src_channel, dst_channel;
+	const route_ttable_entry_t *sptr;
+	ttable_dst_t *dptr;
+	if (src_ttable == NULL)
+		return 0;
+	data = (route_t *)plugin->extra_data;
+	dptr = data->ttable;
+	sptr = src_ttable;
+	plugin->private_free = route_free;
+	for (dst_channel = 0; dst_channel < plugin->dst_format.channels; ++dst_channel) {
+		route_ttable_entry_t t = 0;
+		int att = 0;
+		int nsrcs = 0;
+		ttable_src_t srcs[plugin->src_format.channels];
+		for (src_channel = 0; src_channel < plugin->src_format.channels; ++src_channel) {
+			snd_assert(*sptr >= 0 || *sptr <= FULL, return -ENXIO);
+			if (*sptr != 0) {
+				srcs[nsrcs].channel = src_channel;
+				srcs[nsrcs].as_int = *sptr;
+				if (*sptr != FULL)
+					att = 1;
+				t += *sptr;
+				nsrcs++;
+			}
+			sptr++;
+		}
+		dptr->att = att;
+		dptr->nsrcs = nsrcs;
+		if (nsrcs == 0)
+			dptr->func = route_to_channel_from_zero;
+		else if (nsrcs == 1 && !att)
+			dptr->func = route_to_channel_from_one;
+		else
+			dptr->func = route_to_channel;
+		if (nsrcs > 0) {
+                        int srcidx;
+			dptr->srcs = kcalloc(nsrcs, sizeof(*srcs), GFP_KERNEL);
+                        for(srcidx = 0; srcidx < nsrcs; srcidx++)
+				dptr->srcs[srcidx] = srcs[srcidx];
+		} else
+			dptr->srcs = NULL;
+		dptr++;
+	}
+	return 0;
+}
+
+static snd_pcm_sframes_t route_transfer(snd_pcm_plugin_t *plugin,
+			      const snd_pcm_plugin_channel_t *src_channels,
+			      snd_pcm_plugin_channel_t *dst_channels,
+			      snd_pcm_uframes_t frames)
+{
+	route_t *data;
+	int src_nchannels, dst_nchannels;
+	int dst_channel;
+	ttable_dst_t *ttp;
+	snd_pcm_plugin_channel_t *dvp;
+
+	snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO);
+	if (frames == 0)
+		return 0;
+	data = (route_t *)plugin->extra_data;
+
+	src_nchannels = plugin->src_format.channels;
+	dst_nchannels = plugin->dst_format.channels;
+
+#ifdef CONFIG_SND_DEBUG
+	{
+		int src_channel;
+		for (src_channel = 0; src_channel < src_nchannels; ++src_channel) {
+			snd_assert(src_channels[src_channel].area.first % 8 == 0 ||
+				   src_channels[src_channel].area.step % 8 == 0,
+				   return -ENXIO);
+		}
+		for (dst_channel = 0; dst_channel < dst_nchannels; ++dst_channel) {
+			snd_assert(dst_channels[dst_channel].area.first % 8 == 0 ||
+				   dst_channels[dst_channel].area.step % 8 == 0,
+				   return -ENXIO);
+		}
+	}
+#endif
+
+	ttp = data->ttable;
+	dvp = dst_channels;
+	for (dst_channel = 0; dst_channel < dst_nchannels; ++dst_channel) {
+		ttp->func(plugin, src_channels, dvp, ttp, frames);
+		dvp++;
+		ttp++;
+	}
+	return frames;
+}
+
+int getput_index(int format)
+{
+	int sign, width, endian;
+	sign = !snd_pcm_format_signed(format);
+	width = snd_pcm_format_width(format) / 8 - 1;
+	if (width < 0 || width > 3) {
+		snd_printk(KERN_ERR "snd-pcm-oss: invalid format %d\n", format);
+		width = 0;
+	}
+#ifdef SNDRV_LITTLE_ENDIAN
+	endian = snd_pcm_format_big_endian(format);
+#else
+	endian = snd_pcm_format_little_endian(format);
+#endif
+	if (endian < 0)
+		endian = 0;
+	return width * 4 + endian * 2 + sign;
+}
+
+int snd_pcm_plugin_build_route(snd_pcm_plug_t *plug,
+			       snd_pcm_plugin_format_t *src_format,
+			       snd_pcm_plugin_format_t *dst_format,
+			       route_ttable_entry_t *ttable,
+			       snd_pcm_plugin_t **r_plugin)
+{
+	route_t *data;
+	snd_pcm_plugin_t *plugin;
+	int err;
+
+	snd_assert(r_plugin != NULL, return -ENXIO);
+	*r_plugin = NULL;
+	snd_assert(src_format->rate == dst_format->rate, return -ENXIO);
+	snd_assert(snd_pcm_format_linear(src_format->format) != 0 &&
+		   snd_pcm_format_linear(dst_format->format) != 0,
+		   return -ENXIO);
+
+	err = snd_pcm_plugin_build(plug, "attenuated route conversion",
+				   src_format, dst_format,
+				   sizeof(route_t) + sizeof(data->ttable[0]) * dst_format->channels,
+				   &plugin);
+	if (err < 0)
+		return err;
+
+	data = (route_t *) plugin->extra_data;
+
+	data->get = getput_index(src_format->format);
+	snd_assert(data->get >= 0 && data->get < 4*2*2, return -EINVAL);
+	data->put = getput_index(dst_format->format);
+	snd_assert(data->get >= 0 && data->get < 4*2*2, return -EINVAL);
+	data->conv = conv_index(src_format->format, dst_format->format);
+
+	if (snd_pcm_format_width(src_format->format) == 32)
+		data->sum_type = R_UINT64;
+	else
+		data->sum_type = R_UINT32;
+	data->src_sample_size = snd_pcm_format_width(src_format->format) / 8;
+
+	if ((err = route_load_ttable(plugin, ttable)) < 0) {
+		snd_pcm_plugin_free(plugin);
+		return err;
+	}
+	plugin->transfer = route_transfer;
+	plugin->src_channels_mask = route_src_channels_mask;
+	plugin->dst_channels_mask = route_dst_channels_mask;
+	*r_plugin = plugin;
+	return 0;
+}
diff --git a/sound/core/pcm.c b/sound/core/pcm.c
new file mode 100644
index 000000000000..8d94325529a8
--- /dev/null
+++ b/sound/core/pcm.c
@@ -0,0 +1,1074 @@
+/*
+ *  Digital Audio (PCM) abstract layer
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+#include <sound/info.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>, Abramo Bagnara <abramo@alsa-project.org>");
+MODULE_DESCRIPTION("Midlevel PCM code for ALSA.");
+MODULE_LICENSE("GPL");
+
+snd_pcm_t *snd_pcm_devices[SNDRV_CARDS * SNDRV_PCM_DEVICES];
+static LIST_HEAD(snd_pcm_notify_list);
+static DECLARE_MUTEX(register_mutex);
+
+static int snd_pcm_free(snd_pcm_t *pcm);
+static int snd_pcm_dev_free(snd_device_t *device);
+static int snd_pcm_dev_register(snd_device_t *device);
+static int snd_pcm_dev_disconnect(snd_device_t *device);
+static int snd_pcm_dev_unregister(snd_device_t *device);
+
+static int snd_pcm_control_ioctl(snd_card_t * card,
+				 snd_ctl_file_t * control,
+				 unsigned int cmd, unsigned long arg)
+{
+	unsigned int tmp;
+
+	tmp = card->number * SNDRV_PCM_DEVICES;
+	switch (cmd) {
+	case SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE:
+		{
+			int device;
+
+			if (get_user(device, (int __user *)arg))
+				return -EFAULT;
+			device = device < 0 ? 0 : device + 1;
+			while (device < SNDRV_PCM_DEVICES) {
+				if (snd_pcm_devices[tmp + device])
+					break;
+				device++;
+			}
+			if (device == SNDRV_PCM_DEVICES)
+				device = -1;
+			if (put_user(device, (int __user *)arg))
+				return -EFAULT;
+			return 0;
+		}
+	case SNDRV_CTL_IOCTL_PCM_INFO:
+		{
+			snd_pcm_info_t __user *info;
+			unsigned int device, subdevice;
+			snd_pcm_stream_t stream;
+			snd_pcm_t *pcm;
+			snd_pcm_str_t *pstr;
+			snd_pcm_substream_t *substream;
+			info = (snd_pcm_info_t __user *)arg;
+			if (get_user(device, &info->device))
+				return -EFAULT;
+			if (device >= SNDRV_PCM_DEVICES)
+				return -ENXIO;
+			pcm = snd_pcm_devices[tmp + device];
+			if (pcm == NULL)
+				return -ENXIO;
+			if (get_user(stream, &info->stream))
+				return -EFAULT;
+			if (stream < 0 || stream > 1)
+				return -EINVAL;
+			pstr = &pcm->streams[stream];
+			if (pstr->substream_count == 0)
+				return -ENOENT;
+			if (get_user(subdevice, &info->subdevice))
+				return -EFAULT;
+			if (subdevice >= pstr->substream_count)
+				return -ENXIO;
+			for (substream = pstr->substream; substream; substream = substream->next)
+				if (substream->number == (int)subdevice)
+					break;
+			if (substream == NULL)
+				return -ENXIO;
+			return snd_pcm_info_user(substream, info);
+		}
+	case SNDRV_CTL_IOCTL_PCM_PREFER_SUBDEVICE:
+		{
+			int val;
+			
+			if (get_user(val, (int __user *)arg))
+				return -EFAULT;
+			control->prefer_pcm_subdevice = val;
+			return 0;
+		}
+	}
+	return -ENOIOCTLCMD;
+}
+#define STATE(v) [SNDRV_PCM_STATE_##v] = #v
+#define STREAM(v) [SNDRV_PCM_STREAM_##v] = #v
+#define READY(v) [SNDRV_PCM_READY_##v] = #v
+#define XRUN(v) [SNDRV_PCM_XRUN_##v] = #v
+#define SILENCE(v) [SNDRV_PCM_SILENCE_##v] = #v
+#define TSTAMP(v) [SNDRV_PCM_TSTAMP_##v] = #v
+#define ACCESS(v) [SNDRV_PCM_ACCESS_##v] = #v
+#define START(v) [SNDRV_PCM_START_##v] = #v
+#define FORMAT(v) [SNDRV_PCM_FORMAT_##v] = #v
+#define SUBFORMAT(v) [SNDRV_PCM_SUBFORMAT_##v] = #v 
+
+static char *snd_pcm_stream_names[] = {
+	STREAM(PLAYBACK),
+	STREAM(CAPTURE),
+};
+
+static char *snd_pcm_state_names[] = {
+	STATE(OPEN),
+	STATE(SETUP),
+	STATE(PREPARED),
+	STATE(RUNNING),
+	STATE(XRUN),
+	STATE(DRAINING),
+	STATE(PAUSED),
+	STATE(SUSPENDED),
+};
+
+static char *snd_pcm_access_names[] = {
+	ACCESS(MMAP_INTERLEAVED), 
+	ACCESS(MMAP_NONINTERLEAVED),
+	ACCESS(MMAP_COMPLEX),
+	ACCESS(RW_INTERLEAVED),
+	ACCESS(RW_NONINTERLEAVED),
+};
+
+static char *snd_pcm_format_names[] = {
+	FORMAT(S8),
+	FORMAT(U8),
+	FORMAT(S16_LE),
+	FORMAT(S16_BE),
+	FORMAT(U16_LE),
+	FORMAT(U16_BE),
+	FORMAT(S24_LE),
+	FORMAT(S24_BE),
+	FORMAT(U24_LE),
+	FORMAT(U24_BE),
+	FORMAT(S32_LE),
+	FORMAT(S32_BE),
+	FORMAT(U32_LE),
+	FORMAT(U32_BE),
+	FORMAT(FLOAT_LE),
+	FORMAT(FLOAT_BE),
+	FORMAT(FLOAT64_LE),
+	FORMAT(FLOAT64_BE),
+	FORMAT(IEC958_SUBFRAME_LE),
+	FORMAT(IEC958_SUBFRAME_BE),
+	FORMAT(MU_LAW),
+	FORMAT(A_LAW),
+	FORMAT(IMA_ADPCM),
+	FORMAT(MPEG),
+	FORMAT(GSM),
+	FORMAT(SPECIAL),
+	FORMAT(S24_3LE),
+	FORMAT(S24_3BE),
+	FORMAT(U24_3LE),
+	FORMAT(U24_3BE),
+	FORMAT(S20_3LE),
+	FORMAT(S20_3BE),
+	FORMAT(U20_3LE),
+	FORMAT(U20_3BE),
+	FORMAT(S18_3LE),
+	FORMAT(S18_3BE),
+	FORMAT(U18_3LE),
+	FORMAT(U18_3BE),
+};
+
+static char *snd_pcm_subformat_names[] = {
+	SUBFORMAT(STD), 
+};
+
+static char *snd_pcm_tstamp_mode_names[] = {
+	TSTAMP(NONE),
+	TSTAMP(MMAP),
+};
+
+static const char *snd_pcm_stream_name(snd_pcm_stream_t stream)
+{
+	snd_assert(stream <= SNDRV_PCM_STREAM_LAST, return NULL);
+	return snd_pcm_stream_names[stream];
+}
+
+static const char *snd_pcm_access_name(snd_pcm_access_t access)
+{
+	snd_assert(access <= SNDRV_PCM_ACCESS_LAST, return NULL);
+	return snd_pcm_access_names[access];
+}
+
+const char *snd_pcm_format_name(snd_pcm_format_t format)
+{
+	snd_assert(format <= SNDRV_PCM_FORMAT_LAST, return NULL);
+	return snd_pcm_format_names[format];
+}
+
+static const char *snd_pcm_subformat_name(snd_pcm_subformat_t subformat)
+{
+	snd_assert(subformat <= SNDRV_PCM_SUBFORMAT_LAST, return NULL);
+	return snd_pcm_subformat_names[subformat];
+}
+
+static const char *snd_pcm_tstamp_mode_name(snd_pcm_tstamp_t mode)
+{
+	snd_assert(mode <= SNDRV_PCM_TSTAMP_LAST, return NULL);
+	return snd_pcm_tstamp_mode_names[mode];
+}
+
+static const char *snd_pcm_state_name(snd_pcm_state_t state)
+{
+	snd_assert(state <= SNDRV_PCM_STATE_LAST, return NULL);
+	return snd_pcm_state_names[state];
+}
+
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+#include <linux/soundcard.h>
+static const char *snd_pcm_oss_format_name(int format)
+{
+	switch (format) {
+	case AFMT_MU_LAW:
+		return "MU_LAW";
+	case AFMT_A_LAW:
+		return "A_LAW";
+	case AFMT_IMA_ADPCM:
+		return "IMA_ADPCM";
+	case AFMT_U8:
+		return "U8";
+	case AFMT_S16_LE:
+		return "S16_LE";
+	case AFMT_S16_BE:
+		return "S16_BE";
+	case AFMT_S8:
+		return "S8";
+	case AFMT_U16_LE:
+		return "U16_LE";
+	case AFMT_U16_BE:
+		return "U16_BE";
+	case AFMT_MPEG:
+		return "MPEG";
+	default:
+		return "unknown";
+	}
+}
+#endif
+
+#ifdef CONFIG_PROC_FS
+static void snd_pcm_proc_info_read(snd_pcm_substream_t *substream, snd_info_buffer_t *buffer)
+{
+	snd_pcm_info_t *info;
+	int err;
+
+	snd_runtime_check(substream, return);
+
+	info = kmalloc(sizeof(*info), GFP_KERNEL);
+	if (! info) {
+		printk(KERN_DEBUG "snd_pcm_proc_info_read: cannot malloc\n");
+		return;
+	}
+
+	err = snd_pcm_info(substream, info);
+	if (err < 0) {
+		snd_iprintf(buffer, "error %d\n", err);
+		kfree(info);
+		return;
+	}
+	snd_iprintf(buffer, "card: %d\n", info->card);
+	snd_iprintf(buffer, "device: %d\n", info->device);
+	snd_iprintf(buffer, "subdevice: %d\n", info->subdevice);
+	snd_iprintf(buffer, "stream: %s\n", snd_pcm_stream_name(info->stream));
+	snd_iprintf(buffer, "id: %s\n", info->id);
+	snd_iprintf(buffer, "name: %s\n", info->name);
+	snd_iprintf(buffer, "subname: %s\n", info->subname);
+	snd_iprintf(buffer, "class: %d\n", info->dev_class);
+	snd_iprintf(buffer, "subclass: %d\n", info->dev_subclass);
+	snd_iprintf(buffer, "subdevices_count: %d\n", info->subdevices_count);
+	snd_iprintf(buffer, "subdevices_avail: %d\n", info->subdevices_avail);
+	kfree(info);
+}
+
+static void snd_pcm_stream_proc_info_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+	snd_pcm_proc_info_read(((snd_pcm_str_t *)entry->private_data)->substream, buffer);
+}
+
+static void snd_pcm_substream_proc_info_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+	snd_pcm_proc_info_read((snd_pcm_substream_t *)entry->private_data, buffer);
+}
+
+static void snd_pcm_substream_proc_hw_params_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+	snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	if (!runtime) {
+		snd_iprintf(buffer, "closed\n");
+		return;
+	}
+	snd_pcm_stream_lock_irq(substream);
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+		snd_iprintf(buffer, "no setup\n");
+		snd_pcm_stream_unlock_irq(substream);
+		return;
+	}
+	snd_iprintf(buffer, "access: %s\n", snd_pcm_access_name(runtime->access));
+	snd_iprintf(buffer, "format: %s\n", snd_pcm_format_name(runtime->format));
+	snd_iprintf(buffer, "subformat: %s\n", snd_pcm_subformat_name(runtime->subformat));
+	snd_iprintf(buffer, "channels: %u\n", runtime->channels);	
+	snd_iprintf(buffer, "rate: %u (%u/%u)\n", runtime->rate, runtime->rate_num, runtime->rate_den);	
+	snd_iprintf(buffer, "period_size: %lu\n", runtime->period_size);	
+	snd_iprintf(buffer, "buffer_size: %lu\n", runtime->buffer_size);	
+	snd_iprintf(buffer, "tick_time: %u\n", runtime->tick_time);
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+	if (substream->oss.oss) {
+		snd_iprintf(buffer, "OSS format: %s\n", snd_pcm_oss_format_name(runtime->oss.format));
+		snd_iprintf(buffer, "OSS channels: %u\n", runtime->oss.channels);	
+		snd_iprintf(buffer, "OSS rate: %u\n", runtime->oss.rate);
+		snd_iprintf(buffer, "OSS period bytes: %lu\n", (unsigned long)runtime->oss.period_bytes);
+		snd_iprintf(buffer, "OSS periods: %u\n", runtime->oss.periods);
+		snd_iprintf(buffer, "OSS period frames: %lu\n", (unsigned long)runtime->oss.period_frames);
+	}
+#endif
+	snd_pcm_stream_unlock_irq(substream);
+}
+
+static void snd_pcm_substream_proc_sw_params_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+	snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	if (!runtime) {
+		snd_iprintf(buffer, "closed\n");
+		return;
+	}
+	snd_pcm_stream_lock_irq(substream);
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+		snd_iprintf(buffer, "no setup\n");
+		snd_pcm_stream_unlock_irq(substream);
+		return;
+	}
+	snd_iprintf(buffer, "tstamp_mode: %s\n", snd_pcm_tstamp_mode_name(runtime->tstamp_mode));
+	snd_iprintf(buffer, "period_step: %u\n", runtime->period_step);
+	snd_iprintf(buffer, "sleep_min: %u\n", runtime->sleep_min);
+	snd_iprintf(buffer, "avail_min: %lu\n", runtime->control->avail_min);
+	snd_iprintf(buffer, "xfer_align: %lu\n", runtime->xfer_align);
+	snd_iprintf(buffer, "start_threshold: %lu\n", runtime->start_threshold);
+	snd_iprintf(buffer, "stop_threshold: %lu\n", runtime->stop_threshold);
+	snd_iprintf(buffer, "silence_threshold: %lu\n", runtime->silence_threshold);
+	snd_iprintf(buffer, "silence_size: %lu\n", runtime->silence_size);
+	snd_iprintf(buffer, "boundary: %lu\n", runtime->boundary);
+	snd_pcm_stream_unlock_irq(substream);
+}
+
+static void snd_pcm_substream_proc_status_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+	snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_status_t status;
+	int err;
+	if (!runtime) {
+		snd_iprintf(buffer, "closed\n");
+		return;
+	}
+	memset(&status, 0, sizeof(status));
+	err = snd_pcm_status(substream, &status);
+	if (err < 0) {
+		snd_iprintf(buffer, "error %d\n", err);
+		return;
+	}
+	snd_iprintf(buffer, "state: %s\n", snd_pcm_state_name(status.state));
+	snd_iprintf(buffer, "trigger_time: %ld.%09ld\n",
+		status.trigger_tstamp.tv_sec, status.trigger_tstamp.tv_nsec);
+	snd_iprintf(buffer, "tstamp      : %ld.%09ld\n",
+		status.tstamp.tv_sec, status.tstamp.tv_nsec);
+	snd_iprintf(buffer, "delay       : %ld\n", status.delay);
+	snd_iprintf(buffer, "avail       : %ld\n", status.avail);
+	snd_iprintf(buffer, "avail_max   : %ld\n", status.avail_max);
+	snd_iprintf(buffer, "-----\n");
+	snd_iprintf(buffer, "hw_ptr      : %ld\n", runtime->status->hw_ptr);
+	snd_iprintf(buffer, "appl_ptr    : %ld\n", runtime->control->appl_ptr);
+}
+#endif
+
+#ifdef CONFIG_SND_DEBUG
+static void snd_pcm_xrun_debug_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+	snd_pcm_str_t *pstr = (snd_pcm_str_t *)entry->private_data;
+	snd_iprintf(buffer, "%d\n", pstr->xrun_debug);
+}
+
+static void snd_pcm_xrun_debug_write(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+	snd_pcm_str_t *pstr = (snd_pcm_str_t *)entry->private_data;
+	char line[64];
+	if (!snd_info_get_line(buffer, line, sizeof(line)))
+		pstr->xrun_debug = simple_strtoul(line, NULL, 10);
+}
+#endif
+
+static int snd_pcm_stream_proc_init(snd_pcm_str_t *pstr)
+{
+	snd_pcm_t *pcm = pstr->pcm;
+	snd_info_entry_t *entry;
+	char name[16];
+
+	sprintf(name, "pcm%i%c", pcm->device, 
+		pstr->stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c');
+	if ((entry = snd_info_create_card_entry(pcm->card, name, pcm->card->proc_root)) == NULL)
+		return -ENOMEM;
+	entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
+	if (snd_info_register(entry) < 0) {
+		snd_info_free_entry(entry);
+		return -ENOMEM;
+	}
+	pstr->proc_root = entry;
+
+	if ((entry = snd_info_create_card_entry(pcm->card, "info", pstr->proc_root)) != NULL) {
+		snd_info_set_text_ops(entry, pstr, 256, snd_pcm_stream_proc_info_read);
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	pstr->proc_info_entry = entry;
+
+#ifdef CONFIG_SND_DEBUG
+	if ((entry = snd_info_create_card_entry(pcm->card, "xrun_debug", pstr->proc_root)) != NULL) {
+		entry->c.text.read_size = 64;
+		entry->c.text.read = snd_pcm_xrun_debug_read;
+		entry->c.text.write_size = 64;
+		entry->c.text.write = snd_pcm_xrun_debug_write;
+		entry->private_data = pstr;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	pstr->proc_xrun_debug_entry = entry;
+#endif
+	return 0;
+}
+
+static int snd_pcm_stream_proc_done(snd_pcm_str_t *pstr)
+{
+#ifdef CONFIG_SND_DEBUG
+	if (pstr->proc_xrun_debug_entry) {
+		snd_info_unregister(pstr->proc_xrun_debug_entry);
+		pstr->proc_xrun_debug_entry = NULL;
+	}
+#endif
+	if (pstr->proc_info_entry) {
+		snd_info_unregister(pstr->proc_info_entry);
+		pstr->proc_info_entry = NULL;
+	}
+	if (pstr->proc_root) {
+		snd_info_unregister(pstr->proc_root);
+		pstr->proc_root = NULL;
+	}
+	return 0;
+}
+
+static int snd_pcm_substream_proc_init(snd_pcm_substream_t *substream)
+{
+	snd_info_entry_t *entry;
+	snd_card_t *card;
+	char name[16];
+
+	card = substream->pcm->card;
+
+	sprintf(name, "sub%i", substream->number);
+	if ((entry = snd_info_create_card_entry(card, name, substream->pstr->proc_root)) == NULL)
+		return -ENOMEM;
+	entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
+	if (snd_info_register(entry) < 0) {
+		snd_info_free_entry(entry);
+		return -ENOMEM;
+	}
+	substream->proc_root = entry;
+
+	if ((entry = snd_info_create_card_entry(card, "info", substream->proc_root)) != NULL) {
+		snd_info_set_text_ops(entry, substream, 256, snd_pcm_substream_proc_info_read);
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	substream->proc_info_entry = entry;
+
+	if ((entry = snd_info_create_card_entry(card, "hw_params", substream->proc_root)) != NULL) {
+		snd_info_set_text_ops(entry, substream, 256, snd_pcm_substream_proc_hw_params_read);
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	substream->proc_hw_params_entry = entry;
+
+	if ((entry = snd_info_create_card_entry(card, "sw_params", substream->proc_root)) != NULL) {
+		snd_info_set_text_ops(entry, substream, 256, snd_pcm_substream_proc_sw_params_read);
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	substream->proc_sw_params_entry = entry;
+
+	if ((entry = snd_info_create_card_entry(card, "status", substream->proc_root)) != NULL) {
+		snd_info_set_text_ops(entry, substream, 256, snd_pcm_substream_proc_status_read);
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	substream->proc_status_entry = entry;
+
+	return 0;
+}
+		
+static int snd_pcm_substream_proc_done(snd_pcm_substream_t *substream)
+{
+	if (substream->proc_info_entry) {
+		snd_info_unregister(substream->proc_info_entry);
+		substream->proc_info_entry = NULL;
+	}
+	if (substream->proc_hw_params_entry) {
+		snd_info_unregister(substream->proc_hw_params_entry);
+		substream->proc_hw_params_entry = NULL;
+	}
+	if (substream->proc_sw_params_entry) {
+		snd_info_unregister(substream->proc_sw_params_entry);
+		substream->proc_sw_params_entry = NULL;
+	}
+	if (substream->proc_status_entry) {
+		snd_info_unregister(substream->proc_status_entry);
+		substream->proc_status_entry = NULL;
+	}
+	if (substream->proc_root) {
+		snd_info_unregister(substream->proc_root);
+		substream->proc_root = NULL;
+	}
+	return 0;
+}
+
+/**
+ * snd_pcm_new_stream - create a new PCM stream
+ * @pcm: the pcm instance
+ * @stream: the stream direction, SNDRV_PCM_STREAM_XXX
+ * @substream_count: the number of substreams
+ *
+ * Creates a new stream for the pcm.
+ * The corresponding stream on the pcm must have been empty before
+ * calling this, i.e. zero must be given to the argument of
+ * snd_pcm_new().
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_new_stream(snd_pcm_t *pcm, int stream, int substream_count)
+{
+	int idx, err;
+	snd_pcm_str_t *pstr = &pcm->streams[stream];
+	snd_pcm_substream_t *substream, *prev;
+
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+	init_MUTEX(&pstr->oss.setup_mutex);
+#endif
+	pstr->stream = stream;
+	pstr->pcm = pcm;
+	pstr->substream_count = substream_count;
+	pstr->reg = &snd_pcm_reg[stream];
+	if (substream_count > 0) {
+		err = snd_pcm_stream_proc_init(pstr);
+		if (err < 0)
+			return err;
+	}
+	prev = NULL;
+	for (idx = 0, prev = NULL; idx < substream_count; idx++) {
+		substream = kcalloc(1, sizeof(*substream), GFP_KERNEL);
+		if (substream == NULL)
+			return -ENOMEM;
+		substream->pcm = pcm;
+		substream->pstr = pstr;
+		substream->number = idx;
+		substream->stream = stream;
+		sprintf(substream->name, "subdevice #%i", idx);
+		substream->buffer_bytes_max = UINT_MAX;
+		if (prev == NULL)
+			pstr->substream = substream;
+		else
+			prev->next = substream;
+		err = snd_pcm_substream_proc_init(substream);
+		if (err < 0) {
+			kfree(substream);
+			return err;
+		}
+		substream->group = &substream->self_group;
+		spin_lock_init(&substream->self_group.lock);
+		INIT_LIST_HEAD(&substream->self_group.substreams);
+		list_add_tail(&substream->link_list, &substream->self_group.substreams);
+		spin_lock_init(&substream->timer_lock);
+		prev = substream;
+	}
+	return 0;
+}				
+
+/**
+ * snd_pcm_new - create a new PCM instance
+ * @card: the card instance
+ * @id: the id string
+ * @device: the device index (zero based)
+ * @playback_count: the number of substreams for playback
+ * @capture_count: the number of substreams for capture
+ * @rpcm: the pointer to store the new pcm instance
+ *
+ * Creates a new PCM instance.
+ *
+ * The pcm operators have to be set afterwards to the new instance
+ * via snd_pcm_set_ops().
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_new(snd_card_t * card, char *id, int device,
+		int playback_count, int capture_count,
+	        snd_pcm_t ** rpcm)
+{
+	snd_pcm_t *pcm;
+	int err;
+	static snd_device_ops_t ops = {
+		.dev_free = snd_pcm_dev_free,
+		.dev_register =	snd_pcm_dev_register,
+		.dev_disconnect = snd_pcm_dev_disconnect,
+		.dev_unregister = snd_pcm_dev_unregister
+	};
+
+	snd_assert(rpcm != NULL, return -EINVAL);
+	*rpcm = NULL;
+	snd_assert(card != NULL, return -ENXIO);
+	pcm = kcalloc(1, sizeof(*pcm), GFP_KERNEL);
+	if (pcm == NULL)
+		return -ENOMEM;
+	pcm->card = card;
+	pcm->device = device;
+	if (id) {
+		strlcpy(pcm->id, id, sizeof(pcm->id));
+	}
+	if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
+		snd_pcm_free(pcm);
+		return err;
+	}
+	if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
+		snd_pcm_free(pcm);
+		return err;
+	}
+	init_MUTEX(&pcm->open_mutex);
+	init_waitqueue_head(&pcm->open_wait);
+	if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
+		snd_pcm_free(pcm);
+		return err;
+	}
+	*rpcm = pcm;
+	return 0;
+}
+
+static void snd_pcm_free_stream(snd_pcm_str_t * pstr)
+{
+	snd_pcm_substream_t *substream, *substream_next;
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+	snd_pcm_oss_setup_t *setup, *setupn;
+#endif
+	substream = pstr->substream;
+	while (substream) {
+		substream_next = substream->next;
+		snd_pcm_substream_proc_done(substream);
+		kfree(substream);
+		substream = substream_next;
+	}
+	snd_pcm_stream_proc_done(pstr);
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+	for (setup = pstr->oss.setup_list; setup; setup = setupn) {
+		setupn = setup->next;
+		kfree(setup->task_name);
+		kfree(setup);
+	}
+#endif
+}
+
+static int snd_pcm_free(snd_pcm_t *pcm)
+{
+	snd_assert(pcm != NULL, return -ENXIO);
+	if (pcm->private_free)
+		pcm->private_free(pcm);
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+	snd_pcm_free_stream(&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK]);
+	snd_pcm_free_stream(&pcm->streams[SNDRV_PCM_STREAM_CAPTURE]);
+	kfree(pcm);
+	return 0;
+}
+
+static int snd_pcm_dev_free(snd_device_t *device)
+{
+	snd_pcm_t *pcm = device->device_data;
+	return snd_pcm_free(pcm);
+}
+
+static void snd_pcm_tick_timer_func(unsigned long data)
+{
+	snd_pcm_substream_t *substream = (snd_pcm_substream_t*) data;
+	snd_pcm_tick_elapsed(substream);
+}
+
+int snd_pcm_open_substream(snd_pcm_t *pcm, int stream,
+			   snd_pcm_substream_t **rsubstream)
+{
+	snd_pcm_str_t * pstr;
+	snd_pcm_substream_t * substream;
+	snd_pcm_runtime_t * runtime;
+	snd_ctl_file_t *kctl;
+	snd_card_t *card;
+	struct list_head *list;
+	int prefer_subdevice = -1;
+	size_t size;
+
+	snd_assert(rsubstream != NULL, return -EINVAL);
+	*rsubstream = NULL;
+	snd_assert(pcm != NULL, return -ENXIO);
+	pstr = &pcm->streams[stream];
+	if (pstr->substream == NULL)
+		return -ENODEV;
+
+	card = pcm->card;
+	down_read(&card->controls_rwsem);
+	list_for_each(list, &card->ctl_files) {
+		kctl = snd_ctl_file(list);
+		if (kctl->pid == current->pid) {
+			prefer_subdevice = kctl->prefer_pcm_subdevice;
+			break;
+		}
+	}
+	up_read(&card->controls_rwsem);
+
+	if (pstr->substream_count == 0)
+		return -ENODEV;
+	switch (stream) {
+	case SNDRV_PCM_STREAM_PLAYBACK:
+		if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) {
+			for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; substream; substream = substream->next) {
+				if (SUBSTREAM_BUSY(substream))
+					return -EAGAIN;
+			}
+		}
+		break;
+	case SNDRV_PCM_STREAM_CAPTURE:
+		if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) {
+			for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next) {
+				if (SUBSTREAM_BUSY(substream))
+					return -EAGAIN;
+			}
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (prefer_subdevice >= 0) {
+		for (substream = pstr->substream; substream; substream = substream->next)
+			if (!SUBSTREAM_BUSY(substream) && substream->number == prefer_subdevice)
+				goto __ok;
+	}
+	for (substream = pstr->substream; substream; substream = substream->next)
+		if (!SUBSTREAM_BUSY(substream))
+			break;
+      __ok:
+	if (substream == NULL)
+		return -EAGAIN;
+
+	runtime = kcalloc(1, sizeof(*runtime), GFP_KERNEL);
+	if (runtime == NULL)
+		return -ENOMEM;
+
+	size = PAGE_ALIGN(sizeof(snd_pcm_mmap_status_t));
+	runtime->status = snd_malloc_pages(size, GFP_KERNEL);
+	if (runtime->status == NULL) {
+		kfree(runtime);
+		return -ENOMEM;
+	}
+	memset((void*)runtime->status, 0, size);
+
+	size = PAGE_ALIGN(sizeof(snd_pcm_mmap_control_t));
+	runtime->control = snd_malloc_pages(size, GFP_KERNEL);
+	if (runtime->control == NULL) {
+		snd_free_pages((void*)runtime->status, PAGE_ALIGN(sizeof(snd_pcm_mmap_status_t)));
+		kfree(runtime);
+		return -ENOMEM;
+	}
+	memset((void*)runtime->control, 0, size);
+
+	init_waitqueue_head(&runtime->sleep);
+	atomic_set(&runtime->mmap_count, 0);
+	init_timer(&runtime->tick_timer);
+	runtime->tick_timer.function = snd_pcm_tick_timer_func;
+	runtime->tick_timer.data = (unsigned long) substream;
+
+	runtime->status->state = SNDRV_PCM_STATE_OPEN;
+
+	substream->runtime = runtime;
+	substream->private_data = pcm->private_data;
+	pstr->substream_opened++;
+	*rsubstream = substream;
+	return 0;
+}
+
+void snd_pcm_release_substream(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t * runtime;
+	substream->file = NULL;
+	runtime = substream->runtime;
+	snd_assert(runtime != NULL, return);
+	if (runtime->private_free != NULL)
+		runtime->private_free(runtime);
+	snd_free_pages((void*)runtime->status, PAGE_ALIGN(sizeof(snd_pcm_mmap_status_t)));
+	snd_free_pages((void*)runtime->control, PAGE_ALIGN(sizeof(snd_pcm_mmap_control_t)));
+	kfree(runtime->hw_constraints.rules);
+	kfree(runtime);
+	substream->runtime = NULL;
+	substream->pstr->substream_opened--;
+}
+
+static int snd_pcm_dev_register(snd_device_t *device)
+{
+	int idx, cidx, err;
+	unsigned short minor;
+	snd_pcm_substream_t *substream;
+	struct list_head *list;
+	char str[16];
+	snd_pcm_t *pcm = device->device_data;
+
+	snd_assert(pcm != NULL && device != NULL, return -ENXIO);
+	down(&register_mutex);
+	idx = (pcm->card->number * SNDRV_PCM_DEVICES) + pcm->device;
+	if (snd_pcm_devices[idx]) {
+		up(&register_mutex);
+		return -EBUSY;
+	}
+	snd_pcm_devices[idx] = pcm;
+	for (cidx = 0; cidx < 2; cidx++) {
+		int devtype = -1;
+		if (pcm->streams[cidx].substream == NULL)
+			continue;
+		switch (cidx) {
+		case SNDRV_PCM_STREAM_PLAYBACK:
+			sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);
+			minor = SNDRV_MINOR_PCM_PLAYBACK + idx;
+			devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
+			break;
+		case SNDRV_PCM_STREAM_CAPTURE:
+			sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);
+			minor = SNDRV_MINOR_PCM_CAPTURE + idx;
+			devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
+			break;
+		}
+		if ((err = snd_register_device(devtype, pcm->card, pcm->device, pcm->streams[cidx].reg, str)) < 0) {
+			snd_pcm_devices[idx] = NULL;
+			up(&register_mutex);
+			return err;
+		}
+		for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
+			snd_pcm_timer_init(substream);
+	}
+	list_for_each(list, &snd_pcm_notify_list) {
+		snd_pcm_notify_t *notify;
+		notify = list_entry(list, snd_pcm_notify_t, list);
+		notify->n_register(pcm);
+	}
+	up(&register_mutex);
+	return 0;
+}
+
+static int snd_pcm_dev_disconnect(snd_device_t *device)
+{
+	snd_pcm_t *pcm = device->device_data;
+	struct list_head *list;
+	snd_pcm_substream_t *substream;
+	int idx, cidx;
+
+	down(&register_mutex);
+	idx = (pcm->card->number * SNDRV_PCM_DEVICES) + pcm->device;
+	snd_pcm_devices[idx] = NULL;
+	for (cidx = 0; cidx < 2; cidx++)
+		for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
+			if (substream->runtime)
+				substream->runtime->status->state = SNDRV_PCM_STATE_DISCONNECTED;
+	list_for_each(list, &snd_pcm_notify_list) {
+		snd_pcm_notify_t *notify;
+		notify = list_entry(list, snd_pcm_notify_t, list);
+		notify->n_disconnect(pcm);
+	}
+	up(&register_mutex);
+	return 0;
+}
+
+static int snd_pcm_dev_unregister(snd_device_t *device)
+{
+	int idx, cidx, devtype;
+	snd_pcm_substream_t *substream;
+	struct list_head *list;
+	snd_pcm_t *pcm = device->device_data;
+
+	snd_assert(pcm != NULL, return -ENXIO);
+	down(&register_mutex);
+	idx = (pcm->card->number * SNDRV_PCM_DEVICES) + pcm->device;
+	snd_pcm_devices[idx] = NULL;
+	for (cidx = 0; cidx < 2; cidx++) {
+		devtype = -1;
+		switch (cidx) {
+		case SNDRV_PCM_STREAM_PLAYBACK:
+			devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
+			break;
+		case SNDRV_PCM_STREAM_CAPTURE:
+			devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
+			break;
+		}
+		snd_unregister_device(devtype, pcm->card, pcm->device);
+		for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
+			snd_pcm_timer_done(substream);
+	}
+	list_for_each(list, &snd_pcm_notify_list) {
+		snd_pcm_notify_t *notify;
+		notify = list_entry(list, snd_pcm_notify_t, list);
+		notify->n_unregister(pcm);
+	}
+	up(&register_mutex);
+	return snd_pcm_free(pcm);
+}
+
+int snd_pcm_notify(snd_pcm_notify_t *notify, int nfree)
+{
+	int idx;
+
+	snd_assert(notify != NULL && notify->n_register != NULL && notify->n_unregister != NULL, return -EINVAL);
+	down(&register_mutex);
+	if (nfree) {
+		list_del(&notify->list);
+		for (idx = 0; idx < SNDRV_CARDS * SNDRV_PCM_DEVICES; idx++) {
+			if (snd_pcm_devices[idx] == NULL)
+				continue;
+			notify->n_unregister(snd_pcm_devices[idx]);
+		}
+	} else {
+		list_add_tail(&notify->list, &snd_pcm_notify_list);
+		for (idx = 0; idx < SNDRV_CARDS * SNDRV_PCM_DEVICES; idx++) {
+			if (snd_pcm_devices[idx] == NULL)
+				continue;
+			notify->n_register(snd_pcm_devices[idx]);
+		}
+	}
+	up(&register_mutex);
+	return 0;
+}
+
+/*
+ *  Info interface
+ */
+
+static void snd_pcm_proc_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+	int idx;
+	snd_pcm_t *pcm;
+
+	down(&register_mutex);
+	for (idx = 0; idx < SNDRV_CARDS * SNDRV_PCM_DEVICES; idx++) {
+		pcm = snd_pcm_devices[idx];
+		if (pcm == NULL)
+			continue;
+		snd_iprintf(buffer, "%02i-%02i: %s : %s", idx / SNDRV_PCM_DEVICES,
+			    idx % SNDRV_PCM_DEVICES, pcm->id, pcm->name);
+		if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream)
+			snd_iprintf(buffer, " : playback %i", pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count);
+		if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream)
+			snd_iprintf(buffer, " : capture %i", pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count);
+		snd_iprintf(buffer, "\n");
+	}
+	up(&register_mutex);
+}
+
+/*
+ *  ENTRY functions
+ */
+
+static snd_info_entry_t *snd_pcm_proc_entry = NULL;
+
+static int __init alsa_pcm_init(void)
+{
+	snd_info_entry_t *entry;
+
+	snd_ctl_register_ioctl(snd_pcm_control_ioctl);
+	snd_ctl_register_ioctl_compat(snd_pcm_control_ioctl);
+	if ((entry = snd_info_create_module_entry(THIS_MODULE, "pcm", NULL)) != NULL) {
+		snd_info_set_text_ops(entry, NULL, SNDRV_CARDS * SNDRV_PCM_DEVICES * 128, snd_pcm_proc_read);
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	snd_pcm_proc_entry = entry;
+	return 0;
+}
+
+static void __exit alsa_pcm_exit(void)
+{
+	snd_ctl_unregister_ioctl(snd_pcm_control_ioctl);
+	snd_ctl_unregister_ioctl_compat(snd_pcm_control_ioctl);
+	if (snd_pcm_proc_entry) {
+		snd_info_unregister(snd_pcm_proc_entry);
+		snd_pcm_proc_entry = NULL;
+	}
+}
+
+module_init(alsa_pcm_init)
+module_exit(alsa_pcm_exit)
+
+EXPORT_SYMBOL(snd_pcm_devices);
+EXPORT_SYMBOL(snd_pcm_new);
+EXPORT_SYMBOL(snd_pcm_new_stream);
+EXPORT_SYMBOL(snd_pcm_notify);
+EXPORT_SYMBOL(snd_pcm_open_substream);
+EXPORT_SYMBOL(snd_pcm_release_substream);
+EXPORT_SYMBOL(snd_pcm_format_name);
+  /* pcm_native.c */
+EXPORT_SYMBOL(snd_pcm_link_rwlock);
+EXPORT_SYMBOL(snd_pcm_start);
+#ifdef CONFIG_PM
+EXPORT_SYMBOL(snd_pcm_suspend);
+EXPORT_SYMBOL(snd_pcm_suspend_all);
+#endif
+EXPORT_SYMBOL(snd_pcm_kernel_playback_ioctl);
+EXPORT_SYMBOL(snd_pcm_kernel_capture_ioctl);
+EXPORT_SYMBOL(snd_pcm_kernel_ioctl);
+EXPORT_SYMBOL(snd_pcm_mmap_data);
+#if SNDRV_PCM_INFO_MMAP_IOMEM
+EXPORT_SYMBOL(snd_pcm_lib_mmap_iomem);
+#endif
+ /* pcm_misc.c */
+EXPORT_SYMBOL(snd_pcm_format_signed);
+EXPORT_SYMBOL(snd_pcm_format_unsigned);
+EXPORT_SYMBOL(snd_pcm_format_linear);
+EXPORT_SYMBOL(snd_pcm_format_little_endian);
+EXPORT_SYMBOL(snd_pcm_format_big_endian);
+EXPORT_SYMBOL(snd_pcm_format_width);
+EXPORT_SYMBOL(snd_pcm_format_physical_width);
+EXPORT_SYMBOL(snd_pcm_format_silence_64);
+EXPORT_SYMBOL(snd_pcm_format_set_silence);
+EXPORT_SYMBOL(snd_pcm_build_linear_format);
+EXPORT_SYMBOL(snd_pcm_limit_hw_rates);
diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c
new file mode 100644
index 000000000000..3920bf0eebbf
--- /dev/null
+++ b/sound/core/pcm_compat.c
@@ -0,0 +1,513 @@
+/*
+ *   32bit -> 64bit ioctl wrapper for PCM API
+ *   Copyright (c) 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
+ *
+ */
+
+/* This file included from pcm_native.c */
+
+#include <linux/compat.h>
+
+static int snd_pcm_ioctl_delay_compat(snd_pcm_substream_t *substream,
+				      s32 __user *src)
+{
+	snd_pcm_sframes_t delay;
+	mm_segment_t fs;
+	int err;
+
+	fs = snd_enter_user();
+	err = snd_pcm_delay(substream, &delay);
+	snd_leave_user(fs);
+	if (err < 0)
+		return err;
+	if (put_user(delay, src))
+		return -EFAULT;
+	return err;
+}
+
+static int snd_pcm_ioctl_rewind_compat(snd_pcm_substream_t *substream,
+				       u32 __user *src)
+{
+	snd_pcm_uframes_t frames;
+	int err;
+
+	if (get_user(frames, src))
+		return -EFAULT;
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		err = snd_pcm_playback_rewind(substream, frames);
+	else
+		err = snd_pcm_capture_rewind(substream, frames);
+	if (put_user(err, src))
+		return -EFAULT;
+	return err < 0 ? err : 0;
+}
+
+static int snd_pcm_ioctl_forward_compat(snd_pcm_substream_t *substream,
+				       u32 __user *src)
+{
+	snd_pcm_uframes_t frames;
+	int err;
+
+	if (get_user(frames, src))
+		return -EFAULT;
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		err = snd_pcm_playback_forward(substream, frames);
+	else
+		err = snd_pcm_capture_forward(substream, frames);
+	if (put_user(err, src))
+		return -EFAULT;
+	return err < 0 ? err : 0;
+}
+
+struct sndrv_pcm_hw_params32 {
+	u32 flags;
+	struct sndrv_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK + 1]; /* this must be identical */
+	struct sndrv_mask mres[5];	/* reserved masks */
+	struct sndrv_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];
+	struct sndrv_interval ires[9];	/* reserved intervals */
+	u32 rmask;
+	u32 cmask;
+	u32 info;
+	u32 msbits;
+	u32 rate_num;
+	u32 rate_den;
+	u32 fifo_size;
+	unsigned char reserved[64];
+};
+
+struct sndrv_pcm_sw_params32 {
+	s32 tstamp_mode;
+	u32 period_step;
+	u32 sleep_min;
+	u32 avail_min;
+	u32 xfer_align;
+	u32 start_threshold;
+	u32 stop_threshold;
+	u32 silence_threshold;
+	u32 silence_size;
+	u32 boundary;
+	unsigned char reserved[64];
+};
+
+static int snd_pcm_ioctl_sw_params_compat(snd_pcm_substream_t *substream,
+					  struct sndrv_pcm_sw_params32 __user *src)
+{
+	snd_pcm_sw_params_t params;
+	int err;
+
+	memset(&params, 0, sizeof(params));
+	if (get_user(params.tstamp_mode, &src->tstamp_mode) ||
+	    get_user(params.period_step, &src->period_step) ||
+	    get_user(params.sleep_min, &src->sleep_min) ||
+	    get_user(params.avail_min, &src->avail_min) ||
+	    get_user(params.xfer_align, &src->xfer_align) ||
+	    get_user(params.start_threshold, &src->start_threshold) ||
+	    get_user(params.stop_threshold, &src->stop_threshold) ||
+	    get_user(params.silence_threshold, &src->silence_threshold) ||
+	    get_user(params.silence_size, &src->silence_size))
+		return -EFAULT;
+	err = snd_pcm_sw_params(substream, &params);
+	if (err < 0)
+		return err;
+	if (put_user(params.boundary, &src->boundary))
+		return -EFAULT;
+	return err;
+}
+
+struct sndrv_pcm_channel_info32 {
+	u32 channel;
+	u32 offset;
+	u32 first;
+	u32 step;
+};
+
+static int snd_pcm_ioctl_channel_info_compat(snd_pcm_substream_t *substream,
+					     struct sndrv_pcm_channel_info32 __user *src)
+{
+	snd_pcm_channel_info_t info;
+	int err;
+
+	if (get_user(info.channel, &src->channel) ||
+	    get_user(info.offset, &src->offset) ||
+	    get_user(info.first, &src->first) ||
+	    get_user(info.step, &src->step))
+		return -EFAULT;
+	err = snd_pcm_channel_info(substream, &info);
+	if (err < 0)
+		return err;
+	if (put_user(info.channel, &src->channel) ||
+	    put_user(info.offset, &src->offset) ||
+	    put_user(info.first, &src->first) ||
+	    put_user(info.step, &src->step))
+		return -EFAULT;
+	return err;
+}
+
+struct sndrv_pcm_status32 {
+	s32 state;
+	struct compat_timespec trigger_tstamp;
+	struct compat_timespec tstamp;
+	u32 appl_ptr;
+	u32 hw_ptr;
+	s32 delay;
+	u32 avail;
+	u32 avail_max;
+	u32 overrange;
+	s32 suspended_state;
+	unsigned char reserved[60];
+} __attribute__((packed));
+
+
+static int snd_pcm_status_user_compat(snd_pcm_substream_t *substream,
+				      struct sndrv_pcm_status32 __user *src)
+{
+	snd_pcm_status_t status;
+	int err;
+
+	err = snd_pcm_status(substream, &status);
+	if (err < 0)
+		return err;
+
+	if (put_user(status.state, &src->state) ||
+	    put_user(status.trigger_tstamp.tv_sec, &src->trigger_tstamp.tv_sec) ||
+	    put_user(status.trigger_tstamp.tv_nsec, &src->trigger_tstamp.tv_nsec) ||
+	    put_user(status.tstamp.tv_sec, &src->tstamp.tv_sec) ||
+	    put_user(status.tstamp.tv_nsec, &src->tstamp.tv_nsec) ||
+	    put_user(status.appl_ptr, &src->appl_ptr) ||
+	    put_user(status.hw_ptr, &src->hw_ptr) ||
+	    put_user(status.delay, &src->delay) ||
+	    put_user(status.avail, &src->avail) ||
+	    put_user(status.avail_max, &src->avail_max) ||
+	    put_user(status.overrange, &src->overrange) ||
+	    put_user(status.suspended_state, &src->suspended_state))
+		return -EFAULT;
+
+	return err;
+}
+
+/* recalcuate the boundary within 32bit */
+static void recalculate_boundary(snd_pcm_runtime_t *runtime)
+{
+	if (! runtime->buffer_size)
+		return;
+	runtime->boundary = runtime->buffer_size;
+	while (runtime->boundary * 2 <= 0x7fffffffUL - runtime->buffer_size)
+		runtime->boundary *= 2;
+}
+
+/* both for HW_PARAMS and HW_REFINE */
+static int snd_pcm_ioctl_hw_params_compat(snd_pcm_substream_t *substream,
+					  int refine, 
+					  struct sndrv_pcm_hw_params32 __user *data32)
+{
+	struct sndrv_pcm_hw_params *data;
+	snd_pcm_runtime_t *runtime;
+	int err;
+
+	if (! (runtime = substream->runtime))
+		return -ENOTTY;
+
+	data = kmalloc(sizeof(*data), GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+	/* only fifo_size is different, so just copy all */
+	if (copy_from_user(data, data32, sizeof(*data32))) {
+		err = -EFAULT;
+		goto error;
+	}
+	if (refine)
+		err = snd_pcm_hw_refine(substream, data);
+	else
+		err = snd_pcm_hw_params(substream, data);
+	if (err < 0)
+		goto error;
+	if (copy_to_user(data32, data, sizeof(*data32)) ||
+	    put_user(data->fifo_size, &data32->fifo_size)) {
+		err = -EFAULT;
+		goto error;
+	}
+
+	if (! refine)
+		recalculate_boundary(runtime);
+ error:
+	kfree(data);
+	return err;
+}
+
+
+/*
+ */
+struct sndrv_xferi32 {
+	s32 result;
+	u32 buf;
+	u32 frames;
+};
+
+static int snd_pcm_ioctl_xferi_compat(snd_pcm_substream_t *substream,
+				      int dir, struct sndrv_xferi32 __user *data32)
+{
+	compat_caddr_t buf;
+	u32 frames;
+	int err;
+
+	if (! substream->runtime)
+		return -ENOTTY;
+	if (substream->stream != dir)
+		return -EINVAL;
+	if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
+		return -EBADFD;
+
+	if (get_user(buf, &data32->buf) ||
+	    get_user(frames, &data32->frames))
+		return -EFAULT;
+
+	if (dir == SNDRV_PCM_STREAM_PLAYBACK)
+		err = snd_pcm_lib_write(substream, compat_ptr(buf), frames);
+	else
+		err = snd_pcm_lib_read(substream, compat_ptr(buf), frames);
+	if (err < 0)
+		return err;
+	/* copy the result */
+	if (put_user(err, &data32->result))
+		return -EFAULT;
+	return 0;
+}
+
+
+/* snd_xfern needs remapping of bufs */
+struct sndrv_xfern32 {
+	s32 result;
+	u32 bufs;  /* this is void **; */
+	u32 frames;
+};
+
+/*
+ * xfern ioctl nees to copy (up to) 128 pointers on stack.
+ * although we may pass the copied pointers through f_op->ioctl, but the ioctl
+ * handler there expands again the same 128 pointers on stack, so it is better
+ * to handle the function (calling pcm_readv/writev) directly in this handler.
+ */
+static int snd_pcm_ioctl_xfern_compat(snd_pcm_substream_t *substream,
+				      int dir, struct sndrv_xfern32 __user *data32)
+{
+	compat_caddr_t buf;
+	compat_caddr_t __user *bufptr;
+	u32 frames;
+	void __user **bufs;
+	int err, ch, i;
+
+	if (! substream->runtime)
+		return -ENOTTY;
+	if (substream->stream != dir)
+		return -EINVAL;
+
+	if ((ch = substream->runtime->channels) > 128)
+		return -EINVAL;
+	if (get_user(buf, &data32->bufs) ||
+	    get_user(frames, &data32->frames))
+		return -EFAULT;
+	bufptr = compat_ptr(buf);
+	bufs = kmalloc(sizeof(void __user *) * ch, GFP_KERNEL);
+	if (bufs == NULL)
+		return -ENOMEM;
+	for (i = 0; i < ch; i++) {
+		u32 ptr;
+		if (get_user(ptr, bufptr)) {
+			kfree(bufs);
+			return -EFAULT;
+		}
+		bufs[ch] = compat_ptr(ptr);
+		bufptr++;
+	}
+	if (dir == SNDRV_PCM_STREAM_PLAYBACK)
+		err = snd_pcm_lib_writev(substream, bufs, frames);
+	else
+		err = snd_pcm_lib_readv(substream, bufs, frames);
+	if (err >= 0) {
+		if (put_user(err, &data32->result))
+			err = -EFAULT;
+	}
+	kfree(bufs);
+	return err;
+}
+
+
+struct sndrv_pcm_mmap_status32 {
+	s32 state;
+	s32 pad1;
+	u32 hw_ptr;
+	struct compat_timespec tstamp;
+	s32 suspended_state;
+} __attribute__((packed));
+
+struct sndrv_pcm_mmap_control32 {
+	u32 appl_ptr;
+	u32 avail_min;
+};
+
+struct sndrv_pcm_sync_ptr32 {
+	u32 flags;
+	union {
+		struct sndrv_pcm_mmap_status32 status;
+		unsigned char reserved[64];
+	} s;
+	union {
+		struct sndrv_pcm_mmap_control32 control;
+		unsigned char reserved[64];
+	} c;
+} __attribute__((packed));
+
+static int snd_pcm_ioctl_sync_ptr_compat(snd_pcm_substream_t *substream,
+					 struct sndrv_pcm_sync_ptr32 __user *src)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	volatile struct sndrv_pcm_mmap_status *status;
+	volatile struct sndrv_pcm_mmap_control *control;
+	u32 sflags;
+	struct sndrv_pcm_mmap_control scontrol;
+	struct sndrv_pcm_mmap_status sstatus;
+	int err;
+
+	snd_assert(runtime, return -EINVAL);
+
+	if (get_user(sflags, &src->flags) ||
+	    get_user(scontrol.appl_ptr, &src->c.control.appl_ptr) ||
+	    get_user(scontrol.avail_min, &src->c.control.avail_min))
+		return -EFAULT;
+	if (sflags & SNDRV_PCM_SYNC_PTR_HWSYNC) {
+		err = snd_pcm_hwsync(substream);
+		if (err < 0)
+			return err;
+	}
+	status = runtime->status;
+	control = runtime->control;
+	snd_pcm_stream_lock_irq(substream);
+	if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL))
+		control->appl_ptr = scontrol.appl_ptr;
+	else
+		scontrol.appl_ptr = control->appl_ptr;
+	if (!(sflags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN))
+		control->avail_min = scontrol.avail_min;
+	else
+		scontrol.avail_min = control->avail_min;
+	sstatus.state = status->state;
+	sstatus.hw_ptr = status->hw_ptr;
+	sstatus.tstamp = status->tstamp;
+	sstatus.suspended_state = status->suspended_state;
+	snd_pcm_stream_unlock_irq(substream);
+	if (put_user(sstatus.state, &src->s.status.state) ||
+	    put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) ||
+	    put_user(sstatus.tstamp.tv_sec, &src->s.status.tstamp.tv_sec) ||
+	    put_user(sstatus.tstamp.tv_nsec, &src->s.status.tstamp.tv_nsec) ||
+	    put_user(sstatus.suspended_state, &src->s.status.suspended_state) ||
+	    put_user(scontrol.appl_ptr, &src->c.control.appl_ptr) ||
+	    put_user(scontrol.avail_min, &src->c.control.avail_min))
+		return -EFAULT;
+
+	return 0;
+}
+
+
+/*
+ */
+enum {
+	SNDRV_PCM_IOCTL_HW_REFINE32 = _IOWR('A', 0x10, struct sndrv_pcm_hw_params32),
+	SNDRV_PCM_IOCTL_HW_PARAMS32 = _IOWR('A', 0x11, struct sndrv_pcm_hw_params32),
+	SNDRV_PCM_IOCTL_SW_PARAMS32 = _IOWR('A', 0x13, struct sndrv_pcm_sw_params32),
+	SNDRV_PCM_IOCTL_STATUS32 = _IOR('A', 0x20, struct sndrv_pcm_status32),
+	SNDRV_PCM_IOCTL_DELAY32 = _IOR('A', 0x21, s32),
+	SNDRV_PCM_IOCTL_CHANNEL_INFO32 = _IOR('A', 0x32, struct sndrv_pcm_channel_info32),
+	SNDRV_PCM_IOCTL_REWIND32 = _IOW('A', 0x46, u32),
+	SNDRV_PCM_IOCTL_FORWARD32 = _IOW('A', 0x49, u32),
+	SNDRV_PCM_IOCTL_WRITEI_FRAMES32 = _IOW('A', 0x50, struct sndrv_xferi32),
+	SNDRV_PCM_IOCTL_READI_FRAMES32 = _IOR('A', 0x51, struct sndrv_xferi32),
+	SNDRV_PCM_IOCTL_WRITEN_FRAMES32 = _IOW('A', 0x52, struct sndrv_xfern32),
+	SNDRV_PCM_IOCTL_READN_FRAMES32 = _IOR('A', 0x53, struct sndrv_xfern32),
+	SNDRV_PCM_IOCTL_SYNC_PTR32 = _IOWR('A', 0x23, struct sndrv_pcm_sync_ptr32),
+
+};
+
+static long snd_pcm_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	snd_pcm_file_t *pcm_file;
+	snd_pcm_substream_t *substream;
+	void __user *argp = compat_ptr(arg);
+
+	pcm_file = file->private_data;
+	if (! pcm_file)
+		return -ENOTTY;
+	substream = pcm_file->substream;
+	if (! substream)
+		return -ENOTTY;
+
+	/*
+	 * When PCM is used on 32bit mode, we need to disable
+	 * mmap of PCM status/control records because of the size
+	 * incompatibility.
+	 */
+	substream->no_mmap_ctrl = 1;
+
+	switch (cmd) {
+	case SNDRV_PCM_IOCTL_PVERSION:
+	case SNDRV_PCM_IOCTL_INFO:
+	case SNDRV_PCM_IOCTL_TSTAMP:
+	case SNDRV_PCM_IOCTL_HWSYNC:
+	case SNDRV_PCM_IOCTL_PREPARE:
+	case SNDRV_PCM_IOCTL_RESET:
+	case SNDRV_PCM_IOCTL_START:
+	case SNDRV_PCM_IOCTL_DROP:
+	case SNDRV_PCM_IOCTL_DRAIN:
+	case SNDRV_PCM_IOCTL_PAUSE:
+	case SNDRV_PCM_IOCTL_HW_FREE:
+	case SNDRV_PCM_IOCTL_RESUME:
+	case SNDRV_PCM_IOCTL_XRUN:
+	case SNDRV_PCM_IOCTL_LINK:
+	case SNDRV_PCM_IOCTL_UNLINK:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			return snd_pcm_playback_ioctl1(substream, cmd, argp);
+		else
+			return snd_pcm_capture_ioctl1(substream, cmd, argp);
+	case SNDRV_PCM_IOCTL_HW_REFINE32:
+		return snd_pcm_ioctl_hw_params_compat(substream, 1, argp);
+	case SNDRV_PCM_IOCTL_HW_PARAMS32:
+		return snd_pcm_ioctl_hw_params_compat(substream, 0, argp);
+	case SNDRV_PCM_IOCTL_SW_PARAMS32:
+		return snd_pcm_ioctl_sw_params_compat(substream, argp);
+	case SNDRV_PCM_IOCTL_STATUS32:
+		return snd_pcm_status_user_compat(substream, argp);
+	case SNDRV_PCM_IOCTL_SYNC_PTR32:
+		return snd_pcm_ioctl_sync_ptr_compat(substream, argp);
+	case SNDRV_PCM_IOCTL_CHANNEL_INFO32:
+		return snd_pcm_ioctl_channel_info_compat(substream, argp);
+	case SNDRV_PCM_IOCTL_WRITEI_FRAMES32:
+		return snd_pcm_ioctl_xferi_compat(substream, SNDRV_PCM_STREAM_PLAYBACK, argp);
+	case SNDRV_PCM_IOCTL_READI_FRAMES32:
+		return snd_pcm_ioctl_xferi_compat(substream, SNDRV_PCM_STREAM_CAPTURE, argp);
+	case SNDRV_PCM_IOCTL_WRITEN_FRAMES32:
+		return snd_pcm_ioctl_xfern_compat(substream, SNDRV_PCM_STREAM_PLAYBACK, argp);
+	case SNDRV_PCM_IOCTL_READN_FRAMES32:
+		return snd_pcm_ioctl_xfern_compat(substream, SNDRV_PCM_STREAM_CAPTURE, argp);
+	case SNDRV_PCM_IOCTL_DELAY32:
+		return snd_pcm_ioctl_delay_compat(substream, argp);
+	case SNDRV_PCM_IOCTL_REWIND32:
+		return snd_pcm_ioctl_rewind_compat(substream, argp);
+	case SNDRV_PCM_IOCTL_FORWARD32:
+		return snd_pcm_ioctl_forward_compat(substream, argp);
+	}
+
+	return -ENOIOCTLCMD;
+}
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
new file mode 100644
index 000000000000..151fd99ca2c9
--- /dev/null
+++ b/sound/core/pcm_lib.c
@@ -0,0 +1,2612 @@
+/*
+ *  Digital Audio (PCM) abstract layer
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *                   Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/timer.h>
+
+/*
+ * fill ring buffer with silence
+ * runtime->silence_start: starting pointer to silence area
+ * runtime->silence_filled: size filled with silence
+ * runtime->silence_threshold: threshold from application
+ * runtime->silence_size: maximal size from application
+ *
+ * when runtime->silence_size >= runtime->boundary - fill processed area with silence immediately
+ */
+void snd_pcm_playback_silence(snd_pcm_substream_t *substream, snd_pcm_uframes_t new_hw_ptr)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_uframes_t frames, ofs, transfer;
+
+	if (runtime->silence_size < runtime->boundary) {
+		snd_pcm_sframes_t noise_dist, n;
+		if (runtime->silence_start != runtime->control->appl_ptr) {
+			n = runtime->control->appl_ptr - runtime->silence_start;
+			if (n < 0)
+				n += runtime->boundary;
+			if ((snd_pcm_uframes_t)n < runtime->silence_filled)
+				runtime->silence_filled -= n;
+			else
+				runtime->silence_filled = 0;
+			runtime->silence_start = runtime->control->appl_ptr;
+		}
+		if (runtime->silence_filled == runtime->buffer_size)
+			return;
+		snd_assert(runtime->silence_filled <= runtime->buffer_size, return);
+		noise_dist = snd_pcm_playback_hw_avail(runtime) + runtime->silence_filled;
+		if (noise_dist >= (snd_pcm_sframes_t) runtime->silence_threshold)
+			return;
+		frames = runtime->silence_threshold - noise_dist;
+		if (frames > runtime->silence_size)
+			frames = runtime->silence_size;
+	} else {
+		if (new_hw_ptr == ULONG_MAX) {	/* initialization */
+			snd_pcm_sframes_t avail = snd_pcm_playback_hw_avail(runtime);
+			runtime->silence_filled = avail > 0 ? avail : 0;
+			runtime->silence_start = (runtime->status->hw_ptr +
+						  runtime->silence_filled) %
+						 runtime->boundary;
+		} else {
+			ofs = runtime->status->hw_ptr;
+			frames = new_hw_ptr - ofs;
+			if ((snd_pcm_sframes_t)frames < 0)
+				frames += runtime->boundary;
+			runtime->silence_filled -= frames;
+			if ((snd_pcm_sframes_t)runtime->silence_filled < 0) {
+				runtime->silence_filled = 0;
+				runtime->silence_start = (ofs + frames) - runtime->buffer_size;
+			} else {
+				runtime->silence_start = ofs - runtime->silence_filled;
+			}
+			if ((snd_pcm_sframes_t)runtime->silence_start < 0)
+				runtime->silence_start += runtime->boundary;
+		}
+		frames = runtime->buffer_size - runtime->silence_filled;
+	}
+	snd_assert(frames <= runtime->buffer_size, return);
+	if (frames == 0)
+		return;
+	ofs = (runtime->silence_start + runtime->silence_filled) % runtime->buffer_size;
+	while (frames > 0) {
+		transfer = ofs + frames > runtime->buffer_size ? runtime->buffer_size - ofs : frames;
+		if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED ||
+		    runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) {
+			if (substream->ops->silence) {
+				int err;
+				err = substream->ops->silence(substream, -1, ofs, transfer);
+				snd_assert(err >= 0, );
+			} else {
+				char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, ofs);
+				snd_pcm_format_set_silence(runtime->format, hwbuf, transfer * runtime->channels);
+			}
+		} else {
+			unsigned int c;
+			unsigned int channels = runtime->channels;
+			if (substream->ops->silence) {
+				for (c = 0; c < channels; ++c) {
+					int err;
+					err = substream->ops->silence(substream, c, ofs, transfer);
+					snd_assert(err >= 0, );
+				}
+			} else {
+				size_t dma_csize = runtime->dma_bytes / channels;
+				for (c = 0; c < channels; ++c) {
+					char *hwbuf = runtime->dma_area + (c * dma_csize) + samples_to_bytes(runtime, ofs);
+					snd_pcm_format_set_silence(runtime->format, hwbuf, transfer);
+				}
+			}
+		}
+		runtime->silence_filled += transfer;
+		frames -= transfer;
+		ofs = 0;
+	}
+}
+
+static void xrun(snd_pcm_substream_t *substream)
+{
+	snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+#ifdef CONFIG_SND_DEBUG
+	if (substream->pstr->xrun_debug) {
+		snd_printd(KERN_DEBUG "XRUN: pcmC%dD%d%c\n",
+			   substream->pcm->card->number,
+			   substream->pcm->device,
+			   substream->stream ? 'c' : 'p');
+		if (substream->pstr->xrun_debug > 1)
+			dump_stack();
+	}
+#endif
+}
+
+static inline snd_pcm_uframes_t snd_pcm_update_hw_ptr_pos(snd_pcm_substream_t *substream,
+							  snd_pcm_runtime_t *runtime)
+{
+	snd_pcm_uframes_t pos;
+
+	pos = substream->ops->pointer(substream);
+	if (pos == SNDRV_PCM_POS_XRUN)
+		return pos; /* XRUN */
+	if (runtime->tstamp_mode & SNDRV_PCM_TSTAMP_MMAP)
+		snd_timestamp_now((snd_timestamp_t*)&runtime->status->tstamp, runtime->tstamp_timespec);
+#ifdef CONFIG_SND_DEBUG
+	if (pos >= runtime->buffer_size) {
+		snd_printk(KERN_ERR  "BUG: stream = %i, pos = 0x%lx, buffer size = 0x%lx, period size = 0x%lx\n", substream->stream, pos, runtime->buffer_size, runtime->period_size);
+	} else
+#endif
+	snd_runtime_check(pos < runtime->buffer_size, return 0);
+	pos -= pos % runtime->min_align;
+	return pos;
+}
+
+static inline int snd_pcm_update_hw_ptr_post(snd_pcm_substream_t *substream,
+					     snd_pcm_runtime_t *runtime)
+{
+	snd_pcm_uframes_t avail;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		avail = snd_pcm_playback_avail(runtime);
+	else
+		avail = snd_pcm_capture_avail(runtime);
+	if (avail > runtime->avail_max)
+		runtime->avail_max = avail;
+	if (avail >= runtime->stop_threshold) {
+		if (substream->runtime->status->state == SNDRV_PCM_STATE_DRAINING)
+			snd_pcm_drain_done(substream);
+		else
+			xrun(substream);
+		return -EPIPE;
+	}
+	if (avail >= runtime->control->avail_min)
+		wake_up(&runtime->sleep);
+	return 0;
+}
+
+static inline int snd_pcm_update_hw_ptr_interrupt(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_uframes_t pos;
+	snd_pcm_uframes_t new_hw_ptr, hw_ptr_interrupt;
+	snd_pcm_sframes_t delta;
+
+	pos = snd_pcm_update_hw_ptr_pos(substream, runtime);
+	if (pos == SNDRV_PCM_POS_XRUN) {
+		xrun(substream);
+		return -EPIPE;
+	}
+	if (runtime->period_size == runtime->buffer_size)
+		goto __next_buf;
+	new_hw_ptr = runtime->hw_ptr_base + pos;
+	hw_ptr_interrupt = runtime->hw_ptr_interrupt + runtime->period_size;
+
+	delta = hw_ptr_interrupt - new_hw_ptr;
+	if (delta > 0) {
+		if ((snd_pcm_uframes_t)delta < runtime->buffer_size / 2) {
+#ifdef CONFIG_SND_DEBUG
+			if (runtime->periods > 1 && substream->pstr->xrun_debug) {
+				snd_printd(KERN_ERR "Unexpected hw_pointer value [1] (stream = %i, delta: -%ld, max jitter = %ld): wrong interrupt acknowledge?\n", substream->stream, (long) delta, runtime->buffer_size / 2);
+				if (substream->pstr->xrun_debug > 1)
+					dump_stack();
+			}
+#endif
+			return 0;
+		}
+	      __next_buf:
+		runtime->hw_ptr_base += runtime->buffer_size;
+		if (runtime->hw_ptr_base == runtime->boundary)
+			runtime->hw_ptr_base = 0;
+		new_hw_ptr = runtime->hw_ptr_base + pos;
+	}
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+	    runtime->silence_size > 0)
+		snd_pcm_playback_silence(substream, new_hw_ptr);
+
+	runtime->status->hw_ptr = new_hw_ptr;
+	runtime->hw_ptr_interrupt = new_hw_ptr - new_hw_ptr % runtime->period_size;
+
+	return snd_pcm_update_hw_ptr_post(substream, runtime);
+}
+
+/* CAUTION: call it with irq disabled */
+int snd_pcm_update_hw_ptr(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_uframes_t pos;
+	snd_pcm_uframes_t old_hw_ptr, new_hw_ptr;
+	snd_pcm_sframes_t delta;
+
+	old_hw_ptr = runtime->status->hw_ptr;
+	pos = snd_pcm_update_hw_ptr_pos(substream, runtime);
+	if (pos == SNDRV_PCM_POS_XRUN) {
+		xrun(substream);
+		return -EPIPE;
+	}
+	new_hw_ptr = runtime->hw_ptr_base + pos;
+
+	delta = old_hw_ptr - new_hw_ptr;
+	if (delta > 0) {
+		if ((snd_pcm_uframes_t)delta < runtime->buffer_size / 2) {
+#ifdef CONFIG_SND_DEBUG
+			if (runtime->periods > 2 && substream->pstr->xrun_debug) {
+				snd_printd(KERN_ERR "Unexpected hw_pointer value [2] (stream = %i, delta: -%ld, max jitter = %ld): wrong interrupt acknowledge?\n", substream->stream, (long) delta, runtime->buffer_size / 2);
+				if (substream->pstr->xrun_debug > 1)
+					dump_stack();
+			}
+#endif
+			return 0;
+		}
+		runtime->hw_ptr_base += runtime->buffer_size;
+		if (runtime->hw_ptr_base == runtime->boundary)
+			runtime->hw_ptr_base = 0;
+		new_hw_ptr = runtime->hw_ptr_base + pos;
+	}
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+	    runtime->silence_size > 0)
+		snd_pcm_playback_silence(substream, new_hw_ptr);
+
+	runtime->status->hw_ptr = new_hw_ptr;
+
+	return snd_pcm_update_hw_ptr_post(substream, runtime);
+}
+
+/**
+ * snd_pcm_set_ops - set the PCM operators
+ * @pcm: the pcm instance
+ * @direction: stream direction, SNDRV_PCM_STREAM_XXX
+ * @ops: the operator table
+ *
+ * Sets the given PCM operators to the pcm instance.
+ */
+void snd_pcm_set_ops(snd_pcm_t *pcm, int direction, snd_pcm_ops_t *ops)
+{
+	snd_pcm_str_t *stream = &pcm->streams[direction];
+	snd_pcm_substream_t *substream;
+	
+	for (substream = stream->substream; substream != NULL; substream = substream->next)
+		substream->ops = ops;
+}
+
+
+/**
+ * snd_pcm_sync - set the PCM sync id
+ * @substream: the pcm substream
+ *
+ * Sets the PCM sync identifier for the card.
+ */
+void snd_pcm_set_sync(snd_pcm_substream_t * substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	
+	runtime->sync.id32[0] = substream->pcm->card->number;
+	runtime->sync.id32[1] = -1;
+	runtime->sync.id32[2] = -1;
+	runtime->sync.id32[3] = -1;
+}
+
+/*
+ *  Standard ioctl routine
+ */
+
+/* Code taken from alsa-lib */
+#define assert(a) snd_assert((a), return -EINVAL)
+
+static inline unsigned int div32(unsigned int a, unsigned int b, 
+				 unsigned int *r)
+{
+	if (b == 0) {
+		*r = 0;
+		return UINT_MAX;
+	}
+	*r = a % b;
+	return a / b;
+}
+
+static inline unsigned int div_down(unsigned int a, unsigned int b)
+{
+	if (b == 0)
+		return UINT_MAX;
+	return a / b;
+}
+
+static inline unsigned int div_up(unsigned int a, unsigned int b)
+{
+	unsigned int r;
+	unsigned int q;
+	if (b == 0)
+		return UINT_MAX;
+	q = div32(a, b, &r);
+	if (r)
+		++q;
+	return q;
+}
+
+static inline unsigned int mul(unsigned int a, unsigned int b)
+{
+	if (a == 0)
+		return 0;
+	if (div_down(UINT_MAX, a) < b)
+		return UINT_MAX;
+	return a * b;
+}
+
+static inline unsigned int muldiv32(unsigned int a, unsigned int b,
+				    unsigned int c, unsigned int *r)
+{
+	u_int64_t n = (u_int64_t) a * b;
+	if (c == 0) {
+		snd_assert(n > 0, );
+		*r = 0;
+		return UINT_MAX;
+	}
+	div64_32(&n, c, r);
+	if (n >= UINT_MAX) {
+		*r = 0;
+		return UINT_MAX;
+	}
+	return n;
+}
+
+static int snd_interval_refine_min(snd_interval_t *i, unsigned int min, int openmin)
+{
+	int changed = 0;
+	assert(!snd_interval_empty(i));
+	if (i->min < min) {
+		i->min = min;
+		i->openmin = openmin;
+		changed = 1;
+	} else if (i->min == min && !i->openmin && openmin) {
+		i->openmin = 1;
+		changed = 1;
+	}
+	if (i->integer) {
+		if (i->openmin) {
+			i->min++;
+			i->openmin = 0;
+		}
+	}
+	if (snd_interval_checkempty(i)) {
+		snd_interval_none(i);
+		return -EINVAL;
+	}
+	return changed;
+}
+
+static int snd_interval_refine_max(snd_interval_t *i, unsigned int max, int openmax)
+{
+	int changed = 0;
+	assert(!snd_interval_empty(i));
+	if (i->max > max) {
+		i->max = max;
+		i->openmax = openmax;
+		changed = 1;
+	} else if (i->max == max && !i->openmax && openmax) {
+		i->openmax = 1;
+		changed = 1;
+	}
+	if (i->integer) {
+		if (i->openmax) {
+			i->max--;
+			i->openmax = 0;
+		}
+	}
+	if (snd_interval_checkempty(i)) {
+		snd_interval_none(i);
+		return -EINVAL;
+	}
+	return changed;
+}
+
+/**
+ * snd_interval_refine - refine the interval value of configurator
+ * @i: the interval value to refine
+ * @v: the interval value to refer to
+ *
+ * Refines the interval value with the reference value.
+ * The interval is changed to the range satisfying both intervals.
+ * The interval status (min, max, integer, etc.) are evaluated.
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+int snd_interval_refine(snd_interval_t *i, const snd_interval_t *v)
+{
+	int changed = 0;
+	assert(!snd_interval_empty(i));
+	if (i->min < v->min) {
+		i->min = v->min;
+		i->openmin = v->openmin;
+		changed = 1;
+	} else if (i->min == v->min && !i->openmin && v->openmin) {
+		i->openmin = 1;
+		changed = 1;
+	}
+	if (i->max > v->max) {
+		i->max = v->max;
+		i->openmax = v->openmax;
+		changed = 1;
+	} else if (i->max == v->max && !i->openmax && v->openmax) {
+		i->openmax = 1;
+		changed = 1;
+	}
+	if (!i->integer && v->integer) {
+		i->integer = 1;
+		changed = 1;
+	}
+	if (i->integer) {
+		if (i->openmin) {
+			i->min++;
+			i->openmin = 0;
+		}
+		if (i->openmax) {
+			i->max--;
+			i->openmax = 0;
+		}
+	} else if (!i->openmin && !i->openmax && i->min == i->max)
+		i->integer = 1;
+	if (snd_interval_checkempty(i)) {
+		snd_interval_none(i);
+		return -EINVAL;
+	}
+	return changed;
+}
+
+static int snd_interval_refine_first(snd_interval_t *i)
+{
+	assert(!snd_interval_empty(i));
+	if (snd_interval_single(i))
+		return 0;
+	i->max = i->min;
+	i->openmax = i->openmin;
+	if (i->openmax)
+		i->max++;
+	return 1;
+}
+
+static int snd_interval_refine_last(snd_interval_t *i)
+{
+	assert(!snd_interval_empty(i));
+	if (snd_interval_single(i))
+		return 0;
+	i->min = i->max;
+	i->openmin = i->openmax;
+	if (i->openmin)
+		i->min--;
+	return 1;
+}
+
+static int snd_interval_refine_set(snd_interval_t *i, unsigned int val)
+{
+	snd_interval_t t;
+	t.empty = 0;
+	t.min = t.max = val;
+	t.openmin = t.openmax = 0;
+	t.integer = 1;
+	return snd_interval_refine(i, &t);
+}
+
+void snd_interval_mul(const snd_interval_t *a, const snd_interval_t *b, snd_interval_t *c)
+{
+	if (a->empty || b->empty) {
+		snd_interval_none(c);
+		return;
+	}
+	c->empty = 0;
+	c->min = mul(a->min, b->min);
+	c->openmin = (a->openmin || b->openmin);
+	c->max = mul(a->max,  b->max);
+	c->openmax = (a->openmax || b->openmax);
+	c->integer = (a->integer && b->integer);
+}
+
+/**
+ * snd_interval_div - refine the interval value with division
+ *
+ * c = a / b
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+void snd_interval_div(const snd_interval_t *a, const snd_interval_t *b, snd_interval_t *c)
+{
+	unsigned int r;
+	if (a->empty || b->empty) {
+		snd_interval_none(c);
+		return;
+	}
+	c->empty = 0;
+	c->min = div32(a->min, b->max, &r);
+	c->openmin = (r || a->openmin || b->openmax);
+	if (b->min > 0) {
+		c->max = div32(a->max, b->min, &r);
+		if (r) {
+			c->max++;
+			c->openmax = 1;
+		} else
+			c->openmax = (a->openmax || b->openmin);
+	} else {
+		c->max = UINT_MAX;
+		c->openmax = 0;
+	}
+	c->integer = 0;
+}
+
+/**
+ * snd_interval_muldivk - refine the interval value
+ *
+ * c = a * b / k
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+void snd_interval_muldivk(const snd_interval_t *a, const snd_interval_t *b,
+		      unsigned int k, snd_interval_t *c)
+{
+	unsigned int r;
+	if (a->empty || b->empty) {
+		snd_interval_none(c);
+		return;
+	}
+	c->empty = 0;
+	c->min = muldiv32(a->min, b->min, k, &r);
+	c->openmin = (r || a->openmin || b->openmin);
+	c->max = muldiv32(a->max, b->max, k, &r);
+	if (r) {
+		c->max++;
+		c->openmax = 1;
+	} else
+		c->openmax = (a->openmax || b->openmax);
+	c->integer = 0;
+}
+
+/**
+ * snd_interval_mulkdiv - refine the interval value
+ *
+ * c = a * k / b
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+void snd_interval_mulkdiv(const snd_interval_t *a, unsigned int k,
+		      const snd_interval_t *b, snd_interval_t *c)
+{
+	unsigned int r;
+	if (a->empty || b->empty) {
+		snd_interval_none(c);
+		return;
+	}
+	c->empty = 0;
+	c->min = muldiv32(a->min, k, b->max, &r);
+	c->openmin = (r || a->openmin || b->openmax);
+	if (b->min > 0) {
+		c->max = muldiv32(a->max, k, b->min, &r);
+		if (r) {
+			c->max++;
+			c->openmax = 1;
+		} else
+			c->openmax = (a->openmax || b->openmin);
+	} else {
+		c->max = UINT_MAX;
+		c->openmax = 0;
+	}
+	c->integer = 0;
+}
+
+#undef assert
+/* ---- */
+
+
+/**
+ * snd_interval_ratnum - refine the interval value
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+int snd_interval_ratnum(snd_interval_t *i,
+		    unsigned int rats_count, ratnum_t *rats,
+		    unsigned int *nump, unsigned int *denp)
+{
+	unsigned int best_num, best_diff, best_den;
+	unsigned int k;
+	snd_interval_t t;
+	int err;
+
+	best_num = best_den = best_diff = 0;
+	for (k = 0; k < rats_count; ++k) {
+		unsigned int num = rats[k].num;
+		unsigned int den;
+		unsigned int q = i->min;
+		int diff;
+		if (q == 0)
+			q = 1;
+		den = div_down(num, q);
+		if (den < rats[k].den_min)
+			continue;
+		if (den > rats[k].den_max)
+			den = rats[k].den_max;
+		else {
+			unsigned int r;
+			r = (den - rats[k].den_min) % rats[k].den_step;
+			if (r != 0)
+				den -= r;
+		}
+		diff = num - q * den;
+		if (best_num == 0 ||
+		    diff * best_den < best_diff * den) {
+			best_diff = diff;
+			best_den = den;
+			best_num = num;
+		}
+	}
+	if (best_den == 0) {
+		i->empty = 1;
+		return -EINVAL;
+	}
+	t.min = div_down(best_num, best_den);
+	t.openmin = !!(best_num % best_den);
+	
+	best_num = best_den = best_diff = 0;
+	for (k = 0; k < rats_count; ++k) {
+		unsigned int num = rats[k].num;
+		unsigned int den;
+		unsigned int q = i->max;
+		int diff;
+		if (q == 0) {
+			i->empty = 1;
+			return -EINVAL;
+		}
+		den = div_up(num, q);
+		if (den > rats[k].den_max)
+			continue;
+		if (den < rats[k].den_min)
+			den = rats[k].den_min;
+		else {
+			unsigned int r;
+			r = (den - rats[k].den_min) % rats[k].den_step;
+			if (r != 0)
+				den += rats[k].den_step - r;
+		}
+		diff = q * den - num;
+		if (best_num == 0 ||
+		    diff * best_den < best_diff * den) {
+			best_diff = diff;
+			best_den = den;
+			best_num = num;
+		}
+	}
+	if (best_den == 0) {
+		i->empty = 1;
+		return -EINVAL;
+	}
+	t.max = div_up(best_num, best_den);
+	t.openmax = !!(best_num % best_den);
+	t.integer = 0;
+	err = snd_interval_refine(i, &t);
+	if (err < 0)
+		return err;
+
+	if (snd_interval_single(i)) {
+		if (nump)
+			*nump = best_num;
+		if (denp)
+			*denp = best_den;
+	}
+	return err;
+}
+
+/**
+ * snd_interval_ratden - refine the interval value
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+static int snd_interval_ratden(snd_interval_t *i,
+			       unsigned int rats_count, ratden_t *rats,
+			       unsigned int *nump, unsigned int *denp)
+{
+	unsigned int best_num, best_diff, best_den;
+	unsigned int k;
+	snd_interval_t t;
+	int err;
+
+	best_num = best_den = best_diff = 0;
+	for (k = 0; k < rats_count; ++k) {
+		unsigned int num;
+		unsigned int den = rats[k].den;
+		unsigned int q = i->min;
+		int diff;
+		num = mul(q, den);
+		if (num > rats[k].num_max)
+			continue;
+		if (num < rats[k].num_min)
+			num = rats[k].num_max;
+		else {
+			unsigned int r;
+			r = (num - rats[k].num_min) % rats[k].num_step;
+			if (r != 0)
+				num += rats[k].num_step - r;
+		}
+		diff = num - q * den;
+		if (best_num == 0 ||
+		    diff * best_den < best_diff * den) {
+			best_diff = diff;
+			best_den = den;
+			best_num = num;
+		}
+	}
+	if (best_den == 0) {
+		i->empty = 1;
+		return -EINVAL;
+	}
+	t.min = div_down(best_num, best_den);
+	t.openmin = !!(best_num % best_den);
+	
+	best_num = best_den = best_diff = 0;
+	for (k = 0; k < rats_count; ++k) {
+		unsigned int num;
+		unsigned int den = rats[k].den;
+		unsigned int q = i->max;
+		int diff;
+		num = mul(q, den);
+		if (num < rats[k].num_min)
+			continue;
+		if (num > rats[k].num_max)
+			num = rats[k].num_max;
+		else {
+			unsigned int r;
+			r = (num - rats[k].num_min) % rats[k].num_step;
+			if (r != 0)
+				num -= r;
+		}
+		diff = q * den - num;
+		if (best_num == 0 ||
+		    diff * best_den < best_diff * den) {
+			best_diff = diff;
+			best_den = den;
+			best_num = num;
+		}
+	}
+	if (best_den == 0) {
+		i->empty = 1;
+		return -EINVAL;
+	}
+	t.max = div_up(best_num, best_den);
+	t.openmax = !!(best_num % best_den);
+	t.integer = 0;
+	err = snd_interval_refine(i, &t);
+	if (err < 0)
+		return err;
+
+	if (snd_interval_single(i)) {
+		if (nump)
+			*nump = best_num;
+		if (denp)
+			*denp = best_den;
+	}
+	return err;
+}
+
+/**
+ * snd_interval_list - refine the interval value from the list
+ * @i: the interval value to refine
+ * @count: the number of elements in the list
+ * @list: the value list
+ * @mask: the bit-mask to evaluate
+ *
+ * Refines the interval value from the list.
+ * When mask is non-zero, only the elements corresponding to bit 1 are
+ * evaluated.
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+int snd_interval_list(snd_interval_t *i, unsigned int count, unsigned int *list, unsigned int mask)
+{
+        unsigned int k;
+	int changed = 0;
+        for (k = 0; k < count; k++) {
+		if (mask && !(mask & (1 << k)))
+			continue;
+                if (i->min == list[k] && !i->openmin)
+                        goto _l1;
+                if (i->min < list[k]) {
+                        i->min = list[k];
+			i->openmin = 0;
+			changed = 1;
+                        goto _l1;
+                }
+        }
+        i->empty = 1;
+        return -EINVAL;
+ _l1:
+        for (k = count; k-- > 0;) {
+		if (mask && !(mask & (1 << k)))
+			continue;
+                if (i->max == list[k] && !i->openmax)
+                        goto _l2;
+                if (i->max > list[k]) {
+                        i->max = list[k];
+			i->openmax = 0;
+			changed = 1;
+                        goto _l2;
+                }
+        }
+        i->empty = 1;
+        return -EINVAL;
+ _l2:
+	if (snd_interval_checkempty(i)) {
+		i->empty = 1;
+		return -EINVAL;
+	}
+        return changed;
+}
+
+static int snd_interval_step(snd_interval_t *i, unsigned int min, unsigned int step)
+{
+	unsigned int n;
+	int changed = 0;
+	n = (i->min - min) % step;
+	if (n != 0 || i->openmin) {
+		i->min += step - n;
+		changed = 1;
+	}
+	n = (i->max - min) % step;
+	if (n != 0 || i->openmax) {
+		i->max -= n;
+		changed = 1;
+	}
+	if (snd_interval_checkempty(i)) {
+		i->empty = 1;
+		return -EINVAL;
+	}
+	return changed;
+}
+
+/* Info constraints helpers */
+
+/**
+ * snd_pcm_hw_rule_add - add the hw-constraint rule
+ * @runtime: the pcm runtime instance
+ * @cond: condition bits
+ * @var: the variable to evaluate
+ * @func: the evaluation function
+ * @private: the private data pointer passed to function
+ * @dep: the dependent variables
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_hw_rule_add(snd_pcm_runtime_t *runtime, unsigned int cond,
+			int var,
+			snd_pcm_hw_rule_func_t func, void *private,
+			int dep, ...)
+{
+	snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints;
+	snd_pcm_hw_rule_t *c;
+	unsigned int k;
+	va_list args;
+	va_start(args, dep);
+	if (constrs->rules_num >= constrs->rules_all) {
+		snd_pcm_hw_rule_t *new;
+		unsigned int new_rules = constrs->rules_all + 16;
+		new = kcalloc(new_rules, sizeof(*c), GFP_KERNEL);
+		if (!new)
+			return -ENOMEM;
+		if (constrs->rules) {
+			memcpy(new, constrs->rules,
+			       constrs->rules_num * sizeof(*c));
+			kfree(constrs->rules);
+		}
+		constrs->rules = new;
+		constrs->rules_all = new_rules;
+	}
+	c = &constrs->rules[constrs->rules_num];
+	c->cond = cond;
+	c->func = func;
+	c->var = var;
+	c->private = private;
+	k = 0;
+	while (1) {
+		snd_assert(k < ARRAY_SIZE(c->deps), return -EINVAL);
+		c->deps[k++] = dep;
+		if (dep < 0)
+			break;
+		dep = va_arg(args, int);
+	}
+	constrs->rules_num++;
+	va_end(args);
+	return 0;
+}				    
+
+/**
+ * snd_pcm_hw_constraint_mask
+ */
+int snd_pcm_hw_constraint_mask(snd_pcm_runtime_t *runtime, snd_pcm_hw_param_t var,
+			       u_int32_t mask)
+{
+	snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints;
+	snd_mask_t *maskp = constrs_mask(constrs, var);
+	*maskp->bits &= mask;
+	memset(maskp->bits + 1, 0, (SNDRV_MASK_MAX-32) / 8); /* clear rest */
+	if (*maskp->bits == 0)
+		return -EINVAL;
+	return 0;
+}
+
+/**
+ * snd_pcm_hw_constraint_mask64
+ */
+int snd_pcm_hw_constraint_mask64(snd_pcm_runtime_t *runtime, snd_pcm_hw_param_t var,
+				 u_int64_t mask)
+{
+	snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints;
+	snd_mask_t *maskp = constrs_mask(constrs, var);
+	maskp->bits[0] &= (u_int32_t)mask;
+	maskp->bits[1] &= (u_int32_t)(mask >> 32);
+	memset(maskp->bits + 2, 0, (SNDRV_MASK_MAX-64) / 8); /* clear rest */
+	if (! maskp->bits[0] && ! maskp->bits[1])
+		return -EINVAL;
+	return 0;
+}
+
+/**
+ * snd_pcm_hw_constraint_integer
+ */
+int snd_pcm_hw_constraint_integer(snd_pcm_runtime_t *runtime, snd_pcm_hw_param_t var)
+{
+	snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints;
+	return snd_interval_setinteger(constrs_interval(constrs, var));
+}
+
+/**
+ * snd_pcm_hw_constraint_minmax
+ */
+int snd_pcm_hw_constraint_minmax(snd_pcm_runtime_t *runtime, snd_pcm_hw_param_t var,
+				 unsigned int min, unsigned int max)
+{
+	snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints;
+	snd_interval_t t;
+	t.min = min;
+	t.max = max;
+	t.openmin = t.openmax = 0;
+	t.integer = 0;
+	return snd_interval_refine(constrs_interval(constrs, var), &t);
+}
+
+static int snd_pcm_hw_rule_list(snd_pcm_hw_params_t *params,
+				snd_pcm_hw_rule_t *rule)
+{
+	snd_pcm_hw_constraint_list_t *list = rule->private;
+	return snd_interval_list(hw_param_interval(params, rule->var), list->count, list->list, list->mask);
+}		
+
+
+/**
+ * snd_pcm_hw_constraint_list
+ */
+int snd_pcm_hw_constraint_list(snd_pcm_runtime_t *runtime,
+			       unsigned int cond,
+			       snd_pcm_hw_param_t var,
+			       snd_pcm_hw_constraint_list_t *l)
+{
+	return snd_pcm_hw_rule_add(runtime, cond, var,
+				   snd_pcm_hw_rule_list, l,
+				   var, -1);
+}
+
+static int snd_pcm_hw_rule_ratnums(snd_pcm_hw_params_t *params,
+				   snd_pcm_hw_rule_t *rule)
+{
+	snd_pcm_hw_constraint_ratnums_t *r = rule->private;
+	unsigned int num = 0, den = 0;
+	int err;
+	err = snd_interval_ratnum(hw_param_interval(params, rule->var),
+				  r->nrats, r->rats, &num, &den);
+	if (err >= 0 && den && rule->var == SNDRV_PCM_HW_PARAM_RATE) {
+		params->rate_num = num;
+		params->rate_den = den;
+	}
+	return err;
+}
+
+/**
+ * snd_pcm_hw_constraint_ratnums
+ */
+int snd_pcm_hw_constraint_ratnums(snd_pcm_runtime_t *runtime, 
+				  unsigned int cond,
+				  snd_pcm_hw_param_t var,
+				  snd_pcm_hw_constraint_ratnums_t *r)
+{
+	return snd_pcm_hw_rule_add(runtime, cond, var,
+				   snd_pcm_hw_rule_ratnums, r,
+				   var, -1);
+}
+
+static int snd_pcm_hw_rule_ratdens(snd_pcm_hw_params_t *params,
+				   snd_pcm_hw_rule_t *rule)
+{
+	snd_pcm_hw_constraint_ratdens_t *r = rule->private;
+	unsigned int num = 0, den = 0;
+	int err = snd_interval_ratden(hw_param_interval(params, rule->var),
+				  r->nrats, r->rats, &num, &den);
+	if (err >= 0 && den && rule->var == SNDRV_PCM_HW_PARAM_RATE) {
+		params->rate_num = num;
+		params->rate_den = den;
+	}
+	return err;
+}
+
+/**
+ * snd_pcm_hw_constraint_ratdens
+ */
+int snd_pcm_hw_constraint_ratdens(snd_pcm_runtime_t *runtime, 
+				  unsigned int cond,
+				  snd_pcm_hw_param_t var,
+				  snd_pcm_hw_constraint_ratdens_t *r)
+{
+	return snd_pcm_hw_rule_add(runtime, cond, var,
+				   snd_pcm_hw_rule_ratdens, r,
+				   var, -1);
+}
+
+static int snd_pcm_hw_rule_msbits(snd_pcm_hw_params_t *params,
+				  snd_pcm_hw_rule_t *rule)
+{
+	unsigned int l = (unsigned long) rule->private;
+	int width = l & 0xffff;
+	unsigned int msbits = l >> 16;
+	snd_interval_t *i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
+	if (snd_interval_single(i) && snd_interval_value(i) == width)
+		params->msbits = msbits;
+	return 0;
+}
+
+/**
+ * snd_pcm_hw_constraint_msbits
+ */
+int snd_pcm_hw_constraint_msbits(snd_pcm_runtime_t *runtime, 
+				 unsigned int cond,
+				 unsigned int width,
+				 unsigned int msbits)
+{
+	unsigned long l = (msbits << 16) | width;
+	return snd_pcm_hw_rule_add(runtime, cond, -1,
+				    snd_pcm_hw_rule_msbits,
+				    (void*) l,
+				    SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
+}
+
+static int snd_pcm_hw_rule_step(snd_pcm_hw_params_t *params,
+				snd_pcm_hw_rule_t *rule)
+{
+	unsigned long step = (unsigned long) rule->private;
+	return snd_interval_step(hw_param_interval(params, rule->var), 0, step);
+}
+
+/**
+ * snd_pcm_hw_constraint_step
+ */
+int snd_pcm_hw_constraint_step(snd_pcm_runtime_t *runtime,
+			       unsigned int cond,
+			       snd_pcm_hw_param_t var,
+			       unsigned long step)
+{
+	return snd_pcm_hw_rule_add(runtime, cond, var, 
+				   snd_pcm_hw_rule_step, (void *) step,
+				   var, -1);
+}
+
+static int snd_pcm_hw_rule_pow2(snd_pcm_hw_params_t *params, snd_pcm_hw_rule_t *rule)
+{
+	static int pow2_sizes[] = {
+		1<<0, 1<<1, 1<<2, 1<<3, 1<<4, 1<<5, 1<<6, 1<<7,
+		1<<8, 1<<9, 1<<10, 1<<11, 1<<12, 1<<13, 1<<14, 1<<15,
+		1<<16, 1<<17, 1<<18, 1<<19, 1<<20, 1<<21, 1<<22, 1<<23,
+		1<<24, 1<<25, 1<<26, 1<<27, 1<<28, 1<<29, 1<<30
+	};
+	return snd_interval_list(hw_param_interval(params, rule->var),
+				 ARRAY_SIZE(pow2_sizes), pow2_sizes, 0);
+}		
+
+/**
+ * snd_pcm_hw_constraint_pow2
+ */
+int snd_pcm_hw_constraint_pow2(snd_pcm_runtime_t *runtime,
+			       unsigned int cond,
+			       snd_pcm_hw_param_t var)
+{
+	return snd_pcm_hw_rule_add(runtime, cond, var, 
+				   snd_pcm_hw_rule_pow2, NULL,
+				   var, -1);
+}
+
+/* To use the same code we have in alsa-lib */
+#define snd_pcm_t snd_pcm_substream_t
+#define assert(i) snd_assert((i), return -EINVAL)
+#ifndef INT_MIN
+#define INT_MIN ((int)((unsigned int)INT_MAX+1))
+#endif
+
+void _snd_pcm_hw_param_any(snd_pcm_hw_params_t *params, snd_pcm_hw_param_t var)
+{
+	if (hw_is_mask(var)) {
+		snd_mask_any(hw_param_mask(params, var));
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+		return;
+	}
+	if (hw_is_interval(var)) {
+		snd_interval_any(hw_param_interval(params, var));
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+		return;
+	}
+	snd_BUG();
+}
+
+/**
+ * snd_pcm_hw_param_any
+ */
+int snd_pcm_hw_param_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			 snd_pcm_hw_param_t var)
+{
+	_snd_pcm_hw_param_any(params, var);
+	return snd_pcm_hw_refine(pcm, params);
+}
+
+void _snd_pcm_hw_params_any(snd_pcm_hw_params_t *params)
+{
+	unsigned int k;
+	memset(params, 0, sizeof(*params));
+	for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++)
+		_snd_pcm_hw_param_any(params, k);
+	for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++)
+		_snd_pcm_hw_param_any(params, k);
+	params->info = ~0U;
+}
+
+/**
+ * snd_pcm_hw_params_any
+ *
+ * Fill PARAMS with full configuration space boundaries
+ */
+int snd_pcm_hw_params_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	_snd_pcm_hw_params_any(params);
+	return snd_pcm_hw_refine(pcm, params);
+}
+
+/**
+ * snd_pcm_hw_param_value
+ *
+ * Return the value for field PAR if it's fixed in configuration space 
+ *  defined by PARAMS. Return -EINVAL otherwise
+ */
+int snd_pcm_hw_param_value(const snd_pcm_hw_params_t *params,
+			   snd_pcm_hw_param_t var, int *dir)
+{
+	if (hw_is_mask(var)) {
+		const snd_mask_t *mask = hw_param_mask_c(params, var);
+		if (!snd_mask_single(mask))
+			return -EINVAL;
+		if (dir)
+			*dir = 0;
+		return snd_mask_value(mask);
+	}
+	if (hw_is_interval(var)) {
+		const snd_interval_t *i = hw_param_interval_c(params, var);
+		if (!snd_interval_single(i))
+			return -EINVAL;
+		if (dir)
+			*dir = i->openmin;
+		return snd_interval_value(i);
+	}
+	assert(0);
+	return -EINVAL;
+}
+
+/**
+ * snd_pcm_hw_param_value_min
+ *
+ * Return the minimum value for field PAR.
+ */
+unsigned int snd_pcm_hw_param_value_min(const snd_pcm_hw_params_t *params,
+					snd_pcm_hw_param_t var, int *dir)
+{
+	if (hw_is_mask(var)) {
+		if (dir)
+			*dir = 0;
+		return snd_mask_min(hw_param_mask_c(params, var));
+	}
+	if (hw_is_interval(var)) {
+		const snd_interval_t *i = hw_param_interval_c(params, var);
+		if (dir)
+			*dir = i->openmin;
+		return snd_interval_min(i);
+	}
+	assert(0);
+	return -EINVAL;
+}
+
+/**
+ * snd_pcm_hw_param_value_max
+ *
+ * Return the maximum value for field PAR.
+ */
+unsigned int snd_pcm_hw_param_value_max(const snd_pcm_hw_params_t *params,
+					snd_pcm_hw_param_t var, int *dir)
+{
+	if (hw_is_mask(var)) {
+		if (dir)
+			*dir = 0;
+		return snd_mask_max(hw_param_mask_c(params, var));
+	}
+	if (hw_is_interval(var)) {
+		const snd_interval_t *i = hw_param_interval_c(params, var);
+		if (dir)
+			*dir = - (int) i->openmax;
+		return snd_interval_max(i);
+	}
+	assert(0);
+	return -EINVAL;
+}
+
+void _snd_pcm_hw_param_setempty(snd_pcm_hw_params_t *params,
+				snd_pcm_hw_param_t var)
+{
+	if (hw_is_mask(var)) {
+		snd_mask_none(hw_param_mask(params, var));
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	} else if (hw_is_interval(var)) {
+		snd_interval_none(hw_param_interval(params, var));
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	} else {
+		snd_BUG();
+	}
+}
+
+int _snd_pcm_hw_param_setinteger(snd_pcm_hw_params_t *params,
+				 snd_pcm_hw_param_t var)
+{
+	int changed;
+	assert(hw_is_interval(var));
+	changed = snd_interval_setinteger(hw_param_interval(params, var));
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+	
+/**
+ * snd_pcm_hw_param_setinteger
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all 
+ * non integer values. Reduce configuration space accordingly.
+ * Return -EINVAL if the configuration space is empty
+ */
+int snd_pcm_hw_param_setinteger(snd_pcm_t *pcm, 
+				snd_pcm_hw_params_t *params,
+				snd_pcm_hw_param_t var)
+{
+	int changed = _snd_pcm_hw_param_setinteger(params, var);
+	if (changed < 0)
+		return changed;
+	if (params->rmask) {
+		int err = snd_pcm_hw_refine(pcm, params);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+int _snd_pcm_hw_param_first(snd_pcm_hw_params_t *params,
+			    snd_pcm_hw_param_t var)
+{
+	int changed;
+	if (hw_is_mask(var))
+		changed = snd_mask_refine_first(hw_param_mask(params, var));
+	else if (hw_is_interval(var))
+		changed = snd_interval_refine_first(hw_param_interval(params, var));
+	else {
+		assert(0);
+		return -EINVAL;
+	}
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+
+/**
+ * snd_pcm_hw_param_first
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all 
+ * values > minimum. Reduce configuration space accordingly.
+ * Return the minimum.
+ */
+int snd_pcm_hw_param_first(snd_pcm_t *pcm, 
+			   snd_pcm_hw_params_t *params, 
+			   snd_pcm_hw_param_t var, int *dir)
+{
+	int changed = _snd_pcm_hw_param_first(params, var);
+	if (changed < 0)
+		return changed;
+	if (params->rmask) {
+		int err = snd_pcm_hw_refine(pcm, params);
+		assert(err >= 0);
+	}
+	return snd_pcm_hw_param_value(params, var, dir);
+}
+
+int _snd_pcm_hw_param_last(snd_pcm_hw_params_t *params,
+			   snd_pcm_hw_param_t var)
+{
+	int changed;
+	if (hw_is_mask(var))
+		changed = snd_mask_refine_last(hw_param_mask(params, var));
+	else if (hw_is_interval(var))
+		changed = snd_interval_refine_last(hw_param_interval(params, var));
+	else {
+		assert(0);
+		return -EINVAL;
+	}
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+
+/**
+ * snd_pcm_hw_param_last
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all 
+ * values < maximum. Reduce configuration space accordingly.
+ * Return the maximum.
+ */
+int snd_pcm_hw_param_last(snd_pcm_t *pcm, 
+			  snd_pcm_hw_params_t *params,
+			  snd_pcm_hw_param_t var, int *dir)
+{
+	int changed = _snd_pcm_hw_param_last(params, var);
+	if (changed < 0)
+		return changed;
+	if (params->rmask) {
+		int err = snd_pcm_hw_refine(pcm, params);
+		assert(err >= 0);
+	}
+	return snd_pcm_hw_param_value(params, var, dir);
+}
+
+int _snd_pcm_hw_param_min(snd_pcm_hw_params_t *params,
+			  snd_pcm_hw_param_t var, unsigned int val, int dir)
+{
+	int changed;
+	int open = 0;
+	if (dir) {
+		if (dir > 0) {
+			open = 1;
+		} else if (dir < 0) {
+			if (val > 0) {
+				open = 1;
+				val--;
+			}
+		}
+	}
+	if (hw_is_mask(var))
+		changed = snd_mask_refine_min(hw_param_mask(params, var), val + !!open);
+	else if (hw_is_interval(var))
+		changed = snd_interval_refine_min(hw_param_interval(params, var), val, open);
+	else {
+		assert(0);
+		return -EINVAL;
+	}
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+/**
+ * snd_pcm_hw_param_min
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all 
+ * values < VAL. Reduce configuration space accordingly.
+ * Return new minimum or -EINVAL if the configuration space is empty
+ */
+int snd_pcm_hw_param_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			 snd_pcm_hw_param_t var, unsigned int val, int *dir)
+{
+	int changed = _snd_pcm_hw_param_min(params, var, val, dir ? *dir : 0);
+	if (changed < 0)
+		return changed;
+	if (params->rmask) {
+		int err = snd_pcm_hw_refine(pcm, params);
+		if (err < 0)
+			return err;
+	}
+	return snd_pcm_hw_param_value_min(params, var, dir);
+}
+
+int _snd_pcm_hw_param_max(snd_pcm_hw_params_t *params,
+			   snd_pcm_hw_param_t var, unsigned int val, int dir)
+{
+	int changed;
+	int open = 0;
+	if (dir) {
+		if (dir < 0) {
+			open = 1;
+		} else if (dir > 0) {
+			open = 1;
+			val++;
+		}
+	}
+	if (hw_is_mask(var)) {
+		if (val == 0 && open) {
+			snd_mask_none(hw_param_mask(params, var));
+			changed = -EINVAL;
+		} else
+			changed = snd_mask_refine_max(hw_param_mask(params, var), val - !!open);
+	} else if (hw_is_interval(var))
+		changed = snd_interval_refine_max(hw_param_interval(params, var), val, open);
+	else {
+		assert(0);
+		return -EINVAL;
+	}
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+/**
+ * snd_pcm_hw_param_max
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all 
+ *  values >= VAL + 1. Reduce configuration space accordingly.
+ *  Return new maximum or -EINVAL if the configuration space is empty
+ */
+int snd_pcm_hw_param_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			  snd_pcm_hw_param_t var, unsigned int val, int *dir)
+{
+	int changed = _snd_pcm_hw_param_max(params, var, val, dir ? *dir : 0);
+	if (changed < 0)
+		return changed;
+	if (params->rmask) {
+		int err = snd_pcm_hw_refine(pcm, params);
+		if (err < 0)
+			return err;
+	}
+	return snd_pcm_hw_param_value_max(params, var, dir);
+}
+
+int _snd_pcm_hw_param_set(snd_pcm_hw_params_t *params,
+			  snd_pcm_hw_param_t var, unsigned int val, int dir)
+{
+	int changed;
+	if (hw_is_mask(var)) {
+		snd_mask_t *m = hw_param_mask(params, var);
+		if (val == 0 && dir < 0) {
+			changed = -EINVAL;
+			snd_mask_none(m);
+		} else {
+			if (dir > 0)
+				val++;
+			else if (dir < 0)
+				val--;
+			changed = snd_mask_refine_set(hw_param_mask(params, var), val);
+		}
+	} else if (hw_is_interval(var)) {
+		snd_interval_t *i = hw_param_interval(params, var);
+		if (val == 0 && dir < 0) {
+			changed = -EINVAL;
+			snd_interval_none(i);
+		} else if (dir == 0)
+			changed = snd_interval_refine_set(i, val);
+		else {
+			snd_interval_t t;
+			t.openmin = 1;
+			t.openmax = 1;
+			t.empty = 0;
+			t.integer = 0;
+			if (dir < 0) {
+				t.min = val - 1;
+				t.max = val;
+			} else {
+				t.min = val;
+				t.max = val+1;
+			}
+			changed = snd_interval_refine(i, &t);
+		}
+	} else {
+		assert(0);
+		return -EINVAL;
+	}
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+/**
+ * snd_pcm_hw_param_set
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all 
+ * values != VAL. Reduce configuration space accordingly.
+ *  Return VAL or -EINVAL if the configuration space is empty
+ */
+int snd_pcm_hw_param_set(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			 snd_pcm_hw_param_t var, unsigned int val, int dir)
+{
+	int changed = _snd_pcm_hw_param_set(params, var, val, dir);
+	if (changed < 0)
+		return changed;
+	if (params->rmask) {
+		int err = snd_pcm_hw_refine(pcm, params);
+		if (err < 0)
+			return err;
+	}
+	return snd_pcm_hw_param_value(params, var, NULL);
+}
+
+int _snd_pcm_hw_param_mask(snd_pcm_hw_params_t *params,
+			   snd_pcm_hw_param_t var, const snd_mask_t *val)
+{
+	int changed;
+	assert(hw_is_mask(var));
+	changed = snd_mask_refine(hw_param_mask(params, var), val);
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+/**
+ * snd_pcm_hw_param_mask
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all values
+ * not contained in MASK. Reduce configuration space accordingly.
+ * This function can be called only for SNDRV_PCM_HW_PARAM_ACCESS,
+ * SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_SUBFORMAT.
+ * Return 0 on success or -EINVAL
+ * if the configuration space is empty
+ */
+int snd_pcm_hw_param_mask(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			  snd_pcm_hw_param_t var, const snd_mask_t *val)
+{
+	int changed = _snd_pcm_hw_param_mask(params, var, val);
+	if (changed < 0)
+		return changed;
+	if (params->rmask) {
+		int err = snd_pcm_hw_refine(pcm, params);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int boundary_sub(int a, int adir,
+			int b, int bdir,
+			int *c, int *cdir)
+{
+	adir = adir < 0 ? -1 : (adir > 0 ? 1 : 0);
+	bdir = bdir < 0 ? -1 : (bdir > 0 ? 1 : 0);
+	*c = a - b;
+	*cdir = adir - bdir;
+	if (*cdir == -2) {
+		assert(*c > INT_MIN);
+		(*c)--;
+	} else if (*cdir == 2) {
+		assert(*c < INT_MAX);
+		(*c)++;
+	}
+	return 0;
+}
+
+static int boundary_lt(unsigned int a, int adir,
+		       unsigned int b, int bdir)
+{
+	assert(a > 0 || adir >= 0);
+	assert(b > 0 || bdir >= 0);
+	if (adir < 0) {
+		a--;
+		adir = 1;
+	} else if (adir > 0)
+		adir = 1;
+	if (bdir < 0) {
+		b--;
+		bdir = 1;
+	} else if (bdir > 0)
+		bdir = 1;
+	return a < b || (a == b && adir < bdir);
+}
+
+/* Return 1 if min is nearer to best than max */
+static int boundary_nearer(int min, int mindir,
+			   int best, int bestdir,
+			   int max, int maxdir)
+{
+	int dmin, dmindir;
+	int dmax, dmaxdir;
+	boundary_sub(best, bestdir, min, mindir, &dmin, &dmindir);
+	boundary_sub(max, maxdir, best, bestdir, &dmax, &dmaxdir);
+	return boundary_lt(dmin, dmindir, dmax, dmaxdir);
+}
+
+/**
+ * snd_pcm_hw_param_near
+ *
+ * Inside configuration space defined by PARAMS set PAR to the available value
+ * nearest to VAL. Reduce configuration space accordingly.
+ * This function cannot be called for SNDRV_PCM_HW_PARAM_ACCESS,
+ * SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_SUBFORMAT.
+ * Return the value found.
+  */
+int snd_pcm_hw_param_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			  snd_pcm_hw_param_t var, unsigned int best, int *dir)
+{
+	snd_pcm_hw_params_t *save = NULL;
+	int v;
+	unsigned int saved_min;
+	int last = 0;
+	int min, max;
+	int mindir, maxdir;
+	int valdir = dir ? *dir : 0;
+	/* FIXME */
+	if (best > INT_MAX)
+		best = INT_MAX;
+	min = max = best;
+	mindir = maxdir = valdir;
+	if (maxdir > 0)
+		maxdir = 0;
+	else if (maxdir == 0)
+		maxdir = -1;
+	else {
+		maxdir = 1;
+		max--;
+	}
+	save = kmalloc(sizeof(*save), GFP_KERNEL);
+	if (save == NULL)
+		return -ENOMEM;
+	*save = *params;
+	saved_min = min;
+	min = snd_pcm_hw_param_min(pcm, params, var, min, &mindir);
+	if (min >= 0) {
+		snd_pcm_hw_params_t *params1;
+		if (max < 0)
+			goto _end;
+		if ((unsigned int)min == saved_min && mindir == valdir)
+			goto _end;
+		params1 = kmalloc(sizeof(*params1), GFP_KERNEL);
+		if (params1 == NULL) {
+			kfree(save);
+			return -ENOMEM;
+		}
+		*params1 = *save;
+		max = snd_pcm_hw_param_max(pcm, params1, var, max, &maxdir);
+		if (max < 0) {
+			kfree(params1);
+			goto _end;
+		}
+		if (boundary_nearer(max, maxdir, best, valdir, min, mindir)) {
+			*params = *params1;
+			last = 1;
+		}
+		kfree(params1);
+	} else {
+		*params = *save;
+		max = snd_pcm_hw_param_max(pcm, params, var, max, &maxdir);
+		assert(max >= 0);
+		last = 1;
+	}
+ _end:
+ 	kfree(save);
+	if (last)
+		v = snd_pcm_hw_param_last(pcm, params, var, dir);
+	else
+		v = snd_pcm_hw_param_first(pcm, params, var, dir);
+	assert(v >= 0);
+	return v;
+}
+
+/**
+ * snd_pcm_hw_param_choose
+ *
+ * Choose one configuration from configuration space defined by PARAMS
+ * The configuration chosen is that obtained fixing in this order:
+ * first access, first format, first subformat, min channels,
+ * min rate, min period time, max buffer size, min tick time
+ */
+int snd_pcm_hw_params_choose(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	int err;
+
+	err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_ACCESS, NULL);
+	assert(err >= 0);
+
+	err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_FORMAT, NULL);
+	assert(err >= 0);
+
+	err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_SUBFORMAT, NULL);
+	assert(err >= 0);
+
+	err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_CHANNELS, NULL);
+	assert(err >= 0);
+
+	err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_RATE, NULL);
+	assert(err >= 0);
+
+	err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_PERIOD_TIME, NULL);
+	assert(err >= 0);
+
+	err = snd_pcm_hw_param_last(pcm, params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, NULL);
+	assert(err >= 0);
+
+	err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_TICK_TIME, NULL);
+	assert(err >= 0);
+
+	return 0;
+}
+
+#undef snd_pcm_t
+#undef assert
+
+static int snd_pcm_lib_ioctl_reset(snd_pcm_substream_t *substream,
+				   void *arg)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	unsigned long flags;
+	snd_pcm_stream_lock_irqsave(substream, flags);
+	if (snd_pcm_running(substream) &&
+	    snd_pcm_update_hw_ptr(substream) >= 0)
+		runtime->status->hw_ptr %= runtime->buffer_size;
+	else
+		runtime->status->hw_ptr = 0;
+	snd_pcm_stream_unlock_irqrestore(substream, flags);
+	return 0;
+}
+
+static int snd_pcm_lib_ioctl_channel_info(snd_pcm_substream_t *substream,
+					  void *arg)
+{
+	snd_pcm_channel_info_t *info = arg;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int width;
+	if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) {
+		info->offset = -1;
+		return 0;
+	}
+	width = snd_pcm_format_physical_width(runtime->format);
+	if (width < 0)
+		return width;
+	info->offset = 0;
+	switch (runtime->access) {
+	case SNDRV_PCM_ACCESS_MMAP_INTERLEAVED:
+	case SNDRV_PCM_ACCESS_RW_INTERLEAVED:
+		info->first = info->channel * width;
+		info->step = runtime->channels * width;
+		break;
+	case SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED:
+	case SNDRV_PCM_ACCESS_RW_NONINTERLEAVED:
+	{
+		size_t size = runtime->dma_bytes / runtime->channels;
+		info->first = info->channel * size * 8;
+		info->step = width;
+		break;
+	}
+	default:
+		snd_BUG();
+		break;
+	}
+	return 0;
+}
+
+/**
+ * snd_pcm_lib_ioctl - a generic PCM ioctl callback
+ * @substream: the pcm substream instance
+ * @cmd: ioctl command
+ * @arg: ioctl argument
+ *
+ * Processes the generic ioctl commands for PCM.
+ * Can be passed as the ioctl callback for PCM ops.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_lib_ioctl(snd_pcm_substream_t *substream,
+		      unsigned int cmd, void *arg)
+{
+	switch (cmd) {
+	case SNDRV_PCM_IOCTL1_INFO:
+		return 0;
+	case SNDRV_PCM_IOCTL1_RESET:
+		return snd_pcm_lib_ioctl_reset(substream, arg);
+	case SNDRV_PCM_IOCTL1_CHANNEL_INFO:
+		return snd_pcm_lib_ioctl_channel_info(substream, arg);
+	}
+	return -ENXIO;
+}
+
+/*
+ *  Conditions
+ */
+
+static void snd_pcm_system_tick_set(snd_pcm_substream_t *substream, 
+				    unsigned long ticks)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	if (ticks == 0)
+		del_timer(&runtime->tick_timer);
+	else {
+		ticks += (1000000 / HZ) - 1;
+		ticks /= (1000000 / HZ);
+		mod_timer(&runtime->tick_timer, jiffies + ticks);
+	}
+}
+
+/* Temporary alias */
+void snd_pcm_tick_set(snd_pcm_substream_t *substream, unsigned long ticks)
+{
+	snd_pcm_system_tick_set(substream, ticks);
+}
+
+void snd_pcm_tick_prepare(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_uframes_t frames = ULONG_MAX;
+	snd_pcm_uframes_t avail, dist;
+	unsigned int ticks;
+	u_int64_t n;
+	u_int32_t r;
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		if (runtime->silence_size >= runtime->boundary) {
+			frames = 1;
+		} else if (runtime->silence_size > 0 &&
+			   runtime->silence_filled < runtime->buffer_size) {
+			snd_pcm_sframes_t noise_dist;
+			noise_dist = snd_pcm_playback_hw_avail(runtime) + runtime->silence_filled;
+			snd_assert(noise_dist <= (snd_pcm_sframes_t)runtime->silence_threshold, );
+			frames = noise_dist - runtime->silence_threshold;
+		}
+		avail = snd_pcm_playback_avail(runtime);
+	} else {
+		avail = snd_pcm_capture_avail(runtime);
+	}
+	if (avail < runtime->control->avail_min) {
+		snd_pcm_sframes_t n = runtime->control->avail_min - avail;
+		if (n > 0 && frames > (snd_pcm_uframes_t)n)
+			frames = n;
+	}
+	if (avail < runtime->buffer_size) {
+		snd_pcm_sframes_t n = runtime->buffer_size - avail;
+		if (n > 0 && frames > (snd_pcm_uframes_t)n)
+			frames = n;
+	}
+	if (frames == ULONG_MAX) {
+		snd_pcm_tick_set(substream, 0);
+		return;
+	}
+	dist = runtime->status->hw_ptr - runtime->hw_ptr_base;
+	/* Distance to next interrupt */
+	dist = runtime->period_size - dist % runtime->period_size;
+	if (dist <= frames) {
+		snd_pcm_tick_set(substream, 0);
+		return;
+	}
+	/* the base time is us */
+	n = frames;
+	n *= 1000000;
+	div64_32(&n, runtime->tick_time * runtime->rate, &r);
+	ticks = n + (r > 0 ? 1 : 0);
+	if (ticks < runtime->sleep_min)
+		ticks = runtime->sleep_min;
+	snd_pcm_tick_set(substream, (unsigned long) ticks);
+}
+
+void snd_pcm_tick_elapsed(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime;
+	unsigned long flags;
+	
+	snd_assert(substream != NULL, return);
+	runtime = substream->runtime;
+	snd_assert(runtime != NULL, return);
+
+	snd_pcm_stream_lock_irqsave(substream, flags);
+	if (!snd_pcm_running(substream) ||
+	    snd_pcm_update_hw_ptr(substream) < 0)
+		goto _end;
+	if (runtime->sleep_min)
+		snd_pcm_tick_prepare(substream);
+ _end:
+	snd_pcm_stream_unlock_irqrestore(substream, flags);
+}
+
+/**
+ * snd_pcm_period_elapsed - update the pcm status for the next period
+ * @substream: the pcm substream instance
+ *
+ * This function is called from the interrupt handler when the
+ * PCM has processed the period size.  It will update the current
+ * pointer, set up the tick, wake up sleepers, etc.
+ *
+ * Even if more than one periods have elapsed since the last call, you
+ * have to call this only once.
+ */
+void snd_pcm_period_elapsed(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime;
+	unsigned long flags;
+
+	snd_assert(substream != NULL, return);
+	runtime = substream->runtime;
+	snd_assert(runtime != NULL, return);
+
+	if (runtime->transfer_ack_begin)
+		runtime->transfer_ack_begin(substream);
+
+	snd_pcm_stream_lock_irqsave(substream, flags);
+	if (!snd_pcm_running(substream) ||
+	    snd_pcm_update_hw_ptr_interrupt(substream) < 0)
+		goto _end;
+
+	if (substream->timer_running)
+		snd_timer_interrupt(substream->timer, 1);
+	if (runtime->sleep_min)
+		snd_pcm_tick_prepare(substream);
+ _end:
+	snd_pcm_stream_unlock_irqrestore(substream, flags);
+	if (runtime->transfer_ack_end)
+		runtime->transfer_ack_end(substream);
+	kill_fasync(&runtime->fasync, SIGIO, POLL_IN);
+}
+
+static int snd_pcm_lib_write_transfer(snd_pcm_substream_t *substream,
+				      unsigned int hwoff,
+				      unsigned long data, unsigned int off,
+				      snd_pcm_uframes_t frames)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int err;
+	char __user *buf = (char __user *) data + frames_to_bytes(runtime, off);
+	if (substream->ops->copy) {
+		if ((err = substream->ops->copy(substream, -1, hwoff, buf, frames)) < 0)
+			return err;
+	} else {
+		char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
+		snd_assert(runtime->dma_area, return -EFAULT);
+		if (copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames)))
+			return -EFAULT;
+	}
+	return 0;
+}
+ 
+typedef int (*transfer_f)(snd_pcm_substream_t *substream, unsigned int hwoff,
+			  unsigned long data, unsigned int off,
+			  snd_pcm_uframes_t size);
+
+static snd_pcm_sframes_t snd_pcm_lib_write1(snd_pcm_substream_t *substream, 
+					    unsigned long data,
+					    snd_pcm_uframes_t size,
+					    int nonblock,
+					    transfer_f transfer)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_uframes_t xfer = 0;
+	snd_pcm_uframes_t offset = 0;
+	int err = 0;
+
+	if (size == 0)
+		return 0;
+	if (size > runtime->xfer_align)
+		size -= size % runtime->xfer_align;
+
+	snd_pcm_stream_lock_irq(substream);
+	switch (runtime->status->state) {
+	case SNDRV_PCM_STATE_PREPARED:
+	case SNDRV_PCM_STATE_RUNNING:
+	case SNDRV_PCM_STATE_PAUSED:
+		break;
+	case SNDRV_PCM_STATE_XRUN:
+		err = -EPIPE;
+		goto _end_unlock;
+	case SNDRV_PCM_STATE_SUSPENDED:
+		err = -ESTRPIPE;
+		goto _end_unlock;
+	default:
+		err = -EBADFD;
+		goto _end_unlock;
+	}
+
+	while (size > 0) {
+		snd_pcm_uframes_t frames, appl_ptr, appl_ofs;
+		snd_pcm_uframes_t avail;
+		snd_pcm_uframes_t cont;
+		if (runtime->sleep_min == 0 && runtime->status->state == SNDRV_PCM_STATE_RUNNING)
+			snd_pcm_update_hw_ptr(substream);
+		avail = snd_pcm_playback_avail(runtime);
+		if (((avail < runtime->control->avail_min && size > avail) ||
+		   (size >= runtime->xfer_align && avail < runtime->xfer_align))) {
+			wait_queue_t wait;
+			enum { READY, SIGNALED, ERROR, SUSPENDED, EXPIRED } state;
+			long tout;
+
+			if (nonblock) {
+				err = -EAGAIN;
+				goto _end_unlock;
+			}
+
+			init_waitqueue_entry(&wait, current);
+			add_wait_queue(&runtime->sleep, &wait);
+			while (1) {
+				if (signal_pending(current)) {
+					state = SIGNALED;
+					break;
+				}
+				set_current_state(TASK_INTERRUPTIBLE);
+				snd_pcm_stream_unlock_irq(substream);
+				tout = schedule_timeout(10 * HZ);
+				snd_pcm_stream_lock_irq(substream);
+				if (tout == 0) {
+					if (runtime->status->state != SNDRV_PCM_STATE_PREPARED &&
+					    runtime->status->state != SNDRV_PCM_STATE_PAUSED) {
+						state = runtime->status->state == SNDRV_PCM_STATE_SUSPENDED ? SUSPENDED : EXPIRED;
+						break;
+					}
+				}
+				switch (runtime->status->state) {
+				case SNDRV_PCM_STATE_XRUN:
+				case SNDRV_PCM_STATE_DRAINING:
+					state = ERROR;
+					goto _end_loop;
+				case SNDRV_PCM_STATE_SUSPENDED:
+					state = SUSPENDED;
+					goto _end_loop;
+				default:
+					break;
+				}
+				avail = snd_pcm_playback_avail(runtime);
+				if (avail >= runtime->control->avail_min) {
+					state = READY;
+					break;
+				}
+			}
+		       _end_loop:
+			remove_wait_queue(&runtime->sleep, &wait);
+
+			switch (state) {
+			case ERROR:
+				err = -EPIPE;
+				goto _end_unlock;
+			case SUSPENDED:
+				err = -ESTRPIPE;
+				goto _end_unlock;
+			case SIGNALED:
+				err = -ERESTARTSYS;
+				goto _end_unlock;
+			case EXPIRED:
+				snd_printd("playback write error (DMA or IRQ trouble?)\n");
+				err = -EIO;
+				goto _end_unlock;
+			default:
+				break;
+			}
+		}
+		if (avail > runtime->xfer_align)
+			avail -= avail % runtime->xfer_align;
+		frames = size > avail ? avail : size;
+		cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size;
+		if (frames > cont)
+			frames = cont;
+		snd_assert(frames != 0, snd_pcm_stream_unlock_irq(substream); return -EINVAL);
+		appl_ptr = runtime->control->appl_ptr;
+		appl_ofs = appl_ptr % runtime->buffer_size;
+		snd_pcm_stream_unlock_irq(substream);
+		if ((err = transfer(substream, appl_ofs, data, offset, frames)) < 0)
+			goto _end;
+		snd_pcm_stream_lock_irq(substream);
+		switch (runtime->status->state) {
+		case SNDRV_PCM_STATE_XRUN:
+			err = -EPIPE;
+			goto _end_unlock;
+		case SNDRV_PCM_STATE_SUSPENDED:
+			err = -ESTRPIPE;
+			goto _end_unlock;
+		default:
+			break;
+		}
+		appl_ptr += frames;
+		if (appl_ptr >= runtime->boundary)
+			appl_ptr -= runtime->boundary;
+		runtime->control->appl_ptr = appl_ptr;
+		if (substream->ops->ack)
+			substream->ops->ack(substream);
+
+		offset += frames;
+		size -= frames;
+		xfer += frames;
+		if (runtime->status->state == SNDRV_PCM_STATE_PREPARED &&
+		    snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold) {
+			err = snd_pcm_start(substream);
+			if (err < 0)
+				goto _end_unlock;
+		}
+		if (runtime->sleep_min &&
+		    runtime->status->state == SNDRV_PCM_STATE_RUNNING)
+			snd_pcm_tick_prepare(substream);
+	}
+ _end_unlock:
+	snd_pcm_stream_unlock_irq(substream);
+ _end:
+	return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
+}
+
+snd_pcm_sframes_t snd_pcm_lib_write(snd_pcm_substream_t *substream, const void __user *buf, snd_pcm_uframes_t size)
+{
+	snd_pcm_runtime_t *runtime;
+	int nonblock;
+
+	snd_assert(substream != NULL, return -ENXIO);
+	runtime = substream->runtime;
+	snd_assert(runtime != NULL, return -ENXIO);
+	snd_assert(substream->ops->copy != NULL || runtime->dma_area != NULL, return -EINVAL);
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+		return -EBADFD;
+
+	snd_assert(substream->ffile != NULL, return -ENXIO);
+	nonblock = !!(substream->ffile->f_flags & O_NONBLOCK);
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+	if (substream->oss.oss) {
+		snd_pcm_oss_setup_t *setup = substream->oss.setup;
+		if (setup != NULL) {
+			if (setup->nonblock)
+				nonblock = 1;
+			else if (setup->block)
+				nonblock = 0;
+		}
+	}
+#endif
+
+	if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED &&
+	    runtime->channels > 1)
+		return -EINVAL;
+	return snd_pcm_lib_write1(substream, (unsigned long)buf, size, nonblock,
+				  snd_pcm_lib_write_transfer);
+}
+
+static int snd_pcm_lib_writev_transfer(snd_pcm_substream_t *substream,
+				       unsigned int hwoff,
+				       unsigned long data, unsigned int off,
+				       snd_pcm_uframes_t frames)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int err;
+	void __user **bufs = (void __user **)data;
+	int channels = runtime->channels;
+	int c;
+	if (substream->ops->copy) {
+		snd_assert(substream->ops->silence != NULL, return -EINVAL);
+		for (c = 0; c < channels; ++c, ++bufs) {
+			if (*bufs == NULL) {
+				if ((err = substream->ops->silence(substream, c, hwoff, frames)) < 0)
+					return err;
+			} else {
+				char __user *buf = *bufs + samples_to_bytes(runtime, off);
+				if ((err = substream->ops->copy(substream, c, hwoff, buf, frames)) < 0)
+					return err;
+			}
+		}
+	} else {
+		/* default transfer behaviour */
+		size_t dma_csize = runtime->dma_bytes / channels;
+		snd_assert(runtime->dma_area, return -EFAULT);
+		for (c = 0; c < channels; ++c, ++bufs) {
+			char *hwbuf = runtime->dma_area + (c * dma_csize) + samples_to_bytes(runtime, hwoff);
+			if (*bufs == NULL) {
+				snd_pcm_format_set_silence(runtime->format, hwbuf, frames);
+			} else {
+				char __user *buf = *bufs + samples_to_bytes(runtime, off);
+				if (copy_from_user(hwbuf, buf, samples_to_bytes(runtime, frames)))
+					return -EFAULT;
+			}
+		}
+	}
+	return 0;
+}
+ 
+snd_pcm_sframes_t snd_pcm_lib_writev(snd_pcm_substream_t *substream,
+				     void __user **bufs,
+				     snd_pcm_uframes_t frames)
+{
+	snd_pcm_runtime_t *runtime;
+	int nonblock;
+
+	snd_assert(substream != NULL, return -ENXIO);
+	runtime = substream->runtime;
+	snd_assert(runtime != NULL, return -ENXIO);
+	snd_assert(substream->ops->copy != NULL || runtime->dma_area != NULL, return -EINVAL);
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+		return -EBADFD;
+
+	snd_assert(substream->ffile != NULL, return -ENXIO);
+	nonblock = !!(substream->ffile->f_flags & O_NONBLOCK);
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+	if (substream->oss.oss) {
+		snd_pcm_oss_setup_t *setup = substream->oss.setup;
+		if (setup != NULL) {
+			if (setup->nonblock)
+				nonblock = 1;
+			else if (setup->block)
+				nonblock = 0;
+		}
+	}
+#endif
+
+	if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED)
+		return -EINVAL;
+	return snd_pcm_lib_write1(substream, (unsigned long)bufs, frames,
+				  nonblock, snd_pcm_lib_writev_transfer);
+}
+
+static int snd_pcm_lib_read_transfer(snd_pcm_substream_t *substream, 
+				     unsigned int hwoff,
+				     unsigned long data, unsigned int off,
+				     snd_pcm_uframes_t frames)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int err;
+	char __user *buf = (char __user *) data + frames_to_bytes(runtime, off);
+	if (substream->ops->copy) {
+		if ((err = substream->ops->copy(substream, -1, hwoff, buf, frames)) < 0)
+			return err;
+	} else {
+		char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
+		snd_assert(runtime->dma_area, return -EFAULT);
+		if (copy_to_user(buf, hwbuf, frames_to_bytes(runtime, frames)))
+			return -EFAULT;
+	}
+	return 0;
+}
+
+static snd_pcm_sframes_t snd_pcm_lib_read1(snd_pcm_substream_t *substream,
+					   unsigned long data,
+					   snd_pcm_uframes_t size,
+					   int nonblock,
+					   transfer_f transfer)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_uframes_t xfer = 0;
+	snd_pcm_uframes_t offset = 0;
+	int err = 0;
+
+	if (size == 0)
+		return 0;
+	if (size > runtime->xfer_align)
+		size -= size % runtime->xfer_align;
+
+	snd_pcm_stream_lock_irq(substream);
+	switch (runtime->status->state) {
+	case SNDRV_PCM_STATE_PREPARED:
+		if (size >= runtime->start_threshold) {
+			err = snd_pcm_start(substream);
+			if (err < 0)
+				goto _end_unlock;
+		}
+		break;
+	case SNDRV_PCM_STATE_DRAINING:
+	case SNDRV_PCM_STATE_RUNNING:
+	case SNDRV_PCM_STATE_PAUSED:
+		break;
+	case SNDRV_PCM_STATE_XRUN:
+		err = -EPIPE;
+		goto _end_unlock;
+	case SNDRV_PCM_STATE_SUSPENDED:
+		err = -ESTRPIPE;
+		goto _end_unlock;
+	default:
+		err = -EBADFD;
+		goto _end_unlock;
+	}
+
+	while (size > 0) {
+		snd_pcm_uframes_t frames, appl_ptr, appl_ofs;
+		snd_pcm_uframes_t avail;
+		snd_pcm_uframes_t cont;
+		if (runtime->sleep_min == 0 && runtime->status->state == SNDRV_PCM_STATE_RUNNING)
+			snd_pcm_update_hw_ptr(substream);
+	      __draining:
+		avail = snd_pcm_capture_avail(runtime);
+		if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) {
+			if (avail < runtime->xfer_align) {
+				err = -EPIPE;
+				goto _end_unlock;
+			}
+		} else if ((avail < runtime->control->avail_min && size > avail) ||
+			   (size >= runtime->xfer_align && avail < runtime->xfer_align)) {
+			wait_queue_t wait;
+			enum { READY, SIGNALED, ERROR, SUSPENDED, EXPIRED } state;
+			long tout;
+
+			if (nonblock) {
+				err = -EAGAIN;
+				goto _end_unlock;
+			}
+
+			init_waitqueue_entry(&wait, current);
+			add_wait_queue(&runtime->sleep, &wait);
+			while (1) {
+				if (signal_pending(current)) {
+					state = SIGNALED;
+					break;
+				}
+				set_current_state(TASK_INTERRUPTIBLE);
+				snd_pcm_stream_unlock_irq(substream);
+				tout = schedule_timeout(10 * HZ);
+				snd_pcm_stream_lock_irq(substream);
+				if (tout == 0) {
+					if (runtime->status->state != SNDRV_PCM_STATE_PREPARED &&
+					    runtime->status->state != SNDRV_PCM_STATE_PAUSED) {
+						state = runtime->status->state == SNDRV_PCM_STATE_SUSPENDED ? SUSPENDED : EXPIRED;
+						break;
+					}
+				}
+				switch (runtime->status->state) {
+				case SNDRV_PCM_STATE_XRUN:
+					state = ERROR;
+					goto _end_loop;
+				case SNDRV_PCM_STATE_SUSPENDED:
+					state = SUSPENDED;
+					goto _end_loop;
+				case SNDRV_PCM_STATE_DRAINING:
+					goto __draining;
+				default:
+					break;
+				}
+				avail = snd_pcm_capture_avail(runtime);
+				if (avail >= runtime->control->avail_min) {
+					state = READY;
+					break;
+				}
+			}
+		       _end_loop:
+			remove_wait_queue(&runtime->sleep, &wait);
+
+			switch (state) {
+			case ERROR:
+				err = -EPIPE;
+				goto _end_unlock;
+			case SUSPENDED:
+				err = -ESTRPIPE;
+				goto _end_unlock;
+			case SIGNALED:
+				err = -ERESTARTSYS;
+				goto _end_unlock;
+			case EXPIRED:
+				snd_printd("capture read error (DMA or IRQ trouble?)\n");
+				err = -EIO;
+				goto _end_unlock;
+			default:
+				break;
+			}
+		}
+		if (avail > runtime->xfer_align)
+			avail -= avail % runtime->xfer_align;
+		frames = size > avail ? avail : size;
+		cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size;
+		if (frames > cont)
+			frames = cont;
+		snd_assert(frames != 0, snd_pcm_stream_unlock_irq(substream); return -EINVAL);
+		appl_ptr = runtime->control->appl_ptr;
+		appl_ofs = appl_ptr % runtime->buffer_size;
+		snd_pcm_stream_unlock_irq(substream);
+		if ((err = transfer(substream, appl_ofs, data, offset, frames)) < 0)
+			goto _end;
+		snd_pcm_stream_lock_irq(substream);
+		switch (runtime->status->state) {
+		case SNDRV_PCM_STATE_XRUN:
+			err = -EPIPE;
+			goto _end_unlock;
+		case SNDRV_PCM_STATE_SUSPENDED:
+			err = -ESTRPIPE;
+			goto _end_unlock;
+		default:
+			break;
+		}
+		appl_ptr += frames;
+		if (appl_ptr >= runtime->boundary)
+			appl_ptr -= runtime->boundary;
+		runtime->control->appl_ptr = appl_ptr;
+		if (substream->ops->ack)
+			substream->ops->ack(substream);
+
+		offset += frames;
+		size -= frames;
+		xfer += frames;
+		if (runtime->sleep_min &&
+		    runtime->status->state == SNDRV_PCM_STATE_RUNNING)
+			snd_pcm_tick_prepare(substream);
+	}
+ _end_unlock:
+	snd_pcm_stream_unlock_irq(substream);
+ _end:
+	return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
+}
+
+snd_pcm_sframes_t snd_pcm_lib_read(snd_pcm_substream_t *substream, void __user *buf, snd_pcm_uframes_t size)
+{
+	snd_pcm_runtime_t *runtime;
+	int nonblock;
+	
+	snd_assert(substream != NULL, return -ENXIO);
+	runtime = substream->runtime;
+	snd_assert(runtime != NULL, return -ENXIO);
+	snd_assert(substream->ops->copy != NULL || runtime->dma_area != NULL, return -EINVAL);
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+		return -EBADFD;
+
+	snd_assert(substream->ffile != NULL, return -ENXIO);
+	nonblock = !!(substream->ffile->f_flags & O_NONBLOCK);
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+	if (substream->oss.oss) {
+		snd_pcm_oss_setup_t *setup = substream->oss.setup;
+		if (setup != NULL) {
+			if (setup->nonblock)
+				nonblock = 1;
+			else if (setup->block)
+				nonblock = 0;
+		}
+	}
+#endif
+	if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED)
+		return -EINVAL;
+	return snd_pcm_lib_read1(substream, (unsigned long)buf, size, nonblock, snd_pcm_lib_read_transfer);
+}
+
+static int snd_pcm_lib_readv_transfer(snd_pcm_substream_t *substream,
+				      unsigned int hwoff,
+				      unsigned long data, unsigned int off,
+				      snd_pcm_uframes_t frames)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int err;
+	void __user **bufs = (void __user **)data;
+	int channels = runtime->channels;
+	int c;
+	if (substream->ops->copy) {
+		for (c = 0; c < channels; ++c, ++bufs) {
+			char __user *buf;
+			if (*bufs == NULL)
+				continue;
+			buf = *bufs + samples_to_bytes(runtime, off);
+			if ((err = substream->ops->copy(substream, c, hwoff, buf, frames)) < 0)
+				return err;
+		}
+	} else {
+		snd_pcm_uframes_t dma_csize = runtime->dma_bytes / channels;
+		snd_assert(runtime->dma_area, return -EFAULT);
+		for (c = 0; c < channels; ++c, ++bufs) {
+			char *hwbuf;
+			char __user *buf;
+			if (*bufs == NULL)
+				continue;
+
+			hwbuf = runtime->dma_area + (c * dma_csize) + samples_to_bytes(runtime, hwoff);
+			buf = *bufs + samples_to_bytes(runtime, off);
+			if (copy_to_user(buf, hwbuf, samples_to_bytes(runtime, frames)))
+				return -EFAULT;
+		}
+	}
+	return 0;
+}
+ 
+snd_pcm_sframes_t snd_pcm_lib_readv(snd_pcm_substream_t *substream,
+				    void __user **bufs,
+				    snd_pcm_uframes_t frames)
+{
+	snd_pcm_runtime_t *runtime;
+	int nonblock;
+
+	snd_assert(substream != NULL, return -ENXIO);
+	runtime = substream->runtime;
+	snd_assert(runtime != NULL, return -ENXIO);
+	snd_assert(substream->ops->copy != NULL || runtime->dma_area != NULL, return -EINVAL);
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+		return -EBADFD;
+
+	snd_assert(substream->ffile != NULL, return -ENXIO);
+	nonblock = !!(substream->ffile->f_flags & O_NONBLOCK);
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+	if (substream->oss.oss) {
+		snd_pcm_oss_setup_t *setup = substream->oss.setup;
+		if (setup != NULL) {
+			if (setup->nonblock)
+				nonblock = 1;
+			else if (setup->block)
+				nonblock = 0;
+		}
+	}
+#endif
+
+	if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED)
+		return -EINVAL;
+	return snd_pcm_lib_read1(substream, (unsigned long)bufs, frames, nonblock, snd_pcm_lib_readv_transfer);
+}
+
+/*
+ *  Exported symbols
+ */
+
+EXPORT_SYMBOL(snd_interval_refine);
+EXPORT_SYMBOL(snd_interval_list);
+EXPORT_SYMBOL(snd_interval_ratnum);
+EXPORT_SYMBOL(snd_interval_muldivk);
+EXPORT_SYMBOL(snd_interval_mulkdiv);
+EXPORT_SYMBOL(snd_interval_div);
+EXPORT_SYMBOL(_snd_pcm_hw_params_any);
+EXPORT_SYMBOL(_snd_pcm_hw_param_min);
+EXPORT_SYMBOL(_snd_pcm_hw_param_set);
+EXPORT_SYMBOL(_snd_pcm_hw_param_setempty);
+EXPORT_SYMBOL(_snd_pcm_hw_param_setinteger);
+EXPORT_SYMBOL(snd_pcm_hw_param_value_min);
+EXPORT_SYMBOL(snd_pcm_hw_param_value_max);
+EXPORT_SYMBOL(snd_pcm_hw_param_mask);
+EXPORT_SYMBOL(snd_pcm_hw_param_first);
+EXPORT_SYMBOL(snd_pcm_hw_param_last);
+EXPORT_SYMBOL(snd_pcm_hw_param_near);
+EXPORT_SYMBOL(snd_pcm_hw_param_set);
+EXPORT_SYMBOL(snd_pcm_hw_refine);
+EXPORT_SYMBOL(snd_pcm_hw_params);
+EXPORT_SYMBOL(snd_pcm_hw_constraints_init);
+EXPORT_SYMBOL(snd_pcm_hw_constraints_complete);
+EXPORT_SYMBOL(snd_pcm_hw_constraint_list);
+EXPORT_SYMBOL(snd_pcm_hw_constraint_step);
+EXPORT_SYMBOL(snd_pcm_hw_constraint_ratnums);
+EXPORT_SYMBOL(snd_pcm_hw_constraint_ratdens);
+EXPORT_SYMBOL(snd_pcm_hw_constraint_msbits);
+EXPORT_SYMBOL(snd_pcm_hw_constraint_minmax);
+EXPORT_SYMBOL(snd_pcm_hw_constraint_integer);
+EXPORT_SYMBOL(snd_pcm_hw_constraint_pow2);
+EXPORT_SYMBOL(snd_pcm_hw_rule_add);
+EXPORT_SYMBOL(snd_pcm_set_ops);
+EXPORT_SYMBOL(snd_pcm_set_sync);
+EXPORT_SYMBOL(snd_pcm_lib_ioctl);
+EXPORT_SYMBOL(snd_pcm_stop);
+EXPORT_SYMBOL(snd_pcm_period_elapsed);
+EXPORT_SYMBOL(snd_pcm_lib_write);
+EXPORT_SYMBOL(snd_pcm_lib_read);
+EXPORT_SYMBOL(snd_pcm_lib_writev);
+EXPORT_SYMBOL(snd_pcm_lib_readv);
+EXPORT_SYMBOL(snd_pcm_lib_buffer_bytes);
+EXPORT_SYMBOL(snd_pcm_lib_period_bytes);
+/* pcm_memory.c */
+EXPORT_SYMBOL(snd_pcm_lib_preallocate_free_for_all);
+EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages);
+EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages_for_all);
+EXPORT_SYMBOL(snd_pcm_sgbuf_ops_page);
+EXPORT_SYMBOL(snd_pcm_lib_malloc_pages);
+EXPORT_SYMBOL(snd_pcm_lib_free_pages);
diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c
new file mode 100644
index 000000000000..f1d5f7a6ee0c
--- /dev/null
+++ b/sound/core/pcm_memory.c
@@ -0,0 +1,363 @@
+/*
+ *  Digital Audio (PCM) abstract layer
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <asm/io.h>
+#include <linux/time.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+
+static int preallocate_dma = 1;
+module_param(preallocate_dma, int, 0444);
+MODULE_PARM_DESC(preallocate_dma, "Preallocate DMA memory when the PCM devices are initialized.");
+
+static int maximum_substreams = 4;
+module_param(maximum_substreams, int, 0444);
+MODULE_PARM_DESC(maximum_substreams, "Maximum substreams with preallocated DMA memory.");
+
+static const size_t snd_minimum_buffer = 16384;
+
+
+/*
+ * try to allocate as the large pages as possible.
+ * stores the resultant memory size in *res_size.
+ *
+ * the minimum size is snd_minimum_buffer.  it should be power of 2.
+ */
+static int preallocate_pcm_pages(snd_pcm_substream_t *substream, size_t size)
+{
+	struct snd_dma_buffer *dmab = &substream->dma_buffer;
+	int err;
+
+	snd_assert(size > 0, return -EINVAL);
+
+	/* already reserved? */
+	if (snd_dma_get_reserved_buf(dmab, substream->dma_buf_id) > 0) {
+		if (dmab->bytes >= size)
+			return 0; /* yes */
+		/* no, free the reserved block */
+		snd_dma_free_pages(dmab);
+		dmab->bytes = 0;
+	}
+
+	do {
+		if ((err = snd_dma_alloc_pages(dmab->dev.type, dmab->dev.dev,
+					       size, dmab)) < 0) {
+			if (err != -ENOMEM)
+				return err; /* fatal error */
+		} else
+			return 0;
+		size >>= 1;
+	} while (size >= snd_minimum_buffer);
+	dmab->bytes = 0; /* tell error */
+	return 0;
+}
+
+/*
+ * release the preallocated buffer if not yet done.
+ */
+static void snd_pcm_lib_preallocate_dma_free(snd_pcm_substream_t *substream)
+{
+	if (substream->dma_buffer.area == NULL)
+		return;
+	if (substream->dma_buf_id)
+		snd_dma_reserve_buf(&substream->dma_buffer, substream->dma_buf_id);
+	else
+		snd_dma_free_pages(&substream->dma_buffer);
+	substream->dma_buffer.area = NULL;
+}
+
+/**
+ * snd_pcm_lib_preallocate_free - release the preallocated buffer of the specified substream.
+ * @substream: the pcm substream instance
+ *
+ * Releases the pre-allocated buffer of the given substream.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_lib_preallocate_free(snd_pcm_substream_t *substream)
+{
+	snd_pcm_lib_preallocate_dma_free(substream);
+	if (substream->proc_prealloc_entry) {
+		snd_info_unregister(substream->proc_prealloc_entry);
+		substream->proc_prealloc_entry = NULL;
+	}
+	return 0;
+}
+
+/**
+ * snd_pcm_lib_preallocate_free_for_all - release all pre-allocated buffers on the pcm
+ * @pcm: the pcm instance
+ *
+ * Releases all the pre-allocated buffers on the given pcm.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_lib_preallocate_free_for_all(snd_pcm_t *pcm)
+{
+	snd_pcm_substream_t *substream;
+	int stream;
+
+	for (stream = 0; stream < 2; stream++)
+		for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
+			snd_pcm_lib_preallocate_free(substream);
+	return 0;
+}
+
+/*
+ * read callback for prealloc proc file
+ *
+ * prints the current allocated size in kB.
+ */
+static void snd_pcm_lib_preallocate_proc_read(snd_info_entry_t *entry,
+					      snd_info_buffer_t *buffer)
+{
+	snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data;
+	snd_iprintf(buffer, "%lu\n", (unsigned long) substream->dma_buffer.bytes / 1024);
+}
+
+/*
+ * write callback for prealloc proc file
+ *
+ * accepts the preallocation size in kB.
+ */
+static void snd_pcm_lib_preallocate_proc_write(snd_info_entry_t *entry,
+					       snd_info_buffer_t *buffer)
+{
+	snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data;
+	char line[64], str[64];
+	size_t size;
+	struct snd_dma_buffer new_dmab;
+
+	if (substream->runtime) {
+		buffer->error = -EBUSY;
+		return;
+	}
+	if (!snd_info_get_line(buffer, line, sizeof(line))) {
+		snd_info_get_str(str, line, sizeof(str));
+		size = simple_strtoul(str, NULL, 10) * 1024;
+		if ((size != 0 && size < 8192) || size > substream->dma_max) {
+			buffer->error = -EINVAL;
+			return;
+		}
+		if (substream->dma_buffer.bytes == size)
+			return;
+		memset(&new_dmab, 0, sizeof(new_dmab));
+		new_dmab.dev = substream->dma_buffer.dev;
+		if (size > 0) {
+			if (snd_dma_alloc_pages(substream->dma_buffer.dev.type,
+						substream->dma_buffer.dev.dev,
+						size, &new_dmab) < 0) {
+				buffer->error = -ENOMEM;
+				return;
+			}
+			substream->buffer_bytes_max = size;
+		} else {
+			substream->buffer_bytes_max = UINT_MAX;
+		}
+		if (substream->dma_buffer.area)
+			snd_dma_free_pages(&substream->dma_buffer);
+		substream->dma_buffer = new_dmab;
+	} else {
+		buffer->error = -EINVAL;
+	}
+}
+
+/*
+ * pre-allocate the buffer and create a proc file for the substream
+ */
+static int snd_pcm_lib_preallocate_pages1(snd_pcm_substream_t *substream,
+					  size_t size, size_t max)
+{
+	snd_info_entry_t *entry;
+
+	if (size > 0 && preallocate_dma && substream->number < maximum_substreams)
+		preallocate_pcm_pages(substream, size);
+
+	if (substream->dma_buffer.bytes > 0)
+		substream->buffer_bytes_max = substream->dma_buffer.bytes;
+	substream->dma_max = max;
+	if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc", substream->proc_root)) != NULL) {
+		entry->c.text.read_size = 64;
+		entry->c.text.read = snd_pcm_lib_preallocate_proc_read;
+		entry->c.text.write_size = 64;
+		entry->c.text.write = snd_pcm_lib_preallocate_proc_write;
+		entry->private_data = substream;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	substream->proc_prealloc_entry = entry;
+	return 0;
+}
+
+
+/**
+ * snd_pcm_lib_preallocate_pages - pre-allocation for the given DMA type
+ * @substream: the pcm substream instance
+ * @type: DMA type (SNDRV_DMA_TYPE_*)
+ * @data: DMA type dependant data
+ * @size: the requested pre-allocation size in bytes
+ * @max: the max. allowed pre-allocation size
+ *
+ * Do pre-allocation for the given DMA buffer type.
+ *
+ * When substream->dma_buf_id is set, the function tries to look for
+ * the reserved buffer, and the buffer is not freed but reserved at
+ * destruction time.  The dma_buf_id must be unique for all systems
+ * (in the same DMA buffer type) e.g. using snd_dma_pci_buf_id().
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_lib_preallocate_pages(snd_pcm_substream_t *substream,
+				  int type, struct device *data,
+				  size_t size, size_t max)
+{
+	substream->dma_buffer.dev.type = type;
+	substream->dma_buffer.dev.dev = data;
+	return snd_pcm_lib_preallocate_pages1(substream, size, max);
+}
+
+/**
+ * snd_pcm_lib_preallocate_pages_for_all - pre-allocation for continous memory type (all substreams)
+ * @substream: the pcm substream instance
+ * @type: DMA type (SNDRV_DMA_TYPE_*)
+ * @data: DMA type dependant data
+ * @size: the requested pre-allocation size in bytes
+ * @max: the max. allowed pre-allocation size
+ *
+ * Do pre-allocation to all substreams of the given pcm for the
+ * specified DMA type.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_lib_preallocate_pages_for_all(snd_pcm_t *pcm,
+					  int type, void *data,
+					  size_t size, size_t max)
+{
+	snd_pcm_substream_t *substream;
+	int stream, err;
+
+	for (stream = 0; stream < 2; stream++)
+		for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
+			if ((err = snd_pcm_lib_preallocate_pages(substream, type, data, size, max)) < 0)
+				return err;
+	return 0;
+}
+
+/**
+ * snd_pcm_sgbuf_ops_page - get the page struct at the given offset
+ * @substream: the pcm substream instance
+ * @offset: the buffer offset
+ *
+ * Returns the page struct at the given buffer offset.
+ * Used as the page callback of PCM ops.
+ */
+struct page *snd_pcm_sgbuf_ops_page(snd_pcm_substream_t *substream, unsigned long offset)
+{
+	struct snd_sg_buf *sgbuf = snd_pcm_substream_sgbuf(substream);
+
+	unsigned int idx = offset >> PAGE_SHIFT;
+	if (idx >= (unsigned int)sgbuf->pages)
+		return NULL;
+	return sgbuf->page_table[idx];
+}
+
+/**
+ * snd_pcm_lib_malloc_pages - allocate the DMA buffer
+ * @substream: the substream to allocate the DMA buffer to
+ * @size: the requested buffer size in bytes
+ *
+ * Allocates the DMA buffer on the BUS type given earlier to
+ * snd_pcm_lib_preallocate_xxx_pages().
+ *
+ * Returns 1 if the buffer is changed, 0 if not changed, or a negative
+ * code on failure.
+ */
+int snd_pcm_lib_malloc_pages(snd_pcm_substream_t *substream, size_t size)
+{
+	snd_pcm_runtime_t *runtime;
+	struct snd_dma_buffer *dmab = NULL;
+
+	snd_assert(substream->dma_buffer.dev.type != SNDRV_DMA_TYPE_UNKNOWN, return -EINVAL);
+	snd_assert(substream != NULL, return -EINVAL);
+	runtime = substream->runtime;
+	snd_assert(runtime != NULL, return -EINVAL);
+
+	if (runtime->dma_buffer_p) {
+		/* perphaps, we might free the large DMA memory region
+		   to save some space here, but the actual solution
+		   costs us less time */
+		if (runtime->dma_buffer_p->bytes >= size) {
+			runtime->dma_bytes = size;
+			return 0;	/* ok, do not change */
+		}
+		snd_pcm_lib_free_pages(substream);
+	}
+	if (substream->dma_buffer.area != NULL && substream->dma_buffer.bytes >= size) {
+		dmab = &substream->dma_buffer; /* use the pre-allocated buffer */
+	} else {
+		dmab = kcalloc(1, sizeof(*dmab), GFP_KERNEL);
+		if (! dmab)
+			return -ENOMEM;
+		dmab->dev = substream->dma_buffer.dev;
+		if (snd_dma_alloc_pages(substream->dma_buffer.dev.type,
+					substream->dma_buffer.dev.dev,
+					size, dmab) < 0) {
+			kfree(dmab);
+			return -ENOMEM;
+		}
+	}
+	snd_pcm_set_runtime_buffer(substream, dmab);
+	runtime->dma_bytes = size;
+	return 1;			/* area was changed */
+}
+
+/**
+ * snd_pcm_lib_free_pages - release the allocated DMA buffer.
+ * @substream: the substream to release the DMA buffer
+ *
+ * Releases the DMA buffer allocated via snd_pcm_lib_malloc_pages().
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_lib_free_pages(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime;
+
+	snd_assert(substream != NULL, return -EINVAL);
+	runtime = substream->runtime;
+	snd_assert(runtime != NULL, return -EINVAL);
+	if (runtime->dma_area == NULL)
+		return 0;
+	if (runtime->dma_buffer_p != &substream->dma_buffer) {
+		/* it's a newly allocated buffer.  release it now. */
+		snd_dma_free_pages(runtime->dma_buffer_p);
+		kfree(runtime->dma_buffer_p);
+	}
+	snd_pcm_set_runtime_buffer(substream, NULL);
+	return 0;
+}
diff --git a/sound/core/pcm_misc.c b/sound/core/pcm_misc.c
new file mode 100644
index 000000000000..422b8db14154
--- /dev/null
+++ b/sound/core/pcm_misc.c
@@ -0,0 +1,481 @@
+/*
+ *  PCM Interface - misc routines
+ *  Copyright (c) 1998 by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#define SND_PCM_FORMAT_UNKNOWN (-1)
+
+/* NOTE: "signed" prefix must be given below since the default char is
+ *       unsigned on some architectures!
+ */
+struct pcm_format_data {
+	unsigned char width;	/* bit width */
+	unsigned char phys;	/* physical bit width */
+	signed char le;	/* 0 = big-endian, 1 = little-endian, -1 = others */
+	signed char signd;	/* 0 = unsigned, 1 = signed, -1 = others */
+	unsigned char silence[8];	/* silence data to fill */
+};
+
+static struct pcm_format_data pcm_formats[SNDRV_PCM_FORMAT_LAST+1] = {
+	[SNDRV_PCM_FORMAT_S8] = {
+		.width = 8, .phys = 8, .le = -1, .signd = 1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_U8] = {
+		.width = 8, .phys = 8, .le = -1, .signd = 0,
+		.silence = { 0x80 },
+	},
+	[SNDRV_PCM_FORMAT_S16_LE] = {
+		.width = 16, .phys = 16, .le = 1, .signd = 1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_S16_BE] = {
+		.width = 16, .phys = 16, .le = 0, .signd = 1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_U16_LE] = {
+		.width = 16, .phys = 16, .le = 1, .signd = 0,
+		.silence = { 0x00, 0x80 },
+	},
+	[SNDRV_PCM_FORMAT_U16_BE] = {
+		.width = 16, .phys = 16, .le = 0, .signd = 0,
+		.silence = { 0x80, 0x00 },
+	},
+	[SNDRV_PCM_FORMAT_S24_LE] = {
+		.width = 24, .phys = 32, .le = 1, .signd = 1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_S24_BE] = {
+		.width = 24, .phys = 32, .le = 0, .signd = 1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_U24_LE] = {
+		.width = 24, .phys = 32, .le = 1, .signd = 0,
+		.silence = { 0x00, 0x00, 0x80 },
+	},
+	[SNDRV_PCM_FORMAT_U24_BE] = {
+		.width = 24, .phys = 32, .le = 0, .signd = 0,
+		.silence = { 0x80, 0x00, 0x00 },
+	},
+	[SNDRV_PCM_FORMAT_S32_LE] = {
+		.width = 32, .phys = 32, .le = 1, .signd = 1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_S32_BE] = {
+		.width = 32, .phys = 32, .le = 0, .signd = 1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_U32_LE] = {
+		.width = 32, .phys = 32, .le = 1, .signd = 0,
+		.silence = { 0x00, 0x00, 0x00, 0x80 },
+	},
+	[SNDRV_PCM_FORMAT_U32_BE] = {
+		.width = 32, .phys = 32, .le = 0, .signd = 0,
+		.silence = { 0x80, 0x00, 0x00, 0x00 },
+	},
+	[SNDRV_PCM_FORMAT_FLOAT_LE] = {
+		.width = 32, .phys = 32, .le = 1, .signd = -1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_FLOAT_BE] = {
+		.width = 32, .phys = 32, .le = 0, .signd = -1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_FLOAT64_LE] = {
+		.width = 64, .phys = 64, .le = 1, .signd = -1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_FLOAT64_BE] = {
+		.width = 64, .phys = 64, .le = 0, .signd = -1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE] = {
+		.width = 32, .phys = 32, .le = 1, .signd = -1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE] = {
+		.width = 32, .phys = 32, .le = 0, .signd = -1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_MU_LAW] = {
+		.width = 8, .phys = 8, .le = -1, .signd = -1,
+		.silence = { 0x7f },
+	},
+	[SNDRV_PCM_FORMAT_A_LAW] = {
+		.width = 8, .phys = 8, .le = -1, .signd = -1,
+		.silence = { 0x55 },
+	},
+	[SNDRV_PCM_FORMAT_IMA_ADPCM] = {
+		.width = 4, .phys = 4, .le = -1, .signd = -1,
+		.silence = {},
+	},
+	/* FIXME: the following three formats are not defined properly yet */
+	[SNDRV_PCM_FORMAT_MPEG] = {
+		.le = -1, .signd = -1,
+	},
+	[SNDRV_PCM_FORMAT_GSM] = {
+		.le = -1, .signd = -1,
+	},
+	[SNDRV_PCM_FORMAT_SPECIAL] = {
+		.le = -1, .signd = -1,
+	},
+	[SNDRV_PCM_FORMAT_S24_3LE] = {
+		.width = 24, .phys = 24, .le = 1, .signd = 1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_S24_3BE] = {
+		.width = 24, .phys = 24, .le = 0, .signd = 1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_U24_3LE] = {
+		.width = 24, .phys = 24, .le = 1, .signd = 0,
+		.silence = { 0x00, 0x00, 0x80 },
+	},
+	[SNDRV_PCM_FORMAT_U24_3BE] = {
+		.width = 24, .phys = 24, .le = 0, .signd = 0,
+		.silence = { 0x80, 0x00, 0x00 },
+	},
+	[SNDRV_PCM_FORMAT_S20_3LE] = {
+		.width = 20, .phys = 24, .le = 1, .signd = 1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_S20_3BE] = {
+		.width = 20, .phys = 24, .le = 0, .signd = 1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_U20_3LE] = {
+		.width = 20, .phys = 24, .le = 1, .signd = 0,
+		.silence = { 0x00, 0x00, 0x08 },
+	},
+	[SNDRV_PCM_FORMAT_U20_3BE] = {
+		.width = 20, .phys = 24, .le = 0, .signd = 0,
+		.silence = { 0x08, 0x00, 0x00 },
+	},
+	[SNDRV_PCM_FORMAT_S18_3LE] = {
+		.width = 18, .phys = 24, .le = 1, .signd = 1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_S18_3BE] = {
+		.width = 18, .phys = 24, .le = 0, .signd = 1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_U18_3LE] = {
+		.width = 18, .phys = 24, .le = 1, .signd = 0,
+		.silence = { 0x00, 0x00, 0x02 },
+	},
+	[SNDRV_PCM_FORMAT_U18_3BE] = {
+		.width = 18, .phys = 24, .le = 0, .signd = 0,
+		.silence = { 0x02, 0x00, 0x00 },
+	},
+};
+
+
+/**
+ * snd_pcm_format_signed - Check the PCM format is signed linear
+ * @format: the format to check
+ *
+ * Returns 1 if the given PCM format is signed linear, 0 if unsigned
+ * linear, and a negative error code for non-linear formats.
+ */
+int snd_pcm_format_signed(snd_pcm_format_t format)
+{
+	int val;
+	if (format < 0 || format > SNDRV_PCM_FORMAT_LAST)
+		return -EINVAL;
+	if ((val = pcm_formats[format].signd) < 0)
+		return -EINVAL;
+	return val;
+}
+
+/**
+ * snd_pcm_format_unsigned - Check the PCM format is unsigned linear
+ * @format: the format to check
+ *
+ * Returns 1 if the given PCM format is unsigned linear, 0 if signed
+ * linear, and a negative error code for non-linear formats.
+ */
+int snd_pcm_format_unsigned(snd_pcm_format_t format)
+{
+	int val;
+
+	val = snd_pcm_format_signed(format);
+	if (val < 0)
+		return val;
+	return !val;
+}
+
+/**
+ * snd_pcm_format_linear - Check the PCM format is linear
+ * @format: the format to check
+ *
+ * Returns 1 if the given PCM format is linear, 0 if not.
+ */
+int snd_pcm_format_linear(snd_pcm_format_t format)
+{
+	return snd_pcm_format_signed(format) >= 0;
+}
+
+/**
+ * snd_pcm_format_little_endian - Check the PCM format is little-endian
+ * @format: the format to check
+ *
+ * Returns 1 if the given PCM format is little-endian, 0 if
+ * big-endian, or a negative error code if endian not specified.
+ */
+int snd_pcm_format_little_endian(snd_pcm_format_t format)
+{
+	int val;
+	if (format < 0 || format > SNDRV_PCM_FORMAT_LAST)
+		return -EINVAL;
+	if ((val = pcm_formats[format].le) < 0)
+		return -EINVAL;
+	return val;
+}
+
+/**
+ * snd_pcm_format_big_endian - Check the PCM format is big-endian
+ * @format: the format to check
+ *
+ * Returns 1 if the given PCM format is big-endian, 0 if
+ * little-endian, or a negative error code if endian not specified.
+ */
+int snd_pcm_format_big_endian(snd_pcm_format_t format)
+{
+	int val;
+
+	val = snd_pcm_format_little_endian(format);
+	if (val < 0)
+		return val;
+	return !val;
+}
+
+/**
+ * snd_pcm_format_cpu_endian - Check the PCM format is CPU-endian
+ * @format: the format to check
+ *
+ * Returns 1 if the given PCM format is CPU-endian, 0 if
+ * opposite, or a negative error code if endian not specified.
+ */
+int snd_pcm_format_cpu_endian(snd_pcm_format_t format)
+{
+#ifdef SNDRV_LITTLE_ENDIAN
+	return snd_pcm_format_little_endian(format);
+#else
+	return snd_pcm_format_big_endian(format);
+#endif
+}
+
+/**
+ * snd_pcm_format_width - return the bit-width of the format
+ * @format: the format to check
+ *
+ * Returns the bit-width of the format, or a negative error code
+ * if unknown format.
+ */
+int snd_pcm_format_width(snd_pcm_format_t format)
+{
+	int val;
+	if (format < 0 || format > SNDRV_PCM_FORMAT_LAST)
+		return -EINVAL;
+	if ((val = pcm_formats[format].width) == 0)
+		return -EINVAL;
+	return val;
+}
+
+/**
+ * snd_pcm_format_physical_width - return the physical bit-width of the format
+ * @format: the format to check
+ *
+ * Returns the physical bit-width of the format, or a negative error code
+ * if unknown format.
+ */
+int snd_pcm_format_physical_width(snd_pcm_format_t format)
+{
+	int val;
+	if (format < 0 || format > SNDRV_PCM_FORMAT_LAST)
+		return -EINVAL;
+	if ((val = pcm_formats[format].phys) == 0)
+		return -EINVAL;
+	return val;
+}
+
+/**
+ * snd_pcm_format_size - return the byte size of samples on the given format
+ * @format: the format to check
+ *
+ * Returns the byte size of the given samples for the format, or a
+ * negative error code if unknown format.
+ */
+ssize_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples)
+{
+	int phys_width = snd_pcm_format_physical_width(format);
+	if (phys_width < 0)
+		return -EINVAL;
+	return samples * phys_width / 8;
+}
+
+/**
+ * snd_pcm_format_silence_64 - return the silent data in 8 bytes array
+ * @format: the format to check
+ *
+ * Returns the format pattern to fill or NULL if error.
+ */
+const unsigned char *snd_pcm_format_silence_64(snd_pcm_format_t format)
+{
+	if (format < 0 || format > SNDRV_PCM_FORMAT_LAST)
+		return NULL;
+	if (! pcm_formats[format].phys)
+		return NULL;
+	return pcm_formats[format].silence;
+}
+
+/**
+ * snd_pcm_format_set_silence - set the silence data on the buffer
+ * @format: the PCM format
+ * @data: the buffer pointer
+ * @samples: the number of samples to set silence
+ *
+ * Sets the silence data on the buffer for the given samples.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_format_set_silence(snd_pcm_format_t format, void *data, unsigned int samples)
+{
+	int width;
+	unsigned char *dst, *pat;
+
+	if (format < 0 || format > SNDRV_PCM_FORMAT_LAST)
+		return -EINVAL;
+	if (samples == 0)
+		return 0;
+	width = pcm_formats[format].phys; /* physical width */
+	pat = pcm_formats[format].silence;
+	if (! width)
+		return -EINVAL;
+	/* signed or 1 byte data */
+	if (pcm_formats[format].signd == 1 || width <= 8) {
+		unsigned int bytes = samples * width / 8;
+		memset(data, *pat, bytes);
+		return 0;
+	}
+	/* non-zero samples, fill using a loop */
+	width /= 8;
+	dst = data;
+#if 0
+	while (samples--) {
+		memcpy(dst, pat, width);
+		dst += width;
+	}
+#else
+	/* a bit optimization for constant width */
+	switch (width) {
+	case 2:
+		while (samples--) {
+			memcpy(dst, pat, 2);
+			dst += 2;
+		}
+		break;
+	case 3:
+		while (samples--) {
+			memcpy(dst, pat, 3);
+			dst += 3;
+		}
+		break;
+	case 4:
+		while (samples--) {
+			memcpy(dst, pat, 4);
+			dst += 4;
+		}
+		break;
+	case 8:
+		while (samples--) {
+			memcpy(dst, pat, 8);
+			dst += 8;
+		}
+		break;
+	}
+#endif
+	return 0;
+}
+
+/* [width][unsigned][bigendian] */
+static int linear_formats[4][2][2] = {
+	{{ SNDRV_PCM_FORMAT_S8, SNDRV_PCM_FORMAT_S8},
+	 { SNDRV_PCM_FORMAT_U8, SNDRV_PCM_FORMAT_U8}},
+	{{SNDRV_PCM_FORMAT_S16_LE, SNDRV_PCM_FORMAT_S16_BE},
+	 {SNDRV_PCM_FORMAT_U16_LE, SNDRV_PCM_FORMAT_U16_BE}},
+	{{SNDRV_PCM_FORMAT_S24_LE, SNDRV_PCM_FORMAT_S24_BE},
+	 {SNDRV_PCM_FORMAT_U24_LE, SNDRV_PCM_FORMAT_U24_BE}},
+	{{SNDRV_PCM_FORMAT_S32_LE, SNDRV_PCM_FORMAT_S32_BE},
+	 {SNDRV_PCM_FORMAT_U32_LE, SNDRV_PCM_FORMAT_U32_BE}}
+};
+
+/**
+ * snd_pcm_build_linear_format - return the suitable linear format for the given condition
+ * @width: the bit-width
+ * @unsignd: 1 if unsigned, 0 if signed.
+ * @big_endian: 1 if big-endian, 0 if little-endian
+ *
+ * Returns the suitable linear format for the given condition.
+ */
+snd_pcm_format_t snd_pcm_build_linear_format(int width, int unsignd, int big_endian)
+{
+	if (width & 7)
+		return SND_PCM_FORMAT_UNKNOWN;
+	width = (width / 8) - 1;
+	if (width < 0 || width >= 4)
+		return SND_PCM_FORMAT_UNKNOWN;
+	return linear_formats[width][!!unsignd][!!big_endian];
+}
+
+/**
+ * snd_pcm_limit_hw_rates - determine rate_min/rate_max fields
+ * @runtime: the runtime instance
+ *
+ * Determines the rate_min and rate_max fields from the rates bits of
+ * the given runtime->hw.
+ *
+ * Returns zero if successful.
+ */
+int snd_pcm_limit_hw_rates(snd_pcm_runtime_t *runtime)
+{
+	static unsigned rates[] = {
+		/* ATTENTION: these values depend on the definition in pcm.h! */
+		5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000,
+		64000, 88200, 96000, 176400, 192000
+	};
+	int i;
+	for (i = 0; i < (int)ARRAY_SIZE(rates); i++) {
+		if (runtime->hw.rates & (1 << i)) {
+			runtime->hw.rate_min = rates[i];
+			break;
+		}
+	}
+	for (i = (int)ARRAY_SIZE(rates) - 1; i >= 0; i--) {
+		if (runtime->hw.rates & (1 << i)) {
+			runtime->hw.rate_max = rates[i];
+			break;
+		}
+	}
+	return 0;
+}
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
new file mode 100644
index 000000000000..cad9bbde9986
--- /dev/null
+++ b/sound/core/pcm_native.c
@@ -0,0 +1,3364 @@
+/*
+ *  Digital Audio (PCM) abstract layer
+ *  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/mm.h>
+#include <linux/smp_lock.h>
+#include <linux/file.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/uio.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/timer.h>
+#include <sound/minors.h>
+#include <asm/io.h>
+
+/*
+ *  Compatibility
+ */
+
+struct sndrv_pcm_hw_params_old {
+	unsigned int flags;
+	unsigned int masks[SNDRV_PCM_HW_PARAM_SUBFORMAT -
+			   SNDRV_PCM_HW_PARAM_ACCESS + 1];
+	struct sndrv_interval intervals[SNDRV_PCM_HW_PARAM_TICK_TIME -
+					SNDRV_PCM_HW_PARAM_SAMPLE_BITS + 1];
+	unsigned int rmask;
+	unsigned int cmask;
+	unsigned int info;
+	unsigned int msbits;
+	unsigned int rate_num;
+	unsigned int rate_den;
+	sndrv_pcm_uframes_t fifo_size;
+	unsigned char reserved[64];
+};
+
+#define SNDRV_PCM_IOCTL_HW_REFINE_OLD _IOWR('A', 0x10, struct sndrv_pcm_hw_params_old)
+#define SNDRV_PCM_IOCTL_HW_PARAMS_OLD _IOWR('A', 0x11, struct sndrv_pcm_hw_params_old)
+
+static int snd_pcm_hw_refine_old_user(snd_pcm_substream_t * substream, struct sndrv_pcm_hw_params_old __user * _oparams);
+static int snd_pcm_hw_params_old_user(snd_pcm_substream_t * substream, struct sndrv_pcm_hw_params_old __user * _oparams);
+
+/*
+ *
+ */
+
+DEFINE_RWLOCK(snd_pcm_link_rwlock);
+static DECLARE_RWSEM(snd_pcm_link_rwsem);
+
+
+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);
+}
+
+
+
+int snd_pcm_info(snd_pcm_substream_t * substream, snd_pcm_info_t *info)
+{
+	snd_pcm_runtime_t * runtime;
+	snd_pcm_t *pcm = substream->pcm;
+	snd_pcm_str_t *pstr = substream->pstr;
+
+	snd_assert(substream != NULL, return -ENXIO);
+	memset(info, 0, sizeof(*info));
+	info->card = pcm->card->number;
+	info->device = pcm->device;
+	info->stream = substream->stream;
+	info->subdevice = substream->number;
+	strlcpy(info->id, pcm->id, sizeof(info->id));
+	strlcpy(info->name, pcm->name, sizeof(info->name));
+	info->dev_class = pcm->dev_class;
+	info->dev_subclass = pcm->dev_subclass;
+	info->subdevices_count = pstr->substream_count;
+	info->subdevices_avail = pstr->substream_count - pstr->substream_opened;
+	strlcpy(info->subname, substream->name, sizeof(info->subname));
+	runtime = substream->runtime;
+	/* AB: FIXME!!! This is definitely nonsense */
+	if (runtime) {
+		info->sync = runtime->sync;
+		substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_INFO, info);
+	}
+	return 0;
+}
+
+int snd_pcm_info_user(snd_pcm_substream_t * substream, snd_pcm_info_t __user * _info)
+{
+	snd_pcm_info_t *info;
+	int err;
+
+	info = kmalloc(sizeof(*info), GFP_KERNEL);
+	if (! info)
+		return -ENOMEM;
+	err = snd_pcm_info(substream, info);
+	if (err >= 0) {
+		if (copy_to_user(_info, info, sizeof(*info)))
+			err = -EFAULT;
+	}
+	kfree(info);
+	return err;
+}
+
+#undef RULES_DEBUG
+
+#ifdef RULES_DEBUG
+#define HW_PARAM(v) [SNDRV_PCM_HW_PARAM_##v] = #v
+char *snd_pcm_hw_param_names[] = {
+	HW_PARAM(ACCESS),
+	HW_PARAM(FORMAT),
+	HW_PARAM(SUBFORMAT),
+	HW_PARAM(SAMPLE_BITS),
+	HW_PARAM(FRAME_BITS),
+	HW_PARAM(CHANNELS),
+	HW_PARAM(RATE),
+	HW_PARAM(PERIOD_TIME),
+	HW_PARAM(PERIOD_SIZE),
+	HW_PARAM(PERIOD_BYTES),
+	HW_PARAM(PERIODS),
+	HW_PARAM(BUFFER_TIME),
+	HW_PARAM(BUFFER_SIZE),
+	HW_PARAM(BUFFER_BYTES),
+	HW_PARAM(TICK_TIME),
+};
+#endif
+
+int snd_pcm_hw_refine(snd_pcm_substream_t *substream, 
+		      snd_pcm_hw_params_t *params)
+{
+	unsigned int k;
+	snd_pcm_hardware_t *hw;
+	snd_interval_t *i = NULL;
+	snd_mask_t *m = NULL;
+	snd_pcm_hw_constraints_t *constrs = &substream->runtime->hw_constraints;
+	unsigned int rstamps[constrs->rules_num];
+	unsigned int vstamps[SNDRV_PCM_HW_PARAM_LAST_INTERVAL + 1];
+	unsigned int stamp = 2;
+	int changed, again;
+
+	params->info = 0;
+	params->fifo_size = 0;
+	if (params->rmask & (1 << SNDRV_PCM_HW_PARAM_SAMPLE_BITS))
+		params->msbits = 0;
+	if (params->rmask & (1 << SNDRV_PCM_HW_PARAM_RATE)) {
+		params->rate_num = 0;
+		params->rate_den = 0;
+	}
+
+	for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) {
+		m = hw_param_mask(params, k);
+		if (snd_mask_empty(m))
+			return -EINVAL;
+		if (!(params->rmask & (1 << k)))
+			continue;
+#ifdef RULES_DEBUG
+		printk("%s = ", snd_pcm_hw_param_names[k]);
+		printk("%04x%04x%04x%04x -> ", m->bits[3], m->bits[2], m->bits[1], m->bits[0]);
+#endif
+		changed = snd_mask_refine(m, constrs_mask(constrs, k));
+#ifdef RULES_DEBUG
+		printk("%04x%04x%04x%04x\n", m->bits[3], m->bits[2], m->bits[1], m->bits[0]);
+#endif
+		if (changed)
+			params->cmask |= 1 << k;
+		if (changed < 0)
+			return changed;
+	}
+
+	for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) {
+		i = hw_param_interval(params, k);
+		if (snd_interval_empty(i))
+			return -EINVAL;
+		if (!(params->rmask & (1 << k)))
+			continue;
+#ifdef RULES_DEBUG
+		printk("%s = ", snd_pcm_hw_param_names[k]);
+		if (i->empty)
+			printk("empty");
+		else
+			printk("%c%u %u%c", 
+			       i->openmin ? '(' : '[', i->min,
+			       i->max, i->openmax ? ')' : ']');
+		printk(" -> ");
+#endif
+		changed = snd_interval_refine(i, constrs_interval(constrs, k));
+#ifdef RULES_DEBUG
+		if (i->empty)
+			printk("empty\n");
+		else 
+			printk("%c%u %u%c\n", 
+			       i->openmin ? '(' : '[', i->min,
+			       i->max, i->openmax ? ')' : ']');
+#endif
+		if (changed)
+			params->cmask |= 1 << k;
+		if (changed < 0)
+			return changed;
+	}
+
+	for (k = 0; k < constrs->rules_num; k++)
+		rstamps[k] = 0;
+	for (k = 0; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) 
+		vstamps[k] = (params->rmask & (1 << k)) ? 1 : 0;
+	do {
+		again = 0;
+		for (k = 0; k < constrs->rules_num; k++) {
+			snd_pcm_hw_rule_t *r = &constrs->rules[k];
+			unsigned int d;
+			int doit = 0;
+			if (r->cond && !(r->cond & params->flags))
+				continue;
+			for (d = 0; r->deps[d] >= 0; d++) {
+				if (vstamps[r->deps[d]] > rstamps[k]) {
+					doit = 1;
+					break;
+				}
+			}
+			if (!doit)
+				continue;
+#ifdef RULES_DEBUG
+			printk("Rule %d [%p]: ", k, r->func);
+			if (r->var >= 0) {
+				printk("%s = ", snd_pcm_hw_param_names[r->var]);
+				if (hw_is_mask(r->var)) {
+					m = hw_param_mask(params, r->var);
+					printk("%x", *m->bits);
+				} else {
+					i = hw_param_interval(params, r->var);
+					if (i->empty)
+						printk("empty");
+					else
+						printk("%c%u %u%c", 
+						       i->openmin ? '(' : '[', i->min,
+						       i->max, i->openmax ? ')' : ']');
+				}
+			}
+#endif
+			changed = r->func(params, r);
+#ifdef RULES_DEBUG
+			if (r->var >= 0) {
+				printk(" -> ");
+				if (hw_is_mask(r->var))
+					printk("%x", *m->bits);
+				else {
+					if (i->empty)
+						printk("empty");
+					else
+						printk("%c%u %u%c", 
+						       i->openmin ? '(' : '[', i->min,
+						       i->max, i->openmax ? ')' : ']');
+				}
+			}
+			printk("\n");
+#endif
+			rstamps[k] = stamp;
+			if (changed && r->var >= 0) {
+				params->cmask |= (1 << r->var);
+				vstamps[r->var] = stamp;
+				again = 1;
+			}
+			if (changed < 0)
+				return changed;
+			stamp++;
+		}
+	} while (again);
+	if (!params->msbits) {
+		i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
+		if (snd_interval_single(i))
+			params->msbits = snd_interval_value(i);
+	}
+
+	if (!params->rate_den) {
+		i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+		if (snd_interval_single(i)) {
+			params->rate_num = snd_interval_value(i);
+			params->rate_den = 1;
+		}
+	}
+
+	hw = &substream->runtime->hw;
+	if (!params->info)
+		params->info = hw->info;
+	if (!params->fifo_size)
+		params->fifo_size = hw->fifo_size;
+	params->rmask = 0;
+	return 0;
+}
+
+static int snd_pcm_hw_refine_user(snd_pcm_substream_t * substream, snd_pcm_hw_params_t __user * _params)
+{
+	snd_pcm_hw_params_t *params;
+	int err;
+
+	params = kmalloc(sizeof(*params), GFP_KERNEL);
+	if (!params) {
+		err = -ENOMEM;
+		goto out;
+	}
+	if (copy_from_user(params, _params, sizeof(*params))) {
+		err = -EFAULT;
+		goto out;
+	}
+	err = snd_pcm_hw_refine(substream, params);
+	if (copy_to_user(_params, params, sizeof(*params))) {
+		if (!err)
+			err = -EFAULT;
+	}
+out:
+	kfree(params);
+	return err;
+}
+
+int snd_pcm_hw_params(snd_pcm_substream_t *substream,
+		      snd_pcm_hw_params_t *params)
+{
+	snd_pcm_runtime_t *runtime;
+	int err;
+	unsigned int bits;
+	snd_pcm_uframes_t frames;
+
+	snd_assert(substream != NULL, return -ENXIO);
+	runtime = substream->runtime;
+	snd_assert(runtime != NULL, return -ENXIO);
+	snd_pcm_stream_lock_irq(substream);
+	switch (runtime->status->state) {
+	case SNDRV_PCM_STATE_OPEN:
+	case SNDRV_PCM_STATE_SETUP:
+	case SNDRV_PCM_STATE_PREPARED:
+		break;
+	default:
+		snd_pcm_stream_unlock_irq(substream);
+		return -EBADFD;
+	}
+	snd_pcm_stream_unlock_irq(substream);
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+	if (!substream->oss.oss)
+#endif
+		if (atomic_read(&runtime->mmap_count))
+			return -EBADFD;
+
+	params->rmask = ~0U;
+	err = snd_pcm_hw_refine(substream, params);
+	if (err < 0)
+		goto _error;
+
+	err = snd_pcm_hw_params_choose(substream, params);
+	if (err < 0)
+		goto _error;
+
+	if (substream->ops->hw_params != NULL) {
+		err = substream->ops->hw_params(substream, params);
+		if (err < 0)
+			goto _error;
+	}
+
+	runtime->access = params_access(params);
+	runtime->format = params_format(params);
+	runtime->subformat = params_subformat(params);
+	runtime->channels = params_channels(params);
+	runtime->rate = params_rate(params);
+	runtime->period_size = params_period_size(params);
+	runtime->periods = params_periods(params);
+	runtime->buffer_size = params_buffer_size(params);
+	runtime->tick_time = params_tick_time(params);
+	runtime->info = params->info;
+	runtime->rate_num = params->rate_num;
+	runtime->rate_den = params->rate_den;
+
+	bits = snd_pcm_format_physical_width(runtime->format);
+	runtime->sample_bits = bits;
+	bits *= runtime->channels;
+	runtime->frame_bits = bits;
+	frames = 1;
+	while (bits % 8 != 0) {
+		bits *= 2;
+		frames *= 2;
+	}
+	runtime->byte_align = bits / 8;
+	runtime->min_align = frames;
+
+	/* Default sw params */
+	runtime->tstamp_mode = SNDRV_PCM_TSTAMP_NONE;
+	runtime->period_step = 1;
+	runtime->sleep_min = 0;
+	runtime->control->avail_min = runtime->period_size;
+	runtime->xfer_align = runtime->period_size;
+	runtime->start_threshold = 1;
+	runtime->stop_threshold = runtime->buffer_size;
+	runtime->silence_threshold = 0;
+	runtime->silence_size = 0;
+	runtime->boundary = runtime->buffer_size;
+	while (runtime->boundary * 2 <= LONG_MAX - runtime->buffer_size)
+		runtime->boundary *= 2;
+
+	snd_pcm_timer_resolution_change(substream);
+	runtime->status->state = SNDRV_PCM_STATE_SETUP;
+	return 0;
+ _error:
+	/* hardware might be unuseable from this time,
+	   so we force application to retry to set
+	   the correct hardware parameter settings */
+	runtime->status->state = SNDRV_PCM_STATE_OPEN;
+	if (substream->ops->hw_free != NULL)
+		substream->ops->hw_free(substream);
+	return err;
+}
+
+static int snd_pcm_hw_params_user(snd_pcm_substream_t * substream, snd_pcm_hw_params_t __user * _params)
+{
+	snd_pcm_hw_params_t *params;
+	int err;
+
+	params = kmalloc(sizeof(*params), GFP_KERNEL);
+	if (!params) {
+		err = -ENOMEM;
+		goto out;
+	}
+	if (copy_from_user(params, _params, sizeof(*params))) {
+		err = -EFAULT;
+		goto out;
+	}
+	err = snd_pcm_hw_params(substream, params);
+	if (copy_to_user(_params, params, sizeof(*params))) {
+		if (!err)
+			err = -EFAULT;
+	}
+out:
+	kfree(params);
+	return err;
+}
+
+static int snd_pcm_hw_free(snd_pcm_substream_t * substream)
+{
+	snd_pcm_runtime_t *runtime;
+	int result = 0;
+
+	snd_assert(substream != NULL, return -ENXIO);
+	runtime = substream->runtime;
+	snd_assert(runtime != NULL, return -ENXIO);
+	snd_pcm_stream_lock_irq(substream);
+	switch (runtime->status->state) {
+	case SNDRV_PCM_STATE_SETUP:
+	case SNDRV_PCM_STATE_PREPARED:
+		break;
+	default:
+		snd_pcm_stream_unlock_irq(substream);
+		return -EBADFD;
+	}
+	snd_pcm_stream_unlock_irq(substream);
+	if (atomic_read(&runtime->mmap_count))
+		return -EBADFD;
+	if (substream->ops->hw_free)
+		result = substream->ops->hw_free(substream);
+	runtime->status->state = SNDRV_PCM_STATE_OPEN;
+	return result;
+}
+
+static int snd_pcm_sw_params(snd_pcm_substream_t * substream, snd_pcm_sw_params_t *params)
+{
+	snd_pcm_runtime_t *runtime;
+
+	snd_assert(substream != NULL, return -ENXIO);
+	runtime = substream->runtime;
+	snd_assert(runtime != NULL, return -ENXIO);
+	snd_pcm_stream_lock_irq(substream);
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+		snd_pcm_stream_unlock_irq(substream);
+		return -EBADFD;
+	}
+	snd_pcm_stream_unlock_irq(substream);
+
+	if (params->tstamp_mode > SNDRV_PCM_TSTAMP_LAST)
+		return -EINVAL;
+	if (params->avail_min == 0)
+		return -EINVAL;
+	if (params->xfer_align == 0 ||
+	    params->xfer_align % runtime->min_align != 0)
+		return -EINVAL;
+	if (params->silence_size >= runtime->boundary) {
+		if (params->silence_threshold != 0)
+			return -EINVAL;
+	} else {
+		if (params->silence_size > params->silence_threshold)
+			return -EINVAL;
+		if (params->silence_threshold > runtime->buffer_size)
+			return -EINVAL;
+	}
+	snd_pcm_stream_lock_irq(substream);
+	runtime->tstamp_mode = params->tstamp_mode;
+	runtime->sleep_min = params->sleep_min;
+	runtime->period_step = params->period_step;
+	runtime->control->avail_min = params->avail_min;
+	runtime->start_threshold = params->start_threshold;
+	runtime->stop_threshold = params->stop_threshold;
+	runtime->silence_threshold = params->silence_threshold;
+	runtime->silence_size = params->silence_size;
+	runtime->xfer_align = params->xfer_align;
+        params->boundary = runtime->boundary;
+	if (snd_pcm_running(substream)) {
+		if (runtime->sleep_min)
+			snd_pcm_tick_prepare(substream);
+		else
+			snd_pcm_tick_set(substream, 0);
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+		    runtime->silence_size > 0)
+			snd_pcm_playback_silence(substream, ULONG_MAX);
+		wake_up(&runtime->sleep);
+	}
+	snd_pcm_stream_unlock_irq(substream);
+	return 0;
+}
+
+static int snd_pcm_sw_params_user(snd_pcm_substream_t * substream, snd_pcm_sw_params_t __user * _params)
+{
+	snd_pcm_sw_params_t params;
+	int err;
+	if (copy_from_user(&params, _params, sizeof(params)))
+		return -EFAULT;
+	err = snd_pcm_sw_params(substream, &params);
+	if (copy_to_user(_params, &params, sizeof(params)))
+		return -EFAULT;
+	return err;
+}
+
+int snd_pcm_status(snd_pcm_substream_t *substream,
+		   snd_pcm_status_t *status)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+
+	snd_pcm_stream_lock_irq(substream);
+	status->state = runtime->status->state;
+	status->suspended_state = runtime->status->suspended_state;
+	if (status->state == SNDRV_PCM_STATE_OPEN)
+		goto _end;
+	status->trigger_tstamp = runtime->trigger_tstamp;
+	if (snd_pcm_running(substream)) {
+		snd_pcm_update_hw_ptr(substream);
+		if (runtime->tstamp_mode & SNDRV_PCM_TSTAMP_MMAP)
+			status->tstamp = runtime->status->tstamp;
+		else
+			snd_timestamp_now(&status->tstamp, runtime->tstamp_timespec);
+	} else
+		snd_timestamp_now(&status->tstamp, runtime->tstamp_timespec);
+	status->appl_ptr = runtime->control->appl_ptr;
+	status->hw_ptr = runtime->status->hw_ptr;
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		status->avail = snd_pcm_playback_avail(runtime);
+		if (runtime->status->state == SNDRV_PCM_STATE_RUNNING ||
+		    runtime->status->state == SNDRV_PCM_STATE_DRAINING)
+			status->delay = runtime->buffer_size - status->avail;
+		else
+			status->delay = 0;
+	} else {
+		status->avail = snd_pcm_capture_avail(runtime);
+		if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)
+			status->delay = status->avail;
+		else
+			status->delay = 0;
+	}
+	status->avail_max = runtime->avail_max;
+	status->overrange = runtime->overrange;
+	runtime->avail_max = 0;
+	runtime->overrange = 0;
+ _end:
+ 	snd_pcm_stream_unlock_irq(substream);
+	return 0;
+}
+
+static int snd_pcm_status_user(snd_pcm_substream_t * substream, snd_pcm_status_t __user * _status)
+{
+	snd_pcm_status_t status;
+	snd_pcm_runtime_t *runtime;
+	int res;
+	
+	snd_assert(substream != NULL, return -ENXIO);
+	runtime = substream->runtime;
+	memset(&status, 0, sizeof(status));
+	res = snd_pcm_status(substream, &status);
+	if (res < 0)
+		return res;
+	if (copy_to_user(_status, &status, sizeof(status)))
+		return -EFAULT;
+	return 0;
+}
+
+static int snd_pcm_channel_info(snd_pcm_substream_t * substream, snd_pcm_channel_info_t * info)
+{
+	snd_pcm_runtime_t *runtime;
+	unsigned int channel;
+	
+	snd_assert(substream != NULL, return -ENXIO);
+	channel = info->channel;
+	runtime = substream->runtime;
+	snd_pcm_stream_lock_irq(substream);
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+		snd_pcm_stream_unlock_irq(substream);
+		return -EBADFD;
+	}
+	snd_pcm_stream_unlock_irq(substream);
+	if (channel >= runtime->channels)
+		return -EINVAL;
+	memset(info, 0, sizeof(*info));
+	info->channel = channel;
+	return substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_CHANNEL_INFO, info);
+}
+
+static int snd_pcm_channel_info_user(snd_pcm_substream_t * substream, snd_pcm_channel_info_t __user * _info)
+{
+	snd_pcm_channel_info_t info;
+	int res;
+	
+	if (copy_from_user(&info, _info, sizeof(info)))
+		return -EFAULT;
+	res = snd_pcm_channel_info(substream, &info);
+	if (res < 0)
+		return res;
+	if (copy_to_user(_info, &info, sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+static void snd_pcm_trigger_tstamp(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	if (runtime->trigger_master == NULL)
+		return;
+	if (runtime->trigger_master == substream) {
+		snd_timestamp_now(&runtime->trigger_tstamp, runtime->tstamp_timespec);
+	} else {
+		snd_pcm_trigger_tstamp(runtime->trigger_master);
+		runtime->trigger_tstamp = runtime->trigger_master->runtime->trigger_tstamp;
+	}
+	runtime->trigger_master = NULL;
+}
+
+struct action_ops {
+	int (*pre_action)(snd_pcm_substream_t *substream, int state);
+	int (*do_action)(snd_pcm_substream_t *substream, int state);
+	void (*undo_action)(snd_pcm_substream_t *substream, int state);
+	void (*post_action)(snd_pcm_substream_t *substream, int state);
+};
+
+/*
+ *  this functions is core for handling of linked stream
+ *  Note: the stream state might be changed also on failure
+ *  Note2: call with calling stream lock + link lock
+ */
+static int snd_pcm_action_group(struct action_ops *ops,
+				snd_pcm_substream_t *substream,
+				int state, int do_lock)
+{
+	struct list_head *pos;
+	snd_pcm_substream_t *s = NULL;
+	snd_pcm_substream_t *s1;
+	int res = 0;
+
+	snd_pcm_group_for_each(pos, substream) {
+		s = snd_pcm_group_substream_entry(pos);
+		if (do_lock && s != substream)
+			spin_lock(&s->self_group.lock);
+		res = ops->pre_action(s, state);
+		if (res < 0)
+			goto _unlock;
+	}
+	snd_pcm_group_for_each(pos, substream) {
+		s = snd_pcm_group_substream_entry(pos);
+		res = ops->do_action(s, state);
+		if (res < 0) {
+			if (ops->undo_action) {
+				snd_pcm_group_for_each(pos, substream) {
+					s1 = snd_pcm_group_substream_entry(pos);
+					if (s1 == s) /* failed stream */
+						break;
+					ops->undo_action(s1, state);
+				}
+			}
+			s = NULL; /* unlock all */
+			goto _unlock;
+		}
+	}
+	snd_pcm_group_for_each(pos, substream) {
+		s = snd_pcm_group_substream_entry(pos);
+		ops->post_action(s, state);
+	}
+ _unlock:
+	if (do_lock) {
+		/* unlock streams */
+		snd_pcm_group_for_each(pos, substream) {
+			s1 = snd_pcm_group_substream_entry(pos);
+			if (s1 != substream)
+				spin_unlock(&s1->self_group.lock);
+			if (s1 == s)	/* end */
+				break;
+		}
+	}
+	return res;
+}
+
+/*
+ *  Note: call with stream lock
+ */
+static int snd_pcm_action_single(struct action_ops *ops,
+				 snd_pcm_substream_t *substream,
+				 int state)
+{
+	int res;
+	
+	res = ops->pre_action(substream, state);
+	if (res < 0)
+		return res;
+	res = ops->do_action(substream, state);
+	if (res == 0)
+		ops->post_action(substream, state);
+	else if (ops->undo_action)
+		ops->undo_action(substream, state);
+	return res;
+}
+
+/*
+ *  Note: call with stream lock
+ */
+static int snd_pcm_action(struct action_ops *ops,
+			  snd_pcm_substream_t *substream,
+			  int state)
+{
+	int res;
+
+	if (snd_pcm_stream_linked(substream)) {
+		if (!spin_trylock(&substream->group->lock)) {
+			spin_unlock(&substream->self_group.lock);
+			spin_lock(&substream->group->lock);
+			spin_lock(&substream->self_group.lock);
+		}
+		res = snd_pcm_action_group(ops, substream, state, 1);
+		spin_unlock(&substream->group->lock);
+	} else {
+		res = snd_pcm_action_single(ops, substream, state);
+	}
+	return res;
+}
+
+/*
+ *  Note: don't use any locks before
+ */
+static int snd_pcm_action_lock_irq(struct action_ops *ops,
+				   snd_pcm_substream_t *substream,
+				   int state)
+{
+	int res;
+
+	read_lock_irq(&snd_pcm_link_rwlock);
+	if (snd_pcm_stream_linked(substream)) {
+		spin_lock(&substream->group->lock);
+		spin_lock(&substream->self_group.lock);
+		res = snd_pcm_action_group(ops, substream, state, 1);
+		spin_unlock(&substream->self_group.lock);
+		spin_unlock(&substream->group->lock);
+	} else {
+		spin_lock(&substream->self_group.lock);
+		res = snd_pcm_action_single(ops, substream, state);
+		spin_unlock(&substream->self_group.lock);
+	}
+	read_unlock_irq(&snd_pcm_link_rwlock);
+	return res;
+}
+
+/*
+ */
+static int snd_pcm_action_nonatomic(struct action_ops *ops,
+				    snd_pcm_substream_t *substream,
+				    int state)
+{
+	int res;
+
+	down_read(&snd_pcm_link_rwsem);
+	if (snd_pcm_stream_linked(substream))
+		res = snd_pcm_action_group(ops, substream, state, 0);
+	else
+		res = snd_pcm_action_single(ops, substream, state);
+	up_read(&snd_pcm_link_rwsem);
+	return res;
+}
+
+/*
+ * start callbacks
+ */
+static int snd_pcm_pre_start(snd_pcm_substream_t *substream, int state)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	if (runtime->status->state != SNDRV_PCM_STATE_PREPARED)
+		return -EBADFD;
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+	    !snd_pcm_playback_data(substream))
+		return -EPIPE;
+	runtime->trigger_master = substream;
+	return 0;
+}
+
+static int snd_pcm_do_start(snd_pcm_substream_t *substream, int state)
+{
+	if (substream->runtime->trigger_master != substream)
+		return 0;
+	return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
+}
+
+static void snd_pcm_undo_start(snd_pcm_substream_t *substream, int state)
+{
+	if (substream->runtime->trigger_master == substream)
+		substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
+}
+
+static void snd_pcm_post_start(snd_pcm_substream_t *substream, int state)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_trigger_tstamp(substream);
+	runtime->status->state = state;
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+	    runtime->silence_size > 0)
+		snd_pcm_playback_silence(substream, ULONG_MAX);
+	if (runtime->sleep_min)
+		snd_pcm_tick_prepare(substream);
+	if (substream->timer)
+		snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTART, &runtime->trigger_tstamp);
+}
+
+static struct action_ops snd_pcm_action_start = {
+	.pre_action = snd_pcm_pre_start,
+	.do_action = snd_pcm_do_start,
+	.undo_action = snd_pcm_undo_start,
+	.post_action = snd_pcm_post_start
+};
+
+/**
+ * snd_pcm_start
+ *
+ * Start all linked streams.
+ */
+int snd_pcm_start(snd_pcm_substream_t *substream)
+{
+	return snd_pcm_action(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
+}
+
+/*
+ * stop callbacks
+ */
+static int snd_pcm_pre_stop(snd_pcm_substream_t *substream, int state)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+		return -EBADFD;
+	runtime->trigger_master = substream;
+	return 0;
+}
+
+static int snd_pcm_do_stop(snd_pcm_substream_t *substream, int state)
+{
+	if (substream->runtime->trigger_master == substream &&
+	    snd_pcm_running(substream))
+		substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
+	return 0; /* unconditonally stop all substreams */
+}
+
+static void snd_pcm_post_stop(snd_pcm_substream_t *substream, int state)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	if (runtime->status->state != state) {
+		snd_pcm_trigger_tstamp(substream);
+		if (substream->timer)
+			snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTOP, &runtime->trigger_tstamp);
+		runtime->status->state = state;
+		snd_pcm_tick_set(substream, 0);
+	}
+	wake_up(&runtime->sleep);
+}
+
+static struct action_ops snd_pcm_action_stop = {
+	.pre_action = snd_pcm_pre_stop,
+	.do_action = snd_pcm_do_stop,
+	.post_action = snd_pcm_post_stop
+};
+
+/**
+ * snd_pcm_stop
+ *
+ * Try to stop all running streams in the substream group.
+ * The state of each stream is changed to the given value after that unconditionally.
+ */
+int snd_pcm_stop(snd_pcm_substream_t *substream, int state)
+{
+	return snd_pcm_action(&snd_pcm_action_stop, substream, state);
+}
+
+/**
+ * snd_pcm_drain_done
+ *
+ * Stop the DMA only when the given stream is playback.
+ * The state is changed to SETUP.
+ * Unlike snd_pcm_stop(), this affects only the given stream.
+ */
+int snd_pcm_drain_done(snd_pcm_substream_t *substream)
+{
+	return snd_pcm_action_single(&snd_pcm_action_stop, substream, SNDRV_PCM_STATE_SETUP);
+}
+
+/*
+ * pause callbacks
+ */
+static int snd_pcm_pre_pause(snd_pcm_substream_t *substream, int push)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	if (!(runtime->info & SNDRV_PCM_INFO_PAUSE))
+		return -ENOSYS;
+	if (push) {
+		if (runtime->status->state != SNDRV_PCM_STATE_RUNNING)
+			return -EBADFD;
+	} else if (runtime->status->state != SNDRV_PCM_STATE_PAUSED)
+		return -EBADFD;
+	runtime->trigger_master = substream;
+	return 0;
+}
+
+static int snd_pcm_do_pause(snd_pcm_substream_t *substream, int push)
+{
+	if (substream->runtime->trigger_master != substream)
+		return 0;
+	return substream->ops->trigger(substream,
+				       push ? SNDRV_PCM_TRIGGER_PAUSE_PUSH :
+					      SNDRV_PCM_TRIGGER_PAUSE_RELEASE);
+}
+
+static void snd_pcm_undo_pause(snd_pcm_substream_t *substream, int push)
+{
+	if (substream->runtime->trigger_master == substream)
+		substream->ops->trigger(substream,
+					push ? SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
+					SNDRV_PCM_TRIGGER_PAUSE_PUSH);
+}
+
+static void snd_pcm_post_pause(snd_pcm_substream_t *substream, int push)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_trigger_tstamp(substream);
+	if (push) {
+		runtime->status->state = SNDRV_PCM_STATE_PAUSED;
+		if (substream->timer)
+			snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MPAUSE, &runtime->trigger_tstamp);
+		snd_pcm_tick_set(substream, 0);
+		wake_up(&runtime->sleep);
+	} else {
+		runtime->status->state = SNDRV_PCM_STATE_RUNNING;
+		if (runtime->sleep_min)
+			snd_pcm_tick_prepare(substream);
+		if (substream->timer)
+			snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MCONTINUE, &runtime->trigger_tstamp);
+	}
+}
+
+static struct action_ops snd_pcm_action_pause = {
+	.pre_action = snd_pcm_pre_pause,
+	.do_action = snd_pcm_do_pause,
+	.undo_action = snd_pcm_undo_pause,
+	.post_action = snd_pcm_post_pause
+};
+
+/*
+ * Push/release the pause for all linked streams.
+ */
+static int snd_pcm_pause(snd_pcm_substream_t *substream, int push)
+{
+	return snd_pcm_action(&snd_pcm_action_pause, substream, push);
+}
+
+#ifdef CONFIG_PM
+/* suspend */
+
+static int snd_pcm_pre_suspend(snd_pcm_substream_t *substream, int state)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED)
+		return -EBUSY;
+	runtime->trigger_master = substream;
+	return 0;
+}
+
+static int snd_pcm_do_suspend(snd_pcm_substream_t *substream, int state)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	if (runtime->trigger_master != substream)
+		return 0;
+	if (! snd_pcm_running(substream))
+		return 0;
+	substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND);
+	return 0; /* suspend unconditionally */
+}
+
+static void snd_pcm_post_suspend(snd_pcm_substream_t *substream, int state)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_trigger_tstamp(substream);
+	if (substream->timer)
+		snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MPAUSE, &runtime->trigger_tstamp);
+	runtime->status->suspended_state = runtime->status->state;
+	runtime->status->state = SNDRV_PCM_STATE_SUSPENDED;
+	snd_pcm_tick_set(substream, 0);
+	wake_up(&runtime->sleep);
+}
+
+static struct action_ops snd_pcm_action_suspend = {
+	.pre_action = snd_pcm_pre_suspend,
+	.do_action = snd_pcm_do_suspend,
+	.post_action = snd_pcm_post_suspend
+};
+
+/**
+ * snd_pcm_suspend
+ *
+ * Trigger SUSPEND to all linked streams.
+ * After this call, all streams are changed to SUSPENDED state.
+ */
+int snd_pcm_suspend(snd_pcm_substream_t *substream)
+{
+	int err;
+	unsigned long flags;
+
+	snd_pcm_stream_lock_irqsave(substream, flags);
+	err = snd_pcm_action(&snd_pcm_action_suspend, substream, 0);
+	snd_pcm_stream_unlock_irqrestore(substream, flags);
+	return err;
+}
+
+/**
+ * snd_pcm_suspend_all
+ *
+ * Trigger SUSPEND to all substreams in the given pcm.
+ * After this call, all streams are changed to SUSPENDED state.
+ */
+int snd_pcm_suspend_all(snd_pcm_t *pcm)
+{
+	snd_pcm_substream_t *substream;
+	int stream, err = 0;
+
+	for (stream = 0; stream < 2; stream++) {
+		for (substream = pcm->streams[stream].substream; substream; substream = substream->next) {
+			/* FIXME: the open/close code should lock this as well */
+			if (substream->runtime == NULL)
+				continue;
+			err = snd_pcm_suspend(substream);
+			if (err < 0 && err != -EBUSY)
+				return err;
+		}
+	}
+	return 0;
+}
+
+/* resume */
+
+static int snd_pcm_pre_resume(snd_pcm_substream_t *substream, int state)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	if (!(runtime->info & SNDRV_PCM_INFO_RESUME))
+		return -ENOSYS;
+	runtime->trigger_master = substream;
+	return 0;
+}
+
+static int snd_pcm_do_resume(snd_pcm_substream_t *substream, int state)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	if (runtime->trigger_master != substream)
+		return 0;
+	/* DMA not running previously? */
+	if (runtime->status->suspended_state != SNDRV_PCM_STATE_RUNNING &&
+	    (runtime->status->suspended_state != SNDRV_PCM_STATE_DRAINING ||
+	     substream->stream != SNDRV_PCM_STREAM_PLAYBACK))
+		return 0;
+	return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_RESUME);
+}
+
+static void snd_pcm_undo_resume(snd_pcm_substream_t *substream, int state)
+{
+	if (substream->runtime->trigger_master == substream &&
+	    snd_pcm_running(substream))
+		substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND);
+}
+
+static void snd_pcm_post_resume(snd_pcm_substream_t *substream, int state)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_trigger_tstamp(substream);
+	if (substream->timer)
+		snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MCONTINUE, &runtime->trigger_tstamp);
+	runtime->status->state = runtime->status->suspended_state;
+	if (runtime->sleep_min)
+		snd_pcm_tick_prepare(substream);
+}
+
+static struct action_ops snd_pcm_action_resume = {
+	.pre_action = snd_pcm_pre_resume,
+	.do_action = snd_pcm_do_resume,
+	.undo_action = snd_pcm_undo_resume,
+	.post_action = snd_pcm_post_resume
+};
+
+static int snd_pcm_resume(snd_pcm_substream_t *substream)
+{
+	snd_card_t *card = substream->pcm->card;
+	int res;
+
+	snd_power_lock(card);
+	if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile)) >= 0)
+		res = snd_pcm_action_lock_irq(&snd_pcm_action_resume, substream, 0);
+	snd_power_unlock(card);
+	return res;
+}
+
+#else
+
+static int snd_pcm_resume(snd_pcm_substream_t *substream)
+{
+	return -ENOSYS;
+}
+
+#endif /* CONFIG_PM */
+
+/*
+ * xrun ioctl
+ *
+ * Change the RUNNING stream(s) to XRUN state.
+ */
+static int snd_pcm_xrun(snd_pcm_substream_t *substream)
+{
+	snd_card_t *card = substream->pcm->card;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int result;
+
+	snd_power_lock(card);
+	if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+		result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
+		if (result < 0)
+			goto _unlock;
+	}
+
+	snd_pcm_stream_lock_irq(substream);
+	switch (runtime->status->state) {
+	case SNDRV_PCM_STATE_XRUN:
+		result = 0;	/* already there */
+		break;
+	case SNDRV_PCM_STATE_RUNNING:
+		result = snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+		break;
+	default:
+		result = -EBADFD;
+	}
+	snd_pcm_stream_unlock_irq(substream);
+ _unlock:
+	snd_power_unlock(card);
+	return result;
+}
+
+/*
+ * reset ioctl
+ */
+static int snd_pcm_pre_reset(snd_pcm_substream_t * substream, int state)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	switch (runtime->status->state) {
+	case SNDRV_PCM_STATE_RUNNING:
+	case SNDRV_PCM_STATE_PREPARED:
+	case SNDRV_PCM_STATE_PAUSED:
+	case SNDRV_PCM_STATE_SUSPENDED:
+		return 0;
+	default:
+		return -EBADFD;
+	}
+}
+
+static int snd_pcm_do_reset(snd_pcm_substream_t * substream, int state)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int err = substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL);
+	if (err < 0)
+		return err;
+	// snd_assert(runtime->status->hw_ptr < runtime->buffer_size, );
+	runtime->hw_ptr_base = 0;
+	runtime->hw_ptr_interrupt = runtime->status->hw_ptr - runtime->status->hw_ptr % runtime->period_size;
+	runtime->silence_start = runtime->status->hw_ptr;
+	runtime->silence_filled = 0;
+	return 0;
+}
+
+static void snd_pcm_post_reset(snd_pcm_substream_t * substream, int state)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	runtime->control->appl_ptr = runtime->status->hw_ptr;
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+	    runtime->silence_size > 0)
+		snd_pcm_playback_silence(substream, ULONG_MAX);
+}
+
+static struct action_ops snd_pcm_action_reset = {
+	.pre_action = snd_pcm_pre_reset,
+	.do_action = snd_pcm_do_reset,
+	.post_action = snd_pcm_post_reset
+};
+
+static int snd_pcm_reset(snd_pcm_substream_t *substream)
+{
+	return snd_pcm_action_nonatomic(&snd_pcm_action_reset, substream, 0);
+}
+
+/*
+ * prepare ioctl
+ */
+static int snd_pcm_pre_prepare(snd_pcm_substream_t * substream, int state)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+		return -EBADFD;
+	if (snd_pcm_running(substream))
+		return -EBUSY;
+	return 0;
+}
+
+static int snd_pcm_do_prepare(snd_pcm_substream_t * substream, int state)
+{
+	int err;
+	err = substream->ops->prepare(substream);
+	if (err < 0)
+		return err;
+	return snd_pcm_do_reset(substream, 0);
+}
+
+static void snd_pcm_post_prepare(snd_pcm_substream_t * substream, int state)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	runtime->control->appl_ptr = runtime->status->hw_ptr;
+	runtime->status->state = SNDRV_PCM_STATE_PREPARED;
+}
+
+static struct action_ops snd_pcm_action_prepare = {
+	.pre_action = snd_pcm_pre_prepare,
+	.do_action = snd_pcm_do_prepare,
+	.post_action = snd_pcm_post_prepare
+};
+
+/**
+ * snd_pcm_prepare
+ */
+int snd_pcm_prepare(snd_pcm_substream_t *substream)
+{
+	int res;
+	snd_card_t *card = substream->pcm->card;
+
+	snd_power_lock(card);
+	if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile)) >= 0)
+		res = snd_pcm_action_nonatomic(&snd_pcm_action_prepare, substream, 0);
+	snd_power_unlock(card);
+	return res;
+}
+
+/*
+ * drain ioctl
+ */
+
+static int snd_pcm_pre_drain_init(snd_pcm_substream_t * substream, int state)
+{
+	if (substream->ffile->f_flags & O_NONBLOCK)
+		return -EAGAIN;
+	substream->runtime->trigger_master = substream;
+	return 0;
+}
+
+static int snd_pcm_do_drain_init(snd_pcm_substream_t * substream, int state)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		switch (runtime->status->state) {
+		case SNDRV_PCM_STATE_PREPARED:
+			/* start playback stream if possible */
+			if (! snd_pcm_playback_empty(substream)) {
+				snd_pcm_do_start(substream, SNDRV_PCM_STATE_DRAINING);
+				snd_pcm_post_start(substream, SNDRV_PCM_STATE_DRAINING);
+			}
+			break;
+		case SNDRV_PCM_STATE_RUNNING:
+			runtime->status->state = SNDRV_PCM_STATE_DRAINING;
+			break;
+		default:
+			break;
+		}
+	} else {
+		/* stop running stream */
+		if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) {
+			int state = snd_pcm_capture_avail(runtime) > 0 ?
+				SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP;
+			snd_pcm_do_stop(substream, state);
+			snd_pcm_post_stop(substream, state);
+		}
+	}
+	return 0;
+}
+
+static void snd_pcm_post_drain_init(snd_pcm_substream_t * substream, int state)
+{
+}
+
+static struct action_ops snd_pcm_action_drain_init = {
+	.pre_action = snd_pcm_pre_drain_init,
+	.do_action = snd_pcm_do_drain_init,
+	.post_action = snd_pcm_post_drain_init
+};
+
+struct drain_rec {
+	snd_pcm_substream_t *substream;
+	wait_queue_t wait;
+	snd_pcm_uframes_t stop_threshold;
+};
+
+static int snd_pcm_drop(snd_pcm_substream_t *substream);
+
+/*
+ * Drain the stream(s).
+ * When the substream is linked, sync until the draining of all playback streams
+ * is finished.
+ * After this call, all streams are supposed to be either SETUP or DRAINING
+ * (capture only) state.
+ */
+static int snd_pcm_drain(snd_pcm_substream_t *substream)
+{
+	snd_card_t *card;
+	snd_pcm_runtime_t *runtime;
+	struct list_head *pos;
+	int result = 0;
+	int i, num_drecs;
+	struct drain_rec *drec, drec_tmp, *d;
+
+	snd_assert(substream != NULL, return -ENXIO);
+	card = substream->pcm->card;
+	runtime = substream->runtime;
+
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+		return -EBADFD;
+
+	down_read(&snd_pcm_link_rwsem);
+	snd_power_lock(card);
+	if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+		result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
+		if (result < 0)
+			goto _unlock;
+	}
+
+	/* allocate temporary record for drain sync */
+	if (snd_pcm_stream_linked(substream)) {
+		drec = kmalloc(substream->group->count * sizeof(*drec), GFP_KERNEL);
+		if (! drec) {
+			result = -ENOMEM;
+			goto _unlock;
+		}
+	} else
+		drec = &drec_tmp;
+
+	snd_pcm_stream_lock_irq(substream);
+	/* resume pause */
+	if (runtime->status->state == SNDRV_PCM_STATE_PAUSED)
+		snd_pcm_pause(substream, 0);
+
+	/* pre-start/stop - all running streams are changed to DRAINING state */
+	result = snd_pcm_action(&snd_pcm_action_drain_init, substream, 0);
+	if (result < 0)
+		goto _end;
+
+	/* check streams with PLAYBACK & DRAINING */
+	num_drecs = 0;
+	snd_pcm_group_for_each(pos, substream) {
+		snd_pcm_substream_t *s = snd_pcm_group_substream_entry(pos);
+		runtime = s->runtime;
+		if (runtime->status->state != SNDRV_PCM_STATE_DRAINING) {
+			runtime->status->state = SNDRV_PCM_STATE_SETUP;
+			continue;
+		}
+		if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			d = &drec[num_drecs++];
+			d->substream = s;
+			init_waitqueue_entry(&d->wait, current);
+			add_wait_queue(&runtime->sleep, &d->wait);
+			/* stop_threshold fixup to avoid endless loop when
+			 * stop_threshold > buffer_size
+			 */
+			d->stop_threshold = runtime->stop_threshold;
+			if (runtime->stop_threshold > runtime->buffer_size)
+				runtime->stop_threshold = runtime->buffer_size;
+		}
+	}
+
+	if (! num_drecs)
+		goto _end;
+
+	for (;;) {
+		long tout;
+		if (signal_pending(current)) {
+			result = -ERESTARTSYS;
+			break;
+		}
+		set_current_state(TASK_INTERRUPTIBLE);
+		snd_pcm_stream_unlock_irq(substream);
+		snd_power_unlock(card);
+		tout = schedule_timeout(10 * HZ);
+		snd_power_lock(card);
+		snd_pcm_stream_lock_irq(substream);
+		if (tout == 0) {
+			if (substream->runtime->status->state == SNDRV_PCM_STATE_SUSPENDED)
+				result = -ESTRPIPE;
+			else {
+				snd_printd("playback drain error (DMA or IRQ trouble?)\n");
+				snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
+				result = -EIO;
+			}
+			break;
+		}
+		/* all finished? */
+		for (i = 0; i < num_drecs; i++) {
+			runtime = drec[i].substream->runtime;
+			if (runtime->status->state == SNDRV_PCM_STATE_DRAINING)
+				break;
+		}
+		if (i == num_drecs)
+			break;
+	}
+	for (i = 0; i < num_drecs; i++) {
+		d = &drec[i];
+		runtime = d->substream->runtime;
+		remove_wait_queue(&runtime->sleep, &d->wait);
+		runtime->stop_threshold = d->stop_threshold;
+	}
+
+ _end:
+	snd_pcm_stream_unlock_irq(substream);
+	if (drec != &drec_tmp)
+		kfree(drec);
+ _unlock:
+	snd_power_unlock(card);
+	up_read(&snd_pcm_link_rwsem);
+
+	return result;
+}
+
+/*
+ * drop ioctl
+ *
+ * Immediately put all linked substreams into SETUP state.
+ */
+static int snd_pcm_drop(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime;
+	snd_card_t *card;
+	int result = 0;
+	
+	snd_assert(substream != NULL, return -ENXIO);
+	runtime = substream->runtime;
+	card = substream->pcm->card;
+
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+		return -EBADFD;
+
+	snd_power_lock(card);
+	if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+		result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
+		if (result < 0)
+			goto _unlock;
+	}
+
+	snd_pcm_stream_lock_irq(substream);
+	/* resume pause */
+	if (runtime->status->state == SNDRV_PCM_STATE_PAUSED)
+		snd_pcm_pause(substream, 0);
+
+	snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
+	/* runtime->control->appl_ptr = runtime->status->hw_ptr; */
+	snd_pcm_stream_unlock_irq(substream);
+ _unlock:
+	snd_power_unlock(card);
+	return result;
+}
+
+
+/* WARNING: Don't forget to fput back the file */
+extern int snd_major;
+static struct file *snd_pcm_file_fd(int fd)
+{
+	struct file *file;
+	struct inode *inode;
+	unsigned short minor;
+	file = fget(fd);
+	if (!file)
+		return NULL;
+	inode = file->f_dentry->d_inode;
+	if (!S_ISCHR(inode->i_mode) ||
+	    imajor(inode) != snd_major) {
+		fput(file);
+		return NULL;
+	}
+	minor = iminor(inode);
+	if (minor >= 256 || 
+	    minor % SNDRV_MINOR_DEVICES < SNDRV_MINOR_PCM_PLAYBACK) {
+		fput(file);
+		return NULL;
+	}
+	return file;
+}
+
+/*
+ * PCM link handling
+ */
+static int snd_pcm_link(snd_pcm_substream_t *substream, int fd)
+{
+	int res = 0;
+	struct file *file;
+	snd_pcm_file_t *pcm_file;
+	snd_pcm_substream_t *substream1;
+
+	file = snd_pcm_file_fd(fd);
+	if (!file)
+		return -EBADFD;
+	pcm_file = file->private_data;
+	substream1 = pcm_file->substream;
+	down_write(&snd_pcm_link_rwsem);
+	write_lock_irq(&snd_pcm_link_rwlock);
+	if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN ||
+	    substream->runtime->status->state != substream1->runtime->status->state) {
+		res = -EBADFD;
+		goto _end;
+	}
+	if (snd_pcm_stream_linked(substream1)) {
+		res = -EALREADY;
+		goto _end;
+	}
+	if (!snd_pcm_stream_linked(substream)) {
+		substream->group = kmalloc(sizeof(snd_pcm_group_t), GFP_ATOMIC);
+		if (substream->group == NULL) {
+			res = -ENOMEM;
+			goto _end;
+		}
+		spin_lock_init(&substream->group->lock);
+		INIT_LIST_HEAD(&substream->group->substreams);
+		list_add_tail(&substream->link_list, &substream->group->substreams);
+		substream->group->count = 1;
+	}
+	list_add_tail(&substream1->link_list, &substream->group->substreams);
+	substream->group->count++;
+	substream1->group = substream->group;
+ _end:
+	write_unlock_irq(&snd_pcm_link_rwlock);
+	up_write(&snd_pcm_link_rwsem);
+	fput(file);
+	return res;
+}
+
+static void relink_to_local(snd_pcm_substream_t *substream)
+{
+	substream->group = &substream->self_group;
+	INIT_LIST_HEAD(&substream->self_group.substreams);
+	list_add_tail(&substream->link_list, &substream->self_group.substreams);
+}
+
+static int snd_pcm_unlink(snd_pcm_substream_t *substream)
+{
+	struct list_head *pos;
+	int res = 0;
+
+	down_write(&snd_pcm_link_rwsem);
+	write_lock_irq(&snd_pcm_link_rwlock);
+	if (!snd_pcm_stream_linked(substream)) {
+		res = -EALREADY;
+		goto _end;
+	}
+	list_del(&substream->link_list);
+	substream->group->count--;
+	if (substream->group->count == 1) {	/* detach the last stream, too */
+		snd_pcm_group_for_each(pos, substream) {
+			relink_to_local(snd_pcm_group_substream_entry(pos));
+			break;
+		}
+		kfree(substream->group);
+	}
+	relink_to_local(substream);
+       _end:
+	write_unlock_irq(&snd_pcm_link_rwlock);
+	up_write(&snd_pcm_link_rwsem);
+	return res;
+}
+
+/*
+ * hw configurator
+ */
+static int snd_pcm_hw_rule_mul(snd_pcm_hw_params_t *params,
+			       snd_pcm_hw_rule_t *rule)
+{
+	snd_interval_t t;
+	snd_interval_mul(hw_param_interval_c(params, rule->deps[0]),
+		     hw_param_interval_c(params, rule->deps[1]), &t);
+	return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
+static int snd_pcm_hw_rule_div(snd_pcm_hw_params_t *params,
+			       snd_pcm_hw_rule_t *rule)
+{
+	snd_interval_t t;
+	snd_interval_div(hw_param_interval_c(params, rule->deps[0]),
+		     hw_param_interval_c(params, rule->deps[1]), &t);
+	return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
+static int snd_pcm_hw_rule_muldivk(snd_pcm_hw_params_t *params,
+				   snd_pcm_hw_rule_t *rule)
+{
+	snd_interval_t t;
+	snd_interval_muldivk(hw_param_interval_c(params, rule->deps[0]),
+			 hw_param_interval_c(params, rule->deps[1]),
+			 (unsigned long) rule->private, &t);
+	return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
+static int snd_pcm_hw_rule_mulkdiv(snd_pcm_hw_params_t *params,
+				   snd_pcm_hw_rule_t *rule)
+{
+	snd_interval_t t;
+	snd_interval_mulkdiv(hw_param_interval_c(params, rule->deps[0]),
+			 (unsigned long) rule->private,
+			 hw_param_interval_c(params, rule->deps[1]), &t);
+	return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
+static int snd_pcm_hw_rule_format(snd_pcm_hw_params_t *params,
+				  snd_pcm_hw_rule_t *rule)
+{
+	unsigned int k;
+	snd_interval_t *i = hw_param_interval(params, rule->deps[0]);
+	snd_mask_t m;
+	snd_mask_t *mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+	snd_mask_any(&m);
+	for (k = 0; k <= SNDRV_PCM_FORMAT_LAST; ++k) {
+		int bits;
+		if (! snd_mask_test(mask, k))
+			continue;
+		bits = snd_pcm_format_physical_width(k);
+		if (bits <= 0)
+			continue; /* ignore invalid formats */
+		if ((unsigned)bits < i->min || (unsigned)bits > i->max)
+			snd_mask_reset(&m, k);
+	}
+	return snd_mask_refine(mask, &m);
+}
+
+static int snd_pcm_hw_rule_sample_bits(snd_pcm_hw_params_t *params,
+				       snd_pcm_hw_rule_t *rule)
+{
+	snd_interval_t t;
+	unsigned int k;
+	t.min = UINT_MAX;
+	t.max = 0;
+	t.openmin = 0;
+	t.openmax = 0;
+	for (k = 0; k <= SNDRV_PCM_FORMAT_LAST; ++k) {
+		int bits;
+		if (! snd_mask_test(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), k))
+			continue;
+		bits = snd_pcm_format_physical_width(k);
+		if (bits <= 0)
+			continue; /* ignore invalid formats */
+		if (t.min > (unsigned)bits)
+			t.min = bits;
+		if (t.max < (unsigned)bits)
+			t.max = bits;
+	}
+	t.integer = 1;
+	return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
+#if SNDRV_PCM_RATE_5512 != 1 << 0 || SNDRV_PCM_RATE_192000 != 1 << 12
+#error "Change this table"
+#endif
+
+static unsigned int rates[] = { 5512, 8000, 11025, 16000, 22050, 32000, 44100,
+                                 48000, 64000, 88200, 96000, 176400, 192000 };
+
+static int snd_pcm_hw_rule_rate(snd_pcm_hw_params_t *params,
+				snd_pcm_hw_rule_t *rule)
+{
+	snd_pcm_hardware_t *hw = rule->private;
+	return snd_interval_list(hw_param_interval(params, rule->var),
+				 ARRAY_SIZE(rates), rates, hw->rates);
+}		
+
+static int snd_pcm_hw_rule_buffer_bytes_max(snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_rule_t *rule)
+{
+	snd_interval_t t;
+	snd_pcm_substream_t *substream = rule->private;
+	t.min = 0;
+	t.max = substream->buffer_bytes_max;
+	t.openmin = 0;
+	t.openmax = 0;
+	t.integer = 1;
+	return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}		
+
+int snd_pcm_hw_constraints_init(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints;
+	int k, err;
+
+	for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) {
+		snd_mask_any(constrs_mask(constrs, k));
+	}
+
+	for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) {
+		snd_interval_any(constrs_interval(constrs, k));
+	}
+
+	snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_CHANNELS));
+	snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_SIZE));
+	snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_BYTES));
+	snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_SAMPLE_BITS));
+	snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_FRAME_BITS));
+
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,
+				   snd_pcm_hw_rule_format, NULL,
+				   SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, 
+				  snd_pcm_hw_rule_sample_bits, NULL,
+				  SNDRV_PCM_HW_PARAM_FORMAT, 
+				  SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, 
+				  snd_pcm_hw_rule_div, NULL,
+				  SNDRV_PCM_HW_PARAM_FRAME_BITS, SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS, 
+				  snd_pcm_hw_rule_mul, NULL,
+				  SNDRV_PCM_HW_PARAM_SAMPLE_BITS, SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS, 
+				  snd_pcm_hw_rule_mulkdiv, (void*) 8,
+				  SNDRV_PCM_HW_PARAM_PERIOD_BYTES, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS, 
+				  snd_pcm_hw_rule_mulkdiv, (void*) 8,
+				  SNDRV_PCM_HW_PARAM_BUFFER_BYTES, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, 
+				  snd_pcm_hw_rule_div, NULL,
+				  SNDRV_PCM_HW_PARAM_FRAME_BITS, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, 
+				  snd_pcm_hw_rule_mulkdiv, (void*) 1000000,
+				  SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_PERIOD_TIME, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, 
+				  snd_pcm_hw_rule_mulkdiv, (void*) 1000000,
+				  SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_BUFFER_TIME, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIODS, 
+				  snd_pcm_hw_rule_div, NULL,
+				  SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 
+				  snd_pcm_hw_rule_div, NULL,
+				  SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_PERIODS, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 
+				  snd_pcm_hw_rule_mulkdiv, (void*) 8,
+				  SNDRV_PCM_HW_PARAM_PERIOD_BYTES, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 
+				  snd_pcm_hw_rule_muldivk, (void*) 1000000,
+				  SNDRV_PCM_HW_PARAM_PERIOD_TIME, SNDRV_PCM_HW_PARAM_RATE, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 
+				  snd_pcm_hw_rule_mul, NULL,
+				  SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_PERIODS, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 
+				  snd_pcm_hw_rule_mulkdiv, (void*) 8,
+				  SNDRV_PCM_HW_PARAM_BUFFER_BYTES, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 
+				  snd_pcm_hw_rule_muldivk, (void*) 1000000,
+				  SNDRV_PCM_HW_PARAM_BUFFER_TIME, SNDRV_PCM_HW_PARAM_RATE, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 
+				  snd_pcm_hw_rule_muldivk, (void*) 8,
+				  SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 
+				  snd_pcm_hw_rule_muldivk, (void*) 8,
+				  SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 
+				  snd_pcm_hw_rule_mulkdiv, (void*) 1000000,
+				  SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_RATE, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_TIME, 
+				  snd_pcm_hw_rule_mulkdiv, (void*) 1000000,
+				  SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_RATE, -1);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+int snd_pcm_hw_constraints_complete(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_hardware_t *hw = &runtime->hw;
+	int err;
+	unsigned int mask = 0;
+
+        if (hw->info & SNDRV_PCM_INFO_INTERLEAVED)
+		mask |= 1 << SNDRV_PCM_ACCESS_RW_INTERLEAVED;
+        if (hw->info & SNDRV_PCM_INFO_NONINTERLEAVED)
+		mask |= 1 << SNDRV_PCM_ACCESS_RW_NONINTERLEAVED;
+	if (hw->info & SNDRV_PCM_INFO_MMAP) {
+		if (hw->info & SNDRV_PCM_INFO_INTERLEAVED)
+			mask |= 1 << SNDRV_PCM_ACCESS_MMAP_INTERLEAVED;
+		if (hw->info & SNDRV_PCM_INFO_NONINTERLEAVED)
+			mask |= 1 << SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED;
+		if (hw->info & SNDRV_PCM_INFO_COMPLEX)
+			mask |= 1 << SNDRV_PCM_ACCESS_MMAP_COMPLEX;
+	}
+	err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_ACCESS, mask);
+	snd_assert(err >= 0, return -EINVAL);
+
+	err = snd_pcm_hw_constraint_mask64(runtime, SNDRV_PCM_HW_PARAM_FORMAT, hw->formats);
+	snd_assert(err >= 0, return -EINVAL);
+
+	err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_SUBFORMAT, 1 << SNDRV_PCM_SUBFORMAT_STD);
+	snd_assert(err >= 0, return -EINVAL);
+
+	err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS,
+					   hw->channels_min, hw->channels_max);
+	snd_assert(err >= 0, return -EINVAL);
+
+	err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE,
+					   hw->rate_min, hw->rate_max);
+	snd_assert(err >= 0, return -EINVAL);
+
+	err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+					   hw->period_bytes_min, hw->period_bytes_max);
+	snd_assert(err >= 0, return -EINVAL);
+
+	err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIODS,
+					   hw->periods_min, hw->periods_max);
+	snd_assert(err >= 0, return -EINVAL);
+
+	err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+					   hw->period_bytes_min, hw->buffer_bytes_max);
+	snd_assert(err >= 0, return -EINVAL);
+
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 
+				  snd_pcm_hw_rule_buffer_bytes_max, substream,
+				  SNDRV_PCM_HW_PARAM_BUFFER_BYTES, -1);
+	if (err < 0)
+		return err;
+
+	/* FIXME: remove */
+	if (runtime->dma_bytes) {
+		err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 0, runtime->dma_bytes);
+		snd_assert(err >= 0, return -EINVAL);
+	}
+
+	if (!(hw->rates & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))) {
+		err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, 
+					  snd_pcm_hw_rule_rate, hw,
+					  SNDRV_PCM_HW_PARAM_RATE, -1);
+		if (err < 0)
+			return err;
+	}
+
+	/* FIXME: this belong to lowlevel */
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_TICK_TIME,
+				     1000000 / HZ, 1000000 / HZ);
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
+
+	return 0;
+}
+
+static void snd_pcm_add_file(snd_pcm_str_t *str,
+			     snd_pcm_file_t *pcm_file)
+{
+	pcm_file->next = str->files;
+	str->files = pcm_file;
+}
+
+static void snd_pcm_remove_file(snd_pcm_str_t *str,
+				snd_pcm_file_t *pcm_file)
+{
+	snd_pcm_file_t * pcm_file1;
+	if (str->files == pcm_file) {
+		str->files = pcm_file->next;
+	} else {
+		pcm_file1 = str->files;
+		while (pcm_file1 && pcm_file1->next != pcm_file)
+			pcm_file1 = pcm_file1->next;
+		if (pcm_file1 != NULL)
+			pcm_file1->next = pcm_file->next;
+	}
+}
+
+static int snd_pcm_release_file(snd_pcm_file_t * pcm_file)
+{
+	snd_pcm_substream_t *substream;
+	snd_pcm_runtime_t *runtime;
+	snd_pcm_str_t * str;
+
+	snd_assert(pcm_file != NULL, return -ENXIO);
+	substream = pcm_file->substream;
+	snd_assert(substream != NULL, return -ENXIO);
+	runtime = substream->runtime;
+	str = substream->pstr;
+	snd_pcm_unlink(substream);
+	if (substream->open_flag) {
+		if (substream->ops->hw_free != NULL)
+			substream->ops->hw_free(substream);
+		substream->ops->close(substream);
+		substream->open_flag = 0;
+	}
+	substream->ffile = NULL;
+	snd_pcm_remove_file(str, pcm_file);
+	snd_pcm_release_substream(substream);
+	kfree(pcm_file);
+	return 0;
+}
+
+static int snd_pcm_open_file(struct file *file,
+			     snd_pcm_t *pcm,
+			     int stream,
+			     snd_pcm_file_t **rpcm_file)
+{
+	int err = 0;
+	snd_pcm_file_t *pcm_file;
+	snd_pcm_substream_t *substream;
+	snd_pcm_str_t *str;
+
+	snd_assert(rpcm_file != NULL, return -EINVAL);
+	*rpcm_file = NULL;
+
+	pcm_file = kcalloc(1, sizeof(*pcm_file), GFP_KERNEL);
+	if (pcm_file == NULL) {
+		return -ENOMEM;
+	}
+
+	if ((err = snd_pcm_open_substream(pcm, stream, &substream)) < 0) {
+		kfree(pcm_file);
+		return err;
+	}
+
+	str = substream->pstr;
+	substream->file = pcm_file;
+	substream->no_mmap_ctrl = 0;
+
+	pcm_file->substream = substream;
+
+	snd_pcm_add_file(str, pcm_file);
+
+	err = snd_pcm_hw_constraints_init(substream);
+	if (err < 0) {
+		snd_printd("snd_pcm_hw_constraints_init failed\n");
+		snd_pcm_release_file(pcm_file);
+		return err;
+	}
+
+	if ((err = substream->ops->open(substream)) < 0) {
+		snd_pcm_release_file(pcm_file);
+		return err;
+	}
+	substream->open_flag = 1;
+
+	err = snd_pcm_hw_constraints_complete(substream);
+	if (err < 0) {
+		snd_printd("snd_pcm_hw_constraints_complete failed\n");
+		substream->ops->close(substream);
+		snd_pcm_release_file(pcm_file);
+		return err;
+	}
+
+	substream->ffile = file;
+
+	file->private_data = pcm_file;
+	*rpcm_file = pcm_file;
+	return 0;
+}
+
+static int snd_pcm_open(struct inode *inode, struct file *file)
+{
+	int cardnum = SNDRV_MINOR_CARD(iminor(inode));
+	int device = SNDRV_MINOR_DEVICE(iminor(inode));
+	int err;
+	snd_pcm_t *pcm;
+	snd_pcm_file_t *pcm_file;
+	wait_queue_t wait;
+
+	snd_runtime_check(device >= SNDRV_MINOR_PCM_PLAYBACK && device < SNDRV_MINOR_DEVICES, return -ENXIO);
+	pcm = snd_pcm_devices[(cardnum * SNDRV_PCM_DEVICES) + (device % SNDRV_MINOR_PCMS)];
+	if (pcm == NULL) {
+		err = -ENODEV;
+		goto __error1;
+	}
+	err = snd_card_file_add(pcm->card, file);
+	if (err < 0)
+		goto __error1;
+	if (!try_module_get(pcm->card->module)) {
+		err = -EFAULT;
+		goto __error2;
+	}
+	init_waitqueue_entry(&wait, current);
+	add_wait_queue(&pcm->open_wait, &wait);
+	down(&pcm->open_mutex);
+	while (1) {
+		err = snd_pcm_open_file(file, pcm, device >= SNDRV_MINOR_PCM_CAPTURE ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK, &pcm_file);
+		if (err >= 0)
+			break;
+		if (err == -EAGAIN) {
+			if (file->f_flags & O_NONBLOCK) {
+				err = -EBUSY;
+				break;
+			}
+		} else
+			break;
+		set_current_state(TASK_INTERRUPTIBLE);
+		up(&pcm->open_mutex);
+		schedule();
+		down(&pcm->open_mutex);
+		if (signal_pending(current)) {
+			err = -ERESTARTSYS;
+			break;
+		}
+	}
+	remove_wait_queue(&pcm->open_wait, &wait);
+	up(&pcm->open_mutex);
+	if (err < 0)
+		goto __error;
+	return err;
+
+      __error:
+	module_put(pcm->card->module);
+      __error2:
+      	snd_card_file_remove(pcm->card, file);
+      __error1:
+      	return err;
+}
+
+static int snd_pcm_release(struct inode *inode, struct file *file)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_substream_t *substream;
+	snd_pcm_file_t *pcm_file;
+
+	pcm_file = file->private_data;
+	substream = pcm_file->substream;
+	snd_assert(substream != NULL, return -ENXIO);
+	snd_assert(!atomic_read(&substream->runtime->mmap_count), );
+	pcm = substream->pcm;
+	snd_pcm_drop(substream);
+	fasync_helper(-1, file, 0, &substream->runtime->fasync);
+	down(&pcm->open_mutex);
+	snd_pcm_release_file(pcm_file);
+	up(&pcm->open_mutex);
+	wake_up(&pcm->open_wait);
+	module_put(pcm->card->module);
+	snd_card_file_remove(pcm->card, file);
+	return 0;
+}
+
+static snd_pcm_sframes_t snd_pcm_playback_rewind(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_sframes_t appl_ptr;
+	snd_pcm_sframes_t ret;
+	snd_pcm_sframes_t hw_avail;
+
+	if (frames == 0)
+		return 0;
+
+	snd_pcm_stream_lock_irq(substream);
+	switch (runtime->status->state) {
+	case SNDRV_PCM_STATE_PREPARED:
+		break;
+	case SNDRV_PCM_STATE_DRAINING:
+	case SNDRV_PCM_STATE_RUNNING:
+		if (snd_pcm_update_hw_ptr(substream) >= 0)
+			break;
+		/* Fall through */
+	case SNDRV_PCM_STATE_XRUN:
+		ret = -EPIPE;
+		goto __end;
+	default:
+		ret = -EBADFD;
+		goto __end;
+	}
+
+	hw_avail = snd_pcm_playback_hw_avail(runtime);
+	if (hw_avail <= 0) {
+		ret = 0;
+		goto __end;
+	}
+	if (frames > (snd_pcm_uframes_t)hw_avail)
+		frames = hw_avail;
+	else
+		frames -= frames % runtime->xfer_align;
+	appl_ptr = runtime->control->appl_ptr - frames;
+	if (appl_ptr < 0)
+		appl_ptr += runtime->boundary;
+	runtime->control->appl_ptr = appl_ptr;
+	if (runtime->status->state == SNDRV_PCM_STATE_RUNNING &&
+	    runtime->sleep_min)
+		snd_pcm_tick_prepare(substream);
+	ret = frames;
+ __end:
+	snd_pcm_stream_unlock_irq(substream);
+	return ret;
+}
+
+static snd_pcm_sframes_t snd_pcm_capture_rewind(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_sframes_t appl_ptr;
+	snd_pcm_sframes_t ret;
+	snd_pcm_sframes_t hw_avail;
+
+	if (frames == 0)
+		return 0;
+
+	snd_pcm_stream_lock_irq(substream);
+	switch (runtime->status->state) {
+	case SNDRV_PCM_STATE_PREPARED:
+	case SNDRV_PCM_STATE_DRAINING:
+		break;
+	case SNDRV_PCM_STATE_RUNNING:
+		if (snd_pcm_update_hw_ptr(substream) >= 0)
+			break;
+		/* Fall through */
+	case SNDRV_PCM_STATE_XRUN:
+		ret = -EPIPE;
+		goto __end;
+	default:
+		ret = -EBADFD;
+		goto __end;
+	}
+
+	hw_avail = snd_pcm_capture_hw_avail(runtime);
+	if (hw_avail <= 0) {
+		ret = 0;
+		goto __end;
+	}
+	if (frames > (snd_pcm_uframes_t)hw_avail)
+		frames = hw_avail;
+	else
+		frames -= frames % runtime->xfer_align;
+	appl_ptr = runtime->control->appl_ptr - frames;
+	if (appl_ptr < 0)
+		appl_ptr += runtime->boundary;
+	runtime->control->appl_ptr = appl_ptr;
+	if (runtime->status->state == SNDRV_PCM_STATE_RUNNING &&
+	    runtime->sleep_min)
+		snd_pcm_tick_prepare(substream);
+	ret = frames;
+ __end:
+	snd_pcm_stream_unlock_irq(substream);
+	return ret;
+}
+
+static snd_pcm_sframes_t snd_pcm_playback_forward(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_sframes_t appl_ptr;
+	snd_pcm_sframes_t ret;
+	snd_pcm_sframes_t avail;
+
+	if (frames == 0)
+		return 0;
+
+	snd_pcm_stream_lock_irq(substream);
+	switch (runtime->status->state) {
+	case SNDRV_PCM_STATE_PREPARED:
+	case SNDRV_PCM_STATE_PAUSED:
+		break;
+	case SNDRV_PCM_STATE_DRAINING:
+	case SNDRV_PCM_STATE_RUNNING:
+		if (snd_pcm_update_hw_ptr(substream) >= 0)
+			break;
+		/* Fall through */
+	case SNDRV_PCM_STATE_XRUN:
+		ret = -EPIPE;
+		goto __end;
+	default:
+		ret = -EBADFD;
+		goto __end;
+	}
+
+	avail = snd_pcm_playback_avail(runtime);
+	if (avail <= 0) {
+		ret = 0;
+		goto __end;
+	}
+	if (frames > (snd_pcm_uframes_t)avail)
+		frames = avail;
+	else
+		frames -= frames % runtime->xfer_align;
+	appl_ptr = runtime->control->appl_ptr + frames;
+	if (appl_ptr >= (snd_pcm_sframes_t)runtime->boundary)
+		appl_ptr -= runtime->boundary;
+	runtime->control->appl_ptr = appl_ptr;
+	if (runtime->status->state == SNDRV_PCM_STATE_RUNNING &&
+	    runtime->sleep_min)
+		snd_pcm_tick_prepare(substream);
+	ret = frames;
+ __end:
+	snd_pcm_stream_unlock_irq(substream);
+	return ret;
+}
+
+static snd_pcm_sframes_t snd_pcm_capture_forward(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_sframes_t appl_ptr;
+	snd_pcm_sframes_t ret;
+	snd_pcm_sframes_t avail;
+
+	if (frames == 0)
+		return 0;
+
+	snd_pcm_stream_lock_irq(substream);
+	switch (runtime->status->state) {
+	case SNDRV_PCM_STATE_PREPARED:
+	case SNDRV_PCM_STATE_DRAINING:
+	case SNDRV_PCM_STATE_PAUSED:
+		break;
+	case SNDRV_PCM_STATE_RUNNING:
+		if (snd_pcm_update_hw_ptr(substream) >= 0)
+			break;
+		/* Fall through */
+	case SNDRV_PCM_STATE_XRUN:
+		ret = -EPIPE;
+		goto __end;
+	default:
+		ret = -EBADFD;
+		goto __end;
+	}
+
+	avail = snd_pcm_capture_avail(runtime);
+	if (avail <= 0) {
+		ret = 0;
+		goto __end;
+	}
+	if (frames > (snd_pcm_uframes_t)avail)
+		frames = avail;
+	else
+		frames -= frames % runtime->xfer_align;
+	appl_ptr = runtime->control->appl_ptr + frames;
+	if (appl_ptr >= (snd_pcm_sframes_t)runtime->boundary)
+		appl_ptr -= runtime->boundary;
+	runtime->control->appl_ptr = appl_ptr;
+	if (runtime->status->state == SNDRV_PCM_STATE_RUNNING &&
+	    runtime->sleep_min)
+		snd_pcm_tick_prepare(substream);
+	ret = frames;
+ __end:
+	snd_pcm_stream_unlock_irq(substream);
+	return ret;
+}
+
+static int snd_pcm_hwsync(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int err;
+
+	snd_pcm_stream_lock_irq(substream);
+	switch (runtime->status->state) {
+	case SNDRV_PCM_STATE_DRAINING:
+		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+			goto __badfd;
+	case SNDRV_PCM_STATE_RUNNING:
+		if ((err = snd_pcm_update_hw_ptr(substream)) < 0)
+			break;
+		/* Fall through */
+	case SNDRV_PCM_STATE_PREPARED:
+	case SNDRV_PCM_STATE_SUSPENDED:
+		err = 0;
+		break;
+	case SNDRV_PCM_STATE_XRUN:
+		err = -EPIPE;
+		break;
+	default:
+	      __badfd:
+		err = -EBADFD;
+		break;
+	}
+	snd_pcm_stream_unlock_irq(substream);
+	return err;
+}
+		
+static int snd_pcm_delay(snd_pcm_substream_t *substream, snd_pcm_sframes_t __user *res)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	int err;
+	snd_pcm_sframes_t n = 0;
+
+	snd_pcm_stream_lock_irq(substream);
+	switch (runtime->status->state) {
+	case SNDRV_PCM_STATE_DRAINING:
+		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+			goto __badfd;
+	case SNDRV_PCM_STATE_RUNNING:
+		if ((err = snd_pcm_update_hw_ptr(substream)) < 0)
+			break;
+		/* Fall through */
+	case SNDRV_PCM_STATE_PREPARED:
+	case SNDRV_PCM_STATE_SUSPENDED:
+		err = 0;
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			n = snd_pcm_playback_hw_avail(runtime);
+		else
+			n = snd_pcm_capture_avail(runtime);
+		break;
+	case SNDRV_PCM_STATE_XRUN:
+		err = -EPIPE;
+		break;
+	default:
+	      __badfd:
+		err = -EBADFD;
+		break;
+	}
+	snd_pcm_stream_unlock_irq(substream);
+	if (!err)
+		if (put_user(n, res))
+			err = -EFAULT;
+	return err;
+}
+		
+static int snd_pcm_sync_ptr(snd_pcm_substream_t *substream, struct sndrv_pcm_sync_ptr __user *_sync_ptr)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	struct sndrv_pcm_sync_ptr sync_ptr;
+	volatile struct sndrv_pcm_mmap_status *status;
+	volatile struct sndrv_pcm_mmap_control *control;
+	int err;
+
+	memset(&sync_ptr, 0, sizeof(sync_ptr));
+	if (get_user(sync_ptr.flags, (unsigned __user *)&(_sync_ptr->flags)))
+		return -EFAULT;
+	if (copy_from_user(&sync_ptr.c.control, &(_sync_ptr->c.control), sizeof(struct sndrv_pcm_mmap_control)))
+		return -EFAULT;	
+	status = runtime->status;
+	control = runtime->control;
+	if (sync_ptr.flags & SNDRV_PCM_SYNC_PTR_HWSYNC) {
+		err = snd_pcm_hwsync(substream);
+		if (err < 0)
+			return err;
+	}
+	snd_pcm_stream_lock_irq(substream);
+	if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_APPL))
+		control->appl_ptr = sync_ptr.c.control.appl_ptr;
+	else
+		sync_ptr.c.control.appl_ptr = control->appl_ptr;
+	if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN))
+		control->avail_min = sync_ptr.c.control.avail_min;
+	else
+		sync_ptr.c.control.avail_min = control->avail_min;
+	sync_ptr.s.status.state = status->state;
+	sync_ptr.s.status.hw_ptr = status->hw_ptr;
+	sync_ptr.s.status.tstamp = status->tstamp;
+	sync_ptr.s.status.suspended_state = status->suspended_state;
+	snd_pcm_stream_unlock_irq(substream);
+	if (copy_to_user(_sync_ptr, &sync_ptr, sizeof(sync_ptr)))
+		return -EFAULT;
+	return 0;
+}
+		
+static int snd_pcm_playback_ioctl1(snd_pcm_substream_t *substream,
+				   unsigned int cmd, void __user *arg);
+static int snd_pcm_capture_ioctl1(snd_pcm_substream_t *substream,
+				  unsigned int cmd, void __user *arg);
+
+static int snd_pcm_common_ioctl1(snd_pcm_substream_t *substream,
+				 unsigned int cmd, void __user *arg)
+{
+	snd_assert(substream != NULL, return -ENXIO);
+
+	switch (cmd) {
+	case SNDRV_PCM_IOCTL_PVERSION:
+		return put_user(SNDRV_PCM_VERSION, (int __user *)arg) ? -EFAULT : 0;
+	case SNDRV_PCM_IOCTL_INFO:
+		return snd_pcm_info_user(substream, arg);
+	case SNDRV_PCM_IOCTL_TSTAMP:
+	{
+		int xarg;
+		if (get_user(xarg, (int __user *)arg))
+			return -EFAULT;
+		substream->runtime->tstamp_timespec = xarg ? 1 : 0;
+		return 0;
+	}
+	case SNDRV_PCM_IOCTL_HW_REFINE:
+		return snd_pcm_hw_refine_user(substream, arg);
+	case SNDRV_PCM_IOCTL_HW_PARAMS:
+		return snd_pcm_hw_params_user(substream, arg);
+	case SNDRV_PCM_IOCTL_HW_FREE:
+		return snd_pcm_hw_free(substream);
+	case SNDRV_PCM_IOCTL_SW_PARAMS:
+		return snd_pcm_sw_params_user(substream, arg);
+	case SNDRV_PCM_IOCTL_STATUS:
+		return snd_pcm_status_user(substream, arg);
+	case SNDRV_PCM_IOCTL_CHANNEL_INFO:
+		return snd_pcm_channel_info_user(substream, arg);
+	case SNDRV_PCM_IOCTL_PREPARE:
+		return snd_pcm_prepare(substream);
+	case SNDRV_PCM_IOCTL_RESET:
+		return snd_pcm_reset(substream);
+	case SNDRV_PCM_IOCTL_START:
+		return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
+	case SNDRV_PCM_IOCTL_LINK:
+		return snd_pcm_link(substream, (int)(unsigned long) arg);
+	case SNDRV_PCM_IOCTL_UNLINK:
+		return snd_pcm_unlink(substream);
+	case SNDRV_PCM_IOCTL_RESUME:
+		return snd_pcm_resume(substream);
+	case SNDRV_PCM_IOCTL_XRUN:
+		return snd_pcm_xrun(substream);
+	case SNDRV_PCM_IOCTL_HWSYNC:
+		return snd_pcm_hwsync(substream);
+	case SNDRV_PCM_IOCTL_DELAY:
+		return snd_pcm_delay(substream, arg);
+	case SNDRV_PCM_IOCTL_SYNC_PTR:
+		return snd_pcm_sync_ptr(substream, arg);
+	case SNDRV_PCM_IOCTL_HW_REFINE_OLD:
+		return snd_pcm_hw_refine_old_user(substream, arg);
+	case SNDRV_PCM_IOCTL_HW_PARAMS_OLD:
+		return snd_pcm_hw_params_old_user(substream, arg);
+	case SNDRV_PCM_IOCTL_DRAIN:
+		return snd_pcm_drain(substream);
+	case SNDRV_PCM_IOCTL_DROP:
+		return snd_pcm_drop(substream);
+	}
+	snd_printd("unknown ioctl = 0x%x\n", cmd);
+	return -ENOTTY;
+}
+
+static int snd_pcm_playback_ioctl1(snd_pcm_substream_t *substream,
+				   unsigned int cmd, void __user *arg)
+{
+	snd_assert(substream != NULL, return -ENXIO);
+	snd_assert(substream->stream == SNDRV_PCM_STREAM_PLAYBACK, return -EINVAL);
+	switch (cmd) {
+	case SNDRV_PCM_IOCTL_WRITEI_FRAMES:
+	{
+		snd_xferi_t xferi;
+		snd_xferi_t __user *_xferi = arg;
+		snd_pcm_runtime_t *runtime = substream->runtime;
+		snd_pcm_sframes_t result;
+		if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+			return -EBADFD;
+		if (put_user(0, &_xferi->result))
+			return -EFAULT;
+		if (copy_from_user(&xferi, _xferi, sizeof(xferi)))
+			return -EFAULT;
+		result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames);
+		__put_user(result, &_xferi->result);
+		return result < 0 ? result : 0;
+	}
+	case SNDRV_PCM_IOCTL_WRITEN_FRAMES:
+	{
+		snd_xfern_t xfern;
+		snd_xfern_t __user *_xfern = arg;
+		snd_pcm_runtime_t *runtime = substream->runtime;
+		void __user **bufs;
+		snd_pcm_sframes_t result;
+		if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+			return -EBADFD;
+		if (runtime->channels > 128)
+			return -EINVAL;
+		if (put_user(0, &_xfern->result))
+			return -EFAULT;
+		if (copy_from_user(&xfern, _xfern, sizeof(xfern)))
+			return -EFAULT;
+		bufs = kmalloc(sizeof(void *) * runtime->channels, GFP_KERNEL);
+		if (bufs == NULL)
+			return -ENOMEM;
+		if (copy_from_user(bufs, xfern.bufs, sizeof(void *) * runtime->channels)) {
+			kfree(bufs);
+			return -EFAULT;
+		}
+		result = snd_pcm_lib_writev(substream, bufs, xfern.frames);
+		kfree(bufs);
+		__put_user(result, &_xfern->result);
+		return result < 0 ? result : 0;
+	}
+	case SNDRV_PCM_IOCTL_REWIND:
+	{
+		snd_pcm_uframes_t frames;
+		snd_pcm_uframes_t __user *_frames = arg;
+		snd_pcm_sframes_t result;
+		if (get_user(frames, _frames))
+			return -EFAULT;
+		if (put_user(0, _frames))
+			return -EFAULT;
+		result = snd_pcm_playback_rewind(substream, frames);
+		__put_user(result, _frames);
+		return result < 0 ? result : 0;
+	}
+	case SNDRV_PCM_IOCTL_FORWARD:
+	{
+		snd_pcm_uframes_t frames;
+		snd_pcm_uframes_t __user *_frames = arg;
+		snd_pcm_sframes_t result;
+		if (get_user(frames, _frames))
+			return -EFAULT;
+		if (put_user(0, _frames))
+			return -EFAULT;
+		result = snd_pcm_playback_forward(substream, frames);
+		__put_user(result, _frames);
+		return result < 0 ? result : 0;
+	}
+	case SNDRV_PCM_IOCTL_PAUSE:
+	{
+		int res;
+		snd_pcm_stream_lock_irq(substream);
+		res = snd_pcm_pause(substream, (int)(unsigned long)arg);
+		snd_pcm_stream_unlock_irq(substream);
+		return res;
+	}
+	}
+	return snd_pcm_common_ioctl1(substream, cmd, arg);
+}
+
+static int snd_pcm_capture_ioctl1(snd_pcm_substream_t *substream,
+				  unsigned int cmd, void __user *arg)
+{
+	snd_assert(substream != NULL, return -ENXIO);
+	snd_assert(substream->stream == SNDRV_PCM_STREAM_CAPTURE, return -EINVAL);
+	switch (cmd) {
+	case SNDRV_PCM_IOCTL_READI_FRAMES:
+	{
+		snd_xferi_t xferi;
+		snd_xferi_t __user *_xferi = arg;
+		snd_pcm_runtime_t *runtime = substream->runtime;
+		snd_pcm_sframes_t result;
+		if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+			return -EBADFD;
+		if (put_user(0, &_xferi->result))
+			return -EFAULT;
+		if (copy_from_user(&xferi, _xferi, sizeof(xferi)))
+			return -EFAULT;
+		result = snd_pcm_lib_read(substream, xferi.buf, xferi.frames);
+		__put_user(result, &_xferi->result);
+		return result < 0 ? result : 0;
+	}
+	case SNDRV_PCM_IOCTL_READN_FRAMES:
+	{
+		snd_xfern_t xfern;
+		snd_xfern_t __user *_xfern = arg;
+		snd_pcm_runtime_t *runtime = substream->runtime;
+		void *bufs;
+		snd_pcm_sframes_t result;
+		if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+			return -EBADFD;
+		if (runtime->channels > 128)
+			return -EINVAL;
+		if (put_user(0, &_xfern->result))
+			return -EFAULT;
+		if (copy_from_user(&xfern, _xfern, sizeof(xfern)))
+			return -EFAULT;
+		bufs = kmalloc(sizeof(void *) * runtime->channels, GFP_KERNEL);
+		if (bufs == NULL)
+			return -ENOMEM;
+		if (copy_from_user(bufs, xfern.bufs, sizeof(void *) * runtime->channels)) {
+			kfree(bufs);
+			return -EFAULT;
+		}
+		result = snd_pcm_lib_readv(substream, bufs, xfern.frames);
+		kfree(bufs);
+		__put_user(result, &_xfern->result);
+		return result < 0 ? result : 0;
+	}
+	case SNDRV_PCM_IOCTL_REWIND:
+	{
+		snd_pcm_uframes_t frames;
+		snd_pcm_uframes_t __user *_frames = arg;
+		snd_pcm_sframes_t result;
+		if (get_user(frames, _frames))
+			return -EFAULT;
+		if (put_user(0, _frames))
+			return -EFAULT;
+		result = snd_pcm_capture_rewind(substream, frames);
+		__put_user(result, _frames);
+		return result < 0 ? result : 0;
+	}
+	case SNDRV_PCM_IOCTL_FORWARD:
+	{
+		snd_pcm_uframes_t frames;
+		snd_pcm_uframes_t __user *_frames = arg;
+		snd_pcm_sframes_t result;
+		if (get_user(frames, _frames))
+			return -EFAULT;
+		if (put_user(0, _frames))
+			return -EFAULT;
+		result = snd_pcm_capture_forward(substream, frames);
+		__put_user(result, _frames);
+		return result < 0 ? result : 0;
+	}
+	}
+	return snd_pcm_common_ioctl1(substream, cmd, arg);
+}
+
+static long snd_pcm_playback_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	snd_pcm_file_t *pcm_file;
+
+	pcm_file = file->private_data;
+
+	if (((cmd >> 8) & 0xff) != 'A')
+		return -ENOTTY;
+
+	return snd_pcm_playback_ioctl1(pcm_file->substream, cmd, (void __user *)arg);
+}
+
+static long snd_pcm_capture_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	snd_pcm_file_t *pcm_file;
+
+	pcm_file = file->private_data;
+
+	if (((cmd >> 8) & 0xff) != 'A')
+		return -ENOTTY;
+
+	return snd_pcm_capture_ioctl1(pcm_file->substream, cmd, (void __user *)arg);
+}
+
+int snd_pcm_kernel_playback_ioctl(snd_pcm_substream_t *substream,
+				  unsigned int cmd, void *arg)
+{
+	mm_segment_t fs;
+	int result;
+	
+	fs = snd_enter_user();
+	result = snd_pcm_playback_ioctl1(substream, cmd, (void __user *)arg);
+	snd_leave_user(fs);
+	return result;
+}
+
+int snd_pcm_kernel_capture_ioctl(snd_pcm_substream_t *substream,
+				 unsigned int cmd, void *arg)
+{
+	mm_segment_t fs;
+	int result;
+	
+	fs = snd_enter_user();
+	result = snd_pcm_capture_ioctl1(substream, cmd, (void __user *)arg);
+	snd_leave_user(fs);
+	return result;
+}
+
+int snd_pcm_kernel_ioctl(snd_pcm_substream_t *substream,
+			 unsigned int cmd, void *arg)
+{
+	switch (substream->stream) {
+	case SNDRV_PCM_STREAM_PLAYBACK:
+		return snd_pcm_kernel_playback_ioctl(substream, cmd, arg);
+	case SNDRV_PCM_STREAM_CAPTURE:
+		return snd_pcm_kernel_capture_ioctl(substream, cmd, arg);
+	default:
+		return -EINVAL;
+	}
+}
+
+static ssize_t snd_pcm_read(struct file *file, char __user *buf, size_t count, loff_t * offset)
+{
+	snd_pcm_file_t *pcm_file;
+	snd_pcm_substream_t *substream;
+	snd_pcm_runtime_t *runtime;
+	snd_pcm_sframes_t result;
+
+	pcm_file = file->private_data;
+	substream = pcm_file->substream;
+	snd_assert(substream != NULL, return -ENXIO);
+	runtime = substream->runtime;
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+		return -EBADFD;
+	if (!frame_aligned(runtime, count))
+		return -EINVAL;
+	count = bytes_to_frames(runtime, count);
+	result = snd_pcm_lib_read(substream, buf, count);
+	if (result > 0)
+		result = frames_to_bytes(runtime, result);
+	return result;
+}
+
+static ssize_t snd_pcm_write(struct file *file, const char __user *buf, size_t count, loff_t * offset)
+{
+	snd_pcm_file_t *pcm_file;
+	snd_pcm_substream_t *substream;
+	snd_pcm_runtime_t *runtime;
+	snd_pcm_sframes_t result;
+
+	pcm_file = file->private_data;
+	substream = pcm_file->substream;
+	snd_assert(substream != NULL, result = -ENXIO; goto end);
+	runtime = substream->runtime;
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+		result = -EBADFD;
+		goto end;
+	}
+	if (!frame_aligned(runtime, count)) {
+		result = -EINVAL;
+		goto end;
+	}
+	count = bytes_to_frames(runtime, count);
+	result = snd_pcm_lib_write(substream, buf, count);
+	if (result > 0)
+		result = frames_to_bytes(runtime, result);
+ end:
+	return result;
+}
+
+static ssize_t snd_pcm_readv(struct file *file, const struct iovec *_vector,
+			     unsigned long count, loff_t * offset)
+
+{
+	snd_pcm_file_t *pcm_file;
+	snd_pcm_substream_t *substream;
+	snd_pcm_runtime_t *runtime;
+	snd_pcm_sframes_t result;
+	unsigned long i;
+	void __user **bufs;
+	snd_pcm_uframes_t frames;
+
+	pcm_file = file->private_data;
+	substream = pcm_file->substream;
+	snd_assert(substream != NULL, return -ENXIO);
+	runtime = substream->runtime;
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+		return -EBADFD;
+	if (count > 1024 || count != runtime->channels)
+		return -EINVAL;
+	if (!frame_aligned(runtime, _vector->iov_len))
+		return -EINVAL;
+	frames = bytes_to_samples(runtime, _vector->iov_len);
+	bufs = kmalloc(sizeof(void *) * count, GFP_KERNEL);
+	if (bufs == NULL)
+		return -ENOMEM;
+	for (i = 0; i < count; ++i)
+		bufs[i] = _vector[i].iov_base;
+	result = snd_pcm_lib_readv(substream, bufs, frames);
+	if (result > 0)
+		result = frames_to_bytes(runtime, result);
+	kfree(bufs);
+	return result;
+}
+
+static ssize_t snd_pcm_writev(struct file *file, const struct iovec *_vector,
+			      unsigned long count, loff_t * offset)
+{
+	snd_pcm_file_t *pcm_file;
+	snd_pcm_substream_t *substream;
+	snd_pcm_runtime_t *runtime;
+	snd_pcm_sframes_t result;
+	unsigned long i;
+	void __user **bufs;
+	snd_pcm_uframes_t frames;
+
+	pcm_file = file->private_data;
+	substream = pcm_file->substream;
+	snd_assert(substream != NULL, result = -ENXIO; goto end);
+	runtime = substream->runtime;
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+		result = -EBADFD;
+		goto end;
+	}
+	if (count > 128 || count != runtime->channels ||
+	    !frame_aligned(runtime, _vector->iov_len)) {
+		result = -EINVAL;
+		goto end;
+	}
+	frames = bytes_to_samples(runtime, _vector->iov_len);
+	bufs = kmalloc(sizeof(void *) * count, GFP_KERNEL);
+	if (bufs == NULL)
+		return -ENOMEM;
+	for (i = 0; i < count; ++i)
+		bufs[i] = _vector[i].iov_base;
+	result = snd_pcm_lib_writev(substream, bufs, frames);
+	if (result > 0)
+		result = frames_to_bytes(runtime, result);
+	kfree(bufs);
+ end:
+	return result;
+}
+
+static unsigned int snd_pcm_playback_poll(struct file *file, poll_table * wait)
+{
+	snd_pcm_file_t *pcm_file;
+	snd_pcm_substream_t *substream;
+	snd_pcm_runtime_t *runtime;
+        unsigned int mask;
+	snd_pcm_uframes_t avail;
+
+	pcm_file = file->private_data;
+
+	substream = pcm_file->substream;
+	snd_assert(substream != NULL, return -ENXIO);
+	runtime = substream->runtime;
+
+	poll_wait(file, &runtime->sleep, wait);
+
+	snd_pcm_stream_lock_irq(substream);
+	avail = snd_pcm_playback_avail(runtime);
+	switch (runtime->status->state) {
+	case SNDRV_PCM_STATE_RUNNING:
+	case SNDRV_PCM_STATE_PREPARED:
+	case SNDRV_PCM_STATE_PAUSED:
+		if (avail >= runtime->control->avail_min) {
+			mask = POLLOUT | POLLWRNORM;
+			break;
+		}
+		/* Fall through */
+	case SNDRV_PCM_STATE_DRAINING:
+		mask = 0;
+		break;
+	default:
+		mask = POLLOUT | POLLWRNORM | POLLERR;
+		break;
+	}
+	snd_pcm_stream_unlock_irq(substream);
+	return mask;
+}
+
+static unsigned int snd_pcm_capture_poll(struct file *file, poll_table * wait)
+{
+	snd_pcm_file_t *pcm_file;
+	snd_pcm_substream_t *substream;
+	snd_pcm_runtime_t *runtime;
+        unsigned int mask;
+	snd_pcm_uframes_t avail;
+
+	pcm_file = file->private_data;
+
+	substream = pcm_file->substream;
+	snd_assert(substream != NULL, return -ENXIO);
+	runtime = substream->runtime;
+
+	poll_wait(file, &runtime->sleep, wait);
+
+	snd_pcm_stream_lock_irq(substream);
+	avail = snd_pcm_capture_avail(runtime);
+	switch (runtime->status->state) {
+	case SNDRV_PCM_STATE_RUNNING:
+	case SNDRV_PCM_STATE_PREPARED:
+	case SNDRV_PCM_STATE_PAUSED:
+		if (avail >= runtime->control->avail_min) {
+			mask = POLLIN | POLLRDNORM;
+			break;
+		}
+		mask = 0;
+		break;
+	case SNDRV_PCM_STATE_DRAINING:
+		if (avail > 0) {
+			mask = POLLIN | POLLRDNORM;
+			break;
+		}
+		/* Fall through */
+	default:
+		mask = POLLIN | POLLRDNORM | POLLERR;
+		break;
+	}
+	snd_pcm_stream_unlock_irq(substream);
+	return mask;
+}
+
+/*
+ * mmap support
+ */
+
+/*
+ * Only on coherent architectures, we can mmap the status and the control records
+ * for effcient data transfer.  On others, we have to use HWSYNC ioctl...
+ */
+#if defined(CONFIG_X86) || defined(CONFIG_PPC) || defined(CONFIG_ALPHA)
+/*
+ * mmap status record
+ */
+static struct page * snd_pcm_mmap_status_nopage(struct vm_area_struct *area, unsigned long address, int *type)
+{
+	snd_pcm_substream_t *substream = (snd_pcm_substream_t *)area->vm_private_data;
+	snd_pcm_runtime_t *runtime;
+	struct page * page;
+	
+	if (substream == NULL)
+		return NOPAGE_OOM;
+	runtime = substream->runtime;
+	page = virt_to_page(runtime->status);
+	if (!PageReserved(page))
+		get_page(page);
+	if (type)
+		*type = VM_FAULT_MINOR;
+	return page;
+}
+
+static struct vm_operations_struct snd_pcm_vm_ops_status =
+{
+	.nopage =	snd_pcm_mmap_status_nopage,
+};
+
+static int snd_pcm_mmap_status(snd_pcm_substream_t *substream, struct file *file,
+			       struct vm_area_struct *area)
+{
+	snd_pcm_runtime_t *runtime;
+	long size;
+	if (!(area->vm_flags & VM_READ))
+		return -EINVAL;
+	runtime = substream->runtime;
+	snd_assert(runtime != NULL, return -EAGAIN);
+	size = area->vm_end - area->vm_start;
+	if (size != PAGE_ALIGN(sizeof(snd_pcm_mmap_status_t)))
+		return -EINVAL;
+	area->vm_ops = &snd_pcm_vm_ops_status;
+	area->vm_private_data = substream;
+	area->vm_flags |= VM_RESERVED;
+	return 0;
+}
+
+/*
+ * mmap control record
+ */
+static struct page * snd_pcm_mmap_control_nopage(struct vm_area_struct *area, unsigned long address, int *type)
+{
+	snd_pcm_substream_t *substream = (snd_pcm_substream_t *)area->vm_private_data;
+	snd_pcm_runtime_t *runtime;
+	struct page * page;
+	
+	if (substream == NULL)
+		return NOPAGE_OOM;
+	runtime = substream->runtime;
+	page = virt_to_page(runtime->control);
+	if (!PageReserved(page))
+		get_page(page);
+	if (type)
+		*type = VM_FAULT_MINOR;
+	return page;
+}
+
+static struct vm_operations_struct snd_pcm_vm_ops_control =
+{
+	.nopage =	snd_pcm_mmap_control_nopage,
+};
+
+static int snd_pcm_mmap_control(snd_pcm_substream_t *substream, struct file *file,
+				struct vm_area_struct *area)
+{
+	snd_pcm_runtime_t *runtime;
+	long size;
+	if (!(area->vm_flags & VM_READ))
+		return -EINVAL;
+	runtime = substream->runtime;
+	snd_assert(runtime != NULL, return -EAGAIN);
+	size = area->vm_end - area->vm_start;
+	if (size != PAGE_ALIGN(sizeof(snd_pcm_mmap_control_t)))
+		return -EINVAL;
+	area->vm_ops = &snd_pcm_vm_ops_control;
+	area->vm_private_data = substream;
+	area->vm_flags |= VM_RESERVED;
+	return 0;
+}
+#else /* ! coherent mmap */
+/*
+ * don't support mmap for status and control records.
+ */
+static int snd_pcm_mmap_status(snd_pcm_substream_t *substream, struct file *file,
+			       struct vm_area_struct *area)
+{
+	return -ENXIO;
+}
+static int snd_pcm_mmap_control(snd_pcm_substream_t *substream, struct file *file,
+				struct vm_area_struct *area)
+{
+	return -ENXIO;
+}
+#endif /* coherent mmap */
+
+/*
+ * nopage callback for mmapping a RAM page
+ */
+static struct page *snd_pcm_mmap_data_nopage(struct vm_area_struct *area, unsigned long address, int *type)
+{
+	snd_pcm_substream_t *substream = (snd_pcm_substream_t *)area->vm_private_data;
+	snd_pcm_runtime_t *runtime;
+	unsigned long offset;
+	struct page * page;
+	void *vaddr;
+	size_t dma_bytes;
+	
+	if (substream == NULL)
+		return NOPAGE_OOM;
+	runtime = substream->runtime;
+	offset = area->vm_pgoff << PAGE_SHIFT;
+	offset += address - area->vm_start;
+	snd_assert((offset % PAGE_SIZE) == 0, return NOPAGE_OOM);
+	dma_bytes = PAGE_ALIGN(runtime->dma_bytes);
+	if (offset > dma_bytes - PAGE_SIZE)
+		return NOPAGE_SIGBUS;
+	if (substream->ops->page) {
+		page = substream->ops->page(substream, offset);
+		if (! page)
+			return NOPAGE_OOM;
+	} else {
+		vaddr = runtime->dma_area + offset;
+		page = virt_to_page(vaddr);
+	}
+	if (!PageReserved(page))
+		get_page(page);
+	if (type)
+		*type = VM_FAULT_MINOR;
+	return page;
+}
+
+static struct vm_operations_struct snd_pcm_vm_ops_data =
+{
+	.open =		snd_pcm_mmap_data_open,
+	.close =	snd_pcm_mmap_data_close,
+	.nopage =	snd_pcm_mmap_data_nopage,
+};
+
+/*
+ * mmap the DMA buffer on RAM
+ */
+static int snd_pcm_default_mmap(snd_pcm_substream_t *substream, struct vm_area_struct *area)
+{
+	area->vm_ops = &snd_pcm_vm_ops_data;
+	area->vm_private_data = substream;
+	area->vm_flags |= VM_RESERVED;
+	atomic_inc(&substream->runtime->mmap_count);
+	return 0;
+}
+
+/*
+ * mmap the DMA buffer on I/O memory area
+ */
+#if SNDRV_PCM_INFO_MMAP_IOMEM
+static struct vm_operations_struct snd_pcm_vm_ops_data_mmio =
+{
+	.open =		snd_pcm_mmap_data_open,
+	.close =	snd_pcm_mmap_data_close,
+};
+
+int snd_pcm_lib_mmap_iomem(snd_pcm_substream_t *substream, struct vm_area_struct *area)
+{
+	long size;
+	unsigned long offset;
+
+#ifdef pgprot_noncached
+	area->vm_page_prot = pgprot_noncached(area->vm_page_prot);
+#endif
+	area->vm_ops = &snd_pcm_vm_ops_data_mmio;
+	area->vm_private_data = substream;
+	area->vm_flags |= VM_IO;
+	size = area->vm_end - area->vm_start;
+	offset = area->vm_pgoff << PAGE_SHIFT;
+	if (io_remap_pfn_range(area, area->vm_start,
+				(substream->runtime->dma_addr + offset) >> PAGE_SHIFT,
+				size, area->vm_page_prot))
+		return -EAGAIN;
+	atomic_inc(&substream->runtime->mmap_count);
+	return 0;
+}
+#endif /* SNDRV_PCM_INFO_MMAP */
+
+/*
+ * mmap DMA buffer
+ */
+int snd_pcm_mmap_data(snd_pcm_substream_t *substream, struct file *file,
+		      struct vm_area_struct *area)
+{
+	snd_pcm_runtime_t *runtime;
+	long size;
+	unsigned long offset;
+	size_t dma_bytes;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		if (!(area->vm_flags & (VM_WRITE|VM_READ)))
+			return -EINVAL;
+	} else {
+		if (!(area->vm_flags & VM_READ))
+			return -EINVAL;
+	}
+	runtime = substream->runtime;
+	snd_assert(runtime != NULL, return -EAGAIN);
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+		return -EBADFD;
+	if (!(runtime->info & SNDRV_PCM_INFO_MMAP))
+		return -ENXIO;
+	if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED ||
+	    runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED)
+		return -EINVAL;
+	size = area->vm_end - area->vm_start;
+	offset = area->vm_pgoff << PAGE_SHIFT;
+	dma_bytes = PAGE_ALIGN(runtime->dma_bytes);
+	if ((size_t)size > dma_bytes)
+		return -EINVAL;
+	if (offset > dma_bytes - size)
+		return -EINVAL;
+
+	if (substream->ops->mmap)
+		return substream->ops->mmap(substream, area);
+	else
+		return snd_pcm_default_mmap(substream, area);
+}
+
+static int snd_pcm_mmap(struct file *file, struct vm_area_struct *area)
+{
+	snd_pcm_file_t * pcm_file;
+	snd_pcm_substream_t *substream;	
+	unsigned long offset;
+	
+	pcm_file = file->private_data;
+	substream = pcm_file->substream;
+	snd_assert(substream != NULL, return -ENXIO);
+
+	offset = area->vm_pgoff << PAGE_SHIFT;
+	switch (offset) {
+	case SNDRV_PCM_MMAP_OFFSET_STATUS:
+		if (substream->no_mmap_ctrl)
+			return -ENXIO;
+		return snd_pcm_mmap_status(substream, file, area);
+	case SNDRV_PCM_MMAP_OFFSET_CONTROL:
+		if (substream->no_mmap_ctrl)
+			return -ENXIO;
+		return snd_pcm_mmap_control(substream, file, area);
+	default:
+		return snd_pcm_mmap_data(substream, file, area);
+	}
+	return 0;
+}
+
+static int snd_pcm_fasync(int fd, struct file * file, int on)
+{
+	snd_pcm_file_t * pcm_file;
+	snd_pcm_substream_t *substream;
+	snd_pcm_runtime_t *runtime;
+	int err;
+
+	pcm_file = file->private_data;
+	substream = pcm_file->substream;
+	snd_assert(substream != NULL, return -ENXIO);
+	runtime = substream->runtime;
+
+	err = fasync_helper(fd, file, on, &runtime->fasync);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+/*
+ * ioctl32 compat
+ */
+#ifdef CONFIG_COMPAT
+#include "pcm_compat.c"
+#else
+#define snd_pcm_ioctl_compat	NULL
+#endif
+
+/*
+ *  To be removed helpers to keep binary compatibility
+ */
+
+#define __OLD_TO_NEW_MASK(x) ((x&7)|((x&0x07fffff8)<<5))
+#define __NEW_TO_OLD_MASK(x) ((x&7)|((x&0xffffff00)>>5))
+
+static void snd_pcm_hw_convert_from_old_params(snd_pcm_hw_params_t *params, struct sndrv_pcm_hw_params_old *oparams)
+{
+	unsigned int i;
+
+	memset(params, 0, sizeof(*params));
+	params->flags = oparams->flags;
+	for (i = 0; i < ARRAY_SIZE(oparams->masks); i++)
+		params->masks[i].bits[0] = oparams->masks[i];
+	memcpy(params->intervals, oparams->intervals, sizeof(oparams->intervals));
+	params->rmask = __OLD_TO_NEW_MASK(oparams->rmask);
+	params->cmask = __OLD_TO_NEW_MASK(oparams->cmask);
+	params->info = oparams->info;
+	params->msbits = oparams->msbits;
+	params->rate_num = oparams->rate_num;
+	params->rate_den = oparams->rate_den;
+	params->fifo_size = oparams->fifo_size;
+}
+
+static void snd_pcm_hw_convert_to_old_params(struct sndrv_pcm_hw_params_old *oparams, snd_pcm_hw_params_t *params)
+{
+	unsigned int i;
+
+	memset(oparams, 0, sizeof(*oparams));
+	oparams->flags = params->flags;
+	for (i = 0; i < ARRAY_SIZE(oparams->masks); i++)
+		oparams->masks[i] = params->masks[i].bits[0];
+	memcpy(oparams->intervals, params->intervals, sizeof(oparams->intervals));
+	oparams->rmask = __NEW_TO_OLD_MASK(params->rmask);
+	oparams->cmask = __NEW_TO_OLD_MASK(params->cmask);
+	oparams->info = params->info;
+	oparams->msbits = params->msbits;
+	oparams->rate_num = params->rate_num;
+	oparams->rate_den = params->rate_den;
+	oparams->fifo_size = params->fifo_size;
+}
+
+static int snd_pcm_hw_refine_old_user(snd_pcm_substream_t * substream, struct sndrv_pcm_hw_params_old __user * _oparams)
+{
+	snd_pcm_hw_params_t *params;
+	struct sndrv_pcm_hw_params_old *oparams = NULL;
+	int err;
+
+	params = kmalloc(sizeof(*params), GFP_KERNEL);
+	if (!params) {
+		err = -ENOMEM;
+		goto out;
+	}
+	oparams = kmalloc(sizeof(*oparams), GFP_KERNEL);
+	if (!oparams) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	if (copy_from_user(oparams, _oparams, sizeof(*oparams))) {
+		err = -EFAULT;
+		goto out;
+	}
+	snd_pcm_hw_convert_from_old_params(params, oparams);
+	err = snd_pcm_hw_refine(substream, params);
+	snd_pcm_hw_convert_to_old_params(oparams, params);
+	if (copy_to_user(_oparams, oparams, sizeof(*oparams))) {
+		if (!err)
+			err = -EFAULT;
+	}
+out:
+	kfree(params);
+	kfree(oparams);
+	return err;
+}
+
+static int snd_pcm_hw_params_old_user(snd_pcm_substream_t * substream, struct sndrv_pcm_hw_params_old __user * _oparams)
+{
+	snd_pcm_hw_params_t *params;
+	struct sndrv_pcm_hw_params_old *oparams = NULL;
+	int err;
+
+	params = kmalloc(sizeof(*params), GFP_KERNEL);
+	if (!params) {
+		err = -ENOMEM;
+		goto out;
+	}
+	oparams = kmalloc(sizeof(*oparams), GFP_KERNEL);
+	if (!oparams) {
+		err = -ENOMEM;
+		goto out;
+	}
+	if (copy_from_user(oparams, _oparams, sizeof(*oparams))) {
+		err = -EFAULT;
+		goto out;
+	}
+	snd_pcm_hw_convert_from_old_params(params, oparams);
+	err = snd_pcm_hw_params(substream, params);
+	snd_pcm_hw_convert_to_old_params(oparams, params);
+	if (copy_to_user(_oparams, oparams, sizeof(*oparams))) {
+		if (!err)
+			err = -EFAULT;
+	}
+out:
+	kfree(params);
+	kfree(oparams);
+	return err;
+}
+
+/*
+ *  Register section
+ */
+
+static struct file_operations snd_pcm_f_ops_playback = {
+	.owner =	THIS_MODULE,
+	.write =	snd_pcm_write,
+	.writev =	snd_pcm_writev,
+	.open =		snd_pcm_open,
+	.release =	snd_pcm_release,
+	.poll =		snd_pcm_playback_poll,
+	.unlocked_ioctl =	snd_pcm_playback_ioctl,
+	.compat_ioctl = snd_pcm_ioctl_compat,
+	.mmap =		snd_pcm_mmap,
+	.fasync =	snd_pcm_fasync,
+};
+
+static struct file_operations snd_pcm_f_ops_capture = {
+	.owner =	THIS_MODULE,
+	.read =		snd_pcm_read,
+	.readv =	snd_pcm_readv,
+	.open =		snd_pcm_open,
+	.release =	snd_pcm_release,
+	.poll =		snd_pcm_capture_poll,
+	.unlocked_ioctl =	snd_pcm_capture_ioctl,
+	.compat_ioctl = snd_pcm_ioctl_compat,
+	.mmap =		snd_pcm_mmap,
+	.fasync =	snd_pcm_fasync,
+};
+
+snd_minor_t snd_pcm_reg[2] =
+{
+	{
+		.comment =	"digital audio playback",
+		.f_ops =	&snd_pcm_f_ops_playback,
+	},
+	{
+		.comment =	"digital audio capture",
+		.f_ops =	&snd_pcm_f_ops_capture,
+	}
+};
diff --git a/sound/core/pcm_timer.c b/sound/core/pcm_timer.c
new file mode 100644
index 000000000000..884eaea31fec
--- /dev/null
+++ b/sound/core/pcm_timer.c
@@ -0,0 +1,161 @@
+/*
+ *  Digital Audio (PCM) abstract layer
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/timer.h>
+
+/*
+ *  Timer functions
+ */
+
+/* Greatest common divisor */
+static unsigned long gcd(unsigned long a, unsigned long b)
+{
+	unsigned long r;
+	if (a < b) {
+		r = a;
+		a = b;
+		b = r;
+	}
+	while ((r = a % b) != 0) {
+		a = b;
+		b = r;
+	}
+	return b;
+}
+
+void snd_pcm_timer_resolution_change(snd_pcm_substream_t *substream)
+{
+	unsigned long rate, mult, fsize, l, post;
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	
+        mult = 1000000000;
+	rate = runtime->rate;
+	snd_assert(rate != 0, return);
+	l = gcd(mult, rate);
+	mult /= l;
+	rate /= l;
+	fsize = runtime->period_size;
+	snd_assert(fsize != 0, return);
+	l = gcd(rate, fsize);
+	rate /= l;
+	fsize /= l;
+	post = 1;
+	while ((mult * fsize) / fsize != mult) {
+		mult /= 2;
+		post *= 2;
+	}
+	if (rate == 0) {
+		snd_printk(KERN_ERR "pcm timer resolution out of range (rate = %u, period_size = %lu)\n", runtime->rate, runtime->period_size);
+		runtime->timer_resolution = -1;
+		return;
+	}
+	runtime->timer_resolution = (mult * fsize / rate) * post;
+}
+
+static unsigned long snd_pcm_timer_resolution(snd_timer_t * timer)
+{
+	snd_pcm_substream_t * substream;
+	
+	substream = timer->private_data;
+	return substream->runtime ? substream->runtime->timer_resolution : 0;
+}
+
+static int snd_pcm_timer_start(snd_timer_t * timer)
+{
+	unsigned long flags;
+	snd_pcm_substream_t * substream;
+	
+	substream = snd_timer_chip(timer);
+	spin_lock_irqsave(&substream->timer_lock, flags);
+	substream->timer_running = 1;
+	spin_unlock_irqrestore(&substream->timer_lock, flags);
+	return 0;
+}
+
+static int snd_pcm_timer_stop(snd_timer_t * timer)
+{
+	unsigned long flags;
+	snd_pcm_substream_t * substream;
+	
+	substream = snd_timer_chip(timer);
+	spin_lock_irqsave(&substream->timer_lock, flags);
+	substream->timer_running = 0;
+	spin_unlock_irqrestore(&substream->timer_lock, flags);
+	return 0;
+}
+
+static struct _snd_timer_hardware snd_pcm_timer =
+{
+	.flags =	SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_SLAVE,
+	.resolution =	0,
+	.ticks =	1,
+	.c_resolution =	snd_pcm_timer_resolution,
+	.start =	snd_pcm_timer_start,
+	.stop =		snd_pcm_timer_stop,
+};
+
+/*
+ *  Init functions
+ */
+
+static void snd_pcm_timer_free(snd_timer_t *timer)
+{
+	snd_pcm_substream_t *substream = timer->private_data;
+	substream->timer = NULL;
+}
+
+void snd_pcm_timer_init(snd_pcm_substream_t *substream)
+{
+	snd_timer_id_t tid;
+	snd_timer_t *timer;
+	
+	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+	tid.dev_class = SNDRV_TIMER_CLASS_PCM;
+	tid.card = substream->pcm->card->number;
+	tid.device = substream->pcm->device;
+	tid.subdevice = (substream->number << 1) | (substream->stream & 1);
+	if (snd_timer_new(substream->pcm->card, "PCM", &tid, &timer) < 0)
+		return;
+	sprintf(timer->name, "PCM %s %i-%i-%i",
+			substream->stream == SNDRV_PCM_STREAM_CAPTURE ?
+				"capture" : "playback",
+			tid.card, tid.device, tid.subdevice);
+	timer->hw = snd_pcm_timer;
+	if (snd_device_register(timer->card, timer) < 0) {
+		snd_device_free(timer->card, timer);
+		return;
+	}
+	timer->private_data = substream;
+	timer->private_free = snd_pcm_timer_free;
+	substream->timer = timer;
+}
+
+void snd_pcm_timer_done(snd_pcm_substream_t *substream)
+{
+	if (substream->timer) {
+		snd_device_free(substream->pcm->card, substream->timer);
+		substream->timer = NULL;
+	}
+}
diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c
new file mode 100644
index 000000000000..edba4118271c
--- /dev/null
+++ b/sound/core/rawmidi.c
@@ -0,0 +1,1680 @@
+/*
+ *  Abstract layer for MIDI v1.0 stream
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <linux/major.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/moduleparam.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <sound/rawmidi.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/minors.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Midlevel RawMidi code for ALSA.");
+MODULE_LICENSE("GPL");
+
+#ifdef CONFIG_SND_OSSEMUL
+static int midi_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 0};
+static int amidi_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1};
+module_param_array(midi_map, int, NULL, 0444);
+MODULE_PARM_DESC(midi_map, "Raw MIDI device number assigned to 1st OSS device.");
+module_param_array(amidi_map, int, NULL, 0444);
+MODULE_PARM_DESC(amidi_map, "Raw MIDI device number assigned to 2nd OSS device.");
+#endif /* CONFIG_SND_OSSEMUL */
+
+static int snd_rawmidi_free(snd_rawmidi_t *rawmidi);
+static int snd_rawmidi_dev_free(snd_device_t *device);
+static int snd_rawmidi_dev_register(snd_device_t *device);
+static int snd_rawmidi_dev_disconnect(snd_device_t *device);
+static int snd_rawmidi_dev_unregister(snd_device_t *device);
+
+static snd_rawmidi_t *snd_rawmidi_devices[SNDRV_CARDS * SNDRV_RAWMIDI_DEVICES];
+
+static DECLARE_MUTEX(register_mutex);
+
+static inline unsigned short snd_rawmidi_file_flags(struct file *file)
+{
+	switch (file->f_mode & (FMODE_READ | FMODE_WRITE)) {
+	case FMODE_WRITE:
+		return SNDRV_RAWMIDI_LFLG_OUTPUT;
+	case FMODE_READ:
+		return SNDRV_RAWMIDI_LFLG_INPUT;
+	default:
+		return SNDRV_RAWMIDI_LFLG_OPEN;
+	}
+}
+
+static inline int snd_rawmidi_ready(snd_rawmidi_substream_t * substream)
+{
+	snd_rawmidi_runtime_t *runtime = substream->runtime;
+	return runtime->avail >= runtime->avail_min;
+}
+
+static inline int snd_rawmidi_ready_append(snd_rawmidi_substream_t * substream, size_t count)
+{
+	snd_rawmidi_runtime_t *runtime = substream->runtime;
+	return runtime->avail >= runtime->avail_min &&
+	       (!substream->append || runtime->avail >= count);
+}
+
+static void snd_rawmidi_input_event_tasklet(unsigned long data)
+{
+	snd_rawmidi_substream_t *substream = (snd_rawmidi_substream_t *)data;
+	substream->runtime->event(substream);
+}
+
+static void snd_rawmidi_output_trigger_tasklet(unsigned long data)
+{
+	snd_rawmidi_substream_t *substream = (snd_rawmidi_substream_t *)data;
+	substream->ops->trigger(substream, 1);
+}
+
+static int snd_rawmidi_runtime_create(snd_rawmidi_substream_t * substream)
+{
+	snd_rawmidi_runtime_t *runtime;
+
+	if ((runtime = kcalloc(1, sizeof(*runtime), GFP_KERNEL)) == NULL)
+		return -ENOMEM;
+	spin_lock_init(&runtime->lock);
+	init_waitqueue_head(&runtime->sleep);
+	if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT)
+		tasklet_init(&runtime->tasklet,
+			     snd_rawmidi_input_event_tasklet,
+			     (unsigned long)substream);
+	else
+		tasklet_init(&runtime->tasklet,
+			     snd_rawmidi_output_trigger_tasklet,
+			     (unsigned long)substream);
+	runtime->event = NULL;
+	runtime->buffer_size = PAGE_SIZE;
+	runtime->avail_min = 1;
+	if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT)
+		runtime->avail = 0;
+	else
+		runtime->avail = runtime->buffer_size;
+	if ((runtime->buffer = kmalloc(runtime->buffer_size, GFP_KERNEL)) == NULL) {
+		kfree(runtime);
+		return -ENOMEM;
+	}
+	runtime->appl_ptr = runtime->hw_ptr = 0;
+	substream->runtime = runtime;
+	return 0;
+}
+
+static int snd_rawmidi_runtime_free(snd_rawmidi_substream_t * substream)
+{
+	snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+	kfree(runtime->buffer);
+	kfree(runtime);
+	substream->runtime = NULL;
+	return 0;
+}
+
+static inline void snd_rawmidi_output_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+	if (up) {
+		tasklet_hi_schedule(&substream->runtime->tasklet);
+	} else {
+		tasklet_kill(&substream->runtime->tasklet);
+		substream->ops->trigger(substream, 0);
+	}
+}
+
+static void snd_rawmidi_input_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+	substream->ops->trigger(substream, up);
+	if (!up && substream->runtime->event)
+		tasklet_kill(&substream->runtime->tasklet);
+}
+
+int snd_rawmidi_drop_output(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+	snd_rawmidi_output_trigger(substream, 0);
+	runtime->drain = 0;
+	spin_lock_irqsave(&runtime->lock, flags);
+	runtime->appl_ptr = runtime->hw_ptr = 0;
+	runtime->avail = runtime->buffer_size;
+	spin_unlock_irqrestore(&runtime->lock, flags);
+	return 0;
+}
+
+int snd_rawmidi_drain_output(snd_rawmidi_substream_t * substream)
+{
+	int err;
+	long timeout;
+	snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+	err = 0;
+	runtime->drain = 1;
+	timeout = wait_event_interruptible_timeout(runtime->sleep,
+				(runtime->avail >= runtime->buffer_size),
+				10*HZ);
+	if (signal_pending(current))
+		err = -ERESTARTSYS;
+	if (runtime->avail < runtime->buffer_size && !timeout) {
+		snd_printk(KERN_WARNING "rawmidi drain error (avail = %li, buffer_size = %li)\n", (long)runtime->avail, (long)runtime->buffer_size);
+		err = -EIO;
+	}
+	runtime->drain = 0;
+	if (err != -ERESTARTSYS) {
+		/* we need wait a while to make sure that Tx FIFOs are empty */
+		if (substream->ops->drain)
+			substream->ops->drain(substream);
+		else
+			msleep(50);
+		snd_rawmidi_drop_output(substream);
+	}
+	return err;
+}
+
+int snd_rawmidi_drain_input(snd_rawmidi_substream_t * substream)
+{
+	unsigned long flags;
+	snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+	snd_rawmidi_input_trigger(substream, 0);
+	runtime->drain = 0;
+	spin_lock_irqsave(&runtime->lock, flags);
+	runtime->appl_ptr = runtime->hw_ptr = 0;
+	runtime->avail = 0;
+	spin_unlock_irqrestore(&runtime->lock, flags);
+	return 0;
+}
+
+int snd_rawmidi_kernel_open(int cardnum, int device, int subdevice,
+			    int mode, snd_rawmidi_file_t * rfile)
+{
+	snd_rawmidi_t *rmidi;
+	struct list_head *list1, *list2;
+	snd_rawmidi_substream_t *sinput = NULL, *soutput = NULL;
+	snd_rawmidi_runtime_t *input = NULL, *output = NULL;
+	int err;
+
+	if (rfile)
+		rfile->input = rfile->output = NULL;
+	rmidi = snd_rawmidi_devices[(cardnum * SNDRV_RAWMIDI_DEVICES) + device];
+	if (rmidi == NULL) {
+		err = -ENODEV;
+		goto __error1;
+	}
+	if (!try_module_get(rmidi->card->module)) {
+		err = -EFAULT;
+		goto __error1;
+	}
+	if (!(mode & SNDRV_RAWMIDI_LFLG_NOOPENLOCK))
+		down(&rmidi->open_mutex);
+	if (mode & SNDRV_RAWMIDI_LFLG_INPUT) {
+		if (!(rmidi->info_flags & SNDRV_RAWMIDI_INFO_INPUT)) {
+			err = -ENXIO;
+			goto __error;
+		}
+		if (subdevice >= 0 && (unsigned int)subdevice >= rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substream_count) {
+			err = -ENODEV;
+			goto __error;
+		}
+		if (rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substream_opened >=
+		    rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substream_count) {
+			err = -EAGAIN;
+			goto __error;
+		}
+	}
+	if (mode & SNDRV_RAWMIDI_LFLG_OUTPUT) {
+		if (!(rmidi->info_flags & SNDRV_RAWMIDI_INFO_OUTPUT)) {
+			err = -ENXIO;
+			goto __error;
+		}
+		if (subdevice >= 0 && (unsigned int)subdevice >= rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substream_count) {
+			err = -ENODEV;
+			goto __error;
+		}
+		if (rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substream_opened >=
+		    rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substream_count) {
+			err = -EAGAIN;
+			goto __error;
+		}
+	}
+	list1 = rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams.next;
+	while (1) {
+		if (list1 == &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams) {
+			sinput = NULL;
+			if (mode & SNDRV_RAWMIDI_LFLG_INPUT) {
+				err = -EAGAIN;
+				goto __error;
+			}
+			break;
+		}
+		sinput = list_entry(list1, snd_rawmidi_substream_t, list);
+		if ((mode & SNDRV_RAWMIDI_LFLG_INPUT) && sinput->opened)
+			goto __nexti;
+		if (subdevice < 0 || (subdevice >= 0 && subdevice == sinput->number))
+			break;
+	      __nexti:
+		list1 = list1->next;
+	}
+	list2 = rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams.next;
+	while (1) {
+		if (list2 == &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams) {
+			soutput = NULL;
+			if (mode & SNDRV_RAWMIDI_LFLG_OUTPUT) {
+				err = -EAGAIN;
+				goto __error;
+			}
+			break;
+		}
+		soutput = list_entry(list2, snd_rawmidi_substream_t, list);
+		if (mode & SNDRV_RAWMIDI_LFLG_OUTPUT) {
+			if (mode & SNDRV_RAWMIDI_LFLG_APPEND) {
+				if (soutput->opened && !soutput->append)
+					goto __nexto;
+			} else {
+				if (soutput->opened)
+					goto __nexto;
+			}
+		}
+		if (subdevice < 0 || (subdevice >= 0 && subdevice == soutput->number))
+			break;
+	      __nexto:
+		list2 = list2->next;
+	}
+	if (mode & SNDRV_RAWMIDI_LFLG_INPUT) {
+		if ((err = snd_rawmidi_runtime_create(sinput)) < 0)
+			goto __error;
+		input = sinput->runtime;
+		if ((err = sinput->ops->open(sinput)) < 0)
+			goto __error;
+		sinput->opened = 1;
+		rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substream_opened++;
+	} else {
+		sinput = NULL;
+	}
+	if (mode & SNDRV_RAWMIDI_LFLG_OUTPUT) {
+		if (soutput->opened)
+			goto __skip_output;
+		if ((err = snd_rawmidi_runtime_create(soutput)) < 0) {
+			if (mode & SNDRV_RAWMIDI_LFLG_INPUT)
+				sinput->ops->close(sinput);
+			goto __error;
+		}
+		output = soutput->runtime;
+		if ((err = soutput->ops->open(soutput)) < 0) {
+			if (mode & SNDRV_RAWMIDI_LFLG_INPUT)
+				sinput->ops->close(sinput);
+			goto __error;
+		}
+	      __skip_output:
+		soutput->opened = 1;
+		if (mode & SNDRV_RAWMIDI_LFLG_APPEND)
+			soutput->append = 1;
+	      	if (soutput->use_count++ == 0)
+			soutput->active_sensing = 1;
+		rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substream_opened++;
+	} else {
+		soutput = NULL;
+	}
+	if (!(mode & SNDRV_RAWMIDI_LFLG_NOOPENLOCK))
+		up(&rmidi->open_mutex);
+	if (rfile) {
+		rfile->rmidi = rmidi;
+		rfile->input = sinput;
+		rfile->output = soutput;
+	}
+	return 0;
+
+      __error:
+	if (input != NULL)
+		snd_rawmidi_runtime_free(sinput);
+	if (output != NULL)
+		snd_rawmidi_runtime_free(soutput);
+	module_put(rmidi->card->module);
+	if (!(mode & SNDRV_RAWMIDI_LFLG_NOOPENLOCK))
+		up(&rmidi->open_mutex);
+      __error1:
+	return err;
+}
+
+static int snd_rawmidi_open(struct inode *inode, struct file *file)
+{
+	int maj = imajor(inode);
+	int cardnum;
+	snd_card_t *card;
+	int device, subdevice;
+	unsigned short fflags;
+	int err;
+	snd_rawmidi_t *rmidi;
+	snd_rawmidi_file_t *rawmidi_file;
+	wait_queue_t wait;
+	struct list_head *list;
+	snd_ctl_file_t *kctl;
+
+	switch (maj) {
+	case CONFIG_SND_MAJOR:
+		cardnum = SNDRV_MINOR_CARD(iminor(inode));
+		cardnum %= SNDRV_CARDS;
+		device = SNDRV_MINOR_DEVICE(iminor(inode)) - SNDRV_MINOR_RAWMIDI;
+		device %= SNDRV_MINOR_RAWMIDIS;
+		break;
+#ifdef CONFIG_SND_OSSEMUL
+	case SOUND_MAJOR:
+		cardnum = SNDRV_MINOR_OSS_CARD(iminor(inode));
+		cardnum %= SNDRV_CARDS;
+		device = SNDRV_MINOR_OSS_DEVICE(iminor(inode)) == SNDRV_MINOR_OSS_MIDI ?
+			midi_map[cardnum] : amidi_map[cardnum];
+		break;
+#endif
+	default:
+		return -ENXIO;
+	}
+
+	rmidi = snd_rawmidi_devices[(cardnum * SNDRV_RAWMIDI_DEVICES) + device];
+	if (rmidi == NULL)
+		return -ENODEV;
+#ifdef CONFIG_SND_OSSEMUL
+	if (maj == SOUND_MAJOR && !rmidi->ossreg)
+		return -ENXIO;
+#endif
+	if ((file->f_flags & O_APPEND) && !(file->f_flags & O_NONBLOCK)) 
+		return -EINVAL;		/* invalid combination */
+	card = rmidi->card;
+	err = snd_card_file_add(card, file);
+	if (err < 0)
+		return -ENODEV;
+	fflags = snd_rawmidi_file_flags(file);
+	if ((file->f_flags & O_APPEND) || maj != CONFIG_SND_MAJOR) /* OSS emul? */
+		fflags |= SNDRV_RAWMIDI_LFLG_APPEND;
+	fflags |= SNDRV_RAWMIDI_LFLG_NOOPENLOCK;
+	rawmidi_file = kmalloc(sizeof(*rawmidi_file), GFP_KERNEL);
+	if (rawmidi_file == NULL) {
+		snd_card_file_remove(card, file);
+		return -ENOMEM;
+	}
+	init_waitqueue_entry(&wait, current);
+	add_wait_queue(&rmidi->open_wait, &wait);
+	down(&rmidi->open_mutex);
+	while (1) {
+		subdevice = -1;
+		down_read(&card->controls_rwsem);
+		list_for_each(list, &card->ctl_files) {
+			kctl = snd_ctl_file(list);
+			if (kctl->pid == current->pid) {
+				subdevice = kctl->prefer_rawmidi_subdevice;
+				break;
+			}
+		}
+		up_read(&card->controls_rwsem);
+		err = snd_rawmidi_kernel_open(cardnum, device, subdevice, fflags, rawmidi_file);
+		if (err >= 0)
+			break;
+		if (err == -EAGAIN) {
+			if (file->f_flags & O_NONBLOCK) {
+				err = -EBUSY;
+				break;
+			}
+		} else
+			break;
+		set_current_state(TASK_INTERRUPTIBLE);
+		up(&rmidi->open_mutex);
+		schedule();
+		down(&rmidi->open_mutex);
+		if (signal_pending(current)) {
+			err = -ERESTARTSYS;
+			break;
+		}
+	}
+#ifdef CONFIG_SND_OSSEMUL
+	if (rawmidi_file->input && rawmidi_file->input->runtime)
+		rawmidi_file->input->runtime->oss = (maj == SOUND_MAJOR);
+	if (rawmidi_file->output && rawmidi_file->output->runtime)
+		rawmidi_file->output->runtime->oss = (maj == SOUND_MAJOR);
+#endif
+	remove_wait_queue(&rmidi->open_wait, &wait);
+	if (err >= 0) {
+		file->private_data = rawmidi_file;
+	} else {
+		snd_card_file_remove(card, file);
+		kfree(rawmidi_file);
+	}
+	up(&rmidi->open_mutex);
+	return err;
+}
+
+int snd_rawmidi_kernel_release(snd_rawmidi_file_t * rfile)
+{
+	snd_rawmidi_t *rmidi;
+	snd_rawmidi_substream_t *substream;
+	snd_rawmidi_runtime_t *runtime;
+
+	snd_assert(rfile != NULL, return -ENXIO);
+	snd_assert(rfile->input != NULL || rfile->output != NULL, return -ENXIO);
+	rmidi = rfile->rmidi;
+	down(&rmidi->open_mutex);
+	if (rfile->input != NULL) {
+		substream = rfile->input;
+		rfile->input = NULL;
+		runtime = substream->runtime;
+		snd_rawmidi_input_trigger(substream, 0);
+		substream->ops->close(substream);
+		if (runtime->private_free != NULL)
+			runtime->private_free(substream);
+		snd_rawmidi_runtime_free(substream);
+		substream->opened = 0;
+		rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substream_opened--;
+	}
+	if (rfile->output != NULL) {
+		substream = rfile->output;
+		rfile->output = NULL;
+		if (--substream->use_count == 0) {
+			runtime = substream->runtime;
+			if (substream->active_sensing) {
+				unsigned char buf = 0xfe;
+				/* sending single active sensing message to shut the device up */
+				snd_rawmidi_kernel_write(substream, &buf, 1);
+			}
+			if (snd_rawmidi_drain_output(substream) == -ERESTARTSYS)
+				snd_rawmidi_output_trigger(substream, 0);
+			substream->ops->close(substream);
+			if (runtime->private_free != NULL)
+				runtime->private_free(substream);
+			snd_rawmidi_runtime_free(substream);
+			substream->opened = 0;
+			substream->append = 0;
+		}
+		rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substream_opened--;
+	}
+	up(&rmidi->open_mutex);
+	module_put(rmidi->card->module);
+	return 0;
+}
+
+static int snd_rawmidi_release(struct inode *inode, struct file *file)
+{
+	snd_rawmidi_file_t *rfile;
+	snd_rawmidi_t *rmidi;
+	int err;
+
+	rfile = file->private_data;
+	err = snd_rawmidi_kernel_release(rfile);
+	rmidi = rfile->rmidi;
+	wake_up(&rmidi->open_wait);
+	kfree(rfile);
+	snd_card_file_remove(rmidi->card, file);
+	return err;
+}
+
+int snd_rawmidi_info(snd_rawmidi_substream_t *substream, snd_rawmidi_info_t *info)
+{
+	snd_rawmidi_t *rmidi;
+	
+	if (substream == NULL)
+		return -ENODEV;
+	rmidi = substream->rmidi;
+	memset(info, 0, sizeof(*info));
+	info->card = rmidi->card->number;
+	info->device = rmidi->device;
+	info->subdevice = substream->number;
+	info->stream = substream->stream;
+	info->flags = rmidi->info_flags;
+	strcpy(info->id, rmidi->id);
+	strcpy(info->name, rmidi->name);
+	strcpy(info->subname, substream->name);
+	info->subdevices_count = substream->pstr->substream_count;
+	info->subdevices_avail = (substream->pstr->substream_count -
+				  substream->pstr->substream_opened);
+	return 0;
+}
+
+static int snd_rawmidi_info_user(snd_rawmidi_substream_t *substream, snd_rawmidi_info_t __user * _info)
+{
+	snd_rawmidi_info_t info;
+	int err;
+	if ((err = snd_rawmidi_info(substream, &info)) < 0)
+		return err;
+	if (copy_to_user(_info, &info, sizeof(snd_rawmidi_info_t)))
+		return -EFAULT;
+	return 0;
+}
+
+int snd_rawmidi_info_select(snd_card_t *card, snd_rawmidi_info_t *info)
+{
+	snd_rawmidi_t *rmidi;
+	snd_rawmidi_str_t *pstr;
+	snd_rawmidi_substream_t *substream;
+	struct list_head *list;
+	if (info->device >= SNDRV_RAWMIDI_DEVICES)
+		return -ENXIO;
+	rmidi = snd_rawmidi_devices[card->number * SNDRV_RAWMIDI_DEVICES + info->device];
+	if (info->stream < 0 || info->stream > 1)
+		return -EINVAL;
+	pstr = &rmidi->streams[info->stream];
+	if (pstr->substream_count == 0)
+		return -ENOENT;
+	if (info->subdevice >= pstr->substream_count)
+		return -ENXIO;
+	list_for_each(list, &pstr->substreams) {
+		substream = list_entry(list, snd_rawmidi_substream_t, list);
+		if ((unsigned int)substream->number == info->subdevice)
+			return snd_rawmidi_info(substream, info);
+	}
+	return -ENXIO;
+}
+
+static int snd_rawmidi_info_select_user(snd_card_t *card,
+					snd_rawmidi_info_t __user *_info)
+{
+	int err;
+	snd_rawmidi_info_t info;
+	if (get_user(info.device, &_info->device))
+		return -EFAULT;
+	if (get_user(info.stream, &_info->stream))
+		return -EFAULT;
+	if (get_user(info.subdevice, &_info->subdevice))
+		return -EFAULT;
+	if ((err = snd_rawmidi_info_select(card, &info)) < 0)
+		return err;
+	if (copy_to_user(_info, &info, sizeof(snd_rawmidi_info_t)))
+		return -EFAULT;
+	return 0;
+}
+
+int snd_rawmidi_output_params(snd_rawmidi_substream_t * substream,
+			      snd_rawmidi_params_t * params)
+{
+	char *newbuf;
+	snd_rawmidi_runtime_t *runtime = substream->runtime;
+	
+	if (substream->append && substream->use_count > 1)
+		return -EBUSY;
+	snd_rawmidi_drain_output(substream);
+	if (params->buffer_size < 32 || params->buffer_size > 1024L * 1024L) {
+		return -EINVAL;
+	}
+	if (params->avail_min < 1 || params->avail_min > params->buffer_size) {
+		return -EINVAL;
+	}
+	if (params->buffer_size != runtime->buffer_size) {
+		if ((newbuf = (char *) kmalloc(params->buffer_size, GFP_KERNEL)) == NULL)
+			return -ENOMEM;
+		kfree(runtime->buffer);
+		runtime->buffer = newbuf;
+		runtime->buffer_size = params->buffer_size;
+	}
+	runtime->avail_min = params->avail_min;
+	substream->active_sensing = !params->no_active_sensing;
+	return 0;
+}
+
+int snd_rawmidi_input_params(snd_rawmidi_substream_t * substream,
+			     snd_rawmidi_params_t * params)
+{
+	char *newbuf;
+	snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+	snd_rawmidi_drain_input(substream);
+	if (params->buffer_size < 32 || params->buffer_size > 1024L * 1024L) {
+		return -EINVAL;
+	}
+	if (params->avail_min < 1 || params->avail_min > params->buffer_size) {
+		return -EINVAL;
+	}
+	if (params->buffer_size != runtime->buffer_size) {
+		if ((newbuf = (char *) kmalloc(params->buffer_size, GFP_KERNEL)) == NULL)
+			return -ENOMEM;
+		kfree(runtime->buffer);
+		runtime->buffer = newbuf;
+		runtime->buffer_size = params->buffer_size;
+	}
+	runtime->avail_min = params->avail_min;
+	return 0;
+}
+
+static int snd_rawmidi_output_status(snd_rawmidi_substream_t * substream,
+				     snd_rawmidi_status_t * status)
+{
+	snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+	memset(status, 0, sizeof(*status));
+	status->stream = SNDRV_RAWMIDI_STREAM_OUTPUT;
+	spin_lock_irq(&runtime->lock);
+	status->avail = runtime->avail;
+	spin_unlock_irq(&runtime->lock);
+	return 0;
+}
+
+static int snd_rawmidi_input_status(snd_rawmidi_substream_t * substream,
+				    snd_rawmidi_status_t * status)
+{
+	snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+	memset(status, 0, sizeof(*status));
+	status->stream = SNDRV_RAWMIDI_STREAM_INPUT;
+	spin_lock_irq(&runtime->lock);
+	status->avail = runtime->avail;
+	status->xruns = runtime->xruns;
+	runtime->xruns = 0;
+	spin_unlock_irq(&runtime->lock);
+	return 0;
+}
+
+static long snd_rawmidi_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	snd_rawmidi_file_t *rfile;
+	void __user *argp = (void __user *)arg;
+
+	rfile = file->private_data;
+	if (((cmd >> 8) & 0xff) != 'W')
+		return -ENOTTY;
+	switch (cmd) {
+	case SNDRV_RAWMIDI_IOCTL_PVERSION:
+		return put_user(SNDRV_RAWMIDI_VERSION, (int __user *)argp) ? -EFAULT : 0;
+	case SNDRV_RAWMIDI_IOCTL_INFO:
+	{
+		snd_rawmidi_stream_t stream;
+		snd_rawmidi_info_t __user *info = argp;
+		if (get_user(stream, &info->stream))
+			return -EFAULT;
+		switch (stream) {
+		case SNDRV_RAWMIDI_STREAM_INPUT:
+			return snd_rawmidi_info_user(rfile->input, info);
+		case SNDRV_RAWMIDI_STREAM_OUTPUT:
+			return snd_rawmidi_info_user(rfile->output, info);
+		default:
+			return -EINVAL;
+		}
+	}
+	case SNDRV_RAWMIDI_IOCTL_PARAMS:
+	{
+		snd_rawmidi_params_t params;
+		if (copy_from_user(&params, argp, sizeof(snd_rawmidi_params_t)))
+			return -EFAULT;
+		switch (params.stream) {
+		case SNDRV_RAWMIDI_STREAM_OUTPUT:
+			if (rfile->output == NULL)
+				return -EINVAL;
+			return snd_rawmidi_output_params(rfile->output, &params);
+		case SNDRV_RAWMIDI_STREAM_INPUT:
+			if (rfile->input == NULL)
+				return -EINVAL;
+			return snd_rawmidi_input_params(rfile->input, &params);
+		default:
+			return -EINVAL;
+		}
+	}
+	case SNDRV_RAWMIDI_IOCTL_STATUS:
+	{
+		int err = 0;
+		snd_rawmidi_status_t status;
+		if (copy_from_user(&status, argp, sizeof(snd_rawmidi_status_t)))
+			return -EFAULT;
+		switch (status.stream) {
+		case SNDRV_RAWMIDI_STREAM_OUTPUT:
+			if (rfile->output == NULL)
+				return -EINVAL;
+			err = snd_rawmidi_output_status(rfile->output, &status);
+			break;
+		case SNDRV_RAWMIDI_STREAM_INPUT:
+			if (rfile->input == NULL)
+				return -EINVAL;
+			err = snd_rawmidi_input_status(rfile->input, &status);
+			break;
+		default:
+			return -EINVAL;
+		}
+		if (err < 0)
+			return err;
+		if (copy_to_user(argp, &status, sizeof(snd_rawmidi_status_t)))
+			return -EFAULT;
+		return 0;
+	}
+	case SNDRV_RAWMIDI_IOCTL_DROP:
+	{
+		int val;
+		if (get_user(val, (int __user *) argp))
+			return -EFAULT;
+		switch (val) {
+		case SNDRV_RAWMIDI_STREAM_OUTPUT:
+			if (rfile->output == NULL)
+				return -EINVAL;
+			return snd_rawmidi_drop_output(rfile->output);
+		default:
+			return -EINVAL;
+		}
+	}
+	case SNDRV_RAWMIDI_IOCTL_DRAIN:
+	{
+		int val;
+		if (get_user(val, (int __user *) argp))
+			return -EFAULT;
+		switch (val) {
+		case SNDRV_RAWMIDI_STREAM_OUTPUT:
+			if (rfile->output == NULL)
+				return -EINVAL;
+			return snd_rawmidi_drain_output(rfile->output);
+		case SNDRV_RAWMIDI_STREAM_INPUT:
+			if (rfile->input == NULL)
+				return -EINVAL;
+			return snd_rawmidi_drain_input(rfile->input);
+		default:
+			return -EINVAL;
+		}
+	}
+#ifdef CONFIG_SND_DEBUG
+	default:
+		snd_printk(KERN_WARNING "rawmidi: unknown command = 0x%x\n", cmd);
+#endif
+	}
+	return -ENOTTY;
+}
+
+static int snd_rawmidi_control_ioctl(snd_card_t * card,
+				     snd_ctl_file_t * control,
+				     unsigned int cmd,
+				     unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	unsigned int tmp;
+
+	tmp = card->number * SNDRV_RAWMIDI_DEVICES;
+	switch (cmd) {
+	case SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE:
+	{
+		int device;
+		
+		if (get_user(device, (int __user *)argp))
+			return -EFAULT;
+		device = device < 0 ? 0 : device + 1;
+		while (device < SNDRV_RAWMIDI_DEVICES) {
+			if (snd_rawmidi_devices[tmp + device])
+				break;
+			device++;
+		}
+		if (device == SNDRV_RAWMIDI_DEVICES)
+			device = -1;
+		if (put_user(device, (int __user *)argp))
+			return -EFAULT;
+		return 0;
+	}
+	case SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE:
+	{
+		int val;
+		
+		if (get_user(val, (int __user *)argp))
+			return -EFAULT;
+		control->prefer_rawmidi_subdevice = val;
+		return 0;
+	}
+	case SNDRV_CTL_IOCTL_RAWMIDI_INFO:
+		return snd_rawmidi_info_select_user(card, argp);
+	}
+	return -ENOIOCTLCMD;
+}
+
+/**
+ * snd_rawmidi_receive - receive the input data from the device
+ * @substream: the rawmidi substream
+ * @buffer: the buffer pointer
+ * @count: the data size to read
+ *
+ * Reads the data from the internal buffer.
+ *
+ * Returns the size of read data, or a negative error code on failure.
+ */
+int snd_rawmidi_receive(snd_rawmidi_substream_t * substream, const unsigned char *buffer, int count)
+{
+	unsigned long flags;
+	int result = 0, count1;
+	snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+	if (runtime->buffer == NULL) {
+		snd_printd("snd_rawmidi_receive: input is not active!!!\n");
+		return -EINVAL;
+	}
+	spin_lock_irqsave(&runtime->lock, flags);
+	if (count == 1) {	/* special case, faster code */
+		substream->bytes++;
+		if (runtime->avail < runtime->buffer_size) {
+			runtime->buffer[runtime->hw_ptr++] = buffer[0];
+			runtime->hw_ptr %= runtime->buffer_size;
+			runtime->avail++;
+			result++;
+		} else {
+			runtime->xruns++;
+		}
+	} else {
+		substream->bytes += count;
+		count1 = runtime->buffer_size - runtime->hw_ptr;
+		if (count1 > count)
+			count1 = count;
+		if (count1 > (int)(runtime->buffer_size - runtime->avail))
+			count1 = runtime->buffer_size - runtime->avail;
+		memcpy(runtime->buffer + runtime->hw_ptr, buffer, count1);
+		runtime->hw_ptr += count1;
+		runtime->hw_ptr %= runtime->buffer_size;
+		runtime->avail += count1;
+		count -= count1;
+		result += count1;
+		if (count > 0) {
+			buffer += count1;
+			count1 = count;
+			if (count1 > (int)(runtime->buffer_size - runtime->avail)) {
+				count1 = runtime->buffer_size - runtime->avail;
+				runtime->xruns += count - count1;
+			}
+			if (count1 > 0) {
+				memcpy(runtime->buffer, buffer, count1);
+				runtime->hw_ptr = count1;
+				runtime->avail += count1;
+				result += count1;
+			}
+		}
+	}
+	if (result > 0) {
+		if (runtime->event)
+			tasklet_hi_schedule(&runtime->tasklet);
+		else if (snd_rawmidi_ready(substream))
+			wake_up(&runtime->sleep);
+	}
+	spin_unlock_irqrestore(&runtime->lock, flags);
+	return result;
+}
+
+static long snd_rawmidi_kernel_read1(snd_rawmidi_substream_t *substream,
+				     unsigned char *buf, long count, int kernel)
+{
+	unsigned long flags;
+	long result = 0, count1;
+	snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+	while (count > 0 && runtime->avail) {
+		count1 = runtime->buffer_size - runtime->appl_ptr;
+		if (count1 > count)
+			count1 = count;
+		spin_lock_irqsave(&runtime->lock, flags);
+		if (count1 > (int)runtime->avail)
+			count1 = runtime->avail;
+		if (kernel) {
+			memcpy(buf + result, runtime->buffer + runtime->appl_ptr, count1);
+		} else {
+			spin_unlock_irqrestore(&runtime->lock, flags);
+			if (copy_to_user((char __user *)buf + result,
+					 runtime->buffer + runtime->appl_ptr, count1)) {
+				return result > 0 ? result : -EFAULT;
+			}
+			spin_lock_irqsave(&runtime->lock, flags);
+		}
+		runtime->appl_ptr += count1;
+		runtime->appl_ptr %= runtime->buffer_size;
+		runtime->avail -= count1;
+		spin_unlock_irqrestore(&runtime->lock, flags);
+		result += count1;
+		count -= count1;
+	}
+	return result;
+}
+
+long snd_rawmidi_kernel_read(snd_rawmidi_substream_t *substream, unsigned char *buf, long count)
+{
+	snd_rawmidi_input_trigger(substream, 1);
+	return snd_rawmidi_kernel_read1(substream, buf, count, 1);
+}
+
+static ssize_t snd_rawmidi_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
+{
+	long result;
+	int count1;
+	snd_rawmidi_file_t *rfile;
+	snd_rawmidi_substream_t *substream;
+	snd_rawmidi_runtime_t *runtime;
+
+	rfile = file->private_data;
+	substream = rfile->input;
+	if (substream == NULL)
+		return -EIO;
+	runtime = substream->runtime;
+	snd_rawmidi_input_trigger(substream, 1);
+	result = 0;
+	while (count > 0) {
+		spin_lock_irq(&runtime->lock);
+		while (!snd_rawmidi_ready(substream)) {
+			wait_queue_t wait;
+			if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) {
+				spin_unlock_irq(&runtime->lock);
+				return result > 0 ? result : -EAGAIN;
+			}
+			init_waitqueue_entry(&wait, current);
+			add_wait_queue(&runtime->sleep, &wait);
+			set_current_state(TASK_INTERRUPTIBLE);
+			spin_unlock_irq(&runtime->lock);
+			schedule();
+			remove_wait_queue(&runtime->sleep, &wait);
+			if (signal_pending(current))
+				return result > 0 ? result : -ERESTARTSYS;
+			if (!runtime->avail)
+				return result > 0 ? result : -EIO;
+			spin_lock_irq(&runtime->lock);
+		}
+		spin_unlock_irq(&runtime->lock);
+		count1 = snd_rawmidi_kernel_read1(substream, (unsigned char *)buf, count, 0);
+		if (count1 < 0)
+			return result > 0 ? result : count1;
+		result += count1;
+		buf += count1;
+		count -= count1;
+	}
+	return result;
+}
+
+/**
+ * snd_rawmidi_transmit_empty - check whether the output buffer is empty
+ * @substream: the rawmidi substream
+ * 
+ * Returns 1 if the internal output buffer is empty, 0 if not.
+ */
+int snd_rawmidi_transmit_empty(snd_rawmidi_substream_t * substream)
+{
+	snd_rawmidi_runtime_t *runtime = substream->runtime;
+	int result;
+	unsigned long flags;
+
+	if (runtime->buffer == NULL) {
+		snd_printd("snd_rawmidi_transmit_empty: output is not active!!!\n");
+		return 1;
+	}
+	spin_lock_irqsave(&runtime->lock, flags);
+	result = runtime->avail >= runtime->buffer_size;
+	spin_unlock_irqrestore(&runtime->lock, flags);
+	return result;		
+}
+
+/**
+ * snd_rawmidi_transmit_peek - copy data from the internal buffer
+ * @substream: the rawmidi substream
+ * @buffer: the buffer pointer
+ * @count: data size to transfer
+ *
+ * Copies data from the internal output buffer to the given buffer.
+ *
+ * Call this in the interrupt handler when the midi output is ready,
+ * and call snd_rawmidi_transmit_ack() after the transmission is
+ * finished.
+ *
+ * Returns the size of copied data, or a negative error code on failure.
+ */
+int snd_rawmidi_transmit_peek(snd_rawmidi_substream_t * substream, unsigned char *buffer, int count)
+{
+	unsigned long flags;
+	int result, count1;
+	snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+	if (runtime->buffer == NULL) {
+		snd_printd("snd_rawmidi_transmit_peek: output is not active!!!\n");
+		return -EINVAL;
+	}
+	result = 0;
+	spin_lock_irqsave(&runtime->lock, flags);
+	if (runtime->avail >= runtime->buffer_size) {
+		/* warning: lowlevel layer MUST trigger down the hardware */
+		goto __skip;
+	}
+	if (count == 1) {	/* special case, faster code */
+		*buffer = runtime->buffer[runtime->hw_ptr];
+		result++;
+	} else {
+		count1 = runtime->buffer_size - runtime->hw_ptr;
+		if (count1 > count)
+			count1 = count;
+		if (count1 > (int)(runtime->buffer_size - runtime->avail))
+			count1 = runtime->buffer_size - runtime->avail;
+		memcpy(buffer, runtime->buffer + runtime->hw_ptr, count1);
+		count -= count1;
+		result += count1;
+		if (count > 0) {
+			if (count > (int)(runtime->buffer_size - runtime->avail - count1))
+				count = runtime->buffer_size - runtime->avail - count1;
+			memcpy(buffer + count1, runtime->buffer, count);
+			result += count;
+		}
+	}
+      __skip:
+	spin_unlock_irqrestore(&runtime->lock, flags);
+	return result;
+}
+
+/**
+ * snd_rawmidi_transmit_ack - acknowledge the transmission
+ * @substream: the rawmidi substream
+ * @count: the tranferred count
+ *
+ * Advances the hardware pointer for the internal output buffer with
+ * the given size and updates the condition.
+ * Call after the transmission is finished.
+ *
+ * Returns the advanced size if successful, or a negative error code on failure.
+ */
+int snd_rawmidi_transmit_ack(snd_rawmidi_substream_t * substream, int count)
+{
+	unsigned long flags;
+	snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+	if (runtime->buffer == NULL) {
+		snd_printd("snd_rawmidi_transmit_ack: output is not active!!!\n");
+		return -EINVAL;
+	}
+	spin_lock_irqsave(&runtime->lock, flags);
+	snd_assert(runtime->avail + count <= runtime->buffer_size, );
+	runtime->hw_ptr += count;
+	runtime->hw_ptr %= runtime->buffer_size;
+	runtime->avail += count;
+	substream->bytes += count;
+	if (count > 0) {
+		if (runtime->drain || snd_rawmidi_ready(substream))
+			wake_up(&runtime->sleep);
+	}
+	spin_unlock_irqrestore(&runtime->lock, flags);
+	return count;
+}
+
+/**
+ * snd_rawmidi_transmit - copy from the buffer to the device
+ * @substream: the rawmidi substream
+ * @buf: the buffer pointer
+ * @count: the data size to transfer
+ * 
+ * Copies data from the buffer to the device and advances the pointer.
+ *
+ * Returns the copied size if successful, or a negative error code on failure.
+ */
+int snd_rawmidi_transmit(snd_rawmidi_substream_t * substream, unsigned char *buffer, int count)
+{
+	count = snd_rawmidi_transmit_peek(substream, buffer, count);
+	if (count < 0)
+		return count;
+	return snd_rawmidi_transmit_ack(substream, count);
+}
+
+static long snd_rawmidi_kernel_write1(snd_rawmidi_substream_t * substream, const unsigned char *buf, long count, int kernel)
+{
+	unsigned long flags;
+	long count1, result;
+	snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+	snd_assert(buf != NULL, return -EINVAL);
+	snd_assert(runtime->buffer != NULL, return -EINVAL);
+
+	result = 0;
+	spin_lock_irqsave(&runtime->lock, flags);
+	if (substream->append) {
+		if ((long)runtime->avail < count) {
+			spin_unlock_irqrestore(&runtime->lock, flags);
+			return -EAGAIN;
+		}
+	}
+	while (count > 0 && runtime->avail > 0) {
+		count1 = runtime->buffer_size - runtime->appl_ptr;
+		if (count1 > count)
+			count1 = count;
+		if (count1 > (long)runtime->avail)
+			count1 = runtime->avail;
+		if (kernel) {
+			memcpy(runtime->buffer + runtime->appl_ptr, buf, count1);
+		} else {
+			spin_unlock_irqrestore(&runtime->lock, flags);
+			if (copy_from_user(runtime->buffer + runtime->appl_ptr,
+					   (char __user *)buf, count1)) {
+				spin_lock_irqsave(&runtime->lock, flags);
+				result = result > 0 ? result : -EFAULT;
+				goto __end;
+			}
+			spin_lock_irqsave(&runtime->lock, flags);
+		}
+		runtime->appl_ptr += count1;
+		runtime->appl_ptr %= runtime->buffer_size;
+		runtime->avail -= count1;
+		result += count1;
+		buf += count1;
+		count -= count1;
+	}
+      __end:
+	count1 = runtime->avail < runtime->buffer_size;
+	spin_unlock_irqrestore(&runtime->lock, flags);
+	if (count1)
+		snd_rawmidi_output_trigger(substream, 1);
+	return result;
+}
+
+long snd_rawmidi_kernel_write(snd_rawmidi_substream_t * substream, const unsigned char *buf, long count)
+{
+	return snd_rawmidi_kernel_write1(substream, buf, count, 1);
+}
+
+static ssize_t snd_rawmidi_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
+{
+	long result, timeout;
+	int count1;
+	snd_rawmidi_file_t *rfile;
+	snd_rawmidi_runtime_t *runtime;
+	snd_rawmidi_substream_t *substream;
+
+	rfile = file->private_data;
+	substream = rfile->output;
+	runtime = substream->runtime;
+	/* we cannot put an atomic message to our buffer */
+	if (substream->append && count > runtime->buffer_size)
+		return -EIO;
+	result = 0;
+	while (count > 0) {
+		spin_lock_irq(&runtime->lock);
+		while (!snd_rawmidi_ready_append(substream, count)) {
+			wait_queue_t wait;
+			if (file->f_flags & O_NONBLOCK) {
+				spin_unlock_irq(&runtime->lock);
+				return result > 0 ? result : -EAGAIN;
+			}
+			init_waitqueue_entry(&wait, current);
+			add_wait_queue(&runtime->sleep, &wait);
+			set_current_state(TASK_INTERRUPTIBLE);
+			spin_unlock_irq(&runtime->lock);
+			timeout = schedule_timeout(30 * HZ);
+			remove_wait_queue(&runtime->sleep, &wait);
+			if (signal_pending(current))
+				return result > 0 ? result : -ERESTARTSYS;
+			if (!runtime->avail && !timeout)
+				return result > 0 ? result : -EIO;
+			spin_lock_irq(&runtime->lock);
+		}
+		spin_unlock_irq(&runtime->lock);
+		count1 = snd_rawmidi_kernel_write1(substream, (unsigned char *)buf, count, 0);
+		if (count1 < 0)
+			return result > 0 ? result : count1;
+		result += count1;
+		buf += count1;
+		if ((size_t)count1 < count && (file->f_flags & O_NONBLOCK))
+			break;
+		count -= count1;
+	}
+	if (file->f_flags & O_SYNC) {
+		spin_lock_irq(&runtime->lock);
+		while (runtime->avail != runtime->buffer_size) {
+			wait_queue_t wait;
+			unsigned int last_avail = runtime->avail;
+			init_waitqueue_entry(&wait, current);
+			add_wait_queue(&runtime->sleep, &wait);
+			set_current_state(TASK_INTERRUPTIBLE);
+			spin_unlock_irq(&runtime->lock);
+			timeout = schedule_timeout(30 * HZ);
+			remove_wait_queue(&runtime->sleep, &wait);
+			if (signal_pending(current))
+				return result > 0 ? result : -ERESTARTSYS;
+			if (runtime->avail == last_avail && !timeout)
+				return result > 0 ? result : -EIO;
+			spin_lock_irq(&runtime->lock);
+		}
+		spin_unlock_irq(&runtime->lock);
+	}
+	return result;
+}
+
+static unsigned int snd_rawmidi_poll(struct file *file, poll_table * wait)
+{
+	snd_rawmidi_file_t *rfile;
+	snd_rawmidi_runtime_t *runtime;
+	unsigned int mask;
+
+	rfile = file->private_data;
+	if (rfile->input != NULL) {
+		runtime = rfile->input->runtime;
+		snd_rawmidi_input_trigger(rfile->input, 1);
+		poll_wait(file, &runtime->sleep, wait);
+	}
+	if (rfile->output != NULL) {
+		runtime = rfile->output->runtime;
+		poll_wait(file, &runtime->sleep, wait);
+	}
+	mask = 0;
+	if (rfile->input != NULL) {
+		if (snd_rawmidi_ready(rfile->input))
+			mask |= POLLIN | POLLRDNORM;
+	}
+	if (rfile->output != NULL) {
+		if (snd_rawmidi_ready(rfile->output))
+			mask |= POLLOUT | POLLWRNORM;
+	}
+	return mask;
+}
+
+/*
+ */
+#ifdef CONFIG_COMPAT
+#include "rawmidi_compat.c"
+#else
+#define snd_rawmidi_ioctl_compat	NULL
+#endif
+
+/*
+
+ */
+
+static void snd_rawmidi_proc_info_read(snd_info_entry_t *entry,
+				       snd_info_buffer_t * buffer)
+{
+	snd_rawmidi_t *rmidi;
+	snd_rawmidi_substream_t *substream;
+	snd_rawmidi_runtime_t *runtime;
+	struct list_head *list;
+
+	rmidi = entry->private_data;
+	snd_iprintf(buffer, "%s\n\n", rmidi->name);
+	down(&rmidi->open_mutex);
+	if (rmidi->info_flags & SNDRV_RAWMIDI_INFO_OUTPUT) {
+		list_for_each(list, &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams) {
+			substream = list_entry(list, snd_rawmidi_substream_t, list);
+			snd_iprintf(buffer,
+				    "Output %d\n"
+				    "  Tx bytes     : %lu\n",
+				    substream->number,
+				    (unsigned long) substream->bytes);
+			if (substream->opened) {
+				runtime = substream->runtime;
+				snd_iprintf(buffer,
+				    "  Mode         : %s\n"
+				    "  Buffer size  : %lu\n"
+				    "  Avail        : %lu\n",
+				    runtime->oss ? "OSS compatible" : "native",
+				    (unsigned long) runtime->buffer_size,
+				    (unsigned long) runtime->avail);
+			}
+		}
+	}
+	if (rmidi->info_flags & SNDRV_RAWMIDI_INFO_INPUT) {
+		list_for_each(list, &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams) {
+			substream = list_entry(list, snd_rawmidi_substream_t, list);
+			snd_iprintf(buffer,
+				    "Input %d\n"
+				    "  Rx bytes     : %lu\n",
+				    substream->number,
+				    (unsigned long) substream->bytes);
+			if (substream->opened) {
+				runtime = substream->runtime;
+				snd_iprintf(buffer,
+					    "  Buffer size  : %lu\n"
+					    "  Avail        : %lu\n"
+					    "  Overruns     : %lu\n",
+					    (unsigned long) runtime->buffer_size,
+					    (unsigned long) runtime->avail,
+					    (unsigned long) runtime->xruns);
+			}
+		}
+	}
+	up(&rmidi->open_mutex);
+}
+
+/*
+ *  Register functions
+ */
+
+static struct file_operations snd_rawmidi_f_ops =
+{
+	.owner =	THIS_MODULE,
+	.read =		snd_rawmidi_read,
+	.write =	snd_rawmidi_write,
+	.open =		snd_rawmidi_open,
+	.release =	snd_rawmidi_release,
+	.poll =		snd_rawmidi_poll,
+	.unlocked_ioctl =	snd_rawmidi_ioctl,
+	.compat_ioctl =	snd_rawmidi_ioctl_compat,
+};
+
+static snd_minor_t snd_rawmidi_reg =
+{
+	.comment =	"raw midi",
+	.f_ops =	&snd_rawmidi_f_ops,
+};
+
+static int snd_rawmidi_alloc_substreams(snd_rawmidi_t *rmidi,
+					snd_rawmidi_str_t *stream,
+					int direction,
+					int count)
+{
+	snd_rawmidi_substream_t *substream;
+	int idx;
+
+	INIT_LIST_HEAD(&stream->substreams);
+	for (idx = 0; idx < count; idx++) {
+		substream = kcalloc(1, sizeof(*substream), GFP_KERNEL);
+		if (substream == NULL)
+			return -ENOMEM;
+		substream->stream = direction;
+		substream->number = idx;
+		substream->rmidi = rmidi;
+		substream->pstr = stream;
+		list_add_tail(&substream->list, &stream->substreams);
+		stream->substream_count++;
+	}
+	return 0;
+}
+
+/**
+ * snd_rawmidi_new - create a rawmidi instance
+ * @card: the card instance
+ * @id: the id string
+ * @device: the device index
+ * @output_count: the number of output streams
+ * @input_count: the number of input streams
+ * @rrawmidi: the pointer to store the new rawmidi instance
+ *
+ * Creates a new rawmidi instance.
+ * Use snd_rawmidi_set_ops() to set the operators to the new instance.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_rawmidi_new(snd_card_t * card, char *id, int device,
+		    int output_count, int input_count,
+		    snd_rawmidi_t ** rrawmidi)
+{
+	snd_rawmidi_t *rmidi;
+	int err;
+	static snd_device_ops_t ops = {
+		.dev_free = snd_rawmidi_dev_free,
+		.dev_register = snd_rawmidi_dev_register,
+		.dev_disconnect = snd_rawmidi_dev_disconnect,
+		.dev_unregister = snd_rawmidi_dev_unregister
+	};
+
+	snd_assert(rrawmidi != NULL, return -EINVAL);
+	*rrawmidi = NULL;
+	snd_assert(card != NULL, return -ENXIO);
+	rmidi = kcalloc(1, sizeof(*rmidi), GFP_KERNEL);
+	if (rmidi == NULL)
+		return -ENOMEM;
+	rmidi->card = card;
+	rmidi->device = device;
+	init_MUTEX(&rmidi->open_mutex);
+	init_waitqueue_head(&rmidi->open_wait);
+	if (id != NULL)
+		strlcpy(rmidi->id, id, sizeof(rmidi->id));
+	if ((err = snd_rawmidi_alloc_substreams(rmidi, &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT], SNDRV_RAWMIDI_STREAM_INPUT, input_count)) < 0) {
+		snd_rawmidi_free(rmidi);
+		return err;
+	}
+	if ((err = snd_rawmidi_alloc_substreams(rmidi, &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT], SNDRV_RAWMIDI_STREAM_OUTPUT, output_count)) < 0) {
+		snd_rawmidi_free(rmidi);
+		return err;
+	}
+	if ((err = snd_device_new(card, SNDRV_DEV_RAWMIDI, rmidi, &ops)) < 0) {
+		snd_rawmidi_free(rmidi);
+		return err;
+	}
+	*rrawmidi = rmidi;
+	return 0;
+}
+
+static void snd_rawmidi_free_substreams(snd_rawmidi_str_t *stream)
+{
+	snd_rawmidi_substream_t *substream;
+
+	while (!list_empty(&stream->substreams)) {
+		substream = list_entry(stream->substreams.next, snd_rawmidi_substream_t, list);
+		list_del(&substream->list);
+		kfree(substream);
+	}
+}
+
+static int snd_rawmidi_free(snd_rawmidi_t *rmidi)
+{
+	snd_assert(rmidi != NULL, return -ENXIO);	
+	snd_rawmidi_free_substreams(&rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]);
+	snd_rawmidi_free_substreams(&rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]);
+	if (rmidi->private_free)
+		rmidi->private_free(rmidi);
+	kfree(rmidi);
+	return 0;
+}
+
+static int snd_rawmidi_dev_free(snd_device_t *device)
+{
+	snd_rawmidi_t *rmidi = device->device_data;
+	return snd_rawmidi_free(rmidi);
+}
+
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+static void snd_rawmidi_dev_seq_free(snd_seq_device_t *device)
+{
+	snd_rawmidi_t *rmidi = device->private_data;
+	rmidi->seq_dev = NULL;
+}
+#endif
+
+static int snd_rawmidi_dev_register(snd_device_t *device)
+{
+	int idx, err;
+	snd_info_entry_t *entry;
+	char name[16];
+	snd_rawmidi_t *rmidi = device->device_data;
+
+	if (rmidi->device >= SNDRV_RAWMIDI_DEVICES)
+		return -ENOMEM;
+	down(&register_mutex);
+	idx = (rmidi->card->number * SNDRV_RAWMIDI_DEVICES) + rmidi->device;
+	if (snd_rawmidi_devices[idx] != NULL) {
+		up(&register_mutex);
+		return -EBUSY;
+	}
+	snd_rawmidi_devices[idx] = rmidi;
+	sprintf(name, "midiC%iD%i", rmidi->card->number, rmidi->device);
+	if ((err = snd_register_device(SNDRV_DEVICE_TYPE_RAWMIDI,
+				       rmidi->card, rmidi->device,
+				       &snd_rawmidi_reg, name)) < 0) {
+		snd_printk(KERN_ERR "unable to register rawmidi device %i:%i\n", rmidi->card->number, rmidi->device);
+		snd_rawmidi_devices[idx] = NULL;
+		up(&register_mutex);
+		return err;
+	}
+	if (rmidi->ops && rmidi->ops->dev_register &&
+	    (err = rmidi->ops->dev_register(rmidi)) < 0) {
+		snd_unregister_device(SNDRV_DEVICE_TYPE_RAWMIDI, rmidi->card, rmidi->device);
+		snd_rawmidi_devices[idx] = NULL;
+		up(&register_mutex);
+		return err;
+	}
+#ifdef CONFIG_SND_OSSEMUL
+	rmidi->ossreg = 0;
+	if ((int)rmidi->device == midi_map[rmidi->card->number]) {
+		if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI,
+					    rmidi->card, 0, &snd_rawmidi_reg, name) < 0) {
+			snd_printk(KERN_ERR "unable to register OSS rawmidi device %i:%i\n", rmidi->card->number, 0);
+		} else {
+			rmidi->ossreg++;
+#ifdef SNDRV_OSS_INFO_DEV_MIDI
+			snd_oss_info_register(SNDRV_OSS_INFO_DEV_MIDI, rmidi->card->number, rmidi->name);
+#endif
+		}
+	}
+	if ((int)rmidi->device == amidi_map[rmidi->card->number]) {
+		if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI,
+					    rmidi->card, 1, &snd_rawmidi_reg, name) < 0) {
+			snd_printk(KERN_ERR "unable to register OSS rawmidi device %i:%i\n", rmidi->card->number, 1);
+		} else {
+			rmidi->ossreg++;
+		}
+	}
+#endif /* CONFIG_SND_OSSEMUL */
+	up(&register_mutex);
+	sprintf(name, "midi%d", rmidi->device);
+	entry = snd_info_create_card_entry(rmidi->card, name, rmidi->card->proc_root);
+	if (entry) {
+		entry->private_data = rmidi;
+		entry->c.text.read_size = 1024;
+		entry->c.text.read = snd_rawmidi_proc_info_read;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	rmidi->proc_entry = entry;
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+	if (!rmidi->ops || !rmidi->ops->dev_register) { /* own registration mechanism */
+		if (snd_seq_device_new(rmidi->card, rmidi->device, SNDRV_SEQ_DEV_ID_MIDISYNTH, 0, &rmidi->seq_dev) >= 0) {
+			rmidi->seq_dev->private_data = rmidi;
+			rmidi->seq_dev->private_free = snd_rawmidi_dev_seq_free;
+			sprintf(rmidi->seq_dev->name, "MIDI %d-%d", rmidi->card->number, rmidi->device);
+			snd_device_register(rmidi->card, rmidi->seq_dev);
+		}
+	}
+#endif
+	return 0;
+}
+
+static int snd_rawmidi_dev_disconnect(snd_device_t *device)
+{
+	snd_rawmidi_t *rmidi = device->device_data;
+	int idx;
+
+	down(&register_mutex);
+	idx = (rmidi->card->number * SNDRV_RAWMIDI_DEVICES) + rmidi->device;
+	snd_rawmidi_devices[idx] = NULL;
+	up(&register_mutex);
+	return 0;
+}
+
+static int snd_rawmidi_dev_unregister(snd_device_t *device)
+{
+	int idx;
+	snd_rawmidi_t *rmidi = device->device_data;
+
+	snd_assert(rmidi != NULL, return -ENXIO);
+	down(&register_mutex);
+	idx = (rmidi->card->number * SNDRV_RAWMIDI_DEVICES) + rmidi->device;
+	snd_rawmidi_devices[idx] = NULL;
+	if (rmidi->proc_entry) {
+		snd_info_unregister(rmidi->proc_entry);
+		rmidi->proc_entry = NULL;
+	}
+#ifdef CONFIG_SND_OSSEMUL
+	if (rmidi->ossreg) {
+		if ((int)rmidi->device == midi_map[rmidi->card->number]) {
+			snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, rmidi->card, 0);
+#ifdef SNDRV_OSS_INFO_DEV_MIDI
+			snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_MIDI, rmidi->card->number);
+#endif
+		}
+		if ((int)rmidi->device == amidi_map[rmidi->card->number])
+			snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, rmidi->card, 1);
+		rmidi->ossreg = 0;
+	}
+#endif /* CONFIG_SND_OSSEMUL */
+	if (rmidi->ops && rmidi->ops->dev_unregister)
+		rmidi->ops->dev_unregister(rmidi);
+	snd_unregister_device(SNDRV_DEVICE_TYPE_RAWMIDI, rmidi->card, rmidi->device);
+	up(&register_mutex);
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+	if (rmidi->seq_dev) {
+		snd_device_free(rmidi->card, rmidi->seq_dev);
+		rmidi->seq_dev = NULL;
+	}
+#endif
+	return snd_rawmidi_free(rmidi);
+}
+
+/**
+ * snd_rawmidi_set_ops - set the rawmidi operators
+ * @rmidi: the rawmidi instance
+ * @stream: the stream direction, SNDRV_RAWMIDI_STREAM_XXX
+ * @ops: the operator table
+ *
+ * Sets the rawmidi operators for the given stream direction.
+ */
+void snd_rawmidi_set_ops(snd_rawmidi_t *rmidi, int stream, snd_rawmidi_ops_t *ops)
+{
+	struct list_head *list;
+	snd_rawmidi_substream_t *substream;
+	
+	list_for_each(list, &rmidi->streams[stream].substreams) {
+		substream = list_entry(list, snd_rawmidi_substream_t, list);
+		substream->ops = ops;
+	}
+}
+
+/*
+ *  ENTRY functions
+ */
+
+static int __init alsa_rawmidi_init(void)
+{
+
+	snd_ctl_register_ioctl(snd_rawmidi_control_ioctl);
+	snd_ctl_register_ioctl_compat(snd_rawmidi_control_ioctl);
+#ifdef CONFIG_SND_OSSEMUL
+	{ int i;
+	/* check device map table */
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		if (midi_map[i] < 0 || midi_map[i] >= SNDRV_RAWMIDI_DEVICES) {
+			snd_printk(KERN_ERR "invalid midi_map[%d] = %d\n", i, midi_map[i]);
+			midi_map[i] = 0;
+		}
+		if (amidi_map[i] < 0 || amidi_map[i] >= SNDRV_RAWMIDI_DEVICES) {
+			snd_printk(KERN_ERR "invalid amidi_map[%d] = %d\n", i, amidi_map[i]);
+			amidi_map[i] = 1;
+		}
+	}
+	}
+#endif /* CONFIG_SND_OSSEMUL */
+	return 0;
+}
+
+static void __exit alsa_rawmidi_exit(void)
+{
+	snd_ctl_unregister_ioctl(snd_rawmidi_control_ioctl);
+	snd_ctl_unregister_ioctl_compat(snd_rawmidi_control_ioctl);
+}
+
+module_init(alsa_rawmidi_init)
+module_exit(alsa_rawmidi_exit)
+
+EXPORT_SYMBOL(snd_rawmidi_output_params);
+EXPORT_SYMBOL(snd_rawmidi_input_params);
+EXPORT_SYMBOL(snd_rawmidi_drop_output);
+EXPORT_SYMBOL(snd_rawmidi_drain_output);
+EXPORT_SYMBOL(snd_rawmidi_drain_input);
+EXPORT_SYMBOL(snd_rawmidi_receive);
+EXPORT_SYMBOL(snd_rawmidi_transmit_empty);
+EXPORT_SYMBOL(snd_rawmidi_transmit_peek);
+EXPORT_SYMBOL(snd_rawmidi_transmit_ack);
+EXPORT_SYMBOL(snd_rawmidi_transmit);
+EXPORT_SYMBOL(snd_rawmidi_new);
+EXPORT_SYMBOL(snd_rawmidi_set_ops);
+EXPORT_SYMBOL(snd_rawmidi_info);
+EXPORT_SYMBOL(snd_rawmidi_info_select);
+EXPORT_SYMBOL(snd_rawmidi_kernel_open);
+EXPORT_SYMBOL(snd_rawmidi_kernel_release);
+EXPORT_SYMBOL(snd_rawmidi_kernel_read);
+EXPORT_SYMBOL(snd_rawmidi_kernel_write);
diff --git a/sound/core/rawmidi_compat.c b/sound/core/rawmidi_compat.c
new file mode 100644
index 000000000000..d97631c3f3ad
--- /dev/null
+++ b/sound/core/rawmidi_compat.c
@@ -0,0 +1,120 @@
+/*
+ *   32bit -> 64bit ioctl wrapper for raw MIDI API
+ *   Copyright (c) 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
+ *
+ */
+
+/* This file included from rawmidi.c */
+
+#include <linux/compat.h>
+
+struct sndrv_rawmidi_params32 {
+	s32 stream;
+	u32 buffer_size;
+	u32 avail_min;
+	unsigned int no_active_sensing; /* avoid bit-field */
+	unsigned char reserved[16];
+} __attribute__((packed));
+
+static int snd_rawmidi_ioctl_params_compat(snd_rawmidi_file_t *rfile,
+					   struct sndrv_rawmidi_params32 __user *src)
+{
+	snd_rawmidi_params_t params;
+	unsigned int val;
+
+	if (rfile->output == NULL)
+		return -EINVAL;
+	if (get_user(params.stream, &src->stream) ||
+	    get_user(params.buffer_size, &src->buffer_size) ||
+	    get_user(params.avail_min, &src->avail_min) ||
+	    get_user(val, &src->no_active_sensing))
+		return -EFAULT;
+	params.no_active_sensing = val;
+	switch (params.stream) {
+	case SNDRV_RAWMIDI_STREAM_OUTPUT:
+		return snd_rawmidi_output_params(rfile->output, &params);
+	case SNDRV_RAWMIDI_STREAM_INPUT:
+		return snd_rawmidi_input_params(rfile->input, &params);
+	}
+	return -EINVAL;
+}
+
+struct sndrv_rawmidi_status32 {
+	s32 stream;
+	struct compat_timespec tstamp;
+	u32 avail;
+	u32 xruns;
+	unsigned char reserved[16];
+} __attribute__((packed));
+
+static int snd_rawmidi_ioctl_status_compat(snd_rawmidi_file_t *rfile,
+					   struct sndrv_rawmidi_status32 __user *src)
+{
+	int err;
+	snd_rawmidi_status_t status;
+
+	if (rfile->output == NULL)
+		return -EINVAL;
+	if (get_user(status.stream, &src->stream))
+		return -EFAULT;
+
+	switch (status.stream) {
+	case SNDRV_RAWMIDI_STREAM_OUTPUT:
+		err = snd_rawmidi_output_status(rfile->output, &status);
+		break;
+	case SNDRV_RAWMIDI_STREAM_INPUT:
+		err = snd_rawmidi_input_status(rfile->input, &status);
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (err < 0)
+		return err;
+
+	if (put_user(status.tstamp.tv_sec, &src->tstamp.tv_sec) ||
+	    put_user(status.tstamp.tv_nsec, &src->tstamp.tv_nsec) ||
+	    put_user(status.avail, &src->avail) ||
+	    put_user(status.xruns, &src->xruns))
+		return -EFAULT;
+
+	return 0;
+}
+
+enum {
+	SNDRV_RAWMIDI_IOCTL_PARAMS32 = _IOWR('W', 0x10, struct sndrv_rawmidi_params32),
+	SNDRV_RAWMIDI_IOCTL_STATUS32 = _IOWR('W', 0x20, struct sndrv_rawmidi_status32),
+};
+
+static long snd_rawmidi_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	snd_rawmidi_file_t *rfile;
+	void __user *argp = compat_ptr(arg);
+
+	rfile = file->private_data;
+	switch (cmd) {
+	case SNDRV_RAWMIDI_IOCTL_PVERSION:
+	case SNDRV_RAWMIDI_IOCTL_INFO:
+	case SNDRV_RAWMIDI_IOCTL_DROP:
+	case SNDRV_RAWMIDI_IOCTL_DRAIN:
+		return snd_rawmidi_ioctl(file, cmd, (unsigned long)argp);
+	case SNDRV_RAWMIDI_IOCTL_PARAMS32:
+		return snd_rawmidi_ioctl_params_compat(rfile, argp);
+	case SNDRV_RAWMIDI_IOCTL_STATUS32:
+		return snd_rawmidi_ioctl_status_compat(rfile, argp);
+	}
+	return -ENOIOCTLCMD;
+}
diff --git a/sound/core/rtctimer.c b/sound/core/rtctimer.c
new file mode 100644
index 000000000000..bd5d584d284d
--- /dev/null
+++ b/sound/core/rtctimer.c
@@ -0,0 +1,188 @@
+/*
+ *  RTC based high-frequency timer
+ *
+ *  Copyright (C) 2000 Takashi Iwai
+ *	based on rtctimer.c by Steve Ratcliffe
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/threads.h>
+#include <linux/interrupt.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/timer.h>
+#include <sound/info.h>
+
+#if defined(CONFIG_RTC) || defined(CONFIG_RTC_MODULE)
+
+#include <linux/mc146818rtc.h>
+
+#define RTC_FREQ	1024		/* default frequency */
+#define NANO_SEC	1000000000L	/* 10^9 in sec */
+
+/*
+ * prototypes
+ */
+static int rtctimer_open(snd_timer_t *t);
+static int rtctimer_close(snd_timer_t *t);
+static int rtctimer_start(snd_timer_t *t);
+static int rtctimer_stop(snd_timer_t *t);
+
+
+/*
+ * The hardware dependent description for this timer.
+ */
+static struct _snd_timer_hardware rtc_hw = {
+	.flags =	SNDRV_TIMER_HW_FIRST|SNDRV_TIMER_HW_AUTO,
+	.ticks =	100000000L,		/* FIXME: XXX */
+	.open =		rtctimer_open,
+	.close =	rtctimer_close,
+	.start =	rtctimer_start,
+	.stop =		rtctimer_stop,
+};
+
+static int rtctimer_freq = RTC_FREQ;		/* frequency */
+static snd_timer_t *rtctimer;
+static atomic_t rtc_inc = ATOMIC_INIT(0);
+static rtc_task_t rtc_task;
+
+
+static int
+rtctimer_open(snd_timer_t *t)
+{
+	int err;
+
+	err = rtc_register(&rtc_task);
+	if (err < 0)
+		return err;
+	t->private_data = &rtc_task;
+	return 0;
+}
+
+static int
+rtctimer_close(snd_timer_t *t)
+{
+	rtc_task_t *rtc = t->private_data;
+	if (rtc) {
+		rtc_unregister(rtc);
+		t->private_data = NULL;
+	}
+	return 0;
+}
+
+static int
+rtctimer_start(snd_timer_t *timer)
+{
+	rtc_task_t *rtc = timer->private_data;
+	snd_assert(rtc != NULL, return -EINVAL);
+	rtc_control(rtc, RTC_IRQP_SET, rtctimer_freq);
+	rtc_control(rtc, RTC_PIE_ON, 0);
+	atomic_set(&rtc_inc, 0);
+	return 0;
+}
+
+static int
+rtctimer_stop(snd_timer_t *timer)
+{
+	rtc_task_t *rtc = timer->private_data;
+	snd_assert(rtc != NULL, return -EINVAL);
+	rtc_control(rtc, RTC_PIE_OFF, 0);
+	return 0;
+}
+
+/*
+ * interrupt
+ */
+static void rtctimer_interrupt(void *private_data)
+{
+	int ticks;
+
+	atomic_inc(&rtc_inc);
+	ticks = atomic_read(&rtc_inc);
+	snd_timer_interrupt((snd_timer_t*)private_data, ticks);
+	atomic_sub(ticks, &rtc_inc);
+}
+
+
+/*
+ *  ENTRY functions
+ */
+static int __init rtctimer_init(void)
+{
+	int order, err;
+	snd_timer_t *timer;
+
+	if (rtctimer_freq < 2 || rtctimer_freq > 8192) {
+		snd_printk(KERN_ERR "rtctimer: invalid frequency %d\n", rtctimer_freq);
+		return -EINVAL;
+	}
+	for (order = 1; rtctimer_freq > order; order <<= 1)
+		;
+	if (rtctimer_freq != order) {
+		snd_printk(KERN_ERR "rtctimer: invalid frequency %d\n", rtctimer_freq);
+		return -EINVAL;
+	}
+
+	/* Create a new timer and set up the fields */
+	err = snd_timer_global_new("rtc", SNDRV_TIMER_GLOBAL_RTC, &timer);
+	if (err < 0)
+		return err;
+
+	strcpy(timer->name, "RTC timer");
+	timer->hw = rtc_hw;
+	timer->hw.resolution = NANO_SEC / rtctimer_freq;
+
+	/* set up RTC callback */
+	rtc_task.func = rtctimer_interrupt;
+	rtc_task.private_data = timer;
+
+	err = snd_timer_global_register(timer);
+	if (err < 0) {
+		snd_timer_global_free(timer);
+		return err;
+	}
+	rtctimer = timer; /* remember this */
+
+	return 0;
+}
+
+static void __exit rtctimer_exit(void)
+{
+	if (rtctimer) {
+		snd_timer_global_unregister(rtctimer);
+		rtctimer = NULL;
+	}
+}
+
+
+/*
+ * exported stuff
+ */
+module_init(rtctimer_init)
+module_exit(rtctimer_exit)
+
+module_param(rtctimer_freq, int, 0444);
+MODULE_PARM_DESC(rtctimer_freq, "timer frequency in Hz");
+
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS("snd-timer-" __stringify(SNDRV_TIMER_GLOBAL_RTC));
+
+#endif /* CONFIG_RTC || CONFIG_RTC_MODULE */
diff --git a/sound/core/seq/Makefile b/sound/core/seq/Makefile
new file mode 100644
index 000000000000..64cb50d7b589
--- /dev/null
+++ b/sound/core/seq/Makefile
@@ -0,0 +1,44 @@
+#
+# Makefile for ALSA
+# Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+#
+
+obj-$(CONFIG_SND) += instr/
+ifeq ($(CONFIG_SND_SEQUENCER_OSS),y)
+  obj-$(CONFIG_SND_SEQUENCER) += oss/
+endif
+
+snd-seq-device-objs := seq_device.o
+snd-seq-objs := seq.o seq_lock.o seq_clientmgr.o seq_memory.o seq_queue.o \
+                seq_fifo.o seq_prioq.o seq_timer.o \
+                seq_system.o seq_ports.o seq_info.o
+snd-seq-midi-objs := seq_midi.o
+snd-seq-midi-emul-objs := seq_midi_emul.o
+snd-seq-midi-event-objs := seq_midi_event.o
+snd-seq-instr-objs := seq_instr.o
+snd-seq-dummy-objs := seq_dummy.o
+snd-seq-virmidi-objs := seq_virmidi.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_SEQUENCER) += snd-seq.o snd-seq-device.o
+ifeq ($(CONFIG_SND_SEQUENCER_OSS),y)
+obj-$(CONFIG_SND_SEQUENCER) += snd-seq-midi-event.o
+endif
+obj-$(CONFIG_SND_SEQ_DUMMY) += snd-seq-dummy.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_VIRMIDI) += snd-seq-virmidi.o snd-seq-midi-event.o
+obj-$(call sequencer,$(CONFIG_SND_RAWMIDI)) += snd-seq-midi.o snd-seq-midi-event.o
+obj-$(call sequencer,$(CONFIG_SND_OPL3_LIB)) += snd-seq-midi-event.o snd-seq-midi-emul.o snd-seq-instr.o
+obj-$(call sequencer,$(CONFIG_SND_OPL4_LIB)) += snd-seq-midi-event.o snd-seq-midi-emul.o snd-seq-instr.o
+obj-$(call sequencer,$(CONFIG_SND_GUS_SYNTH)) += snd-seq-instr.o
+obj-$(call sequencer,$(CONFIG_SND_SBAWE)) += snd-seq-midi-emul.o snd-seq-virmidi.o
+obj-$(call sequencer,$(CONFIG_SND_EMU10K1)) += snd-seq-midi-emul.o snd-seq-virmidi.o
+obj-$(call sequencer,$(CONFIG_SND_TRIDENT)) += snd-seq-midi-emul.o snd-seq-instr.o
diff --git a/sound/core/seq/instr/Makefile b/sound/core/seq/instr/Makefile
new file mode 100644
index 000000000000..69138f30a293
--- /dev/null
+++ b/sound/core/seq/instr/Makefile
@@ -0,0 +1,23 @@
+#
+# Makefile for ALSA
+# Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-ainstr-fm-objs := ainstr_fm.o
+snd-ainstr-simple-objs := ainstr_simple.o
+snd-ainstr-gf1-objs := ainstr_gf1.o
+snd-ainstr-iw-objs := ainstr_iw.o
+
+#
+# this function returns:
+#   "m" - CONFIG_SND_SEQUENCER is m
+#   <empty string> - CONFIG_SND_SEQUENCER is undefined
+#   otherwise parameter #1 value
+#
+sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1)))
+
+# Toplevel Module Dependency
+obj-$(call sequencer,$(CONFIG_SND_OPL3_LIB)) += snd-ainstr-fm.o
+obj-$(call sequencer,$(CONFIG_SND_OPL4_LIB)) += snd-ainstr-fm.o
+obj-$(call sequencer,$(CONFIG_SND_GUS_SYNTH)) += snd-ainstr-gf1.o snd-ainstr-simple.o snd-ainstr-iw.o
+obj-$(call sequencer,$(CONFIG_SND_TRIDENT)) += snd-ainstr-simple.o
diff --git a/sound/core/seq/instr/ainstr_fm.c b/sound/core/seq/instr/ainstr_fm.c
new file mode 100644
index 000000000000..5c671e69884f
--- /dev/null
+++ b/sound/core/seq/instr/ainstr_fm.c
@@ -0,0 +1,156 @@
+/*
+ *  FM (OPL2/3) Instrument routines
+ *  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/driver.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <sound/core.h>
+#include <sound/ainstr_fm.h>
+#include <sound/initval.h>
+#include <asm/uaccess.h>
+
+MODULE_AUTHOR("Uros Bizjak <uros@kss-loka.si>");
+MODULE_DESCRIPTION("Advanced Linux Sound Architecture FM Instrument support.");
+MODULE_LICENSE("GPL");
+
+static int snd_seq_fm_put(void *private_data, snd_seq_kinstr_t *instr,
+			  char __user *instr_data, long len, int atomic, int cmd)
+{
+	fm_instrument_t *ip;
+	fm_xinstrument_t ix;
+	int idx;
+
+	if (cmd != SNDRV_SEQ_INSTR_PUT_CMD_CREATE)
+		return -EINVAL;
+	/* copy instrument data */
+	if (len < (long)sizeof(ix))
+		return -EINVAL;
+	if (copy_from_user(&ix, instr_data, sizeof(ix)))
+		return -EFAULT;
+	if (ix.stype != FM_STRU_INSTR)
+		return -EINVAL;
+	ip = (fm_instrument_t *)KINSTR_DATA(instr);
+	ip->share_id[0] = le32_to_cpu(ix.share_id[0]);
+	ip->share_id[1] = le32_to_cpu(ix.share_id[1]);
+	ip->share_id[2] = le32_to_cpu(ix.share_id[2]);
+	ip->share_id[3] = le32_to_cpu(ix.share_id[3]);
+	ip->type = ix.type;
+	for (idx = 0; idx < 4; idx++) {
+		ip->op[idx].am_vib = ix.op[idx].am_vib;
+		ip->op[idx].ksl_level = ix.op[idx].ksl_level;
+		ip->op[idx].attack_decay = ix.op[idx].attack_decay;
+		ip->op[idx].sustain_release = ix.op[idx].sustain_release;
+		ip->op[idx].wave_select = ix.op[idx].wave_select;
+	}
+	for (idx = 0; idx < 2; idx++) {
+		ip->feedback_connection[idx] = ix.feedback_connection[idx];
+	}
+	ip->echo_delay = ix.echo_delay;
+	ip->echo_atten = ix.echo_atten;
+	ip->chorus_spread = ix.chorus_spread;
+	ip->trnsps = ix.trnsps;
+	ip->fix_dur = ix.fix_dur;
+	ip->modes = ix.modes;
+	ip->fix_key = ix.fix_key;
+	return 0;
+}
+
+static int snd_seq_fm_get(void *private_data, snd_seq_kinstr_t *instr,
+			  char __user *instr_data, long len, int atomic,
+			  int cmd)
+{
+	fm_instrument_t *ip;
+	fm_xinstrument_t ix;
+	int idx;
+	
+	if (cmd != SNDRV_SEQ_INSTR_GET_CMD_FULL)
+		return -EINVAL;
+	if (len < (long)sizeof(ix))
+		return -ENOMEM;
+	memset(&ix, 0, sizeof(ix));
+	ip = (fm_instrument_t *)KINSTR_DATA(instr);
+	ix.stype = FM_STRU_INSTR;
+	ix.share_id[0] = cpu_to_le32(ip->share_id[0]);
+	ix.share_id[1] = cpu_to_le32(ip->share_id[1]);
+	ix.share_id[2] = cpu_to_le32(ip->share_id[2]);
+	ix.share_id[3] = cpu_to_le32(ip->share_id[3]);
+	ix.type = ip->type;
+	for (idx = 0; idx < 4; idx++) {
+		ix.op[idx].am_vib = ip->op[idx].am_vib;
+		ix.op[idx].ksl_level = ip->op[idx].ksl_level;
+		ix.op[idx].attack_decay = ip->op[idx].attack_decay;
+		ix.op[idx].sustain_release = ip->op[idx].sustain_release;
+		ix.op[idx].wave_select = ip->op[idx].wave_select;
+	}
+	for (idx = 0; idx < 2; idx++) {
+		ix.feedback_connection[idx] = ip->feedback_connection[idx];
+	}
+	if (copy_to_user(instr_data, &ix, sizeof(ix)))
+		return -EFAULT;
+	ix.echo_delay = ip->echo_delay;
+	ix.echo_atten = ip->echo_atten;
+	ix.chorus_spread = ip->chorus_spread;
+	ix.trnsps = ip->trnsps;
+	ix.fix_dur = ip->fix_dur;
+	ix.modes = ip->modes;
+	ix.fix_key = ip->fix_key;
+	return 0;
+}
+
+static int snd_seq_fm_get_size(void *private_data, snd_seq_kinstr_t *instr,
+			       long *size)
+{
+	*size = sizeof(fm_xinstrument_t);
+	return 0;
+}
+
+int snd_seq_fm_init(snd_seq_kinstr_ops_t *ops,
+		    snd_seq_kinstr_ops_t *next)
+{
+	memset(ops, 0, sizeof(*ops));
+	// ops->private_data = private_data;
+	ops->add_len = sizeof(fm_instrument_t);
+	ops->instr_type = SNDRV_SEQ_INSTR_ID_OPL2_3;
+	ops->put = snd_seq_fm_put;
+	ops->get = snd_seq_fm_get;
+	ops->get_size = snd_seq_fm_get_size;
+	// ops->remove = snd_seq_fm_remove;
+	// ops->notify = snd_seq_fm_notify;
+	ops->next = next;
+	return 0;
+}
+
+/*
+ *  Init part
+ */
+
+static int __init alsa_ainstr_fm_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_ainstr_fm_exit(void)
+{
+}
+
+module_init(alsa_ainstr_fm_init)
+module_exit(alsa_ainstr_fm_exit)
+
+EXPORT_SYMBOL(snd_seq_fm_init);
diff --git a/sound/core/seq/instr/ainstr_gf1.c b/sound/core/seq/instr/ainstr_gf1.c
new file mode 100644
index 000000000000..0779c41ca037
--- /dev/null
+++ b/sound/core/seq/instr/ainstr_gf1.c
@@ -0,0 +1,358 @@
+/*
+ *   GF1 (GUS) Patch - Instrument routines
+ *   Copyright (c) 1999 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/sched.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/ainstr_gf1.h>
+#include <sound/initval.h>
+#include <asm/uaccess.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Advanced Linux Sound Architecture GF1 (GUS) Patch support.");
+MODULE_LICENSE("GPL");
+
+static unsigned int snd_seq_gf1_size(unsigned int size, unsigned int format)
+{
+	unsigned int result = size;
+	
+	if (format & GF1_WAVE_16BIT)
+		result <<= 1;
+	if (format & GF1_WAVE_STEREO)
+		result <<= 1;
+	return format;
+}
+
+static int snd_seq_gf1_copy_wave_from_stream(snd_gf1_ops_t *ops,
+					     gf1_instrument_t *ip,
+					     char __user **data,
+					     long *len,
+					     int atomic)
+{
+	gf1_wave_t *wp, *prev;
+	gf1_xwave_t xp;
+	int err, gfp_mask;
+	unsigned int real_size;
+	
+	gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL;
+	if (*len < (long)sizeof(xp))
+		return -EINVAL;
+	if (copy_from_user(&xp, *data, sizeof(xp)))
+		return -EFAULT;
+	*data += sizeof(xp);
+	*len -= sizeof(xp);
+	wp = kcalloc(1, sizeof(*wp), gfp_mask);
+	if (wp == NULL)
+		return -ENOMEM;
+	wp->share_id[0] = le32_to_cpu(xp.share_id[0]);
+	wp->share_id[1] = le32_to_cpu(xp.share_id[1]);
+	wp->share_id[2] = le32_to_cpu(xp.share_id[2]);
+	wp->share_id[3] = le32_to_cpu(xp.share_id[3]);
+	wp->format = le32_to_cpu(xp.format);
+	wp->size = le32_to_cpu(xp.size);
+	wp->start = le32_to_cpu(xp.start);
+	wp->loop_start = le32_to_cpu(xp.loop_start);
+	wp->loop_end = le32_to_cpu(xp.loop_end);
+	wp->loop_repeat = le16_to_cpu(xp.loop_repeat);
+	wp->flags = xp.flags;
+	wp->sample_rate = le32_to_cpu(xp.sample_rate);
+	wp->low_frequency = le32_to_cpu(xp.low_frequency);
+	wp->high_frequency = le32_to_cpu(xp.high_frequency);
+	wp->root_frequency = le32_to_cpu(xp.root_frequency);
+	wp->tune = le16_to_cpu(xp.tune);
+	wp->balance = xp.balance;
+	memcpy(wp->envelope_rate, xp.envelope_rate, 6);
+	memcpy(wp->envelope_offset, xp.envelope_offset, 6);
+	wp->tremolo_sweep = xp.tremolo_sweep;
+	wp->tremolo_rate = xp.tremolo_rate;
+	wp->tremolo_depth = xp.tremolo_depth;
+	wp->vibrato_sweep = xp.vibrato_sweep;
+	wp->vibrato_rate = xp.vibrato_rate;
+	wp->vibrato_depth = xp.vibrato_depth;
+	wp->scale_frequency = le16_to_cpu(xp.scale_frequency);
+	wp->scale_factor = le16_to_cpu(xp.scale_factor);
+	real_size = snd_seq_gf1_size(wp->size, wp->format);
+	if ((long)real_size > *len) {
+		kfree(wp);
+		return -ENOMEM;
+	}
+	if (ops->put_sample) {
+		err = ops->put_sample(ops->private_data, wp,
+				      *data, real_size, atomic);
+		if (err < 0) {
+			kfree(wp);
+			return err;
+		}
+	}
+	*data += real_size;
+	*len -= real_size;
+	prev = ip->wave;
+	if (prev) {
+		while (prev->next) prev = prev->next;
+		prev->next = wp;
+	} else {
+		ip->wave = wp;
+	}
+	return 0;
+}
+
+static void snd_seq_gf1_wave_free(snd_gf1_ops_t *ops,
+				  gf1_wave_t *wave,
+				  int atomic)
+{
+	if (ops->remove_sample)
+		ops->remove_sample(ops->private_data, wave, atomic);
+	kfree(wave);
+}
+
+static void snd_seq_gf1_instr_free(snd_gf1_ops_t *ops,
+				   gf1_instrument_t *ip,
+				   int atomic)
+{
+	gf1_wave_t *wave;
+	
+	while ((wave = ip->wave) != NULL) {
+		ip->wave = wave->next;
+		snd_seq_gf1_wave_free(ops, wave, atomic);
+	}
+}
+
+static int snd_seq_gf1_put(void *private_data, snd_seq_kinstr_t *instr,
+			   char __user *instr_data, long len, int atomic,
+			   int cmd)
+{
+	snd_gf1_ops_t *ops = (snd_gf1_ops_t *)private_data;
+	gf1_instrument_t *ip;
+	gf1_xinstrument_t ix;
+	int err, gfp_mask;
+
+	if (cmd != SNDRV_SEQ_INSTR_PUT_CMD_CREATE)
+		return -EINVAL;
+	gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL;
+	/* copy instrument data */
+	if (len < (long)sizeof(ix))
+		return -EINVAL;
+	if (copy_from_user(&ix, instr_data, sizeof(ix)))
+		return -EFAULT;
+	if (ix.stype != GF1_STRU_INSTR)
+		return -EINVAL;
+	instr_data += sizeof(ix);
+	len -= sizeof(ix);
+	ip = (gf1_instrument_t *)KINSTR_DATA(instr);
+	ip->exclusion = le16_to_cpu(ix.exclusion);
+	ip->exclusion_group = le16_to_cpu(ix.exclusion_group);
+	ip->effect1 = ix.effect1;
+	ip->effect1_depth = ix.effect1_depth;
+	ip->effect2 = ix.effect2;
+	ip->effect2_depth = ix.effect2_depth;
+	/* copy layers */
+	while (len > (long)sizeof(__u32)) {
+		__u32 stype;
+
+		if (copy_from_user(&stype, instr_data, sizeof(stype)))
+			return -EFAULT;
+		if (stype != GF1_STRU_WAVE) {
+			snd_seq_gf1_instr_free(ops, ip, atomic);
+			return -EINVAL;
+		}
+		err = snd_seq_gf1_copy_wave_from_stream(ops,
+							ip,
+							&instr_data,
+							&len,
+							atomic);
+		if (err < 0) {
+			snd_seq_gf1_instr_free(ops, ip, atomic);
+			return err;
+		}
+	}
+	return 0;
+}
+
+static int snd_seq_gf1_copy_wave_to_stream(snd_gf1_ops_t *ops,
+					   gf1_instrument_t *ip,
+					   char __user **data,
+					   long *len,
+					   int atomic)
+{
+	gf1_wave_t *wp;
+	gf1_xwave_t xp;
+	int err;
+	unsigned int real_size;
+	
+	for (wp = ip->wave; wp; wp = wp->next) {
+		if (*len < (long)sizeof(xp))
+			return -ENOMEM;
+		memset(&xp, 0, sizeof(xp));
+		xp.stype = GF1_STRU_WAVE;
+		xp.share_id[0] = cpu_to_le32(wp->share_id[0]);
+		xp.share_id[1] = cpu_to_le32(wp->share_id[1]);
+		xp.share_id[2] = cpu_to_le32(wp->share_id[2]);
+		xp.share_id[3] = cpu_to_le32(wp->share_id[3]);
+		xp.format = cpu_to_le32(wp->format);
+		xp.size = cpu_to_le32(wp->size);
+		xp.start = cpu_to_le32(wp->start);
+		xp.loop_start = cpu_to_le32(wp->loop_start);
+		xp.loop_end = cpu_to_le32(wp->loop_end);
+		xp.loop_repeat = cpu_to_le32(wp->loop_repeat);
+		xp.flags = wp->flags;
+		xp.sample_rate = cpu_to_le32(wp->sample_rate);
+		xp.low_frequency = cpu_to_le32(wp->low_frequency);
+		xp.high_frequency = cpu_to_le32(wp->high_frequency);
+		xp.root_frequency = cpu_to_le32(wp->root_frequency);
+		xp.tune = cpu_to_le16(wp->tune);
+		xp.balance = wp->balance;
+		memcpy(xp.envelope_rate, wp->envelope_rate, 6);
+		memcpy(xp.envelope_offset, wp->envelope_offset, 6);
+		xp.tremolo_sweep = wp->tremolo_sweep;
+		xp.tremolo_rate = wp->tremolo_rate;
+		xp.tremolo_depth = wp->tremolo_depth;
+		xp.vibrato_sweep = wp->vibrato_sweep;
+		xp.vibrato_rate = wp->vibrato_rate;
+		xp.vibrato_depth = wp->vibrato_depth;
+		xp.scale_frequency = cpu_to_le16(wp->scale_frequency);
+		xp.scale_factor = cpu_to_le16(wp->scale_factor);
+		if (copy_to_user(*data, &xp, sizeof(xp)))
+			return -EFAULT;
+		*data += sizeof(xp);
+		*len -= sizeof(xp);
+		real_size = snd_seq_gf1_size(wp->size, wp->format);
+		if (*len < (long)real_size)
+			return -ENOMEM;
+		if (ops->get_sample) {
+			err = ops->get_sample(ops->private_data, wp,
+					      *data, real_size, atomic);
+			if (err < 0)
+				return err;
+		}
+		*data += wp->size;
+		*len -= wp->size;
+	}
+	return 0;
+}
+
+static int snd_seq_gf1_get(void *private_data, snd_seq_kinstr_t *instr,
+			   char __user *instr_data, long len, int atomic,
+			   int cmd)
+{
+	snd_gf1_ops_t *ops = (snd_gf1_ops_t *)private_data;
+	gf1_instrument_t *ip;
+	gf1_xinstrument_t ix;
+	
+	if (cmd != SNDRV_SEQ_INSTR_GET_CMD_FULL)
+		return -EINVAL;
+	if (len < (long)sizeof(ix))
+		return -ENOMEM;
+	memset(&ix, 0, sizeof(ix));
+	ip = (gf1_instrument_t *)KINSTR_DATA(instr);
+	ix.stype = GF1_STRU_INSTR;
+	ix.exclusion = cpu_to_le16(ip->exclusion);
+	ix.exclusion_group = cpu_to_le16(ip->exclusion_group);
+	ix.effect1 = cpu_to_le16(ip->effect1);
+	ix.effect1_depth = cpu_to_le16(ip->effect1_depth);
+	ix.effect2 = ip->effect2;
+	ix.effect2_depth = ip->effect2_depth;
+	if (copy_to_user(instr_data, &ix, sizeof(ix)))
+		return -EFAULT;
+	instr_data += sizeof(ix);
+	len -= sizeof(ix);
+	return snd_seq_gf1_copy_wave_to_stream(ops,
+					       ip,
+					       &instr_data,
+					       &len,
+					       atomic);
+}
+
+static int snd_seq_gf1_get_size(void *private_data, snd_seq_kinstr_t *instr,
+				long *size)
+{
+	long result;
+	gf1_instrument_t *ip;
+	gf1_wave_t *wp;
+
+	*size = 0;
+	ip = (gf1_instrument_t *)KINSTR_DATA(instr);
+	result = sizeof(gf1_xinstrument_t);
+	for (wp = ip->wave; wp; wp = wp->next) {
+		result += sizeof(gf1_xwave_t);
+		result += wp->size;
+	}
+	*size = result;
+	return 0;
+}
+
+static int snd_seq_gf1_remove(void *private_data,
+			      snd_seq_kinstr_t *instr,
+                              int atomic)
+{
+	snd_gf1_ops_t *ops = (snd_gf1_ops_t *)private_data;
+	gf1_instrument_t *ip;
+
+	ip = (gf1_instrument_t *)KINSTR_DATA(instr);
+	snd_seq_gf1_instr_free(ops, ip, atomic);
+	return 0;
+}
+
+static void snd_seq_gf1_notify(void *private_data,
+			       snd_seq_kinstr_t *instr,
+			       int what)
+{
+	snd_gf1_ops_t *ops = (snd_gf1_ops_t *)private_data;
+
+	if (ops->notify)
+		ops->notify(ops->private_data, instr, what);
+}
+
+int snd_seq_gf1_init(snd_gf1_ops_t *ops,
+		     void *private_data,
+		     snd_seq_kinstr_ops_t *next)
+{
+	memset(ops, 0, sizeof(*ops));
+	ops->private_data = private_data;
+	ops->kops.private_data = ops;
+	ops->kops.add_len = sizeof(gf1_instrument_t);
+	ops->kops.instr_type = SNDRV_SEQ_INSTR_ID_GUS_PATCH;
+	ops->kops.put = snd_seq_gf1_put;
+	ops->kops.get = snd_seq_gf1_get;
+	ops->kops.get_size = snd_seq_gf1_get_size;
+	ops->kops.remove = snd_seq_gf1_remove;
+	ops->kops.notify = snd_seq_gf1_notify;
+	ops->kops.next = next;
+	return 0;
+}
+
+/*
+ *  Init part
+ */
+
+static int __init alsa_ainstr_gf1_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_ainstr_gf1_exit(void)
+{
+}
+
+module_init(alsa_ainstr_gf1_init)
+module_exit(alsa_ainstr_gf1_exit)
+
+EXPORT_SYMBOL(snd_seq_gf1_init);
diff --git a/sound/core/seq/instr/ainstr_iw.c b/sound/core/seq/instr/ainstr_iw.c
new file mode 100644
index 000000000000..39ff72b2aab3
--- /dev/null
+++ b/sound/core/seq/instr/ainstr_iw.c
@@ -0,0 +1,622 @@
+/*
+ *   IWFFFF - AMD InterWave (tm) - Instrument routines
+ *   Copyright (c) 1999 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/sched.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/ainstr_iw.h>
+#include <sound/initval.h>
+#include <asm/uaccess.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Advanced Linux Sound Architecture IWFFFF support.");
+MODULE_LICENSE("GPL");
+
+static unsigned int snd_seq_iwffff_size(unsigned int size, unsigned int format)
+{
+	unsigned int result = size;
+	
+	if (format & IWFFFF_WAVE_16BIT)
+		result <<= 1;
+	if (format & IWFFFF_WAVE_STEREO)
+		result <<= 1;
+	return result;
+}
+
+static void snd_seq_iwffff_copy_lfo_from_stream(iwffff_lfo_t *fp,
+						iwffff_xlfo_t *fx)
+{
+	fp->freq = le16_to_cpu(fx->freq);
+	fp->depth = le16_to_cpu(fx->depth);
+	fp->sweep = le16_to_cpu(fx->sweep);
+	fp->shape = fx->shape;
+	fp->delay = fx->delay;
+}
+
+static int snd_seq_iwffff_copy_env_from_stream(__u32 req_stype,
+					       iwffff_layer_t *lp,
+					       iwffff_env_t *ep,
+					       iwffff_xenv_t *ex,
+					       char __user **data,
+					       long *len,
+					       int gfp_mask)
+{
+	__u32 stype;
+	iwffff_env_record_t *rp, *rp_last;
+	iwffff_xenv_record_t rx;
+	iwffff_env_point_t *pp;
+	iwffff_xenv_point_t px;
+	int points_size, idx;
+
+	ep->flags = ex->flags;
+	ep->mode = ex->mode;
+	ep->index = ex->index;
+	rp_last = NULL;
+	while (1) {
+		if (*len < (long)sizeof(__u32))
+			return -EINVAL;
+		if (copy_from_user(&stype, *data, sizeof(stype)))
+			return -EFAULT;
+		if (stype == IWFFFF_STRU_WAVE)
+			return 0;
+		if (req_stype != stype) {
+			if (stype == IWFFFF_STRU_ENV_RECP ||
+			    stype == IWFFFF_STRU_ENV_RECV)
+				return 0;
+		}
+		if (*len < (long)sizeof(rx))
+			return -EINVAL;
+		if (copy_from_user(&rx, *data, sizeof(rx)))
+			return -EFAULT;
+		*data += sizeof(rx);
+		*len -= sizeof(rx);
+		points_size = (le16_to_cpu(rx.nattack) + le16_to_cpu(rx.nrelease)) * 2 * sizeof(__u16);
+		if (points_size > *len)
+			return -EINVAL;
+		rp = kcalloc(1, sizeof(*rp) + points_size, gfp_mask);
+		if (rp == NULL)
+			return -ENOMEM;
+		rp->nattack = le16_to_cpu(rx.nattack);
+		rp->nrelease = le16_to_cpu(rx.nrelease);
+		rp->sustain_offset = le16_to_cpu(rx.sustain_offset);
+		rp->sustain_rate = le16_to_cpu(rx.sustain_rate);
+		rp->release_rate = le16_to_cpu(rx.release_rate);
+		rp->hirange = rx.hirange;
+		pp = (iwffff_env_point_t *)(rp + 1);
+		for (idx = 0; idx < rp->nattack + rp->nrelease; idx++) {
+			if (copy_from_user(&px, *data, sizeof(px)))
+				return -EFAULT;
+			*data += sizeof(px);
+			*len -= sizeof(px);
+			pp->offset = le16_to_cpu(px.offset);
+			pp->rate = le16_to_cpu(px.rate);
+		}
+		if (ep->record == NULL) {
+			ep->record = rp;
+		} else {
+			rp_last = rp;
+		}
+		rp_last = rp;
+	}
+	return 0;
+}
+
+static int snd_seq_iwffff_copy_wave_from_stream(snd_iwffff_ops_t *ops,
+						iwffff_layer_t *lp,
+					        char __user **data,
+					        long *len,
+					        int atomic)
+{
+	iwffff_wave_t *wp, *prev;
+	iwffff_xwave_t xp;
+	int err, gfp_mask;
+	unsigned int real_size;
+	
+	gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL;
+	if (*len < (long)sizeof(xp))
+		return -EINVAL;
+	if (copy_from_user(&xp, *data, sizeof(xp)))
+		return -EFAULT;
+	*data += sizeof(xp);
+	*len -= sizeof(xp);
+	wp = kcalloc(1, sizeof(*wp), gfp_mask);
+	if (wp == NULL)
+		return -ENOMEM;
+	wp->share_id[0] = le32_to_cpu(xp.share_id[0]);
+	wp->share_id[1] = le32_to_cpu(xp.share_id[1]);
+	wp->share_id[2] = le32_to_cpu(xp.share_id[2]);
+	wp->share_id[3] = le32_to_cpu(xp.share_id[3]);
+	wp->format = le32_to_cpu(xp.format);
+	wp->address.memory = le32_to_cpu(xp.offset);
+	wp->size = le32_to_cpu(xp.size);
+	wp->start = le32_to_cpu(xp.start);
+	wp->loop_start = le32_to_cpu(xp.loop_start);
+	wp->loop_end = le32_to_cpu(xp.loop_end);
+	wp->loop_repeat = le16_to_cpu(xp.loop_repeat);
+	wp->sample_ratio = le32_to_cpu(xp.sample_ratio);
+	wp->attenuation = xp.attenuation;
+	wp->low_note = xp.low_note;
+	wp->high_note = xp.high_note;
+	real_size = snd_seq_iwffff_size(wp->size, wp->format);
+	if (!(wp->format & IWFFFF_WAVE_ROM)) {
+		if ((long)real_size > *len) {
+			kfree(wp);
+			return -ENOMEM;
+		}
+	}
+	if (ops->put_sample) {
+		err = ops->put_sample(ops->private_data, wp,
+				      *data, real_size, atomic);
+		if (err < 0) {
+			kfree(wp);
+			return err;
+		}
+	}
+	if (!(wp->format & IWFFFF_WAVE_ROM)) {
+		*data += real_size;
+		*len -= real_size;
+	}
+	prev = lp->wave;
+	if (prev) {
+		while (prev->next) prev = prev->next;
+		prev->next = wp;
+	} else {
+		lp->wave = wp;
+	}
+	return 0;
+}
+
+static void snd_seq_iwffff_env_free(snd_iwffff_ops_t *ops,
+				    iwffff_env_t *env,
+				    int atomic)
+{
+	iwffff_env_record_t *rec;
+	
+	while ((rec = env->record) != NULL) {
+		env->record = rec->next;
+		kfree(rec);
+	}
+}
+				    
+static void snd_seq_iwffff_wave_free(snd_iwffff_ops_t *ops,
+				     iwffff_wave_t *wave,
+				     int atomic)
+{
+	if (ops->remove_sample)
+		ops->remove_sample(ops->private_data, wave, atomic);
+	kfree(wave);
+}
+
+static void snd_seq_iwffff_instr_free(snd_iwffff_ops_t *ops,
+                                      iwffff_instrument_t *ip,
+                                      int atomic)
+{
+	iwffff_layer_t *layer;
+	iwffff_wave_t *wave;
+	
+	while ((layer = ip->layer) != NULL) {
+		ip->layer = layer->next;
+		snd_seq_iwffff_env_free(ops, &layer->penv, atomic);
+		snd_seq_iwffff_env_free(ops, &layer->venv, atomic);
+		while ((wave = layer->wave) != NULL) {
+			layer->wave = wave->next;
+			snd_seq_iwffff_wave_free(ops, wave, atomic);
+		}
+		kfree(layer);
+	}
+}
+
+static int snd_seq_iwffff_put(void *private_data, snd_seq_kinstr_t *instr,
+			      char __user *instr_data, long len, int atomic,
+			      int cmd)
+{
+	snd_iwffff_ops_t *ops = (snd_iwffff_ops_t *)private_data;
+	iwffff_instrument_t *ip;
+	iwffff_xinstrument_t ix;
+	iwffff_layer_t *lp, *prev_lp;
+	iwffff_xlayer_t lx;
+	int err, gfp_mask;
+
+	if (cmd != SNDRV_SEQ_INSTR_PUT_CMD_CREATE)
+		return -EINVAL;
+	gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL;
+	/* copy instrument data */
+	if (len < (long)sizeof(ix))
+		return -EINVAL;
+	if (copy_from_user(&ix, instr_data, sizeof(ix)))
+		return -EFAULT;
+	if (ix.stype != IWFFFF_STRU_INSTR)
+		return -EINVAL;
+	instr_data += sizeof(ix);
+	len -= sizeof(ix);
+	ip = (iwffff_instrument_t *)KINSTR_DATA(instr);
+	ip->exclusion = le16_to_cpu(ix.exclusion);
+	ip->layer_type = le16_to_cpu(ix.layer_type);
+	ip->exclusion_group = le16_to_cpu(ix.exclusion_group);
+	ip->effect1 = ix.effect1;
+	ip->effect1_depth = ix.effect1_depth;
+	ip->effect2 = ix.effect2;
+	ip->effect2_depth = ix.effect2_depth;
+	/* copy layers */
+	prev_lp = NULL;
+	while (len > 0) {
+		if (len < (long)sizeof(iwffff_xlayer_t)) {
+			snd_seq_iwffff_instr_free(ops, ip, atomic);
+			return -EINVAL;
+		}
+		if (copy_from_user(&lx, instr_data, sizeof(lx)))
+			return -EFAULT;
+		instr_data += sizeof(lx);
+		len -= sizeof(lx);
+		if (lx.stype != IWFFFF_STRU_LAYER) {
+			snd_seq_iwffff_instr_free(ops, ip, atomic);
+			return -EINVAL;
+		}
+		lp = kcalloc(1, sizeof(*lp), gfp_mask);
+		if (lp == NULL) {
+			snd_seq_iwffff_instr_free(ops, ip, atomic);
+			return -ENOMEM;
+		}
+		if (prev_lp) {
+			prev_lp->next = lp;
+		} else {
+			ip->layer = lp;
+		}
+		prev_lp = lp;
+		lp->flags = lx.flags;
+		lp->velocity_mode = lx.velocity_mode;
+		lp->layer_event = lx.layer_event;
+		lp->low_range = lx.low_range;
+		lp->high_range = lx.high_range;
+		lp->pan = lx.pan;
+		lp->pan_freq_scale = lx.pan_freq_scale;
+		lp->attenuation = lx.attenuation;
+		snd_seq_iwffff_copy_lfo_from_stream(&lp->tremolo, &lx.tremolo);
+		snd_seq_iwffff_copy_lfo_from_stream(&lp->vibrato, &lx.vibrato);
+		lp->freq_scale = le16_to_cpu(lx.freq_scale);
+		lp->freq_center = lx.freq_center;
+		err = snd_seq_iwffff_copy_env_from_stream(IWFFFF_STRU_ENV_RECP,
+							  lp,
+							  &lp->penv, &lx.penv,
+						          &instr_data, &len,
+						          gfp_mask);
+		if (err < 0) {
+			snd_seq_iwffff_instr_free(ops, ip, atomic);
+			return err;
+		}
+		err = snd_seq_iwffff_copy_env_from_stream(IWFFFF_STRU_ENV_RECV,
+							  lp,
+							  &lp->venv, &lx.venv,
+						          &instr_data, &len,
+						          gfp_mask);
+		if (err < 0) {
+			snd_seq_iwffff_instr_free(ops, ip, atomic);
+			return err;
+		}
+		while (len > (long)sizeof(__u32)) {
+			__u32 stype;
+
+			if (copy_from_user(&stype, instr_data, sizeof(stype)))
+				return -EFAULT;
+			if (stype != IWFFFF_STRU_WAVE)
+				break;
+			err = snd_seq_iwffff_copy_wave_from_stream(ops,
+								   lp,
+							    	   &instr_data,
+								   &len,
+								   atomic);
+			if (err < 0) {
+				snd_seq_iwffff_instr_free(ops, ip, atomic);
+				return err;
+			}
+		}
+	}
+	return 0;
+}
+
+static void snd_seq_iwffff_copy_lfo_to_stream(iwffff_xlfo_t *fx,
+					      iwffff_lfo_t *fp)
+{
+	fx->freq = cpu_to_le16(fp->freq);
+	fx->depth = cpu_to_le16(fp->depth);
+	fx->sweep = cpu_to_le16(fp->sweep);
+	fp->shape = fx->shape;
+	fp->delay = fx->delay;
+}
+
+static int snd_seq_iwffff_copy_env_to_stream(__u32 req_stype,
+					     iwffff_layer_t *lp,
+					     iwffff_xenv_t *ex,
+					     iwffff_env_t *ep,
+					     char __user **data,
+					     long *len)
+{
+	iwffff_env_record_t *rp;
+	iwffff_xenv_record_t rx;
+	iwffff_env_point_t *pp;
+	iwffff_xenv_point_t px;
+	int points_size, idx;
+
+	ex->flags = ep->flags;
+	ex->mode = ep->mode;
+	ex->index = ep->index;
+	for (rp = ep->record; rp; rp = rp->next) {
+		if (*len < (long)sizeof(rx))
+			return -ENOMEM;
+		memset(&rx, 0, sizeof(rx));
+		rx.stype = req_stype;
+		rx.nattack = cpu_to_le16(rp->nattack);
+		rx.nrelease = cpu_to_le16(rp->nrelease);
+		rx.sustain_offset = cpu_to_le16(rp->sustain_offset);
+		rx.sustain_rate = cpu_to_le16(rp->sustain_rate);
+		rx.release_rate = cpu_to_le16(rp->release_rate);
+		rx.hirange = cpu_to_le16(rp->hirange);
+		if (copy_to_user(*data, &rx, sizeof(rx)))
+			return -EFAULT;
+		*data += sizeof(rx);
+		*len -= sizeof(rx);
+		points_size = (rp->nattack + rp->nrelease) * 2 * sizeof(__u16);
+		if (*len < points_size)
+			return -ENOMEM;
+		pp = (iwffff_env_point_t *)(rp + 1);
+		for (idx = 0; idx < rp->nattack + rp->nrelease; idx++) {
+			px.offset = cpu_to_le16(pp->offset);
+			px.rate = cpu_to_le16(pp->rate);
+			if (copy_to_user(*data, &px, sizeof(px)))
+				return -EFAULT;
+			*data += sizeof(px);
+			*len -= sizeof(px);
+		}
+	}
+	return 0;
+}
+
+static int snd_seq_iwffff_copy_wave_to_stream(snd_iwffff_ops_t *ops,
+					      iwffff_layer_t *lp,
+					      char __user **data,
+					      long *len,
+					      int atomic)
+{
+	iwffff_wave_t *wp;
+	iwffff_xwave_t xp;
+	int err;
+	unsigned int real_size;
+	
+	for (wp = lp->wave; wp; wp = wp->next) {
+		if (*len < (long)sizeof(xp))
+			return -ENOMEM;
+		memset(&xp, 0, sizeof(xp));
+		xp.stype = IWFFFF_STRU_WAVE;
+		xp.share_id[0] = cpu_to_le32(wp->share_id[0]);
+		xp.share_id[1] = cpu_to_le32(wp->share_id[1]);
+		xp.share_id[2] = cpu_to_le32(wp->share_id[2]);
+		xp.share_id[3] = cpu_to_le32(wp->share_id[3]);
+		xp.format = cpu_to_le32(wp->format);
+		if (wp->format & IWFFFF_WAVE_ROM)
+			xp.offset = cpu_to_le32(wp->address.memory);
+		xp.size = cpu_to_le32(wp->size);
+		xp.start = cpu_to_le32(wp->start);
+		xp.loop_start = cpu_to_le32(wp->loop_start);
+		xp.loop_end = cpu_to_le32(wp->loop_end);
+		xp.loop_repeat = cpu_to_le32(wp->loop_repeat);
+		xp.sample_ratio = cpu_to_le32(wp->sample_ratio);
+		xp.attenuation = wp->attenuation;
+		xp.low_note = wp->low_note;
+		xp.high_note = wp->high_note;
+		if (copy_to_user(*data, &xp, sizeof(xp)))
+			return -EFAULT;
+		*data += sizeof(xp);
+		*len -= sizeof(xp);
+		real_size = snd_seq_iwffff_size(wp->size, wp->format);
+		if (!(wp->format & IWFFFF_WAVE_ROM)) {
+			if (*len < (long)real_size)
+				return -ENOMEM;
+		}
+		if (ops->get_sample) {
+			err = ops->get_sample(ops->private_data, wp,
+					      *data, real_size, atomic);
+			if (err < 0)
+				return err;
+		}
+		if (!(wp->format & IWFFFF_WAVE_ROM)) {
+			*data += real_size;
+			*len -= real_size;
+		}
+	}
+	return 0;
+}
+
+static int snd_seq_iwffff_get(void *private_data, snd_seq_kinstr_t *instr,
+			      char __user *instr_data, long len, int atomic, int cmd)
+{
+	snd_iwffff_ops_t *ops = (snd_iwffff_ops_t *)private_data;
+	iwffff_instrument_t *ip;
+	iwffff_xinstrument_t ix;
+	iwffff_layer_t *lp;
+	iwffff_xlayer_t lx;
+	char __user *layer_instr_data;
+	int err;
+	
+	if (cmd != SNDRV_SEQ_INSTR_GET_CMD_FULL)
+		return -EINVAL;
+	if (len < (long)sizeof(ix))
+		return -ENOMEM;
+	memset(&ix, 0, sizeof(ix));
+	ip = (iwffff_instrument_t *)KINSTR_DATA(instr);
+	ix.stype = IWFFFF_STRU_INSTR;
+	ix.exclusion = cpu_to_le16(ip->exclusion);
+	ix.layer_type = cpu_to_le16(ip->layer_type);
+	ix.exclusion_group = cpu_to_le16(ip->exclusion_group);
+	ix.effect1 = cpu_to_le16(ip->effect1);
+	ix.effect1_depth = cpu_to_le16(ip->effect1_depth);
+	ix.effect2 = ip->effect2;
+	ix.effect2_depth = ip->effect2_depth;
+	if (copy_to_user(instr_data, &ix, sizeof(ix)))
+		return -EFAULT;
+	instr_data += sizeof(ix);
+	len -= sizeof(ix);
+	for (lp = ip->layer; lp; lp = lp->next) {
+		if (len < (long)sizeof(lx))
+			return -ENOMEM;
+		memset(&lx, 0, sizeof(lx));
+		lx.stype = IWFFFF_STRU_LAYER;
+		lx.flags = lp->flags;
+		lx.velocity_mode = lp->velocity_mode;
+		lx.layer_event = lp->layer_event;
+		lx.low_range = lp->low_range;
+		lx.high_range = lp->high_range;
+		lx.pan = lp->pan;
+		lx.pan_freq_scale = lp->pan_freq_scale;
+		lx.attenuation = lp->attenuation;
+		snd_seq_iwffff_copy_lfo_to_stream(&lx.tremolo, &lp->tremolo);
+		snd_seq_iwffff_copy_lfo_to_stream(&lx.vibrato, &lp->vibrato);
+		layer_instr_data = instr_data;
+		instr_data += sizeof(lx);
+		len -= sizeof(lx);
+		err = snd_seq_iwffff_copy_env_to_stream(IWFFFF_STRU_ENV_RECP,
+							lp,
+							&lx.penv, &lp->penv,
+						        &instr_data, &len);
+		if (err < 0)
+			return err;
+		err = snd_seq_iwffff_copy_env_to_stream(IWFFFF_STRU_ENV_RECV,
+							lp,
+							&lx.venv, &lp->venv,
+						        &instr_data, &len);
+		if (err < 0)
+			return err;
+		/* layer structure updating is now finished */
+		if (copy_to_user(layer_instr_data, &lx, sizeof(lx)))
+			return -EFAULT;
+		err = snd_seq_iwffff_copy_wave_to_stream(ops,
+							 lp,
+							 &instr_data,
+							 &len,
+							 atomic);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static long snd_seq_iwffff_env_size_in_stream(iwffff_env_t *ep)
+{
+	long result = 0;
+	iwffff_env_record_t *rp;
+
+	for (rp = ep->record; rp; rp = rp->next) {
+		result += sizeof(iwffff_xenv_record_t);
+		result += (rp->nattack + rp->nrelease) * 2 * sizeof(__u16);
+	}
+	return 0;
+}
+
+static long snd_seq_iwffff_wave_size_in_stream(iwffff_layer_t *lp)
+{
+	long result = 0;
+	iwffff_wave_t *wp;
+	
+	for (wp = lp->wave; wp; wp = wp->next) {
+		result += sizeof(iwffff_xwave_t);
+		if (!(wp->format & IWFFFF_WAVE_ROM))
+			result += wp->size;
+	}
+	return result;
+}
+
+static int snd_seq_iwffff_get_size(void *private_data, snd_seq_kinstr_t *instr,
+				   long *size)
+{
+	long result;
+	iwffff_instrument_t *ip;
+	iwffff_layer_t *lp;
+
+	*size = 0;
+	ip = (iwffff_instrument_t *)KINSTR_DATA(instr);
+	result = sizeof(iwffff_xinstrument_t);
+	for (lp = ip->layer; lp; lp = lp->next) {
+		result += sizeof(iwffff_xlayer_t);
+		result += snd_seq_iwffff_env_size_in_stream(&lp->penv);
+		result += snd_seq_iwffff_env_size_in_stream(&lp->venv);
+		result += snd_seq_iwffff_wave_size_in_stream(lp);
+	}
+	*size = result;
+	return 0;
+}
+
+static int snd_seq_iwffff_remove(void *private_data,
+				 snd_seq_kinstr_t *instr,
+                                 int atomic)
+{
+	snd_iwffff_ops_t *ops = (snd_iwffff_ops_t *)private_data;
+	iwffff_instrument_t *ip;
+
+	ip = (iwffff_instrument_t *)KINSTR_DATA(instr);
+	snd_seq_iwffff_instr_free(ops, ip, atomic);
+	return 0;
+}
+
+static void snd_seq_iwffff_notify(void *private_data,
+				  snd_seq_kinstr_t *instr,
+                                  int what)
+{
+	snd_iwffff_ops_t *ops = (snd_iwffff_ops_t *)private_data;
+
+	if (ops->notify)
+		ops->notify(ops->private_data, instr, what);
+}
+
+int snd_seq_iwffff_init(snd_iwffff_ops_t *ops,
+			void *private_data,
+			snd_seq_kinstr_ops_t *next)
+{
+	memset(ops, 0, sizeof(*ops));
+	ops->private_data = private_data;
+	ops->kops.private_data = ops;
+	ops->kops.add_len = sizeof(iwffff_instrument_t);
+	ops->kops.instr_type = SNDRV_SEQ_INSTR_ID_INTERWAVE;
+	ops->kops.put = snd_seq_iwffff_put;
+	ops->kops.get = snd_seq_iwffff_get;
+	ops->kops.get_size = snd_seq_iwffff_get_size;
+	ops->kops.remove = snd_seq_iwffff_remove;
+	ops->kops.notify = snd_seq_iwffff_notify;
+	ops->kops.next = next;
+	return 0;
+}
+
+/*
+ *  Init part
+ */
+
+static int __init alsa_ainstr_iw_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_ainstr_iw_exit(void)
+{
+}
+
+module_init(alsa_ainstr_iw_init)
+module_exit(alsa_ainstr_iw_exit)
+
+EXPORT_SYMBOL(snd_seq_iwffff_init);
diff --git a/sound/core/seq/instr/ainstr_simple.c b/sound/core/seq/instr/ainstr_simple.c
new file mode 100644
index 000000000000..6183d2151034
--- /dev/null
+++ b/sound/core/seq/instr/ainstr_simple.c
@@ -0,0 +1,215 @@
+/*
+ *   Simple (MOD player) - Instrument routines
+ *   Copyright (c) 1999 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/sched.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/ainstr_simple.h>
+#include <sound/initval.h>
+#include <asm/uaccess.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Advanced Linux Sound Architecture Simple Instrument support.");
+MODULE_LICENSE("GPL");
+
+static unsigned int snd_seq_simple_size(unsigned int size, unsigned int format)
+{
+	unsigned int result = size;
+	
+	if (format & SIMPLE_WAVE_16BIT)
+		result <<= 1;
+	if (format & SIMPLE_WAVE_STEREO)
+		result <<= 1;
+	return result;
+}
+
+static void snd_seq_simple_instr_free(snd_simple_ops_t *ops,
+				      simple_instrument_t *ip,
+				      int atomic)
+{
+	if (ops->remove_sample)
+		ops->remove_sample(ops->private_data, ip, atomic);
+}
+
+static int snd_seq_simple_put(void *private_data, snd_seq_kinstr_t *instr,
+			      char __user *instr_data, long len,
+			      int atomic, int cmd)
+{
+	snd_simple_ops_t *ops = (snd_simple_ops_t *)private_data;
+	simple_instrument_t *ip;
+	simple_xinstrument_t ix;
+	int err, gfp_mask;
+	unsigned int real_size;
+
+	if (cmd != SNDRV_SEQ_INSTR_PUT_CMD_CREATE)
+		return -EINVAL;
+	gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL;
+	/* copy instrument data */
+	if (len < (long)sizeof(ix))
+		return -EINVAL;
+	if (copy_from_user(&ix, instr_data, sizeof(ix)))
+		return -EFAULT;
+	if (ix.stype != SIMPLE_STRU_INSTR)
+		return -EINVAL;
+	instr_data += sizeof(ix);
+	len -= sizeof(ix);
+	ip = (simple_instrument_t *)KINSTR_DATA(instr);
+	ip->share_id[0] = le32_to_cpu(ix.share_id[0]);
+	ip->share_id[1] = le32_to_cpu(ix.share_id[1]);
+	ip->share_id[2] = le32_to_cpu(ix.share_id[2]);
+	ip->share_id[3] = le32_to_cpu(ix.share_id[3]);
+	ip->format = le32_to_cpu(ix.format);
+	ip->size = le32_to_cpu(ix.size);
+	ip->start = le32_to_cpu(ix.start);
+	ip->loop_start = le32_to_cpu(ix.loop_start);
+	ip->loop_end = le32_to_cpu(ix.loop_end);
+	ip->loop_repeat = le16_to_cpu(ix.loop_repeat);
+	ip->effect1 = ix.effect1;
+	ip->effect1_depth = ix.effect1_depth;
+	ip->effect2 = ix.effect2;
+	ip->effect2_depth = ix.effect2_depth;
+	real_size = snd_seq_simple_size(ip->size, ip->format);
+	if (len < (long)real_size)
+		return -EINVAL;
+	if (ops->put_sample) {
+		err = ops->put_sample(ops->private_data, ip,
+				      instr_data, real_size, atomic);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int snd_seq_simple_get(void *private_data, snd_seq_kinstr_t *instr,
+			      char __user *instr_data, long len,
+			      int atomic, int cmd)
+{
+	snd_simple_ops_t *ops = (snd_simple_ops_t *)private_data;
+	simple_instrument_t *ip;
+	simple_xinstrument_t ix;
+	int err;
+	unsigned int real_size;
+	
+	if (cmd != SNDRV_SEQ_INSTR_GET_CMD_FULL)
+		return -EINVAL;
+	if (len < (long)sizeof(ix))
+		return -ENOMEM;
+	memset(&ix, 0, sizeof(ix));
+	ip = (simple_instrument_t *)KINSTR_DATA(instr);
+	ix.stype = SIMPLE_STRU_INSTR;
+	ix.share_id[0] = cpu_to_le32(ip->share_id[0]);
+	ix.share_id[1] = cpu_to_le32(ip->share_id[1]);
+	ix.share_id[2] = cpu_to_le32(ip->share_id[2]);
+	ix.share_id[3] = cpu_to_le32(ip->share_id[3]);
+	ix.format = cpu_to_le32(ip->format);
+	ix.size = cpu_to_le32(ip->size);
+	ix.start = cpu_to_le32(ip->start);
+	ix.loop_start = cpu_to_le32(ip->loop_start);
+	ix.loop_end = cpu_to_le32(ip->loop_end);
+	ix.loop_repeat = cpu_to_le32(ip->loop_repeat);
+	ix.effect1 = cpu_to_le16(ip->effect1);
+	ix.effect1_depth = cpu_to_le16(ip->effect1_depth);
+	ix.effect2 = ip->effect2;
+	ix.effect2_depth = ip->effect2_depth;
+	if (copy_to_user(instr_data, &ix, sizeof(ix)))
+		return -EFAULT;
+	instr_data += sizeof(ix);
+	len -= sizeof(ix);
+	real_size = snd_seq_simple_size(ip->size, ip->format);
+	if (len < (long)real_size)
+		return -ENOMEM;
+	if (ops->get_sample) {
+		err = ops->get_sample(ops->private_data, ip,
+				      instr_data, real_size, atomic);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int snd_seq_simple_get_size(void *private_data, snd_seq_kinstr_t *instr,
+				   long *size)
+{
+	simple_instrument_t *ip;
+
+	ip = (simple_instrument_t *)KINSTR_DATA(instr);
+	*size = sizeof(simple_xinstrument_t) + snd_seq_simple_size(ip->size, ip->format);
+	return 0;
+}
+
+static int snd_seq_simple_remove(void *private_data,
+			         snd_seq_kinstr_t *instr,
+                                 int atomic)
+{
+	snd_simple_ops_t *ops = (snd_simple_ops_t *)private_data;
+	simple_instrument_t *ip;
+
+	ip = (simple_instrument_t *)KINSTR_DATA(instr);
+	snd_seq_simple_instr_free(ops, ip, atomic);
+	return 0;
+}
+
+static void snd_seq_simple_notify(void *private_data,
+			          snd_seq_kinstr_t *instr,
+                                  int what)
+{
+	snd_simple_ops_t *ops = (snd_simple_ops_t *)private_data;
+
+	if (ops->notify)
+		ops->notify(ops->private_data, instr, what);
+}
+
+int snd_seq_simple_init(snd_simple_ops_t *ops,
+		        void *private_data,
+		        snd_seq_kinstr_ops_t *next)
+{
+	memset(ops, 0, sizeof(*ops));
+	ops->private_data = private_data;
+	ops->kops.private_data = ops;
+	ops->kops.add_len = sizeof(simple_instrument_t);
+	ops->kops.instr_type = SNDRV_SEQ_INSTR_ID_SIMPLE;
+	ops->kops.put = snd_seq_simple_put;
+	ops->kops.get = snd_seq_simple_get;
+	ops->kops.get_size = snd_seq_simple_get_size;
+	ops->kops.remove = snd_seq_simple_remove;
+	ops->kops.notify = snd_seq_simple_notify;
+	ops->kops.next = next;
+	return 0;
+}
+
+/*
+ *  Init part
+ */
+
+static int __init alsa_ainstr_simple_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_ainstr_simple_exit(void)
+{
+}
+
+module_init(alsa_ainstr_simple_init)
+module_exit(alsa_ainstr_simple_exit)
+
+EXPORT_SYMBOL(snd_seq_simple_init);
diff --git a/sound/core/seq/oss/Makefile b/sound/core/seq/oss/Makefile
new file mode 100644
index 000000000000..a37ddedf7107
--- /dev/null
+++ b/sound/core/seq/oss/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for ALSA
+# Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-seq-oss-objs  := seq_oss.o seq_oss_init.o seq_oss_timer.o seq_oss_ioctl.o \
+		     seq_oss_event.o seq_oss_rw.o seq_oss_synth.o \
+		     seq_oss_midi.o seq_oss_readq.o seq_oss_writeq.o
+
+obj-$(CONFIG_SND_SEQUENCER) += snd-seq-oss.o
diff --git a/sound/core/seq/oss/seq_oss.c b/sound/core/seq/oss/seq_oss.c
new file mode 100644
index 000000000000..4c0558c0a8b4
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss.c
@@ -0,0 +1,317 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * registration of device and proc
+ *
+ * Copyright (C) 1998,99 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/init.h>
+#include <linux/smp_lock.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/initval.h>
+#include "seq_oss_device.h"
+#include "seq_oss_synth.h"
+
+/*
+ * module option
+ */
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("OSS-compatible sequencer module");
+MODULE_LICENSE("GPL");
+/* Takashi says this is really only for sound-service-0-, but this is OK. */
+MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_SEQUENCER);
+MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_MUSIC);
+
+#ifdef SNDRV_SEQ_OSS_DEBUG
+module_param(seq_oss_debug, int, 0644);
+MODULE_PARM_DESC(seq_oss_debug, "debug option");
+int seq_oss_debug = 0;
+#endif
+
+
+/*
+ * prototypes
+ */
+static int register_device(void);
+static void unregister_device(void);
+static int register_proc(void);
+static void unregister_proc(void);
+
+static int odev_open(struct inode *inode, struct file *file);
+static int odev_release(struct inode *inode, struct file *file);
+static ssize_t odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset);
+static ssize_t odev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset);
+static long odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+static unsigned int odev_poll(struct file *file, poll_table * wait);
+#ifdef CONFIG_PROC_FS
+static void info_read(snd_info_entry_t *entry, snd_info_buffer_t *buf);
+#endif
+
+
+/*
+ * module interface
+ */
+
+static int __init alsa_seq_oss_init(void)
+{
+	int rc;
+	static snd_seq_dev_ops_t ops = {
+		snd_seq_oss_synth_register,
+		snd_seq_oss_synth_unregister,
+	};
+
+	snd_seq_autoload_lock();
+	if ((rc = register_device()) < 0)
+		goto error;
+	if ((rc = register_proc()) < 0) {
+		unregister_device();
+		goto error;
+	}
+	if ((rc = snd_seq_oss_create_client()) < 0) {
+		unregister_proc();
+		unregister_device();
+		goto error;
+	}
+
+	if ((rc = snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_OSS, &ops,
+						 sizeof(snd_seq_oss_reg_t))) < 0) {
+		snd_seq_oss_delete_client();
+		unregister_proc();
+		unregister_device();
+		goto error;
+	}
+
+	/* success */
+	snd_seq_oss_synth_init();
+
+ error:
+	snd_seq_autoload_unlock();
+	return rc;
+}
+
+static void __exit alsa_seq_oss_exit(void)
+{
+	snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_OSS);
+	snd_seq_oss_delete_client();
+	unregister_proc();
+	unregister_device();
+}
+
+module_init(alsa_seq_oss_init)
+module_exit(alsa_seq_oss_exit)
+
+/*
+ * ALSA minor device interface
+ */
+
+static DECLARE_MUTEX(register_mutex);
+
+static int
+odev_open(struct inode *inode, struct file *file)
+{
+	int level, rc;
+
+	if (iminor(inode) == SNDRV_MINOR_OSS_MUSIC)
+		level = SNDRV_SEQ_OSS_MODE_MUSIC;
+	else
+		level = SNDRV_SEQ_OSS_MODE_SYNTH;
+
+	down(&register_mutex);
+	rc = snd_seq_oss_open(file, level);
+	up(&register_mutex);
+
+	return rc;
+}
+
+static int
+odev_release(struct inode *inode, struct file *file)
+{
+	seq_oss_devinfo_t *dp;
+
+	if ((dp = file->private_data) == NULL)
+		return 0;
+
+	snd_seq_oss_drain_write(dp);
+
+	down(&register_mutex);
+	snd_seq_oss_release(dp);
+	up(&register_mutex);
+
+	return 0;
+}
+
+static ssize_t
+odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
+{
+	seq_oss_devinfo_t *dp;
+	dp = file->private_data;
+	snd_assert(dp != NULL, return -EIO);
+	return snd_seq_oss_read(dp, buf, count);
+}
+
+
+static ssize_t
+odev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
+{
+	seq_oss_devinfo_t *dp;
+	dp = file->private_data;
+	snd_assert(dp != NULL, return -EIO);
+	return snd_seq_oss_write(dp, buf, count, file);
+}
+
+static long
+odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	seq_oss_devinfo_t *dp;
+	dp = file->private_data;
+	snd_assert(dp != NULL, return -EIO);
+	return snd_seq_oss_ioctl(dp, cmd, arg);
+}
+
+#ifdef CONFIG_COMPAT
+#define odev_ioctl_compat	odev_ioctl
+#else
+#define odev_ioctl_compat	NULL
+#endif
+
+static unsigned int
+odev_poll(struct file *file, poll_table * wait)
+{
+	seq_oss_devinfo_t *dp;
+	dp = file->private_data;
+	snd_assert(dp != NULL, return 0);
+	return snd_seq_oss_poll(dp, file, wait);
+}
+
+/*
+ * registration of sequencer minor device
+ */
+
+static struct file_operations seq_oss_f_ops =
+{
+	.owner =	THIS_MODULE,
+	.read =		odev_read,
+	.write =	odev_write,
+	.open =		odev_open,
+	.release =	odev_release,
+	.poll =		odev_poll,
+	.unlocked_ioctl =	odev_ioctl,
+	.compat_ioctl =	odev_ioctl_compat,
+};
+
+static snd_minor_t seq_oss_reg = {
+	.comment =	"sequencer",
+	.f_ops =	&seq_oss_f_ops,
+};
+
+static int __init
+register_device(void)
+{
+	int rc;
+
+	down(&register_mutex);
+	if ((rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER,
+					  NULL, 0,
+					  &seq_oss_reg,
+					  SNDRV_SEQ_OSS_DEVNAME)) < 0) {
+		snd_printk(KERN_ERR "can't register device seq\n");
+		up(&register_mutex);
+		return rc;
+	}
+	if ((rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC,
+					  NULL, 0,
+					  &seq_oss_reg,
+					  SNDRV_SEQ_OSS_DEVNAME)) < 0) {
+		snd_printk(KERN_ERR "can't register device music\n");
+		snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0);
+		up(&register_mutex);
+		return rc;
+	}
+	debug_printk(("device registered\n"));
+	up(&register_mutex);
+	return 0;
+}
+
+static void
+unregister_device(void)
+{
+	down(&register_mutex);
+	debug_printk(("device unregistered\n"));
+	if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC, NULL, 0) < 0)		
+		snd_printk(KERN_ERR "error unregister device music\n");
+	if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0) < 0)
+		snd_printk(KERN_ERR "error unregister device seq\n");
+	up(&register_mutex);
+}
+
+/*
+ * /proc interface
+ */
+
+#ifdef CONFIG_PROC_FS
+
+static snd_info_entry_t *info_entry;
+
+static void
+info_read(snd_info_entry_t *entry, snd_info_buffer_t *buf)
+{
+	down(&register_mutex);
+	snd_iprintf(buf, "OSS sequencer emulation version %s\n", SNDRV_SEQ_OSS_VERSION_STR);
+	snd_seq_oss_system_info_read(buf);
+	snd_seq_oss_synth_info_read(buf);
+	snd_seq_oss_midi_info_read(buf);
+	up(&register_mutex);
+}
+
+#endif /* CONFIG_PROC_FS */
+
+static int __init
+register_proc(void)
+{
+#ifdef CONFIG_PROC_FS
+	snd_info_entry_t *entry;
+
+	entry = snd_info_create_module_entry(THIS_MODULE, SNDRV_SEQ_OSS_PROCNAME, snd_seq_root);
+	if (entry == NULL)
+		return -ENOMEM;
+
+	entry->content = SNDRV_INFO_CONTENT_TEXT;
+	entry->private_data = NULL;
+	entry->c.text.read_size = 1024;
+	entry->c.text.read = info_read;
+	if (snd_info_register(entry) < 0) {
+		snd_info_free_entry(entry);
+		return -ENOMEM;
+	}
+	info_entry = entry;
+#endif
+	return 0;
+}
+
+static void
+unregister_proc(void)
+{
+#ifdef CONFIG_PROC_FS
+	if (info_entry)
+		snd_info_unregister(info_entry);
+	info_entry = NULL;
+#endif
+}
diff --git a/sound/core/seq/oss/seq_oss_device.h b/sound/core/seq/oss/seq_oss_device.h
new file mode 100644
index 000000000000..da23c4db8dd5
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_device.h
@@ -0,0 +1,198 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * Copyright (C) 1998,99 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 __SEQ_OSS_DEVICE_H
+#define __SEQ_OSS_DEVICE_H
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <sound/core.h>
+#include <sound/seq_oss.h>
+#include <sound/rawmidi.h>
+#include <sound/seq_kernel.h>
+#include <sound/info.h>
+
+/* enable debug print */
+#define SNDRV_SEQ_OSS_DEBUG
+
+/* max. applications */
+#define SNDRV_SEQ_OSS_MAX_CLIENTS	16
+#define SNDRV_SEQ_OSS_MAX_SYNTH_DEVS	16
+#define SNDRV_SEQ_OSS_MAX_MIDI_DEVS	32
+
+/* version */
+#define SNDRV_SEQ_OSS_MAJOR_VERSION	0
+#define SNDRV_SEQ_OSS_MINOR_VERSION	1
+#define SNDRV_SEQ_OSS_TINY_VERSION	8
+#define SNDRV_SEQ_OSS_VERSION_STR	"0.1.8"
+
+/* device and proc interface name */
+#define SNDRV_SEQ_OSS_DEVNAME		"seq_oss"
+#define SNDRV_SEQ_OSS_PROCNAME		"oss"
+
+
+/*
+ * type definitions
+ */
+
+typedef struct seq_oss_devinfo_t seq_oss_devinfo_t;
+typedef struct seq_oss_writeq_t seq_oss_writeq_t;
+typedef struct seq_oss_readq_t seq_oss_readq_t;
+typedef struct seq_oss_timer_t seq_oss_timer_t;
+typedef struct seq_oss_synthinfo_t seq_oss_synthinfo_t;
+typedef struct seq_oss_synth_sysex_t seq_oss_synth_sysex_t;
+typedef struct seq_oss_chinfo_t seq_oss_chinfo_t;
+typedef unsigned int reltime_t;
+typedef unsigned int abstime_t;
+typedef union evrec_t evrec_t;
+
+
+/*
+ * synthesizer channel information
+ */
+struct seq_oss_chinfo_t {
+	int note, vel;
+};
+
+/*
+ * synthesizer information
+ */
+struct seq_oss_synthinfo_t {
+	snd_seq_oss_arg_t arg;
+	seq_oss_chinfo_t *ch;
+	seq_oss_synth_sysex_t *sysex;
+	int nr_voices;
+	int opened;
+	int is_midi;
+	int midi_mapped;
+};
+
+
+/*
+ * sequencer client information
+ */
+
+struct seq_oss_devinfo_t {
+
+	int index;	/* application index */
+	int cseq;	/* sequencer client number */
+	int port;	/* sequencer port number */
+	int queue;	/* sequencer queue number */
+
+	snd_seq_addr_t addr;	/* address of this device */
+
+	int seq_mode;	/* sequencer mode */
+	int file_mode;	/* file access */
+
+	/* midi device table */
+	int max_mididev;
+
+	/* synth device table */
+	int max_synthdev;
+	seq_oss_synthinfo_t synths[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS];
+	int synth_opened;
+
+	/* output queue */
+	seq_oss_writeq_t *writeq;
+
+	/* midi input queue */
+	seq_oss_readq_t *readq;
+
+	/* timer */
+	seq_oss_timer_t *timer;
+};
+
+
+/*
+ * function prototypes
+ */
+
+/* create/delete OSS sequencer client */
+int snd_seq_oss_create_client(void);
+int snd_seq_oss_delete_client(void);
+
+/* device file interface */
+int snd_seq_oss_open(struct file *file, int level);
+void snd_seq_oss_release(seq_oss_devinfo_t *dp);
+int snd_seq_oss_ioctl(seq_oss_devinfo_t *dp, unsigned int cmd, unsigned long arg);
+int snd_seq_oss_read(seq_oss_devinfo_t *dev, char __user *buf, int count);
+int snd_seq_oss_write(seq_oss_devinfo_t *dp, const char __user *buf, int count, struct file *opt);
+unsigned int snd_seq_oss_poll(seq_oss_devinfo_t *dp, struct file *file, poll_table * wait);
+
+void snd_seq_oss_reset(seq_oss_devinfo_t *dp);
+void snd_seq_oss_drain_write(seq_oss_devinfo_t *dp);
+
+/* */
+void snd_seq_oss_process_queue(seq_oss_devinfo_t *dp, abstime_t time);
+
+
+/* proc interface */
+void snd_seq_oss_system_info_read(snd_info_buffer_t *buf);
+void snd_seq_oss_midi_info_read(snd_info_buffer_t *buf);
+void snd_seq_oss_synth_info_read(snd_info_buffer_t *buf);
+void snd_seq_oss_readq_info_read(seq_oss_readq_t *q, snd_info_buffer_t *buf);
+
+/* file mode macros */
+#define is_read_mode(mode)	((mode) & SNDRV_SEQ_OSS_FILE_READ)
+#define is_write_mode(mode)	((mode) & SNDRV_SEQ_OSS_FILE_WRITE)
+#define is_nonblock_mode(mode)	((mode) & SNDRV_SEQ_OSS_FILE_NONBLOCK)
+
+/* dispatch event */
+inline static int
+snd_seq_oss_dispatch(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, int atomic, int hop)
+{
+	return snd_seq_kernel_client_dispatch(dp->cseq, ev, atomic, hop);
+}
+
+/* ioctl */
+inline static int
+snd_seq_oss_control(seq_oss_devinfo_t *dp, unsigned int type, void *arg)
+{
+	return snd_seq_kernel_client_ctl(dp->cseq, type, arg);
+}
+
+/* fill the addresses in header */
+inline static void
+snd_seq_oss_fill_addr(seq_oss_devinfo_t *dp, snd_seq_event_t *ev,
+		     int dest_client, int dest_port)
+{
+	ev->queue = dp->queue;
+	ev->source = dp->addr;
+	ev->dest.client = dest_client;
+	ev->dest.port = dest_port;
+}
+
+
+/* misc. functions for proc interface */
+char *enabled_str(int bool);
+
+
+/* for debug */
+#ifdef SNDRV_SEQ_OSS_DEBUG
+extern int seq_oss_debug;
+#define debug_printk(x)	do { if (seq_oss_debug > 0) snd_printk x; } while (0)
+#else
+#define debug_printk(x)	/**/
+#endif
+
+#endif /* __SEQ_OSS_DEVICE_H */
diff --git a/sound/core/seq/oss/seq_oss_event.c b/sound/core/seq/oss/seq_oss_event.c
new file mode 100644
index 000000000000..58e52ddd2927
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_event.c
@@ -0,0 +1,447 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * Copyright (C) 1998,99 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 "seq_oss_device.h"
+#include "seq_oss_synth.h"
+#include "seq_oss_midi.h"
+#include "seq_oss_event.h"
+#include "seq_oss_timer.h"
+#include <sound/seq_oss_legacy.h>
+#include "seq_oss_readq.h"
+#include "seq_oss_writeq.h"
+
+
+/*
+ * prototypes
+ */
+static int extended_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev);
+static int chn_voice_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, snd_seq_event_t *ev);
+static int chn_common_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, snd_seq_event_t *ev);
+static int timing_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, snd_seq_event_t *ev);
+static int local_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, snd_seq_event_t *ev);
+static int old_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev);
+static int note_on_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, snd_seq_event_t *ev);
+static int note_off_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, snd_seq_event_t *ev);
+static int set_note_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int note, int vel, snd_seq_event_t *ev);
+static int set_control_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int param, int val, snd_seq_event_t *ev);
+static int set_echo_event(seq_oss_devinfo_t *dp, evrec_t *rec, snd_seq_event_t *ev);
+
+
+/*
+ * convert an OSS event to ALSA event
+ * return 0 : enqueued
+ *        non-zero : invalid - ignored
+ */
+
+int
+snd_seq_oss_process_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
+{
+	switch (q->s.code) {
+	case SEQ_EXTENDED:
+		return extended_event(dp, q, ev);
+
+	case EV_CHN_VOICE:
+		return chn_voice_event(dp, q, ev);
+
+	case EV_CHN_COMMON:
+		return chn_common_event(dp, q, ev);
+
+	case EV_TIMING:
+		return timing_event(dp, q, ev);
+
+	case EV_SEQ_LOCAL:
+		return local_event(dp, q, ev);
+
+	case EV_SYSEX:
+		return snd_seq_oss_synth_sysex(dp, q->x.dev, q->x.buf, ev);
+
+	case SEQ_MIDIPUTC:
+		if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
+			return -EINVAL;
+		/* put a midi byte */
+		if (! is_write_mode(dp->file_mode))
+			break;
+		if (snd_seq_oss_midi_open(dp, q->s.dev, SNDRV_SEQ_OSS_FILE_WRITE))
+			break;
+		if (snd_seq_oss_midi_filemode(dp, q->s.dev) & SNDRV_SEQ_OSS_FILE_WRITE)
+			return snd_seq_oss_midi_putc(dp, q->s.dev, q->s.parm1, ev);
+		break;
+
+	case SEQ_ECHO:
+		if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
+			return -EINVAL;
+		return set_echo_event(dp, q, ev);
+
+	case SEQ_PRIVATE:
+		if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
+			return -EINVAL;
+		return snd_seq_oss_synth_raw_event(dp, q->c[1], q->c, ev);
+
+	default:
+		if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
+			return -EINVAL;
+		return old_event(dp, q, ev);
+	}
+	return -EINVAL;
+}
+
+/* old type events: mode1 only */
+static int
+old_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
+{
+	switch (q->s.code) {
+	case SEQ_NOTEOFF:
+		return note_off_event(dp, 0, q->n.chn, q->n.note, q->n.vel, ev);
+
+	case SEQ_NOTEON:
+		return note_on_event(dp, 0, q->n.chn, q->n.note, q->n.vel, ev);
+
+	case SEQ_WAIT:
+		/* skip */
+		break;
+
+	case SEQ_PGMCHANGE:
+		return set_control_event(dp, 0, SNDRV_SEQ_EVENT_PGMCHANGE,
+					 q->n.chn, 0, q->n.note, ev);
+
+	case SEQ_SYNCTIMER:
+		return snd_seq_oss_timer_reset(dp->timer);
+	}
+
+	return -EINVAL;
+}
+
+/* 8bytes extended event: mode1 only */
+static int
+extended_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
+{
+	int val;
+
+	switch (q->e.cmd) {
+	case SEQ_NOTEOFF:
+		return note_off_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, ev);
+
+	case SEQ_NOTEON:
+		return note_on_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, ev);
+
+	case SEQ_PGMCHANGE:
+		return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_PGMCHANGE,
+					 q->e.chn, 0, q->e.p1, ev);
+
+	case SEQ_AFTERTOUCH:
+		return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_CHANPRESS,
+					 q->e.chn, 0, q->e.p1, ev);
+
+	case SEQ_BALANCE:
+		/* convert -128:127 to 0:127 */
+		val = (char)q->e.p1;
+		val = (val + 128) / 2;
+		return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_CONTROLLER,
+					 q->e.chn, CTL_PAN, val, ev);
+
+	case SEQ_CONTROLLER:
+		val = ((short)q->e.p3 << 8) | (short)q->e.p2;
+		switch (q->e.p1) {
+		case CTRL_PITCH_BENDER: /* SEQ1 V2 control */
+			/* -0x2000:0x1fff */
+			return set_control_event(dp, q->e.dev,
+						 SNDRV_SEQ_EVENT_PITCHBEND,
+						 q->e.chn, 0, val, ev);
+		case CTRL_PITCH_BENDER_RANGE:
+			/* conversion: 100/semitone -> 128/semitone */
+			return set_control_event(dp, q->e.dev,
+						 SNDRV_SEQ_EVENT_REGPARAM,
+						 q->e.chn, 0, val*128/100, ev);
+		default:
+			return set_control_event(dp, q->e.dev,
+						  SNDRV_SEQ_EVENT_CONTROL14,
+						  q->e.chn, q->e.p1, val, ev);
+		}
+
+	case SEQ_VOLMODE:
+		return snd_seq_oss_synth_raw_event(dp, q->e.dev, q->c, ev);
+
+	}
+	return -EINVAL;
+}
+
+/* channel voice events: mode1 and 2 */
+static int
+chn_voice_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
+{
+	if (q->v.chn >= 32)
+		return -EINVAL;
+	switch (q->v.cmd) {
+	case MIDI_NOTEON:
+		return note_on_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, ev);
+
+	case MIDI_NOTEOFF:
+		return note_off_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, ev);
+
+	case MIDI_KEY_PRESSURE:
+		return set_note_event(dp, q->v.dev, SNDRV_SEQ_EVENT_KEYPRESS,
+				       q->v.chn, q->v.note, q->v.parm, ev);
+
+	}
+	return -EINVAL;
+}
+
+/* channel common events: mode1 and 2 */
+static int
+chn_common_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
+{
+	if (q->l.chn >= 32)
+		return -EINVAL;
+	switch (q->l.cmd) {
+	case MIDI_PGM_CHANGE:
+		return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_PGMCHANGE,
+					  q->l.chn, 0, q->l.p1, ev);
+
+	case MIDI_CTL_CHANGE:
+		return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_CONTROLLER,
+					  q->l.chn, q->l.p1, q->l.val, ev);
+
+	case MIDI_PITCH_BEND:
+		/* conversion: 0:0x3fff -> -0x2000:0x1fff */
+		return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_PITCHBEND,
+					  q->l.chn, 0, q->l.val - 8192, ev);
+		
+	case MIDI_CHN_PRESSURE:
+		return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_CHANPRESS,
+					  q->l.chn, 0, q->l.val, ev);
+	}
+	return -EINVAL;
+}
+
+/* timer events: mode1 and mode2 */
+static int
+timing_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
+{
+	switch (q->t.cmd) {
+	case TMR_ECHO:
+		if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
+			return set_echo_event(dp, q, ev);
+		else {
+			evrec_t tmp;
+			memset(&tmp, 0, sizeof(tmp));
+			/* XXX: only for little-endian! */
+			tmp.echo = (q->t.time << 8) | SEQ_ECHO;
+			return set_echo_event(dp, &tmp, ev);
+		} 
+
+	case TMR_STOP:
+		if (dp->seq_mode)
+			return snd_seq_oss_timer_stop(dp->timer);
+		return 0;
+
+	case TMR_CONTINUE:
+		if (dp->seq_mode)
+			return snd_seq_oss_timer_continue(dp->timer);
+		return 0;
+
+	case TMR_TEMPO:
+		if (dp->seq_mode)
+			return snd_seq_oss_timer_tempo(dp->timer, q->t.time);
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+/* local events: mode1 and 2 */
+static int
+local_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
+{
+	return -EINVAL;
+}
+
+/*
+ * process note-on event for OSS synth
+ * three different modes are available:
+ * - SNDRV_SEQ_OSS_PROCESS_EVENTS  (for one-voice per channel mode)
+ *	Accept note 255 as volume change.
+ * - SNDRV_SEQ_OSS_PASS_EVENTS
+ *	Pass all events to lowlevel driver anyway
+ * - SNDRV_SEQ_OSS_PROCESS_KEYPRESS  (mostly for Emu8000)
+ *	Use key-pressure if note >= 128
+ */
+static int
+note_on_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, snd_seq_event_t *ev)
+{
+	seq_oss_synthinfo_t *info = &dp->synths[dev];
+	switch (info->arg.event_passing) {
+	case SNDRV_SEQ_OSS_PROCESS_EVENTS:
+		if (! info->ch || ch < 0 || ch >= info->nr_voices) {
+			/* pass directly */
+			return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
+		}
+
+		if (note == 255 && info->ch[ch].note >= 0) {
+			/* volume control */
+			int type;
+			//if (! vel)
+				/* set volume to zero -- note off */
+			//	type = SNDRV_SEQ_EVENT_NOTEOFF;
+			//else
+				if (info->ch[ch].vel)
+				/* sample already started -- volume change */
+				type = SNDRV_SEQ_EVENT_KEYPRESS;
+			else
+				/* sample not started -- start now */
+				type = SNDRV_SEQ_EVENT_NOTEON;
+			info->ch[ch].vel = vel;
+			return set_note_event(dp, dev, type, ch, info->ch[ch].note, vel, ev);
+		} else if (note >= 128)
+			return -EINVAL; /* invalid */
+
+		if (note != info->ch[ch].note && info->ch[ch].note >= 0)
+			/* note changed - note off at beginning */
+			set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, info->ch[ch].note, 0, ev);
+		/* set current status */
+		info->ch[ch].note = note;
+		info->ch[ch].vel = vel;
+		if (vel) /* non-zero velocity - start the note now */
+			return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
+		return -EINVAL;
+		
+	case SNDRV_SEQ_OSS_PASS_EVENTS:
+		/* pass the event anyway */
+		return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
+
+	case SNDRV_SEQ_OSS_PROCESS_KEYPRESS:
+		if (note >= 128) /* key pressure: shifted by 128 */
+			return set_note_event(dp, dev, SNDRV_SEQ_EVENT_KEYPRESS, ch, note - 128, vel, ev);
+		else /* normal note-on event */
+			return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
+	}
+	return -EINVAL;
+}
+
+/*
+ * process note-off event for OSS synth
+ */
+static int
+note_off_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, snd_seq_event_t *ev)
+{
+	seq_oss_synthinfo_t *info = &dp->synths[dev];
+	switch (info->arg.event_passing) {
+	case SNDRV_SEQ_OSS_PROCESS_EVENTS:
+		if (! info->ch || ch < 0 || ch >= info->nr_voices) {
+			/* pass directly */
+			return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
+		}
+
+		if (info->ch[ch].note >= 0) {
+			note = info->ch[ch].note;
+			info->ch[ch].vel = 0;
+			info->ch[ch].note = -1;
+			return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, note, vel, ev);
+		}
+		return -EINVAL; /* invalid */
+
+	case SNDRV_SEQ_OSS_PASS_EVENTS:
+	case SNDRV_SEQ_OSS_PROCESS_KEYPRESS:
+		/* pass the event anyway */
+		return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, note, vel, ev);
+
+	}
+	return -EINVAL;
+}
+
+/*
+ * create a note event
+ */
+static int
+set_note_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int note, int vel, snd_seq_event_t *ev)
+{
+	if (! snd_seq_oss_synth_is_valid(dp, dev))
+		return -ENXIO;
+	
+	ev->type = type;
+	snd_seq_oss_synth_addr(dp, dev, ev);
+	ev->data.note.channel = ch;
+	ev->data.note.note = note;
+	ev->data.note.velocity = vel;
+
+	return 0;
+}
+
+/*
+ * create a control event
+ */
+static int
+set_control_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int param, int val, snd_seq_event_t *ev)
+{
+	if (! snd_seq_oss_synth_is_valid(dp, dev))
+		return -ENXIO;
+	
+	ev->type = type;
+	snd_seq_oss_synth_addr(dp, dev, ev);
+	ev->data.control.channel = ch;
+	ev->data.control.param = param;
+	ev->data.control.value = val;
+
+	return 0;
+}
+
+/*
+ * create an echo event
+ */
+static int
+set_echo_event(seq_oss_devinfo_t *dp, evrec_t *rec, snd_seq_event_t *ev)
+{
+	ev->type = SNDRV_SEQ_EVENT_ECHO;
+	/* echo back to itself */
+	snd_seq_oss_fill_addr(dp, ev, dp->addr.client, dp->addr.port);
+	memcpy(&ev->data, rec, LONG_EVENT_SIZE);
+	return 0;
+}
+
+/*
+ * event input callback from ALSA sequencer:
+ * the echo event is processed here.
+ */
+int
+snd_seq_oss_event_input(snd_seq_event_t *ev, int direct, void *private_data,
+			int atomic, int hop)
+{
+	seq_oss_devinfo_t *dp = (seq_oss_devinfo_t *)private_data;
+	evrec_t *rec;
+
+	if (ev->type != SNDRV_SEQ_EVENT_ECHO)
+		return snd_seq_oss_midi_input(ev, direct, private_data);
+
+	if (ev->source.client != dp->cseq)
+		return 0; /* ignored */
+
+	rec = (evrec_t*)&ev->data;
+	if (rec->s.code == SEQ_SYNCTIMER) {
+		/* sync echo back */
+		snd_seq_oss_writeq_wakeup(dp->writeq, rec->t.time);
+		
+	} else {
+		/* echo back event */
+		if (dp->readq == NULL)
+			return 0;
+		snd_seq_oss_readq_put_event(dp->readq, rec);
+	}
+	return 0;
+}
+
diff --git a/sound/core/seq/oss/seq_oss_event.h b/sound/core/seq/oss/seq_oss_event.h
new file mode 100644
index 000000000000..bf1d4d3f53c9
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_event.h
@@ -0,0 +1,112 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * seq_oss_event.h - OSS event queue record
+ *
+ * Copyright (C) 1998,99 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 __SEQ_OSS_EVENT_H
+#define __SEQ_OSS_EVENT_H
+
+#include "seq_oss_device.h"
+
+#define SHORT_EVENT_SIZE	4
+#define LONG_EVENT_SIZE		8
+
+/* short event (4bytes) */
+typedef struct evrec_short_t {
+	unsigned char code;
+	unsigned char parm1;
+	unsigned char dev;
+	unsigned char parm2;
+} evrec_short_t;
+	
+/* short note events (4bytes) */
+typedef struct evrec_note_t {
+	unsigned char code;
+	unsigned char chn;
+	unsigned char note;
+	unsigned char vel;
+} evrec_note_t;
+	
+/* long timer events (8bytes) */
+typedef struct evrec_timer_t {
+	unsigned char code;
+	unsigned char cmd;
+	unsigned char dummy1, dummy2;
+	unsigned int time;
+} evrec_timer_t;
+
+/* long extended events (8bytes) */
+typedef struct evrec_extended_t {
+	unsigned char code;
+	unsigned char cmd;
+	unsigned char dev;
+	unsigned char chn;
+	unsigned char p1, p2, p3, p4;
+} evrec_extended_t;
+
+/* long channel events (8bytes) */
+typedef struct evrec_long_t {
+	unsigned char code;
+	unsigned char dev;
+	unsigned char cmd;
+	unsigned char chn;
+	unsigned char p1, p2;
+	unsigned short val;
+} evrec_long_t;
+	
+/* channel voice events (8bytes) */
+typedef struct evrec_voice_t {
+	unsigned char code;
+	unsigned char dev;
+	unsigned char cmd;
+	unsigned char chn;
+	unsigned char note, parm;
+	unsigned short dummy;
+} evrec_voice_t;
+
+/* sysex events (8bytes) */
+typedef struct evrec_sysex_t {
+	unsigned char code;
+	unsigned char dev;
+	unsigned char buf[6];
+} evrec_sysex_t;
+
+/* event record */
+union evrec_t {
+	evrec_short_t s;
+	evrec_note_t n;
+	evrec_long_t l;
+	evrec_voice_t v;
+	evrec_timer_t t;
+	evrec_extended_t e;
+	evrec_sysex_t x;
+	unsigned int echo;
+	unsigned char c[LONG_EVENT_SIZE];
+};
+
+#define ev_is_long(ev) ((ev)->s.code >= 128)
+#define ev_length(ev) ((ev)->s.code >= 128 ? LONG_EVENT_SIZE : SHORT_EVENT_SIZE)
+
+int snd_seq_oss_process_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev);
+int snd_seq_oss_process_timer_event(seq_oss_timer_t *rec, evrec_t *q);
+int snd_seq_oss_event_input(snd_seq_event_t *ev, int direct, void *private_data, int atomic, int hop);
+
+
+#endif /* __SEQ_OSS_EVENT_H */
diff --git a/sound/core/seq/oss/seq_oss_init.c b/sound/core/seq/oss/seq_oss_init.c
new file mode 100644
index 000000000000..bac4b4f1a94e
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_init.c
@@ -0,0 +1,555 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * open/close and reset interface
+ *
+ * Copyright (C) 1998-1999 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 "seq_oss_device.h"
+#include "seq_oss_synth.h"
+#include "seq_oss_midi.h"
+#include "seq_oss_writeq.h"
+#include "seq_oss_readq.h"
+#include "seq_oss_timer.h"
+#include "seq_oss_event.h"
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+
+/*
+ * common variables
+ */
+static int maxqlen = SNDRV_SEQ_OSS_MAX_QLEN;
+module_param(maxqlen, int, 0444);
+MODULE_PARM_DESC(maxqlen, "maximum queue length");
+
+static int system_client = -1; /* ALSA sequencer client number */
+static int system_port = -1;
+
+static int num_clients;
+static seq_oss_devinfo_t *client_table[SNDRV_SEQ_OSS_MAX_CLIENTS];
+
+
+/*
+ * prototypes
+ */
+static int receive_announce(snd_seq_event_t *ev, int direct, void *private, int atomic, int hop);
+static int translate_mode(struct file *file);
+static int create_port(seq_oss_devinfo_t *dp);
+static int delete_port(seq_oss_devinfo_t *dp);
+static int alloc_seq_queue(seq_oss_devinfo_t *dp);
+static int delete_seq_queue(int queue);
+static void free_devinfo(void *private);
+
+#define call_ctl(type,rec) snd_seq_kernel_client_ctl(system_client, type, rec)
+
+
+/*
+ * create sequencer client for OSS sequencer
+ */
+int __init
+snd_seq_oss_create_client(void)
+{
+	int rc;
+	snd_seq_client_callback_t callback;
+	snd_seq_client_info_t *info;
+	snd_seq_port_info_t *port;
+	snd_seq_port_callback_t port_callback;
+
+	info = kmalloc(sizeof(*info), GFP_KERNEL);
+	port = kmalloc(sizeof(*port), GFP_KERNEL);
+	if (!info || !port) {
+		rc = -ENOMEM;
+		goto __error;
+	}
+
+	/* create ALSA client */
+	memset(&callback, 0, sizeof(callback));
+
+	callback.private_data = NULL;
+	callback.allow_input = 1;
+	callback.allow_output = 1;
+
+	rc = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_OSS, &callback);
+	if (rc < 0)
+		goto __error;
+
+	system_client = rc;
+	debug_printk(("new client = %d\n", rc));
+
+	/* set client information */
+	memset(info, 0, sizeof(*info));
+	info->client = system_client;
+	info->type = KERNEL_CLIENT;
+	strcpy(info->name, "OSS sequencer");
+
+	rc = call_ctl(SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, info);
+
+	/* look up midi devices */
+	snd_seq_oss_midi_lookup_ports(system_client);
+
+	/* create annoucement receiver port */
+	memset(port, 0, sizeof(*port));
+	strcpy(port->name, "Receiver");
+	port->addr.client = system_client;
+	port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* receive only */
+	port->type = 0;
+
+	memset(&port_callback, 0, sizeof(port_callback));
+	/* don't set port_callback.owner here. otherwise the module counter
+	 * is incremented and we can no longer release the module..
+	 */
+	port_callback.event_input = receive_announce;
+	port->kernel = &port_callback;
+	
+	call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, port);
+	if ((system_port = port->addr.port) >= 0) {
+		snd_seq_port_subscribe_t subs;
+
+		memset(&subs, 0, sizeof(subs));
+		subs.sender.client = SNDRV_SEQ_CLIENT_SYSTEM;
+		subs.sender.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE;
+		subs.dest.client = system_client;
+		subs.dest.port = system_port;
+		call_ctl(SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs);
+	}
+	rc = 0;
+
+ __error:
+	kfree(port);
+	kfree(info);
+	return rc;
+}
+
+
+/*
+ * receive annoucement from system port, and check the midi device
+ */
+static int
+receive_announce(snd_seq_event_t *ev, int direct, void *private, int atomic, int hop)
+{
+	snd_seq_port_info_t pinfo;
+
+	if (atomic)
+		return 0; /* it must not happen */
+
+	switch (ev->type) {
+	case SNDRV_SEQ_EVENT_PORT_START:
+	case SNDRV_SEQ_EVENT_PORT_CHANGE:
+		if (ev->data.addr.client == system_client)
+			break; /* ignore myself */
+		memset(&pinfo, 0, sizeof(pinfo));
+		pinfo.addr = ev->data.addr;
+		if (call_ctl(SNDRV_SEQ_IOCTL_GET_PORT_INFO, &pinfo) >= 0)
+			snd_seq_oss_midi_check_new_port(&pinfo);
+		break;
+
+	case SNDRV_SEQ_EVENT_PORT_EXIT:
+		if (ev->data.addr.client == system_client)
+			break; /* ignore myself */
+		snd_seq_oss_midi_check_exit_port(ev->data.addr.client,
+						ev->data.addr.port);
+		break;
+	}
+	return 0;
+}
+
+
+/*
+ * delete OSS sequencer client
+ */
+int
+snd_seq_oss_delete_client(void)
+{
+	if (system_client >= 0)
+		snd_seq_delete_kernel_client(system_client);
+
+	snd_seq_oss_midi_clear_all();
+
+	return 0;
+}
+
+
+/*
+ * open sequencer device
+ */
+int
+snd_seq_oss_open(struct file *file, int level)
+{
+	int i, rc;
+	seq_oss_devinfo_t *dp;
+
+	if ((dp = kcalloc(1, sizeof(*dp), GFP_KERNEL)) == NULL) {
+		snd_printk(KERN_ERR "can't malloc device info\n");
+		return -ENOMEM;
+	}
+	debug_printk(("oss_open: dp = %p\n", dp));
+
+	for (i = 0; i < SNDRV_SEQ_OSS_MAX_CLIENTS; i++) {
+		if (client_table[i] == NULL)
+			break;
+	}
+	if (i >= SNDRV_SEQ_OSS_MAX_CLIENTS) {
+		snd_printk(KERN_ERR "too many applications\n");
+		kfree(dp);
+		return -ENOMEM;
+	}
+
+	dp->index = i;
+	dp->cseq = system_client;
+	dp->port = -1;
+	dp->queue = -1;
+	dp->readq = NULL;
+	dp->writeq = NULL;
+
+	/* look up synth and midi devices */
+	snd_seq_oss_synth_setup(dp);
+	snd_seq_oss_midi_setup(dp);
+
+	if (dp->synth_opened == 0 && dp->max_mididev == 0) {
+		/* snd_printk(KERN_ERR "no device found\n"); */
+		rc = -ENODEV;
+		goto _error;
+	}
+
+	/* create port */
+	debug_printk(("create new port\n"));
+	if ((rc = create_port(dp)) < 0) {
+		snd_printk(KERN_ERR "can't create port\n");
+		goto _error;
+	}
+
+	/* allocate queue */
+	debug_printk(("allocate queue\n"));
+	if ((rc = alloc_seq_queue(dp)) < 0)
+		goto _error;
+
+	/* set address */
+	dp->addr.client = dp->cseq;
+	dp->addr.port = dp->port;
+	/*dp->addr.queue = dp->queue;*/
+	/*dp->addr.channel = 0;*/
+
+	dp->seq_mode = level;
+
+	/* set up file mode */
+	dp->file_mode = translate_mode(file);
+
+	/* initialize read queue */
+	debug_printk(("initialize read queue\n"));
+	if (is_read_mode(dp->file_mode)) {
+		if ((dp->readq = snd_seq_oss_readq_new(dp, maxqlen)) == NULL) {
+			rc = -ENOMEM;
+			goto _error;
+		}
+	}
+
+	/* initialize write queue */
+	debug_printk(("initialize write queue\n"));
+	if (is_write_mode(dp->file_mode)) {
+		dp->writeq = snd_seq_oss_writeq_new(dp, maxqlen);
+		if (dp->writeq == NULL) {
+			rc = -ENOMEM;
+			goto _error;
+		}
+	}
+
+	/* initialize timer */
+	debug_printk(("initialize timer\n"));
+	if ((dp->timer = snd_seq_oss_timer_new(dp)) == NULL) {
+		snd_printk(KERN_ERR "can't alloc timer\n");
+		rc = -ENOMEM;
+		goto _error;
+	}
+	debug_printk(("timer initialized\n"));
+
+	/* set private data pointer */
+	file->private_data = dp;
+
+	/* set up for mode2 */
+	if (level == SNDRV_SEQ_OSS_MODE_MUSIC)
+		snd_seq_oss_synth_setup_midi(dp);
+	else if (is_read_mode(dp->file_mode))
+		snd_seq_oss_midi_open_all(dp, SNDRV_SEQ_OSS_FILE_READ);
+
+	client_table[dp->index] = dp;
+	num_clients++;
+
+	debug_printk(("open done\n"));
+	return 0;
+
+ _error:
+	snd_seq_oss_synth_cleanup(dp);
+	snd_seq_oss_midi_cleanup(dp);
+	i = dp->queue;
+	delete_port(dp);
+	delete_seq_queue(i);
+
+	return rc;
+}
+
+/*
+ * translate file flags to private mode
+ */
+static int
+translate_mode(struct file *file)
+{
+	int file_mode = 0;
+	if ((file->f_flags & O_ACCMODE) != O_RDONLY)
+		file_mode |= SNDRV_SEQ_OSS_FILE_WRITE;
+	if ((file->f_flags & O_ACCMODE) != O_WRONLY)
+		file_mode |= SNDRV_SEQ_OSS_FILE_READ;
+	if (file->f_flags & O_NONBLOCK)
+		file_mode |= SNDRV_SEQ_OSS_FILE_NONBLOCK;
+	return file_mode;
+}
+
+
+/*
+ * create sequencer port
+ */
+static int
+create_port(seq_oss_devinfo_t *dp)
+{
+	int rc;
+	snd_seq_port_info_t port;
+	snd_seq_port_callback_t callback;
+
+	memset(&port, 0, sizeof(port));
+	port.addr.client = dp->cseq;
+	sprintf(port.name, "Sequencer-%d", dp->index);
+	port.capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_WRITE; /* no subscription */
+	port.type = SNDRV_SEQ_PORT_TYPE_SPECIFIC;
+	port.midi_channels = 128;
+	port.synth_voices = 128;
+
+	memset(&callback, 0, sizeof(callback));
+	callback.owner = THIS_MODULE;
+	callback.private_data = dp;
+	callback.event_input = snd_seq_oss_event_input;
+	callback.private_free = free_devinfo;
+	port.kernel = &callback;
+
+	rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, &port);
+	if (rc < 0)
+		return rc;
+
+	dp->port = port.addr.port;
+	debug_printk(("new port = %d\n", port.addr.port));
+
+	return 0;
+}
+
+/*
+ * delete ALSA port
+ */
+static int
+delete_port(seq_oss_devinfo_t *dp)
+{
+	if (dp->port < 0)
+		return 0;
+
+	debug_printk(("delete_port %i\n", dp->port));
+	return snd_seq_event_port_detach(dp->cseq, dp->port);
+}
+
+/*
+ * allocate a queue
+ */
+static int
+alloc_seq_queue(seq_oss_devinfo_t *dp)
+{
+	snd_seq_queue_info_t qinfo;
+	int rc;
+
+	memset(&qinfo, 0, sizeof(qinfo));
+	qinfo.owner = system_client;
+	qinfo.locked = 1;
+	strcpy(qinfo.name, "OSS Sequencer Emulation");
+	if ((rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_QUEUE, &qinfo)) < 0)
+		return rc;
+	dp->queue = qinfo.queue;
+	return 0;
+}
+
+/*
+ * release queue
+ */
+static int
+delete_seq_queue(int queue)
+{
+	snd_seq_queue_info_t qinfo;
+	int rc;
+
+	if (queue < 0)
+		return 0;
+	memset(&qinfo, 0, sizeof(qinfo));
+	qinfo.queue = queue;
+	rc = call_ctl(SNDRV_SEQ_IOCTL_DELETE_QUEUE, &qinfo);
+	if (rc < 0)
+		printk(KERN_ERR "seq-oss: unable to delete queue %d (%d)\n", queue, rc);
+	return rc;
+}
+
+
+/*
+ * free device informations - private_free callback of port
+ */
+static void
+free_devinfo(void *private)
+{
+	seq_oss_devinfo_t *dp = (seq_oss_devinfo_t *)private;
+
+	if (dp->timer)
+		snd_seq_oss_timer_delete(dp->timer);
+		
+	if (dp->writeq)
+		snd_seq_oss_writeq_delete(dp->writeq);
+
+	if (dp->readq)
+		snd_seq_oss_readq_delete(dp->readq);
+	
+	kfree(dp);
+}
+
+
+/*
+ * close sequencer device
+ */
+void
+snd_seq_oss_release(seq_oss_devinfo_t *dp)
+{
+	int queue;
+
+	client_table[dp->index] = NULL;
+	num_clients--;
+
+	debug_printk(("resetting..\n"));
+	snd_seq_oss_reset(dp);
+
+	debug_printk(("cleaning up..\n"));
+	snd_seq_oss_synth_cleanup(dp);
+	snd_seq_oss_midi_cleanup(dp);
+
+	/* clear slot */
+	debug_printk(("releasing resource..\n"));
+	queue = dp->queue;
+	if (dp->port >= 0)
+		delete_port(dp);
+	delete_seq_queue(queue);
+
+	debug_printk(("release done\n"));
+}
+
+
+/*
+ * Wait until the queue is empty (if we don't have nonblock)
+ */
+void
+snd_seq_oss_drain_write(seq_oss_devinfo_t *dp)
+{
+	if (! dp->timer->running)
+		return;
+	if (is_write_mode(dp->file_mode) && !is_nonblock_mode(dp->file_mode) &&
+	    dp->writeq) {
+		debug_printk(("syncing..\n"));
+		while (snd_seq_oss_writeq_sync(dp->writeq))
+			;
+	}
+}
+
+
+/*
+ * reset sequencer devices
+ */
+void
+snd_seq_oss_reset(seq_oss_devinfo_t *dp)
+{
+	int i;
+
+	/* reset all synth devices */
+	for (i = 0; i < dp->max_synthdev; i++)
+		snd_seq_oss_synth_reset(dp, i);
+
+	/* reset all midi devices */
+	if (dp->seq_mode != SNDRV_SEQ_OSS_MODE_MUSIC) {
+		for (i = 0; i < dp->max_mididev; i++)
+			snd_seq_oss_midi_reset(dp, i);
+	}
+
+	/* remove queues */
+	if (dp->readq)
+		snd_seq_oss_readq_clear(dp->readq);
+	if (dp->writeq)
+		snd_seq_oss_writeq_clear(dp->writeq);
+
+	/* reset timer */
+	snd_seq_oss_timer_stop(dp->timer);
+}
+
+
+/*
+ * misc. functions for proc interface
+ */
+char *
+enabled_str(int bool)
+{
+	return bool ? "enabled" : "disabled";
+}
+
+static char *
+filemode_str(int val)
+{
+	static char *str[] = {
+		"none", "read", "write", "read/write",
+	};
+	return str[val & SNDRV_SEQ_OSS_FILE_ACMODE];
+}
+
+
+/*
+ * proc interface
+ */
+void
+snd_seq_oss_system_info_read(snd_info_buffer_t *buf)
+{
+	int i;
+	seq_oss_devinfo_t *dp;
+
+	snd_iprintf(buf, "ALSA client number %d\n", system_client);
+	snd_iprintf(buf, "ALSA receiver port %d\n", system_port);
+
+	snd_iprintf(buf, "\nNumber of applications: %d\n", num_clients);
+	for (i = 0; i < num_clients; i++) {
+		snd_iprintf(buf, "\nApplication %d: ", i);
+		if ((dp = client_table[i]) == NULL) {
+			snd_iprintf(buf, "*empty*\n");
+			continue;
+		}
+		snd_iprintf(buf, "port %d : queue %d\n", dp->port, dp->queue);
+		snd_iprintf(buf, "  sequencer mode = %s : file open mode = %s\n",
+			    (dp->seq_mode ? "music" : "synth"),
+			    filemode_str(dp->file_mode));
+		if (dp->seq_mode)
+			snd_iprintf(buf, "  timer tempo = %d, timebase = %d\n",
+				    dp->timer->oss_tempo, dp->timer->oss_timebase);
+		snd_iprintf(buf, "  max queue length %d\n", maxqlen);
+		if (is_read_mode(dp->file_mode) && dp->readq)
+			snd_seq_oss_readq_info_read(dp->readq, buf);
+	}
+}
+
diff --git a/sound/core/seq/oss/seq_oss_ioctl.c b/sound/core/seq/oss/seq_oss_ioctl.c
new file mode 100644
index 000000000000..e86f18d00f39
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_ioctl.c
@@ -0,0 +1,209 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * OSS compatible i/o control
+ *
+ * Copyright (C) 1998,99 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 "seq_oss_device.h"
+#include "seq_oss_readq.h"
+#include "seq_oss_writeq.h"
+#include "seq_oss_timer.h"
+#include "seq_oss_synth.h"
+#include "seq_oss_midi.h"
+#include "seq_oss_event.h"
+
+static int snd_seq_oss_synth_info_user(seq_oss_devinfo_t *dp, void __user *arg)
+{
+	struct synth_info info;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+	if (snd_seq_oss_synth_make_info(dp, info.device, &info) < 0)
+		return -EINVAL;
+	if (copy_to_user(arg, &info, sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+static int snd_seq_oss_midi_info_user(seq_oss_devinfo_t *dp, void __user *arg)
+{
+	struct midi_info info;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+	if (snd_seq_oss_midi_make_info(dp, info.device, &info) < 0)
+		return -EINVAL;
+	if (copy_to_user(arg, &info, sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+static int snd_seq_oss_oob_user(seq_oss_devinfo_t *dp, void __user *arg)
+{
+	unsigned char ev[8];
+	snd_seq_event_t tmpev;
+
+	if (copy_from_user(ev, arg, 8))
+		return -EFAULT;
+	memset(&tmpev, 0, sizeof(tmpev));
+	snd_seq_oss_fill_addr(dp, &tmpev, dp->addr.port, dp->addr.client);
+	tmpev.time.tick = 0;
+	if (! snd_seq_oss_process_event(dp, (evrec_t*)ev, &tmpev)) {
+		snd_seq_oss_dispatch(dp, &tmpev, 0, 0);
+	}
+	return 0;
+}
+
+int
+snd_seq_oss_ioctl(seq_oss_devinfo_t *dp, unsigned int cmd, unsigned long carg)
+{
+	int dev, val;
+	void __user *arg = (void __user *)carg;
+	int __user *p = arg;
+
+	switch (cmd) {
+	case SNDCTL_TMR_TIMEBASE:
+	case SNDCTL_TMR_TEMPO:
+	case SNDCTL_TMR_START:
+	case SNDCTL_TMR_STOP:
+	case SNDCTL_TMR_CONTINUE:
+	case SNDCTL_TMR_METRONOME:
+	case SNDCTL_TMR_SOURCE:
+	case SNDCTL_TMR_SELECT:
+	case SNDCTL_SEQ_CTRLRATE:
+		return snd_seq_oss_timer_ioctl(dp->timer, cmd, arg);
+
+	case SNDCTL_SEQ_PANIC:
+		debug_printk(("panic\n"));
+		snd_seq_oss_reset(dp);
+		return -EINVAL;
+
+	case SNDCTL_SEQ_SYNC:
+		debug_printk(("sync\n"));
+		if (! is_write_mode(dp->file_mode) || dp->writeq == NULL)
+			return 0;
+		while (snd_seq_oss_writeq_sync(dp->writeq))
+			;
+		if (signal_pending(current))
+			return -ERESTARTSYS;
+		return 0;
+
+	case SNDCTL_SEQ_RESET:
+		debug_printk(("reset\n"));
+		snd_seq_oss_reset(dp);
+		return 0;
+
+	case SNDCTL_SEQ_TESTMIDI:
+		debug_printk(("test midi\n"));
+		if (get_user(dev, p))
+			return -EFAULT;
+		return snd_seq_oss_midi_open(dp, dev, dp->file_mode);
+
+	case SNDCTL_SEQ_GETINCOUNT:
+		debug_printk(("get in count\n"));
+		if (dp->readq == NULL || ! is_read_mode(dp->file_mode))
+			return 0;
+		return put_user(dp->readq->qlen, p) ? -EFAULT : 0;
+
+	case SNDCTL_SEQ_GETOUTCOUNT:
+		debug_printk(("get out count\n"));
+		if (! is_write_mode(dp->file_mode) || dp->writeq == NULL)
+			return 0;
+		return put_user(snd_seq_oss_writeq_get_free_size(dp->writeq), p) ? -EFAULT : 0;
+
+	case SNDCTL_SEQ_GETTIME:
+		debug_printk(("get time\n"));
+		return put_user(snd_seq_oss_timer_cur_tick(dp->timer), p) ? -EFAULT : 0;
+
+	case SNDCTL_SEQ_RESETSAMPLES:
+		debug_printk(("reset samples\n"));
+		if (get_user(dev, p))
+			return -EFAULT;
+		return snd_seq_oss_synth_ioctl(dp, dev, cmd, carg);
+
+	case SNDCTL_SEQ_NRSYNTHS:
+		debug_printk(("nr synths\n"));
+		return put_user(dp->max_synthdev, p) ? -EFAULT : 0;
+
+	case SNDCTL_SEQ_NRMIDIS:
+		debug_printk(("nr midis\n"));
+		return put_user(dp->max_mididev, p) ? -EFAULT : 0;
+
+	case SNDCTL_SYNTH_MEMAVL:
+		debug_printk(("mem avail\n"));
+		if (get_user(dev, p))
+			return -EFAULT;
+		val = snd_seq_oss_synth_ioctl(dp, dev, cmd, carg);
+		return put_user(val, p) ? -EFAULT : 0;
+
+	case SNDCTL_FM_4OP_ENABLE:
+		debug_printk(("4op\n"));
+		if (get_user(dev, p))
+			return -EFAULT;
+		snd_seq_oss_synth_ioctl(dp, dev, cmd, carg);
+		return 0;
+
+	case SNDCTL_SYNTH_INFO:
+	case SNDCTL_SYNTH_ID:
+		debug_printk(("synth info\n"));
+		return snd_seq_oss_synth_info_user(dp, arg);
+
+	case SNDCTL_SEQ_OUTOFBAND:
+		debug_printk(("out of band\n"));
+		return snd_seq_oss_oob_user(dp, arg);
+
+	case SNDCTL_MIDI_INFO:
+		debug_printk(("midi info\n"));
+		return snd_seq_oss_midi_info_user(dp, arg);
+
+	case SNDCTL_SEQ_THRESHOLD:
+		debug_printk(("threshold\n"));
+		if (! is_write_mode(dp->file_mode))
+			return 0;
+		if (get_user(val, p))
+			return -EFAULT;
+		if (val < 1)
+			val = 1;
+		if (val >= dp->writeq->maxlen)
+			val = dp->writeq->maxlen - 1;
+		snd_seq_oss_writeq_set_output(dp->writeq, val);
+		return 0;
+
+	case SNDCTL_MIDI_PRETIME:
+		debug_printk(("pretime\n"));
+		if (dp->readq == NULL || !is_read_mode(dp->file_mode))
+			return 0;
+		if (get_user(val, p))
+			return -EFAULT;
+		if (val <= 0)
+			val = -1;
+		else
+			val = (HZ * val) / 10;
+		dp->readq->pre_event_timeout = val;
+		return put_user(val, p) ? -EFAULT : 0;
+
+	default:
+		debug_printk(("others\n"));
+		if (! is_write_mode(dp->file_mode))
+			return -EIO;
+		return snd_seq_oss_synth_ioctl(dp, 0, cmd, carg);
+	}
+	return 0;
+}
+
diff --git a/sound/core/seq/oss/seq_oss_midi.c b/sound/core/seq/oss/seq_oss_midi.c
new file mode 100644
index 000000000000..9aece6c65dbc
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_midi.c
@@ -0,0 +1,710 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * MIDI device handlers
+ *
+ * Copyright (C) 1998,99 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 "seq_oss_midi.h"
+#include "seq_oss_readq.h"
+#include "seq_oss_timer.h"
+#include "seq_oss_event.h"
+#include <sound/seq_midi_event.h>
+#include "../seq_lock.h"
+#include <linux/init.h>
+
+
+/*
+ * constants
+ */
+#define SNDRV_SEQ_OSS_MAX_MIDI_NAME	30
+
+/*
+ * definition of midi device record
+ */
+struct seq_oss_midi_t {
+	int seq_device;		/* device number */
+	int client;		/* sequencer client number */
+	int port;		/* sequencer port number */
+	unsigned int flags;	/* port capability */
+	int opened;		/* flag for opening */
+	unsigned char name[SNDRV_SEQ_OSS_MAX_MIDI_NAME];
+	snd_midi_event_t *coder;	/* MIDI event coder */
+	seq_oss_devinfo_t *devinfo;	/* assigned OSSseq device */
+	snd_use_lock_t use_lock;
+};
+
+
+/*
+ * midi device table
+ */
+static int max_midi_devs;
+static seq_oss_midi_t *midi_devs[SNDRV_SEQ_OSS_MAX_MIDI_DEVS];
+
+static DEFINE_SPINLOCK(register_lock);
+
+/*
+ * prototypes
+ */
+static seq_oss_midi_t *get_mdev(int dev);
+static seq_oss_midi_t *get_mididev(seq_oss_devinfo_t *dp, int dev);
+static int send_synth_event(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, int dev);
+static int send_midi_event(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, seq_oss_midi_t *mdev);
+
+/*
+ * look up the existing ports
+ * this looks a very exhausting job.
+ */
+int __init
+snd_seq_oss_midi_lookup_ports(int client)
+{
+	snd_seq_client_info_t *clinfo;
+	snd_seq_port_info_t *pinfo;
+
+	clinfo = kcalloc(1, sizeof(*clinfo), GFP_KERNEL);
+	pinfo = kcalloc(1, sizeof(*pinfo), GFP_KERNEL);
+	if (! clinfo || ! pinfo) {
+		kfree(clinfo);
+		kfree(pinfo);
+		return -ENOMEM;
+	}
+	clinfo->client = -1;
+	while (snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT, clinfo) == 0) {
+		if (clinfo->client == client)
+			continue; /* ignore myself */
+		pinfo->addr.client = clinfo->client;
+		pinfo->addr.port = -1;
+		while (snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, pinfo) == 0)
+			snd_seq_oss_midi_check_new_port(pinfo);
+	}
+	kfree(clinfo);
+	kfree(pinfo);
+	return 0;
+}
+
+
+/*
+ */
+static seq_oss_midi_t *
+get_mdev(int dev)
+{
+	seq_oss_midi_t *mdev;
+	unsigned long flags;
+
+	spin_lock_irqsave(&register_lock, flags);
+	mdev = midi_devs[dev];
+	if (mdev)
+		snd_use_lock_use(&mdev->use_lock);
+	spin_unlock_irqrestore(&register_lock, flags);
+	return mdev;
+}
+
+/*
+ * look for the identical slot
+ */
+static seq_oss_midi_t *
+find_slot(int client, int port)
+{
+	int i;
+	seq_oss_midi_t *mdev;
+	unsigned long flags;
+
+	spin_lock_irqsave(&register_lock, flags);
+	for (i = 0; i < max_midi_devs; i++) {
+		mdev = midi_devs[i];
+		if (mdev && mdev->client == client && mdev->port == port) {
+			/* found! */
+			snd_use_lock_use(&mdev->use_lock);
+			spin_unlock_irqrestore(&register_lock, flags);
+			return mdev;
+		}
+	}
+	spin_unlock_irqrestore(&register_lock, flags);
+	return NULL;
+}
+
+
+#define PERM_WRITE (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_SUBS_WRITE)
+#define PERM_READ (SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ)
+/*
+ * register a new port if it doesn't exist yet
+ */
+int
+snd_seq_oss_midi_check_new_port(snd_seq_port_info_t *pinfo)
+{
+	int i;
+	seq_oss_midi_t *mdev;
+	unsigned long flags;
+
+	debug_printk(("check for MIDI client %d port %d\n", pinfo->addr.client, pinfo->addr.port));
+	/* the port must include generic midi */
+	if (! (pinfo->type & SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC))
+		return 0;
+	/* either read or write subscribable */
+	if ((pinfo->capability & PERM_WRITE) != PERM_WRITE &&
+	    (pinfo->capability & PERM_READ) != PERM_READ)
+		return 0;
+
+	/*
+	 * look for the identical slot
+	 */
+	if ((mdev = find_slot(pinfo->addr.client, pinfo->addr.port)) != NULL) {
+		/* already exists */
+		snd_use_lock_free(&mdev->use_lock);
+		return 0;
+	}
+
+	/*
+	 * allocate midi info record
+	 */
+	if ((mdev = kcalloc(1, sizeof(*mdev), GFP_KERNEL)) == NULL) {
+		snd_printk(KERN_ERR "can't malloc midi info\n");
+		return -ENOMEM;
+	}
+
+	/* copy the port information */
+	mdev->client = pinfo->addr.client;
+	mdev->port = pinfo->addr.port;
+	mdev->flags = pinfo->capability;
+	mdev->opened = 0;
+	snd_use_lock_init(&mdev->use_lock);
+
+	/* copy and truncate the name of synth device */
+	strlcpy(mdev->name, pinfo->name, sizeof(mdev->name));
+
+	/* create MIDI coder */
+	if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &mdev->coder) < 0) {
+		snd_printk(KERN_ERR "can't malloc midi coder\n");
+		kfree(mdev);
+		return -ENOMEM;
+	}
+	/* OSS sequencer adds running status to all sequences */
+	snd_midi_event_no_status(mdev->coder, 1);
+
+	/*
+	 * look for en empty slot
+	 */
+	spin_lock_irqsave(&register_lock, flags);
+	for (i = 0; i < max_midi_devs; i++) {
+		if (midi_devs[i] == NULL)
+			break;
+	}
+	if (i >= max_midi_devs) {
+		if (max_midi_devs >= SNDRV_SEQ_OSS_MAX_MIDI_DEVS) {
+			spin_unlock_irqrestore(&register_lock, flags);
+			snd_midi_event_free(mdev->coder);
+			kfree(mdev);
+			return -ENOMEM;
+		}
+		max_midi_devs++;
+	}
+	mdev->seq_device = i;
+	midi_devs[mdev->seq_device] = mdev;
+	spin_unlock_irqrestore(&register_lock, flags);
+
+	return 0;
+}
+
+/*
+ * release the midi device if it was registered
+ */
+int
+snd_seq_oss_midi_check_exit_port(int client, int port)
+{
+	seq_oss_midi_t *mdev;
+	unsigned long flags;
+	int index;
+
+	if ((mdev = find_slot(client, port)) != NULL) {
+		spin_lock_irqsave(&register_lock, flags);
+		midi_devs[mdev->seq_device] = NULL;
+		spin_unlock_irqrestore(&register_lock, flags);
+		snd_use_lock_free(&mdev->use_lock);
+		snd_use_lock_sync(&mdev->use_lock);
+		if (mdev->coder)
+			snd_midi_event_free(mdev->coder);
+		kfree(mdev);
+	}
+	spin_lock_irqsave(&register_lock, flags);
+	for (index = max_midi_devs - 1; index >= 0; index--) {
+		if (midi_devs[index])
+			break;
+	}
+	max_midi_devs = index + 1;
+	spin_unlock_irqrestore(&register_lock, flags);
+	return 0;
+}
+
+
+/*
+ * release the midi device if it was registered
+ */
+void
+snd_seq_oss_midi_clear_all(void)
+{
+	int i;
+	seq_oss_midi_t *mdev;
+	unsigned long flags;
+
+	spin_lock_irqsave(&register_lock, flags);
+	for (i = 0; i < max_midi_devs; i++) {
+		if ((mdev = midi_devs[i]) != NULL) {
+			if (mdev->coder)
+				snd_midi_event_free(mdev->coder);
+			kfree(mdev);
+			midi_devs[i] = NULL;
+		}
+	}
+	max_midi_devs = 0;
+	spin_unlock_irqrestore(&register_lock, flags);
+}
+
+
+/*
+ * set up midi tables
+ */
+void
+snd_seq_oss_midi_setup(seq_oss_devinfo_t *dp)
+{
+	dp->max_mididev = max_midi_devs;
+}
+
+/*
+ * clean up midi tables
+ */
+void
+snd_seq_oss_midi_cleanup(seq_oss_devinfo_t *dp)
+{
+	int i;
+	for (i = 0; i < dp->max_mididev; i++)
+		snd_seq_oss_midi_close(dp, i);
+	dp->max_mididev = 0;
+}
+
+
+/*
+ * open all midi devices.  ignore errors.
+ */
+void
+snd_seq_oss_midi_open_all(seq_oss_devinfo_t *dp, int file_mode)
+{
+	int i;
+	for (i = 0; i < dp->max_mididev; i++)
+		snd_seq_oss_midi_open(dp, i, file_mode);
+}
+
+
+/*
+ * get the midi device information
+ */
+static seq_oss_midi_t *
+get_mididev(seq_oss_devinfo_t *dp, int dev)
+{
+	if (dev < 0 || dev >= dp->max_mididev)
+		return NULL;
+	return get_mdev(dev);
+}
+
+
+/*
+ * open the midi device if not opened yet
+ */
+int
+snd_seq_oss_midi_open(seq_oss_devinfo_t *dp, int dev, int fmode)
+{
+	int perm;
+	seq_oss_midi_t *mdev;
+	snd_seq_port_subscribe_t subs;
+
+	if ((mdev = get_mididev(dp, dev)) == NULL)
+		return -ENODEV;
+
+	/* already used? */
+	if (mdev->opened && mdev->devinfo != dp) {
+		snd_use_lock_free(&mdev->use_lock);
+		return -EBUSY;
+	}
+
+	perm = 0;
+	if (is_write_mode(fmode))
+		perm |= PERM_WRITE;
+	if (is_read_mode(fmode))
+		perm |= PERM_READ;
+	perm &= mdev->flags;
+	if (perm == 0) {
+		snd_use_lock_free(&mdev->use_lock);
+		return -ENXIO;
+	}
+
+	/* already opened? */
+	if ((mdev->opened & perm) == perm) {
+		snd_use_lock_free(&mdev->use_lock);
+		return 0;
+	}
+
+	perm &= ~mdev->opened;
+
+	memset(&subs, 0, sizeof(subs));
+
+	if (perm & PERM_WRITE) {
+		subs.sender = dp->addr;
+		subs.dest.client = mdev->client;
+		subs.dest.port = mdev->port;
+		if (snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs) >= 0)
+			mdev->opened |= PERM_WRITE;
+	}
+	if (perm & PERM_READ) {
+		subs.sender.client = mdev->client;
+		subs.sender.port = mdev->port;
+		subs.dest = dp->addr;
+		subs.flags = SNDRV_SEQ_PORT_SUBS_TIMESTAMP;
+		subs.queue = dp->queue;		/* queue for timestamps */
+		if (snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs) >= 0)
+			mdev->opened |= PERM_READ;
+	}
+
+	if (! mdev->opened) {
+		snd_use_lock_free(&mdev->use_lock);
+		return -ENXIO;
+	}
+
+	mdev->devinfo = dp;
+	snd_use_lock_free(&mdev->use_lock);
+	return 0;
+}
+
+/*
+ * close the midi device if already opened
+ */
+int
+snd_seq_oss_midi_close(seq_oss_devinfo_t *dp, int dev)
+{
+	seq_oss_midi_t *mdev;
+	snd_seq_port_subscribe_t subs;
+
+	if ((mdev = get_mididev(dp, dev)) == NULL)
+		return -ENODEV;
+	if (! mdev->opened || mdev->devinfo != dp) {
+		snd_use_lock_free(&mdev->use_lock);
+		return 0;
+	}
+
+	debug_printk(("closing client %d port %d mode %d\n", mdev->client, mdev->port, mdev->opened));
+	memset(&subs, 0, sizeof(subs));
+	if (mdev->opened & PERM_WRITE) {
+		subs.sender = dp->addr;
+		subs.dest.client = mdev->client;
+		subs.dest.port = mdev->port;
+		snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, &subs);
+	}
+	if (mdev->opened & PERM_READ) {
+		subs.sender.client = mdev->client;
+		subs.sender.port = mdev->port;
+		subs.dest = dp->addr;
+		snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, &subs);
+	}
+
+	mdev->opened = 0;
+	mdev->devinfo = NULL;
+
+	snd_use_lock_free(&mdev->use_lock);
+	return 0;
+}
+
+/*
+ * change seq capability flags to file mode flags
+ */
+int
+snd_seq_oss_midi_filemode(seq_oss_devinfo_t *dp, int dev)
+{
+	seq_oss_midi_t *mdev;
+	int mode;
+
+	if ((mdev = get_mididev(dp, dev)) == NULL)
+		return 0;
+
+	mode = 0;
+	if (mdev->opened & PERM_WRITE)
+		mode |= SNDRV_SEQ_OSS_FILE_WRITE;
+	if (mdev->opened & PERM_READ)
+		mode |= SNDRV_SEQ_OSS_FILE_READ;
+
+	snd_use_lock_free(&mdev->use_lock);
+	return mode;
+}
+
+/*
+ * reset the midi device and close it:
+ * so far, only close the device.
+ */
+void
+snd_seq_oss_midi_reset(seq_oss_devinfo_t *dp, int dev)
+{
+	seq_oss_midi_t *mdev;
+
+	if ((mdev = get_mididev(dp, dev)) == NULL)
+		return;
+	if (! mdev->opened) {
+		snd_use_lock_free(&mdev->use_lock);
+		return;
+	}
+
+	if (mdev->opened & PERM_WRITE) {
+		snd_seq_event_t ev;
+		int c;
+
+		debug_printk(("resetting client %d port %d\n", mdev->client, mdev->port));
+		memset(&ev, 0, sizeof(ev));
+		ev.dest.client = mdev->client;
+		ev.dest.port = mdev->port;
+		ev.queue = dp->queue;
+		ev.source.port = dp->port;
+		if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH) {
+			ev.type = SNDRV_SEQ_EVENT_SENSING;
+			snd_seq_oss_dispatch(dp, &ev, 0, 0); /* active sensing */
+		}
+		for (c = 0; c < 16; c++) {
+			ev.type = SNDRV_SEQ_EVENT_CONTROLLER;
+			ev.data.control.channel = c;
+			ev.data.control.param = 123;
+			snd_seq_oss_dispatch(dp, &ev, 0, 0); /* all notes off */
+			if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) {
+				ev.data.control.param = 121;
+				snd_seq_oss_dispatch(dp, &ev, 0, 0); /* reset all controllers */
+				ev.type = SNDRV_SEQ_EVENT_PITCHBEND;
+				ev.data.control.value = 0;
+				snd_seq_oss_dispatch(dp, &ev, 0, 0); /* bender off */
+			}
+		}
+	}
+	// snd_seq_oss_midi_close(dp, dev);
+	snd_use_lock_free(&mdev->use_lock);
+}
+
+
+/*
+ * get client/port of the specified MIDI device
+ */
+void
+snd_seq_oss_midi_get_addr(seq_oss_devinfo_t *dp, int dev, snd_seq_addr_t *addr)
+{
+	seq_oss_midi_t *mdev;
+
+	if ((mdev = get_mididev(dp, dev)) == NULL)
+		return;
+	addr->client = mdev->client;
+	addr->port = mdev->port;
+	snd_use_lock_free(&mdev->use_lock);
+}
+
+
+/*
+ * input callback - this can be atomic
+ */
+int
+snd_seq_oss_midi_input(snd_seq_event_t *ev, int direct, void *private_data)
+{
+	seq_oss_devinfo_t *dp = (seq_oss_devinfo_t *)private_data;
+	seq_oss_midi_t *mdev;
+	int rc;
+
+	if (dp->readq == NULL)
+		return 0;
+	if ((mdev = find_slot(ev->source.client, ev->source.port)) == NULL)
+		return 0;
+	if (! (mdev->opened & PERM_READ)) {
+		snd_use_lock_free(&mdev->use_lock);
+		return 0;
+	}
+
+	if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
+		rc = send_synth_event(dp, ev, mdev->seq_device);
+	else
+		rc = send_midi_event(dp, ev, mdev);
+
+	snd_use_lock_free(&mdev->use_lock);
+	return rc;
+}
+
+/*
+ * convert ALSA sequencer event to OSS synth event
+ */
+static int
+send_synth_event(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, int dev)
+{
+	evrec_t ossev;
+
+	memset(&ossev, 0, sizeof(ossev));
+
+	switch (ev->type) {
+	case SNDRV_SEQ_EVENT_NOTEON:
+		ossev.v.cmd = MIDI_NOTEON; break;
+	case SNDRV_SEQ_EVENT_NOTEOFF:
+		ossev.v.cmd = MIDI_NOTEOFF; break;
+	case SNDRV_SEQ_EVENT_KEYPRESS:
+		ossev.v.cmd = MIDI_KEY_PRESSURE; break;
+	case SNDRV_SEQ_EVENT_CONTROLLER:
+		ossev.l.cmd = MIDI_CTL_CHANGE; break;
+	case SNDRV_SEQ_EVENT_PGMCHANGE:
+		ossev.l.cmd = MIDI_PGM_CHANGE; break;
+	case SNDRV_SEQ_EVENT_CHANPRESS:
+		ossev.l.cmd = MIDI_CHN_PRESSURE; break;
+	case SNDRV_SEQ_EVENT_PITCHBEND:
+		ossev.l.cmd = MIDI_PITCH_BEND; break;
+	default:
+		return 0; /* not supported */
+	}
+
+	ossev.v.dev = dev;
+
+	switch (ev->type) {
+	case SNDRV_SEQ_EVENT_NOTEON:
+	case SNDRV_SEQ_EVENT_NOTEOFF:
+	case SNDRV_SEQ_EVENT_KEYPRESS:
+		ossev.v.code = EV_CHN_VOICE;
+		ossev.v.note = ev->data.note.note;
+		ossev.v.parm = ev->data.note.velocity;
+		ossev.v.chn = ev->data.note.channel;
+		break;
+	case SNDRV_SEQ_EVENT_CONTROLLER:
+	case SNDRV_SEQ_EVENT_PGMCHANGE:
+	case SNDRV_SEQ_EVENT_CHANPRESS:
+		ossev.l.code = EV_CHN_COMMON;
+		ossev.l.p1 = ev->data.control.param;
+		ossev.l.val = ev->data.control.value;
+		ossev.l.chn = ev->data.control.channel;
+		break;
+	case SNDRV_SEQ_EVENT_PITCHBEND:
+		ossev.l.code = EV_CHN_COMMON;
+		ossev.l.val = ev->data.control.value + 8192;
+		ossev.l.chn = ev->data.control.channel;
+		break;
+	}
+	
+	snd_seq_oss_readq_put_timestamp(dp->readq, ev->time.tick, dp->seq_mode);
+	snd_seq_oss_readq_put_event(dp->readq, &ossev);
+
+	return 0;
+}
+
+/*
+ * decode event and send MIDI bytes to read queue
+ */
+static int
+send_midi_event(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, seq_oss_midi_t *mdev)
+{
+	char msg[32];
+	int len;
+	
+	snd_seq_oss_readq_put_timestamp(dp->readq, ev->time.tick, dp->seq_mode);
+	if (!dp->timer->running)
+		len = snd_seq_oss_timer_start(dp->timer);
+	if (ev->type == SNDRV_SEQ_EVENT_SYSEX) {
+		if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) == SNDRV_SEQ_EVENT_LENGTH_VARIABLE)
+			snd_seq_oss_readq_puts(dp->readq, mdev->seq_device,
+					       ev->data.ext.ptr, ev->data.ext.len);
+	} else {
+		len = snd_midi_event_decode(mdev->coder, msg, sizeof(msg), ev);
+		if (len > 0)
+			snd_seq_oss_readq_puts(dp->readq, mdev->seq_device, msg, len);
+	}
+
+	return 0;
+}
+
+
+/*
+ * dump midi data
+ * return 0 : enqueued
+ *        non-zero : invalid - ignored
+ */
+int
+snd_seq_oss_midi_putc(seq_oss_devinfo_t *dp, int dev, unsigned char c, snd_seq_event_t *ev)
+{
+	seq_oss_midi_t *mdev;
+
+	if ((mdev = get_mididev(dp, dev)) == NULL)
+		return -ENODEV;
+	if (snd_midi_event_encode_byte(mdev->coder, c, ev) > 0) {
+		snd_seq_oss_fill_addr(dp, ev, mdev->client, mdev->port);
+		snd_use_lock_free(&mdev->use_lock);
+		return 0;
+	}
+	snd_use_lock_free(&mdev->use_lock);
+	return -EINVAL;
+}
+
+/*
+ * create OSS compatible midi_info record
+ */
+int
+snd_seq_oss_midi_make_info(seq_oss_devinfo_t *dp, int dev, struct midi_info *inf)
+{
+	seq_oss_midi_t *mdev;
+
+	if ((mdev = get_mididev(dp, dev)) == NULL)
+		return -ENXIO;
+	inf->device = dev;
+	inf->dev_type = 0; /* FIXME: ?? */
+	inf->capabilities = 0; /* FIXME: ?? */
+	strlcpy(inf->name, mdev->name, sizeof(inf->name));
+	snd_use_lock_free(&mdev->use_lock);
+	return 0;
+}
+
+
+/*
+ * proc interface
+ */
+static char *
+capmode_str(int val)
+{
+	val &= PERM_READ|PERM_WRITE;
+	if (val == (PERM_READ|PERM_WRITE))
+		return "read/write";
+	else if (val == PERM_READ)
+		return "read";
+	else if (val == PERM_WRITE)
+		return "write";
+	else
+		return "none";
+}
+
+void
+snd_seq_oss_midi_info_read(snd_info_buffer_t *buf)
+{
+	int i;
+	seq_oss_midi_t *mdev;
+
+	snd_iprintf(buf, "\nNumber of MIDI devices: %d\n", max_midi_devs);
+	for (i = 0; i < max_midi_devs; i++) {
+		snd_iprintf(buf, "\nmidi %d: ", i);
+		mdev = get_mdev(i);
+		if (mdev == NULL) {
+			snd_iprintf(buf, "*empty*\n");
+			continue;
+		}
+		snd_iprintf(buf, "[%s] ALSA port %d:%d\n", mdev->name,
+			    mdev->client, mdev->port);
+		snd_iprintf(buf, "  capability %s / opened %s\n",
+			    capmode_str(mdev->flags),
+			    capmode_str(mdev->opened));
+		snd_use_lock_free(&mdev->use_lock);
+	}
+}
+
diff --git a/sound/core/seq/oss/seq_oss_midi.h b/sound/core/seq/oss/seq_oss_midi.h
new file mode 100644
index 000000000000..462484b2b6fe
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_midi.h
@@ -0,0 +1,49 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * midi device information
+ *
+ * Copyright (C) 1998,99 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 __SEQ_OSS_MIDI_H
+#define __SEQ_OSS_MIDI_H
+
+#include "seq_oss_device.h"
+#include <sound/seq_oss_legacy.h>
+
+typedef struct seq_oss_midi_t seq_oss_midi_t;
+
+int snd_seq_oss_midi_lookup_ports(int client);
+int snd_seq_oss_midi_check_new_port(snd_seq_port_info_t *pinfo);
+int snd_seq_oss_midi_check_exit_port(int client, int port);
+void snd_seq_oss_midi_clear_all(void);
+
+void snd_seq_oss_midi_setup(seq_oss_devinfo_t *dp);
+void snd_seq_oss_midi_cleanup(seq_oss_devinfo_t *dp);
+
+int snd_seq_oss_midi_open(seq_oss_devinfo_t *dp, int dev, int file_mode);
+void snd_seq_oss_midi_open_all(seq_oss_devinfo_t *dp, int file_mode);
+int snd_seq_oss_midi_close(seq_oss_devinfo_t *dp, int dev);
+void snd_seq_oss_midi_reset(seq_oss_devinfo_t *dp, int dev);
+int snd_seq_oss_midi_putc(seq_oss_devinfo_t *dp, int dev, unsigned char c, snd_seq_event_t *ev);
+int snd_seq_oss_midi_input(snd_seq_event_t *ev, int direct, void *private);
+int snd_seq_oss_midi_filemode(seq_oss_devinfo_t *dp, int dev);
+int snd_seq_oss_midi_make_info(seq_oss_devinfo_t *dp, int dev, struct midi_info *inf);
+void snd_seq_oss_midi_get_addr(seq_oss_devinfo_t *dp, int dev, snd_seq_addr_t *addr);
+
+#endif
diff --git a/sound/core/seq/oss/seq_oss_readq.c b/sound/core/seq/oss/seq_oss_readq.c
new file mode 100644
index 000000000000..0a6f2a64f692
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_readq.c
@@ -0,0 +1,234 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * seq_oss_readq.c - MIDI input queue
+ *
+ * Copyright (C) 1998,99 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 "seq_oss_readq.h"
+#include "seq_oss_event.h"
+#include <sound/seq_oss_legacy.h>
+#include "../seq_lock.h"
+#include <linux/wait.h>
+
+/*
+ * constants
+ */
+//#define SNDRV_SEQ_OSS_MAX_TIMEOUT	(unsigned long)(-1)
+#define SNDRV_SEQ_OSS_MAX_TIMEOUT	(HZ * 3600)
+
+
+/*
+ * prototypes
+ */
+
+
+/*
+ * create a read queue
+ */
+seq_oss_readq_t *
+snd_seq_oss_readq_new(seq_oss_devinfo_t *dp, int maxlen)
+{
+	seq_oss_readq_t *q;
+
+	if ((q = kcalloc(1, sizeof(*q), GFP_KERNEL)) == NULL) {
+		snd_printk(KERN_ERR "can't malloc read queue\n");
+		return NULL;
+	}
+
+	if ((q->q = kcalloc(maxlen, sizeof(evrec_t), GFP_KERNEL)) == NULL) {
+		snd_printk(KERN_ERR "can't malloc read queue buffer\n");
+		kfree(q);
+		return NULL;
+	}
+
+	q->maxlen = maxlen;
+	q->qlen = 0;
+	q->head = q->tail = 0;
+	init_waitqueue_head(&q->midi_sleep);
+	spin_lock_init(&q->lock);
+	q->pre_event_timeout = SNDRV_SEQ_OSS_MAX_TIMEOUT;
+	q->input_time = (unsigned long)-1;
+
+	return q;
+}
+
+/*
+ * delete the read queue
+ */
+void
+snd_seq_oss_readq_delete(seq_oss_readq_t *q)
+{
+	if (q) {
+		kfree(q->q);
+		kfree(q);
+	}
+}
+
+/*
+ * reset the read queue
+ */
+void
+snd_seq_oss_readq_clear(seq_oss_readq_t *q)
+{
+	if (q->qlen) {
+		q->qlen = 0;
+		q->head = q->tail = 0;
+	}
+	/* if someone sleeping, wake'em up */
+	if (waitqueue_active(&q->midi_sleep))
+		wake_up(&q->midi_sleep);
+	q->input_time = (unsigned long)-1;
+}
+
+/*
+ * put a midi byte
+ */
+int
+snd_seq_oss_readq_puts(seq_oss_readq_t *q, int dev, unsigned char *data, int len)
+{
+	evrec_t rec;
+	int result;
+
+	memset(&rec, 0, sizeof(rec));
+	rec.c[0] = SEQ_MIDIPUTC;
+	rec.c[2] = dev;
+
+	while (len-- > 0) {
+		rec.c[1] = *data++;
+		result = snd_seq_oss_readq_put_event(q, &rec);
+		if (result < 0)
+			return result;
+	}
+	return 0;
+}
+
+/*
+ * copy an event to input queue:
+ * return zero if enqueued
+ */
+int
+snd_seq_oss_readq_put_event(seq_oss_readq_t *q, evrec_t *ev)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&q->lock, flags);
+	if (q->qlen >= q->maxlen - 1) {
+		spin_unlock_irqrestore(&q->lock, flags);
+		return -ENOMEM;
+	}
+
+	memcpy(&q->q[q->tail], ev, sizeof(*ev));
+	q->tail = (q->tail + 1) % q->maxlen;
+	q->qlen++;
+
+	/* wake up sleeper */
+	if (waitqueue_active(&q->midi_sleep))
+		wake_up(&q->midi_sleep);
+
+	spin_unlock_irqrestore(&q->lock, flags);
+
+	return 0;
+}
+
+
+/*
+ * pop queue
+ * caller must hold lock
+ */
+int
+snd_seq_oss_readq_pick(seq_oss_readq_t *q, evrec_t *rec)
+{
+	if (q->qlen == 0)
+		return -EAGAIN;
+	memcpy(rec, &q->q[q->head], sizeof(*rec));
+	return 0;
+}
+
+/*
+ * sleep until ready
+ */
+void
+snd_seq_oss_readq_wait(seq_oss_readq_t *q)
+{
+	wait_event_interruptible_timeout(q->midi_sleep,
+					 (q->qlen > 0 || q->head == q->tail),
+					 q->pre_event_timeout);
+}
+
+/*
+ * drain one record
+ * caller must hold lock
+ */
+void
+snd_seq_oss_readq_free(seq_oss_readq_t *q)
+{
+	if (q->qlen > 0) {
+		q->head = (q->head + 1) % q->maxlen;
+		q->qlen--;
+	}
+}
+
+/*
+ * polling/select:
+ * return non-zero if readq is not empty.
+ */
+unsigned int
+snd_seq_oss_readq_poll(seq_oss_readq_t *q, struct file *file, poll_table *wait)
+{
+	poll_wait(file, &q->midi_sleep, wait);
+	return q->qlen;
+}
+
+/*
+ * put a timestamp
+ */
+int
+snd_seq_oss_readq_put_timestamp(seq_oss_readq_t *q, unsigned long curt, int seq_mode)
+{
+	if (curt != q->input_time) {
+		evrec_t rec;
+		memset(&rec, 0, sizeof(rec));
+		switch (seq_mode) {
+		case SNDRV_SEQ_OSS_MODE_SYNTH:
+			rec.echo = (curt << 8) | SEQ_WAIT;
+			snd_seq_oss_readq_put_event(q, &rec);
+			break;
+		case SNDRV_SEQ_OSS_MODE_MUSIC:
+			rec.t.code = EV_TIMING;
+			rec.t.cmd = TMR_WAIT_ABS;
+			rec.t.time = curt;
+			snd_seq_oss_readq_put_event(q, &rec);
+			break;
+		}
+		q->input_time = curt;
+	}
+	return 0;
+}
+
+
+/*
+ * proc interface
+ */
+void
+snd_seq_oss_readq_info_read(seq_oss_readq_t *q, snd_info_buffer_t *buf)
+{
+	snd_iprintf(buf, "  read queue [%s] length = %d : tick = %ld\n",
+		    (waitqueue_active(&q->midi_sleep) ? "sleeping":"running"),
+		    q->qlen, q->input_time);
+}
diff --git a/sound/core/seq/oss/seq_oss_readq.h b/sound/core/seq/oss/seq_oss_readq.h
new file mode 100644
index 000000000000..303b9298f206
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_readq.h
@@ -0,0 +1,56 @@
+/*
+ * OSS compatible sequencer driver
+ * read fifo queue
+ *
+ * Copyright (C) 1998,99 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 __SEQ_OSS_READQ_H
+#define __SEQ_OSS_READQ_H
+
+#include "seq_oss_device.h"
+
+
+/*
+ * definition of read queue
+ */
+struct seq_oss_readq_t {
+	evrec_t *q;
+	int qlen;
+	int maxlen;
+	int head, tail;
+	unsigned long pre_event_timeout;
+	unsigned long input_time;
+	wait_queue_head_t midi_sleep;
+	spinlock_t lock;
+};
+
+seq_oss_readq_t *snd_seq_oss_readq_new(seq_oss_devinfo_t *dp, int maxlen);
+void snd_seq_oss_readq_delete(seq_oss_readq_t *q);
+void snd_seq_oss_readq_clear(seq_oss_readq_t *readq);
+unsigned int snd_seq_oss_readq_poll(seq_oss_readq_t *readq, struct file *file, poll_table *wait);
+int snd_seq_oss_readq_puts(seq_oss_readq_t *readq, int dev, unsigned char *data, int len);
+int snd_seq_oss_readq_put_event(seq_oss_readq_t *readq, evrec_t *ev);
+int snd_seq_oss_readq_put_timestamp(seq_oss_readq_t *readq, unsigned long curt, int seq_mode);
+int snd_seq_oss_readq_pick(seq_oss_readq_t *q, evrec_t *rec);
+void snd_seq_oss_readq_wait(seq_oss_readq_t *q);
+void snd_seq_oss_readq_free(seq_oss_readq_t *q);
+
+#define snd_seq_oss_readq_lock(q, flags) spin_lock_irqsave(&(q)->lock, flags)
+#define snd_seq_oss_readq_unlock(q, flags) spin_unlock_irqrestore(&(q)->lock, flags)
+
+#endif
diff --git a/sound/core/seq/oss/seq_oss_rw.c b/sound/core/seq/oss/seq_oss_rw.c
new file mode 100644
index 000000000000..1d8fbd22e3e3
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_rw.c
@@ -0,0 +1,216 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * read/write/select interface to device file
+ *
+ * Copyright (C) 1998,99 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 "seq_oss_device.h"
+#include "seq_oss_readq.h"
+#include "seq_oss_writeq.h"
+#include "seq_oss_synth.h"
+#include <sound/seq_oss_legacy.h>
+#include "seq_oss_event.h"
+#include "seq_oss_timer.h"
+#include "../seq_clientmgr.h"
+
+
+/*
+ * protoypes
+ */
+static int insert_queue(seq_oss_devinfo_t *dp, evrec_t *rec, struct file *opt);
+
+
+/*
+ * read interface
+ */
+
+int
+snd_seq_oss_read(seq_oss_devinfo_t *dp, char __user *buf, int count)
+{
+	seq_oss_readq_t *readq = dp->readq;
+	int result = 0, err = 0;
+	int ev_len;
+	evrec_t rec;
+	unsigned long flags;
+
+	if (readq == NULL || ! is_read_mode(dp->file_mode))
+		return -ENXIO;
+
+	while (count >= SHORT_EVENT_SIZE) {
+		snd_seq_oss_readq_lock(readq, flags);
+		err = snd_seq_oss_readq_pick(readq, &rec);
+		if (err == -EAGAIN &&
+		    !is_nonblock_mode(dp->file_mode) && result == 0) {
+			snd_seq_oss_readq_unlock(readq, flags);
+			snd_seq_oss_readq_wait(readq);
+			snd_seq_oss_readq_lock(readq, flags);
+			if (signal_pending(current))
+				err = -ERESTARTSYS;
+			else
+				err = snd_seq_oss_readq_pick(readq, &rec);
+		}
+		if (err < 0) {
+			snd_seq_oss_readq_unlock(readq, flags);
+			break;
+		}
+		ev_len = ev_length(&rec);
+		if (ev_len < count) {
+			snd_seq_oss_readq_unlock(readq, flags);
+			break;
+		}
+		snd_seq_oss_readq_free(readq);
+		snd_seq_oss_readq_unlock(readq, flags);
+		if (copy_to_user(buf, &rec, ev_len)) {
+			err = -EFAULT;
+			break;
+		}
+		result += ev_len;
+		buf += ev_len;
+		count -= ev_len;
+	}
+	return result > 0 ? result : err;
+}
+
+
+/*
+ * write interface
+ */
+
+int
+snd_seq_oss_write(seq_oss_devinfo_t *dp, const char __user *buf, int count, struct file *opt)
+{
+	int result = 0, err = 0;
+	int ev_size, fmt;
+	evrec_t rec;
+
+	if (! is_write_mode(dp->file_mode) || dp->writeq == NULL)
+		return -ENXIO;
+
+	while (count >= SHORT_EVENT_SIZE) {
+		if (copy_from_user(&rec, buf, SHORT_EVENT_SIZE)) {
+			err = -EFAULT;
+			break;
+		}
+		if (rec.s.code == SEQ_FULLSIZE) {
+			/* load patch */
+			if (result > 0) {
+				err = -EINVAL;
+				break;
+			}
+			fmt = (*(unsigned short *)rec.c) & 0xffff;
+			/* FIXME the return value isn't correct */
+			return snd_seq_oss_synth_load_patch(dp, rec.s.dev,
+							    fmt, buf, 0, count);
+		}
+		if (ev_is_long(&rec)) {
+			/* extended code */
+			if (rec.s.code == SEQ_EXTENDED &&
+			    dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) {
+				err = -EINVAL;
+				break;
+			}
+			ev_size = LONG_EVENT_SIZE;
+			if (count < ev_size)
+				break;
+			/* copy the reset 4 bytes */
+			if (copy_from_user(rec.c + SHORT_EVENT_SIZE,
+					   buf + SHORT_EVENT_SIZE,
+					   LONG_EVENT_SIZE - SHORT_EVENT_SIZE)) {
+				err = -EFAULT;
+				break;
+			}
+		} else {
+			/* old-type code */
+			if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) {
+				err = -EINVAL;
+				break;
+			}
+			ev_size = SHORT_EVENT_SIZE;
+		}
+
+		/* insert queue */
+		if ((err = insert_queue(dp, &rec, opt)) < 0)
+			break;
+
+		result += ev_size;
+		buf += ev_size;
+		count -= ev_size;
+	}
+	return result > 0 ? result : err;
+}
+
+
+/*
+ * insert event record to write queue
+ * return: 0 = OK, non-zero = NG
+ */
+static int
+insert_queue(seq_oss_devinfo_t *dp, evrec_t *rec, struct file *opt)
+{
+	int rc = 0;
+	snd_seq_event_t event;
+
+	/* if this is a timing event, process the current time */
+	if (snd_seq_oss_process_timer_event(dp->timer, rec))
+		return 0; /* no need to insert queue */
+
+	/* parse this event */
+	memset(&event, 0, sizeof(event));
+	/* set dummy -- to be sure */
+	event.type = SNDRV_SEQ_EVENT_NOTEOFF;
+	snd_seq_oss_fill_addr(dp, &event, dp->addr.port, dp->addr.client);
+
+	if (snd_seq_oss_process_event(dp, rec, &event))
+		return 0; /* invalid event - no need to insert queue */
+
+	event.time.tick = snd_seq_oss_timer_cur_tick(dp->timer);
+	if (dp->timer->realtime || !dp->timer->running) {
+		snd_seq_oss_dispatch(dp, &event, 0, 0);
+	} else {
+		if (is_nonblock_mode(dp->file_mode))
+			rc = snd_seq_kernel_client_enqueue(dp->cseq, &event, 0, 0);
+		else
+			rc = snd_seq_kernel_client_enqueue_blocking(dp->cseq, &event, opt, 0, 0);
+	}
+	return rc;
+}
+		
+
+/*
+ * select / poll
+ */
+  
+unsigned int
+snd_seq_oss_poll(seq_oss_devinfo_t *dp, struct file *file, poll_table * wait)
+{
+	unsigned int mask = 0;
+
+	/* input */
+	if (dp->readq && is_read_mode(dp->file_mode)) {
+		if (snd_seq_oss_readq_poll(dp->readq, file, wait))
+			mask |= POLLIN | POLLRDNORM;
+	}
+
+	/* output */
+	if (dp->writeq && is_write_mode(dp->file_mode)) {
+		if (snd_seq_kernel_client_write_poll(dp->cseq, file, wait))
+			mask |= POLLOUT | POLLWRNORM;
+	}
+	return mask;
+}
diff --git a/sound/core/seq/oss/seq_oss_synth.c b/sound/core/seq/oss/seq_oss_synth.c
new file mode 100644
index 000000000000..638cc148706d
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_synth.c
@@ -0,0 +1,659 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * synth device handlers
+ *
+ * Copyright (C) 1998,99 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 "seq_oss_synth.h"
+#include "seq_oss_midi.h"
+#include "../seq_lock.h"
+#include <linux/init.h>
+
+/*
+ * constants
+ */
+#define SNDRV_SEQ_OSS_MAX_SYNTH_NAME	30
+#define MAX_SYSEX_BUFLEN		128
+
+
+/*
+ * definition of synth info records
+ */
+
+/* sysex buffer */
+struct seq_oss_synth_sysex_t {
+	int len;
+	int skip;
+	unsigned char buf[MAX_SYSEX_BUFLEN];
+};
+
+/* synth info */
+struct seq_oss_synth_t {
+	int seq_device;
+
+	/* for synth_info */
+	int synth_type;
+	int synth_subtype;
+	int nr_voices;
+
+	char name[SNDRV_SEQ_OSS_MAX_SYNTH_NAME];
+	snd_seq_oss_callback_t oper;
+
+	int opened;
+
+	void *private_data;
+	snd_use_lock_t use_lock;
+};
+
+
+/*
+ * device table
+ */
+static int max_synth_devs;
+static seq_oss_synth_t *synth_devs[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS];
+static seq_oss_synth_t midi_synth_dev = {
+	-1, /* seq_device */
+	SYNTH_TYPE_MIDI, /* synth_type */
+	0, /* synth_subtype */
+	16, /* nr_voices */
+	"MIDI", /* name */
+};
+
+static DEFINE_SPINLOCK(register_lock);
+
+/*
+ * prototypes
+ */
+static seq_oss_synth_t *get_synthdev(seq_oss_devinfo_t *dp, int dev);
+static void reset_channels(seq_oss_synthinfo_t *info);
+
+/*
+ * global initialization
+ */
+void __init
+snd_seq_oss_synth_init(void)
+{
+	snd_use_lock_init(&midi_synth_dev.use_lock);
+}
+
+/*
+ * registration of the synth device
+ */
+int
+snd_seq_oss_synth_register(snd_seq_device_t *dev)
+{
+	int i;
+	seq_oss_synth_t *rec;
+	snd_seq_oss_reg_t *reg = SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	unsigned long flags;
+
+	if ((rec = kcalloc(1, sizeof(*rec), GFP_KERNEL)) == NULL) {
+		snd_printk(KERN_ERR "can't malloc synth info\n");
+		return -ENOMEM;
+	}
+	rec->seq_device = -1;
+	rec->synth_type = reg->type;
+	rec->synth_subtype = reg->subtype;
+	rec->nr_voices = reg->nvoices;
+	rec->oper = reg->oper;
+	rec->private_data = reg->private_data;
+	rec->opened = 0;
+	snd_use_lock_init(&rec->use_lock);
+
+	/* copy and truncate the name of synth device */
+	strlcpy(rec->name, dev->name, sizeof(rec->name));
+
+	/* registration */
+	spin_lock_irqsave(&register_lock, flags);
+	for (i = 0; i < max_synth_devs; i++) {
+		if (synth_devs[i] == NULL)
+			break;
+	}
+	if (i >= max_synth_devs) {
+		if (max_synth_devs >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS) {
+			spin_unlock_irqrestore(&register_lock, flags);
+			snd_printk(KERN_ERR "no more synth slot\n");
+			kfree(rec);
+			return -ENOMEM;
+		}
+		max_synth_devs++;
+	}
+	rec->seq_device = i;
+	synth_devs[i] = rec;
+	debug_printk(("synth %s registered %d\n", rec->name, i));
+	spin_unlock_irqrestore(&register_lock, flags);
+	dev->driver_data = rec;
+#ifdef SNDRV_OSS_INFO_DEV_SYNTH
+	if (i < SNDRV_CARDS)
+		snd_oss_info_register(SNDRV_OSS_INFO_DEV_SYNTH, i, rec->name);
+#endif
+	return 0;
+}
+
+
+int
+snd_seq_oss_synth_unregister(snd_seq_device_t *dev)
+{
+	int index;
+	seq_oss_synth_t *rec = dev->driver_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&register_lock, flags);
+	for (index = 0; index < max_synth_devs; index++) {
+		if (synth_devs[index] == rec)
+			break;
+	}
+	if (index >= max_synth_devs) {
+		spin_unlock_irqrestore(&register_lock, flags);
+		snd_printk(KERN_ERR "can't unregister synth\n");
+		return -EINVAL;
+	}
+	synth_devs[index] = NULL;
+	if (index == max_synth_devs - 1) {
+		for (index--; index >= 0; index--) {
+			if (synth_devs[index])
+				break;
+		}
+		max_synth_devs = index + 1;
+	}
+	spin_unlock_irqrestore(&register_lock, flags);
+#ifdef SNDRV_OSS_INFO_DEV_SYNTH
+	if (rec->seq_device < SNDRV_CARDS)
+		snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_SYNTH, rec->seq_device);
+#endif
+
+	snd_use_lock_sync(&rec->use_lock);
+	kfree(rec);
+
+	return 0;
+}
+
+
+/*
+ */
+static seq_oss_synth_t *
+get_sdev(int dev)
+{
+	seq_oss_synth_t *rec;
+	unsigned long flags;
+
+	spin_lock_irqsave(&register_lock, flags);
+	rec = synth_devs[dev];
+	if (rec)
+		snd_use_lock_use(&rec->use_lock);
+	spin_unlock_irqrestore(&register_lock, flags);
+	return rec;
+}
+
+
+/*
+ * set up synth tables
+ */
+
+void
+snd_seq_oss_synth_setup(seq_oss_devinfo_t *dp)
+{
+	int i;
+	seq_oss_synth_t *rec;
+	seq_oss_synthinfo_t *info;
+
+	dp->max_synthdev = max_synth_devs;
+	dp->synth_opened = 0;
+	memset(dp->synths, 0, sizeof(dp->synths));
+	for (i = 0; i < dp->max_synthdev; i++) {
+		rec = get_sdev(i);
+		if (rec == NULL)
+			continue;
+		if (rec->oper.open == NULL || rec->oper.close == NULL) {
+			snd_use_lock_free(&rec->use_lock);
+			continue;
+		}
+		info = &dp->synths[i];
+		info->arg.app_index = dp->port;
+		info->arg.file_mode = dp->file_mode;
+		info->arg.seq_mode = dp->seq_mode;
+		if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH)
+			info->arg.event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS;
+		else
+			info->arg.event_passing = SNDRV_SEQ_OSS_PASS_EVENTS;
+		info->opened = 0;
+		if (!try_module_get(rec->oper.owner)) {
+			snd_use_lock_free(&rec->use_lock);
+			continue;
+		}
+		if (rec->oper.open(&info->arg, rec->private_data) < 0) {
+			module_put(rec->oper.owner);
+			snd_use_lock_free(&rec->use_lock);
+			continue;
+		}
+		info->nr_voices = rec->nr_voices;
+		if (info->nr_voices > 0) {
+			info->ch = kcalloc(info->nr_voices, sizeof(seq_oss_chinfo_t), GFP_KERNEL);
+			if (!info->ch)
+				BUG();
+			reset_channels(info);
+		}
+		debug_printk(("synth %d assigned\n", i));
+		info->opened++;
+		rec->opened++;
+		dp->synth_opened++;
+		snd_use_lock_free(&rec->use_lock);
+	}
+}
+
+
+/*
+ * set up synth tables for MIDI emulation - /dev/music mode only
+ */
+
+void
+snd_seq_oss_synth_setup_midi(seq_oss_devinfo_t *dp)
+{
+	int i;
+
+	if (dp->max_synthdev >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS)
+		return;
+
+	for (i = 0; i < dp->max_mididev; i++) {
+		seq_oss_synthinfo_t *info;
+		info = &dp->synths[dp->max_synthdev];
+		if (snd_seq_oss_midi_open(dp, i, dp->file_mode) < 0)
+			continue;
+		info->arg.app_index = dp->port;
+		info->arg.file_mode = dp->file_mode;
+		info->arg.seq_mode = dp->seq_mode;
+		info->arg.private_data = info;
+		info->is_midi = 1;
+		info->midi_mapped = i;
+		info->arg.event_passing = SNDRV_SEQ_OSS_PASS_EVENTS;
+		snd_seq_oss_midi_get_addr(dp, i, &info->arg.addr);
+		info->opened = 1;
+		midi_synth_dev.opened++;
+		dp->max_synthdev++;
+		if (dp->max_synthdev >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS)
+			break;
+	}
+}
+
+
+/*
+ * clean up synth tables
+ */
+
+void
+snd_seq_oss_synth_cleanup(seq_oss_devinfo_t *dp)
+{
+	int i;
+	seq_oss_synth_t *rec;
+	seq_oss_synthinfo_t *info;
+
+	snd_assert(dp->max_synthdev <= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS, return);
+	for (i = 0; i < dp->max_synthdev; i++) {
+		info = &dp->synths[i];
+		if (! info->opened)
+			continue;
+		if (info->is_midi) {
+			if (midi_synth_dev.opened > 0) {
+				snd_seq_oss_midi_close(dp, info->midi_mapped);
+				midi_synth_dev.opened--;
+			}
+		} else {
+			rec = get_sdev(i);
+			if (rec == NULL)
+				continue;
+			if (rec->opened > 0) {
+				debug_printk(("synth %d closed\n", i));
+				rec->oper.close(&info->arg);
+				module_put(rec->oper.owner);
+				rec->opened = 0;
+			}
+			snd_use_lock_free(&rec->use_lock);
+		}
+		if (info->sysex) {
+			kfree(info->sysex);
+			info->sysex = NULL;
+		}
+		if (info->ch) {
+			kfree(info->ch);
+			info->ch = NULL;
+		}
+	}
+	dp->synth_opened = 0;
+	dp->max_synthdev = 0;
+}
+
+/*
+ * check if the specified device is MIDI mapped device
+ */
+static int
+is_midi_dev(seq_oss_devinfo_t *dp, int dev)
+{
+	if (dev < 0 || dev >= dp->max_synthdev)
+		return 0;
+	if (dp->synths[dev].is_midi)
+		return 1;
+	return 0;
+}
+
+/*
+ * return synth device information pointer
+ */
+static seq_oss_synth_t *
+get_synthdev(seq_oss_devinfo_t *dp, int dev)
+{
+	seq_oss_synth_t *rec;
+	if (dev < 0 || dev >= dp->max_synthdev)
+		return NULL;
+	if (! dp->synths[dev].opened)
+		return NULL;
+	if (dp->synths[dev].is_midi)
+		return &midi_synth_dev;
+	if ((rec = get_sdev(dev)) == NULL)
+		return NULL;
+	if (! rec->opened) {
+		snd_use_lock_free(&rec->use_lock);
+		return NULL;
+	}
+	return rec;
+}
+
+
+/*
+ * reset note and velocity on each channel.
+ */
+static void
+reset_channels(seq_oss_synthinfo_t *info)
+{
+	int i;
+	if (info->ch == NULL || ! info->nr_voices)
+		return;
+	for (i = 0; i < info->nr_voices; i++) {
+		info->ch[i].note = -1;
+		info->ch[i].vel = 0;
+	}
+}
+
+
+/*
+ * reset synth device:
+ * call reset callback.  if no callback is defined, send a heartbeat
+ * event to the corresponding port.
+ */
+void
+snd_seq_oss_synth_reset(seq_oss_devinfo_t *dp, int dev)
+{
+	seq_oss_synth_t *rec;
+	seq_oss_synthinfo_t *info;
+
+	snd_assert(dev >= 0 && dev < dp->max_synthdev, return);
+	info = &dp->synths[dev];
+	if (! info->opened)
+		return;
+	if (info->sysex)
+		info->sysex->len = 0; /* reset sysex */
+	reset_channels(info);
+	if (info->is_midi) {
+		if (midi_synth_dev.opened <= 0)
+			return;
+		snd_seq_oss_midi_reset(dp, info->midi_mapped);
+		/* reopen the device */
+		snd_seq_oss_midi_close(dp, dev);
+		if (snd_seq_oss_midi_open(dp, info->midi_mapped,
+					  dp->file_mode) < 0) {
+			midi_synth_dev.opened--;
+			info->opened = 0;
+			if (info->sysex) {
+				kfree(info->sysex);
+				info->sysex = NULL;
+			}
+			if (info->ch) {
+				kfree(info->ch);
+				info->ch = NULL;
+			}
+		}
+		return;
+	}
+
+	rec = get_sdev(dev);
+	if (rec == NULL)
+		return;
+	if (rec->oper.reset) {
+		rec->oper.reset(&info->arg);
+	} else {
+		snd_seq_event_t ev;
+		memset(&ev, 0, sizeof(ev));
+		snd_seq_oss_fill_addr(dp, &ev, info->arg.addr.client,
+				      info->arg.addr.port);
+		ev.type = SNDRV_SEQ_EVENT_RESET;
+		snd_seq_oss_dispatch(dp, &ev, 0, 0);
+	}
+	snd_use_lock_free(&rec->use_lock);
+}
+
+
+/*
+ * load a patch record:
+ * call load_patch callback function
+ */
+int
+snd_seq_oss_synth_load_patch(seq_oss_devinfo_t *dp, int dev, int fmt,
+			    const char __user *buf, int p, int c)
+{
+	seq_oss_synth_t *rec;
+	int rc;
+
+	if (dev < 0 || dev >= dp->max_synthdev)
+		return -ENXIO;
+
+	if (is_midi_dev(dp, dev))
+		return 0;
+	if ((rec = get_synthdev(dp, dev)) == NULL)
+		return -ENXIO;
+
+	if (rec->oper.load_patch == NULL)
+		rc = -ENXIO;
+	else
+		rc = rec->oper.load_patch(&dp->synths[dev].arg, fmt, buf, p, c);
+	snd_use_lock_free(&rec->use_lock);
+	return rc;
+}
+
+/*
+ * check if the device is valid synth device
+ */
+int
+snd_seq_oss_synth_is_valid(seq_oss_devinfo_t *dp, int dev)
+{
+	seq_oss_synth_t *rec;
+	rec = get_synthdev(dp, dev);
+	if (rec) {
+		snd_use_lock_free(&rec->use_lock);
+		return 1;
+	}
+	return 0;
+}
+
+
+/*
+ * receive OSS 6 byte sysex packet:
+ * the full sysex message will be sent if it reaches to the end of data
+ * (0xff).
+ */
+int
+snd_seq_oss_synth_sysex(seq_oss_devinfo_t *dp, int dev, unsigned char *buf, snd_seq_event_t *ev)
+{
+	int i, send;
+	unsigned char *dest;
+	seq_oss_synth_sysex_t *sysex;
+
+	if (! snd_seq_oss_synth_is_valid(dp, dev))
+		return -ENXIO;
+
+	sysex = dp->synths[dev].sysex;
+	if (sysex == NULL) {
+		sysex = kcalloc(1, sizeof(*sysex), GFP_KERNEL);
+		if (sysex == NULL)
+			return -ENOMEM;
+		dp->synths[dev].sysex = sysex;
+	}
+
+	send = 0;
+	dest = sysex->buf + sysex->len;
+	/* copy 6 byte packet to the buffer */
+	for (i = 0; i < 6; i++) {
+		if (buf[i] == 0xff) {
+			send = 1;
+			break;
+		}
+		dest[i] = buf[i];
+		sysex->len++;
+		if (sysex->len >= MAX_SYSEX_BUFLEN) {
+			sysex->len = 0;
+			sysex->skip = 1;
+			break;
+		}
+	}
+
+	if (sysex->len && send) {
+		if (sysex->skip) {
+			sysex->skip = 0;
+			sysex->len = 0;
+			return -EINVAL; /* skip */
+		}
+		/* copy the data to event record and send it */
+		ev->flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE;
+		if (snd_seq_oss_synth_addr(dp, dev, ev))
+			return -EINVAL;
+		ev->data.ext.len = sysex->len;
+		ev->data.ext.ptr = sysex->buf;
+		sysex->len = 0;
+		return 0;
+	}
+
+	return -EINVAL; /* skip */
+}
+
+/*
+ * fill the event source/destination addresses
+ */
+int
+snd_seq_oss_synth_addr(seq_oss_devinfo_t *dp, int dev, snd_seq_event_t *ev)
+{
+	if (! snd_seq_oss_synth_is_valid(dp, dev))
+		return -EINVAL;
+	snd_seq_oss_fill_addr(dp, ev, dp->synths[dev].arg.addr.client,
+			      dp->synths[dev].arg.addr.port);
+	return 0;
+}
+
+
+/*
+ * OSS compatible ioctl
+ */
+int
+snd_seq_oss_synth_ioctl(seq_oss_devinfo_t *dp, int dev, unsigned int cmd, unsigned long addr)
+{
+	seq_oss_synth_t *rec;
+	int rc;
+
+	if (is_midi_dev(dp, dev))
+		return -ENXIO;
+	if ((rec = get_synthdev(dp, dev)) == NULL)
+		return -ENXIO;
+	if (rec->oper.ioctl == NULL)
+		rc = -ENXIO;
+	else
+		rc = rec->oper.ioctl(&dp->synths[dev].arg, cmd, addr);
+	snd_use_lock_free(&rec->use_lock);
+	return rc;
+}
+
+
+/*
+ * send OSS raw events - SEQ_PRIVATE and SEQ_VOLUME
+ */
+int
+snd_seq_oss_synth_raw_event(seq_oss_devinfo_t *dp, int dev, unsigned char *data, snd_seq_event_t *ev)
+{
+	if (! snd_seq_oss_synth_is_valid(dp, dev) || is_midi_dev(dp, dev))
+		return -ENXIO;
+	ev->type = SNDRV_SEQ_EVENT_OSS;
+	memcpy(ev->data.raw8.d, data, 8);
+	return snd_seq_oss_synth_addr(dp, dev, ev);
+}
+
+
+/*
+ * create OSS compatible synth_info record
+ */
+int
+snd_seq_oss_synth_make_info(seq_oss_devinfo_t *dp, int dev, struct synth_info *inf)
+{
+	seq_oss_synth_t *rec;
+
+	if (dp->synths[dev].is_midi) {
+		struct midi_info minf;
+		snd_seq_oss_midi_make_info(dp, dp->synths[dev].midi_mapped, &minf);
+		inf->synth_type = SYNTH_TYPE_MIDI;
+		inf->synth_subtype = 0;
+		inf->nr_voices = 16;
+		inf->device = dev;
+		strlcpy(inf->name, minf.name, sizeof(inf->name));
+	} else {
+		if ((rec = get_synthdev(dp, dev)) == NULL)
+			return -ENXIO;
+		inf->synth_type = rec->synth_type;
+		inf->synth_subtype = rec->synth_subtype;
+		inf->nr_voices = rec->nr_voices;
+		inf->device = dev;
+		strlcpy(inf->name, rec->name, sizeof(inf->name));
+		snd_use_lock_free(&rec->use_lock);
+	}
+	return 0;
+}
+
+
+/*
+ * proc interface
+ */
+void
+snd_seq_oss_synth_info_read(snd_info_buffer_t *buf)
+{
+	int i;
+	seq_oss_synth_t *rec;
+
+	snd_iprintf(buf, "\nNumber of synth devices: %d\n", max_synth_devs);
+	for (i = 0; i < max_synth_devs; i++) {
+		snd_iprintf(buf, "\nsynth %d: ", i);
+		rec = get_sdev(i);
+		if (rec == NULL) {
+			snd_iprintf(buf, "*empty*\n");
+			continue;
+		}
+		snd_iprintf(buf, "[%s]\n", rec->name);
+		snd_iprintf(buf, "  type 0x%x : subtype 0x%x : voices %d\n",
+			    rec->synth_type, rec->synth_subtype,
+			    rec->nr_voices);
+		snd_iprintf(buf, "  capabilities : ioctl %s / load_patch %s\n",
+			    enabled_str((long)rec->oper.ioctl),
+			    enabled_str((long)rec->oper.load_patch));
+		snd_use_lock_free(&rec->use_lock);
+	}
+}
+
diff --git a/sound/core/seq/oss/seq_oss_synth.h b/sound/core/seq/oss/seq_oss_synth.h
new file mode 100644
index 000000000000..07bc0e2cfb82
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_synth.h
@@ -0,0 +1,49 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * synth device information
+ *
+ * Copyright (C) 1998,99 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 __SEQ_OSS_SYNTH_H
+#define __SEQ_OSS_SYNTH_H
+
+#include "seq_oss_device.h"
+#include <sound/seq_oss_legacy.h>
+#include <sound/seq_device.h>
+
+typedef struct seq_oss_synth_t seq_oss_synth_t;
+
+void snd_seq_oss_synth_init(void);
+int snd_seq_oss_synth_register(snd_seq_device_t *dev);
+int snd_seq_oss_synth_unregister(snd_seq_device_t *dev);
+void snd_seq_oss_synth_setup(seq_oss_devinfo_t *dp);
+void snd_seq_oss_synth_setup_midi(seq_oss_devinfo_t *dp);
+void snd_seq_oss_synth_cleanup(seq_oss_devinfo_t *dp);
+
+void snd_seq_oss_synth_reset(seq_oss_devinfo_t *dp, int dev);
+int snd_seq_oss_synth_load_patch(seq_oss_devinfo_t *dp, int dev, int fmt, const char __user *buf, int p, int c);
+int snd_seq_oss_synth_is_valid(seq_oss_devinfo_t *dp, int dev);
+int snd_seq_oss_synth_sysex(seq_oss_devinfo_t *dp, int dev, unsigned char *buf, snd_seq_event_t *ev);
+int snd_seq_oss_synth_addr(seq_oss_devinfo_t *dp, int dev, snd_seq_event_t *ev);
+int snd_seq_oss_synth_ioctl(seq_oss_devinfo_t *dp, int dev, unsigned int cmd, unsigned long addr);
+int snd_seq_oss_synth_raw_event(seq_oss_devinfo_t *dp, int dev, unsigned char *data, snd_seq_event_t *ev);
+
+int snd_seq_oss_synth_make_info(seq_oss_devinfo_t *dp, int dev, struct synth_info *inf);
+
+#endif
diff --git a/sound/core/seq/oss/seq_oss_timer.c b/sound/core/seq/oss/seq_oss_timer.c
new file mode 100644
index 000000000000..42ca9493fa60
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_timer.c
@@ -0,0 +1,283 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * Timer control routines
+ *
+ * Copyright (C) 1998,99 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 "seq_oss_timer.h"
+#include "seq_oss_event.h"
+#include <sound/seq_oss_legacy.h>
+
+/*
+ */
+#define MIN_OSS_TEMPO		8
+#define MAX_OSS_TEMPO		360
+#define MIN_OSS_TIMEBASE	1
+#define MAX_OSS_TIMEBASE	1000
+
+/*
+ */
+static void calc_alsa_tempo(seq_oss_timer_t *timer);
+static int send_timer_event(seq_oss_devinfo_t *dp, int type, int value);
+
+
+/*
+ * create and register a new timer.
+ * if queue is not started yet, start it.
+ */
+seq_oss_timer_t *
+snd_seq_oss_timer_new(seq_oss_devinfo_t *dp)
+{
+	seq_oss_timer_t *rec;
+
+	rec = kcalloc(1, sizeof(*rec), GFP_KERNEL);
+	if (rec == NULL)
+		return NULL;
+
+	rec->dp = dp;
+	rec->cur_tick = 0;
+	rec->realtime = 0;
+	rec->running = 0;
+	rec->oss_tempo = 60;
+	rec->oss_timebase = 100;
+	calc_alsa_tempo(rec);
+
+	return rec;
+}
+
+
+/*
+ * delete timer.
+ * if no more timer exists, stop the queue.
+ */
+void
+snd_seq_oss_timer_delete(seq_oss_timer_t *rec)
+{
+	if (rec) {
+		snd_seq_oss_timer_stop(rec);
+		kfree(rec);
+	}
+}
+
+
+/*
+ * process one timing event
+ * return 1 : event proceseed -- skip this event
+ *        0 : not a timer event -- enqueue this event
+ */
+int
+snd_seq_oss_process_timer_event(seq_oss_timer_t *rec, evrec_t *ev)
+{
+	abstime_t parm = ev->t.time;
+
+	if (ev->t.code == EV_TIMING) {
+		switch (ev->t.cmd) {
+		case TMR_WAIT_REL:
+			parm += rec->cur_tick;
+			rec->realtime = 0;
+			/* continue to next */
+		case TMR_WAIT_ABS:
+			if (parm == 0) {
+				rec->realtime = 1;
+			} else if (parm >= rec->cur_tick) {
+				rec->realtime = 0;
+				rec->cur_tick = parm;
+			}
+			return 1;	/* skip this event */
+			
+		case TMR_START:
+			snd_seq_oss_timer_start(rec);
+			return 1;
+
+		}
+	} else if (ev->s.code == SEQ_WAIT) {
+		/* time = from 1 to 3 bytes */
+		parm = (ev->echo >> 8) & 0xffffff;
+		if (parm > rec->cur_tick) {
+			/* set next event time */
+			rec->cur_tick = parm;
+			rec->realtime = 0;
+		}
+		return 1;
+	}
+
+	return 0;
+}
+
+
+/*
+ * convert tempo units
+ */
+static void
+calc_alsa_tempo(seq_oss_timer_t *timer)
+{
+	timer->tempo = (60 * 1000000) / timer->oss_tempo;
+	timer->ppq = timer->oss_timebase;
+}
+
+
+/*
+ * dispatch a timer event
+ */
+static int
+send_timer_event(seq_oss_devinfo_t *dp, int type, int value)
+{
+	snd_seq_event_t ev;
+
+	memset(&ev, 0, sizeof(ev));
+	ev.type = type;
+	ev.source.client = dp->cseq;
+	ev.source.port = 0;
+	ev.dest.client = SNDRV_SEQ_CLIENT_SYSTEM;
+	ev.dest.port = SNDRV_SEQ_PORT_SYSTEM_TIMER;
+	ev.queue = dp->queue;
+	ev.data.queue.queue = dp->queue;
+	ev.data.queue.param.value = value;
+	return snd_seq_kernel_client_dispatch(dp->cseq, &ev, 1, 0);
+}
+
+/*
+ * set queue tempo and start queue
+ */
+int
+snd_seq_oss_timer_start(seq_oss_timer_t *timer)
+{
+	seq_oss_devinfo_t *dp = timer->dp;
+	snd_seq_queue_tempo_t tmprec;
+
+	if (timer->running)
+		snd_seq_oss_timer_stop(timer);
+
+	memset(&tmprec, 0, sizeof(tmprec));
+	tmprec.queue = dp->queue;
+	tmprec.ppq = timer->ppq;
+	tmprec.tempo = timer->tempo;
+	snd_seq_set_queue_tempo(dp->cseq, &tmprec);
+
+	send_timer_event(dp, SNDRV_SEQ_EVENT_START, 0);
+	timer->running = 1;
+	timer->cur_tick = 0;
+	return 0;
+}
+
+
+/*
+ * stop queue
+ */
+int
+snd_seq_oss_timer_stop(seq_oss_timer_t *timer)
+{
+	if (! timer->running)
+		return 0;
+	send_timer_event(timer->dp, SNDRV_SEQ_EVENT_STOP, 0);
+	timer->running = 0;
+	return 0;
+}
+
+
+/*
+ * continue queue
+ */
+int
+snd_seq_oss_timer_continue(seq_oss_timer_t *timer)
+{
+	if (timer->running)
+		return 0;
+	send_timer_event(timer->dp, SNDRV_SEQ_EVENT_CONTINUE, 0);
+	timer->running = 1;
+	return 0;
+}
+
+
+/*
+ * change queue tempo
+ */
+int
+snd_seq_oss_timer_tempo(seq_oss_timer_t *timer, int value)
+{
+	if (value < MIN_OSS_TEMPO)
+		value = MIN_OSS_TEMPO;
+	else if (value > MAX_OSS_TEMPO)
+		value = MAX_OSS_TEMPO;
+	timer->oss_tempo = value;
+	calc_alsa_tempo(timer);
+	if (timer->running)
+		send_timer_event(timer->dp, SNDRV_SEQ_EVENT_TEMPO, timer->tempo);
+	return 0;
+}
+
+
+/*
+ * ioctls
+ */
+int
+snd_seq_oss_timer_ioctl(seq_oss_timer_t *timer, unsigned int cmd, int __user *arg)
+{
+	int value;
+
+	if (cmd == SNDCTL_SEQ_CTRLRATE) {
+		debug_printk(("ctrl rate\n"));
+		/* if *arg == 0, just return the current rate */
+		if (get_user(value, arg))
+			return -EFAULT;
+		if (value)
+			return -EINVAL;
+		value = ((timer->oss_tempo * timer->oss_timebase) + 30) / 60;
+		return put_user(value, arg) ? -EFAULT : 0;
+	}
+
+	if (timer->dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH)
+		return 0;
+
+	switch (cmd) {
+	case SNDCTL_TMR_START:
+		debug_printk(("timer start\n"));
+		return snd_seq_oss_timer_start(timer);
+	case SNDCTL_TMR_STOP:
+		debug_printk(("timer stop\n"));
+		return snd_seq_oss_timer_stop(timer);
+	case SNDCTL_TMR_CONTINUE:
+		debug_printk(("timer continue\n"));
+		return snd_seq_oss_timer_continue(timer);
+	case SNDCTL_TMR_TEMPO:
+		debug_printk(("timer tempo\n"));
+		if (get_user(value, arg))
+			return -EFAULT;
+		return snd_seq_oss_timer_tempo(timer, value);
+	case SNDCTL_TMR_TIMEBASE:
+		debug_printk(("timer timebase\n"));
+		if (get_user(value, arg))
+			return -EFAULT;
+		if (value < MIN_OSS_TIMEBASE)
+			value = MIN_OSS_TIMEBASE;
+		else if (value > MAX_OSS_TIMEBASE)
+			value = MAX_OSS_TIMEBASE;
+		timer->oss_timebase = value;
+		calc_alsa_tempo(timer);
+		return 0;
+
+	case SNDCTL_TMR_METRONOME:
+	case SNDCTL_TMR_SELECT:
+	case SNDCTL_TMR_SOURCE:
+		debug_printk(("timer XXX\n"));
+		/* not supported */
+		return 0;
+	}
+	return 0;
+}
diff --git a/sound/core/seq/oss/seq_oss_timer.h b/sound/core/seq/oss/seq_oss_timer.h
new file mode 100644
index 000000000000..6e4dbd8504c1
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_timer.h
@@ -0,0 +1,70 @@
+/*
+ * OSS compatible sequencer driver
+ * timer handling routines
+ *
+ * Copyright (C) 1998,99 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 __SEQ_OSS_TIMER_H
+#define __SEQ_OSS_TIMER_H
+
+#include "seq_oss_device.h"
+
+/*
+ * timer information definition
+ */
+struct seq_oss_timer_t {
+	seq_oss_devinfo_t *dp;
+	reltime_t cur_tick;
+	int realtime;
+	int running;
+	int tempo, ppq;	/* ALSA queue */
+	int oss_tempo, oss_timebase;
+};	
+
+
+seq_oss_timer_t *snd_seq_oss_timer_new(seq_oss_devinfo_t *dp);
+void snd_seq_oss_timer_delete(seq_oss_timer_t *dp);
+
+int snd_seq_oss_timer_start(seq_oss_timer_t *timer);
+int snd_seq_oss_timer_stop(seq_oss_timer_t *timer);
+int snd_seq_oss_timer_continue(seq_oss_timer_t *timer);
+int snd_seq_oss_timer_tempo(seq_oss_timer_t *timer, int value);
+#define snd_seq_oss_timer_reset  snd_seq_oss_timer_start
+
+int snd_seq_oss_timer_ioctl(seq_oss_timer_t *timer, unsigned int cmd, int __user *arg);
+
+/*
+ * get current processed time
+ */
+static inline abstime_t
+snd_seq_oss_timer_cur_tick(seq_oss_timer_t *timer)
+{
+	return timer->cur_tick;
+}
+
+
+/*
+ * is realtime event?
+ */
+static inline int
+snd_seq_oss_timer_is_realtime(seq_oss_timer_t *timer)
+{
+	return timer->realtime;
+}
+
+#endif
diff --git a/sound/core/seq/oss/seq_oss_writeq.c b/sound/core/seq/oss/seq_oss_writeq.c
new file mode 100644
index 000000000000..87f85f7ee814
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_writeq.c
@@ -0,0 +1,170 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * seq_oss_writeq.c - write queue and sync
+ *
+ * Copyright (C) 1998,99 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 "seq_oss_writeq.h"
+#include "seq_oss_event.h"
+#include "seq_oss_timer.h"
+#include <sound/seq_oss_legacy.h>
+#include "../seq_lock.h"
+#include "../seq_clientmgr.h"
+#include <linux/wait.h>
+
+
+/*
+ * create a write queue record
+ */
+seq_oss_writeq_t *
+snd_seq_oss_writeq_new(seq_oss_devinfo_t *dp, int maxlen)
+{
+	seq_oss_writeq_t *q;
+	snd_seq_client_pool_t pool;
+
+	if ((q = kcalloc(1, sizeof(*q), GFP_KERNEL)) == NULL)
+		return NULL;
+	q->dp = dp;
+	q->maxlen = maxlen;
+	spin_lock_init(&q->sync_lock);
+	q->sync_event_put = 0;
+	q->sync_time = 0;
+	init_waitqueue_head(&q->sync_sleep);
+
+	memset(&pool, 0, sizeof(pool));
+	pool.client = dp->cseq;
+	pool.output_pool = maxlen;
+	pool.output_room = maxlen / 2;
+
+	snd_seq_oss_control(dp, SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, &pool);
+
+	return q;
+}
+
+/*
+ * delete the write queue
+ */
+void
+snd_seq_oss_writeq_delete(seq_oss_writeq_t *q)
+{
+	snd_seq_oss_writeq_clear(q);	/* to be sure */
+	kfree(q);
+}
+
+
+/*
+ * reset the write queue
+ */
+void
+snd_seq_oss_writeq_clear(seq_oss_writeq_t *q)
+{
+	snd_seq_remove_events_t reset;
+
+	memset(&reset, 0, sizeof(reset));
+	reset.remove_mode = SNDRV_SEQ_REMOVE_OUTPUT; /* remove all */
+	snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_REMOVE_EVENTS, &reset);
+
+	/* wake up sleepers if any */
+	snd_seq_oss_writeq_wakeup(q, 0);
+}
+
+/*
+ * wait until the write buffer has enough room
+ */
+int
+snd_seq_oss_writeq_sync(seq_oss_writeq_t *q)
+{
+	seq_oss_devinfo_t *dp = q->dp;
+	abstime_t time;
+
+	time = snd_seq_oss_timer_cur_tick(dp->timer);
+	if (q->sync_time >= time)
+		return 0; /* already finished */
+
+	if (! q->sync_event_put) {
+		snd_seq_event_t ev;
+		evrec_t *rec;
+
+		/* put echoback event */
+		memset(&ev, 0, sizeof(ev));
+		ev.flags = 0;
+		ev.type = SNDRV_SEQ_EVENT_ECHO;
+		ev.time.tick = time;
+		/* echo back to itself */
+		snd_seq_oss_fill_addr(dp, &ev, dp->addr.client, dp->addr.port);
+		rec = (evrec_t*)&ev.data;
+		rec->t.code = SEQ_SYNCTIMER;
+		rec->t.time = time;
+		q->sync_event_put = 1;
+		snd_seq_kernel_client_enqueue_blocking(dp->cseq, &ev, NULL, 0, 0);
+	}
+
+	wait_event_interruptible_timeout(q->sync_sleep, ! q->sync_event_put, HZ);
+	if (signal_pending(current))
+		/* interrupted - return 0 to finish sync */
+		q->sync_event_put = 0;
+	if (! q->sync_event_put || q->sync_time >= time)
+		return 0;
+	return 1;
+}
+
+/*
+ * wake up sync - echo event was catched
+ */
+void
+snd_seq_oss_writeq_wakeup(seq_oss_writeq_t *q, abstime_t time)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&q->sync_lock, flags);
+	q->sync_time = time;
+	q->sync_event_put = 0;
+	if (waitqueue_active(&q->sync_sleep)) {
+		wake_up(&q->sync_sleep);
+	}
+	spin_unlock_irqrestore(&q->sync_lock, flags);
+}
+
+
+/*
+ * return the unused pool size
+ */
+int
+snd_seq_oss_writeq_get_free_size(seq_oss_writeq_t *q)
+{
+	snd_seq_client_pool_t pool;
+	pool.client = q->dp->cseq;
+	snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, &pool);
+	return pool.output_free;
+}
+
+
+/*
+ * set output threshold size from ioctl
+ */
+void
+snd_seq_oss_writeq_set_output(seq_oss_writeq_t *q, int val)
+{
+	snd_seq_client_pool_t pool;
+	pool.client = q->dp->cseq;
+	snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, &pool);
+	pool.output_room = val;
+	snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, &pool);
+}
+
diff --git a/sound/core/seq/oss/seq_oss_writeq.h b/sound/core/seq/oss/seq_oss_writeq.h
new file mode 100644
index 000000000000..6a13c85e2399
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_writeq.h
@@ -0,0 +1,50 @@
+/*
+ * OSS compatible sequencer driver
+ * write priority queue
+ *
+ * Copyright (C) 1998,99 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 __SEQ_OSS_WRITEQ_H
+#define __SEQ_OSS_WRITEQ_H
+
+#include "seq_oss_device.h"
+
+
+struct seq_oss_writeq_t {
+	seq_oss_devinfo_t *dp;
+	int maxlen;
+	abstime_t sync_time;
+	int sync_event_put;
+	wait_queue_head_t sync_sleep;
+	spinlock_t sync_lock;
+};
+
+
+/*
+ * seq_oss_writeq.c
+ */
+seq_oss_writeq_t *snd_seq_oss_writeq_new(seq_oss_devinfo_t *dp, int maxlen);
+void snd_seq_oss_writeq_delete(seq_oss_writeq_t *q);
+void snd_seq_oss_writeq_clear(seq_oss_writeq_t *q);
+int snd_seq_oss_writeq_sync(seq_oss_writeq_t *q);
+void snd_seq_oss_writeq_wakeup(seq_oss_writeq_t *q, abstime_t time);
+int snd_seq_oss_writeq_get_free_size(seq_oss_writeq_t *q);
+void snd_seq_oss_writeq_set_output(seq_oss_writeq_t *q, int size);
+
+
+#endif
diff --git a/sound/core/seq/seq.c b/sound/core/seq/seq.c
new file mode 100644
index 000000000000..7449d2a62629
--- /dev/null
+++ b/sound/core/seq/seq.c
@@ -0,0 +1,147 @@
+/*
+ *  ALSA sequencer main module
+ *  Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ *   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/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+
+#include <sound/seq_kernel.h>
+#include "seq_clientmgr.h"
+#include "seq_memory.h"
+#include "seq_queue.h"
+#include "seq_lock.h"
+#include "seq_timer.h"
+#include "seq_system.h"
+#include "seq_info.h"
+#include <sound/seq_device.h>
+
+#if defined(CONFIG_SND_SEQ_DUMMY_MODULE)
+int seq_client_load[64] = {[0] = SNDRV_SEQ_CLIENT_DUMMY, [1 ... 63] = -1};
+#else
+int seq_client_load[64] = {[0 ... 63] = -1};
+#endif
+int seq_default_timer_class = SNDRV_TIMER_CLASS_GLOBAL;
+int seq_default_timer_sclass = SNDRV_TIMER_SCLASS_NONE;
+int seq_default_timer_card = -1;
+int seq_default_timer_device = SNDRV_TIMER_GLOBAL_SYSTEM;
+int seq_default_timer_subdevice = 0;
+int seq_default_timer_resolution = 0;	/* Hz */
+
+MODULE_AUTHOR("Frank van de Pol <fvdpol@coil.demon.nl>, Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer.");
+MODULE_LICENSE("GPL");
+
+module_param_array(seq_client_load, int, NULL, 0444);
+MODULE_PARM_DESC(seq_client_load, "The numbers of global (system) clients to load through kmod.");
+module_param(seq_default_timer_class, int, 0644);
+MODULE_PARM_DESC(seq_default_timer_class, "The default timer class.");
+module_param(seq_default_timer_sclass, int, 0644);
+MODULE_PARM_DESC(seq_default_timer_sclass, "The default timer slave class.");
+module_param(seq_default_timer_card, int, 0644);
+MODULE_PARM_DESC(seq_default_timer_card, "The default timer card number.");
+module_param(seq_default_timer_device, int, 0644);
+MODULE_PARM_DESC(seq_default_timer_device, "The default timer device number.");
+module_param(seq_default_timer_subdevice, int, 0644);
+MODULE_PARM_DESC(seq_default_timer_subdevice, "The default timer subdevice number.");
+module_param(seq_default_timer_resolution, int, 0644);
+MODULE_PARM_DESC(seq_default_timer_resolution, "The default timer resolution in Hz.");
+
+/*
+ *  INIT PART
+ */
+
+static int __init alsa_seq_init(void)
+{
+	int err;
+
+	snd_seq_autoload_lock();
+	if ((err = client_init_data()) < 0)
+		goto error;
+
+	/* init memory, room for selected events */
+	if ((err = snd_sequencer_memory_init()) < 0)
+		goto error;
+
+	/* init event queues */
+	if ((err = snd_seq_queues_init()) < 0)
+		goto error;
+
+	/* register sequencer device */
+	if ((err = snd_sequencer_device_init()) < 0)
+		goto error;
+
+	/* register proc interface */
+	if ((err = snd_seq_info_init()) < 0)
+		goto error;
+
+	/* register our internal client */
+	if ((err = snd_seq_system_client_init()) < 0)
+		goto error;
+
+ error:
+	snd_seq_autoload_unlock();
+	return err;
+}
+
+static void __exit alsa_seq_exit(void)
+{
+	/* unregister our internal client */
+	snd_seq_system_client_done();
+
+	/* unregister proc interface */
+	snd_seq_info_done();
+	
+	/* delete timing queues */
+	snd_seq_queues_delete();
+
+	/* unregister sequencer device */
+	snd_sequencer_device_done();
+
+	/* release event memory */
+	snd_sequencer_memory_done();
+}
+
+module_init(alsa_seq_init)
+module_exit(alsa_seq_exit)
+
+  /* seq_clientmgr.c */
+EXPORT_SYMBOL(snd_seq_create_kernel_client);
+EXPORT_SYMBOL(snd_seq_delete_kernel_client);
+EXPORT_SYMBOL(snd_seq_kernel_client_enqueue);
+EXPORT_SYMBOL(snd_seq_kernel_client_enqueue_blocking);
+EXPORT_SYMBOL(snd_seq_kernel_client_dispatch);
+EXPORT_SYMBOL(snd_seq_kernel_client_ctl);
+EXPORT_SYMBOL(snd_seq_kernel_client_write_poll);
+EXPORT_SYMBOL(snd_seq_set_queue_tempo);
+  /* seq_memory.c */
+EXPORT_SYMBOL(snd_seq_expand_var_event);
+EXPORT_SYMBOL(snd_seq_dump_var_event);
+  /* seq_ports.c */
+EXPORT_SYMBOL(snd_seq_event_port_attach);
+EXPORT_SYMBOL(snd_seq_event_port_detach);
+  /* seq_lock.c */
+#if defined(CONFIG_SMP) || defined(CONFIG_SND_DEBUG)
+/*EXPORT_SYMBOL(snd_seq_sleep_in_lock);*/
+/*EXPORT_SYMBOL(snd_seq_sleep_timeout_in_lock);*/
+EXPORT_SYMBOL(snd_use_lock_sync_helper);
+#endif
diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c
new file mode 100644
index 000000000000..d8f76afd284b
--- /dev/null
+++ b/sound/core/seq/seq_clientmgr.c
@@ -0,0 +1,2503 @@
+/*
+ *  ALSA sequencer Client Manager
+ *  Copyright (c) 1998-2001 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *                             Jaroslav Kysela <perex@suse.cz>
+ *                             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/init.h>
+#include <linux/smp_lock.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <linux/kmod.h>
+
+#include <sound/seq_kernel.h>
+#include "seq_clientmgr.h"
+#include "seq_memory.h"
+#include "seq_queue.h"
+#include "seq_timer.h"
+#include "seq_info.h"
+#include "seq_system.h"
+#include <sound/seq_device.h>
+#ifdef CONFIG_COMPAT
+#include <linux/compat.h>
+#endif
+
+/* Client Manager
+
+ * this module handles the connections of userland and kernel clients
+ * 
+ */
+
+#define SNDRV_SEQ_LFLG_INPUT	0x0001
+#define SNDRV_SEQ_LFLG_OUTPUT	0x0002
+#define SNDRV_SEQ_LFLG_OPEN	(SNDRV_SEQ_LFLG_INPUT|SNDRV_SEQ_LFLG_OUTPUT)
+
+static DEFINE_SPINLOCK(clients_lock);
+static DECLARE_MUTEX(register_mutex);
+
+/*
+ * client table
+ */
+static char clienttablock[SNDRV_SEQ_MAX_CLIENTS];
+static client_t *clienttab[SNDRV_SEQ_MAX_CLIENTS];
+static usage_t client_usage;
+
+/*
+ * prototypes
+ */
+static int bounce_error_event(client_t *client, snd_seq_event_t *event, int err, int atomic, int hop);
+static int snd_seq_deliver_single_event(client_t *client, snd_seq_event_t *event, int filter, int atomic, int hop);
+
+/*
+ */
+ 
+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);
+}
+
+/*
+ */
+static inline unsigned short snd_seq_file_flags(struct file *file)
+{
+        switch (file->f_mode & (FMODE_READ | FMODE_WRITE)) {
+        case FMODE_WRITE:
+                return SNDRV_SEQ_LFLG_OUTPUT;
+        case FMODE_READ:
+                return SNDRV_SEQ_LFLG_INPUT;
+        default:
+                return SNDRV_SEQ_LFLG_OPEN;
+        }
+}
+
+static inline int snd_seq_write_pool_allocated(client_t *client)
+{
+	return snd_seq_total_cells(client->pool) > 0;
+}
+
+/* return pointer to client structure for specified id */
+static client_t *clientptr(int clientid)
+{
+	if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) {
+		snd_printd("Seq: oops. Trying to get pointer to client %d\n", clientid);
+		return NULL;
+	}
+	return clienttab[clientid];
+}
+
+extern int seq_client_load[];
+
+client_t *snd_seq_client_use_ptr(int clientid)
+{
+	unsigned long flags;
+	client_t *client;
+
+	if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) {
+		snd_printd("Seq: oops. Trying to get pointer to client %d\n", clientid);
+		return NULL;
+	}
+	spin_lock_irqsave(&clients_lock, flags);
+	client = clientptr(clientid);
+	if (client)
+		goto __lock;
+	if (clienttablock[clientid]) {
+		spin_unlock_irqrestore(&clients_lock, flags);
+		return NULL;
+	}
+	spin_unlock_irqrestore(&clients_lock, flags);
+#ifdef CONFIG_KMOD
+	if (!in_interrupt() && current->fs->root) {
+		static char client_requested[64];
+		static char card_requested[SNDRV_CARDS];
+		if (clientid < 64) {
+			int idx;
+			
+			if (! client_requested[clientid] && current->fs->root) {
+				client_requested[clientid] = 1;
+				for (idx = 0; idx < 64; idx++) {
+					if (seq_client_load[idx] < 0)
+						break;
+					if (seq_client_load[idx] == clientid) {
+						request_module("snd-seq-client-%i", clientid);
+						break;
+					}
+				}
+			}
+		} else if (clientid >= 64 && clientid < 128) {
+			int card = (clientid - 64) / 8;
+			if (card < snd_ecards_limit) {
+				if (! card_requested[card]) {
+					card_requested[card] = 1;
+					snd_request_card(card);
+				}
+				snd_seq_device_load_drivers();
+			}
+		}
+		spin_lock_irqsave(&clients_lock, flags);
+		client = clientptr(clientid);
+		if (client)
+			goto __lock;
+		spin_unlock_irqrestore(&clients_lock, flags);
+	}
+#endif
+	return NULL;
+
+      __lock:
+	snd_use_lock_use(&client->use_lock);
+	spin_unlock_irqrestore(&clients_lock, flags);
+	return client;
+}
+
+static void usage_alloc(usage_t * res, int num)
+{
+	res->cur += num;
+	if (res->cur > res->peak)
+		res->peak = res->cur;
+}
+
+static void usage_free(usage_t * res, int num)
+{
+	res->cur -= num;
+}
+
+/* initialise data structures */
+int __init client_init_data(void)
+{
+	/* zap out the client table */
+	memset(&clienttablock, 0, sizeof(clienttablock));
+	memset(&clienttab, 0, sizeof(clienttab));
+	return 0;
+}
+
+
+static client_t *seq_create_client1(int client_index, int poolsize)
+{
+	unsigned long flags;
+	int c;
+	client_t *client;
+
+	/* init client data */
+	client = kcalloc(1, sizeof(*client), GFP_KERNEL);
+	if (client == NULL)
+		return NULL;
+	client->pool = snd_seq_pool_new(poolsize);
+	if (client->pool == NULL) {
+		kfree(client);
+		return NULL;
+	}
+	client->type = NO_CLIENT;
+	snd_use_lock_init(&client->use_lock);
+	rwlock_init(&client->ports_lock);
+	init_MUTEX(&client->ports_mutex);
+	INIT_LIST_HEAD(&client->ports_list_head);
+
+	/* find free slot in the client table */
+	spin_lock_irqsave(&clients_lock, flags);
+	if (client_index < 0) {
+		for (c = 128; c < SNDRV_SEQ_MAX_CLIENTS; c++) {
+			if (clienttab[c] || clienttablock[c])
+				continue;
+			clienttab[client->number = c] = client;
+			spin_unlock_irqrestore(&clients_lock, flags);
+			return client;
+		}
+	} else {
+		if (clienttab[client_index] == NULL && !clienttablock[client_index]) {
+			clienttab[client->number = client_index] = client;
+			spin_unlock_irqrestore(&clients_lock, flags);
+			return client;
+		}
+	}
+	spin_unlock_irqrestore(&clients_lock, flags);
+	snd_seq_pool_delete(&client->pool);
+	kfree(client);
+	return NULL;	/* no free slot found or busy, return failure code */
+}
+
+
+static int seq_free_client1(client_t *client)
+{
+	unsigned long flags;
+
+	snd_assert(client != NULL, return -EINVAL);
+	snd_seq_delete_all_ports(client);
+	snd_seq_queue_client_leave(client->number);
+	spin_lock_irqsave(&clients_lock, flags);
+	clienttablock[client->number] = 1;
+	clienttab[client->number] = NULL;
+	spin_unlock_irqrestore(&clients_lock, flags);
+	snd_use_lock_sync(&client->use_lock);
+	snd_seq_queue_client_termination(client->number);
+	if (client->pool)
+		snd_seq_pool_delete(&client->pool);
+	spin_lock_irqsave(&clients_lock, flags);
+	clienttablock[client->number] = 0;
+	spin_unlock_irqrestore(&clients_lock, flags);
+	return 0;
+}
+
+
+static void seq_free_client(client_t * client)
+{
+	down(&register_mutex);
+	switch (client->type) {
+	case NO_CLIENT:
+		snd_printk(KERN_WARNING "Seq: Trying to free unused client %d\n", client->number);
+		break;
+	case USER_CLIENT:
+	case KERNEL_CLIENT:
+		seq_free_client1(client);
+		usage_free(&client_usage, 1);
+		break;
+
+	default:
+		snd_printk(KERN_ERR "Seq: Trying to free client %d with undefined type = %d\n", client->number, client->type);
+	}
+	up(&register_mutex);
+
+	snd_seq_system_client_ev_client_exit(client->number);
+}
+
+
+
+/* -------------------------------------------------------- */
+
+/* create a user client */
+static int snd_seq_open(struct inode *inode, struct file *file)
+{
+	int c, mode;			/* client id */
+	client_t *client;
+	user_client_t *user;
+
+	if (down_interruptible(&register_mutex))
+		return -ERESTARTSYS;
+	client = seq_create_client1(-1, SNDRV_SEQ_DEFAULT_EVENTS);
+	if (client == NULL) {
+		up(&register_mutex);
+		return -ENOMEM;	/* failure code */
+	}
+
+	mode = snd_seq_file_flags(file);
+	if (mode & SNDRV_SEQ_LFLG_INPUT)
+		client->accept_input = 1;
+	if (mode & SNDRV_SEQ_LFLG_OUTPUT)
+		client->accept_output = 1;
+
+	user = &client->data.user;
+	user->fifo = NULL;
+	user->fifo_pool_size = 0;
+
+	if (mode & SNDRV_SEQ_LFLG_INPUT) {
+		user->fifo_pool_size = SNDRV_SEQ_DEFAULT_CLIENT_EVENTS;
+		user->fifo = snd_seq_fifo_new(user->fifo_pool_size);
+		if (user->fifo == NULL) {
+			seq_free_client1(client);
+			kfree(client);
+			up(&register_mutex);
+			return -ENOMEM;
+		}
+	}
+
+	usage_alloc(&client_usage, 1);
+	client->type = USER_CLIENT;
+	up(&register_mutex);
+
+	c = client->number;
+	file->private_data = client;
+
+	/* fill client data */
+	user->file = file;
+	sprintf(client->name, "Client-%d", c);
+
+	/* make others aware this new client */
+	snd_seq_system_client_ev_client_start(c);
+
+	return 0;
+}
+
+/* delete a user client */
+static int snd_seq_release(struct inode *inode, struct file *file)
+{
+	client_t *client = (client_t *) file->private_data;
+
+	if (client) {
+		seq_free_client(client);
+		if (client->data.user.fifo)
+			snd_seq_fifo_delete(&client->data.user.fifo);
+		kfree(client);
+	}
+
+	return 0;
+}
+
+
+/* handle client read() */
+/* possible error values:
+ *	-ENXIO	invalid client or file open mode
+ *	-ENOSPC	FIFO overflow (the flag is cleared after this error report)
+ *	-EINVAL	no enough user-space buffer to write the whole event
+ *	-EFAULT	seg. fault during copy to user space
+ */
+static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
+{
+	client_t *client = (client_t *) file->private_data;
+	fifo_t *fifo;
+	int err;
+	long result = 0;
+	snd_seq_event_cell_t *cell;
+
+	if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_INPUT))
+		return -ENXIO;
+
+	if (!access_ok(VERIFY_WRITE, buf, count))
+		return -EFAULT;
+
+	/* check client structures are in place */
+	snd_assert(client != NULL, return -ENXIO);
+
+	if (!client->accept_input || (fifo = client->data.user.fifo) == NULL)
+		return -ENXIO;
+
+	if (atomic_read(&fifo->overflow) > 0) {
+		/* buffer overflow is detected */
+		snd_seq_fifo_clear(fifo);
+		/* return error code */
+		return -ENOSPC;
+	}
+
+	cell = NULL;
+	err = 0;
+	snd_seq_fifo_lock(fifo);
+
+	/* while data available in queue */
+	while (count >= sizeof(snd_seq_event_t)) {
+		int nonblock;
+
+		nonblock = (file->f_flags & O_NONBLOCK) || result > 0;
+		if ((err = snd_seq_fifo_cell_out(fifo, &cell, nonblock)) < 0) {
+			break;
+		}
+		if (snd_seq_ev_is_variable(&cell->event)) {
+			snd_seq_event_t tmpev;
+			tmpev = cell->event;
+			tmpev.data.ext.len &= ~SNDRV_SEQ_EXT_MASK;
+			if (copy_to_user(buf, &tmpev, sizeof(snd_seq_event_t))) {
+				err = -EFAULT;
+				break;
+			}
+			count -= sizeof(snd_seq_event_t);
+			buf += sizeof(snd_seq_event_t);
+			err = snd_seq_expand_var_event(&cell->event, count, (char *)buf, 0, sizeof(snd_seq_event_t));
+			if (err < 0)
+				break;
+			result += err;
+			count -= err;
+			buf += err;
+		} else {
+			if (copy_to_user(buf, &cell->event, sizeof(snd_seq_event_t))) {
+				err = -EFAULT;
+				break;
+			}
+			count -= sizeof(snd_seq_event_t);
+			buf += sizeof(snd_seq_event_t);
+		}
+		snd_seq_cell_free(cell);
+		cell = NULL; /* to be sure */
+		result += sizeof(snd_seq_event_t);
+	}
+
+	if (err < 0) {
+		if (cell)
+			snd_seq_fifo_cell_putback(fifo, cell);
+		if (err == -EAGAIN && result > 0)
+			err = 0;
+	}
+	snd_seq_fifo_unlock(fifo);
+
+	return (err < 0) ? err : result;
+}
+
+
+/*
+ * check access permission to the port
+ */
+static int check_port_perm(client_port_t *port, unsigned int flags)
+{
+	if ((port->capability & flags) != flags)
+		return 0;
+	return flags;
+}
+
+/*
+ * check if the destination client is available, and return the pointer
+ * if filter is non-zero, client filter bitmap is tested.
+ */
+static client_t *get_event_dest_client(snd_seq_event_t *event, int filter)
+{
+	client_t *dest;
+
+	dest = snd_seq_client_use_ptr(event->dest.client);
+	if (dest == NULL)
+		return NULL;
+	if (! dest->accept_input)
+		goto __not_avail;
+	if ((dest->filter & SNDRV_SEQ_FILTER_USE_EVENT) &&
+	    ! test_bit(event->type, dest->event_filter))
+		goto __not_avail;
+	if (filter && !(dest->filter & filter))
+		goto __not_avail;
+
+	return dest; /* ok - accessible */
+__not_avail:
+	snd_seq_client_unlock(dest);
+	return NULL;
+}
+
+
+/*
+ * Return the error event.
+ *
+ * If the receiver client is a user client, the original event is
+ * encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event.  If
+ * the original event is also variable length, the external data is
+ * copied after the event record. 
+ * If the receiver client is a kernel client, the original event is
+ * quoted in SNDRV_SEQ_EVENT_KERNEL_ERROR, since this requires no extra
+ * kmalloc.
+ */
+static int bounce_error_event(client_t *client, snd_seq_event_t *event,
+			      int err, int atomic, int hop)
+{
+	snd_seq_event_t bounce_ev;
+	int result;
+
+	if (client == NULL ||
+	    ! (client->filter & SNDRV_SEQ_FILTER_BOUNCE) ||
+	    ! client->accept_input)
+		return 0; /* ignored */
+
+	/* set up quoted error */
+	memset(&bounce_ev, 0, sizeof(bounce_ev));
+	bounce_ev.type = SNDRV_SEQ_EVENT_KERNEL_ERROR;
+	bounce_ev.flags = SNDRV_SEQ_EVENT_LENGTH_FIXED;
+	bounce_ev.queue = SNDRV_SEQ_QUEUE_DIRECT;
+	bounce_ev.source.client = SNDRV_SEQ_CLIENT_SYSTEM;
+	bounce_ev.source.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE;
+	bounce_ev.dest.client = client->number;
+	bounce_ev.dest.port = event->source.port;
+	bounce_ev.data.quote.origin = event->dest;
+	bounce_ev.data.quote.event = event;
+	bounce_ev.data.quote.value = -err; /* use positive value */
+	result = snd_seq_deliver_single_event(NULL, &bounce_ev, 0, atomic, hop + 1);
+	if (result < 0) {
+		client->event_lost++;
+		return result;
+	}
+
+	return result;
+}
+
+
+/*
+ * rewrite the time-stamp of the event record with the curren time
+ * of the given queue.
+ * return non-zero if updated.
+ */
+static int update_timestamp_of_queue(snd_seq_event_t *event, int queue, int real_time)
+{
+	queue_t *q;
+
+	q = queueptr(queue);
+	if (! q)
+		return 0;
+	event->queue = queue;
+	event->flags &= ~SNDRV_SEQ_TIME_STAMP_MASK;
+	if (real_time) {
+		event->time.time = snd_seq_timer_get_cur_time(q->timer);
+		event->flags |= SNDRV_SEQ_TIME_STAMP_REAL;
+	} else {
+		event->time.tick = snd_seq_timer_get_cur_tick(q->timer);
+		event->flags |= SNDRV_SEQ_TIME_STAMP_TICK;
+	}
+	queuefree(q);
+	return 1;
+}
+
+
+/*
+ * deliver an event to the specified destination.
+ * if filter is non-zero, client filter bitmap is tested.
+ *
+ *  RETURN VALUE: 0 : if succeeded
+ *		 <0 : error
+ */
+static int snd_seq_deliver_single_event(client_t *client,
+					snd_seq_event_t *event,
+					int filter, int atomic, int hop)
+{
+	client_t *dest = NULL;
+	client_port_t *dest_port = NULL;
+	int result = -ENOENT;
+	int direct;
+
+	direct = snd_seq_ev_is_direct(event);
+
+	dest = get_event_dest_client(event, filter);
+	if (dest == NULL)
+		goto __skip;
+	dest_port = snd_seq_port_use_ptr(dest, event->dest.port);
+	if (dest_port == NULL)
+		goto __skip;
+
+	/* check permission */
+	if (! check_port_perm(dest_port, SNDRV_SEQ_PORT_CAP_WRITE)) {
+		result = -EPERM;
+		goto __skip;
+	}
+		
+	if (dest_port->timestamping)
+		update_timestamp_of_queue(event, dest_port->time_queue,
+					  dest_port->time_real);
+
+	switch (dest->type) {
+	case USER_CLIENT:
+		if (dest->data.user.fifo)
+			result = snd_seq_fifo_event_in(dest->data.user.fifo, event);
+		break;
+
+	case KERNEL_CLIENT:
+		if (dest_port->event_input == NULL)
+			break;
+		result = dest_port->event_input(event, direct, dest_port->private_data, atomic, hop);
+		break;
+	default:
+		break;
+	}
+
+  __skip:
+	if (dest_port)
+		snd_seq_port_unlock(dest_port);
+	if (dest)
+		snd_seq_client_unlock(dest);
+
+	if (result < 0 && !direct) {
+		result = bounce_error_event(client, event, result, atomic, hop);
+	}
+	return result;
+}
+
+
+/*
+ * send the event to all subscribers:
+ */
+static int deliver_to_subscribers(client_t *client,
+				  snd_seq_event_t *event,
+				  int atomic, int hop)
+{
+	subscribers_t *subs;
+	int err = 0, num_ev = 0;
+	snd_seq_event_t event_saved;
+	client_port_t *src_port;
+	struct list_head *p;
+	port_subs_info_t *grp;
+
+	src_port = snd_seq_port_use_ptr(client, event->source.port);
+	if (src_port == NULL)
+		return -EINVAL; /* invalid source port */
+	/* save original event record */
+	event_saved = *event;
+	grp = &src_port->c_src;
+	
+	/* lock list */
+	if (atomic)
+		read_lock(&grp->list_lock);
+	else
+		down_read(&grp->list_mutex);
+	list_for_each(p, &grp->list_head) {
+		subs = list_entry(p, subscribers_t, src_list);
+		event->dest = subs->info.dest;
+		if (subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP)
+			/* convert time according to flag with subscription */
+			update_timestamp_of_queue(event, subs->info.queue,
+						  subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIME_REAL);
+		err = snd_seq_deliver_single_event(client, event,
+						   0, atomic, hop);
+		if (err < 0)
+			break;
+		num_ev++;
+		/* restore original event record */
+		*event = event_saved;
+	}
+	if (atomic)
+		read_unlock(&grp->list_lock);
+	else
+		up_read(&grp->list_mutex);
+	*event = event_saved; /* restore */
+	snd_seq_port_unlock(src_port);
+	return (err < 0) ? err : num_ev;
+}
+
+
+#ifdef SUPPORT_BROADCAST 
+/*
+ * broadcast to all ports:
+ */
+static int port_broadcast_event(client_t *client,
+				snd_seq_event_t *event,
+				int atomic, int hop)
+{
+	int num_ev = 0, err = 0;
+	client_t *dest_client;
+	struct list_head *p;
+
+	dest_client = get_event_dest_client(event, SNDRV_SEQ_FILTER_BROADCAST);
+	if (dest_client == NULL)
+		return 0; /* no matching destination */
+
+	read_lock(&dest_client->ports_lock);
+	list_for_each(p, &dest_client->ports_list_head) {
+		client_port_t *port = list_entry(p, client_port_t, list);
+		event->dest.port = port->addr.port;
+		/* pass NULL as source client to avoid error bounce */
+		err = snd_seq_deliver_single_event(NULL, event,
+						   SNDRV_SEQ_FILTER_BROADCAST,
+						   atomic, hop);
+		if (err < 0)
+			break;
+		num_ev++;
+	}
+	read_unlock(&dest_client->ports_lock);
+	snd_seq_client_unlock(dest_client);
+	event->dest.port = SNDRV_SEQ_ADDRESS_BROADCAST; /* restore */
+	return (err < 0) ? err : num_ev;
+}
+
+/*
+ * send the event to all clients:
+ * if destination port is also ADDRESS_BROADCAST, deliver to all ports.
+ */
+static int broadcast_event(client_t *client,
+			   snd_seq_event_t *event, int atomic, int hop)
+{
+	int err = 0, num_ev = 0;
+	int dest;
+	snd_seq_addr_t addr;
+
+	addr = event->dest; /* save */
+
+	for (dest = 0; dest < SNDRV_SEQ_MAX_CLIENTS; dest++) {
+		/* don't send to itself */
+		if (dest == client->number)
+			continue;
+		event->dest.client = dest;
+		event->dest.port = addr.port;
+		if (addr.port == SNDRV_SEQ_ADDRESS_BROADCAST)
+			err = port_broadcast_event(client, event, atomic, hop);
+		else
+			/* pass NULL as source client to avoid error bounce */
+			err = snd_seq_deliver_single_event(NULL, event,
+							   SNDRV_SEQ_FILTER_BROADCAST,
+							   atomic, hop);
+		if (err < 0)
+			break;
+		num_ev += err;
+	}
+	event->dest = addr; /* restore */
+	return (err < 0) ? err : num_ev;
+}
+
+
+/* multicast - not supported yet */
+static int multicast_event(client_t *client, snd_seq_event_t *event,
+			   int atomic, int hop)
+{
+	snd_printd("seq: multicast not supported yet.\n");
+	return 0; /* ignored */
+}
+#endif /* SUPPORT_BROADCAST */
+
+
+/* deliver an event to the destination port(s).
+ * if the event is to subscribers or broadcast, the event is dispatched
+ * to multiple targets.
+ *
+ * RETURN VALUE: n > 0  : the number of delivered events.
+ *               n == 0 : the event was not passed to any client.
+ *               n < 0  : error - event was not processed.
+ */
+static int snd_seq_deliver_event(client_t *client, snd_seq_event_t *event,
+				 int atomic, int hop)
+{
+	int result;
+
+	hop++;
+	if (hop >= SNDRV_SEQ_MAX_HOPS) {
+		snd_printd("too long delivery path (%d:%d->%d:%d)\n",
+			   event->source.client, event->source.port,
+			   event->dest.client, event->dest.port);
+		return -EMLINK;
+	}
+
+	if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS ||
+	    event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS)
+		result = deliver_to_subscribers(client, event, atomic, hop);
+#ifdef SUPPORT_BROADCAST
+	else if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST ||
+		 event->dest.client == SNDRV_SEQ_ADDRESS_BROADCAST)
+		result = broadcast_event(client, event, atomic, hop);
+	else if (event->dest.client >= SNDRV_SEQ_MAX_CLIENTS)
+		result = multicast_event(client, event, atomic, hop);
+	else if (event->dest.port == SNDRV_SEQ_ADDRESS_BROADCAST)
+		result = port_broadcast_event(client, event, atomic, hop);
+#endif
+	else
+		result = snd_seq_deliver_single_event(client, event, 0, atomic, hop);
+
+	return result;
+}
+
+/*
+ * dispatch an event cell:
+ * This function is called only from queue check routines in timer
+ * interrupts or after enqueued.
+ * The event cell shall be released or re-queued in this function.
+ *
+ * RETURN VALUE: n > 0  : the number of delivered events.
+ *		 n == 0 : the event was not passed to any client.
+ *		 n < 0  : error - event was not processed.
+ */
+int snd_seq_dispatch_event(snd_seq_event_cell_t *cell, int atomic, int hop)
+{
+	client_t *client;
+	int result;
+
+	snd_assert(cell != NULL, return -EINVAL);
+
+	client = snd_seq_client_use_ptr(cell->event.source.client);
+	if (client == NULL) {
+		snd_seq_cell_free(cell); /* release this cell */
+		return -EINVAL;
+	}
+
+	if (cell->event.type == SNDRV_SEQ_EVENT_NOTE) {
+		/* NOTE event:
+		 * the event cell is re-used as a NOTE-OFF event and
+		 * enqueued again.
+		 */
+		snd_seq_event_t tmpev, *ev;
+
+		/* reserve this event to enqueue note-off later */
+		tmpev = cell->event;
+		tmpev.type = SNDRV_SEQ_EVENT_NOTEON;
+		result = snd_seq_deliver_event(client, &tmpev, atomic, hop);
+
+		/*
+		 * This was originally a note event.  We now re-use the
+		 * cell for the note-off event.
+		 */
+
+		ev = &cell->event;
+		ev->type = SNDRV_SEQ_EVENT_NOTEOFF;
+		ev->flags |= SNDRV_SEQ_PRIORITY_HIGH;
+
+		/* add the duration time */
+		switch (ev->flags & SNDRV_SEQ_TIME_STAMP_MASK) {
+		case SNDRV_SEQ_TIME_STAMP_TICK:
+			ev->time.tick += ev->data.note.duration;
+			break;
+		case SNDRV_SEQ_TIME_STAMP_REAL:
+			/* unit for duration is ms */
+			ev->time.time.tv_nsec += 1000000 * (ev->data.note.duration % 1000);
+			ev->time.time.tv_sec += ev->data.note.duration / 1000 +
+						ev->time.time.tv_nsec / 1000000000;
+			ev->time.time.tv_nsec %= 1000000000;
+			break;
+		}
+		ev->data.note.velocity = ev->data.note.off_velocity;
+
+		/* Now queue this cell as the note off event */
+		if (snd_seq_enqueue_event(cell, atomic, hop) < 0)
+			snd_seq_cell_free(cell); /* release this cell */
+
+	} else {
+		/* Normal events:
+		 * event cell is freed after processing the event
+		 */
+
+		result = snd_seq_deliver_event(client, &cell->event, atomic, hop);
+		snd_seq_cell_free(cell);
+	}
+
+	snd_seq_client_unlock(client);
+	return result;
+}
+
+
+/* Allocate a cell from client pool and enqueue it to queue:
+ * if pool is empty and blocking is TRUE, sleep until a new cell is
+ * available.
+ */
+static int snd_seq_client_enqueue_event(client_t *client,
+					snd_seq_event_t *event,
+					struct file *file, int blocking,
+					int atomic, int hop)
+{
+	snd_seq_event_cell_t *cell;
+	int err;
+
+	/* special queue values - force direct passing */
+	if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) {
+		event->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
+		event->queue = SNDRV_SEQ_QUEUE_DIRECT;
+	} else
+#ifdef SUPPORT_BROADCAST
+		if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST) {
+			event->dest.client = SNDRV_SEQ_ADDRESS_BROADCAST;
+			event->queue = SNDRV_SEQ_QUEUE_DIRECT;
+		}
+#endif
+	if (event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) {
+		/* check presence of source port */
+		client_port_t *src_port = snd_seq_port_use_ptr(client, event->source.port);
+		if (src_port == NULL)
+			return -EINVAL;
+		snd_seq_port_unlock(src_port);
+	}
+
+	/* direct event processing without enqueued */
+	if (snd_seq_ev_is_direct(event)) {
+		if (event->type == SNDRV_SEQ_EVENT_NOTE)
+			return -EINVAL; /* this event must be enqueued! */
+		return snd_seq_deliver_event(client, event, atomic, hop);
+	}
+
+	/* Not direct, normal queuing */
+	if (snd_seq_queue_is_used(event->queue, client->number) <= 0)
+		return -EINVAL;  /* invalid queue */
+	if (! snd_seq_write_pool_allocated(client))
+		return -ENXIO; /* queue is not allocated */
+
+	/* allocate an event cell */
+	err = snd_seq_event_dup(client->pool, event, &cell, !blocking || atomic, file);
+	if (err < 0)
+		return err;
+
+	/* we got a cell. enqueue it. */
+	if ((err = snd_seq_enqueue_event(cell, atomic, hop)) < 0) {
+		snd_seq_cell_free(cell);
+		return err;
+	}
+
+	return 0;
+}
+
+
+/*
+ * check validity of event type and data length.
+ * return non-zero if invalid.
+ */
+static int check_event_type_and_length(snd_seq_event_t *ev)
+{
+	switch (snd_seq_ev_length_type(ev)) {
+	case SNDRV_SEQ_EVENT_LENGTH_FIXED:
+		if (snd_seq_ev_is_variable_type(ev))
+			return -EINVAL;
+		break;
+	case SNDRV_SEQ_EVENT_LENGTH_VARIABLE:
+		if (! snd_seq_ev_is_variable_type(ev) ||
+		    (ev->data.ext.len & ~SNDRV_SEQ_EXT_MASK) >= SNDRV_SEQ_MAX_EVENT_LEN)
+			return -EINVAL;
+		break;
+	case SNDRV_SEQ_EVENT_LENGTH_VARUSR:
+		if (! snd_seq_ev_is_instr_type(ev) ||
+		    ! snd_seq_ev_is_direct(ev))
+			return -EINVAL;
+		break;
+	}
+	return 0;
+}
+
+
+/* handle write() */
+/* possible error values:
+ *	-ENXIO	invalid client or file open mode
+ *	-ENOMEM	malloc failed
+ *	-EFAULT	seg. fault during copy from user space
+ *	-EINVAL	invalid event
+ *	-EAGAIN	no space in output pool
+ *	-EINTR	interrupts while sleep
+ *	-EMLINK	too many hops
+ *	others	depends on return value from driver callback
+ */
+static ssize_t snd_seq_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
+{
+	client_t *client = (client_t *) file->private_data;
+	int written = 0, len;
+	int err = -EINVAL;
+	snd_seq_event_t event;
+
+	if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT))
+		return -ENXIO;
+
+	/* check client structures are in place */
+	snd_assert(client != NULL, return -ENXIO);
+		
+	if (!client->accept_output || client->pool == NULL)
+		return -ENXIO;
+
+	/* allocate the pool now if the pool is not allocated yet */ 
+	if (client->pool->size > 0 && !snd_seq_write_pool_allocated(client)) {
+		if (snd_seq_pool_init(client->pool) < 0)
+			return -ENOMEM;
+	}
+
+	/* only process whole events */
+	while (count >= sizeof(snd_seq_event_t)) {
+		/* Read in the event header from the user */
+		len = sizeof(event);
+		if (copy_from_user(&event, buf, len)) {
+			err = -EFAULT;
+			break;
+		}
+		event.source.client = client->number;	/* fill in client number */
+		/* Check for extension data length */
+		if (check_event_type_and_length(&event)) {
+			err = -EINVAL;
+			break;
+		}
+
+		/* check for special events */
+		if (event.type == SNDRV_SEQ_EVENT_NONE)
+			goto __skip_event;
+		else if (snd_seq_ev_is_reserved(&event)) {
+			err = -EINVAL;
+			break;
+		}
+
+		if (snd_seq_ev_is_variable(&event)) {
+			int extlen = event.data.ext.len & ~SNDRV_SEQ_EXT_MASK;
+			if ((size_t)(extlen + len) > count) {
+				/* back out, will get an error this time or next */
+				err = -EINVAL;
+				break;
+			}
+			/* set user space pointer */
+			event.data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR;
+			event.data.ext.ptr = (char*)buf + sizeof(snd_seq_event_t);
+			len += extlen; /* increment data length */
+		} else {
+#ifdef CONFIG_COMPAT
+			if (client->convert32 && snd_seq_ev_is_varusr(&event)) {
+				void *ptr = compat_ptr(event.data.raw32.d[1]);
+				event.data.ext.ptr = ptr;
+			}
+#endif
+		}
+
+		/* ok, enqueue it */
+		err = snd_seq_client_enqueue_event(client, &event, file,
+						   !(file->f_flags & O_NONBLOCK),
+						   0, 0);
+		if (err < 0)
+			break;
+
+	__skip_event:
+		/* Update pointers and counts */
+		count -= len;
+		buf += len;
+		written += len;
+	}
+
+	return written ? written : err;
+}
+
+
+/*
+ * handle polling
+ */
+static unsigned int snd_seq_poll(struct file *file, poll_table * wait)
+{
+	client_t *client = (client_t *) file->private_data;
+	unsigned int mask = 0;
+
+	/* check client structures are in place */
+	snd_assert(client != NULL, return -ENXIO);
+
+	if ((snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_INPUT) &&
+	    client->data.user.fifo) {
+
+		/* check if data is available in the outqueue */
+		if (snd_seq_fifo_poll_wait(client->data.user.fifo, file, wait))
+			mask |= POLLIN | POLLRDNORM;
+	}
+
+	if (snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT) {
+
+		/* check if data is available in the pool */
+		if (!snd_seq_write_pool_allocated(client) ||
+		    snd_seq_pool_poll_wait(client->pool, file, wait))
+			mask |= POLLOUT | POLLWRNORM;
+	}
+
+	return mask;
+}
+
+
+/*-----------------------------------------------------*/
+
+
+/* SYSTEM_INFO ioctl() */
+static int snd_seq_ioctl_system_info(client_t *client, void __user *arg)
+{
+	snd_seq_system_info_t info;
+
+	memset(&info, 0, sizeof(info));
+	/* fill the info fields */
+	info.queues = SNDRV_SEQ_MAX_QUEUES;
+	info.clients = SNDRV_SEQ_MAX_CLIENTS;
+	info.ports = 256;	/* fixed limit */
+	info.channels = 256;	/* fixed limit */
+	info.cur_clients = client_usage.cur;
+	info.cur_queues = snd_seq_queue_get_cur_queues();
+
+	if (copy_to_user(arg, &info, sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+
+/* RUNNING_MODE ioctl() */
+static int snd_seq_ioctl_running_mode(client_t *client, void __user *arg)
+{
+	struct sndrv_seq_running_info info;
+	client_t *cptr;
+	int err = 0;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+
+	/* requested client number */
+	cptr = snd_seq_client_use_ptr(info.client);
+	if (cptr == NULL)
+		return -ENOENT;		/* don't change !!! */
+
+#ifdef SNDRV_BIG_ENDIAN
+	if (! info.big_endian) {
+		err = -EINVAL;
+		goto __err;
+	}
+#else
+	if (info.big_endian) {
+		err = -EINVAL;
+		goto __err;
+	}
+
+#endif
+	if (info.cpu_mode > sizeof(long)) {
+		err = -EINVAL;
+		goto __err;
+	}
+	cptr->convert32 = (info.cpu_mode < sizeof(long));
+ __err:
+	snd_seq_client_unlock(cptr);
+	return err;
+}
+
+/* CLIENT_INFO ioctl() */
+static void get_client_info(client_t *cptr, snd_seq_client_info_t *info)
+{
+	info->client = cptr->number;
+
+	/* fill the info fields */
+	info->type = cptr->type;
+	strcpy(info->name, cptr->name);
+	info->filter = cptr->filter;
+	info->event_lost = cptr->event_lost;
+	memcpy(info->event_filter, cptr->event_filter, 32);
+	info->num_ports = cptr->num_ports;
+	memset(info->reserved, 0, sizeof(info->reserved));
+}
+
+static int snd_seq_ioctl_get_client_info(client_t * client, void __user *arg)
+{
+	client_t *cptr;
+	snd_seq_client_info_t client_info;
+
+	if (copy_from_user(&client_info, arg, sizeof(client_info)))
+		return -EFAULT;
+
+	/* requested client number */
+	cptr = snd_seq_client_use_ptr(client_info.client);
+	if (cptr == NULL)
+		return -ENOENT;		/* don't change !!! */
+
+	get_client_info(cptr, &client_info);
+	snd_seq_client_unlock(cptr);
+
+	if (copy_to_user(arg, &client_info, sizeof(client_info)))
+		return -EFAULT;
+	return 0;
+}
+
+
+/* CLIENT_INFO ioctl() */
+static int snd_seq_ioctl_set_client_info(client_t * client, void __user *arg)
+{
+	snd_seq_client_info_t client_info;
+
+	if (copy_from_user(&client_info, arg, sizeof(client_info)))
+		return -EFAULT;
+
+	/* it is not allowed to set the info fields for an another client */
+	if (client->number != client_info.client)
+		return -EPERM;
+	/* also client type must be set now */
+	if (client->type != client_info.type)
+		return -EINVAL;
+
+	/* fill the info fields */
+	if (client_info.name[0])
+		strlcpy(client->name, client_info.name, sizeof(client->name));
+
+	client->filter = client_info.filter;
+	client->event_lost = client_info.event_lost;
+	memcpy(client->event_filter, client_info.event_filter, 32);
+
+	return 0;
+}
+
+
+/* 
+ * CREATE PORT ioctl() 
+ */
+static int snd_seq_ioctl_create_port(client_t * client, void __user *arg)
+{
+	client_port_t *port;
+	snd_seq_port_info_t info;
+	snd_seq_port_callback_t *callback;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+
+	/* it is not allowed to create the port for an another client */
+	if (info.addr.client != client->number)
+		return -EPERM;
+
+	port = snd_seq_create_port(client, (info.flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT) ? info.addr.port : -1);
+	if (port == NULL)
+		return -ENOMEM;
+
+	if (client->type == USER_CLIENT && info.kernel) {
+		snd_seq_delete_port(client, port->addr.port);
+		return -EINVAL;
+	}
+	if (client->type == KERNEL_CLIENT) {
+		if ((callback = info.kernel) != NULL) {
+			if (callback->owner)
+				port->owner = callback->owner;
+			port->private_data = callback->private_data;
+			port->private_free = callback->private_free;
+			port->callback_all = callback->callback_all;
+			port->event_input = callback->event_input;
+			port->c_src.open = callback->subscribe;
+			port->c_src.close = callback->unsubscribe;
+			port->c_dest.open = callback->use;
+			port->c_dest.close = callback->unuse;
+		}
+	}
+
+	info.addr = port->addr;
+
+	snd_seq_set_port_info(port, &info);
+	snd_seq_system_client_ev_port_start(port->addr.client, port->addr.port);
+
+	if (copy_to_user(arg, &info, sizeof(info)))
+		return -EFAULT;
+
+	return 0;
+}
+
+/* 
+ * DELETE PORT ioctl() 
+ */
+static int snd_seq_ioctl_delete_port(client_t * client, void __user *arg)
+{
+	snd_seq_port_info_t info;
+	int err;
+
+	/* set passed parameters */
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+	
+	/* it is not allowed to remove the port for an another client */
+	if (info.addr.client != client->number)
+		return -EPERM;
+
+	err = snd_seq_delete_port(client, info.addr.port);
+	if (err >= 0)
+		snd_seq_system_client_ev_port_exit(client->number, info.addr.port);
+	return err;
+}
+
+
+/* 
+ * GET_PORT_INFO ioctl() (on any client) 
+ */
+static int snd_seq_ioctl_get_port_info(client_t *client, void __user *arg)
+{
+	client_t *cptr;
+	client_port_t *port;
+	snd_seq_port_info_t info;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+	cptr = snd_seq_client_use_ptr(info.addr.client);
+	if (cptr == NULL)
+		return -ENXIO;
+
+	port = snd_seq_port_use_ptr(cptr, info.addr.port);
+	if (port == NULL) {
+		snd_seq_client_unlock(cptr);
+		return -ENOENT;			/* don't change */
+	}
+
+	/* get port info */
+	snd_seq_get_port_info(port, &info);
+	snd_seq_port_unlock(port);
+	snd_seq_client_unlock(cptr);
+
+	if (copy_to_user(arg, &info, sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+
+/* 
+ * SET_PORT_INFO ioctl() (only ports on this/own client) 
+ */
+static int snd_seq_ioctl_set_port_info(client_t * client, void __user *arg)
+{
+	client_port_t *port;
+	snd_seq_port_info_t info;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+
+	if (info.addr.client != client->number) /* only set our own ports ! */
+		return -EPERM;
+	port = snd_seq_port_use_ptr(client, info.addr.port);
+	if (port) {
+		snd_seq_set_port_info(port, &info);
+		snd_seq_port_unlock(port);
+	}
+	return 0;
+}
+
+
+/*
+ * port subscription (connection)
+ */
+#define PERM_RD		(SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ)
+#define PERM_WR		(SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_SUBS_WRITE)
+
+static int check_subscription_permission(client_t *client, client_port_t *sport,
+					 client_port_t *dport,
+					 snd_seq_port_subscribe_t *subs)
+{
+	if (client->number != subs->sender.client &&
+	    client->number != subs->dest.client) {
+		/* connection by third client - check export permission */
+		if (check_port_perm(sport, SNDRV_SEQ_PORT_CAP_NO_EXPORT))
+			return -EPERM;
+		if (check_port_perm(dport, SNDRV_SEQ_PORT_CAP_NO_EXPORT))
+			return -EPERM;
+	}
+
+	/* check read permission */
+	/* if sender or receiver is the subscribing client itself,
+	 * no permission check is necessary
+	 */
+	if (client->number != subs->sender.client) {
+		if (! check_port_perm(sport, PERM_RD))
+			return -EPERM;
+	}
+	/* check write permission */
+	if (client->number != subs->dest.client) {
+		if (! check_port_perm(dport, PERM_WR))
+			return -EPERM;
+	}
+	return 0;
+}
+
+/*
+ * send an subscription notify event to user client:
+ * client must be user client.
+ */
+int snd_seq_client_notify_subscription(int client, int port,
+				       snd_seq_port_subscribe_t *info, int evtype)
+{
+	snd_seq_event_t event;
+
+	memset(&event, 0, sizeof(event));
+	event.type = evtype;
+	event.data.connect.dest = info->dest;
+	event.data.connect.sender = info->sender;
+
+	return snd_seq_system_notify(client, port, &event);  /* non-atomic */
+}
+
+
+/* 
+ * add to port's subscription list IOCTL interface 
+ */
+static int snd_seq_ioctl_subscribe_port(client_t * client, void __user *arg)
+{
+	int result = -EINVAL;
+	client_t *receiver = NULL, *sender = NULL;
+	client_port_t *sport = NULL, *dport = NULL;
+	snd_seq_port_subscribe_t subs;
+
+	if (copy_from_user(&subs, arg, sizeof(subs)))
+		return -EFAULT;
+
+	if ((receiver = snd_seq_client_use_ptr(subs.dest.client)) == NULL)
+		goto __end;
+	if ((sender = snd_seq_client_use_ptr(subs.sender.client)) == NULL)
+		goto __end;
+	if ((sport = snd_seq_port_use_ptr(sender, subs.sender.port)) == NULL)
+		goto __end;
+	if ((dport = snd_seq_port_use_ptr(receiver, subs.dest.port)) == NULL)
+		goto __end;
+
+	result = check_subscription_permission(client, sport, dport, &subs);
+	if (result < 0)
+		goto __end;
+
+	/* connect them */
+	result = snd_seq_port_connect(client, sender, sport, receiver, dport, &subs);
+	if (! result) /* broadcast announce */
+		snd_seq_client_notify_subscription(SNDRV_SEQ_ADDRESS_SUBSCRIBERS, 0,
+						   &subs, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED);
+      __end:
+      	if (sport)
+		snd_seq_port_unlock(sport);
+	if (dport)
+		snd_seq_port_unlock(dport);
+	if (sender)
+		snd_seq_client_unlock(sender);
+	if (receiver)
+		snd_seq_client_unlock(receiver);
+	return result;
+}
+
+
+/* 
+ * remove from port's subscription list 
+ */
+static int snd_seq_ioctl_unsubscribe_port(client_t * client, void __user *arg)
+{
+	int result = -ENXIO;
+	client_t *receiver = NULL, *sender = NULL;
+	client_port_t *sport = NULL, *dport = NULL;
+	snd_seq_port_subscribe_t subs;
+
+	if (copy_from_user(&subs, arg, sizeof(subs)))
+		return -EFAULT;
+
+	if ((receiver = snd_seq_client_use_ptr(subs.dest.client)) == NULL)
+		goto __end;
+	if ((sender = snd_seq_client_use_ptr(subs.sender.client)) == NULL)
+		goto __end;
+	if ((sport = snd_seq_port_use_ptr(sender, subs.sender.port)) == NULL)
+		goto __end;
+	if ((dport = snd_seq_port_use_ptr(receiver, subs.dest.port)) == NULL)
+		goto __end;
+
+	result = check_subscription_permission(client, sport, dport, &subs);
+	if (result < 0)
+		goto __end;
+
+	result = snd_seq_port_disconnect(client, sender, sport, receiver, dport, &subs);
+	if (! result) /* broadcast announce */
+		snd_seq_client_notify_subscription(SNDRV_SEQ_ADDRESS_SUBSCRIBERS, 0,
+						   &subs, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED);
+      __end:
+      	if (sport)
+		snd_seq_port_unlock(sport);
+	if (dport)
+		snd_seq_port_unlock(dport);
+	if (sender)
+		snd_seq_client_unlock(sender);
+	if (receiver)
+		snd_seq_client_unlock(receiver);
+	return result;
+}
+
+
+/* CREATE_QUEUE ioctl() */
+static int snd_seq_ioctl_create_queue(client_t *client, void __user *arg)
+{
+	snd_seq_queue_info_t info;
+	int result;
+	queue_t *q;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+
+	result = snd_seq_queue_alloc(client->number, info.locked, info.flags);
+	if (result < 0)
+		return result;
+
+	q = queueptr(result);
+	if (q == NULL)
+		return -EINVAL;
+
+	info.queue = q->queue;
+	info.locked = q->locked;
+	info.owner = q->owner;
+
+	/* set queue name */
+	if (! info.name[0])
+		snprintf(info.name, sizeof(info.name), "Queue-%d", q->queue);
+	strlcpy(q->name, info.name, sizeof(q->name));
+	queuefree(q);
+
+	if (copy_to_user(arg, &info, sizeof(info)))
+		return -EFAULT;
+
+	return 0;
+}
+
+/* DELETE_QUEUE ioctl() */
+static int snd_seq_ioctl_delete_queue(client_t *client, void __user *arg)
+{
+	snd_seq_queue_info_t info;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+
+	return snd_seq_queue_delete(client->number, info.queue);
+}
+
+/* GET_QUEUE_INFO ioctl() */
+static int snd_seq_ioctl_get_queue_info(client_t *client, void __user *arg)
+{
+	snd_seq_queue_info_t info;
+	queue_t *q;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+
+	q = queueptr(info.queue);
+	if (q == NULL)
+		return -EINVAL;
+
+	memset(&info, 0, sizeof(info));
+	info.queue = q->queue;
+	info.owner = q->owner;
+	info.locked = q->locked;
+	strlcpy(info.name, q->name, sizeof(info.name));
+	queuefree(q);
+
+	if (copy_to_user(arg, &info, sizeof(info)))
+		return -EFAULT;
+
+	return 0;
+}
+
+/* SET_QUEUE_INFO ioctl() */
+static int snd_seq_ioctl_set_queue_info(client_t *client, void __user *arg)
+{
+	snd_seq_queue_info_t info;
+	queue_t *q;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+
+	if (info.owner != client->number)
+		return -EINVAL;
+
+	/* change owner/locked permission */
+	if (snd_seq_queue_check_access(info.queue, client->number)) {
+		if (snd_seq_queue_set_owner(info.queue, client->number, info.locked) < 0)
+			return -EPERM;
+		if (info.locked)
+			snd_seq_queue_use(info.queue, client->number, 1);
+	} else {
+		return -EPERM;
+	}	
+
+	q = queueptr(info.queue);
+	if (! q)
+		return -EINVAL;
+	if (q->owner != client->number) {
+		queuefree(q);
+		return -EPERM;
+	}
+	strlcpy(q->name, info.name, sizeof(q->name));
+	queuefree(q);
+
+	return 0;
+}
+
+/* GET_NAMED_QUEUE ioctl() */
+static int snd_seq_ioctl_get_named_queue(client_t *client, void __user *arg)
+{
+	snd_seq_queue_info_t info;
+	queue_t *q;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+
+	q = snd_seq_queue_find_name(info.name);
+	if (q == NULL)
+		return -EINVAL;
+	info.queue = q->queue;
+	info.owner = q->owner;
+	info.locked = q->locked;
+	queuefree(q);
+
+	if (copy_to_user(arg, &info, sizeof(info)))
+		return -EFAULT;
+
+	return 0;
+}
+
+/* GET_QUEUE_STATUS ioctl() */
+static int snd_seq_ioctl_get_queue_status(client_t * client, void __user *arg)
+{
+	snd_seq_queue_status_t status;
+	queue_t *queue;
+	seq_timer_t *tmr;
+
+	if (copy_from_user(&status, arg, sizeof(status)))
+		return -EFAULT;
+
+	queue = queueptr(status.queue);
+	if (queue == NULL)
+		return -EINVAL;
+	memset(&status, 0, sizeof(status));
+	status.queue = queue->queue;
+	
+	tmr = queue->timer;
+	status.events = queue->tickq->cells + queue->timeq->cells;
+
+	status.time = snd_seq_timer_get_cur_time(tmr);
+	status.tick = snd_seq_timer_get_cur_tick(tmr);
+
+	status.running = tmr->running;
+
+	status.flags = queue->flags;
+	queuefree(queue);
+
+	if (copy_to_user(arg, &status, sizeof(status)))
+		return -EFAULT;
+	return 0;
+}
+
+
+/* GET_QUEUE_TEMPO ioctl() */
+static int snd_seq_ioctl_get_queue_tempo(client_t * client, void __user *arg)
+{
+	snd_seq_queue_tempo_t tempo;
+	queue_t *queue;
+	seq_timer_t *tmr;
+
+	if (copy_from_user(&tempo, arg, sizeof(tempo)))
+		return -EFAULT;
+
+	queue = queueptr(tempo.queue);
+	if (queue == NULL)
+		return -EINVAL;
+	memset(&tempo, 0, sizeof(tempo));
+	tempo.queue = queue->queue;
+	
+	tmr = queue->timer;
+
+	tempo.tempo = tmr->tempo;
+	tempo.ppq = tmr->ppq;
+	tempo.skew_value = tmr->skew;
+	tempo.skew_base = tmr->skew_base;
+	queuefree(queue);
+
+	if (copy_to_user(arg, &tempo, sizeof(tempo)))
+		return -EFAULT;
+	return 0;
+}
+
+
+/* SET_QUEUE_TEMPO ioctl() */
+int snd_seq_set_queue_tempo(int client, snd_seq_queue_tempo_t *tempo)
+{
+	if (!snd_seq_queue_check_access(tempo->queue, client))
+		return -EPERM;
+	return snd_seq_queue_timer_set_tempo(tempo->queue, client, tempo);
+}
+
+static int snd_seq_ioctl_set_queue_tempo(client_t * client, void __user *arg)
+{
+	int result;
+	snd_seq_queue_tempo_t tempo;
+
+	if (copy_from_user(&tempo, arg, sizeof(tempo)))
+		return -EFAULT;
+
+	result = snd_seq_set_queue_tempo(client->number, &tempo);
+	return result < 0 ? result : 0;
+}
+
+
+/* GET_QUEUE_TIMER ioctl() */
+static int snd_seq_ioctl_get_queue_timer(client_t * client, void __user *arg)
+{
+	snd_seq_queue_timer_t timer;
+	queue_t *queue;
+	seq_timer_t *tmr;
+
+	if (copy_from_user(&timer, arg, sizeof(timer)))
+		return -EFAULT;
+
+	queue = queueptr(timer.queue);
+	if (queue == NULL)
+		return -EINVAL;
+
+	if (down_interruptible(&queue->timer_mutex)) {
+		queuefree(queue);
+		return -ERESTARTSYS;
+	}
+	tmr = queue->timer;
+	memset(&timer, 0, sizeof(timer));
+	timer.queue = queue->queue;
+
+	timer.type = tmr->type;
+	if (tmr->type == SNDRV_SEQ_TIMER_ALSA) {
+		timer.u.alsa.id = tmr->alsa_id;
+		timer.u.alsa.resolution = tmr->preferred_resolution;
+	}
+	up(&queue->timer_mutex);
+	queuefree(queue);
+	
+	if (copy_to_user(arg, &timer, sizeof(timer)))
+		return -EFAULT;
+	return 0;
+}
+
+
+/* SET_QUEUE_TIMER ioctl() */
+static int snd_seq_ioctl_set_queue_timer(client_t * client, void __user *arg)
+{
+	int result = 0;
+	snd_seq_queue_timer_t timer;
+
+	if (copy_from_user(&timer, arg, sizeof(timer)))
+		return -EFAULT;
+
+	if (timer.type != SNDRV_SEQ_TIMER_ALSA)
+		return -EINVAL;
+
+	if (snd_seq_queue_check_access(timer.queue, client->number)) {
+		queue_t *q;
+		seq_timer_t *tmr;
+
+		q = queueptr(timer.queue);
+		if (q == NULL)
+			return -ENXIO;
+		if (down_interruptible(&q->timer_mutex)) {
+			queuefree(q);
+			return -ERESTARTSYS;
+		}
+		tmr = q->timer;
+		snd_seq_queue_timer_close(timer.queue);
+		tmr->type = timer.type;
+		if (tmr->type == SNDRV_SEQ_TIMER_ALSA) {
+			tmr->alsa_id = timer.u.alsa.id;
+			tmr->preferred_resolution = timer.u.alsa.resolution;
+		}
+		result = snd_seq_queue_timer_open(timer.queue);
+		up(&q->timer_mutex);
+		queuefree(q);
+	} else {
+		return -EPERM;
+	}	
+
+	return result;
+}
+
+
+/* GET_QUEUE_CLIENT ioctl() */
+static int snd_seq_ioctl_get_queue_client(client_t * client, void __user *arg)
+{
+	snd_seq_queue_client_t info;
+	int used;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+
+	used = snd_seq_queue_is_used(info.queue, client->number);
+	if (used < 0)
+		return -EINVAL;
+	info.used = used;
+	info.client = client->number;
+
+	if (copy_to_user(arg, &info, sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+
+/* SET_QUEUE_CLIENT ioctl() */
+static int snd_seq_ioctl_set_queue_client(client_t * client, void __user *arg)
+{
+	int err;
+	snd_seq_queue_client_t info;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+
+	if (info.used >= 0) {
+		err = snd_seq_queue_use(info.queue, client->number, info.used);
+		if (err < 0)
+			return err;
+	}
+
+	return snd_seq_ioctl_get_queue_client(client, arg);
+}
+
+
+/* GET_CLIENT_POOL ioctl() */
+static int snd_seq_ioctl_get_client_pool(client_t * client, void __user *arg)
+{
+	snd_seq_client_pool_t info;
+	client_t *cptr;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+
+	cptr = snd_seq_client_use_ptr(info.client);
+	if (cptr == NULL)
+		return -ENOENT;
+	memset(&info, 0, sizeof(info));
+	info.output_pool = cptr->pool->size;
+	info.output_room = cptr->pool->room;
+	info.output_free = info.output_pool;
+	if (cptr->pool)
+		info.output_free = snd_seq_unused_cells(cptr->pool);
+	if (cptr->type == USER_CLIENT) {
+		info.input_pool = cptr->data.user.fifo_pool_size;
+		info.input_free = info.input_pool;
+		if (cptr->data.user.fifo)
+			info.input_free = snd_seq_unused_cells(cptr->data.user.fifo->pool);
+	} else {
+		info.input_pool = 0;
+		info.input_free = 0;
+	}
+	snd_seq_client_unlock(cptr);
+	
+	if (copy_to_user(arg, &info, sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+/* SET_CLIENT_POOL ioctl() */
+static int snd_seq_ioctl_set_client_pool(client_t * client, void __user *arg)
+{
+	snd_seq_client_pool_t info;
+	int rc;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+
+	if (client->number != info.client)
+		return -EINVAL; /* can't change other clients */
+
+	if (info.output_pool >= 1 && info.output_pool <= SNDRV_SEQ_MAX_EVENTS &&
+	    (! snd_seq_write_pool_allocated(client) ||
+	     info.output_pool != client->pool->size)) {
+		if (snd_seq_write_pool_allocated(client)) {
+			/* remove all existing cells */
+			snd_seq_queue_client_leave_cells(client->number);
+			snd_seq_pool_done(client->pool);
+		}
+		client->pool->size = info.output_pool;
+		rc = snd_seq_pool_init(client->pool);
+		if (rc < 0)
+			return rc;
+	}
+	if (client->type == USER_CLIENT && client->data.user.fifo != NULL &&
+	    info.input_pool >= 1 &&
+	    info.input_pool <= SNDRV_SEQ_MAX_CLIENT_EVENTS &&
+	    info.input_pool != client->data.user.fifo_pool_size) {
+		/* change pool size */
+		rc = snd_seq_fifo_resize(client->data.user.fifo, info.input_pool);
+		if (rc < 0)
+			return rc;
+		client->data.user.fifo_pool_size = info.input_pool;
+	}
+	if (info.output_room >= 1 &&
+	    info.output_room <= client->pool->size) {
+		client->pool->room  = info.output_room;
+	}
+
+	return snd_seq_ioctl_get_client_pool(client, arg);
+}
+
+
+/* REMOVE_EVENTS ioctl() */
+static int snd_seq_ioctl_remove_events(client_t * client, void __user *arg)
+{
+	snd_seq_remove_events_t info;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+
+	/*
+	 * Input mostly not implemented XXX.
+	 */
+	if (info.remove_mode & SNDRV_SEQ_REMOVE_INPUT) {
+		/*
+		 * No restrictions so for a user client we can clear
+		 * the whole fifo
+		 */
+		if (client->type == USER_CLIENT)
+			snd_seq_fifo_clear(client->data.user.fifo);
+	}
+
+	if (info.remove_mode & SNDRV_SEQ_REMOVE_OUTPUT)
+		snd_seq_queue_remove_cells(client->number, &info);
+
+	return 0;
+}
+
+
+/*
+ * get subscription info
+ */
+static int snd_seq_ioctl_get_subscription(client_t *client, void __user *arg)
+{
+	int result;
+	client_t *sender = NULL;
+	client_port_t *sport = NULL;
+	snd_seq_port_subscribe_t subs;
+	subscribers_t *p;
+
+	if (copy_from_user(&subs, arg, sizeof(subs)))
+		return -EFAULT;
+
+	result = -EINVAL;
+	if ((sender = snd_seq_client_use_ptr(subs.sender.client)) == NULL)
+		goto __end;
+	if ((sport = snd_seq_port_use_ptr(sender, subs.sender.port)) == NULL)
+		goto __end;
+	p = snd_seq_port_get_subscription(&sport->c_src, &subs.dest);
+	if (p) {
+		result = 0;
+		subs = p->info;
+	} else
+		result = -ENOENT;
+
+      __end:
+      	if (sport)
+		snd_seq_port_unlock(sport);
+	if (sender)
+		snd_seq_client_unlock(sender);
+	if (result >= 0) {
+		if (copy_to_user(arg, &subs, sizeof(subs)))
+			return -EFAULT;
+	}
+	return result;
+}
+
+
+/*
+ * get subscription info - check only its presence
+ */
+static int snd_seq_ioctl_query_subs(client_t *client, void __user *arg)
+{
+	int result = -ENXIO;
+	client_t *cptr = NULL;
+	client_port_t *port = NULL;
+	snd_seq_query_subs_t subs;
+	port_subs_info_t *group;
+	struct list_head *p;
+	int i;
+
+	if (copy_from_user(&subs, arg, sizeof(subs)))
+		return -EFAULT;
+
+	if ((cptr = snd_seq_client_use_ptr(subs.root.client)) == NULL)
+		goto __end;
+	if ((port = snd_seq_port_use_ptr(cptr, subs.root.port)) == NULL)
+		goto __end;
+
+	switch (subs.type) {
+	case SNDRV_SEQ_QUERY_SUBS_READ:
+		group = &port->c_src;
+		break;
+	case SNDRV_SEQ_QUERY_SUBS_WRITE:
+		group = &port->c_dest;
+		break;
+	default:
+		goto __end;
+	}
+
+	down_read(&group->list_mutex);
+	/* search for the subscriber */
+	subs.num_subs = group->count;
+	i = 0;
+	result = -ENOENT;
+	list_for_each(p, &group->list_head) {
+		if (i++ == subs.index) {
+			/* found! */
+			subscribers_t *s;
+			if (subs.type == SNDRV_SEQ_QUERY_SUBS_READ) {
+				s = list_entry(p, subscribers_t, src_list);
+				subs.addr = s->info.dest;
+			} else {
+				s = list_entry(p, subscribers_t, dest_list);
+				subs.addr = s->info.sender;
+			}
+			subs.flags = s->info.flags;
+			subs.queue = s->info.queue;
+			result = 0;
+			break;
+		}
+	}
+	up_read(&group->list_mutex);
+
+      __end:
+   	if (port)
+		snd_seq_port_unlock(port);
+	if (cptr)
+		snd_seq_client_unlock(cptr);
+	if (result >= 0) {
+		if (copy_to_user(arg, &subs, sizeof(subs)))
+			return -EFAULT;
+	}
+	return result;
+}
+
+
+/*
+ * query next client
+ */
+static int snd_seq_ioctl_query_next_client(client_t *client, void __user *arg)
+{
+	client_t *cptr = NULL;
+	snd_seq_client_info_t info;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+
+	/* search for next client */
+	info.client++;
+	if (info.client < 0)
+		info.client = 0;
+	for (; info.client < SNDRV_SEQ_MAX_CLIENTS; info.client++) {
+		cptr = snd_seq_client_use_ptr(info.client);
+		if (cptr)
+			break; /* found */
+	}
+	if (cptr == NULL)
+		return -ENOENT;
+
+	get_client_info(cptr, &info);
+	snd_seq_client_unlock(cptr);
+
+	if (copy_to_user(arg, &info, sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+/* 
+ * query next port
+ */
+static int snd_seq_ioctl_query_next_port(client_t *client, void __user *arg)
+{
+	client_t *cptr;
+	client_port_t *port = NULL;
+	snd_seq_port_info_t info;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+	cptr = snd_seq_client_use_ptr(info.addr.client);
+	if (cptr == NULL)
+		return -ENXIO;
+
+	/* search for next port */
+	info.addr.port++;
+	port = snd_seq_port_query_nearest(cptr, &info);
+	if (port == NULL) {
+		snd_seq_client_unlock(cptr);
+		return -ENOENT;
+	}
+
+	/* get port info */
+	info.addr = port->addr;
+	snd_seq_get_port_info(port, &info);
+	snd_seq_port_unlock(port);
+	snd_seq_client_unlock(cptr);
+
+	if (copy_to_user(arg, &info, sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+/* -------------------------------------------------------- */
+
+static struct seq_ioctl_table {
+	unsigned int cmd;
+	int (*func)(client_t *client, void __user * arg);
+} ioctl_tables[] = {
+	{ SNDRV_SEQ_IOCTL_SYSTEM_INFO, snd_seq_ioctl_system_info },
+	{ SNDRV_SEQ_IOCTL_RUNNING_MODE, snd_seq_ioctl_running_mode },
+	{ SNDRV_SEQ_IOCTL_GET_CLIENT_INFO, snd_seq_ioctl_get_client_info },
+	{ SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, snd_seq_ioctl_set_client_info },
+	{ SNDRV_SEQ_IOCTL_CREATE_PORT, snd_seq_ioctl_create_port },
+	{ SNDRV_SEQ_IOCTL_DELETE_PORT, snd_seq_ioctl_delete_port },
+	{ SNDRV_SEQ_IOCTL_GET_PORT_INFO, snd_seq_ioctl_get_port_info },
+	{ SNDRV_SEQ_IOCTL_SET_PORT_INFO, snd_seq_ioctl_set_port_info },
+	{ SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, snd_seq_ioctl_subscribe_port },
+	{ SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, snd_seq_ioctl_unsubscribe_port },
+	{ SNDRV_SEQ_IOCTL_CREATE_QUEUE, snd_seq_ioctl_create_queue },
+	{ SNDRV_SEQ_IOCTL_DELETE_QUEUE, snd_seq_ioctl_delete_queue },
+	{ SNDRV_SEQ_IOCTL_GET_QUEUE_INFO, snd_seq_ioctl_get_queue_info },
+	{ SNDRV_SEQ_IOCTL_SET_QUEUE_INFO, snd_seq_ioctl_set_queue_info },
+	{ SNDRV_SEQ_IOCTL_GET_NAMED_QUEUE, snd_seq_ioctl_get_named_queue },
+	{ SNDRV_SEQ_IOCTL_GET_QUEUE_STATUS, snd_seq_ioctl_get_queue_status },
+	{ SNDRV_SEQ_IOCTL_GET_QUEUE_TEMPO, snd_seq_ioctl_get_queue_tempo },
+	{ SNDRV_SEQ_IOCTL_SET_QUEUE_TEMPO, snd_seq_ioctl_set_queue_tempo },
+	{ SNDRV_SEQ_IOCTL_GET_QUEUE_TIMER, snd_seq_ioctl_get_queue_timer },
+	{ SNDRV_SEQ_IOCTL_SET_QUEUE_TIMER, snd_seq_ioctl_set_queue_timer },
+	{ SNDRV_SEQ_IOCTL_GET_QUEUE_CLIENT, snd_seq_ioctl_get_queue_client },
+	{ SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT, snd_seq_ioctl_set_queue_client },
+	{ SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, snd_seq_ioctl_get_client_pool },
+	{ SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, snd_seq_ioctl_set_client_pool },
+	{ SNDRV_SEQ_IOCTL_GET_SUBSCRIPTION, snd_seq_ioctl_get_subscription },
+	{ SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT, snd_seq_ioctl_query_next_client },
+	{ SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, snd_seq_ioctl_query_next_port },
+	{ SNDRV_SEQ_IOCTL_REMOVE_EVENTS, snd_seq_ioctl_remove_events },
+	{ SNDRV_SEQ_IOCTL_QUERY_SUBS, snd_seq_ioctl_query_subs },
+	{ 0, NULL },
+};
+
+static int snd_seq_do_ioctl(client_t *client, unsigned int cmd, void __user *arg)
+{
+	struct seq_ioctl_table *p;
+
+	switch (cmd) {
+	case SNDRV_SEQ_IOCTL_PVERSION:
+		/* return sequencer version number */
+		return put_user(SNDRV_SEQ_VERSION, (int __user *)arg) ? -EFAULT : 0;
+	case SNDRV_SEQ_IOCTL_CLIENT_ID:
+		/* return the id of this client */
+		return put_user(client->number, (int __user *)arg) ? -EFAULT : 0;
+	}
+
+	if (! arg)
+		return -EFAULT;
+	for (p = ioctl_tables; p->cmd; p++) {
+		if (p->cmd == cmd)
+			return p->func(client, arg);
+	}
+	snd_printd("seq unknown ioctl() 0x%x (type='%c', number=0x%2x)\n",
+		   cmd, _IOC_TYPE(cmd), _IOC_NR(cmd));
+	return -ENOTTY;
+}
+
+
+static long snd_seq_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	client_t *client = (client_t *) file->private_data;
+
+	snd_assert(client != NULL, return -ENXIO);
+		
+	return snd_seq_do_ioctl(client, cmd, (void __user *) arg);
+}
+
+#ifdef CONFIG_COMPAT
+#include "seq_compat.c"
+#else
+#define snd_seq_ioctl_compat	NULL
+#endif
+
+/* -------------------------------------------------------- */
+
+
+/* exported to kernel modules */
+int snd_seq_create_kernel_client(snd_card_t *card, int client_index, snd_seq_client_callback_t * callback)
+{
+	client_t *client;
+
+	snd_assert(! in_interrupt(), return -EBUSY);
+
+	if (callback == NULL)
+		return -EINVAL;
+	if (card && client_index > 7)
+		return -EINVAL;
+	if (card == NULL && client_index > 63)
+		return -EINVAL;
+	if (card)
+		client_index += 64 + (card->number << 3);
+
+	if (down_interruptible(&register_mutex))
+		return -ERESTARTSYS;
+	/* empty write queue as default */
+	client = seq_create_client1(client_index, 0);
+	if (client == NULL) {
+		up(&register_mutex);
+		return -EBUSY;	/* failure code */
+	}
+	usage_alloc(&client_usage, 1);
+
+	client->accept_input = callback->allow_output;
+	client->accept_output = callback->allow_input;
+		
+	/* fill client data */
+	client->data.kernel.card = card;
+	client->data.kernel.private_data = callback->private_data;
+	sprintf(client->name, "Client-%d", client->number);
+
+	client->type = KERNEL_CLIENT;
+	up(&register_mutex);
+
+	/* make others aware this new client */
+	snd_seq_system_client_ev_client_start(client->number);
+	
+	/* return client number to caller */
+	return client->number;
+}
+
+/* exported to kernel modules */
+int snd_seq_delete_kernel_client(int client)
+{
+	client_t *ptr;
+
+	snd_assert(! in_interrupt(), return -EBUSY);
+
+	ptr = clientptr(client);
+	if (ptr == NULL)
+		return -EINVAL;
+
+	seq_free_client(ptr);
+	kfree(ptr);
+	return 0;
+}
+
+
+/* skeleton to enqueue event, called from snd_seq_kernel_client_enqueue
+ * and snd_seq_kernel_client_enqueue_blocking
+ */
+static int kernel_client_enqueue(int client, snd_seq_event_t *ev,
+				 struct file *file, int blocking,
+				 int atomic, int hop)
+{
+	client_t *cptr;
+	int result;
+
+	snd_assert(ev != NULL, return -EINVAL);
+
+	if (ev->type == SNDRV_SEQ_EVENT_NONE)
+		return 0; /* ignore this */
+	if (ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR)
+		return -EINVAL; /* quoted events can't be enqueued */
+
+	/* fill in client number */
+	ev->source.client = client;
+
+	if (check_event_type_and_length(ev))
+		return -EINVAL;
+
+	cptr = snd_seq_client_use_ptr(client);
+	if (cptr == NULL)
+		return -EINVAL;
+	
+	if (! cptr->accept_output)
+		result = -EPERM;
+	else /* send it */
+		result = snd_seq_client_enqueue_event(cptr, ev, file, blocking, atomic, hop);
+
+	snd_seq_client_unlock(cptr);
+	return result;
+}
+
+/*
+ * exported, called by kernel clients to enqueue events (w/o blocking)
+ *
+ * RETURN VALUE: zero if succeed, negative if error
+ */
+int snd_seq_kernel_client_enqueue(int client, snd_seq_event_t * ev,
+				  int atomic, int hop)
+{
+	return kernel_client_enqueue(client, ev, NULL, 0, atomic, hop);
+}
+
+/*
+ * exported, called by kernel clients to enqueue events (with blocking)
+ *
+ * RETURN VALUE: zero if succeed, negative if error
+ */
+int snd_seq_kernel_client_enqueue_blocking(int client, snd_seq_event_t * ev,
+					   struct file *file,
+					   int atomic, int hop)
+{
+	return kernel_client_enqueue(client, ev, file, 1, atomic, hop);
+}
+
+
+/* 
+ * exported, called by kernel clients to dispatch events directly to other
+ * clients, bypassing the queues.  Event time-stamp will be updated.
+ *
+ * RETURN VALUE: negative = delivery failed,
+ *		 zero, or positive: the number of delivered events
+ */
+int snd_seq_kernel_client_dispatch(int client, snd_seq_event_t * ev,
+				   int atomic, int hop)
+{
+	client_t *cptr;
+	int result;
+
+	snd_assert(ev != NULL, return -EINVAL);
+
+	/* fill in client number */
+	ev->queue = SNDRV_SEQ_QUEUE_DIRECT;
+	ev->source.client = client;
+
+	if (check_event_type_and_length(ev))
+		return -EINVAL;
+
+	cptr = snd_seq_client_use_ptr(client);
+	if (cptr == NULL)
+		return -EINVAL;
+
+	if (!cptr->accept_output)
+		result = -EPERM;
+	else
+		result = snd_seq_deliver_event(cptr, ev, atomic, hop);
+
+	snd_seq_client_unlock(cptr);
+	return result;
+}
+
+
+/*
+ * exported, called by kernel clients to perform same functions as with
+ * userland ioctl() 
+ */
+int snd_seq_kernel_client_ctl(int clientid, unsigned int cmd, void *arg)
+{
+	client_t *client;
+	mm_segment_t fs;
+	int result;
+
+	client = clientptr(clientid);
+	if (client == NULL)
+		return -ENXIO;
+	fs = snd_enter_user();
+	result = snd_seq_do_ioctl(client, cmd, (void __user *)arg);
+	snd_leave_user(fs);
+	return result;
+}
+
+
+/* exported (for OSS emulator) */
+int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table *wait)
+{
+	client_t *client;
+
+	client = clientptr(clientid);
+	if (client == NULL)
+		return -ENXIO;
+
+	if (! snd_seq_write_pool_allocated(client))
+		return 1;
+	if (snd_seq_pool_poll_wait(client->pool, file, wait))
+		return 1;
+	return 0;
+}
+
+/*---------------------------------------------------------------------------*/
+
+/*
+ *  /proc interface
+ */
+static void snd_seq_info_dump_subscribers(snd_info_buffer_t *buffer, port_subs_info_t *group, int is_src, char *msg)
+{
+	struct list_head *p;
+	subscribers_t *s;
+	int count = 0;
+
+	down_read(&group->list_mutex);
+	if (list_empty(&group->list_head)) {
+		up_read(&group->list_mutex);
+		return;
+	}
+	snd_iprintf(buffer, msg);
+	list_for_each(p, &group->list_head) {
+		if (is_src)
+			s = list_entry(p, subscribers_t, src_list);
+		else
+			s = list_entry(p, subscribers_t, dest_list);
+		if (count++)
+			snd_iprintf(buffer, ", ");
+		snd_iprintf(buffer, "%d:%d",
+			    is_src ? s->info.dest.client : s->info.sender.client,
+			    is_src ? s->info.dest.port : s->info.sender.port);
+		if (s->info.flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP)
+			snd_iprintf(buffer, "[%c:%d]", ((s->info.flags & SNDRV_SEQ_PORT_SUBS_TIME_REAL) ? 'r' : 't'), s->info.queue);
+		if (group->exclusive)
+			snd_iprintf(buffer, "[ex]");
+	}
+	up_read(&group->list_mutex);
+	snd_iprintf(buffer, "\n");
+}
+
+#define FLAG_PERM_RD(perm) ((perm) & SNDRV_SEQ_PORT_CAP_READ ? ((perm) & SNDRV_SEQ_PORT_CAP_SUBS_READ ? 'R' : 'r') : '-')
+#define FLAG_PERM_WR(perm) ((perm) & SNDRV_SEQ_PORT_CAP_WRITE ? ((perm) & SNDRV_SEQ_PORT_CAP_SUBS_WRITE ? 'W' : 'w') : '-')
+#define FLAG_PERM_EX(perm) ((perm) & SNDRV_SEQ_PORT_CAP_NO_EXPORT ? '-' : 'e')
+
+#define FLAG_PERM_DUPLEX(perm) ((perm) & SNDRV_SEQ_PORT_CAP_DUPLEX ? 'X' : '-')
+
+static void snd_seq_info_dump_ports(snd_info_buffer_t *buffer, client_t *client)
+{
+	struct list_head *l;
+
+	down(&client->ports_mutex);
+	list_for_each(l, &client->ports_list_head) {
+		client_port_t *p = list_entry(l, client_port_t, list);
+		snd_iprintf(buffer, "  Port %3d : \"%s\" (%c%c%c%c)\n",
+			    p->addr.port, p->name,
+			    FLAG_PERM_RD(p->capability),
+			    FLAG_PERM_WR(p->capability),
+			    FLAG_PERM_EX(p->capability),
+			    FLAG_PERM_DUPLEX(p->capability));
+		snd_seq_info_dump_subscribers(buffer, &p->c_src, 1, "    Connecting To: ");
+		snd_seq_info_dump_subscribers(buffer, &p->c_dest, 0, "    Connected From: ");
+	}
+	up(&client->ports_mutex);
+}
+
+
+/* exported to seq_info.c */
+void snd_seq_info_clients_read(snd_info_entry_t *entry, 
+			       snd_info_buffer_t * buffer)
+{
+	extern void snd_seq_info_pool(snd_info_buffer_t * buffer, pool_t * pool, char *space);
+	int c;
+	client_t *client;
+
+	snd_iprintf(buffer, "Client info\n");
+	snd_iprintf(buffer, "  cur  clients : %d\n", client_usage.cur);
+	snd_iprintf(buffer, "  peak clients : %d\n", client_usage.peak);
+	snd_iprintf(buffer, "  max  clients : %d\n", SNDRV_SEQ_MAX_CLIENTS);
+	snd_iprintf(buffer, "\n");
+
+	/* list the client table */
+	for (c = 0; c < SNDRV_SEQ_MAX_CLIENTS; c++) {
+		client = snd_seq_client_use_ptr(c);
+		if (client == NULL)
+			continue;
+		if (client->type == NO_CLIENT) {
+			snd_seq_client_unlock(client);
+			continue;
+		}
+
+		snd_iprintf(buffer, "Client %3d : \"%s\" [%s]\n",
+			    c, client->name,
+			    client->type == USER_CLIENT ? "User" : "Kernel");
+		snd_seq_info_dump_ports(buffer, client);
+		if (snd_seq_write_pool_allocated(client)) {
+			snd_iprintf(buffer, "  Output pool :\n");
+			snd_seq_info_pool(buffer, client->pool, "    ");
+		}
+		if (client->type == USER_CLIENT && client->data.user.fifo &&
+		    client->data.user.fifo->pool) {
+			snd_iprintf(buffer, "  Input pool :\n");
+			snd_seq_info_pool(buffer, client->data.user.fifo->pool, "    ");
+		}
+		snd_seq_client_unlock(client);
+	}
+}
+
+
+/*---------------------------------------------------------------------------*/
+
+
+/*
+ *  REGISTRATION PART
+ */
+
+static struct file_operations snd_seq_f_ops =
+{
+	.owner =	THIS_MODULE,
+	.read =		snd_seq_read,
+	.write =	snd_seq_write,
+	.open =		snd_seq_open,
+	.release =	snd_seq_release,
+	.poll =		snd_seq_poll,
+	.unlocked_ioctl =	snd_seq_ioctl,
+	.compat_ioctl =	snd_seq_ioctl_compat,
+};
+
+static snd_minor_t snd_seq_reg =
+{
+	.comment =	"sequencer",
+	.f_ops =	&snd_seq_f_ops,
+};
+
+
+/* 
+ * register sequencer device 
+ */
+int __init snd_sequencer_device_init(void)
+{
+	int err;
+
+	if (down_interruptible(&register_mutex))
+		return -ERESTARTSYS;
+
+	if ((err = snd_register_device(SNDRV_DEVICE_TYPE_SEQUENCER, NULL, 0, &snd_seq_reg, "seq")) < 0) {
+		up(&register_mutex);
+		return err;
+	}
+	
+	up(&register_mutex);
+
+	return 0;
+}
+
+
+
+/* 
+ * unregister sequencer device 
+ */
+void __exit snd_sequencer_device_done(void)
+{
+	snd_unregister_device(SNDRV_DEVICE_TYPE_SEQUENCER, NULL, 0);
+}
diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h
new file mode 100644
index 000000000000..3715c36183d3
--- /dev/null
+++ b/sound/core/seq/seq_clientmgr.h
@@ -0,0 +1,104 @@
+/*
+ *   ALSA sequencer Client Manager
+ *   Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ *   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 __SND_SEQ_CLIENTMGR_H
+#define __SND_SEQ_CLIENTMGR_H
+
+#include <sound/seq_kernel.h>
+#include <linux/bitops.h>
+#include "seq_fifo.h"
+#include "seq_ports.h"
+#include "seq_lock.h"
+
+
+/* client manager */
+
+struct _snd_seq_user_client {
+	struct file *file;	/* file struct of client */
+	/* ... */
+	
+	/* fifo */
+	fifo_t *fifo;	/* queue for incoming events */
+	int fifo_pool_size;
+};
+
+struct _snd_seq_kernel_client {
+	snd_card_t *card;
+	/* pointer to client functions */
+	void *private_data;			/* private data for client */
+	/* ... */
+};
+
+
+struct _snd_seq_client {
+	snd_seq_client_type_t type;
+	unsigned int accept_input: 1,
+		accept_output: 1;
+	char name[64];		/* client name */
+	int number;		/* client number */
+	unsigned int filter;	/* filter flags */
+	DECLARE_BITMAP(event_filter, 256);
+	snd_use_lock_t use_lock;
+	int event_lost;
+	/* ports */
+	int num_ports;		/* number of ports */
+	struct list_head ports_list_head;
+	rwlock_t ports_lock;
+	struct semaphore ports_mutex;
+	int convert32;		/* convert 32->64bit */
+
+	/* output pool */
+	pool_t *pool;		/* memory pool for this client */
+
+	union {
+		user_client_t user;
+		kernel_client_t kernel;
+	} data;
+};
+
+/* usage statistics */
+typedef struct {
+	int cur;
+	int peak;
+} usage_t;
+
+
+extern int client_init_data(void);
+extern int snd_sequencer_device_init(void);
+extern void snd_sequencer_device_done(void);
+
+/* get locked pointer to client */
+extern client_t *snd_seq_client_use_ptr(int clientid);
+
+/* unlock pointer to client */
+#define snd_seq_client_unlock(client) snd_use_lock_free(&(client)->use_lock)
+
+/* dispatch event to client(s) */
+extern int snd_seq_dispatch_event(snd_seq_event_cell_t *cell, int atomic, int hop);
+
+/* exported to other modules */
+extern int snd_seq_register_kernel_client(snd_seq_client_callback_t *callback, void *private_data);
+extern int snd_seq_unregister_kernel_client(int client);
+extern int snd_seq_kernel_client_enqueue(int client, snd_seq_event_t *ev, int atomic, int hop);
+int snd_seq_kernel_client_enqueue_blocking(int client, snd_seq_event_t * ev, struct file *file, int atomic, int hop);
+int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table *wait);
+int snd_seq_client_notify_subscription(int client, int port, snd_seq_port_subscribe_t *info, int evtype);
+
+#endif
diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c
new file mode 100644
index 000000000000..902ad8b0c355
--- /dev/null
+++ b/sound/core/seq/seq_compat.c
@@ -0,0 +1,137 @@
+/*
+ *   32bit -> 64bit ioctl wrapper for sequencer API
+ *   Copyright (c) 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
+ *
+ */
+
+/* This file included from seq.c */
+
+#include <linux/compat.h>
+
+struct sndrv_seq_port_info32 {
+	struct sndrv_seq_addr addr;	/* client/port numbers */
+	char name[64];			/* port name */
+
+	u32 capability;	/* port capability bits */
+	u32 type;		/* port type bits */
+	s32 midi_channels;		/* channels per MIDI port */
+	s32 midi_voices;		/* voices per MIDI port */
+	s32 synth_voices;		/* voices per SYNTH port */
+
+	s32 read_use;			/* R/O: subscribers for output (from this port) */
+	s32 write_use;			/* R/O: subscribers for input (to this port) */
+
+	u32 kernel;			/* reserved for kernel use (must be NULL) */
+	u32 flags;		/* misc. conditioning */
+	unsigned char time_queue;	/* queue # for timestamping */
+	char reserved[59];		/* for future use */
+};
+
+static int snd_seq_call_port_info_ioctl(client_t *client, unsigned int cmd,
+					struct sndrv_seq_port_info32 __user *data32)
+{
+	int err = -EFAULT;
+	snd_seq_port_info_t *data;
+	mm_segment_t fs;
+
+	data = kmalloc(sizeof(*data), GFP_KERNEL);
+	if (! data)
+		return -ENOMEM;
+
+	if (copy_from_user(data, data32, sizeof(*data32)) ||
+	    get_user(data->flags, &data32->flags) ||
+	    get_user(data->time_queue, &data32->time_queue))
+		goto error;
+	data->kernel = NULL;
+
+	fs = snd_enter_user();
+	err = snd_seq_do_ioctl(client, cmd, data);
+	snd_leave_user(fs);
+	if (err < 0)
+		goto error;
+
+	if (copy_to_user(data32, data, sizeof(*data32)) ||
+	    put_user(data->flags, &data32->flags) ||
+	    put_user(data->time_queue, &data32->time_queue))
+		err = -EFAULT;
+
+ error:
+	kfree(data);
+	return err;
+}
+
+
+
+/*
+ */
+
+enum {
+	SNDRV_SEQ_IOCTL_CREATE_PORT32 = _IOWR('S', 0x20, struct sndrv_seq_port_info32),
+	SNDRV_SEQ_IOCTL_DELETE_PORT32 = _IOW ('S', 0x21, struct sndrv_seq_port_info32),
+	SNDRV_SEQ_IOCTL_GET_PORT_INFO32 = _IOWR('S', 0x22, struct sndrv_seq_port_info32),
+	SNDRV_SEQ_IOCTL_SET_PORT_INFO32 = _IOW ('S', 0x23, struct sndrv_seq_port_info32),
+	SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT32 = _IOWR('S', 0x52, struct sndrv_seq_port_info32),
+};
+
+static long snd_seq_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	client_t *client = (client_t *) file->private_data;
+	void __user *argp = compat_ptr(arg);
+
+	snd_assert(client != NULL, return -ENXIO);
+
+	switch (cmd) {
+	case SNDRV_SEQ_IOCTL_PVERSION:
+	case SNDRV_SEQ_IOCTL_CLIENT_ID:
+	case SNDRV_SEQ_IOCTL_SYSTEM_INFO:
+	case SNDRV_SEQ_IOCTL_GET_CLIENT_INFO:
+	case SNDRV_SEQ_IOCTL_SET_CLIENT_INFO:
+	case SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT:
+	case SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT:
+	case SNDRV_SEQ_IOCTL_CREATE_QUEUE:
+	case SNDRV_SEQ_IOCTL_DELETE_QUEUE:
+	case SNDRV_SEQ_IOCTL_GET_QUEUE_INFO:
+	case SNDRV_SEQ_IOCTL_SET_QUEUE_INFO:
+	case SNDRV_SEQ_IOCTL_GET_NAMED_QUEUE:
+	case SNDRV_SEQ_IOCTL_GET_QUEUE_STATUS:
+	case SNDRV_SEQ_IOCTL_GET_QUEUE_TEMPO:
+	case SNDRV_SEQ_IOCTL_SET_QUEUE_TEMPO:
+	case SNDRV_SEQ_IOCTL_GET_QUEUE_TIMER:
+	case SNDRV_SEQ_IOCTL_SET_QUEUE_TIMER:
+	case SNDRV_SEQ_IOCTL_GET_QUEUE_CLIENT:
+	case SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT:
+	case SNDRV_SEQ_IOCTL_GET_CLIENT_POOL:
+	case SNDRV_SEQ_IOCTL_SET_CLIENT_POOL:
+	case SNDRV_SEQ_IOCTL_REMOVE_EVENTS:
+	case SNDRV_SEQ_IOCTL_QUERY_SUBS:
+	case SNDRV_SEQ_IOCTL_GET_SUBSCRIPTION:
+	case SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT:
+	case SNDRV_SEQ_IOCTL_RUNNING_MODE:
+		return snd_seq_do_ioctl(client, cmd, argp);
+	case SNDRV_SEQ_IOCTL_CREATE_PORT32:
+		return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_CREATE_PORT, argp);
+	case SNDRV_SEQ_IOCTL_DELETE_PORT32:
+		return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_DELETE_PORT, argp);
+	case SNDRV_SEQ_IOCTL_GET_PORT_INFO32:
+		return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_GET_PORT_INFO, argp);
+	case SNDRV_SEQ_IOCTL_SET_PORT_INFO32:
+		return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_SET_PORT_INFO, argp);
+	case SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT32:
+		return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, argp);
+	}
+	return -ENOIOCTLCMD;
+}
diff --git a/sound/core/seq/seq_device.c b/sound/core/seq/seq_device.c
new file mode 100644
index 000000000000..4d80f39612e8
--- /dev/null
+++ b/sound/core/seq/seq_device.c
@@ -0,0 +1,575 @@
+/*
+ *  ALSA sequencer device management
+ *  Copyright (c) 1999 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
+ *
+ *
+ *----------------------------------------------------------------
+ *
+ * This device handler separates the card driver module from sequencer
+ * stuff (sequencer core, synth drivers, etc), so that user can avoid
+ * to spend unnecessary resources e.g. if he needs only listening to
+ * MP3s.
+ *
+ * The card (or lowlevel) driver creates a sequencer device entry
+ * via snd_seq_device_new().  This is an entry pointer to communicate
+ * with the sequencer device "driver", which is involved with the
+ * actual part to communicate with the sequencer core.
+ * Each sequencer device entry has an id string and the corresponding
+ * driver with the same id is loaded when required.  For example,
+ * lowlevel codes to access emu8000 chip on sbawe card are included in
+ * emu8000-synth module.  To activate this module, the hardware
+ * resources like i/o port are passed via snd_seq_device argument.
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/seq_device.h>
+#include <sound/seq_kernel.h>
+#include <sound/initval.h>
+#include <linux/kmod.h>
+#include <linux/slab.h>
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("ALSA sequencer device management");
+MODULE_LICENSE("GPL");
+
+/*
+ * driver list
+ */
+typedef struct ops_list ops_list_t;
+
+/* driver state */
+#define DRIVER_EMPTY		0
+#define DRIVER_LOADED		(1<<0)
+#define DRIVER_REQUESTED	(1<<1)
+#define DRIVER_LOCKED		(1<<2)
+
+struct ops_list {
+	char id[ID_LEN];	/* driver id */
+	int driver;		/* driver state */
+	int used;		/* reference counter */
+	int argsize;		/* argument size */
+
+	/* operators */
+	snd_seq_dev_ops_t ops;
+
+	/* registred devices */
+	struct list_head dev_list;	/* list of devices */
+	int num_devices;	/* number of associated devices */
+	int num_init_devices;	/* number of initialized devices */
+	struct semaphore reg_mutex;
+
+	struct list_head list;	/* next driver */
+};
+
+
+static LIST_HEAD(opslist);
+static int num_ops;
+static DECLARE_MUTEX(ops_mutex);
+static snd_info_entry_t *info_entry = NULL;
+
+/*
+ * prototypes
+ */
+static int snd_seq_device_free(snd_seq_device_t *dev);
+static int snd_seq_device_dev_free(snd_device_t *device);
+static int snd_seq_device_dev_register(snd_device_t *device);
+static int snd_seq_device_dev_disconnect(snd_device_t *device);
+static int snd_seq_device_dev_unregister(snd_device_t *device);
+
+static int init_device(snd_seq_device_t *dev, ops_list_t *ops);
+static int free_device(snd_seq_device_t *dev, ops_list_t *ops);
+static ops_list_t *find_driver(char *id, int create_if_empty);
+static ops_list_t *create_driver(char *id);
+static void unlock_driver(ops_list_t *ops);
+static void remove_drivers(void);
+
+/*
+ * show all drivers and their status
+ */
+
+static void snd_seq_device_info(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+	struct list_head *head;
+
+	down(&ops_mutex);
+	list_for_each(head, &opslist) {
+		ops_list_t *ops = list_entry(head, ops_list_t, list);
+		snd_iprintf(buffer, "snd-%s%s%s%s,%d\n",
+				ops->id,
+				ops->driver & DRIVER_LOADED ? ",loaded" : (ops->driver == DRIVER_EMPTY ? ",empty" : ""),
+				ops->driver & DRIVER_REQUESTED ? ",requested" : "",
+				ops->driver & DRIVER_LOCKED ? ",locked" : "",
+				ops->num_devices);
+	}
+	up(&ops_mutex);	
+}
+ 
+/*
+ * load all registered drivers (called from seq_clientmgr.c)
+ */
+
+#ifdef CONFIG_KMOD
+/* avoid auto-loading during module_init() */
+static int snd_seq_in_init;
+void snd_seq_autoload_lock(void)
+{
+	snd_seq_in_init++;
+}
+
+void snd_seq_autoload_unlock(void)
+{
+	snd_seq_in_init--;
+}
+#endif
+
+void snd_seq_device_load_drivers(void)
+{
+#ifdef CONFIG_KMOD
+	struct list_head *head;
+
+	/* Calling request_module during module_init()
+	 * may cause blocking.
+	 */
+	if (snd_seq_in_init)
+		return;
+
+	if (! current->fs->root)
+		return;
+
+	down(&ops_mutex);
+	list_for_each(head, &opslist) {
+		ops_list_t *ops = list_entry(head, ops_list_t, list);
+		if (! (ops->driver & DRIVER_LOADED) &&
+		    ! (ops->driver & DRIVER_REQUESTED)) {
+			ops->used++;
+			up(&ops_mutex);
+			ops->driver |= DRIVER_REQUESTED;
+			request_module("snd-%s", ops->id);
+			down(&ops_mutex);
+			ops->used--;
+		}
+	}
+	up(&ops_mutex);
+#endif
+}
+
+/*
+ * register a sequencer device
+ * card = card info (NULL allowed)
+ * device = device number (if any)
+ * id = id of driver
+ * result = return pointer (NULL allowed if unnecessary)
+ */
+int snd_seq_device_new(snd_card_t *card, int device, char *id, int argsize,
+		       snd_seq_device_t **result)
+{
+	snd_seq_device_t *dev;
+	ops_list_t *ops;
+	int err;
+	static snd_device_ops_t dops = {
+		.dev_free = snd_seq_device_dev_free,
+		.dev_register = snd_seq_device_dev_register,
+		.dev_disconnect = snd_seq_device_dev_disconnect,
+		.dev_unregister = snd_seq_device_dev_unregister
+	};
+
+	if (result)
+		*result = NULL;
+
+	snd_assert(id != NULL, return -EINVAL);
+
+	ops = find_driver(id, 1);
+	if (ops == NULL)
+		return -ENOMEM;
+
+	dev = kcalloc(1, sizeof(*dev)*2 + argsize, GFP_KERNEL);
+	if (dev == NULL) {
+		unlock_driver(ops);
+		return -ENOMEM;
+	}
+
+	/* set up device info */
+	dev->card = card;
+	dev->device = device;
+	strlcpy(dev->id, id, sizeof(dev->id));
+	dev->argsize = argsize;
+	dev->status = SNDRV_SEQ_DEVICE_FREE;
+
+	/* add this device to the list */
+	down(&ops->reg_mutex);
+	list_add_tail(&dev->list, &ops->dev_list);
+	ops->num_devices++;
+	up(&ops->reg_mutex);
+
+	unlock_driver(ops);
+	
+	if ((err = snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops)) < 0) {
+		snd_seq_device_free(dev);
+		return err;
+	}
+	
+	if (result)
+		*result = dev;
+
+	return 0;
+}
+
+/*
+ * free the existing device
+ */
+static int snd_seq_device_free(snd_seq_device_t *dev)
+{
+	ops_list_t *ops;
+
+	snd_assert(dev != NULL, return -EINVAL);
+
+	ops = find_driver(dev->id, 0);
+	if (ops == NULL)
+		return -ENXIO;
+
+	/* remove the device from the list */
+	down(&ops->reg_mutex);
+	list_del(&dev->list);
+	ops->num_devices--;
+	up(&ops->reg_mutex);
+
+	free_device(dev, ops);
+	if (dev->private_free)
+		dev->private_free(dev);
+	kfree(dev);
+
+	unlock_driver(ops);
+
+	return 0;
+}
+
+static int snd_seq_device_dev_free(snd_device_t *device)
+{
+	snd_seq_device_t *dev = device->device_data;
+	return snd_seq_device_free(dev);
+}
+
+/*
+ * register the device
+ */
+static int snd_seq_device_dev_register(snd_device_t *device)
+{
+	snd_seq_device_t *dev = device->device_data;
+	ops_list_t *ops;
+
+	ops = find_driver(dev->id, 0);
+	if (ops == NULL)
+		return -ENOENT;
+
+	/* initialize this device if the corresponding driver was
+	 * already loaded
+	 */
+	if (ops->driver & DRIVER_LOADED)
+		init_device(dev, ops);
+
+	unlock_driver(ops);
+	return 0;
+}
+
+/*
+ * disconnect the device
+ */
+static int snd_seq_device_dev_disconnect(snd_device_t *device)
+{
+	snd_seq_device_t *dev = device->device_data;
+	ops_list_t *ops;
+
+	ops = find_driver(dev->id, 0);
+	if (ops == NULL)
+		return -ENOENT;
+
+	free_device(dev, ops);
+
+	unlock_driver(ops);
+	return 0;
+}
+
+/*
+ * unregister the existing device
+ */
+static int snd_seq_device_dev_unregister(snd_device_t *device)
+{
+	snd_seq_device_t *dev = device->device_data;
+	return snd_seq_device_free(dev);
+}
+
+/*
+ * register device driver
+ * id = driver id
+ * entry = driver operators - duplicated to each instance
+ */
+int snd_seq_device_register_driver(char *id, snd_seq_dev_ops_t *entry, int argsize)
+{
+	struct list_head *head;
+	ops_list_t *ops;
+
+	if (id == NULL || entry == NULL ||
+	    entry->init_device == NULL || entry->free_device == NULL)
+		return -EINVAL;
+
+	snd_seq_autoload_lock();
+	ops = find_driver(id, 1);
+	if (ops == NULL) {
+		snd_seq_autoload_unlock();
+		return -ENOMEM;
+	}
+	if (ops->driver & DRIVER_LOADED) {
+		snd_printk(KERN_WARNING "driver_register: driver '%s' already exists\n", id);
+		unlock_driver(ops);
+		snd_seq_autoload_unlock();
+		return -EBUSY;
+	}
+
+	down(&ops->reg_mutex);
+	/* copy driver operators */
+	ops->ops = *entry;
+	ops->driver |= DRIVER_LOADED;
+	ops->argsize = argsize;
+
+	/* initialize existing devices if necessary */
+	list_for_each(head, &ops->dev_list) {
+		snd_seq_device_t *dev = list_entry(head, snd_seq_device_t, list);
+		init_device(dev, ops);
+	}
+	up(&ops->reg_mutex);
+
+	unlock_driver(ops);
+	snd_seq_autoload_unlock();
+
+	return 0;
+}
+
+
+/*
+ * create driver record
+ */
+static ops_list_t * create_driver(char *id)
+{
+	ops_list_t *ops;
+
+	ops = kmalloc(sizeof(*ops), GFP_KERNEL);
+	if (ops == NULL)
+		return ops;
+	memset(ops, 0, sizeof(*ops));
+
+	/* set up driver entry */
+	strlcpy(ops->id, id, sizeof(ops->id));
+	init_MUTEX(&ops->reg_mutex);
+	ops->driver = DRIVER_EMPTY;
+	INIT_LIST_HEAD(&ops->dev_list);
+	/* lock this instance */
+	ops->used = 1;
+
+	/* register driver entry */
+	down(&ops_mutex);
+	list_add_tail(&ops->list, &opslist);
+	num_ops++;
+	up(&ops_mutex);
+
+	return ops;
+}
+
+
+/*
+ * unregister the specified driver
+ */
+int snd_seq_device_unregister_driver(char *id)
+{
+	struct list_head *head;
+	ops_list_t *ops;
+
+	ops = find_driver(id, 0);
+	if (ops == NULL)
+		return -ENXIO;
+	if (! (ops->driver & DRIVER_LOADED) ||
+	    (ops->driver & DRIVER_LOCKED)) {
+		snd_printk(KERN_ERR "driver_unregister: cannot unload driver '%s': status=%x\n", id, ops->driver);
+		unlock_driver(ops);
+		return -EBUSY;
+	}
+
+	/* close and release all devices associated with this driver */
+	down(&ops->reg_mutex);
+	ops->driver |= DRIVER_LOCKED; /* do not remove this driver recursively */
+	list_for_each(head, &ops->dev_list) {
+		snd_seq_device_t *dev = list_entry(head, snd_seq_device_t, list);
+		free_device(dev, ops);
+	}
+
+	ops->driver = 0;
+	if (ops->num_init_devices > 0)
+		snd_printk(KERN_ERR "free_driver: init_devices > 0!! (%d)\n", ops->num_init_devices);
+	up(&ops->reg_mutex);
+
+	unlock_driver(ops);
+
+	/* remove empty driver entries */
+	remove_drivers();
+
+	return 0;
+}
+
+
+/*
+ * remove empty driver entries
+ */
+static void remove_drivers(void)
+{
+	struct list_head *head;
+
+	down(&ops_mutex);
+	head = opslist.next;
+	while (head != &opslist) {
+		ops_list_t *ops = list_entry(head, ops_list_t, list);
+		if (! (ops->driver & DRIVER_LOADED) &&
+		    ops->used == 0 && ops->num_devices == 0) {
+			head = head->next;
+			list_del(&ops->list);
+			kfree(ops);
+			num_ops--;
+		} else
+			head = head->next;
+	}
+	up(&ops_mutex);
+}
+
+/*
+ * initialize the device - call init_device operator
+ */
+static int init_device(snd_seq_device_t *dev, ops_list_t *ops)
+{
+	if (! (ops->driver & DRIVER_LOADED))
+		return 0; /* driver is not loaded yet */
+	if (dev->status != SNDRV_SEQ_DEVICE_FREE)
+		return 0; /* already initialized */
+	if (ops->argsize != dev->argsize) {
+		snd_printk(KERN_ERR "incompatible device '%s' for plug-in '%s' (%d %d)\n", dev->name, ops->id, ops->argsize, dev->argsize);
+		return -EINVAL;
+	}
+	if (ops->ops.init_device(dev) >= 0) {
+		dev->status = SNDRV_SEQ_DEVICE_REGISTERED;
+		ops->num_init_devices++;
+	} else {
+		snd_printk(KERN_ERR "init_device failed: %s: %s\n", dev->name, dev->id);
+	}
+
+	return 0;
+}
+
+/*
+ * release the device - call free_device operator
+ */
+static int free_device(snd_seq_device_t *dev, ops_list_t *ops)
+{
+	int result;
+
+	if (! (ops->driver & DRIVER_LOADED))
+		return 0; /* driver is not loaded yet */
+	if (dev->status != SNDRV_SEQ_DEVICE_REGISTERED)
+		return 0; /* not registered */
+	if (ops->argsize != dev->argsize) {
+		snd_printk(KERN_ERR "incompatible device '%s' for plug-in '%s' (%d %d)\n", dev->name, ops->id, ops->argsize, dev->argsize);
+		return -EINVAL;
+	}
+	if ((result = ops->ops.free_device(dev)) >= 0 || result == -ENXIO) {
+		dev->status = SNDRV_SEQ_DEVICE_FREE;
+		dev->driver_data = NULL;
+		ops->num_init_devices--;
+	} else {
+		snd_printk(KERN_ERR "free_device failed: %s: %s\n", dev->name, dev->id);
+	}
+
+	return 0;
+}
+
+/*
+ * find the matching driver with given id
+ */
+static ops_list_t * find_driver(char *id, int create_if_empty)
+{
+	struct list_head *head;
+
+	down(&ops_mutex);
+	list_for_each(head, &opslist) {
+		ops_list_t *ops = list_entry(head, ops_list_t, list);
+		if (strcmp(ops->id, id) == 0) {
+			ops->used++;
+			up(&ops_mutex);
+			return ops;
+		}
+	}
+	up(&ops_mutex);
+	if (create_if_empty)
+		return create_driver(id);
+	return NULL;
+}
+
+static void unlock_driver(ops_list_t *ops)
+{
+	down(&ops_mutex);
+	ops->used--;
+	up(&ops_mutex);
+}
+
+
+/*
+ * module part
+ */
+
+static int __init alsa_seq_device_init(void)
+{
+	info_entry = snd_info_create_module_entry(THIS_MODULE, "drivers", snd_seq_root);
+	if (info_entry == NULL)
+		return -ENOMEM;
+	info_entry->content = SNDRV_INFO_CONTENT_TEXT;
+	info_entry->c.text.read_size = 2048;
+	info_entry->c.text.read = snd_seq_device_info;
+	if (snd_info_register(info_entry) < 0) {
+		snd_info_free_entry(info_entry);
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+static void __exit alsa_seq_device_exit(void)
+{
+	remove_drivers();
+	snd_info_unregister(info_entry);
+	if (num_ops)
+		snd_printk(KERN_ERR "drivers not released (%d)\n", num_ops);
+}
+
+module_init(alsa_seq_device_init)
+module_exit(alsa_seq_device_exit)
+
+EXPORT_SYMBOL(snd_seq_device_load_drivers);
+EXPORT_SYMBOL(snd_seq_device_new);
+EXPORT_SYMBOL(snd_seq_device_register_driver);
+EXPORT_SYMBOL(snd_seq_device_unregister_driver);
+#ifdef CONFIG_KMOD
+EXPORT_SYMBOL(snd_seq_autoload_lock);
+EXPORT_SYMBOL(snd_seq_autoload_unlock);
+#endif
diff --git a/sound/core/seq/seq_dummy.c b/sound/core/seq/seq_dummy.c
new file mode 100644
index 000000000000..e88967c5b93d
--- /dev/null
+++ b/sound/core/seq/seq_dummy.c
@@ -0,0 +1,273 @@
+/*
+ * ALSA sequencer MIDI-through client
+ * Copyright (c) 1999-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
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include "seq_clientmgr.h"
+#include <sound/initval.h>
+#include <sound/asoundef.h>
+
+/*
+
+  Sequencer MIDI-through client
+
+  This gives a simple midi-through client.  All the normal input events
+  are redirected to output port immediately.
+  The routing can be done via aconnect program in alsa-utils.
+
+  Each client has a static client number 62 (= SNDRV_SEQ_CLIENT_DUMMY).
+  If you want to auto-load this module, you may add the following alias
+  in your /etc/conf.modules file.
+
+	alias snd-seq-client-62  snd-seq-dummy
+
+  The module is loaded on demand for client 62, or /proc/asound/seq/
+  is accessed.  If you don't need this module to be loaded, alias
+  snd-seq-client-62 as "off".  This will help modprobe.
+
+  The number of ports to be created can be specified via the module
+  parameter "ports".  For example, to create four ports, add the
+  following option in /etc/modprobe.conf:
+
+	option snd-seq-dummy ports=4
+
+  The modle option "duplex=1" enables duplex operation to the port.
+  In duplex mode, a pair of ports are created instead of single port,
+  and events are tunneled between pair-ports.  For example, input to
+  port A is sent to output port of another port B and vice versa.
+  In duplex mode, each port has DUPLEX capability.
+
+ */
+
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("ALSA sequencer MIDI-through client");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("snd-seq-client-" __stringify(SNDRV_SEQ_CLIENT_DUMMY));
+
+static int ports = 1;
+static int duplex = 0;
+
+module_param(ports, int, 0444);
+MODULE_PARM_DESC(ports, "number of ports to be created");
+module_param(duplex, bool, 0444);
+MODULE_PARM_DESC(duplex, "create DUPLEX ports");
+
+typedef struct snd_seq_dummy_port {
+	int client;
+	int port;
+	int duplex;
+	int connect;
+} snd_seq_dummy_port_t;
+
+static int my_client = -1;
+
+/*
+ * unuse callback - send ALL_SOUNDS_OFF and RESET_CONTROLLERS events
+ * to subscribers.
+ * Note: this callback is called only after all subscribers are removed.
+ */
+static int
+dummy_unuse(void *private_data, snd_seq_port_subscribe_t *info)
+{
+	snd_seq_dummy_port_t *p;
+	int i;
+	snd_seq_event_t ev;
+
+	p = private_data;
+	memset(&ev, 0, sizeof(ev));
+	if (p->duplex)
+		ev.source.port = p->connect;
+	else
+		ev.source.port = p->port;
+	ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
+	ev.type = SNDRV_SEQ_EVENT_CONTROLLER;
+	for (i = 0; i < 16; i++) {
+		ev.data.control.channel = i;
+		ev.data.control.param = MIDI_CTL_ALL_SOUNDS_OFF;
+		snd_seq_kernel_client_dispatch(p->client, &ev, 0, 0);
+		ev.data.control.param = MIDI_CTL_RESET_CONTROLLERS;
+		snd_seq_kernel_client_dispatch(p->client, &ev, 0, 0);
+	}
+	return 0;
+}
+
+/*
+ * event input callback - just redirect events to subscribers
+ */
+static int
+dummy_input(snd_seq_event_t *ev, int direct, void *private_data, int atomic, int hop)
+{
+	snd_seq_dummy_port_t *p;
+	snd_seq_event_t tmpev;
+
+	p = private_data;
+	if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM ||
+	    ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR)
+		return 0; /* ignore system messages */
+	tmpev = *ev;
+	if (p->duplex)
+		tmpev.source.port = p->connect;
+	else
+		tmpev.source.port = p->port;
+	tmpev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
+	return snd_seq_kernel_client_dispatch(p->client, &tmpev, atomic, hop);
+}
+
+/*
+ * free_private callback
+ */
+static void
+dummy_free(void *private_data)
+{
+	snd_seq_dummy_port_t *p;
+
+	p = private_data;
+	kfree(p);
+}
+
+/*
+ * create a port
+ */
+static snd_seq_dummy_port_t __init *
+create_port(int idx, int type)
+{
+	snd_seq_port_info_t pinfo;
+	snd_seq_port_callback_t pcb;
+	snd_seq_dummy_port_t *rec;
+
+	if ((rec = kcalloc(1, sizeof(*rec), GFP_KERNEL)) == NULL)
+		return NULL;
+
+	rec->client = my_client;
+	rec->duplex = duplex;
+	rec->connect = 0;
+	memset(&pinfo, 0, sizeof(pinfo));
+	pinfo.addr.client = my_client;
+	if (duplex)
+		sprintf(pinfo.name, "Midi Through Port-%d:%c", idx,
+			(type ? 'B' : 'A'));
+	else
+		sprintf(pinfo.name, "Midi Through Port-%d", idx);
+	pinfo.capability = SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
+	pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
+	if (duplex)
+		pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
+	pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC;
+	memset(&pcb, 0, sizeof(pcb));
+	pcb.owner = THIS_MODULE;
+	pcb.unuse = dummy_unuse;
+	pcb.event_input = dummy_input;
+	pcb.private_free = dummy_free;
+	pcb.private_data = rec;
+	pinfo.kernel = &pcb;
+	if (snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo) < 0) {
+		kfree(rec);
+		return NULL;
+	}
+	rec->port = pinfo.addr.port;
+	return rec;
+}
+
+/*
+ * register client and create ports
+ */
+static int __init
+register_client(void)
+{
+	snd_seq_client_callback_t cb;
+	snd_seq_client_info_t cinfo;
+	snd_seq_dummy_port_t *rec1, *rec2;
+	int i;
+
+	if (ports < 1) {
+		snd_printk(KERN_ERR "invalid number of ports %d\n", ports);
+		return -EINVAL;
+	}
+
+	/* create client */
+	memset(&cb, 0, sizeof(cb));
+	cb.allow_input = 1;
+	cb.allow_output = 1;
+	my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY, &cb);
+	if (my_client < 0)
+		return my_client;
+
+	/* set client name */
+	memset(&cinfo, 0, sizeof(cinfo));
+	cinfo.client = my_client;
+	cinfo.type = KERNEL_CLIENT;
+	strcpy(cinfo.name, "Midi Through");
+	snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &cinfo);
+
+	/* create ports */
+	for (i = 0; i < ports; i++) {
+		rec1 = create_port(i, 0);
+		if (rec1 == NULL) {
+			snd_seq_delete_kernel_client(my_client);
+			return -ENOMEM;
+		}
+		if (duplex) {
+			rec2 = create_port(i, 1);
+			if (rec2 == NULL) {
+				snd_seq_delete_kernel_client(my_client);
+				return -ENOMEM;
+			}
+			rec1->connect = rec2->port;
+			rec2->connect = rec1->port;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * delete client if exists
+ */
+static void __exit
+delete_client(void)
+{
+	if (my_client >= 0)
+		snd_seq_delete_kernel_client(my_client);
+}
+
+/*
+ *  Init part
+ */
+
+static int __init alsa_seq_dummy_init(void)
+{
+	int err;
+	snd_seq_autoload_lock();
+	err = register_client();
+	snd_seq_autoload_unlock();
+	return err;
+}
+
+static void __exit alsa_seq_dummy_exit(void)
+{
+	delete_client();
+}
+
+module_init(alsa_seq_dummy_init)
+module_exit(alsa_seq_dummy_exit)
diff --git a/sound/core/seq/seq_fifo.c b/sound/core/seq/seq_fifo.c
new file mode 100644
index 000000000000..3b7647ca7ad9
--- /dev/null
+++ b/sound/core/seq/seq_fifo.c
@@ -0,0 +1,264 @@
+/*
+ *   ALSA sequencer FIFO
+ *   Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ *   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 <linux/slab.h>
+#include "seq_fifo.h"
+#include "seq_lock.h"
+
+
+/* FIFO */
+
+/* create new fifo */
+fifo_t *snd_seq_fifo_new(int poolsize)
+{
+	fifo_t *f;
+
+	f = kcalloc(1, sizeof(*f), GFP_KERNEL);
+	if (f == NULL) {
+		snd_printd("malloc failed for snd_seq_fifo_new() \n");
+		return NULL;
+	}
+
+	f->pool = snd_seq_pool_new(poolsize);
+	if (f->pool == NULL) {
+		kfree(f);
+		return NULL;
+	}
+	if (snd_seq_pool_init(f->pool) < 0) {
+		snd_seq_pool_delete(&f->pool);
+		kfree(f);
+		return NULL;
+	}
+
+	spin_lock_init(&f->lock);
+	snd_use_lock_init(&f->use_lock);
+	init_waitqueue_head(&f->input_sleep);
+	atomic_set(&f->overflow, 0);
+
+	f->head = NULL;
+	f->tail = NULL;
+	f->cells = 0;
+	
+	return f;
+}
+
+void snd_seq_fifo_delete(fifo_t **fifo)
+{
+	fifo_t *f;
+
+	snd_assert(fifo != NULL, return);
+	f = *fifo;
+	snd_assert(f != NULL, return);
+	*fifo = NULL;
+
+	snd_seq_fifo_clear(f);
+
+	/* wake up clients if any */
+	if (waitqueue_active(&f->input_sleep))
+		wake_up(&f->input_sleep);
+
+	/* release resources...*/
+	/*....................*/
+
+	if (f->pool) {
+		snd_seq_pool_done(f->pool);
+		snd_seq_pool_delete(&f->pool);
+	}
+	
+	kfree(f);
+}
+
+static snd_seq_event_cell_t *fifo_cell_out(fifo_t *f);
+
+/* clear queue */
+void snd_seq_fifo_clear(fifo_t *f)
+{
+	snd_seq_event_cell_t *cell;
+	unsigned long flags;
+
+	/* clear overflow flag */
+	atomic_set(&f->overflow, 0);
+
+	snd_use_lock_sync(&f->use_lock);
+	spin_lock_irqsave(&f->lock, flags);
+	/* drain the fifo */
+	while ((cell = fifo_cell_out(f)) != NULL) {
+		snd_seq_cell_free(cell);
+	}
+	spin_unlock_irqrestore(&f->lock, flags);
+}
+
+
+/* enqueue event to fifo */
+int snd_seq_fifo_event_in(fifo_t *f, snd_seq_event_t *event)
+{
+	snd_seq_event_cell_t *cell;
+	unsigned long flags;
+	int err;
+
+	snd_assert(f != NULL, return -EINVAL);
+
+	snd_use_lock_use(&f->use_lock);
+	err = snd_seq_event_dup(f->pool, event, &cell, 1, NULL); /* always non-blocking */
+	if (err < 0) {
+		if (err == -ENOMEM)
+			atomic_inc(&f->overflow);
+		snd_use_lock_free(&f->use_lock);
+		return err;
+	}
+		
+	/* append new cells to fifo */
+	spin_lock_irqsave(&f->lock, flags);
+	if (f->tail != NULL)
+		f->tail->next = cell;
+	f->tail = cell;
+	if (f->head == NULL)
+		f->head = cell;
+	f->cells++;
+	spin_unlock_irqrestore(&f->lock, flags);
+
+	/* wakeup client */
+	if (waitqueue_active(&f->input_sleep))
+		wake_up(&f->input_sleep);
+
+	snd_use_lock_free(&f->use_lock);
+
+	return 0; /* success */
+
+}
+
+/* dequeue cell from fifo */
+static snd_seq_event_cell_t *fifo_cell_out(fifo_t *f)
+{
+	snd_seq_event_cell_t *cell;
+
+	if ((cell = f->head) != NULL) {
+		f->head = cell->next;
+
+		/* reset tail if this was the last element */
+		if (f->tail == cell)
+			f->tail = NULL;
+
+		cell->next = NULL;
+		f->cells--;
+	}
+
+	return cell;
+}
+
+/* dequeue cell from fifo and copy on user space */
+int snd_seq_fifo_cell_out(fifo_t *f, snd_seq_event_cell_t **cellp, int nonblock)
+{
+	snd_seq_event_cell_t *cell;
+	unsigned long flags;
+	wait_queue_t wait;
+
+	snd_assert(f != NULL, return -EINVAL);
+
+	*cellp = NULL;
+	init_waitqueue_entry(&wait, current);
+	spin_lock_irqsave(&f->lock, flags);
+	while ((cell = fifo_cell_out(f)) == NULL) {
+		if (nonblock) {
+			/* non-blocking - return immediately */
+			spin_unlock_irqrestore(&f->lock, flags);
+			return -EAGAIN;
+		}
+		set_current_state(TASK_INTERRUPTIBLE);
+		add_wait_queue(&f->input_sleep, &wait);
+		spin_unlock_irq(&f->lock);
+		schedule();
+		spin_lock_irq(&f->lock);
+		remove_wait_queue(&f->input_sleep, &wait);
+		if (signal_pending(current)) {
+			spin_unlock_irqrestore(&f->lock, flags);
+			return -ERESTARTSYS;
+		}
+	}
+	spin_unlock_irqrestore(&f->lock, flags);
+	*cellp = cell;
+
+	return 0;
+}
+
+
+void snd_seq_fifo_cell_putback(fifo_t *f, snd_seq_event_cell_t *cell)
+{
+	unsigned long flags;
+
+	if (cell) {
+		spin_lock_irqsave(&f->lock, flags);
+		cell->next = f->head;
+		f->head = cell;
+		f->cells++;
+		spin_unlock_irqrestore(&f->lock, flags);
+	}
+}
+
+
+/* polling; return non-zero if queue is available */
+int snd_seq_fifo_poll_wait(fifo_t *f, struct file *file, poll_table *wait)
+{
+	poll_wait(file, &f->input_sleep, wait);
+	return (f->cells > 0);
+}
+
+/* change the size of pool; all old events are removed */
+int snd_seq_fifo_resize(fifo_t *f, int poolsize)
+{
+	unsigned long flags;
+	pool_t *newpool, *oldpool;
+	snd_seq_event_cell_t *cell, *next, *oldhead;
+
+	snd_assert(f != NULL && f->pool != NULL, return -EINVAL);
+
+	/* allocate new pool */
+	newpool = snd_seq_pool_new(poolsize);
+	if (newpool == NULL)
+		return -ENOMEM;
+	if (snd_seq_pool_init(newpool) < 0) {
+		snd_seq_pool_delete(&newpool);
+		return -ENOMEM;
+	}
+
+	spin_lock_irqsave(&f->lock, flags);
+	/* remember old pool */
+	oldpool = f->pool;
+	oldhead = f->head;
+	/* exchange pools */
+	f->pool = newpool;
+	f->head = NULL;
+	f->tail = NULL;
+	f->cells = 0;
+	/* NOTE: overflow flag is not cleared */
+	spin_unlock_irqrestore(&f->lock, flags);
+
+	/* release cells in old pool */
+	for (cell = oldhead; cell; cell = next) {
+		next = cell->next;
+		snd_seq_cell_free(cell);
+	}
+	snd_seq_pool_delete(&oldpool);
+
+	return 0;
+}
diff --git a/sound/core/seq/seq_fifo.h b/sound/core/seq/seq_fifo.h
new file mode 100644
index 000000000000..d677c261b0a4
--- /dev/null
+++ b/sound/core/seq/seq_fifo.h
@@ -0,0 +1,72 @@
+/*
+ *   ALSA sequencer FIFO
+ *   Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ *   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 __SND_SEQ_FIFO_H
+#define __SND_SEQ_FIFO_H
+
+#include "seq_memory.h"
+#include "seq_lock.h"
+
+
+/* === FIFO === */
+
+typedef struct {
+	pool_t *pool;			/* FIFO pool */
+	snd_seq_event_cell_t* head;    	/* pointer to head of fifo */
+	snd_seq_event_cell_t* tail;    	/* pointer to tail of fifo */
+	int cells;
+	spinlock_t lock;
+	snd_use_lock_t use_lock;
+	wait_queue_head_t input_sleep;
+	atomic_t overflow;
+
+} fifo_t;
+
+/* create new fifo (constructor) */
+extern fifo_t *snd_seq_fifo_new(int poolsize);
+
+/* delete fifo (destructor) */
+extern void snd_seq_fifo_delete(fifo_t **f);
+
+
+/* enqueue event to fifo */
+extern int snd_seq_fifo_event_in(fifo_t *f, snd_seq_event_t *event);
+
+/* lock fifo from release */
+#define snd_seq_fifo_lock(fifo)		snd_use_lock_use(&(fifo)->use_lock)
+#define snd_seq_fifo_unlock(fifo)	snd_use_lock_free(&(fifo)->use_lock)
+
+/* get a cell from fifo - fifo should be locked */
+int snd_seq_fifo_cell_out(fifo_t *f, snd_seq_event_cell_t **cellp, int nonblock);
+
+/* free dequeued cell - fifo should be locked */
+extern void snd_seq_fifo_cell_putback(fifo_t *f, snd_seq_event_cell_t *cell);
+
+/* clean up queue */
+extern void snd_seq_fifo_clear(fifo_t *f);
+
+/* polling */
+extern int snd_seq_fifo_poll_wait(fifo_t *f, struct file *file, poll_table *wait);
+
+/* resize pool in fifo */
+int snd_seq_fifo_resize(fifo_t *f, int poolsize);
+
+
+#endif
diff --git a/sound/core/seq/seq_info.c b/sound/core/seq/seq_info.c
new file mode 100644
index 000000000000..b50b695c41c4
--- /dev/null
+++ b/sound/core/seq/seq_info.c
@@ -0,0 +1,75 @@
+/*
+ *   ALSA sequencer /proc interface
+ *   Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ *   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 <sound/core.h>
+
+#include "seq_info.h"
+#include "seq_clientmgr.h"
+#include "seq_timer.h"
+
+
+static snd_info_entry_t *queues_entry;
+static snd_info_entry_t *clients_entry;
+static snd_info_entry_t *timer_entry;
+
+
+static snd_info_entry_t * __init
+create_info_entry(char *name, int size, void (*read)(snd_info_entry_t *, snd_info_buffer_t *))
+{
+	snd_info_entry_t *entry;
+
+	entry = snd_info_create_module_entry(THIS_MODULE, name, snd_seq_root);
+	if (entry == NULL)
+		return NULL;
+	entry->content = SNDRV_INFO_CONTENT_TEXT;
+	entry->c.text.read_size = size;
+	entry->c.text.read = read;
+	if (snd_info_register(entry) < 0) {
+		snd_info_free_entry(entry);
+		return NULL;
+	}
+	return entry;
+}
+
+
+/* create all our /proc entries */
+int __init snd_seq_info_init(void)
+{
+	queues_entry = create_info_entry("queues", 512 + (256 * SNDRV_SEQ_MAX_QUEUES),
+					 snd_seq_info_queues_read);
+	clients_entry = create_info_entry("clients", 512 + (256 * SNDRV_SEQ_MAX_CLIENTS),
+					  snd_seq_info_clients_read);
+	timer_entry = create_info_entry("timer", 1024, snd_seq_info_timer_read);
+	return 0;
+}
+
+int __exit snd_seq_info_done(void)
+{
+	if (queues_entry)
+		snd_info_unregister(queues_entry);
+	if (clients_entry)
+		snd_info_unregister(clients_entry);
+	if (timer_entry)
+		snd_info_unregister(timer_entry);
+	return 0;
+}
diff --git a/sound/core/seq/seq_info.h b/sound/core/seq/seq_info.h
new file mode 100644
index 000000000000..efd099a858e4
--- /dev/null
+++ b/sound/core/seq/seq_info.h
@@ -0,0 +1,36 @@
+/*
+ *   ALSA sequencer /proc info
+ *   Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ *   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 __SND_SEQ_INFO_H
+#define __SND_SEQ_INFO_H
+
+#include <sound/info.h>
+#include <sound/seq_kernel.h>
+
+void snd_seq_info_clients_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer);
+void snd_seq_info_timer_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer);
+void snd_seq_info_queues_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer);
+
+
+int snd_seq_info_init( void );
+int snd_seq_info_done( void );
+
+
+#endif
diff --git a/sound/core/seq/seq_instr.c b/sound/core/seq/seq_instr.c
new file mode 100644
index 000000000000..5b40ea2ba8f4
--- /dev/null
+++ b/sound/core/seq/seq_instr.c
@@ -0,0 +1,653 @@
+/*
+ *   Generic Instrument routines for ALSA sequencer
+ *   Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+ 
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include "seq_clientmgr.h"
+#include <sound/seq_instr.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer instrument library.");
+MODULE_LICENSE("GPL");
+
+
+static void snd_instr_lock_ops(snd_seq_kinstr_list_t *list)
+{
+	if (!(list->flags & SNDRV_SEQ_INSTR_FLG_DIRECT)) {
+		spin_lock_irqsave(&list->ops_lock, list->ops_flags);
+	} else {
+		down(&list->ops_mutex);
+	}
+}
+
+static void snd_instr_unlock_ops(snd_seq_kinstr_list_t *list)
+{
+	if (!(list->flags & SNDRV_SEQ_INSTR_FLG_DIRECT)) {
+		spin_unlock_irqrestore(&list->ops_lock, list->ops_flags);
+	} else {
+		up(&list->ops_mutex);
+	}
+}
+
+static snd_seq_kinstr_t *snd_seq_instr_new(int add_len, int atomic)
+{
+	snd_seq_kinstr_t *instr;
+	
+	instr = kcalloc(1, sizeof(snd_seq_kinstr_t) + add_len, atomic ? GFP_ATOMIC : GFP_KERNEL);
+	if (instr == NULL)
+		return NULL;
+	instr->add_len = add_len;
+	return instr;
+}
+
+static int snd_seq_instr_free(snd_seq_kinstr_t *instr, int atomic)
+{
+	int result = 0;
+
+	if (instr == NULL)
+		return -EINVAL;
+	if (instr->ops && instr->ops->remove)
+		result = instr->ops->remove(instr->ops->private_data, instr, 1);
+	if (!result)
+		kfree(instr);
+	return result;
+}
+
+snd_seq_kinstr_list_t *snd_seq_instr_list_new(void)
+{
+	snd_seq_kinstr_list_t *list;
+
+	list = kcalloc(1, sizeof(snd_seq_kinstr_list_t), GFP_KERNEL);
+	if (list == NULL)
+		return NULL;
+	spin_lock_init(&list->lock);
+	spin_lock_init(&list->ops_lock);
+	init_MUTEX(&list->ops_mutex);
+	list->owner = -1;
+	return list;
+}
+
+void snd_seq_instr_list_free(snd_seq_kinstr_list_t **list_ptr)
+{
+	snd_seq_kinstr_list_t *list;
+	snd_seq_kinstr_t *instr;
+	snd_seq_kcluster_t *cluster;
+	int idx;
+	unsigned long flags;
+
+	if (list_ptr == NULL)
+		return;
+	list = *list_ptr;
+	*list_ptr = NULL;
+	if (list == NULL)
+		return;
+	
+	for (idx = 0; idx < SNDRV_SEQ_INSTR_HASH_SIZE; idx++) {		
+		while ((instr = list->hash[idx]) != NULL) {
+			list->hash[idx] = instr->next;
+			list->count--;
+			spin_lock_irqsave(&list->lock, flags);
+			while (instr->use) {
+				spin_unlock_irqrestore(&list->lock, flags);
+				set_current_state(TASK_INTERRUPTIBLE);
+				schedule_timeout(1);
+				spin_lock_irqsave(&list->lock, flags);
+			}				
+			spin_unlock_irqrestore(&list->lock, flags);
+			if (snd_seq_instr_free(instr, 0)<0)
+				snd_printk(KERN_WARNING "instrument free problem\n");
+		}
+		while ((cluster = list->chash[idx]) != NULL) {
+			list->chash[idx] = cluster->next;
+			list->ccount--;
+			kfree(cluster);
+		}
+	}
+	kfree(list);
+}
+
+static int instr_free_compare(snd_seq_kinstr_t *instr,
+			      snd_seq_instr_header_t *ifree,
+			      unsigned int client)
+{
+	switch (ifree->cmd) {
+	case SNDRV_SEQ_INSTR_FREE_CMD_ALL:
+		/* all, except private for other clients */
+		if ((instr->instr.std & 0xff000000) == 0)
+			return 0;
+		if (((instr->instr.std >> 24) & 0xff) == client)
+			return 0;
+		return 1;
+	case SNDRV_SEQ_INSTR_FREE_CMD_PRIVATE:
+		/* all my private instruments */
+		if ((instr->instr.std & 0xff000000) == 0)
+			return 1;
+		if (((instr->instr.std >> 24) & 0xff) == client)
+			return 0;
+		return 1;
+	case SNDRV_SEQ_INSTR_FREE_CMD_CLUSTER:
+		/* all my private instruments */
+		if ((instr->instr.std & 0xff000000) == 0) {
+			if (instr->instr.cluster == ifree->id.cluster)
+				return 0;
+			return 1;
+		}
+		if (((instr->instr.std >> 24) & 0xff) == client) {
+			if (instr->instr.cluster == ifree->id.cluster)
+				return 0;
+		}
+		return 1;
+	}
+	return 1;
+}
+
+int snd_seq_instr_list_free_cond(snd_seq_kinstr_list_t *list,
+			         snd_seq_instr_header_t *ifree,
+			         int client,
+			         int atomic)
+{
+	snd_seq_kinstr_t *instr, *prev, *next, *flist;
+	int idx;
+	unsigned long flags;
+
+	snd_instr_lock_ops(list);
+	for (idx = 0; idx < SNDRV_SEQ_INSTR_HASH_SIZE; idx++) {
+		spin_lock_irqsave(&list->lock, flags);
+		instr = list->hash[idx];
+		prev = flist = NULL;
+		while (instr) {
+			while (instr && instr_free_compare(instr, ifree, (unsigned int)client)) {
+				prev = instr;
+				instr = instr->next;
+			}
+			if (instr == NULL)
+				continue;
+			if (instr->ops && instr->ops->notify)
+				instr->ops->notify(instr->ops->private_data, instr, SNDRV_SEQ_INSTR_NOTIFY_REMOVE);
+			next = instr->next;
+			if (prev == NULL) {
+				list->hash[idx] = next;
+			} else {
+				prev->next = next;
+			}
+			list->count--;
+			instr->next = flist;
+			flist = instr;
+			instr = next;
+		}
+		spin_unlock_irqrestore(&list->lock, flags);
+		while (flist) {
+			instr = flist;
+			flist = instr->next;
+			while (instr->use) {
+				set_current_state(TASK_INTERRUPTIBLE);
+				schedule_timeout(1);
+			}				
+			if (snd_seq_instr_free(instr, atomic)<0)
+				snd_printk(KERN_WARNING "instrument free problem\n");
+			instr = next;
+		}
+	}
+	snd_instr_unlock_ops(list);
+	return 0;	
+}
+
+static int compute_hash_instr_key(snd_seq_instr_t *instr)
+{
+	int result;
+	
+	result = instr->bank | (instr->prg << 16);
+	result += result >> 24;
+	result += result >> 16;
+	result += result >> 8;
+	return result & (SNDRV_SEQ_INSTR_HASH_SIZE-1);
+}
+
+#if 0
+static int compute_hash_cluster_key(snd_seq_instr_cluster_t cluster)
+{
+	int result;
+	
+	result = cluster;
+	result += result >> 24;
+	result += result >> 16;
+	result += result >> 8;
+	return result & (SNDRV_SEQ_INSTR_HASH_SIZE-1);
+}
+#endif
+
+static int compare_instr(snd_seq_instr_t *i1, snd_seq_instr_t *i2, int exact)
+{
+	if (exact) {
+		if (i1->cluster != i2->cluster ||
+		    i1->bank != i2->bank ||
+		    i1->prg != i2->prg)
+			return 1;
+		if ((i1->std & 0xff000000) != (i2->std & 0xff000000))
+			return 1;
+		if (!(i1->std & i2->std))
+			return 1;
+		return 0;
+	} else {
+		unsigned int client_check;
+		
+		if (i2->cluster && i1->cluster != i2->cluster)
+			return 1;
+		client_check = i2->std & 0xff000000;
+		if (client_check) {
+			if ((i1->std & 0xff000000) != client_check)
+				return 1;
+		} else {
+			if ((i1->std & i2->std) != i2->std)
+				return 1;
+		}
+		return i1->bank != i2->bank || i1->prg != i2->prg;
+	}
+}
+
+snd_seq_kinstr_t *snd_seq_instr_find(snd_seq_kinstr_list_t *list,
+				     snd_seq_instr_t *instr,
+				     int exact,
+				     int follow_alias)
+{
+	unsigned long flags;
+	int depth = 0;
+	snd_seq_kinstr_t *result;
+
+	if (list == NULL || instr == NULL)
+		return NULL;
+	spin_lock_irqsave(&list->lock, flags);
+      __again:
+	result = list->hash[compute_hash_instr_key(instr)];
+	while (result) {
+		if (!compare_instr(&result->instr, instr, exact)) {
+			if (follow_alias && (result->type == SNDRV_SEQ_INSTR_ATYPE_ALIAS)) {
+				instr = (snd_seq_instr_t *)KINSTR_DATA(result);
+				if (++depth > 10)
+					goto __not_found;
+				goto __again;
+			}
+			result->use++;
+			spin_unlock_irqrestore(&list->lock, flags);
+			return result;
+		}
+		result = result->next;
+	}
+      __not_found:
+	spin_unlock_irqrestore(&list->lock, flags);
+	return NULL;
+}
+
+void snd_seq_instr_free_use(snd_seq_kinstr_list_t *list,
+			    snd_seq_kinstr_t *instr)
+{
+	unsigned long flags;
+
+	if (list == NULL || instr == NULL)
+		return;
+	spin_lock_irqsave(&list->lock, flags);
+	if (instr->use <= 0) {
+		snd_printk(KERN_ERR "free_use: fatal!!! use = %i, name = '%s'\n", instr->use, instr->name);
+	} else {
+		instr->use--;
+	}
+	spin_unlock_irqrestore(&list->lock, flags);
+}
+
+static snd_seq_kinstr_ops_t *instr_ops(snd_seq_kinstr_ops_t *ops, char *instr_type)
+{
+	while (ops) {
+		if (!strcmp(ops->instr_type, instr_type))
+			return ops;
+		ops = ops->next;
+	}
+	return NULL;
+}
+
+static int instr_result(snd_seq_event_t *ev,
+			int type, int result,
+			int atomic)
+{
+	snd_seq_event_t sev;
+	
+	memset(&sev, 0, sizeof(sev));
+	sev.type = SNDRV_SEQ_EVENT_RESULT;
+	sev.flags = SNDRV_SEQ_TIME_STAMP_REAL | SNDRV_SEQ_EVENT_LENGTH_FIXED |
+	            SNDRV_SEQ_PRIORITY_NORMAL;
+	sev.source = ev->dest;
+	sev.dest = ev->source;
+	sev.data.result.event = type;
+	sev.data.result.result = result;
+#if 0
+	printk("instr result - type = %i, result = %i, queue = %i, source.client:port = %i:%i, dest.client:port = %i:%i\n",
+				type, result,
+				sev.queue,
+				sev.source.client, sev.source.port,
+				sev.dest.client, sev.dest.port);
+#endif
+	return snd_seq_kernel_client_dispatch(sev.source.client, &sev, atomic, 0);
+}
+
+static int instr_begin(snd_seq_kinstr_ops_t *ops,
+		       snd_seq_kinstr_list_t *list,
+		       snd_seq_event_t *ev,
+		       int atomic, int hop)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&list->lock, flags);
+	if (list->owner >= 0 && list->owner != ev->source.client) {
+		spin_unlock_irqrestore(&list->lock, flags);
+		return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_BEGIN, -EBUSY, atomic);
+	}
+	list->owner = ev->source.client;
+	spin_unlock_irqrestore(&list->lock, flags);
+	return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_BEGIN, 0, atomic);
+}
+
+static int instr_end(snd_seq_kinstr_ops_t *ops,
+		     snd_seq_kinstr_list_t *list,
+		     snd_seq_event_t *ev,
+		     int atomic, int hop)
+{
+	unsigned long flags;
+
+	/* TODO: timeout handling */
+	spin_lock_irqsave(&list->lock, flags);
+	if (list->owner == ev->source.client) {
+		list->owner = -1;
+		spin_unlock_irqrestore(&list->lock, flags);
+		return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_END, 0, atomic);
+	}
+	spin_unlock_irqrestore(&list->lock, flags);
+	return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_END, -EINVAL, atomic);
+}
+
+static int instr_info(snd_seq_kinstr_ops_t *ops,
+		      snd_seq_kinstr_list_t *list,
+		      snd_seq_event_t *ev,
+		      int atomic, int hop)
+{
+	return -ENXIO;
+}
+
+static int instr_format_info(snd_seq_kinstr_ops_t *ops,
+			     snd_seq_kinstr_list_t *list,
+			     snd_seq_event_t *ev,
+			     int atomic, int hop)
+{
+	return -ENXIO;
+}
+
+static int instr_reset(snd_seq_kinstr_ops_t *ops,
+		       snd_seq_kinstr_list_t *list,
+		       snd_seq_event_t *ev,
+		       int atomic, int hop)
+{
+	return -ENXIO;
+}
+
+static int instr_status(snd_seq_kinstr_ops_t *ops,
+			snd_seq_kinstr_list_t *list,
+			snd_seq_event_t *ev,
+			int atomic, int hop)
+{
+	return -ENXIO;
+}
+
+static int instr_put(snd_seq_kinstr_ops_t *ops,
+		     snd_seq_kinstr_list_t *list,
+		     snd_seq_event_t *ev,
+		     int atomic, int hop)
+{
+	unsigned long flags;
+	snd_seq_instr_header_t put;
+	snd_seq_kinstr_t *instr;
+	int result = -EINVAL, len, key;
+
+	if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARUSR)
+		goto __return;
+
+	if (ev->data.ext.len < sizeof(snd_seq_instr_header_t))
+		goto __return;
+	if (copy_from_user(&put, (void __user *)ev->data.ext.ptr, sizeof(snd_seq_instr_header_t))) {
+		result = -EFAULT;
+		goto __return;
+	}
+	snd_instr_lock_ops(list);
+	if (put.id.instr.std & 0xff000000) {	/* private instrument */
+		put.id.instr.std &= 0x00ffffff;
+		put.id.instr.std |= (unsigned int)ev->source.client << 24;
+	}
+	if ((instr = snd_seq_instr_find(list, &put.id.instr, 1, 0))) {
+		snd_seq_instr_free_use(list, instr);
+		snd_instr_unlock_ops(list);
+		result = -EBUSY;
+		goto __return;
+	}
+	ops = instr_ops(ops, put.data.data.format);
+	if (ops == NULL) {
+		snd_instr_unlock_ops(list);
+		goto __return;
+	}
+	len = ops->add_len;
+	if (put.data.type == SNDRV_SEQ_INSTR_ATYPE_ALIAS)
+		len = sizeof(snd_seq_instr_t);
+	instr = snd_seq_instr_new(len, atomic);
+	if (instr == NULL) {
+		snd_instr_unlock_ops(list);
+		result = -ENOMEM;
+		goto __return;
+	}
+	instr->ops = ops;
+	instr->instr = put.id.instr;
+	strlcpy(instr->name, put.data.name, sizeof(instr->name));
+	instr->type = put.data.type;
+	if (instr->type == SNDRV_SEQ_INSTR_ATYPE_DATA) {
+		result = ops->put(ops->private_data,
+				  instr,
+				  (void __user *)ev->data.ext.ptr + sizeof(snd_seq_instr_header_t),
+				  ev->data.ext.len - sizeof(snd_seq_instr_header_t),
+				  atomic,
+				  put.cmd);
+		if (result < 0) {
+			snd_seq_instr_free(instr, atomic);
+			snd_instr_unlock_ops(list);
+			goto __return;
+		}
+	}
+	key = compute_hash_instr_key(&instr->instr);
+	spin_lock_irqsave(&list->lock, flags);
+	instr->next = list->hash[key];
+	list->hash[key] = instr;
+	list->count++;
+	spin_unlock_irqrestore(&list->lock, flags);
+	snd_instr_unlock_ops(list);
+	result = 0;
+      __return:
+	instr_result(ev, SNDRV_SEQ_EVENT_INSTR_PUT, result, atomic);
+	return result;
+}
+
+static int instr_get(snd_seq_kinstr_ops_t *ops,
+		     snd_seq_kinstr_list_t *list,
+		     snd_seq_event_t *ev,
+		     int atomic, int hop)
+{
+	return -ENXIO;
+}
+
+static int instr_free(snd_seq_kinstr_ops_t *ops,
+		      snd_seq_kinstr_list_t *list,
+		      snd_seq_event_t *ev,
+		      int atomic, int hop)
+{
+	snd_seq_instr_header_t ifree;
+	snd_seq_kinstr_t *instr, *prev;
+	int result = -EINVAL;
+	unsigned long flags;
+	unsigned int hash;
+
+	if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARUSR)
+		goto __return;
+
+	if (ev->data.ext.len < sizeof(snd_seq_instr_header_t))
+		goto __return;
+	if (copy_from_user(&ifree, (void __user *)ev->data.ext.ptr, sizeof(snd_seq_instr_header_t))) {
+		result = -EFAULT;
+		goto __return;
+	}
+	if (ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_ALL ||
+	    ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_PRIVATE ||
+	    ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_CLUSTER) {
+	    	result = snd_seq_instr_list_free_cond(list, &ifree, ev->dest.client, atomic);
+	    	goto __return;
+	}
+	if (ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_SINGLE) {
+		if (ifree.id.instr.std & 0xff000000) {
+			ifree.id.instr.std &= 0x00ffffff;
+			ifree.id.instr.std |= (unsigned int)ev->source.client << 24;
+		}
+		hash = compute_hash_instr_key(&ifree.id.instr);
+		snd_instr_lock_ops(list);
+		spin_lock_irqsave(&list->lock, flags);
+		instr = list->hash[hash];
+		prev = NULL;
+		while (instr) {
+			if (!compare_instr(&instr->instr, &ifree.id.instr, 1))
+				goto __free_single;
+			prev = instr;
+			instr = instr->next;
+		}
+		result = -ENOENT;
+		spin_unlock_irqrestore(&list->lock, flags);
+		snd_instr_unlock_ops(list);
+		goto __return;
+		
+	      __free_single:
+		if (prev) {
+			prev->next = instr->next;
+		} else {
+			list->hash[hash] = instr->next;
+		}
+		if (instr->ops && instr->ops->notify)
+			instr->ops->notify(instr->ops->private_data, instr, SNDRV_SEQ_INSTR_NOTIFY_REMOVE);
+		while (instr->use) {
+			spin_unlock_irqrestore(&list->lock, flags);
+			set_current_state(TASK_INTERRUPTIBLE);
+			schedule_timeout(1);
+			spin_lock_irqsave(&list->lock, flags);
+		}				
+		spin_unlock_irqrestore(&list->lock, flags);
+		result = snd_seq_instr_free(instr, atomic);
+		snd_instr_unlock_ops(list);
+		goto __return;
+	}
+
+      __return:
+	instr_result(ev, SNDRV_SEQ_EVENT_INSTR_FREE, result, atomic);
+	return result;
+}
+
+static int instr_list(snd_seq_kinstr_ops_t *ops,
+		      snd_seq_kinstr_list_t *list,
+		      snd_seq_event_t *ev,
+		      int atomic, int hop)
+{
+	return -ENXIO;
+}
+
+static int instr_cluster(snd_seq_kinstr_ops_t *ops,
+			 snd_seq_kinstr_list_t *list,
+			 snd_seq_event_t *ev,
+			 int atomic, int hop)
+{
+	return -ENXIO;
+}
+
+int snd_seq_instr_event(snd_seq_kinstr_ops_t *ops,
+			snd_seq_kinstr_list_t *list,
+			snd_seq_event_t *ev,
+			int client,
+			int atomic,
+			int hop)
+{
+	int direct = 0;
+
+	snd_assert(ops != NULL && list != NULL && ev != NULL, return -EINVAL);
+	if (snd_seq_ev_is_direct(ev)) {
+		direct = 1;
+		switch (ev->type) {
+		case SNDRV_SEQ_EVENT_INSTR_BEGIN:
+			return instr_begin(ops, list, ev, atomic, hop);
+		case SNDRV_SEQ_EVENT_INSTR_END:
+			return instr_end(ops, list, ev, atomic, hop);
+		}
+	}
+	if ((list->flags & SNDRV_SEQ_INSTR_FLG_DIRECT) && !direct)
+		return -EINVAL;
+	switch (ev->type) {
+	case SNDRV_SEQ_EVENT_INSTR_INFO:
+		return instr_info(ops, list, ev, atomic, hop);
+	case SNDRV_SEQ_EVENT_INSTR_FINFO:
+		return instr_format_info(ops, list, ev, atomic, hop);
+	case SNDRV_SEQ_EVENT_INSTR_RESET:
+		return instr_reset(ops, list, ev, atomic, hop);
+	case SNDRV_SEQ_EVENT_INSTR_STATUS:
+		return instr_status(ops, list, ev, atomic, hop);
+	case SNDRV_SEQ_EVENT_INSTR_PUT:
+		return instr_put(ops, list, ev, atomic, hop);
+	case SNDRV_SEQ_EVENT_INSTR_GET:
+		return instr_get(ops, list, ev, atomic, hop);
+	case SNDRV_SEQ_EVENT_INSTR_FREE:
+		return instr_free(ops, list, ev, atomic, hop);
+	case SNDRV_SEQ_EVENT_INSTR_LIST:
+		return instr_list(ops, list, ev, atomic, hop);
+	case SNDRV_SEQ_EVENT_INSTR_CLUSTER:
+		return instr_cluster(ops, list, ev, atomic, hop);
+	}
+	return -EINVAL;
+}
+			
+/*
+ *  Init part
+ */
+
+static int __init alsa_seq_instr_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_seq_instr_exit(void)
+{
+}
+
+module_init(alsa_seq_instr_init)
+module_exit(alsa_seq_instr_exit)
+
+EXPORT_SYMBOL(snd_seq_instr_list_new);
+EXPORT_SYMBOL(snd_seq_instr_list_free);
+EXPORT_SYMBOL(snd_seq_instr_list_free_cond);
+EXPORT_SYMBOL(snd_seq_instr_find);
+EXPORT_SYMBOL(snd_seq_instr_free_use);
+EXPORT_SYMBOL(snd_seq_instr_event);
diff --git a/sound/core/seq/seq_lock.c b/sound/core/seq/seq_lock.c
new file mode 100644
index 000000000000..b09cee058fa7
--- /dev/null
+++ b/sound/core/seq/seq_lock.c
@@ -0,0 +1,48 @@
+/*
+ *  Do sleep inside a spin-lock
+ *  Copyright (c) 1999 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 "seq_lock.h"
+
+#if defined(CONFIG_SMP) || defined(CONFIG_SND_DEBUG)
+
+/* wait until all locks are released */
+void snd_use_lock_sync_helper(snd_use_lock_t *lockp, const char *file, int line)
+{
+	int max_count = 5 * HZ;
+
+	if (atomic_read(lockp) < 0) {
+		printk(KERN_WARNING "seq_lock: lock trouble [counter = %d] in %s:%d\n", atomic_read(lockp), file, line);
+		return;
+	}
+	while (atomic_read(lockp) > 0) {
+		if (max_count == 0) {
+			snd_printk(KERN_WARNING "seq_lock: timeout [%d left] in %s:%d\n", atomic_read(lockp), file, line);
+			break;
+		}
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(1);
+		max_count--;
+	}
+}
+
+#endif
diff --git a/sound/core/seq/seq_lock.h b/sound/core/seq/seq_lock.h
new file mode 100644
index 000000000000..54044bc2c9ef
--- /dev/null
+++ b/sound/core/seq/seq_lock.h
@@ -0,0 +1,33 @@
+#ifndef __SND_SEQ_LOCK_H
+#define __SND_SEQ_LOCK_H
+
+#include <linux/sched.h>
+
+#if defined(CONFIG_SMP) || defined(CONFIG_SND_DEBUG)
+
+typedef atomic_t snd_use_lock_t;
+
+/* initialize lock */
+#define snd_use_lock_init(lockp) atomic_set(lockp, 0)
+
+/* increment lock */
+#define snd_use_lock_use(lockp) atomic_inc(lockp)
+
+/* release lock */
+#define snd_use_lock_free(lockp) atomic_dec(lockp)
+
+/* wait until all locks are released */
+void snd_use_lock_sync_helper(snd_use_lock_t *lock, const char *file, int line);
+#define snd_use_lock_sync(lockp) snd_use_lock_sync_helper(lockp, __BASE_FILE__, __LINE__)
+
+#else /* SMP || CONFIG_SND_DEBUG */
+
+typedef spinlock_t snd_use_lock_t;	/* dummy */
+#define snd_use_lock_init(lockp) /**/
+#define snd_use_lock_use(lockp) /**/
+#define snd_use_lock_free(lockp) /**/
+#define snd_use_lock_sync(lockp) /**/
+
+#endif /* SMP || CONFIG_SND_DEBUG */
+
+#endif /* __SND_SEQ_LOCK_H */
diff --git a/sound/core/seq/seq_memory.c b/sound/core/seq/seq_memory.c
new file mode 100644
index 000000000000..00d841e82fbc
--- /dev/null
+++ b/sound/core/seq/seq_memory.c
@@ -0,0 +1,510 @@
+/*
+ *  ALSA sequencer Memory Manager
+ *  Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *                        Jaroslav Kysela <perex@suse.cz>
+ *                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
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <sound/core.h>
+
+#include <sound/seq_kernel.h>
+#include "seq_memory.h"
+#include "seq_queue.h"
+#include "seq_info.h"
+#include "seq_lock.h"
+
+/* semaphore in struct file record */
+#define semaphore_of(fp)	((fp)->f_dentry->d_inode->i_sem)
+
+
+inline static int snd_seq_pool_available(pool_t *pool)
+{
+	return pool->total_elements - atomic_read(&pool->counter);
+}
+
+inline static int snd_seq_output_ok(pool_t *pool)
+{
+	return snd_seq_pool_available(pool) >= pool->room;
+}
+
+/*
+ * Variable length event:
+ * The event like sysex uses variable length type.
+ * The external data may be stored in three different formats.
+ * 1) kernel space
+ *    This is the normal case.
+ *      ext.data.len = length
+ *      ext.data.ptr = buffer pointer
+ * 2) user space
+ *    When an event is generated via read(), the external data is
+ *    kept in user space until expanded.
+ *      ext.data.len = length | SNDRV_SEQ_EXT_USRPTR
+ *      ext.data.ptr = userspace pointer
+ * 3) chained cells
+ *    When the variable length event is enqueued (in prioq or fifo),
+ *    the external data is decomposed to several cells.
+ *      ext.data.len = length | SNDRV_SEQ_EXT_CHAINED
+ *      ext.data.ptr = the additiona cell head
+ *         -> cell.next -> cell.next -> ..
+ */
+
+/*
+ * exported:
+ * call dump function to expand external data.
+ */
+
+static int get_var_len(const snd_seq_event_t *event)
+{
+	if ((event->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE)
+		return -EINVAL;
+
+	return event->data.ext.len & ~SNDRV_SEQ_EXT_MASK;
+}
+
+int snd_seq_dump_var_event(const snd_seq_event_t *event, snd_seq_dump_func_t func, void *private_data)
+{
+	int len, err;
+	snd_seq_event_cell_t *cell;
+
+	if ((len = get_var_len(event)) <= 0)
+		return len;
+
+	if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) {
+		char buf[32];
+		char __user *curptr = (char __user *)event->data.ext.ptr;
+		while (len > 0) {
+			int size = sizeof(buf);
+			if (len < size)
+				size = len;
+			if (copy_from_user(buf, curptr, size))
+				return -EFAULT;
+			err = func(private_data, buf, size);
+			if (err < 0)
+				return err;
+			curptr += size;
+			len -= size;
+		}
+		return 0;
+	} if (! (event->data.ext.len & SNDRV_SEQ_EXT_CHAINED)) {
+		return func(private_data, event->data.ext.ptr, len);
+	}
+
+	cell = (snd_seq_event_cell_t*)event->data.ext.ptr;
+	for (; len > 0 && cell; cell = cell->next) {
+		int size = sizeof(snd_seq_event_t);
+		if (len < size)
+			size = len;
+		err = func(private_data, &cell->event, size);
+		if (err < 0)
+			return err;
+		len -= size;
+	}
+	return 0;
+}
+
+
+/*
+ * exported:
+ * expand the variable length event to linear buffer space.
+ */
+
+static int seq_copy_in_kernel(char **bufptr, const void *src, int size)
+{
+	memcpy(*bufptr, src, size);
+	*bufptr += size;
+	return 0;
+}
+
+static int seq_copy_in_user(char __user **bufptr, const void *src, int size)
+{
+	if (copy_to_user(*bufptr, src, size))
+		return -EFAULT;
+	*bufptr += size;
+	return 0;
+}
+
+int snd_seq_expand_var_event(const snd_seq_event_t *event, int count, char *buf, int in_kernel, int size_aligned)
+{
+	int len, newlen;
+	int err;
+
+	if ((len = get_var_len(event)) < 0)
+		return len;
+	newlen = len;
+	if (size_aligned > 0)
+		newlen = ((len + size_aligned - 1) / size_aligned) * size_aligned;
+	if (count < newlen)
+		return -EAGAIN;
+
+	if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) {
+		if (! in_kernel)
+			return -EINVAL;
+		if (copy_from_user(buf, (void __user *)event->data.ext.ptr, len))
+			return -EFAULT;
+		return newlen;
+	}
+	err = snd_seq_dump_var_event(event,
+				     in_kernel ? (snd_seq_dump_func_t)seq_copy_in_kernel :
+				     (snd_seq_dump_func_t)seq_copy_in_user,
+				     &buf);
+	return err < 0 ? err : newlen;
+}
+
+
+/*
+ * release this cell, free extended data if available
+ */
+
+static inline void free_cell(pool_t *pool, snd_seq_event_cell_t *cell)
+{
+	cell->next = pool->free;
+	pool->free = cell;
+	atomic_dec(&pool->counter);
+}
+
+void snd_seq_cell_free(snd_seq_event_cell_t * cell)
+{
+	unsigned long flags;
+	pool_t *pool;
+
+	snd_assert(cell != NULL, return);
+	pool = cell->pool;
+	snd_assert(pool != NULL, return);
+
+	spin_lock_irqsave(&pool->lock, flags);
+	free_cell(pool, cell);
+	if (snd_seq_ev_is_variable(&cell->event)) {
+		if (cell->event.data.ext.len & SNDRV_SEQ_EXT_CHAINED) {
+			snd_seq_event_cell_t *curp, *nextptr;
+			curp = cell->event.data.ext.ptr;
+			for (; curp; curp = nextptr) {
+				nextptr = curp->next;
+				curp->next = pool->free;
+				free_cell(pool, curp);
+			}
+		}
+	}
+	if (waitqueue_active(&pool->output_sleep)) {
+		/* has enough space now? */
+		if (snd_seq_output_ok(pool))
+			wake_up(&pool->output_sleep);
+	}
+	spin_unlock_irqrestore(&pool->lock, flags);
+}
+
+
+/*
+ * allocate an event cell.
+ */
+static int snd_seq_cell_alloc(pool_t *pool, snd_seq_event_cell_t **cellp, int nonblock, struct file *file)
+{
+	snd_seq_event_cell_t *cell;
+	unsigned long flags;
+	int err = -EAGAIN;
+	wait_queue_t wait;
+
+	if (pool == NULL)
+		return -EINVAL;
+
+	*cellp = NULL;
+
+	init_waitqueue_entry(&wait, current);
+	spin_lock_irqsave(&pool->lock, flags);
+	if (pool->ptr == NULL) {	/* not initialized */
+		snd_printd("seq: pool is not initialized\n");
+		err = -EINVAL;
+		goto __error;
+	}
+	while (pool->free == NULL && ! nonblock && ! pool->closing) {
+
+		set_current_state(TASK_INTERRUPTIBLE);
+		add_wait_queue(&pool->output_sleep, &wait);
+		spin_unlock_irq(&pool->lock);
+		schedule();
+		spin_lock_irq(&pool->lock);
+		remove_wait_queue(&pool->output_sleep, &wait);
+		/* interrupted? */
+		if (signal_pending(current)) {
+			err = -ERESTARTSYS;
+			goto __error;
+		}
+	}
+	if (pool->closing) { /* closing.. */
+		err = -ENOMEM;
+		goto __error;
+	}
+
+	cell = pool->free;
+	if (cell) {
+		int used;
+		pool->free = cell->next;
+		atomic_inc(&pool->counter);
+		used = atomic_read(&pool->counter);
+		if (pool->max_used < used)
+			pool->max_used = used;
+		pool->event_alloc_success++;
+		/* clear cell pointers */
+		cell->next = NULL;
+		err = 0;
+	} else
+		pool->event_alloc_failures++;
+	*cellp = cell;
+
+__error:
+	spin_unlock_irqrestore(&pool->lock, flags);
+	return err;
+}
+
+
+/*
+ * duplicate the event to a cell.
+ * if the event has external data, the data is decomposed to additional
+ * cells.
+ */
+int snd_seq_event_dup(pool_t *pool, snd_seq_event_t *event, snd_seq_event_cell_t **cellp, int nonblock, struct file *file)
+{
+	int ncells, err;
+	unsigned int extlen;
+	snd_seq_event_cell_t *cell;
+
+	*cellp = NULL;
+
+	ncells = 0;
+	extlen = 0;
+	if (snd_seq_ev_is_variable(event)) {
+		extlen = event->data.ext.len & ~SNDRV_SEQ_EXT_MASK;
+		ncells = (extlen + sizeof(snd_seq_event_t) - 1) / sizeof(snd_seq_event_t);
+	}
+	if (ncells >= pool->total_elements)
+		return -ENOMEM;
+
+	err = snd_seq_cell_alloc(pool, &cell, nonblock, file);
+	if (err < 0)
+		return err;
+
+	/* copy the event */
+	cell->event = *event;
+
+	/* decompose */
+	if (snd_seq_ev_is_variable(event)) {
+		int len = extlen;
+		int is_chained = event->data.ext.len & SNDRV_SEQ_EXT_CHAINED;
+		int is_usrptr = event->data.ext.len & SNDRV_SEQ_EXT_USRPTR;
+		snd_seq_event_cell_t *src, *tmp, *tail;
+		char *buf;
+
+		cell->event.data.ext.len = extlen | SNDRV_SEQ_EXT_CHAINED;
+		cell->event.data.ext.ptr = NULL;
+
+		src = (snd_seq_event_cell_t*)event->data.ext.ptr;
+		buf = (char *)event->data.ext.ptr;
+		tail = NULL;
+
+		while (ncells-- > 0) {
+			int size = sizeof(snd_seq_event_t);
+			if (len < size)
+				size = len;
+			err = snd_seq_cell_alloc(pool, &tmp, nonblock, file);
+			if (err < 0)
+				goto __error;
+			if (cell->event.data.ext.ptr == NULL)
+				cell->event.data.ext.ptr = tmp;
+			if (tail)
+				tail->next = tmp;
+			tail = tmp;
+			/* copy chunk */
+			if (is_chained && src) {
+				tmp->event = src->event;
+				src = src->next;
+			} else if (is_usrptr) {
+				if (copy_from_user(&tmp->event, (char __user *)buf, size)) {
+					err = -EFAULT;
+					goto __error;
+				}
+			} else {
+				memcpy(&tmp->event, buf, size);
+			}
+			buf += size;
+			len -= size;
+		}
+	}
+
+	*cellp = cell;
+	return 0;
+
+__error:
+	snd_seq_cell_free(cell);
+	return err;
+}
+  
+
+/* poll wait */
+int snd_seq_pool_poll_wait(pool_t *pool, struct file *file, poll_table *wait)
+{
+	poll_wait(file, &pool->output_sleep, wait);
+	return snd_seq_output_ok(pool);
+}
+
+
+/* allocate room specified number of events */
+int snd_seq_pool_init(pool_t *pool)
+{
+	int cell;
+	snd_seq_event_cell_t *cellptr;
+	unsigned long flags;
+
+	snd_assert(pool != NULL, return -EINVAL);
+	if (pool->ptr)			/* should be atomic? */
+		return 0;
+
+	pool->ptr = vmalloc(sizeof(snd_seq_event_cell_t) * pool->size);
+	if (pool->ptr == NULL) {
+		snd_printd("seq: malloc for sequencer events failed\n");
+		return -ENOMEM;
+	}
+
+	/* add new cells to the free cell list */
+	spin_lock_irqsave(&pool->lock, flags);
+	pool->free = NULL;
+
+	for (cell = 0; cell < pool->size; cell++) {
+		cellptr = pool->ptr + cell;
+		cellptr->pool = pool;
+		cellptr->next = pool->free;
+		pool->free = cellptr;
+	}
+	pool->room = (pool->size + 1) / 2;
+
+	/* init statistics */
+	pool->max_used = 0;
+	pool->total_elements = pool->size;
+	spin_unlock_irqrestore(&pool->lock, flags);
+	return 0;
+}
+
+/* remove events */
+int snd_seq_pool_done(pool_t *pool)
+{
+	unsigned long flags;
+	snd_seq_event_cell_t *ptr;
+	int max_count = 5 * HZ;
+
+	snd_assert(pool != NULL, return -EINVAL);
+
+	/* wait for closing all threads */
+	spin_lock_irqsave(&pool->lock, flags);
+	pool->closing = 1;
+	spin_unlock_irqrestore(&pool->lock, flags);
+
+	if (waitqueue_active(&pool->output_sleep))
+		wake_up(&pool->output_sleep);
+
+	while (atomic_read(&pool->counter) > 0) {
+		if (max_count == 0) {
+			snd_printk(KERN_WARNING "snd_seq_pool_done timeout: %d cells remain\n", atomic_read(&pool->counter));
+			break;
+		}
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(1);
+		max_count--;
+	}
+	
+	/* release all resources */
+	spin_lock_irqsave(&pool->lock, flags);
+	ptr = pool->ptr;
+	pool->ptr = NULL;
+	pool->free = NULL;
+	pool->total_elements = 0;
+	spin_unlock_irqrestore(&pool->lock, flags);
+
+	vfree(ptr);
+
+	spin_lock_irqsave(&pool->lock, flags);
+	pool->closing = 0;
+	spin_unlock_irqrestore(&pool->lock, flags);
+
+	return 0;
+}
+
+
+/* init new memory pool */
+pool_t *snd_seq_pool_new(int poolsize)
+{
+	pool_t *pool;
+
+	/* create pool block */
+	pool = kcalloc(1, sizeof(*pool), GFP_KERNEL);
+	if (pool == NULL) {
+		snd_printd("seq: malloc failed for pool\n");
+		return NULL;
+	}
+	spin_lock_init(&pool->lock);
+	pool->ptr = NULL;
+	pool->free = NULL;
+	pool->total_elements = 0;
+	atomic_set(&pool->counter, 0);
+	pool->closing = 0;
+	init_waitqueue_head(&pool->output_sleep);
+	
+	pool->size = poolsize;
+
+	/* init statistics */
+	pool->max_used = 0;
+	return pool;
+}
+
+/* remove memory pool */
+int snd_seq_pool_delete(pool_t **ppool)
+{
+	pool_t *pool = *ppool;
+
+	*ppool = NULL;
+	if (pool == NULL)
+		return 0;
+	snd_seq_pool_done(pool);
+	kfree(pool);
+	return 0;
+}
+
+/* initialize sequencer memory */
+int __init snd_sequencer_memory_init(void)
+{
+	return 0;
+}
+
+/* release sequencer memory */
+void __exit snd_sequencer_memory_done(void)
+{
+}
+
+
+/* exported to seq_clientmgr.c */
+void snd_seq_info_pool(snd_info_buffer_t * buffer, pool_t *pool, char *space)
+{
+	if (pool == NULL)
+		return;
+	snd_iprintf(buffer, "%sPool size          : %d\n", space, pool->total_elements);
+	snd_iprintf(buffer, "%sCells in use       : %d\n", space, atomic_read(&pool->counter));
+	snd_iprintf(buffer, "%sPeak cells in use  : %d\n", space, pool->max_used);
+	snd_iprintf(buffer, "%sAlloc success      : %d\n", space, pool->event_alloc_success);
+	snd_iprintf(buffer, "%sAlloc failures     : %d\n", space, pool->event_alloc_failures);
+}
diff --git a/sound/core/seq/seq_memory.h b/sound/core/seq/seq_memory.h
new file mode 100644
index 000000000000..6c4dde5d3d6f
--- /dev/null
+++ b/sound/core/seq/seq_memory.h
@@ -0,0 +1,104 @@
+/*
+ *  ALSA sequencer Memory Manager
+ *  Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ *   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 __SND_SEQ_MEMORYMGR_H
+#define __SND_SEQ_MEMORYMGR_H
+
+#include <sound/seq_kernel.h>
+#include <linux/poll.h>
+
+typedef struct pool pool_t;
+
+/* container for sequencer event (internal use) */
+typedef struct snd_seq_event_cell_t {
+	snd_seq_event_t event;
+	pool_t *pool;				/* used pool */
+	struct snd_seq_event_cell_t *next;	/* next cell */
+} snd_seq_event_cell_t;
+
+/* design note: the pool is a contigious block of memory, if we dynamicly
+   want to add additional cells to the pool be better store this in another
+   pool as we need to know the base address of the pool when releasing
+   memory. */
+
+struct pool {
+	snd_seq_event_cell_t *ptr;	/* pointer to first event chunk */
+	snd_seq_event_cell_t *free;	/* pointer to the head of the free list */
+
+	int total_elements;	/* pool size actually allocated */
+	atomic_t counter;	/* cells free */
+
+	int size;		/* pool size to be allocated */
+	int room;		/* watermark for sleep/wakeup */
+
+	int closing;
+
+	/* statistics */
+	int max_used;
+	int event_alloc_nopool;
+	int event_alloc_failures;
+	int event_alloc_success;
+
+	/* Write locking */
+	wait_queue_head_t output_sleep;
+
+	/* Pool lock */
+	spinlock_t lock;
+};
+
+extern void snd_seq_cell_free(snd_seq_event_cell_t* cell);
+
+int snd_seq_event_dup(pool_t *pool, snd_seq_event_t *event, snd_seq_event_cell_t **cellp, int nonblock, struct file *file);
+
+/* return number of unused (free) cells */
+static inline int snd_seq_unused_cells(pool_t *pool)
+{
+	return pool ? pool->total_elements - atomic_read(&pool->counter) : 0;
+}
+
+/* return total number of allocated cells */
+static inline int snd_seq_total_cells(pool_t *pool)
+{
+	return pool ? pool->total_elements : 0;
+}
+
+/* init pool - allocate events */
+int snd_seq_pool_init(pool_t *pool);
+
+/* done pool - free events */
+int snd_seq_pool_done(pool_t *pool);
+
+/* create pool */
+pool_t *snd_seq_pool_new(int poolsize);
+
+/* remove pool */
+int snd_seq_pool_delete(pool_t **pool);
+
+/* init memory */
+int snd_sequencer_memory_init(void);
+            
+/* release event memory */
+void snd_sequencer_memory_done(void);
+
+/* polling */
+int snd_seq_pool_poll_wait(pool_t *pool, struct file *file, poll_table *wait);
+
+
+#endif
diff --git a/sound/core/seq/seq_midi.c b/sound/core/seq/seq_midi.c
new file mode 100644
index 000000000000..18247db45db6
--- /dev/null
+++ b/sound/core/seq/seq_midi.c
@@ -0,0 +1,489 @@
+/*
+ *   Generic MIDI synth driver for ALSA sequencer
+ *   Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *                         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
+ *
+ */
+ 
+/* 
+Possible options for midisynth module:
+	- automatic opening of midi ports on first received event or subscription
+	  (close will be performed when client leaves)
+*/
+
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/moduleparam.h>
+#include <asm/semaphore.h>
+#include <sound/core.h>
+#include <sound/rawmidi.h>
+#include <sound/seq_kernel.h>
+#include <sound/seq_device.h>
+#include <sound/seq_midi_event.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Frank van de Pol <fvdpol@coil.demon.nl>, Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI synth.");
+MODULE_LICENSE("GPL");
+static int output_buffer_size = PAGE_SIZE;
+module_param(output_buffer_size, int, 0644);
+MODULE_PARM_DESC(output_buffer_size, "Output buffer size in bytes.");
+static int input_buffer_size = PAGE_SIZE;
+module_param(input_buffer_size, int, 0644);
+MODULE_PARM_DESC(input_buffer_size, "Input buffer size in bytes.");
+
+/* data for this midi synth driver */
+typedef struct {
+	snd_card_t *card;
+	int device;
+	int subdevice;
+	snd_rawmidi_file_t input_rfile;
+	snd_rawmidi_file_t output_rfile;
+	int seq_client;
+	int seq_port;
+	snd_midi_event_t *parser;
+} seq_midisynth_t;
+
+typedef struct {
+	int seq_client;
+	int num_ports;
+	int ports_per_device[SNDRV_RAWMIDI_DEVICES];
+ 	seq_midisynth_t *ports[SNDRV_RAWMIDI_DEVICES];
+} seq_midisynth_client_t;
+
+static seq_midisynth_client_t *synths[SNDRV_CARDS];
+static DECLARE_MUTEX(register_mutex);
+
+/* handle rawmidi input event (MIDI v1.0 stream) */
+static void snd_midi_input_event(snd_rawmidi_substream_t * substream)
+{
+	snd_rawmidi_runtime_t *runtime;
+	seq_midisynth_t *msynth;
+	snd_seq_event_t ev;
+	char buf[16], *pbuf;
+	long res, count;
+
+	if (substream == NULL)
+		return;
+	runtime = substream->runtime;
+	msynth = (seq_midisynth_t *) runtime->private_data;
+	if (msynth == NULL)
+		return;
+	memset(&ev, 0, sizeof(ev));
+	while (runtime->avail > 0) {
+		res = snd_rawmidi_kernel_read(substream, buf, sizeof(buf));
+		if (res <= 0)
+			continue;
+		if (msynth->parser == NULL)
+			continue;
+		pbuf = buf;
+		while (res > 0) {
+			count = snd_midi_event_encode(msynth->parser, pbuf, res, &ev);
+			if (count < 0)
+				break;
+			pbuf += count;
+			res -= count;
+			if (ev.type != SNDRV_SEQ_EVENT_NONE) {
+				ev.source.port = msynth->seq_port;
+				ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
+				snd_seq_kernel_client_dispatch(msynth->seq_client, &ev, 1, 0);
+				/* clear event and reset header */
+				memset(&ev, 0, sizeof(ev));
+			}
+		}
+	}
+}
+
+static int dump_midi(snd_rawmidi_substream_t *substream, const char *buf, int count)
+{
+	snd_rawmidi_runtime_t *runtime;
+	int tmp;
+
+	snd_assert(substream != NULL || buf != NULL, return -EINVAL);
+	runtime = substream->runtime;
+	if ((tmp = runtime->avail) < count) {
+		snd_printd("warning, output event was lost (count = %i, available = %i)\n", count, tmp);
+		return -ENOMEM;
+	}
+	if (snd_rawmidi_kernel_write(substream, buf, count) < count)
+		return -EINVAL;
+	return 0;
+}
+
+static int event_process_midi(snd_seq_event_t * ev, int direct,
+			      void *private_data, int atomic, int hop)
+{
+	seq_midisynth_t *msynth = (seq_midisynth_t *) private_data;
+	unsigned char msg[10];	/* buffer for constructing midi messages */
+	snd_rawmidi_substream_t *substream;
+	int res;
+
+	snd_assert(msynth != NULL, return -EINVAL);
+	substream = msynth->output_rfile.output;
+	if (substream == NULL)
+		return -ENODEV;
+	if (ev->type == SNDRV_SEQ_EVENT_SYSEX) {	/* special case, to save space */
+		if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) {
+			/* invalid event */
+			snd_printd("seq_midi: invalid sysex event flags = 0x%x\n", ev->flags);
+			return 0;
+		}
+		res = snd_seq_dump_var_event(ev, (snd_seq_dump_func_t)dump_midi, substream);
+		snd_midi_event_reset_decode(msynth->parser);
+		if (res < 0)
+			return res;
+	} else {
+		if (msynth->parser == NULL)
+			return -EIO;
+		res = snd_midi_event_decode(msynth->parser, msg, sizeof(msg), ev);
+		if (res < 0)
+			return res;
+		if ((res = dump_midi(substream, msg, res)) < 0) {
+			snd_midi_event_reset_decode(msynth->parser);
+			return res;
+		}
+	}
+	return 0;
+}
+
+
+static int snd_seq_midisynth_new(seq_midisynth_t *msynth,
+				 snd_card_t *card,
+				 int device,
+				 int subdevice)
+{
+	if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &msynth->parser) < 0)
+		return -ENOMEM;
+	msynth->card = card;
+	msynth->device = device;
+	msynth->subdevice = subdevice;
+	return 0;
+}
+
+/* open associated midi device for input */
+static int midisynth_subscribe(void *private_data, snd_seq_port_subscribe_t *info)
+{
+	int err;
+	seq_midisynth_t *msynth = (seq_midisynth_t *)private_data;
+	snd_rawmidi_runtime_t *runtime;
+	snd_rawmidi_params_t params;
+
+	/* open midi port */
+	if ((err = snd_rawmidi_kernel_open(msynth->card->number, msynth->device, msynth->subdevice, SNDRV_RAWMIDI_LFLG_INPUT, &msynth->input_rfile)) < 0) {
+		snd_printd("midi input open failed!!!\n");
+		return err;
+	}
+	runtime = msynth->input_rfile.input->runtime;
+	memset(&params, 0, sizeof(params));
+	params.avail_min = 1;
+	params.buffer_size = input_buffer_size;
+	if ((err = snd_rawmidi_input_params(msynth->input_rfile.input, &params)) < 0) {
+		snd_rawmidi_kernel_release(&msynth->input_rfile);
+		return err;
+	}
+	snd_midi_event_reset_encode(msynth->parser);
+	runtime->event = snd_midi_input_event;
+	runtime->private_data = msynth;
+	snd_rawmidi_kernel_read(msynth->input_rfile.input, NULL, 0);
+	return 0;
+}
+
+/* close associated midi device for input */
+static int midisynth_unsubscribe(void *private_data, snd_seq_port_subscribe_t *info)
+{
+	int err;
+	seq_midisynth_t *msynth = (seq_midisynth_t *)private_data;
+
+	snd_assert(msynth->input_rfile.input != NULL, return -EINVAL);
+	err = snd_rawmidi_kernel_release(&msynth->input_rfile);
+	return err;
+}
+
+/* open associated midi device for output */
+static int midisynth_use(void *private_data, snd_seq_port_subscribe_t *info)
+{
+	int err;
+	seq_midisynth_t *msynth = (seq_midisynth_t *)private_data;
+	snd_rawmidi_params_t params;
+
+	/* open midi port */
+	if ((err = snd_rawmidi_kernel_open(msynth->card->number, msynth->device, msynth->subdevice, SNDRV_RAWMIDI_LFLG_OUTPUT, &msynth->output_rfile)) < 0) {
+		snd_printd("midi output open failed!!!\n");
+		return err;
+	}
+	memset(&params, 0, sizeof(params));
+	params.avail_min = 1;
+	params.buffer_size = output_buffer_size;
+	if ((err = snd_rawmidi_output_params(msynth->output_rfile.output, &params)) < 0) {
+		snd_rawmidi_kernel_release(&msynth->output_rfile);
+		return err;
+	}
+	snd_midi_event_reset_decode(msynth->parser);
+	return 0;
+}
+
+/* close associated midi device for output */
+static int midisynth_unuse(void *private_data, snd_seq_port_subscribe_t *info)
+{
+	seq_midisynth_t *msynth = (seq_midisynth_t *)private_data;
+	unsigned char buf = 0xff; /* MIDI reset */
+
+	snd_assert(msynth->output_rfile.output != NULL, return -EINVAL);
+	/* sending single MIDI reset message to shut the device up */
+	snd_rawmidi_kernel_write(msynth->output_rfile.output, &buf, 1);
+	snd_rawmidi_drain_output(msynth->output_rfile.output);
+	return snd_rawmidi_kernel_release(&msynth->output_rfile);
+}
+
+/* delete given midi synth port */
+static void snd_seq_midisynth_delete(seq_midisynth_t *msynth)
+{
+	if (msynth == NULL)
+		return;
+
+	if (msynth->seq_client > 0) {
+		/* delete port */
+		snd_seq_event_port_detach(msynth->seq_client, msynth->seq_port);
+	}
+
+	if (msynth->parser)
+		snd_midi_event_free(msynth->parser);
+}
+
+/* set our client name */
+static int set_client_name(seq_midisynth_client_t *client, snd_card_t *card,
+			   snd_rawmidi_info_t *rmidi)
+{
+	snd_seq_client_info_t cinfo;
+	const char *name;
+
+	memset(&cinfo, 0, sizeof(cinfo));
+	cinfo.client = client->seq_client;
+	cinfo.type = KERNEL_CLIENT;
+	name = rmidi->name[0] ? (const char *)rmidi->name : "External MIDI";
+	strlcpy(cinfo.name, name, sizeof(cinfo.name));
+	return snd_seq_kernel_client_ctl(client->seq_client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &cinfo);
+}
+
+/* register new midi synth port */
+static int
+snd_seq_midisynth_register_port(snd_seq_device_t *dev)
+{
+	seq_midisynth_client_t *client;
+	seq_midisynth_t *msynth, *ms;
+	snd_seq_port_info_t *port;
+	snd_rawmidi_info_t *info;
+	int newclient = 0;
+	unsigned int p, ports;
+	snd_seq_client_callback_t callbacks;
+	snd_seq_port_callback_t pcallbacks;
+	snd_card_t *card = dev->card;
+	int device = dev->device;
+	unsigned int input_count = 0, output_count = 0;
+
+	snd_assert(card != NULL && device >= 0 && device < SNDRV_RAWMIDI_DEVICES, return -EINVAL);
+	info = kmalloc(sizeof(*info), GFP_KERNEL);
+	if (! info)
+		return -ENOMEM;
+	info->device = device;
+	info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT;
+	info->subdevice = 0;
+	if (snd_rawmidi_info_select(card, info) >= 0)
+		output_count = info->subdevices_count;
+	info->stream = SNDRV_RAWMIDI_STREAM_INPUT;
+	if (snd_rawmidi_info_select(card, info) >= 0) {
+		input_count = info->subdevices_count;
+	}
+	ports = output_count;
+	if (ports < input_count)
+		ports = input_count;
+	if (ports == 0) {
+		kfree(info);
+		return -ENODEV;
+	}
+	if (ports > (256 / SNDRV_RAWMIDI_DEVICES))
+		ports = 256 / SNDRV_RAWMIDI_DEVICES;
+
+	down(&register_mutex);
+	client = synths[card->number];
+	if (client == NULL) {
+		newclient = 1;
+		client = kcalloc(1, sizeof(*client), GFP_KERNEL);
+		if (client == NULL) {
+			up(&register_mutex);
+			kfree(info);
+			return -ENOMEM;
+		}
+		memset(&callbacks, 0, sizeof(callbacks));
+		callbacks.private_data = client;
+		callbacks.allow_input = callbacks.allow_output = 1;
+		client->seq_client = snd_seq_create_kernel_client(card, 0, &callbacks);
+		if (client->seq_client < 0) {
+			kfree(client);
+			up(&register_mutex);
+			kfree(info);
+			return -ENOMEM;
+		}
+		set_client_name(client, card, info);
+	} else if (device == 0)
+		set_client_name(client, card, info); /* use the first device's name */
+
+	msynth = kcalloc(ports, sizeof(seq_midisynth_t), GFP_KERNEL);
+	port = kmalloc(sizeof(*port), GFP_KERNEL);
+	if (msynth == NULL || port == NULL)
+		goto __nomem;
+
+	for (p = 0; p < ports; p++) {
+		ms = &msynth[p];
+
+		if (snd_seq_midisynth_new(ms, card, device, p) < 0)
+			goto __nomem;
+
+		/* declare port */
+		memset(port, 0, sizeof(*port));
+		port->addr.client = client->seq_client;
+		port->addr.port = device * (256 / SNDRV_RAWMIDI_DEVICES) + p;
+		port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
+		memset(info, 0, sizeof(*info));
+		info->device = device;
+		if (p < output_count)
+			info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT;
+		else
+			info->stream = SNDRV_RAWMIDI_STREAM_INPUT;
+		info->subdevice = p;
+		if (snd_rawmidi_info_select(card, info) >= 0)
+			strcpy(port->name, info->subname);
+		if (! port->name[0]) {
+			if (info->name[0]) {
+				if (ports > 1)
+					snprintf(port->name, sizeof(port->name), "%s-%d", info->name, p);
+				else
+					snprintf(port->name, sizeof(port->name), "%s", info->name);
+			} else {
+				/* last resort */
+				if (ports > 1)
+					sprintf(port->name, "MIDI %d-%d-%d", card->number, device, p);
+				else
+					sprintf(port->name, "MIDI %d-%d", card->number, device);
+			}
+		}
+		if ((info->flags & SNDRV_RAWMIDI_INFO_OUTPUT) && p < output_count)
+			port->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
+		if ((info->flags & SNDRV_RAWMIDI_INFO_INPUT) && p < input_count)
+			port->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
+		if ((port->capability & (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ)) == (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ) &&
+		    info->flags & SNDRV_RAWMIDI_INFO_DUPLEX)
+			port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
+		port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC;
+		port->midi_channels = 16;
+		memset(&pcallbacks, 0, sizeof(pcallbacks));
+		pcallbacks.owner = THIS_MODULE;
+		pcallbacks.private_data = ms;
+		pcallbacks.subscribe = midisynth_subscribe;
+		pcallbacks.unsubscribe = midisynth_unsubscribe;
+		pcallbacks.use = midisynth_use;
+		pcallbacks.unuse = midisynth_unuse;
+		pcallbacks.event_input = event_process_midi;
+		port->kernel = &pcallbacks;
+		if (snd_seq_kernel_client_ctl(client->seq_client, SNDRV_SEQ_IOCTL_CREATE_PORT, port)<0)
+			goto __nomem;
+		ms->seq_client = client->seq_client;
+		ms->seq_port = port->addr.port;
+	}
+	client->ports_per_device[device] = ports;
+	client->ports[device] = msynth;
+	client->num_ports++;
+	if (newclient)
+		synths[card->number] = client;
+	up(&register_mutex);
+	return 0;	/* success */
+
+      __nomem:
+	if (msynth != NULL) {
+	      	for (p = 0; p < ports; p++)
+	      		snd_seq_midisynth_delete(&msynth[p]);
+		kfree(msynth);
+	}
+	if (newclient) {
+		snd_seq_delete_kernel_client(client->seq_client);
+		kfree(client);
+	}
+	kfree(info);
+	kfree(port);
+	up(&register_mutex);
+	return -ENOMEM;
+}
+
+/* release midi synth port */
+static int
+snd_seq_midisynth_unregister_port(snd_seq_device_t *dev)
+{
+	seq_midisynth_client_t *client;
+	seq_midisynth_t *msynth;
+	snd_card_t *card = dev->card;
+	int device = dev->device, p, ports;
+	
+	down(&register_mutex);
+	client = synths[card->number];
+	if (client == NULL || client->ports[device] == NULL) {
+		up(&register_mutex);
+		return -ENODEV;
+	}
+	ports = client->ports_per_device[device];
+	client->ports_per_device[device] = 0;
+	msynth = client->ports[device];
+	client->ports[device] = NULL;
+	snd_runtime_check(msynth != NULL || ports <= 0, goto __skip);
+	for (p = 0; p < ports; p++)
+		snd_seq_midisynth_delete(&msynth[p]);
+	kfree(msynth);
+      __skip:
+	client->num_ports--;
+	if (client->num_ports <= 0) {
+		snd_seq_delete_kernel_client(client->seq_client);
+		synths[card->number] = NULL;
+		kfree(client);
+	}
+	up(&register_mutex);
+	return 0;
+}
+
+
+static int __init alsa_seq_midi_init(void)
+{
+	static snd_seq_dev_ops_t ops = {
+		snd_seq_midisynth_register_port,
+		snd_seq_midisynth_unregister_port,
+	};
+	memset(&synths, 0, sizeof(synths));
+	snd_seq_autoload_lock();
+	snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_MIDISYNTH, &ops, 0);
+	snd_seq_autoload_unlock();
+	return 0;
+}
+
+static void __exit alsa_seq_midi_exit(void)
+{
+	snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_MIDISYNTH);
+}
+
+module_init(alsa_seq_midi_init)
+module_exit(alsa_seq_midi_exit)
diff --git a/sound/core/seq/seq_midi_emul.c b/sound/core/seq/seq_midi_emul.c
new file mode 100644
index 000000000000..35fe8a7e34bf
--- /dev/null
+++ b/sound/core/seq/seq_midi_emul.c
@@ -0,0 +1,735 @@
+/*
+ *  GM/GS/XG midi module.
+ *
+ *  Copyright (C) 1999 Steve Ratcliffe
+ *
+ *  Based on awe_wave.c by Takashi Iwai
+ *
+ *   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 module is used to keep track of the current midi state.
+ * It can be used for drivers that are required to emulate midi when
+ * the hardware doesn't.
+ *
+ * It was written for a AWE64 driver, but there should be no AWE specific
+ * code in here.  If there is it should be reported as a bug.
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <sound/core.h>
+#include <sound/seq_kernel.h>
+#include <sound/seq_midi_emul.h>
+#include <sound/initval.h>
+#include <sound/asoundef.h>
+
+MODULE_AUTHOR("Takashi Iwai / Steve Ratcliffe");
+MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI emulation.");
+MODULE_LICENSE("GPL");
+
+/* Prototypes for static functions */
+static void note_off(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan, int note, int vel);
+static void do_control(snd_midi_op_t *ops, void *private,
+		       snd_midi_channel_set_t *chset, snd_midi_channel_t *chan,
+		       int control, int value);
+static void rpn(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan, snd_midi_channel_set_t *chset);
+static void nrpn(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan, snd_midi_channel_set_t *chset);
+static void sysex(snd_midi_op_t *ops, void *private, unsigned char *sysex, int len, snd_midi_channel_set_t *chset);
+static void all_sounds_off(snd_midi_op_t *ops, void *private, snd_midi_channel_t *chan);
+static void all_notes_off(snd_midi_op_t *ops, void *private, snd_midi_channel_t *chan);
+static void snd_midi_reset_controllers(snd_midi_channel_t *chan);
+static void reset_all_channels(snd_midi_channel_set_t *chset);
+
+
+/*
+ * Process an event in a driver independent way.  This means dealing
+ * with RPN, NRPN, SysEx etc that are defined for common midi applications
+ * such as GM, GS and XG.
+ * There modes that this module will run in are:
+ *   Generic MIDI - no interpretation at all, it will just save current values
+ *                  of controlers etc.
+ *   GM - You can use all gm_ prefixed elements of chan.  Controls, RPN, NRPN,
+ *        SysEx will be interpreded as defined in General Midi.
+ *   GS - You can use all gs_ prefixed elements of chan. Codes for GS will be
+ *        interpreted.
+ *   XG - You can use all xg_ prefixed elements of chan.  Codes for XG will
+ *        be interpreted.
+ */
+void
+snd_midi_process_event(snd_midi_op_t *ops,
+		       snd_seq_event_t *ev, snd_midi_channel_set_t *chanset)
+{
+	snd_midi_channel_t *chan;
+	void *drv;
+	int dest_channel = 0;
+
+	if (ev == NULL || chanset == NULL) {
+		snd_printd("ev or chanbase NULL (snd_midi_process_event)\n");
+		return;
+	}
+	if (chanset->channels == NULL)
+		return;
+
+	if (snd_seq_ev_is_channel_type(ev)) {
+		dest_channel = ev->data.note.channel;
+		if (dest_channel >= chanset->max_channels) {
+			snd_printd("dest channel is %d, max is %d\n", dest_channel, chanset->max_channels);
+			return;
+		}
+	}
+
+	chan = chanset->channels + dest_channel;
+	drv  = chanset->private_data;
+
+	/* EVENT_NOTE should be processed before queued */
+	if (ev->type == SNDRV_SEQ_EVENT_NOTE)
+		return;
+
+	/* Make sure that we don't have a note on that should really be
+	 * a note off */
+	if (ev->type == SNDRV_SEQ_EVENT_NOTEON && ev->data.note.velocity == 0)
+		ev->type = SNDRV_SEQ_EVENT_NOTEOFF;
+
+	/* Make sure the note is within array range */
+	if (ev->type == SNDRV_SEQ_EVENT_NOTEON ||
+	    ev->type == SNDRV_SEQ_EVENT_NOTEOFF ||
+	    ev->type == SNDRV_SEQ_EVENT_KEYPRESS) {
+		if (ev->data.note.note >= 128)
+			return;
+	}
+
+	switch (ev->type) {
+	case SNDRV_SEQ_EVENT_NOTEON:
+		if (chan->note[ev->data.note.note] & SNDRV_MIDI_NOTE_ON) {
+			if (ops->note_off)
+				ops->note_off(drv, ev->data.note.note, 0, chan);
+		}
+		chan->note[ev->data.note.note] = SNDRV_MIDI_NOTE_ON;
+		if (ops->note_on)
+			ops->note_on(drv, ev->data.note.note, ev->data.note.velocity, chan);
+		break;
+	case SNDRV_SEQ_EVENT_NOTEOFF:
+		if (! (chan->note[ev->data.note.note] & SNDRV_MIDI_NOTE_ON))
+			break;
+		if (ops->note_off)
+			note_off(ops, drv, chan, ev->data.note.note, ev->data.note.velocity);
+		break;
+	case SNDRV_SEQ_EVENT_KEYPRESS:
+		if (ops->key_press)
+			ops->key_press(drv, ev->data.note.note, ev->data.note.velocity, chan);
+		break;
+	case SNDRV_SEQ_EVENT_CONTROLLER:
+		do_control(ops, drv, chanset, chan,
+			   ev->data.control.param, ev->data.control.value);
+		break;
+	case SNDRV_SEQ_EVENT_PGMCHANGE:
+		chan->midi_program = ev->data.control.value;
+		break;
+	case SNDRV_SEQ_EVENT_PITCHBEND:
+		chan->midi_pitchbend = ev->data.control.value;
+		if (ops->control)
+			ops->control(drv, MIDI_CTL_PITCHBEND, chan);
+		break;
+	case SNDRV_SEQ_EVENT_CHANPRESS:
+		chan->midi_pressure = ev->data.control.value;
+		if (ops->control)
+			ops->control(drv, MIDI_CTL_CHAN_PRESSURE, chan);
+		break;
+	case SNDRV_SEQ_EVENT_CONTROL14:
+		/* Best guess is that this is any of the 14 bit controller values */
+		if (ev->data.control.param < 32) {
+			/* set low part first */
+			chan->control[ev->data.control.param + 32] =
+				ev->data.control.value & 0x7f;
+			do_control(ops, drv, chanset, chan,
+				   ev->data.control.param,
+				   ((ev->data.control.value>>7) & 0x7f));
+		} else
+			do_control(ops, drv, chanset, chan,
+				   ev->data.control.param,
+				   ev->data.control.value);
+		break;
+	case SNDRV_SEQ_EVENT_NONREGPARAM:
+		/* Break it back into its controler values */
+		chan->param_type = SNDRV_MIDI_PARAM_TYPE_NONREGISTERED;
+		chan->control[MIDI_CTL_MSB_DATA_ENTRY]
+			= (ev->data.control.value >> 7) & 0x7f;
+		chan->control[MIDI_CTL_LSB_DATA_ENTRY]
+			= ev->data.control.value & 0x7f;
+		chan->control[MIDI_CTL_NONREG_PARM_NUM_MSB]
+			= (ev->data.control.param >> 7) & 0x7f;
+		chan->control[MIDI_CTL_NONREG_PARM_NUM_LSB]
+			= ev->data.control.param & 0x7f;
+		nrpn(ops, drv, chan, chanset);
+		break;
+	case SNDRV_SEQ_EVENT_REGPARAM:
+		/* Break it back into its controler values */
+		chan->param_type = SNDRV_MIDI_PARAM_TYPE_REGISTERED;
+		chan->control[MIDI_CTL_MSB_DATA_ENTRY]
+			= (ev->data.control.value >> 7) & 0x7f;
+		chan->control[MIDI_CTL_LSB_DATA_ENTRY]
+			= ev->data.control.value & 0x7f;
+		chan->control[MIDI_CTL_REGIST_PARM_NUM_MSB]
+			= (ev->data.control.param >> 7) & 0x7f;
+		chan->control[MIDI_CTL_REGIST_PARM_NUM_LSB]
+			= ev->data.control.param & 0x7f;
+		rpn(ops, drv, chan, chanset);
+		break;
+	case SNDRV_SEQ_EVENT_SYSEX:
+		if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) == SNDRV_SEQ_EVENT_LENGTH_VARIABLE) {
+			unsigned char sysexbuf[64];
+			int len;
+			len = snd_seq_expand_var_event(ev, sizeof(sysexbuf), sysexbuf, 1, 0);
+			if (len > 0)
+				sysex(ops, drv, sysexbuf, len, chanset);
+		}
+		break;
+	case SNDRV_SEQ_EVENT_SONGPOS:
+	case SNDRV_SEQ_EVENT_SONGSEL:
+	case SNDRV_SEQ_EVENT_CLOCK:
+	case SNDRV_SEQ_EVENT_START:
+	case SNDRV_SEQ_EVENT_CONTINUE:
+	case SNDRV_SEQ_EVENT_STOP:
+	case SNDRV_SEQ_EVENT_QFRAME:
+	case SNDRV_SEQ_EVENT_TEMPO:
+	case SNDRV_SEQ_EVENT_TIMESIGN:
+	case SNDRV_SEQ_EVENT_KEYSIGN:
+		goto not_yet;
+	case SNDRV_SEQ_EVENT_SENSING:
+		break;
+	case SNDRV_SEQ_EVENT_CLIENT_START:
+	case SNDRV_SEQ_EVENT_CLIENT_EXIT:
+	case SNDRV_SEQ_EVENT_CLIENT_CHANGE:
+	case SNDRV_SEQ_EVENT_PORT_START:
+	case SNDRV_SEQ_EVENT_PORT_EXIT:
+	case SNDRV_SEQ_EVENT_PORT_CHANGE:
+	case SNDRV_SEQ_EVENT_SAMPLE:
+	case SNDRV_SEQ_EVENT_SAMPLE_START:
+	case SNDRV_SEQ_EVENT_SAMPLE_STOP:
+	case SNDRV_SEQ_EVENT_SAMPLE_FREQ:
+	case SNDRV_SEQ_EVENT_SAMPLE_VOLUME:
+	case SNDRV_SEQ_EVENT_SAMPLE_LOOP:
+	case SNDRV_SEQ_EVENT_SAMPLE_POSITION:
+	case SNDRV_SEQ_EVENT_ECHO:
+	not_yet:
+	default:
+		/*snd_printd("Unimplemented event %d\n", ev->type);*/
+		break;
+	}
+}
+
+
+/*
+ * release note
+ */
+static void
+note_off(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan, int note, int vel)
+{
+	if (chan->gm_hold) {
+		/* Hold this note until pedal is turned off */
+		chan->note[note] |= SNDRV_MIDI_NOTE_RELEASED;
+	} else if (chan->note[note] & SNDRV_MIDI_NOTE_SOSTENUTO) {
+		/* Mark this note as release; it will be turned off when sostenuto
+		 * is turned off */
+		chan->note[note] |= SNDRV_MIDI_NOTE_RELEASED;
+	} else {
+		chan->note[note] = 0;
+		if (ops->note_off)
+			ops->note_off(drv, note, vel, chan);
+	}
+}
+
+/*
+ * Do all driver independent operations for this controler and pass
+ * events that need to take place immediately to the driver.
+ */
+static void
+do_control(snd_midi_op_t *ops, void *drv, snd_midi_channel_set_t *chset,
+	   snd_midi_channel_t *chan, int control, int value)
+{
+	int  i;
+
+	/* Switches */
+	if ((control >=64 && control <=69) || (control >= 80 && control <= 83)) {
+		/* These are all switches; either off or on so set to 0 or 127 */
+		value = (value >= 64)? 127: 0;
+	}
+	chan->control[control] = value;
+
+	switch (control) {
+	case MIDI_CTL_SUSTAIN:
+		if (value == 0) {
+			/* Sustain has been released, turn off held notes */
+			for (i = 0; i < 128; i++) {
+				if (chan->note[i] & SNDRV_MIDI_NOTE_RELEASED) {
+					chan->note[i] = SNDRV_MIDI_NOTE_OFF;
+					if (ops->note_off)
+						ops->note_off(drv, i, 0, chan);
+				}
+			}
+		}
+		break;
+	case MIDI_CTL_PORTAMENTO:
+		break;
+	case MIDI_CTL_SOSTENUTO:
+		if (value) {
+			/* Mark each note that is currently held down */
+			for (i = 0; i < 128; i++) {
+				if (chan->note[i] & SNDRV_MIDI_NOTE_ON)
+					chan->note[i] |= SNDRV_MIDI_NOTE_SOSTENUTO;
+			}
+		} else {
+			/* release all notes that were held */
+			for (i = 0; i < 128; i++) {
+				if (chan->note[i] & SNDRV_MIDI_NOTE_SOSTENUTO) {
+					chan->note[i] &= ~SNDRV_MIDI_NOTE_SOSTENUTO;
+					if (chan->note[i] & SNDRV_MIDI_NOTE_RELEASED) {
+						chan->note[i] = SNDRV_MIDI_NOTE_OFF;
+						if (ops->note_off)
+							ops->note_off(drv, i, 0, chan);
+					}
+				}
+			}
+		}
+		break;
+	case MIDI_CTL_MSB_DATA_ENTRY:
+		chan->control[MIDI_CTL_LSB_DATA_ENTRY] = 0;
+		/* go through here */
+	case MIDI_CTL_LSB_DATA_ENTRY:
+		if (chan->param_type == SNDRV_MIDI_PARAM_TYPE_REGISTERED)
+			rpn(ops, drv, chan, chset);
+		else
+			nrpn(ops, drv, chan, chset);
+		break;
+	case MIDI_CTL_REGIST_PARM_NUM_LSB:
+	case MIDI_CTL_REGIST_PARM_NUM_MSB:
+		chan->param_type = SNDRV_MIDI_PARAM_TYPE_REGISTERED;
+		break;
+	case MIDI_CTL_NONREG_PARM_NUM_LSB:
+	case MIDI_CTL_NONREG_PARM_NUM_MSB:
+		chan->param_type = SNDRV_MIDI_PARAM_TYPE_NONREGISTERED;
+		break;
+
+	case MIDI_CTL_ALL_SOUNDS_OFF:
+		all_sounds_off(ops, drv, chan);
+		break;
+
+	case MIDI_CTL_ALL_NOTES_OFF:
+		all_notes_off(ops, drv, chan);
+		break;
+
+	case MIDI_CTL_MSB_BANK:
+		if (chset->midi_mode == SNDRV_MIDI_MODE_XG) {
+			if (value == 127)
+				chan->drum_channel = 1;
+			else
+				chan->drum_channel = 0;
+		}
+		break;
+	case MIDI_CTL_LSB_BANK:
+		break;
+
+	case MIDI_CTL_RESET_CONTROLLERS:
+		snd_midi_reset_controllers(chan);
+		break;
+
+	case MIDI_CTL_SOFT_PEDAL:
+	case MIDI_CTL_LEGATO_FOOTSWITCH:
+	case MIDI_CTL_HOLD2:
+	case MIDI_CTL_SC1_SOUND_VARIATION:
+	case MIDI_CTL_SC2_TIMBRE:
+	case MIDI_CTL_SC3_RELEASE_TIME:
+	case MIDI_CTL_SC4_ATTACK_TIME:
+	case MIDI_CTL_SC5_BRIGHTNESS:
+	case MIDI_CTL_E1_REVERB_DEPTH:
+	case MIDI_CTL_E2_TREMOLO_DEPTH:
+	case MIDI_CTL_E3_CHORUS_DEPTH:
+	case MIDI_CTL_E4_DETUNE_DEPTH:
+	case MIDI_CTL_E5_PHASER_DEPTH:
+		goto notyet;
+	notyet:
+	default:
+		if (ops->control)
+			ops->control(drv, control, chan);
+		break;
+	}
+}
+
+
+/*
+ * initialize the MIDI status
+ */
+void
+snd_midi_channel_set_clear(snd_midi_channel_set_t *chset)
+{
+	int i;
+
+	chset->midi_mode = SNDRV_MIDI_MODE_GM;
+	chset->gs_master_volume = 127;
+
+	for (i = 0; i < chset->max_channels; i++) {
+		snd_midi_channel_t *chan = chset->channels + i;
+		memset(chan->note, 0, sizeof(chan->note));
+
+		chan->midi_aftertouch = 0;
+		chan->midi_pressure = 0;
+		chan->midi_program = 0;
+		chan->midi_pitchbend = 0;
+		snd_midi_reset_controllers(chan);
+		chan->gm_rpn_pitch_bend_range = 256; /* 2 semitones */
+		chan->gm_rpn_fine_tuning = 0;
+		chan->gm_rpn_coarse_tuning = 0;
+
+		if (i == 9)
+			chan->drum_channel = 1;
+		else
+			chan->drum_channel = 0;
+	}
+}
+
+/*
+ * Process a rpn message.
+ */
+static void
+rpn(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan,
+    snd_midi_channel_set_t *chset)
+{
+	int type;
+	int val;
+
+	if (chset->midi_mode != SNDRV_MIDI_MODE_NONE) {
+		type = (chan->control[MIDI_CTL_REGIST_PARM_NUM_MSB] << 8) |
+			chan->control[MIDI_CTL_REGIST_PARM_NUM_LSB];
+		val = (chan->control[MIDI_CTL_MSB_DATA_ENTRY] << 7) |
+			chan->control[MIDI_CTL_LSB_DATA_ENTRY];
+
+		switch (type) {
+		case 0x0000: /* Pitch bend sensitivity */
+			/* MSB only / 1 semitone per 128 */
+			chan->gm_rpn_pitch_bend_range = val;
+			break;
+					
+		case 0x0001: /* fine tuning: */
+			/* MSB/LSB, 8192=center, 100/8192 cent step */
+			chan->gm_rpn_fine_tuning = val - 8192;
+			break;
+
+		case 0x0002: /* coarse tuning */
+			/* MSB only / 8192=center, 1 semitone per 128 */
+			chan->gm_rpn_coarse_tuning = val - 8192;
+			break;
+
+		case 0x7F7F: /* "lock-in" RPN */
+			/* ignored */
+			break;
+		}
+	}
+	/* should call nrpn or rpn callback here.. */
+}
+
+/*
+ * Process an nrpn message.
+ */
+static void
+nrpn(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan,
+     snd_midi_channel_set_t *chset)
+{
+	/* parse XG NRPNs here if possible */
+	if (ops->nrpn)
+		ops->nrpn(drv, chan, chset);
+}
+
+
+/*
+ * convert channel parameter in GS sysex
+ */
+static int
+get_channel(unsigned char cmd)
+{
+	int p = cmd & 0x0f;
+	if (p == 0)
+		p = 9;
+	else if (p < 10)
+		p--;
+	return p;
+}
+
+
+/*
+ * Process a sysex message.
+ */
+static void
+sysex(snd_midi_op_t *ops, void *private, unsigned char *buf, int len, snd_midi_channel_set_t *chset)
+{
+	/* GM on */
+	static unsigned char gm_on_macro[] = {
+		0x7e,0x7f,0x09,0x01,
+	};
+	/* XG on */
+	static unsigned char xg_on_macro[] = {
+		0x43,0x10,0x4c,0x00,0x00,0x7e,0x00,
+	};
+	/* GS prefix
+	 * drum channel: XX=0x1?(channel), YY=0x15, ZZ=on/off
+	 * reverb mode: XX=0x01, YY=0x30, ZZ=0-7
+	 * chorus mode: XX=0x01, YY=0x38, ZZ=0-7
+	 * master vol:  XX=0x00, YY=0x04, ZZ=0-127
+	 */
+	static unsigned char gs_pfx_macro[] = {
+		0x41,0x10,0x42,0x12,0x40,/*XX,YY,ZZ*/
+	};
+
+	int parsed = SNDRV_MIDI_SYSEX_NOT_PARSED;
+
+	if (len <= 0 || buf[0] != 0xf0)
+		return;
+	/* skip first byte */
+	buf++;
+	len--;
+
+	/* GM on */
+	if (len >= (int)sizeof(gm_on_macro) &&
+	    memcmp(buf, gm_on_macro, sizeof(gm_on_macro)) == 0) {
+		if (chset->midi_mode != SNDRV_MIDI_MODE_GS &&
+		    chset->midi_mode != SNDRV_MIDI_MODE_XG) {
+			chset->midi_mode = SNDRV_MIDI_MODE_GM;
+			reset_all_channels(chset);
+			parsed = SNDRV_MIDI_SYSEX_GM_ON;
+		}
+	}
+
+	/* GS macros */
+	else if (len >= 8 &&
+		 memcmp(buf, gs_pfx_macro, sizeof(gs_pfx_macro)) == 0) {
+		if (chset->midi_mode != SNDRV_MIDI_MODE_GS &&
+		    chset->midi_mode != SNDRV_MIDI_MODE_XG)
+			chset->midi_mode = SNDRV_MIDI_MODE_GS;
+
+		if (buf[5] == 0x00 && buf[6] == 0x7f && buf[7] == 0x00) {
+			/* GS reset */
+			parsed = SNDRV_MIDI_SYSEX_GS_RESET;
+			reset_all_channels(chset);
+		}
+
+		else if ((buf[5] & 0xf0) == 0x10 && buf[6] == 0x15) {
+			/* drum pattern */
+			int p = get_channel(buf[5]);
+			if (p < chset->max_channels) {
+				parsed = SNDRV_MIDI_SYSEX_GS_DRUM_CHANNEL;
+				if (buf[7])
+					chset->channels[p].drum_channel = 1;
+				else
+					chset->channels[p].drum_channel = 0;
+			}
+
+		} else if ((buf[5] & 0xf0) == 0x10 && buf[6] == 0x21) {
+			/* program */
+			int p = get_channel(buf[5]);
+			if (p < chset->max_channels &&
+			    ! chset->channels[p].drum_channel) {
+				parsed = SNDRV_MIDI_SYSEX_GS_DRUM_CHANNEL;
+				chset->channels[p].midi_program = buf[7];
+			}
+
+		} else if (buf[5] == 0x01 && buf[6] == 0x30) {
+			/* reverb mode */
+			parsed = SNDRV_MIDI_SYSEX_GS_REVERB_MODE;
+			chset->gs_reverb_mode = buf[7];
+
+		} else if (buf[5] == 0x01 && buf[6] == 0x38) {
+			/* chorus mode */
+			parsed = SNDRV_MIDI_SYSEX_GS_CHORUS_MODE;
+			chset->gs_chorus_mode = buf[7];
+
+		} else if (buf[5] == 0x00 && buf[6] == 0x04) {
+			/* master volume */
+			parsed = SNDRV_MIDI_SYSEX_GS_MASTER_VOLUME;
+			chset->gs_master_volume = buf[7];
+
+		}
+	}
+
+	/* XG on */
+	else if (len >= (int)sizeof(xg_on_macro) &&
+		 memcmp(buf, xg_on_macro, sizeof(xg_on_macro)) == 0) {
+		int i;
+		chset->midi_mode = SNDRV_MIDI_MODE_XG;
+		parsed = SNDRV_MIDI_SYSEX_XG_ON;
+		/* reset CC#0 for drums */
+		for (i = 0; i < chset->max_channels; i++) {
+			if (chset->channels[i].drum_channel)
+				chset->channels[i].control[MIDI_CTL_MSB_BANK] = 127;
+			else
+				chset->channels[i].control[MIDI_CTL_MSB_BANK] = 0;
+		}
+	}
+
+	if (ops->sysex)
+		ops->sysex(private, buf - 1, len + 1, parsed, chset);
+}
+
+/*
+ * all sound off
+ */
+static void
+all_sounds_off(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan)
+{
+	int n;
+
+	if (! ops->note_terminate)
+		return;
+	for (n = 0; n < 128; n++) {
+		if (chan->note[n]) {
+			ops->note_terminate(drv, n, chan);
+			chan->note[n] = 0;
+		}
+	}
+}
+
+/*
+ * all notes off
+ */
+static void
+all_notes_off(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan)
+{
+	int n;
+
+	if (! ops->note_off)
+		return;
+	for (n = 0; n < 128; n++) {
+		if (chan->note[n] == SNDRV_MIDI_NOTE_ON)
+			note_off(ops, drv, chan, n, 0);
+	}
+}
+
+/*
+ * Initialise a single midi channel control block.
+ */
+static void snd_midi_channel_init(snd_midi_channel_t *p, int n)
+{
+	if (p == NULL)
+		return;
+
+	memset(p, 0, sizeof(snd_midi_channel_t));
+	p->private = NULL;
+	p->number = n;
+
+	snd_midi_reset_controllers(p);
+	p->gm_rpn_pitch_bend_range = 256; /* 2 semitones */
+	p->gm_rpn_fine_tuning = 0;
+	p->gm_rpn_coarse_tuning = 0;
+
+	if (n == 9)
+		p->drum_channel = 1;	/* Default ch 10 as drums */
+}
+
+/*
+ * Allocate and initialise a set of midi channel control blocks.
+ */
+static snd_midi_channel_t *snd_midi_channel_init_set(int n)
+{
+	snd_midi_channel_t *chan;
+	int  i;
+
+	chan = kmalloc(n * sizeof(snd_midi_channel_t), GFP_KERNEL);
+	if (chan) {
+		for (i = 0; i < n; i++)
+			snd_midi_channel_init(chan+i, i);
+	}
+
+	return chan;
+}
+
+/*
+ * reset all midi channels
+ */
+static void
+reset_all_channels(snd_midi_channel_set_t *chset)
+{
+	int ch;
+	for (ch = 0; ch < chset->max_channels; ch++) {
+		snd_midi_channel_t *chan = chset->channels + ch;
+		snd_midi_reset_controllers(chan);
+		chan->gm_rpn_pitch_bend_range = 256; /* 2 semitones */
+		chan->gm_rpn_fine_tuning = 0;
+		chan->gm_rpn_coarse_tuning = 0;
+
+		if (ch == 9)
+			chan->drum_channel = 1;
+		else
+			chan->drum_channel = 0;
+	}
+}
+
+
+/*
+ * Allocate and initialise a midi channel set.
+ */
+snd_midi_channel_set_t *snd_midi_channel_alloc_set(int n)
+{
+	snd_midi_channel_set_t *chset;
+
+	chset = kmalloc(sizeof(*chset), GFP_KERNEL);
+	if (chset) {
+		chset->channels = snd_midi_channel_init_set(n);
+		chset->private_data = NULL;
+		chset->max_channels = n;
+	}
+	return chset;
+}
+
+/*
+ * Reset the midi controllers on a particular channel to default values.
+ */
+static void snd_midi_reset_controllers(snd_midi_channel_t *chan)
+{
+	memset(chan->control, 0, sizeof(chan->control));
+	chan->gm_volume = 127;
+	chan->gm_expression = 127;
+	chan->gm_pan = 64;
+}
+
+
+/*
+ * Free a midi channel set.
+ */
+void snd_midi_channel_free_set(snd_midi_channel_set_t *chset)
+{
+	if (chset == NULL)
+		return;
+	kfree(chset->channels);
+	kfree(chset);
+}
+
+static int __init alsa_seq_midi_emul_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_seq_midi_emul_exit(void)
+{
+}
+
+module_init(alsa_seq_midi_emul_init)
+module_exit(alsa_seq_midi_emul_exit)
+
+EXPORT_SYMBOL(snd_midi_process_event);
+EXPORT_SYMBOL(snd_midi_channel_set_clear);
+EXPORT_SYMBOL(snd_midi_channel_alloc_set);
+EXPORT_SYMBOL(snd_midi_channel_free_set);
diff --git a/sound/core/seq/seq_midi_event.c b/sound/core/seq/seq_midi_event.c
new file mode 100644
index 000000000000..21e569062bc3
--- /dev/null
+++ b/sound/core/seq/seq_midi_event.c
@@ -0,0 +1,539 @@
+/*
+ *  MIDI byte <-> sequencer event coder
+ *
+ *  Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>,
+ *                        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/slab.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <sound/core.h>
+#include <sound/seq_kernel.h>
+#include <sound/seq_midi_event.h>
+#include <sound/asoundef.h>
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>, Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("MIDI byte <-> sequencer event coder");
+MODULE_LICENSE("GPL");
+
+/* queue type */
+/* from 0 to 7 are normal commands (note off, on, etc.) */
+#define ST_NOTEOFF	0
+#define ST_NOTEON	1
+#define ST_SPECIAL	8
+#define ST_SYSEX	ST_SPECIAL
+/* from 8 to 15 are events for 0xf0-0xf7 */
+
+
+/* status event types */
+typedef void (*event_encode_t)(snd_midi_event_t *dev, snd_seq_event_t *ev);
+typedef void (*event_decode_t)(snd_seq_event_t *ev, unsigned char *buf);
+
+/*
+ * prototypes
+ */
+static void note_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
+static void one_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
+static void pitchbend_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
+static void two_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
+static void one_param_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
+static void songpos_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
+static void note_decode(snd_seq_event_t *ev, unsigned char *buf);
+static void one_param_decode(snd_seq_event_t *ev, unsigned char *buf);
+static void pitchbend_decode(snd_seq_event_t *ev, unsigned char *buf);
+static void two_param_decode(snd_seq_event_t *ev, unsigned char *buf);
+static void songpos_decode(snd_seq_event_t *ev, unsigned char *buf);
+
+/*
+ * event list
+ */
+static struct status_event_list_t {
+	int event;
+	int qlen;
+	event_encode_t encode;
+	event_decode_t decode;
+} status_event[] = {
+	/* 0x80 - 0xf0 */
+	{SNDRV_SEQ_EVENT_NOTEOFF,	2, note_event, note_decode},
+	{SNDRV_SEQ_EVENT_NOTEON,	2, note_event, note_decode},
+	{SNDRV_SEQ_EVENT_KEYPRESS,	2, note_event, note_decode},
+	{SNDRV_SEQ_EVENT_CONTROLLER,	2, two_param_ctrl_event, two_param_decode},
+	{SNDRV_SEQ_EVENT_PGMCHANGE,	1, one_param_ctrl_event, one_param_decode},
+	{SNDRV_SEQ_EVENT_CHANPRESS,	1, one_param_ctrl_event, one_param_decode},
+	{SNDRV_SEQ_EVENT_PITCHBEND,	2, pitchbend_ctrl_event, pitchbend_decode},
+	{SNDRV_SEQ_EVENT_NONE,		0, NULL, NULL}, /* 0xf0 */
+	/* 0xf0 - 0xff */
+	{SNDRV_SEQ_EVENT_SYSEX,		1, NULL, NULL}, /* sysex: 0xf0 */
+	{SNDRV_SEQ_EVENT_QFRAME,	1, one_param_event, one_param_decode}, /* 0xf1 */
+	{SNDRV_SEQ_EVENT_SONGPOS,	2, songpos_event, songpos_decode}, /* 0xf2 */
+	{SNDRV_SEQ_EVENT_SONGSEL,	1, one_param_event, one_param_decode}, /* 0xf3 */
+	{SNDRV_SEQ_EVENT_NONE,		0, NULL, NULL}, /* 0xf4 */
+	{SNDRV_SEQ_EVENT_NONE,		0, NULL, NULL}, /* 0xf5 */
+	{SNDRV_SEQ_EVENT_TUNE_REQUEST,	0, NULL, NULL},	/* 0xf6 */
+	{SNDRV_SEQ_EVENT_NONE,		0, NULL, NULL}, /* 0xf7 */
+	{SNDRV_SEQ_EVENT_CLOCK,		0, NULL, NULL}, /* 0xf8 */
+	{SNDRV_SEQ_EVENT_NONE,		0, NULL, NULL}, /* 0xf9 */
+	{SNDRV_SEQ_EVENT_START,		0, NULL, NULL}, /* 0xfa */
+	{SNDRV_SEQ_EVENT_CONTINUE,	0, NULL, NULL}, /* 0xfb */
+	{SNDRV_SEQ_EVENT_STOP, 		0, NULL, NULL}, /* 0xfc */
+	{SNDRV_SEQ_EVENT_NONE, 		0, NULL, NULL}, /* 0xfd */
+	{SNDRV_SEQ_EVENT_SENSING, 	0, NULL, NULL}, /* 0xfe */
+	{SNDRV_SEQ_EVENT_RESET, 	0, NULL, NULL}, /* 0xff */
+};
+
+static int extra_decode_ctrl14(snd_midi_event_t *dev, unsigned char *buf, int len, snd_seq_event_t *ev);
+static int extra_decode_xrpn(snd_midi_event_t *dev, unsigned char *buf, int count, snd_seq_event_t *ev);
+
+static struct extra_event_list_t {
+	int event;
+	int (*decode)(snd_midi_event_t *dev, unsigned char *buf, int len, snd_seq_event_t *ev);
+} extra_event[] = {
+	{SNDRV_SEQ_EVENT_CONTROL14, extra_decode_ctrl14},
+	{SNDRV_SEQ_EVENT_NONREGPARAM, extra_decode_xrpn},
+	{SNDRV_SEQ_EVENT_REGPARAM, extra_decode_xrpn},
+};
+
+/*
+ *  new/delete record
+ */
+
+int snd_midi_event_new(int bufsize, snd_midi_event_t **rdev)
+{
+	snd_midi_event_t *dev;
+
+	*rdev = NULL;
+	dev = kcalloc(1, sizeof(*dev), GFP_KERNEL);
+	if (dev == NULL)
+		return -ENOMEM;
+	if (bufsize > 0) {
+		dev->buf = kmalloc(bufsize, GFP_KERNEL);
+		if (dev->buf == NULL) {
+			kfree(dev);
+			return -ENOMEM;
+		}
+	}
+	dev->bufsize = bufsize;
+	dev->lastcmd = 0xff;
+	spin_lock_init(&dev->lock);
+	*rdev = dev;
+	return 0;
+}
+
+void snd_midi_event_free(snd_midi_event_t *dev)
+{
+	if (dev != NULL) {
+		kfree(dev->buf);
+		kfree(dev);
+	}
+}
+
+/*
+ * initialize record
+ */
+inline static void reset_encode(snd_midi_event_t *dev)
+{
+	dev->read = 0;
+	dev->qlen = 0;
+	dev->type = 0;
+}
+
+void snd_midi_event_reset_encode(snd_midi_event_t *dev)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	reset_encode(dev);
+	spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+void snd_midi_event_reset_decode(snd_midi_event_t *dev)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	dev->lastcmd = 0xff;
+	spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+void snd_midi_event_init(snd_midi_event_t *dev)
+{
+	snd_midi_event_reset_encode(dev);
+	snd_midi_event_reset_decode(dev);
+}
+
+void snd_midi_event_no_status(snd_midi_event_t *dev, int on)
+{
+	dev->nostat = on ? 1 : 0;
+}
+
+/*
+ * resize buffer
+ */
+int snd_midi_event_resize_buffer(snd_midi_event_t *dev, int bufsize)
+{
+	unsigned char *new_buf, *old_buf;
+	unsigned long flags;
+
+	if (bufsize == dev->bufsize)
+		return 0;
+	new_buf = kmalloc(bufsize, GFP_KERNEL);
+	if (new_buf == NULL)
+		return -ENOMEM;
+	spin_lock_irqsave(&dev->lock, flags);
+	old_buf = dev->buf;
+	dev->buf = new_buf;
+	dev->bufsize = bufsize;
+	reset_encode(dev);
+	spin_unlock_irqrestore(&dev->lock, flags);
+	kfree(old_buf);
+	return 0;
+}
+
+/*
+ *  read bytes and encode to sequencer event if finished
+ *  return the size of encoded bytes
+ */
+long snd_midi_event_encode(snd_midi_event_t *dev, unsigned char *buf, long count, snd_seq_event_t *ev)
+{
+	long result = 0;
+	int rc;
+
+	ev->type = SNDRV_SEQ_EVENT_NONE;
+
+	while (count-- > 0) {
+		rc = snd_midi_event_encode_byte(dev, *buf++, ev);
+		result++;
+		if (rc < 0)
+			return rc;
+		else if (rc > 0)
+			return result;
+	}
+
+	return result;
+}
+
+/*
+ *  read one byte and encode to sequencer event:
+ *  return 1 if MIDI bytes are encoded to an event
+ *         0 data is not finished
+ *         negative for error
+ */
+int snd_midi_event_encode_byte(snd_midi_event_t *dev, int c, snd_seq_event_t *ev)
+{
+	int rc = 0;
+	unsigned long flags;
+
+	c &= 0xff;
+
+	if (c >= MIDI_CMD_COMMON_CLOCK) {
+		/* real-time event */
+		ev->type = status_event[ST_SPECIAL + c - 0xf0].event;
+		ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
+		ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED;
+		return 1;
+	}
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (dev->qlen > 0) {
+		/* rest of command */
+		dev->buf[dev->read++] = c;
+		if (dev->type != ST_SYSEX)
+			dev->qlen--;
+	} else {
+		/* new command */
+		dev->read = 1;
+		if (c & 0x80) {
+			dev->buf[0] = c;
+			if ((c & 0xf0) == 0xf0) /* special events */
+				dev->type = (c & 0x0f) + ST_SPECIAL;
+			else
+				dev->type = (c >> 4) & 0x07;
+			dev->qlen = status_event[dev->type].qlen;
+		} else {
+			/* process this byte as argument */
+			dev->buf[dev->read++] = c;
+			dev->qlen = status_event[dev->type].qlen - 1;
+		}
+	}
+	if (dev->qlen == 0) {
+		ev->type = status_event[dev->type].event;
+		ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
+		ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED;
+		if (status_event[dev->type].encode) /* set data values */
+			status_event[dev->type].encode(dev, ev);
+		rc = 1;
+	} else 	if (dev->type == ST_SYSEX) {
+		if (c == MIDI_CMD_COMMON_SYSEX_END ||
+		    dev->read >= dev->bufsize) {
+			ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
+			ev->flags |= SNDRV_SEQ_EVENT_LENGTH_VARIABLE;
+			ev->type = SNDRV_SEQ_EVENT_SYSEX;
+			ev->data.ext.len = dev->read;
+			ev->data.ext.ptr = dev->buf;
+			if (c != MIDI_CMD_COMMON_SYSEX_END)
+				dev->read = 0; /* continue to parse */
+			else
+				reset_encode(dev); /* all parsed */
+			rc = 1;
+		}
+	}
+
+	spin_unlock_irqrestore(&dev->lock, flags);
+	return rc;
+}
+
+/* encode note event */
+static void note_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
+{
+	ev->data.note.channel = dev->buf[0] & 0x0f;
+	ev->data.note.note = dev->buf[1];
+	ev->data.note.velocity = dev->buf[2];
+}
+
+/* encode one parameter controls */
+static void one_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
+{
+	ev->data.control.channel = dev->buf[0] & 0x0f;
+	ev->data.control.value = dev->buf[1];
+}
+
+/* encode pitch wheel change */
+static void pitchbend_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
+{
+	ev->data.control.channel = dev->buf[0] & 0x0f;
+	ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1] - 8192;
+}
+
+/* encode midi control change */
+static void two_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
+{
+	ev->data.control.channel = dev->buf[0] & 0x0f;
+	ev->data.control.param = dev->buf[1];
+	ev->data.control.value = dev->buf[2];
+}
+
+/* encode one parameter value*/
+static void one_param_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
+{
+	ev->data.control.value = dev->buf[1];
+}
+
+/* encode song position */
+static void songpos_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
+{
+	ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1];
+}
+
+/*
+ * decode from a sequencer event to midi bytes
+ * return the size of decoded midi events
+ */
+long snd_midi_event_decode(snd_midi_event_t *dev, unsigned char *buf, long count, snd_seq_event_t *ev)
+{
+	unsigned int cmd, type;
+
+	if (ev->type == SNDRV_SEQ_EVENT_NONE)
+		return -ENOENT;
+
+	for (type = 0; type < ARRAY_SIZE(status_event); type++) {
+		if (ev->type == status_event[type].event)
+			goto __found;
+	}
+	for (type = 0; type < ARRAY_SIZE(extra_event); type++) {
+		if (ev->type == extra_event[type].event)
+			return extra_event[type].decode(dev, buf, count, ev);
+	}
+	return -ENOENT;
+
+      __found:
+	if (type >= ST_SPECIAL)
+		cmd = 0xf0 + (type - ST_SPECIAL);
+	else
+		/* data.note.channel and data.control.channel is identical */
+		cmd = 0x80 | (type << 4) | (ev->data.note.channel & 0x0f);
+
+
+	if (cmd == MIDI_CMD_COMMON_SYSEX) {
+		snd_midi_event_reset_decode(dev);
+		return snd_seq_expand_var_event(ev, count, buf, 1, 0);
+	} else {
+		int qlen;
+		unsigned char xbuf[4];
+		unsigned long flags;
+
+		spin_lock_irqsave(&dev->lock, flags);
+		if ((cmd & 0xf0) == 0xf0 || dev->lastcmd != cmd || dev->nostat) {
+			dev->lastcmd = cmd;
+			spin_unlock_irqrestore(&dev->lock, flags);
+			xbuf[0] = cmd;
+			if (status_event[type].decode)
+				status_event[type].decode(ev, xbuf + 1);
+			qlen = status_event[type].qlen + 1;
+		} else {
+			spin_unlock_irqrestore(&dev->lock, flags);
+			if (status_event[type].decode)
+				status_event[type].decode(ev, xbuf + 0);
+			qlen = status_event[type].qlen;
+		}
+		if (count < qlen)
+			return -ENOMEM;
+		memcpy(buf, xbuf, qlen);
+		return qlen;
+	}
+}
+
+
+/* decode note event */
+static void note_decode(snd_seq_event_t *ev, unsigned char *buf)
+{
+	buf[0] = ev->data.note.note & 0x7f;
+	buf[1] = ev->data.note.velocity & 0x7f;
+}
+
+/* decode one parameter controls */
+static void one_param_decode(snd_seq_event_t *ev, unsigned char *buf)
+{
+	buf[0] = ev->data.control.value & 0x7f;
+}
+
+/* decode pitch wheel change */
+static void pitchbend_decode(snd_seq_event_t *ev, unsigned char *buf)
+{
+	int value = ev->data.control.value + 8192;
+	buf[0] = value & 0x7f;
+	buf[1] = (value >> 7) & 0x7f;
+}
+
+/* decode midi control change */
+static void two_param_decode(snd_seq_event_t *ev, unsigned char *buf)
+{
+	buf[0] = ev->data.control.param & 0x7f;
+	buf[1] = ev->data.control.value & 0x7f;
+}
+
+/* decode song position */
+static void songpos_decode(snd_seq_event_t *ev, unsigned char *buf)
+{
+	buf[0] = ev->data.control.value & 0x7f;
+	buf[1] = (ev->data.control.value >> 7) & 0x7f;
+}
+
+/* decode 14bit control */
+static int extra_decode_ctrl14(snd_midi_event_t *dev, unsigned char *buf, int count, snd_seq_event_t *ev)
+{
+	unsigned char cmd;
+	int idx = 0;
+
+	cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f);
+	if (ev->data.control.param < 0x20) {
+		if (count < 4)
+			return -ENOMEM;
+		if (dev->nostat && count < 6)
+			return -ENOMEM;
+		if (cmd != dev->lastcmd || dev->nostat) {
+			if (count < 5)
+				return -ENOMEM;
+			buf[idx++] = dev->lastcmd = cmd;
+		}
+		buf[idx++] = ev->data.control.param;
+		buf[idx++] = (ev->data.control.value >> 7) & 0x7f;
+		if (dev->nostat)
+			buf[idx++] = cmd;
+		buf[idx++] = ev->data.control.param + 0x20;
+		buf[idx++] = ev->data.control.value & 0x7f;
+	} else {
+		if (count < 2)
+			return -ENOMEM;
+		if (cmd != dev->lastcmd || dev->nostat) {
+			if (count < 3)
+				return -ENOMEM;
+			buf[idx++] = dev->lastcmd = cmd;
+		}
+		buf[idx++] = ev->data.control.param & 0x7f;
+		buf[idx++] = ev->data.control.value & 0x7f;
+	}
+	return idx;
+}
+
+/* decode reg/nonreg param */
+static int extra_decode_xrpn(snd_midi_event_t *dev, unsigned char *buf, int count, snd_seq_event_t *ev)
+{
+	unsigned char cmd;
+	char *cbytes;
+	static char cbytes_nrpn[4] = { MIDI_CTL_NONREG_PARM_NUM_MSB,
+				       MIDI_CTL_NONREG_PARM_NUM_LSB,
+				       MIDI_CTL_MSB_DATA_ENTRY,
+				       MIDI_CTL_LSB_DATA_ENTRY };
+	static char cbytes_rpn[4] =  { MIDI_CTL_REGIST_PARM_NUM_MSB,
+				       MIDI_CTL_REGIST_PARM_NUM_LSB,
+				       MIDI_CTL_MSB_DATA_ENTRY,
+				       MIDI_CTL_LSB_DATA_ENTRY };
+	unsigned char bytes[4];
+	int idx = 0, i;
+
+	if (count < 8)
+		return -ENOMEM;
+	if (dev->nostat && count < 12)
+		return -ENOMEM;
+	cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f);
+	bytes[0] = ev->data.control.param & 0x007f;
+	bytes[1] = (ev->data.control.param & 0x3f80) >> 7;
+	bytes[2] = ev->data.control.value & 0x007f;
+	bytes[3] = (ev->data.control.value & 0x3f80) >> 7;
+	if (cmd != dev->lastcmd && !dev->nostat) {
+		if (count < 9)
+			return -ENOMEM;
+		buf[idx++] = dev->lastcmd = cmd;
+	}
+	cbytes = ev->type == SNDRV_SEQ_EVENT_NONREGPARAM ? cbytes_nrpn : cbytes_rpn;
+	for (i = 0; i < 4; i++) {
+		if (dev->nostat)
+			buf[idx++] = dev->lastcmd = cmd;
+		buf[idx++] = cbytes[i];
+		buf[idx++] = bytes[i];
+	}
+	return idx;
+}
+
+/*
+ *  exports
+ */
+ 
+EXPORT_SYMBOL(snd_midi_event_new);
+EXPORT_SYMBOL(snd_midi_event_free);
+EXPORT_SYMBOL(snd_midi_event_resize_buffer);
+EXPORT_SYMBOL(snd_midi_event_init);
+EXPORT_SYMBOL(snd_midi_event_reset_encode);
+EXPORT_SYMBOL(snd_midi_event_reset_decode);
+EXPORT_SYMBOL(snd_midi_event_no_status);
+EXPORT_SYMBOL(snd_midi_event_encode);
+EXPORT_SYMBOL(snd_midi_event_encode_byte);
+EXPORT_SYMBOL(snd_midi_event_decode);
+
+static int __init alsa_seq_midi_event_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_seq_midi_event_exit(void)
+{
+}
+
+module_init(alsa_seq_midi_event_init)
+module_exit(alsa_seq_midi_event_exit)
diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c
new file mode 100644
index 000000000000..b976951fc100
--- /dev/null
+++ b/sound/core/seq/seq_ports.c
@@ -0,0 +1,674 @@
+/*
+ *   ALSA sequencer Ports
+ *   Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *                         Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <linux/slab.h>
+#include "seq_system.h"
+#include "seq_ports.h"
+#include "seq_clientmgr.h"
+
+/*
+
+   registration of client ports
+
+ */
+
+
+/* 
+
+NOTE: the current implementation of the port structure as a linked list is
+not optimal for clients that have many ports. For sending messages to all
+subscribers of a port we first need to find the address of the port
+structure, which means we have to traverse the list. A direct access table
+(array) would be better, but big preallocated arrays waste memory.
+
+Possible actions:
+
+1) leave it this way, a client does normaly does not have more than a few
+ports
+
+2) replace the linked list of ports by a array of pointers which is
+dynamicly kmalloced. When a port is added or deleted we can simply allocate
+a new array, copy the corresponding pointers, and delete the old one. We
+then only need a pointer to this array, and an integer that tells us how
+much elements are in array.
+
+*/
+
+/* return pointer to port structure - port is locked if found */
+client_port_t *snd_seq_port_use_ptr(client_t *client, int num)
+{
+	struct list_head *p;
+	client_port_t *port;
+
+	if (client == NULL)
+		return NULL;
+	read_lock(&client->ports_lock);
+	list_for_each(p, &client->ports_list_head) {
+		port = list_entry(p, client_port_t, list);
+		if (port->addr.port == num) {
+			if (port->closing)
+				break; /* deleting now */
+			snd_use_lock_use(&port->use_lock);
+			read_unlock(&client->ports_lock);
+			return port;
+		}
+	}
+	read_unlock(&client->ports_lock);
+	return NULL;		/* not found */
+}
+
+
+/* search for the next port - port is locked if found */
+client_port_t *snd_seq_port_query_nearest(client_t *client, snd_seq_port_info_t *pinfo)
+{
+	int num;
+	struct list_head *p;
+	client_port_t *port, *found;
+
+	num = pinfo->addr.port;
+	found = NULL;
+	read_lock(&client->ports_lock);
+	list_for_each(p, &client->ports_list_head) {
+		port = list_entry(p, client_port_t, list);
+		if (port->addr.port < num)
+			continue;
+		if (port->addr.port == num) {
+			found = port;
+			break;
+		}
+		if (found == NULL || port->addr.port < found->addr.port)
+			found = port;
+	}
+	if (found) {
+		if (found->closing)
+			found = NULL;
+		else
+			snd_use_lock_use(&found->use_lock);
+	}
+	read_unlock(&client->ports_lock);
+	return found;
+}
+
+
+/* initialize port_subs_info_t */
+static void port_subs_info_init(port_subs_info_t *grp)
+{
+	INIT_LIST_HEAD(&grp->list_head);
+	grp->count = 0;
+	grp->exclusive = 0;
+	rwlock_init(&grp->list_lock);
+	init_rwsem(&grp->list_mutex);
+	grp->open = NULL;
+	grp->close = NULL;
+}
+
+
+/* create a port, port number is returned (-1 on failure) */
+client_port_t *snd_seq_create_port(client_t *client, int port)
+{
+	unsigned long flags;
+	client_port_t *new_port;
+	struct list_head *l;
+	int num = -1;
+	
+	/* sanity check */
+	snd_assert(client, return NULL);
+
+	if (client->num_ports >= SNDRV_SEQ_MAX_PORTS - 1) {
+		snd_printk(KERN_WARNING "too many ports for client %d\n", client->number);
+		return NULL;
+	}
+
+	/* create a new port */
+	new_port = kcalloc(1, sizeof(*new_port), GFP_KERNEL);
+	if (! new_port) {
+		snd_printd("malloc failed for registering client port\n");
+		return NULL;	/* failure, out of memory */
+	}
+	/* init port data */
+	new_port->addr.client = client->number;
+	new_port->addr.port = -1;
+	new_port->owner = THIS_MODULE;
+	sprintf(new_port->name, "port-%d", num);
+	snd_use_lock_init(&new_port->use_lock);
+	port_subs_info_init(&new_port->c_src);
+	port_subs_info_init(&new_port->c_dest);
+
+	num = port >= 0 ? port : 0;
+	down(&client->ports_mutex);
+	write_lock_irqsave(&client->ports_lock, flags);
+	list_for_each(l, &client->ports_list_head) {
+		client_port_t *p = list_entry(l, client_port_t, list);
+		if (p->addr.port > num)
+			break;
+		if (port < 0) /* auto-probe mode */
+			num = p->addr.port + 1;
+	}
+	/* insert the new port */
+	list_add_tail(&new_port->list, l);
+	client->num_ports++;
+	new_port->addr.port = num;	/* store the port number in the port */
+	write_unlock_irqrestore(&client->ports_lock, flags);
+	up(&client->ports_mutex);
+	sprintf(new_port->name, "port-%d", num);
+
+	return new_port;
+}
+
+/* */
+enum group_type_t {
+	SRC_LIST, DEST_LIST
+};
+
+static int subscribe_port(client_t *client, client_port_t *port, port_subs_info_t *grp, snd_seq_port_subscribe_t *info, int send_ack);
+static int unsubscribe_port(client_t *client, client_port_t *port, port_subs_info_t *grp, snd_seq_port_subscribe_t *info, int send_ack);
+
+
+static client_port_t *get_client_port(snd_seq_addr_t *addr, client_t **cp)
+{
+	client_port_t *p;
+	*cp = snd_seq_client_use_ptr(addr->client);
+	if (*cp) {
+		p = snd_seq_port_use_ptr(*cp, addr->port);
+		if (! p) {
+			snd_seq_client_unlock(*cp);
+			*cp = NULL;
+		}
+		return p;
+	}
+	return NULL;
+}
+
+/*
+ * remove all subscribers on the list
+ * this is called from port_delete, for each src and dest list.
+ */
+static void clear_subscriber_list(client_t *client, client_port_t *port,
+				  port_subs_info_t *grp, int grptype)
+{
+	struct list_head *p, *n;
+
+	down_write(&grp->list_mutex);
+	list_for_each_safe(p, n, &grp->list_head) {
+		subscribers_t *subs;
+		client_t *c;
+		client_port_t *aport;
+
+		if (grptype == SRC_LIST) {
+			subs = list_entry(p, subscribers_t, src_list);
+			aport = get_client_port(&subs->info.dest, &c);
+		} else {
+			subs = list_entry(p, subscribers_t, dest_list);
+			aport = get_client_port(&subs->info.sender, &c);
+		}
+		list_del(p);
+		unsubscribe_port(client, port, grp, &subs->info, 0);
+		if (!aport) {
+			/* looks like the connected port is being deleted.
+			 * we decrease the counter, and when both ports are deleted
+			 * remove the subscriber info
+			 */
+			if (atomic_dec_and_test(&subs->ref_count))
+				kfree(subs);
+		} else {
+			/* ok we got the connected port */
+			port_subs_info_t *agrp;
+			agrp = (grptype == SRC_LIST) ? &aport->c_dest : &aport->c_src;
+			down_write(&agrp->list_mutex);
+			if (grptype == SRC_LIST)
+				list_del(&subs->dest_list);
+			else
+				list_del(&subs->src_list);
+			unsubscribe_port(c, aport, agrp, &subs->info, 1);
+			kfree(subs);
+			up_write(&agrp->list_mutex);
+			snd_seq_port_unlock(aport);
+			snd_seq_client_unlock(c);
+		}
+	}
+	up_write(&grp->list_mutex);
+}
+
+/* delete port data */
+static int port_delete(client_t *client, client_port_t *port)
+{
+	/* set closing flag and wait for all port access are gone */
+	port->closing = 1;
+	snd_use_lock_sync(&port->use_lock); 
+
+	/* clear subscribers info */
+	clear_subscriber_list(client, port, &port->c_src, SRC_LIST);
+	clear_subscriber_list(client, port, &port->c_dest, DEST_LIST);
+
+	if (port->private_free)
+		port->private_free(port->private_data);
+
+	snd_assert(port->c_src.count == 0,);
+	snd_assert(port->c_dest.count == 0,);
+
+	kfree(port);
+	return 0;
+}
+
+
+/* delete a port with the given port id */
+int snd_seq_delete_port(client_t *client, int port)
+{
+	unsigned long flags;
+	struct list_head *l;
+	client_port_t *found = NULL;
+
+	down(&client->ports_mutex);
+	write_lock_irqsave(&client->ports_lock, flags);
+	list_for_each(l, &client->ports_list_head) {
+		client_port_t *p = list_entry(l, client_port_t, list);
+		if (p->addr.port == port) {
+			/* ok found.  delete from the list at first */
+			list_del(l);
+			client->num_ports--;
+			found = p;
+			break;
+		}
+	}
+	write_unlock_irqrestore(&client->ports_lock, flags);
+	up(&client->ports_mutex);
+	if (found)
+		return port_delete(client, found);
+	else
+		return -ENOENT;
+}
+
+/* delete the all ports belonging to the given client */
+int snd_seq_delete_all_ports(client_t *client)
+{
+	unsigned long flags;
+	struct list_head deleted_list, *p, *n;
+	
+	/* move the port list to deleted_list, and
+	 * clear the port list in the client data.
+	 */
+	down(&client->ports_mutex);
+	write_lock_irqsave(&client->ports_lock, flags);
+	if (! list_empty(&client->ports_list_head)) {
+		__list_add(&deleted_list,
+			   client->ports_list_head.prev,
+			   client->ports_list_head.next);
+		INIT_LIST_HEAD(&client->ports_list_head);
+	} else {
+		INIT_LIST_HEAD(&deleted_list);
+	}
+	client->num_ports = 0;
+	write_unlock_irqrestore(&client->ports_lock, flags);
+
+	/* remove each port in deleted_list */
+	list_for_each_safe(p, n, &deleted_list) {
+		client_port_t *port = list_entry(p, client_port_t, list);
+		list_del(p);
+		snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port);
+		port_delete(client, port);
+	}
+	up(&client->ports_mutex);
+	return 0;
+}
+
+/* set port info fields */
+int snd_seq_set_port_info(client_port_t * port, snd_seq_port_info_t * info)
+{
+	snd_assert(port && info, return -EINVAL);
+
+	/* set port name */
+	if (info->name[0])
+		strlcpy(port->name, info->name, sizeof(port->name));
+	
+	/* set capabilities */
+	port->capability = info->capability;
+	
+	/* get port type */
+	port->type = info->type;
+
+	/* information about supported channels/voices */
+	port->midi_channels = info->midi_channels;
+	port->midi_voices = info->midi_voices;
+	port->synth_voices = info->synth_voices;
+
+	/* timestamping */
+	port->timestamping = (info->flags & SNDRV_SEQ_PORT_FLG_TIMESTAMP) ? 1 : 0;
+	port->time_real = (info->flags & SNDRV_SEQ_PORT_FLG_TIME_REAL) ? 1 : 0;
+	port->time_queue = info->time_queue;
+
+	return 0;
+}
+
+/* get port info fields */
+int snd_seq_get_port_info(client_port_t * port, snd_seq_port_info_t * info)
+{
+	snd_assert(port && info, return -EINVAL);
+
+	/* get port name */
+	strlcpy(info->name, port->name, sizeof(info->name));
+	
+	/* get capabilities */
+	info->capability = port->capability;
+
+	/* get port type */
+	info->type = port->type;
+
+	/* information about supported channels/voices */
+	info->midi_channels = port->midi_channels;
+	info->midi_voices = port->midi_voices;
+	info->synth_voices = port->synth_voices;
+
+	/* get subscriber counts */
+	info->read_use = port->c_src.count;
+	info->write_use = port->c_dest.count;
+	
+	/* timestamping */
+	info->flags = 0;
+	if (port->timestamping) {
+		info->flags |= SNDRV_SEQ_PORT_FLG_TIMESTAMP;
+		if (port->time_real)
+			info->flags |= SNDRV_SEQ_PORT_FLG_TIME_REAL;
+		info->time_queue = port->time_queue;
+	}
+
+	return 0;
+}
+
+
+
+/*
+ * call callback functions (if any):
+ * the callbacks are invoked only when the first (for connection) or
+ * the last subscription (for disconnection) is done.  Second or later
+ * subscription results in increment of counter, but no callback is
+ * invoked.
+ * This feature is useful if these callbacks are associated with
+ * initialization or termination of devices (see seq_midi.c).
+ *
+ * If callback_all option is set, the callback function is invoked
+ * at each connnection/disconnection. 
+ */
+
+static int subscribe_port(client_t *client, client_port_t *port, port_subs_info_t *grp,
+			  snd_seq_port_subscribe_t *info, int send_ack)
+{
+	int err = 0;
+
+	if (!try_module_get(port->owner))
+		return -EFAULT;
+	grp->count++;
+	if (grp->open && (port->callback_all || grp->count == 1)) {
+		err = grp->open(port->private_data, info);
+		if (err < 0) {
+			module_put(port->owner);
+			grp->count--;
+		}
+	}
+	if (err >= 0 && send_ack && client->type == USER_CLIENT)
+		snd_seq_client_notify_subscription(port->addr.client, port->addr.port,
+						   info, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED);
+
+	return err;
+}
+
+static int unsubscribe_port(client_t *client, client_port_t *port,
+			    port_subs_info_t *grp,
+			    snd_seq_port_subscribe_t *info, int send_ack)
+{
+	int err = 0;
+
+	if (! grp->count)
+		return -EINVAL;
+	grp->count--;
+	if (grp->close && (port->callback_all || grp->count == 0))
+		err = grp->close(port->private_data, info);
+	if (send_ack && client->type == USER_CLIENT)
+		snd_seq_client_notify_subscription(port->addr.client, port->addr.port,
+						   info, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED);
+	module_put(port->owner);
+	return err;
+}
+
+
+
+/* check if both addresses are identical */
+static inline int addr_match(snd_seq_addr_t *r, snd_seq_addr_t *s)
+{
+	return (r->client == s->client) && (r->port == s->port);
+}
+
+/* check the two subscribe info match */
+/* if flags is zero, checks only sender and destination addresses */
+static int match_subs_info(snd_seq_port_subscribe_t *r,
+			   snd_seq_port_subscribe_t *s)
+{
+	if (addr_match(&r->sender, &s->sender) &&
+	    addr_match(&r->dest, &s->dest)) {
+		if (r->flags && r->flags == s->flags)
+			return r->queue == s->queue;
+		else if (! r->flags)
+			return 1;
+	}
+	return 0;
+}
+
+
+/* connect two ports */
+int snd_seq_port_connect(client_t *connector,
+			 client_t *src_client, client_port_t *src_port,
+			 client_t *dest_client, client_port_t *dest_port,
+			 snd_seq_port_subscribe_t *info)
+{
+	port_subs_info_t *src = &src_port->c_src;
+	port_subs_info_t *dest = &dest_port->c_dest;
+	subscribers_t *subs;
+	struct list_head *p;
+	int err, src_called = 0;
+	unsigned long flags;
+	int exclusive;
+
+	subs = kcalloc(1, sizeof(*subs), GFP_KERNEL);
+	if (! subs)
+		return -ENOMEM;
+
+	subs->info = *info;
+	atomic_set(&subs->ref_count, 2);
+
+	down_write(&src->list_mutex);
+	down_write(&dest->list_mutex);
+
+	exclusive = info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE ? 1 : 0;
+	err = -EBUSY;
+	if (exclusive) {
+		if (! list_empty(&src->list_head) || ! list_empty(&dest->list_head))
+			goto __error;
+	} else {
+		if (src->exclusive || dest->exclusive)
+			goto __error;
+		/* check whether already exists */
+		list_for_each(p, &src->list_head) {
+			subscribers_t *s = list_entry(p, subscribers_t, src_list);
+			if (match_subs_info(info, &s->info))
+				goto __error;
+		}
+		list_for_each(p, &dest->list_head) {
+			subscribers_t *s = list_entry(p, subscribers_t, dest_list);
+			if (match_subs_info(info, &s->info))
+				goto __error;
+		}
+	}
+
+	if ((err = subscribe_port(src_client, src_port, src, info,
+				  connector->number != src_client->number)) < 0)
+		goto __error;
+	src_called = 1;
+
+	if ((err = subscribe_port(dest_client, dest_port, dest, info,
+				  connector->number != dest_client->number)) < 0)
+		goto __error;
+
+	/* add to list */
+	write_lock_irqsave(&src->list_lock, flags);
+	// write_lock(&dest->list_lock); // no other lock yet
+	list_add_tail(&subs->src_list, &src->list_head);
+	list_add_tail(&subs->dest_list, &dest->list_head);
+	// write_unlock(&dest->list_lock); // no other lock yet
+	write_unlock_irqrestore(&src->list_lock, flags);
+
+	src->exclusive = dest->exclusive = exclusive;
+
+	up_write(&dest->list_mutex);
+	up_write(&src->list_mutex);
+	return 0;
+
+ __error:
+	if (src_called)
+		unsubscribe_port(src_client, src_port, src, info,
+				 connector->number != src_client->number);
+	kfree(subs);
+	up_write(&dest->list_mutex);
+	up_write(&src->list_mutex);
+	return err;
+}
+
+
+/* remove the connection */
+int snd_seq_port_disconnect(client_t *connector,
+			    client_t *src_client, client_port_t *src_port,
+			    client_t *dest_client, client_port_t *dest_port,
+			    snd_seq_port_subscribe_t *info)
+{
+	port_subs_info_t *src = &src_port->c_src;
+	port_subs_info_t *dest = &dest_port->c_dest;
+	subscribers_t *subs;
+	struct list_head *p;
+	int err = -ENOENT;
+	unsigned long flags;
+
+	down_write(&src->list_mutex);
+	down_write(&dest->list_mutex);
+
+	/* look for the connection */
+	list_for_each(p, &src->list_head) {
+		subs = list_entry(p, subscribers_t, src_list);
+		if (match_subs_info(info, &subs->info)) {
+			write_lock_irqsave(&src->list_lock, flags);
+			// write_lock(&dest->list_lock);  // no lock yet
+			list_del(&subs->src_list);
+			list_del(&subs->dest_list);
+			// write_unlock(&dest->list_lock);
+			write_unlock_irqrestore(&src->list_lock, flags);
+			src->exclusive = dest->exclusive = 0;
+			unsubscribe_port(src_client, src_port, src, info,
+					 connector->number != src_client->number);
+			unsubscribe_port(dest_client, dest_port, dest, info,
+					 connector->number != dest_client->number);
+			kfree(subs);
+			err = 0;
+			break;
+		}
+	}
+
+	up_write(&dest->list_mutex);
+	up_write(&src->list_mutex);
+	return err;
+}
+
+
+/* get matched subscriber */
+subscribers_t *snd_seq_port_get_subscription(port_subs_info_t *src_grp,
+					     snd_seq_addr_t *dest_addr)
+{
+	struct list_head *p;
+	subscribers_t *s, *found = NULL;
+
+	down_read(&src_grp->list_mutex);
+	list_for_each(p, &src_grp->list_head) {
+		s = list_entry(p, subscribers_t, src_list);
+		if (addr_match(dest_addr, &s->info.dest)) {
+			found = s;
+			break;
+		}
+	}
+	up_read(&src_grp->list_mutex);
+	return found;
+}
+
+/*
+ * Attach a device driver that wants to receive events from the
+ * sequencer.  Returns the new port number on success.
+ * A driver that wants to receive the events converted to midi, will
+ * use snd_seq_midisynth_register_port().
+ */
+/* exported */
+int snd_seq_event_port_attach(int client,
+			      snd_seq_port_callback_t *pcbp,
+			      int cap, int type, int midi_channels,
+			      int midi_voices, char *portname)
+{
+	snd_seq_port_info_t portinfo;
+	int  ret;
+
+	/* Set up the port */
+	memset(&portinfo, 0, sizeof(portinfo));
+	portinfo.addr.client = client;
+	strlcpy(portinfo.name, portname ? portname : "Unamed port",
+		sizeof(portinfo.name));
+
+	portinfo.capability = cap;
+	portinfo.type = type;
+	portinfo.kernel = pcbp;
+	portinfo.midi_channels = midi_channels;
+	portinfo.midi_voices = midi_voices;
+
+	/* Create it */
+	ret = snd_seq_kernel_client_ctl(client,
+					SNDRV_SEQ_IOCTL_CREATE_PORT,
+					&portinfo);
+
+	if (ret >= 0)
+		ret = portinfo.addr.port;
+
+	return ret;
+}
+
+
+/*
+ * Detach the driver from a port.
+ */
+/* exported */
+int snd_seq_event_port_detach(int client, int port)
+{
+	snd_seq_port_info_t portinfo;
+	int  err;
+
+	memset(&portinfo, 0, sizeof(portinfo));
+	portinfo.addr.client = client;
+	portinfo.addr.port   = port;
+	err = snd_seq_kernel_client_ctl(client,
+					SNDRV_SEQ_IOCTL_DELETE_PORT,
+					&portinfo);
+
+	return err;
+}
diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h
new file mode 100644
index 000000000000..89fd4416f6fa
--- /dev/null
+++ b/sound/core/seq/seq_ports.h
@@ -0,0 +1,128 @@
+/*
+ *   ALSA sequencer Ports 
+ *   Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ *   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 __SND_SEQ_PORTS_H
+#define __SND_SEQ_PORTS_H
+
+#include <sound/seq_kernel.h>
+#include "seq_lock.h"
+
+/* list of 'exported' ports */
+
+/* Client ports that are not exported are still accessible, but are
+ anonymous ports. 
+ 
+ If a port supports SUBSCRIPTION, that port can send events to all
+ subscribersto a special address, with address
+ (queue==SNDRV_SEQ_ADDRESS_SUBSCRIBERS). The message is then send to all
+ recipients that are registered in the subscription list. A typical
+ application for these SUBSCRIPTION events is handling of incoming MIDI
+ data. The port doesn't 'know' what other clients are interested in this
+ message. If for instance a MIDI recording application would like to receive
+ the events from that port, it will first have to subscribe with that port.
+ 
+*/
+
+typedef struct subscribers_t {
+	snd_seq_port_subscribe_t info;	/* additional info */
+	struct list_head src_list;	/* link of sources */
+	struct list_head dest_list;	/* link of destinations */
+	atomic_t ref_count;
+} subscribers_t;
+
+typedef struct port_subs_info_t {
+	struct list_head list_head;	/* list of subscribed ports */
+	unsigned int count;		/* count of subscribers */
+	unsigned int exclusive: 1;	/* exclusive mode */
+	struct rw_semaphore list_mutex;
+	rwlock_t list_lock;
+	snd_seq_kernel_port_open_t *open;
+	snd_seq_kernel_port_close_t *close;
+} port_subs_info_t;
+
+typedef struct client_port_t {
+
+	snd_seq_addr_t addr;		/* client/port number */
+	struct module *owner;		/* owner of this port */
+	char name[64];			/* port name */	
+	struct list_head list;		/* port list */
+	snd_use_lock_t use_lock;
+
+	/* subscribers */
+	port_subs_info_t c_src;		/* read (sender) list */
+	port_subs_info_t c_dest;	/* write (dest) list */
+
+	snd_seq_kernel_port_input_t *event_input;
+	snd_seq_kernel_port_private_free_t *private_free;
+	void *private_data;
+	unsigned int callback_all : 1;
+	unsigned int closing : 1;
+	unsigned int timestamping: 1;
+	unsigned int time_real: 1;
+	int time_queue;
+	
+	/* capability, inport, output, sync */
+	unsigned int capability;	/* port capability bits */
+	unsigned int type;		/* port type bits */
+
+	/* supported channels */
+	int midi_channels;
+	int midi_voices;
+	int synth_voices;
+		
+} client_port_t;
+
+/* return pointer to port structure and lock port */
+client_port_t *snd_seq_port_use_ptr(client_t *client, int num);
+
+/* search for next port - port is locked if found */
+client_port_t *snd_seq_port_query_nearest(client_t *client, snd_seq_port_info_t *pinfo);
+
+/* unlock the port */
+#define snd_seq_port_unlock(port) snd_use_lock_free(&(port)->use_lock)
+
+/* create a port, port number is returned (-1 on failure) */
+client_port_t *snd_seq_create_port(client_t *client, int port_index);
+
+/* delete a port */
+int snd_seq_delete_port(client_t *client, int port);
+
+/* delete all ports */
+int snd_seq_delete_all_ports(client_t *client);
+
+/* set port info fields */
+int snd_seq_set_port_info(client_port_t *port, snd_seq_port_info_t *info);
+
+/* get port info fields */
+int snd_seq_get_port_info(client_port_t *port, snd_seq_port_info_t *info);
+
+/* add subscriber to subscription list */
+int snd_seq_port_connect(client_t *caller, client_t *s, client_port_t *sp, client_t *d, client_port_t *dp, snd_seq_port_subscribe_t *info);
+
+/* remove subscriber from subscription list */ 
+int snd_seq_port_disconnect(client_t *caller, client_t *s, client_port_t *sp, client_t *d, client_port_t *dp, snd_seq_port_subscribe_t *info);
+
+/* subscribe port */
+int snd_seq_port_subscribe(client_port_t *port, snd_seq_port_subscribe_t *info);
+
+/* get matched subscriber */
+subscribers_t *snd_seq_port_get_subscription(port_subs_info_t *src_grp, snd_seq_addr_t *dest_addr);
+
+#endif
diff --git a/sound/core/seq/seq_prioq.c b/sound/core/seq/seq_prioq.c
new file mode 100644
index 000000000000..a519732ed833
--- /dev/null
+++ b/sound/core/seq/seq_prioq.c
@@ -0,0 +1,449 @@
+/*
+ *   ALSA sequencer Priority Queue
+ *   Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include "seq_timer.h"
+#include "seq_prioq.h"
+
+
+/* Implementation is a simple linked list for now...
+
+   This priority queue orders the events on timestamp. For events with an
+   equeal timestamp the queue behaves as a FIFO. 
+
+   *
+   *           +-------+
+   *  Head --> | first |
+   *           +-------+
+   *                 |next
+   *           +-----v-+
+   *           |       |
+   *           +-------+
+   *                 |
+   *           +-----v-+
+   *           |       |
+   *           +-------+
+   *                 |
+   *           +-----v-+
+   *  Tail --> | last  |
+   *           +-------+
+   *
+
+ */
+
+
+
+/* create new prioq (constructor) */
+prioq_t *snd_seq_prioq_new(void)
+{
+	prioq_t *f;
+
+	f = kcalloc(1, sizeof(*f), GFP_KERNEL);
+	if (f == NULL) {
+		snd_printd("oops: malloc failed for snd_seq_prioq_new()\n");
+		return NULL;
+	}
+	
+	spin_lock_init(&f->lock);
+	f->head = NULL;
+	f->tail = NULL;
+	f->cells = 0;
+	
+	return f;
+}
+
+/* delete prioq (destructor) */
+void snd_seq_prioq_delete(prioq_t **fifo)
+{
+	prioq_t *f = *fifo;
+	*fifo = NULL;
+
+	if (f == NULL) {
+		snd_printd("oops: snd_seq_prioq_delete() called with NULL prioq\n");
+		return;
+	}
+
+	/* release resources...*/
+	/*....................*/
+	
+	if (f->cells > 0) {
+		/* drain prioQ */
+		while (f->cells > 0)
+			snd_seq_cell_free(snd_seq_prioq_cell_out(f));
+	}
+	
+	kfree(f);
+}
+
+
+
+
+/* compare timestamp between events */
+/* return 1 if a >= b; 0 */
+static inline int compare_timestamp(snd_seq_event_t * a, snd_seq_event_t * b)
+{
+	if ((a->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) {
+		/* compare ticks */
+		return (snd_seq_compare_tick_time(&a->time.tick, &b->time.tick));
+	} else {
+		/* compare real time */
+		return (snd_seq_compare_real_time(&a->time.time, &b->time.time));
+	}
+}
+
+/* compare timestamp between events */
+/* return negative if a < b;
+ *        zero     if a = b;
+ *        positive if a > b;
+ */
+static inline int compare_timestamp_rel(snd_seq_event_t *a, snd_seq_event_t *b)
+{
+	if ((a->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) {
+		/* compare ticks */
+		if (a->time.tick > b->time.tick)
+			return 1;
+		else if (a->time.tick == b->time.tick)
+			return 0;
+		else
+			return -1;
+	} else {
+		/* compare real time */
+		if (a->time.time.tv_sec > b->time.time.tv_sec)
+			return 1;
+		else if (a->time.time.tv_sec == b->time.time.tv_sec) {
+			if (a->time.time.tv_nsec > b->time.time.tv_nsec)
+				return 1;
+			else if (a->time.time.tv_nsec == b->time.time.tv_nsec)
+				return 0;
+			else
+				return -1;
+		} else
+			return -1;
+	}
+}
+
+/* enqueue cell to prioq */
+int snd_seq_prioq_cell_in(prioq_t * f, snd_seq_event_cell_t * cell)
+{
+	snd_seq_event_cell_t *cur, *prev;
+	unsigned long flags;
+	int count;
+	int prior;
+
+	snd_assert(f, return -EINVAL);
+	snd_assert(cell, return -EINVAL);
+	
+	/* check flags */
+	prior = (cell->event.flags & SNDRV_SEQ_PRIORITY_MASK);
+
+	spin_lock_irqsave(&f->lock, flags);
+
+	/* check if this element needs to inserted at the end (ie. ordered 
+	   data is inserted) This will be very likeley if a sequencer 
+	   application or midi file player is feeding us (sequential) data */
+	if (f->tail && !prior) {
+		if (compare_timestamp(&cell->event, &f->tail->event)) {
+			/* add new cell to tail of the fifo */
+			f->tail->next = cell;
+			f->tail = cell;
+			cell->next = NULL;
+			f->cells++;
+			spin_unlock_irqrestore(&f->lock, flags);
+			return 0;
+		}
+	}
+	/* traverse list of elements to find the place where the new cell is
+	   to be inserted... Note that this is a order n process ! */
+
+	prev = NULL;		/* previous cell */
+	cur = f->head;		/* cursor */
+
+	count = 10000; /* FIXME: enough big, isn't it? */
+	while (cur != NULL) {
+		/* compare timestamps */
+		int rel = compare_timestamp_rel(&cell->event, &cur->event);
+		if (rel < 0)
+			/* new cell has earlier schedule time, */
+			break;
+		else if (rel == 0 && prior)
+			/* equal schedule time and prior to others */
+			break;
+		/* new cell has equal or larger schedule time, */
+		/* move cursor to next cell */
+		prev = cur;
+		cur = cur->next;
+		if (! --count) {
+			spin_unlock_irqrestore(&f->lock, flags);
+			snd_printk(KERN_ERR "cannot find a pointer.. infinite loop?\n");
+			return -EINVAL;
+		}
+	}
+
+	/* insert it before cursor */
+	if (prev != NULL)
+		prev->next = cell;
+	cell->next = cur;
+
+	if (f->head == cur) /* this is the first cell, set head to it */
+		f->head = cell;
+	if (cur == NULL) /* reached end of the list */
+		f->tail = cell;
+	f->cells++;
+	spin_unlock_irqrestore(&f->lock, flags);
+	return 0;
+}
+
+/* dequeue cell from prioq */
+snd_seq_event_cell_t *snd_seq_prioq_cell_out(prioq_t * f)
+{
+	snd_seq_event_cell_t *cell;
+	unsigned long flags;
+
+	if (f == NULL) {
+		snd_printd("oops: snd_seq_prioq_cell_in() called with NULL prioq\n");
+		return NULL;
+	}
+	spin_lock_irqsave(&f->lock, flags);
+
+	cell = f->head;
+	if (cell) {
+		f->head = cell->next;
+
+		/* reset tail if this was the last element */
+		if (f->tail == cell)
+			f->tail = NULL;
+
+		cell->next = NULL;
+		f->cells--;
+	}
+
+	spin_unlock_irqrestore(&f->lock, flags);
+	return cell;
+}
+
+/* return number of events available in prioq */
+int snd_seq_prioq_avail(prioq_t * f)
+{
+	if (f == NULL) {
+		snd_printd("oops: snd_seq_prioq_cell_in() called with NULL prioq\n");
+		return 0;
+	}
+	return f->cells;
+}
+
+
+/* peek at cell at the head of the prioq */
+snd_seq_event_cell_t *snd_seq_prioq_cell_peek(prioq_t * f)
+{
+	if (f == NULL) {
+		snd_printd("oops: snd_seq_prioq_cell_in() called with NULL prioq\n");
+		return NULL;
+	}
+	return f->head;
+}
+
+
+static inline int prioq_match(snd_seq_event_cell_t *cell, int client, int timestamp)
+{
+	if (cell->event.source.client == client ||
+	    cell->event.dest.client == client)
+		return 1;
+	if (!timestamp)
+		return 0;
+	switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) {
+	case SNDRV_SEQ_TIME_STAMP_TICK:
+		if (cell->event.time.tick)
+			return 1;
+		break;
+	case SNDRV_SEQ_TIME_STAMP_REAL:
+		if (cell->event.time.time.tv_sec ||
+		    cell->event.time.time.tv_nsec)
+			return 1;
+		break;
+	}
+	return 0;
+}
+
+/* remove cells for left client */
+void snd_seq_prioq_leave(prioq_t * f, int client, int timestamp)
+{
+	register snd_seq_event_cell_t *cell, *next;
+	unsigned long flags;
+	snd_seq_event_cell_t *prev = NULL;
+	snd_seq_event_cell_t *freefirst = NULL, *freeprev = NULL, *freenext;
+
+	/* collect all removed cells */
+	spin_lock_irqsave(&f->lock, flags);
+	cell = f->head;
+	while (cell) {
+		next = cell->next;
+		if (prioq_match(cell, client, timestamp)) {
+			/* remove cell from prioq */
+			if (cell == f->head) {
+				f->head = cell->next;
+			} else {
+				prev->next = cell->next;
+			}
+			if (cell == f->tail)
+				f->tail = cell->next;
+			f->cells--;
+			/* add cell to free list */
+			cell->next = NULL;
+			if (freefirst == NULL) {
+				freefirst = cell;
+			} else {
+				freeprev->next = cell;
+			}
+			freeprev = cell;
+		} else {
+#if 0
+			printk("type = %i, source = %i, dest = %i, client = %i\n",
+				cell->event.type,
+				cell->event.source.client,
+				cell->event.dest.client,
+				client);
+#endif
+			prev = cell;
+		}
+		cell = next;		
+	}
+	spin_unlock_irqrestore(&f->lock, flags);	
+
+	/* remove selected cells */
+	while (freefirst) {
+		freenext = freefirst->next;
+		snd_seq_cell_free(freefirst);
+		freefirst = freenext;
+	}
+}
+
+static int prioq_remove_match(snd_seq_remove_events_t *info,
+	snd_seq_event_t *ev)
+{
+	int res;
+
+	if (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) {
+		if (ev->dest.client != info->dest.client ||
+				ev->dest.port != info->dest.port)
+			return 0;
+	}
+	if (info->remove_mode & SNDRV_SEQ_REMOVE_DEST_CHANNEL) {
+		if (! snd_seq_ev_is_channel_type(ev))
+			return 0;
+		/* data.note.channel and data.control.channel are identical */
+		if (ev->data.note.channel != info->channel)
+			return 0;
+	}
+	if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_AFTER) {
+		if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_TICK)
+			res = snd_seq_compare_tick_time(&ev->time.tick, &info->time.tick);
+		else
+			res = snd_seq_compare_real_time(&ev->time.time, &info->time.time);
+		if (!res)
+			return 0;
+	}
+	if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_BEFORE) {
+		if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_TICK)
+			res = snd_seq_compare_tick_time(&ev->time.tick, &info->time.tick);
+		else
+			res = snd_seq_compare_real_time(&ev->time.time, &info->time.time);
+		if (res)
+			return 0;
+	}
+	if (info->remove_mode & SNDRV_SEQ_REMOVE_EVENT_TYPE) {
+		if (ev->type != info->type)
+			return 0;
+	}
+	if (info->remove_mode & SNDRV_SEQ_REMOVE_IGNORE_OFF) {
+		/* Do not remove off events */
+		switch (ev->type) {
+		case SNDRV_SEQ_EVENT_NOTEOFF:
+		/* case SNDRV_SEQ_EVENT_SAMPLE_STOP: */
+			return 0;
+		default:
+			break;
+		}
+	}
+	if (info->remove_mode & SNDRV_SEQ_REMOVE_TAG_MATCH) {
+		if (info->tag != ev->tag)
+			return 0;
+	}
+
+	return 1;
+}
+
+/* remove cells matching remove criteria */
+void snd_seq_prioq_remove_events(prioq_t * f, int client,
+	snd_seq_remove_events_t *info)
+{
+	register snd_seq_event_cell_t *cell, *next;
+	unsigned long flags;
+	snd_seq_event_cell_t *prev = NULL;
+	snd_seq_event_cell_t *freefirst = NULL, *freeprev = NULL, *freenext;
+
+	/* collect all removed cells */
+	spin_lock_irqsave(&f->lock, flags);
+	cell = f->head;
+
+	while (cell) {
+		next = cell->next;
+		if (cell->event.source.client == client &&
+			prioq_remove_match(info, &cell->event)) {
+
+			/* remove cell from prioq */
+			if (cell == f->head) {
+				f->head = cell->next;
+			} else {
+				prev->next = cell->next;
+			}
+
+			if (cell == f->tail)
+				f->tail = cell->next;
+			f->cells--;
+
+			/* add cell to free list */
+			cell->next = NULL;
+			if (freefirst == NULL) {
+				freefirst = cell;
+			} else {
+				freeprev->next = cell;
+			}
+
+			freeprev = cell;
+		} else {
+			prev = cell;
+		}
+		cell = next;		
+	}
+	spin_unlock_irqrestore(&f->lock, flags);	
+
+	/* remove selected cells */
+	while (freefirst) {
+		freenext = freefirst->next;
+		snd_seq_cell_free(freefirst);
+		freefirst = freenext;
+	}
+}
+
+
diff --git a/sound/core/seq/seq_prioq.h b/sound/core/seq/seq_prioq.h
new file mode 100644
index 000000000000..f12af79308b8
--- /dev/null
+++ b/sound/core/seq/seq_prioq.h
@@ -0,0 +1,62 @@
+/*
+ *   ALSA sequencer Priority Queue
+ *   Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ *   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 __SND_SEQ_PRIOQ_H
+#define __SND_SEQ_PRIOQ_H
+
+#include "seq_memory.h"
+
+
+/* === PRIOQ === */
+
+typedef struct {
+	snd_seq_event_cell_t* head;      /* pointer to head of prioq */
+	snd_seq_event_cell_t* tail;      /* pointer to tail of prioq */
+	int cells;
+	spinlock_t lock;
+} prioq_t;
+
+
+/* create new prioq (constructor) */
+extern prioq_t *snd_seq_prioq_new(void);
+
+/* delete prioq (destructor) */
+extern void snd_seq_prioq_delete(prioq_t **fifo);
+
+/* enqueue cell to prioq */
+extern int snd_seq_prioq_cell_in(prioq_t *f, snd_seq_event_cell_t *cell);
+
+/* dequeue cell from prioq */ 
+extern snd_seq_event_cell_t *snd_seq_prioq_cell_out(prioq_t *f);
+
+/* return number of events available in prioq */
+extern int snd_seq_prioq_avail(prioq_t *f);
+
+/* peek at cell at the head of the prioq */
+extern snd_seq_event_cell_t *snd_seq_prioq_cell_peek(prioq_t *f);
+
+/* client left queue */
+extern void snd_seq_prioq_leave(prioq_t *f, int client, int timestamp);        
+
+/* Remove events */
+void snd_seq_prioq_remove_events(prioq_t * f, int client,
+	snd_seq_remove_events_t *info);
+
+#endif
diff --git a/sound/core/seq/seq_queue.c b/sound/core/seq/seq_queue.c
new file mode 100644
index 000000000000..3afc7cc0c9a7
--- /dev/null
+++ b/sound/core/seq/seq_queue.c
@@ -0,0 +1,783 @@
+/*
+ *   ALSA sequencer Timing queue handling
+ *   Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *   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
+ *
+ * MAJOR CHANGES
+ *   Nov. 13, 1999	Takashi Iwai <iwai@ww.uni-erlangen.de>
+ *     - Queues are allocated dynamically via ioctl.
+ *     - When owner client is deleted, all owned queues are deleted, too.
+ *     - Owner of unlocked queue is kept unmodified even if it is
+ *	 manipulated by other clients.
+ *     - Owner field in SET_QUEUE_OWNER ioctl must be identical with the
+ *       caller client.  i.e. Changing owner to a third client is not
+ *       allowed.
+ *
+ *  Aug. 30, 2000	Takashi Iwai
+ *     - Queues are managed in static array again, but with better way.
+ *       The API itself is identical.
+ *     - The queue is locked when queue_t pinter is returned via
+ *       queueptr().  This pointer *MUST* be released afterward by
+ *       queuefree(ptr).
+ *     - Addition of experimental sync support.
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+
+#include "seq_memory.h"
+#include "seq_queue.h"
+#include "seq_clientmgr.h"
+#include "seq_fifo.h"
+#include "seq_timer.h"
+#include "seq_info.h"
+
+/* list of allocated queues */
+static queue_t *queue_list[SNDRV_SEQ_MAX_QUEUES];
+static DEFINE_SPINLOCK(queue_list_lock);
+/* number of queues allocated */
+static int num_queues;
+
+int snd_seq_queue_get_cur_queues(void)
+{
+	return num_queues;
+}
+
+/*----------------------------------------------------------------*/
+
+/* assign queue id and insert to list */
+static int queue_list_add(queue_t *q)
+{
+	int i;
+	unsigned long flags;
+
+	spin_lock_irqsave(&queue_list_lock, flags);
+	for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
+		if (! queue_list[i]) {
+			queue_list[i] = q;
+			q->queue = i;
+			num_queues++;
+			spin_unlock_irqrestore(&queue_list_lock, flags);
+			return i;
+		}
+	}
+	spin_unlock_irqrestore(&queue_list_lock, flags);
+	return -1;
+}
+
+static queue_t *queue_list_remove(int id, int client)
+{
+	queue_t *q;
+	unsigned long flags;
+
+	spin_lock_irqsave(&queue_list_lock, flags);
+	q = queue_list[id];
+	if (q) {
+		spin_lock(&q->owner_lock);
+		if (q->owner == client) {
+			/* found */
+			q->klocked = 1;
+			spin_unlock(&q->owner_lock);
+			queue_list[id] = NULL;
+			num_queues--;
+			spin_unlock_irqrestore(&queue_list_lock, flags);
+			return q;
+		}
+		spin_unlock(&q->owner_lock);
+	}
+	spin_unlock_irqrestore(&queue_list_lock, flags);
+	return NULL;
+}
+
+/*----------------------------------------------------------------*/
+
+/* create new queue (constructor) */
+static queue_t *queue_new(int owner, int locked)
+{
+	queue_t *q;
+
+	q = kcalloc(1, sizeof(*q), GFP_KERNEL);
+	if (q == NULL) {
+		snd_printd("malloc failed for snd_seq_queue_new()\n");
+		return NULL;
+	}
+
+	spin_lock_init(&q->owner_lock);
+	spin_lock_init(&q->check_lock);
+	init_MUTEX(&q->timer_mutex);
+	snd_use_lock_init(&q->use_lock);
+	q->queue = -1;
+
+	q->tickq = snd_seq_prioq_new();
+	q->timeq = snd_seq_prioq_new();
+	q->timer = snd_seq_timer_new();
+	if (q->tickq == NULL || q->timeq == NULL || q->timer == NULL) {
+		snd_seq_prioq_delete(&q->tickq);
+		snd_seq_prioq_delete(&q->timeq);
+		snd_seq_timer_delete(&q->timer);
+		kfree(q);
+		return NULL;
+	}
+
+	q->owner = owner;
+	q->locked = locked;
+	q->klocked = 0;
+
+	return q;
+}
+
+/* delete queue (destructor) */
+static void queue_delete(queue_t *q)
+{
+	/* stop and release the timer */
+	snd_seq_timer_stop(q->timer);
+	snd_seq_timer_close(q);
+	/* wait until access free */
+	snd_use_lock_sync(&q->use_lock);
+	/* release resources... */
+	snd_seq_prioq_delete(&q->tickq);
+	snd_seq_prioq_delete(&q->timeq);
+	snd_seq_timer_delete(&q->timer);
+
+	kfree(q);
+}
+
+
+/*----------------------------------------------------------------*/
+
+/* setup queues */
+int __init snd_seq_queues_init(void)
+{
+	/*
+	memset(queue_list, 0, sizeof(queue_list));
+	num_queues = 0;
+	*/
+	return 0;
+}
+
+/* delete all existing queues */
+void __exit snd_seq_queues_delete(void)
+{
+	int i;
+
+	/* clear list */
+	for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
+		if (queue_list[i])
+			queue_delete(queue_list[i]);
+	}
+}
+
+/* allocate a new queue -
+ * return queue index value or negative value for error
+ */
+int snd_seq_queue_alloc(int client, int locked, unsigned int info_flags)
+{
+	queue_t *q;
+
+	q = queue_new(client, locked);
+	if (q == NULL)
+		return -ENOMEM;
+	q->info_flags = info_flags;
+	if (queue_list_add(q) < 0) {
+		queue_delete(q);
+		return -ENOMEM;
+	}
+	snd_seq_queue_use(q->queue, client, 1); /* use this queue */
+	return q->queue;
+}
+
+/* delete a queue - queue must be owned by the client */
+int snd_seq_queue_delete(int client, int queueid)
+{
+	queue_t *q;
+
+	if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES)
+		return -EINVAL;
+	q = queue_list_remove(queueid, client);
+	if (q == NULL)
+		return -EINVAL;
+	queue_delete(q);
+
+	return 0;
+}
+
+
+/* return pointer to queue structure for specified id */
+queue_t *queueptr(int queueid)
+{
+	queue_t *q;
+	unsigned long flags;
+
+	if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES)
+		return NULL;
+	spin_lock_irqsave(&queue_list_lock, flags);
+	q = queue_list[queueid];
+	if (q)
+		snd_use_lock_use(&q->use_lock);
+	spin_unlock_irqrestore(&queue_list_lock, flags);
+	return q;
+}
+
+/* return the (first) queue matching with the specified name */
+queue_t *snd_seq_queue_find_name(char *name)
+{
+	int i;
+	queue_t *q;
+
+	for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
+		if ((q = queueptr(i)) != NULL) {
+			if (strncmp(q->name, name, sizeof(q->name)) == 0)
+				return q;
+			queuefree(q);
+		}
+	}
+	return NULL;
+}
+
+
+/* -------------------------------------------------------- */
+
+void snd_seq_check_queue(queue_t *q, int atomic, int hop)
+{
+	unsigned long flags;
+	snd_seq_event_cell_t *cell;
+
+	if (q == NULL)
+		return;
+
+	/* make this function non-reentrant */
+	spin_lock_irqsave(&q->check_lock, flags);
+	if (q->check_blocked) {
+		q->check_again = 1;
+		spin_unlock_irqrestore(&q->check_lock, flags);
+		return;		/* other thread is already checking queues */
+	}
+	q->check_blocked = 1;
+	spin_unlock_irqrestore(&q->check_lock, flags);
+
+      __again:
+	/* Process tick queue... */
+	while ((cell = snd_seq_prioq_cell_peek(q->tickq)) != NULL) {
+		if (snd_seq_compare_tick_time(&q->timer->tick.cur_tick, &cell->event.time.tick)) {
+			cell = snd_seq_prioq_cell_out(q->tickq);
+			if (cell)
+				snd_seq_dispatch_event(cell, atomic, hop);
+		} else {
+			/* event remains in the queue */
+			break;
+		}
+	}
+
+
+	/* Process time queue... */
+	while ((cell = snd_seq_prioq_cell_peek(q->timeq)) != NULL) {
+		if (snd_seq_compare_real_time(&q->timer->cur_time, &cell->event.time.time)) {
+			cell = snd_seq_prioq_cell_out(q->timeq);
+			if (cell)
+				snd_seq_dispatch_event(cell, atomic, hop);
+		} else {
+			/* event remains in the queue */
+			break;
+		}
+	}
+
+	/* free lock */
+	spin_lock_irqsave(&q->check_lock, flags);
+	if (q->check_again) {
+		q->check_again = 0;
+		spin_unlock_irqrestore(&q->check_lock, flags);
+		goto __again;
+	}
+	q->check_blocked = 0;
+	spin_unlock_irqrestore(&q->check_lock, flags);
+}
+
+
+/* enqueue a event to singe queue */
+int snd_seq_enqueue_event(snd_seq_event_cell_t *cell, int atomic, int hop)
+{
+	int dest, err;
+	queue_t *q;
+
+	snd_assert(cell != NULL, return -EINVAL);
+	dest = cell->event.queue;	/* destination queue */
+	q = queueptr(dest);
+	if (q == NULL)
+		return -EINVAL;
+	/* handle relative time stamps, convert them into absolute */
+	if ((cell->event.flags & SNDRV_SEQ_TIME_MODE_MASK) == SNDRV_SEQ_TIME_MODE_REL) {
+		switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) {
+		case SNDRV_SEQ_TIME_STAMP_TICK:
+			cell->event.time.tick += q->timer->tick.cur_tick;
+			break;
+
+		case SNDRV_SEQ_TIME_STAMP_REAL:
+			snd_seq_inc_real_time(&cell->event.time.time, &q->timer->cur_time);
+			break;
+		}
+		cell->event.flags &= ~SNDRV_SEQ_TIME_MODE_MASK;
+		cell->event.flags |= SNDRV_SEQ_TIME_MODE_ABS;
+	}
+	/* enqueue event in the real-time or midi queue */
+	switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) {
+	case SNDRV_SEQ_TIME_STAMP_TICK:
+		err = snd_seq_prioq_cell_in(q->tickq, cell);
+		break;
+
+	case SNDRV_SEQ_TIME_STAMP_REAL:
+	default:
+		err = snd_seq_prioq_cell_in(q->timeq, cell);
+		break;
+	}
+
+	if (err < 0) {
+		queuefree(q); /* unlock */
+		return err;
+	}
+
+	/* trigger dispatching */
+	snd_seq_check_queue(q, atomic, hop);
+
+	queuefree(q); /* unlock */
+
+	return 0;
+}
+
+
+/*----------------------------------------------------------------*/
+
+static inline int check_access(queue_t *q, int client)
+{
+	return (q->owner == client) || (!q->locked && !q->klocked);
+}
+
+/* check if the client has permission to modify queue parameters.
+ * if it does, lock the queue
+ */
+static int queue_access_lock(queue_t *q, int client)
+{
+	unsigned long flags;
+	int access_ok;
+	
+	spin_lock_irqsave(&q->owner_lock, flags);
+	access_ok = check_access(q, client);
+	if (access_ok)
+		q->klocked = 1;
+	spin_unlock_irqrestore(&q->owner_lock, flags);
+	return access_ok;
+}
+
+/* unlock the queue */
+static inline void queue_access_unlock(queue_t *q)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&q->owner_lock, flags);
+	q->klocked = 0;
+	spin_unlock_irqrestore(&q->owner_lock, flags);
+}
+
+/* exported - only checking permission */
+int snd_seq_queue_check_access(int queueid, int client)
+{
+	queue_t *q = queueptr(queueid);
+	int access_ok;
+	unsigned long flags;
+
+	if (! q)
+		return 0;
+	spin_lock_irqsave(&q->owner_lock, flags);
+	access_ok = check_access(q, client);
+	spin_unlock_irqrestore(&q->owner_lock, flags);
+	queuefree(q);
+	return access_ok;
+}
+
+/*----------------------------------------------------------------*/
+
+/*
+ * change queue's owner and permission
+ */
+int snd_seq_queue_set_owner(int queueid, int client, int locked)
+{
+	queue_t *q = queueptr(queueid);
+
+	if (q == NULL)
+		return -EINVAL;
+
+	if (! queue_access_lock(q, client)) {
+		queuefree(q);
+		return -EPERM;
+	}
+
+	q->locked = locked ? 1 : 0;
+	q->owner = client;
+	queue_access_unlock(q);
+	queuefree(q);
+
+	return 0;
+}
+
+
+/*----------------------------------------------------------------*/
+
+/* open timer -
+ * q->use mutex should be down before calling this function to avoid
+ * confliction with snd_seq_queue_use()
+ */
+int snd_seq_queue_timer_open(int queueid)
+{
+	int result = 0;
+	queue_t *queue;
+	seq_timer_t *tmr;
+
+	queue = queueptr(queueid);
+	if (queue == NULL)
+		return -EINVAL;
+	tmr = queue->timer;
+	if ((result = snd_seq_timer_open(queue)) < 0) {
+		snd_seq_timer_defaults(tmr);
+		result = snd_seq_timer_open(queue);
+	}
+	queuefree(queue);
+	return result;
+}
+
+/* close timer -
+ * q->use mutex should be down before calling this function
+ */
+int snd_seq_queue_timer_close(int queueid)
+{
+	queue_t *queue;
+	seq_timer_t *tmr;
+	int result = 0;
+
+	queue = queueptr(queueid);
+	if (queue == NULL)
+		return -EINVAL;
+	tmr = queue->timer;
+	snd_seq_timer_close(queue);
+	queuefree(queue);
+	return result;
+}
+
+/* change queue tempo and ppq */
+int snd_seq_queue_timer_set_tempo(int queueid, int client, snd_seq_queue_tempo_t *info)
+{
+	queue_t *q = queueptr(queueid);
+	int result;
+
+	if (q == NULL)
+		return -EINVAL;
+	if (! queue_access_lock(q, client)) {
+		queuefree(q);
+		return -EPERM;
+	}
+
+	result = snd_seq_timer_set_tempo(q->timer, info->tempo);
+	if (result >= 0)
+		result = snd_seq_timer_set_ppq(q->timer, info->ppq);
+	if (result >= 0 && info->skew_base > 0)
+		result = snd_seq_timer_set_skew(q->timer, info->skew_value, info->skew_base);
+	queue_access_unlock(q);
+	queuefree(q);
+	return result;
+}
+
+
+/* use or unuse this queue -
+ * if it is the first client, starts the timer.
+ * if it is not longer used by any clients, stop the timer.
+ */
+int snd_seq_queue_use(int queueid, int client, int use)
+{
+	queue_t *queue;
+
+	queue = queueptr(queueid);
+	if (queue == NULL)
+		return -EINVAL;
+	down(&queue->timer_mutex);
+	if (use) {
+		if (!test_and_set_bit(client, queue->clients_bitmap))
+			queue->clients++;
+	} else {
+		if (test_and_clear_bit(client, queue->clients_bitmap))
+			queue->clients--;
+	}
+	if (queue->clients) {
+		if (use && queue->clients == 1)
+			snd_seq_timer_defaults(queue->timer);
+		snd_seq_timer_open(queue);
+	} else {
+		snd_seq_timer_close(queue);
+	}
+	up(&queue->timer_mutex);
+	queuefree(queue);
+	return 0;
+}
+
+/*
+ * check if queue is used by the client
+ * return negative value if the queue is invalid.
+ * return 0 if not used, 1 if used.
+ */
+int snd_seq_queue_is_used(int queueid, int client)
+{
+	queue_t *q;
+	int result;
+
+	q = queueptr(queueid);
+	if (q == NULL)
+		return -EINVAL; /* invalid queue */
+	result = test_bit(client, q->clients_bitmap) ? 1 : 0;
+	queuefree(q);
+	return result;
+}
+
+
+/*----------------------------------------------------------------*/
+
+/* notification that client has left the system -
+ * stop the timer on all queues owned by this client
+ */
+void snd_seq_queue_client_termination(int client)
+{
+	unsigned long flags;
+	int i;
+	queue_t *q;
+
+	for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
+		if ((q = queueptr(i)) == NULL)
+			continue;
+		spin_lock_irqsave(&q->owner_lock, flags);
+		if (q->owner == client)
+			q->klocked = 1;
+		spin_unlock_irqrestore(&q->owner_lock, flags);
+		if (q->owner == client) {
+			if (q->timer->running)
+				snd_seq_timer_stop(q->timer);
+			snd_seq_timer_reset(q->timer);
+		}
+		queuefree(q);
+	}
+}
+
+/* final stage notification -
+ * remove cells for no longer exist client (for non-owned queue)
+ * or delete this queue (for owned queue)
+ */
+void snd_seq_queue_client_leave(int client)
+{
+	int i;
+	queue_t *q;
+
+	/* delete own queues from queue list */
+	for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
+		if ((q = queue_list_remove(i, client)) != NULL)
+			queue_delete(q);
+	}
+
+	/* remove cells from existing queues -
+	 * they are not owned by this client
+	 */
+	for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
+		if ((q = queueptr(i)) == NULL)
+			continue;
+		if (test_bit(client, q->clients_bitmap)) {
+			snd_seq_prioq_leave(q->tickq, client, 0);
+			snd_seq_prioq_leave(q->timeq, client, 0);
+			snd_seq_queue_use(q->queue, client, 0);
+		}
+		queuefree(q);
+	}
+}
+
+
+
+/*----------------------------------------------------------------*/
+
+/* remove cells from all queues */
+void snd_seq_queue_client_leave_cells(int client)
+{
+	int i;
+	queue_t *q;
+
+	for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
+		if ((q = queueptr(i)) == NULL)
+			continue;
+		snd_seq_prioq_leave(q->tickq, client, 0);
+		snd_seq_prioq_leave(q->timeq, client, 0);
+		queuefree(q);
+	}
+}
+
+/* remove cells based on flush criteria */
+void snd_seq_queue_remove_cells(int client, snd_seq_remove_events_t *info)
+{
+	int i;
+	queue_t *q;
+
+	for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
+		if ((q = queueptr(i)) == NULL)
+			continue;
+		if (test_bit(client, q->clients_bitmap) &&
+		    (! (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) ||
+		     q->queue == info->queue)) {
+			snd_seq_prioq_remove_events(q->tickq, client, info);
+			snd_seq_prioq_remove_events(q->timeq, client, info);
+		}
+		queuefree(q);
+	}
+}
+
+/*----------------------------------------------------------------*/
+
+/*
+ * send events to all subscribed ports
+ */
+static void queue_broadcast_event(queue_t *q, snd_seq_event_t *ev, int atomic, int hop)
+{
+	snd_seq_event_t sev;
+
+	sev = *ev;
+	
+	sev.flags = SNDRV_SEQ_TIME_STAMP_TICK|SNDRV_SEQ_TIME_MODE_ABS;
+	sev.time.tick = q->timer->tick.cur_tick;
+	sev.queue = q->queue;
+	sev.data.queue.queue = q->queue;
+
+	/* broadcast events from Timer port */
+	sev.source.client = SNDRV_SEQ_CLIENT_SYSTEM;
+	sev.source.port = SNDRV_SEQ_PORT_SYSTEM_TIMER;
+	sev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
+	snd_seq_kernel_client_dispatch(SNDRV_SEQ_CLIENT_SYSTEM, &sev, atomic, hop);
+}
+
+/*
+ * process a received queue-control event.
+ * this function is exported for seq_sync.c.
+ */
+void snd_seq_queue_process_event(queue_t *q, snd_seq_event_t *ev, int atomic, int hop)
+{
+	switch (ev->type) {
+	case SNDRV_SEQ_EVENT_START:
+		snd_seq_prioq_leave(q->tickq, ev->source.client, 1);
+		snd_seq_prioq_leave(q->timeq, ev->source.client, 1);
+		if (! snd_seq_timer_start(q->timer))
+			queue_broadcast_event(q, ev, atomic, hop);
+		break;
+
+	case SNDRV_SEQ_EVENT_CONTINUE:
+		if (! snd_seq_timer_continue(q->timer))
+			queue_broadcast_event(q, ev, atomic, hop);
+		break;
+
+	case SNDRV_SEQ_EVENT_STOP:
+		snd_seq_timer_stop(q->timer);
+		queue_broadcast_event(q, ev, atomic, hop);
+		break;
+
+	case SNDRV_SEQ_EVENT_TEMPO:
+		snd_seq_timer_set_tempo(q->timer, ev->data.queue.param.value);
+		queue_broadcast_event(q, ev, atomic, hop);
+		break;
+
+	case SNDRV_SEQ_EVENT_SETPOS_TICK:
+		if (snd_seq_timer_set_position_tick(q->timer, ev->data.queue.param.time.tick) == 0) {
+			queue_broadcast_event(q, ev, atomic, hop);
+		}
+		break;
+
+	case SNDRV_SEQ_EVENT_SETPOS_TIME:
+		if (snd_seq_timer_set_position_time(q->timer, ev->data.queue.param.time.time) == 0) {
+			queue_broadcast_event(q, ev, atomic, hop);
+		}
+		break;
+	case SNDRV_SEQ_EVENT_QUEUE_SKEW:
+		if (snd_seq_timer_set_skew(q->timer,
+					   ev->data.queue.param.skew.value,
+					   ev->data.queue.param.skew.base) == 0) {
+			queue_broadcast_event(q, ev, atomic, hop);
+		}
+		break;
+	}
+}
+
+
+/*
+ * Queue control via timer control port:
+ * this function is exported as a callback of timer port.
+ */
+int snd_seq_control_queue(snd_seq_event_t *ev, int atomic, int hop)
+{
+	queue_t *q;
+
+	snd_assert(ev != NULL, return -EINVAL);
+	q = queueptr(ev->data.queue.queue);
+
+	if (q == NULL)
+		return -EINVAL;
+
+	if (! queue_access_lock(q, ev->source.client)) {
+		queuefree(q);
+		return -EPERM;
+	}
+
+	snd_seq_queue_process_event(q, ev, atomic, hop);
+
+	queue_access_unlock(q);
+	queuefree(q);
+	return 0;
+}
+
+
+/*----------------------------------------------------------------*/
+
+/* exported to seq_info.c */
+void snd_seq_info_queues_read(snd_info_entry_t *entry, 
+			      snd_info_buffer_t * buffer)
+{
+	int i, bpm;
+	queue_t *q;
+	seq_timer_t *tmr;
+
+	for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
+		if ((q = queueptr(i)) == NULL)
+			continue;
+
+		tmr = q->timer;
+		if (tmr->tempo)
+			bpm = 60000000 / tmr->tempo;
+		else
+			bpm = 0;
+
+		snd_iprintf(buffer, "queue %d: [%s]\n", q->queue, q->name);
+		snd_iprintf(buffer, "owned by client    : %d\n", q->owner);
+		snd_iprintf(buffer, "lock status        : %s\n", q->locked ? "Locked" : "Free");
+		snd_iprintf(buffer, "queued time events : %d\n", snd_seq_prioq_avail(q->timeq));
+		snd_iprintf(buffer, "queued tick events : %d\n", snd_seq_prioq_avail(q->tickq));
+		snd_iprintf(buffer, "timer state        : %s\n", tmr->running ? "Running" : "Stopped");
+		snd_iprintf(buffer, "timer PPQ          : %d\n", tmr->ppq);
+		snd_iprintf(buffer, "current tempo      : %d\n", tmr->tempo);
+		snd_iprintf(buffer, "current BPM        : %d\n", bpm);
+		snd_iprintf(buffer, "current time       : %d.%09d s\n", tmr->cur_time.tv_sec, tmr->cur_time.tv_nsec);
+		snd_iprintf(buffer, "current tick       : %d\n", tmr->tick.cur_tick);
+		snd_iprintf(buffer, "\n");
+		queuefree(q);
+	}
+}
diff --git a/sound/core/seq/seq_queue.h b/sound/core/seq/seq_queue.h
new file mode 100644
index 000000000000..b1bf5519fb3b
--- /dev/null
+++ b/sound/core/seq/seq_queue.h
@@ -0,0 +1,140 @@
+/*
+ *   ALSA sequencer Queue handling
+ *   Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *   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 __SND_SEQ_QUEUE_H
+#define __SND_SEQ_QUEUE_H
+
+#include "seq_memory.h"
+#include "seq_prioq.h"
+#include "seq_timer.h"
+#include "seq_lock.h"
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/bitops.h>
+
+#define SEQ_QUEUE_NO_OWNER (-1)
+
+struct _snd_seq_queue {
+	int queue;		/* queue number */
+
+	char name[64];		/* name of this queue */
+
+	prioq_t	*tickq;		/* midi tick event queue */
+	prioq_t	*timeq;		/* real-time event queue */	
+	
+	seq_timer_t *timer;	/* time keeper for this queue */
+	int	owner;		/* client that 'owns' the timer */
+	unsigned int	locked:1,	/* timer is only accesibble by owner if set */
+		klocked:1,	/* kernel lock (after START) */	
+		check_again:1,
+		check_blocked:1;
+
+	unsigned int flags;		/* status flags */
+	unsigned int info_flags;	/* info for sync */
+
+	spinlock_t owner_lock;
+	spinlock_t check_lock;
+
+	/* clients which uses this queue (bitmap) */
+	DECLARE_BITMAP(clients_bitmap, SNDRV_SEQ_MAX_CLIENTS);
+	unsigned int clients;	/* users of this queue */
+	struct semaphore timer_mutex;
+
+	snd_use_lock_t use_lock;
+};
+
+
+/* get the number of current queues */
+int snd_seq_queue_get_cur_queues(void);
+
+/* init queues structure */
+int snd_seq_queues_init(void);
+
+/* delete queues */ 
+void snd_seq_queues_delete(void);
+
+
+/* create new queue (constructor) */
+int snd_seq_queue_alloc(int client, int locked, unsigned int flags);
+
+/* delete queue (destructor) */
+int snd_seq_queue_delete(int client, int queueid);
+
+/* notification that client has left the system */
+void snd_seq_queue_client_termination(int client);
+
+/* final stage */
+void snd_seq_queue_client_leave(int client);
+
+/* enqueue a event received from one the clients */
+int snd_seq_enqueue_event(snd_seq_event_cell_t *cell, int atomic, int hop);
+
+/* Remove events */
+void snd_seq_queue_client_leave_cells(int client);
+void snd_seq_queue_remove_cells(int client, snd_seq_remove_events_t *info);
+
+/* return pointer to queue structure for specified id */
+queue_t *queueptr(int queueid);
+/* unlock */
+#define queuefree(q) snd_use_lock_free(&(q)->use_lock)
+
+/* return the (first) queue matching with the specified name */
+queue_t *snd_seq_queue_find_name(char *name);
+
+/* check single queue and dispatch events */
+void snd_seq_check_queue(queue_t *q, int atomic, int hop);
+
+/* access to queue's parameters */
+int snd_seq_queue_check_access(int queueid, int client);
+int snd_seq_queue_timer_set_tempo(int queueid, int client, snd_seq_queue_tempo_t *info);
+int snd_seq_queue_set_owner(int queueid, int client, int locked);
+int snd_seq_queue_set_locked(int queueid, int client, int locked);
+int snd_seq_queue_timer_open(int queueid);
+int snd_seq_queue_timer_close(int queueid);
+int snd_seq_queue_use(int queueid, int client, int use);
+int snd_seq_queue_is_used(int queueid, int client);
+
+int snd_seq_control_queue(snd_seq_event_t *ev, int atomic, int hop);
+void snd_seq_queue_process_event(queue_t *q, snd_seq_event_t *ev, int atomic, int hop);
+
+/*
+ * 64bit division - for sync stuff..
+ */
+#if defined(i386) || defined(i486)
+
+#define udiv_qrnnd(q, r, n1, n0, d) \
+  __asm__ ("divl %4"		\
+	   : "=a" ((u32)(q)),	\
+	     "=d" ((u32)(r))	\
+	   : "0" ((u32)(n0)),	\
+	     "1" ((u32)(n1)),	\
+	     "rm" ((u32)(d)))
+
+#define u64_div(x,y,q) do {u32 __tmp; udiv_qrnnd(q, __tmp, (x)>>32, x, y);} while (0)
+#define u64_mod(x,y,r) do {u32 __tmp; udiv_qrnnd(__tmp, q, (x)>>32, x, y);} while (0)
+#define u64_divmod(x,y,q,r) udiv_qrnnd(q, r, (x)>>32, x, y)
+
+#else
+#define u64_div(x,y,q)	((q) = (u32)((u64)(x) / (u64)(y)))
+#define u64_mod(x,y,r)	((r) = (u32)((u64)(x) % (u64)(y)))
+#define u64_divmod(x,y,q,r) (u64_div(x,y,q), u64_mod(x,y,r))
+#endif
+
+
+#endif
diff --git a/sound/core/seq/seq_system.c b/sound/core/seq/seq_system.c
new file mode 100644
index 000000000000..e8f0a6683d50
--- /dev/null
+++ b/sound/core/seq/seq_system.c
@@ -0,0 +1,190 @@
+/*
+ *   ALSA sequencer System services Client
+ *   Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ *   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 <sound/core.h>
+#include "seq_system.h"
+#include "seq_timer.h"
+#include "seq_queue.h"
+
+/* internal client that provide system services, access to timer etc. */
+
+/*
+ * Port "Timer"
+ *      - send tempo /start/stop etc. events to this port to manipulate the 
+ *        queue's timer. The queue address is specified in
+ *	  data.queue.queue.
+ *      - this port supports subscription. The received timer events are 
+ *        broadcasted to all subscribed clients. The modified tempo
+ *	  value is stored on data.queue.value.
+ *	  The modifier client/port is not send.
+ *
+ * Port "Announce"
+ *      - does not receive message
+ *      - supports supscription. For each client or port attaching to or 
+ *        detaching from the system an announcement is send to the subscribed
+ *        clients.
+ *
+ * Idea: the subscription mechanism might also work handy for distributing 
+ * synchronisation and timing information. In this case we would ideally have
+ * a list of subscribers for each type of sync (time, tick), for each timing
+ * queue.
+ *
+ * NOTE: the queue to be started, stopped, etc. must be specified
+ *	 in data.queue.addr.queue field.  queue is used only for
+ *	 scheduling, and no longer referred as affected queue.
+ *	 They are used only for timer broadcast (see above).
+ *							-- iwai
+ */
+
+
+/* client id of our system client */
+static int sysclient = -1;
+
+/* port id numbers for this client */
+static int announce_port = -1;
+
+
+
+/* fill standard header data, source port & channel are filled in */
+static int setheader(snd_seq_event_t * ev, int client, int port)
+{
+	if (announce_port < 0)
+		return -ENODEV;
+
+	memset(ev, 0, sizeof(snd_seq_event_t));
+
+	ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
+	ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED;
+
+	ev->source.client = sysclient;
+	ev->source.port = announce_port;
+	ev->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
+
+	/* fill data */
+	/*ev->data.addr.queue = SNDRV_SEQ_ADDRESS_UNKNOWN;*/
+	ev->data.addr.client = client;
+	ev->data.addr.port = port;
+
+	return 0;
+}
+
+
+/* entry points for broadcasting system events */
+void snd_seq_system_broadcast(int client, int port, int type)
+{
+	snd_seq_event_t ev;
+	
+	if (setheader(&ev, client, port) < 0)
+		return;
+	ev.type = type;
+	snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0);
+}
+
+/* entry points for broadcasting system events */
+int snd_seq_system_notify(int client, int port, snd_seq_event_t *ev)
+{
+	ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED;
+	ev->source.client = sysclient;
+	ev->source.port = announce_port;
+	ev->dest.client = client;
+	ev->dest.port = port;
+	return snd_seq_kernel_client_dispatch(sysclient, ev, 0, 0);
+}
+
+/* call-back handler for timer events */
+static int event_input_timer(snd_seq_event_t * ev, int direct, void *private_data, int atomic, int hop)
+{
+	return snd_seq_control_queue(ev, atomic, hop);
+}
+
+/* register our internal client */
+int __init snd_seq_system_client_init(void)
+{
+
+	snd_seq_client_callback_t callbacks;
+	snd_seq_port_callback_t pcallbacks;
+	snd_seq_client_info_t *inf;
+	snd_seq_port_info_t *port;
+
+	inf = kcalloc(1, sizeof(*inf), GFP_KERNEL);
+	port = kcalloc(1, sizeof(*port), GFP_KERNEL);
+	if (! inf || ! port) {
+		kfree(inf);
+		kfree(port);
+		return -ENOMEM;
+	}
+
+	memset(&callbacks, 0, sizeof(callbacks));
+	memset(&pcallbacks, 0, sizeof(pcallbacks));
+	pcallbacks.owner = THIS_MODULE;
+	pcallbacks.event_input = event_input_timer;
+
+	/* register client */
+	callbacks.allow_input = callbacks.allow_output = 1;
+	sysclient = snd_seq_create_kernel_client(NULL, 0, &callbacks);
+
+	/* set our name */
+	inf->client = 0;
+	inf->type = KERNEL_CLIENT;
+	strcpy(inf->name, "System");
+	snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, inf);
+
+	/* register timer */
+	strcpy(port->name, "Timer");
+	port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* accept queue control */
+	port->capability |= SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ; /* for broadcast */
+	port->kernel = &pcallbacks;
+	port->type = 0;
+	port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
+	port->addr.client = sysclient;
+	port->addr.port = SNDRV_SEQ_PORT_SYSTEM_TIMER;
+	snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_CREATE_PORT, port);
+
+	/* register announcement port */
+	strcpy(port->name, "Announce");
+	port->capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ; /* for broadcast only */
+	port->kernel = NULL;
+	port->type = 0;
+	port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
+	port->addr.client = sysclient;
+	port->addr.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE;
+	snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_CREATE_PORT, port);
+	announce_port = port->addr.port;
+
+	kfree(inf);
+	kfree(port);
+	return 0;
+}
+
+
+/* unregister our internal client */
+void __exit snd_seq_system_client_done(void)
+{
+	int oldsysclient = sysclient;
+
+	if (oldsysclient >= 0) {
+		sysclient = -1;
+		announce_port = -1;
+		snd_seq_delete_kernel_client(oldsysclient);
+	}
+}
diff --git a/sound/core/seq/seq_system.h b/sound/core/seq/seq_system.h
new file mode 100644
index 000000000000..900007255bb4
--- /dev/null
+++ b/sound/core/seq/seq_system.h
@@ -0,0 +1,46 @@
+/*
+ *  ALSA sequencer System Client
+ *  Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ *   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 __SND_SEQ_SYSTEM_H
+#define __SND_SEQ_SYSTEM_H
+
+#include <sound/seq_kernel.h>
+
+
+/* entry points for broadcasting system events */
+void snd_seq_system_broadcast(int client, int port, int type);
+
+#define snd_seq_system_client_ev_client_start(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_START)
+#define snd_seq_system_client_ev_client_exit(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_EXIT)
+#define snd_seq_system_client_ev_client_change(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_CHANGE)
+#define snd_seq_system_client_ev_port_start(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_START)
+#define snd_seq_system_client_ev_port_exit(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_EXIT)
+#define snd_seq_system_client_ev_port_change(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_CHANGE)
+
+int snd_seq_system_notify(int client, int port, snd_seq_event_t *ev);
+
+/* register our internal client */
+int snd_seq_system_client_init(void);
+
+/* unregister our internal client */
+void snd_seq_system_client_done(void);
+
+
+#endif
diff --git a/sound/core/seq/seq_timer.c b/sound/core/seq/seq_timer.c
new file mode 100644
index 000000000000..753f1c0863cc
--- /dev/null
+++ b/sound/core/seq/seq_timer.c
@@ -0,0 +1,435 @@
+/*
+ *   ALSA sequencer Timer
+ *   Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *                              Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <linux/slab.h>
+#include "seq_timer.h"
+#include "seq_queue.h"
+#include "seq_info.h"
+
+extern int seq_default_timer_class;
+extern int seq_default_timer_sclass;
+extern int seq_default_timer_card;
+extern int seq_default_timer_device;
+extern int seq_default_timer_subdevice;
+extern int seq_default_timer_resolution;
+
+#define SKEW_BASE	0x10000	/* 16bit shift */
+
+void snd_seq_timer_set_tick_resolution(seq_timer_tick_t *tick, int tempo, int ppq, int nticks)
+{
+	if (tempo < 1000000)
+		tick->resolution = (tempo * 1000) / ppq;
+	else {
+		/* might overflow.. */
+		unsigned int s;
+		s = tempo % ppq;
+		s = (s * 1000) / ppq;
+		tick->resolution = (tempo / ppq) * 1000;
+		tick->resolution += s;
+	}
+	if (tick->resolution <= 0)
+		tick->resolution = 1;
+	tick->resolution *= nticks;
+	snd_seq_timer_update_tick(tick, 0);
+}
+
+/* create new timer (constructor) */
+seq_timer_t *snd_seq_timer_new(void)
+{
+	seq_timer_t *tmr;
+	
+	tmr = kcalloc(1, sizeof(*tmr), GFP_KERNEL);
+	if (tmr == NULL) {
+		snd_printd("malloc failed for snd_seq_timer_new() \n");
+		return NULL;
+	}
+	spin_lock_init(&tmr->lock);
+
+	/* reset setup to defaults */
+	snd_seq_timer_defaults(tmr);
+	
+	/* reset time */
+	snd_seq_timer_reset(tmr);
+	
+	return tmr;
+}
+
+/* delete timer (destructor) */
+void snd_seq_timer_delete(seq_timer_t **tmr)
+{
+	seq_timer_t *t = *tmr;
+	*tmr = NULL;
+
+	if (t == NULL) {
+		snd_printd("oops: snd_seq_timer_delete() called with NULL timer\n");
+		return;
+	}
+	t->running = 0;
+
+	/* reset time */
+	snd_seq_timer_stop(t);
+	snd_seq_timer_reset(t);
+
+	kfree(t);
+}
+
+void snd_seq_timer_defaults(seq_timer_t * tmr)
+{
+	/* setup defaults */
+	tmr->ppq = 96;		/* 96 PPQ */
+	tmr->tempo = 500000;	/* 120 BPM */
+	snd_seq_timer_set_tick_resolution(&tmr->tick, tmr->tempo, tmr->ppq, 1);
+	tmr->running = 0;
+
+	tmr->type = SNDRV_SEQ_TIMER_ALSA;
+	tmr->alsa_id.dev_class = seq_default_timer_class;
+	tmr->alsa_id.dev_sclass = seq_default_timer_sclass;
+	tmr->alsa_id.card = seq_default_timer_card;
+	tmr->alsa_id.device = seq_default_timer_device;
+	tmr->alsa_id.subdevice = seq_default_timer_subdevice;
+	tmr->preferred_resolution = seq_default_timer_resolution;
+
+	tmr->skew = tmr->skew_base = SKEW_BASE;
+}
+
+void snd_seq_timer_reset(seq_timer_t * tmr)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&tmr->lock, flags);
+
+	/* reset time & songposition */
+	tmr->cur_time.tv_sec = 0;
+	tmr->cur_time.tv_nsec = 0;
+
+	tmr->tick.cur_tick = 0;
+	tmr->tick.fraction = 0;
+
+	spin_unlock_irqrestore(&tmr->lock, flags);
+}
+
+
+/* called by timer interrupt routine. the period time since previous invocation is passed */
+static void snd_seq_timer_interrupt(snd_timer_instance_t *timeri,
+				    unsigned long resolution,
+				    unsigned long ticks)
+{
+	unsigned long flags;
+	queue_t *q = (queue_t *)timeri->callback_data;
+	seq_timer_t *tmr;
+
+	if (q == NULL)
+		return;
+	tmr = q->timer;
+	if (tmr == NULL)
+		return;
+	if (!tmr->running)
+		return;
+
+	resolution *= ticks;
+	if (tmr->skew != tmr->skew_base) {
+		/* FIXME: assuming skew_base = 0x10000 */
+		resolution = (resolution >> 16) * tmr->skew +
+			(((resolution & 0xffff) * tmr->skew) >> 16);
+	}
+
+	spin_lock_irqsave(&tmr->lock, flags);
+
+	/* update timer */
+	snd_seq_inc_time_nsec(&tmr->cur_time, resolution);
+
+	/* calculate current tick */
+	snd_seq_timer_update_tick(&tmr->tick, resolution);
+
+	/* register actual time of this timer update */
+	do_gettimeofday(&tmr->last_update);
+
+	spin_unlock_irqrestore(&tmr->lock, flags);
+
+	/* check queues and dispatch events */
+	snd_seq_check_queue(q, 1, 0);
+}
+
+/* set current tempo */
+int snd_seq_timer_set_tempo(seq_timer_t * tmr, int tempo)
+{
+	unsigned long flags;
+
+	snd_assert(tmr, return -EINVAL);
+	if (tempo <= 0)
+		return -EINVAL;
+	spin_lock_irqsave(&tmr->lock, flags);
+	if ((unsigned int)tempo != tmr->tempo) {
+		tmr->tempo = tempo;
+		snd_seq_timer_set_tick_resolution(&tmr->tick, tmr->tempo, tmr->ppq, 1);
+	}
+	spin_unlock_irqrestore(&tmr->lock, flags);
+	return 0;
+}
+
+/* set current ppq */
+int snd_seq_timer_set_ppq(seq_timer_t * tmr, int ppq)
+{
+	unsigned long flags;
+
+	snd_assert(tmr, return -EINVAL);
+	if (ppq <= 0)
+		return -EINVAL;
+	spin_lock_irqsave(&tmr->lock, flags);
+	if (tmr->running && (ppq != tmr->ppq)) {
+		/* refuse to change ppq on running timers */
+		/* because it will upset the song position (ticks) */
+		spin_unlock_irqrestore(&tmr->lock, flags);
+		snd_printd("seq: cannot change ppq of a running timer\n");
+		return -EBUSY;
+	}
+
+	tmr->ppq = ppq;
+	snd_seq_timer_set_tick_resolution(&tmr->tick, tmr->tempo, tmr->ppq, 1);
+	spin_unlock_irqrestore(&tmr->lock, flags);
+	return 0;
+}
+
+/* set current tick position */
+int snd_seq_timer_set_position_tick(seq_timer_t *tmr, snd_seq_tick_time_t position)
+{
+	unsigned long flags;
+
+	snd_assert(tmr, return -EINVAL);
+
+	spin_lock_irqsave(&tmr->lock, flags);
+	tmr->tick.cur_tick = position;
+	tmr->tick.fraction = 0;
+	spin_unlock_irqrestore(&tmr->lock, flags);
+	return 0;
+}
+
+/* set current real-time position */
+int snd_seq_timer_set_position_time(seq_timer_t *tmr, snd_seq_real_time_t position)
+{
+	unsigned long flags;
+
+	snd_assert(tmr, return -EINVAL);
+
+	snd_seq_sanity_real_time(&position);
+	spin_lock_irqsave(&tmr->lock, flags);
+	tmr->cur_time = position;
+	spin_unlock_irqrestore(&tmr->lock, flags);
+	return 0;
+}
+
+/* set timer skew */
+int snd_seq_timer_set_skew(seq_timer_t *tmr, unsigned int skew, unsigned int base)
+{
+	unsigned long flags;
+
+	snd_assert(tmr, return -EINVAL);
+
+	/* FIXME */
+	if (base != SKEW_BASE) {
+		snd_printd("invalid skew base 0x%x\n", base);
+		return -EINVAL;
+	}
+	spin_lock_irqsave(&tmr->lock, flags);
+	tmr->skew = skew;
+	spin_unlock_irqrestore(&tmr->lock, flags);
+	return 0;
+}
+
+int snd_seq_timer_open(queue_t *q)
+{
+	snd_timer_instance_t *t;
+	seq_timer_t *tmr;
+	char str[32];
+	int err;
+
+	tmr = q->timer;
+	snd_assert(tmr != NULL, return -EINVAL);
+	if (tmr->timeri)
+		return -EBUSY;
+	sprintf(str, "sequencer queue %i", q->queue);
+	if (tmr->type != SNDRV_SEQ_TIMER_ALSA)	/* standard ALSA timer */
+		return -EINVAL;
+	if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE)
+		tmr->alsa_id.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER;
+	err = snd_timer_open(&t, str, &tmr->alsa_id, q->queue);
+	if (err < 0 && tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE) {
+		if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_GLOBAL ||
+		    tmr->alsa_id.device != SNDRV_TIMER_GLOBAL_SYSTEM) {
+			snd_timer_id_t tid;
+			memset(&tid, 0, sizeof(tid));
+			tid.dev_class = SNDRV_TIMER_CLASS_GLOBAL;
+			tid.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER;
+			tid.card = -1;
+			tid.device = SNDRV_TIMER_GLOBAL_SYSTEM;
+			err = snd_timer_open(&t, str, &tid, q->queue);
+		}
+		if (err < 0) {
+			snd_printk(KERN_ERR "seq fatal error: cannot create timer (%i)\n", err);
+			return err;
+		}
+	}
+	t->callback = snd_seq_timer_interrupt;
+	t->callback_data = q;
+	t->flags |= SNDRV_TIMER_IFLG_AUTO;
+	tmr->timeri = t;
+	return 0;
+}
+
+int snd_seq_timer_close(queue_t *q)
+{
+	seq_timer_t *tmr;
+	
+	tmr = q->timer;
+	snd_assert(tmr != NULL, return -EINVAL);
+	if (tmr->timeri) {
+		snd_timer_stop(tmr->timeri);
+		snd_timer_close(tmr->timeri);
+		tmr->timeri = NULL;
+	}
+	return 0;
+}
+
+int snd_seq_timer_stop(seq_timer_t * tmr)
+{
+	if (! tmr->timeri)
+		return -EINVAL;
+	if (!tmr->running)
+		return 0;
+	tmr->running = 0;
+	snd_timer_pause(tmr->timeri);
+	return 0;
+}
+
+static int initialize_timer(seq_timer_t *tmr)
+{
+	snd_timer_t *t;
+	t = tmr->timeri->timer;
+	snd_assert(t, return -EINVAL);
+
+	tmr->ticks = 1;
+	if (tmr->preferred_resolution &&
+	    ! (t->hw.flags & SNDRV_TIMER_HW_SLAVE)) {
+		unsigned long r = t->hw.resolution;
+		if (! r && t->hw.c_resolution)
+			r = t->hw.c_resolution(t);
+		if (r) {
+			tmr->ticks = (unsigned int)(1000000000uL / (r * tmr->preferred_resolution));
+			if (! tmr->ticks)
+				tmr->ticks = 1;
+		}
+	}
+	tmr->initialized = 1;
+	return 0;
+}
+
+int snd_seq_timer_start(seq_timer_t * tmr)
+{
+	if (! tmr->timeri)
+		return -EINVAL;
+	if (tmr->running)
+		snd_seq_timer_stop(tmr);
+	snd_seq_timer_reset(tmr);
+	if (initialize_timer(tmr) < 0)
+		return -EINVAL;
+	snd_timer_start(tmr->timeri, tmr->ticks);
+	tmr->running = 1;
+	do_gettimeofday(&tmr->last_update);
+	return 0;
+}
+
+int snd_seq_timer_continue(seq_timer_t * tmr)
+{
+	if (! tmr->timeri)
+		return -EINVAL;
+	if (tmr->running)
+		return -EBUSY;
+	if (! tmr->initialized) {
+		snd_seq_timer_reset(tmr);
+		if (initialize_timer(tmr) < 0)
+			return -EINVAL;
+	}
+	snd_timer_start(tmr->timeri, tmr->ticks);
+	tmr->running = 1;
+	do_gettimeofday(&tmr->last_update);
+	return 0;
+}
+
+/* return current 'real' time. use timeofday() to get better granularity. */
+snd_seq_real_time_t snd_seq_timer_get_cur_time(seq_timer_t *tmr)
+{
+	snd_seq_real_time_t cur_time;
+
+	cur_time = tmr->cur_time;
+	if (tmr->running) { 
+		struct timeval tm;
+		int usec;
+		do_gettimeofday(&tm);
+		usec = (int)(tm.tv_usec - tmr->last_update.tv_usec);
+		if (usec < 0) {
+			cur_time.tv_nsec += (1000000 + usec) * 1000;
+			cur_time.tv_sec += tm.tv_sec - tmr->last_update.tv_sec - 1;
+		} else {
+			cur_time.tv_nsec += usec * 1000;
+			cur_time.tv_sec += tm.tv_sec - tmr->last_update.tv_sec;
+		}
+		snd_seq_sanity_real_time(&cur_time);
+	}
+                
+	return cur_time;	
+}
+
+/* TODO: use interpolation on tick queue (will only be useful for very
+ high PPQ values) */
+snd_seq_tick_time_t snd_seq_timer_get_cur_tick(seq_timer_t *tmr)
+{
+	return tmr->tick.cur_tick;
+}
+
+
+/* exported to seq_info.c */
+void snd_seq_info_timer_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+	int idx;
+	queue_t *q;
+	seq_timer_t *tmr;
+	snd_timer_instance_t *ti;
+	unsigned long resolution;
+	
+	for (idx = 0; idx < SNDRV_SEQ_MAX_QUEUES; idx++) {
+		q = queueptr(idx);
+		if (q == NULL)
+			continue;
+		if ((tmr = q->timer) == NULL ||
+		    (ti = tmr->timeri) == NULL) {
+			queuefree(q);
+			continue;
+		}
+		snd_iprintf(buffer, "Timer for queue %i : %s\n", q->queue, ti->timer->name);
+		resolution = snd_timer_resolution(ti) * tmr->ticks;
+		snd_iprintf(buffer, "  Period time : %lu.%09lu\n", resolution / 1000000000, resolution % 1000000000);
+		snd_iprintf(buffer, "  Skew : %u / %u\n", tmr->skew, tmr->skew_base);
+		queuefree(q);
+ 	}
+}
diff --git a/sound/core/seq/seq_timer.h b/sound/core/seq/seq_timer.h
new file mode 100644
index 000000000000..4c0872df8931
--- /dev/null
+++ b/sound/core/seq/seq_timer.h
@@ -0,0 +1,141 @@
+/*
+ *  ALSA sequencer Timer
+ *  Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ *   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 __SND_SEQ_TIMER_H
+#define __SND_SEQ_TIMER_H
+
+#include <sound/timer.h>
+#include <sound/seq_kernel.h>
+
+typedef struct {
+	snd_seq_tick_time_t	cur_tick;	/* current tick */
+	unsigned long		resolution;	/* time per tick in nsec */
+	unsigned long		fraction;	/* current time per tick in nsec */
+} seq_timer_tick_t;
+
+typedef struct {
+	/* ... tempo / offset / running state */
+
+	unsigned int		running:1,	/* running state of queue */	
+				initialized:1;	/* timer is initialized */
+
+	unsigned int		tempo;		/* current tempo, us/tick */
+	int			ppq;		/* time resolution, ticks/quarter */
+
+	snd_seq_real_time_t	cur_time;	/* current time */
+	seq_timer_tick_t	tick;		/* current tick */
+	int tick_updated;
+	
+	int			type;		/* timer type */
+	snd_timer_id_t		alsa_id;	/* ALSA's timer ID */
+	snd_timer_instance_t	*timeri;	/* timer instance */
+	unsigned int		ticks;
+	unsigned long		preferred_resolution; /* timer resolution, ticks/sec */
+
+	unsigned int skew;
+	unsigned int skew_base;
+
+	struct timeval 		last_update;	 /* time of last clock update, used for interpolation */
+
+	spinlock_t lock;
+} seq_timer_t;
+
+
+/* create new timer (constructor) */
+extern seq_timer_t *snd_seq_timer_new(void);
+
+/* delete timer (destructor) */
+extern void snd_seq_timer_delete(seq_timer_t **tmr);
+
+void snd_seq_timer_set_tick_resolution(seq_timer_tick_t *tick, int tempo, int ppq, int nticks);
+
+/* */
+static inline void snd_seq_timer_update_tick(seq_timer_tick_t *tick, unsigned long resolution)
+{
+	if (tick->resolution > 0) {
+		tick->fraction += resolution;
+		tick->cur_tick += (unsigned int)(tick->fraction / tick->resolution);
+		tick->fraction %= tick->resolution;
+	}
+}
+
+
+/* compare timestamp between events */
+/* return 1 if a >= b; otherwise return 0 */
+static inline int snd_seq_compare_tick_time(snd_seq_tick_time_t *a, snd_seq_tick_time_t *b)
+{
+	/* compare ticks */
+	return (*a >= *b);
+}
+
+static inline int snd_seq_compare_real_time(snd_seq_real_time_t *a, snd_seq_real_time_t *b)
+{
+	/* compare real time */
+	if (a->tv_sec > b->tv_sec)
+		return 1;
+	if ((a->tv_sec == b->tv_sec) && (a->tv_nsec >= b->tv_nsec))
+		return 1;
+	return 0;
+}
+
+
+static inline void snd_seq_sanity_real_time(snd_seq_real_time_t *tm)
+{
+	while (tm->tv_nsec >= 1000000000) {
+		/* roll-over */
+		tm->tv_nsec -= 1000000000;
+                tm->tv_sec++;
+        }
+}
+
+
+/* increment timestamp */
+static inline void snd_seq_inc_real_time(snd_seq_real_time_t *tm, snd_seq_real_time_t *inc)
+{
+	tm->tv_sec  += inc->tv_sec;
+	tm->tv_nsec += inc->tv_nsec;
+	snd_seq_sanity_real_time(tm);
+}
+
+static inline void snd_seq_inc_time_nsec(snd_seq_real_time_t *tm, unsigned long nsec)
+{
+	tm->tv_nsec  += nsec;
+	snd_seq_sanity_real_time(tm);
+}
+
+/* called by timer isr */
+int snd_seq_timer_open(queue_t *q);
+int snd_seq_timer_close(queue_t *q);
+int snd_seq_timer_midi_open(queue_t *q);
+int snd_seq_timer_midi_close(queue_t *q);
+void snd_seq_timer_defaults(seq_timer_t *tmr);
+void snd_seq_timer_reset(seq_timer_t *tmr);
+int snd_seq_timer_stop(seq_timer_t *tmr);
+int snd_seq_timer_start(seq_timer_t *tmr);
+int snd_seq_timer_continue(seq_timer_t *tmr);
+int snd_seq_timer_set_tempo(seq_timer_t *tmr, int tempo);
+int snd_seq_timer_set_ppq(seq_timer_t *tmr, int ppq);
+int snd_seq_timer_set_position_tick(seq_timer_t *tmr, snd_seq_tick_time_t position);
+int snd_seq_timer_set_position_time(seq_timer_t *tmr, snd_seq_real_time_t position);
+int snd_seq_timer_set_skew(seq_timer_t *tmr, unsigned int skew, unsigned int base);
+snd_seq_real_time_t snd_seq_timer_get_cur_time(seq_timer_t *tmr);
+snd_seq_tick_time_t snd_seq_timer_get_cur_tick(seq_timer_t *tmr);
+
+#endif
diff --git a/sound/core/seq/seq_virmidi.c b/sound/core/seq/seq_virmidi.c
new file mode 100644
index 000000000000..6b4e630ace54
--- /dev/null
+++ b/sound/core/seq/seq_virmidi.c
@@ -0,0 +1,551 @@
+/*
+ *  Virtual Raw MIDI client on Sequencer
+ *
+ *  Copyright (c) 2000 by Takashi Iwai <tiwai@suse.de>,
+ *                        Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*
+ * Virtual Raw MIDI client
+ *
+ * The virtual rawmidi client is a sequencer client which associate
+ * a rawmidi device file.  The created rawmidi device file can be
+ * accessed as a normal raw midi, but its MIDI source and destination
+ * are arbitrary.  For example, a user-client software synth connected
+ * to this port can be used as a normal midi device as well.
+ *
+ * The virtual rawmidi device accepts also multiple opens.  Each file
+ * has its own input buffer, so that no conflict would occur.  The drain
+ * of input/output buffer acts only to the local buffer.
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/rawmidi.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/minors.h>
+#include <sound/seq_kernel.h>
+#include <sound/seq_midi_event.h>
+#include <sound/seq_virmidi.h>
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("Virtual Raw MIDI client on Sequencer");
+MODULE_LICENSE("GPL");
+
+/*
+ * initialize an event record
+ */
+static void snd_virmidi_init_event(snd_virmidi_t *vmidi, snd_seq_event_t *ev)
+{
+	memset(ev, 0, sizeof(*ev));
+	ev->source.port = vmidi->port;
+	switch (vmidi->seq_mode) {
+	case SNDRV_VIRMIDI_SEQ_DISPATCH:
+		ev->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
+		break;
+	case SNDRV_VIRMIDI_SEQ_ATTACH:
+		/* FIXME: source and destination are same - not good.. */
+		ev->dest.client = vmidi->client;
+		ev->dest.port = vmidi->port;
+		break;
+	}
+	ev->type = SNDRV_SEQ_EVENT_NONE;
+}
+
+/*
+ * decode input event and put to read buffer of each opened file
+ */
+static int snd_virmidi_dev_receive_event(snd_virmidi_dev_t *rdev, snd_seq_event_t *ev)
+{
+	snd_virmidi_t *vmidi;
+	struct list_head *list;
+	unsigned char msg[4];
+	int len;
+
+	read_lock(&rdev->filelist_lock);
+	list_for_each(list, &rdev->filelist) {
+		vmidi = list_entry(list, snd_virmidi_t, list);
+		if (!vmidi->trigger)
+			continue;
+		if (ev->type == SNDRV_SEQ_EVENT_SYSEX) {
+			if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE)
+				continue;
+			snd_seq_dump_var_event(ev, (snd_seq_dump_func_t)snd_rawmidi_receive, vmidi->substream);
+		} else {
+			len = snd_midi_event_decode(vmidi->parser, msg, sizeof(msg), ev);
+			if (len > 0)
+				snd_rawmidi_receive(vmidi->substream, msg, len);
+		}
+	}
+	read_unlock(&rdev->filelist_lock);
+
+	return 0;
+}
+
+/*
+ * receive an event from the remote virmidi port
+ *
+ * for rawmidi inputs, you can call this function from the event
+ * handler of a remote port which is attached to the virmidi via
+ * SNDRV_VIRMIDI_SEQ_ATTACH.
+ */
+/* exported */
+int snd_virmidi_receive(snd_rawmidi_t *rmidi, snd_seq_event_t *ev)
+{
+	snd_virmidi_dev_t *rdev;
+
+	rdev = rmidi->private_data;
+	return snd_virmidi_dev_receive_event(rdev, ev);
+}
+
+/*
+ * event handler of virmidi port
+ */
+static int snd_virmidi_event_input(snd_seq_event_t *ev, int direct,
+				   void *private_data, int atomic, int hop)
+{
+	snd_virmidi_dev_t *rdev;
+
+	rdev = private_data;
+	if (!(rdev->flags & SNDRV_VIRMIDI_USE))
+		return 0; /* ignored */
+	return snd_virmidi_dev_receive_event(rdev, ev);
+}
+
+/*
+ * trigger rawmidi stream for input
+ */
+static void snd_virmidi_input_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+	snd_virmidi_t *vmidi = substream->runtime->private_data;
+
+	if (up) {
+		vmidi->trigger = 1;
+	} else {
+		vmidi->trigger = 0;
+	}
+}
+
+/*
+ * trigger rawmidi stream for output
+ */
+static void snd_virmidi_output_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+	snd_virmidi_t *vmidi = substream->runtime->private_data;
+	int count, res;
+	unsigned char buf[32], *pbuf;
+
+	if (up) {
+		vmidi->trigger = 1;
+		if (vmidi->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH &&
+		    !(vmidi->rdev->flags & SNDRV_VIRMIDI_SUBSCRIBE)) {
+			snd_rawmidi_transmit_ack(substream, substream->runtime->buffer_size - substream->runtime->avail);
+			return;		/* ignored */
+		}
+		if (vmidi->event.type != SNDRV_SEQ_EVENT_NONE) {
+			if (snd_seq_kernel_client_dispatch(vmidi->client, &vmidi->event, 0, 0) < 0)
+				return;
+			vmidi->event.type = SNDRV_SEQ_EVENT_NONE;
+		}
+		while (1) {
+			count = snd_rawmidi_transmit_peek(substream, buf, sizeof(buf));
+			if (count <= 0)
+				break;
+			pbuf = buf;
+			while (count > 0) {
+				res = snd_midi_event_encode(vmidi->parser, pbuf, count, &vmidi->event);
+				if (res < 0) {
+					snd_midi_event_reset_encode(vmidi->parser);
+					continue;
+				}
+				snd_rawmidi_transmit_ack(substream, res);
+				pbuf += res;
+				count -= res;
+				if (vmidi->event.type != SNDRV_SEQ_EVENT_NONE) {
+					if (snd_seq_kernel_client_dispatch(vmidi->client, &vmidi->event, 0, 0) < 0)
+						return;
+					vmidi->event.type = SNDRV_SEQ_EVENT_NONE;
+				}
+			}
+		}
+	} else {
+		vmidi->trigger = 0;
+	}
+}
+
+/*
+ * open rawmidi handle for input
+ */
+static int snd_virmidi_input_open(snd_rawmidi_substream_t * substream)
+{
+	snd_virmidi_dev_t *rdev = substream->rmidi->private_data;
+	snd_rawmidi_runtime_t *runtime = substream->runtime;
+	snd_virmidi_t *vmidi;
+	unsigned long flags;
+
+	vmidi = kcalloc(1, sizeof(*vmidi), GFP_KERNEL);
+	if (vmidi == NULL)
+		return -ENOMEM;
+	vmidi->substream = substream;
+	if (snd_midi_event_new(0, &vmidi->parser) < 0) {
+		kfree(vmidi);
+		return -ENOMEM;
+	}
+	vmidi->seq_mode = rdev->seq_mode;
+	vmidi->client = rdev->client;
+	vmidi->port = rdev->port;	
+	runtime->private_data = vmidi;
+	write_lock_irqsave(&rdev->filelist_lock, flags);
+	list_add_tail(&vmidi->list, &rdev->filelist);
+	write_unlock_irqrestore(&rdev->filelist_lock, flags);
+	vmidi->rdev = rdev;
+	return 0;
+}
+
+/*
+ * open rawmidi handle for output
+ */
+static int snd_virmidi_output_open(snd_rawmidi_substream_t * substream)
+{
+	snd_virmidi_dev_t *rdev = substream->rmidi->private_data;
+	snd_rawmidi_runtime_t *runtime = substream->runtime;
+	snd_virmidi_t *vmidi;
+
+	vmidi = kcalloc(1, sizeof(*vmidi), GFP_KERNEL);
+	if (vmidi == NULL)
+		return -ENOMEM;
+	vmidi->substream = substream;
+	if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &vmidi->parser) < 0) {
+		kfree(vmidi);
+		return -ENOMEM;
+	}
+	vmidi->seq_mode = rdev->seq_mode;
+	vmidi->client = rdev->client;
+	vmidi->port = rdev->port;
+	snd_virmidi_init_event(vmidi, &vmidi->event);
+	vmidi->rdev = rdev;
+	runtime->private_data = vmidi;
+	return 0;
+}
+
+/*
+ * close rawmidi handle for input
+ */
+static int snd_virmidi_input_close(snd_rawmidi_substream_t * substream)
+{
+	snd_virmidi_t *vmidi = substream->runtime->private_data;
+	snd_midi_event_free(vmidi->parser);
+	list_del(&vmidi->list);
+	substream->runtime->private_data = NULL;
+	kfree(vmidi);
+	return 0;
+}
+
+/*
+ * close rawmidi handle for output
+ */
+static int snd_virmidi_output_close(snd_rawmidi_substream_t * substream)
+{
+	snd_virmidi_t *vmidi = substream->runtime->private_data;
+	snd_midi_event_free(vmidi->parser);
+	substream->runtime->private_data = NULL;
+	kfree(vmidi);
+	return 0;
+}
+
+/*
+ * subscribe callback - allow output to rawmidi device
+ */
+static int snd_virmidi_subscribe(void *private_data, snd_seq_port_subscribe_t *info)
+{
+	snd_virmidi_dev_t *rdev;
+
+	rdev = private_data;
+	if (!try_module_get(rdev->card->module))
+		return -EFAULT;
+	rdev->flags |= SNDRV_VIRMIDI_SUBSCRIBE;
+	return 0;
+}
+
+/*
+ * unsubscribe callback - disallow output to rawmidi device
+ */
+static int snd_virmidi_unsubscribe(void *private_data, snd_seq_port_subscribe_t *info)
+{
+	snd_virmidi_dev_t *rdev;
+
+	rdev = private_data;
+	rdev->flags &= ~SNDRV_VIRMIDI_SUBSCRIBE;
+	module_put(rdev->card->module);
+	return 0;
+}
+
+
+/*
+ * use callback - allow input to rawmidi device
+ */
+static int snd_virmidi_use(void *private_data, snd_seq_port_subscribe_t *info)
+{
+	snd_virmidi_dev_t *rdev;
+
+	rdev = private_data;
+	if (!try_module_get(rdev->card->module))
+		return -EFAULT;
+	rdev->flags |= SNDRV_VIRMIDI_USE;
+	return 0;
+}
+
+/*
+ * unuse callback - disallow input to rawmidi device
+ */
+static int snd_virmidi_unuse(void *private_data, snd_seq_port_subscribe_t *info)
+{
+	snd_virmidi_dev_t *rdev;
+
+	rdev = private_data;
+	rdev->flags &= ~SNDRV_VIRMIDI_USE;
+	module_put(rdev->card->module);
+	return 0;
+}
+
+
+/*
+ *  Register functions
+ */
+
+static snd_rawmidi_ops_t snd_virmidi_input_ops = {
+	.open = snd_virmidi_input_open,
+	.close = snd_virmidi_input_close,
+	.trigger = snd_virmidi_input_trigger,
+};
+
+static snd_rawmidi_ops_t snd_virmidi_output_ops = {
+	.open = snd_virmidi_output_open,
+	.close = snd_virmidi_output_close,
+	.trigger = snd_virmidi_output_trigger,
+};
+
+/*
+ * create a sequencer client and a port
+ */
+static int snd_virmidi_dev_attach_seq(snd_virmidi_dev_t *rdev)
+{
+	int client;
+	snd_seq_client_callback_t callbacks;
+	snd_seq_port_callback_t pcallbacks;
+	snd_seq_client_info_t *info;
+	snd_seq_port_info_t *pinfo;
+	int err;
+
+	if (rdev->client >= 0)
+		return 0;
+
+	info = kmalloc(sizeof(*info), GFP_KERNEL);
+	pinfo = kmalloc(sizeof(*pinfo), GFP_KERNEL);
+	if (! info || ! pinfo) {
+		err = -ENOMEM;
+		goto __error;
+	}
+
+	memset(&callbacks, 0, sizeof(callbacks));
+	callbacks.private_data = rdev;
+	callbacks.allow_input = 1;
+	callbacks.allow_output = 1;
+	client = snd_seq_create_kernel_client(rdev->card, rdev->device, &callbacks);
+	if (client < 0) {
+		err = client;
+		goto __error;
+	}
+	rdev->client = client;
+
+	/* set client name */
+	memset(info, 0, sizeof(*info));
+	info->client = client;
+	info->type = KERNEL_CLIENT;
+	sprintf(info->name, "%s %d-%d", rdev->rmidi->name, rdev->card->number, rdev->device);
+	snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &info);
+
+	/* create a port */
+	memset(pinfo, 0, sizeof(*pinfo));
+	pinfo->addr.client = client;
+	sprintf(pinfo->name, "VirMIDI %d-%d", rdev->card->number, rdev->device);
+	/* set all capabilities */
+	pinfo->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
+	pinfo->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
+	pinfo->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
+	pinfo->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC;
+	pinfo->midi_channels = 16;
+	memset(&pcallbacks, 0, sizeof(pcallbacks));
+	pcallbacks.owner = THIS_MODULE;
+	pcallbacks.private_data = rdev;
+	pcallbacks.subscribe = snd_virmidi_subscribe;
+	pcallbacks.unsubscribe = snd_virmidi_unsubscribe;
+	pcallbacks.use = snd_virmidi_use;
+	pcallbacks.unuse = snd_virmidi_unuse;
+	pcallbacks.event_input = snd_virmidi_event_input;
+	pinfo->kernel = &pcallbacks;
+	err = snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo);
+	if (err < 0) {
+		snd_seq_delete_kernel_client(client);
+		rdev->client = -1;
+		goto __error;
+	}
+
+	rdev->port = pinfo->addr.port;
+	err = 0; /* success */
+
+ __error:
+	kfree(info);
+	kfree(pinfo);
+	return err;
+}
+
+
+/*
+ * release the sequencer client
+ */
+static void snd_virmidi_dev_detach_seq(snd_virmidi_dev_t *rdev)
+{
+	if (rdev->client >= 0) {
+		snd_seq_delete_kernel_client(rdev->client);
+		rdev->client = -1;
+	}
+}
+
+/*
+ * register the device
+ */
+static int snd_virmidi_dev_register(snd_rawmidi_t *rmidi)
+{
+	snd_virmidi_dev_t *rdev = rmidi->private_data;
+	int err;
+
+	switch (rdev->seq_mode) {
+	case SNDRV_VIRMIDI_SEQ_DISPATCH:
+		err = snd_virmidi_dev_attach_seq(rdev);
+		if (err < 0)
+			return err;
+		break;
+	case SNDRV_VIRMIDI_SEQ_ATTACH:
+		if (rdev->client == 0)
+			return -EINVAL;
+		/* should check presence of port more strictly.. */
+		break;
+	default:
+		snd_printk(KERN_ERR "seq_mode is not set: %d\n", rdev->seq_mode);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+
+/*
+ * unregister the device
+ */
+static int snd_virmidi_dev_unregister(snd_rawmidi_t *rmidi)
+{
+	snd_virmidi_dev_t *rdev = rmidi->private_data;
+
+	if (rdev->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH)
+		snd_virmidi_dev_detach_seq(rdev);
+	return 0;
+}
+
+/*
+ *
+ */
+static snd_rawmidi_global_ops_t snd_virmidi_global_ops = {
+	.dev_register = snd_virmidi_dev_register,
+	.dev_unregister = snd_virmidi_dev_unregister,
+};
+
+/*
+ * free device
+ */
+static void snd_virmidi_free(snd_rawmidi_t *rmidi)
+{
+	snd_virmidi_dev_t *rdev = rmidi->private_data;
+	kfree(rdev);
+}
+
+/*
+ * create a new device
+ *
+ */
+/* exported */
+int snd_virmidi_new(snd_card_t *card, int device, snd_rawmidi_t **rrmidi)
+{
+	snd_rawmidi_t *rmidi;
+	snd_virmidi_dev_t *rdev;
+	int err;
+	
+	*rrmidi = NULL;
+	if ((err = snd_rawmidi_new(card, "VirMidi", device,
+				   16,	/* may be configurable */
+				   16,	/* may be configurable */
+				   &rmidi)) < 0)
+		return err;
+	strcpy(rmidi->name, rmidi->id);
+	rdev = kcalloc(1, sizeof(*rdev), GFP_KERNEL);
+	if (rdev == NULL) {
+		snd_device_free(card, rmidi);
+		return -ENOMEM;
+	}
+	rdev->card = card;
+	rdev->rmidi = rmidi;
+	rdev->device = device;
+	rdev->client = -1;
+	rwlock_init(&rdev->filelist_lock);
+	INIT_LIST_HEAD(&rdev->filelist);
+	rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH;
+	rmidi->private_data = rdev;
+	rmidi->private_free = snd_virmidi_free;
+	rmidi->ops = &snd_virmidi_global_ops;
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_virmidi_input_ops);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_virmidi_output_ops);
+	rmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT |
+			    SNDRV_RAWMIDI_INFO_OUTPUT |
+			    SNDRV_RAWMIDI_INFO_DUPLEX;
+	*rrmidi = rmidi;
+	return 0;
+}
+
+/*
+ *  ENTRY functions
+ */
+
+static int __init alsa_virmidi_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_virmidi_exit(void)
+{
+}
+
+module_init(alsa_virmidi_init)
+module_exit(alsa_virmidi_exit)
+
+EXPORT_SYMBOL(snd_virmidi_new);
+EXPORT_SYMBOL(snd_virmidi_receive);
diff --git a/sound/core/sgbuf.c b/sound/core/sgbuf.c
new file mode 100644
index 000000000000..74745da9deb6
--- /dev/null
+++ b/sound/core/sgbuf.c
@@ -0,0 +1,111 @@
+/*
+ * Scatter-Gather buffer
+ *
+ *  Copyright (c) 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 <linux/config.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+#include <sound/memalloc.h>
+
+
+/* table entries are align to 32 */
+#define SGBUF_TBL_ALIGN		32
+#define sgbuf_align_table(tbl)	((((tbl) + SGBUF_TBL_ALIGN - 1) / SGBUF_TBL_ALIGN) * SGBUF_TBL_ALIGN)
+
+int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab)
+{
+	struct snd_sg_buf *sgbuf = dmab->private_data;
+	struct snd_dma_buffer tmpb;
+	int i;
+
+	if (! sgbuf)
+		return -EINVAL;
+
+	tmpb.dev.type = SNDRV_DMA_TYPE_DEV;
+	tmpb.dev.dev = sgbuf->dev;
+	for (i = 0; i < sgbuf->pages; i++) {
+		tmpb.area = sgbuf->table[i].buf;
+		tmpb.addr = sgbuf->table[i].addr;
+		tmpb.bytes = PAGE_SIZE;
+		snd_dma_free_pages(&tmpb);
+	}
+	if (dmab->area)
+		vunmap(dmab->area);
+	dmab->area = NULL;
+
+	kfree(sgbuf->table);
+	kfree(sgbuf->page_table);
+	kfree(sgbuf);
+	dmab->private_data = NULL;
+	
+	return 0;
+}
+
+void *snd_malloc_sgbuf_pages(struct device *device,
+			     size_t size, struct snd_dma_buffer *dmab,
+			     size_t *res_size)
+{
+	struct snd_sg_buf *sgbuf;
+	unsigned int i, pages;
+	struct snd_dma_buffer tmpb;
+
+	dmab->area = NULL;
+	dmab->addr = 0;
+	dmab->private_data = sgbuf = kmalloc(sizeof(*sgbuf), GFP_KERNEL);
+	if (! sgbuf)
+		return NULL;
+	memset(sgbuf, 0, sizeof(*sgbuf));
+	sgbuf->dev = device;
+	pages = snd_sgbuf_aligned_pages(size);
+	sgbuf->tblsize = sgbuf_align_table(pages);
+	sgbuf->table = kmalloc(sizeof(*sgbuf->table) * sgbuf->tblsize, GFP_KERNEL);
+	if (! sgbuf->table)
+		goto _failed;
+	memset(sgbuf->table, 0, sizeof(*sgbuf->table) * sgbuf->tblsize);
+	sgbuf->page_table = kmalloc(sizeof(*sgbuf->page_table) * sgbuf->tblsize, GFP_KERNEL);
+	if (! sgbuf->page_table)
+		goto _failed;
+	memset(sgbuf->page_table, 0, sizeof(*sgbuf->page_table) * sgbuf->tblsize);
+
+	/* allocate each page */
+	for (i = 0; i < pages; i++) {
+		if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, device, PAGE_SIZE, &tmpb) < 0) {
+			if (res_size == NULL)
+				goto _failed;
+			*res_size = size = sgbuf->pages * PAGE_SIZE;
+			break;
+		}
+		sgbuf->table[i].buf = tmpb.area;
+		sgbuf->table[i].addr = tmpb.addr;
+		sgbuf->page_table[i] = virt_to_page(tmpb.area);
+		sgbuf->pages++;
+	}
+
+	sgbuf->size = size;
+	dmab->area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, PAGE_KERNEL);
+	if (! dmab->area)
+		goto _failed;
+	return dmab->area;
+
+ _failed:
+	snd_free_sgbuf_pages(dmab); /* free the table */
+	return NULL;
+}
diff --git a/sound/core/sound.c b/sound/core/sound.c
new file mode 100644
index 000000000000..88e052079f85
--- /dev/null
+++ b/sound/core/sound.c
@@ -0,0 +1,491 @@
+/*
+ *  Advanced Linux Sound Architecture
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/info.h>
+#include <sound/version.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include <linux/kmod.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/device.h>
+
+#define SNDRV_OS_MINORS 256
+
+static int major = CONFIG_SND_MAJOR;
+int snd_major;
+static int cards_limit = 1;
+static int device_mode = S_IFCHR | S_IRUGO | S_IWUGO;
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Advanced Linux Sound Architecture driver for soundcards.");
+MODULE_LICENSE("GPL");
+module_param(major, int, 0444);
+MODULE_PARM_DESC(major, "Major # for sound driver.");
+module_param(cards_limit, int, 0444);
+MODULE_PARM_DESC(cards_limit, "Count of auto-loadable soundcards.");
+#ifdef CONFIG_DEVFS_FS
+module_param(device_mode, int, 0444);
+MODULE_PARM_DESC(device_mode, "Device file permission mask for devfs.");
+#endif
+MODULE_ALIAS_CHARDEV_MAJOR(CONFIG_SND_MAJOR);
+
+/* this one holds the actual max. card number currently available.
+ * as default, it's identical with cards_limit option.  when more
+ * modules are loaded manually, this limit number increases, too.
+ */
+int snd_ecards_limit;
+
+static struct list_head snd_minors_hash[SNDRV_CARDS];
+
+static DECLARE_MUTEX(sound_mutex);
+
+extern struct class_simple *sound_class;
+
+
+#ifdef CONFIG_KMOD
+
+/**
+ * snd_request_card - try to load the card module
+ * @card: the card number
+ *
+ * Tries to load the module "snd-card-X" for the given card number
+ * via KMOD.  Returns immediately if already loaded.
+ */
+void snd_request_card(int card)
+{
+	int locked;
+
+	if (! current->fs->root)
+		return;
+	read_lock(&snd_card_rwlock);
+	locked = snd_cards_lock & (1 << card);
+	read_unlock(&snd_card_rwlock);
+	if (locked)
+		return;
+	if (card < 0 || card >= cards_limit)
+		return;
+	request_module("snd-card-%i", card);
+}
+
+static void snd_request_other(int minor)
+{
+	char *str;
+
+	if (! current->fs->root)
+		return;
+	switch (minor) {
+	case SNDRV_MINOR_SEQUENCER:	str = "snd-seq";	break;
+	case SNDRV_MINOR_TIMER:		str = "snd-timer";	break;
+	default:			return;
+	}
+	request_module(str);
+}
+
+#endif				/* request_module support */
+
+static snd_minor_t *snd_minor_search(int minor)
+{
+	struct list_head *list;
+	snd_minor_t *mptr;
+
+	list_for_each(list, &snd_minors_hash[SNDRV_MINOR_CARD(minor)]) {
+		mptr = list_entry(list, snd_minor_t, list);
+		if (mptr->number == minor)
+			return mptr;
+	}
+	return NULL;
+}
+
+static int snd_open(struct inode *inode, struct file *file)
+{
+	int minor = iminor(inode);
+	int card = SNDRV_MINOR_CARD(minor);
+	int dev = SNDRV_MINOR_DEVICE(minor);
+	snd_minor_t *mptr = NULL;
+	struct file_operations *old_fops;
+	int err = 0;
+
+	if (dev != SNDRV_MINOR_SEQUENCER && dev != SNDRV_MINOR_TIMER) {
+		if (snd_cards[card] == NULL) {
+#ifdef CONFIG_KMOD
+			snd_request_card(card);
+			if (snd_cards[card] == NULL)
+#endif
+				return -ENODEV;
+		}
+	} else {
+#ifdef CONFIG_KMOD
+		if ((mptr = snd_minor_search(minor)) == NULL)
+			snd_request_other(minor);
+#endif
+	}
+	if (mptr == NULL && (mptr = snd_minor_search(minor)) == NULL)
+		return -ENODEV;
+	old_fops = file->f_op;
+	file->f_op = fops_get(mptr->f_ops);
+	if (file->f_op->open)
+		err = file->f_op->open(inode, file);
+	if (err) {
+		fops_put(file->f_op);
+		file->f_op = fops_get(old_fops);
+	}
+	fops_put(old_fops);
+	return err;
+}
+
+static struct file_operations snd_fops =
+{
+	.owner =	THIS_MODULE,
+	.open =		snd_open
+};
+
+static int snd_kernel_minor(int type, snd_card_t * card, int dev)
+{
+	int minor;
+
+	switch (type) {
+	case SNDRV_DEVICE_TYPE_SEQUENCER:
+	case SNDRV_DEVICE_TYPE_TIMER:
+		minor = type;
+		break;
+	case SNDRV_DEVICE_TYPE_CONTROL:
+		snd_assert(card != NULL, return -EINVAL);
+		minor = SNDRV_MINOR(card->number, type);
+		break;
+	case SNDRV_DEVICE_TYPE_HWDEP:
+	case SNDRV_DEVICE_TYPE_RAWMIDI:
+	case SNDRV_DEVICE_TYPE_PCM_PLAYBACK:
+	case SNDRV_DEVICE_TYPE_PCM_CAPTURE:
+		snd_assert(card != NULL, return -EINVAL);
+		minor = SNDRV_MINOR(card->number, type + dev);
+		break;
+	default:
+		return -EINVAL;
+	}
+	snd_assert(minor >= 0 && minor < SNDRV_OS_MINORS, return -EINVAL);
+	return minor;
+}
+
+/**
+ * snd_register_device - Register the ALSA device file for the card
+ * @type: the device type, SNDRV_DEVICE_TYPE_XXX
+ * @card: the card instance
+ * @dev: the device index
+ * @reg: the snd_minor_t record
+ * @name: the device file name
+ *
+ * Registers an ALSA device file for the given card.
+ * The operators have to be set in reg parameter.
+ *
+ * Retrurns zero if successful, or a negative error code on failure.
+ */
+int snd_register_device(int type, snd_card_t * card, int dev, snd_minor_t * reg, const char *name)
+{
+	int minor = snd_kernel_minor(type, card, dev);
+	snd_minor_t *preg;
+	struct device *device = NULL;
+
+	if (minor < 0)
+		return minor;
+	snd_assert(name, return -EINVAL);
+	preg = (snd_minor_t *)kmalloc(sizeof(snd_minor_t) + strlen(name) + 1, GFP_KERNEL);
+	if (preg == NULL)
+		return -ENOMEM;
+	*preg = *reg;
+	preg->number = minor;
+	preg->device = dev;
+	strcpy(preg->name, name);
+	down(&sound_mutex);
+	if (snd_minor_search(minor)) {
+		up(&sound_mutex);
+		kfree(preg);
+		return -EBUSY;
+	}
+	list_add_tail(&preg->list, &snd_minors_hash[SNDRV_MINOR_CARD(minor)]);
+	if (strncmp(name, "controlC", 8) || card->number >= cards_limit)
+		devfs_mk_cdev(MKDEV(major, minor), S_IFCHR | device_mode, "snd/%s", name);
+	if (card)
+		device = card->dev;
+	class_simple_device_add(sound_class, MKDEV(major, minor), device, name);
+
+	up(&sound_mutex);
+	return 0;
+}
+
+/**
+ * snd_unregister_device - unregister the device on the given card
+ * @type: the device type, SNDRV_DEVICE_TYPE_XXX
+ * @card: the card instance
+ * @dev: the device index
+ *
+ * Unregisters the device file already registered via
+ * snd_register_device().
+ *
+ * Returns zero if sucecessful, or a negative error code on failure
+ */
+int snd_unregister_device(int type, snd_card_t * card, int dev)
+{
+	int minor = snd_kernel_minor(type, card, dev);
+	snd_minor_t *mptr;
+
+	if (minor < 0)
+		return minor;
+	down(&sound_mutex);
+	if ((mptr = snd_minor_search(minor)) == NULL) {
+		up(&sound_mutex);
+		return -EINVAL;
+	}
+
+	if (strncmp(mptr->name, "controlC", 8) || card->number >= cards_limit) /* created in sound.c */
+		devfs_remove("snd/%s", mptr->name);
+	class_simple_device_remove(MKDEV(major, minor));
+
+	list_del(&mptr->list);
+	up(&sound_mutex);
+	kfree(mptr);
+	return 0;
+}
+
+/*
+ *  INFO PART
+ */
+
+static snd_info_entry_t *snd_minor_info_entry = NULL;
+
+static void snd_minor_info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+	int card, device;
+	struct list_head *list;
+	snd_minor_t *mptr;
+
+	down(&sound_mutex);
+	for (card = 0; card < SNDRV_CARDS; card++) {
+		list_for_each(list, &snd_minors_hash[card]) {
+			mptr = list_entry(list, snd_minor_t, list);
+			if (SNDRV_MINOR_DEVICE(mptr->number) != SNDRV_MINOR_SEQUENCER) {
+				if ((device = mptr->device) >= 0)
+					snd_iprintf(buffer, "%3i: [%i-%2i]: %s\n", mptr->number, card, device, mptr->comment);
+				else
+					snd_iprintf(buffer, "%3i: [%i]   : %s\n", mptr->number, card, mptr->comment);
+			} else {
+				snd_iprintf(buffer, "%3i:       : %s\n", mptr->number, mptr->comment);
+			}
+		}
+	}
+	up(&sound_mutex);
+}
+
+int __init snd_minor_info_init(void)
+{
+	snd_info_entry_t *entry;
+
+	entry = snd_info_create_module_entry(THIS_MODULE, "devices", NULL);
+	if (entry) {
+		entry->c.text.read_size = PAGE_SIZE;
+		entry->c.text.read = snd_minor_info_read;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	snd_minor_info_entry = entry;
+	return 0;
+}
+
+int __exit snd_minor_info_done(void)
+{
+	if (snd_minor_info_entry)
+		snd_info_unregister(snd_minor_info_entry);
+	return 0;
+}
+
+/*
+ *  INIT PART
+ */
+
+static int __init alsa_sound_init(void)
+{
+	short controlnum;
+	int err;
+	int card;
+
+	snd_major = major;
+	snd_ecards_limit = cards_limit;
+	for (card = 0; card < SNDRV_CARDS; card++)
+		INIT_LIST_HEAD(&snd_minors_hash[card]);
+	if ((err = snd_oss_init_module()) < 0)
+		return err;
+	devfs_mk_dir("snd");
+	if (register_chrdev(major, "alsa", &snd_fops)) {
+		snd_printk(KERN_ERR "unable to register native major device number %d\n", major);
+		devfs_remove("snd");
+		return -EIO;
+	}
+	snd_memory_init();
+	if (snd_info_init() < 0) {
+		snd_memory_done();
+		unregister_chrdev(major, "alsa");
+		devfs_remove("snd");
+		return -ENOMEM;
+	}
+	snd_info_minor_register();
+	for (controlnum = 0; controlnum < cards_limit; controlnum++)
+		devfs_mk_cdev(MKDEV(major, controlnum<<5), S_IFCHR | device_mode, "snd/controlC%d", controlnum);
+#ifndef MODULE
+	printk(KERN_INFO "Advanced Linux Sound Architecture Driver Version " CONFIG_SND_VERSION CONFIG_SND_DATE ".\n");
+#endif
+	return 0;
+}
+
+static void __exit alsa_sound_exit(void)
+{
+	short controlnum;
+
+	for (controlnum = 0; controlnum < cards_limit; controlnum++)
+		devfs_remove("snd/controlC%d", controlnum);
+
+	snd_info_minor_unregister();
+	snd_info_done();
+	snd_memory_done();
+	if (unregister_chrdev(major, "alsa") != 0)
+		snd_printk(KERN_ERR "unable to unregister major device number %d\n", major);
+	devfs_remove("snd");
+}
+
+module_init(alsa_sound_init)
+module_exit(alsa_sound_exit)
+
+  /* sound.c */
+EXPORT_SYMBOL(snd_major);
+EXPORT_SYMBOL(snd_ecards_limit);
+#if defined(CONFIG_KMOD)
+EXPORT_SYMBOL(snd_request_card);
+#endif
+EXPORT_SYMBOL(snd_register_device);
+EXPORT_SYMBOL(snd_unregister_device);
+#if defined(CONFIG_SND_OSSEMUL)
+EXPORT_SYMBOL(snd_register_oss_device);
+EXPORT_SYMBOL(snd_unregister_oss_device);
+#endif
+  /* memory.c */
+#ifdef CONFIG_SND_DEBUG_MEMORY
+EXPORT_SYMBOL(snd_hidden_kmalloc);
+EXPORT_SYMBOL(snd_hidden_kcalloc);
+EXPORT_SYMBOL(snd_hidden_kfree);
+EXPORT_SYMBOL(snd_hidden_vmalloc);
+EXPORT_SYMBOL(snd_hidden_vfree);
+#endif
+EXPORT_SYMBOL(snd_kmalloc_strdup);
+EXPORT_SYMBOL(copy_to_user_fromio);
+EXPORT_SYMBOL(copy_from_user_toio);
+  /* init.c */
+EXPORT_SYMBOL(snd_cards);
+#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
+EXPORT_SYMBOL(snd_mixer_oss_notify_callback);
+#endif
+EXPORT_SYMBOL(snd_card_new);
+EXPORT_SYMBOL(snd_card_disconnect);
+EXPORT_SYMBOL(snd_card_free);
+EXPORT_SYMBOL(snd_card_free_in_thread);
+EXPORT_SYMBOL(snd_card_register);
+EXPORT_SYMBOL(snd_component_add);
+EXPORT_SYMBOL(snd_card_file_add);
+EXPORT_SYMBOL(snd_card_file_remove);
+#ifdef CONFIG_PM
+EXPORT_SYMBOL(snd_power_wait);
+EXPORT_SYMBOL(snd_card_set_pm_callback);
+#if defined(CONFIG_PM) && defined(CONFIG_SND_GENERIC_PM)
+EXPORT_SYMBOL(snd_card_set_generic_pm_callback);
+#endif
+#ifdef CONFIG_PCI
+EXPORT_SYMBOL(snd_card_pci_suspend);
+EXPORT_SYMBOL(snd_card_pci_resume);
+#endif
+#endif
+  /* device.c */
+EXPORT_SYMBOL(snd_device_new);
+EXPORT_SYMBOL(snd_device_register);
+EXPORT_SYMBOL(snd_device_free);
+EXPORT_SYMBOL(snd_device_free_all);
+  /* isadma.c */
+#ifdef CONFIG_ISA
+EXPORT_SYMBOL(snd_dma_program);
+EXPORT_SYMBOL(snd_dma_disable);
+EXPORT_SYMBOL(snd_dma_pointer);
+#endif
+  /* info.c */
+#ifdef CONFIG_PROC_FS
+EXPORT_SYMBOL(snd_seq_root);
+EXPORT_SYMBOL(snd_iprintf);
+EXPORT_SYMBOL(snd_info_get_line);
+EXPORT_SYMBOL(snd_info_get_str);
+EXPORT_SYMBOL(snd_info_create_module_entry);
+EXPORT_SYMBOL(snd_info_create_card_entry);
+EXPORT_SYMBOL(snd_info_free_entry);
+EXPORT_SYMBOL(snd_info_register);
+EXPORT_SYMBOL(snd_info_unregister);
+EXPORT_SYMBOL(snd_card_proc_new);
+#endif
+  /* info_oss.c */
+#if defined(CONFIG_SND_OSSEMUL) && defined(CONFIG_PROC_FS)
+EXPORT_SYMBOL(snd_oss_info_register);
+#endif
+  /* control.c */
+EXPORT_SYMBOL(snd_ctl_new);
+EXPORT_SYMBOL(snd_ctl_new1);
+EXPORT_SYMBOL(snd_ctl_free_one);
+EXPORT_SYMBOL(snd_ctl_add);
+EXPORT_SYMBOL(snd_ctl_remove);
+EXPORT_SYMBOL(snd_ctl_remove_id);
+EXPORT_SYMBOL(snd_ctl_rename_id);
+EXPORT_SYMBOL(snd_ctl_find_numid);
+EXPORT_SYMBOL(snd_ctl_find_id);
+EXPORT_SYMBOL(snd_ctl_notify);
+EXPORT_SYMBOL(snd_ctl_register_ioctl);
+EXPORT_SYMBOL(snd_ctl_unregister_ioctl);
+#ifdef CONFIG_COMPAT
+EXPORT_SYMBOL(snd_ctl_register_ioctl_compat);
+EXPORT_SYMBOL(snd_ctl_unregister_ioctl_compat);
+#endif
+EXPORT_SYMBOL(snd_ctl_elem_read);
+EXPORT_SYMBOL(snd_ctl_elem_write);
+  /* misc.c */
+EXPORT_SYMBOL(snd_task_name);
+#ifdef CONFIG_SND_VERBOSE_PRINTK
+EXPORT_SYMBOL(snd_verbose_printk);
+#endif
+#if defined(CONFIG_SND_DEBUG) && defined(CONFIG_SND_VERBOSE_PRINTK)
+EXPORT_SYMBOL(snd_verbose_printd);
+#endif
+  /* wrappers */
+#ifdef CONFIG_SND_DEBUG_MEMORY
+EXPORT_SYMBOL(snd_wrapper_kmalloc);
+EXPORT_SYMBOL(snd_wrapper_kfree);
+EXPORT_SYMBOL(snd_wrapper_vmalloc);
+EXPORT_SYMBOL(snd_wrapper_vfree);
+#endif
diff --git a/sound/core/sound_oss.c b/sound/core/sound_oss.c
new file mode 100644
index 000000000000..de39d212bc15
--- /dev/null
+++ b/sound/core/sound_oss.c
@@ -0,0 +1,250 @@
+/*
+ *  Advanced Linux Sound Architecture
+ *  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>
+
+#ifdef CONFIG_SND_OSSEMUL
+
+#if !defined(CONFIG_SOUND) && !(defined(MODULE) && defined(CONFIG_SOUND_MODULE))
+#error "Enable the OSS soundcore multiplexer (CONFIG_SOUND) in the kernel."
+#endif
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/info.h>
+#include <linux/sound.h>
+
+#define SNDRV_OS_MINORS		256
+
+static struct list_head snd_oss_minors_hash[SNDRV_CARDS];
+
+static DECLARE_MUTEX(sound_oss_mutex);
+
+static snd_minor_t *snd_oss_minor_search(int minor)
+{
+	struct list_head *list;
+	snd_minor_t *mptr;
+
+	list_for_each(list, &snd_oss_minors_hash[SNDRV_MINOR_OSS_CARD(minor)]) {
+		mptr = list_entry(list, snd_minor_t, list);
+		if (mptr->number == minor)
+			return mptr;
+	}
+	return NULL;
+}
+
+static int snd_oss_kernel_minor(int type, snd_card_t * card, int dev)
+{
+	int minor;
+
+	switch (type) {
+	case SNDRV_OSS_DEVICE_TYPE_MIXER:
+		snd_assert(card != NULL && dev <= 1, return -EINVAL);
+		minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_MIXER1 : SNDRV_MINOR_OSS_MIXER));
+		break;
+	case SNDRV_OSS_DEVICE_TYPE_SEQUENCER:
+		minor = SNDRV_MINOR_OSS_SEQUENCER;
+		break;
+	case SNDRV_OSS_DEVICE_TYPE_MUSIC:
+		minor = SNDRV_MINOR_OSS_MUSIC;
+		break;
+	case SNDRV_OSS_DEVICE_TYPE_PCM:
+		snd_assert(card != NULL && dev <= 1, return -EINVAL);
+		minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_PCM1 : SNDRV_MINOR_OSS_PCM));
+		break;
+	case SNDRV_OSS_DEVICE_TYPE_MIDI:
+		snd_assert(card != NULL && dev <= 1, return -EINVAL);
+		minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_MIDI1 : SNDRV_MINOR_OSS_MIDI));
+		break;
+	case SNDRV_OSS_DEVICE_TYPE_DMFM:
+		minor = SNDRV_MINOR_OSS(card->number, SNDRV_MINOR_OSS_DMFM);
+		break;
+	case SNDRV_OSS_DEVICE_TYPE_SNDSTAT:
+		minor = SNDRV_MINOR_OSS_SNDSTAT;
+		break;
+	default:
+		return -EINVAL;
+	}
+	snd_assert(minor >= 0 && minor < SNDRV_OS_MINORS, return -EINVAL);
+	return minor;
+}
+
+int snd_register_oss_device(int type, snd_card_t * card, int dev, snd_minor_t * reg, const char *name)
+{
+	int minor = snd_oss_kernel_minor(type, card, dev);
+	int minor_unit;
+	snd_minor_t *preg;
+	int cidx = SNDRV_MINOR_OSS_CARD(minor);
+	int track2 = -1;
+	int register1 = -1, register2 = -1;
+
+	if (minor < 0)
+		return minor;
+	preg = (snd_minor_t *)kmalloc(sizeof(snd_minor_t), GFP_KERNEL);
+	if (preg == NULL)
+		return -ENOMEM;
+	*preg = *reg;
+	preg->number = minor;
+	preg->device = dev;
+	down(&sound_oss_mutex);
+	list_add_tail(&preg->list, &snd_oss_minors_hash[cidx]);
+	minor_unit = SNDRV_MINOR_OSS_DEVICE(minor);
+	switch (minor_unit) {
+	case SNDRV_MINOR_OSS_PCM:
+		track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_AUDIO);
+		break;
+	case SNDRV_MINOR_OSS_MIDI:
+		track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI);
+		break;
+	case SNDRV_MINOR_OSS_MIDI1:
+		track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI1);
+		break;
+	}
+	register1 = register_sound_special(reg->f_ops, minor);
+	if (register1 != minor)
+		goto __end;
+	if (track2 >= 0) {
+		register2 = register_sound_special(reg->f_ops, track2);
+		if (register2 != track2)
+			goto __end;
+	}
+	up(&sound_oss_mutex);
+	return 0;
+
+      __end:
+      	if (register2 >= 0)
+      		unregister_sound_special(register2);
+      	if (register1 >= 0)
+      		unregister_sound_special(register1);
+      	list_del(&preg->list);
+	up(&sound_oss_mutex);
+	kfree(preg);
+      	return -EBUSY;
+}
+
+int snd_unregister_oss_device(int type, snd_card_t * card, int dev)
+{
+	int minor = snd_oss_kernel_minor(type, card, dev);
+	int cidx = SNDRV_MINOR_OSS_CARD(minor);
+	int track2 = -1;
+	snd_minor_t *mptr;
+
+	if (minor < 0)
+		return minor;
+	down(&sound_oss_mutex);
+	mptr = snd_oss_minor_search(minor);
+	if (mptr == NULL) {
+		up(&sound_oss_mutex);
+		return -ENOENT;
+	}
+	unregister_sound_special(minor);
+	switch (SNDRV_MINOR_OSS_DEVICE(minor)) {
+	case SNDRV_MINOR_OSS_PCM:
+		track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_AUDIO);
+		break;
+	case SNDRV_MINOR_OSS_MIDI:
+		track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI);
+		break;
+	case SNDRV_MINOR_OSS_MIDI1:
+		track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI1);
+		break;
+	}
+	if (track2 >= 0)
+		unregister_sound_special(track2);
+	list_del(&mptr->list);
+	up(&sound_oss_mutex);
+	kfree(mptr);
+	return 0;
+}
+
+/*
+ *  INFO PART
+ */
+
+#ifdef CONFIG_PROC_FS
+
+static snd_info_entry_t *snd_minor_info_oss_entry = NULL;
+
+static void snd_minor_info_oss_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+	int card, dev;
+	struct list_head *list;
+	snd_minor_t *mptr;
+
+	down(&sound_oss_mutex);
+	for (card = 0; card < SNDRV_CARDS; card++) {
+		list_for_each(list, &snd_oss_minors_hash[card]) {
+			mptr = list_entry(list, snd_minor_t, list);
+			dev = SNDRV_MINOR_OSS_DEVICE(mptr->number);
+		        if (dev != SNDRV_MINOR_OSS_SNDSTAT &&
+			    dev != SNDRV_MINOR_OSS_SEQUENCER &&
+			    dev != SNDRV_MINOR_OSS_MUSIC)
+				snd_iprintf(buffer, "%3i: [%i-%2i]: %s\n", mptr->number, card, dev, mptr->comment);
+			else
+				snd_iprintf(buffer, "%3i:       : %s\n", mptr->number, mptr->comment);
+		}
+	}
+	up(&sound_oss_mutex);
+}
+
+#endif /* CONFIG_PROC_FS */
+
+int __init snd_minor_info_oss_init(void)
+{
+#ifdef CONFIG_PROC_FS
+	snd_info_entry_t *entry;
+
+	entry = snd_info_create_module_entry(THIS_MODULE, "devices", snd_oss_root);
+	if (entry) {
+		entry->c.text.read_size = PAGE_SIZE;
+		entry->c.text.read = snd_minor_info_oss_read;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	snd_minor_info_oss_entry = entry;
+#endif
+	return 0;
+}
+
+int __exit snd_minor_info_oss_done(void)
+{
+#ifdef CONFIG_PROC_FS
+	if (snd_minor_info_oss_entry)
+		snd_info_unregister(snd_minor_info_oss_entry);
+#endif
+	return 0;
+}
+
+int __init snd_oss_init_module(void)
+{
+	int card;
+	
+	for (card = 0; card < SNDRV_CARDS; card++)
+		INIT_LIST_HEAD(&snd_oss_minors_hash[card]);
+	return 0;
+}
+
+#endif /* CONFIG_SND_OSSEMUL */
diff --git a/sound/core/timer.c b/sound/core/timer.c
new file mode 100644
index 000000000000..fa762ca439be
--- /dev/null
+++ b/sound/core/timer.c
@@ -0,0 +1,1901 @@
+/*
+ *  Timers abstract layer
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/timer.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/minors.h>
+#include <sound/initval.h>
+#include <linux/kmod.h>
+#ifdef CONFIG_KERNELD
+#include <linux/kerneld.h>
+#endif
+
+#if defined(CONFIG_SND_HPET) || defined(CONFIG_SND_HPET_MODULE)
+#define DEFAULT_TIMER_LIMIT 3
+#elif defined(CONFIG_SND_RTCTIMER) || defined(CONFIG_SND_RTCTIMER_MODULE)
+#define DEFAULT_TIMER_LIMIT 2
+#else
+#define DEFAULT_TIMER_LIMIT 1
+#endif
+
+static int timer_limit = DEFAULT_TIMER_LIMIT;
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>, Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("ALSA timer interface");
+MODULE_LICENSE("GPL");
+module_param(timer_limit, int, 0444);
+MODULE_PARM_DESC(timer_limit, "Maximum global timers in system.");
+
+typedef struct {
+	snd_timer_instance_t *timeri;
+	int tread;			/* enhanced read with timestamps and events */
+	unsigned long ticks;
+	unsigned long overrun;
+	int qhead;
+	int qtail;
+	int qused;
+	int queue_size;
+	snd_timer_read_t *queue;
+	snd_timer_tread_t *tqueue;
+	spinlock_t qlock;
+	unsigned long last_resolution;
+	unsigned int filter;
+	struct timespec tstamp;		/* trigger tstamp */
+	wait_queue_head_t qchange_sleep;
+	struct fasync_struct *fasync;
+} snd_timer_user_t;
+
+/* list of timers */
+static LIST_HEAD(snd_timer_list);
+
+/* list of slave instances */
+static LIST_HEAD(snd_timer_slave_list);
+
+/* lock for slave active lists */
+static DEFINE_SPINLOCK(slave_active_lock);
+
+static DECLARE_MUTEX(register_mutex);
+
+static int snd_timer_free(snd_timer_t *timer);
+static int snd_timer_dev_free(snd_device_t *device);
+static int snd_timer_dev_register(snd_device_t *device);
+static int snd_timer_dev_unregister(snd_device_t *device);
+
+static void snd_timer_reschedule(snd_timer_t * timer, unsigned long ticks_left);
+
+/*
+ * create a timer instance with the given owner string.
+ * when timer is not NULL, increments the module counter
+ */
+static snd_timer_instance_t *snd_timer_instance_new(char *owner, snd_timer_t *timer)
+{
+	snd_timer_instance_t *timeri;
+	timeri = kcalloc(1, sizeof(*timeri), GFP_KERNEL);
+	if (timeri == NULL)
+		return NULL;
+	timeri->owner = snd_kmalloc_strdup(owner, GFP_KERNEL);
+	if (! timeri->owner) {
+		kfree(timeri);
+		return NULL;
+	}
+	INIT_LIST_HEAD(&timeri->open_list);
+	INIT_LIST_HEAD(&timeri->active_list);
+	INIT_LIST_HEAD(&timeri->ack_list);
+	INIT_LIST_HEAD(&timeri->slave_list_head);
+	INIT_LIST_HEAD(&timeri->slave_active_head);
+
+	timeri->timer = timer;
+	if (timer && timer->card && !try_module_get(timer->card->module)) {
+		kfree(timeri->owner);
+		kfree(timeri);
+		return NULL;
+	}
+
+	return timeri;
+}
+
+/*
+ * find a timer instance from the given timer id
+ */
+static snd_timer_t *snd_timer_find(snd_timer_id_t *tid)
+{
+	snd_timer_t *timer = NULL;
+	struct list_head *p;
+
+	list_for_each(p, &snd_timer_list) {
+		timer = (snd_timer_t *)list_entry(p, snd_timer_t, device_list);
+
+		if (timer->tmr_class != tid->dev_class)
+			continue;
+		if ((timer->tmr_class == SNDRV_TIMER_CLASS_CARD ||
+		     timer->tmr_class == SNDRV_TIMER_CLASS_PCM) &&
+		    (timer->card == NULL ||
+		     timer->card->number != tid->card))
+			continue;
+		if (timer->tmr_device != tid->device)
+			continue;
+		if (timer->tmr_subdevice != tid->subdevice)
+			continue;
+		return timer;
+	}
+	return NULL;
+}
+
+#ifdef CONFIG_KMOD
+
+static void snd_timer_request(snd_timer_id_t *tid)
+{
+	if (! current->fs->root)
+		return;
+	switch (tid->dev_class) {
+	case SNDRV_TIMER_CLASS_GLOBAL:
+		if (tid->device < timer_limit)
+			request_module("snd-timer-%i", tid->device);
+		break;
+	case SNDRV_TIMER_CLASS_CARD:
+	case SNDRV_TIMER_CLASS_PCM:
+		if (tid->card < snd_ecards_limit)
+			request_module("snd-card-%i", tid->card);
+		break;
+	default:
+		break;
+	}
+}
+
+#endif
+
+/*
+ * look for a master instance matching with the slave id of the given slave.
+ * when found, relink the open_link of the slave.
+ *
+ * call this with register_mutex down.
+ */
+static void snd_timer_check_slave(snd_timer_instance_t *slave)
+{
+	snd_timer_t *timer;
+	snd_timer_instance_t *master;
+	struct list_head *p, *q;
+
+	/* FIXME: it's really dumb to look up all entries.. */
+	list_for_each(p, &snd_timer_list) {
+		timer = (snd_timer_t *)list_entry(p, snd_timer_t, device_list);
+		list_for_each(q, &timer->open_list_head) {
+			master = (snd_timer_instance_t *)list_entry(q, snd_timer_instance_t, open_list);
+			if (slave->slave_class == master->slave_class &&
+			    slave->slave_id == master->slave_id) {
+				list_del(&slave->open_list);
+				list_add_tail(&slave->open_list, &master->slave_list_head);
+				spin_lock_irq(&slave_active_lock);
+				slave->master = master;
+				slave->timer = master->timer;
+				spin_unlock_irq(&slave_active_lock);
+				return;
+			}
+		}
+	}
+}
+
+/*
+ * look for slave instances matching with the slave id of the given master.
+ * when found, relink the open_link of slaves.
+ *
+ * call this with register_mutex down.
+ */
+static void snd_timer_check_master(snd_timer_instance_t *master)
+{
+	snd_timer_instance_t *slave;
+	struct list_head *p, *n;
+
+	/* check all pending slaves */
+	list_for_each_safe(p, n, &snd_timer_slave_list) {
+		slave = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, open_list);
+		if (slave->slave_class == master->slave_class &&
+		    slave->slave_id == master->slave_id) {
+			list_del(p);
+			list_add_tail(p, &master->slave_list_head);
+			spin_lock_irq(&slave_active_lock);
+			slave->master = master;
+			slave->timer = master->timer;
+			if (slave->flags & SNDRV_TIMER_IFLG_RUNNING)
+				list_add_tail(&slave->active_list, &master->slave_active_head);
+			spin_unlock_irq(&slave_active_lock);
+		}
+	}
+}
+
+/*
+ * open a timer instance
+ * when opening a master, the slave id must be here given.
+ */
+int snd_timer_open(snd_timer_instance_t **ti,
+		   char *owner, snd_timer_id_t *tid,
+		   unsigned int slave_id)
+{
+	snd_timer_t *timer;
+	snd_timer_instance_t *timeri = NULL;
+	
+	if (tid->dev_class == SNDRV_TIMER_CLASS_SLAVE) {
+		/* open a slave instance */
+		if (tid->dev_sclass <= SNDRV_TIMER_SCLASS_NONE ||
+		    tid->dev_sclass > SNDRV_TIMER_SCLASS_OSS_SEQUENCER) {
+			snd_printd("invalid slave class %i\n", tid->dev_sclass);
+			return -EINVAL;
+		}
+		down(&register_mutex);
+		timeri = snd_timer_instance_new(owner, NULL);
+		timeri->slave_class = tid->dev_sclass;
+		timeri->slave_id = tid->device;
+		timeri->flags |= SNDRV_TIMER_IFLG_SLAVE;
+		list_add_tail(&timeri->open_list, &snd_timer_slave_list);
+		snd_timer_check_slave(timeri);
+		up(&register_mutex);
+		*ti = timeri;
+		return 0;
+	}
+
+	/* open a master instance */
+	down(&register_mutex);
+	timer = snd_timer_find(tid);
+#ifdef CONFIG_KMOD
+	if (timer == NULL) {
+		up(&register_mutex);
+		snd_timer_request(tid);
+		down(&register_mutex);
+		timer = snd_timer_find(tid);
+	}
+#endif
+	if (timer) {
+		if (!list_empty(&timer->open_list_head)) {
+			timeri = (snd_timer_instance_t *)list_entry(timer->open_list_head.next, snd_timer_instance_t, open_list);
+			if (timeri->flags & SNDRV_TIMER_IFLG_EXCLUSIVE) {
+				up(&register_mutex);
+				return -EBUSY;
+			}
+		}
+		timeri = snd_timer_instance_new(owner, timer);
+		if (timeri) {
+			timeri->slave_class = tid->dev_sclass;
+			timeri->slave_id = slave_id;
+			if (list_empty(&timer->open_list_head) && timer->hw.open)
+				timer->hw.open(timer);
+			list_add_tail(&timeri->open_list, &timer->open_list_head);
+			snd_timer_check_master(timeri);
+		}
+	} else {
+		up(&register_mutex);
+		return -ENODEV;
+	}
+	up(&register_mutex);
+	*ti = timeri;
+	return 0;
+}
+
+static int _snd_timer_stop(snd_timer_instance_t * timeri, int keep_flag, enum sndrv_timer_event event);
+
+/*
+ * close a timer instance
+ */
+int snd_timer_close(snd_timer_instance_t * timeri)
+{
+	snd_timer_t *timer = NULL;
+	struct list_head *p, *n;
+	snd_timer_instance_t *slave;
+
+	snd_assert(timeri != NULL, return -ENXIO);
+
+	/* force to stop the timer */
+	snd_timer_stop(timeri);
+
+	if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) {
+		/* wait, until the active callback is finished */
+		spin_lock_irq(&slave_active_lock);
+		while (timeri->flags & SNDRV_TIMER_IFLG_CALLBACK) {
+			spin_unlock_irq(&slave_active_lock);
+			udelay(10);
+			spin_lock_irq(&slave_active_lock);
+		}
+		spin_unlock_irq(&slave_active_lock);
+		down(&register_mutex);
+		list_del(&timeri->open_list);
+		up(&register_mutex);
+	} else {
+		timer = timeri->timer;
+		/* wait, until the active callback is finished */
+		spin_lock_irq(&timer->lock);
+		while (timeri->flags & SNDRV_TIMER_IFLG_CALLBACK) {
+			spin_unlock_irq(&timer->lock);
+			udelay(10);
+			spin_lock_irq(&timer->lock);
+		}
+		spin_unlock_irq(&timer->lock);
+		down(&register_mutex);
+		list_del(&timeri->open_list);
+		if (timer && list_empty(&timer->open_list_head) && timer->hw.close)
+			timer->hw.close(timer);
+		/* remove slave links */
+		list_for_each_safe(p, n, &timeri->slave_list_head) {
+			slave = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, open_list);
+			spin_lock_irq(&slave_active_lock);
+			_snd_timer_stop(slave, 1, SNDRV_TIMER_EVENT_RESOLUTION);
+			list_del(p);
+			list_add_tail(p, &snd_timer_slave_list);
+			slave->master = NULL;
+			slave->timer = NULL;
+			spin_unlock_irq(&slave_active_lock);
+		}
+		up(&register_mutex);
+	}
+	if (timeri->private_free)
+		timeri->private_free(timeri);
+	kfree(timeri->owner);
+	kfree(timeri);
+	if (timer && timer->card)
+		module_put(timer->card->module);
+	return 0;
+}
+
+unsigned long snd_timer_resolution(snd_timer_instance_t * timeri)
+{
+	snd_timer_t * timer;
+
+	if (timeri == NULL)
+		return 0;
+	if ((timer = timeri->timer) != NULL) {
+		if (timer->hw.c_resolution)
+			return timer->hw.c_resolution(timer);
+		return timer->hw.resolution;
+	}
+	return 0;
+}
+
+static void snd_timer_notify1(snd_timer_instance_t *ti, enum sndrv_timer_event event)
+{
+	snd_timer_t *timer;
+	unsigned long flags;
+	unsigned long resolution = 0;
+	snd_timer_instance_t *ts;
+	struct list_head *n;
+	struct timespec tstamp;
+
+	snd_timestamp_now(&tstamp, 1);
+	snd_assert(event >= SNDRV_TIMER_EVENT_START && event <= SNDRV_TIMER_EVENT_PAUSE, return);
+	if (event == SNDRV_TIMER_EVENT_START || event == SNDRV_TIMER_EVENT_CONTINUE)
+		resolution = snd_timer_resolution(ti);
+	if (ti->ccallback)
+		ti->ccallback(ti, SNDRV_TIMER_EVENT_START, &tstamp, resolution);
+	if (ti->flags & SNDRV_TIMER_IFLG_SLAVE)
+		return;
+	timer = ti->timer;
+	if (timer == NULL)
+		return;
+	if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE)
+		return;
+	spin_lock_irqsave(&timer->lock, flags);
+	list_for_each(n, &ti->slave_active_head) {
+		ts = (snd_timer_instance_t *)list_entry(n, snd_timer_instance_t, active_list);
+		if (ts->ccallback)
+			ts->ccallback(ti, event + 100, &tstamp, resolution);
+	}
+	spin_unlock_irqrestore(&timer->lock, flags);
+}
+
+static int snd_timer_start1(snd_timer_t *timer, snd_timer_instance_t *timeri, unsigned long sticks)
+{
+	list_del(&timeri->active_list);
+	list_add_tail(&timeri->active_list, &timer->active_list_head);
+	if (timer->running) {
+		if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE)
+			goto __start_now;
+		timer->flags |= SNDRV_TIMER_FLG_RESCHED;
+		timeri->flags |= SNDRV_TIMER_IFLG_START;
+		return 1;	/* delayed start */
+	} else {
+		timer->sticks = sticks;
+		timer->hw.start(timer);
+	      __start_now:
+		timer->running++;
+		timeri->flags |= SNDRV_TIMER_IFLG_RUNNING;
+		return 0;
+	}
+}
+
+static int snd_timer_start_slave(snd_timer_instance_t *timeri)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&slave_active_lock, flags);
+	timeri->flags |= SNDRV_TIMER_IFLG_RUNNING;
+	if (timeri->master)
+		list_add_tail(&timeri->active_list, &timeri->master->slave_active_head);
+	spin_unlock_irqrestore(&slave_active_lock, flags);
+	return 1; /* delayed start */
+}
+
+/*
+ *  start the timer instance
+ */ 
+int snd_timer_start(snd_timer_instance_t * timeri, unsigned int ticks)
+{
+	snd_timer_t *timer;
+	int result = -EINVAL;
+	unsigned long flags;
+
+	if (timeri == NULL || ticks < 1)
+		return -EINVAL;
+	if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) {
+		result = snd_timer_start_slave(timeri);
+		snd_timer_notify1(timeri, SNDRV_TIMER_EVENT_START);
+		return result;
+	}
+	timer = timeri->timer;
+	if (timer == NULL)
+		return -EINVAL;
+	spin_lock_irqsave(&timer->lock, flags);
+	timeri->ticks = timeri->cticks = ticks;
+	timeri->pticks = 0;
+	result = snd_timer_start1(timer, timeri, ticks);
+	spin_unlock_irqrestore(&timer->lock, flags);
+	snd_timer_notify1(timeri, SNDRV_TIMER_EVENT_START);
+	return result;
+}
+
+static int _snd_timer_stop(snd_timer_instance_t * timeri, int keep_flag, enum sndrv_timer_event event)
+{
+	snd_timer_t *timer;
+	unsigned long flags;
+
+	snd_assert(timeri != NULL, return -ENXIO);
+
+	if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) {
+		if (!keep_flag) {
+			spin_lock_irqsave(&slave_active_lock, flags);
+			timeri->flags &= ~SNDRV_TIMER_IFLG_RUNNING;
+			spin_unlock_irqrestore(&slave_active_lock, flags);
+		}
+		goto __end;
+	}
+	timer = timeri->timer;
+	if (!timer)
+		return -EINVAL;
+	spin_lock_irqsave(&timer->lock, flags);
+	list_del_init(&timeri->ack_list);
+	list_del_init(&timeri->active_list);
+	if ((timeri->flags & SNDRV_TIMER_IFLG_RUNNING) &&
+	    !(--timer->running)) {
+		timer->hw.stop(timer);
+		if (timer->flags & SNDRV_TIMER_FLG_RESCHED) {
+			timer->flags &= ~SNDRV_TIMER_FLG_RESCHED;
+			snd_timer_reschedule(timer, 0);
+			if (timer->flags & SNDRV_TIMER_FLG_CHANGE) {
+				timer->flags &= ~SNDRV_TIMER_FLG_CHANGE;
+				timer->hw.start(timer);
+			}
+		}
+	}
+	if (!keep_flag)
+		timeri->flags &= ~(SNDRV_TIMER_IFLG_RUNNING|SNDRV_TIMER_IFLG_START);
+	spin_unlock_irqrestore(&timer->lock, flags);
+      __end:
+	if (event != SNDRV_TIMER_EVENT_RESOLUTION)
+		snd_timer_notify1(timeri, event);
+	return 0;
+}
+
+/*
+ * stop the timer instance.
+ *
+ * do not call this from the timer callback!
+ */
+int snd_timer_stop(snd_timer_instance_t * timeri)
+{
+	snd_timer_t *timer;
+	unsigned long flags;
+	int err;
+
+	err = _snd_timer_stop(timeri, 0, SNDRV_TIMER_EVENT_STOP);
+	if (err < 0)
+		return err;
+	timer = timeri->timer;
+	spin_lock_irqsave(&timer->lock, flags);
+	timeri->cticks = timeri->ticks;
+	timeri->pticks = 0;
+	spin_unlock_irqrestore(&timer->lock, flags);
+	return 0;
+}
+
+/*
+ * start again..  the tick is kept.
+ */
+int snd_timer_continue(snd_timer_instance_t * timeri)
+{
+	snd_timer_t *timer;
+	int result = -EINVAL;
+	unsigned long flags;
+
+	if (timeri == NULL)
+		return result;
+	if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE)
+		return snd_timer_start_slave(timeri);
+	timer = timeri->timer;
+	if (! timer)
+		return -EINVAL;
+	spin_lock_irqsave(&timer->lock, flags);
+	if (!timeri->cticks)
+		timeri->cticks = 1;
+	timeri->pticks = 0;
+	result = snd_timer_start1(timer, timeri, timer->sticks);
+	spin_unlock_irqrestore(&timer->lock, flags);
+	snd_timer_notify1(timeri, SNDRV_TIMER_EVENT_CONTINUE);
+	return result;
+}
+
+/*
+ * pause.. remember the ticks left
+ */
+int snd_timer_pause(snd_timer_instance_t * timeri)
+{
+	return _snd_timer_stop(timeri, 0, SNDRV_TIMER_EVENT_PAUSE);
+}
+
+/*
+ * reschedule the timer
+ *
+ * start pending instances and check the scheduling ticks.
+ * when the scheduling ticks is changed set CHANGE flag to reprogram the timer.
+ */
+static void snd_timer_reschedule(snd_timer_t * timer, unsigned long ticks_left)
+{
+	snd_timer_instance_t *ti;
+	unsigned long ticks = ~0UL;
+	struct list_head *p;
+
+	list_for_each(p, &timer->active_list_head) {
+		ti = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, active_list);
+		if (ti->flags & SNDRV_TIMER_IFLG_START) {
+			ti->flags &= ~SNDRV_TIMER_IFLG_START;
+			ti->flags |= SNDRV_TIMER_IFLG_RUNNING;
+			timer->running++;
+		}
+		if (ti->flags & SNDRV_TIMER_IFLG_RUNNING) {
+			if (ticks > ti->cticks)
+				ticks = ti->cticks;
+		}
+	}
+	if (ticks == ~0UL) {
+		timer->flags &= ~SNDRV_TIMER_FLG_RESCHED;
+		return;
+	}
+	if (ticks > timer->hw.ticks)
+		ticks = timer->hw.ticks;
+	if (ticks_left != ticks)
+		timer->flags |= SNDRV_TIMER_FLG_CHANGE;
+	timer->sticks = ticks;
+}
+
+/*
+ * timer tasklet
+ *
+ */
+static void snd_timer_tasklet(unsigned long arg)
+{
+	snd_timer_t *timer = (snd_timer_t *) arg;
+	snd_timer_instance_t *ti;
+	struct list_head *p;
+	unsigned long resolution, ticks;
+
+	spin_lock(&timer->lock);
+	/* now process all callbacks */
+	while (!list_empty(&timer->sack_list_head)) {
+		p = timer->sack_list_head.next;		/* get first item */
+		ti = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, ack_list);
+
+		/* remove from ack_list and make empty */
+		list_del_init(p);
+		
+		ticks = ti->pticks;
+		ti->pticks = 0;
+		resolution = ti->resolution;
+
+		ti->flags |= SNDRV_TIMER_IFLG_CALLBACK;
+		spin_unlock(&timer->lock);
+		if (ti->callback)
+			ti->callback(ti, resolution, ticks);
+		spin_lock(&timer->lock);
+		ti->flags &= ~SNDRV_TIMER_IFLG_CALLBACK;
+	}
+	spin_unlock(&timer->lock);
+}
+
+/*
+ * timer interrupt
+ *
+ * ticks_left is usually equal to timer->sticks.
+ *
+ */
+void snd_timer_interrupt(snd_timer_t * timer, unsigned long ticks_left)
+{
+	snd_timer_instance_t *ti, *ts;
+	unsigned long resolution, ticks;
+	struct list_head *p, *q, *n;
+	int use_tasklet = 0;
+
+	if (timer == NULL)
+		return;
+
+	spin_lock(&timer->lock);
+
+	/* remember the current resolution */
+	if (timer->hw.c_resolution)
+		resolution = timer->hw.c_resolution(timer);
+	else
+		resolution = timer->hw.resolution;
+
+	/* loop for all active instances
+	 * here we cannot use list_for_each because the active_list of a processed
+	 * instance is relinked to done_list_head before callback is called.
+	 */
+	list_for_each_safe(p, n, &timer->active_list_head) {
+		ti = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, active_list);
+		if (!(ti->flags & SNDRV_TIMER_IFLG_RUNNING))
+			continue;
+		ti->pticks += ticks_left;
+		ti->resolution = resolution;
+		if (ti->cticks < ticks_left)
+			ti->cticks = 0;
+		else
+			ti->cticks -= ticks_left;
+		if (ti->cticks) /* not expired */
+			continue;
+		if (ti->flags & SNDRV_TIMER_IFLG_AUTO) {
+			ti->cticks = ti->ticks;
+		} else {
+			ti->flags &= ~SNDRV_TIMER_IFLG_RUNNING;
+			if (--timer->running)
+				list_del(p);
+		}
+		if (list_empty(&ti->ack_list)) {
+			if ((timer->hw.flags & SNDRV_TIMER_HW_TASKLET) ||
+			    (ti->flags & SNDRV_TIMER_IFLG_FAST)) {
+				list_add_tail(&ti->ack_list, &timer->ack_list_head);
+			} else {
+				list_add_tail(&ti->ack_list, &timer->sack_list_head);
+			}
+		}
+		list_for_each(q, &ti->slave_active_head) {
+			ts = (snd_timer_instance_t *)list_entry(q, snd_timer_instance_t, active_list);
+			ts->pticks = ti->pticks;
+			ts->resolution = resolution;
+			if (list_empty(&ts->ack_list)) {
+				if ((timer->hw.flags & SNDRV_TIMER_HW_TASKLET) ||
+				    (ti->flags & SNDRV_TIMER_IFLG_FAST)) {
+					list_add_tail(&ts->ack_list, &timer->ack_list_head);
+				} else {
+					list_add_tail(&ts->ack_list, &timer->sack_list_head);
+				}
+			}
+		}
+	}
+	if (timer->flags & SNDRV_TIMER_FLG_RESCHED)
+		snd_timer_reschedule(timer, ticks_left);
+	if (timer->running) {
+		if (timer->hw.flags & SNDRV_TIMER_HW_STOP) {
+			timer->hw.stop(timer);
+			timer->flags |= SNDRV_TIMER_FLG_CHANGE;
+		}
+		if (!(timer->hw.flags & SNDRV_TIMER_HW_AUTO) ||
+		    (timer->flags & SNDRV_TIMER_FLG_CHANGE)) {
+			/* restart timer */
+			timer->flags &= ~SNDRV_TIMER_FLG_CHANGE;
+			timer->hw.start(timer);
+		}
+	} else {
+		timer->hw.stop(timer);
+	}
+
+	/* now process all fast callbacks */
+	while (!list_empty(&timer->ack_list_head)) {
+		p = timer->ack_list_head.next;		/* get first item */
+		ti = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, ack_list);
+		
+		/* remove from ack_list and make empty */
+		list_del_init(p);
+		
+		ticks = ti->pticks;
+		ti->pticks = 0;
+
+		ti->flags |= SNDRV_TIMER_IFLG_CALLBACK;
+		spin_unlock(&timer->lock);
+		if (ti->callback)
+			ti->callback(ti, resolution, ticks);
+		spin_lock(&timer->lock);
+		ti->flags &= ~SNDRV_TIMER_IFLG_CALLBACK;
+	}
+
+	/* do we have any slow callbacks? */
+	use_tasklet = !list_empty(&timer->sack_list_head);
+	spin_unlock(&timer->lock);
+
+	if (use_tasklet)
+		tasklet_hi_schedule(&timer->task_queue);
+}
+
+/*
+
+ */
+
+int snd_timer_new(snd_card_t *card, char *id, snd_timer_id_t *tid, snd_timer_t ** rtimer)
+{
+	snd_timer_t *timer;
+	int err;
+	static snd_device_ops_t ops = {
+		.dev_free = snd_timer_dev_free,
+		.dev_register = snd_timer_dev_register,
+		.dev_unregister = snd_timer_dev_unregister
+	};
+
+	snd_assert(tid != NULL, return -EINVAL);
+	snd_assert(rtimer != NULL, return -EINVAL);
+	*rtimer = NULL;
+	timer = kcalloc(1, sizeof(*timer), GFP_KERNEL);
+	if (timer == NULL)
+		return -ENOMEM;
+	timer->tmr_class = tid->dev_class;
+	timer->card = card;
+	timer->tmr_device = tid->device;
+	timer->tmr_subdevice = tid->subdevice;
+	if (id)
+		strlcpy(timer->id, id, sizeof(timer->id));
+	INIT_LIST_HEAD(&timer->device_list);
+	INIT_LIST_HEAD(&timer->open_list_head);
+	INIT_LIST_HEAD(&timer->active_list_head);
+	INIT_LIST_HEAD(&timer->ack_list_head);
+	INIT_LIST_HEAD(&timer->sack_list_head);
+	spin_lock_init(&timer->lock);
+	tasklet_init(&timer->task_queue, snd_timer_tasklet, (unsigned long)timer);
+	if (card != NULL) {
+		if ((err = snd_device_new(card, SNDRV_DEV_TIMER, timer, &ops)) < 0) {
+			snd_timer_free(timer);
+			return err;
+		}
+	}
+	*rtimer = timer;
+	return 0;
+}
+
+static int snd_timer_free(snd_timer_t *timer)
+{
+	snd_assert(timer != NULL, return -ENXIO);
+	if (timer->private_free)
+		timer->private_free(timer);
+	kfree(timer);
+	return 0;
+}
+
+int snd_timer_dev_free(snd_device_t *device)
+{
+	snd_timer_t *timer = device->device_data;
+	return snd_timer_free(timer);
+}
+
+int snd_timer_dev_register(snd_device_t *dev)
+{
+	snd_timer_t *timer = dev->device_data;
+	snd_timer_t *timer1;
+	struct list_head *p;
+
+	snd_assert(timer != NULL && timer->hw.start != NULL && timer->hw.stop != NULL, return -ENXIO);
+	if (!(timer->hw.flags & SNDRV_TIMER_HW_SLAVE) &&
+	    !timer->hw.resolution && timer->hw.c_resolution == NULL)
+	    	return -EINVAL;
+
+	down(&register_mutex);
+	list_for_each(p, &snd_timer_list) {
+		timer1 = (snd_timer_t *)list_entry(p, snd_timer_t, device_list);
+		if (timer1->tmr_class > timer->tmr_class)
+			break;
+		if (timer1->tmr_class < timer->tmr_class)
+			continue;
+		if (timer1->card && timer->card) {
+			if (timer1->card->number > timer->card->number)
+				break;
+			if (timer1->card->number < timer->card->number)
+				continue;
+		}
+		if (timer1->tmr_device > timer->tmr_device)
+			break;
+		if (timer1->tmr_device < timer->tmr_device)
+			continue;
+		if (timer1->tmr_subdevice > timer->tmr_subdevice)
+			break;
+		if (timer1->tmr_subdevice < timer->tmr_subdevice)
+			continue;
+		/* conflicts.. */
+		up(&register_mutex);
+		return -EBUSY;
+	}
+	list_add_tail(&timer->device_list, p);
+	up(&register_mutex);
+	return 0;
+}
+
+int snd_timer_unregister(snd_timer_t *timer)
+{
+	struct list_head *p, *n;
+	snd_timer_instance_t *ti;
+
+	snd_assert(timer != NULL, return -ENXIO);
+	down(&register_mutex);
+	if (! list_empty(&timer->open_list_head)) {
+		snd_printk(KERN_WARNING "timer 0x%lx is busy?\n", (long)timer);
+		list_for_each_safe(p, n, &timer->open_list_head) {
+			list_del_init(p);
+			ti = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, open_list);
+			ti->timer = NULL;
+		}
+	}
+	list_del(&timer->device_list);
+	up(&register_mutex);
+	return snd_timer_free(timer);
+}
+
+static int snd_timer_dev_unregister(snd_device_t *device)
+{
+	snd_timer_t *timer = device->device_data;
+	return snd_timer_unregister(timer);
+}
+
+void snd_timer_notify(snd_timer_t *timer, enum sndrv_timer_event event, struct timespec *tstamp)
+{
+	unsigned long flags;
+	unsigned long resolution = 0;
+	snd_timer_instance_t *ti, *ts;
+	struct list_head *p, *n;
+
+	snd_runtime_check(timer->hw.flags & SNDRV_TIMER_HW_SLAVE, return);	
+	snd_assert(event >= SNDRV_TIMER_EVENT_MSTART && event <= SNDRV_TIMER_EVENT_MPAUSE, return);
+	spin_lock_irqsave(&timer->lock, flags);
+	if (event == SNDRV_TIMER_EVENT_MSTART || event == SNDRV_TIMER_EVENT_MCONTINUE) {
+		if (timer->hw.c_resolution)
+			resolution = timer->hw.c_resolution(timer);
+		else
+			resolution = timer->hw.resolution;
+	}
+	list_for_each(p, &timer->active_list_head) {
+		ti = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, active_list);
+		if (ti->ccallback)
+			ti->ccallback(ti, event, tstamp, resolution);
+		list_for_each(n, &ti->slave_active_head) {
+			ts = (snd_timer_instance_t *)list_entry(n, snd_timer_instance_t, active_list);
+			if (ts->ccallback)
+				ts->ccallback(ts, event, tstamp, resolution);
+		}
+	}
+	spin_unlock_irqrestore(&timer->lock, flags);
+}
+
+/*
+ * exported functions for global timers
+ */
+int snd_timer_global_new(char *id, int device, snd_timer_t **rtimer)
+{
+	snd_timer_id_t tid;
+	
+	tid.dev_class = SNDRV_TIMER_CLASS_GLOBAL;
+	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+	tid.card = -1;
+	tid.device = device;
+	tid.subdevice = 0;
+	return snd_timer_new(NULL, id, &tid, rtimer);
+}
+
+int snd_timer_global_free(snd_timer_t *timer)
+{
+	return snd_timer_free(timer);
+}
+
+int snd_timer_global_register(snd_timer_t *timer)
+{
+	snd_device_t dev;
+
+	memset(&dev, 0, sizeof(dev));
+	dev.device_data = timer;
+	return snd_timer_dev_register(&dev);
+}
+
+int snd_timer_global_unregister(snd_timer_t *timer)
+{
+	return snd_timer_unregister(timer);
+}
+
+/* 
+ *  System timer
+ */
+
+struct snd_timer_system_private {
+	struct timer_list tlist;
+	struct timer * timer;
+	unsigned long last_expires;
+	unsigned long last_jiffies;
+	unsigned long correction;
+};
+
+unsigned int snd_timer_system_resolution(void)
+{
+	return 1000000000L / HZ;
+}
+
+static void snd_timer_s_function(unsigned long data)
+{
+	snd_timer_t *timer = (snd_timer_t *)data;
+	struct snd_timer_system_private *priv = timer->private_data;
+	unsigned long jiff = jiffies;
+	if (time_after(jiff, priv->last_expires))
+		priv->correction = (long)jiff - (long)priv->last_expires;
+	snd_timer_interrupt(timer, (long)jiff - (long)priv->last_jiffies);
+}
+
+static int snd_timer_s_start(snd_timer_t * timer)
+{
+	struct snd_timer_system_private *priv;
+	unsigned long njiff;
+
+	priv = (struct snd_timer_system_private *) timer->private_data;
+	njiff = (priv->last_jiffies = jiffies);
+	if (priv->correction > timer->sticks - 1) {
+		priv->correction -= timer->sticks - 1;
+		njiff++;
+	} else {
+		njiff += timer->sticks - priv->correction;
+		priv->correction -= timer->sticks;
+	}
+	priv->last_expires = priv->tlist.expires = njiff;
+	add_timer(&priv->tlist);
+	return 0;
+}
+
+static int snd_timer_s_stop(snd_timer_t * timer)
+{
+	struct snd_timer_system_private *priv;
+	unsigned long jiff;
+
+	priv = (struct snd_timer_system_private *) timer->private_data;
+	del_timer(&priv->tlist);
+	jiff = jiffies;
+	if (time_before(jiff, priv->last_expires))
+		timer->sticks = priv->last_expires - jiff;
+	else
+		timer->sticks = 1;
+	return 0;
+}
+
+static struct _snd_timer_hardware snd_timer_system =
+{
+	.flags =	SNDRV_TIMER_HW_FIRST | SNDRV_TIMER_HW_TASKLET,
+	.resolution =	1000000000L / HZ,
+	.ticks =	10000000L,
+	.start =	snd_timer_s_start,
+	.stop =		snd_timer_s_stop
+};
+
+static void snd_timer_free_system(snd_timer_t *timer)
+{
+	kfree(timer->private_data);
+}
+
+static int snd_timer_register_system(void)
+{
+	snd_timer_t *timer;
+	struct snd_timer_system_private *priv;
+	int err;
+
+	if ((err = snd_timer_global_new("system", SNDRV_TIMER_GLOBAL_SYSTEM, &timer)) < 0)
+		return err;
+	strcpy(timer->name, "system timer");
+	timer->hw = snd_timer_system;
+	priv = kcalloc(1, sizeof(*priv), GFP_KERNEL);
+	if (priv == NULL) {
+		snd_timer_free(timer);
+		return -ENOMEM;
+	}
+	init_timer(&priv->tlist);
+	priv->tlist.function = snd_timer_s_function;
+	priv->tlist.data = (unsigned long) timer;
+	timer->private_data = priv;
+	timer->private_free = snd_timer_free_system;
+	return snd_timer_global_register(timer);
+}
+
+/*
+ *  Info interface
+ */
+
+static void snd_timer_proc_read(snd_info_entry_t *entry,
+				snd_info_buffer_t * buffer)
+{
+	unsigned long flags;
+	snd_timer_t *timer;
+	snd_timer_instance_t *ti;
+	struct list_head *p, *q;
+
+	down(&register_mutex);
+	list_for_each(p, &snd_timer_list) {
+		timer = (snd_timer_t *)list_entry(p, snd_timer_t, device_list);
+		switch (timer->tmr_class) {
+		case SNDRV_TIMER_CLASS_GLOBAL:
+			snd_iprintf(buffer, "G%i: ", timer->tmr_device);
+			break;
+		case SNDRV_TIMER_CLASS_CARD:
+			snd_iprintf(buffer, "C%i-%i: ", timer->card->number, timer->tmr_device);
+			break;
+		case SNDRV_TIMER_CLASS_PCM:
+			snd_iprintf(buffer, "P%i-%i-%i: ", timer->card->number, timer->tmr_device, timer->tmr_subdevice);
+			break;
+		default:
+			snd_iprintf(buffer, "?%i-%i-%i-%i: ", timer->tmr_class, timer->card ? timer->card->number : -1, timer->tmr_device, timer->tmr_subdevice);
+		}
+		snd_iprintf(buffer, "%s :", timer->name);
+		if (timer->hw.resolution)
+			snd_iprintf(buffer, " %lu.%03luus (%lu ticks)", timer->hw.resolution / 1000, timer->hw.resolution % 1000, timer->hw.ticks);
+		if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE)
+			snd_iprintf(buffer, " SLAVE");
+		snd_iprintf(buffer, "\n");
+		spin_lock_irqsave(&timer->lock, flags);
+		list_for_each(q, &timer->open_list_head) {
+			ti = (snd_timer_instance_t *)list_entry(q, snd_timer_instance_t, open_list);
+			snd_iprintf(buffer, "  Client %s : %s : lost interrupts %li\n",
+					ti->owner ? ti->owner : "unknown",
+					ti->flags & (SNDRV_TIMER_IFLG_START|SNDRV_TIMER_IFLG_RUNNING) ? "running" : "stopped",
+					ti->lost);
+		}
+		spin_unlock_irqrestore(&timer->lock, flags);
+	}
+	up(&register_mutex);
+}
+
+/*
+ *  USER SPACE interface
+ */
+
+static void snd_timer_user_interrupt(snd_timer_instance_t *timeri,
+				     unsigned long resolution,
+				     unsigned long ticks)
+{
+	snd_timer_user_t *tu = timeri->callback_data;
+	snd_timer_read_t *r;
+	int prev;
+	
+	spin_lock(&tu->qlock);
+	if (tu->qused > 0) {
+		prev = tu->qtail == 0 ? tu->queue_size - 1 : tu->qtail - 1;
+		r = &tu->queue[prev];
+		if (r->resolution == resolution) {
+			r->ticks += ticks;
+			goto __wake;
+		}
+	}
+	if (tu->qused >= tu->queue_size) {
+		tu->overrun++;
+	} else {
+		r = &tu->queue[tu->qtail++];
+		tu->qtail %= tu->queue_size;
+		r->resolution = resolution;
+		r->ticks = ticks;
+		tu->qused++;
+	}
+      __wake:
+	spin_unlock(&tu->qlock);
+	kill_fasync(&tu->fasync, SIGIO, POLL_IN);
+	wake_up(&tu->qchange_sleep);
+}
+
+static void snd_timer_user_append_to_tqueue(snd_timer_user_t *tu, snd_timer_tread_t *tread)
+{
+	if (tu->qused >= tu->queue_size) {
+		tu->overrun++;
+	} else {
+		memcpy(&tu->tqueue[tu->qtail++], tread, sizeof(*tread));
+		tu->qtail %= tu->queue_size;
+		tu->qused++;
+	}
+}
+
+static void snd_timer_user_ccallback(snd_timer_instance_t *timeri,
+				     enum sndrv_timer_event event,
+				     struct timespec *tstamp,
+				     unsigned long resolution)
+{
+	snd_timer_user_t *tu = timeri->callback_data;
+	snd_timer_tread_t r1;
+
+	if (event >= SNDRV_TIMER_EVENT_START && event <= SNDRV_TIMER_EVENT_PAUSE)
+		tu->tstamp = *tstamp;
+	if ((tu->filter & (1 << event)) == 0 || !tu->tread)
+		return;
+	r1.event = event;
+	r1.tstamp = *tstamp;
+	r1.val = resolution;
+	spin_lock(&tu->qlock);
+	snd_timer_user_append_to_tqueue(tu, &r1);
+	spin_unlock(&tu->qlock);
+	kill_fasync(&tu->fasync, SIGIO, POLL_IN);
+	wake_up(&tu->qchange_sleep);
+}
+
+static void snd_timer_user_tinterrupt(snd_timer_instance_t *timeri,
+				      unsigned long resolution,
+				      unsigned long ticks)
+{
+	snd_timer_user_t *tu = timeri->callback_data;
+	snd_timer_tread_t *r, r1;
+	struct timespec tstamp;
+	int prev, append = 0;
+
+	snd_timestamp_zero(&tstamp);
+	spin_lock(&tu->qlock);
+	if ((tu->filter & ((1 << SNDRV_TIMER_EVENT_RESOLUTION)|(1 << SNDRV_TIMER_EVENT_TICK))) == 0) {
+		spin_unlock(&tu->qlock);
+		return;
+	}
+	if (tu->last_resolution != resolution || ticks > 0)
+		snd_timestamp_now(&tstamp, 1);
+	if ((tu->filter & (1 << SNDRV_TIMER_EVENT_RESOLUTION)) && tu->last_resolution != resolution) {
+		r1.event = SNDRV_TIMER_EVENT_RESOLUTION;
+		r1.tstamp = tstamp;
+		r1.val = resolution;
+		snd_timer_user_append_to_tqueue(tu, &r1);
+		tu->last_resolution = resolution;
+		append++;
+	}
+	if ((tu->filter & (1 << SNDRV_TIMER_EVENT_TICK)) == 0)
+		goto __wake;
+	if (ticks == 0)
+		goto __wake;
+	if (tu->qused > 0) {
+		prev = tu->qtail == 0 ? tu->queue_size - 1 : tu->qtail - 1;
+		r = &tu->tqueue[prev];
+		if (r->event == SNDRV_TIMER_EVENT_TICK) {
+			r->tstamp = tstamp;
+			r->val += ticks;
+			append++;
+			goto __wake;
+		}
+	}
+	r1.event = SNDRV_TIMER_EVENT_TICK;
+	r1.tstamp = tstamp;
+	r1.val = ticks;
+	snd_timer_user_append_to_tqueue(tu, &r1);
+	append++;
+      __wake:
+	spin_unlock(&tu->qlock);
+	if (append == 0)
+		return;
+	kill_fasync(&tu->fasync, SIGIO, POLL_IN);
+	wake_up(&tu->qchange_sleep);
+}
+
+static int snd_timer_user_open(struct inode *inode, struct file *file)
+{
+	snd_timer_user_t *tu;
+	
+	tu = kcalloc(1, sizeof(*tu), GFP_KERNEL);
+	if (tu == NULL)
+		return -ENOMEM;
+	spin_lock_init(&tu->qlock);
+	init_waitqueue_head(&tu->qchange_sleep);
+	tu->ticks = 1;
+	tu->queue_size = 128;
+	tu->queue = (snd_timer_read_t *)kmalloc(tu->queue_size * sizeof(snd_timer_read_t), GFP_KERNEL);
+	if (tu->queue == NULL) {
+		kfree(tu);
+		return -ENOMEM;
+	}
+	file->private_data = tu;
+	return 0;
+}
+
+static int snd_timer_user_release(struct inode *inode, struct file *file)
+{
+	snd_timer_user_t *tu;
+
+	if (file->private_data) {
+		tu = file->private_data;
+		file->private_data = NULL;
+		fasync_helper(-1, file, 0, &tu->fasync);
+		if (tu->timeri)
+			snd_timer_close(tu->timeri);
+		kfree(tu->queue);
+		kfree(tu->tqueue);
+		kfree(tu);
+	}
+	return 0;
+}
+
+static void snd_timer_user_zero_id(snd_timer_id_t *id)
+{
+	id->dev_class = SNDRV_TIMER_CLASS_NONE;
+	id->dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+	id->card = -1;
+	id->device = -1;
+	id->subdevice = -1;
+}
+
+static void snd_timer_user_copy_id(snd_timer_id_t *id, snd_timer_t *timer)
+{
+	id->dev_class = timer->tmr_class;
+	id->dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+	id->card = timer->card ? timer->card->number : -1;
+	id->device = timer->tmr_device;
+	id->subdevice = timer->tmr_subdevice;
+}
+
+static int snd_timer_user_next_device(snd_timer_id_t __user *_tid)
+{
+	snd_timer_id_t id;
+	snd_timer_t *timer;
+	struct list_head *p;
+	
+	if (copy_from_user(&id, _tid, sizeof(id)))
+		return -EFAULT;
+	down(&register_mutex);
+	if (id.dev_class < 0) {		/* first item */
+		if (list_empty(&snd_timer_list))
+			snd_timer_user_zero_id(&id);
+		else {
+			timer = (snd_timer_t *)list_entry(snd_timer_list.next, snd_timer_t, device_list);
+			snd_timer_user_copy_id(&id, timer);
+		}
+	} else {
+		switch (id.dev_class) {
+		case SNDRV_TIMER_CLASS_GLOBAL:
+			id.device = id.device < 0 ? 0 : id.device + 1;
+			list_for_each(p, &snd_timer_list) {
+				timer = (snd_timer_t *)list_entry(p, snd_timer_t, device_list);
+				if (timer->tmr_class > SNDRV_TIMER_CLASS_GLOBAL) {
+					snd_timer_user_copy_id(&id, timer);
+					break;
+				}
+				if (timer->tmr_device >= id.device) {
+					snd_timer_user_copy_id(&id, timer);
+					break;
+				}
+			}
+			if (p == &snd_timer_list)
+				snd_timer_user_zero_id(&id);
+			break;
+		case SNDRV_TIMER_CLASS_CARD:
+		case SNDRV_TIMER_CLASS_PCM:
+			if (id.card < 0) {
+				id.card = 0;
+			} else {
+				if (id.card < 0) {
+					id.card = 0;
+				} else {
+					if (id.device < 0) {
+						id.device = 0;
+					} else {
+						id.subdevice = id.subdevice < 0 ? 0 : id.subdevice + 1;
+					}
+				}
+			}
+			list_for_each(p, &snd_timer_list) {
+				timer = (snd_timer_t *)list_entry(p, snd_timer_t, device_list);
+				if (timer->tmr_class > id.dev_class) {
+					snd_timer_user_copy_id(&id, timer);
+					break;
+				}
+				if (timer->tmr_class < id.dev_class)
+					continue;
+				if (timer->card->number > id.card) {
+					snd_timer_user_copy_id(&id, timer);
+					break;
+				}
+				if (timer->card->number < id.card)
+					continue;
+				if (timer->tmr_device > id.device) {
+					snd_timer_user_copy_id(&id, timer);
+					break;
+				}
+				if (timer->tmr_device < id.device)
+					continue;
+				if (timer->tmr_subdevice > id.subdevice) {
+					snd_timer_user_copy_id(&id, timer);
+					break;
+				}
+				if (timer->tmr_subdevice < id.subdevice)
+					continue;
+				snd_timer_user_copy_id(&id, timer);
+				break;
+			}
+			if (p == &snd_timer_list)
+				snd_timer_user_zero_id(&id);
+			break;
+		default:
+			snd_timer_user_zero_id(&id);
+		}
+	}
+	up(&register_mutex);
+	if (copy_to_user(_tid, &id, sizeof(*_tid)))
+		return -EFAULT;
+	return 0;
+} 
+
+static int snd_timer_user_ginfo(struct file *file, snd_timer_ginfo_t __user *_ginfo)
+{
+	snd_timer_ginfo_t *ginfo;
+	snd_timer_id_t tid;
+	snd_timer_t *t;
+	struct list_head *p;
+	int err = 0;
+
+	ginfo = kmalloc(sizeof(*ginfo), GFP_KERNEL);
+	if (! ginfo)
+		return -ENOMEM;
+	if (copy_from_user(ginfo, _ginfo, sizeof(*ginfo))) {
+		kfree(ginfo);
+		return -EFAULT;
+	}
+	tid = ginfo->tid;
+	memset(ginfo, 0, sizeof(*ginfo));
+	ginfo->tid = tid;
+	down(&register_mutex);
+	t = snd_timer_find(&tid);
+	if (t != NULL) {
+		ginfo->card = t->card ? t->card->number : -1;
+		if (t->hw.flags & SNDRV_TIMER_HW_SLAVE)
+			ginfo->flags |= SNDRV_TIMER_FLG_SLAVE;
+		strlcpy(ginfo->id, t->id, sizeof(ginfo->id));
+		strlcpy(ginfo->name, t->name, sizeof(ginfo->name));
+		ginfo->resolution = t->hw.resolution;
+		if (t->hw.resolution_min > 0) {
+			ginfo->resolution_min = t->hw.resolution_min;
+			ginfo->resolution_max = t->hw.resolution_max;
+		}
+		list_for_each(p, &t->open_list_head) {
+			ginfo->clients++;
+		}
+	} else {
+		err = -ENODEV;
+	}
+	up(&register_mutex);
+	if (err >= 0 && copy_to_user(_ginfo, ginfo, sizeof(*ginfo)))
+		err = -EFAULT;
+	kfree(ginfo);
+	return err;
+}
+
+static int snd_timer_user_gparams(struct file *file, snd_timer_gparams_t __user *_gparams)
+{
+	snd_timer_gparams_t gparams;
+	snd_timer_t *t;
+	int err;
+
+	if (copy_from_user(&gparams, _gparams, sizeof(gparams)))
+		return -EFAULT;
+	down(&register_mutex);
+	t = snd_timer_find(&gparams.tid);
+	if (t != NULL) {
+		if (list_empty(&t->open_list_head)) {
+			if (t->hw.set_period)
+				err = t->hw.set_period(t, gparams.period_num, gparams.period_den);
+			else
+				err = -ENOSYS;
+		} else {
+			err = -EBUSY;
+		}
+	} else {
+		err = -ENODEV;
+	}
+	up(&register_mutex);
+	return err;
+}
+
+static int snd_timer_user_gstatus(struct file *file, snd_timer_gstatus_t __user *_gstatus)
+{
+	snd_timer_gstatus_t gstatus;
+	snd_timer_id_t tid;
+	snd_timer_t *t;
+	int err = 0;
+
+	if (copy_from_user(&gstatus, _gstatus, sizeof(gstatus)))
+		return -EFAULT;
+	tid = gstatus.tid;
+	memset(&gstatus, 0, sizeof(gstatus));
+	gstatus.tid = tid;
+	down(&register_mutex);
+	t = snd_timer_find(&tid);
+	if (t != NULL) {
+		if (t->hw.c_resolution)
+			gstatus.resolution = t->hw.c_resolution(t);
+		else
+			gstatus.resolution = t->hw.resolution;
+		if (t->hw.precise_resolution) {
+			t->hw.precise_resolution(t, &gstatus.resolution_num, &gstatus.resolution_den);
+		} else {
+			gstatus.resolution_num = gstatus.resolution;
+			gstatus.resolution_den = 1000000000uL;
+		}
+	} else {
+		err = -ENODEV;
+	}
+	up(&register_mutex);
+	if (err >= 0 && copy_to_user(_gstatus, &gstatus, sizeof(gstatus)))
+		err = -EFAULT;
+	return err;
+}
+
+static int snd_timer_user_tselect(struct file *file, snd_timer_select_t __user *_tselect)
+{
+	snd_timer_user_t *tu;
+	snd_timer_select_t tselect;
+	char str[32];
+	int err;
+	
+	tu = file->private_data;
+	if (tu->timeri)
+		snd_timer_close(tu->timeri);
+	if (copy_from_user(&tselect, _tselect, sizeof(tselect)))
+		return -EFAULT;
+	sprintf(str, "application %i", current->pid);
+	if (tselect.id.dev_class != SNDRV_TIMER_CLASS_SLAVE)
+		tselect.id.dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION;
+	if ((err = snd_timer_open(&tu->timeri, str, &tselect.id, current->pid)) < 0)
+		return err;
+
+	if (tu->queue) {
+		kfree(tu->queue);
+		tu->queue = NULL;
+	}
+	if (tu->tqueue) {
+		kfree(tu->tqueue);
+		tu->tqueue = NULL;
+	}
+	if (tu->tread) {
+		tu->tqueue = (snd_timer_tread_t *)kmalloc(tu->queue_size * sizeof(snd_timer_tread_t), GFP_KERNEL);
+		if (tu->tqueue == NULL) {
+			snd_timer_close(tu->timeri);
+			return -ENOMEM;
+		}
+	} else {
+		tu->queue = (snd_timer_read_t *)kmalloc(tu->queue_size * sizeof(snd_timer_read_t), GFP_KERNEL);
+		if (tu->queue == NULL) {
+			snd_timer_close(tu->timeri);
+			return -ENOMEM;
+		}
+	}
+	
+	tu->timeri->flags |= SNDRV_TIMER_IFLG_FAST;
+	tu->timeri->callback = tu->tread ? snd_timer_user_tinterrupt : snd_timer_user_interrupt;
+	tu->timeri->ccallback = snd_timer_user_ccallback;
+	tu->timeri->callback_data = (void *)tu;
+	return 0;
+}
+
+static int snd_timer_user_info(struct file *file, snd_timer_info_t __user *_info)
+{
+	snd_timer_user_t *tu;
+	snd_timer_info_t *info;
+	snd_timer_t *t;
+	int err = 0;
+
+	tu = file->private_data;
+	snd_assert(tu->timeri != NULL, return -ENXIO);
+	t = tu->timeri->timer;
+	snd_assert(t != NULL, return -ENXIO);
+
+	info = kcalloc(1, sizeof(*info), GFP_KERNEL);
+	if (! info)
+		return -ENOMEM;
+	info->card = t->card ? t->card->number : -1;
+	if (t->hw.flags & SNDRV_TIMER_HW_SLAVE)
+		info->flags |= SNDRV_TIMER_FLG_SLAVE;
+	strlcpy(info->id, t->id, sizeof(info->id));
+	strlcpy(info->name, t->name, sizeof(info->name));
+	info->resolution = t->hw.resolution;
+	if (copy_to_user(_info, info, sizeof(*_info)))
+		err = -EFAULT;
+	kfree(info);
+	return err;
+}
+
+static int snd_timer_user_params(struct file *file, snd_timer_params_t __user *_params)
+{
+	snd_timer_user_t *tu;
+	snd_timer_params_t params;
+	snd_timer_t *t;
+	snd_timer_read_t *tr;
+	snd_timer_tread_t *ttr;
+	int err;
+	
+	tu = file->private_data;
+	snd_assert(tu->timeri != NULL, return -ENXIO);
+	t = tu->timeri->timer;
+	snd_assert(t != NULL, return -ENXIO);
+	if (copy_from_user(&params, _params, sizeof(params)))
+		return -EFAULT;
+	if (!(t->hw.flags & SNDRV_TIMER_HW_SLAVE) && params.ticks < 1) {
+		err = -EINVAL;
+		goto _end;
+	}
+	if (params.queue_size > 0 && (params.queue_size < 32 || params.queue_size > 1024)) {
+		err = -EINVAL;
+		goto _end;
+	}
+	if (params.filter & ~((1<<SNDRV_TIMER_EVENT_RESOLUTION)|
+			      (1<<SNDRV_TIMER_EVENT_TICK)|
+			      (1<<SNDRV_TIMER_EVENT_START)|
+			      (1<<SNDRV_TIMER_EVENT_STOP)|
+			      (1<<SNDRV_TIMER_EVENT_CONTINUE)|
+			      (1<<SNDRV_TIMER_EVENT_PAUSE)|
+			      (1<<SNDRV_TIMER_EVENT_MSTART)|
+			      (1<<SNDRV_TIMER_EVENT_MSTOP)|
+			      (1<<SNDRV_TIMER_EVENT_MCONTINUE)|
+			      (1<<SNDRV_TIMER_EVENT_MPAUSE))) {
+		err = -EINVAL;
+		goto _end;
+	}
+	snd_timer_stop(tu->timeri);
+	spin_lock_irq(&t->lock);
+	tu->timeri->flags &= ~(SNDRV_TIMER_IFLG_AUTO|
+			       SNDRV_TIMER_IFLG_EXCLUSIVE|
+			       SNDRV_TIMER_IFLG_EARLY_EVENT);
+	if (params.flags & SNDRV_TIMER_PSFLG_AUTO)
+		tu->timeri->flags |= SNDRV_TIMER_IFLG_AUTO;
+	if (params.flags & SNDRV_TIMER_PSFLG_EXCLUSIVE)
+		tu->timeri->flags |= SNDRV_TIMER_IFLG_EXCLUSIVE;
+	if (params.flags & SNDRV_TIMER_PSFLG_EARLY_EVENT)
+		tu->timeri->flags |= SNDRV_TIMER_IFLG_EARLY_EVENT;
+	spin_unlock_irq(&t->lock);
+	if (params.queue_size > 0 && (unsigned int)tu->queue_size != params.queue_size) {
+		if (tu->tread) {
+			ttr = (snd_timer_tread_t *)kmalloc(params.queue_size * sizeof(snd_timer_tread_t), GFP_KERNEL);
+			if (ttr) {
+				kfree(tu->tqueue);
+				tu->queue_size = params.queue_size;
+				tu->tqueue = ttr;
+			}
+		} else {
+			tr = (snd_timer_read_t *)kmalloc(params.queue_size * sizeof(snd_timer_read_t), GFP_KERNEL);
+			if (tr) {
+				kfree(tu->queue);
+				tu->queue_size = params.queue_size;
+				tu->queue = tr;
+			}
+		}
+	}
+	tu->qhead = tu->qtail = tu->qused = 0;
+	if (tu->timeri->flags & SNDRV_TIMER_IFLG_EARLY_EVENT) {
+		if (tu->tread) {
+			snd_timer_tread_t tread;
+			tread.event = SNDRV_TIMER_EVENT_EARLY;
+			tread.tstamp.tv_sec = 0;
+			tread.tstamp.tv_nsec = 0;
+			tread.val = 0;
+			snd_timer_user_append_to_tqueue(tu, &tread);
+		} else {
+			snd_timer_read_t *r = &tu->queue[0];
+			r->resolution = 0;
+			r->ticks = 0;
+			tu->qused++;
+			tu->qtail++;
+		}
+		
+	}
+	tu->filter = params.filter;
+	tu->ticks = params.ticks;
+	err = 0;
+ _end:
+	if (copy_to_user(_params, &params, sizeof(params)))
+		return -EFAULT;
+	return err;
+}
+
+static int snd_timer_user_status(struct file *file, snd_timer_status_t __user *_status)
+{
+	snd_timer_user_t *tu;
+	snd_timer_status_t status;
+	
+	tu = file->private_data;
+	snd_assert(tu->timeri != NULL, return -ENXIO);
+	memset(&status, 0, sizeof(status));
+	status.tstamp = tu->tstamp;
+	status.resolution = snd_timer_resolution(tu->timeri);
+	status.lost = tu->timeri->lost;
+	status.overrun = tu->overrun;
+	spin_lock_irq(&tu->qlock);
+	status.queue = tu->qused;
+	spin_unlock_irq(&tu->qlock);
+	if (copy_to_user(_status, &status, sizeof(status)))
+		return -EFAULT;
+	return 0;
+}
+
+static int snd_timer_user_start(struct file *file)
+{
+	int err;
+	snd_timer_user_t *tu;
+		
+	tu = file->private_data;
+	snd_assert(tu->timeri != NULL, return -ENXIO);
+	snd_timer_stop(tu->timeri);
+	tu->timeri->lost = 0;
+	tu->last_resolution = 0;
+	return (err = snd_timer_start(tu->timeri, tu->ticks)) < 0 ? err : 0;
+}
+
+static int snd_timer_user_stop(struct file *file)
+{
+	int err;
+	snd_timer_user_t *tu;
+		
+	tu = file->private_data;
+	snd_assert(tu->timeri != NULL, return -ENXIO);
+	return (err = snd_timer_stop(tu->timeri)) < 0 ? err : 0;
+}
+
+static int snd_timer_user_continue(struct file *file)
+{
+	int err;
+	snd_timer_user_t *tu;
+		
+	tu = file->private_data;
+	snd_assert(tu->timeri != NULL, return -ENXIO);
+	tu->timeri->lost = 0;
+	return (err = snd_timer_continue(tu->timeri)) < 0 ? err : 0;
+}
+
+static long snd_timer_user_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	snd_timer_user_t *tu;
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	
+	tu = file->private_data;
+	switch (cmd) {
+	case SNDRV_TIMER_IOCTL_PVERSION:
+		return put_user(SNDRV_TIMER_VERSION, p) ? -EFAULT : 0;
+	case SNDRV_TIMER_IOCTL_NEXT_DEVICE:
+		return snd_timer_user_next_device(argp);
+	case SNDRV_TIMER_IOCTL_TREAD:
+	{
+		int xarg;
+		
+		if (tu->timeri)		/* too late */
+			return -EBUSY;
+		if (get_user(xarg, p))
+			return -EFAULT;
+		tu->tread = xarg ? 1 : 0;
+		return 0;
+	}
+	case SNDRV_TIMER_IOCTL_GINFO:
+		return snd_timer_user_ginfo(file, argp);
+	case SNDRV_TIMER_IOCTL_GPARAMS:
+		return snd_timer_user_gparams(file, argp);
+	case SNDRV_TIMER_IOCTL_GSTATUS:
+		return snd_timer_user_gstatus(file, argp);
+	case SNDRV_TIMER_IOCTL_SELECT:
+		return snd_timer_user_tselect(file, argp);
+	case SNDRV_TIMER_IOCTL_INFO:
+		return snd_timer_user_info(file, argp);
+	case SNDRV_TIMER_IOCTL_PARAMS:
+		return snd_timer_user_params(file, argp);
+	case SNDRV_TIMER_IOCTL_STATUS:
+		return snd_timer_user_status(file, argp);
+	case SNDRV_TIMER_IOCTL_START:
+		return snd_timer_user_start(file);
+	case SNDRV_TIMER_IOCTL_STOP:
+		return snd_timer_user_stop(file);
+	case SNDRV_TIMER_IOCTL_CONTINUE:
+		return snd_timer_user_continue(file);
+	}
+	return -ENOTTY;
+}
+
+static int snd_timer_user_fasync(int fd, struct file * file, int on)
+{
+	snd_timer_user_t *tu;
+	int err;
+	
+	tu = file->private_data;
+	err = fasync_helper(fd, file, on, &tu->fasync);
+        if (err < 0)
+		return err;
+	return 0;
+}
+
+static ssize_t snd_timer_user_read(struct file *file, char __user *buffer, size_t count, loff_t *offset)
+{
+	snd_timer_user_t *tu;
+	long result = 0, unit;
+	int err = 0;
+	
+	tu = file->private_data;
+	unit = tu->tread ? sizeof(snd_timer_tread_t) : sizeof(snd_timer_read_t);
+	spin_lock_irq(&tu->qlock);
+	while ((long)count - result >= unit) {
+		while (!tu->qused) {
+			wait_queue_t wait;
+
+			if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) {
+				err = -EAGAIN;
+				break;
+			}
+
+			set_current_state(TASK_INTERRUPTIBLE);
+			init_waitqueue_entry(&wait, current);
+			add_wait_queue(&tu->qchange_sleep, &wait);
+
+			spin_unlock_irq(&tu->qlock);
+			schedule();
+			spin_lock_irq(&tu->qlock);
+
+			remove_wait_queue(&tu->qchange_sleep, &wait);
+
+			if (signal_pending(current)) {
+				err = -ERESTARTSYS;
+				break;
+			}
+		}
+
+		spin_unlock_irq(&tu->qlock);
+		if (err < 0)
+			goto _error;
+
+		if (tu->tread) {
+			if (copy_to_user(buffer, &tu->tqueue[tu->qhead++], sizeof(snd_timer_tread_t))) {
+				err = -EFAULT;
+				goto _error;
+			}
+		} else {
+			if (copy_to_user(buffer, &tu->queue[tu->qhead++], sizeof(snd_timer_read_t))) {
+				err = -EFAULT;
+				goto _error;
+			}
+		}
+
+		tu->qhead %= tu->queue_size;
+
+		result += unit;
+		buffer += unit;
+
+		spin_lock_irq(&tu->qlock);
+		tu->qused--;
+	}
+	spin_unlock_irq(&tu->qlock);
+ _error:
+	return result > 0 ? result : err;
+}
+
+static unsigned int snd_timer_user_poll(struct file *file, poll_table * wait)
+{
+        unsigned int mask;
+        snd_timer_user_t *tu;
+
+        tu = file->private_data;
+
+        poll_wait(file, &tu->qchange_sleep, wait);
+	
+	mask = 0;
+	if (tu->qused)
+		mask |= POLLIN | POLLRDNORM;
+
+	return mask;
+}
+
+#ifdef CONFIG_COMPAT
+#include "timer_compat.c"
+#else
+#define snd_timer_user_ioctl_compat	NULL
+#endif
+
+static struct file_operations snd_timer_f_ops =
+{
+	.owner =	THIS_MODULE,
+	.read =		snd_timer_user_read,
+	.open =		snd_timer_user_open,
+	.release =	snd_timer_user_release,
+	.poll =		snd_timer_user_poll,
+	.unlocked_ioctl =	snd_timer_user_ioctl,
+	.compat_ioctl =	snd_timer_user_ioctl_compat,
+	.fasync = 	snd_timer_user_fasync,
+};
+
+static snd_minor_t snd_timer_reg =
+{
+	.comment =	"timer",
+	.f_ops =	&snd_timer_f_ops,
+};
+
+/*
+ *  ENTRY functions
+ */
+
+static snd_info_entry_t *snd_timer_proc_entry = NULL;
+
+static int __init alsa_timer_init(void)
+{
+	int err;
+	snd_info_entry_t *entry;
+
+#ifdef SNDRV_OSS_INFO_DEV_TIMERS
+	snd_oss_info_register(SNDRV_OSS_INFO_DEV_TIMERS, SNDRV_CARDS - 1, "system timer");
+#endif
+	if ((entry = snd_info_create_module_entry(THIS_MODULE, "timers", NULL)) != NULL) {
+		entry->c.text.read_size = SNDRV_TIMER_DEVICES * 128;
+		entry->c.text.read = snd_timer_proc_read;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	snd_timer_proc_entry = entry;
+	if ((err = snd_timer_register_system()) < 0)
+		snd_printk(KERN_ERR "unable to register system timer (%i)\n", err);
+	if ((err = snd_register_device(SNDRV_DEVICE_TYPE_TIMER,
+					NULL, 0, &snd_timer_reg, "timer"))<0)
+		snd_printk(KERN_ERR "unable to register timer device (%i)\n", err);
+	return 0;
+}
+
+static void __exit alsa_timer_exit(void)
+{
+	struct list_head *p, *n;
+
+	snd_unregister_device(SNDRV_DEVICE_TYPE_TIMER, NULL, 0);
+	/* unregister the system timer */
+	list_for_each_safe(p, n, &snd_timer_list) {
+		snd_timer_t *timer = (snd_timer_t *)list_entry(p, snd_timer_t, device_list);
+		snd_timer_unregister(timer);
+	}
+	if (snd_timer_proc_entry) {
+		snd_info_unregister(snd_timer_proc_entry);
+		snd_timer_proc_entry = NULL;
+	}
+#ifdef SNDRV_OSS_INFO_DEV_TIMERS
+	snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_TIMERS, SNDRV_CARDS - 1);
+#endif
+}
+
+module_init(alsa_timer_init)
+module_exit(alsa_timer_exit)
+
+EXPORT_SYMBOL(snd_timer_open);
+EXPORT_SYMBOL(snd_timer_close);
+EXPORT_SYMBOL(snd_timer_resolution);
+EXPORT_SYMBOL(snd_timer_start);
+EXPORT_SYMBOL(snd_timer_stop);
+EXPORT_SYMBOL(snd_timer_continue);
+EXPORT_SYMBOL(snd_timer_pause);
+EXPORT_SYMBOL(snd_timer_new);
+EXPORT_SYMBOL(snd_timer_notify);
+EXPORT_SYMBOL(snd_timer_global_new);
+EXPORT_SYMBOL(snd_timer_global_free);
+EXPORT_SYMBOL(snd_timer_global_register);
+EXPORT_SYMBOL(snd_timer_global_unregister);
+EXPORT_SYMBOL(snd_timer_interrupt);
+EXPORT_SYMBOL(snd_timer_system_resolution);
diff --git a/sound/core/timer_compat.c b/sound/core/timer_compat.c
new file mode 100644
index 000000000000..9fbc3957a22d
--- /dev/null
+++ b/sound/core/timer_compat.c
@@ -0,0 +1,119 @@
+/*
+ *   32bit -> 64bit ioctl wrapper for timer API
+ *   Copyright (c) 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
+ *
+ */
+
+/* This file included from timer.c */
+
+#include <linux/compat.h>
+
+struct sndrv_timer_info32 {
+	u32 flags;
+	s32 card;
+	unsigned char id[64];
+	unsigned char name[80];
+	u32 reserved0;
+	u32 resolution;
+	unsigned char reserved[64];
+};
+
+static int snd_timer_user_info_compat(struct file *file,
+				      struct sndrv_timer_info32 __user *_info)
+{
+	snd_timer_user_t *tu;
+	struct sndrv_timer_info32 info;
+	snd_timer_t *t;
+
+	tu = file->private_data;
+	snd_assert(tu->timeri != NULL, return -ENXIO);
+	t = tu->timeri->timer;
+	snd_assert(t != NULL, return -ENXIO);
+	memset(&info, 0, sizeof(info));
+	info.card = t->card ? t->card->number : -1;
+	if (t->hw.flags & SNDRV_TIMER_HW_SLAVE)
+		info.flags |= SNDRV_TIMER_FLG_SLAVE;
+	strlcpy(info.id, t->id, sizeof(info.id));
+	strlcpy(info.name, t->name, sizeof(info.name));
+	info.resolution = t->hw.resolution;
+	if (copy_to_user(_info, &info, sizeof(*_info)))
+		return -EFAULT;
+	return 0;
+}
+
+struct sndrv_timer_status32 {
+	struct compat_timespec tstamp;
+	u32 resolution;
+	u32 lost;
+	u32 overrun;
+	u32 queue;
+	unsigned char reserved[64];
+};
+
+static int snd_timer_user_status_compat(struct file *file,
+					struct sndrv_timer_status32 __user *_status)
+{
+	snd_timer_user_t *tu;
+	snd_timer_status_t status;
+	
+	tu = file->private_data;
+	snd_assert(tu->timeri != NULL, return -ENXIO);
+	memset(&status, 0, sizeof(status));
+	status.tstamp = tu->tstamp;
+	status.resolution = snd_timer_resolution(tu->timeri);
+	status.lost = tu->timeri->lost;
+	status.overrun = tu->overrun;
+	spin_lock_irq(&tu->qlock);
+	status.queue = tu->qused;
+	spin_unlock_irq(&tu->qlock);
+	if (copy_to_user(_status, &status, sizeof(status)))
+		return -EFAULT;
+	return 0;
+}
+
+/*
+ */
+
+enum {
+	SNDRV_TIMER_IOCTL_INFO32 = _IOR('T', 0x11, struct sndrv_timer_info32),
+	SNDRV_TIMER_IOCTL_STATUS32 = _IOW('T', 0x14, struct sndrv_timer_status32),
+};
+
+static long snd_timer_user_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = compat_ptr(arg);
+
+	switch (cmd) {
+	case SNDRV_TIMER_IOCTL_PVERSION:
+	case SNDRV_TIMER_IOCTL_TREAD:
+	case SNDRV_TIMER_IOCTL_GINFO:
+	case SNDRV_TIMER_IOCTL_GPARAMS:
+	case SNDRV_TIMER_IOCTL_GSTATUS:
+	case SNDRV_TIMER_IOCTL_SELECT:
+	case SNDRV_TIMER_IOCTL_PARAMS:
+	case SNDRV_TIMER_IOCTL_START:
+	case SNDRV_TIMER_IOCTL_STOP:
+	case SNDRV_TIMER_IOCTL_CONTINUE:
+	case SNDRV_TIMER_IOCTL_NEXT_DEVICE:
+		return snd_timer_user_ioctl(file, cmd, (unsigned long)argp);
+	case SNDRV_TIMER_IOCTL_INFO32:
+		return snd_timer_user_info_compat(file, argp);
+	case SNDRV_TIMER_IOCTL_STATUS32:
+		return snd_timer_user_status_compat(file, argp);
+	}
+	return -ENOIOCTLCMD;
+}
diff --git a/sound/core/wrappers.c b/sound/core/wrappers.c
new file mode 100644
index 000000000000..9f393023c327
--- /dev/null
+++ b/sound/core/wrappers.c
@@ -0,0 +1,50 @@
+/*
+ *  Various wrappers
+ *  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 <linux/config.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/vmalloc.h>
+#include <linux/fs.h>
+
+#ifdef CONFIG_SND_DEBUG_MEMORY
+void *snd_wrapper_kmalloc(size_t size, int flags)
+{
+	return kmalloc(size, flags);
+}
+
+void snd_wrapper_kfree(const void *obj)
+{
+	kfree(obj);
+}
+
+void *snd_wrapper_vmalloc(unsigned long size)
+{
+	return vmalloc(size);
+}
+
+void snd_wrapper_vfree(void *obj)
+{
+	vfree(obj);
+}
+#endif
+